Skip to content

fix(e2e): improve robustness of flaky tablet/mobile E2E tests#207

Merged
steilerDev merged 3 commits into
betafrom
fix/e2e-flaky-tests
Feb 23, 2026
Merged

fix(e2e): improve robustness of flaky tablet/mobile E2E tests#207
steilerDev merged 3 commits into
betafrom
fix/e2e-flaky-tests

Conversation

@steilerDev
Copy link
Copy Markdown
Owner

Summary

  • proxy-setup.spec.ts ([desktop] lines 65 & 115): Replaced duplicated inline login code across 3 tests with a shared loginThroughProxy() helper that uses Promise.all to start the API response listener before clicking Submit (prevents the race condition where the login POST response arrives before the listener is attached through the extra nginx hop). Uses waitForURL() instead of expect(page).not.toHaveURL() for a condition-based wait that reliably exits once the React router navigates away.

  • WorkItemsPage.ts search() / clearSearch(): The waitForResponse listener was registered AFTER searchInput.fill(), creating a race on WebKit/tablet where the 300ms-debounced request completed before the listener was attached (listener would never resolve). Fix: register the listener before the action, then await it. Additionally, waitForLoaded() is called after the response to ensure React has flushed the filtered data into the DOM before callers read titles.

  • WorkItemsPage.ts confirmDelete(): Added a waitForResponse listener for the DELETE 204 response before clicking confirm, so the test reliably knows the server has processed the deletion before checking list state.

  • work-items-list.spec.ts delete test (line 272, [mobile]): Replaced waitForLoaded() after confirmDelete() with an explicit waitForResponse for the post-delete list-refresh GET, registered before the delete action — this avoids the race between modal close and list re-render.

  • work-item-create.spec.ts (line 75, [mobile]): Removed hardcoded { timeout: 7000 } from waitForURL and the heading toHaveText assertion. These now use the project-level navigationTimeout (15s for mobile/tablet WebKit) which is sufficient for the redirect chain after form submission.

Test plan

  • CI Quality Gates pass (lint, typecheck, build, unit tests)
  • E2E smoke tests pass in CI (e2e-smoke job)
  • Post-merge E2E full suite on beta shows the 8 previously-failing tests as passing

Root Cause Summary

All failures were race conditions between network responses and DOM updates on slow WebKit CI runners:

  1. waitForResponse registered after the triggering action (missed the response)
  2. URL assertions without waiting for the navigation to complete
  3. Hardcoded timeouts shorter than the mobile/tablet project-level timeout

🤖 Generated with Claude Code

## Root Causes

### search() / clearSearch() race condition (work-items-list.spec.ts tablet timeouts)
The waitForResponse listener was registered AFTER searchInput.fill(), creating a
race where the debounced 300ms request could complete before the listener was
attached. On WebKit/tablet, this caused the listener to never resolve. Fix:
register the listener BEFORE the fill action, then await it after. Also added
waitForLoaded() after the response to ensure React has flushed filtered data into
the DOM before callers read titles.

### confirmDelete() missing DELETE response wait (work-items-list.spec.ts mobile timeout)
The confirmDelete() method clicked the button and waited for the modal to hide,
but didn't wait for the DELETE API response. This caused a race where the test
proceeded to assert list state before the server confirmed deletion and before
React re-fetched the list. Fix: register a DELETE 204 response listener before
the click. In the test, the post-delete list-refresh GET listener is also
registered before confirmDelete() is called.

### waitForURL timeout too short for mobile WebKit (work-item-create.spec.ts)
The waitForURL('**/work-items/**', { timeout: 7000 }) hardcoded a 7s timeout that
was too short for mobile WebKit (project-level navigationTimeout is 15s). Removed
the hardcoded timeout so the project-level setting applies. Same fix for the
heading assertion.

### proxy login race condition (proxy-setup.spec.ts desktop)
The login tests used `expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })`
after button click, which could time out if React's router update lagged behind
session establishment. Replaced with a shared loginThroughProxy() helper that
uses Promise.all to start the API response listener BEFORE clicking Submit, then
waits for URL change with waitForURL() — a condition-based wait that is more
reliable than polling not.toHaveURL. Eliminated duplicated login code across 3
tests.

Co-Authored-By: Claude <qa-integration-tester> (Sonnet 4.6) <noreply@anthropic.com>
Use a proper `import type { Page }` declaration instead of an inline
`import()` type annotation, which is forbidden by the
`@typescript-eslint/consistent-type-imports` rule.

Co-Authored-By: Claude <qa-integration-tester> (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude <qa-integration-tester> (Sonnet 4.6) <noreply@anthropic.com>
@steilerDev steilerDev merged commit be42d89 into beta Feb 23, 2026
6 checks passed
@steilerDev steilerDev deleted the fix/e2e-flaky-tests branch February 23, 2026 12:09
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.9.0-beta.52 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

steilerDev pushed a commit that referenced this pull request Feb 23, 2026
845+ E2E tests across desktop, tablet, and mobile viewports covering all
budget pages: categories, vendors, invoices, financing sources, subsidy
programs, work item budget lines, and budget overview dashboard. Includes
full page coverage for 11 previously uncovered pages. (#172, #188, #207)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants