Skip to content

Add mobile web E2E coverage for viewport, touch, safe-area, and overflow#224

Closed
auerbachb wants to merge 1 commit into
mainfrom
cursor/mobile-e2e-192-5dcc
Closed

Add mobile web E2E coverage for viewport, touch, safe-area, and overflow#224
auerbachb wants to merge 1 commit into
mainfrom
cursor/mobile-e2e-192-5dcc

Conversation

@auerbachb
Copy link
Copy Markdown
Owner

@auerbachb auerbachb commented Apr 24, 2026

User description

Summary

  • add Playwright mobile-first test infrastructure (playwright.config.ts) with three mobile projects:
    • mobile-chromium-narrow (iPhone SE)
    • mobile-chromium-wide (iPhone 13 Pro Max)
    • mobile-webkit-iphone (iPhone 13 / Safari-class behavior)
  • add reusable mobile helpers in e2e/utils/mobile-helpers.ts for tap interactions, tap-target sizing, overflow checks, viewport/safe-area visibility assertions, and overlap checks
  • add mobile auth/session/layout specs:
    • e2e/auth/login.spec.ts
    • e2e/session/session-flow.spec.ts
    • e2e/layout/safe-area.spec.ts
    • e2e/layout/overflow.spec.ts
  • add deterministic API mocking fixture in e2e/fixtures/auth.fixture.ts so mobile E2E scenarios run consistently in CI
  • add CI workflow .github/workflows/e2e-mobile.yml to run mobile matrix jobs (Chromium narrow + WebKit iPhone)
  • add Playwright scripts in package.json and ignore Playwright artifact directories in .gitignore
  • adjust small mobile-control sizing details in app UI to satisfy critical tap-target checks:
    • src/components/SessionView.tsx
    • src/components/CompletionScreen.tsx
    • src/app/app/page.tsx

Acceptance Criteria Mapping

  • Login → session shell touch path: covered with tap() and bottom nav transitions in e2e/auth/login.spec.ts
  • Session complete flow without hover reliance: touch-only flow validated in e2e/session/session-flow.spec.ts
  • Safe-area / bottom bar non-overlap: covered by bounding-box assertions and overlap checks in e2e/layout/safe-area.spec.ts
  • No horizontal overflow: validated on auth/home/history/session contexts via e2e/layout/overflow.spec.ts
  • Scroll + sticky nav behavior: long history list scroll and overlap guard in e2e/layout/overflow.spec.ts
  • Landscape smoke: included in e2e/session/session-flow.spec.ts
  • Tap target checks (44px guideline): critical controls asserted via helper checks
  • Pull-to-refresh / overscroll resilience: session continuity check in e2e/session/session-flow.spec.ts
  • Zoom/text scaling (120%): smoke coverage in e2e/layout/overflow.spec.ts
  • Keyboard/focus reachability: auth focus checks in e2e/auth/login.spec.ts
  • 401/network error contract on narrow viewport: covered in e2e/auth/login.spec.ts
  • At least two mobile widths OR width+WebKit tradeoff: both provided (two Chromium widths and WebKit)
  • No-overlap proof on session screen: explicit no-overlap assertions in safe-area specs

CI Matrix Tradeoff Note

This change exceeds the minimum by including two mobile Chromium widths plus a WebKit iPhone project. The workflow currently runs narrow Chromium + WebKit as matrix jobs, while wide Chromium remains available as a local/optional CI target via project selection.

Validation

  • npm run test:e2e -- --project mobile-chromium-narrow
  • npm run test:e2e -- --project mobile-chromium-wide
  • npm run test:e2e -- --project mobile-webkit-iphone

Note on CodeRabbit CLI checkpoint

Attempted coderabbit review --prompt-only as requested, but CLI auth is required in this environment (Authentication required. Please run 'coderabbit auth login' or provide --api-key).

Open in Web Open in Cursor 

CodeAnt-AI Description

Add mobile web test coverage for login, session flow, and layout safety

What Changed

  • Added mobile browser tests that cover sign up, login, session start/end, history browsing, completion, and retry states on small touch screens
  • Added checks for tap target size, safe-area spacing, viewport visibility, and horizontal overflow so key controls stay usable on phones
  • Added a mobile test runner setup and CI job to run the new checks on Chromium and Safari-like browsers
  • Increased the size of key mobile buttons so they meet touch guidelines and stay easier to tap

