Preview browser smoke (Playwriter)
Use this when you need to prove that a deployed OS preview works through the real browser, Iterate Auth Worker, TanStack Start routing, and the app UI.
Existing Smoke#
pnpm e2e -t "OS preview smoke" runs apps/os/e2e/vitest/preview-smoke.e2e.test.ts.
It verifies the preview worker, unauthenticated redirect behavior, admin-token project setup, and
MCP and itx metadata wiring.
The itx e2e suites (apps/os/e2e/vitest/) run through the same pnpm e2e
config. Slack coverage lives in apps/os/e2e/vitest/slack-agent.e2e.test.ts (synthetic
signed-webhook smoke through the integrations domain).
Authenticated Browser Smoke#
Preview OS configs use the matching slot's Iterate Auth Worker as their issuer. The preview provisioner creates the clients and Doppler configs; run it when setting up slots or rotating their auth credentials:
doppler run --project _shared --config prd -- \
pnpm preview provision-auth-preview-configsCreate or reuse an auth-worker user that has access to the target organization and project. Then open the preview with Playwriter headless:
SID=$(playwriter session new --browser headless 2>&1 | sed -n 's/^Session \([0-9][0-9]*\) created.*/\1/p')
playwriter -s "$SID" --timeout 60000 -e "$(cat <<'EOF'
state.page = context.pages().find((p) => p.url() === 'about:blank') ?? (await context.newPage());
await state.page.goto('https://os.iterate-preview-2.com/projects/<projectSlug>/streams', { waitUntil: 'domcontentloaded' });
console.log(await snapshot({ page: state.page }));
EOF
)"The snapshot should show the project-bound Streams page, including the
breadcrumb, filter/create combo box, Reset, Create stream, and the sortable
Stream path, Created, and Woke table headers.
To prove the UI can mutate deployed state, create a stream from the combo box
using locators from a fresh snapshot() (fill + click create), then confirm the
URL ends with something like:
https://os.iterate-preview-2.com/projects/<projectSlug>/streams/playwriter-ui-smokeUnattended sign-in (no human)#
Use headless Playwriter (never the user's real Chrome for unattended automation). Example:
playwriter browser install # one-time Chrome for Testing
SID=$(playwriter session new --browser headless 2>&1 | sed -n 's/^Session \([0-9][0-9]*\) created.*/\1/p')For a fully unattended admin session, authenticate the slot's bootstrap admin against better-auth's API, inject the resulting session cookies into the Playwright context, then let the OAuth flow complete:
-
Sign in via the better-auth API (email/password IS enabled at the API level even though the UI hides it). Keep the secret in a shell var — never echo it:
export AUTH_ORIGIN=https://auth.iterate-preview-N.com export SECRET=$(doppler secrets get APP_CONFIG_SERVICE_AUTH_TOKEN --project auth --config preview_N --plain) curl -s -c /tmp/auth.txt -X POST "$AUTH_ORIGIN/api/auth/sign-in/email" \ -H 'content-type: application/json' \ --data "$(python3 -c 'import json,os;print(json.dumps({"email":"admin@nustom.com","password":os.environ["SECRET"]}))')"- email:
admin@nustom.com(BOOTSTRAP_ADMIN_EMAIL) - password: the auth worker's
APP_CONFIG_SERVICE_AUTH_TOKEN(Dopplerauth/prd)
- email:
-
Inject the
__Secure-better-auth.session_{token,data}cookies into the Playwriter page context (Playwrightcontext.addCookiesfrom the jar). -
Run the OS OAuth flow: navigate to
https://os.iterate-preview-N.com/api/iterate-auth/login. With the session present it lands on the matching preview auth consent page. -
Approve consent via snapshot + click (React-wired buttons need a real click / DOM activation, not a silent evaluate unless needed).
-
Land on
/projects, authenticated.
Each preview authenticates against its own auth.iterate-preview-N.com worker
and D1 database. For a fresh non-admin user, provision it via that auth
worker's service-token-gated internal oRPC API
(internal.user.upsertVerifiedEmail + internal.organization.createForUser).
Driving a real agent conversation#
Use snapshot → fill message → click send on the agent stream URL, then verify
server state with pnpm cli itx run if the live UI does not update.
Gotchas#
- The OS session JWT is short-lived. If project-scoped pages start throwing
Project <id> not foundin the error boundary, re-run the sign-in flow. - Live stream display uses WebSocket. If messages do not render live, verify
via
pnpm cli itx runand Workers logs.