donobu 5.60.1 → 5.60.3

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.
Files changed (43) hide show
  1. package/dist/cli/donobu-cli.js +158 -52
  2. package/dist/envVars.d.ts +4 -0
  3. package/dist/envVars.js +12 -0
  4. package/dist/esm/cli/donobu-cli.js +158 -52
  5. package/dist/esm/envVars.d.ts +4 -0
  6. package/dist/esm/envVars.js +12 -0
  7. package/dist/esm/lib/page/extendPage.d.ts +6 -0
  8. package/dist/esm/lib/page/extendPage.js +24 -1
  9. package/dist/esm/lib/test/healRerunGate.d.ts +102 -0
  10. package/dist/esm/lib/test/healRerunGate.js +228 -0
  11. package/dist/esm/lib/test/testExtension.d.ts +1 -0
  12. package/dist/esm/lib/test/testExtension.js +20 -10
  13. package/dist/esm/managers/DonobuStack.d.ts +19 -19
  14. package/dist/esm/reporter/buildReport.js +54 -1
  15. package/dist/esm/reporter/merge.d.ts +1 -6
  16. package/dist/esm/reporter/merge.js +57 -35
  17. package/dist/esm/reporter/model.d.ts +16 -0
  18. package/dist/esm/reporter/model.js +10 -1
  19. package/dist/esm/reporter/render.js +34 -12
  20. package/dist/esm/reporter/renderMarkdown.js +148 -93
  21. package/dist/esm/reporter/renderSlack.js +39 -28
  22. package/dist/esm/reporter/reportWalk.d.ts +16 -6
  23. package/dist/esm/reporter/reportWalk.js +63 -13
  24. package/dist/esm/utils/BrowserUtils.d.ts +4 -4
  25. package/dist/lib/page/extendPage.d.ts +6 -0
  26. package/dist/lib/page/extendPage.js +24 -1
  27. package/dist/lib/test/healRerunGate.d.ts +102 -0
  28. package/dist/lib/test/healRerunGate.js +228 -0
  29. package/dist/lib/test/testExtension.d.ts +1 -0
  30. package/dist/lib/test/testExtension.js +20 -10
  31. package/dist/managers/DonobuStack.d.ts +19 -19
  32. package/dist/reporter/buildReport.js +54 -1
  33. package/dist/reporter/merge.d.ts +1 -6
  34. package/dist/reporter/merge.js +57 -35
  35. package/dist/reporter/model.d.ts +16 -0
  36. package/dist/reporter/model.js +10 -1
  37. package/dist/reporter/render.js +34 -12
  38. package/dist/reporter/renderMarkdown.js +148 -93
  39. package/dist/reporter/renderSlack.js +39 -28
  40. package/dist/reporter/reportWalk.d.ts +16 -6
  41. package/dist/reporter/reportWalk.js +63 -13
  42. package/dist/utils/BrowserUtils.d.ts +4 -4
  43. package/package.json +4 -4
@@ -62,6 +62,7 @@ const path = __importStar(require("path"));
62
62
  const v4_1 = require("zod/v4");
63
63
  const envVars_1 = require("../envVars");
64
64
  const gptClients_1 = require("../lib/test/fixtures/gptClients");
65
+ const healRerunGate_1 = require("../lib/test/healRerunGate");
65
66
  const donobuTestStack_1 = require("../lib/test/utils/donobuTestStack");
66
67
  const triageTestFailure_1 = require("../lib/test/utils/triageTestFailure");
67
68
  const fileUploadWorkerRegistry_1 = require("../persistence/files/fileUploadWorkerRegistry");
@@ -73,6 +74,7 @@ const render_1 = require("../reporter/render");
73
74
  const renderMarkdown_1 = require("../reporter/renderMarkdown");
74
75
  const renderPullRequestBody_1 = require("../reporter/renderPullRequestBody");
75
76
  const renderSlack_1 = require("../reporter/renderSlack");
77
+ const reportWalk_1 = require("../reporter/reportWalk");
76
78
  const slack_1 = require("../reporter/slack");
77
79
  const Logger_1 = require("../utils/Logger");
78
80
  const FAILURE_EVIDENCE_PREFIX = 'failure-evidence-';
