fix(e2e): realign selectors with the olp UI and upstream route shape

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC
This commit is contained in:
Amal 2026-07-17 09:42:49 -07:00 committed by QA Runner
parent 94987e6bd7
commit ffad860ed6
5 changed files with 35 additions and 21 deletions

View file

@ -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 <ChatInput> 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 ──

View file

@ -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

View file

@ -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

View file

@ -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 };

View file

@ -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({