Skip to content

Add a Playground e2e check for the PKCE flow - #19

Draft
roborourke wants to merge 4 commits into
roborourke/pkce-supportfrom
roborourke/playground-e2e
Draft

roborourke wants to merge 4 commits into
roborourke/pkce-supportfrom
roborourke/playground-e2e

Conversation

@roborourke

@roborourke roborourke commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

This adds end-to-end tests that run the PKCE flow over real HTTP against WordPress Playground.

It is based on the PKCE branch from WP-API#85, so the diff only shows the e2e work. Once WP-API#85 merges, this needs a new PR against WP-API/OAuth2 main.

The tests use Playwright Test. tests/e2e/pkce.spec.ts starts Playground with runCLI(), mounts the plugin and creates two clients: one that requires PKCE and one that does not. The tests then log in with a password, submit the consent form, exchange the code for a token, and call /wp/v2/users/me with the token. They use Playwright's request API and never follow redirects, so they check every redirect target.

There are 22 tests. They cover the S256 flow from start to finish, wrong, missing and URL-only verifiers, the challenge sent as the verifier, code reuse, and a verifier sent for a code without PKCE. They also cover bad authorize parameters, an unregistered redirect URI, the implicit grant refusal and the cancel button. Each test links to the RFC section it checks.

The tests do not open a browser, so no browser install is needed. Run them with npm ci and then npm run test:e2e. A new E2E (Playground) job runs them in CI.

🤖 Generated with Claude Code

The PHPUnit tests call the grant types and token endpoint directly, so they never cover a real browser-style round trip: cookie login, the consent form and its nonce, the redirect back to the client, the token exchange over HTTP, and using the token on the REST API. This adds a harness that does exactly that against WordPress Playground.

tests/e2e/run.sh starts Playground with the plugin mounted and a blueprint that activates it and creates two clients, one with PKCE required and one without. setup.php writes their IDs to a JSON file in the web root so the checks can read them over HTTP. pkce.py then runs 23 checks: the S256 happy path end to end, wrong, missing and URL-only verifiers, code reuse and cross-client redemption, the downgrade case of a verifier sent for a non-PKCE code, malformed authorize parameters, the implicit grant refusal, and cancel on the consent screen.

pkce.py uses only the Python standard library, so the CI job needs nothing beyond the Node and Python that ubuntu-latest already ships. Playwright was the other option, but none of these checks need a browser engine, and plain HTTP lets every redirect be inspected without following it.

run.sh waits for Playground's "Ready!" log line as well as the clients file. Playground answers some requests while it is still booting, so polling the file alone let the checks start against a server that then stopped responding.

The code-reuse checks accept 400 or 404, since an unknown code is currently a 404 and WP-API#89 changes it to the 400 that RFC 6749 expects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roborourke and others added 3 commits September 23, 2026 16:43
The first version mixed a bash script, a Python script, a PHP setup file and a JSON blueprint, with its own pass/fail bookkeeping. Playwright Test gives a real test runner instead: named tests, per-test failure reports, and the usual CLI filters. It is also the approach the Playground handbook documents for plugin e2e tests.

The spec starts Playground in beforeAll through runCLI() from @wp-playground/cli, with the plugin mounted and activated by an inline blueprint. The OAuth clients are created with playground.run(), which returns the PHP output directly. That removes setup.php and the client-ID JSON file that had to be served from the web root.

All requests go through Playwright's APIRequestContext with maxRedirects: 0, so every Location header is still checked. One context holds the logged-in user's cookies and a second, empty one acts as the OAuth client at the token endpoint. The specs never drive a page, so CI skips "playwright install" and needs only npm ci.

The tests do not depend on each other, so they are not in serial mode. With one worker, a failure starts a fresh worker and Playground, and the remaining tests still run and report. Serial mode skipped everything after the first failure.

The wait-for-"Ready!" workaround in run.sh is gone: runCLI() resolves only once the server and blueprint are done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each test now has a docblock naming the RFC rule it checks, with a @see link to that section, matching the PHPUnit tests. The test that sends the verifier in a JSON body and the optional-client test without PKCE have none, since they check plugin behaviour rather than an RFC rule.

Two tests are added to match gaps found in the PHPUnit suite:

- RFC 7636 section 4.6: sending the S256 challenge itself as the verifier is refused with invalid_grant.
- RFC 6749 section 4.1.2.1: a PKCE error on a request with an unregistered redirect_uri shows an error page and sends no Location header. Moving the PKCE check ahead of the redirect URI check makes this test fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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