Migrate Cypress E2E from cy.intercept to MSW service worker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 09:30:22 +08:00
parent b978071f94
commit 3d1de913f8
27 changed files with 184 additions and 307 deletions

View File

@@ -12,7 +12,6 @@ describe("Account Management", () => {
it("displays user list on account admin page", () => { it("displays user list on account admin page", () => {
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers");
// Should display users from fixture // Should display users from fixture
cy.contains("Test Admin").should("exist"); cy.contains("Test Admin").should("exist");
cy.contains("Alice Wang").should("exist"); cy.contains("Alice Wang").should("exist");
@@ -21,14 +20,13 @@ describe("Account Management", () => {
it("shows active/inactive status badges", () => { it("shows active/inactive status badges", () => {
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
// The user list should show status indicators // The user list should show status indicators
cy.contains("testadmin").should("exist"); cy.contains("testadmin").should("exist");
}); });
it("navigates to my-account page", () => { it("navigates to my-account page", () => {
cy.visit("/my-account"); cy.visit("/my-account");
cy.wait("@getMyAccount");
cy.url().should("include", "/my-account"); cy.url().should("include", "/my-account");
}); });
}); });

View File

@@ -12,17 +12,23 @@ describe("Account duplication check.", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
}); });
it("When an account already exists, show error message on confirm.", () => { it("When an account already exists, show error message on confirm.", () => {
const testAccountName = "000000"; const testAccountName = "000000";
// First creation: account doesn't exist yet // First creation: account doesn't exist yet — override via MSW
cy.intercept("GET", "/api/users/000000", { cy.window().then((win) => {
statusCode: 404, const { http, HttpResponse } = win.__msw__;
body: { detail: "Not found" }, win.__mswWorker__.use(
}).as("checkNewUser"); http.get("/api/users/000000", () =>
HttpResponse.json(
{ detail: "Not found" },
{ status: 404 },
)),
);
});
cy.contains("button", "Create New").should("be.visible").click(); cy.contains("button", "Create New").should("be.visible").click();
cy.get("#input_account_field").type(testAccountName); cy.get("#input_account_field").type(testAccountName);
@@ -34,20 +40,22 @@ describe("Account duplication check.", () => {
.should("be.visible") .should("be.visible")
.and("be.enabled") .and("be.enabled")
.click(); .click();
cy.wait("@postUser");
cy.contains("Account added").should("be.visible"); cy.contains("Account added").should("be.visible");
// Second creation: now account exists — override to return 200 // Second creation: now account exists — override to return 200 via MSW
cy.intercept("GET", "/api/users/000000", { cy.window().then((win) => {
statusCode: 200, const { http, HttpResponse } = win.__msw__;
body: { win.__mswWorker__.use(
username: "000000", http.get("/api/users/000000", () =>
name: "000000", HttpResponse.json({
is_admin: false, username: "000000",
is_active: true, name: "000000",
roles: [], is_admin: false,
}, is_active: true,
}).as("checkExistingUser"); roles: [],
})),
);
});
cy.contains("button", "Create New").should("be.visible").click(); cy.contains("button", "Create New").should("be.visible").click();
cy.get("#input_account_field").type(testAccountName); cy.get("#input_account_field").type(testAccountName);

View File

@@ -10,7 +10,7 @@ describe("Password validation on create account.", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
}); });
it("When password is too short, confirm button stays disabled.", () => { it("When password is too short, confirm button stays disabled.", () => {

View File

@@ -9,13 +9,22 @@ import { loginWithFixtures } from "../../support/intercept";
describe("Create an Account", () => { describe("Create an Account", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
// Override: new usernames should return 404 (account doesn't exist yet)
cy.intercept("GET", "/api/users/unit-test-*", {
statusCode: 404,
body: { detail: "Not found" },
}).as("checkNewUser");
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
// Override: new usernames should return 404 (account doesn't exist yet)
cy.window().then((win) => {
const { http, HttpResponse } = win.__msw__;
win.__mswWorker__.use(
http.get("/api/users/:username", ({ params }) => {
if (params.username.startsWith("unit-test-")) {
return HttpResponse.json(
{ detail: "Not found" },
{ status: 404 },
);
}
}),
);
});
}); });
it("Create a new account with admin role; should show saved message.", () => { it("Create a new account with admin role; should show saved message.", () => {
@@ -30,7 +39,6 @@ describe("Create an Account", () => {
.should("be.visible") .should("be.visible")
.and("be.enabled") .and("be.enabled")
.click(); .click();
cy.wait("@postUser");
cy.contains("Account added").should("be.visible"); cy.contains("Account added").should("be.visible");
}); });

View File

@@ -10,14 +10,13 @@ describe("Delete an Account", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
}); });
it("Delete button opens confirmation modal and deletes on confirm.", () => { it("Delete button opens confirmation modal and deletes on confirm.", () => {
cy.get("img.delete-account").first().click(); cy.get("img.delete-account").first().click();
cy.contains("ARE YOU SURE TO DELETE").should("be.visible"); cy.contains("ARE YOU SURE TO DELETE").should("be.visible");
cy.get("#sure_to_delete_acct_btn").click(); cy.get("#sure_to_delete_acct_btn").click();
cy.wait("@deleteUser");
cy.contains("Account deleted").should("be.visible"); cy.contains("Account deleted").should("be.visible");
}); });

View File

@@ -13,20 +13,17 @@ describe("Edit an account", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
}); });
it("Edit an account; modify name and see saved message.", () => { it("Edit an account; modify name and see saved message.", () => {
cy.get(".btn-edit").first().click(); cy.get(".btn-edit").first().click();
cy.wait("@getUserDetail");
cy.contains("h1", MODAL_TITLE_ACCOUNT_EDIT).should("exist"); cy.contains("h1", MODAL_TITLE_ACCOUNT_EDIT).should("exist");
cy.get("#input_name_field").clear(); cy.get("#input_name_field").clear();
cy.get("#input_name_field").type("Updated Name"); cy.get("#input_name_field").type("Updated Name");
cy.contains("button", "Confirm").should("be.visible").and("be.enabled"); cy.contains("button", "Confirm").should("be.visible").and("be.enabled");
cy.contains("button", "Confirm").click(); cy.contains("button", "Confirm").click();
cy.wait("@putUser");
cy.contains(MSG_ACCOUNT_EDITED).should("be.visible"); cy.contains(MSG_ACCOUNT_EDITED).should("be.visible");
}); });
}); });

