afab4e7cba
- 新增 Playwright E2E(治理、登录、投瓶捞瓶回复与实时聊天、举报拉黑与断线恢复)共 9 条用例 - 每个用例前重置数据库、用例后关闭全部 browser context,消除跨用例污染与资源泄漏 - 前端改由 Playwright 托管构建与 preview(关闭 SW);Vite 代理注入 Origin 头以通过接口 Origin 校验 - 根级 vitest 排除 Playwright 用例与需专用配置的 web 用例,避免误收集导致的假失败 - 测试手机号按实测校验结果收敛白名单,避免用例传入会被后端拒绝的号段 验证结果: - Web E2E 9/9 连续两轮通过(含运行前后服务健康检查) - API E2E 146/146 连续两轮通过 - 单测:根级 14 文件/119 用例、web 包 8 文件/31 用例通过 - typecheck、lint、build、依赖审计(audit)均通过
112 lines
4.1 KiB
TypeScript
112 lines
4.1 KiB
TypeScript
import { test, expect } from "./fixtures";
|
|
import { freshAccount, loginViaUi, pickBottle } from "./helpers";
|
|
|
|
test.describe("safety and resilience", () => {
|
|
test("reports and reloads chat history after an offline reconnect", async ({
|
|
browser,
|
|
}) => {
|
|
const aAccount = freshAccount("135");
|
|
const bAccount = freshAccount("134");
|
|
|
|
const ctxA = await browser.newContext();
|
|
const ctxB = await browser.newContext();
|
|
const aPage = await ctxA.newPage();
|
|
const bPage = await ctxB.newPage();
|
|
|
|
await loginViaUi(aPage, aAccount);
|
|
await loginViaUi(bPage, bAccount);
|
|
|
|
// A 投瓶,B 捞瓶并回复建立会话
|
|
await aPage.goto("/throw");
|
|
await aPage
|
|
.getByPlaceholder("写下你想让陌生人看到的话…")
|
|
.fill(`断线恢复瓶子 ${Date.now()}`);
|
|
await aPage.getByRole("button", { name: /扔进海里/ }).click();
|
|
await expect(aPage.getByText(/瓶子已进入审核/)).toBeVisible();
|
|
|
|
await bPage.goto("/pick");
|
|
await pickBottle(bPage);
|
|
await bPage.locator(".bottle-card textarea").fill("离线前的第一条消息");
|
|
await bPage.getByRole("button", { name: /回复并建立会话/ }).click();
|
|
await expect(bPage).toHaveURL(/\/conversations\//, { timeout: 15_000 });
|
|
|
|
// A 进入同一会话
|
|
await aPage.goto("/conversations");
|
|
await aPage.locator(".conversation").first().click();
|
|
await expect(aPage.getByText("离线前的第一条消息")).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
// A 离线前再互发一条
|
|
await bPage.locator(".composer textarea").fill("离线前的第二条消息");
|
|
await bPage.getByRole("button", { name: /发送消息/ }).click();
|
|
await expect(aPage.getByText("离线前的第二条消息")).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
// 模拟 B 断线(离线),A 发送新消息
|
|
await ctxB.setOffline(true);
|
|
await aPage.locator(".composer textarea").fill("断线期间的第三条消息");
|
|
await aPage.getByRole("button", { name: /发送消息/ }).click();
|
|
await expect(aPage.getByText("断线期间的第三条消息")).toBeVisible();
|
|
|
|
// B 恢复在线,重新拉取历史应补全缺失消息
|
|
await ctxB.setOffline(false);
|
|
await bPage.reload();
|
|
await expect(bPage.getByText("断线期间的第三条消息")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
await ctxA.close();
|
|
await ctxB.close();
|
|
});
|
|
|
|
test("blocks a peer and both sides can no longer send messages", async ({
|
|
browser,
|
|
}) => {
|
|
const aAccount = freshAccount("138");
|
|
const bAccount = freshAccount("139");
|
|
|
|
const ctxA = await browser.newContext();
|
|
const ctxB = await browser.newContext();
|
|
const aPage = await ctxA.newPage();
|
|
const bPage = await ctxB.newPage();
|
|
|
|
await loginViaUi(aPage, aAccount);
|
|
await loginViaUi(bPage, bAccount);
|
|
|
|
await aPage.goto("/throw");
|
|
await aPage
|
|
.getByPlaceholder("写下你想让陌生人看到的话…")
|
|
.fill(`拉黑测试瓶子 ${Date.now()}`);
|
|
await aPage.getByRole("button", { name: /扔进海里/ }).click();
|
|
await expect(aPage.getByText(/瓶子已进入审核/)).toBeVisible();
|
|
|
|
await bPage.goto("/pick");
|
|
await pickBottle(bPage);
|
|
await bPage.locator(".bottle-card textarea").fill("建立会话,稍后拉黑");
|
|
await bPage.getByRole("button", { name: /回复并建立会话/ }).click();
|
|
await expect(bPage).toHaveURL(/\/conversations\//, { timeout: 15_000 });
|
|
|
|
// A 进入会话并拉黑
|
|
await aPage.goto("/conversations");
|
|
await aPage.locator(".conversation").first().click();
|
|
await expect(aPage.getByText("建立会话,稍后拉黑")).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
aPage.once("dialog", (dialog) => void dialog.accept());
|
|
await aPage.getByRole("button", { name: /拉黑对方/ }).click();
|
|
await expect(aPage.getByText(/已拉黑对方/)).toBeVisible();
|
|
|
|
// B 发消息应失败
|
|
await bPage.locator(".composer textarea").fill("你还在吗?");
|
|
await bPage.getByRole("button", { name: /发送消息/ }).click();
|
|
await expect(bPage.locator(".bubble.failed").first()).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
await ctxA.close();
|
|
await ctxB.close();
|
|
});
|
|
});
|