@@ -988,12 +990,21 @@ function evaluateAutoHealEligibility(plans) {
988
990
  /**
989
991
  * Coalesce directives from one or more treatment plans into a single Playwright
990
992
  * invocation. Multiple failed tests can be healed in a single rerun, so we
991
- * gather all relevant files/projects/titles here.
993
+ * gather all relevant projects (or files, as a fallback) here.
994
+ *
995
+ * The rerun deliberately targets whole Playwright *projects*, not individual
996
+ * failed tests. Suites commonly chain ordered tests — checkpoint guards,
997
+ * serial files, cross-file prerequisites within a project — and a rerun that
998
+ * filters down to just the failed titles can never heal such a test: its
999
+ * prerequisite never runs, the test skips itself, and no fix is exercised.
1000
+ * The project is the unit where Playwright encodes that ordering (testMatch
1001
+ * order, workers, dependencies), so it is the narrowest scope that is always
1002
+ * safe to re-run. File narrowing is kept only as a fallback when no project
1003
+ * name could be resolved.
992
1004
  */
993
1005
  function derivePlaywrightDirectiveArgs(descriptors) {
994
1006
  const targetFiles = new Set();
995
1007
  const targetProjects = new Set();
996
- const targetTitles = new Set();
997
1008
  const additionalArgs = [];
998
1009
  for (const descriptor of descriptors) {
999
1010
  const directives = descriptor.plan.automationDirectives;
@@ -1008,9 +1019,6 @@ function derivePlaywrightDirectiveArgs(descriptors) {
1008
1019
  if (projectCandidate && !looksLikePath(projectCandidate)) {
1009
1020
  targetProjects.add(projectCandidate);
1010
1021
  }
1011
- if (descriptor.testCase.title) {
1012
- targetTitles.add(descriptor.testCase.title);
1013
- }
1014
1022
  if (directives.additionalPlaywrightArgs) {
1015
1023
  directives.additionalPlaywrightArgs.forEach((arg) => {
1016
1024
  additionalArgs.push(arg);
@@ -1018,20 +1026,11 @@ function derivePlaywrightDirectiveArgs(descriptors) {
1018
1026
  }
1019
1027
  }
1020
1028
  return {
1021
- files: Array.from(targetFiles),
1029
+ files: targetProjects.size > 0 ? [] : Array.from(targetFiles),
1022
1030
  projects: Array.from(targetProjects),
1023
- grepPattern: targetTitles.size > 0
1024
- ? Array.from(targetTitles)
1025
- .map((title) => escapeRegex(title))
1026
- .join('|')
1027
- : undefined,
1028
1031
  extras: additionalArgs,
1029
1032
  };
1030
1033
  }
1031
- // We match test titles via `--grep`, so ensure literal characters are escaped.
1032
- function escapeRegex(value) {
1033
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1034
- }
1035
1034
  // Some teams name projects after directories (e.g. `projects/mobile`); treat those as file paths.
1036
1035
  function looksLikePath(value) {
1037
1036
  return value.includes('/') || value.includes('\\');
@@ -1057,21 +1056,34 @@ function extractOriginalFiles(args) {
1057
1056
  }
1058
1057
  /**
1059
1058
  * Preserve most user-provided Playwright flags (e.g. `--config`, `--workers`).
1060
- * We only strip flags we know we're going to replace (projects, grep, reporter).
1059
+ * We only strip flags we know we're going to replace (projects, reporter).
1060
+ * The user's own `--grep` IS preserved: it scoped the initial run, so the heal
1061
+ * rerun must stay inside it (the rerun no longer adds a grep of its own).
1061
1062
  */
1062
1063
  function extractPreservedOptions(args) {
1063
- return args.slice(1).filter((arg) => {
1064
+ const preserved = [];
1065
+ const rest = args.slice(1);
1066
+ for (let i = 0; i < rest.length; i += 1) {
1067
+ const arg = rest[i];
1064
1068
  if (!arg.startsWith('--')) {
1065
- return false;
1069
+ continue;
1066
1070
  }
1067
- const optionName = arg.startsWith('--') ? arg.split('=')[0] : arg;
1068
- return (optionName !== '--project' &&
1069
- !optionName.startsWith('--project=') &&
1070
- optionName !== '--grep' &&
1071
- optionName !== '--reporter' &&
1072
- !optionName.startsWith('--grep=') &&
1073
- !optionName.startsWith('--reporter='));
1074
- });
1071
+ const optionName = arg.split('=')[0];
1072
+ if (optionName === '--project' || optionName === '--reporter') {
1073
+ continue;
1074
+ }
1075
+ preserved.push(arg);
1076
+ // Space-separated flag values (`--grep pattern`) would otherwise be
1077
+ // dropped by the `--`-prefix filter above; carry the grep value along.
1078
+ if ((optionName === '--grep' || optionName === '--grep-invert') &&
1079
+ arg === optionName &&
1080
+ i + 1 < rest.length &&
1081
+ !rest[i + 1].startsWith('--')) {
1082
+ preserved.push(rest[i + 1]);
1083
+ i += 1;
1084
+ }
1085
+ }
1086
+ return preserved;
1075
1087
  }
1076
1088
  /**
1077
1089
  * Merge the user's original Playwright command with the automation directives
@@ -1084,14 +1096,14 @@ function buildPlaywrightArgsWithDirectives(originalArgs, directives) {
1084
1096
  : extractOriginalFiles(originalArgs);
1085
1097
  const preservedOptions = extractPreservedOptions(originalArgs);
1086
1098
  const projectArgs = directives.projects.map((project) => `--project=${project}`);
1087
- const grepArgs = directives.grepPattern
1088
- ? ['--grep', directives.grepPattern]
1089
- : [];
1099
+ // When the rerun targets whole projects, file filters from the original
1100
+ // invocation would shrink the scope below the project's testMatch and can
1101
+ // exclude prerequisite tests — drop them; `--project` already narrows.
1102
+ const fileArgs = directives.projects.length > 0 ? [] : files;
1090
1103
  const finalArgs = [
1091
1104
  'test',
1092
- ...files,
1105
+ ...fileArgs,
1093
1106
  ...projectArgs,
1094
- ...grepArgs,
1095
1107
  ...directives.extras,
1096
1108
  ...preservedOptions,
1097
1109
  ];
@@ -1317,12 +1329,28 @@ async function attemptAutoHealRun(params) {
1317
1329
  let healExitCode = params.currentExitCode;
1318
1330
  const healOptions = {
1319
1331
  ...params.options,
1320
- clearAiCache: params.options.clearAiCache || evaluation.clearPageAiCache === true,
1321
1332
  autoHeal: false,
1322
1333
  triageOutputDir: staging.triageBaseDir,
1323
1334
  };
1324
- if (evaluation.clearPageAiCache && !params.options.clearAiCache) {
1325
- Logger_1.appLogger.info('Auto-heal: clearing Page.AI cache as recommended by the treatment plan.');
1335
+ // Heal targets, expanded with their declared `describe.serial` siblings so
1336
+ // the rerun executes the whole declared group (Playwright's own retry
1337
+ // semantics for serial chains). The serial flags come from the initial
1338
+ // run's Donobu report; without it, targets run unexpanded.
1339
+ const healTargets = (0, healRerunGate_1.expandTargetsWithSerialCompanions)(evaluation.eligiblePlans.map((record) => ({
1340
+ file: record.evidence.failureContext.testCase.file ?? '',
1341
+ title: record.evidence.failureContext.testCase.title,
1342
+ projectName: record.evidence.failureContext.testCase.projectName,
1343
+ })), params.initialReport ?? null);
1344
+ // Cache invalidation is scoped to the heal targets' spec files: serial
1345
+ // prerequisites and any other re-running test keep their fast cache replay.
1346
+ // The user's own `--clear-ai-cache` flag stays run-wide.
1347
+ const clearCacheFiles = evaluation.clearPageAiCache && !params.options.clearAiCache
1348
+ ? Array.from(new Set(healTargets
1349
+ .map((target) => target.file)
1350
+ .filter((file) => Boolean(file))))
1351
+ : [];
1352
+ if (clearCacheFiles.length > 0) {
1353
+ Logger_1.appLogger.info(`Auto-heal: clearing Page.AI cache for ${clearCacheFiles.length} target spec file(s) as recommended by the treatment plan.`);
1326
1354
  }
1327
1355
  const healArgsWithDirectives = buildPlaywrightArgsWithDirectives(params.playwrightArgs, evaluation.directives);
1328
1356
  const healArgsForRun = overrideOutputDir(healArgsWithDirectives, staging.playwrightOutputDir);
@@ -1346,7 +1374,22 @@ async function attemptAutoHealRun(params) {
1346
1374
  applyJsonReportEnv(envOverrides, staging.playwrightOutputDir);
1347
1375
  // Flag downstream systems so they know this invocation came from auto-heal.
1348
1376
  envOverrides.DONOBU_AUTO_HEAL_ACTIVE = '1';
1349
- Logger_1.appLogger.info(`Auto-heal: applying directives from ${evaluation.eligiblePlans.length} treatment plan(s) and re-running Playwright...`);
1377
+ if (clearCacheFiles.length > 0) {
1378
+ envOverrides.DONOBU_PAGE_AI_CLEAR_CACHE_FILES =
1379
+ JSON.stringify(clearCacheFiles);
1380
+ }
1381
+ // The rerun plan drives the runtime gate in the test fixture: within the
1382
+ // gated projects, only the heal targets (and their declared
1383
+ // `describe.serial` siblings) execute. Projects outside the gate —
1384
+ // declared dependencies of target projects, teardown projects — run in
1385
+ // full, exactly as Playwright schedules them.
1386
+ 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');
1391
+ envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
1392
+ Logger_1.appLogger.info(`Auto-heal: re-running ${healTargets.length} targeted test(s) from ${evaluation.eligiblePlans.length} treatment plan(s)...`);
1350
1393
  const healJsonReportPath = path.join(staging.playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME);
1351
1394
  const reporterSetup = await ensureJsonReporter(healArgsForRun, {
1352
1395
  jsonOutputFile: healJsonReportPath,
@@ -1396,6 +1439,30 @@ async function attemptAutoHealRun(params) {
1396
1439
  // Prefer the heal reporter's state file; fall back to Playwright's JSON
1397
1440
  // reporter output for configs that don't wire up the Donobu reporter.
1398
1441
  const healReport = await loadDonobuReportForMerge(staging.playwrightOutputDir, healReportCopy?.destinationPath ?? undefined);
1442
+ // A heal rerun "succeeds" only when every test it set out to heal
1443
+ // actually passed in the rerun. The rerun's exit code alone can't tell:
1444
+ // a target that skipped itself (e.g. a precondition guard fired) exits
1445
+ // zero, and the project-scope rerun includes unrelated tests. Matched on
1446
+ // title + project — file paths differ in shape between treatment plans
1447
+ // (absolute) and reports (rootDir-relative).
1448
+ if (healExitCode === 0) {
1449
+ const unhealedTargets = evaluation.eligiblePlans.filter((record) => {
1450
+ const testCase = record.evidence.failureContext.testCase;
1451
+ const healTest = findTestInReport(healReport, testCase.title, testCase.projectName);
1452
+ if (!healTest) {
1453
+ return true;
1454
+ }
1455
+ const status = (0, reportWalk_1.statusOf)(healTest);
1456
+ return status !== 'passed' && status !== 'flaky' && status !== 'healed';
1457
+ });
1458
+ if (unhealedTargets.length > 0) {
1459
+ const titles = unhealedTargets
1460
+ .map((record) => record.evidence.failureContext.testCase.title)
1461
+ .join('", "');
1462
+ Logger_1.appLogger.warn(`Auto-heal rerun did not fix ${unhealedTargets.length} targeted test(s): "${titles}" (failed again, or never re-attempted because a precondition was missing). Keeping failing status.`);
1463
+ healExitCode = 1;
1464
+ }
1465
+ }
1399
1466
  if (params.initialReport || healReport) {
1400
1467
  // Write the merged report directly to the user's JSON reporter target
1401
1468
  // when one exists. When the user did not configure a JSON reporter,
@@ -1409,20 +1476,15 @@ async function attemptAutoHealRun(params) {
1409
1476
  healReport,
1410
1477
  healReportSourcePath: healReportCopy?.destinationPath ?? undefined,
1411
1478
  mergedReportPath: params.userJsonOutputFile ?? undefined,
1412
- healedTests: evaluation.eligiblePlans.map((record) => ({
1413
- plan: record.plan,
1414
- testCase: record.evidence.failureContext.testCase,
1415
- })),
1416
- healSucceeded: healExitCode === 0,
1417
1479
  outputDir: params.playwrightOutputDir,
1418
1480
  triageRunDir: params.triageRunDir,
1419
1481
  });
1420
1482
  if (mergedReport) {
1421
1483
  await regenerateDonobuReports(mergedReport);
1422
1484
  await writeAutoHealPullRequestBody(mergedReport, params.playwrightOutputDir);
1423
- // The heal rerun only re-runs the grep-filtered subset of healable
1424
- // tests, so `healExitCode` is blind to failures triage declined to
1425
- // retry (e.g. real product bugs). Re-derive the status from the merged
1485
+ // The heal rerun only re-runs the projects containing healable tests,
1486
+ // so `healExitCode` is blind to failures triage declined to retry
1487
+ // (e.g. real product bugs). Re-derive the status from the merged
1426
1488
  // report so those remaining failures still fail CI. We only escalate
1427
1489
  // (never downgrade a non-zero heal exit) to preserve infra/crash codes.
1428
1490
  const remainingFailures = countUnexpectedTests(mergedReport);
@@ -1450,6 +1512,43 @@ function countUnexpectedTests(report) {
1450
1512
  const unexpected = stats?.unexpected;
1451
1513
  return typeof unexpected === 'number' ? unexpected : undefined;
1452
1514
  }
1515
+ /**
1516
+ * Find a test entry in a report by title + project name. File paths are
1517
+ * deliberately not part of the lookup: treatment plans carry absolute paths
1518
+ * while reports carry rootDir-relative ones, and title + project is already
1519
+ * how Playwright disambiguates tests within a run.
1520
+ */
1521
+ function findTestInReport(report, title, projectName) {
1522
+ if (!report?.suites || !title) {
1523
+ return null;
1524
+ }
1525
+ const visitSuite = (suite) => {
1526
+ for (const spec of suite.specs ?? []) {
1527
+ if (spec.title !== title) {
1528
+ continue;
1529
+ }
1530
+ for (const test of spec.tests ?? []) {
1531
+ if (!projectName || (test.projectName ?? '') === projectName) {
1532
+ return test;
1533
+ }
1534
+ }
1535
+ }
1536
+ for (const child of suite.suites ?? []) {
1537
+ const found = visitSuite(child);
1538
+ if (found) {
1539
+ return found;
1540
+ }
1541
+ }
1542
+ return null;
1543
+ };
1544
+ for (const suite of report.suites) {
1545
+ const found = visitSuite(suite);
1546
+ if (found) {
1547
+ return found;
1548
+ }
1549
+ }
1550
+ return null;
1551
+ }
1453
1552
  /**
1454
1553
  * Filename of the auto-heal PR body artifact. Lands in the Playwright output
1455
1554
  * directory alongside the merged JSON / HTML / Markdown reports so the
@@ -1667,8 +1766,6 @@ async function mergePlaywrightJsonReports(params) {
1667
1766
  const merged = (0, merge_1.mergeReports)({
1668
1767
  initialReport: params.initialReport,
1669
1768
  healReport: params.healReport,
1670
- healedTests: params.healedTests,
1671
- healSucceeded: params.healSucceeded,
1672
1769
  triageRunDir: params.triageRunDir,
1673
1770
  initialReportSourcePath: params.initialReportSourcePath,
1674
1771
  healReportSourcePath: params.healReportSourcePath,
@@ -2085,6 +2182,22 @@ async function runHealCommand(cliArgs) {
2085
2182
  applyJsonReportEnv(envOverrides, playwrightOutputDir);
2086
2183
  // Downstream hooks check this flag to avoid recursive auto-heal loops.
2087
2184
  envOverrides.DONOBU_AUTO_HEAL_ACTIVE = '1';
2185
+ // Same runtime gating as the automatic rerun: only the plan's test (plus
2186
+ // declared `describe.serial` siblings) executes within its own project;
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.
2189
+ const healPlanPath = path.join(os.tmpdir(), `donobu-heal-rerun-plan-${Date.now()}.json`);
2190
+ await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
2191
+ targets: [
2192
+ {
2193
+ file: persisted.failure.testCase.file,
2194
+ title: persisted.failure.testCase.title,
2195
+ projectName: persisted.failure.testCase.projectName,
2196
+ },
2197
+ ],
2198
+ gatedProjects: (0, healRerunGate_1.computeGatedProjects)([persisted.failure.testCase.projectName], undefined),
2199
+ }), 'utf8');
2200
+ envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
2088
2201
  Logger_1.appLogger.info(`Re-running Playwright using treatment plan at ${parsed.planPath}...`);
2089
2202
  const healJsonReportPath = path.join(playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME);
2090
2203
  const reporterSetup = await ensureJsonReporter(healArgsWithDirectives, {
@@ -2123,13 +2236,6 @@ async function runHealCommand(cliArgs) {
2123
2236
  healReport,
2124
2237
  healReportSourcePath: healReportCopy?.destinationPath ?? undefined,
2125
2238
  mergedReportPath,
2126
- healedTests: [
2127
- {
2128
- plan: persisted.plan,
2129
- testCase: persisted.failure.testCase,
2130
- },
2131
- ],
2132
- healSucceeded: exitCode === 0,
2133
2239
  outputDir: path.dirname(mergedReportPath),
2134
2240
  });
2135
2241
  if (mergedReport) {
package/dist/envVars.d.ts CHANGED
@@ -59,6 +59,8 @@ export declare const env: Env<{
59
59
  true: "true";
60
60
  false: "false";
61
61
  }>>;
62
+ DONOBU_PAGE_AI_CLEAR_CACHE_FILES: z.ZodOptional<z.ZodArray<z.ZodString>>;
63
+ DONOBU_AUTO_HEAL_PLAN_PATH: z.ZodOptional<z.ZodString>;
62
64
  GOOGLE_GENERATIVE_AI_API_KEY: z.ZodOptional<z.ZodString>;
63
65
  GOOGLE_GENERATIVE_AI_MODEL_NAME: z.ZodOptional<z.ZodString>;
64
66
  OLLAMA_MODEL_NAME: z.ZodOptional<z.ZodString>;
@@ -116,6 +118,8 @@ export declare const env: Env<{
116
118
  CI_COMMIT_REF_NAME?: string | undefined;
117
119
  PLAYWRIGHT_JSON_OUTPUT_DIR?: string | undefined;
118
120
  DONOBU_PAGE_AI_CLEAR_CACHE?: "0" | "1" | "true" | "false" | undefined;
121
+ DONOBU_PAGE_AI_CLEAR_CACHE_FILES?: string[] | undefined;
122
+ DONOBU_AUTO_HEAL_PLAN_PATH?: string | undefined;
119
123
  GOOGLE_GENERATIVE_AI_API_KEY?: string | undefined;
120
124
  GOOGLE_GENERATIVE_AI_MODEL_NAME?: string | undefined;
121
125
  OLLAMA_MODEL_NAME?: string | undefined;
package/dist/envVars.js CHANGED
@@ -128,6 +128,18 @@ re-render reports from merged data).`),
128
128
  bypass and invalidate Page.AI cache entries for the current run. The Donobu
129
129
  CLI sets this automatically when invoked with \`--clear-ai-cache\` so a retry
130
130
  always regenerates selectors from scratch.`),
131
+ DONOBU_PAGE_AI_CLEAR_CACHE_FILES: v4_1.z.array(v4_1.z.string()).optional()
132
+ .describe(`Spec file paths whose Page.AI cache entries should be bypassed and
133
+ invalidated for the current run (JSON-encoded on the wire). Set by the
134
+ auto-heal orchestrator so the rerun regenerates selectors only for the spec
135
+ files that contain heal targets, while every other re-running test keeps its
136
+ fast deterministic cache replay.`),
137
+ DONOBU_AUTO_HEAL_PLAN_PATH: v4_1.z.string().optional()
138
+ .describe(`Path to the auto-heal rerun plan (JSON) written by the Donobu CLI before the
139
+ heal rerun. When set, a Donobu auto fixture skips every test that is not part
140
+ of the plan (heal targets and, for targets inside a \`describe.serial\` group,
141
+ their serial siblings) before any browser fixture initializes, so the rerun
142
+ only pays for what the heal actually needs.`),
131
143
  GOOGLE_GENERATIVE_AI_API_KEY: v4_1.z.string().optional()
132
144
  .describe(`Automatically create GPT configurations for Google Gemini using this API key.
133
145
  For convenience, the created configuration names will reflect the