Files
clawmates/frontend/tests/e2e/p6-oauth.spec.ts
T
Omar SobhandClaude Fable 5 ace66d7ffb P6 complete: PWA, route motion, dex browser-flow OAuth, release pipeline
- PWA (§16): hand-rolled 60-line service worker (network-first pages with
  offline fallback, cache-first hashed statics, /api NEVER touched — SSE
  and approvals stay live), app manifest with §2 identity, stdlib-
  generated coral claw icons, prod-only registration. E2E asserts
  manifest, real PNG icons, an ACTIVATED service worker, and the /api
  bypass. (Serwist was tried and dropped: its webpack plugin fights
  Next 16's Turbopack builds; sixty lines we own beat a plugin we fight.)
- Route motion (§3): (workspace) template re-mounts per navigation with a
  quiet fade-rise, zeroed under prefers-reduced-motion. The a11y sweep
  now settles running animations before scanning — axe was reading
  mid-fade opacity as contrast failures
- OAuth browser flow vs REAL dex: the e2e harness boots dexidp/dex with
  static client + password; the journey drives the actual dex login form
  from /api/apps/oauth/start through the callback 303 and asserts the
  app reads connected (closing the P4 deferral honestly)
- release.yml: tag-triggered — builds all four images + postgres, saves
  tarballs, assembles the SIGNED air-gapped bundle (compose, config,
  migrations, seccomp profile, installer, bundler binary), derives the
  public key via the new Could not find command "pubkey". subcommand (tested), verifies
  the bundle customer-style with the public half only, attaches tarball
  + public key to the GitHub release

153 Rust + 63 frontend tests + 29 Playwright journeys.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 09:57:21 -05:00

52 lines
2.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
// P6: the OAuth connect round trip driven through a REAL browser against
// a REAL dex IdP — authorize redirect, dex's own login form, code
// exchange at dex's token endpoint, broker-held token, connected app.
const OWNER_EMAIL = "[email protected]";
const OWNER_PASSWORD = "e2e-password";
const BACKEND = "http://127.0.0.1:8080";
test("connecting an app via OAuth walks the real dex login", async ({
page,
request,
}) => {
// API session for start + verification.
const login = await request.post(`${BACKEND}/api/auth/login`, {
data: { email: OWNER_EMAIL, password: OWNER_PASSWORD },
});
const { token } = (await login.json()) as { token: string };
const auth = { Authorization: `Bearer ${token}` };
const claws = (await (
await request.get(`${BACKEND}/api/team/claws`, { headers: auth })
).json()) as { id: string; name: string }[];
const scout = claws.find((claw) => claw.name === "Scout")!;
const start = await request.post(`${BACKEND}/api/apps/oauth/start`, {
headers: auth,
data: { clawId: scout.id, provider: "linear" },
});
expect(start.status()).toBe(200);
const { authorize_url } = (await start.json()) as { authorize_url: string };
expect(authorize_url).toContain("/dex/auth");
// The REAL browser flow: dex serves its login form; sign in as the
// static user; dex redirects back through our callback.
await page.goto(authorize_url);
await page.getByPlaceholder("email address").fill("[email protected]");
await page.getByPlaceholder("password").fill("password");
await page.getByRole("button", { name: /Log ?in/i }).click();
// The callback 303s into the claw's Add Apps panel.
await page.waitForURL(/app=apps/);
// The connection exists and the app reads connected.
const directory = (await (
await request.get(`${BACKEND}/api/apps?clawId=${scout.id}`, {
headers: auth,
})
).json()) as { id: string; connected: boolean }[];
expect(directory.find((app) => app.id === "linear")?.connected).toBe(true);
});