From f69af0a6c9a02fe64227abc49f878953adad0104 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 14 Feb 2026 14:32:54 +0000 Subject: [PATCH] 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"); + }); +});