|
| 1 | +--- |
| 2 | +nav: |
| 3 | + title: Language Agnostic Testing |
| 4 | + position: 20 |
| 5 | +--- |
| 6 | + |
| 7 | +# Language Agnostic Testing in @shopware-ag/acceptance-test-suite |
| 8 | + |
| 9 | +Language agnostic testing allows you to write acceptance tests that work across different languages without hardcoding text strings. Tests use translation keys instead of hardcoded strings and automatically adapt to different locales via environment variables. |
| 10 | + |
| 11 | +## translate() Function |
| 12 | + |
| 13 | +Use the `translate()` function in page objects to replace hardcoded strings with translation keys. |
| 14 | + |
| 15 | +### Usage in Page Objects |
| 16 | + |
| 17 | +```typescript |
| 18 | +import { translate } from '../../services/LanguageHelper'; |
| 19 | + |
| 20 | +export class CategoryListing implements PageObject { |
| 21 | + constructor(page: Page) { |
| 22 | + this.createButton = page.getByRole('button', { |
| 23 | + name: translate('administration:category:actions.createCategory'), |
| 24 | + }); |
| 25 | + } |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +## Translate Fixture |
| 30 | + |
| 31 | +The `Translate` fixture provides translation functionality in tests. |
| 32 | + |
| 33 | +### Usage in Tests |
| 34 | + |
| 35 | +```typescript |
| 36 | +import { test, expect } from '@shopware-ag/acceptance-test-suite'; |
| 37 | + |
| 38 | +test('Category creation', async ({ AdminPage, Translate }) => { |
| 39 | + const saveText = Translate('administration:category:general.save'); |
| 40 | + await AdminPage.getByRole('button', { name: saveText }).click(); |
| 41 | +}); |
| 42 | +``` |
| 43 | + |
| 44 | +## Environment Control |
| 45 | + |
| 46 | +Switch test language using environment variables: |
| 47 | + |
| 48 | +```bash |
| 49 | +LANG=de npm run test # German |
| 50 | +LANG=en npm run test # English (default) |
| 51 | +``` |
| 52 | + |
| 53 | +## Translation Keys |
| 54 | + |
| 55 | +Translation keys follow the pattern: `area:module:section.key` |
| 56 | + |
| 57 | +### Examples |
| 58 | + |
| 59 | +```typescript |
| 60 | +'administration:category:general.save'; |
| 61 | +'administration:category:actions.createCategory'; |
| 62 | +'storefront:account:fields.firstName'; |
| 63 | +'storefront:checkout:payment.invoice'; |
| 64 | +``` |
| 65 | + |
| 66 | +### Locale Files |
| 67 | + |
| 68 | +Translations are stored in JSON files organized by language and area: |
| 69 | + |
| 70 | +- `locales/en/administration/category.json` |
| 71 | +- `locales/de/administration/category.json` |
| 72 | +- `locales/en/storefront/account.json` |
| 73 | +- `locales/de/storefront/account.json` |
| 74 | + |
| 75 | +### Example Translation Files |
| 76 | + |
| 77 | +**English (`locales/en/administration/category.json`):** |
| 78 | + |
| 79 | +```json |
| 80 | +{ |
| 81 | + "general": { |
| 82 | + "save": "Save", |
| 83 | + "cancel": "Cancel" |
| 84 | + }, |
| 85 | + "actions": { |
| 86 | + "createCategory": "Create category" |
| 87 | + } |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +**German (`locales/de/administration/category.json`):** |
| 92 | + |
| 93 | +```json |
| 94 | +{ |
| 95 | + "general": { |
| 96 | + "save": "Speichern", |
| 97 | + "cancel": "Abbrechen" |
| 98 | + }, |
| 99 | + "actions": { |
| 100 | + "createCategory": "Kategorie erstellen" |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +## Supported Locales |
| 106 | + |
| 107 | +**Translation Resources**: `en` (English), `de` (German) |
| 108 | +**Browser UI**: `en`, `de`, `fr`, `es`, `it`, `nl`, `pt` |
| 109 | + |
| 110 | +## Common Issues |
| 111 | + |
| 112 | +**Translation key not found:** |
| 113 | + |
| 114 | +- Verify key exists in both EN/DE locale files |
| 115 | +- Check import in `locales/index.ts` |
| 116 | +- Ensure proper namespace structure |
| 117 | + |
| 118 | +**Tests fail with LANG changes:** |
| 119 | + |
| 120 | +- Move `translate()` calls inside constructors/functions, not at module level |
| 121 | +- Ensure translation resources are properly loaded |
| 122 | + |
| 123 | +**JSON import errors:** |
| 124 | + |
| 125 | +- Always use `with { type: 'json' }` import attribute |
| 126 | +- Check file paths and naming conventions |
| 127 | + |
| 128 | +**Browser locale not matching:** |
| 129 | + |
| 130 | +- Verify locale mapping in `playwright.config.ts` |
| 131 | +- Check browser args configuration |
| 132 | +- Ensure language detection is working correctly |
| 133 | + |
| 134 | +## Using in Your Own Project |
| 135 | + |
| 136 | +If you want to use the `@shopware-ag/acceptance-test-suite` in your own project with custom translations, you can extend the base test suite with your own translation fixture. |
| 137 | + |
| 138 | +### Installation |
| 139 | + |
| 140 | +First, install the required dependencies: |
| 141 | + |
| 142 | +```bash |
| 143 | +npm install @shopware-ag/acceptance-test-suite @playwright/test |
| 144 | +npm install -D @types/node |
| 145 | +``` |
| 146 | + |
| 147 | +### Create Custom Translation Fixture |
| 148 | + |
| 149 | +Create a new fixture file (e.g., `fixtures/CustomTranslation.ts`): |
| 150 | + |
| 151 | +```typescript |
| 152 | +import { |
| 153 | + test as base, |
| 154 | + LanguageHelper, |
| 155 | + TranslationKey, |
| 156 | + TranslateFn, |
| 157 | + BUNDLED_RESOURCES, |
| 158 | + baseNamespaces, |
| 159 | +} from '@shopware-ag/acceptance-test-suite'; |
| 160 | +import { LOCALE_RESOURCES, enNamespaces } from '../locales'; |
| 161 | + |
| 162 | +// Merge base BUNDLED_RESOURCES with your custom LOCALE_RESOURCES |
| 163 | +const MERGED_RESOURCES = { |
| 164 | + en: { ...BUNDLED_RESOURCES.en, ...LOCALE_RESOURCES.en }, |
| 165 | + de: { ...BUNDLED_RESOURCES.de, ...LOCALE_RESOURCES.de }, |
| 166 | +} as const; |
| 167 | + |
| 168 | +// Merge base and custom namespaces |
| 169 | +const mergedNamespaces = { |
| 170 | + ...baseNamespaces, |
| 171 | + ...enNamespaces, |
| 172 | +} as const; |
| 173 | + |
| 174 | +type CustomTranslationKey = TranslationKey<typeof mergedNamespaces>; |
| 175 | + |
| 176 | +interface CustomTranslateFixture { |
| 177 | + Translate: TranslateFn<CustomTranslationKey>; |
| 178 | +} |
| 179 | + |
| 180 | +export const test = base.extend<CustomTranslateFixture>({ |
| 181 | + Translate: async ({}, use) => { |
| 182 | + let lang = process.env.lang || process.env.LANGUAGE || process.env.LANG || 'en'; |
| 183 | + let language = lang.split(/[_.-]/)[0].toLowerCase(); |
| 184 | + |
| 185 | + if (!MERGED_RESOURCES[language as keyof typeof MERGED_RESOURCES]) { |
| 186 | + console.warn( |
| 187 | + `⚠️ Translation resources for '${language}' not available. Supported: ${Object.keys( |
| 188 | + MERGED_RESOURCES |
| 189 | + ).join(', ')}. Falling back to 'en'.` |
| 190 | + ); |
| 191 | + language = 'en'; |
| 192 | + } |
| 193 | + |
| 194 | + const languageHelper = await LanguageHelper.createInstance( |
| 195 | + language, |
| 196 | + MERGED_RESOURCES as unknown as typeof BUNDLED_RESOURCES |
| 197 | + ); |
| 198 | + |
| 199 | + const translate: TranslateFn<CustomTranslationKey> = (key, options) => { |
| 200 | + return languageHelper.translate(key as TranslationKey, options); |
| 201 | + }; |
| 202 | + |
| 203 | + await use(translate); |
| 204 | + }, |
| 205 | +}); |
| 206 | + |
| 207 | +export * from '@shopware-ag/acceptance-test-suite'; |
| 208 | +export type { CustomTranslationKey }; |
| 209 | +``` |
| 210 | + |
| 211 | +### Create Locale Files Structure |
| 212 | + |
| 213 | +Organize your translation files by language and area: |
| 214 | + |
| 215 | +``` |
| 216 | +project-root/ |
| 217 | +├── locales/ |
| 218 | +│ ├── en/ |
| 219 | +│ │ ├── administration/ |
| 220 | +│ │ │ ├── common.json |
| 221 | +│ │ │ └── product.json |
| 222 | +│ │ └── storefront/ |
| 223 | +│ │ ├── account.json |
| 224 | +│ │ └── checkout.json |
| 225 | +│ ├── de/ |
| 226 | +│ │ ├── administration/ |
| 227 | +│ │ │ ├── common.json |
| 228 | +│ │ │ └── product.json |
| 229 | +│ │ └── storefront/ |
| 230 | +│ │ ├── account.json |
| 231 | +│ │ └── checkout.json |
| 232 | +│ └── index.ts |
| 233 | +├── fixtures/ |
| 234 | +│ └── CustomTranslation.ts |
| 235 | +├── types/ |
| 236 | +│ └── TranslationTypes.ts |
| 237 | +└── tests/ |
| 238 | + └── your-test.spec.ts |
| 239 | +``` |
| 240 | + |
| 241 | +### Create Locales Index |
| 242 | + |
| 243 | +Create `locales/index.ts` to import and export your translation files: |
| 244 | + |
| 245 | +```typescript |
| 246 | +// Import all locale files |
| 247 | +import enAdministrationCommon from './en/administration/common.json' with { type: 'json' }; |
| 248 | +import enStorefrontAccount from './en/storefront/account.json' with { type: 'json' }; |
| 249 | + |
| 250 | +import deAdministrationCommon from './de/administration/common.json' with { type: 'json' }; |
| 251 | +import deStorefrontAccount from './de/storefront/account.json' with { type: 'json' }; |
| 252 | + |
| 253 | +// Export the bundled resources for i18next |
| 254 | +export const LOCALE_RESOURCES = { |
| 255 | + en: { |
| 256 | + 'administration/common': enAdministrationCommon, |
| 257 | + 'storefront/account': enStorefrontAccount, |
| 258 | + }, |
| 259 | + de: { |
| 260 | + 'administration/common': deAdministrationCommon, |
| 261 | + 'storefront/account': deStorefrontAccount, |
| 262 | + }, |
| 263 | +} as const; |
| 264 | + |
| 265 | +export const enNamespaces = { |
| 266 | + administration: { |
| 267 | + common: enAdministrationCommon, |
| 268 | + }, |
| 269 | + storefront: { |
| 270 | + account: enStorefrontAccount, |
| 271 | + }, |
| 272 | +} as const; |
| 273 | +``` |
| 274 | + |
| 275 | +### Create Translation Types |
| 276 | + |
| 277 | +Create `types/TranslationTypes.ts` to define your custom translation types. This provides: |
| 278 | + |
| 279 | +- **Type Safety**: Ensures translation keys exist in your locale files |
| 280 | +- **IntelliSense**: Auto-completion for available translation keys |
| 281 | +- **Compile-time Validation**: Catches typos and missing keys before runtime |
| 282 | + |
| 283 | +```typescript |
| 284 | +import { TranslationKey, TranslateFn } from '@shopware-ag/acceptance-test-suite'; |
| 285 | +import { enNamespaces } from '../locales'; |
| 286 | + |
| 287 | +export type CustomTranslationKey = TranslationKey<typeof enNamespaces>; |
| 288 | + |
| 289 | +export type CustomTranslateFn = TranslateFn<CustomTranslationKey>; |
| 290 | +``` |
| 291 | + |
| 292 | +### Merge with Base Test Suite |
| 293 | + |
| 294 | +Create your main test fixture that merges the base test suite with your custom translation: |
| 295 | + |
| 296 | +```typescript |
| 297 | +import { test as ShopwareTestSuite, mergeTests } from '@shopware-ag/acceptance-test-suite'; |
| 298 | +import { test as CustomTranslation } from './fixtures/CustomTranslation'; |
| 299 | + |
| 300 | +export * from '@shopware-ag/acceptance-test-suite'; |
| 301 | + |
| 302 | +export const test = mergeTests(ShopwareTestSuite, CustomTranslation); |
| 303 | +``` |
| 304 | + |
| 305 | +**Note**: Save this as `test.ts` or `index.ts` in your project root and import it in your test files. |
| 306 | + |
| 307 | +### Usage in Your Tests |
| 308 | + |
| 309 | +Now you can use the `Translate` fixture in your tests: |
| 310 | + |
| 311 | +```typescript |
| 312 | +import { test } from './your-main-test-fixture'; |
| 313 | + |
| 314 | +test('My localized test', async ({ Translate, AdminPage }) => { |
| 315 | + const saveText = Translate('administration:common:button.save'); |
| 316 | + await AdminPage.getByRole('button', { name: saveText }).click(); |
| 317 | +}); |
| 318 | +``` |
| 319 | + |
| 320 | +### Environment Configuration |
| 321 | + |
| 322 | +Set up your Playwright configuration to support language switching: |
| 323 | + |
| 324 | +```typescript |
| 325 | +// playwright.config.ts |
| 326 | +import { defineConfig, devices } from '@playwright/test'; |
| 327 | + |
| 328 | +const LOCALES = { de: 'de-DE', en: 'en-US', fr: 'fr-FR' }; |
| 329 | + |
| 330 | +function getLanguage(): string { |
| 331 | + let lang = process.env.lang || process.env.LANGUAGE || process.env.LANG || 'en'; |
| 332 | + return lang.split(/[_.-]/)[0].toLowerCase(); |
| 333 | +} |
| 334 | + |
| 335 | +function getLocaleConfig() { |
| 336 | + const lang = getLanguage(); |
| 337 | + const browserLocale = LOCALES[lang as keyof typeof LOCALES] || 'en-US'; |
| 338 | + const browserArgs = |
| 339 | + lang !== 'en' && LOCALES[lang as keyof typeof LOCALES] |
| 340 | + ? [`--lang=${browserLocale}`, `--accept-lang=${browserLocale},${lang};q=0.9,en;q=0.8`] |
| 341 | + : []; |
| 342 | + |
| 343 | + return { lang, browserLocale, browserArgs }; |
| 344 | +} |
| 345 | + |
| 346 | +export default defineConfig({ |
| 347 | + use: { |
| 348 | + locale: getLocaleConfig().browserLocale, |
| 349 | + }, |
| 350 | + projects: [ |
| 351 | + { |
| 352 | + name: 'Platform', |
| 353 | + use: { |
| 354 | + ...devices['Desktop Chrome'], |
| 355 | + launchOptions: { |
| 356 | + args: [...getLocaleConfig().browserArgs], |
| 357 | + }, |
| 358 | + }, |
| 359 | + }, |
| 360 | + ], |
| 361 | +}); |
| 362 | +``` |
| 363 | + |
| 364 | +### Running Tests with Different Languages |
| 365 | + |
| 366 | +```bash |
| 367 | +# German |
| 368 | +lang=de npx playwright test |
| 369 | + |
| 370 | +# English (default) |
| 371 | +npx playwright test |
| 372 | + |
| 373 | +# Using system environment |
| 374 | +LANG=de npx playwright test |
| 375 | +``` |
0 commit comments