Skip to content

Commit 1e2510d

Browse files
committed
Targeted fixes after 24 hours of stress test runs on E2E full suite
1 parent 3c3ec03 commit 1e2510d

7 files changed

Lines changed: 105 additions & 28 deletions

File tree

src/components/CallToAction/Newsletter/client/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,14 @@ export class NewsletterFormElement extends LitElement {
168168
addScriptBreadcrumb(context)
169169

170170
try {
171-
if (!this.submitButton || !this.buttonText || !this.buttonArrow || !this.buttonSpinner) return
171+
if (!this.submitButton || !this.buttonText || !this.buttonArrow || !this.buttonSpinner) return
172172

173-
this.submitButton.disabled = loading
174-
this.submitButton.dataset['e2eState'] = loading ? 'loading' : 'idle'
173+
const state = loading ? 'loading' : 'idle'
174+
175+
this.submitButton.disabled = loading
176+
this.submitButton.dataset['e2eState'] = state
177+
this.submitButton.setAttribute('data-e2e-state', state)
178+
this.submitButton.setAttribute('aria-busy', loading ? 'true' : 'false')
175179

176180
if (loading) {
177181
this.buttonText.textContent = 'Subscribing...'

test/e2e/helpers/pageObjectModels/BasePage.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,77 @@ export class BasePage {
352352
await this._page.keyboard.type(text)
353353
}
354354

355+
/**
356+
* Deterministically open the mobile navigation menu and wait for it to finish animating
357+
*/
358+
async openMobileMenu(options?: { timeout?: number }): Promise<void> {
359+
const timeout = options?.timeout ?? EXTENDED_NAVIGATION_TIMEOUT
360+
361+
await this.waitForHeaderComponents({ timeout })
362+
await this._page.waitForFunction(
363+
() => !document.documentElement?.hasAttribute('data-astro-transition'),
364+
undefined,
365+
{ timeout }
366+
)
367+
368+
const toggleButton = this._page.locator('button[aria-label="toggle menu"]')
369+
await expect(toggleButton).toBeVisible({ timeout })
370+
371+
const expanded = await toggleButton.getAttribute('aria-expanded')
372+
if (expanded !== 'true') {
373+
await toggleButton.click()
374+
}
375+
376+
await this._page.waitForFunction(
377+
() => {
378+
const header = document.getElementById('header')
379+
const menu = document.querySelector('.main-nav-menu')
380+
const body = document.body
381+
382+
const headerExpanded = header?.classList.contains('aria-expanded-true') ?? false
383+
const menuVisible = menu?.classList.contains('menu-visible') ?? false
384+
const bodyScrollLocked = body?.classList.contains('no-scroll') ?? false
385+
386+
return headerExpanded && menuVisible && bodyScrollLocked
387+
},
388+
undefined,
389+
{ timeout }
390+
)
391+
}
392+
393+
/**
394+
* Deterministically close the mobile navigation menu and wait for scroll lock to clear
395+
*/
396+
async closeMobileMenu(options?: { timeout?: number }): Promise<void> {
397+
const timeout = options?.timeout ?? DEFAULT_NAVIGATION_TIMEOUT
398+
const toggleButton = this._page.locator('button[aria-label="toggle menu"]')
399+
400+
await expect(toggleButton).toBeVisible({ timeout })
401+
402+
const expanded = await toggleButton.getAttribute('aria-expanded')
403+
if (expanded !== 'true') {
404+
return
405+
}
406+
407+
await toggleButton.click()
408+
409+
await this._page.waitForFunction(
410+
() => {
411+
const header = document.getElementById('header')
412+
const menu = document.querySelector('.main-nav-menu')
413+
const body = document.body
414+
415+
const headerCollapsed = !(header?.classList.contains('aria-expanded-true') ?? false)
416+
const menuHidden = !(menu?.classList.contains('menu-visible') ?? false)
417+
const bodyScrollRestored = !(body?.classList.contains('no-scroll') ?? false)
418+
419+
return headerCollapsed && menuHidden && bodyScrollRestored
420+
},
421+
undefined,
422+
{ timeout }
423+
)
424+
}
425+
355426
/**
356427
* Get keyboard object for advanced keyboard operations
357428
*/

test/e2e/helpers/pageObjectModels/BreadCrumbPage.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,24 @@ export class BreadCrumbPage extends BasePage {
5555
return
5656
}
5757

58-
const waitForLoad = this.waitForPageLoad({ requireNext: true })
58+
const supportsViewTransitions = await this.evaluate(() => {
59+
if (typeof document === 'undefined') {
60+
return false
61+
}
62+
return typeof document.startViewTransition === 'function'
63+
})
64+
65+
if (supportsViewTransitions) {
66+
const waitForLoad = this.waitForPageLoad({ requireNext: true })
67+
await this.click(`a[href="${targetHref}"]`)
68+
await waitForLoad
69+
return
70+
}
71+
72+
const navigationPromise = this.page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 15000 })
5973
await this.click(`a[href="${targetHref}"]`)
60-
await waitForLoad
74+
await navigationPromise
75+
await this.waitForLoadState('networkidle')
6176
}
6277

