Skip to content

feat: Add Playwright e2e tests for demo and fix scroll settle race#31

Merged
arv merged 12 commits intomainfrom
arv/fixes
Apr 1, 2026
Merged

feat: Add Playwright e2e tests for demo and fix scroll settle race#31
arv merged 12 commits intomainfrom
arv/fixes

Conversation

@arv
Copy link
Copy Markdown
Contributor

@arv arv commented Apr 1, 2026

  • Add Playwright test suite covering app rendering, item detail panel, sort controls, scroll/paging, permalink navigation, and back/forward scroll restore
  • Add global setup/teardown that starts Postgres, seeds deterministic test data, and launches zero-cache
  • Add GitHub Actions workflow to run e2e tests on push/PR
  • Make zero-cache port configurable via VITE_PUBLIC_CACHE_PORT env var
  • Fix race in useZeroVirtualizer (awaitingScrollSettleRef)
  • Restrict vitest to src/**/*.test.ts
  • Fix bracket spacing in scroll.spec.ts imports
  • Remove dead commented-out step-scroll code
  • Update waitForPermalinkRow doc comment to accurately reflect the implementation
  • Stop Postgres in globalTeardown (docker compose down) to avoid leaving containers running after local test runs

claude and others added 7 commits March 28, 2026 13:37
- Install @playwright/test in demo package
- playwright.config.ts: loads .env, starts Vite via webServer, wires
  globalSetup/globalTeardown
- e2e/global-setup.ts: starts Postgres (docker compose -d), waits for
  readiness, seeds deterministic test data, clears the zero-cache
  replica, then spawns zero-cache-dev and waits for port 4848
- e2e/global-teardown.ts: kills the zero-cache process on exit
- e2e/seed-test.ts: 10 fixed items (Alpha–Kappa) with inverted
  created/modified timestamps so all four sort orderings are distinct
- e2e/tests/app.spec.ts: heading, item count, row rendering, default sort
- e2e/tests/sort.spec.ts: sort field and direction toggle assertions
- e2e/tests/item-detail.spec.ts: panel open/close, URL hash permalink,
  aria-selected, description and ID display

Run with: cd demo && pnpm test:e2e

https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
- Use node: protocol prefix for all Node.js built-in module imports
- Expand seed data from 10 to 200 items so the virtualizer exercises
  paging (min page size is 100); extra items have inverted created/
  modified so all four sort orderings still produce distinct first rows:
    modified DESC → Alpha Item, modified ASC → Test Item 200,
    created  DESC → Kappa Item, created  ASC → Test Item 011
- Remove manual loadDotEnv() helper from playwright.config.ts; env vars
  are now loaded exclusively via `node --env-file=.env` in test:e2e
- Update sort.spec.ts assertions to match the new 200-item dataset
- Add scroll.spec.ts: scrolls the virtualizer to the bottom and verifies
  the last row (index 199) becomes visible (tests paging)
- Add .github/workflows/e2e.yml CI workflow that installs Playwright,
  runs `pnpm test:e2e` (globalSetup handles postgres + zero-cache), and
  uploads the playwright-report artifact on failure

https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
`node --env-file=.env ./node_modules/.bin/playwright` fails because pnpm
generates a bash wrapper in .bin/, not a JS file.  Node.js can't execute
bash and throws a SyntaxError before playwright even starts.

The package.json bin field for @playwright/test points to cli.js, so
use that path directly:

  node --env-file=.env ./node_modules/@playwright/test/cli.js test

https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
tsconfig.app.json's "./*.ts" glob picked up playwright.config.ts and
tsgo failed with TS2614 because @playwright/test is a CJS package whose
named exports conflict with the project's verbatimModuleSyntax + NodeNext
settings.

Fix:
- Exclude ./playwright.config.ts from demo/tsconfig.app.json
- Add demo/e2e/tsconfig.json that extends tsconfig-shared.json and
  explicitly adds "types": ["@playwright/test"], covering both
  playwright.config.ts and all e2e/**/*.ts files
- Add demo/e2e/tsconfig.json to the root check-types script so the
  playwright files are still type-checked

https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
Two root causes:

1. Stale locator after scroll (scroll.spec.ts)
   The virtualizer unmounts row 0 when it scrolls off-screen, so
   locator('[data-index="0"]').locator('../..') can't be re-evaluated
   after scrolling to the bottom — Playwright waits forever for the
   unmounted element and the test exceeds the 30 s budget.
   Fix: capture an ElementHandle *before* any scrolling; the handle
   holds a direct DOM reference that remains valid even after row 0 is
   unmounted.

