Skip to content

test: move frontend-only e2e specs to the vitest-browser tier#563

Open
mariusvniekerk wants to merge 15 commits into
mainfrom
t3code/3bc127bc
Open

test: move frontend-only e2e specs to the vitest-browser tier#563
mariusvniekerk wants to merge 15 commits into
mainfrom
t3code/3bc127bc

Conversation

@mariusvniekerk

Copy link
Copy Markdown
Collaborator

Reimplements PR #499's Playwright→Vitest e2e demotions on the current vitest-browser test tier. Test-only; no production code changes.

  • Move frontend-only behaviors from e2e-full Playwright specs to browser-tier App.*.browser.svelte.ts specs that mount the real app against mocked fetch: provider capabilities, force-mobile routes, stack-panel rendering, repo-dropdown route sync, repo-group collapse, view navigation, activity row links, item-reference routing, PR timeline filters, and the repo grouping toggle.
  • Keep genuinely backend-dependent cases in trimmed Playwright specs: force-push commit ordering (Go computes commit_order_key), review-thread reply layout, the live __e2e regroup hook, provider-collision persistence, and native checkbox-focus behavior.
  • Trim repo-filter-multiselect to its live-stack cases only; its component logic is already covered by existing jsdom RepoTypeahead/RepoTreeNode/repoTree tests and Go's repo-filter test.
  • Leave app-startup full-stack (the browser mock can't gate /healthz or fast-forward the settings timeout) with a header documenting why.

🤖 Generated with Claude Code

generated by a clanker

mariusvniekerk and others added 15 commits June 19, 2026 14:55
The provider-capabilities e2e-full spec exercised two pieces of pure UI
rendering -- the locked-PR chip and the GitLab read-only issue timeline --
through a seeded Go server and two Playwright browser projects. That is
slow and flaky for behavior that only needs a real DOM, so this reimplements
both cases on the vitest-browser tier: the real App is mounted in Chromium
with the capability data mocked at the fetch boundary, so the rendered chip
and the presence/absence of the timeline edit control reflect exactly what
the capability fields produced.

The spec's raw /api/v1 capabilities JSON sub-assertion is intentionally not
carried over. GitLab's real capability default is CommentMutation=true
(internal/platform/gitlab/client.go); the e2e-server fixture constructs the
read-only scenario, so that assertion only pinned the test server's own
fixture wiring. The provider capability contract is owned by the Go platform
tests (e.g. internal/platform/gitlab/mutation_test.go).

First unit of reimplementing PR #499's Playwright->Vitest demotion on the
current browser tier, which postdates and supersedes #499's jsdom approach.

Validation: full browser project green (15 files, 50 tests).

Generated with Claude Code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The force-mobile-routes e2e-full spec checked a pure presentation contract:
the __MIDDLEMAN_FORCE_MOBILE_ROUTES__ global makes /issues render the focus
layout (no mobile shell, no app header) even on a desktop-width viewport.
That needs only a real DOM, so it moves to the vitest-browser tier, where the
real App is mounted in Chromium with the flag set before render. A contrast
case without the flag pins that the same route falls back to the standard
desktop shell, proving the presentation is flag-driven rather than incidental.

App.svelte reads the flag at render time (shouldForceMobileRoutes), so setting
window.__MIDDLEMAN_FORCE_MOBILE_ROUTES__ before mountBrowserApp is sufficient
and the value is reset between cases.

Validation: browser project file green (2 tests).

Generated with Claude Code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The stack-status e2e-full spec covered three things through a seeded Go
server: the stack panel rendering (summary, per-member conflict labels,
member rows, passive base row), focus-route member navigation, and the
activity-drawer member navigation. The first two are pure stack-data
rendering plus client-side routing, so they move to the vitest-browser tier:
the real App is mounted in Chromium with the acme/tools auth-stack detail
mocked at the fetch boundary, and the rendered panel and the post-navigation
URL are the genuine outputs of that data.

The member data mirrors internal/testutil/fixtures.go. The raw DB marks only
#10 dirty, but the stack API propagates downstack conflicts upward (the spec
asserts the conflict label three times), so the mocked stack response carries
mergeable_state dirty on every member -- the response shape the panel renders.

The activity-drawer case stays in Playwright: opening the drawer from a
selected= URL param and rewriting the route query on member navigation is
cross-layer drawer/routing glue better exercised against the live backend.

Validation: browser project file green (2 tests).

Generated with Claude Code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The link-navigation-repo-sync spec is a regression guard: deep-linking to a
PR/issue used to leave the repo dropdown and sidebar list pinned to a
previously chosen repo while the detail jumped elsewhere. The PR-side cases
(initial cross-repo deep-link, back-to-back PR switches via popstate, all-repos
selection, and preserving the chosen repo on a bare /pulls) are client-side
routing + dropdown state, so they move to the vitest-browser tier: the real App
is mounted in Chromium with the list/detail mocked at the fetch boundary, and
the pull list honors the repo= query param the store sends so the sidebar group
reflects the same filter the live backend applies.

Reproducing the sync requires both repos configured (the normalization resolves
only configured repos) and driving the global-repo store directly, since its
state initializes from localStorage once at module load. The issue deep-link
variant stays in Playwright: the issues-route sync interacts with the issue
detail and settings load in a way exercised most faithfully end-to-end.

Validation: browser project file green (4 tests).

Generated with Claude Code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The collapsible-repos spec exercised repo/status group collapse, the retained
header count, localStorage persistence across reload, and the independence of
the pulls vs issues collapse sets. These are real DOM + localStorage behaviors
with no backend dependency, so they move to the vitest-browser tier: the app is
mounted in Chromium with the pull/issue lists mocked at the fetch boundary, and
a remount stands in for the reload to assert the persisted state.

