# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: scripts/acceptance_e2e.ts >> console-v2-convergence-A acceptance >> ACCEPT-3 wire-shape: zero canonical-absence fallbacks
- Location: scripts/acceptance_e2e.ts:46:7

# Error details

```
Test timeout of 60000ms exceeded.
```

```
Error: locator.click: Test timeout of 60000ms exceeded.
Call log:
  - waiting for locator('[data-testid="hierarchy-tree"] [data-testid*="episode"]').first()

```

# Page snapshot

```yaml
- generic [ref=e1]:
  - generic [ref=e2]:
    - generic [ref=e7]: Recoil Console
    - generic [ref=e8]: v2.0.1
    - button "connection connected" [ref=e9] [cursor=pointer]:
      - generic [ref=e11]: connected
    - generic [ref=e12]:
      - button "Tweaks" [ref=e13] [cursor=pointer]: ⚙
      - button "commands reference" [ref=e14] [cursor=pointer]: "?"
      - generic [ref=e15]: jt
      - generic [ref=e16]: ·
      - generic [ref=e17]: 12:10
  - generic [ref=e18]:
    - button "tartarus ▾" [ref=e23] [cursor=pointer]:
      - generic [ref=e24]: tartarus
      - generic [ref=e25]: ▾
    - navigation [ref=e26]:
      - button "Takes" [ref=e27]
      - button "Lineage" [ref=e28]
      - button "Memory" [ref=e29]
      - button "Queue" [ref=e30]
      - button "Events" [ref=e31]
    - generic [ref=e32]:
      - button "filter" [ref=e33] [cursor=pointer]
      - 'button "sort: idx" [ref=e34] [cursor=pointer]'
      - button "retry selected ⌘R" [ref=e35] [cursor=pointer]
      - generic [ref=e36]:
        - generic [ref=e37]:
          - checkbox "dry-run" [checked] [ref=e38]
          - text: dry-run
        - button "Generate" [ref=e39]
  - generic [ref=e40]:
    - generic [ref=e41]:
      - generic [ref=e42]:
        - button "HIERARCHY" [ref=e43] [cursor=pointer]
        - button "RECENT" [ref=e44] [cursor=pointer]
      - generic [ref=e45]:
        - generic [ref=e46]:
          - generic [ref=e47]: HIERARCHY
          - button "⌕" [ref=e49]
        - generic [ref=e50]: tartarus
        - generic [ref=e51]:
          - generic [ref=e52]:
            - generic [ref=e53]: ▸
            - generic "EP001" [ref=e55]
          - generic [ref=e56]:
            - generic [ref=e57]: ▸
            - generic "EP002" [ref=e59]
          - generic [ref=e60]:
            - generic [ref=e61]: ▸
            - generic "EP999" [ref=e63]
    - generic "drag to resize" [ref=e64]
    - generic [ref=e67]: no takes for this beat — pick a beat with generated takes
    - generic "drag to resize" [ref=e68]
    - generic [ref=e69]:
      - generic [ref=e70]:
        - generic [ref=e71]: Selected context — clicks bind to chat
        - generic [ref=e72]:
          - generic [ref=e73]: project
          - generic [ref=e74]: tartarus
        - generic [ref=e75]:
          - generic [ref=e76]: focus
          - generic [ref=e77]: tartarus
        - generic [ref=e78]:
          - generic [ref=e79]: selection
          - generic [ref=e80]: —
      - generic [ref=e85]: —
      - iframe [active] [ref=e87]:
        - textbox "Terminal input" [active] [ref=f1e6]
      - generic [ref=e88]:
        - generic [ref=e89]:
          - generic [ref=e90]: Pasteboard
          - generic [ref=e91]:
            - button "Copy" [disabled] [ref=e92]
            - button "Choose files…" [ref=e93] [cursor=pointer]
            - button "Clear" [disabled] [ref=e94]
        - generic [ref=e95]: Drag files here or use Choose files…
  - 'button "LIVE 0 RENDERING 0 EVALUATING 0 QUEUED · in-flight $0.00 · spend·today $0.00 | 96 fails video_i2v: video_complete ⌘\\ open queue →" [ref=e97] [cursor=pointer]':
    - generic [ref=e98]: LIVE
    - generic [ref=e99]:
      - generic [ref=e101]: "0"
      - generic [ref=e102]: RENDERING
    - generic [ref=e103]:
      - generic [ref=e105]: "0"
      - generic [ref=e106]: EVALUATING
    - generic [ref=e107]:
      - generic [ref=e109]: "0"
      - generic [ref=e110]: QUEUED
    - generic [ref=e111]: ·
    - generic [ref=e112]:
      - generic [ref=e113]: in-flight
      - generic [ref=e114]: $0.00
    - generic [ref=e115]: ·
    - generic [ref=e116]:
      - generic [ref=e117]: spend·today
      - generic "synthesized — cost ledger not wired" [ref=e118]: $0.00
    - generic [ref=e119]: "|"
    - 'button "96 fails video_i2v: video_complete ⌘\\" [ref=e120]':
      - generic [ref=e121]: 96 fails
      - generic [ref=e122]: "video_i2v: video_complete"
      - generic [ref=e123]: ⌘\
    - generic [ref=e124]: open queue →
  - generic [ref=e125]:
    - generic [ref=e126]:
      - generic [ref=e127]: engine/generation
      - generic [ref=e128]: "in-flight: 0"
    - generic [ref=e130]: — no recent activity —
  - generic [ref=e131]:
    - generic [ref=e132]: queue-monitor — engine/generation
    - generic [ref=e133]:
      - generic [ref=e134]:
        - text: NaN:NaN:NaN
        - generic [ref=e135]: "[warning]"
        - text: chat/sessions session id capture timeout
      - generic [ref=e136]:
        - text: NaN:NaN:NaN
        - generic [ref=e137]: "[info]"
        - text: selection/changed selection updated
  - generic [ref=e138]:
    - generic [ref=e139]:
      - generic [ref=e140]:
        - generic [ref=e141]: ●
        - generic [ref=e142]: connected
        - generic [ref=e143]: v2_dispatch.py serve
      - generic [ref=e144]:
        - generic [ref=e145]: workers
        - generic [ref=e146]: veo · 0/0 · imagen · 0/0 · 11l · 0/0
      - generic [ref=e147]:
        - generic [ref=e148]: uptime
        - generic [ref=e149]: 156s
    - generic [ref=e150]:
      - generic [ref=e151]:
        - generic [ref=e152]: spend·today
        - generic "synthesized — cost ledger not wired" [ref=e153]: $0.00
      - generic [ref=e154]:
        - generic [ref=e155]: panel
        - generic [ref=e156]: gemini-3.1, sonnet-3.7
      - generic [ref=e157]:
        - generic [ref=e158]: F3
        - generic [ref=e159]: operators
```

