test(e2e): add critical flows for boards, approvals, packs

This commit is contained in:
Abhimanyu Saharan
2026-02-14 14:32:54 +00:00
committed by Abhimanyu Saharan
parent 8981202f8f
commit f69af0a6c9
3 changed files with 178 additions and 4 deletions

View File

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

View File

@@ -0,0 +1,100 @@
/// <reference types="cypress" />
// 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");
});
});

View File

@@ -0,0 +1,75 @@
/// <reference types="cypress" />
// 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");
});
});