Files
story/test/e2e/visual-family-role-fact-controls.mjs
T

112 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createRequire } from "node:module";
import { randomUUID } from "node:crypto";
import { execFileSync } from "node:child_process";
import { mkdir } from "node:fs/promises";
import { fileURLToPath } from "node:url";
const require = createRequire(process.env.PLAYWRIGHT_REQUIRE_FROM ?? import.meta.url);
const { chromium } = require("playwright");
const outputDirectory = new URL("./screenshots/", import.meta.url);
const webURL = process.env.VISUAL_WEB_URL ?? "http://localhost:3100";
const webHostPattern = new RegExp(new URL(webURL).host.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
const oidcURLPattern = new RegExp(process.env.VISUAL_OIDC_URL_PATTERN ?? "localhost:58180");
const username = process.env.VISUAL_USERNAME ?? "owner";
const password = process.env.VISUAL_PASSWORD ?? "LocalOnly123!";
const factInternalID = randomUUID();
const factPublicID = randomUUID();
const factContent = `每周三下午喜欢听越剧(页面事实确认夹具-${factPublicID.slice(0, 8)})`;
const factSourceRef = `family-interview://visual/${factPublicID}`;
const memberID = "10000000-0000-0000-0000-000000000312";
await mkdir(outputDirectory, { recursive: true });
function psql(statement) {
return execFileSync("docker", ["compose", "exec", "-T", "postgres", "psql", "-v", "ON_ERROR_STOP=1", "-U", "voice", "-d", "voice_platform", "-Atc", statement], {
cwd: process.cwd(), encoding: "utf8", stdio: ["ignore", "pipe", "pipe"]
}).trim();
}
function cleanup() {
psql(`DELETE FROM subject_fact WHERE id='${factInternalID}'::uuid;`);
}
psql(`INSERT INTO subject_fact(id,public_id,tenant_id,subject_id,fact_type,content,source_type,source_ref,confirmation_status,sensitivity,version,created_by,updated_by)
VALUES('${factInternalID}'::uuid,'${factPublicID}'::uuid,'10000000-0000-0000-0000-000000000001'::uuid,(SELECT id FROM subject_profile WHERE public_id='10000000-0000-0000-0000-000000000411'::uuid),'兴趣偏好','${factContent}','family_entry','${factSourceRef}','pending','sensitive',7,'10000000-0000-0000-0000-000000000101'::uuid,'10000000-0000-0000-0000-000000000101'::uuid);`);
const browser = await chromium.launch({ headless: true, executablePath: process.env.CHROME_PATH ?? "C:/Program Files/Google/Chrome/Application/chrome.exe" });
const page = await browser.newPage({ viewport: { width: 1440, height: 1050 }, deviceScaleFactor: 1 });
const consoleErrors = [];
const failedRequests = [];
const mutations = [];
page.on("console", (message) => { if (message.type() === "error") consoleErrors.push(message.text()); });
page.on("request", (request) => {
if (request.method() === "PATCH" && (/\/api\/v1\/households\/[^/]+\/members\/[^/]+$/.test(request.url()) || /\/api\/v1\/subjects\/[^/]+\/facts\/[^/]+$/.test(request.url()))) mutations.push(`${request.method()} ${request.url()}`);
});
page.on("requestfailed", (request) => {
const failure = request.failure()?.errorText ?? "failed";
const requestURL = new URL(request.url());
if (request.method() === "GET" && requestURL.pathname === "/api/v1/events" && failure.includes("ERR_ABORTED")) return;
failedRequests.push(`${request.method()} ${request.url()} ${failure}`);
});
try {
await page.goto(webURL, { waitUntil: "networkidle" });
await page.getByRole("button", { name: "登录研发环境" }).click();
await page.waitForURL(oidcURLPattern);
await page.locator("#username").fill(username);
await page.locator("#password").fill(password);
await page.locator("#kc-login").click();
await page.waitForURL(webHostPattern, { timeout: 15000 });
await page.getByRole("heading", { name: "家庭业务工作台" }).waitFor();
await page.getByRole("button", { name: "家庭成员" }).click();
const memberRow = page.locator(".member-row").filter({ hasText: "本地编辑者" });
await memberRow.getByRole("button", { name: "调整角色" }).click();
let form = page.getByRole("form", { name: "家庭业务受控操作" });
await form.waitFor();
let text = await form.textContent();
const roleTargetBound = text?.includes(memberID) === true && text?.includes("当前编辑者") === true && text?.includes("记录版本 v1") === true && text?.includes("不会隐式授予录音播放、下载") === true;
let action = form.getByRole("button", { name: "确认调整角色" });
const sameRoleDisabled = await action.isDisabled();
await form.getByLabel("调整后的家庭角色").selectOption("viewer");
const roleNeedsAcknowledgement = await action.isDisabled();
await form.getByLabel(/我确认把本地编辑者从编辑者调整为查看者/).check();
const viewerEnabled = await action.isEnabled();
await form.getByLabel("调整后的家庭角色").selectOption("admin");
const changedRoleNeedsNewAcknowledgement = await action.isDisabled();
text = await form.textContent();
const adminImpactVisible = text?.includes("管理员可以管理家庭成员、长者资料、普通设备配置和故事审核") === true;
await form.getByLabel(/我确认把本地编辑者从编辑者调整为管理员/).check();
const adminEnabled = await action.isEnabled();
await form.getByRole("button", { name: "取消" }).click();
const roleCancelClosed = await form.count() === 0;
await page.getByRole("button", { name: "长者与授权" }).click();
const factRow = page.locator(".data-row").filter({ hasText: factContent });
await factRow.waitFor();
await factRow.getByRole("button", { name: "确认" }).click();
form = page.getByRole("form", { name: "家庭业务受控操作" });
await form.waitFor();
text = await form.textContent();
const factTargetBound = text?.includes(factPublicID) === true && text?.includes(factContent) === true && text?.includes(factSourceRef) === true && text?.includes("家庭录入") === true && text?.includes("敏感") === true && text?.includes("记录版本 v7") === true;
action = form.getByRole("button", { name: "确认该事实" });
const factNeedsAcknowledgement = await action.isDisabled();
await form.getByLabel(/我已核对事实内容、来源和敏感级别/).check();
const factEnabled = await action.isEnabled();
await form.screenshot({ path: fileURLToPath(new URL("family-fact-confirm-control.png", outputDirectory)) });
await form.getByRole("button", { name: "取消" }).click();
const factCancelClosed = await form.count() === 0;
const memberUnchanged = psql(`SELECT role||'|'||version FROM household_member WHERE public_id='${memberID}'::uuid;`) === "editor|1";
const factUnchanged = psql(`SELECT confirmation_status||'|'||version FROM subject_fact WHERE id='${factInternalID}'::uuid;`) === "pending|7";
const overflow = await page.evaluate(() => [...document.querySelectorAll("body *")]
.filter((element) => element instanceof HTMLElement && element.scrollWidth > element.clientWidth + 2 && getComputedStyle(element).overflowX === "visible")
.slice(0, 20)
.map((element) => `${element.tagName}.${element.className}: ${element.clientWidth}/${element.scrollWidth}`));
const result = { roleTargetBound, sameRoleDisabled, roleNeedsAcknowledgement, viewerEnabled, changedRoleNeedsNewAcknowledgement, adminImpactVisible, adminEnabled, roleCancelClosed, factTargetBound, factNeedsAcknowledgement, factEnabled, factCancelClosed, memberUnchanged, factUnchanged, mutations, overflow, consoleErrors, failedRequests };
console.log(JSON.stringify(result, null, 2));
if (Object.entries(result).some(([key, value]) => !["mutations", "overflow", "consoleErrors", "failedRequests"].includes(key) && value !== true) || mutations.length || overflow.length || consoleErrors.length || failedRequests.length) throw new Error("Family role and fact control visual smoke failed");
} finally {
await browser.close();
cleanup();
}