test(ci): close browser tiering evidence gaps
This commit is contained in:
@@ -36,9 +36,7 @@ jobs:
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${GITHUB_EVENT_NAME}" != "pull_request" ]]; then
|
||||
echo "run_browser=true" >> "$GITHUB_OUTPUT"
|
||||
echo "browser_tier=FULL" >> "$GITHUB_OUTPUT"
|
||||
echo "browser_domain=" >> "$GITHUB_OUTPUT"
|
||||
node scripts/classify-browser-impact.mjs </dev/null >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -67,39 +65,13 @@ jobs:
|
||||
if: ${{ steps.scope.outputs.run_browser == 'true' }}
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Browser E2E (FULL)
|
||||
if: ${{ steps.scope.outputs.run_browser == 'true' && steps.scope.outputs.browser_tier == 'FULL' }}
|
||||
env:
|
||||
CI: "true"
|
||||
run: npm run test:e2e
|
||||
|
||||
- name: Browser E2E (DOMAIN)
|
||||
if: ${{ steps.scope.outputs.run_browser == 'true' && steps.scope.outputs.browser_tier == 'DOMAIN' }}
|
||||
- name: Browser E2E
|
||||
if: ${{ steps.scope.outputs.run_browser == 'true' }}
|
||||
env:
|
||||
CI: "true"
|
||||
BROWSER_TIER: ${{ steps.scope.outputs.browser_tier }}
|
||||
BROWSER_DOMAIN: ${{ steps.scope.outputs.browser_domain }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
core=(tests/e2e/app-shell.spec.js)
|
||||
case "$BROWSER_DOMAIN" in
|
||||
ai)
|
||||
domain=(tests/e2e/ai-*.spec.js tests/e2e/closure-ai-followup.spec.js)
|
||||
;;
|
||||
assessment)
|
||||
domain=(tests/e2e/assessment-*.spec.js)
|
||||
;;
|
||||
workbench)
|
||||
domain=(tests/e2e/workbench-*.spec.js)
|
||||
;;
|
||||
*)
|
||||
echo "Unmapped DOMAIN browser suite: $BROWSER_DOMAIN" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
npx playwright test "${core[@]}" "${domain[@]}"
|
||||
run: node scripts/run-browser-verification.mjs
|
||||
|
||||
- name: Preview HTTP smoke
|
||||
if: ${{ steps.scope.outputs.run_browser == 'true' }}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
export const CORE_BROWSER_SUITES = Object.freeze([
|
||||
"tests/e2e/app-shell.spec.js",
|
||||
]);
|
||||
|
||||
export const DOMAIN_BROWSER_SUITES = Object.freeze({
|
||||
ai: Object.freeze([
|
||||
"tests/e2e/ai-assistant.spec.js",
|
||||
"tests/e2e/ai-compaction.spec.js",
|
||||
"tests/e2e/ai-cross-unit-history.spec.js",
|
||||
"tests/e2e/ai-gateway-compaction.spec.js",
|
||||
"tests/e2e/ai-markdown-rendering.spec.js",
|
||||
"tests/e2e/ai-product-closure.spec.js",
|
||||
"tests/e2e/ai-stream-termination.spec.js",
|
||||
"tests/e2e/closure-ai-followup.spec.js",
|
||||
"tests/e2e/conversation-rename-accessibility.spec.js",
|
||||
"tests/e2e/deepseek-browser-settings.spec.js",
|
||||
"tests/e2e/product-closure-ai.spec.js",
|
||||
]),
|
||||
assessment: Object.freeze([
|
||||
"tests/e2e/assessment-lifecycle.spec.js",
|
||||
"tests/e2e/assessment-management.spec.js",
|
||||
"tests/e2e/product-closure-assessment.spec.js",
|
||||
]),
|
||||
workbench: Object.freeze([
|
||||
"tests/e2e/accessibility.spec.js",
|
||||
"tests/e2e/chapter-checkpoints.spec.js",
|
||||
"tests/e2e/code-viewer.spec.js",
|
||||
"tests/e2e/effects-cleanup.spec.js",
|
||||
"tests/e2e/responsive.spec.js",
|
||||
"tests/e2e/surface-boundaries.spec.js",
|
||||
"tests/e2e/workbench-final-acceptance.spec.js",
|
||||
"tests/e2e/workbench-integration.spec.js",
|
||||
]),
|
||||
});
|
||||
|
||||
export const SUPPORTED_BROWSER_DOMAINS = Object.freeze(
|
||||
Object.keys(DOMAIN_BROWSER_SUITES).sort(),
|
||||
);
|
||||
|
||||
export const ALL_BROWSER_SUITES = Object.freeze([
|
||||
...CORE_BROWSER_SUITES,
|
||||
...Object.values(DOMAIN_BROWSER_SUITES).flat(),
|
||||
].sort());
|
||||
|
||||
export function getBrowserSuiteSelection(tier, domain = null) {
|
||||
if (tier === "NONE") {
|
||||
return {
|
||||
executedSuites: [],
|
||||
skippedSuites: [...ALL_BROWSER_SUITES],
|
||||
};
|
||||
}
|
||||
|
||||
if (tier === "FULL") {
|
||||
return {
|
||||
executedSuites: [...ALL_BROWSER_SUITES],
|
||||
skippedSuites: [],
|
||||
};
|
||||
}
|
||||
|
||||
const domainSuites = DOMAIN_BROWSER_SUITES[domain];
|
||||
if (tier !== "DOMAIN" || !domainSuites) {
|
||||
throw new Error(`Unsupported browser verification selection: ${tier}:${domain ?? "none"}`);
|
||||
}
|
||||
|
||||
const executedSuites = [...CORE_BROWSER_SUITES, ...domainSuites].sort();
|
||||
const executedSuiteSet = new Set(executedSuites);
|
||||
return {
|
||||
executedSuites,
|
||||
skippedSuites: ALL_BROWSER_SUITES.filter((suite) => !executedSuiteSet.has(suite)),
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import { appendFile } from "node:fs/promises";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
import { getBrowserImpactOwnership } from "../architecture/ownership-manifest.mjs";
|
||||
import {
|
||||
getBrowserSuiteSelection,
|
||||
SUPPORTED_BROWSER_DOMAINS,
|
||||
} from "./browser-verification-plan.mjs";
|
||||
|
||||
const DOC_ONLY_PREFIXES = ["docs/", ".codex/"];
|
||||
const DOC_ONLY_ROOT_FILES = new Set([
|
||||
@@ -8,7 +13,7 @@ const DOC_ONLY_ROOT_FILES = new Set([
|
||||
"README.md",
|
||||
"issue-rule.md",
|
||||
]);
|
||||
const DOMAIN_BROWSER_SUITES = new Set(["ai", "assessment", "workbench"]);
|
||||
const DOMAIN_BROWSER_SUITES = new Set(SUPPORTED_BROWSER_DOMAINS);
|
||||
|
||||
function normalizePath(file) {
|
||||
return String(file ?? "")
|
||||
@@ -25,44 +30,64 @@ export function isDocumentationOnlyPath(file) {
|
||||
return normalized.endsWith(".md") && !normalized.startsWith("src/");
|
||||
}
|
||||
|
||||
function fullResult({ reason, triggeringPath = null }) {
|
||||
function resultWithSuites(result) {
|
||||
return {
|
||||
runBrowser: true,
|
||||
tier: "FULL",
|
||||
domain: null,
|
||||
reason,
|
||||
triggeringPath,
|
||||
...result,
|
||||
...getBrowserSuiteSelection(result.tier, result.domain),
|
||||
};
|
||||
}
|
||||
|
||||
function fullResult({ changedFiles, detectedDomains, reason, triggeringPath = null }) {
|
||||
return resultWithSuites({
|
||||
runBrowser: true,
|
||||
tier: "FULL",
|
||||
domain: null,
|
||||
changedFiles,
|
||||
detectedDomains,
|
||||
reason,
|
||||
fallbackReason: reason,
|
||||
triggeringPath,
|
||||
});
|
||||
}
|
||||
|
||||
export function classifyBrowserImpact(files) {
|
||||
const normalized = files.map(normalizePath).filter(Boolean);
|
||||
const normalized = [...new Set(files.map(normalizePath).filter(Boolean))].sort();
|
||||
|
||||
if (normalized.length === 0) {
|
||||
return fullResult({ reason: "empty-or-unknown-change-set" });
|
||||
return fullResult({
|
||||
changedFiles: [],
|
||||
detectedDomains: [],
|
||||
reason: "empty-or-unknown-change-set",
|
||||
});
|
||||
}
|
||||
|
||||
const runtimePaths = normalized.filter((file) => !isDocumentationOnlyPath(file));
|
||||
if (runtimePaths.length === 0) {
|
||||
return {
|
||||
return resultWithSuites({
|
||||
runBrowser: false,
|
||||
tier: "NONE",
|
||||
domain: null,
|
||||
changedFiles: normalized,
|
||||
detectedDomains: [],
|
||||
reason: "documentation-only",
|
||||
fallbackReason: null,
|
||||
triggeringPath: null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const ownership = runtimePaths.map((file) => ({
|
||||
file,
|
||||
...getBrowserImpactOwnership(file),
|
||||
}));
|
||||
const detectedDomains = [...new Set(ownership.map(({ owner }) => owner))].sort();
|
||||
|
||||
const fullFallback = ownership.find(({ owner, browserImpact }) => (
|
||||
browserImpact !== "domain" || !DOMAIN_BROWSER_SUITES.has(owner)
|
||||
));
|
||||
if (fullFallback) {
|
||||
return fullResult({
|
||||
changedFiles: normalized,
|
||||
detectedDomains,
|
||||
reason: `full-fallback:${fullFallback.owner}:${fullFallback.browserImpact}`,
|
||||
triggeringPath: fullFallback.file,
|
||||
});
|
||||
@@ -71,19 +96,68 @@ export function classifyBrowserImpact(files) {
|
||||
const domains = new Set(ownership.map(({ owner }) => owner));
|
||||
if (domains.size !== 1) {
|
||||
return fullResult({
|
||||
changedFiles: normalized,
|
||||
detectedDomains,
|
||||
reason: "cross-domain-change-set",
|
||||
triggeringPath: runtimePaths[0],
|
||||
});
|
||||
}
|
||||
|
||||
const [domain] = domains;
|
||||
return {
|
||||
return resultWithSuites({
|
||||
runBrowser: true,
|
||||
tier: "DOMAIN",
|
||||
domain,
|
||||
changedFiles: normalized,
|
||||
detectedDomains,
|
||||
reason: `domain-local:${domain}`,
|
||||
fallbackReason: null,
|
||||
triggeringPath: runtimePaths[0],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function browserEvidenceLines(result) {
|
||||
return [
|
||||
`[browser-impact] changed_files=${JSON.stringify(result.changedFiles)}`,
|
||||
`[browser-impact] detected_domains=${JSON.stringify(result.detectedDomains)}`,
|
||||
`[browser-impact] selected_tier=${result.tier}`,
|
||||
`[browser-impact] executed_suites=${JSON.stringify(result.executedSuites)}`,
|
||||
`[browser-impact] skipped_suites=${JSON.stringify(result.skippedSuites)}`,
|
||||
`[browser-impact] fallback_reason=${result.fallbackReason ?? "none"}`,
|
||||
];
|
||||
}
|
||||
|
||||
export function browserOutputLines(result) {
|
||||
return [
|
||||
`run_browser=${result.runBrowser}`,
|
||||
`browser_tier=${result.tier}`,
|
||||
`browser_domain=${result.domain ?? ""}`,
|
||||
`browser_detected_domains=${JSON.stringify(result.detectedDomains)}`,
|
||||
`browser_executed_suites=${JSON.stringify(result.executedSuites)}`,
|
||||
`browser_skipped_suites=${JSON.stringify(result.skippedSuites)}`,
|
||||
`browser_fallback_reason=${result.fallbackReason ?? ""}`,
|
||||
];
|
||||
}
|
||||
|
||||
function evidenceSummary(result) {
|
||||
const displayList = (values) => (
|
||||
values.length > 0
|
||||
? values.map((value) => `\`${value}\``).join("<br>")
|
||||
: "None"
|
||||
);
|
||||
return [
|
||||
"### Browser verification decision",
|
||||
"",
|
||||
"| Evidence | Value |",
|
||||
"| --- | --- |",
|
||||
`| Changed files | ${displayList(result.changedFiles)} |`,
|
||||
`| Detected domain(s) | ${displayList(result.detectedDomains)} |`,
|
||||
`| Selected tier | \`${result.tier}\` |`,
|
||||
`| Executed suites | ${displayList(result.executedSuites)} |`,
|
||||
`| Skipped suites | ${displayList(result.skippedSuites)} |`,
|
||||
`| Fallback reason | ${result.fallbackReason ? `\`${result.fallbackReason}\`` : "None"} |`,
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
async function readChangedFilesFromStdin() {
|
||||
@@ -95,12 +169,11 @@ async function readChangedFilesFromStdin() {
|
||||
async function main() {
|
||||
const files = await readChangedFilesFromStdin();
|
||||
const result = classifyBrowserImpact(files);
|
||||
process.stdout.write(`run_browser=${result.runBrowser}\n`);
|
||||
process.stdout.write(`browser_tier=${result.tier}\n`);
|
||||
process.stdout.write(`browser_domain=${result.domain ?? ""}\n`);
|
||||
process.stderr.write(
|
||||
`[browser-impact] ${result.tier} ${result.reason}${result.triggeringPath ? `: ${result.triggeringPath}` : ""}\n`,
|
||||
);
|
||||
process.stdout.write(`${browserOutputLines(result).join("\n")}\n`);
|
||||
process.stderr.write(`${browserEvidenceLines(result).join("\n")}\n`);
|
||||
if (process.env.GITHUB_STEP_SUMMARY) {
|
||||
await appendFile(process.env.GITHUB_STEP_SUMMARY, evidenceSummary(result));
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
import { getBrowserSuiteSelection } from "./browser-verification-plan.mjs";
|
||||
|
||||
function run(command, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, { stdio: "inherit" });
|
||||
child.once("error", reject);
|
||||
child.once("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
reject(new Error(`Browser verification terminated by ${signal}`));
|
||||
return;
|
||||
}
|
||||
resolve(code ?? 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const tier = process.env.BROWSER_TIER;
|
||||
const domain = process.env.BROWSER_DOMAIN || null;
|
||||
const { executedSuites } = getBrowserSuiteSelection(tier, domain);
|
||||
|
||||
console.log(`[browser-verification] tier=${tier}`);
|
||||
console.log(`[browser-verification] domain=${domain ?? "none"}`);
|
||||
console.log(`[browser-verification] suites=${JSON.stringify(executedSuites)}`);
|
||||
|
||||
if (tier === "FULL") {
|
||||
process.exitCode = await run("npm", ["run", "test:e2e"]);
|
||||
} else if (tier === "DOMAIN") {
|
||||
process.exitCode = await run("npm", [
|
||||
"exec",
|
||||
"--",
|
||||
"playwright",
|
||||
"test",
|
||||
...executedSuites,
|
||||
]);
|
||||
} else {
|
||||
throw new Error(`Browser runner cannot execute tier ${tier ?? "missing"}`);
|
||||
}
|
||||
@@ -1,25 +1,48 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { readFile, readdir } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
ALL_BROWSER_SUITES,
|
||||
CORE_BROWSER_SUITES,
|
||||
DOMAIN_BROWSER_SUITES,
|
||||
getBrowserSuiteSelection,
|
||||
} from "../scripts/browser-verification-plan.mjs";
|
||||
import {
|
||||
browserEvidenceLines,
|
||||
browserOutputLines,
|
||||
classifyBrowserImpact,
|
||||
isDocumentationOnlyPath,
|
||||
} from "../scripts/classify-browser-impact.mjs";
|
||||
|
||||
function assertDecision(result, expected) {
|
||||
for (const [key, value] of Object.entries(expected)) {
|
||||
assert.deepEqual(result[key], value, `unexpected ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
test("AI, Assessment and Workbench owner-local changes select DOMAIN tier", () => {
|
||||
for (const [file, domain] of [
|
||||
["src/ai/useAiLearningAssistant.js", "ai"],
|
||||
["src/assessment/application/useAssessmentApplication.js", "assessment"],
|
||||
["src/workbench/WorkbenchShell.jsx", "workbench"],
|
||||
]) {
|
||||
assert.deepEqual(classifyBrowserImpact([file]), {
|
||||
const result = classifyBrowserImpact([file]);
|
||||
assertDecision(result, {
|
||||
runBrowser: true,
|
||||
tier: "DOMAIN",
|
||||
domain,
|
||||
changedFiles: [file],
|
||||
detectedDomains: [domain],
|
||||
reason: `domain-local:${domain}`,
|
||||
fallbackReason: null,
|
||||
triggeringPath: file,
|
||||
});
|
||||
assert.ok(result.executedSuites.includes("tests/e2e/app-shell.spec.js"));
|
||||
assert.deepEqual(
|
||||
result.executedSuites,
|
||||
getBrowserSuiteSelection("DOMAIN", domain).executedSuites,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -40,6 +63,9 @@ test("shared, high-risk, unmapped domain and unknown runtime surfaces fail close
|
||||
assert.equal(result.runBrowser, true, `${file} must trigger browser verification`);
|
||||
assert.equal(result.tier, "FULL", `${file} must conservatively use FULL`);
|
||||
assert.equal(result.domain, null);
|
||||
assert.equal(result.fallbackReason, result.reason);
|
||||
assert.deepEqual(result.executedSuites, ALL_BROWSER_SUITES);
|
||||
assert.deepEqual(result.skippedSuites, []);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -49,11 +75,17 @@ test("cross-domain changes fall back to FULL even when both domains have focused
|
||||
"src/assessment/application/useAssessmentApplication.js",
|
||||
]);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
assertDecision(result, {
|
||||
runBrowser: true,
|
||||
tier: "FULL",
|
||||
domain: null,
|
||||
changedFiles: [
|
||||
"src/ai/useAiLearningAssistant.js",
|
||||
"src/assessment/application/useAssessmentApplication.js",
|
||||
],
|
||||
detectedDomains: ["ai", "assessment"],
|
||||
reason: "cross-domain-change-set",
|
||||
fallbackReason: "cross-domain-change-set",
|
||||
triggeringPath: "src/ai/useAiLearningAssistant.js",
|
||||
});
|
||||
});
|
||||
@@ -68,12 +100,17 @@ test("documentation-only changes keep the safe NONE fast path", () => {
|
||||
];
|
||||
|
||||
assert.equal(files.every(isDocumentationOnlyPath), true);
|
||||
assert.deepEqual(classifyBrowserImpact(files), {
|
||||
assertDecision(classifyBrowserImpact(files), {
|
||||
runBrowser: false,
|
||||
tier: "NONE",
|
||||
domain: null,
|
||||
changedFiles: [...files].sort(),
|
||||
detectedDomains: [],
|
||||
reason: "documentation-only",
|
||||
fallbackReason: null,
|
||||
triggeringPath: null,
|
||||
executedSuites: [],
|
||||
skippedSuites: ALL_BROWSER_SUITES,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,27 +126,98 @@ test("documentation mixed with one mapped domain preserves DOMAIN tier", () => {
|
||||
});
|
||||
|
||||
test("empty or unavailable diff fails closed to FULL", () => {
|
||||
assert.deepEqual(classifyBrowserImpact([]), {
|
||||
assertDecision(classifyBrowserImpact([]), {
|
||||
runBrowser: true,
|
||||
tier: "FULL",
|
||||
domain: null,
|
||||
changedFiles: [],
|
||||
detectedDomains: [],
|
||||
reason: "empty-or-unknown-change-set",
|
||||
fallbackReason: "empty-or-unknown-change-set",
|
||||
triggeringPath: null,
|
||||
executedSuites: ALL_BROWSER_SUITES,
|
||||
skippedSuites: [],
|
||||
});
|
||||
});
|
||||
|
||||
test("classification is independent of changed-file ordering and duplicate paths", () => {
|
||||
const first = classifyBrowserImpact([
|
||||
"src/assessment/application/useAssessmentApplication.js",
|
||||
"src/ai/useAiLearningAssistant.js",
|
||||
]);
|
||||
const second = classifyBrowserImpact([
|
||||
"./src/ai/useAiLearningAssistant.js",
|
||||
"src/assessment/application/useAssessmentApplication.js",
|
||||
"src/ai/useAiLearningAssistant.js",
|
||||
]);
|
||||
|
||||
assert.deepEqual(second, first);
|
||||
});
|
||||
|
||||
test("every browser spec has exactly one explicit core or domain suite owner", async () => {
|
||||
const discoveredSuites = (await readdir(new URL("../tests/e2e/", import.meta.url)))
|
||||
.filter((file) => file.endsWith(".spec.js"))
|
||||
.map((file) => `tests/e2e/${file}`)
|
||||
.sort();
|
||||
const declaredSuites = [
|
||||
...CORE_BROWSER_SUITES,
|
||||
...Object.values(DOMAIN_BROWSER_SUITES).flat(),
|
||||
];
|
||||
|
||||
assert.equal(new Set(declaredSuites).size, declaredSuites.length, "suite ownership must be unique");
|
||||
assert.deepEqual([...declaredSuites].sort(), discoveredSuites);
|
||||
assert.deepEqual(ALL_BROWSER_SUITES, discoveredSuites);
|
||||
});
|
||||
|
||||
test("classifier CLI emits complete machine and human-readable decision evidence", () => {
|
||||
const decision = classifyBrowserImpact(["src/App.jsx"]);
|
||||
const outputLines = browserOutputLines(decision);
|
||||
const evidenceLines = browserEvidenceLines(decision);
|
||||
for (const output of [
|
||||
"run_browser=true",
|
||||
"browser_tier=FULL",
|
||||
"browser_domain=",
|
||||
"browser_detected_domains=",
|
||||
"browser_executed_suites=",
|
||||
"browser_skipped_suites=[]",
|
||||
"browser_fallback_reason=full-fallback:app-integration:full",
|
||||
]) {
|
||||
assert.ok(outputLines.some((line) => line.startsWith(output)), output);
|
||||
}
|
||||
for (const evidence of [
|
||||
"changed_files=",
|
||||
"detected_domains=",
|
||||
"selected_tier=FULL",
|
||||
"executed_suites=",
|
||||
"skipped_suites=[]",
|
||||
"fallback_reason=full-fallback:app-integration:full",
|
||||
]) {
|
||||
assert.ok(
|
||||
evidenceLines.some((line) => line.startsWith(`[browser-impact] ${evidence}`)),
|
||||
evidence,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("Workbench required workflow consumes tier and domain outputs without changing check identity", async () => {
|
||||
const workflow = await readFile(
|
||||
new URL("../.github/workflows/workbench-integration-verify.yml", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const runner = await readFile(
|
||||
new URL("../scripts/run-browser-verification.mjs", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert.match(workflow, /name: Workbench Integration Verify \/ verify/);
|
||||
assert.match(workflow, /node scripts\/classify-browser-impact\.mjs/);
|
||||
assert.match(workflow, /steps\.scope\.outputs\.run_browser == 'true'/);
|
||||
assert.match(workflow, /steps\.scope\.outputs\.browser_tier/);
|
||||
assert.match(workflow, /steps\.scope\.outputs\.browser_domain/);
|
||||
assert.match(workflow, /tests\/e2e\/app-shell\.spec\.js/);
|
||||
assert.match(workflow, /npm run test:e2e/);
|
||||
assert.match(workflow, /node scripts\/run-browser-verification\.mjs/);
|
||||
assert.match(runner, /\["run", "test:e2e"\]/);
|
||||
assert.match(runner, /getBrowserSuiteSelection/);
|
||||
assert.doesNotMatch(workflow, /pull_request:\s*\n\s*paths:/);
|
||||
assert.doesNotMatch(workflow, /continue-on-error:/);
|
||||
assert.doesNotMatch(workflow, /playwright test .*--grep-invert/);
|
||||
});
|
||||
|
||||
@@ -51,5 +51,5 @@ test("browser verification keeps a code-side canonical entry aligned with Workbe
|
||||
assert.match(packageJson.scripts["test:e2e:mock"], /npm run build/);
|
||||
assert.match(packageJson.scripts["test:e2e:mock"], /npm run test:e2e/);
|
||||
assert.match(workbenchWorkflow, /run: npm run build/);
|
||||
assert.match(workbenchWorkflow, /run: npm run test:e2e/);
|
||||
assert.match(workbenchWorkflow, /run: node scripts\/run-browser-verification\.mjs/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user