Files

236 lines
9.6 KiB
TypeScript

import { test, expect } from "./e2e-fixtures";
test("compact management flows, drag persistence, filters and mobile layout", async ({
page,
context,
}) => {
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.goto("/login");
await page.getByLabel("用户名", { exact: true }).fill("ui-review");
await page.getByLabel("密码", { exact: true }).fill("ui-review-local-2026");
await page.getByRole("button", { name: "登录", exact: true }).click();
await expect(page).toHaveURL(/dashboard/);
await page.screenshot({
path: "test-results/dashboard-desktop.png",
fullPage: true,
});
await page.getByRole("link", { name: "节点管理" }).click();
for (const [slug, name] of [
["tokyo", "Tokyo"],
["singapore", "Singapore"],
["frankfurt", "Frankfurt"],
]) {
const create = page
.locator(".compact-create-panel details")
.filter({
has: page.getByRole("heading", { name: "新增 HY2 节点", exact: true }),
});
if ((await create.getAttribute("open")) === null)
await create.locator("summary").click();
await create.getByLabel("Slug", { exact: true }).fill(slug);
await create.getByLabel("显示名", { exact: true }).fill(name);
await create
.getByLabel("客户端地址", { exact: true })
.fill(`${slug}.example.com`);
await create.getByRole("button", { name: "创建节点" }).click();
await expect(
page.locator(".entity-main-cell strong", { hasText: name }),
).toBeVisible();
}
await page.getByRole("searchbox").fill("Tokyo");
await expect(page.locator(".entity-table__item:visible")).toHaveCount(1);
await page.getByRole("button", { name: "清除筛选" }).click();
await expect(page.locator(".entity-table__item:visible")).toHaveCount(3);
await page.screenshot({
path: "test-results/nodes-desktop.png",
fullPage: true,
});
await page.getByRole("link", { name: "订阅配置" }).click();
const main = page.locator(".proxy-group-card").first();
const labels = () =>
main.locator(".proxy-sort-item strong").allTextContents();
await expect(main.locator(".proxy-sort-item")).toHaveCount(3);
const initial = await labels();
const handles = main.getByRole("button", { name: /拖拽排序/ });
await handles.first().focus();
await page.keyboard.press("Space");
await expect(handles.first()).toHaveAttribute("aria-pressed", "true");
// dnd-kit measures droppable rows and attaches its keyboard listener after activation.
await page.evaluate(
() =>
new Promise<void>((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
),
);
await page.keyboard.press("ArrowDown");
await expect(page.locator("[id^=DndLiveRegion]")).toContainText("目标位置 2");
await page.keyboard.press("Space");
await expect.poll(labels).toEqual([initial[1], initial[0], initial[2]]);
await main.getByRole("button", { name: "撤销修改" }).click();
await expect.poll(labels).toEqual(initial);
const from = await handles.first().boundingBox();
const to = await handles.nth(2).boundingBox();
await page.mouse.move(from!.x + 15, from!.y + 15);
await page.mouse.down();
await page.mouse.move(to!.x + 15, to!.y + 15, { steps: 12 });
await page.mouse.up();
await expect.poll(labels).toEqual([initial[1], initial[2], initial[0]]);
await main.getByRole("button", { name: "保存主分组", exact: true }).click();
await expect(main.locator(".action-row [role=status]")).toHaveText("已保存");
await page.reload();
await expect.poll(labels).toEqual([initial[1], initial[2], initial[0]]);
const hidden = main.getByRole("checkbox", { name: `主分组隐藏 ${initial[0]}`, exact: true });
await hidden.check();
await main.getByRole("button", { name: "保存主分组", exact: true }).click();
await expect(main.locator(".action-row [role=status]")).toHaveText("已保存");
await page.reload();
await expect(hidden).toBeChecked();
await hidden.uncheck();
await main.getByRole("button", { name: "撤销修改" }).click();
await expect(hidden).toBeChecked();
const newGroup = page
.locator("details")
.filter({ has: page.locator("summary", { hasText: "新建分组" }) });
await newGroup.locator("summary").click();
await newGroup.getByLabel("新建分组名").fill("Work");
await newGroup.getByRole("button", { name: "全部添加" }).click();
await newGroup.getByRole("button", { name: "新建分组", exact: true }).click();
const group = page
.locator("details")
.filter({ has: page.locator("summary strong", { hasText: /^Work$/ }) });
await expect(group).toBeVisible();
await group.locator("summary").click();
await group.getByRole("button", { name: "删除分组" }).click();
await expect(page.getByRole("alertdialog")).toBeVisible();
await page.getByRole("button", { name: "取消", exact: true }).click();
await expect(group).toBeVisible();
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
await page.getByRole("button", { name: "复制", exact: true }).click();
await expect(page.getByRole("button", { name: "已复制" })).toBeVisible();
await expect
.poll(() => page.evaluate(() => navigator.clipboard.readText()))
.toContain("/api/subscription/");
await page.screenshot({
path: "test-results/subscriptions-desktop.png",
fullPage: true,
});
await group.getByRole("button", { name: "删除分组" }).click();
await page.getByRole("button", { name: "确认删除分组" }).click();
await expect(group).toHaveCount(0);
await page.getByRole("link", { name: "用户管理" }).click();
await page.locator(".compact-create-summary").click();
const createUser = page.locator(".compact-create-panel");
await createUser.getByLabel("用户名", { exact: true }).fill("test-member");
await createUser.getByLabel("密码", { exact: true }).fill("test-member-pass");
await createUser.getByRole("button", { name: "创建用户" }).click();
const user = page
.locator(".entity-table__item")
.filter({ hasText: "test-member" });
await expect(user).toBeVisible();
await user.locator("summary").click();
await user
.getByRole("textbox", { name: "备注", exact: true })
.fill("updated via UI");
await user.getByRole("button", { name: "保存修改" }).click();
await expect(user.locator("p[role=status]")).toHaveText("已保存");
await page.reload();
await user.locator("summary").click();
await expect(
user.getByRole("textbox", { name: "备注", exact: true }),
).toHaveValue("updated via UI");
await page.screenshot({
path: "test-results/users-desktop.png",
fullPage: true,
});
await user
.getByRole("textbox", { name: "备注", exact: true })
.fill("keep on failure");
await page.route("**/admin/users", (route) =>
route.request().headers()["next-action"] ? route.abort() : route.continue(),
);
await user.getByRole("button", { name: "保存修改" }).click();
await expect(user.getByRole("alert")).toContainText("操作失败");
await expect(
user.getByRole("textbox", { name: "备注", exact: true }),
).toHaveValue("keep on failure");
await page.unroute("**/admin/users");
await page.setViewportSize({ width: 390, height: 844 });
for (const route of [
"users",
"nodes",
"subscriptions",
"audits",
"dashboard",
]) {
await page.goto(`/admin/${route}`);
await expect.poll(() => page.evaluate(() => ({
width: document.documentElement.scrollWidth,
viewport: innerWidth,
overflowing: [...document.querySelectorAll("body *")].filter(el => el.getBoundingClientRect().right > innerWidth && getComputedStyle(el).position !== "absolute").slice(0, 6).map(el => ({ tag: el.tagName, class: el.className, right: el.getBoundingClientRect().right })),
}))).toMatchObject({ width: 390, viewport: 390 });
await page.screenshot({
path: `test-results/${route}-mobile.png`,
fullPage: true,
});
}
// Exercise native touch input against the same handles on a narrow viewport.
await page.goto("/admin/subscriptions");
await expect(main.locator(".proxy-sort-item")).toHaveCount(3);
const beforeTouch = await labels();
const touchFrom = await handles.first().boundingBox();
const touchTo = await handles.nth(1).boundingBox();
const cdp = await context.newCDPSession(page);
await cdp.send("Emulation.setTouchEmulationEnabled", { enabled: true });
await cdp.send("Input.dispatchTouchEvent", {
type: "touchStart",
touchPoints: [{ x: touchFrom!.x + 15, y: touchFrom!.y + 15 }],
});
for (let i = 1; i <= 10; i++) {
await cdp.send("Input.dispatchTouchEvent", {
type: "touchMove",
touchPoints: [
{
x: touchFrom!.x + 15,
y: touchFrom!.y + 15 + ((touchTo!.y - touchFrom!.y) * i) / 10,
},
],
});
}
await cdp.send("Input.dispatchTouchEvent", {
type: "touchEnd",
touchPoints: [],
});
await expect
.poll(labels)
.toEqual([beforeTouch[1], beforeTouch[0], beforeTouch[2]]);
await main.getByRole("button", { name: "撤销修改" }).click();
await cdp.detach();
await page.getByRole("button", { name: "退出登录" }).click();
await page.getByLabel("用户名", { exact: true }).fill("test-member");
await page.getByLabel("密码", { exact: true }).fill("test-member-pass");
await page.getByRole("button", { name: "登录", exact: true }).click();
await expect(page).toHaveURL(/\/me$/);
await expect
.poll(() =>
page.evaluate(
() => document.documentElement.scrollWidth <= window.innerWidth,
),
)
.toBe(true);
await page.screenshot({
path: "test-results/account-mobile.png",
fullPage: true,
});
expect(errors).toEqual([]);
});