diff --git a/codev/projects/bugfix-828-test-dashboard-e2e-strict-mode/status.yaml b/codev/projects/bugfix-828-test-dashboard-e2e-strict-mode/status.yaml new file mode 100644 index 000000000..fbaa42a39 --- /dev/null +++ b/codev/projects/bugfix-828-test-dashboard-e2e-strict-mode/status.yaml @@ -0,0 +1,22 @@ +id: bugfix-828 +title: test-dashboard-e2e-strict-mode +protocol: bugfix +phase: pr +plan_phases: [] +current_plan_phase: null +gates: + pr: + status: approved + requested_at: '2026-05-28T10:40:10.269Z' + approved_at: '2026-05-28T10:40:57.591Z' +iteration: 1 +build_complete: false +history: [] +started_at: '2026-05-28T10:30:53.869Z' +updated_at: '2026-05-28T10:40:57.592Z' +pr_history: + - phase: pr + pr_number: 917 + branch: builder/bugfix-828 + created_at: '2026-05-28T10:39:55.080Z' +pr_ready_for_human: false diff --git a/codev/state/bugfix-828_thread.md b/codev/state/bugfix-828_thread.md new file mode 100644 index 000000000..b69736498 --- /dev/null +++ b/codev/state/bugfix-828_thread.md @@ -0,0 +1,49 @@ +# bugfix-828 — strict-mode locator collision + +## Investigate + +Issue #828: scheduled Dashboard E2E failed because `.work-section:has-text("Recently Closed")` matched **two** sections — the actual Recently Closed section *and* the Backlog section, which renders issue #813 ("vscode: migrate Recently Closed view from sidebar to Codev panel tab") as a row whose title contains the substring "Recently Closed". + +`:has-text()` does substring-match across the whole subtree, so any backlog item whose title contains a section heading literal poisons the selector. + +Four occurrences of the loose pattern, all in `packages/codev/src/agent-farm/__tests__/e2e/work-view-backlog.test.ts`: + +- L77 `Backlog` +- L116 `Backlog` +- L152 `Recently Closed` +- L173 `Recently Closed` + +`Backlog` is also vulnerable — any future issue whose title contains "Backlog" would break L77/L116 the same way. + +`grep -rn ':has-text' packages/codev/src/agent-farm/__tests__/e2e/` shows no other `.work-section:has-text` matches — the rest of the `:has-text` usage is on tab buttons and `.instance a`, which aren't vulnerable to the same collision. + +## Plan + +Replace all four with the heading-scoped form: + +```ts +page.locator('.work-section:has(h3.work-section-title:text-is("Backlog"))') +``` + +`:text-is()` matches the exact text content of the heading, not a substring of the section subtree. + +## Implement + +All four `.work-section:has-text(...)` occurrences in `work-view-backlog.test.ts` replaced with `.work-section:has(h3.work-section-title:text-is("..."))`. Single short comment on the first occurrence explains the workaround (referencing #828). Diff: +60 / -5, test-only. + +`porch check` build/tests both pass (after one-time core build to materialize `@cluesmith/codev-core` declaration files in the worktree). + +## Review + +CMAP-3 all APPROVE: +- Gemini — APPROVE / HIGH +- Codex — APPROVE / MEDIUM +- Claude — APPROVE / HIGH + +No KEY_ISSUES from any reviewer. Claude noted `.tab-bar-item:has-text("Work")` uses the same `:has-text` pattern but is safe (button with fixed link text, no dynamic descendants) — verified during sweep. + +PR #917 created with CMAP table in body. Architect notified. + +## Awaiting + +`pr` gate approval from human. diff --git a/packages/codev/src/agent-farm/__tests__/e2e/work-view-backlog.test.ts b/packages/codev/src/agent-farm/__tests__/e2e/work-view-backlog.test.ts index 62c76e050..e583c7db4 100644 --- a/packages/codev/src/agent-farm/__tests__/e2e/work-view-backlog.test.ts +++ b/packages/codev/src/agent-farm/__tests__/e2e/work-view-backlog.test.ts @@ -73,8 +73,11 @@ test.describe('Work view: backlog clickability and artifacts', () => { await workTab.click(); } - // Wait for backlog section to appear - const backlogSection = page.locator('.work-section:has-text("Backlog")'); + // Scope to the heading element — `:has-text` would collide with any backlog + // item whose title contains the section name (see #828). + const backlogSection = page.locator( + '.work-section:has(h3.work-section-title:text-is("Backlog"))', + ); await expect(backlogSection).toBeVisible({ timeout: 15_000 }); // Check if there are backlog rows @@ -113,7 +116,9 @@ test.describe('Work view: backlog clickability and artifacts', () => { await workTab.click(); } - const backlogSection = page.locator('.work-section:has-text("Backlog")'); + const backlogSection = page.locator( + '.work-section:has(h3.work-section-title:text-is("Backlog"))', + ); await expect(backlogSection).toBeVisible({ timeout: 15_000 }); // Check for artifact link buttons (they appear when items have specs/plans/reviews) @@ -149,7 +154,9 @@ test.describe('Work view: backlog clickability and artifacts', () => { if (data.recentlyClosed && data.recentlyClosed.length > 0) { // Recently Closed section should be visible - const closedSection = page.locator('.work-section:has-text("Recently Closed")'); + const closedSection = page.locator( + '.work-section:has(h3.work-section-title:text-is("Recently Closed"))', + ); await expect(closedSection).toBeVisible({ timeout: 5_000 }); // Items should be clickable links @@ -170,7 +177,9 @@ test.describe('Work view: backlog clickability and artifacts', () => { } // If no recently closed items, the section should not be visible else { - const closedSection = page.locator('.work-section:has-text("Recently Closed")'); + const closedSection = page.locator( + '.work-section:has(h3.work-section-title:text-is("Recently Closed"))', + ); await expect(closedSection).not.toBeVisible(); } });