- Fix mock-api data shapes to match actual Vue component interfaces - Replace HeadlessUI TransitionRoot with v-if in SetupView (unmount fix) - Restructure CookiesView to detail-replaces-list pattern (strict mode) - Add ARIA attributes for Playwright selectors (role=switch, aria-label) - Fix 401 interceptor to skip login endpoint redirects - Add confirmation dialogs, error states, and missing UI fields - Rename conflicting button/label text to avoid strict mode violations Co-Authored-By: Paperclip <noreply@paperclip.ing>
165 lines
4.4 KiB
TypeScript
165 lines
4.4 KiB
TypeScript
import { type Page } from "@playwright/test";
|
|
|
|
/**
|
|
* Intercept /admin/dashboard and return a canned response so UI tests
|
|
* don't depend on a running relay server with real data.
|
|
*/
|
|
export async function mockDashboard(page: Page): Promise<void> {
|
|
await page.route("**/admin/dashboard", (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
totalDevices: 3,
|
|
onlineDevices: 2,
|
|
totalCookies: 142,
|
|
uniqueDomains: 8,
|
|
connections: 2,
|
|
syncCount: 57,
|
|
uptimeSeconds: 86400,
|
|
}),
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Intercept /admin/cookies and return a paginated list.
|
|
*/
|
|
export async function mockCookies(page: Page): Promise<void> {
|
|
await page.route("**/admin/cookies*", (route) => {
|
|
if (route.request().method() === "DELETE") {
|
|
return route.fulfill({ status: 200, contentType: "application/json", body: "{}" });
|
|
}
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
cookies: [
|
|
{
|
|
id: "c1",
|
|
deviceId: "dev-001",
|
|
domain: "example.com",
|
|
cookieName: "session",
|
|
path: "/",
|
|
ciphertext: "encrypted-abc123",
|
|
nonce: "nonce1",
|
|
lamportTs: 1,
|
|
updatedAt: "2026-03-01T00:00:00Z",
|
|
expires: "2027-01-01T00:00:00Z",
|
|
secure: true,
|
|
httpOnly: true,
|
|
},
|
|
{
|
|
id: "c2",
|
|
deviceId: "dev-001",
|
|
domain: "example.com",
|
|
cookieName: "pref",
|
|
path: "/",
|
|
ciphertext: "encrypted-dark",
|
|
nonce: "nonce2",
|
|
lamportTs: 2,
|
|
updatedAt: "2026-03-02T00:00:00Z",
|
|
expires: "2027-06-01T00:00:00Z",
|
|
secure: false,
|
|
httpOnly: false,
|
|
},
|
|
{
|
|
id: "c3",
|
|
deviceId: "dev-002",
|
|
domain: "other.io",
|
|
cookieName: "token",
|
|
path: "/",
|
|
ciphertext: "encrypted-xyz",
|
|
nonce: "nonce3",
|
|
lamportTs: 3,
|
|
updatedAt: "2026-03-03T00:00:00Z",
|
|
expires: null,
|
|
secure: true,
|
|
httpOnly: true,
|
|
},
|
|
],
|
|
total: 3,
|
|
page: 1,
|
|
}),
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Intercept /admin/devices and return device list.
|
|
*/
|
|
export async function mockDevices(page: Page): Promise<void> {
|
|
await page.route("**/admin/devices*", (route) => {
|
|
if (route.request().method() !== "GET") return route.continue();
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
devices: [
|
|
{
|
|
deviceId: "d1",
|
|
name: "Chrome on macOS",
|
|
platform: "chrome",
|
|
online: true,
|
|
lastSeen: new Date().toISOString(),
|
|
createdAt: "2026-01-01T00:00:00Z",
|
|
ipAddress: "192.168.1.10",
|
|
extensionVersion: "2.0.0",
|
|
},
|
|
{
|
|
deviceId: "d2",
|
|
name: "Firefox on Windows",
|
|
platform: "firefox",
|
|
online: false,
|
|
lastSeen: "2026-03-15T10:00:00Z",
|
|
createdAt: "2026-02-01T00:00:00Z",
|
|
ipAddress: null,
|
|
extensionVersion: "2.0.0",
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Intercept /admin/settings and return settings object.
|
|
*/
|
|
export async function mockSettings(page: Page): Promise<void> {
|
|
await page.route("**/admin/settings*", (route) => {
|
|
if (route.request().method() === "GET") {
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
autoSync: true,
|
|
syncIntervalMs: 0,
|
|
maxDevices: 10,
|
|
theme: "system",
|
|
sessionTimeoutMinutes: 60,
|
|
language: "zh",
|
|
}),
|
|
});
|
|
}
|
|
return route.fulfill({ status: 200, contentType: "application/json", body: "{}" });
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Simulate a 500 error on the given path — used for error-handling tests.
|
|
*/
|
|
export async function mockAPIError(
|
|
page: Page,
|
|
urlPattern: string,
|
|
status = 500,
|
|
message = "Internal Server Error",
|
|
): Promise<void> {
|
|
await page.route(urlPattern, (route) =>
|
|
route.fulfill({
|
|
status,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ error: message }),
|
|
}),
|
|
);
|
|
}
|