Skip to content

test: add Playwright e2e for playgrounds (dev + built)#990

Merged
antfu merged 9 commits intomainfrom
antfu/e2e-playgrounds
May 1, 2026
Merged

test: add Playwright e2e for playgrounds (dev + built)#990
antfu merged 9 commits intomainfrom
antfu/e2e-playgrounds

Conversation

@antfu
Copy link
Copy Markdown
Member

@antfu antfu commented May 1, 2026

Summary

  • Adds a Playwright e2e harness at tests/e2e/ covering 3 playgrounds (empty, tab-pinia, tab-seo) in both dev and built modes — verifies the Nuxt DevTools iframe loads, the side nav renders, and 8 main tabs navigate with expected content.
  • Drives the iframe via the Vite DevTools dock API directly (panel.store + awaited switchEntry) and bypasses the broken host.devtools.navigate() (which writes to a non-existent panel.store.value) by calling the host:action:navigate hook directly.
  • Wires pnpm test:e2e (sequential dev → built) into ci.yml with browser cache + report artifact on failure; pre-builds playgrounds in globalSetup to keep webServer boot fast; disables Vite DevTools client auth (VITE_DEVTOOLS_DISABLE_CLIENT_AUTH=true) so headless runs are trusted; uses localhost baseURL with HOST=0.0.0.0 to work around macOS IPv6/IPv4 split between the main app server and the Vite DevTools websocket.
  • tab-seo:built is skipped with a comment — its production bundle currently throws useNuxtDevTools is not defined (auto-import from local.ts doesn't bake into the prod build).

Test plan

  • pnpm test:e2e:dev → 34 passed, 8 skipped
  • pnpm test:e2e:built → 2 passed, 40 skipped
  • pnpm test:e2e (full suite) → 36 passed, 48 skipped, 0 failed (~83s)
  • pnpm exec eslint tests/e2e clean
  • CI run on this PR

🤖 Generated with Claude Code

Drives the Vite DevTools dock entry directly to verify the Nuxt DevTools
iframe loads, the side nav renders, and main tabs navigate with expected
content. Covers `empty`, `tab-pinia`, and `tab-seo` playgrounds in both
dev and built modes; pre-builds in globalSetup, disables Vite DevTools
client auth for headless runs, and skips the known `tab-seo:built` issue
(`useNuxtDevTools` not defined in prod bundle).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 1, 2026

Deploying nuxt-devtools with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1d56ea9
Status: ✅  Deploy successful!
Preview URL: https://1207fca9.nuxt-devtools.pages.dev
Branch Preview URL: https://antfu-e2e-playgrounds.nuxt-devtools.pages.dev

View logs

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Playwright-based E2E test infrastructure: package scripts and dependency, pnpm catalog entry, Playwright config with per-playground projects and webServer setup, global setup to pre-build playgrounds, DevTools fixtures, multiple E2E spec files covering DevTools and playground behavior, a tsconfig for tests, a glob utility for project filtering, .gitignore entries for Playwright artifacts, and a GitHub Actions workflow to run the E2E suite and upload the Playwright report.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'test: add Playwright e2e for playgrounds (dev + built)' clearly and specifically describes the main change—adding end-to-end tests with Playwright for multiple playgrounds in different modes.
Description check ✅ Passed The description thoroughly documents the test harness implementation, test coverage, technical approach, integration details, and test results—all directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch antfu/e2e-playgrounds

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/global-setup.ts`:
- Around line 10-21: The Promise that wraps the spawn call in
tests/e2e/global-setup.ts doesn't listen for child-process 'error' events and
also omits the terminating signal when reporting failures; update the spawn
handling around the child variable so you add child.on('error', err =>
reject(new Error(`Failed to spawn pnpm/nuxt for ${name}: ${err.message ||
err}`))) and modify the existing child.on('exit', (code, signal) => ...) branch
in the Promise to reject with a message that includes either the non-zero exit
code or the terminating signal (e.g., `Build failed for ${name}: exit code
${code}` or `terminated by signal ${signal}`) while keeping resolve() when code
=== 0.

In `@tests/e2e/playwright.config.ts`:
- Around line 32-38: The PW_PROJECT filter is being turned into a RegExp without
escaping regex metacharacters, causing crashes or incorrect matches; update the
logic around filter/specs to first escape special regex characters (e.g.,
implement an escapeRegExp helper that replaces characters like .+?^${}()|[]\\
with escaped versions), then convert glob-style '*' to '.*' and build the RegExp
from that escaped pattern; extract this into a shared helper function (e.g.,
buildProjectFilterRegex or escapeAndGlobToRegex) and use it both where filter is
computed (the current const filter/specs block) and in the duplicated logic in
global-setup.ts so both places reuse the safe implementation.

In `@tests/e2e/specs/playground-loads.spec.ts`:
- Around line 27-29: The test currently builds `fatal` from `consoleErrors`
using a broad regex (/HMR|404|favicon|websocket|ws/i) that can hide real
regressions; update the filter used to compute `fatal` so it only ignores
specific benign messages (e.g., exact HMR warnings and known favicon 404 lines
and explicit websocket connection warnings) rather than any occurrence of "404"
or "ws". Locate the `consoleErrors.filter(...)` expression (variable `fatal`)
and replace the broad alternations with tightened patterns that match the full
benign message text or anchored phrases (keep `HMR` and `favicon` matches narrow
and change `404` and `ws|websocket` to their exact harmless messages) so genuine
errors still fail the smoke test. Ensure the new regexes remain case-insensitive
and include explanatory comments next to the `fatal` filter.

In `@tests/e2e/specs/playground-tab-pinia.spec.ts`:
- Around line 13-15: The negative loader check is using the wrong string
('Connecting....' with four dots) so it can false-positive; update the assertion
in the expect call that targets devtoolsFrame().locator('body') to use the exact
loader text 'Connecting...' (three dots) in the .not.toContainText(...) call and
keep the same timeout parameter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6dc7d46-b70e-4315-90f4-82971d923d1b

📥 Commits

Reviewing files that changed from the base of the PR and between 88d7f72 and 28a2250.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • .github/workflows/ci.yml
  • .gitignore
  • package.json
  • pnpm-workspace.yaml
  • tests/e2e/fixtures/devtools.ts
  • tests/e2e/global-setup.ts
  • tests/e2e/playwright.config.ts
  • tests/e2e/specs/iframe.spec.ts
  • tests/e2e/specs/playground-empty.spec.ts
  • tests/e2e/specs/playground-loads.spec.ts
  • tests/e2e/specs/playground-tab-pinia.spec.ts
  • tests/e2e/specs/playground-tab-seo.spec.ts
  • tests/e2e/specs/tabs.spec.ts
  • tests/e2e/tsconfig.json

Comment thread tests/e2e/global-setup.ts Outdated
Comment thread tests/e2e/playwright.config.ts Outdated
Comment thread tests/e2e/specs/playground-loads.spec.ts Outdated
Comment on lines +13 to +15
await expect(devtoolsFrame().locator('body'))
.not
.toContainText('Connecting....', { timeout: 30_000 })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Negative loader check has a false-positive typo.

Line 15 checks 'Connecting....' (four dots), while the loader text is 'Connecting...' (three dots). That mismatch can let the test pass even when the app is still stuck connecting.

Suggested fix
-  await expect(devtoolsFrame().locator('body'))
-    .not
-    .toContainText('Connecting....', { timeout: 30_000 })
+  await expect(devtoolsFrame().locator('body'))
+    .not
+    .toContainText('Connecting...', { timeout: 30_000 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/specs/playground-tab-pinia.spec.ts` around lines 13 - 15, The
negative loader check is using the wrong string ('Connecting....' with four
dots) so it can false-positive; update the assertion in the expect call that
targets devtoolsFrame().locator('body') to use the exact loader text
'Connecting...' (three dots) in the .not.toContainText(...) call and keep the
same timeout parameter.

- Run e2e in autofix.yml (PR CI) — ci.yml only runs on push to main
- Escape regex metacharacters in PW_PROJECT glob, share between
  playwright.config.ts and global-setup.ts via a small helper
- Handle child-process spawn failures and propagate signal in build error
- Narrow benign-error filter in playground-loads.spec.ts (no longer hides
  unrelated 404s or websocket errors)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run e2e tests as their own job so the result is a separate signal from
lint/build/typecheck (and so it parallelizes with the existing ci/autofix
workflows). Triggers on push to main and on PRs (excluding docs-only
changes), with browser cache + report artifact on failure. Removes the
duplicated e2e step from ci.yml and autofix.yml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous run looped on dev tests for `../../local` playgrounds (tab-pinia,
tab-seo) — every test hit the 90s test timeout and (with retries: 2) tried
3x, dragging the job past 30 minutes before manual cancellation. Adding:

- `timeout-minutes: 15` on the e2e job so a stuck run is bounded
- `list` reporter prepended to CI reporters so per-test results show in
  workflow logs (the previous github+html-only reporters were silent on
  intermediate progress, leaving only WebServer dumps to debug from)
- `retries: 1` everywhere (was 2 in CI) — one retry covers the cold-boot
  flake without amplifying genuine timeouts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Local Claude Code state directory; not project content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tab-pinia and tab-seo playgrounds load DevTools via `../../local`,
which spawns a separate Nuxt dev subprocess for the devtools client. On
CI cold-start runners that subprocess can't compile the inner client app
within the test's 90s budget — both the original attempt and the retry
hit the timeout (verified in run 25207464186), and the job runs the full
15-min cap before terminating.

Run the dev-mode devtools coverage on the `empty` playground only (which
uses the prebuilt `@nuxt/devtools` and doesn't need the subprocess), and
keep the built-mode smoke check across all three playgrounds. Local
`pnpm test:e2e` still exercises the full matrix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
globalSetup runs concurrently with webServer startup, so when the built
project's webServer ran \`nuxt preview\` it failed before the build
finished — Playwright then aborted the run before any test fired (last
CI run, build step never logged \`Pre-building empty...\`).

Move the prebuild to a \`test:e2e:prebuild\` script invoked before
\`PW_PROJECT='*:built' playwright test\` (locally and in CI). The dev
projects don't need a prebuild, so they go straight to playwright. This
also drops globalSetup entirely — \`tests/e2e/shared/glob.ts\` is still
used by playwright.config.ts to filter projects + webServer entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
\`nuxt build\` for tab-pinia/tab-seo hangs on CI: after the first such
playground builds, the next one's build never starts (last run, the third
build never logged \`Building Nuxt for production\` and the job ran the
full 15-min cap). The hang is in \`../../local\` — it spawns a subprocess
for the devtools client, and a stale subprocess from the previous build
appears to block the next build's app:resolve hook from completing.

That's the same module path which already prevented those playgrounds
from running dev tests on CI. Drop them from built-mode CI too. Coverage
for the full matrix stays in local \`pnpm test:e2e\`; CI verifies that
\`empty\` (prebuilt \`@nuxt/devtools\`, no subprocess) works in both
modes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All bundled playgrounds switch their devtools module via a
\`NUXT_DEVTOOLS_LOCAL\` env var:

- unset (default): load \`@nuxt/devtools\` (the published/built package,
  resolved to the workspace dist via pnpm). No subprocess. Tests can
  exercise the same code path consumers ship.
- set: load \`../../local\`, the dev-mode entry that proxies to a Nuxt
  dev subprocess for HMR on the devtools client. Devs working on the
  client itself opt in this way.

Previously most playgrounds hard-coded \`../../local\`; that subprocess
hangs nuxt build on cold CI runners and times out dev iframe tests.
Defaulting to the built module unblocks CI for all three e2e
playgrounds (empty, tab-pinia, tab-seo). Local \`pnpm test:e2e\` and
\`pnpm dev\` still work with HMR by setting \`NUXT_DEVTOOLS_LOCAL=1\`.

CI workflow simplified to a single \`pnpm test:e2e\` step now that all
playgrounds work with the default module path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@antfu antfu merged commit bb3fef8 into main May 1, 2026
8 checks passed
@antfu antfu deleted the antfu/e2e-playgrounds branch May 1, 2026 09:44
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.

1 participant