Merge pull request #131 from abhi1693/test/e2e-critical-flows
test(e2e): cover boards, approvals, packs critical flows
This commit is contained in:
@@ -1,18 +1,12 @@
|
|||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
import { setupCommonPageTestHooks } from "../support/testHooks";
|
||||||
|
|
||||||
describe("/boards", () => {
|
describe("/boards", () => {
|
||||||
const apiBase = "**/api/v1";
|
const apiBase = "**/api/v1";
|
||||||
const email = "local-auth-user@example.com";
|
const email = "local-auth-user@example.com";
|
||||||
|
|
||||||
const originalDefaultCommandTimeout = Cypress.config("defaultCommandTimeout");
|
setupCommonPageTestHooks(apiBase);
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
Cypress.config("defaultCommandTimeout", 20_000);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
Cypress.config("defaultCommandTimeout", originalDefaultCommandTimeout);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("auth negative: signed-out user is shown local auth login", () => {
|
it("auth negative: signed-out user is shown local auth login", () => {
|
||||||
cy.visit("/boards");
|
cy.visit("/boards");
|
||||||
@@ -21,7 +15,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*`, {
|
cy.intercept("GET", `${apiBase}/organizations/me/member*`, {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: {
|
body: {
|
||||||
@@ -52,9 +46,7 @@ describe("/boards", () => {
|
|||||||
|
|
||||||
cy.intercept("GET", `${apiBase}/organizations/me/list*`, {
|
cy.intercept("GET", `${apiBase}/organizations/me/list*`, {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: [
|
body: [{ id: "o1", name: "Personal", role: "owner", is_active: true }],
|
||||||
{ id: "o1", name: "Personal", role: "owner", is_active: true },
|
|
||||||
],
|
|
||||||
}).as("organizations");
|
}).as("organizations");
|
||||||
|
|
||||||
cy.intercept("GET", `${apiBase}/boards*`, {
|
cy.intercept("GET", `${apiBase}/boards*`, {
|
||||||
@@ -98,5 +90,6 @@ describe("/boards", () => {
|
|||||||
|
|
||||||
cy.contains(/boards/i).should("be.visible");
|
cy.contains(/boards/i).should("be.visible");
|
||||||
cy.contains("Demo Board").should("be.visible");
|
cy.contains("Demo Board").should("be.visible");
|
||||||
|
cy.contains("a", /create board/i).should("be.visible");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
81
frontend/cypress/e2e/global_approvals.cy.ts
Normal file
81
frontend/cypress/e2e/global_approvals.cy.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
import { setupCommonPageTestHooks } from "../support/testHooks";
|
||||||
|
|
||||||
|
describe("Global approvals", () => {
|
||||||
|
const apiBase = "**/api/v1";
|
||||||
|
|
||||||
|
setupCommonPageTestHooks(apiBase);
|
||||||
|
|
||||||
|
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.loginWithLocalAuth();
|
||||||
|
cy.visit("/approvals");
|
||||||
|
cy.waitForAppLoaded();
|
||||||
|
|
||||||
|
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");
|
||||||
|
// 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 });
|
||||||
|
|
||||||
|
// Status badge should flip to approved.
|
||||||
|
cy.contains(/approved/i).should("be.visible");
|
||||||
|
});
|
||||||
|
});
|
||||||
48
frontend/cypress/e2e/skill_packs_sync.cy.ts
Normal file
48
frontend/cypress/e2e/skill_packs_sync.cy.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
import { setupCommonPageTestHooks } from "../support/testHooks";
|
||||||
|
|
||||||
|
describe("Skill packs", () => {
|
||||||
|
const apiBase = "**/api/v1";
|
||||||
|
|
||||||
|
setupCommonPageTestHooks(apiBase);
|
||||||
|
|
||||||
|
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.loginWithLocalAuth();
|
||||||
|
cy.visit("/skills/packs");
|
||||||
|
cy.waitForAppLoaded();
|
||||||
|
|
||||||
|
cy.wait(["@usersMe", "@organizationsList", "@orgMeMember", "@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");
|
||||||
|
});
|
||||||
|
});
|
||||||
77
frontend/cypress/support/testHooks.ts
Normal file
77
frontend/cypress/support/testHooks.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
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",
|
||||||
|
organizationId = "org1",
|
||||||
|
organizationName = "Testing Org",
|
||||||
|
userId = "u1",
|
||||||
|
userEmail = "local-auth-user@example.com",
|
||||||
|
userName = "Local User",
|
||||||
|
} = 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}/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: {
|
||||||
|
id: "membership-1",
|
||||||
|
organization_id: organizationId,
|
||||||
|
user_id: userId,
|
||||||
|
role: orgMemberRole,
|
||||||
|
all_boards_read: true,
|
||||||
|
all_boards_write: true,
|
||||||
|
board_access: [],
|
||||||
|
},
|
||||||
|
}).as("orgMeMember");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Cypress.config("defaultCommandTimeout", originalDefaultCommandTimeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user