@empiricalrun/test-run 0.13.0 → 0.13.2
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/CHANGELOG.md +14 -0
- package/README.md +42 -3
- package/dist/bin/commands/estimate-time-shard.d.ts +3 -0
- package/dist/bin/commands/estimate-time-shard.d.ts.map +1 -0
- package/dist/bin/commands/estimate-time-shard.js +122 -0
- package/dist/bin/commands/failed-list.d.ts +3 -0
- package/dist/bin/commands/failed-list.d.ts.map +1 -0
- package/dist/bin/commands/failed-list.js +34 -0
- package/dist/bin/commands/merge.d.ts +3 -0
- package/dist/bin/commands/merge.d.ts.map +1 -0
- package/dist/bin/commands/merge.js +20 -0
- package/dist/bin/commands/optimize-shards.d.ts +3 -0
- package/dist/bin/commands/optimize-shards.d.ts.map +1 -0
- package/dist/bin/commands/optimize-shards.js +400 -0
- package/dist/bin/commands/run.d.ts +3 -0
- package/dist/bin/commands/run.d.ts.map +1 -0
- package/dist/bin/commands/run.js +132 -0
- package/dist/bin/index.js +15 -132
- package/dist/failed-test-list.d.ts +35 -0
- package/dist/failed-test-list.d.ts.map +1 -0
- package/dist/failed-test-list.js +267 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/lib/cancellation-watcher.js +1 -1
- package/dist/lib/merge-reports/html.d.ts +2 -0
- package/dist/lib/merge-reports/html.d.ts.map +1 -0
- package/dist/lib/merge-reports/html.js +113 -0
- package/dist/lib/merge-reports/index.d.ts +16 -0
- package/dist/lib/merge-reports/index.d.ts.map +1 -0
- package/dist/lib/merge-reports/index.js +189 -0
- package/dist/lib/merge-reports/json.d.ts +2 -0
- package/dist/lib/merge-reports/json.d.ts.map +1 -0
- package/dist/lib/merge-reports/json.js +67 -0
- package/dist/lib/merge-reports/types.d.ts +15 -0
- package/dist/lib/merge-reports/types.d.ts.map +1 -0
- package/dist/lib/merge-reports/types.js +11 -0
- package/package.json +4 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/bin/merge-reports.d.ts +0 -3
- package/dist/bin/merge-reports.d.ts.map +0 -1
- package/dist/bin/merge-reports.js +0 -26
- package/dist/lib/merge-reports.d.ts +0 -26
- package/dist/lib/merge-reports.d.ts.map +0 -1
- package/dist/lib/merge-reports.js +0 -248
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @empiricalrun/test-run
|
|
2
2
|
|
|
3
|
+
## 0.13.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [1967ac3]
|
|
8
|
+
- @empiricalrun/r2-uploader@0.9.0
|
|
9
|
+
|
|
10
|
+
## 0.13.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [3ee1aec]
|
|
15
|
+
- @empiricalrun/r2-uploader@0.8.0
|
|
16
|
+
|
|
3
17
|
## 0.13.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -27,7 +27,15 @@ You can also use the CLI directly with `npx`.
|
|
|
27
27
|
|
|
28
28
|
## 3. Basic Usage
|
|
29
29
|
|
|
30
|
-
The CLI entry point is defined in `package.json` under `"bin"`.
|
|
30
|
+
The CLI entry point is defined in `package.json` under `"bin"`. The CLI provides three subcommands:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @empiricalrun/test-run run [flags] [Playwright/test arguments] # Run tests (default)
|
|
34
|
+
npx @empiricalrun/test-run merge [flags] # Merge blob reports
|
|
35
|
+
npx @empiricalrun/test-run failed-list <run-id> [flags] # Build test list from failed run
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The `run` command is the default, so you can omit it:
|
|
31
39
|
|
|
32
40
|
```bash
|
|
33
41
|
npx @empiricalrun/test-run [flags] [Playwright/test arguments]
|
|
@@ -45,7 +53,9 @@ Here, `--retries 0` (and any unrecognized options) get passed on to the underlyi
|
|
|
45
53
|
|
|
46
54
|
## 4. CLI Flags
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
### `run` command
|
|
57
|
+
|
|
58
|
+
Below are the main CLI options for the `run` command (implemented in `src/bin/commands/run.ts`):
|
|
49
59
|
| Flag/Option | Description |
|
|
50
60
|
|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
|
|
51
61
|
| `-n, --name <test-name>` | The scenario name (title) of the specific test to run. If provided, only that particular test/scenario is run instead of all tests. |
|
|
@@ -59,6 +69,35 @@ Below are the main CLI options implemented in `src/bin/index.ts`:
|
|
|
59
69
|
|
|
60
70
|
Other unrecognized flags on the command line will be appended as **Playwright arguments**.
|
|
61
71
|
|
|
72
|
+
### `merge` command
|
|
73
|
+
|
|
74
|
+
Merges blob reports from sharded test runs:
|
|
75
|
+
|
|
76
|
+
| Flag/Option | Description |
|
|
77
|
+
|---------------------------|-----------------------------------------------------------------------------|
|
|
78
|
+
| `-b, --blob-dir <path>` | Path to the blob-report directory containing sharded reports. |
|
|
79
|
+
| `-c, --cwd <path>` | Working directory for the merge operation. |
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx @empiricalrun/test-run merge --blob-dir ./blob-reports --cwd ./output
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `failed-list` command
|
|
86
|
+
|
|
87
|
+
Builds a test list from a failed test run for retrying:
|
|
88
|
+
|
|
89
|
+
| Flag/Option | Description |
|
|
90
|
+
|---------------------------|-----------------------------------------------------------------------------|
|
|
91
|
+
| `<run-id>` | (Required) Test run ID to fetch failed tests from. |
|
|
92
|
+
| `-o, --output <path>` | Output file path for the test list. |
|
|
93
|
+
| `-r, --repo-name <name>` | Repository name for project lookup. |
|
|
94
|
+
| `-p, --repo-path <path>` | Path to local repo to expand serial blocks. |
|
|
95
|
+
| `-v, --verbose` | Enable verbose logging. |
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx @empiricalrun/test-run failed-list abc123 --output ./failed-tests.json
|
|
99
|
+
```
|
|
100
|
+
|
|
62
101
|
---
|
|
63
102
|
|
|
64
103
|
## 5. Environment Variables
|
|
@@ -161,4 +200,4 @@ This flow makes sure tests run against the relevant environment or a fresh build
|
|
|
161
200
|
- Skipping teardown blocks.
|
|
162
201
|
- Working with multiple or specialized Playwright projects.
|
|
163
202
|
|
|
164
|
-
Explore the source (particularly `src/bin/
|
|
203
|
+
Explore the source (particularly `src/bin/commands/` and `src/utils`) for deeper technical insights and advanced customization.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate-time-shard.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/estimate-time-shard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6DzC,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,OAAO,QA6HhE"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerEstimateTimeShardCommand = registerEstimateTimeShardCommand;
|
|
4
|
+
const reporter_1 = require("@empiricalrun/reporter");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
async function fetchTestHistory(testCaseIds, dashboardUrl) {
|
|
7
|
+
const apiKey = process.env.EMPIRICALRUN_API_KEY;
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
throw new Error("EMPIRICALRUN_API_KEY environment variable is required");
|
|
10
|
+
}
|
|
11
|
+
const url = `${dashboardUrl}/api/test-cases/history/batch`;
|
|
12
|
+
const response = await fetch(url, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
Authorization: `Bearer ${apiKey}`,
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({ test_case_ids: testCaseIds }),
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
throw new Error(`Failed to fetch test history: ${response.status}`);
|
|
22
|
+
}
|
|
23
|
+
return response.json();
|
|
24
|
+
}
|
|
25
|
+
function calculateEstimatedDuration(history, includeRetries) {
|
|
26
|
+
if (history.length === 0) {
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
const durationField = includeRetries
|
|
30
|
+
? "duration_total"
|
|
31
|
+
: "duration_per_retry";
|
|
32
|
+
const avgDuration = history.reduce((sum, r) => sum + r[durationField], 0) / history.length;
|
|
33
|
+
return avgDuration;
|
|
34
|
+
}
|
|
35
|
+
function registerEstimateTimeShardCommand(program) {
|
|
36
|
+
program
|
|
37
|
+
.command("estimate-time-shard")
|
|
38
|
+
.description("Estimate how long a shard will take based on historical data")
|
|
39
|
+
.requiredOption("--shard <shard>", "Shard identifier in N/M format (e.g., 1/2)")
|
|
40
|
+
.option("--dashboard-url <url>", "Dashboard URL for fetching test history", process.env.DASHBOARD_URL || "https://dash.empirical.run")
|
|
41
|
+
.option("--workers <workers>", "Number of parallel workers", "8")
|
|
42
|
+
.option("--include-retries", "Use total duration including retries (accounts for flakiness)", false)
|
|
43
|
+
.action(async (options) => {
|
|
44
|
+
const { shard, dashboardUrl, includeRetries } = options;
|
|
45
|
+
const workers = parseInt(options.workers, 10);
|
|
46
|
+
const shardMatch = shard.match(/^(\d+)\/(\d+)$/);
|
|
47
|
+
if (!shardMatch) {
|
|
48
|
+
console.error('Invalid shard format. Expected N/M format (e.g., "1/2")');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const shardIndex = parseInt(shardMatch[1], 10);
|
|
52
|
+
const totalShards = parseInt(shardMatch[2], 10);
|
|
53
|
+
if (shardIndex < 1 || shardIndex > totalShards) {
|
|
54
|
+
console.error(`Invalid shard index. Must be between 1 and ${totalShards}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
let jsonReport;
|
|
58
|
+
try {
|
|
59
|
+
const output = (0, child_process_1.execSync)(`npx playwright test --shard ${shard} --list --reporter=json`, {
|
|
60
|
+
encoding: "utf-8",
|
|
61
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
62
|
+
});
|
|
63
|
+
jsonReport = JSON.parse(output);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error.stdout) {
|
|
67
|
+
try {
|
|
68
|
+
jsonReport = JSON.parse(error.stdout);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
console.error("Failed to parse playwright JSON output:", error.message);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error("Failed to get test list from playwright:", error.message);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const flattenedSpecs = (0, reporter_1.getFlattenedTestList)(jsonReport.suites);
|
|
81
|
+
if (flattenedSpecs.length === 0) {
|
|
82
|
+
console.log("No tests found for this shard.");
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
const testCaseIds = flattenedSpecs.map((spec) => spec.id);
|
|
86
|
+
let historyResponse;
|
|
87
|
+
try {
|
|
88
|
+
historyResponse = await fetchTestHistory(testCaseIds, dashboardUrl);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error("Failed to fetch test history:", error.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
let totalEstimatedDuration = 0;
|
|
95
|
+
let testsWithHistory = 0;
|
|
96
|
+
let testsWithoutHistory = 0;
|
|
97
|
+
for (const testId of testCaseIds) {
|
|
98
|
+
const history = historyResponse[testId] || [];
|
|
99
|
+
const estimatedDuration = calculateEstimatedDuration(history, includeRetries);
|
|
100
|
+
if (history.length > 0) {
|
|
101
|
+
testsWithHistory++;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
testsWithoutHistory++;
|
|
105
|
+
}
|
|
106
|
+
totalEstimatedDuration += estimatedDuration;
|
|
107
|
+
}
|
|
108
|
+
const parallelizedDuration = totalEstimatedDuration / workers;
|
|
109
|
+
const totalSeconds = Math.round(parallelizedDuration / 1000);
|
|
110
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
111
|
+
const seconds = totalSeconds % 60;
|
|
112
|
+
console.log(JSON.stringify({
|
|
113
|
+
shard,
|
|
114
|
+
totalTests: testCaseIds.length,
|
|
115
|
+
workers,
|
|
116
|
+
testsWithHistory,
|
|
117
|
+
testsWithoutHistory,
|
|
118
|
+
estimatedDurationMs: Math.round(parallelizedDuration),
|
|
119
|
+
estimatedDurationFormatted: `${minutes}m ${seconds}s`,
|
|
120
|
+
}));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failed-list.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/failed-list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,QAoCzD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerFailedListCommand = registerFailedListCommand;
|
|
4
|
+
const failed_test_list_1 = require("../../failed-test-list");
|
|
5
|
+
function registerFailedListCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("failed-list")
|
|
8
|
+
.description("Build test list from failed test run")
|
|
9
|
+
.argument("<run-id>", "Test run ID to fetch failed tests from")
|
|
10
|
+
.option("-o, --output <path>", "Output file path for the test list")
|
|
11
|
+
.option("-r, --repo-name <name>", "Repository name for project lookup")
|
|
12
|
+
.option("-p, --repo-path <path>", "Path to local repo to expand serial blocks")
|
|
13
|
+
.option("-v, --verbose", "Enable verbose logging")
|
|
14
|
+
.action(async (runId, options) => {
|
|
15
|
+
if (!runId) {
|
|
16
|
+
console.error("Error: run-id argument is required");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const result = await (0, failed_test_list_1.buildTestListFromFailedTestRun)(runId, {
|
|
21
|
+
outputPath: options.output,
|
|
22
|
+
repoName: options.repoName,
|
|
23
|
+
repoPath: options.repoPath,
|
|
24
|
+
verbose: options.verbose,
|
|
25
|
+
});
|
|
26
|
+
console.log(`Found ${result.failedTests.length} failed tests`);
|
|
27
|
+
console.log(`Test list written to: ${result.outputPath}`);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAgBpD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerMergeCommand = registerMergeCommand;
|
|
4
|
+
const merge_reports_1 = require("../../lib/merge-reports");
|
|
5
|
+
function registerMergeCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("merge")
|
|
8
|
+
.description("Merge blob reports from sharded test runs")
|
|
9
|
+
.option("-b, --blob-dir <blob-dir>", "Path to the blob-report directory")
|
|
10
|
+
.option("-c, --cwd <cwd>", "Working directory")
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
const { success } = await (0, merge_reports_1.mergeReports)({
|
|
13
|
+
blobDir: options.blobDir,
|
|
14
|
+
cwd: options.cwd,
|
|
15
|
+
});
|
|
16
|
+
if (!success) {
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimize-shards.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/optimize-shards.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgZzC,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,QAyQ7D"}
|