@nsxbet/playwright-orchestrator 0.7.0 → 0.8.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 +80 -24
- package/dist/commands/extract-timing.d.ts +20 -7
- package/dist/commands/extract-timing.d.ts.map +1 -1
- package/dist/commands/extract-timing.js +74 -21
- package/dist/commands/extract-timing.js.map +1 -1
- package/package.json +9 -1
- package/dist/commands/list-tests.d.ts +0 -16
- package/dist/commands/list-tests.d.ts.map +0 -1
- package/dist/commands/list-tests.js +0 -98
- package/dist/commands/list-tests.js.map +0 -1
package/README.md
CHANGED
|
@@ -25,29 +25,48 @@ This orchestrator:
|
|
|
25
25
|
|
|
26
26
|
Result: All shards finish at roughly the same time.
|
|
27
27
|
|
|
28
|
+
### Test-Level Distribution
|
|
29
|
+
|
|
30
|
+
Unlike other solutions that only distribute at the **file level**, this orchestrator supports **test-level distribution**. This matters when you have files with many tests of varying durations - distributing individual tests achieves much better balance than distributing entire files.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
File-level: login.spec.ts (50 tests, 10min) → all go to shard 1
|
|
34
|
+
Test-level: login.spec.ts tests → spread across shards 1-4
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Test-level distribution requires the reporter AND a test fixture to filter tests at runtime. See [Setup](#setup) below.
|
|
38
|
+
|
|
28
39
|
## Quick Start
|
|
29
40
|
|
|
30
41
|
```bash
|
|
31
42
|
# Install
|
|
32
43
|
bun add -D @nsxbet/playwright-orchestrator
|
|
33
44
|
|
|
34
|
-
#
|
|
35
|
-
playwright
|
|
45
|
+
# Generate test list
|
|
46
|
+
bunx playwright test --list --reporter=json --project "Mobile Chrome" > test-list.json
|
|
36
47
|
|
|
37
|
-
# Assign tests to shards
|
|
38
|
-
playwright-orchestrator assign \
|
|
39
|
-
--test-
|
|
48
|
+
# Assign tests to shards
|
|
49
|
+
bunx playwright-orchestrator assign \
|
|
50
|
+
--test-list ./test-list.json \
|
|
40
51
|
--timing-file ./timing-data.json \
|
|
41
|
-
--shards 4
|
|
42
|
-
|
|
52
|
+
--shards 4 > assignment.json
|
|
53
|
+
|
|
54
|
+
# Extract each shard's tests to separate files
|
|
55
|
+
jq '.shards."1"' assignment.json > shard-1.json
|
|
56
|
+
jq '.shards."2"' assignment.json > shard-2.json
|
|
57
|
+
jq '.shards."3"' assignment.json > shard-3.json
|
|
58
|
+
jq '.shards."4"' assignment.json > shard-4.json
|
|
43
59
|
|
|
44
|
-
#
|
|
45
|
-
|
|
60
|
+
# Run tests for a specific shard (fixture filters based on ORCHESTRATOR_SHARD_FILE)
|
|
61
|
+
ORCHESTRATOR_SHARD_FILE=shard-1.json bunx playwright test --project "Mobile Chrome"
|
|
62
|
+
|
|
63
|
+
# Extract timing from report after tests complete
|
|
64
|
+
bunx playwright-orchestrator extract-timing \
|
|
46
65
|
--report-file ./playwright-report/results.json \
|
|
47
66
|
--output-file ./shard-1-timing.json
|
|
48
67
|
|
|
49
|
-
# Merge timing data
|
|
50
|
-
playwright-orchestrator merge-timing \
|
|
68
|
+
# Merge timing data from all shards
|
|
69
|
+
bunx playwright-orchestrator merge-timing \
|
|
51
70
|
--existing ./timing-data.json \
|
|
52
71
|
--new ./shard-1-timing.json ./shard-2-timing.json \
|
|
53
72
|
--output ./timing-data.json
|
|
@@ -70,9 +89,11 @@ playwright-orchestrator merge-timing \
|
|
|
70
89
|
2. **Run Tests**: Each shard reads its files from `needs.orchestrate.outputs`
|
|
71
90
|
3. **Merge**: Collect timing from all shards, update history with EMA
|
|
72
91
|
|
|
73
|
-
##
|
|
92
|
+
## Setup
|
|
93
|
+
|
|
94
|
+
For test-level distribution to work, you need **two things**:
|
|
74
95
|
|
|
75
|
-
|
|
96
|
+
### 1. Reporter (in `playwright.config.ts`)
|
|
76
97
|
|
|
77
98
|
```typescript
|
|
78
99
|
import { defineConfig } from "@playwright/test";
|
|
@@ -82,7 +103,33 @@ export default defineConfig({
|
|
|
82
103
|
});
|
|
83
104
|
```
|
|
84
105
|
|
|
85
|
-
|
|
106
|
+
### 2. Test Fixture (in your test setup file)
|
|
107
|
+
|
|
108
|
+
Wrap your base test with `withOrchestratorFilter`:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// e2e/setup.ts
|
|
112
|
+
import { test as base } from "@playwright/test";
|
|
113
|
+
import { withOrchestratorFilter } from "@nsxbet/playwright-orchestrator/fixture";
|
|
114
|
+
|
|
115
|
+
export const test = withOrchestratorFilter(base);
|
|
116
|
+
export { expect } from "@playwright/test";
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Then use this `test` in your spec files:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// e2e/login.spec.ts
|
|
123
|
+
import { test, expect } from "./setup";
|
|
124
|
+
|
|
125
|
+
test("should login", async ({ page }) => {
|
|
126
|
+
// ...
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The reporter and fixture work together:
|
|
131
|
+
- **Reporter**: Reads `ORCHESTRATOR_SHARD_FILE` env var to know which tests belong to this shard
|
|
132
|
+
- **Fixture**: Skips tests that don't belong to the current shard at runtime
|
|
86
133
|
|
|
87
134
|
## Local Testing
|
|
88
135
|
|
|
@@ -143,9 +190,9 @@ jobs:
|
|
|
143
190
|
- uses: NSXBet/playwright-orchestrator/.github/actions/orchestrate@v0
|
|
144
191
|
id: orchestrate
|
|
145
192
|
with:
|
|
146
|
-
test-list: test-list.json #
|
|
193
|
+
test-list: test-list.json # Required: pre-generated list
|
|
194
|
+
timing-file: timing-data.json # Required: timing data
|
|
147
195
|
shards: 4
|
|
148
|
-
timing-file: timing-data.json
|
|
149
196
|
|
|
150
197
|
# Phase 2: Run tests (parallel matrix)
|
|
151
198
|
e2e:
|
|
@@ -176,17 +223,14 @@ See [docs/external-integration.md](./docs/external-integration.md) for complete
|
|
|
176
223
|
|
|
177
224
|
## CLI Commands
|
|
178
225
|
|
|
179
|
-
| Command | Description
|
|
180
|
-
| ---------------- |
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `merge-timing` | Merge timing data with EMA smoothing |
|
|
226
|
+
| Command | Description |
|
|
227
|
+
| ---------------- | ---------------------------------------- |
|
|
228
|
+
| `assign` | Distribute tests across shards |
|
|
229
|
+
| `extract-timing` | Extract timing from Playwright report |
|
|
230
|
+
| `merge-timing` | Merge timing data with EMA smoothing |
|
|
185
231
|
|
|
186
232
|
Run `playwright-orchestrator <command> --help` for details.
|
|
187
233
|
|
|
188
|
-
**Important**: The `--project` flag is recommended for both `list-tests` and `assign` commands to ensure accurate test discovery, especially for parameterized tests (e.g., `test.each`).
|
|
189
|
-
|
|
190
234
|
## Development
|
|
191
235
|
|
|
192
236
|
```bash
|
|
@@ -235,6 +279,18 @@ Test scenarios covered in `examples/monorepo/`:
|
|
|
235
279
|
|
|
236
280
|
See [AGENTS.md](./AGENTS.md) for AI assistant instructions.
|
|
237
281
|
|
|
282
|
+
## Cache Strategy
|
|
283
|
+
|
|
284
|
+
GitHub Actions cache is branch-scoped, which creates challenges for sharing timing data between PRs and main. We recommend a **promote-on-merge** pattern:
|
|
285
|
+
|
|
286
|
+
1. Each PR branch saves to its own cache key
|
|
287
|
+
2. PRs restore from their own cache, falling back to main
|
|
288
|
+
3. When a PR is merged, a workflow promotes the PR's cache to main
|
|
289
|
+
|
|
290
|
+
This avoids race conditions between concurrent PRs while ensuring main always has the latest timing data.
|
|
291
|
+
|
|
292
|
+
See [Cache Strategy for PRs](./docs/external-integration.md#cache-strategy-for-prs) for implementation details.
|
|
293
|
+
|
|
238
294
|
## License
|
|
239
295
|
|
|
240
296
|
MIT
|
|
@@ -9,7 +9,8 @@ export default class ExtractTiming extends Command {
|
|
|
9
9
|
project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
};
|
|
12
|
-
private
|
|
12
|
+
private testDir;
|
|
13
|
+
private rootDir;
|
|
13
14
|
run(): Promise<void>;
|
|
14
15
|
/**
|
|
15
16
|
* Extract test-level durations from Playwright report
|
|
@@ -19,19 +20,31 @@ export default class ExtractTiming extends Command {
|
|
|
19
20
|
private extractTestDurations;
|
|
20
21
|
/**
|
|
21
22
|
* Recursively extract test durations from a suite
|
|
23
|
+
*
|
|
24
|
+
* @param suite - Playwright suite from JSON report
|
|
25
|
+
* @param parentTitles - Title path from parent suites (describe blocks)
|
|
26
|
+
* @param testDurations - Map to collect test durations
|
|
27
|
+
* @param isRootSuite - Whether this is a root file suite (title is filename, should be skipped)
|
|
22
28
|
*/
|
|
23
29
|
private extractTestsFromSuite;
|
|
24
30
|
/**
|
|
25
|
-
* Normalize file path to be relative
|
|
26
|
-
*
|
|
31
|
+
* Normalize file path to be relative to testDir.
|
|
32
|
+
*
|
|
33
|
+
* DETERMINISTIC APPROACH:
|
|
34
|
+
* 1. suite.file in Playwright report is always relative to rootDir
|
|
35
|
+
* 2. testDir is either absolute or relative to rootDir
|
|
36
|
+
* 3. We resolve both to get absolute paths, then compute relative path
|
|
37
|
+
*
|
|
38
|
+
* This ensures consistent test IDs regardless of container path differences.
|
|
27
39
|
*/
|
|
28
40
|
private normalizeFilePath;
|
|
29
41
|
/**
|
|
30
|
-
* Extract
|
|
42
|
+
* Extract rootDir and testDir from Playwright report config.
|
|
31
43
|
*
|
|
32
|
-
* CRITICAL:
|
|
33
|
-
*
|
|
44
|
+
* CRITICAL: We need both paths to correctly resolve file locations:
|
|
45
|
+
* - rootDir: where playwright.config.ts is located (absolute)
|
|
46
|
+
* - testDir: where tests are located (can be relative to rootDir)
|
|
34
47
|
*/
|
|
35
|
-
private
|
|
48
|
+
private getPathsFromReport;
|
|
36
49
|
}
|
|
37
50
|
//# sourceMappingURL=extract-timing.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-timing.d.ts","sourceRoot":"","sources":["../../src/commands/extract-timing.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAI7C,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,OAAO;IAChD,OAAgB,WAAW,SACyB;IAEpD,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,KAAK;;;;;;MAyBnB;IAGF,OAAO,CAAC,OAAO,CAAc;IAEvB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"extract-timing.d.ts","sourceRoot":"","sources":["../../src/commands/extract-timing.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAI7C,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,OAAO;IAChD,OAAgB,WAAW,SACyB;IAEpD,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,KAAK;;;;;;MAyBnB;IAGF,OAAO,CAAC,OAAO,CAAc;IAE7B,OAAO,CAAC,OAAO,CAAc;IAEvB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAqD1B;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAe5B;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB;IAmD7B;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;IAiCzB;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;CAoD3B"}
|
|
@@ -34,8 +34,10 @@ export default class ExtractTiming extends Command {
|
|
|
34
34
|
default: false,
|
|
35
35
|
}),
|
|
36
36
|
};
|
|
37
|
-
// Base directory for path resolution (
|
|
38
|
-
|
|
37
|
+
// Base directory for path resolution (resolved testDir)
|
|
38
|
+
testDir = '';
|
|
39
|
+
// Root directory from Playwright config (where config file is)
|
|
40
|
+
rootDir = '';
|
|
39
41
|
async run() {
|
|
40
42
|
const { flags } = await this.parse(ExtractTiming);
|
|
41
43
|
// Read Playwright report
|
|
@@ -48,11 +50,13 @@ export default class ExtractTiming extends Command {
|
|
|
48
50
|
catch {
|
|
49
51
|
this.error(`Failed to read Playwright report: ${reportPath}`);
|
|
50
52
|
}
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
this.
|
|
53
|
+
// Extract rootDir and testDir from report config
|
|
54
|
+
const { rootDir, testDir } = this.getPathsFromReport(report, flags.project);
|
|
55
|
+
this.rootDir = rootDir;
|
|
56
|
+
this.testDir = testDir;
|
|
54
57
|
if (flags.verbose) {
|
|
55
|
-
this.log(`Using
|
|
58
|
+
this.log(`Using rootDir: ${this.rootDir}`);
|
|
59
|
+
this.log(`Using testDir: ${this.testDir}`);
|
|
56
60
|
}
|
|
57
61
|
// Extract test-level durations
|
|
58
62
|
const testDurations = this.extractTestDurations(report);
|
|
@@ -85,16 +89,27 @@ export default class ExtractTiming extends Command {
|
|
|
85
89
|
extractTestDurations(report) {
|
|
86
90
|
const testDurations = {};
|
|
87
91
|
for (const suite of report.suites) {
|
|
88
|
-
|
|
92
|
+
// Root suites represent files - their title is the filename
|
|
93
|
+
// We skip this title from titlePath since it's redundant with file
|
|
94
|
+
// This matches test-discovery.ts behavior
|
|
95
|
+
this.extractTestsFromSuite(suite, [], testDurations, true);
|
|
89
96
|
}
|
|
90
97
|
return testDurations;
|
|
91
98
|
}
|
|
92
99
|
/**
|
|
93
100
|
* Recursively extract test durations from a suite
|
|
101
|
+
*
|
|
102
|
+
* @param suite - Playwright suite from JSON report
|
|
103
|
+
* @param parentTitles - Title path from parent suites (describe blocks)
|
|
104
|
+
* @param testDurations - Map to collect test durations
|
|
105
|
+
* @param isRootSuite - Whether this is a root file suite (title is filename, should be skipped)
|
|
94
106
|
*/
|
|
95
|
-
extractTestsFromSuite(suite, parentTitles, testDurations) {
|
|
107
|
+
extractTestsFromSuite(suite, parentTitles, testDurations, isRootSuite = false) {
|
|
96
108
|
const file = this.normalizeFilePath(suite.file);
|
|
97
|
-
|
|
109
|
+
// Root suites have the filename as title - skip it from titlePath
|
|
110
|
+
// Nested suites (describe blocks) have meaningful titles to include
|
|
111
|
+
// This matches test-discovery.ts behavior exactly
|
|
112
|
+
const currentTitles = !isRootSuite && suite.title && suite.title !== ''
|
|
98
113
|
? [...parentTitles, suite.title]
|
|
99
114
|
: parentTitles;
|
|
100
115
|
// Process specs (actual tests)
|
|
@@ -112,7 +127,7 @@ export default class ExtractTiming extends Command {
|
|
|
112
127
|
testDurations[testId] = totalDuration;
|
|
113
128
|
}
|
|
114
129
|
}
|
|
115
|
-
// Process nested suites
|
|
130
|
+
// Process nested suites (describe blocks - never root suites)
|
|
116
131
|
if (suite.suites) {
|
|
117
132
|
for (const nestedSuite of suite.suites) {
|
|
118
133
|
// Pass the file from parent if nested suite doesn't have one
|
|
@@ -120,29 +135,65 @@ export default class ExtractTiming extends Command {
|
|
|
120
135
|
...nestedSuite,
|
|
121
136
|
file: nestedSuite.file || suite.file,
|
|
122
137
|
};
|
|
123
|
-
this.extractTestsFromSuite(nestedWithFile, currentTitles, testDurations);
|
|
138
|
+
this.extractTestsFromSuite(nestedWithFile, currentTitles, testDurations, false);
|
|
124
139
|
}
|
|
125
140
|
}
|
|
126
141
|
}
|
|
127
142
|
/**
|
|
128
|
-
* Normalize file path to be relative
|
|
129
|
-
*
|
|
143
|
+
* Normalize file path to be relative to testDir.
|
|
144
|
+
*
|
|
145
|
+
* DETERMINISTIC APPROACH:
|
|
146
|
+
* 1. suite.file in Playwright report is always relative to rootDir
|
|
147
|
+
* 2. testDir is either absolute or relative to rootDir
|
|
148
|
+
* 3. We resolve both to get absolute paths, then compute relative path
|
|
149
|
+
*
|
|
150
|
+
* This ensures consistent test IDs regardless of container path differences.
|
|
130
151
|
*/
|
|
131
152
|
normalizeFilePath(filePath) {
|
|
132
|
-
|
|
153
|
+
const normalizedFile = filePath.replace(/\\/g, '/');
|
|
154
|
+
const normalizedTestDir = this.testDir.replace(/\\/g, '/');
|
|
155
|
+
const normalizedRootDir = this.rootDir.replace(/\\/g, '/');
|
|
156
|
+
// suite.file in Playwright JSON report is relative to rootDir
|
|
157
|
+
// Resolve it to get the "logical" absolute path
|
|
158
|
+
const absoluteFile = path.isAbsolute(normalizedFile)
|
|
159
|
+
? normalizedFile
|
|
160
|
+
: path.join(normalizedRootDir, normalizedFile).replace(/\\/g, '/');
|
|
161
|
+
// testDir might be relative to rootDir, resolve it
|
|
162
|
+
const absoluteTestDir = path.isAbsolute(normalizedTestDir)
|
|
163
|
+
? normalizedTestDir
|
|
164
|
+
: path.join(normalizedRootDir, normalizedTestDir).replace(/\\/g, '/');
|
|
165
|
+
// Now compute relative path from testDir to file
|
|
166
|
+
// Both are now in the same "logical" path space
|
|
167
|
+
const relativePath = path
|
|
168
|
+
.relative(absoluteTestDir, absoluteFile)
|
|
169
|
+
.replace(/\\/g, '/');
|
|
170
|
+
// Sanity check: result should not start with ../
|
|
171
|
+
// If it does, the file is outside testDir which shouldn't happen
|
|
172
|
+
if (relativePath.startsWith('../')) {
|
|
173
|
+
// Log warning but continue - use basename as fallback
|
|
174
|
+
// This handles edge cases where paths are truly mismatched
|
|
175
|
+
return path.basename(filePath);
|
|
176
|
+
}
|
|
177
|
+
return relativePath;
|
|
133
178
|
}
|
|
134
179
|
/**
|
|
135
|
-
* Extract
|
|
180
|
+
* Extract rootDir and testDir from Playwright report config.
|
|
136
181
|
*
|
|
137
|
-
* CRITICAL:
|
|
138
|
-
*
|
|
182
|
+
* CRITICAL: We need both paths to correctly resolve file locations:
|
|
183
|
+
* - rootDir: where playwright.config.ts is located (absolute)
|
|
184
|
+
* - testDir: where tests are located (can be relative to rootDir)
|
|
139
185
|
*/
|
|
140
|
-
|
|
186
|
+
getPathsFromReport(report, projectName) {
|
|
141
187
|
const config = report.config;
|
|
142
188
|
if (!config) {
|
|
143
189
|
this.error('[Orchestrator] Report has no config section. ' +
|
|
144
190
|
'Ensure you are using Playwright JSON reporter with config output enabled.');
|
|
145
191
|
}
|
|
192
|
+
// rootDir is where playwright.config.ts is located
|
|
193
|
+
if (!config.rootDir) {
|
|
194
|
+
this.error('[Orchestrator] Report has no rootDir in config. ' +
|
|
195
|
+
'This is required to resolve test file paths correctly.');
|
|
196
|
+
}
|
|
146
197
|
if (!config.projects || config.projects.length === 0) {
|
|
147
198
|
this.error('[Orchestrator] Report has no projects in config. ' +
|
|
148
199
|
'Ensure your playwright.config.ts has at least one project configured.');
|
|
@@ -156,10 +207,12 @@ export default class ExtractTiming extends Command {
|
|
|
156
207
|
}
|
|
157
208
|
if (!project.testDir) {
|
|
158
209
|
this.error(`[Orchestrator] Project "${project.name}" has no testDir in report config. ` +
|
|
159
|
-
'Ensure your playwright.config.ts project has testDir set.
|
|
160
|
-
'Do NOT use rootDir as fallback - it causes path mismatch bugs.');
|
|
210
|
+
'Ensure your playwright.config.ts project has testDir set.');
|
|
161
211
|
}
|
|
162
|
-
return
|
|
212
|
+
return {
|
|
213
|
+
rootDir: config.rootDir,
|
|
214
|
+
testDir: project.testDir,
|
|
215
|
+
};
|
|
163
216
|
}
|
|
164
217
|
}
|
|
165
218
|
//# sourceMappingURL=extract-timing.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-timing.js","sourceRoot":"","sources":["../../src/commands/extract-timing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,OAAO;IAChD,MAAM,CAAU,WAAW,GACzB,iDAAiD,CAAC;IAEpD,MAAM,CAAU,QAAQ,GAAG;QACzB,6GAA6G;QAC7G,mGAAmG;KACpG,CAAC;IAEF,MAAM,CAAU,KAAK,GAAG;QACtB,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC;YAC1B,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,qCAAqC;YAClD,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC;YAC1B,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,kCAAkC;SAChD,CAAC;QACF,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;YACnB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,8BAA8B;YAC3C,OAAO,EAAE,CAAC;SACX,CAAC;QACF,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,yBAAyB;YACtC,OAAO,EAAE,SAAS;SACnB,CAAC;QACF,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC;YACrB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,qBAAqB;YAClC,OAAO,EAAE,KAAK;SACf,CAAC;KACH,CAAC;IAEF,
|
|
1
|
+
{"version":3,"file":"extract-timing.js","sourceRoot":"","sources":["../../src/commands/extract-timing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nsxbet/playwright-orchestrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Intelligent Playwright test distribution across CI shards using historical timing data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
],
|
|
13
13
|
"fixture": [
|
|
14
14
|
"./dist/fixture.d.ts"
|
|
15
|
+
],
|
|
16
|
+
"core": [
|
|
17
|
+
"./dist/core/index.d.ts"
|
|
15
18
|
]
|
|
16
19
|
}
|
|
17
20
|
},
|
|
@@ -30,6 +33,11 @@
|
|
|
30
33
|
"types": "./dist/fixture.d.ts",
|
|
31
34
|
"import": "./dist/fixture.js",
|
|
32
35
|
"require": "./dist/fixture.js"
|
|
36
|
+
},
|
|
37
|
+
"./core": {
|
|
38
|
+
"types": "./dist/core/index.d.ts",
|
|
39
|
+
"import": "./dist/core/index.js",
|
|
40
|
+
"require": "./dist/core/index.js"
|
|
33
41
|
}
|
|
34
42
|
},
|
|
35
43
|
"bin": {
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
export default class ListTests extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static examples: string[];
|
|
5
|
-
static flags: {
|
|
6
|
-
'test-dir': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
-
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
-
'output-format': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
-
'glob-pattern': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
-
'use-fallback': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
-
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
-
};
|
|
13
|
-
run(): Promise<void>;
|
|
14
|
-
private outputResult;
|
|
15
|
-
}
|
|
16
|
-
//# sourceMappingURL=list-tests.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list-tests.d.ts","sourceRoot":"","sources":["../../src/commands/list-tests.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAQ7C,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,OAAO;IAC5C,OAAgB,WAAW,SACiD;IAE5E,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,KAAK;;;;;;;MA6BnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAwC1B,OAAO,CAAC,YAAY;CA4BrB"}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
|
-
import { Command, Flags } from '@oclif/core';
|
|
3
|
-
import { discoverTests, discoverTestsFromFiles, groupTestsByFile, } from '../core/index.js';
|
|
4
|
-
export default class ListTests extends Command {
|
|
5
|
-
static description = 'Discover all tests in a Playwright project using --list or file parsing';
|
|
6
|
-
static examples = [
|
|
7
|
-
'<%= config.bin %> list-tests --test-dir ./src/test/e2e',
|
|
8
|
-
'<%= config.bin %> list-tests --test-dir ./e2e --project "Mobile Chrome" --output-format json',
|
|
9
|
-
];
|
|
10
|
-
static flags = {
|
|
11
|
-
'test-dir': Flags.string({
|
|
12
|
-
char: 'd',
|
|
13
|
-
description: 'Path to test directory',
|
|
14
|
-
required: true,
|
|
15
|
-
}),
|
|
16
|
-
project: Flags.string({
|
|
17
|
-
char: 'p',
|
|
18
|
-
description: 'Playwright project name',
|
|
19
|
-
}),
|
|
20
|
-
'output-format': Flags.string({
|
|
21
|
-
char: 'f',
|
|
22
|
-
description: 'Output format',
|
|
23
|
-
default: 'json',
|
|
24
|
-
options: ['json', 'text'],
|
|
25
|
-
}),
|
|
26
|
-
'glob-pattern': Flags.string({
|
|
27
|
-
description: 'Glob pattern for test files (used for fallback discovery)',
|
|
28
|
-
default: '**/*.spec.ts',
|
|
29
|
-
}),
|
|
30
|
-
'use-fallback': Flags.boolean({
|
|
31
|
-
description: 'Use file parsing instead of Playwright --list',
|
|
32
|
-
default: false,
|
|
33
|
-
}),
|
|
34
|
-
verbose: Flags.boolean({
|
|
35
|
-
char: 'v',
|
|
36
|
-
description: 'Show verbose output',
|
|
37
|
-
default: false,
|
|
38
|
-
}),
|
|
39
|
-
};
|
|
40
|
-
async run() {
|
|
41
|
-
const { flags } = await this.parse(ListTests);
|
|
42
|
-
const testDir = path.resolve(flags['test-dir']);
|
|
43
|
-
if (flags.verbose) {
|
|
44
|
-
this.log(`Discovering tests in ${testDir}...`);
|
|
45
|
-
}
|
|
46
|
-
let tests;
|
|
47
|
-
try {
|
|
48
|
-
if (flags['use-fallback']) {
|
|
49
|
-
// Use file parsing
|
|
50
|
-
tests = discoverTestsFromFiles(testDir, flags['glob-pattern']);
|
|
51
|
-
if (flags.verbose) {
|
|
52
|
-
this.log('Using file parsing for test discovery');
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// Use Playwright --list
|
|
57
|
-
tests = discoverTests(testDir, flags.project);
|
|
58
|
-
if (flags.verbose) {
|
|
59
|
-
this.log('Using Playwright --list for test discovery');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// Fallback to file parsing if Playwright --list fails
|
|
65
|
-
if (flags.verbose) {
|
|
66
|
-
this.warn('Playwright --list failed, falling back to file parsing');
|
|
67
|
-
}
|
|
68
|
-
tests = discoverTestsFromFiles(testDir, flags['glob-pattern']);
|
|
69
|
-
}
|
|
70
|
-
if (flags.verbose) {
|
|
71
|
-
this.log(`Discovered ${tests.length} tests`);
|
|
72
|
-
}
|
|
73
|
-
this.outputResult(tests, flags['output-format'], flags.verbose);
|
|
74
|
-
}
|
|
75
|
-
outputResult(tests, format, verbose = false) {
|
|
76
|
-
if (format === 'json') {
|
|
77
|
-
this.log(JSON.stringify(tests));
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
// Text format - group by file
|
|
81
|
-
const grouped = groupTestsByFile(tests);
|
|
82
|
-
this.log('\n=== Discovered Tests ===\n');
|
|
83
|
-
for (const [file, fileTests] of grouped) {
|
|
84
|
-
this.log(`${file}:`);
|
|
85
|
-
for (const test of fileTests) {
|
|
86
|
-
const titlePath = test.titlePath.join(' > ');
|
|
87
|
-
this.log(` - ${titlePath}`);
|
|
88
|
-
if (verbose) {
|
|
89
|
-
this.log(` ID: ${test.testId}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
this.log('');
|
|
93
|
-
}
|
|
94
|
-
this.log(`Total: ${tests.length} tests in ${grouped.size} files`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
//# sourceMappingURL=list-tests.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list-tests.js","sourceRoot":"","sources":["../../src/commands/list-tests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAEL,aAAa,EACb,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,OAAO;IAC5C,MAAM,CAAU,WAAW,GACzB,yEAAyE,CAAC;IAE5E,MAAM,CAAU,QAAQ,GAAG;QACzB,wDAAwD;QACxD,8FAA8F;KAC/F,CAAC;IAEF,MAAM,CAAU,KAAK,GAAG;QACtB,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC;YACvB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,wBAAwB;YACrC,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,yBAAyB;SACvC,CAAC;QACF,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC;YAC5B,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,eAAe;YAC5B,OAAO,EAAE,MAAM;YACf,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;SAC1B,CAAC;QACF,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC;YAC3B,WAAW,EAAE,2DAA2D;YACxE,OAAO,EAAE,cAAc;SACxB,CAAC;QACF,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC;YAC5B,WAAW,EAAE,+CAA+C;YAC5D,OAAO,EAAE,KAAK;SACf,CAAC;QACF,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC;YACrB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,qBAAqB;YAClC,OAAO,EAAE,KAAK;SACf,CAAC;KACH,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAE9C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;QAEhD,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,wBAAwB,OAAO,KAAK,CAAC,CAAC;QACjD,CAAC;QAED,IAAI,KAAuB,CAAC;QAE5B,IAAI,CAAC;YACH,IAAI,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;gBAC1B,mBAAmB;gBACnB,KAAK,GAAG,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;gBAC/D,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;oBAClB,IAAI,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;gBACpD,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,wBAAwB;gBACxB,KAAK,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC9C,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;oBAClB,IAAI,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sDAAsD;YACtD,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBAClB,IAAI,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;YACtE,CAAC;YACD,KAAK,GAAG,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,cAAc,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAClE,CAAC;IAEO,YAAY,CAClB,KAAuB,EACvB,MAAc,EACd,OAAO,GAAG,KAAK;QAEf,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,8BAA8B;YAC9B,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAExC,IAAI,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;YAEzC,KAAK,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;gBACrB,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;oBAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAC7C,IAAI,CAAC,GAAG,CAAC,OAAO,SAAS,EAAE,CAAC,CAAC;oBAC7B,IAAI,OAAO,EAAE,CAAC;wBACZ,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;oBACrC,CAAC;gBACH,CAAC;gBACD,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACf,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,UAAU,KAAK,CAAC,MAAM,aAAa,OAAO,CAAC,IAAI,QAAQ,CAAC,CAAC;QACpE,CAAC;IACH,CAAC"}
|