From ffad860ed61da5557185df23201d7d3613b7aa28 Mon Sep 17 00:00:00 2001 From: Amal Date: Fri, 17 Jul 2026 09:42:49 -0700 Subject: [PATCH] fix(e2e): realign selectors with the olp UI and upstream route shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit upstream-main (olp's tip) ships the #215/#216 liquid-surfaces UI, so the same selector drifts fixed on the fork's suite (commit 3d46814 on main) also apply here. Ports those realigned hunks, plus one upstream-specific route fix. * Chat input placeholder is "How can I help?", not "Ask a question about your documents..." (ChatInput/TRChatInput). Updated in chat-management (rename/delete/project-assistant) and critical-path. * The project-assistant empty state replaced the "+ Create New" text link with a PillButton reading "Create" (ProjectAssistantTable) — critical-path and chat-management now use getByRole("button", { name: "Create" }). * The sidebar chat row's active marker is APP_SURFACE_ACTIVE_CLASS ("bg-app-surface-active"), not "bg-gray-200/60"; the row wrapper is now h-8, not h-9 (SidebarChatItem). The rename/delete tests locate the row accordingly. * The documents-toolbar folder button is "Folder" (TabPillButton wired to the root createFolderAction in ProjectDocumentsView), not "Add Subfolder"; it still opens the autofocused "Folder name" root input. * NewTRModal's footer submit and the tabular page CTA both read "Create", so the create-review helper scopes to the modal submit (button[name="modalAction"][value="create-review"]). * The built-in workflow detail test navigated to the flat /workflows/[id], which upstream does not route — only the typed /workflows/assistant/[id] and /workflows/tabular-review/[id] exist. builtin-cp-checklist is assistant-type, so it now navigates to /workflows/assistant/builtin-cp-checklist (the path the app itself links to via workflowDetailPath; works on the fork too). Verified: full 27-test suite green against upstream-main + test-harness + demo-mode(+provider-registry), backend + local Supabase (upstream schema.sql + backend/migrations) + MinIO, demo model, e2e@mike.local. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC --- e2e/chat-management.spec.ts | 26 ++++++++++++++------------ e2e/critical-path.spec.ts | 10 +++++----- e2e/project-management.spec.ts | 6 +++++- e2e/tabular-reviews.spec.ts | 7 ++++++- e2e/workflows-account.spec.ts | 7 +++++-- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/e2e/chat-management.spec.ts b/e2e/chat-management.spec.ts index 415bd37a..6ef0a071 100644 --- a/e2e/chat-management.spec.ts +++ b/e2e/chat-management.spec.ts @@ -124,7 +124,7 @@ test("rename chat: sidebar rename interaction updates the title", async ({ page // ── Step 1: create a new chat ───────────────────────────────────────────────── await page.goto("/assistant"); - const textarea = page.getByPlaceholder("Ask a question about your documents..."); + const textarea = page.getByPlaceholder("How can I help?"); await expect(textarea).toBeVisible({ timeout: 10_000 }); // Pick the keyless demo model so the submit isn't blocked by the // ApiKeyMissingModal (no provider key is configured in this run). @@ -155,13 +155,13 @@ test("rename chat: sidebar rename interaction updates the title", async ({ page // ── Step 4: locate the active chat item ────────────────────────────────────── // SidebarChatItem.tsx renders a `div.group.relative` wrapper for each chat. - // When isActive=true the wrapper class includes "bg-gray-200/60" as a - // standalone Tailwind class. Inactive items have "hover:bg-gray-100" (a - // different token), so matching the "bg-gray-200/60" class distinguishes the - // active item. Use an attribute-substring match ([class*=]) to avoid having - // to CSS-escape the "/" in the Tailwind class name. + // When isActive=true the wrapper carries APP_SURFACE_ACTIVE_CLASS + // ("bg-app-surface-active"); inactive items carry APP_SURFACE_HOVER_CLASS + // ("hover:bg-app-surface-hover", a different token), so matching + // "bg-app-surface-active" distinguishes the active item. (The olp liquid- + // surface refresh renamed the old "bg-gray-200/60" active token.) const activeItem = page - .locator('div.group.relative[class*="bg-gray-200/60"]') + .locator('div.group.relative[class*="bg-app-surface-active"]') .first(); // The active item's trigger is already opacity-100, but hover is harmless and @@ -214,7 +214,7 @@ test("delete chat: sidebar delete action removes the chat from history", async ( // ── Step 1: create a new chat ───────────────────────────────────────────────── await page.goto("/assistant"); - const textarea = page.getByPlaceholder("Ask a question about your documents..."); + const textarea = page.getByPlaceholder("How can I help?"); await expect(textarea).toBeVisible({ timeout: 10_000 }); // ── Step 2: create the chat, riding out transient gateway 502s ─────────────── // ChatInput.handleSubmit creates the chat (saveChat → POST /chat/create) then @@ -276,7 +276,7 @@ test("delete chat: sidebar delete action removes the chat from history", async ( // just-created chat is active and prepended, so it is the first row; rename it // via the same three-dot menu the rename test exercises. const uniqueTitle = `Delete Target ${Date.now()}`; - const firstRow = page.locator("div.group.relative.h-9.rounded-md").first(); + const firstRow = page.locator("div.group.relative.h-8.rounded-md").first(); await firstRow.hover(); await firstRow.locator("button").last().click(); await page.getByRole("menuitem", { name: "Rename" }).click(); @@ -290,7 +290,7 @@ test("delete chat: sidebar delete action removes the chat from history", async ( await expect(targetTitle).toBeVisible({ timeout: 10_000 }); // The row wrapper that contains that title button (for reaching its menu). const targetRow = page - .locator("div.group.relative.h-9.rounded-md") + .locator("div.group.relative.h-8.rounded-md") .filter({ has: targetTitle }); // ── Step 5-7: delete that specific chat, riding out flaky Supabase 500s ────── @@ -397,7 +397,9 @@ test("project assistant: create a new chat and submit a question", async ({ page // found". A genuinely broken assistant tab never shows the button on any // attempt, so the final assertion still fails. const assistantUrl = page.url() + "/assistant"; - const createNewBtn = page.getByText("+ Create New"); + // The olp UI replaced the old "+ Create New" text link with a PillButton + // reading "Create" in the ProjectAssistantTable empty state. + const createNewBtn = page.getByRole("button", { name: "Create", exact: true }); const projectNotFound = page.getByText("Project not found"); const TAB_ATTEMPTS = 4; for (let attempt = 0; attempt < TAB_ATTEMPTS; attempt++) { @@ -452,7 +454,7 @@ test("project assistant: create a new chat and submit a question", async ({ page // ── Step 9: assert the ChatInput textarea is visible ───────────────────────── // ProjectAssistantChatPage renders in the right "Project Assistant" // panel (line 1221-1229 of the chat page component). - const chatInput = page.getByPlaceholder("Ask a question about your documents..."); + const chatInput = page.getByPlaceholder("How can I help?"); await expect(chatInput).toBeVisible({ timeout: 10_000 }); // ── Step 10-11: pick an available model, submit a question, assert it clears ── diff --git a/e2e/critical-path.spec.ts b/e2e/critical-path.spec.ts index 2e520493..12e0f29a 100644 --- a/e2e/critical-path.spec.ts +++ b/e2e/critical-path.spec.ts @@ -143,9 +143,11 @@ test("create project, upload PDF, ask a question and receive a response", async the "Assistant" item in the sidebar nav. The workspace fetches getProject() on mount and does NOT retry, so under the local-Supabase load the page can land on a permanent "Project not found" or a slow skeleton; - re-navigate until the assistant tab's "+ Create New" affordance renders. */ + re-navigate until the assistant tab's empty-state "Create" affordance + renders. (The olp UI replaced the old "+ Create New" text link with a + PillButton reading "Create" — ProjectAssistantTable empty state.) */ const projectUrl = page.url().split("?")[0]; - const createNew = page.getByText("+ Create New"); + const createNew = page.getByRole("button", { name: "Create", exact: true }); for (let attempt = 1; attempt <= 6; attempt++) { await page.goto(`${projectUrl}/assistant`); await page @@ -163,9 +165,7 @@ test("create project, upload PDF, ask a question and receive a response", async await page.waitForLoadState("networkidle", { timeout: 20_000 }).catch(() => {}); /* ── Step 7: select the keyless demo model, type a question, submit ───── */ - const chatInput = page.getByPlaceholder( - "Ask a question about your documents...", - ); + const chatInput = page.getByPlaceholder("How can I help?"); await expect(chatInput).toBeVisible({ timeout: 10_000 }); /* The default Gemini model has no key configured, so submitting it would be diff --git a/e2e/project-management.spec.ts b/e2e/project-management.spec.ts index 760b8037..cac7bd71 100644 --- a/e2e/project-management.spec.ts +++ b/e2e/project-management.spec.ts @@ -291,7 +291,11 @@ test("create a folder inside a project", async ({ page }) => { * "Add Subfolder") only appears once the project has loaded, so reload until * it does. */ - const addSubfolderBtn = page.getByRole("button", { name: "Add Subfolder" }); + /* The olp UI renamed the documents-toolbar folder-create button from + "Add Subfolder" to "Folder" (a TabPillButton wired to the root + createFolderAction — ProjectDocumentsView). Clicking it still renders the + autofocused "Folder name" input at root level (creatingIn === null). */ + const addSubfolderBtn = page.getByRole("button", { name: "Folder" }); await waitForProjectLoaded(page, addSubfolderBtn); /* Confirm the uploaded document rendered, i.e. the project is non-empty and diff --git a/e2e/tabular-reviews.spec.ts b/e2e/tabular-reviews.spec.ts index 1cf557da..c845f264 100644 --- a/e2e/tabular-reviews.spec.ts +++ b/e2e/tabular-reviews.spec.ts @@ -133,7 +133,12 @@ async function createReview( const respP = page .waitForResponse(isCreateReviewPost, { timeout: 30_000 }) .catch(() => null); - await page.getByRole("button", { name: "Create", exact: true }).click(); + // The modal footer's submit button and the page's own "Create" CTA both + // read "Create"; scope to the modal's submit (button[name="modalAction"] + // value="create-review") to avoid a strict-mode ambiguity. + await page + .locator('button[name="modalAction"][value="create-review"]') + .click(); const resp = await respP; if (resp && resp.ok()) { review = (await resp.json()) as { id: string }; diff --git a/e2e/workflows-account.spec.ts b/e2e/workflows-account.spec.ts index b5c5d2be..e21b46bf 100644 --- a/e2e/workflows-account.spec.ts +++ b/e2e/workflows-account.spec.ts @@ -134,8 +134,11 @@ test.describe("Workflows", () => { page, }) => { // Navigate directly to the known built-in ID; this avoids having to click - // through the DisplayWorkflowModal "View Page" button. - await page.goto("/workflows/builtin-cp-checklist"); + // through the DisplayWorkflowModal "View Page" button. builtin-cp-checklist + // is an assistant-type workflow, so use the typed detail route + // (/workflows/assistant/[id]) that the app itself links to via + // workflowDetailPath — the flat /workflows/[id] path does not exist here. + await page.goto("/workflows/assistant/builtin-cp-checklist"); // The page loads and shows the built-in workflow title await expect(page.getByText("Draft CP Checklist")).toBeVisible({