-
Notifications
You must be signed in to change notification settings - Fork 15
Improving on the test automation #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,3 +9,4 @@ dist/ | |
|
|
||
| # pnpm | ||
| .pnpm-store/ | ||
| test-results/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| declare module '*.css'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
|
|
||
| const locales = ['en', 'de'] as const; | ||
|
|
||
| function localePath(locale: (typeof locales)[number], path: string = '') { | ||
| return locale === 'en' ? `/${path}` : `/${locale}/${path}`; | ||
| } | ||
|
|
||
| test.describe('About Page', () => { | ||
| for (const locale of locales) { | ||
| test(`loads about page correctly for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale, 'about')); | ||
|
|
||
| // Page should load successfully | ||
| await expect(page).toHaveURL(/\/about/); | ||
|
|
||
| // Should have main content | ||
| await expect(page.locator('main')).toBeVisible(); | ||
|
|
||
| // Should maintain locale in URL | ||
| if (locale === 'de') { | ||
| await expect(page).toHaveURL(/\/de\/about/); | ||
| } else { | ||
| await expect(page).toHaveURL(/^(?!.*\/de\/).*\/about/); | ||
| } | ||
| }); | ||
|
|
||
| test(`about page navigation works for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| // Find and click about link in navigation | ||
| const aboutLink = page.locator('nav a[href*="about"]').first(); | ||
| await aboutLink.click(); | ||
|
|
||
| // Should navigate to about page | ||
| await expect(page).toHaveURL(/\/about/); | ||
|
|
||
| // Should maintain locale | ||
| if (locale === 'de') { | ||
| await expect(page).toHaveURL(/\/de\/about/); | ||
| } | ||
| }); | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
|
|
||
| const locales = ['en', 'de'] as const; | ||
|
|
||
| function localePath(locale: (typeof locales)[number], path: string = '') { | ||
| return locale === 'en' ? `/${path}` : `/${locale}/${path}`; | ||
| } | ||
|
|
||
| test.describe('Accessibility', () => { | ||
| for (const locale of locales) { | ||
| test(`home page has proper heading hierarchy for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| // Should have exactly one h1 | ||
| const h1Count = await page.locator('h1').count(); | ||
| expect(h1Count).toBe(1); | ||
|
|
||
| // H1 should have text content | ||
| const h1Text = await page.locator('h1').first().textContent(); | ||
| expect(h1Text?.trim().length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| test(`navigation has proper ARIA landmarks for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| // Should have navigation landmark | ||
| const nav = page.locator('nav, [role="navigation"]'); | ||
| await expect(nav).toBeVisible(); | ||
|
|
||
| // Should have main landmark | ||
| const main = page.locator('main, [role="main"]'); | ||
| await expect(main).toBeVisible(); | ||
| }); | ||
|
|
||
| test(`all images have alt text for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| // Get all images | ||
| const images = page.locator('img'); | ||
| const count = await images.count(); | ||
|
|
||
| // Check each image has alt attribute | ||
| for (let i = 0; i < count; i++) { | ||
| const img = images.nth(i); | ||
| const alt = await img.getAttribute('alt'); | ||
| // Alt can be empty string for decorative images, but must exist | ||
| expect(alt).not.toBeNull(); | ||
| } | ||
| }); | ||
|
|
||
| test(`interactive elements are keyboard accessible for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| // All links should be keyboard accessible | ||
| const links = page.locator('a[href]'); | ||
| const linkCount = await links.count(); | ||
|
|
||
| if (linkCount > 0) { | ||
| const firstLink = links.first(); | ||
| await expect(firstLink).toBeVisible(); | ||
|
|
||
| // Link should be focusable | ||
| await firstLink.focus(); | ||
| await expect(firstLink).toBeFocused(); | ||
| } | ||
| }); | ||
|
|
||
| test(`page has valid lang attribute for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| const htmlLang = await page.locator('html').getAttribute('lang'); | ||
|
|
||
| if (locale === 'de') { | ||
| expect(htmlLang).toContain('de'); | ||
| } else { | ||
| expect(htmlLang).toContain('en'); | ||
| } | ||
| }); | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
|
|
||
| const locales = ['en', 'de'] as const; | ||
|
|
||
| function localePath(locale: (typeof locales)[number], path: string = '') { | ||
| return locale === 'en' ? `/${path}` : `/${locale}/${path}`; | ||
| } | ||
|
|
||
| test.describe('Contact Page', () => { | ||
| for (const locale of locales) { | ||
| test(`loads contact page correctly for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale, 'contact')); | ||
|
|
||
| // Page should load successfully | ||
| await expect(page).toHaveURL(/\/contact/); | ||
|
|
||
| // Should have main content | ||
| await expect(page.locator('main')).toBeVisible(); | ||
|
|
||
| // Should maintain locale in URL | ||
| if (locale === 'de') { | ||
| await expect(page).toHaveURL(/\/de\/contact/); | ||
| } else { | ||
| await expect(page).toHaveURL(/^(?!.*\/de\/).*\/contact/); | ||
| } | ||
| }); | ||
|
|
||
| test(`contact page navigation works for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| // Find and click contact link in navigation | ||
| const contactLink = page.locator('nav a[href*="contact"]').first(); | ||
|
|
||
| if (await contactLink.count() > 0) { | ||
| await contactLink.click(); | ||
|
|
||
| // Should navigate to contact page | ||
| await expect(page).toHaveURL(/\/contact/); | ||
|
|
||
| // Should maintain locale | ||
| if (locale === 'de') { | ||
| await expect(page).toHaveURL(/\/de\/contact/); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
|
|
||
| const locales = ['en', 'de'] as const; | ||
|
|
||
| function localePath(locale: (typeof locales)[number]) { | ||
| return locale === 'en' ? '/' : `/${locale}/`; | ||
| } | ||
|
|
||
| test.describe('Home Page', () => { | ||
| for (const locale of locales) { | ||
| test(`loads home page correctly for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| // Page should load successfully | ||
| await expect(page).toHaveTitle(/Open Elements/i); | ||
|
|
||
| // Should have main navigation | ||
| await expect(page.locator('nav')).toBeVisible(); | ||
|
|
||
| // Should have main content | ||
| await expect(page.locator('main')).toBeVisible(); | ||
| }); | ||
|
|
||
| test(`home page has proper meta tags for ${locale}`, async ({ page }) => { | ||
| await page.goto(localePath(locale)); | ||
|
|
||
| // Should have meta description | ||
| const metaDescription = page.locator('meta[name="description"]'); | ||
| await expect(metaDescription).toHaveAttribute('content', /.+/); | ||
|
|
||
| // Should have viewport meta tag | ||
| const viewport = page.locator('meta[name="viewport"]'); | ||
| await expect(viewport).toHaveAttribute('content', /.+/); | ||
| }); | ||
|
|
||
| test(`home page is responsive for ${locale}`, async ({ page }) => { | ||
| // Test mobile view | ||
| await page.setViewportSize({ width: 375, height: 667 }); | ||
| await page.goto(localePath(locale)); | ||
| await expect(page.locator('nav')).toBeVisible(); | ||
|
|
||
| // Test tablet view | ||
| await page.setViewportSize({ width: 768, height: 1024 }); | ||
| await expect(page.locator('nav')).toBeVisible(); | ||
|
|
||
| // Test desktop view | ||
| await page.setViewportSize({ width: 1920, height: 1080 }); | ||
| await expect(page.locator('nav')).toBeVisible(); | ||
| }); | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,57 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { test, expect } from '@playwright/test'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test.describe('Locale Switching', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test('can switch between EN and DE on home page', async ({ page }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Start with English | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.goto('/'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(page).not.toHaveURL(/\/de\//); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Find and click language switcher to German | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const languageSwitcher = page.locator('[data-locale-switcher], a[href*="/de/"], button[aria-label*="Deutsch"], button[aria-label*="German"]').first(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (await languageSwitcher.count() > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await languageSwitcher.click(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Should switch to German | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(page).toHaveURL(/\/de\//); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+17
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test('maintains page context when switching locale', async ({ page }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Go to about page in English | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.goto('/about'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Find language switcher | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const languageSwitcher = page.locator('[data-locale-switcher], a[href*="/de/about"], button[aria-label*="Deutsch"]').first(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (await languageSwitcher.count() > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await languageSwitcher.click(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Should be on about page in German | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(page).toHaveURL(/\/de\/about/); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+32
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test('locale persists across navigation', async ({ page }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Start with German | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.goto('/de/'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Navigate to about page | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const aboutLink = page.locator('nav a[href*="about"]').first(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (await aboutLink.count() > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await aboutLink.click(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Should still be in German | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(page).toHaveURL(/\/de\/about/); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Navigate to contact page | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const contactLink = page.locator('nav a[href*="contact"]').first(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (await contactLink.count() > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await contactLink.click(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Should still be in German | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(page).toHaveURL(/\/de\/contact/); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+55
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (await aboutLink.count() > 0) { | |
| await aboutLink.click(); | |
| // Should still be in German | |
| await expect(page).toHaveURL(/\/de\/about/); | |
| } | |
| // Navigate to contact page | |
| const contactLink = page.locator('nav a[href*="contact"]').first(); | |
| if (await contactLink.count() > 0) { | |
| await contactLink.click(); | |
| // Should still be in German | |
| await expect(page).toHaveURL(/\/de\/contact/); | |
| } | |
| await expect(aboutLink).toBeVisible(); | |
| await aboutLink.click(); | |
| // Should still be in German | |
| await expect(page).toHaveURL(/\/de\/about/); | |
| // Navigate to contact page | |
| const contactLink = page.locator('nav a[href*="contact"]').first(); | |
| await expect(contactLink).toBeVisible(); | |
| await contactLink.click(); | |
| // Should still be in German | |
| await expect(page).toHaveURL(/\/de\/contact/); |
Copilot
AI
Feb 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue: guarding the click with if (await contactLink.count() > 0) allows the test to pass even if the Contact link is missing/broken. Make the link required (expect it visible) so the test actually validates locale persistence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This navigation test can pass without validating anything because it only clicks/asserts when
contactLink.count() > 0. If the nav link is expected to exist, remove the guard and assert the link is present/visible before clicking so regressions are caught.