import { test, expect, Page } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; // t-html params are inserted raw (still sanitized) by default, exactly as in // v1.21.1. i18n.escapeParams: true opts into rendering them as text. // The persisted nojs-locale is restored as in v1.21.1 by default; // i18n.strictLocale: true ignores (and removes) a value outside supportedLocales. // Fixture: e2e/examples/compat-i18n.html (?escapeParams=1, ?persist=1, ?strictLocale=1). const FIXTURE = '/e2e/examples/compat-i18n.html'; async function open(page: Page, query = '') { await page.goto(FIXTURE + query); await expect(page.getByTestId('locale')).toHaveText('en'); } test.describe('Compat i18n — t-html params, default (v1.21.1)', () => { test.beforeEach(async ({ page }) => { await open(page); }); test('1 — A link passed as a param renders as a real ', async ({ page }) => { const p = page.getByTestId('link-param'); await expect(p).toHaveText('Read the terms.'); const link = p.locator('a'); await expect(link).toHaveCount(1); await expect(link).toHaveAttribute('href', '/terms'); await expect(link).toHaveText('terms'); }); test('2 — Markup in a label param renders inside the translation link', async ({ page }) => { const link = page.getByTestId('link-in-translation').locator('a'); await expect(link).toHaveAttribute('href', '/terms'); await expect(link.locator('em')).toHaveText('terms'); await expect(link).toHaveText('terms & conditions'); }); test('3 — Markup from reactive state re-renders on change', async ({ page }) => { const p = page.getByTestId('state-markup'); await expect(p.locator('strong')).toHaveText('3'); await page.getByTestId('state-inc').click(); await expect(p.locator('strong')).toHaveText('4'); await expect(p).toHaveText('You have 4 messages.'); }); test('4 — XSS payloads in params are stripped and never execute', async ({ page }) => { await expect(page.getByTestId('xss-script').locator('b')).toHaveText('Ann'); await expect(page.getByTestId('xss-script').locator('script')).toHaveCount(0); const img = page.getByTestId('xss-onerror').locator('img'); await expect(img).toHaveCount(1); expect(await img.getAttribute('onerror')).toBeNull(); const jsLink = page.getByTestId('xss-js-url').locator('a'); await expect(jsLink).toHaveText('click me'); expect(await jsLink.getAttribute('href')).toBeNull(); await jsLink.click(); await expect(page.getByTestId('xss-iframe').locator('iframe')).toHaveCount(0); // Give the broken image time to fire its (stripped) error handler. await page.waitForTimeout(200); expect(await page.evaluate(() => (window as any).__pwned)).toBeUndefined(); }); test('5 — Plain t renders params as literal text', async ({ page }) => { const p = page.getByTestId('plain-t'); await expect(p).toHaveText('Read the terms.'); await expect(p.locator('a')).toHaveCount(0); }); test('6 — Locale switch re-renders raw params in the new locale', async ({ page }) => { await page.getByTestId('to-pt').click(); await expect(page.getByTestId('locale')).toHaveText('pt'); const p = page.getByTestId('link-param'); await expect(p).toHaveText('Leia os termos.'); await expect(p.locator('a')).toHaveAttribute('href', '/terms'); await expect(p.locator('a')).toHaveText('termos'); await expect(page.getByTestId('state-markup').locator('strong')).toHaveText('3'); await expect(page.getByTestId('xss-script').locator('script')).toHaveCount(0); await page.getByTestId('to-en').click(); await expect(page.getByTestId('link-param').locator('a')).toHaveText('terms'); }); test('7 — Accessibility: no axe violations', async ({ page }) => { const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa']) .analyze(); expect(results.violations).toEqual([]); }); }); test.describe('Compat i18n — i18n.escapeParams: true', () => { test.beforeEach(async ({ page }) => { await open(page, '?escapeParams=1'); }); test('8 — A link param renders as literal text', async ({ page }) => { const p = page.getByTestId('link-param'); await expect(p.locator('a')).toHaveCount(0); await expect(p).toHaveText('Read the terms.'); }); test('9 — Translation markup still renders; the param is text', async ({ page }) => { const link = page.getByTestId('link-in-translation').locator('a'); await expect(link).toHaveAttribute('href', '/terms'); await expect(link.locator('em')).toHaveCount(0); await expect(link).toHaveText('terms & conditions'); }); test('10 — XSS payloads render as inert text', async ({ page }) => { for (const id of ['xss-script', 'xss-onerror', 'xss-js-url', 'xss-iframe']) { await expect(page.getByTestId(id).locator('script, img, a, iframe, b')).toHaveCount(0); } await expect(page.getByTestId('xss-script')).toContainText('