Files
mnote/scripts/task110-page-title-single-truth-smoke.js
T

221 lines
7.9 KiB
JavaScript

"use strict";
const { chromium } = require("playwright");
const {
BASE_URL,
UI_TIMEOUT_MS,
assert,
cleanupDocuments,
createTempDocument,
ensureAuthenticated,
openDocument,
openFilesystemView,
openSectionView,
renameDocument,
} = require("./tree-shell-smoke-helpers");
async function waitForPageTitleInput(page) {
const input = page.locator('[data-page-title-input="true"][data-pane-role="primary"]').first();
await input.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
return input;
}
async function waitForTitleSave(page, documentId, expectedTitle) {
await page.waitForResponse(
async (response) => {
if (!response.url().includes("/api/documents/title") || response.request().method() !== "POST") {
return false;
}
const payload = response.request().postDataJSON();
return (
payload?.documentId === documentId &&
payload?.title === expectedTitle &&
payload?.commandName === "page.head.updateTitle" &&
response.ok()
);
},
{ timeout: UI_TIMEOUT_MS },
);
}
async function renameThroughPageHead(page, documentId, title) {
const titleInput = await waitForPageTitleInput(page);
await titleInput.click({ timeout: UI_TIMEOUT_MS });
await page.keyboard.press("Control+a");
await page.keyboard.type(title, { delay: 20 });
await titleInput.blur();
await waitForTitleSave(page, documentId, title);
}
async function waitForBreadcrumbTitle(page, title) {
await page.waitForFunction(
(expectedTitle) => {
const nav = document.querySelector("header nav");
return (nav?.textContent ?? "").includes(expectedTitle);
},
title,
{ timeout: UI_TIMEOUT_MS },
);
}
async function waitForSidebarRowTitle(page, documentId, title) {
await page.waitForFunction(
({ docId, expectedTitle }) => {
const row = document.querySelector(
`[data-node-id="${docId}"] .tree-link-title, [data-testid="page-tree-row"][data-node-id="${docId}"], .wolai-page-row[data-node-id="${docId}"] > .wolai-row-title`,
);
return (row?.textContent ?? "").includes(expectedTitle);
},
{ docId: documentId, expectedTitle: title },
{ timeout: UI_TIMEOUT_MS },
);
}
async function waitForPageTreeTitle(page, documentId, title) {
await openSectionView(page);
await page.waitForFunction(
({ nodeId, expectedTitle }) => {
const row = document.querySelector(
`#sidebar-tree-root .tree-row[data-shell-mode="page"][data-node-id="${nodeId}"] .tree-link-title, [data-testid="page-tree-row"][data-node-id="${nodeId}"], .wolai-page-row[data-node-id="${nodeId}"] > .wolai-row-title`,
);
return (row?.textContent ?? "").includes(expectedTitle);
},
{ nodeId: documentId, expectedTitle: title },
{ timeout: UI_TIMEOUT_MS },
);
}
async function waitForFileTreeTitle(page, documentId, title) {
await openFilesystemView(page);
await page.waitForFunction(
({ docId, expectedTitle }) => {
const row = document.querySelector(
`#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-doc-id="${docId}"] .tree-link-title, #sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-document-id="${docId}"] .tree-link-title, [data-testid="filetree-doc-row"][data-doc-id="${docId}"]`,
);
return (row?.textContent ?? "").includes(expectedTitle);
},
{ docId: documentId, expectedTitle: title },
{ timeout: UI_TIMEOUT_MS },
);
}
async function readVisibleTitle(page) {
const titleInput = page.locator('[data-page-title-input="true"][data-pane-role="primary"]').first();
if (await titleInput.isVisible().catch(() => false)) {
return (await titleInput.inputValue()).trim();
}
const heading = page.locator("h1").first();
return ((await heading.textContent()) ?? "").trim();
}
async function main() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1440, height: 960 },
});
const page = await context.newPage();
let caughtError = null;
let fixture = null;
try {
const viewer = await ensureAuthenticated(page, context.request);
const first = await createTempDocument(context.request, null);
const second = await createTempDocument(context.request, null);
const initialTitle = `phase-i-initial-${Date.now().toString().slice(-6)}`;
const renamedTitle = `phase-i-renamed-${(Date.now() + 1).toString().slice(-6)}`;
const siblingTitle = `phase-i-sibling-${(Date.now() + 2).toString().slice(-6)}`;
await renameDocument(context.request, first.workspaceId, first.documentId, initialTitle);
await renameDocument(context.request, second.workspaceId, second.documentId, siblingTitle);
fixture = {
workspaceId: first.workspaceId,
primaryId: first.documentId,
siblingId: second.documentId,
createdIds: [first.documentId, second.documentId],
initialTitle,
renamedTitle,
siblingTitle,
};
await openDocument(page, fixture.workspaceId, fixture.primaryId);
await waitForPageTitleInput(page);
assert((await readVisibleTitle(page)).includes(initialTitle), "页头未回填初始标题");
await renameThroughPageHead(page, fixture.primaryId, fixture.renamedTitle);
assert((await readVisibleTitle(page)).includes(fixture.renamedTitle), "页头未显示最新标题");
await waitForBreadcrumbTitle(page, fixture.renamedTitle);
await waitForSidebarRowTitle(page, fixture.primaryId, fixture.renamedTitle);
await waitForPageTreeTitle(page, fixture.primaryId, fixture.renamedTitle);
await waitForFileTreeTitle(page, fixture.primaryId, fixture.renamedTitle);
await openDocument(page, fixture.workspaceId, fixture.siblingId);
await waitForPageTitleInput(page);
assert((await readVisibleTitle(page)).includes(fixture.siblingTitle), "切到兄弟页后标题不正确");
await openDocument(page, fixture.workspaceId, fixture.primaryId);
await waitForPageTitleInput(page);
await waitForBreadcrumbTitle(page, fixture.renamedTitle);
await waitForSidebarRowTitle(page, fixture.primaryId, fixture.renamedTitle);
assert((await readVisibleTitle(page)).includes(fixture.renamedTitle), "切页往返后页头标题回闪");
await page.reload({ waitUntil: "commit", timeout: UI_TIMEOUT_MS });
await waitForPageTitleInput(page);
await waitForBreadcrumbTitle(page, fixture.renamedTitle);
await waitForSidebarRowTitle(page, fixture.primaryId, fixture.renamedTitle);
await waitForPageTreeTitle(page, fixture.primaryId, fixture.renamedTitle);
await waitForFileTreeTitle(page, fixture.primaryId, fixture.renamedTitle);
assert((await readVisibleTitle(page)).includes(fixture.renamedTitle), "刷新后页头标题未保持一致");
console.log(
JSON.stringify(
{
ok: true,
baseUrl: BASE_URL,
viewerUserId: viewer.userId,
workspaceId: fixture.workspaceId,
primaryId: fixture.primaryId,
siblingId: fixture.siblingId,
renamedTitle: fixture.renamedTitle,
},
null,
2,
),
);
} catch (error) {
caughtError = error;
} finally {
if (fixture) {
try {
await cleanupDocuments(context.request, fixture.createdIds);
} catch (cleanupError) {
if (!caughtError) {
caughtError = cleanupError;
} else {
console.error(
`清理临时页面失败:${
cleanupError instanceof Error
? cleanupError.stack || cleanupError.message
: String(cleanupError)
}`,
);
}
}
}
await page.close().catch(() => undefined);
await context.close().catch(() => undefined);
await browser.close().catch(() => undefined);
}
if (caughtError) {
throw caughtError;
}
}
main().catch((error) => {
console.error(error instanceof Error ? error.stack || error.message : String(error));
process.exit(1);
});