test: verify workbench scroll and Markdown help
Host commander review: merged PR #170 after checking the diff and runner validation. Scope is limited to Cloud Workbench scroll lock, Markdown help route smoke coverage, and CSS/static checks. No PROD/runtime mutation, no hardware write APIs, and no M3/M4/M5 acceptance claim.
This commit is contained in:
@@ -942,6 +942,12 @@ export async function runDevCloudWorkbenchMobileSmoke() {
|
||||
});
|
||||
await page.goto(server.url, { waitUntil: "networkidle", timeout: 15000 });
|
||||
const closed = await inspectMobileWorkbench(page, { explorerOpen: false });
|
||||
const workspaceScroll = await inspectWorkbenchScrollContract(page, { panelSelector: "#conversation-list" });
|
||||
await page.click('[data-route="help"]');
|
||||
await page.waitForFunction(() => document.querySelector("#help-content")?.dataset.helpState === "ready", null, { timeout: 8000 });
|
||||
const help = await inspectHelpMarkdownRoute(page);
|
||||
await page.click('[data-route="workspace"]');
|
||||
await page.waitForFunction(() => document.querySelector('[data-view="workspace"]')?.hidden === false, null, { timeout: 8000 });
|
||||
await page.click("#explorer-toggle");
|
||||
const open = await inspectMobileWorkbench(page, { explorerOpen: true });
|
||||
await page.click('[data-side-tab-jump="wiring"]');
|
||||
@@ -960,6 +966,25 @@ export async function runDevCloudWorkbenchMobileSmoke() {
|
||||
visibleText: closed.visibleText
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mobile-outer-scroll-lock",
|
||||
status: workspaceScroll.outerScrollLocked && help.rootStillLocked && help.helpPanelScrolls ? "pass" : "blocked",
|
||||
summary: "390x844 page locks outer scrolling while the internal Markdown help pane owns overflow scrolling.",
|
||||
observations: {
|
||||
workspace: workspaceScroll,
|
||||
help: {
|
||||
rootStillLocked: help.rootStillLocked,
|
||||
helpPanelScrolls: help.helpPanelScrolls,
|
||||
helpScroll: help.helpScroll
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "mobile-help-markdown-route",
|
||||
status: help.nonDefaultMarkdownHelp && help.termsPresent ? "pass" : "blocked",
|
||||
summary: "Help opens as a non-default internal route rendered from help.md by the vendored Markdown renderer.",
|
||||
observations: help
|
||||
},
|
||||
{
|
||||
id: "mobile-default-hit-test",
|
||||
status: closed.primaryControlsReachable ? "pass" : "blocked",
|
||||
@@ -1197,6 +1222,130 @@ async function inspectMobileWorkbench(page, { explorerOpen }) {
|
||||
}, { explorerOpen });
|
||||
}
|
||||
|
||||
async function inspectWorkbenchScrollContract(page, { panelSelector }) {
|
||||
return page.evaluate(async ({ panelSelector }) => {
|
||||
function metrics(selector) {
|
||||
const element = selector === "html" ? document.documentElement : selector === "body" ? document.body : document.querySelector(selector);
|
||||
if (!element) return null;
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
selector,
|
||||
overflow: style.overflow,
|
||||
overflowY: style.overflowY,
|
||||
height: element.getBoundingClientRect().height,
|
||||
clientHeight: element.clientHeight,
|
||||
scrollHeight: element.scrollHeight,
|
||||
scrollTop: element.scrollTop
|
||||
};
|
||||
}
|
||||
|
||||
window.scrollTo(0, 240);
|
||||
document.documentElement.scrollTop = 240;
|
||||
document.body.scrollTop = 240;
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
const rootAfterScrollAttempt = {
|
||||
windowScrollY: window.scrollY,
|
||||
htmlScrollTop: document.documentElement.scrollTop,
|
||||
bodyScrollTop: document.body.scrollTop
|
||||
};
|
||||
|
||||
const panel = document.querySelector(panelSelector);
|
||||
const beforePanelScroll = panel?.scrollTop ?? null;
|
||||
if (panel) {
|
||||
panel.scrollTop = 0;
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
panel.scrollTop = panel.scrollHeight;
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
}
|
||||
const afterPanelScroll = panel?.scrollTop ?? null;
|
||||
const html = metrics("html");
|
||||
const body = metrics("body");
|
||||
const shell = metrics("[data-app-shell]");
|
||||
const panelMetrics = metrics(panelSelector);
|
||||
const rootScrollLocked =
|
||||
html?.overflow === "hidden" &&
|
||||
body?.overflow === "hidden" &&
|
||||
Math.abs((shell?.height ?? 0) - window.innerHeight) <= 2 &&
|
||||
document.documentElement.scrollHeight <= document.documentElement.clientHeight + 2 &&
|
||||
document.body.scrollHeight <= document.body.clientHeight + 2 &&
|
||||
window.scrollY === 0 &&
|
||||
document.documentElement.scrollTop === 0 &&
|
||||
document.body.scrollTop === 0;
|
||||
return {
|
||||
viewport: { width: window.innerWidth, height: window.innerHeight },
|
||||
outerScrollLocked: rootScrollLocked,
|
||||
panelScrolls: Boolean(panelMetrics && panelMetrics.scrollHeight > panelMetrics.clientHeight && afterPanelScroll > beforePanelScroll),
|
||||
rootAfterScrollAttempt,
|
||||
html,
|
||||
body,
|
||||
shell,
|
||||
panel: panelMetrics
|
||||
};
|
||||
}, { panelSelector });
|
||||
}
|
||||
|
||||
async function inspectHelpMarkdownRoute(page) {
|
||||
return page.evaluate(async () => {
|
||||
const workspace = document.querySelector('[data-view="workspace"]');
|
||||
const gate = document.querySelector('[data-view="gate"]');
|
||||
const help = document.querySelector('[data-view="help"]');
|
||||
const helpContent = document.querySelector("#help-content");
|
||||
const heading = helpContent?.querySelector("h1");
|
||||
const renderedLists = helpContent?.querySelectorAll("li").length ?? 0;
|
||||
const codeCount = helpContent?.querySelectorAll("code").length ?? 0;
|
||||
const text = helpContent?.textContent ?? "";
|
||||
const helpScroll = await (async () => {
|
||||
if (!helpContent) return null;
|
||||
helpContent.scrollTop = 0;
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
const before = helpContent.scrollTop;
|
||||
helpContent.scrollTop = helpContent.scrollHeight;
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
return {
|
||||
before,
|
||||
after: helpContent.scrollTop,
|
||||
clientHeight: helpContent.clientHeight,
|
||||
scrollHeight: helpContent.scrollHeight,
|
||||
overflowY: getComputedStyle(helpContent).overflowY
|
||||
};
|
||||
})();
|
||||
const rootStillLocked =
|
||||
getComputedStyle(document.documentElement).overflow === "hidden" &&
|
||||
getComputedStyle(document.body).overflow === "hidden" &&
|
||||
document.documentElement.scrollHeight <= document.documentElement.clientHeight + 2 &&
|
||||
document.body.scrollHeight <= document.body.clientHeight + 2;
|
||||
return {
|
||||
hash: window.location.hash,
|
||||
helpState: helpContent?.dataset.helpState ?? null,
|
||||
workspaceHidden: workspace ? workspace.hidden : null,
|
||||
gateHidden: gate ? gate.hidden : null,
|
||||
helpHidden: help ? help.hidden : null,
|
||||
heading: heading?.textContent?.trim() ?? null,
|
||||
renderedLists,
|
||||
codeCount,
|
||||
hasRawMarkdownHeading: Boolean(helpContent?.textContent?.includes("# 云工作台内部使用说明")),
|
||||
termsPresent:
|
||||
["左侧资源与功能导航", "Agent 对话与工作清单", "same-origin", "/v1", ":16666"].every((term) => text.includes(term)) &&
|
||||
codeCount >= 8,
|
||||
helpPanelScrolls: Boolean(helpScroll && helpScroll.scrollHeight > helpScroll.clientHeight && helpScroll.after > helpScroll.before),
|
||||
rootStillLocked,
|
||||
helpScroll,
|
||||
nonDefaultMarkdownHelp:
|
||||
window.location.hash === "#help" &&
|
||||
workspace?.hidden === true &&
|
||||
gate?.hidden === true &&
|
||||
help?.hidden === false &&
|
||||
helpContent?.dataset.helpState === "ready" &&
|
||||
heading?.textContent?.trim() === "云工作台内部使用说明" &&
|
||||
renderedLists >= 8 &&
|
||||
codeCount >= 8 &&
|
||||
!helpContent?.textContent?.includes("# 云工作台内部使用说明") &&
|
||||
rootStillLocked &&
|
||||
Boolean(helpScroll && helpScroll.scrollHeight > helpScroll.clientHeight && helpScroll.after > helpScroll.before)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return String(value).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user