View File

@@ -9,7 +9,7 @@ describe("Account Management CRUD", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
}); });
it("shows Create New button", () => { it("shows Create New button", () => {

View File

@@ -9,7 +9,7 @@ describe("Account Info Modal", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
}); });
it("double-click username opens info modal with user data", () => { it("double-click username opens info modal with user data", () => {

View File

@@ -11,7 +11,7 @@ describe("Compare", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.contains("li", "COMPARE").click(); cy.contains("li", "COMPARE").click();
}); });
@@ -55,7 +55,6 @@ describe("Compare", () => {
cy.get("#compareFile0").drag("#primaryDragCard"); cy.get("#compareFile0").drag("#primaryDragCard");
cy.get("#compareFile1").drag("#secondaryDragCard"); cy.get("#compareFile1").drag("#secondaryDragCard");
cy.contains("button", "Compare").click(); cy.contains("button", "Compare").click();
cy.wait("@getCompare");
cy.url().should("include", "compare"); cy.url().should("include", "compare");
// Assert chart title spans are visible // Assert chart title spans are visible
@@ -73,7 +72,6 @@ describe("Compare", () => {
cy.get("#compareFile0").drag("#primaryDragCard"); cy.get("#compareFile0").drag("#primaryDragCard");
cy.get("#compareFile1").drag("#secondaryDragCard"); cy.get("#compareFile1").drag("#secondaryDragCard");
cy.contains("button", "Compare").click(); cy.contains("button", "Compare").click();
cy.wait("@getCompare");
cy.get("#compareState").should("exist").and("be.visible"); cy.get("#compareState").should("exist").and("be.visible");
}); });
@@ -82,7 +80,6 @@ describe("Compare", () => {
cy.get("#compareFile0").drag("#primaryDragCard"); cy.get("#compareFile0").drag("#primaryDragCard");
cy.get("#compareFile1").drag("#secondaryDragCard"); cy.get("#compareFile1").drag("#secondaryDragCard");
cy.contains("button", "Compare").click(); cy.contains("button", "Compare").click();
cy.wait("@getCompare");
cy.get("aside").should("exist"); cy.get("aside").should("exist");
cy.get("aside li").should("have.length.greaterThan", 0); cy.get("aside li").should("have.length.greaterThan", 0);

View File

@@ -9,7 +9,7 @@ describe("Discover Conformance Page", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/discover/log/297310264/conformance"); cy.visit("/discover/log/297310264/conformance");
cy.wait("@getLogCheckParams"); cy.get(".p-radiobutton, [class*=conformance]").first().should("exist");
}); });
it("page loads and loading overlay disappears", () => { it("page loads and loading overlay disappears", () => {

View File

@@ -9,7 +9,7 @@ describe("Discover Map Page", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/discover/log/297310264/map"); cy.visit("/discover/log/297310264/map");
cy.wait("@getDiscover"); cy.get("#cy").should("exist");
}); });
it("page loads and cytoscape container exists", () => { it("page loads and cytoscape container exists", () => {

View File

@@ -9,7 +9,7 @@ describe("Discover Performance Page", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/discover/log/297310264/performance"); cy.visit("/discover/log/297310264/performance");
cy.wait("@getPerformance"); cy.get(".chart-container, canvas").should("exist");
}); });
it("page loads and loading overlay disappears", () => { it("page loads and loading overlay disappears", () => {

View File

@@ -13,7 +13,7 @@ describe("Discover Tab Navigation", () => {
describe("navigating from Map page", () => { describe("navigating from Map page", () => {
beforeEach(() => { beforeEach(() => {
cy.visit("/discover/log/297310264/map"); cy.visit("/discover/log/297310264/map");
cy.wait("@getDiscover"); cy.get("#cy").should("exist");
}); });
it("shows DISCOVER heading and MAP/CONFORMANCE/PERFORMANCE tabs", () => { it("shows DISCOVER heading and MAP/CONFORMANCE/PERFORMANCE tabs", () => {
@@ -27,7 +27,6 @@ describe("Discover Tab Navigation", () => {
it("clicking PERFORMANCE tab navigates to performance page", () => { it("clicking PERFORMANCE tab navigates to performance page", () => {
cy.get(".nav-item").contains("PERFORMANCE").click(); cy.get(".nav-item").contains("PERFORMANCE").click();
cy.url().should("include", "/performance"); cy.url().should("include", "/performance");
cy.wait("@getPerformance");
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist"); cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
cy.contains("Time Usage").should("be.visible"); cy.contains("Time Usage").should("be.visible");
}); });
@@ -35,7 +34,6 @@ describe("Discover Tab Navigation", () => {
it("clicking CONFORMANCE tab navigates to conformance page", () => { it("clicking CONFORMANCE tab navigates to conformance page", () => {
cy.get(".nav-item").contains("CONFORMANCE").click(); cy.get(".nav-item").contains("CONFORMANCE").click();
cy.url().should("include", "/conformance"); cy.url().should("include", "/conformance");
cy.wait("@getLogCheckParams");
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist"); cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
cy.contains("Rule Settings").should("be.visible"); cy.contains("Rule Settings").should("be.visible");
}); });
@@ -49,21 +47,19 @@ describe("Discover Tab Navigation", () => {
describe("navigating from Performance page", () => { describe("navigating from Performance page", () => {
beforeEach(() => { beforeEach(() => {
cy.visit("/discover/log/297310264/performance"); cy.visit("/discover/log/297310264/performance");
cy.wait("@getPerformance"); cy.get(".chart-container, canvas").should("exist");
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist"); cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
}); });
it("clicking MAP tab navigates to map page", () => { it("clicking MAP tab navigates to map page", () => {
cy.get(".nav-item").contains("MAP").click(); cy.get(".nav-item").contains("MAP").click();
cy.url().should("include", "/map"); cy.url().should("include", "/map");
cy.wait("@getDiscover");
cy.get("#cy").should("exist"); cy.get("#cy").should("exist");
}); });
it("clicking CONFORMANCE tab navigates to conformance page", () => { it("clicking CONFORMANCE tab navigates to conformance page", () => {
cy.get(".nav-item").contains("CONFORMANCE").click(); cy.get(".nav-item").contains("CONFORMANCE").click();
cy.url().should("include", "/conformance"); cy.url().should("include", "/conformance");
cy.wait("@getLogCheckParams");
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist"); cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
cy.contains("Rule Settings").should("be.visible"); cy.contains("Rule Settings").should("be.visible");
}); });
@@ -72,21 +68,19 @@ describe("Discover Tab Navigation", () => {
describe("navigating from Conformance page", () => { describe("navigating from Conformance page", () => {
beforeEach(() => { beforeEach(() => {
cy.visit("/discover/log/297310264/conformance"); cy.visit("/discover/log/297310264/conformance");
cy.wait("@getLogCheckParams"); cy.get(".p-radiobutton, [class*=conformance]").first().should("exist");
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist"); cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
}); });
it("clicking MAP tab navigates to map page", () => { it("clicking MAP tab navigates to map page", () => {
cy.get(".nav-item").contains("MAP").click(); cy.get(".nav-item").contains("MAP").click();
cy.url().should("include", "/map"); cy.url().should("include", "/map");
cy.wait("@getDiscover");
cy.get("#cy").should("exist"); cy.get("#cy").should("exist");
}); });
it("clicking PERFORMANCE tab navigates to performance page", () => { it("clicking PERFORMANCE tab navigates to performance page", () => {
cy.get(".nav-item").contains("PERFORMANCE").click(); cy.get(".nav-item").contains("PERFORMANCE").click();
cy.url().should("include", "/performance"); cy.url().should("include", "/performance");
cy.wait("@getPerformance");
cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist"); cy.get(String.raw`.z-\[9999\]`, { timeout: 10000 }).should("not.exist");
cy.contains("Time Usage").should("be.visible"); cy.contains("Time Usage").should("be.visible");
}); });

View File

@@ -9,13 +9,18 @@ describe("Edge Cases", () => {
describe("Empty states", () => { describe("Empty states", () => {
it("files page handles empty file list", () => { it("files page handles empty file list", () => {
loginWithFixtures(); loginWithFixtures();
// Override files intercept with empty array // Visit any page first to load the app and MSW
cy.intercept("GET", "/api/files", { cy.visit("/files");
statusCode: 200, cy.contains("sample-process.xes").should("exist");
body: [], // Override files endpoint with empty array via MSW
}).as("getEmptyFiles"); cy.window().then((win) => {
const { http, HttpResponse } = win.__msw__;
win.__mswWorker__.use(
http.get("/api/files", () => HttpResponse.json([])),
);
});
// Revisit to trigger the new empty response
cy.visit("/files"); cy.visit("/files");
cy.wait("@getEmptyFiles");
// Table should exist but have no file data // Table should exist but have no file data
cy.get("table").should("exist"); cy.get("table").should("exist");
cy.contains("sample-process.xes").should("not.exist"); cy.contains("sample-process.xes").should("not.exist");
@@ -23,20 +28,36 @@ describe("Edge Cases", () => {
it("account admin handles empty user list", () => { it("account admin handles empty user list", () => {
loginWithFixtures(); loginWithFixtures();
cy.intercept("GET", "/api/users", { cy.visit("/files");
statusCode: 200, cy.contains("sample-process.xes").should("exist");
body: [], // Override users endpoint with empty array via MSW
}).as("getEmptyUsers"); cy.window().then((win) => {
const { http, HttpResponse } = win.__msw__;
win.__mswWorker__.use(
http.get("/api/users", () => HttpResponse.json([])),
);
});
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getEmptyUsers"); // Create New button should exist even with no users
// Create New button should still work
cy.get("#create_new_acct_btn").should("exist"); cy.get("#create_new_acct_btn").should("exist");
cy.contains("Test Admin").should("not.exist");
}); });
});
describe("Authentication guard", () => {
it("unauthenticated user is redirected to login", () => { it("unauthenticated user is redirected to login", () => {
// No loginWithFixtures - not logged in loginWithFixtures();
cy.visit("/files");
cy.contains("sample-process.xes").should("exist");
// Override my-account to return 401
cy.window().then((win) => {
const { http, HttpResponse } = win.__msw__;
win.__mswWorker__.use(
http.get("/api/my-account", () =>
new HttpResponse(null, { status: 401 }),
),
);
});
// Clear cookies to simulate logged out
cy.clearCookies();
cy.visit("/files"); cy.visit("/files");
cy.url().should("include", "/login"); cy.url().should("include", "/login");
}); });
@@ -54,41 +75,52 @@ describe("Edge Cases", () => {
describe("Login validation", () => { describe("Login validation", () => {
it("shows error on failed login", () => { it("shows error on failed login", () => {
cy.intercept("POST", "/api/oauth/token", { // Visit login page first to load the app
statusCode: 401,
body: { detail: "Invalid credentials" },
}).as("failedLogin");
cy.visit("/login"); cy.visit("/login");
cy.get("#account").type("wrong"); cy.get("#login_btn_main_btn").should("exist");
cy.get("#password").type("wrong"); // Override token endpoint to return 401
cy.get("form").submit(); cy.window().then((win) => {
cy.wait("@failedLogin"); const { http, HttpResponse } = win.__msw__;
// Should stay on login page win.__mswWorker__.use(
http.post("/api/oauth/token", () =>
HttpResponse.json(
{ detail: "Invalid credentials" },
{ status: 401 },
),
),
);
});
cy.get("#account").type("wronguser");
cy.get("#password").type("wrongpass");
cy.get("#login_btn_main_btn").click();
cy.url().should("include", "/login"); cy.url().should("include", "/login");
}); });
});
describe("Account creation validation", () => {
beforeEach(() => {
loginWithFixtures();
cy.visit("/account-admin");
cy.wait("@getUsers");
cy.get("#create_new_acct_btn").click();
});
it("confirm stays disabled with only account field filled", () => { it("confirm stays disabled with only account field filled", () => {
cy.get("#input_account_field").type("newuser"); loginWithFixtures();
cy.get(".confirm-btn").should("be.disabled"); cy.visit("/account-admin");
cy.contains("Test Admin").should("exist");
cy.contains("button", "Create New").click();
cy.get("#input_account_field").type("onlyaccount");
cy.contains("button", "Confirm").should("be.disabled");
}); });
it("confirm stays disabled with only name field filled", () => { it("confirm stays disabled with only name field filled", () => {
cy.get("#input_name_field").type("New User"); loginWithFixtures();
cy.get(".confirm-btn").should("be.disabled"); cy.visit("/account-admin");
cy.contains("Test Admin").should("exist");
cy.contains("button", "Create New").click();
cy.get("#input_name_field").type("onlyname");
cy.contains("button", "Confirm").should("be.disabled");
}); });
it("confirm stays disabled with only password field filled", () => { it("confirm stays disabled with only password field filled", () => {
cy.get("#input_first_pwd").type("password1234"); loginWithFixtures();
cy.get(".confirm-btn").should("be.disabled"); cy.visit("/account-admin");
cy.contains("Test Admin").should("exist");
cy.contains("button", "Create New").click();
cy.get("#input_first_pwd").type("onlypassword");
cy.contains("button", "Confirm").should("be.disabled");
}); });
}); });
}); });

View File

@@ -9,7 +9,7 @@ describe("File Operations", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
}); });
it("file list table has sortable columns", () => { it("file list table has sortable columns", () => {

View File

@@ -12,7 +12,7 @@ describe("Files Page", () => {
}); });
it("displays the file list after login", () => { it("displays the file list after login", () => {
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.contains("h2", "All Files").should("exist"); cy.contains("h2", "All Files").should("exist");
// Should display file names from fixture // Should display file names from fixture
cy.contains("sample-process.xes").should("exist"); cy.contains("sample-process.xes").should("exist");
@@ -21,37 +21,37 @@ describe("Files Page", () => {
}); });
it("shows Recently Used section", () => { it("shows Recently Used section", () => {
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.contains("h2", "Recently Used").should("exist"); cy.contains("h2", "Recently Used").should("exist");
}); });
it("switches to DISCOVER tab", () => { it("switches to DISCOVER tab", () => {
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.contains(".nav-item", "DISCOVER").click(); cy.contains(".nav-item", "DISCOVER").click();
// DISCOVER tab shows filtered file types // DISCOVER tab shows filtered file types
cy.contains("h2", "All Files").should("exist"); cy.contains("h2", "All Files").should("exist");
}); });
it("switches to COMPARE tab and shows drag zones", () => { it("switches to COMPARE tab and shows drag zones", () => {
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.contains(".nav-item", "COMPARE").click(); cy.contains(".nav-item", "COMPARE").click();
cy.contains("Performance Comparison").should("exist"); cy.contains("Performance Comparison").should("exist");
cy.contains("Drag and drop a file here").should("exist"); cy.contains("Drag and drop a file here").should("exist");
}); });
it("shows Import button on FILES tab", () => { it("shows Import button on FILES tab", () => {
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.get("#import_btn").should("contain", "Import"); cy.get("#import_btn").should("contain", "Import");
}); });
it("can switch between list and grid view", () => { it("can switch between list and grid view", () => {
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
// DataTable (list view) should be visible by default // DataTable (list view) should be visible by default
cy.get("table").should("exist"); cy.get("table").should("exist");
}); });
it("double-click file navigates to discover page", () => { it("double-click file navigates to discover page", () => {
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
// Double-click the first file row in the table // Double-click the first file row in the table
// The actual route depends on file type (log→map, log-check→conformance, etc.) // The actual route depends on file type (log→map, log-check→conformance, etc.)
cy.get("table tbody tr").first().dblclick(); cy.get("table tbody tr").first().dblclick();

View File

@@ -9,7 +9,7 @@ describe("Files Page - COMPARE Tab", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
// Switch to COMPARE tab // Switch to COMPARE tab
cy.contains("li", "COMPARE").click(); cy.contains("li", "COMPARE").click();
}); });

View File

@@ -9,7 +9,7 @@ describe("Files to Discover Entry Flow", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
}); });
describe("double-click table row to enter Discover", () => { describe("double-click table row to enter Discover", () => {
@@ -17,14 +17,12 @@ describe("Files to Discover Entry Flow", () => {
// Target the Name column (has class .fileName) to avoid matching Dependency column // Target the Name column (has class .fileName) to avoid matching Dependency column
cy.contains("td.fileName", "sample-process.xes").parent("tr").dblclick(); cy.contains("td.fileName", "sample-process.xes").parent("tr").dblclick();
cy.url().should("include", "/discover/log/1/map"); cy.url().should("include", "/discover/log/1/map");
cy.wait("@getDiscover");
cy.get("#cy").should("exist"); cy.get("#cy").should("exist");
}); });
it("double-click filter file navigates to Map page", () => { it("double-click filter file navigates to Map page", () => {
cy.contains("td.fileName", "filtered-sample").parent("tr").dblclick(); cy.contains("td.fileName", "filtered-sample").parent("tr").dblclick();
cy.url().should("include", "/discover/filter/10/map"); cy.url().should("include", "/discover/filter/10/map");
cy.wait("@getFilterDiscover");
cy.get("#cy").should("exist"); cy.get("#cy").should("exist");
}); });
}); });
@@ -39,7 +37,6 @@ describe("Files to Discover Entry Flow", () => {
// Use last() to target the All Files grid section (not Recently Used) // Use last() to target the All Files grid section (not Recently Used)
cy.get('li[title="sample-process.xes"]').last().dblclick(); cy.get('li[title="sample-process.xes"]').last().dblclick();
cy.url().should("include", "/discover/log/1/map"); cy.url().should("include", "/discover/log/1/map");
cy.wait("@getDiscover");
cy.get("#cy").should("exist"); cy.get("#cy").should("exist");
}); });
}); });

View File

@@ -40,23 +40,29 @@ describe("Login Flow", () => {
cy.get("#password").type("password123"); cy.get("#password").type("password123");
cy.get("#login_btn_main_btn").click(); cy.get("#login_btn_main_btn").click();
cy.wait("@postToken");
cy.url().should("include", "/files"); cy.url().should("include", "/files");
}); });
it("failed login shows error message", () => { it("failed login shows error message", () => {
// Override the token intercept to return 401 // Visit login first to load app + MSW
cy.intercept("POST", "/api/oauth/token", {
statusCode: 401,
body: { detail: "Incorrect username or password" },
}).as("postTokenFail");
cy.visit("/login"); cy.visit("/login");
cy.get("#login_btn_main_btn").should("exist");
// Override the token endpoint to return 401 via MSW
cy.window().then((win) => {
const { http, HttpResponse } = win.__msw__;
win.__mswWorker__.use(
http.post("/api/oauth/token", () =>
HttpResponse.json(
{ detail: "Incorrect username or password" },
{ status: 401 },
)),
);
});
cy.get("#account").type("wronguser"); cy.get("#account").type("wronguser");
cy.get("#password").type("wrongpass"); cy.get("#password").type("wrongpass");
cy.get("#login_btn_main_btn").click(); cy.get("#login_btn_main_btn").click();
cy.wait("@postTokenFail");
cy.contains("Incorrect account or password").should("be.visible"); cy.contains("Incorrect account or password").should("be.visible");
}); });

View File

@@ -12,7 +12,7 @@ describe("Logout Flow", () => {
it("shows account menu when head icon is clicked", () => { it("shows account menu when head icon is clicked", () => {
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
// Click the head icon to open account menu // Click the head icon to open account menu
cy.get("#acct_mgmt_button").click(); cy.get("#acct_mgmt_button").click();
@@ -22,7 +22,7 @@ describe("Logout Flow", () => {
it("account menu shows admin management link for admin user", () => { it("account menu shows admin management link for admin user", () => {
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.get("#acct_mgmt_button").click(); cy.get("#acct_mgmt_button").click();
cy.get("#account_menu").should("be.visible"); cy.get("#account_menu").should("be.visible");
@@ -32,7 +32,7 @@ describe("Logout Flow", () => {
it("account menu has logout button", () => { it("account menu has logout button", () => {
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.get("#acct_mgmt_button").click(); cy.get("#acct_mgmt_button").click();
cy.get("#btn_logout_in_menu").should("exist"); cy.get("#btn_logout_in_menu").should("exist");
@@ -40,7 +40,7 @@ describe("Logout Flow", () => {
it("clicking My Account navigates to /my-account", () => { it("clicking My Account navigates to /my-account", () => {
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.get("#acct_mgmt_button").click(); cy.get("#acct_mgmt_button").click();
cy.get("#btn_mang_ur_acct").click(); cy.get("#btn_mang_ur_acct").click();
@@ -49,7 +49,7 @@ describe("Logout Flow", () => {
it("clicking Account Management navigates to /account-admin", () => { it("clicking Account Management navigates to /account-admin", () => {
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.get("#acct_mgmt_button").click(); cy.get("#acct_mgmt_button").click();
cy.get("#btn_acct_mgmt").click(); cy.get("#btn_acct_mgmt").click();
@@ -58,7 +58,7 @@ describe("Logout Flow", () => {
it("logout redirects to login page", () => { it("logout redirects to login page", () => {
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.get("#acct_mgmt_button").click(); cy.get("#acct_mgmt_button").click();
cy.get("#btn_logout_in_menu").click(); cy.get("#btn_logout_in_menu").click();

View File

@@ -9,7 +9,7 @@ describe("My Account Page", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/my-account"); cy.visit("/my-account");
cy.wait("@getUserDetail"); cy.contains("Test Admin").should("exist");
}); });
it("displays user name heading", () => { it("displays user name heading", () => {

View File

@@ -21,7 +21,7 @@ describe("Navigation and Routing", () => {
it("navbar shows correct view name", () => { it("navbar shows correct view name", () => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
cy.get("#nav_bar").should("exist"); cy.get("#nav_bar").should("exist");
cy.get("#nav_bar h2").should("contain", "FILES"); cy.get("#nav_bar h2").should("contain", "FILES");
}); });

View File

@@ -10,7 +10,7 @@ describe("Discover page navigation tabs", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
}); });
it("Double-clicking a log file enters the MAP page.", () => { it("Double-clicking a log file enters the MAP page.", () => {

View File

@@ -20,7 +20,6 @@ describe("Paste URL login redirect", () => {
cy.get("#account").type("testadmin"); cy.get("#account").type("testadmin");
cy.get("#password").type("password123"); cy.get("#password").type("password123");
cy.get("form").submit(); cy.get("form").submit();
cy.wait("@postToken");
// After login, the app should attempt to redirect to the return-to URL. // After login, the app should attempt to redirect to the return-to URL.
// Since window.location.href is used (not router.push), we verify the // Since window.location.href is used (not router.push), we verify the
@@ -35,18 +34,25 @@ describe("Paste URL login redirect", () => {
cy.get("#account").type("testadmin"); cy.get("#account").type("testadmin");
cy.get("#password").type("password123"); cy.get("#password").type("password123");
cy.get("form").submit(); cy.get("form").submit();
cy.wait("@postToken");
cy.url().should("include", "/files"); cy.url().should("include", "/files");
}); });
it("Unauthenticated user cannot access inner pages", () => { it("Unauthenticated user cannot access inner pages", () => {
setupApiIntercepts(); // Visit login first to load the app + MSW
// Override my-account to return 401 (simulate logged-out state) cy.visit("/login");
cy.intercept("GET", "/api/my-account", { cy.get("#login_btn_main_btn").should("exist");
statusCode: 401, // Override my-account to return 401 (simulate logged-out state) via MSW
body: { detail: "Not authenticated" }, cy.window().then((win) => {
}).as("getMyAccountUnauth"); const { http, HttpResponse } = win.__msw__;
win.__mswWorker__.use(
http.get("/api/my-account", () =>
HttpResponse.json(
{ detail: "Not authenticated" },
{ status: 401 },
)),
);
});
cy.visit("/files"); cy.visit("/files");

View File

@@ -10,7 +10,7 @@ describe("SweetAlert2 Modals", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
}); });
it("right-click on table row shows context menu with Rename", () => { it("right-click on table row shows context menu with Rename", () => {
@@ -79,7 +79,7 @@ describe("SweetAlert2 Modals", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/files"); cy.visit("/files");
cy.wait("@getFiles"); cy.contains("sample-process.xes").should("exist");
// Switch to grid view // Switch to grid view
cy.get("svg").parent("li.cursor-pointer").last().click(); cy.get("svg").parent("li.cursor-pointer").last().click();
}); });
@@ -102,15 +102,15 @@ describe("SweetAlert2 Modals", () => {
beforeEach(() => { beforeEach(() => {
loginWithFixtures(); loginWithFixtures();
cy.visit("/account-admin"); cy.visit("/account-admin");
cy.wait("@getUsers"); cy.contains("Test Admin").should("exist");
}); });
it("delete confirmation Yes button triggers delete API", () => { it("delete confirmation Yes button triggers delete API", () => {
cy.get(".delete-account").first().click(); cy.get(".delete-account").first().click();
cy.get("#modal_container").should("be.visible"); cy.get("#modal_container").should("be.visible");
cy.get("#sure_to_delete_acct_btn").click(); cy.get("#sure_to_delete_acct_btn").click();
cy.wait("@deleteUser");
// Modal should close after deletion // Modal should close after deletion
cy.get("#modal_container").should("not.exist");
}); });
}); });
}); });

View File

@@ -4,187 +4,18 @@
// imacat.yang@dsp.im (imacat), 2026/03/05 // imacat.yang@dsp.im (imacat), 2026/03/05
/** /**
* Sets up cy.intercept for all API endpoints using fixture files. * API mocking is now handled by MSW (Mock Service Worker).
* Call setupApiIntercepts() in beforeEach to mock the entire backend. * This function is kept for backward compatibility but does nothing.
*/ */
export function setupApiIntercepts() { export function setupApiIntercepts() {
// Auth // MSW handles all API interception via service worker.
cy.intercept("POST", "/api/oauth/token", {
fixture: "api/token.json",
}).as("postToken");
// User account
cy.intercept("GET", "/api/my-account", {
fixture: "api/my-account.json",
}).as("getMyAccount");
cy.intercept("PUT", "/api/my-account", {
statusCode: 200,
body: { success: true },
}).as("putMyAccount");
// Files
cy.intercept("GET", "/api/files", {
fixture: "api/files.json",
}).as("getFiles");
// Users (account management)
cy.intercept("GET", "/api/users", {
fixture: "api/users.json",
}).as("getUsers");
cy.intercept("POST", "/api/users", {
statusCode: 200,
body: { success: true },
}).as("postUser");
cy.intercept("DELETE", "/api/users/*", {
statusCode: 200,
body: { success: true },
}).as("deleteUser");
cy.intercept("PUT", "/api/users/*", {
statusCode: 200,
body: { success: true },
}).as("putUser");
// User detail (GET /api/users/:username)
cy.intercept("GET", "/api/users/*", {
fixture: "api/user-detail.json",
}).as("getUserDetail");
// User roles
cy.intercept("PUT", "/api/users/*/roles/*", {
statusCode: 200,
body: { success: true },
}).as("putUserRole");
cy.intercept("DELETE", "/api/users/*/roles/*", {
statusCode: 200,
body: { success: true },
}).as("deleteUserRole");
// Filter detail (for fetchFunnel when entering filter from Files)
cy.intercept("GET", /\/api\/filters\/\d+$/, {
statusCode: 200,
body: { rules: [], log: { id: 1 }, name: "filtered-sample" },
}).as("getFilterDetail");
// Discover (map data)
cy.intercept("GET", "/api/logs/*/discover", {
fixture: "api/discover.json",
}).as("getDiscover");
cy.intercept("GET", "/api/filters/*/discover", {
fixture: "api/discover.json",
}).as("getFilterDiscover");
// Performance
cy.intercept("GET", "/api/logs/*/performance", {
fixture: "api/performance.json",
}).as("getPerformance");
cy.intercept("GET", "/api/filters/*/performance", {
fixture: "api/performance.json",
}).as("getFilterPerformance");
// Traces
cy.intercept("GET", "/api/logs/*/traces", {
fixture: "api/traces.json",
}).as("getTraces");
cy.intercept("GET", "/api/filters/*/traces", {
fixture: "api/traces.json",
}).as("getFilterTraces");
// Trace detail (must be after traces list intercepts)
cy.intercept("GET", /\/api\/logs\/.*\/traces\/\d+/, {
fixture: "api/trace-detail.json",
}).as("getTraceDetail");
cy.intercept("GET", /\/api\/filters\/.*\/traces\/\d+/, {
fixture: "api/trace-detail.json",
}).as("getFilterTraceDetail");
// Temp filters
cy.intercept("GET", "/api/temp-filters/*/discover", {
fixture: "api/discover.json",
}).as("getTempFilterDiscover");
cy.intercept("GET", "/api/temp-filters/*/traces", {
fixture: "api/traces.json",
}).as("getTempFilterTraces");
// Filter params
cy.intercept("GET", "/api/filters/params*", {
statusCode: 200,
body: {},
}).as("getFilterParams");
cy.intercept("GET", "/api/filters/has-result*", {
statusCode: 200,
body: false,
}).as("getFilterHasResult");
// Conformance check params
cy.intercept("GET", "/api/log-checks/params*", {
fixture: "api/filter-params.json",
}).as("getLogCheckParams");
cy.intercept("GET", "/api/filter-checks/params*", {
fixture: "api/filter-params.json",
}).as("getFilterCheckParams");
// Compare dashboard
cy.intercept("GET", /\/api\/compare\?datasets=/, {
fixture: "api/compare.json",
}).as("getCompare");
// Dependents (for delete confirmation)
cy.intercept("GET", "/api/logs/*/dependents", {
statusCode: 200,
body: [],
}).as("getLogDependents");
cy.intercept("GET", "/api/filters/*/dependents", {
statusCode: 200,
body: [],
}).as("getFilterDependents");
cy.intercept("GET", "/api/log-checks/*/dependents", {
statusCode: 200,
body: [],
}).as("getLogCheckDependents");
cy.intercept("GET", "/api/filter-checks/*/dependents", {
statusCode: 200,
body: [],
}).as("getFilterCheckDependents");
// Rename
cy.intercept("PUT", "/api/logs/*/rename", {
statusCode: 200,
body: { success: true },
}).as("renameLog");
cy.intercept("PUT", "/api/filters/*/rename", {
statusCode: 200,
body: { success: true },
}).as("renameFilter");
// Deletion
cy.intercept("DELETE", "/api/deletion/*", {
statusCode: 200,
body: { success: true },
}).as("deleteDeletion");
} }
/** /**
* Sets the luciaToken cookie and isLuciaLoggedIn cookie to simulate * Sets the luciaToken cookie and isLuciaLoggedIn cookie to simulate
* a logged-in state, then sets up all API intercepts. * a logged-in state. API interception is handled by MSW.
*/ */
export function loginWithFixtures() { export function loginWithFixtures() {
setupApiIntercepts();
cy.setCookie("luciaToken", "fake-access-token-for-testing"); cy.setCookie("luciaToken", "fake-access-token-for-testing");
cy.setCookie("isLuciaLoggedIn", "true"); cy.setCookie("isLuciaLoggedIn", "true");
} }

View File

@@ -104,8 +104,12 @@ app.directive("tooltip", Tooltip);
*/ */
async function enableMocking() { async function enableMocking() {
if (import.meta.env.VITE_MSW !== "true") return; if (import.meta.env.VITE_MSW !== "true") return;
const { http, HttpResponse } = await import("msw");
const { worker } = await import("./mocks/browser.js"); const { worker } = await import("./mocks/browser.js");
await worker.start({ onUnhandledRequest: "bypass" }); await worker.start({ onUnhandledRequest: "bypass" });
// Expose on window so Cypress can add per-test overrides
window.__mswWorker__ = worker;
window.__msw__ = { http, HttpResponse };
} }
enableMocking().then(() => app.mount("#app")); enableMocking().then(() => app.mount("#app"));