Impact

✅ Fewer mobile login and session flow breaks
✅ Fewer buttons hidden under browser chrome
✅ Easier tapping on phone-sized screens

🔄 Retrigger CodeAnt AI Review

Details

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
still-point Ready Ready Preview, Comment Apr 24, 2026 5:16am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Warning

Rate limit exceeded

@auerbachb has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 53 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d1b6a66-8ebb-4446-afe9-898e4b5e4e15

📥 Commits

Reviewing files that changed from the base of the PR and between b1a5c04 and 7113269.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • .github/workflows/e2e-mobile.yml
  • .gitignore
  • e2e/auth/login.spec.ts
  • e2e/fixtures/auth.fixture.ts
  • e2e/layout/overflow.spec.ts
  • e2e/layout/safe-area.spec.ts
  • e2e/session/session-flow.spec.ts
  • e2e/utils/mobile-helpers.ts
  • package.json
  • playwright.config.ts
  • src/app/app/page.tsx
  • src/components/CompletionScreen.tsx
  • src/components/SessionView.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/mobile-e2e-192-5dcc

Comment @coderabbitai help to get the list of available commands and usage tips.

@auerbachb auerbachb marked this pull request as ready for review April 28, 2026 13:19
Comment thread e2e/auth/login.spec.ts
await expect(passwordInput).toBeFocused();
await expectVisibleInViewport(page, passwordInput, "auth password input");
await expectNoHorizontalOverflow(page);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test missing mock routes will fail in CI

Medium Severity

The "focus stays reachable on auth inputs" test destructures only { page }, so the mockApiState fixture is never initialized and installMockApiRoutes is never called. Without mock API routes, the real /api/auth/me endpoint is hit. In CI (where no POSTGRES_URL is configured), the server returns a 500, causing the app to render a "server issue" error screen instead of the login form with email/password inputs the test expects. This test will reliably fail in the new e2e-mobile.yml CI workflow.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7113269. Configure here.

@auerbachb
Copy link
Copy Markdown
Owner Author

@CodeAnt-AI review

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Apr 30, 2026

CodeAnt AI is running the review.

@auerbachb
Copy link
Copy Markdown
Owner Author

@cursor review

@auerbachb
Copy link
Copy Markdown
Owner Author

@graphite-app re-review

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Apr 30, 2026
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Apr 30, 2026

Sequence Diagram

This PR adds Playwright mobile projects and a deterministic auth fixture so tests can drive a touch-only session flow while asserting safe tap targets, viewport visibility, and overflow behavior against a mocked API state.

sequenceDiagram
    participant MobileTest
    participant Browser
    participant WebApp
    participant MockApi

    MobileTest->>Browser: Open app on mobile viewport
    Browser->>MockApi: Send auth sign up or login request
    MockApi-->>Browser: Return authenticated user and initial state
    Browser->>WebApp: Render home and session controls
    MobileTest->>Browser: Tap Begin and session actions
    Browser->>MockApi: Send request to create session record
    MockApi-->>Browser: Return stored session data
    MobileTest->>MobileTest: Assert UI layout and mock state (no overflow, tap targets)
Loading

Generated by CodeAnt AI

Comment thread playwright.config.ts
Comment on lines +3 to +4
const PORT = process.env.PORT ? Number(process.env.PORT) : 3000;
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${PORT}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: process.env.PORT is coerced with Number(...) without validation, so a non-numeric value (for example from CI/env misconfiguration) becomes NaN. That produces an invalid baseURL and an invalid --port argument for the web server command, causing Playwright startup to fail before tests run. Validate the parsed port and fall back to a known default when parsing fails. [possible bug]

Severity Level: Major ⚠️
- ❌ E2E Mobile Web GitHub workflow fails on bad PORT env.
- ⚠️ Local `test:e2e` runs break under PORT misconfiguration.
Steps of Reproduction ✅
1. Set a non-numeric PORT environment variable before running Playwright tests, for
example: `PORT=abc npx playwright test --project mobile-chromium-narrow` (Playwright entry
configured in `package.json:12` and `playwright.config.ts:1-4`).

