Skip to content

Run the e2e suite against the containers, as a second mode - #13

Merged
evsinev merged 3 commits into
mainfrom
feat/e2e-docker
Sep 11, 2026
Merged

evsinev merged 3 commits into
mainfrom
feat/e2e-docker

Conversation

@evsinev

@evsinev evsinev commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

e2e-tests/ drives a real browser through the payment against every example. Until now it started
each app natively, which is why it needs nine toolchains installed and why dotnet-aspnetcore-js
is onRequestOnly — a missing SDK is a hard failure, not a skip.

docker-compose.yml already builds and runs all nine behind one nginx. This adds a second way to
run the same specs against those containers, so checking an example locally needs Docker and
nothing else. The native mode is untouched.

cd e2e-tests
npm run test:docker         # all nine, .NET included
npm run test:docker:java    # or one of them, by the same short names

The constraint that shapes it

API_URL and SDK_URL must name one origin — every app builds its CSP from SDK_URL and the
card fields are iframes from that host. And the emulator does not read the request's Host: it
rebuilds the signed URL from its own EMULATOR_ORIGIN, so a signature verifies only if the app
signed the very same string.

One address therefore has to be true in three places at once: what the app signs, what the
emulator computes, and what the browser can reach. The shared network namespace the compose file
already uses gives that for free provided the published port equals the internal one.

Host Inside the stack
nginx, the nine apps 127.0.0.1:4020 127.0.0.1:4020
emulator 127.0.0.1:4010 127.0.0.1:4010

The emulator stays on its own port rather than behind the same nginx: the cross-origin boundary is
what checkout.spec.ts's field-state test and every frame-src rule exist to exercise.

The three commits

feat(docker): make the nginx port one variable — docker/nginx/nginx.conf becomes
default.conf.template with listen ${NGINX_PORT};, and one HTTP_PORT now drives the published
port, the internal listen port and PUBLIC_URL, so the three can no longer disagree. The nine
mounted snippets are not templates and keep their $host and $proxy_add_x_forwarded_for intact.

test(e2e): run the suite against the containers as a second mode — a new
docker-compose.e2e.yml read on top of the base file, so the nine services and the nine mounted
deploy/nginx.conf are reused rather than restated. It adds an emulator service (plain
node:22, no Dockerfile, because it imports nothing but Node builtins), publishes 4020/4010, gives
every app the e2e settings through environment: and re-points the key mount at
e2e-tests/.tmp/private_key.pem. It carries its own project name, so a demo stack already up is
left alone; the base file's env_file is dropped, so a root .env with real credentials cannot
reach a test run.

On the suite side the whole one-app-per-port assumption lived in appOrigin(). E2E_TARGET=docker
switches it to the nginx origin, and no spec or fixture needed an edit. The stack comes up as
Playwright's webServer and goes down in a globalTeardown whatever happened.

docs: document the two ways to run the e2e suite — e2e-tests/README.md, the root
README.md and CLAUDE.md.

Verified locally

npm run lint                 biome + tsc, both configs
npm test                     80 passed — the native mode unchanged, eight projects
npm run test:docker:java     10 passed — three containers started, not eleven
npm run test:docker          90 passed — nine projects, .NET no longer opt-in

threeds.spec.ts passing is the same-origin argument holding up end to end: the ACS page is
fetched from 127.0.0.1:4010, posts the signed return to PUBLIC_URL on 127.0.0.1:4020, and
waitForURL matches — a localhost/127.0.0.1 slip anywhere would fail exactly there.
result-signature.spec.ts passing means the app signed the same origin string the emulator
computed.

Afterwards the stack was gone, 4010/4020 were free, and docker compose up -d --force-recreate on
the demo still served all nine through the templated nginx.

No CI change — the suite is local-only, as before.

@evsinev
evsinev merged commit 05b91ec into main Sep 11, 2026
20 checks passed
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