@yasserkhanorg/e2e-agents 0.3.3 → 0.3.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/README.md CHANGED
@@ -212,7 +212,9 @@ Optional config file `e2e-ai-agents.config.json` (JSON):
212
212
  "forceFullOnWarningsAtOrAbove": 2,
213
213
  "forceFullOnP0WithGaps": true,
214
214
  "forceFullOnRiskyFiles": true,
215
- "riskyFilePatterns": ["**/auth/**", "**/permissions/**", "**/security/**", "**/*.sql"]
215
+ "riskyFilePatterns": ["**/auth/**", "**/permissions/**", "**/security/**", "**/*.sql"],
216
+ "enforcementMode": "advisory",
217
+ "blockOnActions": ["must-add-tests"]
216
218
  },
217
219
  "flags": { "defaultState": "on" },
218
220
  "audience": { "defaultRoles": ["member"] },
@@ -257,12 +259,15 @@ Notes:
257
259
  - `impact/gap` pipeline output now includes `pipeline.mcp` (`requested`, `active`, `backend`) so MCP activation is explicit.
258
260
  - `suggest` writes `.e2e-ai-agents/plan.json` with `runSet` (`smoke|targeted|full`) and confidence.
259
261
  - `suggest` also writes `.e2e-ai-agents/ci-summary.md` with CI status: `run-now`, `must-add-tests`, or `safe-to-merge`.
260
- - CLI policy overrides: `--policy-min-confidence`, `--policy-safe-merge-confidence`, `--policy-force-full-on-warnings`, `--policy-risky-patterns`.
262
+ - CLI policy overrides: `--policy-min-confidence`, `--policy-safe-merge-confidence`, `--policy-force-full-on-warnings`, `--policy-risky-patterns`, `--policy-enforcement-mode`, `--policy-block-actions`.
261
263
  - GitHub Actions output wiring: `--github-output $GITHUB_OUTPUT`.
262
264
  - Optional merge gating: `--fail-on-must-add-tests` exits non-zero when uncovered P0/P1 gaps are detected. Leave this flag unset for advisory-only mode.
265
+ - `suggest` now appends run metrics to `.e2e-ai-agents/metrics.jsonl` and writes aggregated `.e2e-ai-agents/metrics-summary.json`.
263
266
  - `impact/gap` now include actionable `testSuggestions` with linked source files and skeleton test code.
264
267
  - `impact/gap` now include `impactModel` metadata (`flowMapping`, `testMapping`, `confidenceClass`, traceability stats, dependency graph stats).
268
+ - `impact/gap` now include `runMetadata` (run id/timestamps/duration/since ref) for auditability.
265
269
  - `impact/gap` now include optional `impactModel.subsystemRisk` stats (map status, matched files/rules, boosted flows).
270
+ - `impact/gap` pipeline result rows now include failure taxonomy (`failureCategory`, `failureCode`) when generation/heal fails.
266
271
  - `feedback` appends outcomes to `.e2e-ai-agents/feedback.json` and recomputes `.e2e-ai-agents/calibration.json`.
267
272
  - `feedback` also computes intelligent flaky scores into `.e2e-ai-agents/flaky-tests.json`.
268
273
  - `traceability-capture` converts Playwright JSON execution report + optional coverage map into `.e2e-ai-agents/traceability-input.json`.
@@ -71,6 +71,8 @@ export interface PolicyConfig {
71
71
  forceFullOnP0WithGaps: boolean;
72
72
  forceFullOnRiskyFiles: boolean;
73
73
  riskyFilePatterns: string[];
74
+ enforcementMode: 'advisory' | 'warn' | 'block';
75
+ blockOnActions: Array<'run-now' | 'must-add-tests' | 'safe-to-merge'>;
74
76
  }
75
77
  export interface DependencyGraphImpactConfig {
76
78
  enabled: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/agent/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/agent/config.ts"],"names":[],"mappings":"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"}
@@ -75,6 +75,7 @@ const DEFAULT_CONFIG = {
75
75
  scenarios: 3,
76
76
  outputDir: 'specs/functional/ai-assisted',
77
77
  heal: true,
78
+ project: 'chrome',
78
79
  mcp: false,
79
80
  mcpAllowFallback: false,
80
81
  },
@@ -123,6 +124,8 @@ const DEFAULT_CONFIG = {
123
124
  '**/*.sql',
124
125
  '**/webhook/**',
125
126
  ],
127
+ enforcementMode: 'advisory',
128
+ blockOnActions: ['must-add-tests'],
126
129
  },
127
130
  flags: {
128
131
  defaultState: 'on',
@@ -269,6 +272,18 @@ function normalizeFlagState(value) {
269
272
  }
270
273
  return undefined;
271
274
  }
