Or press ESC to close.

Localization Testing at Scale: Playwright Strategies for Multi-Language Web Apps

Jun 9th 2024 10 min read
easy
web
ui
playwright1.44.1
javascriptES6

Localization (L10n) is crucial for making our web application accessible to a global audience. Automating the testing of localized content ensures consistency and accuracy across different languages and regions. In this post, we'll explore best practices and share code examples for creating utilities that streamline localization testing with Playwright.

Introduction to Localization Testing

Localization (L10n) is the process of adapting our web application to different languages and regions, taking into account both linguistic and cultural differences. This process involves translating text, adjusting date and number formats, and ensuring that the layout and functionality of our web application are appropriate for users in different locales.

Automating localization testing is essential for several reasons:

Creating Utility Functions for Localization Testing

To efficiently test localization across multiple languages, we need a set of reusable utility functions. These functions will help automate language switching, text validation, and UI consistency checks.

Utility 1: Dynamic Language Switcher
                                     
async function switchLanguage(page, languageCode) {
    await page.click('#language-selector');
    await page.selectOption('#language-selector', languageCode);
}
                        
module.exports = switchLanguage;
                    

The switchLanguage function is designed to automate the process of changing the language of a web application within a Playwright test. This function takes two parameters: page, which represents the Playwright page object, and languageCode, which specifies the language code to switch to (e.g., 'en' for English, 'fr' for French). Inside the function, the page.click method is used to click on the language selector element identified by the #language-selector ID. Following this, the page.selectOption method is called to select the desired language from the dropdown menu using the provided languageCode.

This utility makes it straightforward to switch languages programmatically during automated tests, ensuring that each language version of the web application can be tested for proper localization.

Note that this approach may differ based on the specific implementation of the language selector in the web application being tested.

Utility 2: Dynamic Text Validation
                                     
const i18next = require('i18next');
                        
async function validateText(page, selector, translationKey, languageCode) {
    const element = await page.$(selector);
    const text = await element.textContent();
    const expectedText = i18next.t(translationKey, { lng: languageCode });
    if (text !== expectedText) {
        throw new Error(`Text mismatch: expected "${expectedText}", found "${text}" in ${languageCode}`);
    }
}
                        
module.exports = validateText;
                    

The validateText function is crafted to automate the verification of text translations in a web application using Playwright. It imports the i18next library, which is commonly used for managing translations. The function takes four parameters: page, representing the Playwright page object; selector, which specifies the CSS selector for the element containing the text to be validated; translationKey, the key used to fetch the correct translation; and languageCode, the code of the language being tested.

Inside the function, the page.$ method retrieves the element matching the provided selector. The text content of this element is then obtained using the element.textContent method. The expected translation is fetched from the i18next library using the translation key and language code. A comparison is made between the actual text from the web page and the expected translation. If they do not match, an error is thrown, indicating a discrepancy in the translation for the specified language. This utility function streamlines the process of validating translations across different languages, ensuring the accuracy and consistency of localized content.

Utility 3: Optimized Screenshot Comparison
                                     
async function compareScreenshot(page, maxDiffPixels = 10) {
    await expect(page).toHaveScreenshot({ maxDiffPixels });
}
                          
module.exports = compareScreenshot;
                    

The compareScreenshot function facilitates automated screenshot comparison within a Playwright test, ensuring visual consistency across different executions. This asynchronous function accepts a Playwright page object and an optional parameter maxDiffPixels, which specifies the maximum number of differing pixels allowed between the baseline and current screenshots.

Within the function, the expect(page).toHaveScreenshot method captures the current state of the webpage and compares it against a baseline image. The maxDiffPixels parameter controls the sensitivity of the comparison, allowing for minor differences while maintaining overall visual consistency.

Writing Efficient Test Cases

Example Test Case: Validating Translations Across Multiple Languages

One crucial aspect of localization testing is ensuring that translations are accurate and consistent across different languages. This example test case demonstrates how to validate translations across multiple languages efficiently using Playwright.

                                     
const languages = ['en', 'es', 'fr', 'de', 'ja']; // Add more languages as needed

languages.forEach((language) => {
    test(`Validate translations for ${language}`, async ({ page }) => {
        await switchLanguage(page, language);
                      
        // Example: Validate welcome message translation
        await validateText(page, '#welcome-message', 'welcome_message', language);
                            
        // Example: Validate login button translation
        await validateText(page, '#login-button', 'login_button', language);
                            
        // Add more validation tests for other translated elements
    });
});
                    

In this piece of code, we define an array called languages, which includes a list of language codes ('en', 'es', 'fr', 'de', 'ja'). This array represents the languages we want to test for localization. Using the forEach method, we iterate over each language code in the array, executing a test for each one.

The test, labeled with a template string to indicate the language being tested, is an asynchronous function that receives the page object as an argument. Within each test, the switchLanguage function is called to change the application's language to the current language being tested.

