From f69af0a6c9a02fe64227abc49f878953adad0104 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 14 Feb 2026 14:32:54 +0000 Subject: [PATCH 1/6] test(e2e): add critical flows for boards, approvals, packs --- frontend/cypress/e2e/boards_list.cy.ts | 7 +- frontend/cypress/e2e/global_approvals.cy.ts | 100 ++++++++++++++++++++ frontend/cypress/e2e/skill_packs_sync.cy.ts | 75 +++++++++++++++ 3 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 frontend/cypress/e2e/global_approvals.cy.ts create mode 100644 frontend/cypress/e2e/skill_packs_sync.cy.ts diff --git a/frontend/cypress/e2e/boards_list.cy.ts b/frontend/cypress/e2e/boards_list.cy.ts index b1585cd2..126cbd4f 100644 --- a/frontend/cypress/e2e/boards_list.cy.ts +++ b/frontend/cypress/e2e/boards_list.cy.ts @@ -21,7 +21,7 @@ describe("/boards", () => { ); }); - it("happy path: signed-in user sees boards list", () => { + it("happy path: signed-in user sees boards list and create button", () => { cy.intercept("GET", `${apiBase}/organizations/me/member*`, { statusCode: 200, body: { @@ -52,9 +52,7 @@ describe("/boards", () => { cy.intercept("GET", `${apiBase}/organizations/me/list*`, { statusCode: 200, - body: [ - { id: "o1", name: "Personal", role: "owner", is_active: true }, - ], + body: [{ id: "o1", name: "Personal", role: "owner", is_active: true }], }).as("organizations"); cy.intercept("GET", `${apiBase}/boards*`, { @@ -98,5 +96,6 @@ describe("/boards", () => { cy.contains(/boards/i).should("be.visible"); cy.contains("Demo Board").should("be.visible"); + cy.contains("a", /create board/i).should("be.visible"); }); }); diff --git a/frontend/cypress/e2e/global_approvals.cy.ts b/frontend/cypress/e2e/global_approvals.cy.ts new file mode 100644 index 00000000..91d093f7 --- /dev/null +++ b/frontend/cypress/e2e/global_approvals.cy.ts @@ -0,0 +1,100 @@ +/// + +// Clerk/Next.js occasionally triggers a hydration mismatch on auth routes in CI. +// This is non-deterministic UI noise for these tests; ignore it so assertions can proceed. +Cypress.on("uncaught:exception", (err) => { + if (err.message?.includes("Hydration failed")) { + return false; + } + return true; +}); + +describe("Global approvals", () => { + const apiBase = "**/api/v1"; + const email = Cypress.env("CLERK_TEST_EMAIL") || "jane+clerk_test@example.com"; + + const originalDefaultCommandTimeout = Cypress.config("defaultCommandTimeout"); + + beforeEach(() => { + Cypress.config("defaultCommandTimeout", 20_000); + + cy.intercept("GET", "**/healthz", { + statusCode: 200, + body: { ok: true }, + }).as("healthz"); + + cy.intercept("GET", `${apiBase}/organizations/me/member*`, { + statusCode: 200, + body: { organization_id: "org1", role: "owner" }, + }).as("orgMeMember"); + }); + + afterEach(() => { + Cypress.config("defaultCommandTimeout", originalDefaultCommandTimeout); + }); + + it("can render a pending approval and approve it", () => { + const approval = { + id: "a1", + board_id: "b1", + action_type: "task.closeout", + status: "pending", + confidence: 92, + created_at: "2026-02-14T00:00:00Z", + task_id: "t1", + task_ids: ["t1"], + payload: { + task_id: "t1", + title: "Close task", + reason: "Merged and ready to close", + }, + }; + + cy.intercept("GET", `${apiBase}/boards*`, { + statusCode: 200, + body: { + items: [ + { + id: "b1", + name: "Testing", + group_id: null, + objective: null, + success_metrics: null, + target_date: null, + updated_at: "2026-02-14T00:00:00Z", + created_at: "2026-02-10T00:00:00Z", + }, + ], + }, + }).as("boardsList"); + + cy.intercept("GET", `${apiBase}/boards/b1/approvals*`, { + statusCode: 200, + body: { items: [approval] }, + }).as("approvalsList"); + + cy.intercept("PATCH", `${apiBase}/boards/b1/approvals/a1`, { + statusCode: 200, + body: { ...approval, status: "approved" }, + }).as("approvalUpdate"); + + cy.visit("/sign-in"); + cy.clerkLoaded(); + cy.clerkSignIn({ strategy: "email_code", identifier: email }); + + cy.visit("/approvals"); + cy.waitForAppLoaded(); + + cy.wait(["@boardsList", "@approvalsList"], { timeout: 20_000 }); + + // Pending approval should be visible in the list. + cy.contains(/unapproved tasks/i).should("be.visible"); + cy.contains(/task closeout/i).should("be.visible"); + + cy.contains("button", /^approve$/i).click(); + cy.wait("@approvalUpdate", { timeout: 20_000 }); + + // Status badge should flip to approved. + cy.contains(/approved/i).should("be.visible"); + }); +}); diff --git a/frontend/cypress/e2e/skill_packs_sync.cy.ts b/frontend/cypress/e2e/skill_packs_sync.cy.ts new file mode 100644 index 00000000..1a3c52f7 --- /dev/null +++ b/frontend/cypress/e2e/skill_packs_sync.cy.ts @@ -0,0 +1,75 @@ +/// + +// Clerk/Next.js occasionally triggers a hydration mismatch on auth routes in CI. +// This is non-deterministic UI noise for these tests; ignore it so assertions can proceed. +Cypress.on("uncaught:exception", (err) => { + if (err.message?.includes("Hydration failed")) { + return false; + } + return true; +}); + +describe("Skill packs", () => { + const apiBase = "**/api/v1"; + const email = Cypress.env("CLERK_TEST_EMAIL") || "jane+clerk_test@example.com"; + + const originalDefaultCommandTimeout = Cypress.config("defaultCommandTimeout"); + + beforeEach(() => { + Cypress.config("defaultCommandTimeout", 20_000); + + cy.intercept("GET", "**/healthz", { + statusCode: 200, + body: { ok: true }, + }).as("healthz"); + + cy.intercept("GET", `${apiBase}/organizations/me/member*`, { + statusCode: 200, + body: { organization_id: "org1", role: "owner" }, + }).as("orgMeMember"); + }); + + afterEach(() => { + Cypress.config("defaultCommandTimeout", originalDefaultCommandTimeout); + }); + + it("can sync a pack and surface warnings", () => { + cy.intercept("GET", `${apiBase}/skills/packs*`, { + statusCode: 200, + body: [ + { + id: "p1", + name: "OpenClaw Skills", + description: "Test pack", + source_url: "https://github.com/openclaw/skills", + branch: "main", + skill_count: 12, + updated_at: "2026-02-14T00:00:00Z", + created_at: "2026-02-10T00:00:00Z", + }, + ], + }).as("packsList"); + + cy.intercept("POST", `${apiBase}/skills/packs/p1/sync*`, { + statusCode: 200, + body: { + warnings: ["1 skill skipped (missing SKILL.md)"], + }, + }).as("packSync"); + + cy.visit("/sign-in"); + cy.clerkLoaded(); + cy.clerkSignIn({ strategy: "email_code", identifier: email }); + + cy.visit("/skills/packs"); + cy.waitForAppLoaded(); + + cy.wait("@packsList", { timeout: 20_000 }); + cy.contains(/openclaw skills/i).should("be.visible"); + + cy.contains("button", /^sync$/i).click(); + cy.wait("@packSync", { timeout: 20_000 }); + + cy.contains(/skill skipped/i).should("be.visible"); + }); +}); From ba918d423152c4a86118b7a93b26b2d1f62874d0 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 14 Feb 2026 14:39:22 +0000 Subject: [PATCH 2/6] docs(e2e): add gap analysis + CI determinism notes --- frontend/cypress/E2E_GAP_ANALYSIS.md | 64 ++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 frontend/cypress/E2E_GAP_ANALYSIS.md diff --git a/frontend/cypress/E2E_GAP_ANALYSIS.md b/frontend/cypress/E2E_GAP_ANALYSIS.md new file mode 100644 index 00000000..c7ddc2ea --- /dev/null +++ b/frontend/cypress/E2E_GAP_ANALYSIS.md @@ -0,0 +1,64 @@ +# Frontend E2E gap analysis (Cypress) + +This document tracks **critical user journeys** and the current Cypress E2E coverage. + +Goals: +- Improve release confidence for high-value workflows. +- Keep tests **CI-deterministic** (flake-averse), favoring **API stubs** over live backend state. + +Non-goals: +- Broad UI pixel validation. +- Full end-to-end (DB-backed) integration coverage for every page. + +## Current coverage inventory (existing specs) + +Located under `frontend/cypress/e2e/`: +- `clerk_login.cy.ts` — smoke sign-in via Clerk testing commands + reach a protected route. +- `organizations.cy.ts` — signed-out redirect + role-based invite permissions UI. +- `activity_smoke.cy.ts` — signed-out redirect for `/activity`. +- `activity_feed.cy.ts` — `/activity` happy/empty/error states; stubs SSE + bootstraps via `cy.intercept`. + +## Coverage plan (critical flows) + +Legend: +- **Priority**: P0 (must-have for release confidence), P1 (next most valuable) +- **Backend mode**: **Stubbed** = deterministic `cy.intercept` responses; **Live** = depends on real backend state +- **Runtime impact**: rough incremental CI time for the spec (order-of-magnitude) + +| Flow | Priority | Coverage status | Backend mode | Expected runtime impact | +|---|---:|---|---|---:| +| Boards list renders + Create CTA for admin | P0 | ✅ Implemented (`boards_list.cy.ts`) | Stubbed | ~10–25s | +| Global approvals: render pending approval + approve/reject | P0 | ✅ Implemented (`global_approvals.cy.ts`) | Stubbed | ~15–35s | +| Skill packs: sync a pack + surface warnings | P1 | ✅ Implemented (`skill_packs_sync.cy.ts`) | Stubbed | ~15–35s | +| Board open + task status move (drag/drop) | P0 | ⏳ Planned | Mostly stubbed (may require DOM/drag stability work) | ~30–60s | +| Task CRUD (create/edit/delete) + error state | P0 | ⏳ Planned | Stubbed | ~30–60s | +| Skills marketplace: list + filters/pagination | P1 | ⏳ Planned | Stubbed | ~20–45s | +| Skills marketplace: install/uninstall dialog wiring | P1 | ⏳ Planned | Stubbed | ~25–50s | +| Gateways list/create (admin gating) | P1 | ⏳ Planned | Stubbed | ~20–45s | + +Notes: +- All implemented flows still rely on **auth bootstrapping** (Clerk sign-in) before hitting protected routes. +- We intentionally keep the backend interactions **stubbed** for determinism and speed. + +## CI determinism / flake-avoidance notes + +Patterns used in these specs (and recommended for new ones): +- **Stub `/healthz`** and **org membership** (`/api/v1/organizations/me/member`) so sidebar/admin gating is stable. +- Prefer **API stubs** (`cy.intercept`) over seeding DB state. +- Stub or neutralize **SSE streams** (where applicable) to avoid race-y updates during assertions. +- Set a higher `defaultCommandTimeout` for auth flows (Clerk helpers can be slow in CI). +- Avoid fixed sleeps; prefer `cy.wait(@alias)` or UI assertions that naturally wait. +- Ignore known non-deterministic hydration errors on auth routes (`Hydration failed`). + +## How to run locally + +From repo root: +```bash +npm -C frontend ci +npm -C frontend run e2e +``` + +To run a single spec: +```bash +npm -C frontend run e2e -- --spec "cypress/e2e/boards_list.cy.ts" +``` From c26dddbce88f6a54306bb8a2b7efc5e1612fad1c Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 14 Feb 2026 15:01:03 +0000 Subject: [PATCH 3/6] test(e2e): fix approvals action label assertion for CI --- frontend/cypress/e2e/global_approvals.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/cypress/e2e/global_approvals.cy.ts b/frontend/cypress/e2e/global_approvals.cy.ts index 91d093f7..577e75f5 100644 --- a/frontend/cypress/e2e/global_approvals.cy.ts +++ b/frontend/cypress/e2e/global_approvals.cy.ts @@ -89,7 +89,8 @@ describe("Global approvals", () => { // Pending approval should be visible in the list. cy.contains(/unapproved tasks/i).should("be.visible"); - cy.contains(/task closeout/i).should("be.visible"); + // Action type is humanized as "Task · Closeout" in the UI. + cy.contains(/task\s*(?:·|\u00b7|\u2022)?\s*closeout/i).should("be.visible"); cy.contains("button", /^approve$/i).click(); cy.wait("@approvalUpdate", { timeout: 20_000 }); From d1b08b477729dde7a3409cc0cb2d1e2d3a673ab4 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 14 Feb 2026 16:01:45 +0000 Subject: [PATCH 4/6] chore(e2e): remove gap analysis markdown doc --- frontend/cypress/E2E_GAP_ANALYSIS.md | 64 ---------------------------- 1 file changed, 64 deletions(-) delete mode 100644 frontend/cypress/E2E_GAP_ANALYSIS.md diff --git a/frontend/cypress/E2E_GAP_ANALYSIS.md b/frontend/cypress/E2E_GAP_ANALYSIS.md deleted file mode 100644 index c7ddc2ea..00000000 --- a/frontend/cypress/E2E_GAP_ANALYSIS.md +++ /dev/null @@ -1,64 +0,0 @@ -# Frontend E2E gap analysis (Cypress) - -This document tracks **critical user journeys** and the current Cypress E2E coverage. - -Goals: -- Improve release confidence for high-value workflows. -- Keep tests **CI-deterministic** (flake-averse), favoring **API stubs** over live backend state. - -Non-goals: -- Broad UI pixel validation. -- Full end-to-end (DB-backed) integration coverage for every page. - -## Current coverage inventory (existing specs) - -Located under `frontend/cypress/e2e/`: -- `clerk_login.cy.ts` — smoke sign-in via Clerk testing commands + reach a protected route. -- `organizations.cy.ts` — signed-out redirect + role-based invite permissions UI. -- `activity_smoke.cy.ts` — signed-out redirect for `/activity`. -- `activity_feed.cy.ts` — `/activity` happy/empty/error states; stubs SSE + bootstraps via `cy.intercept`. - -## Coverage plan (critical flows) - -Legend: -- **Priority**: P0 (must-have for release confidence), P1 (next most valuable) -- **Backend mode**: **Stubbed** = deterministic `cy.intercept` responses; **Live** = depends on real backend state -- **Runtime impact**: rough incremental CI time for the spec (order-of-magnitude) - -| Flow | Priority | Coverage status | Backend mode | Expected runtime impact | -|---|---:|---|---|---:| -| Boards list renders + Create CTA for admin | P0 | ✅ Implemented (`boards_list.cy.ts`) | Stubbed | ~10–25s | -| Global approvals: render pending approval + approve/reject | P0 | ✅ Implemented (`global_approvals.cy.ts`) | Stubbed | ~15–35s | -| Skill packs: sync a pack + surface warnings | P1 | ✅ Implemented (`skill_packs_sync.cy.ts`) | Stubbed | ~15–35s | -| Board open + task status move (drag/drop) | P0 | ⏳ Planned | Mostly stubbed (may require DOM/drag stability work) | ~30–60s | -| Task CRUD (create/edit/delete) + error state | P0 | ⏳ Planned | Stubbed | ~30–60s | -| Skills marketplace: list + filters/pagination | P1 | ⏳ Planned | Stubbed | ~20–45s | -| Skills marketplace: install/uninstall dialog wiring | P1 | ⏳ Planned | Stubbed | ~25–50s | -| Gateways list/create (admin gating) | P1 | ⏳ Planned | Stubbed | ~20–45s | - -Notes: -- All implemented flows still rely on **auth bootstrapping** (Clerk sign-in) before hitting protected routes. -- We intentionally keep the backend interactions **stubbed** for determinism and speed. - -## CI determinism / flake-avoidance notes - -Patterns used in these specs (and recommended for new ones): -- **Stub `/healthz`** and **org membership** (`/api/v1/organizations/me/member`) so sidebar/admin gating is stable. -- Prefer **API stubs** (`cy.intercept`) over seeding DB state. -- Stub or neutralize **SSE streams** (where applicable) to avoid race-y updates during assertions. -- Set a higher `defaultCommandTimeout` for auth flows (Clerk helpers can be slow in CI). -- Avoid fixed sleeps; prefer `cy.wait(@alias)` or UI assertions that naturally wait. -- Ignore known non-deterministic hydration errors on auth routes (`Hydration failed`). - -## How to run locally - -From repo root: -```bash -npm -C frontend ci -npm -C frontend run e2e -``` - -To run a single spec: -```bash -npm -C frontend run e2e -- --spec "cypress/e2e/boards_list.cy.ts" -``` From 39f314cd8cb3122c7f3ab52a4c259b5ea79ef157 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 4 Mar 2026 22:24:35 +0530 Subject: [PATCH 5/6] test(e2e): extract shared page test setup hooks --- frontend/cypress/e2e/boards_list.cy.ts | 12 ++------ frontend/cypress/e2e/global_approvals.cy.ts | 22 ++------------ frontend/cypress/e2e/skill_packs_sync.cy.ts | 22 ++------------ frontend/cypress/support/testHooks.ts | 32 +++++++++++++++++++++ 4 files changed, 41 insertions(+), 47 deletions(-) create mode 100644 frontend/cypress/support/testHooks.ts diff --git a/frontend/cypress/e2e/boards_list.cy.ts b/frontend/cypress/e2e/boards_list.cy.ts index 126cbd4f..ee075937 100644 --- a/frontend/cypress/e2e/boards_list.cy.ts +++ b/frontend/cypress/e2e/boards_list.cy.ts @@ -1,18 +1,12 @@ /// +import { setupCommonPageTestHooks } from "../support/testHooks"; + describe("/boards", () => { const apiBase = "**/api/v1"; const email = "local-auth-user@example.com"; - const originalDefaultCommandTimeout = Cypress.config("defaultCommandTimeout"); - - beforeEach(() => { - Cypress.config("defaultCommandTimeout", 20_000); - }); - - afterEach(() => { - Cypress.config("defaultCommandTimeout", originalDefaultCommandTimeout); - }); + setupCommonPageTestHooks(apiBase); it("auth negative: signed-out user is shown local auth login", () => { cy.visit("/boards"); diff --git a/frontend/cypress/e2e/global_approvals.cy.ts b/frontend/cypress/e2e/global_approvals.cy.ts index 577e75f5..f63afea8 100644 --- a/frontend/cypress/e2e/global_approvals.cy.ts +++ b/frontend/cypress/e2e/global_approvals.cy.ts @@ -1,5 +1,7 @@ /// +import { setupCommonPageTestHooks } from "../support/testHooks"; + // Clerk/Next.js occasionally triggers a hydration mismatch on auth routes in CI. // This is non-deterministic UI noise for these tests; ignore it so assertions can proceed. Cypress.on("uncaught:exception", (err) => { @@ -13,25 +15,7 @@ describe("Global approvals", () => { const apiBase = "**/api/v1"; const email = Cypress.env("CLERK_TEST_EMAIL") || "jane+clerk_test@example.com"; - const originalDefaultCommandTimeout = Cypress.config("defaultCommandTimeout"); - - beforeEach(() => { - Cypress.config("defaultCommandTimeout", 20_000); - - cy.intercept("GET", "**/healthz", { - statusCode: 200, - body: { ok: true }, - }).as("healthz"); - - cy.intercept("GET", `${apiBase}/organizations/me/member*`, { - statusCode: 200, - body: { organization_id: "org1", role: "owner" }, - }).as("orgMeMember"); - }); - - afterEach(() => { - Cypress.config("defaultCommandTimeout", originalDefaultCommandTimeout); - }); + setupCommonPageTestHooks(apiBase); it("can render a pending approval and approve it", () => { const approval = { diff --git a/frontend/cypress/e2e/skill_packs_sync.cy.ts b/frontend/cypress/e2e/skill_packs_sync.cy.ts index 1a3c52f7..cc3590fb 100644 --- a/frontend/cypress/e2e/skill_packs_sync.cy.ts +++ b/frontend/cypress/e2e/skill_packs_sync.cy.ts @@ -1,5 +1,7 @@ /// +import { setupCommonPageTestHooks } from "../support/testHooks"; + // Clerk/Next.js occasionally triggers a hydration mismatch on auth routes in CI. // This is non-deterministic UI noise for these tests; ignore it so assertions can proceed. Cypress.on("uncaught:exception", (err) => { @@ -13,25 +15,7 @@ describe("Skill packs", () => { const apiBase = "**/api/v1"; const email = Cypress.env("CLERK_TEST_EMAIL") || "jane+clerk_test@example.com"; - const originalDefaultCommandTimeout = Cypress.config("defaultCommandTimeout"); - - beforeEach(() => { - Cypress.config("defaultCommandTimeout", 20_000); - - cy.intercept("GET", "**/healthz", { - statusCode: 200, - body: { ok: true }, - }).as("healthz"); - - cy.intercept("GET", `${apiBase}/organizations/me/member*`, { - statusCode: 200, - body: { organization_id: "org1", role: "owner" }, - }).as("orgMeMember"); - }); - - afterEach(() => { - Cypress.config("defaultCommandTimeout", originalDefaultCommandTimeout); - }); + setupCommonPageTestHooks(apiBase); it("can sync a pack and surface warnings", () => { cy.intercept("GET", `${apiBase}/skills/packs*`, { diff --git a/frontend/cypress/support/testHooks.ts b/frontend/cypress/support/testHooks.ts new file mode 100644 index 00000000..be07765b --- /dev/null +++ b/frontend/cypress/support/testHooks.ts @@ -0,0 +1,32 @@ +/// + +type CommonPageTestHooksOptions = { + timeoutMs?: number; + orgMemberRole?: string; +}; + +export function setupCommonPageTestHooks( + apiBase: string, + options: CommonPageTestHooksOptions = {}, +): void { + const { timeoutMs = 20_000, orgMemberRole = "owner" } = options; + const originalDefaultCommandTimeout = Cypress.config("defaultCommandTimeout"); + + beforeEach(() => { + Cypress.config("defaultCommandTimeout", timeoutMs); + + cy.intercept("GET", "**/healthz", { + statusCode: 200, + body: { ok: true }, + }).as("healthz"); + + cy.intercept("GET", `${apiBase}/organizations/me/member*`, { + statusCode: 200, + body: { organization_id: "org1", role: orgMemberRole }, + }).as("orgMeMember"); + }); + + afterEach(() => { + Cypress.config("defaultCommandTimeout", originalDefaultCommandTimeout); + }); +} From b6aff9c796aaea96256dd2c03fbfcad65df87a26 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 4 Mar 2026 22:29:47 +0530 Subject: [PATCH 6/6] test(e2e): align critical-flow specs with local auth CI --- frontend/cypress/e2e/global_approvals.cy.ts | 26 +++++------ frontend/cypress/e2e/skill_packs_sync.cy.ts | 19 ++------ frontend/cypress/support/testHooks.ts | 49 ++++++++++++++++++++- 3 files changed, 62 insertions(+), 32 deletions(-) diff --git a/frontend/cypress/e2e/global_approvals.cy.ts b/frontend/cypress/e2e/global_approvals.cy.ts index f63afea8..7e0d7fb4 100644 --- a/frontend/cypress/e2e/global_approvals.cy.ts +++ b/frontend/cypress/e2e/global_approvals.cy.ts @@ -2,18 +2,8 @@ import { setupCommonPageTestHooks } from "../support/testHooks"; -// Clerk/Next.js occasionally triggers a hydration mismatch on auth routes in CI. -// This is non-deterministic UI noise for these tests; ignore it so assertions can proceed. -Cypress.on("uncaught:exception", (err) => { - if (err.message?.includes("Hydration failed")) { - return false; - } - return true; -}); - describe("Global approvals", () => { const apiBase = "**/api/v1"; - const email = Cypress.env("CLERK_TEST_EMAIL") || "jane+clerk_test@example.com"; setupCommonPageTestHooks(apiBase); @@ -62,14 +52,20 @@ describe("Global approvals", () => { body: { ...approval, status: "approved" }, }).as("approvalUpdate"); - cy.visit("/sign-in"); - cy.clerkLoaded(); - cy.clerkSignIn({ strategy: "email_code", identifier: email }); - + cy.loginWithLocalAuth(); cy.visit("/approvals"); cy.waitForAppLoaded(); - cy.wait(["@boardsList", "@approvalsList"], { timeout: 20_000 }); + cy.wait( + [ + "@usersMe", + "@organizationsList", + "@orgMeMember", + "@boardsList", + "@approvalsList", + ], + { timeout: 20_000 }, + ); // Pending approval should be visible in the list. cy.contains(/unapproved tasks/i).should("be.visible"); diff --git a/frontend/cypress/e2e/skill_packs_sync.cy.ts b/frontend/cypress/e2e/skill_packs_sync.cy.ts index cc3590fb..d0cb05fb 100644 --- a/frontend/cypress/e2e/skill_packs_sync.cy.ts +++ b/frontend/cypress/e2e/skill_packs_sync.cy.ts @@ -2,18 +2,8 @@ import { setupCommonPageTestHooks } from "../support/testHooks"; -// Clerk/Next.js occasionally triggers a hydration mismatch on auth routes in CI. -// This is non-deterministic UI noise for these tests; ignore it so assertions can proceed. -Cypress.on("uncaught:exception", (err) => { - if (err.message?.includes("Hydration failed")) { - return false; - } - return true; -}); - describe("Skill packs", () => { const apiBase = "**/api/v1"; - const email = Cypress.env("CLERK_TEST_EMAIL") || "jane+clerk_test@example.com"; setupCommonPageTestHooks(apiBase); @@ -41,14 +31,13 @@ describe("Skill packs", () => { }, }).as("packSync"); - cy.visit("/sign-in"); - cy.clerkLoaded(); - cy.clerkSignIn({ strategy: "email_code", identifier: email }); - + cy.loginWithLocalAuth(); cy.visit("/skills/packs"); cy.waitForAppLoaded(); - cy.wait("@packsList", { timeout: 20_000 }); + cy.wait(["@usersMe", "@organizationsList", "@orgMeMember", "@packsList"], { + timeout: 20_000, + }); cy.contains(/openclaw skills/i).should("be.visible"); cy.contains("button", /^sync$/i).click(); diff --git a/frontend/cypress/support/testHooks.ts b/frontend/cypress/support/testHooks.ts index be07765b..4414132f 100644 --- a/frontend/cypress/support/testHooks.ts +++ b/frontend/cypress/support/testHooks.ts @@ -3,13 +3,26 @@ type CommonPageTestHooksOptions = { timeoutMs?: number; orgMemberRole?: string; + organizationId?: string; + organizationName?: string; + userId?: string; + userEmail?: string; + userName?: string; }; export function setupCommonPageTestHooks( apiBase: string, options: CommonPageTestHooksOptions = {}, ): void { - const { timeoutMs = 20_000, orgMemberRole = "owner" } = options; + const { + timeoutMs = 20_000, + orgMemberRole = "owner", + organizationId = "org1", + organizationName = "Testing Org", + userId = "u1", + userEmail = "local-auth-user@example.com", + userName = "Local User", + } = options; const originalDefaultCommandTimeout = Cypress.config("defaultCommandTimeout"); beforeEach(() => { @@ -20,9 +33,41 @@ export function setupCommonPageTestHooks( body: { ok: true }, }).as("healthz"); + cy.intercept("GET", `${apiBase}/users/me*`, { + statusCode: 200, + body: { + id: userId, + clerk_user_id: "local-auth-user", + email: userEmail, + name: userName, + preferred_name: userName, + timezone: "UTC", + }, + }).as("usersMe"); + + cy.intercept("GET", `${apiBase}/organizations/me/list*`, { + statusCode: 200, + body: [ + { + id: organizationId, + name: organizationName, + is_active: true, + role: orgMemberRole, + }, + ], + }).as("organizationsList"); + cy.intercept("GET", `${apiBase}/organizations/me/member*`, { statusCode: 200, - body: { organization_id: "org1", role: orgMemberRole }, + body: { + id: "membership-1", + organization_id: organizationId, + user_id: userId, + role: orgMemberRole, + all_boards_read: true, + all_boards_write: true, + board_access: [], + }, }).as("orgMeMember"); });