@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +42 -3
  3. package/dist/bin/commands/estimate-time-shard.d.ts +3 -0
  4. package/dist/bin/commands/estimate-time-shard.d.ts.map +1 -0
  5. package/dist/bin/commands/estimate-time-shard.js +122 -0
  6. package/dist/bin/commands/failed-list.d.ts +3 -0
  7. package/dist/bin/commands/failed-list.d.ts.map +1 -0
  8. package/dist/bin/commands/failed-list.js +34 -0
  9. package/dist/bin/commands/merge.d.ts +3 -0
  10. package/dist/bin/commands/merge.d.ts.map +1 -0
  11. package/dist/bin/commands/merge.js +20 -0
  12. package/dist/bin/commands/optimize-shards.d.ts +3 -0
  13. package/dist/bin/commands/optimize-shards.d.ts.map +1 -0
  14. package/dist/bin/commands/optimize-shards.js +400 -0
  15. package/dist/bin/commands/run.d.ts +3 -0
  16. package/dist/bin/commands/run.d.ts.map +1 -0
  17. package/dist/bin/commands/run.js +132 -0
  18. package/dist/bin/index.js +15 -132
  19. package/dist/failed-test-list.d.ts +35 -0
  20. package/dist/failed-test-list.d.ts.map +1 -0
  21. package/dist/failed-test-list.js +267 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -1
  25. package/dist/lib/cancellation-watcher.js +1 -1
  26. package/dist/lib/merge-reports/html.d.ts +2 -0
  27. package/dist/lib/merge-reports/html.d.ts.map +1 -0
  28. package/dist/lib/merge-reports/html.js +113 -0
  29. package/dist/lib/merge-reports/index.d.ts +16 -0
  30. package/dist/lib/merge-reports/index.d.ts.map +1 -0
  31. package/dist/lib/merge-reports/index.js +189 -0
  32. package/dist/lib/merge-reports/json.d.ts +2 -0
  33. package/dist/lib/merge-reports/json.d.ts.map +1 -0
  34. package/dist/lib/merge-reports/json.js +67 -0
  35. package/dist/lib/merge-reports/types.d.ts +15 -0
  36. package/dist/lib/merge-reports/types.d.ts.map +1 -0
  37. package/dist/lib/merge-reports/types.js +11 -0
  38. package/package.json +4 -4
  39. package/tsconfig.tsbuildinfo +1 -1
  40. package/dist/bin/merge-reports.d.ts +0 -3
  41. package/dist/bin/merge-reports.d.ts.map +0 -1
  42. package/dist/bin/merge-reports.js +0 -26
  43. package/dist/lib/merge-reports.d.ts +0 -26
  44. package/dist/lib/merge-reports.d.ts.map +0 -1
  45. 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"`. You can invoke it using:
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
- Below are the main CLI options implemented in `src/bin/index.ts`:
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/index.ts` and `src/utils`) for deeper technical insights and advanced customization.
203
+ Explore the source (particularly `src/bin/commands/` and `src/utils`) for deeper technical insights and advanced customization.
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerEstimateTimeShardCommand(program: Command): void;
3
+ //# sourceMappingURL=estimate-time-shard.d.ts.map
@@ -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,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerFailedListCommand(program: Command): void;
3
+ //# sourceMappingURL=failed-list.d.ts.map
@@ -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,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerMergeCommand(program: Command): void;
3
+ //# sourceMappingURL=merge.d.ts.map
@@ -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,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerOptimizeShardsCommand(program: Command): void;
3
+ //# sourceMappingURL=optimize-shards.d.ts.map
@@ -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"}