2. When Playwright starts, it evaluates `const PORT = process.env.PORT ?
Number(process.env.PORT) : 3000;` at `playwright.config.ts:3`; with `PORT=abc`,
`Number("abc")` yields `NaN`.

3. Still during config evaluation, it constructs `baseURL` as `http://127.0.0.1:${PORT}`
at `playwright.config.ts:4`, producing `http://127.0.0.1:NaN`, and prepares the dev server
command `npm run dev -- --port ${PORT}` at `playwright.config.ts:23-27`, which becomes
`npm run dev -- --port NaN`.

4. Playwright then tries to launch the Next.js dev server using this invalid command
(`--port NaN`) and wait for `url: baseURL` (`http://127.0.0.1:NaN`) as configured at
`playwright.config.ts:23-28`; Next.js and/or Playwright fails to bind or detect a server
on port `NaN`, causing the `E2E Mobile Web` workflow
(`.github/workflows/e2e-mobile.yml:1-19`) and local `npx playwright test` runs to fail
before any tests execute.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** playwright.config.ts
**Line:** 3:4
**Comment:**
	*Possible Bug: `process.env.PORT` is coerced with `Number(...)` without validation, so a non-numeric value (for example from CI/env misconfiguration) becomes `NaN`. That produces an invalid `baseURL` and an invalid `--port` argument for the web server command, causing Playwright startup to fail before tests run. Validate the parsed port and fall back to a known default when parsing fails.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +17 to +20
try {
await target.tap();
} catch {
await target.tap({ force: true });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The tap helper falls back to tap({ force: true }) for any failure, which can trigger clicks on covered or non-actionable elements and make tests pass even when real users cannot tap the control. Restrict the fallback to specific transient errors (or remove forced taps) so interaction failures surface correctly. [logic error]

Severity Level: Major ⚠️
- ❌ Mobile auth and session E2E may miss non-tappable controls.
- ⚠️ CI may show green despite real interaction regressions.
Steps of Reproduction ✅
1. Run the mobile Playwright tests (e.g., `npx playwright test e2e/auth/login.spec.ts`)
which import `tap` from `e2e/utils/mobile-helpers.ts` at `e2e/auth/login.spec.ts:2-7`.

2. In `e2e/auth/login.spec.ts:9-37`, the test `"login to session shell with touch and
mobile nav"` calls `await tap(enterButton);` at line 20 on the `Enter` button locator.

3. The `tap` helper implementation at `e2e/utils/mobile-helpers.ts:14-21` scrolls the
locator into view and calls `await target.tap();` at line 18; under Playwright, this
throws if the element is covered, not hittable, or otherwise non-actionable.

4. The `catch` block at `e2e/utils/mobile-helpers.ts:19-20` unconditionally retries `await
target.tap({ force: true });`, which bypasses Playwright's hit-target checks so the click
succeeds even when the control is effectively untappable for real users, allowing
downstream assertions in `login.spec.ts` and other specs (e.g., navigation headings) to
pass and masking interaction regressions.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** e2e/utils/mobile-helpers.ts
**Line:** 17:20
**Comment:**
	*Logic Error: The `tap` helper falls back to `tap({ force: true })` for any failure, which can trigger clicks on covered or non-actionable elements and make tests pass even when real users cannot tap the control. Restrict the fallback to specific transient errors (or remove forced taps) so interaction failures surface correctly.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +64 to +65
const upperBox = await getRequiredBox(upper, labels.upper);
const lowerBox = await getRequiredBox(lower, labels.lower);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: expectNoVerticalOverlap measures upper and lower after separate scrollIntoViewIfNeeded() calls inside getRequiredBox, so the second measurement can change scroll position and invalidate the first box coordinates. This can produce flaky or incorrect overlap assertions; capture both boxes in the same viewport state. [possible bug]

Severity Level: Major ⚠️
- ❌ Safe-area layout tests can misreport control overlap conditions.
- ⚠️ Overflow scrolling tests may become flaky with tall content.
Steps of Reproduction ✅
1. Run the layout tests `e2e/layout/safe-area.spec.ts` and `e2e/layout/overflow.spec.ts`,
which import `expectNoVerticalOverlap` from `e2e/utils/mobile-helpers.ts` at
`safe-area.spec.ts:1-8` and `overflow.spec.ts:1-8`.

2. In `e2e/layout/safe-area.spec.ts:11-26`, the test `"home and nav controls stay visible
above iOS-like bottom safe area"` calls `await expectNoVerticalOverlap(beginButton,
homeNav, {...});` at line 22; similarly, `e2e/layout/overflow.spec.ts:39-45` calls `await
expectNoVerticalOverlap(lastEntry, homeNav, {...});` at line 41.

3. The `expectNoVerticalOverlap` helper at `e2e/utils/mobile-helpers.ts:58-71` computes
`upperBox` and `lowerBox` by calling `getRequiredBox` twice (lines 64-65);
`getRequiredBox` at lines 6-11 always calls `await target.scrollIntoViewIfNeeded();` and
then `await target.boundingBox();`.

4. When the two locators cannot both be fully visible without scrolling (e.g., tall
history content or future reuse with widely separated elements), the second
`scrollIntoViewIfNeeded()` for `lower` can change the viewport scroll position before its
`boundingBox()` measurement, while `upperBox` still reflects the previous viewport.
Because Playwright's `boundingBox()` coordinates are viewport-relative, `upperBottom` and
`lowerTop` in `expectNoVerticalOverlap` are then taken from different viewport states,
leading to flaky or incorrect overlap assertions in these safe-area and overflow layout
tests.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** e2e/utils/mobile-helpers.ts
**Line:** 64:65
**Comment:**
	*Possible Bug: `expectNoVerticalOverlap` measures `upper` and `lower` after separate `scrollIntoViewIfNeeded()` calls inside `getRequiredBox`, so the second measurement can change scroll position and invalidate the first box coordinates. This can produce flaky or incorrect overlap assertions; capture both boxes in the same viewport state.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Apr 30, 2026

CodeAnt AI finished running the review.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7113269. Configure here.

await page.mouse.down();
await page.mouse.move(centerX, Math.min(viewport.height - 20, 250), { steps: 12 });
await page.mouse.up();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull-to-refresh helper uses mouse, not touch events

Low Severity

simulatePullToRefreshGesture uses page.mouse (move/down/up) to simulate a mobile touch gesture, but Playwright's page.mouse always dispatches mouse events regardless of hasTouch device configuration. All three mobile projects in playwright.config.ts use iPhone device descriptors with hasTouch: true. For a faithful touch-based overscroll simulation, page.touchscreen should be used. The current implementation dispatches mouse events that won't trigger touch-specific browser behaviors like native overscroll or pull-to-refresh interception, making the test less representative of actual mobile user interaction than its name implies.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7113269. Configure here.

@auerbachb
Copy link
Copy Markdown
Owner Author

Closing as superseded by #225, which was merged on 2026-04-24 with the same title and equivalent (in fact strictly broader) coverage.

Verified that current main contains everything in this PR:

  • All 3 Playwright projects (mobile-chromium-narrow, mobile-chromium-wide, mobile-webkit-iphone) — main's playwright.config.ts is a superset (adds globalSetup, lane-run mode, E2E_BASE_URL).
  • All test cases in e2e/auth/login.spec.ts, e2e/session/session-flow.spec.ts, e2e/layout/safe-area.spec.ts, e2e/layout/overflow.spec.ts — main has every test name from this PR plus extras (deleted-account login error, tracking-controls interactivity, quick-minute save).
  • e2e/utils/mobile-helpers.ts and e2e/fixtures/auth.fixture.ts — main has expanded versions, including fix(e2e): provision fixture user before mobile web Playwright (closes #435) #307's fixture-user provisioning fix.
  • UI tap-target tweaks in SessionView (padding 14px 24px), CompletionScreen (minHeight 44px), and src/app/app/page.tsx (lineHeight 1.2, minHeight 44px) are all already on main.
  • package.json scripts (test:e2e, test:e2e:headed, test:e2e:install) and @playwright/test devDependency are on main.
  • .gitignore entries for /playwright-report and /test-results are on main.
  • .github/workflows/e2e-mobile.yml is on main (broader matrix).

Rebasing this branch onto main would either replay #225's work as a no-op or regress #307's fixture fixes. No unique content to salvage.

@auerbachb auerbachb closed this May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants