Skip to content

E2E suite has no phone viewport, so nothing automated has ever rendered the app at phone width #814

Closed

Description

@mforce

What

tools/simulation/ui/playwright.config.ts declares one project — chromium, spreading devices["Desktop Chrome"] and nothing else. devices["Desktop Chrome"] carries a 1280×720 viewport. So every spec in the smoke suite, on every pull request since 2026-08-08, has only ever rendered the app on a desktop-sized window.

The app's primary context is a phone in a hen house. #674 chose direction B — field-first phone — on exactly that reasoning. Nothing automated has ever looked at it.

What this let through

#740. A Confirm order button whose label wraps at phone width, making the box taller than it is wide; border-radius: 999px then resolves to an ellipse the label renders outside of. Measured on the running sim stack:

width height
en @420 100.4 103.2
tl @420 115.7 103.2
en @1280 285.7 40.1
tl @1280 390.4 40.1

At 1280 both are a single 40px line and render correctly. That is why the suite never saw it.

The detail that makes this worth fixing rather than filing under i18n: #740 was filed as a Tagalog/Spanish bug, and reproducing it showed English is broken too — en at 420px has the worst aspect ratio of the three. So this is not a gap in locale coverage that a translated run would have closed. It is a gap in viewport coverage, and an English-only phone-width run would have caught it on day one.

Scope

  • A second project in playwright.config.ts at a phone viewport. devices["Pixel 7"] or an explicit { width: 420, height: 900 } — 420px is the width web: long tl/es strings overflow fixed-width controls on narrow screens #740 reproduces at and is a realistic low end.
  • Decide which specs run there. Running all of them doubles a suite that is on the pull-request critical path (~3 minutes, 10-minute cap). A grep-selected subset — the screens with action bars and dialogs — is probably right, and mirrors how playwright.screenshots.config.ts already splits a chromium-dashboard project out by grep.
  • Note the shell differs below 901px: the sidebar gives way to BottomNav. fixtures.ts's signIn asserts on getByRole("complementary"), which is the sidebar, so a spec that signs in at phone width needs either a resize after sign-in or a different shell assertion. Reproducing web: long tl/es strings overflow fixed-width controls on narrow screens #740 hit exactly this — worth knowing before the first spec is written.

Out of scope

Fixing #740. That is moving to the #674 revamp epic, because the remedy is a decision about how action buttons lay out on a phone. This issue is the missing gate, not the fix — and it is worth landing independently, because the gate is what stops the next one.

Acceptance

  • The suite renders at least one phone-width project on every pull request that already triggers e2e-smoke.yml.
  • A deliberately over-long button label at 420px fails the new project and passes the desktop one — prove it with a mutation, not by assertion (AGENTS.md: mutation first, claim second).
  • The pull-request run stays inside its existing timeout.

Activity

  1. mforce commented on Sep 13, 2026

    @mforce
    OwnerAuthor

    Handoff — #814, phone viewport in the E2E suite

    Written 2026-09-13 so this can be picked up cold. Everything below is read from the repo or measured on a running stack, not recalled. Nothing here is started.

    The one-line problem

    tools/simulation/ui/playwright.config.ts:69-84 declares one project. It spreads devices["Desktop Chrome"] and overrides nothing but launchOptions. That device carries a 1280×720 viewport, so every spec, on every pull request since 2026-08-08, has only ever rendered the app in a desktop window.

    Why it matters more here than in most apps

    The product's primary context is a phone in a hen house — that is the stated reason #674 chose direction B, field-first phone. The one context the app is designed around is the one nothing automated has ever looked at.

    The proof it let something through

    #740, measured on the sim stack at 127.0.0.1:8081:

    width height radius
    en @420 100.4 103.2 999px
    tl @420 115.7 103.2 999px
    en @900 260.4 46.2 999px
    tl @900 260.4 65.2 999px
    en @1280 285.7 40.1 999px
    tl @1280 390.4 40.1 999px

    At 1280 both locales are a single 40px line and render correctly. At 420 the button is taller than it is wide, border-radius: 999px clamps into an ellipse, and the label renders outside its own background. English is the worst aspect ratio of the three, so a translated test run would not have caught it either — this is a viewport gap, not a locale gap. Screenshots are on #740.

    Where to make the change

    tools/simulation/ui/playwright.config.ts, the projects array. The pattern to copy already exists in this repo: playwright.screenshots.config.ts splits a second project out by grep / grepInvert and gives it its own viewport, with a comment explaining why the viewport override must come after the devices[...] spread — a project's use beats the top-level one, and setting the frame above the spread silently captures at the device's size instead. Same trap applies here.

    Four things that will bite, in the order they will bite

    1. signIn asserts on the sidebar, which does not exist at phone width.
    src/fixtures.ts's signIn ends with await expect(page.getByRole("complementary")).toBeVisible() — the <aside>. web/src/styles.css:2798 is @media (max-width: 900px) and sets .sidebar { display: none }, replacing it with BottomNav. So a spec that signs in at 420px hangs and then fails at "waiting for complementary", which reads like a broken login. Two ways out: sign in at desktop width and resize afterwards (what the #740 reproduction did, and it works), or give the fixture a shell assertion that holds at both widths. Prefer the second — the first leaves every phone spec carrying a desktop step it does not want.

    2. shellNav is sidebar-scoped and will match nothing.
    shellNav scopes primary to the sidebar's navigation landmark precisely because BottomNav renders the same destinations and an unscoped getByRole("link") is strict-mode-ambiguous. At phone width the sidebar is gone, so nav.link(...) finds nothing. Any spec using nav needs a phone-aware variant. Note signOut has the same problem from the other side: it lives in the sidebar foot on desktop and inside BottomNav's More sheet on a phone, which is a dialog portalled to <body> and only exists while open.

    3. The pull-request run has a 10-minute cap, and the suite is ~3 minutes.
    .github/workflows/e2e-smoke.yml:83 sets timeout-minutes: 10 on pull_request (45 on dispatch). The measured job is ~3 minutes wall for image build + boot + migrate + seed + 28 specs. Running all 14 spec files at a second viewport roughly doubles the spec portion, which is the part inside the cap. Select a subset by grep rather than running everything. The specs worth having at phone width are the ones with action bars, dialogs and tables — sales, worker, manager, owner, named-entity-picker, pagination. session-refresh and session-races are about token lifetime and have nothing viewport-dependent; i18n persists a language change server-side and is the slowest to make safe.

    4. i18n.spec.ts persists language against a dedicated persona for a reason.
    cast.ts's i18nPersona() exists because LanguageSelector calls PUT /me/language, so switching language is durable against that user. If a phone spec switches language, it must use that persona and restore English in a finally, or it poisons later runs of the persona specs. This is documented at length in cast.ts — read it before adding any language handling.

    Acceptance, and the one part that is not optional

    • At least one phone-width project runs on every pull request that already triggers e2e-smoke.yml.
    • The pull-request job stays inside its 10-minute cap. Measure the new wall time and put the number in the PR, rather than asserting it still fits.
    • Prove the gate can fail. AGENTS.md is explicit — mutation first, claim second, and never write "this catches X" before running the mutation that makes it go red. This repo has the harness for it: mutation-check.sh, with mutants in src/mutants.ts, and its three-phase shape (baseline green → mutant red → restore green) is what a new guard is expected to pass through. A mutant that lengthens a button label, or shrinks the .actions flex basis, should turn the phone project red and leave the desktop project green. A phone project that cannot demonstrate that is a wrong guard, and a wrong guard reads as safety.

    Out of scope

    Fixing #740. That moved to the #674 revamp epic because the remedy is a decision about how action buttons lay out on a phone. This issue is the gate, not the fix, and it is worth landing first — the gate is what stops the next one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions