fix(canvas/e2e): swap workspace-scoped 401s for empty 200s
The staging-tabs E2E has been failing for 6+ hours on the same locator timeout — diagnosed earlier today as the canvas's lib/api.ts:62-74 redirect-on-401 path firing mid-test: e2e/staging-tabs.spec.ts:45:7 › tab: skills TimeoutError: locator.scrollIntoViewIfNeeded: Timeout 5000ms - navigated to "https://scenic-pumpkin-83.authkit.app/?..." Several side-panel tabs (Peers, Skills, Channels, Memory, Audit, and anything workspace-scoped) hit endpoints under `/workspaces/<id>/*` that require a workspace-scoped token, NOT the tenant admin bearer the test uses. The endpoints respond 401 in SaaS mode. canvas/src/lib/api.ts:62-74 reacts to ANY 401 by setting `window.location.href` to AuthKit — yanking the page off the tenant origin mid-test. The test comment at line 18 already acknowledged the 401 class ("Peers tab: 401 without workspace-scoped token") but assumed those would surface as "errored content" rather than a hard navigation. The redirect logic in api.ts was added later and breaks the assumption. Fix: add a Playwright route handler that catches any 401 from `/workspaces/<id>/*` paths and replaces with `200 + empty body`. Body shape is best-effort by URL — list endpoints (paths not ending in a UUID-shaped segment) get `[]`, single-resource endpoints get `{}`. Both are valid JSON and well-written panels render an empty state for either rather than crashing. The two route patterns (`/workspaces/...` and `/cp/auth/me`) don't overlap — the existing `/cp/auth/me` mock continues to gate AuthGate's session check independently. Verification: - Type-check passes (tsc clean for the spec; pre-existing errors in unrelated test files unchanged) - Can't run staging E2E locally without CP admin token; CI will exercise the real path against the freshly-provisioned tenant - E2E Staging SaaS (full lifecycle) is currently green at 08:07Z, confirming the underlying staging infra works — the failures have been narrowly in this Playwright-tabs spec Targets staging per molecule-core convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fc54601999
commit
979d4a0b7a
@ -87,6 +87,53 @@ test.describe("staging canvas tabs", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
// Workspace-scoped 401 → 200 fallback.
|
||||
//
|
||||
// Several side-panel tabs (Peers/Skills/Channels/Memory/Audit and
|
||||
// anything else workspace-scoped) hit endpoints under
|
||||
// `/workspaces/<id>/*` that require a workspace-scoped token, NOT
|
||||
// the tenant admin bearer this test uses. Those endpoints respond
|
||||
// 401 in SaaS mode. canvas/src/lib/api.ts:62-74 reacts to ANY 401
|
||||
// by setting `window.location.href` to the AuthKit login URL —
|
||||
// which yanks the page off the tenant origin mid-test and breaks
|
||||
// every locator assertion that runs after.
|
||||
//
|
||||
// For tab-render tests we don't need real data — the gate is
|
||||
// "panel mounts without crashing, no Failed-to-load toast".
|
||||
// Intercept the 401 and swap it for 200 + empty body. Body shape
|
||||
// is best-effort by URL: list endpoints (collection paths that
|
||||
// don't end in a UUID) get `[]`; single-resource endpoints get
|
||||
// `{}`. Both are valid JSON, neither matches the real schema
|
||||
// exactly, but well-written panels render an empty state for
|
||||
// either rather than throwing.
|
||||
//
|
||||
// The two route patterns don't overlap (`/workspaces/...` vs
|
||||
// `/cp/auth/me`) so handler order doesn't matter — the
|
||||
// `/cp/auth/me` mock above is matched on its own path.
|
||||
await context.route(/\/workspaces\//, async (route, request) => {
|
||||
if (request.resourceType() !== "fetch") {
|
||||
return route.fallback();
|
||||
}
|
||||
let resp;
|
||||
try {
|
||||
resp = await route.fetch();
|
||||
} catch {
|
||||
return route.fallback();
|
||||
}
|
||||
if (resp.status() !== 401) {
|
||||
return route.fulfill({ response: resp });
|
||||
}
|
||||
// 401: swap for empty 200 keyed by URL shape.
|
||||
const lastSeg =
|
||||
new URL(request.url()).pathname.split("/").filter(Boolean).pop() || "";
|
||||
const looksLikeList = !/^[0-9a-f-]{8,}$/.test(lastSeg);
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: looksLikeList ? "[]" : "{}",
|
||||
});
|
||||
});
|
||||
|
||||
const consoleErrors: string[] = [];
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user