Then, the validateText function is used to verify that the text of specific elements (e.g., #welcome-message and #login-button) matches the expected translation for the current language. This approach allows for scalable and efficient validation of translations across multiple languages, ensuring that all localized elements are accurately displayed. Additional validation tests for other translated elements can be added as needed.

Example Test Case: Verifying Date and Number Formats

Ensuring that date and number formats are correctly localized is a crucial aspect of localization testing. This example test case demonstrates how to verify date and number formats across multiple languages using Playwright.

                                     
const dateFormats = {
    en: /^\d{2}\/\d{2}\/\d{4}$/, // MM/DD/YYYY
    de: /^\d{2}\.\d{2}\.\d{4}$/, // DD.MM.YYYY
    es: /^\d{2}\/\d{2}\/\d{4}$/, // DD/MM/YYYY
};
                          
    const numberFormats = {
    en: /^\d{1,3}(,\d{3})*(\.\d{2})?$/, // 1,234.56
    de: /^\d{1,3}(\.\d{3})*(,\d{2})?$/, // 1.234,56
    es: /^\d{1,3}(\.\d{3})*(,\d{2})?$/, // 1.234,56
};

Object.keys(dateFormats).forEach((language) => {
    test(`Verify date format for ${language}`, async ({ page }) => {
      await switchLanguage(page, language);
      const dateText = await page.textContent('#date-field');
      expect(dateText).toMatch(dateFormats[language]);
    });
  });

Object.keys(numberFormats).forEach((language) => {
    test(`Verify number format for ${language}`, async ({ page }) => {
      await switchLanguage(page, language);
      const numberText = await page.textContent('#number-field');
      expect(numberText).toMatch(numberFormats[language]);
    });
});
                    

In this code, we define two objects, dateFormats and numberFormats, mapping language codes to regular expressions that represent expected date and number formats. The dateFormats object includes formats for English (en), German (de), and Spanish (es). For example, English dates follow the MM/DD/YYYY format, while German dates use DD.MM.YYYY, and Spanish dates use DD/MM/YYYY.

Similarly, the numberFormats object specifies that English numbers follow the #,###.## format (e.g., 1,234.56), while both German and Spanish numbers use the #.###,## format (e.g., 1.234,56).

We then iterate over the keys of these objects using Object.keys(), and for each language, we define a test. In the date format tests, the switchLanguage function changes the language of the page, retrieves the text content of the #date-field element, and checks if it matches the expected format. The same approach is applied to the number format tests, using the #number-field element.

This setup efficiently verifies that date and number formats are correctly localized for each language, ensuring consistency and accuracy in the application's localization.

Example Test Case: Ensuring UI Consistency Across Multiple Languages

Ensuring UI consistency across multiple languages is essential for providing a seamless user experience in a globally accessible web application. This test case demonstrates how to use visual regression testing to verify that the user interface remains consistent and visually appealing, regardless of the language setting. By comparing screenshots of the UI in different languages, we can identify and address any discrepancies, ensuring a uniform appearance across all localizations.

                                     
languages.forEach((language) => {
    test(`Ensure UI consistency for ${language}`, async ({ page }) => {
        await switchLanguage(page, language);
        await compareScreenshot(page);
    });
});
                    

In this code snippet, we iterate over an array of language codes using the forEach method. Within each test, the switchLanguage function is called to change the web application's language to the current language being tested. After switching the language, the compareScreenshot function is invoked to capture a screenshot of the page and compare it against a baseline image to check for visual consistency. This approach helps ensure that the user interface remains consistent and visually correct across all supported languages.

Playwright screenshot comparison

Playwright screenshot comparison

Best Practices for Localization Testing

Maintaining Translation Files

Effective management of translation files is crucial for successful localization testing. One best practice is to integrate translation updates into our CI/CD pipeline. This ensures that any new or updated translations are automatically included in our testing process, reducing the risk of outdated or missing translations. Additionally, implementing automated checks can verify that translation files are up-to-date and complete, identifying any gaps or errors in the localization data before they affect the user experience.

Handling Dynamic Content

Dynamic content, such as user-generated text or context-sensitive placeholders, requires special attention in localization testing. Ensuring that placeholders in translations are dynamic and correctly formatted is essential to maintain the integrity and readability of the content. Automated tools can assist by extracting and updating dynamic content keys, simplifying the management of translations that change frequently or are context-dependent.

Cross-Browser and Cross-Device Testing

Localization testing must account for various browsers and devices to ensure a consistent user experience across different environments. Comprehensive coverage can be achieved by testing on a wide range of browsers and devices using cloud-based testing platforms. These platforms provide access to numerous configurations, helping to identify and resolve issues that may only appear in specific contexts. To optimize the testing process, running tests in parallel can significantly reduce testing time, allowing for faster iterations and more efficient identification of localization issues.

Conclusion

Automating localization testing is essential for delivering a consistent and accurate user experience to a global audience. By following best practices and using the utilities and test cases provided, we can streamline our localization testing process, ensuring that our web application meets the linguistic and cultural expectations of users across different regions.

In case you want to try out the examples above, I have added a mock website to our GitHub page that you can run on your localhost. The tests have also been adjusted to suit the website.

Further Reading and Resources: