flakelab 0.1.0 → 0.1.1
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 +224 -37
- package/dist/analysis/blob-report.d.ts +15 -0
- package/dist/analysis/blob-report.js +137 -0
- package/dist/analysis/blob-report.js.map +1 -0
- package/dist/analysis/cli.d.ts +7 -0
- package/dist/analysis/cli.js +28 -0
- package/dist/analysis/cli.js.map +1 -0
- package/dist/analysis/schema.d.ts +175 -0
- package/dist/analysis/schema.js +34 -0
- package/dist/analysis/schema.js.map +1 -0
- package/dist/analysis/zip-archive.d.ts +6 -0
- package/dist/analysis/zip-archive.js +193 -0
- package/dist/analysis/zip-archive.js.map +1 -0
- package/dist/artifacts/paths.d.ts +3 -0
- package/dist/artifacts/paths.js +34 -0
- package/dist/artifacts/paths.js.map +1 -0
- package/dist/artifacts/retention.d.ts +1 -0
- package/dist/artifacts/retention.js +80 -0
- package/dist/artifacts/retention.js.map +1 -0
- package/dist/bisect/engine.js +0 -1
- package/dist/bisect/engine.js.map +1 -1
- package/dist/bisect/schema.d.ts +5 -6
- package/dist/bisect/schema.js +1 -2
- package/dist/bisect/schema.js.map +1 -1
- package/dist/bisect/solari-evaluator.js +9 -1
- package/dist/bisect/solari-evaluator.js.map +1 -1
- package/dist/ci/job-summary.d.ts +2 -0
- package/dist/ci/job-summary.js +120 -2
- package/dist/ci/job-summary.js.map +1 -1
- package/dist/ci/select-command.js +1 -1
- package/dist/ci/select-command.js.map +1 -1
- package/dist/ci/summary-command.js +12 -7
- package/dist/ci/summary-command.js.map +1 -1
- package/dist/cli-arguments.d.ts +44 -0
- package/dist/cli-arguments.js +356 -0
- package/dist/cli-arguments.js.map +1 -0
- package/dist/cli-command-help.d.ts +11 -0
- package/dist/cli-command-help.js +211 -0
- package/dist/cli-command-help.js.map +1 -0
- package/dist/cli-help.d.ts +5 -2
- package/dist/cli-help.js +74 -45
- package/dist/cli-help.js.map +1 -1
- package/dist/cli-option-definitions.d.ts +202 -0
- package/dist/cli-option-definitions.js +59 -0
- package/dist/cli-option-definitions.js.map +1 -0
- package/dist/cli.js +100 -100
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze-summary.d.ts +5 -0
- package/dist/commands/analyze-summary.js +89 -0
- package/dist/commands/analyze-summary.js.map +1 -0
- package/dist/commands/analyze.d.ts +6 -0
- package/dist/commands/analyze.js +128 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/bisect.d.ts +2 -2
- package/dist/commands/bisect.js +32 -3
- package/dist/commands/bisect.js.map +1 -1
- package/dist/commands/diagnose.d.ts +3 -2
- package/dist/commands/diagnose.js +331 -23
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/discover.d.ts +18 -2
- package/dist/commands/discover.js +215 -25
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +66 -25
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/investigate.d.ts +3 -2
- package/dist/commands/investigate.js +22 -3
- package/dist/commands/investigate.js.map +1 -1
- package/dist/commands/options.d.ts +123 -16
- package/dist/commands/options.js +24 -2
- package/dist/commands/options.js.map +1 -1
- package/dist/commands/prove.d.ts +2 -2
- package/dist/commands/prove.js +36 -1
- package/dist/commands/prove.js.map +1 -1
- package/dist/commands/repair.d.ts +8 -2
- package/dist/commands/repair.js +32 -9
- package/dist/commands/repair.js.map +1 -1
- package/dist/commands/replay.d.ts +2 -2
- package/dist/commands/replay.js +15 -2
- package/dist/commands/replay.js.map +1 -1
- package/dist/commands/report.d.ts +2 -2
- package/dist/commands/report.js +35 -10
- package/dist/commands/report.js.map +1 -1
- package/dist/commands/scan-summary.d.ts +7 -0
- package/dist/commands/scan-summary.js +175 -0
- package/dist/commands/scan-summary.js.map +1 -0
- package/dist/commands/scan.d.ts +9 -12
- package/dist/commands/scan.js +66 -62
- package/dist/commands/scan.js.map +1 -1
- package/dist/core/plan.js +1 -1
- package/dist/core/plan.js.map +1 -1
- package/dist/diagnosis/checkpoint.d.ts +6 -0
- package/dist/diagnosis/checkpoint.js +46 -0
- package/dist/diagnosis/checkpoint.js.map +1 -0
- package/dist/diagnosis/cli.d.ts +7 -0
- package/dist/diagnosis/cli.js +84 -0
- package/dist/diagnosis/cli.js.map +1 -0
- package/dist/diagnosis/recommendation.d.ts +13 -0
- package/dist/diagnosis/recommendation.js +95 -0
- package/dist/diagnosis/recommendation.js.map +1 -0
- package/dist/diagnosis/run-state.d.ts +52 -0
- package/dist/diagnosis/run-state.js +173 -0
- package/dist/diagnosis/run-state.js.map +1 -0
- package/dist/diagnosis/schema.d.ts +207 -0
- package/dist/diagnosis/schema.js +108 -0
- package/dist/diagnosis/schema.js.map +1 -0
- package/dist/diagnosis/summary.d.ts +3 -0
- package/dist/diagnosis/summary.js +115 -0
- package/dist/diagnosis/summary.js.map +1 -0
- package/dist/discovery/auth-cookie.d.ts +19 -0
- package/dist/discovery/auth-cookie.js +26 -0
- package/dist/discovery/auth-cookie.js.map +1 -0
- package/dist/discovery/evaluate.d.ts +35 -2
- package/dist/discovery/evaluate.js +136 -30
- package/dist/discovery/evaluate.js.map +1 -1
- package/dist/discovery/event-loop.d.ts +25 -0
- package/dist/discovery/event-loop.js +84 -0
- package/dist/discovery/event-loop.js.map +1 -0
- package/dist/discovery/minimize.d.ts +61 -1
- package/dist/discovery/minimize.js +179 -7
- package/dist/discovery/minimize.js.map +1 -1
- package/dist/discovery/resource-loading.d.ts +25 -0
- package/dist/discovery/resource-loading.js +84 -0
- package/dist/discovery/resource-loading.js.map +1 -0
- package/dist/discovery/runner-environment.d.ts +26 -0
- package/dist/discovery/runner-environment.js +68 -0
- package/dist/discovery/runner-environment.js.map +1 -0
- package/dist/discovery/startup-event.d.ts +25 -0
- package/dist/discovery/startup-event.js +84 -0
- package/dist/discovery/startup-event.js.map +1 -0
- package/dist/discovery/storage-state.d.ts +26 -0
- package/dist/discovery/storage-state.js +86 -0
- package/dist/discovery/storage-state.js.map +1 -0
- package/dist/discovery/temporal-environment.d.ts +31 -0
- package/dist/discovery/temporal-environment.js +43 -0
- package/dist/discovery/temporal-environment.js.map +1 -0
- package/dist/discovery/visual-environment.d.ts +28 -0
- package/dist/discovery/visual-environment.js +42 -0
- package/dist/discovery/visual-environment.js.map +1 -0
- package/dist/domain/schema.d.ts +840 -20
- package/dist/domain/schema.js +216 -6
- package/dist/domain/schema.js.map +1 -1
- package/dist/faults/browser-context.d.ts +16 -0
- package/dist/faults/browser-context.js +60 -0
- package/dist/faults/browser-context.js.map +1 -0
- package/dist/faults/cookie-header.d.ts +5 -0
- package/dist/faults/cookie-header.js +25 -0
- package/dist/faults/cookie-header.js.map +1 -0
- package/dist/faults/document-bootstrap.d.ts +5 -0
- package/dist/faults/document-bootstrap.js +141 -0
- package/dist/faults/document-bootstrap.js.map +1 -0
- package/dist/faults/install.d.ts +1 -1
- package/dist/faults/install.js +199 -6
- package/dist/faults/install.js.map +1 -1
- package/dist/faults/pattern.d.ts +1 -0
- package/dist/faults/pattern.js +24 -0
- package/dist/faults/pattern.js.map +1 -0
- package/dist/investigator/agent.js +39 -17
- package/dist/investigator/agent.js.map +1 -1
- package/dist/investigator/ledger.d.ts +1 -1
- package/dist/investigator/ledger.js +2 -2
- package/dist/investigator/ledger.js.map +1 -1
- package/dist/investigator/safe-source.d.ts +1 -0
- package/dist/investigator/safe-source.js +12 -3
- package/dist/investigator/safe-source.js.map +1 -1
- package/dist/investigator/schema.d.ts +293 -2
- package/dist/investigator/schema.js +100 -2
- package/dist/investigator/schema.js.map +1 -1
- package/dist/proof/cli.d.ts +7 -0
- package/dist/proof/cli.js +108 -0
- package/dist/proof/cli.js.map +1 -0
- package/dist/repair/generator.d.ts +12 -1
- package/dist/repair/generator.js +30 -6
- package/dist/repair/generator.js.map +1 -1
- package/dist/repair/remote-proof-runner.js +6 -10
- package/dist/repair/remote-proof-runner.js.map +1 -1
- package/dist/repair/schema.d.ts +77 -2
- package/dist/repair/schema.js +23 -2
- package/dist/repair/schema.js.map +1 -1
- package/dist/repair/solari-validator.d.ts +2 -0
- package/dist/repair/solari-validator.js +4 -6
- package/dist/repair/solari-validator.js.map +1 -1
- package/dist/repair/summary.d.ts +10 -0
- package/dist/repair/summary.js +58 -0
- package/dist/repair/summary.js.map +1 -0
- package/dist/repair/validator.d.ts +1 -0
- package/dist/repair/validator.js +53 -12
- package/dist/repair/validator.js.map +1 -1
- package/dist/report/bundle.js +16 -37
- package/dist/report/bundle.js.map +1 -1
- package/dist/report/model.js +131 -12
- package/dist/report/model.js.map +1 -1
- package/dist/report/open.d.ts +9 -0
- package/dist/report/open.js +23 -0
- package/dist/report/open.js.map +1 -1
- package/dist/report/report-client.css +2 -0
- package/dist/report/report-client.js +58 -0
- package/dist/report/schema.d.ts +115 -5
- package/dist/report/schema.js +24 -5
- package/dist/report/schema.js.map +1 -1
- package/dist/reproducer/schema.d.ts +89 -4
- package/dist/reproducer/schema.js +3 -4
- package/dist/reproducer/schema.js.map +1 -1
- package/dist/runner/execution-fault.d.ts +10 -0
- package/dist/runner/execution-fault.js +24 -0
- package/dist/runner/execution-fault.js.map +1 -0
- package/dist/runner/fault-proxy.d.ts +7 -0
- package/dist/runner/fault-proxy.js +366 -0
- package/dist/runner/fault-proxy.js.map +1 -0
- package/dist/runner/local.js +2 -2
- package/dist/runner/local.js.map +1 -1
- package/dist/runner/native-scan.d.ts +25 -0
- package/dist/runner/native-scan.js +306 -0
- package/dist/runner/native-scan.js.map +1 -0
- package/dist/runner/playwright-executor.d.ts +3 -0
- package/dist/runner/playwright-executor.js +125 -66
- package/dist/runner/playwright-executor.js.map +1 -1
- package/dist/runner/process-tree.d.ts +7 -0
- package/dist/runner/process-tree.js +62 -0
- package/dist/runner/process-tree.js.map +1 -0
- package/dist/runner/project-bridge.d.ts +7 -0
- package/dist/runner/project-bridge.js +83 -0
- package/dist/runner/project-bridge.js.map +1 -0
- package/dist/runner/trial-reporter.d.ts +7 -0
- package/dist/runner/trial-reporter.js +47 -0
- package/dist/runner/trial-reporter.js.map +1 -0
- package/dist/scan/failure-clusters.d.ts +24 -0
- package/dist/scan/failure-clusters.js +74 -0
- package/dist/scan/failure-clusters.js.map +1 -0
- package/dist/scan/schema.d.ts +170 -0
- package/dist/scan/schema.js +71 -0
- package/dist/scan/schema.js.map +1 -0
- package/dist/solari/executor.d.ts +5 -1
- package/dist/solari/executor.js +25 -17
- package/dist/solari/executor.js.map +1 -1
- package/dist/solari/run-demo.d.ts +13 -1
- package/dist/solari/run-demo.js +35 -16
- package/dist/solari/run-demo.js.map +1 -1
- package/dist/solari/usage.d.ts +8 -2
- package/dist/solari/usage.js +14 -4
- package/dist/solari/usage.js.map +1 -1
- package/dist/ui/boundary.d.ts +17 -0
- package/dist/ui/boundary.js +18 -0
- package/dist/ui/boundary.js.map +1 -0
- package/dist/ui/console.d.ts +4 -0
- package/dist/ui/console.js +11 -0
- package/dist/ui/console.js.map +1 -0
- package/dist/ui/document.d.ts +40 -0
- package/dist/ui/document.js +120 -0
- package/dist/ui/document.js.map +1 -0
- package/dist/ui/format.d.ts +8 -0
- package/dist/ui/format.js +32 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/progress.d.ts +17 -1
- package/dist/ui/progress.js +51 -8
- package/dist/ui/progress.js.map +1 -1
- package/dist/ui/status.d.ts +7 -0
- package/dist/ui/status.js +36 -0
- package/dist/ui/status.js.map +1 -0
- package/dist/ui/style.d.ts +8 -0
- package/dist/ui/style.js +22 -0
- package/dist/ui/style.js.map +1 -0
- package/dist/ui/text.d.ts +17 -0
- package/dist/ui/text.js +123 -0
- package/dist/ui/text.js.map +1 -0
- package/dist/ui/theme.d.ts +23 -0
- package/dist/ui/theme.js +77 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/trial-progress.d.ts +13 -0
- package/dist/ui/trial-progress.js +42 -0
- package/dist/ui/trial-progress.js.map +1 -0
- package/package.json +14 -19
- package/dist/faults/network-delay.d.ts +0 -5
- package/dist/faults/network-delay.js +0 -34
- package/dist/faults/network-delay.js.map +0 -1
- package/dist/faults/request-failure.d.ts +0 -5
- package/dist/faults/request-failure.js +0 -24
- package/dist/faults/request-failure.js.map +0 -1
- package/dist/playwright.d.ts +0 -3
- package/dist/playwright.js +0 -21
- package/dist/playwright.js.map +0 -1
- package/dist/report/browser-entry.d.ts +0 -2
- package/dist/report/browser-entry.js +0 -15
- package/dist/report/browser-entry.js.map +0 -1
- package/dist/report/components/artifacts.d.ts +0 -4
- package/dist/report/components/artifacts.js +0 -6
- package/dist/report/components/artifacts.js.map +0 -1
- package/dist/report/components/experiments.d.ts +0 -4
- package/dist/report/components/experiments.js +0 -7
- package/dist/report/components/experiments.js.map +0 -1
- package/dist/report/components/hypotheses.d.ts +0 -4
- package/dist/report/components/hypotheses.js +0 -26
- package/dist/report/components/hypotheses.js.map +0 -1
- package/dist/report/components/indicators.d.ts +0 -32
- package/dist/report/components/indicators.js +0 -42
- package/dist/report/components/indicators.js.map +0 -1
- package/dist/report/components/layout.d.ts +0 -29
- package/dist/report/components/layout.js +0 -20
- package/dist/report/components/layout.js.map +0 -1
- package/dist/report/components/proof.d.ts +0 -4
- package/dist/report/components/proof.js +0 -34
- package/dist/report/components/proof.js.map +0 -1
- package/dist/report/components/reproduction.d.ts +0 -4
- package/dist/report/components/reproduction.js +0 -19
- package/dist/report/components/reproduction.js.map +0 -1
- package/dist/report/components/summary.d.ts +0 -4
- package/dist/report/components/summary.js +0 -12
- package/dist/report/components/summary.js.map +0 -1
- package/dist/report/components/verdict.d.ts +0 -4
- package/dist/report/components/verdict.js +0 -27
- package/dist/report/components/verdict.js.map +0 -1
- package/dist/report/report-app.d.ts +0 -6
- package/dist/report/report-app.js +0 -16
- package/dist/report/report-app.js.map +0 -1
- package/dist/report/report-details.css +0 -182
- package/dist/report/report.css +0 -404
package/README.md
CHANGED
|
@@ -18,34 +18,93 @@ failure is rare:
|
|
|
18
18
|
```bash
|
|
19
19
|
npx flakelab@latest tests/checkout.spec.ts --runs 20
|
|
20
20
|
npx flakelab@latest tests/checkout.spec.ts --runs 20 --verbose
|
|
21
|
+
npx flakelab@latest scan tests/checkout.spec.ts --runs 20 --json
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
The default command
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
The default command starts one Playwright process and uses Playwright's native `--repeat-each`
|
|
25
|
+
and JSON reporter to classify every selected test as `no-failure-observed`, `mixed-outcomes`,
|
|
26
|
+
`failed-every-run`, `skipped`, or `errored`. These labels describe only the bounded sample; they
|
|
27
|
+
do not claim that a test is permanently stable. The human summary includes the 80% Wilson
|
|
28
|
+
confidence interval—or the upper bound when no failure was observed—and writes machine-readable evidence to
|
|
29
|
+
`.flakelab/runs/scan.json`. The artifact contains direct run totals, per-test identities,
|
|
30
|
+
confidence bounds, and bounded failure-signature clusters. Each cluster records its observed
|
|
31
|
+
rate, first and last attempt, a redacted representative reason, and up to three representative
|
|
32
|
+
trace, screenshot, or video paths. Multiple independent failure modes are called out explicitly.
|
|
33
|
+
The scan does not call
|
|
34
|
+
Groq or Solari. Use `--json` when another tool needs the scan artifact on stdout; progress remains
|
|
35
|
+
on stderr so the JSON stream stays parseable. A scan exits `0` when it observes no failure, `1`
|
|
36
|
+
when it observes mixed outcomes or failure in every run, and `2` when infrastructure makes the
|
|
37
|
+
result inconclusive.
|
|
38
|
+
|
|
39
|
+
For a large target, the terminal summary lists problematic tests first and collapses clean
|
|
40
|
+
observations into a count. The saved artifact, `--json`, and `--verbose` retain every test record.
|
|
41
|
+
|
|
42
|
+
Paths stored in scan evidence are relative to the scanned project. Outside-project paths are
|
|
43
|
+
reduced to a non-identifying placeholder and basename, and the JSON does not record its own output
|
|
44
|
+
location. Playwright directories containing only `.last-run.json` are removed after the scan.
|
|
45
|
+
Evidence-bearing directories receive a FlakeLab ownership marker and only the five newest are
|
|
46
|
+
retained; unmarked directories are always treated as user-owned and are never removed.
|
|
47
|
+
|
|
48
|
+
Every scan target is deliberate: `flakelab .` is the shortest whole-project form, while the
|
|
49
|
+
spelled-out `flakelab scan` command requires its target and never silently substitutes `.`.
|
|
50
|
+
|
|
51
|
+
## Analyze an existing CI failure
|
|
52
|
+
|
|
53
|
+
Point FlakeLab at a Playwright blob-report archive or a directory of shard archives:
|
|
26
54
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
import { expect, test } from "flakelab/playwright"
|
|
55
|
+
```bash
|
|
56
|
+
npx flakelab@latest analyze ./blob-report
|
|
57
|
+
npx flakelab@latest analyze ./blob-report --baseline .flakelab/runs/analyze.json --json
|
|
32
58
|
```
|
|
33
59
|
|
|
60
|
+
Analysis does not rerun tests and needs no provider credentials. FlakeLab copies and validates
|
|
61
|
+
the archives inside an owned working directory, asks Playwright's supported report merger for
|
|
62
|
+
the canonical JSON report, and leaves the source reports untouched. Unsafe paths, links,
|
|
63
|
+
malformed ZIP structures, excessive expansion, and unbounded archive sets are rejected before
|
|
64
|
+
the merger runs.
|
|
65
|
+
|
|
66
|
+
The terminal summary ranks mixed outcomes, recurring failures, diagnostic attachments, and
|
|
67
|
+
failure modes not present in the optional baseline. The complete result is written to
|
|
68
|
+
`.flakelab/runs/analyze.json`; `--json` emits the same validated artifact on stdout. Referenced
|
|
69
|
+
traces, screenshots, and videos are retained with the owned analysis evidence under bounded
|
|
70
|
+
retention, while temporary archive copies and conversion files are removed.
|
|
71
|
+
|
|
72
|
+
The scan explicitly disables Playwright retries so a retry is never mistaken for an independent
|
|
73
|
+
statistical repetition. It uses one Playwright worker by default because arbitrary test suites may
|
|
74
|
+
share ports, databases, caches, or workspace files. Use `--concurrency` greater than `1` only when
|
|
75
|
+
those resources are safe for normal Playwright worker parallelism. Traces, screenshots, videos,
|
|
76
|
+
and ordinary test output stay in a unique directory beneath the selected evidence directory;
|
|
77
|
+
FlakeLab removes only its temporary JSON reporter file.
|
|
78
|
+
|
|
79
|
+
The native JSON reporter contract is tested against `@playwright/test` 1.62.1, which is the current
|
|
80
|
+
peer requirement. The integration fixture covers repetitions, multiple projects, expected
|
|
81
|
+
failures, skips, timeouts, file-backed attachments, and paths containing spaces.
|
|
82
|
+
|
|
83
|
+
The causal workflow works with ordinary Playwright tests. Keep importing `test` and `expect` from
|
|
84
|
+
`@playwright/test`; no per-test FlakeLab fixture or source edit is required. For each experiment,
|
|
85
|
+
FlakeLab creates a temporary config beside the existing Playwright config, retains its projects,
|
|
86
|
+
dependencies, web server, authentication settings, application environment, and reporters, and
|
|
87
|
+
adds a local fault proxy through Playwright's public configuration. The generated config and proxy
|
|
88
|
+
are removed after the trial, including after failure or interruption. The current project-level
|
|
89
|
+
bridge injects matching HTTP requests; it reports an explicit unsupported-path error instead of
|
|
90
|
+
claiming success when a selected route is HTTPS or no request matches.
|
|
91
|
+
|
|
34
92
|
Then explicitly opt into AI investigation and isolated proof:
|
|
35
93
|
|
|
36
94
|
```bash
|
|
37
|
-
npx flakelab@latest tests/checkout.spec.ts \
|
|
38
|
-
--
|
|
95
|
+
npx flakelab@latest prove tests/checkout.spec.ts \
|
|
96
|
+
--fault network-delay \
|
|
39
97
|
--pattern "**/api/checkout" \
|
|
40
98
|
--open
|
|
41
99
|
```
|
|
42
100
|
|
|
43
|
-
|
|
101
|
+
The equivalent target shortcut is `npx flakelab@latest tests/checkout.spec.ts --prove`.
|
|
102
|
+
Proof mode requires `GROQ_API_KEY` and `SOLARI_API_KEY`. It chains trigger discovery, reproducer
|
|
44
103
|
verification, bounded AI investigation, candidate generation, proof in a disposable Solari
|
|
45
104
|
microVM, and the offline evidence report. The candidate remains a reviewable diff and is never
|
|
46
105
|
applied automatically.
|
|
47
106
|
|
|
48
|
-
See every command and
|
|
107
|
+
See every command and its accepted options without running tests:
|
|
49
108
|
|
|
50
109
|
```bash
|
|
51
110
|
npx flakelab@latest --help
|
|
@@ -61,18 +120,56 @@ The doctor checks Node.js, Playwright configuration, Chromium, ignored evidence
|
|
|
61
120
|
credential availability, and test-process credential isolation. It reports only whether a
|
|
62
121
|
credential is configured and where it came from; it never prints the value or a fingerprint.
|
|
63
122
|
|
|
64
|
-
|
|
123
|
+
Use `diagnose` as the adaptive product journey. A target begins with the bounded native scan;
|
|
124
|
+
an existing CI report begins with read-only analysis:
|
|
65
125
|
|
|
66
126
|
```bash
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
127
|
+
npx flakelab@latest diagnose tests/checkout.spec.ts
|
|
128
|
+
npx flakelab@latest diagnose --report ./blob-report
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The command explains what was observed and writes `.flakelab/runs/diagnose.json` with the cheapest
|
|
132
|
+
useful next experiment, its trial bound, an expected duration based on the local scan when
|
|
133
|
+
available, required credentials, and the Solari estimate—or an explicit statement that no
|
|
134
|
+
reliable estimate is available. Local diagnosis never requests provider credentials.
|
|
135
|
+
|
|
136
|
+
Continue only as far as the evidence justifies:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npx flakelab@latest diagnose tests/checkout.spec.ts --discover
|
|
140
|
+
npx flakelab@latest diagnose tests/checkout.spec.ts --investigate
|
|
141
|
+
npx flakelab@latest diagnose tests/checkout.spec.ts --repair
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`--discover` remains local and creates a minimized reproducer. `--investigate` explicitly enables
|
|
145
|
+
bounded Groq usage and retains both the reproducer and investigation evidence. `--repair`
|
|
146
|
+
explicitly enables candidate generation and a disposable Solari proof; a rejected candidate still
|
|
147
|
+
produces the portable evidence report. When starting from `--report`, supply a test target before
|
|
148
|
+
requesting new experiments so FlakeLab never guesses which test to execute.
|
|
149
|
+
|
|
150
|
+
Every diagnosis writes `diagnose.json` atomically after each completed phase and when work fails
|
|
151
|
+
or is interrupted. Continue the exact saved workflow with:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npx flakelab@latest resume .flakelab/runs/diagnose.json
|
|
71
155
|
```
|
|
72
156
|
|
|
73
|
-
|
|
74
|
-
and
|
|
75
|
-
|
|
157
|
+
Resume validates the checkpoint and its original input hash, confines saved paths to the current
|
|
158
|
+
project, and starts at the next safe phase. It does not repeat a confirmed scan, discovery, or
|
|
159
|
+
investigation. Completed checkpoints are a no-op apart from printing their summary. Partial
|
|
160
|
+
checkpoints remain readable and are labelled `running`, `interrupted`, or `failed`; they never
|
|
161
|
+
masquerade as a completed result.
|
|
162
|
+
|
|
163
|
+
The checkpoint shows planned and actual trial counts, wall time, AI token usage and estimated
|
|
164
|
+
cost, Solari sandbox creation and release counts, snapshot-cache decisions, and cleanup status.
|
|
165
|
+
It stores normalized options and project-relative artifact paths, not credentials. `Ctrl+C` stops
|
|
166
|
+
new trials; local Playwright process trees and disposable remote resources are cleaned up by the
|
|
167
|
+
same bounded execution paths used outside diagnosis.
|
|
168
|
+
|
|
169
|
+
Prepared Solari demo snapshots are reused only when a key covering the commit, lockfile, fixture,
|
|
170
|
+
template, runtime command, application path, port, and timeout matches. The metrics artifact gives
|
|
171
|
+
the key and a reason for every hit or miss. Candidate repair workspaces are intentionally not
|
|
172
|
+
cached because each contains a unique patch; their checkpoint explains that decision.
|
|
76
173
|
|
|
77
174
|
## Discover and replay a minimal reproducer
|
|
78
175
|
|
|
@@ -81,25 +178,95 @@ failure-rate and confidence thresholds:
|
|
|
81
178
|
|
|
82
179
|
```bash
|
|
83
180
|
pnpm flakelab discover tests/fixtures/flaky-checkout.spec.ts \
|
|
181
|
+
--fault network-delay \
|
|
84
182
|
--trials 4 \
|
|
85
|
-
--concurrency
|
|
183
|
+
--concurrency 1 \
|
|
86
184
|
--seed 42 \
|
|
87
185
|
--max-delay 125 \
|
|
186
|
+
--max-seconds 300 \
|
|
88
187
|
--min-rate 0.7 \
|
|
89
188
|
--output flakelab.repro.yaml
|
|
90
189
|
```
|
|
91
190
|
|
|
92
191
|
The command writes a strict portable YAML reproducer and a JSON discovery sidecar containing
|
|
93
|
-
the evidence for every candidate.
|
|
94
|
-
|
|
95
|
-
|
|
192
|
+
the evidence for every candidate. Every candidate alternates matched-seed control and intervention
|
|
193
|
+
trials. A pre-existing failure is allowed, but the intervention is causal only when the same
|
|
194
|
+
normalized failure signature reaches the requested rate and its 80% lower confidence bound exceeds
|
|
195
|
+
the control's upper bound. Discovery prints each completed trial to stderr
|
|
196
|
+
and stops at the five-minute default elapsed-time ceiling; set `--max-seconds` explicitly when a
|
|
197
|
+
suite needs a different local budget. Replay the result with:
|
|
96
198
|
|
|
97
199
|
```bash
|
|
98
|
-
pnpm flakelab replay flakelab.repro.yaml --concurrency
|
|
200
|
+
pnpm flakelab replay flakelab.repro.yaml --concurrency 1
|
|
99
201
|
```
|
|
100
202
|
|
|
101
|
-
The current
|
|
102
|
-
|
|
203
|
+
The current project-level fault matrix supports deterministic network delay, type-aware resource
|
|
204
|
+
loading delay, injected HTTP failures, response truncation, response-tail duplication, and response
|
|
205
|
+
reordering. Minimize
|
|
206
|
+
malformed payload triggers with `--fault response-truncation --max-remove-bytes 1024` or
|
|
207
|
+
`--fault response-duplication --max-duplicate-bytes 1024`. Find request races with
|
|
208
|
+
`--fault response-reordering --max-hold-ms 250`; the first response in each adjacent matching pair
|
|
209
|
+
is held while the second proceeds. Response mutations run in the order stored in the bounded fault
|
|
210
|
+
set, so a reproducer can express and replay an exact composition without introducing another schema.
|
|
211
|
+
Isolate startup dependencies with
|
|
212
|
+
`--fault resource-loading-delay --resource-type script --pattern "**/assets/*" --max-delay 250`.
|
|
213
|
+
Resource loading can target `document`, `script`, `stylesheet`, `image`, or `font` without slowing
|
|
214
|
+
unrelated API traffic. Discovery reports the minimum observed timing boundary and saves a
|
|
215
|
+
stability-margin trigger between that boundary and the configured maximum, so the portable
|
|
216
|
+
reproducer is less sensitive to ordinary machine jitter.
|
|
217
|
+
Delay application lifecycle listeners independently with
|
|
218
|
+
`--fault startup-event-delay --startup-event dom-content-loaded --pattern "**/checkout"`.
|
|
219
|
+
The fault supports `dom-content-loaded` and `load`, injects a temporary same-origin bootstrap into
|
|
220
|
+
matching HTML, preserves an existing script nonce, and verifies that the bootstrap actually ran.
|
|
221
|
+
It delays application listeners rather than the browser lifecycle event itself, network traffic,
|
|
222
|
+
or the event loop.
|
|
223
|
+
Probe missed main-thread deadlines with
|
|
224
|
+
`--fault event-loop-stall --max-stall-ms 500 --stall-after-ms 0 --pattern "**/checkout"`.
|
|
225
|
+
The offset is measured from the native `DOMContentLoaded` event and the stall duration is bounded
|
|
226
|
+
at two seconds. Discovery finds the minimum observed duration, confirms it with repeated trials,
|
|
227
|
+
and saves a stability-margin reproducer. The fault blocks only the matching page's main thread;
|
|
228
|
+
it does not simulate system-wide CPU saturation.
|
|
229
|
+
Test expired session handling without recording credentials with
|
|
230
|
+
`--fault auth-cookie-expiry --cookie-name session-id --pattern "**/api/session"`.
|
|
231
|
+
Only the named cookie is withheld from matched requests, and an absent cookie is reported as an
|
|
232
|
+
unapplied fault rather than false causal evidence. Reproducers contain the cookie name but never
|
|
233
|
+
its value. The direct browser adapter restores the original cookie in `finally`.
|
|
234
|
+
Test partially initialized browser state with
|
|
235
|
+
`--fault storage-state-delay --storage local-storage --storage-key auth-token --max-delay 500`
|
|
236
|
+
and a document URL pattern. The bootstrap temporarily makes `getItem` return `null` only for that
|
|
237
|
+
key; it does not delete or record the stored value. Both local and session storage are supported,
|
|
238
|
+
and discovery minimizes the visibility delay before saving a stability-margin trigger.
|
|
239
|
+
Expose wall-clock assumptions with
|
|
240
|
+
`--fault clock-jump --clock-offset-ms 3600000 --jump-after-ms 25 --pattern "**/app"`.
|
|
241
|
+
The fault changes `Date` wall time after a deterministic offset while leaving `performance.now`,
|
|
242
|
+
timeouts, and interval scheduling monotonic. Use `--clock-offset-ms=-3600000` for a backward jump.
|
|
243
|
+
Exercise internationalization assumptions with
|
|
244
|
+
`--fault locale --locale fr-FR --pattern "**/app"` or
|
|
245
|
+
`--fault timezone --timezone America/New_York --pattern "**/app"`. Locale tags and IANA timezone
|
|
246
|
+
names are validated before trials. These two settings are applied through Playwright's browser
|
|
247
|
+
context configuration; the document pattern verifies that the intended target was reached. The
|
|
248
|
+
direct page adapter rejects them explicitly because an existing Playwright context cannot be
|
|
249
|
+
reconfigured faithfully after creation.
|
|
250
|
+
Probe responsive and accessibility-specific behavior with
|
|
251
|
+
`--fault viewport --viewport-width 375 --viewport-height 667` and
|
|
252
|
+
`--fault reduced-motion`. Viewports are bounded from 200 to 7680 pixels in each dimension, and
|
|
253
|
+
reduced motion uses the browser's native media emulation. Test animation races with
|
|
254
|
+
`--fault animation-speed --animation-rate 5`; rates are bounded from 0.1x to 10x and alter Web
|
|
255
|
+
Animations and CSS animation/transition playback without accelerating application timers. Each
|
|
256
|
+
visual fault is reversible, uses a paired control, and requires a matched document before FlakeLab
|
|
257
|
+
accepts its causal evidence.
|
|
258
|
+
Expose suite-level isolation bugs with
|
|
259
|
+
`--fault worker-pressure --max-workers 4`. FlakeLab raises Playwright's supported worker limit,
|
|
260
|
+
enables full parallel scheduling for the selected target, and saves the smallest worker count that
|
|
261
|
+
reproduces the failure. Probe shared accounts, records, ports, and other cross-test state with
|
|
262
|
+
`--fault shared-state-interference --max-copies 4`; this overlaps repeated copies of the selected
|
|
263
|
+
test through Playwright's supported `repeatEach` and worker controls. Both faults use matched-seed
|
|
264
|
+
single-worker controls, are bounded to 16 workers or copies, require an independent 12-trial
|
|
265
|
+
confirmation, and store the selected test path—not a URL glob—as their artifact target.
|
|
266
|
+
The local proxy and generated Playwright config are removed after every trial, including failed and
|
|
267
|
+
interrupted tests. Solari repair proof consumes the same validated fault set as local replay.
|
|
268
|
+
Revision bisect still rejects unsupported fault combinations explicitly rather than approximating
|
|
269
|
+
remote behavior.
|
|
103
270
|
|
|
104
271
|
## AI investigator
|
|
105
272
|
|
|
@@ -109,7 +276,7 @@ terminal requests it through hidden input and keeps it in memory for this run on
|
|
|
109
276
|
```bash
|
|
110
277
|
pnpm flakelab investigate tests/fixtures/flaky-checkout.spec.ts \
|
|
111
278
|
--trials 4 \
|
|
112
|
-
--concurrency
|
|
279
|
+
--concurrency 1 \
|
|
113
280
|
--max-delay 125
|
|
114
281
|
```
|
|
115
282
|
|
|
@@ -168,17 +335,24 @@ tree:
|
|
|
168
335
|
```bash
|
|
169
336
|
pnpm flakelab repair flakelab.investigation.json \
|
|
170
337
|
--reproducer flakelab.repro.yaml \
|
|
338
|
+
--source src/checkout.ts \
|
|
339
|
+
--max-cost 0.25 \
|
|
171
340
|
--patch candidate.diff \
|
|
172
341
|
--proof flakelab.proof.json
|
|
173
342
|
```
|
|
174
343
|
|
|
175
|
-
This command requires both `GROQ_API_KEY` and `SOLARI_API_KEY`.
|
|
176
|
-
|
|
344
|
+
This command requires both `GROQ_API_KEY` and `SOLARI_API_KEY`. `--max-cost` bounds candidate
|
|
345
|
+
generation, and the result reports observed model tokens and estimated cost. Repeat `--source` to
|
|
346
|
+
approve up to seven application source files that a black-box Playwright test does not import.
|
|
347
|
+
Every approved file remains project-confined, JavaScript/TypeScript-only, credential-blocked, and
|
|
348
|
+
inside the shared 64 KiB context limit. The model can propose only exact, bounded edits to source it
|
|
349
|
+
received. FlakeLab rejects test changes,
|
|
177
350
|
assertion weakening, lint suppressions, credential-like additions, path escapes, and numeric-only
|
|
178
351
|
timeout increases before execution.
|
|
179
352
|
|
|
180
353
|
The candidate is copied into a disposable Solari microVM. Typecheck, lint, hostile trials, clean
|
|
181
|
-
controls, and
|
|
354
|
+
controls, and up to 50 nested or co-located `.spec.*` and `.test.*` regression files all run there.
|
|
355
|
+
The machine is destroyed afterward, and the
|
|
182
356
|
candidate is returned as a reviewable diff; FlakeLab never applies it to the local checkout.
|
|
183
357
|
Cold validation installs the pinned Node/pnpm toolchain and Chromium, so it is intentionally
|
|
184
358
|
slower than the future snapshot-backed warm path.
|
|
@@ -199,12 +373,19 @@ pnpm flakelab report flakelab.investigation.json \
|
|
|
199
373
|
The React/Recharts interface is bundled by Vite into a single HTML file. It explains the root
|
|
200
374
|
cause, deterministic ownership classification, experiment timeline, competing hypotheses,
|
|
201
375
|
minimal trigger, before/after proof matrix, static checks, model usage, and reviewable artifacts.
|
|
376
|
+
Each causal conclusion links to both its intervention and clean control. Investigation mode retains
|
|
377
|
+
project-relative Playwright traces, compares one representative passing control with one failing
|
|
378
|
+
intervention, and links each recording from its experiment. The report also gives an exact replay
|
|
379
|
+
command, the bounded source files shown to the investigator, and the line of every candidate edit.
|
|
202
380
|
The evidence is schema-validated and credential-redacted before rendering. A restrictive content
|
|
203
|
-
security policy blocks runtime network access,
|
|
381
|
+
security policy blocks runtime network access, unsafe evidence links are rejected, and the report
|
|
382
|
+
remains usable offline.
|
|
204
383
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
384
|
+
Interactive report generation offers to open the local file in the default browser. `--open`
|
|
385
|
+
accepts that choice without prompting, while CI and redirected sessions never prompt or open a
|
|
386
|
+
browser implicitly. `--publish` is optional and always asks for interactive confirmation
|
|
387
|
+
immediately before creating a public Solari preview. Published reports expire and their hosting
|
|
388
|
+
sandbox is automatically killed after at most 60 minutes.
|
|
208
389
|
|
|
209
390
|
## Statistical Git bisect
|
|
210
391
|
|
|
@@ -264,8 +445,14 @@ Playwright configuration, manifest, or lockfile behavior changes, it falls back
|
|
|
264
445
|
to keep time and cost predictable; `.flakelab/changed-tests.json` preserves the complete selection.
|
|
265
446
|
|
|
266
447
|
The uploaded artifact is retained for seven days and includes the offline HTML report,
|
|
267
|
-
investigation, reproducer, discovery evidence, proof, candidate diff, and selection manifest.
|
|
268
|
-
rejected repair still uploads its evidence before the job reports failure.
|
|
448
|
+
investigation traces, reproducer, discovery evidence, proof, candidate diff, and selection manifest.
|
|
449
|
+
A rejected repair still uploads its evidence before the job reports failure.
|
|
450
|
+
|
|
451
|
+
The same composite action can analyze an existing Playwright blob report without Groq or Solari
|
|
452
|
+
credentials. Pass `blob-report` and, optionally, an artifact downloaded from an earlier run as
|
|
453
|
+
`baseline`. The action does not rerun tests in this mode; it merges shards, ranks failures, marks
|
|
454
|
+
signatures absent from the baseline, uploads the validated analysis, and writes a concise Markdown
|
|
455
|
+
job summary. No hosted database is required.
|
|
269
456
|
|
|
270
457
|
## Parallel Solari runner
|
|
271
458
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ScanTestResult } from "../scan/schema.js";
|
|
2
|
+
import type { AnalysisSourceKind } from "./schema.js";
|
|
3
|
+
export interface BlobReportAnalysisOptions {
|
|
4
|
+
artifactDirectory: string;
|
|
5
|
+
playwrightCliPath?: string;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export interface BlobReportAnalysisResult {
|
|
9
|
+
archiveCount: number;
|
|
10
|
+
artifactDirectory: string | null;
|
|
11
|
+
runnerErrors: string[];
|
|
12
|
+
sourceKind: AnalysisSourceKind;
|
|
13
|
+
tests: ScanTestResult[];
|
|
14
|
+
}
|
|
15
|
+
export declare function analyzeBlobReports(projectRoot: string, source: string, options: BlobReportAnalysisOptions): Promise<BlobReportAnalysisResult>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { copyFile, lstat, mkdir, mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
|
3
|
+
import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { retainOwnedArtifacts } from "../artifacts/retention.js";
|
|
5
|
+
import { redactText } from "../report/redaction.js";
|
|
6
|
+
import { parseNativeScanReport } from "../runner/native-scan.js";
|
|
7
|
+
import { createPlaywrightEnvironment, resolvePlaywrightCliPath, } from "../runner/playwright-executor.js";
|
|
8
|
+
import { waitForProcessTree } from "../runner/process-tree.js";
|
|
9
|
+
import { inspectZipArchive } from "./zip-archive.js";
|
|
10
|
+
const MAX_ARCHIVES = 100;
|
|
11
|
+
const MAX_TOTAL_EXPANDED_BYTES = 1_024 * 1_024 * 1_024;
|
|
12
|
+
async function discoverInput(sourcePath) {
|
|
13
|
+
const information = await lstat(sourcePath);
|
|
14
|
+
if (information.isSymbolicLink()) {
|
|
15
|
+
throw new Error("Blob report input cannot be a symbolic link");
|
|
16
|
+
}
|
|
17
|
+
if (information.isFile()) {
|
|
18
|
+
if (extname(sourcePath).toLowerCase() !== ".zip") {
|
|
19
|
+
throw new Error("Blob report file must use the .zip extension");
|
|
20
|
+
}
|
|
21
|
+
return { archives: [sourcePath], kind: "blob-archive" };
|
|
22
|
+
}
|
|
23
|
+
if (!information.isDirectory()) {
|
|
24
|
+
throw new Error("Blob report input must be a ZIP archive or directory");
|
|
25
|
+
}
|
|
26
|
+
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
27
|
+
const archiveEntries = entries.filter((entry) => extname(entry.name).toLowerCase() === ".zip");
|
|
28
|
+
if (archiveEntries.some((entry) => entry.isSymbolicLink() || !entry.isFile())) {
|
|
29
|
+
throw new Error("Blob report directory contains a non-regular ZIP entry");
|
|
30
|
+
}
|
|
31
|
+
const archives = archiveEntries
|
|
32
|
+
.map((entry) => resolve(sourcePath, entry.name))
|
|
33
|
+
.sort((left, right) => left.localeCompare(right));
|
|
34
|
+
if (archives.length === 0) {
|
|
35
|
+
throw new Error("Blob report directory does not contain any ZIP archives");
|
|
36
|
+
}
|
|
37
|
+
return { archives, kind: "blob-directory" };
|
|
38
|
+
}
|
|
39
|
+
async function validateArchives(archives) {
|
|
40
|
+
if (archives.length > MAX_ARCHIVES) {
|
|
41
|
+
throw new Error(`Blob report input exceeds the ${MAX_ARCHIVES}-archive safety limit`);
|
|
42
|
+
}
|
|
43
|
+
let expandedBytes = 0;
|
|
44
|
+
for (const archive of archives) {
|
|
45
|
+
const summary = await inspectZipArchive(archive);
|
|
46
|
+
expandedBytes += summary.expandedBytes;
|
|
47
|
+
if (expandedBytes > MAX_TOTAL_EXPANDED_BYTES) {
|
|
48
|
+
throw new Error("Blob reports expand beyond the 1 GiB combined safety limit");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function mergeArguments(playwrightCliPath, inputDirectory) {
|
|
53
|
+
return [playwrightCliPath, "merge-reports", "--reporter=json", inputDirectory];
|
|
54
|
+
}
|
|
55
|
+
async function copyArchives(archives, directory) {
|
|
56
|
+
for (const [index, archive] of archives.entries()) {
|
|
57
|
+
const destination = join(directory, `${String(index + 1).padStart(3, "0")}-${basename(archive)}`);
|
|
58
|
+
await copyFile(archive, destination);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function assertDirectChild(parent, directory) {
|
|
62
|
+
const child = relative(resolve(parent), resolve(directory));
|
|
63
|
+
if (!child || child.startsWith("..") || isAbsolute(child) || child.includes("/") || child.includes("\\")) {
|
|
64
|
+
throw new Error("Refusing to clean an analysis path outside its owned parent");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function removeWorkDirectory(parent, directory) {
|
|
68
|
+
assertDirectChild(parent, directory);
|
|
69
|
+
await rm(directory, { force: true, recursive: true });
|
|
70
|
+
}
|
|
71
|
+
function mergeFailure(diagnostic) {
|
|
72
|
+
const safeDiagnostic = redactText(diagnostic).trim().slice(-2_000);
|
|
73
|
+
return new Error(safeDiagnostic || "Playwright could not merge the blob reports");
|
|
74
|
+
}
|
|
75
|
+
async function removeConversionFiles(directory) {
|
|
76
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
77
|
+
const files = entries.filter((entry) => entry.isFile() || entry.isSymbolicLink());
|
|
78
|
+
await Promise.all(files.map(async (entry) => rm(join(directory, entry.name), { force: true })));
|
|
79
|
+
}
|
|
80
|
+
export async function analyzeBlobReports(projectRoot, source, options) {
|
|
81
|
+
const sourcePath = resolve(projectRoot, source);
|
|
82
|
+
const input = await discoverInput(sourcePath);
|
|
83
|
+
const workParent = resolve(projectRoot, options.artifactDirectory, "playwright-analysis");
|
|
84
|
+
await mkdir(workParent, { recursive: true });
|
|
85
|
+
const workDirectory = await mkdtemp(join(workParent, "run-"));
|
|
86
|
+
let cleanupComplete = false;
|
|
87
|
+
try {
|
|
88
|
+
await copyArchives(input.archives, workDirectory);
|
|
89
|
+
const archiveCopies = (await readdir(workDirectory, { withFileTypes: true }))
|
|
90
|
+
.filter((entry) => entry.isFile() && extname(entry.name).toLowerCase() === ".zip")
|
|
91
|
+
.map((entry) => resolve(workDirectory, entry.name))
|
|
92
|
+
.sort((left, right) => left.localeCompare(right));
|
|
93
|
+
await validateArchives(archiveCopies);
|
|
94
|
+
const reportPath = join(workDirectory, "flakelab-merged.json");
|
|
95
|
+
const playwrightCliPath = options.playwrightCliPath ?? resolvePlaywrightCliPath(projectRoot);
|
|
96
|
+
const child = spawn(process.execPath, mergeArguments(playwrightCliPath, workDirectory), {
|
|
97
|
+
cwd: projectRoot,
|
|
98
|
+
detached: process.platform !== "win32",
|
|
99
|
+
env: {
|
|
100
|
+
...createPlaywrightEnvironment(),
|
|
101
|
+
PLAYWRIGHT_JSON_OUTPUT_FILE: reportPath,
|
|
102
|
+
},
|
|
103
|
+
shell: false,
|
|
104
|
+
windowsHide: true,
|
|
105
|
+
});
|
|
106
|
+
const execution = await waitForProcessTree(child, options.signal);
|
|
107
|
+
if (options.signal?.aborted) {
|
|
108
|
+
throw new Error("Playwright blob report analysis was interrupted");
|
|
109
|
+
}
|
|
110
|
+
if (execution.spawnError || execution.exitCode !== 0) {
|
|
111
|
+
throw mergeFailure(execution.spawnError ?? execution.diagnostic);
|
|
112
|
+
}
|
|
113
|
+
let parsed;
|
|
114
|
+
try {
|
|
115
|
+
parsed = parseNativeScanReport(await readFile(reportPath, "utf8"), projectRoot);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
throw new Error("Playwright produced a missing or malformed merged JSON report");
|
|
119
|
+
}
|
|
120
|
+
await removeConversionFiles(workDirectory);
|
|
121
|
+
const artifactDirectory = await retainOwnedArtifacts(projectRoot, workDirectory);
|
|
122
|
+
cleanupComplete = true;
|
|
123
|
+
return {
|
|
124
|
+
archiveCount: input.archives.length,
|
|
125
|
+
artifactDirectory,
|
|
126
|
+
runnerErrors: parsed.runnerErrors,
|
|
127
|
+
sourceKind: input.kind,
|
|
128
|
+
tests: parsed.tests,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
if (!cleanupComplete) {
|
|
133
|
+
await removeWorkDirectory(workParent, workDirectory);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=blob-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-report.js","sourceRoot":"","sources":["../../src/analysis/blob-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
export function parseAnalyzeArguments(args) {
|
|
3
|
+
const parsed = parseArgs({
|
|
4
|
+
args,
|
|
5
|
+
allowPositionals: true,
|
|
6
|
+
strict: true,
|
|
7
|
+
options: {
|
|
8
|
+
artifacts: { type: "string", default: ".flakelab/runs" },
|
|
9
|
+
baseline: { type: "string" },
|
|
10
|
+
json: { type: "boolean", default: false },
|
|
11
|
+
verbose: { type: "boolean", default: false },
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
if (parsed.positionals.length !== 1) {
|
|
15
|
+
throw new Error("analyze requires exactly one blob report path");
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
command: "analyze",
|
|
19
|
+
options: {
|
|
20
|
+
artifacts: parsed.values.artifacts ?? ".flakelab/runs",
|
|
21
|
+
baseline: parsed.values.baseline,
|
|
22
|
+
json: parsed.values.json ?? false,
|
|
23
|
+
verbose: parsed.values.verbose ?? false,
|
|
24
|
+
},
|
|
25
|
+
target: parsed.positionals[0],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/analysis/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAUrC,MAAM,UAAU,qBAAqB,CAAC,IAAc;IAClD,MAAM,MAAM,GAAG,SAAS,CAAC;QACvB,IAAI;QACJ,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE;YACP,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE;YACxD,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC5B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;YACzC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;SAC7C;KACF,CAAC,CAAA;IACF,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;IAClE,CAAC;IACD,OAAO;QACL,OAAO,EAAE,SAAS;QAClB,OAAO,EAAE;YACP,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,IAAI,gBAAgB;YACtD,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ;YAChC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,KAAK;YACjC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,KAAK;SACxC;QACD,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;KAC9B,CAAA;AACH,CAAC"}
|