275
+ function normalizePolicyEnforcementMode(value) {
276
+ if (value === 'advisory' || value === 'warn' || value === 'block') {
277
+ return value;
278
+ }
279
+ return undefined;
280
+ }
281
+ function normalizePolicyBlockAction(value) {
282
+ if (value === 'run-now' || value === 'must-add-tests' || value === 'safe-to-merge') {
283
+ return value;
284
+ }
285
+ return undefined;
286
+ }
272
287
  function extractConfigPatch(raw) {
273
288
  const patch = {};
274
289
  if (typeof raw.path === 'string') {
@@ -465,6 +480,11 @@ function extractConfigPatch(raw) {
465
480
  }
466
481
  if (raw.policy && typeof raw.policy === 'object') {
467
482
  const policy = raw.policy;
483
+ const blockOnActions = Array.isArray(policy.blockOnActions)
484
+ ? policy.blockOnActions
485
+ .map((value) => normalizePolicyBlockAction(value))
486
+ .filter((value) => Boolean(value))
487
+ : DEFAULT_CONFIG.policy.blockOnActions;
468
488
  patch.policy = {
469
489
  minConfidenceForTargeted: coerceNumber(policy.minConfidenceForTargeted) ?? DEFAULT_CONFIG.policy.minConfidenceForTargeted,
470
490
  safeMergeMinConfidence: coerceNumber(policy.safeMergeMinConfidence) ?? DEFAULT_CONFIG.policy.safeMergeMinConfidence,
@@ -478,6 +498,8 @@ function extractConfigPatch(raw) {
478
498
  riskyFilePatterns: Array.isArray(policy.riskyFilePatterns)
479
499
  ? policy.riskyFilePatterns.filter((pattern) => typeof pattern === 'string')
480
500
  : DEFAULT_CONFIG.policy.riskyFilePatterns,
501
+ enforcementMode: normalizePolicyEnforcementMode(policy.enforcementMode) ?? DEFAULT_CONFIG.policy.enforcementMode,
502
+ blockOnActions: blockOnActions.length > 0 ? blockOnActions : DEFAULT_CONFIG.policy.blockOnActions,
481
503
  };
482
504
  }
483
505
  if (raw.flags && typeof raw.flags === 'object') {
@@ -613,6 +635,12 @@ function resolveConfig(cwd, configPath, overrides) {
613
635
  if (overrides.policy.riskyFilePatterns && overrides.policy.riskyFilePatterns.length > 0) {
614
636
  policyPatch.riskyFilePatterns = overrides.policy.riskyFilePatterns;
615
637
  }
638
+ if (overrides.policy.enforcementMode !== undefined) {
639
+ policyPatch.enforcementMode = overrides.policy.enforcementMode;
640
+ }
641
+ if (overrides.policy.blockOnActions && overrides.policy.blockOnActions.length > 0) {
642
+ policyPatch.blockOnActions = overrides.policy.blockOnActions;
643
+ }
616
644
  config.policy = { ...config.policy, ...policyPatch };
617
645
  }
618
646
  if (overrides?.specPDF) {
@@ -1,4 +1,4 @@
1
- import type { PlanReport } from './plan.js';
1
+ import { type PlanReport } from './plan.js';
2
2
  import type { CalibrationSummary } from './feedback.js';
3
3
  export interface FlakyTestRecord {
4
4
  test: string;
@@ -1 +1 @@
1
- {"version":3,"file":"operational_insights.d.ts","sourceRoot":"","sources":["../../src/agent/operational_insights.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAGtD,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,kBAAkB,CAAC;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAChC,KAAK,CAAC,EAAE;QACJ,wBAAwB,EAAE,eAAe,EAAE,CAAC;QAC5C,2BAA2B,EAAE,MAAM,EAAE,CAAC;QACtC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KAC5B,CAAC;IACF,YAAY,CAAC,EAAE;QACX,MAAM,EAAE,iBAAiB,EAAE,CAAC;QAC5B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;KACjC,CAAC;IACF,WAAW,CAAC,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;CAC/C;AA2CD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CA4FtF"}
1
+ {"version":3,"file":"operational_insights.d.ts","sourceRoot":"","sources":["../../src/agent/operational_insights.ts"],"names":[],"mappings":"AAKA,OAAO,EAAyB,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAClE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAGtD,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,kBAAkB,CAAC;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAChC,KAAK,CAAC,EAAE;QACJ,wBAAwB,EAAE,eAAe,EAAE,CAAC;QAC5C,2BAA2B,EAAE,MAAM,EAAE,CAAC;QACtC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KAC5B,CAAC;IACF,YAAY,CAAC,EAAE;QACX,MAAM,EAAE,iBAAiB,EAAE,CAAC;QAC5B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;KACjC,CAAC;IACF,WAAW,CAAC,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;CAC/C;AA2CD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CA4FtF"}
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.applyOperationalInsights = applyOperationalInsights;
6
6
  const fs_1 = require("fs");
7
7
  const path_1 = require("path");
8
+ const plan_js_1 = require("./plan.js");
8
9
  const test_path_js_1 = require("./test_path.js");
9
10
  function readJson(path) {
10
11
  if (!(0, fs_1.existsSync)(path)) {
@@ -122,5 +123,5 @@ function applyOperationalInsights(plan, appRoot) {
122
123
  }
123
124
  }
124
125
  enhanced.insights = insights;
125
- return enhanced;
126
+ return (0, plan_js_1.refreshPlanEnforcement)(enhanced);
126
127
  }
@@ -7,6 +7,8 @@ export interface PipelineResult {
7
7
  generateStatus: 'success' | 'skipped' | 'failed';
8
8
  healStatus?: 'success' | 'skipped' | 'failed';
9
9
  error?: string;
10
+ failureCategory?: 'config' | 'environment' | 'generation' | 'validation' | 'runtime' | 'quality' | 'path-safety' | 'unknown';
11
+ failureCode?: string;
10
12
  }
11
13
  export interface PipelineSummary {
12
14
  runner: 'playwright-agents' | 'e2e-test-gen' | 'package-native' | 'unknown';
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/agent/pipeline.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAG9C,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,mBAAmB,GAAG,cAAc,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAC5E,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE;QACF,SAAS,EAAE,OAAO,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;QAChB,OAAO,EAAE,mBAAmB,GAAG,cAAc,GAAG,gBAAgB,GAAG,SAAS,CAAC;KAChF,CAAC;CACL;AAED,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAwwBD,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EAAE,EACzB,QAAQ,EAAE,cAAc,GACzB,eAAe,CA4FjB;AAgaD,wBAAgB,qBAAqB,CACjC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,cAAc,GACzB,eAAe,CAyIjB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/agent/pipeline.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAG9C,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;IAC7H,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,mBAAmB,GAAG,cAAc,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAC5E,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE;QACF,SAAS,EAAE,OAAO,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;QAChB,OAAO,EAAE,mBAAmB,GAAG,cAAc,GAAG,gBAAgB,GAAG,SAAS,CAAC;KAChF,CAAC;CACL;AAED,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAk9BD,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EAAE,EACzB,QAAQ,EAAE,cAAc,GACzB,eAAe,CA4FjB;AA4bD,wBAAgB,qBAAqB,CACjC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,cAAc,GACzB,eAAe,CAmJjB"}
@@ -15,6 +15,40 @@ function createMcpStatus(backend, requested) {
15
15
  backend,
16
16
  };
17
17
  }
18
+ function classifyPipelineFailure(result) {
19
+ if (result.failureCategory || result.failureCode) {
20
+ return result;
21
+ }
22
+ if (!result.error) {
23
+ return result;
24
+ }
25
+ const errorText = result.error.toLowerCase();
26
+ if (errorText.includes('outside testsroot')) {
27
+ return { ...result, failureCategory: 'path-safety', failureCode: 'path_outside_tests_root' };
28
+ }
29
+ if (errorText.includes('playwright binary') || errorText.includes('not found')) {
30
+ return { ...result, failureCategory: 'environment', failureCode: 'dependency_missing' };
31
+ }
32
+ if (errorText.includes('compile validation')) {
33
+ return { ...result, failureCategory: 'validation', failureCode: 'compile_validation_failed' };
34
+ }
35
+ if (errorText.includes('runtime validation') || errorText.includes('playwright test failed')) {
36
+ return { ...result, failureCategory: 'runtime', failureCode: 'runtime_validation_failed' };
37
+ }
38
+ if (errorText.includes('quality checks failed') || errorText.includes('invalid test content')) {
39
+ return { ...result, failureCategory: 'quality', failureCode: 'quality_guard_failed' };
40
+ }
41
+ if (errorText.includes('generate failed') || errorText.includes('did not produce expected test file')) {
42
+ return { ...result, failureCategory: 'generation', failureCode: 'generation_failed' };
43
+ }
44
+ return { ...result, failureCategory: 'unknown', failureCode: 'unknown' };
45
+ }
46
+ function finalizePipelineSummary(summary) {
47
+ return {
48
+ ...summary,
49
+ results: summary.results.map(classifyPipelineFailure),
50
+ };
51
+ }
18
52
  function hasE2eTestGenCLI(testsRoot) {
19
53
  const cliPath = (0, path_1.join)(testsRoot, 'e2e-test-gen-cli.ts');
20
54
  return (0, fs_1.existsSync)(cliPath) ? cliPath : null;
@@ -118,6 +152,15 @@ function buildNativeStrategyOrder(flow) {
118
152
  return Array.from(new Set(strategies));
119
153
  }
120
154
  function createDefaultApiSurfaceCatalog() {
155
+ const pwNestedMethods = new Map();
156
+ pwNestedMethods.set('apiClient', new Set([
157
+ 'createPost',
158
+ 'createDirectChannel',
159
+ 'createChannel',
160
+ 'getChannels',
161
+ 'getChannelByName',
162
+ 'getPostsSince',
163
+ ]));
121
164
  return {
122
165
  pwProps: new Set([
123
166
  'initSetup',
@@ -127,7 +170,20 @@ function createDefaultApiSurfaceCatalog() {
127
170
  'apiCreateChannel',
128
171
  'apiCreateUser',
129
172
  'apiLogin',
173
+ 'apiClient',
174
+ ]),
175
+ pwNestedMethods,
176
+ initSetupKeys: new Set([
177
+ 'user',
178
+ 'team',
179
+ 'adminClient',
180
+ 'adminUser',
181
+ 'adminConfig',
182
+ 'userClient',
183
+ 'offTopicUrl',
184
+ 'townSquareUrl',
130
185
  ]),
186
+ initSetupVariableMethods: new Map(),
131
187
  testBrowserMethods: new Set([
132
188
  'login',
133
189
  'openNewBrowserContext',
@@ -161,10 +217,57 @@ function collectMatches(content, pattern) {
161
217
  }
162
218
  return out;
163
219
  }
220
+ function addNestedMethod(catalog, objectName, methodName) {
221
+ const methods = catalog.pwNestedMethods.get(objectName) || new Set();
222
+ methods.add(methodName);
223
+ catalog.pwNestedMethods.set(objectName, methods);
224
+ }
225
+ function escapeRegExp(value) {
226
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
227
+ }
228
+ function parseInitSetupBindings(content) {
229
+ const bindings = [];
230
+ for (const match of content.matchAll(/(?:const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*await\s+pw\.initSetup\s*\(/g)) {
231
+ const raw = match[1];
232
+ if (!raw) {
233
+ continue;
234
+ }
235
+ for (const part of raw.split(',')) {
236
+ const cleaned = part.trim();
237
+ if (!cleaned) {
238
+ continue;
239
+ }
240
+ const [leftRaw, rightRaw] = cleaned.split(':');
241
+ const key = (leftRaw || '').trim();
242
+ const variableCandidate = (rightRaw || leftRaw || '').trim().split('=')[0]?.trim();
243
+ if (!key || !variableCandidate) {
244
+ continue;
245
+ }
246
+ bindings.push({ key, variable: variableCandidate });
247
+ }
248
+ }
249
+ return bindings;
250
+ }
251
+ function collectDestructuredInitSetupKeys(content) {
252
+ return new Set(parseInitSetupBindings(content).map((binding) => binding.key));
253
+ }
254
+ function addInitSetupVariableMethod(catalog, variable, methodName) {
255
+ const methods = catalog.initSetupVariableMethods.get(variable) || new Set();
256
+ methods.add(methodName);
257
+ catalog.initSetupVariableMethods.set(variable, methods);
258
+ }
164
259
  function collectApiSurfaceFromContent(content, catalog) {
165
260
  for (const prop of collectMatches(content, /\bpw\.([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
166
261
  catalog.pwProps.add(prop);
167
262
  }
263
+ for (const match of content.matchAll(/\bpw\.([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
264
+ const objectName = match[1];
265
+ const methodName = match[2];
266
+ if (!objectName || !methodName) {
267
+ continue;
268
+ }
269
+ addNestedMethod(catalog, objectName, methodName);
270
+ }
168
271
  for (const method of collectMatches(content, /\bpw\.testBrowser\.([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
169
272
  catalog.testBrowserMethods.add(method);
170
273
  }
@@ -174,6 +277,13 @@ function collectApiSurfaceFromContent(content, catalog) {
174
277
  for (const member of collectMatches(content, /\bchannelsPage\.sidebarRight\.([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
175
278
  catalog.sidebarRightMembers.add(member);
176
279
  }
280
+ for (const binding of parseInitSetupBindings(content)) {
281
+ catalog.initSetupKeys.add(binding.key);
282
+ const methodPattern = new RegExp(`\\b${escapeRegExp(binding.variable)}\\.([A-Za-z_][A-Za-z0-9_]*)\\b`, 'g');
283
+ for (const method of collectMatches(content, methodPattern)) {
284
+ addInitSetupVariableMethod(catalog, binding.variable, method);
285
+ }
286
+ }
177
287
  }
178
288
  function buildApiSurfaceCatalog(testsRoot, seedFile) {
179
289
  const catalog = createDefaultApiSurfaceCatalog();
@@ -273,13 +383,45 @@ function validateGeneratedSpecContent(content, apiSurface) {
273
383
  if (apiSurface) {
274
384
  const unknownPwProps = Array.from(collectMatches(content, /\bpw\.([A-Za-z_][A-Za-z0-9_]*)\b/g)).filter((prop) => !apiSurface.pwProps.has(prop));
275
385
  const unknownBrowserMethods = Array.from(collectMatches(content, /\bpw\.testBrowser\.([A-Za-z_][A-Za-z0-9_]*)\b/g)).filter((method) => !apiSurface.testBrowserMethods.has(method));
386
+ const unknownNestedPwMembers = [];
387
+ for (const match of content.matchAll(/\bpw\.([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
388
+ const objectName = match[1];
389
+ const methodName = match[2];
390
+ if (!objectName || !methodName || objectName === 'testBrowser') {
391
+ continue;
392
+ }
393
+ const knownMethods = apiSurface.pwNestedMethods.get(objectName);
394
+ if (!knownMethods || !knownMethods.has(methodName)) {
395
+ unknownNestedPwMembers.push(`pw.${objectName}.${methodName}`);
396
+ }
397
+ }
276
398
  const unknownChannelMembers = Array.from(collectMatches(content, /\bchannelsPage\.([A-Za-z_][A-Za-z0-9_]*)\b/g)).filter((member) => !apiSurface.channelsPageMembers.has(member));
277
399
  const unknownSidebarMembers = Array.from(collectMatches(content, /\bchannelsPage\.sidebarRight\.([A-Za-z_][A-Za-z0-9_]*)\b/g)).filter((member) => !apiSurface.sidebarRightMembers.has(member));
400
+ const initSetupBindings = parseInitSetupBindings(content);
401
+ const unknownInitSetupKeys = initSetupBindings
402
+ .map((binding) => binding.key)
403
+ .filter((key) => !apiSurface.initSetupKeys.has(key));
404
+ const unknownInitSetupVariableMethods = [];
405
+ for (const binding of initSetupBindings) {
406
+ const knownMethods = apiSurface.initSetupVariableMethods.get(binding.variable);
407
+ if (!knownMethods || knownMethods.size === 0) {
408
+ continue;
409
+ }
410
+ const methodPattern = new RegExp(`\\b${escapeRegExp(binding.variable)}\\.([A-Za-z_][A-Za-z0-9_]*)\\b`, 'g');
411
+ for (const method of collectMatches(content, methodPattern)) {
412
+ if (!knownMethods.has(method)) {
413
+ unknownInitSetupVariableMethods.push(`${binding.variable}.${method}`);
414
+ }
415
+ }
416
+ }
278
417
  const unknown = [
279
418
  ...unknownPwProps.map((value) => `pw.${value}`),
280
419
  ...unknownBrowserMethods.map((value) => `pw.testBrowser.${value}`),
420
+ ...unknownNestedPwMembers,
281
421
  ...unknownChannelMembers.map((value) => `channelsPage.${value}`),
282
422
  ...unknownSidebarMembers.map((value) => `channelsPage.sidebarRight.${value}`),
423
+ ...unknownInitSetupKeys.map((value) => `pw.initSetup.{${value}}`),
424
+ ...unknownInitSetupVariableMethods,
283
425
  ];
284
426
  if (unknown.length > 0) {
285
427
  issues.push({
@@ -450,11 +592,11 @@ function summarizeCommandOutput(stdout, stderr) {
450
592
  const lines = combined.split('\n').slice(-20);
451
593
  return lines.join('\n').slice(0, 2000);
452
594
  }
453
- function runCommand(command, args, cwd) {
595
+ function runCommand(command, args, cwd, timeoutMs = 60 * 60 * 1000) {
454
596
  const result = (0, child_process_1.spawnSync)(command, args, {
455
597
  cwd,
456
598
  encoding: 'utf-8',
457
- timeout: 60 * 60 * 1000,
599
+ timeout: timeoutMs,
458
600
  stdio: 'pipe',
459
601
  });
460
602
  return {
@@ -464,6 +606,37 @@ function runCommand(command, args, cwd) {
464
606
  error: result.error ? result.error.message : undefined,
465
607
  };
466
608
  }
609
+ function runPlaywrightRuntimeValidation(testsRoot, testFile, pipeline, playwrightBinary) {
610
+ if (!playwrightBinary) {
611
+ return {
612
+ status: 'failed',
613
+ detail: 'Playwright binary not found; cannot execute runtime validation.',
614
+ };
615
+ }
616
+ const relativeSpecPath = (0, utils_js_1.normalizePath)((0, path_1.relative)(testsRoot, testFile));
617
+ if (relativeSpecPath.startsWith('../') || relativeSpecPath.startsWith('..\\')) {
618
+ return {
619
+ status: 'failed',
620
+ detail: 'Generated spec path resolved outside testsRoot during runtime validation.',
621
+ };
622
+ }
623
+ const args = ['test', relativeSpecPath, '--workers', '1', '--retries', '0', '--max-failures', '1', '--reporter', 'line'];
624
+ if (pipeline.headless === false) {
625
+ args.push('--headed');
626
+ }
627
+ if (pipeline.project) {
628
+ args.push('--project', pipeline.project);
629
+ }
630
+ const commandResult = runCommand(playwrightBinary, args, testsRoot, 10 * 60 * 1000);
631
+ if (commandResult.status === 0) {
632
+ return { status: 'passed' };
633
+ }
634
+ const summary = summarizeCommandOutput(commandResult.stdout, commandResult.stderr);
635
+ return {
636
+ status: 'failed',
637
+ detail: summary || commandResult.error || `playwright test failed with status ${commandResult.status}`,
638
+ };
639
+ }
467
640
  function runPlaywrightListValidation(testsRoot, testFile, pipeline, playwrightBinary) {
468
641
  if (!playwrightBinary) {
469
642
  return {
@@ -479,6 +652,9 @@ function runPlaywrightListValidation(testsRoot, testFile, pipeline, playwrightBi
479
652
  };
480
653
  }
481
654
  const args = ['test', '--list', relativeSpecPath];
655
+ if (pipeline.headless === false) {
656
+ args.push('--headed');
657
+ }
482
658
  if (pipeline.project) {
483
659
  args.push('--project', pipeline.project);
484
660
  }
@@ -668,12 +844,12 @@ function runTargetedSpecHeal(testsRoot, targets, pipeline) {
668
844
  const mcp = createMcpStatus('package-native', Boolean(pipeline.mcp));
669
845
  if (targets.length === 0) {
670
846
  warnings.add('No targeted specs provided for heal.');
671
- return {
847
+ return finalizePipelineSummary({
672
848
  runner: 'package-native',
673
849
  results,
674
850
  warnings: Array.from(warnings),
675
851
  mcp,
676
- };
852
+ });
677
853
  }
678
854
  const playwrightBinary = pipeline.heal ? resolvePlaywrightBinary(testsRoot) : null;
679
855
  const seedFile = resolveAgentSeedSpec(testsRoot) || 'specs/seed.spec.ts';
@@ -731,12 +907,12 @@ function runTargetedSpecHeal(testsRoot, targets, pipeline) {
731
907
  const syntheticFlow = buildSyntheticFlowFromSpecTarget(relativeSpecPath, target);
732
908
  results.push(runPackageNativeFlow(testsRoot, syntheticFlow, pipeline, (0, utils_js_1.normalizePath)((0, path_1.dirname)(absoluteSpecPath)), absoluteSpecPath, playwrightBinary, apiSurface));
733
909
  }
734
- return {
910
+ return finalizePipelineSummary({
735
911
  runner: 'package-native',
736
912
  results,
737
913
  warnings: Array.from(warnings),
738
914
  mcp,
739
- };
915
+ });
740
916
  }
741
917
  function findSpecFiles(root) {
742
918
  if (!(0, fs_1.existsSync)(root)) {
@@ -882,7 +1058,7 @@ function buildPlaywrightHealerPrompt(testFile, extra) {
882
1058
  }
883
1059
  return lines.join('\n');
884
1060
  }
885
- function runPlaywrightAgentsFlow(testsRoot, flow, pipeline, outputDir, preferredTestFile, seedFile, apiSurface, playwrightBinary, allowRuntimeHeal) {
1061
+ function runPlaywrightAgentsFlow(testsRoot, flow, pipeline, outputDir, preferredTestFile, seedFile, apiSurface, playwrightBinary) {
886
1062
  (0, fs_1.mkdirSync)(outputDir, { recursive: true });
887
1063
  const slug = toSafeSlug(flow.id);
888
1064
  const planFile = (0, utils_js_1.normalizePath)((0, path_1.relative)(testsRoot, (0, path_1.join)(outputDir, `${slug}.plan.md`)));
@@ -896,7 +1072,7 @@ function runPlaywrightAgentsFlow(testsRoot, flow, pipeline, outputDir, preferred
896
1072
  healStatus: pipeline.heal ? 'skipped' : undefined,
897
1073
  };
898
1074
  }
899
- const prompt = buildPlaywrightAgentsPrompt(flow, seedFile, planFile, targetTestFile, allowRuntimeHeal);
1075
+ const prompt = buildPlaywrightAgentsPrompt(flow, seedFile, planFile, targetTestFile, Boolean(pipeline.heal));
900
1076
  const runArgs = [
901
1077
  '-p',
902
1078
  '--permission-mode',
@@ -938,7 +1114,7 @@ function runPlaywrightAgentsFlow(testsRoot, flow, pipeline, outputDir, preferred
938
1114
  }
939
1115
  const relativeActualTestFile = (0, utils_js_1.normalizePath)((0, path_1.relative)(testsRoot, actualTestFile));
940
1116
  let qualityIssues = validateGeneratedSpecContent((0, fs_1.readFileSync)(actualTestFile, 'utf-8'), apiSurface);
941
- if (qualityIssues.length > 0 && allowRuntimeHeal) {
1117
+ if (qualityIssues.length > 0 && pipeline.heal) {
942
1118
  const healResult = runCommand('claude', [
943
1119
  '-p',
944
1120
  '--permission-mode',
@@ -966,9 +1142,9 @@ function runPlaywrightAgentsFlow(testsRoot, flow, pipeline, outputDir, preferred
966
1142
  error: `Playwright agents produced invalid test content: ${qualityIssues.map((issue) => issue.message).join(' | ')}`,
967
1143
  };
968
1144
  }
969
- if (allowRuntimeHeal) {
970
- let validation = runPlaywrightListValidation(testsRoot, actualTestFile, pipeline, playwrightBinary);
971
- if (validation.status === 'failed') {
1145
+ if (pipeline.heal) {
1146
+ let compileValidation = runPlaywrightListValidation(testsRoot, actualTestFile, pipeline, playwrightBinary);
1147
+ if (compileValidation.status === 'failed') {
972
1148
  const healResult = runCommand('claude', [
973
1149
  '-p',
974
1150
  '--permission-mode',
@@ -980,19 +1156,48 @@ function runPlaywrightAgentsFlow(testsRoot, flow, pipeline, outputDir, preferred
980
1156
  '--add-dir',
981
1157
  testsRoot,
982
1158
  '--',
983
- buildPlaywrightHealerPrompt(relativeActualTestFile, validation.detail || 'playwright --list failed'),
1159
+ buildPlaywrightHealerPrompt(relativeActualTestFile, compileValidation.detail || 'playwright --list failed'),
984
1160
  ], testsRoot);
985
1161
  if (healResult.status === 0 && (0, fs_1.existsSync)(actualTestFile)) {
986
- validation = runPlaywrightListValidation(testsRoot, actualTestFile, pipeline, playwrightBinary);
1162
+ compileValidation = runPlaywrightListValidation(testsRoot, actualTestFile, pipeline, playwrightBinary);
987
1163
  }
988
- if (validation.status === 'failed') {
1164
+ if (compileValidation.status === 'failed') {
1165
+ return {
1166
+ flowId: flow.id,
1167
+ flowName: flow.name,
1168
+ generatedDir: outputDir,
1169
+ generateStatus: 'failed',
1170
+ healStatus: 'failed',
1171
+ error: `Playwright agents compile validation failed: ${compileValidation.detail || 'playwright --list failed'}`,
1172
+ };
1173
+ }
1174
+ }
1175
+ let runtimeValidation = runPlaywrightRuntimeValidation(testsRoot, actualTestFile, pipeline, playwrightBinary);
1176
+ if (runtimeValidation.status === 'failed') {
1177
+ const healResult = runCommand('claude', [
1178
+ '-p',
1179
+ '--permission-mode',
1180
+ 'bypassPermissions',
1181
+ '--agent',
1182
+ 'playwright-test-healer',
1183
+ '--mcp-config',
1184
+ '.mcp.json',
1185
+ '--add-dir',
1186
+ testsRoot,
1187
+ '--',
1188
+ buildPlaywrightHealerPrompt(relativeActualTestFile, runtimeValidation.detail || 'playwright runtime failed'),
1189
+ ], testsRoot);
1190
+ if (healResult.status === 0 && (0, fs_1.existsSync)(actualTestFile)) {
1191
+ runtimeValidation = runPlaywrightRuntimeValidation(testsRoot, actualTestFile, pipeline, playwrightBinary);
1192
+ }
1193
+ if (runtimeValidation.status === 'failed') {
989
1194
  return {
990
1195
  flowId: flow.id,
991
1196
  flowName: flow.name,
992
1197
  generatedDir: outputDir,
993
1198
  generateStatus: 'failed',
994
1199
  healStatus: 'failed',
995
- error: `Playwright agents heal failed: ${validation.detail || 'playwright validation failed'}`,
1200
+ error: `Playwright agents runtime validation failed: ${runtimeValidation.detail || 'playwright test failed'}`,
996
1201
  };
997
1202
  }
998
1203
  }
@@ -1034,15 +1239,11 @@ function runPlaywrightAgentsPipeline(testsRoot, flows, pipeline) {
1034
1239
  warnings.push('No seed spec file found under specs/. Playwright planner cannot be initialized.');
1035
1240
  return { runner: 'unknown', results, warnings, mcp: createMcpStatus('unknown', true) };
1036
1241
  }
1037
- const allowRuntimeHeal = Boolean(pipeline.heal && pipeline.baseUrl);
1038
- const playwrightBinary = allowRuntimeHeal ? resolvePlaywrightBinary(testsRoot) : null;
1242
+ const playwrightBinary = pipeline.heal ? resolvePlaywrightBinary(testsRoot) : null;
1039
1243
  const apiSurface = buildApiSurfaceCatalog(testsRoot, seedFile);
1040
- if (allowRuntimeHeal && !playwrightBinary) {
1244
+ if (pipeline.heal && !playwrightBinary) {
1041
1245
  warnings.push('Playwright binary was not found. Healer runtime validation may be limited.');
1042
1246
  }
1043
- if (pipeline.heal && !allowRuntimeHeal) {
1044
- warnings.push('Skipping runtime healer in official MCP mode because no --pipeline-base-url was provided.');
1045
- }
1046
1247
  const outputBase = (0, path_1.resolve)(testsRoot, pipeline.outputDir || 'specs/functional/ai-assisted');
1047
1248
  if (!(0, utils_js_1.isPathWithinRoot)(testsRoot, outputBase)) {
1048
1249
  warnings.push(`Pipeline outputDir resolves outside testsRoot and was blocked: ${pipeline.outputDir}`);
@@ -1075,7 +1276,7 @@ function runPlaywrightAgentsPipeline(testsRoot, flows, pipeline) {
1075
1276
  });
1076
1277
  continue;
1077
1278
  }
1078
- results.push(runPlaywrightAgentsFlow(testsRoot, flow, pipeline, outputDir, testFile, seedFile, apiSurface, playwrightBinary, allowRuntimeHeal));
1279
+ results.push(runPlaywrightAgentsFlow(testsRoot, flow, pipeline, outputDir, testFile, seedFile, apiSurface, playwrightBinary));
1079
1280
  }
1080
1281
  return { runner: 'playwright-agents', results, warnings, mcp: createMcpStatus('playwright-agents', true) };
1081
1282
  }
@@ -1084,32 +1285,37 @@ function runPlaywrightPipeline(testsRoot, flows, pipeline) {
1084
1285
  if (pipeline.mcp) {
1085
1286
  const agentsSummary = runPlaywrightAgentsPipeline(testsRoot, flows, pipeline);
1086
1287
  if (agentsSummary.runner !== 'unknown' || agentsSummary.results.length > 0) {
1087
- return agentsSummary;
1288
+ return finalizePipelineSummary(agentsSummary);
1088
1289
  }
1089
1290
  if (!pipeline.mcpAllowFallback) {
1090
1291
  const warnings = [
1091
1292
  ...agentsSummary.warnings,
1092
1293
  'Official Playwright MCP mode is strict; fallback generation is disabled unless pipeline.mcpAllowFallback=true.',
1093
1294
  ];
1094
- return {
1295
+ return finalizePipelineSummary({
1095
1296
  runner: 'unknown',
1096
1297
  results: agentsSummary.results,
1097
1298
  warnings,
1098
1299
  mcp: createMcpStatus('unknown', true),
1099
- };
1300
+ });
1100
1301
  }
1101
1302
  mcpFallbackWarnings.push(...agentsSummary.warnings);
1102
1303
  }
1103
1304
  const cliPath = hasE2eTestGenCLI(testsRoot);
1104
1305
  if (!cliPath) {
1105
- return runPackageNativePipeline(testsRoot, flows, pipeline, mcpFallbackWarnings);
1306
+ return finalizePipelineSummary(runPackageNativePipeline(testsRoot, flows, pipeline, mcpFallbackWarnings));
1106
1307
  }
1107
1308
  const warnings = [...mcpFallbackWarnings];
1108
1309
  const results = [];
1109
1310
  const outputBase = (0, path_1.resolve)(testsRoot, pipeline.outputDir || 'specs/functional/ai-assisted');
1110
1311
  if (!(0, utils_js_1.isPathWithinRoot)(testsRoot, outputBase)) {
1111
1312
  warnings.push(`Pipeline outputDir resolves outside testsRoot and was blocked: ${pipeline.outputDir}`);
1112
- return { runner: 'unknown', results, warnings, mcp: createMcpStatus('unknown', Boolean(pipeline.mcp)) };
1313
+ return finalizePipelineSummary({
1314
+ runner: 'unknown',
1315
+ results,
1316
+ warnings,
1317
+ mcp: createMcpStatus('unknown', Boolean(pipeline.mcp)),
1318
+ });
1113
1319
  }
1114
1320
  for (const flow of flows) {
1115
1321
  if (flow.priority !== 'P0' && flow.priority !== 'P1') {
@@ -1205,5 +1411,10 @@ function runPlaywrightPipeline(testsRoot, flows, pipeline) {
1205
1411
  healStatus,
1206
1412
  });
1207
1413
  }
1208
- return { runner: 'e2e-test-gen', results, warnings, mcp: createMcpStatus('e2e-test-gen', Boolean(pipeline.mcp)) };
1414
+ return finalizePipelineSummary({
1415
+ runner: 'e2e-test-gen',
1416
+ results,
1417
+ warnings,
1418
+ mcp: createMcpStatus('e2e-test-gen', Boolean(pipeline.mcp)),
1419
+ });
1209
1420
  }