test: add regression and browser contracts

This commit is contained in:
leefer
2026-07-22 23:19:50 +08:00
parent ca7e752def
commit 835bb947d9
5 changed files with 281 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
const { test, expect } = require("@playwright/test");
const dashboard = {
meta: {
trade_date: "2026-07-22",
requested_date: "2026-07-22",
source: "tushare",
realtime: false,
cached: true,
market_status: "closed",
updated_at: "2026-07-22T15:00:00+08:00",
},
overview: {
up_count: 2100,
down_count: 2800,
limit_up_count: 42,
limit_down_count: 8,
broken_count: 17,
seal_rate: 71.2,
amount_billion: 12600,
sentiment_score: 48,
},
limits: [],
broken: [],
down_limits: [],
yesterday_limits: [],
limit_performance: [],
ladders: [],
sectors: [],
sector_rotation: [],
};
function session(role = "admin", subscribed = true) {
return {
authenticated: true,
csrf_token: "test-csrf",
user: {
id: role === "admin" ? 1 : 2,
username: role === "admin" ? "admin_user" : "normal_user",
role,
membership: {
active: role === "admin" || subscribed,
subscribed,
is_admin: role === "admin",
},
},
};
}
async function mockApplication(page, authSession = session()) {
await page.route("**/api/**", async (route) => {
const url = new URL(route.request().url());
let payload = { ok: true };
if (url.pathname === "/api/auth/me") payload = authSession;
else if (url.pathname === "/api/dashboard") payload = dashboard;
else if (url.pathname === "/api/watchlist" || url.pathname === "/api/notes") payload = { items: [] };
else if (url.pathname === "/api/search") payload = { groups: { stocks: [], sectors: [], themes: [], indices: [] } };
else if (url.pathname === "/api/dragon-tiger") {
payload = {
meta: { trade_date: "2026-07-22", requested_date: "2026-07-22", status: "empty", source: "tushare" },
summary: {},
traders: [],
unclassified_seats: [],
};
} else if (url.pathname === "/api/sentiment/history") payload = { rows: [], components: [] };
else if (url.pathname === "/api/rotation/history") payload = { days: [], rows: [], sectors: [] };
else if (url.pathname === "/api/screener/setup") {
payload = {
trade_date: "20260722",
regime: { id: "repair", label: "修复", confidence: 70, reason: "测试", evidence: [] },
regimes: [{ id: "repair", label: "修复" }],
factor_data: { ready: false, date_count: 0 },
llm: { configured: true },
strategies: [],
};
} else if (url.pathname === "/api/mentors/setup") payload = { trade_date: "20260722", mentors: [] };
else if (url.pathname === "/api/heaven/setup") {
await route.fulfill({ status: 503, contentType: "application/json", body: JSON.stringify({ error: "测试环境不加载问天数据" }) });
return;
}
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(payload) });
});
}
test("admin shell opens every primary workspace and global search", async ({ page }) => {
await mockApplication(page);
await page.goto("/index.html");
await expect(page.locator("#authGate")).toBeHidden();
await expect(page.locator("#settingsButton")).toBeVisible();
await expect(page.locator("#syncButton")).toBeVisible();
const views = [
"sentimentCycleView", "limitPool", "brokenView", "downView", "yesterdayView",
"performanceView", "ladderView", "rotationView", "dragonView", "screenerView",
"mentorView", "heavenView", "reviewWorkspaceView",
];
for (const view of views) {
await page.locator(`[data-view="${view}"]`).first().click();
await expect(page.locator(`#${view}`)).toHaveClass(/active-view/);
}
await page.keyboard.press("Control+K");
await expect(page.locator("#globalSearchDialog")).toBeVisible();
await expect(page.locator("#globalSearchInput")).toBeFocused();
});
test("regular account cannot see admin controls and member features are gated", async ({ page }) => {
await mockApplication(page, session("user", false));
await page.goto("/index.html");
await expect(page.locator("#settingsButton")).toBeHidden();
await expect(page.locator("#syncButton")).toBeHidden();
await expect(page.locator("#accountVipLabel")).toHaveText("非会员");
await page.locator('[data-view="screenerView"]').first().click();
await expect(page.locator("#screenerView .member-gate")).toBeVisible();
await expect(page.locator("#screenerRunButton")).toBeDisabled();
});
test("mobile shell stays within the viewport", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await mockApplication(page, session("user", true));
await page.goto("/index.html");
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
expect(overflow).toBeLessThanOrEqual(1);
await expect(page.locator("#globalSearchButton")).toBeVisible();
});