import { expect, test } from "@playwright/test"; const homeH1 = (page: import("@playwright/test").Page) => page.getByRole("heading", { level: 1 }); const loginHeading = (page: import("@playwright/test").Page) => page.getByRole("heading", { name: "登录" }); test.describe("浏览器前进 / 后退触发页面更新", () => { test("软导航后 goBack / goForward 换页", async ({ page }) => { await page.goto("/zh-CN"); await expect(homeH1(page)).toContainText("私有影音库"); await page.getByRole("link", { name: "登录 / 注册" }).first().click(); await expect(page).toHaveURL(/\/zh-CN\/auth\/login/); await expect(loginHeading(page)).toBeVisible(); await page.goBack(); await expect(page).toHaveURL(/\/zh-CN$/); await expect(homeH1(page)).toContainText("私有影音库"); await page.goForward(); await expect(page).toHaveURL(/\/zh-CN\/auth\/login/); await expect(loginHeading(page)).toBeVisible(); }); test("history.state 被抹成 null 后仍能后退换页", async ({ page }) => { await page.goto("/zh-CN"); await expect(homeH1(page)).toContainText("私有影音库"); // 复现旧 bug:replaceState(null) 让 ClientRouter onPopState 直接 return await page.evaluate(() => { window.history.replaceState(null, "", window.location.href); }); await page.locator('a[href="/zh-CN/auth/login"]').first().click(); await expect(page).toHaveURL(/\/zh-CN\/auth\/login/); await page.goBack(); await expect(page).toHaveURL(/\/zh-CN$/); await expect(homeH1(page)).toContainText("私有影音库"); }); test("setQueryParams 式 replaceState 后跨页后退仍换页", async ({ page }) => { await page.goto("/zh-CN/auth/login"); await expect(loginHeading(page)).toBeVisible(); await page.evaluate(() => { const url = new URL(window.location.href); url.searchParams.set("next", "/zh-CN/library"); window.history.replaceState(window.history.state, "", url.toString()); }); await expect(page).toHaveURL(/next=/); await page.locator('a[href="/zh-CN"]').first().click(); await expect(page).toHaveURL(/\/zh-CN$/); await expect(homeH1(page)).toContainText("私有影音库"); await page.goBack(); await expect(page).toHaveURL(/\/zh-CN\/auth\/login/); await expect(loginHeading(page)).toBeVisible(); }); test("绕过守卫写入 null state 后,popstate 兜底仍换页", async ({ page }) => { await page.goto("/zh-CN"); await expect(homeH1(page)).toContainText("私有影音库"); // 用原生 replaceState 直接抹掉 state,验证 popstate 兜底(非补丁路径) await page.evaluate(() => { const proto = History.prototype as History; proto.replaceState.call(window.history, null, "", window.location.href); }); await page.locator('a[href="/zh-CN/auth/login"]').first().click(); await expect(page).toHaveURL(/\/zh-CN\/auth\/login/); await page.goBack(); await expect(page).toHaveURL(/\/zh-CN$/); await expect(homeH1(page)).toContainText("私有影音库"); }); });