fix(e2e): canvas-tabs staging setup waits for RENDERABLE, not online (#2199) #2202
@@ -250,7 +250,38 @@ export default async function globalSetup(_config: FullConfig): Promise<void> {
|
||||
const workspaceId = ws.body.id as string;
|
||||
console.log(`[staging-setup] Workspace created: ${workspaceId}`);
|
||||
|
||||
// 6. Wait for workspace online
|
||||
// 6. Wait for workspace RENDERABLE.
|
||||
//
|
||||
// This harness exists to verify the canvas *tab UI* renders (staging-
|
||||
// tabs.spec.ts: open each of the 13 workspace-panel tabs, assert no hard
|
||||
// crash / no "Failed to load" toast). It does NOT exercise the agent —
|
||||
// no LLM call is made, the spec even mocks /cp/auth/me and 401→200. All
|
||||
// it needs is a workspace ROW that the canvas lists so the node renders
|
||||
// and the side-panel tabs open. A fully-`online` agent is NOT required.
|
||||
//
|
||||
// That distinction became load-bearing on 2026-06-03: workspace-server
|
||||
// #2162 (fix(provision): platform-managed workspace must fail-closed when
|
||||
// CP proxy env absent) made a platform_managed workspace ABORT AT BOOT
|
||||
// with MISSING_PLATFORM_PROXY when MOLECULE_LLM_BASE_URL /
|
||||
// MOLECULE_LLM_USAGE_TOKEN are not present in the tenant's env. The
|
||||
// canvas E2E creates a bare hermes/gpt-4o workspace, which defaults
|
||||
// closed to platform_managed (workspace_provision.go:~1009), and the
|
||||
// staging tenant does not carry the CP proxy env — so the agent never
|
||||
// starts. Pre-#2162 this same workspace booted credential-less (the bug
|
||||
// #2162 fixed) and the tabs rendered fine; #2162 is a correct production
|
||||
// safety fix, but it surfaced here as `status:"failed", uptime_seconds:0,
|
||||
// last_sample_error:null` — the pre-start credential-abort shape — and the
|
||||
// old hard-throw turned a UI-irrelevant boot skip into a main-red
|
||||
// (core#2199). The agent boot stage is simply not what this test gates.
|
||||
//
|
||||
// So: online is the happy path. A `failed` row that is the PRE-START
|
||||
// credential-abort shape (the agent process never ran: uptime_seconds==0
|
||||
// AND no last_sample_error) is treated as RENDERABLE — the row exists,
|
||||
// the node + tabs render, proceed. We do NOT mask a real boot regression:
|
||||
// any `failed` carrying a last_sample_error, OR a non-zero uptime (the
|
||||
// agent started then crashed — image pull, panic, PYTHONPATH, etc.),
|
||||
// still hard-throws. Genuine *infra* provision failure is already caught
|
||||
// loud one step earlier at the org level (instance_status === "failed").
|
||||
await waitFor<boolean>(
|
||||
async () => {
|
||||
const r = await jsonFetch(`${tenantURL}/workspaces/${workspaceId}`, {
|
||||
@@ -259,6 +290,24 @@ export default async function globalSetup(_config: FullConfig): Promise<void> {
|
||||
if (r.status !== 200) return null;
|
||||
if (r.body?.status === "online") return true;
|
||||
if (r.body?.status === "failed") {
|
||||
const uptime = Number(r.body?.uptime_seconds ?? 0);
|
||||
const sampleErr = r.body?.last_sample_error;
|
||||
const preStartCredentialAbort = uptime === 0 && !sampleErr;
|
||||
if (preStartCredentialAbort) {
|
||||
// Agent never started (no LLM cred on this staging tenant — the
|
||||
// expected #2162 platform-proxy gap). The workspace row still
|
||||
// renders, which is all the tab-UI test needs. Proceed, but log
|
||||
// loudly so a real "agent never booted because of something else"
|
||||
// is not silently normalized.
|
||||
console.warn(
|
||||
`[staging-setup] workspace ${workspaceId} is 'failed' with the pre-start ` +
|
||||
`credential-abort shape (uptime_seconds=0, no last_sample_error) — agent did ` +
|
||||
`not boot (expected on staging without CP LLM proxy env, post workspace-server ` +
|
||||
`#2162). The tab-UI test does not exercise the agent; proceeding with the ` +
|
||||
`workspace row, which renders regardless. full body: ${JSON.stringify(r.body)}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
// last_sample_error is often empty when the failure happens before
|
||||
// the agent emits a sample (e.g. boot crash, image pull error,
|
||||
// missing PYTHONPATH, OpenAI quota at startup). Dumping the full
|
||||
@@ -266,8 +315,8 @@ export default async function globalSetup(_config: FullConfig): Promise<void> {
|
||||
// needs without a second probe. Otherwise this propagates as a
|
||||
// bare "Workspace failed: " — the exact useless message that
|
||||
// sent #2632 to the issue tracker.
|
||||
const detail = r.body.last_sample_error
|
||||
? r.body.last_sample_error
|
||||
const detail = sampleErr
|
||||
? sampleErr
|
||||
: `(no last_sample_error) full body: ${JSON.stringify(r.body)}`;
|
||||
throw new Error(`Workspace failed: ${detail}`);
|
||||
}
|
||||
@@ -277,7 +326,7 @@ export default async function globalSetup(_config: FullConfig): Promise<void> {
|
||||
10_000,
|
||||
"workspace online",
|
||||
);
|
||||
console.log(`[staging-setup] Workspace online`);
|
||||
console.log(`[staging-setup] Workspace renderable`);
|
||||
|
||||
// 7. Hand state off to tests + teardown — overwrite the slug-only
|
||||
// bootstrap state with the full state spec tests need.
|
||||
|
||||
Reference in New Issue
Block a user