6378
async openFirstArticleDetail(options?: { navigationMode?: 'client' | 'fresh' }): Promise<void> {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ test.describe('Critical Paths @smoke', () => {
5252
await page.goto('/')
5353

5454
// Open mobile menu before each navigation
55-
await page.click('button[aria-label="toggle menu"]')
56-
await playwrightPage.waitForSelector('.menu-visible', { state: 'visible' })
55+
await page.openMobileMenu()
5756

5857
// Click navigation link
5958
const navigationComplete = page.waitForPageLoad({ requireNext: true })

test/e2e/specs/03-forms/newsletter-subscription.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,14 +222,17 @@ test.describe('Newsletter Subscription Form', () => {
222222
}, undefined, { timeout: 3000 })
223223

224224
const browserName = newsletterPage.context().browser()?.browserType().name()
225-
const delayMs = browserName === 'webkit' ? 400 : 200
225+
const delayMs = browserName === 'webkit' ? 400 : browserName === 'firefox' ? 600 : 200
226226
const delayOverride = await delayFetchForEndpoint(newsletterPage.page, { endpoint: '/api/newsletter', delayMs })
227227
const submitButton = newsletterPage.locator('#newsletter-submit')
228-
const stateTimeout = { timeout: 2000 }
228+
const stateTimeoutMs = 4000
229+
const stateTimeout = { timeout: stateTimeoutMs }
229230
const apiResponsePromise = newsletterPage.page.waitForResponse('/api/newsletter')
230231
const submitPromise = submitButton.click()
232+
const fetchStarted = delayOverride.waitForCall(stateTimeoutMs)
231233

232234
try {
235+
await fetchStarted
233236
await expect(submitButton).toHaveAttribute('data-e2e-state', 'loading', stateTimeout)
234237
await expect(submitButton).toBeDisabled({ timeout: 2000 })
235238
await submitPromise

test/e2e/specs/04-components/navigation-mobile.spec.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,6 @@ test.describe('Mobile Navigation', () => {
399399
const page = await BasePage.init(playwrightPage)
400400
await page.setViewport(375, 667)
401401

402-
// Install fake timers before going to the page
403-
await playwrightPage.clock.install()
404402
await setupTestPage(playwrightPage, '/')
405403

406404
// Check body doesn't have no-scroll class initially
@@ -409,19 +407,17 @@ test.describe('Mobile Navigation', () => {
409407
})
410408
expect(hasNoScrollClassInitial).toBe(false)
411409

412-
// Open menu
413-
await page.click('button[aria-label="toggle menu"]')
414-
await playwrightPage.clock.fastForward(100) // Just enough for the class to be added
410+
// Open menu via helper to wait for the animation and scroll lock
411+
await page.openMobileMenu()
415412

416413
// Body should have no-scroll class when menu is open
417414
const hasNoScrollClassOpen = await playwrightPage.locator('body').evaluate((el) => {
418415
return el.classList.contains('no-scroll')
419416
})
420417
expect(hasNoScrollClassOpen).toBe(true)
421418

422-
// Close menu
423-
await page.click('button[aria-label="toggle menu"]')
424-
await playwrightPage.clock.fastForward(100) // Just enough for the class to be removed
419+
// Close menu via helper to wait for scroll lock to clear
420+
await page.closeMobileMenu()
425421

426422
// Body should not have no-scroll class when menu is closed
427423
const hasNoScrollClassClosed = await playwrightPage.locator('body').evaluate((el) => {

test/e2e/specs/07-performance/PERFORMANCE.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
11
<!-- markdownlint-disable-file -->
22
# Performance
33

4-
continue
5-
Please analyze E2E_STRESS_TESTS.md
6-
Let's proceed with troubleshooting
7-
See E2E_STRESS_TESTS.md and the "Instructions" section. We have an error that occurred after __ runs of the entire test e2e test suite.
8-
Run the tests
9-
Update E2E_STRESS_TESTS.md with the implementation
10-
11-
Please follow the "Instructions" section in E2E_STRESS_TESTS.md. I've added a new error.
12-
13-
In the "Build and Test" GitHub Action workflow on the "Apply Supabase migrations" step after the last recent push, I get the following error output:
14-
154
Latest run (Dec 2, 2025):
165

176
- Mobile Chrome LCP measured 4.3 s (threshold 2.5 s)

0 commit comments

Comments
 (0)