# Test source

```ts
  1   | // recoil/console-v2/scripts/acceptance_e2e.ts
  2   | //
  3   | // Build A Convergence — Playwright acceptance set (8 assertions).
  4   | // Per SYNTHESIS LOCK 3 of console-v2-meta-diagnosis-2026-05-27.
  5   | //
  6   | // Pre-reqs: dev server on http://localhost:5173, API on http://localhost:8431.
  7   | // Run:       pnpm exec playwright test scripts/acceptance_e2e.ts
  8   | //
  9   | // Hard gate: all 8 must pass for BUILD COMPLETE.
  10  | 
  11  | import { test, expect } from "@playwright/test";
  12  | 
  13  | const BASE = "http://localhost:5173";
  14  | const API = "http://localhost:8431";
  15  | 
  16  | const DEAD_FALLBACKS = [
  17  |   "episode_id_derived_from_filename_prefix",
  18  |   "episodes_synthesized_from_shot_index",
  19  |   "scenes_synthesized_one_per_episode",
  20  | ];
  21  | 
  22  | test.describe("console-v2-convergence-A acceptance", () => {
  23  |   test("ACCEPT-1 cold-load: project picker populated", async ({ page }) => {
  24  |     await page.goto(BASE);
  25  |     const picker = page.locator('[data-testid="project-picker"]');
  26  |     await expect(picker).toBeVisible({ timeout: 5000 });
  27  |     await picker.click();
  28  |     const expected = ["tartarus", "driver-beware", "afterimage"];
  29  |     for (const id of expected) {
  30  |       await expect(page.getByText(id, { exact: true }).first()).toBeVisible();
  31  |     }
  32  |   });
  33  | 
  34  |   test("ACCEPT-2 hierarchy load against v2 paths", async ({ page }) => {
  35  |     await page.goto(BASE);
  36  |     await page.locator('[data-testid="project-picker"]').click();
  37  |     await page.getByText("tartarus", { exact: true }).first().click();
  38  |     const tree = page.locator('[data-testid="hierarchy-tree"]');
  39  |     await expect(tree).toBeVisible({ timeout: 3000 });
  40  |     const firstNode = tree.locator('[data-testid*="hierarchy"]').first();
  41  |     await expect(firstNode).toBeVisible();
  42  |     const text = (await firstNode.innerText()).trim();
  43  |     expect(text).toMatch(/^EP\d+/);
  44  |   });
  45  | 
  46  |   test("ACCEPT-3 wire-shape: zero canonical-absence fallbacks", async ({ page, request }) => {
  47  |     await page.goto(BASE);
  48  |     await page.locator('[data-testid="project-picker"]').click();
  49  |     await page.getByText("tartarus", { exact: true }).first().click();
  50  |     const firstEpisode = page.locator('[data-testid="hierarchy-tree"] [data-testid*="episode"]').first();
> 51  |     await firstEpisode.click();
      |                        ^ Error: locator.click: Test timeout of 60000ms exceeded.
  52  |     await page.locator('[data-testid="hierarchy-shot-node"]').first().waitFor({ timeout: 5000 });
  53  | 
  54  |     const resp = await request.get(`${API}/api/events?scopePrefix=api/adapters/&limit=500`);
  55  |     expect(resp.ok()).toBeTruthy();
  56  |     const events = await resp.json();
  57  |     const fallbackHits = events
  58  |       .filter((e: any) => e.severity === "fallback")
  59  |       .map((e: any) => e.summary);
  60  |     for (const dead of DEAD_FALLBACKS) {
  61  |       expect(fallbackHits).not.toContain(dead);
  62  |     }
  63  |   });
  64  | 
  65  |   test("ACCEPT-4 media playback with Range support (206)", async ({ page }) => {
  66  |     await page.goto(BASE);
  67  |     await page.locator('[data-testid="project-picker"]').click();
  68  |     await page.getByText("tartarus", { exact: true }).first().click();
  69  |     const takeNode = page.locator('[data-testid*="take"]').first();
  70  |     await takeNode.click();
  71  |     const player = page.locator('[data-testid="take-video-player"]');
  72  |     await expect(player).toBeVisible({ timeout: 5000 });
  73  | 
  74  |     const mediaResponses: number[] = [];
  75  |     page.on("response", (r) => {
  76  |       if (r.url().includes("/api/media/")) mediaResponses.push(r.status());
  77  |     });
  78  | 
  79  |     await page.evaluate(() => {
  80  |       const v = document.querySelector('[data-testid="take-video-player"] video') as HTMLVideoElement | null;
  81  |       if (v && v.duration) v.currentTime = v.duration * 0.5;
  82  |     });
  83  |     await page.waitForTimeout(2000);
  84  |     expect(mediaResponses.some((s) => s === 206)).toBe(true);
  85  |   });
  86  | 
  87  |   test("ACCEPT-5 Lever->Action: Generate dry-run streams stdout", async ({ page }) => {
  88  |     await page.goto(BASE);
  89  |     await page.locator('[data-testid="project-picker"]').click();
  90  |     await page.getByText("tartarus", { exact: true }).first().click();
  91  | 
  92  |     const dryRun = page.locator('[data-testid="generate-dry-run-checkbox"]');
  93  |     if (!(await dryRun.isChecked())) await dryRun.check();
  94  | 
  95  |     const postPromise = page.waitForRequest((req) =>
  96  |       req.url().includes("/api/proposals/generate") && req.method() === "POST"
  97  |     );
  98  |     await page.locator('[data-testid="generate-button"]').click();
  99  |     const post = await postPromise;
  100 |     const postBody = JSON.parse(post.postData() ?? "{}");
  101 |     expect(postBody.dryRun).toBe(true);
  102 | 
  103 |     const drawer = page.locator('[data-testid="queue-monitor-drawer"]');
  104 |     await expect(drawer).toBeVisible({ timeout: 2000 });
  105 | 
  106 |     await page.waitForFunction(
  107 |       () => document.querySelectorAll('[data-testid="queue-monitor-drawer"] .qm-line').length >= 2,
  108 |       { timeout: 5000 }
  109 |     );
  110 |   });
  111 | 
  112 |   test("ACCEPT-6 SSE-driven activity panel update", async ({ page }) => {
  113 |     await page.goto(BASE);
  114 |     await page.locator('[data-testid="project-picker"]').click();
  115 |     await page.getByText("tartarus", { exact: true }).first().click();
  116 | 
  117 |     const counter = page.locator('[data-testid="activity-in-flight-count"]');
  118 |     const initial = parseInt((await counter.innerText()).trim() || "0", 10);
  119 | 
  120 |     const dryRun = page.locator('[data-testid="generate-dry-run-checkbox"]');
  121 |     if (!(await dryRun.isChecked())) await dryRun.check();
  122 |     await page.locator('[data-testid="generate-button"]').click();
  123 | 
  124 |     await expect(counter).not.toHaveText(String(initial), { timeout: 5000 });
  125 |     await expect(counter).toHaveText(String(initial), { timeout: 30000 });
  126 |   });
  127 | 
  128 |   test("ACCEPT-7 cross-project bleed eliminated", async ({ page }) => {
  129 |     await page.goto(BASE);
  130 |     await page.locator('[data-testid="project-picker"]').click();
  131 |     await page.getByText("tartarus", { exact: true }).first().click();
  132 |     await page.locator('[data-testid="project-picker"]').click();
  133 |     await page.getByText("driver-beware", { exact: true }).first().click();
  134 | 
  135 |     const breadcrumb = page.locator('[data-testid="breadcrumb"]');
  136 |     await expect(breadcrumb).toContainText("driver-beware");
  137 | 
  138 |     const rightRail = page.locator('[data-testid="right-rail-selection"]');
  139 |     const txt = (await rightRail.innerText().catch(() => "")).trim();
  140 |     if (txt) expect(txt).toContain("driver-beware");
  141 | 
  142 |     const sources = await page.evaluate(() => {
  143 |       // @ts-expect-error custom diag hook installed in event_stream.ts
  144 |       const diag = window.__eventStreamDiag;
  145 |       return diag ? diag.openSources() : null;
  146 |     });
  147 |     if (sources !== null) {
  148 |       expect(sources.length).toBe(1);
  149 |       expect(sources[0]).toContain("scope=driver-beware");
  150 |     }
  151 |   });
```