test: browser tests for the demo's MCP + OAuth flows - #4
Merged
Conversation
The bug that shipped in the MCP panel was not reachable by any unit test: an unrelated useEffect rewrote location while the hook was awaiting a dynamic import, so the authorization code was gone by the time it looked. Effect ordering across a real render, a real navigation away and back, and IndexedDB persistence need a browser. Playwright drives the production build of the demo — including the /agent-web-react/ subpath it is actually deployed under — against a real MCP server and a real authorization server on loopback, both CORS-enabled with WWW-Authenticate exposed. That makes the suite a check of the documented deployment requirement rather than of a convenient fiction. Covered: connecting to an unauthenticated server; the whole OAuth round-trip (challenge → dynamic registration → authorize → back with a code → connected), with the spent code gone from the address bar and the visitor back on the panel they were using; a reload reusing the stored tokens without a second registration; and an unreachable server surfacing an error rather than hanging. I checked these are load-bearing rather than decorative: reinstating the demo's old query-stripping router makes the OAuth test fail. The narrower "reads the callback before the first await" ordering is pinned by construction and by the readCallbackParams unit tests — reproducing that interleaving from outside the page turned out to need timing assumptions too fragile to be worth a flaky test. Chromium comes from `playwright install` in CI; a preinstalled browser is used when the sandbox provides one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FKop4At26QqqkwVGEjJur
The Browser job timed out waiting 180s for a web server that had started fine. Vite's default host is `localhost`, resolved through DNS at listen time; Playwright polls the literal 127.0.0.1. When those disagree the run waits out the whole timeout against a healthy server. Passing the same literal address to both takes the resolver out of it. Also pipe the web server's stdout: Playwright ignores it by default, which is why the failure showed a build log followed by silence — the preview banner and any startup error were being dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FKop4At26QqqkwVGEjJur
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The bug that shipped in the MCP panel (#3) was not reachable by any unit test: an unrelated
useEffectrewrotelocationwhile the hook was awaiting a dynamic import, so the authorization code was gone by the time it looked. Effect ordering across a real render, a real navigation away and back, and IndexedDB persistence need a browser.What
Playwright drives the production build of the demo — including the
/agent-web-react/subpath it is actually deployed under, so the tested layout is the shipped one — against a real MCP server (McpServer+StreamableHTTPServerTransport) and a real authorization server on loopback. Both are CORS-enabled withWWW-Authenticateexposed, which makes the suite a check of the documented deployment requirement rather than of a convenient fiction.Are they load-bearing?
I checked rather than assumed: reinstating the demo's old query-stripping router makes the OAuth test fail, so it holds the line on the class of bug that shipped.
The narrower "reads the callback before the first await" ordering is not pinned here — it is guaranteed by construction (the read happens synchronously at the top of the effect) plus the
readCallbackParamsunit tests. Reproducing that exact interleaving from outside the page needs timing assumptions too fragile to be worth a flaky test; I tried an init-script rival and dropped it.CI
A third job (
Browser (demo)) runs alongside the existing library and demo jobs, installs Chromium viaplaywright install --with-deps chromium, and uploads traces on failure. Locally the config uses a preinstalled browser when the sandbox provides one.That job failed on its first run and the second commit fixes it — worth recording, because the failure mode is silent. Playwright timed out after 180s waiting for a web server that had started perfectly well: Vite's default host is
localhost, resolved through DNS at listen time, while Playwright polls the literal127.0.0.1. Passing the same literal address to both removes the resolver from the equation. The log showed a build and then nothing because Playwright ignores web-server stdout by default (only stderr is piped, which is why the build's chunk-size warning appeared alone) — sostdout: 'pipe'is now set, and the next failure will show the server's own banner or its error.typecheck,format:check,build,test(24 passing) andtest:e2e(4 passing, ~20s including the demo build) are green, and all three CI jobs are green on the head commit.Generated by Claude Code