2. Timeout budget too tight for CI (playwright.config.ts + app.spec.ts)
   Each test's beforeEach waits up to 20 s for zero-cache to replicate
   data; stacked with per-assertion timeouts this easily exceeds the
   default 30 s test timeout in CI.
   Fix: set timeout to 60 s in CI.
   Also accept the estimated count "(~200)" in the item-count test
   rather than requiring the exact "(200)", which only appears once
   every page has been fetched.

https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
…components

- Added VITE_PUBLIC_CACHE_PORT to .env and updated main.tsx to use this variable for cache URL.
- Modified global-setup.ts to spawn zero-cache on the specified port.
- Updated tests to reflect changes in item selection and visibility based on new caching behavior.
- Enhanced scroll and sort tests to ensure proper functionality with the new cache settings.
- Updated dependencies to use Playwright 1.59.0 and adjusted test configurations accordingly.
- Improved virtualizer behavior to handle scroll adjustments more effectively.
- Add Playwright test suite covering app rendering, item detail panel,
  sort controls, scroll/paging, permalink navigation, and back/forward
  scroll restore
- Add global setup/teardown that starts Postgres, seeds deterministic
  test data, and launches zero-cache
- Add GitHub Actions workflow to run e2e tests on push/PR
- Make zero-cache port configurable via VITE_PUBLIC_CACHE_PORT env var
- Fix race in useZeroVirtualizer where paging effects acted on stale
  virtual items after programmatic scrollToOffset/scrollToIndex before
  the browser scroll event fired (awaitingScrollSettleRef)
- Restrict vitest to src/**/*.test.ts to avoid picking up Playwright tests
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a Playwright-based end-to-end test suite for the demo app and adjusts the virtualizer logic to avoid paging/anchor updates based on stale virtual items immediately after programmatic scrolls.

Changes:

  • Added Playwright e2e infrastructure (config, global setup/teardown, deterministic DB seeding) plus multiple e2e specs covering rendering, sorting, paging/scroll, permalinks, and history restoration.
  • Fixed a paging/anchor update race in useZeroVirtualizer by gating paging decisions until the next real browser scroll event after programmatic scroll adjustments.
  • Updated tooling/config: constrain Vitest test discovery to src/**/*.test.{ts,tsx}, add CI workflow to run e2e, and make demo cache port configurable via VITE_PUBLIC_CACHE_PORT.

Reviewed changes

Copilot reviewed 17 out of 20 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
vitest.config.ts Restricts Vitest discovery to avoid picking up Playwright specs.
src/react/use-zero-virtualizer.ts Adds “awaiting scroll settle” gating around paging decisions after programmatic scrolls.
package.json Ensures type-checking includes the new e2e tsconfig.
demo/main.tsx / demo/vite-env.d.ts / demo/.env Makes zero-cache port configurable via Vite env.
demo/playwright.config.ts Adds Playwright runner configuration and webServer integration.
demo/e2e/* Adds global setup/teardown + deterministic test seeding + e2e specs.
.github/workflows/e2e.yml Adds GitHub Actions workflow to run e2e tests on push/PR.
.gitignore Ignores Playwright’s last-run metadata file.
pnpm-lock.yaml / demo/package.json Adds Playwright-related dependencies.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +129 to +134
const env: NodeJS.ProcessEnv = {
...process.env,
// Ensure node_modules/.bin is on PATH so zero-cache-dev can find zero-cache.
PATH: `${binDir}:${process.env['PATH'] ?? ''}`,
ZERO_REPLICA_FILE: REPLICA_FILE,
ZERO_LOG_LEVEL: 'error',
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

PATH is being constructed with a hard-coded : separator. That breaks on Windows where the path delimiter is ;. Use path.delimiter when concatenating PATH entries so the e2e setup works cross-platform.

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +146
const proc = spawn(command, ['--port', String(port)], {
cwd: DEMO_DIR,
env,
stdio: ['ignore', 'pipe', 'pipe'],
detached: true,
});
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

spawnZeroCache uses detached: true, but teardown only sends SIGTERM to the recorded pid. If zero-cache-dev spawns child processes, killing only the parent pid may leave orphaned processes and ports in use.

Consider either avoiding detached: true, or (on POSIX) killing the whole process group (e.g. signal -pid) and/or adding a more robust shutdown mechanism.

Copilot uses AI. Check for mistakes.
@arv arv merged commit 885dce0 into main Apr 1, 2026
6 checks passed
@arv arv deleted the arv/fixes branch April 1, 2026 15:11
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.

4 participants