test: guard resource explorer removal

This commit is contained in:
Code Queue Review
2026-05-23 16:12:47 +00:00
parent d7231a0c54
commit 40cdbcc2bb
3 changed files with 163 additions and 46 deletions
+137 -45
View File
@@ -157,6 +157,55 @@ const helpRouteAliases = Object.freeze(["/help"]);
const incompletePrimaryNavLabels = Object.freeze(["台", "证", "诊", "帮"]);
const removedResourceExplorerSourceTerms = Object.freeze([
"resource-explorer",
"explorer-toggle",
"explorer-resize",
"resource-tree",
"explorer-capabilities",
"capability-count",
"tree-count",
"data-focus-command",
"quick-actions",
"tree-panel",
"explorer-collapsed",
"--explorer-width",
"--explorer-min-width",
"--explorer-max-width"
]);
const removedResourceExplorerDomSelectors = Object.freeze([
"aside#resource-explorer",
"#resource-explorer",
"#explorer-toggle",
"#explorer-resize",
"#resource-tree",
".resource-tree",
"#explorer-capabilities",
".explorer-capabilities",
"#capability-count",
"#tree-count",
"[data-focus-command]",
".quick-actions",
".tree-panel",
".explorer-collapsed"
]);
const removedResourceExplorerCopyTerms = Object.freeze([
"资源浏览与硬件资源树",
"资源浏览",
"硬件资源树",
"只读工作区",
"常用能力",
"收起资源树",
"展开资源树",
"拖拽调整左侧资源树宽度",
"资源查看",
"查看接线",
"查看状态",
"编写任务"
]);
const requiredWiringLongTableHeaders = Object.freeze(["res_boxsimu_1", "res_boxsimu_2"]);
const forbiddenLegacyWiringColumns = Object.freeze([
"源设备",
@@ -507,7 +556,7 @@ function runStaticSmoke() {
"aside#resource-explorer absent",
"#explorer-resize absent",
"resource tree / quick actions / capability cards absent",
"right side keeps a 560-740px M3 status workspace boundary through --right-width"
"right side keeps a 560-760px M3 status workspace boundary through --right-width"
]
});
@@ -1052,6 +1101,12 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) {
summary: "Mobile 390x844 default layout has no resource drawer and preserves Agent input plus M3 tabs/controls.",
observations: mobile.default
},
{
id: "layout-feedback-352-resource-explorer-removed",
status: allViewportCoverage(viewportResults, "resourceExplorerRemovalGuard") ? "pass" : "blocked",
summary: "#352 default workbench has no aside#resource-explorer, #explorer-resize, resource tree/capability copy, or reclaimed left panel across desktop and mobile.",
observations: summarizeResourceExplorerRemovalCoverage(viewportResults)
},
{
id: "layout-right-sidebar-resize-desktop",
status: desktop.rightResize.pass ? "pass" : "blocked",
@@ -1177,7 +1232,7 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) {
summary: useLiveUrl
? "Live browser layout smoke verifies workbench layout, hit targets, overflow, /gate usability, and the two-column wiring panel only; it is not M3 hardware acceptance."
: "Static local browser layout smoke verifies workbench layout, hit targets, overflow, /gate usability, and the two-column wiring panel only; deployment still requires DEV live verification.",
refs: ["pikasTech/HWLAB#273", "pikasTech/HWLAB#276", "pikasTech/HWLAB#278", "pikasTech/HWLAB#287", "pikasTech/HWLAB#288", "pikasTech/HWLAB#99", "pikasTech/HWLAB#227"],
refs: ["pikasTech/HWLAB#273", "pikasTech/HWLAB#276", "pikasTech/HWLAB#278", "pikasTech/HWLAB#287", "pikasTech/HWLAB#288", "pikasTech/HWLAB#352", "pikasTech/HWLAB#99", "pikasTech/HWLAB#227"],
viewports: layoutViewports.map(({ id, width, height }) => ({ id, width, height })),
checks,
blockers,
@@ -1351,7 +1406,7 @@ function layoutSourceContract() {
"docs/reference/cloud-workbench.md",
"docs/reference/code-agent-chat-readiness.md"
],
summary: "Cloud Workbench layout smoke protects the #99 workbench route, #227 visible M3 control area, Code Agent input, right hardware/trusted-record containers, /gate current route, and outer-scroll lock without claiming hardware acceptance."
summary: "Cloud Workbench layout smoke protects the #99 workbench route, #227 visible M3 control area, #352 resource-explorer removal, Code Agent input, right hardware/trusted-record containers, /gate current route, and outer-scroll lock without claiming hardware acceptance."
};
}
@@ -2157,34 +2212,8 @@ function hasMobileWorkbenchLayoutContract({ html, app, styles }) {
function hasResourceExplorerRemovalContract({ html, app, styles }) {
const source = `${html}\n${app}\n${styles}`;
const removedSelectorsAbsent = [
"resource-explorer",
"explorer-toggle",
"explorer-resize",
"resource-tree",
"explorer-capabilities",
"capability-count",
"tree-count",
"data-focus-command",
"quick-actions",
"tree-panel",
"explorer-collapsed",
"--explorer-width",
"--explorer-min-width",
"--explorer-max-width"
].every((term) => !source.includes(term));
const removedTextAbsent = [
"资源浏览与硬件资源树",
"只读工作区",
"常用能力",
"收起资源树",
"展开资源树",
"拖拽调整左侧资源树宽度",
"资源查看",
"查看接线",
"查看状态",
"编写任务"
].every((term) => !source.includes(term));
const removedSelectorsAbsent = removedResourceExplorerSourceTerms.every((term) => !source.includes(term));
const removedTextAbsent = removedResourceExplorerCopyTerms.every((term) => !source.includes(term));
return (
removedSelectorsAbsent &&
removedTextAbsent &&
@@ -5778,7 +5807,7 @@ async function inspectRightSidebarResizeDisabled(page, { viewport }) {
}
async function inspectLayoutState(page, { mode, viewport, compareTo }) {
return page.evaluate(async ({ mode, viewport, compareTo }) => {
return page.evaluate(async ({ mode, viewport, compareTo, resourceExplorerGuard }) => {
const failures = [];
const skip = [];
const selectorLabels = [
@@ -5968,6 +5997,21 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
scrolls: element.scrollHeight > element.clientHeight ? element.scrollTop > before : true
};
};
const visibleTextForElement = (root) => {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
const parts = [];
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
const parent = node.parentElement;
if (!parent || parent.closest("[hidden], script, style, noscript")) continue;
const style = getComputedStyle(parent);
if (style.display === "none" || style.visibility === "hidden") continue;
const box = parent.getBoundingClientRect();
if (box.width <= 0 || box.height <= 0) continue;
const text = node.textContent?.replace(/\s+/gu, " ").trim();
if (text) parts.push(text);
}
return parts.join(" ").replace(/\s+/gu, " ").trim();
};
window.scrollTo(0, 240);
document.documentElement.scrollTop = 240;
@@ -6252,23 +6296,22 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
await new Promise((resolve) => requestAnimationFrame(resolve));
}
const removedSelectorStates = Object.fromEntries(
[
["#resource-explorer", "resourceExplorer"],
["#explorer-resize", "explorerResize"],
["#explorer-toggle", "explorerToggle"],
["#resource-tree", "resourceTree"],
[".quick-actions", "quickActions"]
].map(([selector, key]) => [key, document.querySelector(selector) === null])
resourceExplorerGuard.domSelectors.map((selector) => [selector, document.querySelector(selector) === null])
);
const removedSelectorsAbsent = Object.values(removedSelectorStates).every(Boolean);
const visibleWorkbenchText = visibleTextForElement(document.body);
const removedVisibleCopyHits = resourceExplorerGuard.copyTerms.filter((term) => visibleWorkbenchText.includes(term));
const removedVisibleCopyAbsent = removedVisibleCopyHits.length === 0;
const resourceExplorerRemovalGuard = removedSelectorsAbsent && removedVisibleCopyAbsent;
const keyTargetsReachable = blockedKeyTargets.length === 0;
if (!removedSelectorsAbsent) {
if (!resourceExplorerRemovalGuard) {
failures.push({
failureType: "blocked/skip",
selector: "#resource-explorer",
selector: "#resource-explorer,#explorer-resize",
viewport,
summary: "Removed resource explorer selectors are still present in the default workbench DOM.",
removedSelectorStates
summary: "Removed resource explorer DOM or legacy copy returned in the default workbench.",
removedSelectorStates,
removedVisibleCopyHits
});
}
if (!rootScrollLocked) {
@@ -6324,7 +6367,7 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
Object.values(noHorizontalOverflow).every(Boolean) &&
rightWidthTarget &&
keyTargetsReachable &&
removedSelectorsAbsent &&
resourceExplorerRemovalGuard &&
sidePanelOverflowUsable;
const passWithWiring =
pass &&
@@ -6339,6 +6382,9 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
pass: passWithWiring,
removedSelectorStates,
removedSelectorsAbsent,
removedVisibleCopyAbsent,
removedVisibleCopyHits,
resourceExplorerRemovalGuard,
rootScrollLocked,
rootAfterScrollAttempt,
boxes,
@@ -6383,7 +6429,15 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
skip,
failures
};
}, { mode, viewport, compareTo });
}, {
mode,
viewport,
compareTo,
resourceExplorerGuard: {
domSelectors: [...removedResourceExplorerDomSelectors],
copyTerms: [...removedResourceExplorerCopyTerms]
}
});
}
async function inspectGateLayoutState(page, { mode, viewport }) {
@@ -6644,6 +6698,44 @@ function allViewportCoverage(viewportResults, fieldName) {
);
}
function summarizeResourceExplorerRemovalCoverage(viewportResults) {
const coverageEntries = Object.entries(viewportResults).map(([id, result]) => {
const observation = result.default;
return [
id,
{
viewport: observation?.viewport ?? null,
covered: Boolean(observation?.resourceExplorerRemovalGuard),
removedSelectorsAbsent: observation?.removedSelectorsAbsent === true,
removedVisibleCopyAbsent: observation?.removedVisibleCopyAbsent === true,
removedVisibleCopyHits: observation?.removedVisibleCopyHits ?? [],
removedSelectorStates: observation?.removedSelectorStates ?? {}
}
];
});
const coverage = Object.fromEntries(coverageEntries);
const failures = coverageEntries
.filter(([, item]) => !item.covered)
.map(([id, item]) => ({
checkId: "layout-feedback-352-resource-explorer-removed",
viewport: item.viewport,
selector: "#resource-explorer,#explorer-resize",
failureType: "blocked/skip",
summary: `#352 resource explorer removal guard failed in ${id} viewport.`,
removedSelectorsAbsent: item.removedSelectorsAbsent,
removedVisibleCopyAbsent: item.removedVisibleCopyAbsent,
removedVisibleCopyHits: item.removedVisibleCopyHits,
removedSelectorStates: item.removedSelectorStates
}));
return {
status: failures.length === 0 ? "pass" : "blocked",
forbiddenSelectors: [...removedResourceExplorerDomSelectors],
forbiddenCopy: [...removedResourceExplorerCopyTerms],
coverage,
failures
};
}
function summarizeCoverageGap(viewportResults, issueKey) {
const fieldName = {
issue288: "issue288SingleTableReady",