@nsxbet/playwright-orchestrator 1.0.0 → 2.0.0
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 +76 -133
- package/dist/commands/assign.d.ts +2 -0
- package/dist/commands/assign.d.ts.map +1 -1
- package/dist/commands/assign.js +44 -3
- package/dist/commands/assign.js.map +1 -1
- package/dist/commands/extract-timing.d.ts +0 -1
- package/dist/commands/extract-timing.d.ts.map +1 -1
- package/dist/commands/extract-timing.js +2 -32
- package/dist/commands/extract-timing.js.map +1 -1
- package/dist/core/ckk-algorithm.d.ts +1 -1
- package/dist/core/ckk-algorithm.d.ts.map +1 -1
- package/dist/core/ckk-algorithm.js +130 -36
- package/dist/core/ckk-algorithm.js.map +1 -1
- package/dist/core/estimate.d.ts +11 -0
- package/dist/core/estimate.d.ts.map +1 -1
- package/dist/core/estimate.js +43 -0
- package/dist/core/estimate.js.map +1 -1
- package/dist/core/test-discovery.d.ts +17 -3
- package/dist/core/test-discovery.d.ts.map +1 -1
- package/dist/core/test-discovery.js +39 -19
- package/dist/core/test-discovery.js.map +1 -1
- package/dist/core/test-id.d.ts +23 -67
- package/dist/core/test-id.d.ts.map +1 -1
- package/dist/core/test-id.js +29 -86
- package/dist/core/test-id.js.map +1 -1
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/package.json +1 -26
- package/dist/commands/filter-report.d.ts +0 -19
- package/dist/commands/filter-report.d.ts.map +0 -1
- package/dist/commands/filter-report.js +0 -103
- package/dist/commands/filter-report.js.map +0 -1
- package/dist/fixture.d.ts +0 -75
- package/dist/fixture.d.ts.map +0 -1
- package/dist/fixture.js +0 -148
- package/dist/fixture.js.map +0 -1
- package/dist/reporter.d.ts +0 -93
- package/dist/reporter.d.ts.map +0 -1
- package/dist/reporter.js +0 -343
- package/dist/reporter.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Intelligent Playwright test distribution across CI shards using historical timing data.
|
|
4
4
|
|
|
5
|
+
**Requires Playwright 1.56+** (uses the `--test-list` flag for pre-execution test filtering)
|
|
6
|
+
|
|
5
7
|
## The Problem
|
|
6
8
|
|
|
7
9
|
Default Playwright sharding (`--shard=N/M`) distributes tests by **file count**, not by duration. This creates significant imbalance:
|
|
@@ -27,48 +29,45 @@ Result: All shards finish at roughly the same time.
|
|
|
27
29
|
|
|
28
30
|
### Test-Level Distribution
|
|
29
31
|
|
|
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
|
|
32
|
+
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.
|
|
31
33
|
|
|
32
|
-
```
|
|
34
|
+
```text
|
|
33
35
|
File-level: login.spec.ts (50 tests, 10min) → all go to shard 1
|
|
34
36
|
Test-level: login.spec.ts tests → spread across shards 1-4
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
### Zero Runtime Footprint
|
|
40
|
+
|
|
41
|
+
The orchestrator uses Playwright's `--test-list` flag to filter tests **before execution**. This means:
|
|
42
|
+
|
|
43
|
+
- **No fixture** needed in your test setup
|
|
44
|
+
- **No reporter** needed in `playwright.config.ts`
|
|
45
|
+
- **No imports** from `@nsxbet/playwright-orchestrator` in your project code
|
|
46
|
+
- All Playwright reporters (HTML, JSON, blob) produce natively clean output
|
|
38
47
|
|
|
39
48
|
## Quick Start
|
|
40
49
|
|
|
41
50
|
```bash
|
|
42
|
-
# Install
|
|
43
|
-
bun add -D @nsxbet/playwright-orchestrator
|
|
44
|
-
|
|
45
51
|
# Generate test list
|
|
46
|
-
|
|
52
|
+
npx playwright test --list --reporter=json --project "chromium" > test-list.json
|
|
47
53
|
|
|
48
54
|
# Assign tests to shards
|
|
49
|
-
|
|
55
|
+
npx playwright-orchestrator assign \
|
|
50
56
|
--test-list ./test-list.json \
|
|
51
57
|
--timing-file ./timing-data.json \
|
|
52
58
|
--shards 4 > assignment.json
|
|
53
59
|
|
|
54
|
-
#
|
|
55
|
-
|
|
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
|
|
59
|
-
|
|
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"
|
|
60
|
+
# Run tests for a specific shard using --test-list
|
|
61
|
+
npx playwright test --test-list shard-1.txt --project "chromium"
|
|
62
62
|
|
|
63
63
|
# Extract timing from report after tests complete
|
|
64
|
-
|
|
64
|
+
npx playwright-orchestrator extract-timing \
|
|
65
65
|
--report-file ./playwright-report/results.json \
|
|
66
66
|
--output-file ./shard-1-timing.json \
|
|
67
|
-
--
|
|
68
|
-
--project "Mobile Chrome"
|
|
67
|
+
--project "chromium"
|
|
69
68
|
|
|
70
69
|
# Merge timing data from all shards
|
|
71
|
-
|
|
70
|
+
npx playwright-orchestrator merge-timing \
|
|
72
71
|
--existing ./timing-data.json \
|
|
73
72
|
--new ./shard-1-timing.json ./shard-2-timing.json \
|
|
74
73
|
--output ./timing-data.json
|
|
@@ -83,88 +82,52 @@ bunx playwright-orchestrator merge-timing \
|
|
|
83
82
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
84
83
|
│ │ │
|
|
85
84
|
▼ ▼ ▼
|
|
86
|
-
Run CKK once
|
|
87
|
-
Output all shards
|
|
85
|
+
Run CKK once --test-list filter Merge all shards
|
|
86
|
+
Output all shards Clean reports natively Update cache
|
|
88
87
|
```
|
|
89
88
|
|
|
90
|
-
1. **Orchestrate**: Run once, compute assignments for ALL shards
|
|
91
|
-
2. **Run Tests**: Each shard
|
|
92
|
-
3. **Merge**: Collect timing from all shards, update history with EMA
|
|
89
|
+
1. **Orchestrate**: Run once, compute assignments for ALL shards. Output includes `testListFiles` with ready-to-write Playwright test-list content per shard.
|
|
90
|
+
2. **Run Tests**: Each shard writes its test-list file and passes `--test-list <file>` to Playwright. Tests not in the list are removed from the suite tree before execution.
|
|
91
|
+
3. **Merge**: Collect timing from all shards, update history with EMA.
|
|
93
92
|
|
|
94
93
|
## Setup
|
|
95
94
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
### 1. Reporter (in `playwright.config.ts`)
|
|
95
|
+
No changes to `playwright.config.ts` are needed. Just use standard Playwright reporters:
|
|
99
96
|
|
|
100
97
|
```typescript
|
|
101
98
|
import { defineConfig } from "@playwright/test";
|
|
102
99
|
|
|
103
100
|
export default defineConfig({
|
|
104
101
|
reporter: [
|
|
105
|
-
["@nsxbet/playwright-orchestrator/reporter", {
|
|
106
|
-
filterJson: "playwright-report/results.json",
|
|
107
|
-
}],
|
|
108
102
|
["json", { outputFile: "playwright-report/results.json" }],
|
|
109
103
|
["html"],
|
|
110
104
|
],
|
|
111
105
|
});
|
|
112
106
|
```
|
|
113
107
|
|
|
114
|
-
The `filterJson` option (optional) tells the reporter to rewrite the JSON report after tests complete, removing specs not assigned to this shard. This keeps per-shard reports clean and prevents timing corruption from zero-duration orchestrator-skipped entries.
|
|
115
|
-
|
|
116
|
-
### 2. Test Fixture (in your test setup file)
|
|
117
|
-
|
|
118
|
-
Wrap your base test with `withOrchestratorFilter`:
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// e2e/setup.ts
|
|
122
|
-
import { test as base } from "@playwright/test";
|
|
123
|
-
import { withOrchestratorFilter } from "@nsxbet/playwright-orchestrator/fixture";
|
|
124
|
-
|
|
125
|
-
export const test = withOrchestratorFilter(base);
|
|
126
|
-
export { expect } from "@playwright/test";
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Then use this `test` in your spec files:
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// e2e/login.spec.ts
|
|
133
|
-
import { test, expect } from "./setup";
|
|
134
|
-
|
|
135
|
-
test("should login", async ({ page }) => {
|
|
136
|
-
// ...
|
|
137
|
-
});
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
The reporter and fixture work together:
|
|
141
|
-
- **Reporter**: Reads `ORCHESTRATOR_SHARD_FILE` env var to know which tests belong to this shard
|
|
142
|
-
- **Fixture**: Skips tests that don't belong to the current shard at runtime
|
|
143
|
-
|
|
144
108
|
## Local Testing
|
|
145
109
|
|
|
146
110
|
Reproduce CI shard behavior locally:
|
|
147
111
|
|
|
148
112
|
```bash
|
|
149
113
|
# 1. Generate test list (same as CI does)
|
|
150
|
-
npx playwright test --list --reporter=json --project="
|
|
114
|
+
npx playwright test --list --reporter=json --project="chromium" > test-list.json
|
|
151
115
|
|
|
152
|
-
# 2. Get shard distribution
|
|
153
|
-
playwright-orchestrator assign --test-list test-list.json --shards 4
|
|
116
|
+
# 2. Get shard distribution
|
|
117
|
+
playwright-orchestrator assign --test-list test-list.json --shards 4 --output-format json > result.json
|
|
154
118
|
|
|
155
|
-
# 3.
|
|
156
|
-
|
|
157
|
-
```
|
|
119
|
+
# 3. Write test-list file for shard 1 (the assign command includes testListFiles)
|
|
120
|
+
# Or use jq: jq -r '.testListFiles."1"' result.json > shard-1.txt
|
|
158
121
|
|
|
159
|
-
|
|
122
|
+
# 4. Run tests for that shard
|
|
123
|
+
npx playwright test --test-list shard-1.txt --project="chromium"
|
|
124
|
+
```
|
|
160
125
|
|
|
161
126
|
## GitHub Actions (External Repositories)
|
|
162
127
|
|
|
163
128
|
Use the orchestrator in your own repository. The recommended pattern runs orchestration **once** before matrix jobs.
|
|
164
129
|
|
|
165
|
-
**Important**: Use `npx playwright test --list --reporter=json` to generate the test list. This ensures accurate discovery of parameterized tests (`test.each`)
|
|
166
|
-
|
|
167
|
-
**Monorepo Note**: In monorepos, generate the test list from the same directory where tests run (where `playwright.config.ts` lives). See [Monorepo Usage](./docs/external-integration.md#monorepo-usage) for details.
|
|
130
|
+
**Important**: Use `npx playwright test --list --reporter=json` to generate the test list. This ensures accurate discovery of parameterized tests (`test.each`).
|
|
168
131
|
|
|
169
132
|
```yaml
|
|
170
133
|
jobs:
|
|
@@ -172,36 +135,26 @@ jobs:
|
|
|
172
135
|
orchestrate:
|
|
173
136
|
runs-on: ubuntu-24.04
|
|
174
137
|
outputs:
|
|
175
|
-
|
|
138
|
+
test-list-files: ${{ steps.orchestrate.outputs.test-list-files }}
|
|
176
139
|
steps:
|
|
177
140
|
- uses: actions/checkout@v4
|
|
178
|
-
|
|
179
|
-
- uses: actions/setup-node@v4
|
|
180
|
-
with:
|
|
181
|
-
node-version: 20
|
|
182
|
-
cache: npm
|
|
183
|
-
|
|
184
141
|
- run: npm ci
|
|
185
142
|
|
|
186
143
|
- uses: NSXBet/playwright-orchestrator/.github/actions/setup-orchestrator@v0
|
|
187
144
|
|
|
188
|
-
# YOU control cache location
|
|
189
145
|
- uses: actions/cache/restore@v4
|
|
190
146
|
with:
|
|
191
147
|
path: timing-data.json
|
|
192
148
|
key: playwright-timing-${{ github.ref_name }}
|
|
193
149
|
restore-keys: playwright-timing-
|
|
194
150
|
|
|
195
|
-
# IMPORTANT: Generate test list from the directory where tests run
|
|
196
|
-
# In monorepos, use working-directory to match where playwright.config.ts lives
|
|
197
151
|
- run: npx playwright test --list --reporter=json > test-list.json
|
|
198
152
|
|
|
199
|
-
# Action handles all orchestration logic
|
|
200
153
|
- uses: NSXBet/playwright-orchestrator/.github/actions/orchestrate@v0
|
|
201
154
|
id: orchestrate
|
|
202
155
|
with:
|
|
203
|
-
test-list: test-list.json
|
|
204
|
-
timing-file: timing-data.json
|
|
156
|
+
test-list: test-list.json
|
|
157
|
+
timing-file: timing-data.json
|
|
205
158
|
shards: 4
|
|
206
159
|
|
|
207
160
|
# Phase 2: Run tests (parallel matrix)
|
|
@@ -214,91 +167,81 @@ jobs:
|
|
|
214
167
|
shard: [1, 2, 3, 4]
|
|
215
168
|
steps:
|
|
216
169
|
- uses: actions/checkout@v4
|
|
170
|
+
- run: npm ci
|
|
171
|
+
- run: npx playwright install chromium --with-deps
|
|
217
172
|
|
|
218
|
-
# Action outputs shard-file path for reporter
|
|
219
173
|
- uses: NSXBet/playwright-orchestrator/.github/actions/get-shard@v0
|
|
220
174
|
id: shard
|
|
221
175
|
with:
|
|
222
|
-
|
|
176
|
+
test-list-files: ${{ needs.orchestrate.outputs.test-list-files }}
|
|
223
177
|
shard-index: ${{ matrix.shard }}
|
|
224
178
|
shards: 4
|
|
225
179
|
|
|
226
|
-
#
|
|
227
|
-
- run:
|
|
228
|
-
|
|
229
|
-
|
|
180
|
+
# Use --test-list for clean, pre-execution filtering
|
|
181
|
+
- run: |
|
|
182
|
+
TEST_LIST_FILE="${{ steps.shard.outputs.test-list-file }}"
|
|
183
|
+
if [ -n "$TEST_LIST_FILE" ] && [ -f "$TEST_LIST_FILE" ]; then
|
|
184
|
+
npx playwright test --test-list "$TEST_LIST_FILE"
|
|
185
|
+
else
|
|
186
|
+
npx playwright test ${{ steps.shard.outputs.fallback-args }}
|
|
187
|
+
fi
|
|
230
188
|
```
|
|
231
189
|
|
|
232
190
|
See [docs/external-integration.md](./docs/external-integration.md) for complete workflow with timing data persistence.
|
|
233
191
|
|
|
234
192
|
## CLI Commands
|
|
235
193
|
|
|
236
|
-
| Command | Description
|
|
237
|
-
| ---------------- |
|
|
238
|
-
| `assign` | Distribute tests across shards
|
|
239
|
-
| `extract-timing` | Extract timing from Playwright report
|
|
240
|
-
| `merge-timing` | Merge timing data with EMA smoothing
|
|
241
|
-
| `filter-report` | Remove orchestrator-skipped tests from merged JSON report |
|
|
194
|
+
| Command | Description |
|
|
195
|
+
| ---------------- | ------------------------------------- |
|
|
196
|
+
| `assign` | Distribute tests across shards |
|
|
197
|
+
| `extract-timing` | Extract timing from Playwright report |
|
|
198
|
+
| `merge-timing` | Merge timing data with EMA smoothing |
|
|
242
199
|
|
|
243
200
|
Run `playwright-orchestrator <command> --help` for details.
|
|
244
201
|
|
|
245
|
-
|
|
202
|
+
### File Affinity
|
|
203
|
+
|
|
204
|
+
By default, the `assign` command keeps tests from the same file on the same shard when the time difference is small. This reduces redundant page/context initialization costs.
|
|
246
205
|
|
|
247
206
|
```bash
|
|
248
|
-
#
|
|
249
|
-
|
|
207
|
+
# Disable file affinity
|
|
208
|
+
playwright-orchestrator assign --test-list test-list.json --shards 4 --no-file-affinity
|
|
250
209
|
|
|
251
|
-
#
|
|
210
|
+
# Override penalty (in ms)
|
|
211
|
+
playwright-orchestrator assign --test-list test-list.json --shards 4 --file-affinity-penalty 20000
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Development
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
make install # Install dependencies
|
|
252
218
|
make lint # Biome linter
|
|
253
219
|
make typecheck # TypeScript
|
|
254
220
|
make test # Bun test
|
|
255
|
-
|
|
256
|
-
#
|
|
257
|
-
make build
|
|
258
|
-
|
|
259
|
-
# Run CI locally (requires Act)
|
|
260
|
-
make act-test
|
|
221
|
+
make build # Build
|
|
222
|
+
make act-test # Run CI locally (requires Act)
|
|
261
223
|
```
|
|
262
224
|
|
|
263
225
|
## E2E Testing
|
|
264
226
|
|
|
265
|
-
The repository includes comprehensive E2E tests that simulate real-world monorepo usage:
|
|
266
|
-
|
|
267
227
|
```bash
|
|
268
|
-
# Run E2E monorepo workflow with Act
|
|
269
|
-
make act-e2e-monorepo
|
|
228
|
+
make act-e2e-monorepo # Run E2E monorepo workflow with Act
|
|
270
229
|
```
|
|
271
230
|
|
|
272
|
-
The E2E workflow
|
|
231
|
+
The E2E workflow tests the complete orchestration cycle:
|
|
273
232
|
|
|
274
|
-
1. **setup**: Build package, create tarball
|
|
275
|
-
2. **orchestrate**: Use real `orchestrate` action
|
|
276
|
-
3. **e2e-tests** (matrix): Use `get-shard` and `extract-timing` actions
|
|
277
|
-
4. **merge**: Use `merge-timing` action
|
|
278
|
-
|
|
279
|
-
**Note**: Publish validation is handled separately in CI via the `test-publish` job (Verdaccio).
|
|
280
|
-
|
|
281
|
-
Test scenarios covered in `examples/monorepo/`:
|
|
282
|
-
|
|
283
|
-
- Path normalization (orchestrate from root, run from subdirectory)
|
|
284
|
-
- Parameterized tests (`test.each` patterns)
|
|
285
|
-
- Nested describe blocks (4+ levels deep)
|
|
286
|
-
- Special characters in test names (Unicode, brackets)
|
|
287
|
-
- `::` separator conflicts in test titles
|
|
288
|
-
- Skip patterns (`skip`, `fixme`, `slow`, tags)
|
|
289
|
-
- Deep subdirectory paths
|
|
290
|
-
|
|
291
|
-
See [AGENTS.md](./AGENTS.md) for AI assistant instructions.
|
|
233
|
+
1. **setup**: Build package, create tarball
|
|
234
|
+
2. **orchestrate**: Use real `orchestrate` action
|
|
235
|
+
3. **e2e-tests** (matrix): Use `get-shard` with `--test-list` and `extract-timing` actions
|
|
236
|
+
4. **merge**: Use `merge-timing` action
|
|
292
237
|
|
|
293
238
|
## Cache Strategy
|
|
294
239
|
|
|
295
|
-
GitHub Actions cache is branch-scoped
|
|
240
|
+
GitHub Actions cache is branch-scoped. We recommend a **promote-on-merge** pattern:
|
|
296
241
|
|
|
297
242
|
1. Each PR branch saves to its own cache key
|
|
298
243
|
2. PRs restore from their own cache, falling back to main
|
|
299
|
-
3. When a PR is merged,
|
|
300
|
-
|
|
301
|
-
This avoids race conditions between concurrent PRs while ensuring main always has the latest timing data.
|
|
244
|
+
3. When a PR is merged, promote the PR's cache to main
|
|
302
245
|
|
|
303
246
|
See [Cache Strategy for PRs](./docs/external-integration.md#cache-strategy-for-prs) for implementation details.
|
|
304
247
|
|
|
@@ -10,6 +10,8 @@ export default class Assign extends Command {
|
|
|
10
10
|
'output-format': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
12
|
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'file-affinity': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
'file-affinity-penalty': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
15
|
};
|
|
14
16
|
run(): Promise<void>;
|
|
15
17
|
private outputResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assign.d.ts","sourceRoot":"","sources":["../../src/commands/assign.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"assign.d.ts","sourceRoot":"","sources":["../../src/commands/assign.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAe7C,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,OAAO;IACzC,OAAgB,WAAW,SACgC;IAE3D,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,KAAK;;;;;;;;;;MA4CnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAwI1B,OAAO,CAAC,YAAY;IAkCpB,OAAO,CAAC,cAAc;CAQvB"}
|
package/dist/commands/assign.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import { Command, Flags } from '@oclif/core';
|
|
3
|
-
import { assignWithCKK, DEFAULT_CKK_TIMEOUT, getTestDurations,
|
|
3
|
+
import { assignWithCKK, calculateFileAffinityPenalty, DEFAULT_CKK_TIMEOUT, getTestDurations, loadTestListWithConfig, loadTimingData, toTestListFile, } from '../core/index.js';
|
|
4
4
|
export default class Assign extends Command {
|
|
5
5
|
static description = 'Assign tests to shards based on historical timing data';
|
|
6
6
|
static examples = [
|
|
@@ -40,13 +40,31 @@ export default class Assign extends Command {
|
|
|
40
40
|
description: 'CKK algorithm timeout in milliseconds',
|
|
41
41
|
default: DEFAULT_CKK_TIMEOUT,
|
|
42
42
|
}),
|
|
43
|
+
'file-affinity': Flags.boolean({
|
|
44
|
+
description: 'Enable file affinity to keep same-file tests on the same shard',
|
|
45
|
+
default: true,
|
|
46
|
+
allowNo: true,
|
|
47
|
+
}),
|
|
48
|
+
'file-affinity-penalty': Flags.integer({
|
|
49
|
+
description: 'File affinity penalty in milliseconds (overrides auto-calculation)',
|
|
50
|
+
}),
|
|
43
51
|
};
|
|
44
52
|
async run() {
|
|
45
53
|
const { flags } = await this.parse(Assign);
|
|
46
54
|
const testListPath = path.resolve(flags['test-list']);
|
|
47
|
-
const tests =
|
|
55
|
+
const { tests, rootDir, testDir } = loadTestListWithConfig(testListPath, flags.project);
|
|
56
|
+
if (!testDir) {
|
|
57
|
+
throw new Error('[Orchestrator] project.testDir is missing in test-list.json. ' +
|
|
58
|
+
'Regenerate with `npx playwright test --list --reporter=json`.');
|
|
59
|
+
}
|
|
60
|
+
const testDirPrefix = rootDir
|
|
61
|
+
? path.relative(rootDir, testDir).replace(/\\/g, '/')
|
|
62
|
+
: '';
|
|
48
63
|
if (flags.verbose) {
|
|
49
64
|
this.log(`Loaded ${tests.length} tests from ${testListPath}`);
|
|
65
|
+
if (testDirPrefix) {
|
|
66
|
+
this.log(`testDir prefix for --test-list paths: ${testDirPrefix}`);
|
|
67
|
+
}
|
|
50
68
|
}
|
|
51
69
|
if (tests.length === 0) {
|
|
52
70
|
this.warn(`No tests found in ${testListPath}`);
|
|
@@ -56,6 +74,7 @@ export default class Assign extends Command {
|
|
|
56
74
|
totalTests: 0,
|
|
57
75
|
estimatedTests: [],
|
|
58
76
|
isOptimal: true,
|
|
77
|
+
testListFiles: Object.fromEntries(Array.from({ length: flags.shards }, (_, i) => [i + 1, ''])),
|
|
59
78
|
}, flags['output-format']);
|
|
60
79
|
return;
|
|
61
80
|
}
|
|
@@ -76,7 +95,16 @@ export default class Assign extends Command {
|
|
|
76
95
|
duration: t.duration,
|
|
77
96
|
estimated: t.estimated,
|
|
78
97
|
}));
|
|
79
|
-
|
|
98
|
+
let fileAffinityPenalty = 0;
|
|
99
|
+
if (flags['file-affinity']) {
|
|
100
|
+
fileAffinityPenalty =
|
|
101
|
+
flags['file-affinity-penalty'] ??
|
|
102
|
+
calculateFileAffinityPenalty(timingData);
|
|
103
|
+
if (flags.verbose) {
|
|
104
|
+
this.log(`File affinity penalty: ${this.formatDuration(fileAffinityPenalty)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const ckkResult = assignWithCKK(testInputs, flags.shards, flags.timeout, fileAffinityPenalty);
|
|
80
108
|
if (flags.verbose) {
|
|
81
109
|
this.log(`Assignment ${ckkResult.isOptimal ? 'optimal' : 'near-optimal (LPT fallback)'}`);
|
|
82
110
|
this.log(`Makespan: ${this.formatDuration(ckkResult.makespan)}`);
|
|
@@ -85,12 +113,25 @@ export default class Assign extends Command {
|
|
|
85
113
|
for (const assignment of ckkResult.assignments) {
|
|
86
114
|
shardTests[assignment.shardIndex] = assignment.tests;
|
|
87
115
|
}
|
|
116
|
+
const testMap = new Map(tests.map((t) => [t.testId, t]));
|
|
117
|
+
const testListFiles = {};
|
|
118
|
+
for (const [shardIndex, testIds] of Object.entries(shardTests)) {
|
|
119
|
+
const entries = testIds.map((id) => {
|
|
120
|
+
const test = testMap.get(id);
|
|
121
|
+
if (!test) {
|
|
122
|
+
throw new Error(`[Orchestrator] Test not found in discovery: ${id}`);
|
|
123
|
+
}
|
|
124
|
+
return { file: test.file, titlePath: test.titlePath };
|
|
125
|
+
});
|
|
126
|
+
testListFiles[Number(shardIndex)] = toTestListFile(entries, testDirPrefix);
|
|
127
|
+
}
|
|
88
128
|
const result = {
|
|
89
129
|
shards: shardTests,
|
|
90
130
|
expectedDurations: Object.fromEntries(ckkResult.assignments.map((a) => [a.shardIndex, a.expectedDuration])),
|
|
91
131
|
totalTests: tests.length,
|
|
92
132
|
estimatedTests,
|
|
93
133
|
isOptimal: ckkResult.isOptimal,
|
|
134
|
+
testListFiles,
|
|
94
135
|
};
|
|
95
136
|
this.outputResult(result, flags['output-format'], flags.verbose);
|
|
96
137
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assign.js","sourceRoot":"","sources":["../../src/commands/assign.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,aAAa,EACb,mBAAmB,EAEnB,gBAAgB,EAChB,
|
|
1
|
+
{"version":3,"file":"assign.js","sourceRoot":"","sources":["../../src/commands/assign.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,6 @@ export default class ExtractTiming extends Command {
|
|
|
7
7
|
'output-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
8
|
shard: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
-
'shard-file': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
10
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
11
|
};
|
|
13
12
|
private testDir;
|
|
@@ -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
|
|
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"}
|
|
@@ -5,7 +5,7 @@ import { buildTestId } from '../core/index.js';
|
|
|
5
5
|
export default class ExtractTiming extends Command {
|
|
6
6
|
static description = 'Extract timing data from Playwright JSON report';
|
|
7
7
|
static examples = [
|
|
8
|
-
'<%= config.bin %> extract-timing --report-file ./playwright-report/results.json --output-file ./timing.json',
|
|
8
|
+
'<%= config.bin %> extract-timing --report-file ./playwright-report/results.json --output-file ./timing.json --project "chromium"',
|
|
9
9
|
'<%= config.bin %> extract-timing --report-file ./results.json --shard 1 --project "Mobile Chrome"',
|
|
10
10
|
];
|
|
11
11
|
static flags = {
|
|
@@ -28,10 +28,6 @@ export default class ExtractTiming extends Command {
|
|
|
28
28
|
description: 'Playwright project name',
|
|
29
29
|
required: true,
|
|
30
30
|
}),
|
|
31
|
-
'shard-file': Flags.string({
|
|
32
|
-
description: 'Path to shard JSON file — only extract timing for tests in this file',
|
|
33
|
-
required: true,
|
|
34
|
-
}),
|
|
35
31
|
verbose: Flags.boolean({
|
|
36
32
|
char: 'v',
|
|
37
33
|
description: 'Show verbose output',
|
|
@@ -62,34 +58,8 @@ export default class ExtractTiming extends Command {
|
|
|
62
58
|
this.log(`Using rootDir: ${this.rootDir}`);
|
|
63
59
|
this.log(`Using testDir: ${this.testDir}`);
|
|
64
60
|
}
|
|
65
|
-
// Extract test-level durations
|
|
61
|
+
// Extract test-level durations from the natively clean report
|
|
66
62
|
const testDurations = this.extractTestDurations(report);
|
|
67
|
-
// Filter by shard file (required)
|
|
68
|
-
const shardFilePath = path.resolve(flags['shard-file']);
|
|
69
|
-
if (!fs.existsSync(shardFilePath)) {
|
|
70
|
-
this.error(`Shard file not found: ${shardFilePath}`);
|
|
71
|
-
}
|
|
72
|
-
let shardIds;
|
|
73
|
-
try {
|
|
74
|
-
shardIds = JSON.parse(fs.readFileSync(shardFilePath, 'utf-8'));
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
this.error(`Failed to parse shard file: ${shardFilePath}`);
|
|
78
|
-
}
|
|
79
|
-
if (!Array.isArray(shardIds)) {
|
|
80
|
-
this.error(`Shard file must contain a JSON array: ${shardFilePath}`);
|
|
81
|
-
}
|
|
82
|
-
const allowed = new Set(shardIds);
|
|
83
|
-
const beforeCount = Object.keys(testDurations).length;
|
|
84
|
-
for (const testId of Object.keys(testDurations)) {
|
|
85
|
-
if (!allowed.has(testId)) {
|
|
86
|
-
delete testDurations[testId];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (flags.verbose) {
|
|
90
|
-
const afterCount = Object.keys(testDurations).length;
|
|
91
|
-
this.log(`Filtered by shard file: ${beforeCount} → ${afterCount} tests (removed ${beforeCount - afterCount})`);
|
|
92
|
-
}
|
|
93
63
|
if (flags.verbose) {
|
|
94
64
|
this.log(`Extracted timing for ${Object.keys(testDurations).length} tests`);
|
|
95
65
|
}
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"extract-timing.js","sourceRoot":"","sources":["../../src/commands/extract-timing.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,7 +29,7 @@ export interface CKKResult {
|
|
|
29
29
|
* @param timeoutMs - Maximum time to search for optimal solution
|
|
30
30
|
* @returns Optimal (or near-optimal) shard assignments
|
|
31
31
|
*/
|
|
32
|
-
export declare function assignWithCKK(tests: TestWithDuration[], numShards: number, timeoutMs?: number): CKKResult;
|
|
32
|
+
export declare function assignWithCKK(tests: TestWithDuration[], numShards: number, timeoutMs?: number, fileAffinityPenalty?: number): CKKResult;
|
|
33
33
|
/**
|
|
34
34
|
* Calculate theoretical lower bound for makespan
|
|
35
35
|
* This is the best possible makespan if we could partition perfectly
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ckk-algorithm.d.ts","sourceRoot":"","sources":["../../src/core/ckk-algorithm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,wBAAwB;IACxB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,gBAAgB,EAAE,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAA4B,
|
|
1
|
+
{"version":3,"file":"ckk-algorithm.d.ts","sourceRoot":"","sources":["../../src/core/ckk-algorithm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,wBAAwB;IACxB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,gBAAgB,EAAE,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAA4B,EACvC,mBAAmB,SAAI,GACtB,SAAS,CA8MX;AAmHD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EAAE,EACzB,SAAS,EAAE,MAAM,GAChB,MAAM,CAQR"}
|