Skip to content

Commit 43dc565

Browse files
committed
E2E Smoke tests fixes
1 parent ed6599d commit 43dc565

6 files changed

Lines changed: 89 additions & 36 deletions

File tree

‎_TODO.md‎

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
<!-- markdownlint-disable-file -->
22
# TODO
33

4-
## E2E Files with Skipped Tests
5-
6-
`FORCE_COLOR=1 CI=1 npx playwright test`
7-
84
## Visual Regression Testing
95

106
Playwright's Built-in Visual Testing
@@ -17,6 +13,44 @@ Pros: It's free, everything stays local (no third-party services needed), setup
1713

1814
Cons: Browser rendering can be inconsistent across different operating systems and machines, leading to "flaky" tests or false positives. Managing baselines for multiple browsers and resolutions manually can also be challenging at scale.
1915

16+
## E2E
17+
18+
56 …omium] › test/e2e/specs/02-pages/contact.spec.ts:15:3 › Contact Page › @ready hero section displays (5.3s)
19+
20+
70 …test/e2e/specs/02-pages/homepage.spec.ts:25:3 › Homepage › @ready featured services section renders (5.5s)
21+
22+
85 …st/e2e/specs/02-pages/services.spec.ts:21:3 › Services List Page › @ready services section displays (5.3s)
23+
24+
93 [chromium] › test/e2e/specs/02-pages/tags.spec.ts:15:3 › Tags Index Page › @ready tag list displays (6.0s)
25+
26+
100 …nsent-checkbox.spec.ts:89:3 › Newsletter GDPR Consent › @ready privacy policy link opens in new tab (6.6s)
27+
28+
117 …ubscription.spec.ts:28:3 › Newsletter Subscription Form › @ready form rejects invalid email format (373ms)
29+
30+
134 …nts/breadcrumbs.spec.ts:22:3 › Breadcrumbs Component › @ready breadcrumbs display on service pages (305ms)
31+
32+
156 …5:3 › Consent Preferences Component › @containers full stack consent submission hits backend mocks (30.0s)
33+
34+
163 …2e/specs/04-components/footer.spec.ts:30:3 › Footer Component › @ready footer has copyright notice (421ms)
35+
36+
172 …ponents/icon.spec.ts:38:3 › Icon Component › default variant renders every local icon exactly once (30.1s)
37+
38+
204 …/navigation-mobile.spec.ts:232:3 › Mobile Navigation › @ready can navigate to page from mobile menu (6.0s)
39+
40+
219 …ares.spec.ts:46:3 › Social Shares Component › renders social share component inside testing fixture (5.3s)
41+
42+
257 …› Theme Picker Component › System Preferences › @ready respects prefers-color-scheme on first visit (1.2s)
43+
44+
361 …ility/aria-screen-readers.spec.ts:232:3 › ARIA and Screen Readers › @ready page has exactly one h1 (16.7s
45+
46+
397 …/12-performance/lighthouse.spec.ts:9:3 › Lighthouse Performance › run Lighthouse audit on homepage (14.5s)
47+
48+
653 …ares.spec.ts:46:3 › Social Shares Component › renders social share component inside testing fixture (5.5s)
49+
50+
663 …Nanostore Persistence Across Navigation › @ready theme preference persists across View Transitions (30.2s)
51+
52+
795 …ility/aria-screen-readers.spec.ts:232:3 › ARIA and Screen Readers › @ready page has exactly one h1 (10.8s)
53+
2054
## Missing E2E Component Tests
2155

2256
- BugReporter

‎package.json‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,18 @@
7272
"@adobe/remark-gridtables": "^3.0.19",
7373
"@astrojs/check": "0.9.8",
7474
"@astrojs/db": "^0.20.1",
75-
"@astrojs/mdx": "5.0.2",
76-
"@astrojs/preact": "5.0.2",
77-
"@astrojs/rss": "4.0.17",
78-
"@astrojs/sitemap": "^3.7.1",
79-
"@astrojs/vercel": "^10.0.2",
75+
"@astrojs/mdx": "5.0.3",
76+
"@astrojs/preact": "5.1.0",
77+
"@astrojs/rss": "4.0.18",
78+
"@astrojs/sitemap": "^3.7.2",
79+
"@astrojs/vercel": "^10.0.3",
8080
"@axe-core/playwright": "^4.11.1",
8181
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.1",
8282
"@eslint/js": "10.0.1",
8383
"@github/relative-time-element": "^5.0.0",
8484
"@glidejs/glide": "^3.7.1",
8585
"@googlemaps/extended-component-library": "^0.6.14",
86-
"@happy-dom/global-registrator": "^20.8.8",
86+
"@happy-dom/global-registrator": "^20.8.9",
8787
"@lighthousejs/types": "^0.0.3",
8888
"@nanostores/lit": "^0.2.3",
8989
"@nanostores/persistent": "^1.3.3",
@@ -129,14 +129,14 @@
129129
"@upstash/search": "^0.1.7",
130130
"@vercel/analytics": "^2.0.1",
131131
"@vite-pwa/astro": "^1.2.0",
132-
"@vitest/coverage-v8": "^4.1.1",
132+
"@vitest/coverage-v8": "^4.1.2",
133133
"@webcomponents/template-shadowroot": "^0.2.1",
134134
"alex": "^11.0.1",
135-
"astro": "6.0.8",
135+
"astro": "6.1.1",
136136
"astro-link-validator": "github:rodgtr1/astro-link-validator",
137137
"astro-og-canvas": "^0.10.1",
138138
"astro-vtbot": "^2.1.12",
139-
"baseline-browser-mapping": "^2.10.10",
139+
"baseline-browser-mapping": "^2.10.11",
140140
"canvas-confetti": "^1.9.4",
141141
"confusing-browser-globals": "1.0.11",
142142
"cross-env": "^10.1.0",
@@ -157,7 +157,7 @@
157157
"eslint-plugin-yml": "3.3.1",
158158
"focus-trap": "8.0.1",
159159
"gsap": "^3.14.2",
160-
"happy-dom": "^20.8.8",
160+
"happy-dom": "^20.8.9",
161161
"hast-util-heading-rank": "^3.0.0",
162162
"hast-util-is-element": "^3.0.0",
163163
"html-element-attributes": "^3.5.0",
@@ -236,7 +236,7 @@
236236
"uuid": "^13.0.0",
237237
"vercel": "^50.37.1",
238238
"vite": "^8.0.3",
239-
"vitest": "4.1.1",
239+
"vitest": "4.1.2",
240240
"vitest-axe": "0.1.0",
241241
"workbox-build": "7.4.0",
242242
"zod": "4.3.6"

‎test/e2e/specs/01-smoke/critical-paths.spec.ts‎

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@
44
* They should always pass and run quickly.
55
*/
66
import { BasePage, test, expect } from '@test/e2e/helpers'
7+
import { wait } from '@test/e2e/helpers/waitTimeouts'
8+
9+
const matchesPathname = (pathname: string, expectedPath: string) => {
10+
const normalizedPathname = pathname !== '/' ? pathname.replace(/\/$/, '') : pathname
11+
const normalizedExpectedPath = expectedPath !== '/' ? expectedPath.replace(/\/$/, '') : expectedPath
12+
13+
return normalizedPathname === normalizedExpectedPath
14+
}
715

816
test.describe('Critical Paths @smoke', () => {
917
test('@ready all main navigation pages are accessible', async ({ page: playwrightPage }) => {
1018
const page = await BasePage.init(playwrightPage)
11-
for (const { url: path, title } of page.navigationItems) {
19+
for (const { url: path } of page.navigationItems) {
1220
await page.goto(path)
13-
await page.expectTitle(title)
21+
await page.waitForURL(url => matchesPathname(url.pathname, path), { timeout: wait.defaultWait })
1422
await page.expectHeading()
1523
}
1624
})
@@ -24,14 +32,19 @@ test.describe('Critical Paths @smoke', () => {
2432
const page = await BasePage.init(playwrightPage)
2533
for (const { url: path } of page.navigationItems) {
2634
await page.goto('/')
27-
const navigationComplete = page.waitForPageLoad({ requireNext: true })
35+
const navigationComplete = page.waitForPageLoad({ requireNext: true, timeout: wait.navigation })
36+
const urlComplete = page.waitForURL(url => matchesPathname(url.pathname, path), {
37+
timeout: wait.navigation,
38+
})
39+
2840
await page.navigateToPage(path)
29-
await navigationComplete
41+
42+
await urlComplete
43+
await navigationComplete.catch(() => undefined)
3044
await playwrightPage.waitForFunction(() => {
3145
return !document.documentElement.hasAttribute('data-astro-transition')
3246
})
33-
// eslint-disable-next-line security/detect-non-literal-regexp
34-
await page.expectUrl(new RegExp(path))
47+
await page.expectHeading()
3548
}
3649
})
3750

‎test/e2e/specs/01-smoke/dynamic-pages.spec.ts‎

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@
55
*/
66
import { BasePage, test, expect, setupConsoleErrorChecker, logConsoleErrors } from '@test/e2e/helpers'
77

8+
const getFirstHref = async (page: BasePage, selector: string): Promise<string | null> => {
9+
return await page.page.locator(selector).evaluateAll((elements) => {
10+
const firstLink = elements[0]
11+
12+
if (!(firstLink instanceof HTMLAnchorElement)) {
13+
return null
14+
}
15+
16+
return firstLink.getAttribute('href')
17+
})
18+
}
19+
820
test.describe('Dynamic Pages @smoke', () => {
921
test('@ready article detail page loads', async ({ page: playwrightPage }) => {
1022
const page = await BasePage.init(playwrightPage)
@@ -17,7 +29,7 @@ test.describe('Dynamic Pages @smoke', () => {
1729
const firstArticleLink = page.locator('a[href*="/articles/"]').first()
1830
await expect(firstArticleLink).toBeVisible()
1931

20-
const articleUrl = await firstArticleLink.getAttribute('href')
32+
const articleUrl = await getFirstHref(page, 'a[href*="/articles/"]')
2133
expect(articleUrl).toBeTruthy()
2234

2335
// Navigate to the article
@@ -41,8 +53,7 @@ test.describe('Dynamic Pages @smoke', () => {
4153
await page.waitForNetworkIdleBestEffort()
4254

4355
// Find first service link
44-
const firstServiceLink = page.locator('a[href*="/services/"]').first()
45-
const serviceUrl = await firstServiceLink.getAttribute('href')
56+
const serviceUrl = await getFirstHref(page, 'a[href^="/services/"]:not([href="/services/"])')
4657

4758
if (!serviceUrl) {
4859
test.skip()
@@ -70,8 +81,7 @@ test.describe('Dynamic Pages @smoke', () => {
7081
await page.waitForNetworkIdleBestEffort()
7182

7283
// Find first case study link
73-
const firstCaseStudyLink = page.locator('a[href*="/case-studies/"]').first()
74-
const caseStudyUrl = await firstCaseStudyLink.getAttribute('href')
84+
const caseStudyUrl = await getFirstHref(page, 'a[href*="/case-studies/"]')
7585

7686
if (!caseStudyUrl) {
7787
test.skip()
@@ -145,8 +155,7 @@ test.describe('Dynamic Pages @smoke', () => {
145155
// NOTE: Avoid strict 'networkidle' gating on WebKit/mobile-safari (can hang on long-lived requests).
146156
await page.waitForNetworkIdleBestEffort()
147157

148-
const firstArticleLink = page.locator('a[href*="/articles/"]').first()
149-
const articleUrl = await firstArticleLink.getAttribute('href')
158+
const articleUrl = await getFirstHref(page, 'a[href*="/articles/"]')
150159

151160
if (articleUrl) {
152161
resetChecker()
@@ -165,8 +174,7 @@ test.describe('Dynamic Pages @smoke', () => {
165174
// NOTE: Avoid strict 'networkidle' gating on WebKit/mobile-safari (can hang on long-lived requests).
166175
await page.waitForNetworkIdleBestEffort()
167176

168-
const firstServiceLink = page.locator('a[href*="/services/"]').first()
169-
const serviceUrl = await firstServiceLink.getAttribute('href')
177+
const serviceUrl = await getFirstHref(page, 'a[href^="/services/"]:not([href="/services/"])')
170178

171179
if (serviceUrl) {
172180
resetChecker()
@@ -185,8 +193,7 @@ test.describe('Dynamic Pages @smoke', () => {
185193
// NOTE: Avoid strict 'networkidle' gating on WebKit/mobile-safari (can hang on long-lived requests).
186194
await page.waitForNetworkIdleBestEffort()
187195

188-
const firstCaseStudyLink = page.locator('a[href*="/case-studies/"]').first()
189-
const caseStudyUrl = await firstCaseStudyLink.getAttribute('href')
196+
const caseStudyUrl = await getFirstHref(page, 'a[href*="/case-studies/"]')
190197

191198
if (caseStudyUrl) {
192199
resetChecker()
@@ -220,8 +227,7 @@ test.describe('Dynamic Pages @smoke', () => {
220227
// NOTE: Avoid strict 'networkidle' gating on WebKit/mobile-safari (can hang on long-lived requests).
221228
await page.waitForNetworkIdleBestEffort()
222229

223-
const firstArticleLink = page.locator('a[href*="/articles/"]').first()
224-
const articleUrl = await firstArticleLink.getAttribute('href')
230+
const articleUrl = await getFirstHref(page, 'a[href*="/articles/"]')
225231

226232
if (!articleUrl) {
227233
test.skip()

‎test/e2e/specs/11-accessibility/aria-screen-readers.spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ describe('ARIA and Screen Readers', () => {
197197
/**
198198
* Axe checks that form inputs have labels
199199
*/
200-
test.only('@ready form inputs have labels', async ({ page: playwrightPage }) => {
200+
test('@ready form inputs have labels', async ({ page: playwrightPage }) => {
201201
const page = await BasePage.init(playwrightPage)
202202
await runAcrossPages(page, 'check form input labels', async (url) => {
203203
await page.goto(url)

‎test/e2e/specs/11-accessibility/wcag-compliance.spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ describe('WCAG Compliance', () => {
233233
* Axe does not check if prefers-reduced-motion is respected; this is considered
234234
* a complex, context-dependent check that requires manual inspection.
235235
*/
236-
test.only('@ready page is usable without motion', async ({ page: playwrightPage }) => {
236+
test('@ready page is usable without motion', async ({ page: playwrightPage }) => {
237237
const page = await BasePage.init(playwrightPage)
238238
await runAcrossPages(page, 'check reduced motion usability', async (url) => {
239239
await page.page.emulateMedia({ reducedMotion: 'reduce' })

0 commit comments

Comments
 (0)