The keyboard-activation case (Enter/Space on the header button) stays in
Playwright: native button activation is a real key event that the browser
tier's synthetic KeyboardEvent dispatch cannot trigger.

Validation: browser project file green (7 tests).

Generated with Claude Code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The activity-row-link spec guards that flat and threaded activity rows keep a
link button that opens the provider URL via window.open without opening the
in-app drawer, that threaded rows stay clickable after the split detail pane
opens, and that the compact threaded row keeps the expand chevron separate from
the type chip. These are real rendering, real window.open, and real geometry,
so the whole spec moves to the vitest-browser tier: the app is mounted in
Chromium with the activity feed mocked at the fetch boundary, window.open is
spied with vi.spyOn, and the chevron gap is measured with getBoundingClientRect.

Validation: browser project file green (4 tests).

Generated with Claude Code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tier

The navigation spec's app-shell flows that need only the activity feed and the
PR/issue lists move to the vitest-browser tier: selecting an activity item and
returning to it after visiting PRs or the settings gear preserves the selection
in the URL and reopens the split view; the legacy /mail route falls through to
Activity rather than Messages; and clicking a list row opens the detail pane.
The app is mounted in Chromium with those feeds mocked at the fetch boundary.

The cross-mode tab tour, the Kata-shell repo-selector/shortcut specifics, the
direct Docs/Messages loads, and the settings/diff route toggle stay in
Playwright: they depend on the external Kata/Docs/Messages mode-shell backends
and diff rendering, which are full-stack concerns.

Validation: browser project file green (5 tests).

Generated with Claude Code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both cross-repository timeline-reference cases (tracked -> internal navigation,
untracked -> provider window.open) are real fetch-boundary behavior the browser
tier reproduces without a live Go backend, so they move to
App.item-reference-routing.browser.svelte.ts and the e2e-full Playwright spec is
removed.

The detail title (.detail-title) only renders when the repo capabilities include
state_mutation; the mocked repoRef now sets it so the title binds, matching the
seeded server's GitHub capability set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The browser-tier force-mobile test only checked that .focus-layout .focus-list
existed in the DOM, so a regression that left the focus layout mounted but
hidden by CSS would have passed. The deleted Playwright spec asserted
visibility; restore that guarantee with a visibility assertion plus a non-empty
bounding rect (roborev #2385).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The timeline rendering and filter behavior computed entirely in the Svelte
EventTimeline component moves to App.pr-timeline-filters.browser.svelte.ts:
seeded commit/system event rendering, merge/close lifecycle collapse into one
purple row, hiding and restoring the View-menu event buckets, and persisting the
bucket filter across a reload. The event fixture mirrors the Go seed.

The Playwright spec keeps only the cases that genuinely need the live backend:
the force-push commit-generation ordering and fresh-import ordering assert the
order the Go sync computes via commit_order_key metadata (the frontend falls
back to event ID order without it, so a hand fixture cannot exercise the
algorithm), the compact review-thread reply layout depends on
backend-transformed review-thread event cards plus reply round-trips, and the
regroup case drives a live __e2e server hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The grouping toggle is one store shared across the PR list, issue list, and
threaded activity view plus a hide-org-name preference, all of which render
deterministically without real geometry. App.grouping-toggle.browser.svelte.ts
covers them end to end: PR-list grouped headers vs per-item repo chips,
persistence across a reload, sync into the issue list and threaded activity,
cross-repo thread separation, the threaded-only grouping control, hide-org-name
relabeling in flat and threaded activity, and the threaded empty state.

The Playwright spec keeps only j/k navigation, which asserts selection follows
the flat visual order and needs native key handling plus scroll-into-view.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…state

The grouping persistence test remounts the app (the browser harness cannot do a
true page reload without losing the API mocks), which would also pass if a
regression kept the grouping choice only in module-level state. Assert the
persisted middleman:groupingMode value at toggle time so persistence is proven
to route through localStorage, the same value a real reload rereads (roborev
#2561).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The repo-tree selector component logic (multi-select checkboxes, owner cascade,
stale provider-qualified value normalization, provider-collision qualification,
single-repo-owner flatten labels) is already covered by the jsdom component
tests RepoTypeahead/RepoTreeNode/repoTree.test.ts, and the backend repo filter
by internal/server TestAPIRepoFilterAcceptsMultipleRepos. Those four e2e-full
cases were redundant with that faster coverage.

The spec keeps only the cases that genuinely need the live backend: the
provider-collision persistence drives a server seeded with two hosts that
collide by path, and the native-click focus retention proves a real checkbox
click does not blur the filter input (a native interaction jsdom cannot
reproduce).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The app-startup readiness-gating and settings-timeout cases cannot move to the
vitest-browser tier: the browser mock hard-codes /healthz and /livez to 200 so
ordinary browser tests do not spin on startup, which makes the 503 readiness
gating impossible to reproduce there, and the timeout case fast-forwards a fake
clock past the 8s settings-startup timeout, which the browser harness has no
equivalent for. Record that decision in a header so the migration's coverage map
stays legible; the spec behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The navigation browser spec's settings-gear case routes to /settings, which
renders FleetSettings; that component dereferences fleet.sessions at init, so the
hand-built settings override crashed with an unhandled 'reading enabled of
undefined' during the full Vitest run (the assertions still passed, but the
unhandled error risks false positives). Carry the same fleet block the default
mock provides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@roborev-ci

roborev-ci Bot commented Jun 20, 2026

Copy link
Copy Markdown

roborev: Review Unavailable (fb4ffa3)

The review agent repeatedly failed to run (likely an agent or configuration error). roborev will try again on the next commit.

Last error: agent: claude-code failed stream: stream errors: You've hit your session limit · resets 5:50am (UTC): exit status 1

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant