donobu 5.60.3 → 5.60.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/donobu-cli.js +11 -14
- package/dist/esm/cli/donobu-cli.js +11 -14
- package/dist/esm/lib/test/healRerunGate.d.ts +39 -24
- package/dist/esm/lib/test/healRerunGate.js +55 -67
- package/dist/esm/reporter/buildReport.js +1 -22
- package/dist/esm/reporter/model.d.ts +0 -7
- package/dist/lib/test/healRerunGate.d.ts +39 -24
- package/dist/lib/test/healRerunGate.js +55 -67
- package/dist/reporter/buildReport.js +1 -22
- package/dist/reporter/model.d.ts +0 -7
- package/package.json +1 -1
package/dist/cli/donobu-cli.js
CHANGED
|
@@ -1378,16 +1378,16 @@ async function attemptAutoHealRun(params) {
|
|
|
1378
1378
|
envOverrides.DONOBU_PAGE_AI_CLEAR_CACHE_FILES =
|
|
1379
1379
|
JSON.stringify(clearCacheFiles);
|
|
1380
1380
|
}
|
|
1381
|
-
// The rerun plan drives the runtime gate in the test
|
|
1382
|
-
//
|
|
1383
|
-
// `describe.serial` siblings) execute
|
|
1384
|
-
//
|
|
1385
|
-
//
|
|
1381
|
+
// The rerun plan drives the runtime gate in the test wrapper: within the
|
|
1382
|
+
// target projects, only the heal targets (and their declared
|
|
1383
|
+
// `describe.serial` siblings) execute; every other collected test skips.
|
|
1384
|
+
// Declared dependency (setup) projects still run in full — the gate
|
|
1385
|
+
// exempts any test whose project is not a heal-target project, so the
|
|
1386
|
+
// storage-state auth and fixture seeding the targets rely on are produced
|
|
1387
|
+
// (see healRerunGate). Each target carries its `projectName` so the gate
|
|
1388
|
+
// can tell a target project apart from a dependency one.
|
|
1386
1389
|
const healPlanPath = path.join(staging.rootDir, 'heal-rerun-plan.json');
|
|
1387
|
-
await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
|
|
1388
|
-
targets: healTargets,
|
|
1389
|
-
gatedProjects: (0, healRerunGate_1.computeGatedProjects)(healTargets.map((target) => target.projectName), params.initialReport?.metadata?.projectDependencies),
|
|
1390
|
-
}), 'utf8');
|
|
1390
|
+
await fs_1.promises.writeFile(healPlanPath, JSON.stringify({ targets: healTargets }), 'utf8');
|
|
1391
1391
|
envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
|
|
1392
1392
|
Logger_1.appLogger.info(`Auto-heal: re-running ${healTargets.length} targeted test(s) from ${evaluation.eligiblePlans.length} treatment plan(s)...`);
|
|
1393
1393
|
const healJsonReportPath = path.join(staging.playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME);
|
|
@@ -2182,10 +2182,8 @@ async function runHealCommand(cliArgs) {
|
|
|
2182
2182
|
applyJsonReportEnv(envOverrides, playwrightOutputDir);
|
|
2183
2183
|
// Downstream hooks check this flag to avoid recursive auto-heal loops.
|
|
2184
2184
|
envOverrides.DONOBU_AUTO_HEAL_ACTIVE = '1';
|
|
2185
|
-
// Same
|
|
2186
|
-
// declared `describe.serial` siblings) executes
|
|
2187
|
-
// dependency projects run in full. Without an initial report there is no
|
|
2188
|
-
// dependency graph, so the gate scopes to just the target's project.
|
|
2185
|
+
// Same collection-time gating as the automatic rerun: only the plan's test
|
|
2186
|
+
// (plus declared `describe.serial` siblings) executes.
|
|
2189
2187
|
const healPlanPath = path.join(os.tmpdir(), `donobu-heal-rerun-plan-${Date.now()}.json`);
|
|
2190
2188
|
await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
|
|
2191
2189
|
targets: [
|
|
@@ -2195,7 +2193,6 @@ async function runHealCommand(cliArgs) {
|
|
|
2195
2193
|
projectName: persisted.failure.testCase.projectName,
|
|
2196
2194
|
},
|
|
2197
2195
|
],
|
|
2198
|
-
gatedProjects: (0, healRerunGate_1.computeGatedProjects)([persisted.failure.testCase.projectName], undefined),
|
|
2199
2196
|
}), 'utf8');
|
|
2200
2197
|
envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
|
|
2201
2198
|
Logger_1.appLogger.info(`Re-running Playwright using treatment plan at ${parsed.planPath}...`);
|
|
@@ -1378,16 +1378,16 @@ async function attemptAutoHealRun(params) {
|
|
|
1378
1378
|
envOverrides.DONOBU_PAGE_AI_CLEAR_CACHE_FILES =
|
|
1379
1379
|
JSON.stringify(clearCacheFiles);
|
|
1380
1380
|
}
|
|
1381
|
-
// The rerun plan drives the runtime gate in the test
|
|
1382
|
-
//
|
|
1383
|
-
// `describe.serial` siblings) execute
|
|
1384
|
-
//
|
|
1385
|
-
//
|
|
1381
|
+
// The rerun plan drives the runtime gate in the test wrapper: within the
|
|
1382
|
+
// target projects, only the heal targets (and their declared
|
|
1383
|
+
// `describe.serial` siblings) execute; every other collected test skips.
|
|
1384
|
+
// Declared dependency (setup) projects still run in full — the gate
|
|
1385
|
+
// exempts any test whose project is not a heal-target project, so the
|
|
1386
|
+
// storage-state auth and fixture seeding the targets rely on are produced
|
|
1387
|
+
// (see healRerunGate). Each target carries its `projectName` so the gate
|
|
1388
|
+
// can tell a target project apart from a dependency one.
|
|
1386
1389
|
const healPlanPath = path.join(staging.rootDir, 'heal-rerun-plan.json');
|
|
1387
|
-
await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
|
|
1388
|
-
targets: healTargets,
|
|
1389
|
-
gatedProjects: (0, healRerunGate_1.computeGatedProjects)(healTargets.map((target) => target.projectName), params.initialReport?.metadata?.projectDependencies),
|
|
1390
|
-
}), 'utf8');
|
|
1390
|
+
await fs_1.promises.writeFile(healPlanPath, JSON.stringify({ targets: healTargets }), 'utf8');
|
|
1391
1391
|
envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
|
|
1392
1392
|
Logger_1.appLogger.info(`Auto-heal: re-running ${healTargets.length} targeted test(s) from ${evaluation.eligiblePlans.length} treatment plan(s)...`);
|
|
1393
1393
|
const healJsonReportPath = path.join(staging.playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME);
|
|
@@ -2182,10 +2182,8 @@ async function runHealCommand(cliArgs) {
|
|
|
2182
2182
|
applyJsonReportEnv(envOverrides, playwrightOutputDir);
|
|
2183
2183
|
// Downstream hooks check this flag to avoid recursive auto-heal loops.
|
|
2184
2184
|
envOverrides.DONOBU_AUTO_HEAL_ACTIVE = '1';
|
|
2185
|
-
// Same
|
|
2186
|
-
// declared `describe.serial` siblings) executes
|
|
2187
|
-
// dependency projects run in full. Without an initial report there is no
|
|
2188
|
-
// dependency graph, so the gate scopes to just the target's project.
|
|
2185
|
+
// Same collection-time gating as the automatic rerun: only the plan's test
|
|
2186
|
+
// (plus declared `describe.serial` siblings) executes.
|
|
2189
2187
|
const healPlanPath = path.join(os.tmpdir(), `donobu-heal-rerun-plan-${Date.now()}.json`);
|
|
2190
2188
|
await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
|
|
2191
2189
|
targets: [
|
|
@@ -2195,7 +2193,6 @@ async function runHealCommand(cliArgs) {
|
|
|
2195
2193
|
projectName: persisted.failure.testCase.projectName,
|
|
2196
2194
|
},
|
|
2197
2195
|
],
|
|
2198
|
-
gatedProjects: (0, healRerunGate_1.computeGatedProjects)([persisted.failure.testCase.projectName], undefined),
|
|
2199
2196
|
}), 'utf8');
|
|
2200
2197
|
envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
|
|
2201
2198
|
Logger_1.appLogger.info(`Re-running Playwright using treatment plan at ${parsed.planPath}...`);
|
|
@@ -22,7 +22,15 @@
|
|
|
22
22
|
* `expandTargetsWithSerialCompanions`) using the `serialScoped` flags the
|
|
23
23
|
* Donobu reporter recorded during the initial run — the runner process
|
|
24
24
|
* sees the suite tree; the worker (where this gate runs) does not.
|
|
25
|
-
* - Declared dependency projects,
|
|
25
|
+
* - Declared dependency (setup) projects, in full. Playwright schedules them
|
|
26
|
+
* because a target project lists them in `dependencies`, and the rerun
|
|
27
|
+
* command only ever passes `--project=<target>`, so any project that runs
|
|
28
|
+
* and is NOT a heal-target project is, by construction, such a dependency
|
|
29
|
+
* project. The gate exempts every test whose project is not a target
|
|
30
|
+
* project, so the auth/storage-state and fixture seeding those projects
|
|
31
|
+
* perform actually runs. (A prior version skipped them too — every test
|
|
32
|
+
* not literally in the plan — which broke targets that depend on the
|
|
33
|
+
* `.auth` storage state a setup project produces.)
|
|
26
34
|
*
|
|
27
35
|
* Implicit ordering (checkpoint files between plain tests, cross-file state
|
|
28
36
|
* with `workers: 1`) is deliberately NOT honored: tests relying on it will
|
|
@@ -44,34 +52,43 @@ export interface HealRerunPlan {
|
|
|
44
52
|
title: string;
|
|
45
53
|
projectName?: string;
|
|
46
54
|
}>;
|
|
47
|
-
/**
|
|
48
|
-
* Projects the gate applies to. Tests in any other project — declared
|
|
49
|
-
* dependency projects (auth/setup), teardown projects — run untouched,
|
|
50
|
-
* exactly as Playwright schedules them. Computed by the orchestrator via
|
|
51
|
-
* `computeGatedProjects`; when absent, the gate falls back to the targets'
|
|
52
|
-
* own project names.
|
|
53
|
-
*/
|
|
54
|
-
gatedProjects?: string[];
|
|
55
55
|
}
|
|
56
|
-
/** Targets indexed by absolute spec path for O(1) per-test decisions. */
|
|
57
|
-
export type HealRerunPlanIndex = Map<string, Set<string>>;
|
|
58
|
-
export declare function buildPlanIndex(plan: HealRerunPlan): HealRerunPlanIndex;
|
|
59
56
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
57
|
+
* Heal-rerun targets indexed for O(1) per-test decisions.
|
|
58
|
+
*
|
|
59
|
+
* `byFile` maps an absolute spec path to the heal-target titles in that file.
|
|
60
|
+
* `targetProjects` is the set of projects that own those targets; the rerun is
|
|
61
|
+
* launched with `--project=<target>` only, so any project Playwright also runs
|
|
62
|
+
* is there purely as a declared dependency (setup) project — see
|
|
63
|
+
* `shouldRunDuringHealRerun`. Empty when no target carries a project name, in
|
|
64
|
+
* which case the gate degrades to pure file+title matching.
|
|
64
65
|
*/
|
|
65
|
-
export
|
|
66
|
+
export interface HealRerunPlanIndex {
|
|
67
|
+
byFile: Map<string, Set<string>>;
|
|
68
|
+
targetProjects: Set<string>;
|
|
69
|
+
}
|
|
70
|
+
export declare function buildPlanIndex(plan: HealRerunPlan): HealRerunPlanIndex;
|
|
66
71
|
/**
|
|
67
|
-
* Pure decision: should the test in `file` with `title`
|
|
68
|
-
* during the heal rerun? The plan is fully
|
|
69
|
-
* already expanded into it by the
|
|
72
|
+
* Pure decision: should the test in `file` with `title` (owned by project
|
|
73
|
+
* `projectName`) actually execute during the heal rerun? The plan is fully
|
|
74
|
+
* explicit — serial companions were already expanded into it by the
|
|
75
|
+
* orchestrator.
|
|
76
|
+
*
|
|
77
|
+
* Dependency/setup projects (auth login, fixture seeding, …) are pulled in by
|
|
78
|
+
* Playwright because a target project declares them in `dependencies`. They
|
|
79
|
+
* are never themselves heal targets, but their tests MUST run so the state the
|
|
80
|
+
* targets depend on (storage-state auth files, seeded documents) is in place
|
|
81
|
+
* before the rerun. The rerun command only ever passes `--project=<target>`,
|
|
82
|
+
* so any project Playwright runs that is not a target project is, by
|
|
83
|
+
* construction, such a dependency project — run it in full. Guarded on a
|
|
84
|
+
* non-empty target-project set so the gate degrades to pure file+title
|
|
85
|
+
* matching when project names are unavailable.
|
|
70
86
|
*/
|
|
71
87
|
export declare function shouldRunDuringHealRerun(params: {
|
|
72
88
|
index: HealRerunPlanIndex;
|
|
73
89
|
file: string;
|
|
74
90
|
title: string;
|
|
91
|
+
projectName?: string;
|
|
75
92
|
}): boolean;
|
|
76
93
|
/**
|
|
77
94
|
* Expand heal targets with their `describe.serial` siblings, using the
|
|
@@ -91,10 +108,8 @@ export declare function expandTargetsWithSerialCompanions(targets: HealRerunPlan
|
|
|
91
108
|
export declare function resetHealRerunPlanCacheForTesting(): void;
|
|
92
109
|
/**
|
|
93
110
|
* Called from the Donobu auto fixture before any browser fixture initializes.
|
|
94
|
-
* Outside heal reruns this is a no-op. During a rerun, tests
|
|
95
|
-
*
|
|
96
|
-
* no context, no page, no cost. Tests in ungated projects (declared
|
|
97
|
-
* dependencies of the targets, teardown projects) always run.
|
|
111
|
+
* Outside heal reruns this is a no-op. During a rerun, tests outside the plan
|
|
112
|
+
* are annotated and skipped on the spot — no context, no page, no cost.
|
|
98
113
|
*/
|
|
99
114
|
export declare function maybeSkipForHealRerun(testInfo: TestInfo, options?: {
|
|
100
115
|
planPath?: string;
|
|
@@ -23,7 +23,15 @@
|
|
|
23
23
|
* `expandTargetsWithSerialCompanions`) using the `serialScoped` flags the
|
|
24
24
|
* Donobu reporter recorded during the initial run — the runner process
|
|
25
25
|
* sees the suite tree; the worker (where this gate runs) does not.
|
|
26
|
-
* - Declared dependency projects,
|
|
26
|
+
* - Declared dependency (setup) projects, in full. Playwright schedules them
|
|
27
|
+
* because a target project lists them in `dependencies`, and the rerun
|
|
28
|
+
* command only ever passes `--project=<target>`, so any project that runs
|
|
29
|
+
* and is NOT a heal-target project is, by construction, such a dependency
|
|
30
|
+
* project. The gate exempts every test whose project is not a target
|
|
31
|
+
* project, so the auth/storage-state and fixture seeding those projects
|
|
32
|
+
* perform actually runs. (A prior version skipped them too — every test
|
|
33
|
+
* not literally in the plan — which broke targets that depend on the
|
|
34
|
+
* `.auth` storage state a setup project produces.)
|
|
27
35
|
*
|
|
28
36
|
* Implicit ordering (checkpoint files between plain tests, cross-file state
|
|
29
37
|
* with `workers: 1`) is deliberately NOT honored: tests relying on it will
|
|
@@ -41,7 +49,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
41
49
|
};
|
|
42
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
51
|
exports.buildPlanIndex = buildPlanIndex;
|
|
44
|
-
exports.computeGatedProjects = computeGatedProjects;
|
|
45
52
|
exports.shouldRunDuringHealRerun = shouldRunDuringHealRerun;
|
|
46
53
|
exports.expandTargetsWithSerialCompanions = expandTargetsWithSerialCompanions;
|
|
47
54
|
exports.resetHealRerunPlanCacheForTesting = resetHealRerunPlanCacheForTesting;
|
|
@@ -52,52 +59,47 @@ const envVars_1 = require("../../envVars");
|
|
|
52
59
|
const model_1 = require("../../reporter/model");
|
|
53
60
|
const Logger_1 = require("../../utils/Logger");
|
|
54
61
|
function buildPlanIndex(plan) {
|
|
55
|
-
const
|
|
62
|
+
const byFile = new Map();
|
|
63
|
+
const targetProjects = new Set();
|
|
56
64
|
for (const target of plan.targets ?? []) {
|
|
57
65
|
if (!target?.file || !target?.title) {
|
|
58
66
|
continue;
|
|
59
67
|
}
|
|
60
68
|
const file = path_1.default.resolve(target.file);
|
|
61
|
-
if (!
|
|
62
|
-
|
|
69
|
+
if (!byFile.has(file)) {
|
|
70
|
+
byFile.set(file, new Set());
|
|
63
71
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* The projects the rerun gate should apply to: the heal targets' own
|
|
70
|
-
* projects, minus any project that is a declared (transitive) dependency of
|
|
71
|
-
* another target project — the dependency declaration wins, and that project
|
|
72
|
-
* runs in full.
|
|
73
|
-
*/
|
|
74
|
-
function computeGatedProjects(targetProjects, projectDependencies) {
|
|
75
|
-
const targets = [
|
|
76
|
-
...new Set(targetProjects.filter((name) => Boolean(name))),
|
|
77
|
-
];
|
|
78
|
-
if (!projectDependencies) {
|
|
79
|
-
return targets;
|
|
80
|
-
}
|
|
81
|
-
// Every project reachable through any target's declared dependency chain.
|
|
82
|
-
const reachable = new Set();
|
|
83
|
-
const visit = (name) => {
|
|
84
|
-
for (const dependency of projectDependencies[name] ?? []) {
|
|
85
|
-
if (!reachable.has(dependency)) {
|
|
86
|
-
reachable.add(dependency);
|
|
87
|
-
visit(dependency);
|
|
88
|
-
}
|
|
72
|
+
byFile.get(file).add(target.title);
|
|
73
|
+
if (target.projectName) {
|
|
74
|
+
targetProjects.add(target.projectName);
|
|
89
75
|
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return targets.filter((name) => !reachable.has(name));
|
|
76
|
+
}
|
|
77
|
+
return { byFile, targetProjects };
|
|
93
78
|
}
|
|
94
79
|
/**
|
|
95
|
-
* Pure decision: should the test in `file` with `title`
|
|
96
|
-
* during the heal rerun? The plan is fully
|
|
97
|
-
* already expanded into it by the
|
|
80
|
+
* Pure decision: should the test in `file` with `title` (owned by project
|
|
81
|
+
* `projectName`) actually execute during the heal rerun? The plan is fully
|
|
82
|
+
* explicit — serial companions were already expanded into it by the
|
|
83
|
+
* orchestrator.
|
|
84
|
+
*
|
|
85
|
+
* Dependency/setup projects (auth login, fixture seeding, …) are pulled in by
|
|
86
|
+
* Playwright because a target project declares them in `dependencies`. They
|
|
87
|
+
* are never themselves heal targets, but their tests MUST run so the state the
|
|
88
|
+
* targets depend on (storage-state auth files, seeded documents) is in place
|
|
89
|
+
* before the rerun. The rerun command only ever passes `--project=<target>`,
|
|
90
|
+
* so any project Playwright runs that is not a target project is, by
|
|
91
|
+
* construction, such a dependency project — run it in full. Guarded on a
|
|
92
|
+
* non-empty target-project set so the gate degrades to pure file+title
|
|
93
|
+
* matching when project names are unavailable.
|
|
98
94
|
*/
|
|
99
95
|
function shouldRunDuringHealRerun(params) {
|
|
100
|
-
const
|
|
96
|
+
const { index, projectName } = params;
|
|
97
|
+
if (projectName !== undefined &&
|
|
98
|
+
index.targetProjects.size > 0 &&
|
|
99
|
+
!index.targetProjects.has(projectName)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
const titles = index.byFile.get(path_1.default.resolve(params.file));
|
|
101
103
|
return titles?.has(params.title) ?? false;
|
|
102
104
|
}
|
|
103
105
|
/**
|
|
@@ -160,61 +162,47 @@ function expandTargetsWithSerialCompanions(targets, initialReport) {
|
|
|
160
162
|
}
|
|
161
163
|
return expanded;
|
|
162
164
|
}
|
|
163
|
-
let
|
|
164
|
-
function
|
|
165
|
-
if (planPathOverride === undefined &&
|
|
166
|
-
return
|
|
165
|
+
let cachedPlanIndex;
|
|
166
|
+
function getPlanIndex(planPathOverride) {
|
|
167
|
+
if (planPathOverride === undefined && cachedPlanIndex !== undefined) {
|
|
168
|
+
return cachedPlanIndex;
|
|
167
169
|
}
|
|
168
170
|
const planPath = planPathOverride ?? envVars_1.env.data.DONOBU_AUTO_HEAL_PLAN_PATH;
|
|
169
|
-
let
|
|
171
|
+
let index = null;
|
|
170
172
|
if (planPath) {
|
|
171
173
|
try {
|
|
172
174
|
const raw = fs_1.default.readFileSync(planPath, 'utf8');
|
|
173
|
-
|
|
174
|
-
loaded = {
|
|
175
|
-
index: buildPlanIndex(plan),
|
|
176
|
-
gatedProjects: new Set(plan.gatedProjects ??
|
|
177
|
-
// Older plans carry no project list — gate the targets' own
|
|
178
|
-
// projects, leaving everything else (dependencies) untouched.
|
|
179
|
-
plan.targets
|
|
180
|
-
?.map((target) => target.projectName)
|
|
181
|
-
.filter((name) => Boolean(name)) ??
|
|
182
|
-
[]),
|
|
183
|
-
};
|
|
175
|
+
index = buildPlanIndex(JSON.parse(raw));
|
|
184
176
|
}
|
|
185
177
|
catch (error) {
|
|
186
178
|
Logger_1.appLogger.warn(`Auto-heal rerun plan at ${planPath} could not be read; running all collected tests.`, error);
|
|
187
|
-
|
|
179
|
+
index = null;
|
|
188
180
|
}
|
|
189
181
|
}
|
|
190
182
|
if (planPathOverride === undefined) {
|
|
191
|
-
|
|
183
|
+
cachedPlanIndex = index;
|
|
192
184
|
}
|
|
193
|
-
return
|
|
185
|
+
return index;
|
|
194
186
|
}
|
|
195
187
|
/** Test-only: reset the memoized plan so each test can load its own. */
|
|
196
188
|
function resetHealRerunPlanCacheForTesting() {
|
|
197
|
-
|
|
189
|
+
cachedPlanIndex = undefined;
|
|
198
190
|
}
|
|
199
191
|
/**
|
|
200
192
|
* Called from the Donobu auto fixture before any browser fixture initializes.
|
|
201
|
-
* Outside heal reruns this is a no-op. During a rerun, tests
|
|
202
|
-
*
|
|
203
|
-
* no context, no page, no cost. Tests in ungated projects (declared
|
|
204
|
-
* dependencies of the targets, teardown projects) always run.
|
|
193
|
+
* Outside heal reruns this is a no-op. During a rerun, tests outside the plan
|
|
194
|
+
* are annotated and skipped on the spot — no context, no page, no cost.
|
|
205
195
|
*/
|
|
206
196
|
function maybeSkipForHealRerun(testInfo, options) {
|
|
207
|
-
const
|
|
208
|
-
if (!
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (!plan.gatedProjects.has(testInfo.project.name)) {
|
|
197
|
+
const index = getPlanIndex(options?.planPath);
|
|
198
|
+
if (!index) {
|
|
212
199
|
return;
|
|
213
200
|
}
|
|
214
201
|
const shouldRun = shouldRunDuringHealRerun({
|
|
215
|
-
index
|
|
202
|
+
index,
|
|
216
203
|
file: testInfo.file,
|
|
217
204
|
title: testInfo.title,
|
|
205
|
+
projectName: testInfo.project?.name,
|
|
218
206
|
});
|
|
219
207
|
if (shouldRun) {
|
|
220
208
|
return;
|
|
@@ -41,27 +41,6 @@ function buildDonobuReport(resultsByTest, rootDir) {
|
|
|
41
41
|
}
|
|
42
42
|
byTitle.get(test.title).push(test);
|
|
43
43
|
}
|
|
44
|
-
// Declared project dependency graph (`FullProject.dependencies`), keyed by
|
|
45
|
-
// project name. The auto-heal orchestrator uses it to keep the rerun gate
|
|
46
|
-
// away from projects that are declared dependencies of heal targets — those
|
|
47
|
-
// must run in full, exactly as Playwright schedules them.
|
|
48
|
-
const projectDependencies = {};
|
|
49
|
-
for (const test of resultsByTest.keys()) {
|
|
50
|
-
try {
|
|
51
|
-
let suite = test.parent;
|
|
52
|
-
while (suite && suite.type !== 'project') {
|
|
53
|
-
suite = suite.parent;
|
|
54
|
-
}
|
|
55
|
-
const project = suite?.project();
|
|
56
|
-
if (project?.name && !(project.name in projectDependencies)) {
|
|
57
|
-
projectDependencies[project.name] = [...(project.dependencies ?? [])];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
// Reporter shape drift — omit the entry; the orchestrator degrades to
|
|
62
|
-
// gating only the heal targets' own projects.
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
44
|
const suites = [];
|
|
66
45
|
for (const [file, titleMap] of byFile) {
|
|
67
46
|
const specs = [];
|
|
@@ -137,7 +116,7 @@ function buildDonobuReport(resultsByTest, rootDir) {
|
|
|
137
116
|
}
|
|
138
117
|
suites.push({ file, specs });
|
|
139
118
|
}
|
|
140
|
-
return { suites, metadata: {
|
|
119
|
+
return { suites, metadata: {} };
|
|
141
120
|
}
|
|
142
121
|
/**
|
|
143
122
|
* Whether any enclosing suite is in serial mode (`test.describe.serial` or
|
|
@@ -57,13 +57,6 @@ export interface DonobuReportMetadata {
|
|
|
57
57
|
/** True on reports that are the result of merging an initial + heal run. */
|
|
58
58
|
donobuMergedReport?: boolean;
|
|
59
59
|
mergedAtIso?: string;
|
|
60
|
-
/**
|
|
61
|
-
* Declared Playwright project dependency graph (`FullProject.dependencies`)
|
|
62
|
-
* keyed by project name, recorded by the reporter. The auto-heal
|
|
63
|
-
* orchestrator uses it to exclude declared dependencies of heal targets
|
|
64
|
-
* from the rerun gate — they must run in full.
|
|
65
|
-
*/
|
|
66
|
-
projectDependencies?: Record<string, string[]>;
|
|
67
60
|
sources?: {
|
|
68
61
|
initial?: string | null;
|
|
69
62
|
autoHeal?: string | null;
|
|
@@ -22,7 +22,15 @@
|
|
|
22
22
|
* `expandTargetsWithSerialCompanions`) using the `serialScoped` flags the
|
|
23
23
|
* Donobu reporter recorded during the initial run — the runner process
|
|
24
24
|
* sees the suite tree; the worker (where this gate runs) does not.
|
|
25
|
-
* - Declared dependency projects,
|
|
25
|
+
* - Declared dependency (setup) projects, in full. Playwright schedules them
|
|
26
|
+
* because a target project lists them in `dependencies`, and the rerun
|
|
27
|
+
* command only ever passes `--project=<target>`, so any project that runs
|
|
28
|
+
* and is NOT a heal-target project is, by construction, such a dependency
|
|
29
|
+
* project. The gate exempts every test whose project is not a target
|
|
30
|
+
* project, so the auth/storage-state and fixture seeding those projects
|
|
31
|
+
* perform actually runs. (A prior version skipped them too — every test
|
|
32
|
+
* not literally in the plan — which broke targets that depend on the
|
|
33
|
+
* `.auth` storage state a setup project produces.)
|
|
26
34
|
*
|
|
27
35
|
* Implicit ordering (checkpoint files between plain tests, cross-file state
|
|
28
36
|
* with `workers: 1`) is deliberately NOT honored: tests relying on it will
|
|
@@ -44,34 +52,43 @@ export interface HealRerunPlan {
|
|
|
44
52
|
title: string;
|
|
45
53
|
projectName?: string;
|
|
46
54
|
}>;
|
|
47
|
-
/**
|
|
48
|
-
* Projects the gate applies to. Tests in any other project — declared
|
|
49
|
-
* dependency projects (auth/setup), teardown projects — run untouched,
|
|
50
|
-
* exactly as Playwright schedules them. Computed by the orchestrator via
|
|
51
|
-
* `computeGatedProjects`; when absent, the gate falls back to the targets'
|
|
52
|
-
* own project names.
|
|
53
|
-
*/
|
|
54
|
-
gatedProjects?: string[];
|
|
55
55
|
}
|
|
56
|
-
/** Targets indexed by absolute spec path for O(1) per-test decisions. */
|
|
57
|
-
export type HealRerunPlanIndex = Map<string, Set<string>>;
|
|
58
|
-
export declare function buildPlanIndex(plan: HealRerunPlan): HealRerunPlanIndex;
|
|
59
56
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
57
|
+
* Heal-rerun targets indexed for O(1) per-test decisions.
|
|
58
|
+
*
|
|
59
|
+
* `byFile` maps an absolute spec path to the heal-target titles in that file.
|
|
60
|
+
* `targetProjects` is the set of projects that own those targets; the rerun is
|
|
61
|
+
* launched with `--project=<target>` only, so any project Playwright also runs
|
|
62
|
+
* is there purely as a declared dependency (setup) project — see
|
|
63
|
+
* `shouldRunDuringHealRerun`. Empty when no target carries a project name, in
|
|
64
|
+
* which case the gate degrades to pure file+title matching.
|
|
64
65
|
*/
|
|
65
|
-
export
|
|
66
|
+
export interface HealRerunPlanIndex {
|
|
67
|
+
byFile: Map<string, Set<string>>;
|
|
68
|
+
targetProjects: Set<string>;
|
|
69
|
+
}
|
|
70
|
+
export declare function buildPlanIndex(plan: HealRerunPlan): HealRerunPlanIndex;
|
|
66
71
|
/**
|
|
67
|
-
* Pure decision: should the test in `file` with `title`
|
|
68
|
-
* during the heal rerun? The plan is fully
|
|
69
|
-
* already expanded into it by the
|
|
72
|
+
* Pure decision: should the test in `file` with `title` (owned by project
|
|
73
|
+
* `projectName`) actually execute during the heal rerun? The plan is fully
|
|
74
|
+
* explicit — serial companions were already expanded into it by the
|
|
75
|
+
* orchestrator.
|
|
76
|
+
*
|
|
77
|
+
* Dependency/setup projects (auth login, fixture seeding, …) are pulled in by
|
|
78
|
+
* Playwright because a target project declares them in `dependencies`. They
|
|
79
|
+
* are never themselves heal targets, but their tests MUST run so the state the
|
|
80
|
+
* targets depend on (storage-state auth files, seeded documents) is in place
|
|
81
|
+
* before the rerun. The rerun command only ever passes `--project=<target>`,
|
|
82
|
+
* so any project Playwright runs that is not a target project is, by
|
|
83
|
+
* construction, such a dependency project — run it in full. Guarded on a
|
|
84
|
+
* non-empty target-project set so the gate degrades to pure file+title
|
|
85
|
+
* matching when project names are unavailable.
|
|
70
86
|
*/
|
|
71
87
|
export declare function shouldRunDuringHealRerun(params: {
|
|
72
88
|
index: HealRerunPlanIndex;
|
|
73
89
|
file: string;
|
|
74
90
|
title: string;
|
|
91
|
+
projectName?: string;
|
|
75
92
|
}): boolean;
|
|
76
93
|
/**
|
|
77
94
|
* Expand heal targets with their `describe.serial` siblings, using the
|
|
@@ -91,10 +108,8 @@ export declare function expandTargetsWithSerialCompanions(targets: HealRerunPlan
|
|
|
91
108
|
export declare function resetHealRerunPlanCacheForTesting(): void;
|
|
92
109
|
/**
|
|
93
110
|
* Called from the Donobu auto fixture before any browser fixture initializes.
|
|
94
|
-
* Outside heal reruns this is a no-op. During a rerun, tests
|
|
95
|
-
*
|
|
96
|
-
* no context, no page, no cost. Tests in ungated projects (declared
|
|
97
|
-
* dependencies of the targets, teardown projects) always run.
|
|
111
|
+
* Outside heal reruns this is a no-op. During a rerun, tests outside the plan
|
|
112
|
+
* are annotated and skipped on the spot — no context, no page, no cost.
|
|
98
113
|
*/
|
|
99
114
|
export declare function maybeSkipForHealRerun(testInfo: TestInfo, options?: {
|
|
100
115
|
planPath?: string;
|
|
@@ -23,7 +23,15 @@
|
|
|
23
23
|
* `expandTargetsWithSerialCompanions`) using the `serialScoped` flags the
|
|
24
24
|
* Donobu reporter recorded during the initial run — the runner process
|
|
25
25
|
* sees the suite tree; the worker (where this gate runs) does not.
|
|
26
|
-
* - Declared dependency projects,
|
|
26
|
+
* - Declared dependency (setup) projects, in full. Playwright schedules them
|
|
27
|
+
* because a target project lists them in `dependencies`, and the rerun
|
|
28
|
+
* command only ever passes `--project=<target>`, so any project that runs
|
|
29
|
+
* and is NOT a heal-target project is, by construction, such a dependency
|
|
30
|
+
* project. The gate exempts every test whose project is not a target
|
|
31
|
+
* project, so the auth/storage-state and fixture seeding those projects
|
|
32
|
+
* perform actually runs. (A prior version skipped them too — every test
|
|
33
|
+
* not literally in the plan — which broke targets that depend on the
|
|
34
|
+
* `.auth` storage state a setup project produces.)
|
|
27
35
|
*
|
|
28
36
|
* Implicit ordering (checkpoint files between plain tests, cross-file state
|
|
29
37
|
* with `workers: 1`) is deliberately NOT honored: tests relying on it will
|
|
@@ -41,7 +49,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
41
49
|
};
|
|
42
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
51
|
exports.buildPlanIndex = buildPlanIndex;
|
|
44
|
-
exports.computeGatedProjects = computeGatedProjects;
|
|
45
52
|
exports.shouldRunDuringHealRerun = shouldRunDuringHealRerun;
|
|
46
53
|
exports.expandTargetsWithSerialCompanions = expandTargetsWithSerialCompanions;
|
|
47
54
|
exports.resetHealRerunPlanCacheForTesting = resetHealRerunPlanCacheForTesting;
|
|
@@ -52,52 +59,47 @@ const envVars_1 = require("../../envVars");
|
|
|
52
59
|
const model_1 = require("../../reporter/model");
|
|
53
60
|
const Logger_1 = require("../../utils/Logger");
|
|
54
61
|
function buildPlanIndex(plan) {
|
|
55
|
-
const
|
|
62
|
+
const byFile = new Map();
|
|
63
|
+
const targetProjects = new Set();
|
|
56
64
|
for (const target of plan.targets ?? []) {
|
|
57
65
|
if (!target?.file || !target?.title) {
|
|
58
66
|
continue;
|
|
59
67
|
}
|
|
60
68
|
const file = path_1.default.resolve(target.file);
|
|
61
|
-
if (!
|
|
62
|
-
|
|
69
|
+
if (!byFile.has(file)) {
|
|
70
|
+
byFile.set(file, new Set());
|
|
63
71
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* The projects the rerun gate should apply to: the heal targets' own
|
|
70
|
-
* projects, minus any project that is a declared (transitive) dependency of
|
|
71
|
-
* another target project — the dependency declaration wins, and that project
|
|
72
|
-
* runs in full.
|
|
73
|
-
*/
|
|
74
|
-
function computeGatedProjects(targetProjects, projectDependencies) {
|
|
75
|
-
const targets = [
|
|
76
|
-
...new Set(targetProjects.filter((name) => Boolean(name))),
|
|
77
|
-
];
|
|
78
|
-
if (!projectDependencies) {
|
|
79
|
-
return targets;
|
|
80
|
-
}
|
|
81
|
-
// Every project reachable through any target's declared dependency chain.
|
|
82
|
-
const reachable = new Set();
|
|
83
|
-
const visit = (name) => {
|
|
84
|
-
for (const dependency of projectDependencies[name] ?? []) {
|
|
85
|
-
if (!reachable.has(dependency)) {
|
|
86
|
-
reachable.add(dependency);
|
|
87
|
-
visit(dependency);
|
|
88
|
-
}
|
|
72
|
+
byFile.get(file).add(target.title);
|
|
73
|
+
if (target.projectName) {
|
|
74
|
+
targetProjects.add(target.projectName);
|
|
89
75
|
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return targets.filter((name) => !reachable.has(name));
|
|
76
|
+
}
|
|
77
|
+
return { byFile, targetProjects };
|
|
93
78
|
}
|
|
94
79
|
/**
|
|
95
|
-
* Pure decision: should the test in `file` with `title`
|
|
96
|
-
* during the heal rerun? The plan is fully
|
|
97
|
-
* already expanded into it by the
|
|
80
|
+
* Pure decision: should the test in `file` with `title` (owned by project
|
|
81
|
+
* `projectName`) actually execute during the heal rerun? The plan is fully
|
|
82
|
+
* explicit — serial companions were already expanded into it by the
|
|
83
|
+
* orchestrator.
|
|
84
|
+
*
|
|
85
|
+
* Dependency/setup projects (auth login, fixture seeding, …) are pulled in by
|
|
86
|
+
* Playwright because a target project declares them in `dependencies`. They
|
|
87
|
+
* are never themselves heal targets, but their tests MUST run so the state the
|
|
88
|
+
* targets depend on (storage-state auth files, seeded documents) is in place
|
|
89
|
+
* before the rerun. The rerun command only ever passes `--project=<target>`,
|
|
90
|
+
* so any project Playwright runs that is not a target project is, by
|
|
91
|
+
* construction, such a dependency project — run it in full. Guarded on a
|
|
92
|
+
* non-empty target-project set so the gate degrades to pure file+title
|
|
93
|
+
* matching when project names are unavailable.
|
|
98
94
|
*/
|
|
99
95
|
function shouldRunDuringHealRerun(params) {
|
|
100
|
-
const
|
|
96
|
+
const { index, projectName } = params;
|
|
97
|
+
if (projectName !== undefined &&
|
|
98
|
+
index.targetProjects.size > 0 &&
|
|
99
|
+
!index.targetProjects.has(projectName)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
const titles = index.byFile.get(path_1.default.resolve(params.file));
|
|
101
103
|
return titles?.has(params.title) ?? false;
|
|
102
104
|
}
|
|
103
105
|
/**
|
|
@@ -160,61 +162,47 @@ function expandTargetsWithSerialCompanions(targets, initialReport) {
|
|
|
160
162
|
}
|
|
161
163
|
return expanded;
|
|
162
164
|
}
|
|
163
|
-
let
|
|
164
|
-
function
|
|
165
|
-
if (planPathOverride === undefined &&
|
|
166
|
-
return
|
|
165
|
+
let cachedPlanIndex;
|
|
166
|
+
function getPlanIndex(planPathOverride) {
|
|
167
|
+
if (planPathOverride === undefined && cachedPlanIndex !== undefined) {
|
|
168
|
+
return cachedPlanIndex;
|
|
167
169
|
}
|
|
168
170
|
const planPath = planPathOverride ?? envVars_1.env.data.DONOBU_AUTO_HEAL_PLAN_PATH;
|
|
169
|
-
let
|
|
171
|
+
let index = null;
|
|
170
172
|
if (planPath) {
|
|
171
173
|
try {
|
|
172
174
|
const raw = fs_1.default.readFileSync(planPath, 'utf8');
|
|
173
|
-
|
|
174
|
-
loaded = {
|
|
175
|
-
index: buildPlanIndex(plan),
|
|
176
|
-
gatedProjects: new Set(plan.gatedProjects ??
|
|
177
|
-
// Older plans carry no project list — gate the targets' own
|
|
178
|
-
// projects, leaving everything else (dependencies) untouched.
|
|
179
|
-
plan.targets
|
|
180
|
-
?.map((target) => target.projectName)
|
|
181
|
-
.filter((name) => Boolean(name)) ??
|
|
182
|
-
[]),
|
|
183
|
-
};
|
|
175
|
+
index = buildPlanIndex(JSON.parse(raw));
|
|
184
176
|
}
|
|
185
177
|
catch (error) {
|
|
186
178
|
Logger_1.appLogger.warn(`Auto-heal rerun plan at ${planPath} could not be read; running all collected tests.`, error);
|
|
187
|
-
|
|
179
|
+
index = null;
|
|
188
180
|
}
|
|
189
181
|
}
|
|
190
182
|
if (planPathOverride === undefined) {
|
|
191
|
-
|
|
183
|
+
cachedPlanIndex = index;
|
|
192
184
|
}
|
|
193
|
-
return
|
|
185
|
+
return index;
|
|
194
186
|
}
|
|
195
187
|
/** Test-only: reset the memoized plan so each test can load its own. */
|
|
196
188
|
function resetHealRerunPlanCacheForTesting() {
|
|
197
|
-
|
|
189
|
+
cachedPlanIndex = undefined;
|
|
198
190
|
}
|
|
199
191
|
/**
|
|
200
192
|
* Called from the Donobu auto fixture before any browser fixture initializes.
|
|
201
|
-
* Outside heal reruns this is a no-op. During a rerun, tests
|
|
202
|
-
*
|
|
203
|
-
* no context, no page, no cost. Tests in ungated projects (declared
|
|
204
|
-
* dependencies of the targets, teardown projects) always run.
|
|
193
|
+
* Outside heal reruns this is a no-op. During a rerun, tests outside the plan
|
|
194
|
+
* are annotated and skipped on the spot — no context, no page, no cost.
|
|
205
195
|
*/
|
|
206
196
|
function maybeSkipForHealRerun(testInfo, options) {
|
|
207
|
-
const
|
|
208
|
-
if (!
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (!plan.gatedProjects.has(testInfo.project.name)) {
|
|
197
|
+
const index = getPlanIndex(options?.planPath);
|
|
198
|
+
if (!index) {
|
|
212
199
|
return;
|
|
213
200
|
}
|
|
214
201
|
const shouldRun = shouldRunDuringHealRerun({
|
|
215
|
-
index
|
|
202
|
+
index,
|
|
216
203
|
file: testInfo.file,
|
|
217
204
|
title: testInfo.title,
|
|
205
|
+
projectName: testInfo.project?.name,
|
|
218
206
|
});
|
|
219
207
|
if (shouldRun) {
|
|
220
208
|
return;
|
|
@@ -41,27 +41,6 @@ function buildDonobuReport(resultsByTest, rootDir) {
|
|
|
41
41
|
}
|
|
42
42
|
byTitle.get(test.title).push(test);
|
|
43
43
|
}
|
|
44
|
-
// Declared project dependency graph (`FullProject.dependencies`), keyed by
|
|
45
|
-
// project name. The auto-heal orchestrator uses it to keep the rerun gate
|
|
46
|
-
// away from projects that are declared dependencies of heal targets — those
|
|
47
|
-
// must run in full, exactly as Playwright schedules them.
|
|
48
|
-
const projectDependencies = {};
|
|
49
|
-
for (const test of resultsByTest.keys()) {
|
|
50
|
-
try {
|
|
51
|
-
let suite = test.parent;
|
|
52
|
-
while (suite && suite.type !== 'project') {
|
|
53
|
-
suite = suite.parent;
|
|
54
|
-
}
|
|
55
|
-
const project = suite?.project();
|
|
56
|
-
if (project?.name && !(project.name in projectDependencies)) {
|
|
57
|
-
projectDependencies[project.name] = [...(project.dependencies ?? [])];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
// Reporter shape drift — omit the entry; the orchestrator degrades to
|
|
62
|
-
// gating only the heal targets' own projects.
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
44
|
const suites = [];
|
|
66
45
|
for (const [file, titleMap] of byFile) {
|
|
67
46
|
const specs = [];
|
|
@@ -137,7 +116,7 @@ function buildDonobuReport(resultsByTest, rootDir) {
|
|
|
137
116
|
}
|
|
138
117
|
suites.push({ file, specs });
|
|
139
118
|
}
|
|
140
|
-
return { suites, metadata: {
|
|
119
|
+
return { suites, metadata: {} };
|
|
141
120
|
}
|
|
142
121
|
/**
|
|
143
122
|
* Whether any enclosing suite is in serial mode (`test.describe.serial` or
|
package/dist/reporter/model.d.ts
CHANGED
|
@@ -57,13 +57,6 @@ export interface DonobuReportMetadata {
|
|
|
57
57
|
/** True on reports that are the result of merging an initial + heal run. */
|
|
58
58
|
donobuMergedReport?: boolean;
|
|
59
59
|
mergedAtIso?: string;
|
|
60
|
-
/**
|
|
61
|
-
* Declared Playwright project dependency graph (`FullProject.dependencies`)
|
|
62
|
-
* keyed by project name, recorded by the reporter. The auto-heal
|
|
63
|
-
* orchestrator uses it to exclude declared dependencies of heal targets
|
|
64
|
-
* from the rerun gate — they must run in full.
|
|
65
|
-
*/
|
|
66
|
-
projectDependencies?: Record<string, string[]>;
|
|
67
60
|
sources?: {
|
|
68
61
|
initial?: string | null;
|
|
69
62
|
autoHeal?: string | null;
|