@empiricalrun/test-run 0.13.2 → 0.14.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/CHANGELOG.md +18 -0
- package/dist/bin/commands/optimize-shards.d.ts.map +1 -1
- package/dist/bin/commands/optimize-shards.js +166 -22
- package/dist/cmd.d.ts +2 -0
- package/dist/cmd.d.ts.map +1 -0
- package/dist/cmd.js +5 -0
- package/dist/failed-test-list.d.ts +0 -16
- package/dist/failed-test-list.d.ts.map +1 -1
- package/dist/failed-test-list.js +4 -15
- package/dist/lib/merge-reports/index.d.ts.map +1 -1
- package/dist/lib/merge-reports/index.js +24 -4
- package/package.json +7 -3
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @empiricalrun/test-run
|
|
2
2
|
|
|
3
|
+
## 0.14.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [617e9f8]
|
|
8
|
+
- @empiricalrun/reporter@0.27.0
|
|
9
|
+
|
|
10
|
+
## 0.14.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- 0dfc150: fix: exports config between packages
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [0dfc150]
|
|
19
|
+
- @empiricalrun/reporter@0.26.0
|
|
20
|
+
|
|
3
21
|
## 0.13.2
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -1 +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;
|
|
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;AAujBzC,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,QA2T7D"}
|
|
@@ -265,6 +265,109 @@ function formatDuration(ms, workers) {
|
|
|
265
265
|
const seconds = totalSeconds % 60;
|
|
266
266
|
return `${minutes}m ${seconds}s`;
|
|
267
267
|
}
|
|
268
|
+
function buildProjectDurations(tests) {
|
|
269
|
+
const projectDurations = new Map();
|
|
270
|
+
for (const t of tests) {
|
|
271
|
+
const prev = projectDurations.get(t.projectName) ?? 0;
|
|
272
|
+
projectDurations.set(t.projectName, prev + t.estimatedDuration);
|
|
273
|
+
}
|
|
274
|
+
return projectDurations;
|
|
275
|
+
}
|
|
276
|
+
function moveTest(test, from, to) {
|
|
277
|
+
const idx = from.tests.findIndex((t) => t.id === test.id && t.projectName === test.projectName);
|
|
278
|
+
if (idx !== -1)
|
|
279
|
+
from.tests.splice(idx, 1);
|
|
280
|
+
to.tests.push(test);
|
|
281
|
+
from.totalDuration -= test.estimatedDuration;
|
|
282
|
+
to.totalDuration += test.estimatedDuration;
|
|
283
|
+
const project = test.projectName;
|
|
284
|
+
const fromDur = (from.projectDurations.get(project) ?? 0) - test.estimatedDuration;
|
|
285
|
+
if (fromDur <= 0)
|
|
286
|
+
from.projectDurations.delete(project);
|
|
287
|
+
else
|
|
288
|
+
from.projectDurations.set(project, fromDur);
|
|
289
|
+
const toDur = (to.projectDurations.get(project) ?? 0) + test.estimatedDuration;
|
|
290
|
+
to.projectDurations.set(project, toDur);
|
|
291
|
+
}
|
|
292
|
+
function isParallelTest(test) {
|
|
293
|
+
return test.groupKey.includes(":test:");
|
|
294
|
+
}
|
|
295
|
+
function buildMoveCandidates(tests) {
|
|
296
|
+
const parallelTests = [];
|
|
297
|
+
const groupedTests = new Map();
|
|
298
|
+
for (const t of tests) {
|
|
299
|
+
if (isParallelTest(t)) {
|
|
300
|
+
parallelTests.push({ tests: [t], totalDuration: t.estimatedDuration });
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
const group = groupedTests.get(t.groupKey) ?? [];
|
|
304
|
+
group.push(t);
|
|
305
|
+
groupedTests.set(t.groupKey, group);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const serialGroups = [...groupedTests.values()].map((group) => ({
|
|
309
|
+
tests: group,
|
|
310
|
+
totalDuration: group.reduce((sum, t) => sum + t.estimatedDuration, 0),
|
|
311
|
+
}));
|
|
312
|
+
return [...parallelTests, ...serialGroups];
|
|
313
|
+
}
|
|
314
|
+
function tryMoveOneGroup(heavyShard, allShards, currentMakespan) {
|
|
315
|
+
const destCandidates = [...allShards]
|
|
316
|
+
.filter((s) => s.index !== heavyShard.index)
|
|
317
|
+
.sort((a, b) => a.totalDuration - b.totalDuration);
|
|
318
|
+
const moveCandidates = buildMoveCandidates(heavyShard.tests);
|
|
319
|
+
moveCandidates.sort((a, b) => b.totalDuration - a.totalDuration);
|
|
320
|
+
for (const dest of destCandidates) {
|
|
321
|
+
const uninvolvedShardsMax = Math.max(...allShards
|
|
322
|
+
.filter((s) => s.index !== heavyShard.index && s.index !== dest.index)
|
|
323
|
+
.map((s) => s.totalDuration), 0);
|
|
324
|
+
const eligibleCandidates = moveCandidates.filter((c) => c.tests.every((t) => dest.projectDurations.has(t.projectName)));
|
|
325
|
+
if (eligibleCandidates.length === 0)
|
|
326
|
+
continue;
|
|
327
|
+
for (const candidate of eligibleCandidates) {
|
|
328
|
+
const newHeavyDuration = heavyShard.totalDuration - candidate.totalDuration;
|
|
329
|
+
const newDestDuration = dest.totalDuration + candidate.totalDuration;
|
|
330
|
+
const newMakespan = Math.max(newHeavyDuration, newDestDuration, uninvolvedShardsMax);
|
|
331
|
+
if (newMakespan < currentMakespan) {
|
|
332
|
+
for (const t of candidate.tests) {
|
|
333
|
+
moveTest(t, heavyShard, dest);
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
function rebalanceShardsIncrementally(shards, options) {
|
|
342
|
+
const maxMoves = options?.maxMoves ?? 1000;
|
|
343
|
+
const minImbalanceMs = options?.minImbalanceMs ?? 0;
|
|
344
|
+
const getMakespan = () => Math.max(...shards.map((s) => s.totalDuration));
|
|
345
|
+
const getImbalance = () => {
|
|
346
|
+
const durations = shards.map((s) => s.totalDuration);
|
|
347
|
+
return Math.max(...durations) - Math.min(...durations);
|
|
348
|
+
};
|
|
349
|
+
let moves = 0;
|
|
350
|
+
while (moves < maxMoves) {
|
|
351
|
+
const imbalance = getImbalance();
|
|
352
|
+
if (imbalance <= minImbalanceMs)
|
|
353
|
+
break;
|
|
354
|
+
const currentMakespan = getMakespan();
|
|
355
|
+
const shardsByDuration = [...shards].sort((a, b) => b.totalDuration - a.totalDuration);
|
|
356
|
+
let moved = false;
|
|
357
|
+
for (const heavyShard of shardsByDuration) {
|
|
358
|
+
if (heavyShard.totalDuration < currentMakespan * 0.9)
|
|
359
|
+
break;
|
|
360
|
+
if (tryMoveOneGroup(heavyShard, shards, currentMakespan)) {
|
|
361
|
+
moved = true;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!moved)
|
|
366
|
+
break;
|
|
367
|
+
moves++;
|
|
368
|
+
}
|
|
369
|
+
return shards;
|
|
370
|
+
}
|
|
268
371
|
function registerOptimizeShardsCommand(program) {
|
|
269
372
|
program
|
|
270
373
|
.command("optimize-shards")
|
|
@@ -276,8 +379,9 @@ function registerOptimizeShardsCommand(program) {
|
|
|
276
379
|
.option("--default-duration <ms>", "Default duration for tests without history", "30000")
|
|
277
380
|
.option("--output-dir <dir>", "Output directory for test-list files", "./shards")
|
|
278
381
|
.option("--cache-key <key>", "Cache key for duration data (e.g., commit SHA). Ensures all workers see same data.")
|
|
382
|
+
.option("--strategy <strategy>", "Optimization strategy: 'lpt' (bin packing from scratch) or 'incremental' (improve Playwright defaults)", "incremental")
|
|
279
383
|
.action(async (options) => {
|
|
280
|
-
const { shards: shardCountStr, dashboardUrl, includeRetries, outputDir, cacheKey, } = options;
|
|
384
|
+
const { shards: shardCountStr, dashboardUrl, includeRetries, outputDir, cacheKey, strategy, } = options;
|
|
281
385
|
const workers = parseInt(options.workers, 10);
|
|
282
386
|
const shardCount = parseInt(shardCountStr, 10);
|
|
283
387
|
const defaultDuration = parseInt(options.defaultDuration, 10);
|
|
@@ -314,33 +418,66 @@ function registerOptimizeShardsCommand(program) {
|
|
|
314
418
|
const hooksCount = [...specToGroupKey.values()].filter((k) => k.startsWith("hooks:")).length;
|
|
315
419
|
console.log(`Test grouping: ${parallelCount} parallel, ${serialCount} serial/default, ${hooksCount} with hooks`);
|
|
316
420
|
const allTests = flattenedSpecsToTestInfos(allSpecs, historyResponse, includeRetries, defaultDuration, specToGroupKey);
|
|
421
|
+
const validStrategies = ["lpt", "incremental"];
|
|
422
|
+
if (!validStrategies.includes(strategy)) {
|
|
423
|
+
console.error(`Invalid strategy: ${strategy}. Must be one of: ${validStrategies.join(", ")}`);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
317
426
|
console.log(`\n--- Playwright Default Sharding ---`);
|
|
318
|
-
const
|
|
427
|
+
const defaultShards = [];
|
|
319
428
|
for (let i = 1; i <= shardCount; i++) {
|
|
320
429
|
const shardReport = getPlaywrightTestList(`${i}/${shardCount}`);
|
|
321
430
|
const shardSpecs = (0, reporter_1.getFlattenedTestList)(shardReport.suites);
|
|
322
431
|
const shardTests = flattenedSpecsToTestInfos(shardSpecs, historyResponse, includeRetries, defaultDuration);
|
|
323
432
|
const totalDuration = shardTests.reduce((sum, t) => sum + t.estimatedDuration, 0);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
tests: shardTests
|
|
327
|
-
|
|
433
|
+
defaultShards.push({
|
|
434
|
+
index: i,
|
|
435
|
+
tests: shardTests,
|
|
436
|
+
totalDuration,
|
|
437
|
+
projectDurations: buildProjectDurations(shardTests),
|
|
328
438
|
});
|
|
329
439
|
console.log(` Shard ${i}/${shardCount}: ${shardTests.length} tests, ~${formatDuration(totalDuration, workers)}`);
|
|
330
440
|
}
|
|
331
|
-
const playwrightMaxDuration = Math.max(...
|
|
332
|
-
const playwrightMinDuration = Math.min(...
|
|
441
|
+
const playwrightMaxDuration = Math.max(...defaultShards.map((s) => s.totalDuration));
|
|
442
|
+
const playwrightMinDuration = Math.min(...defaultShards.map((s) => s.totalDuration));
|
|
333
443
|
console.log(` Makespan (max shard): ${formatDuration(playwrightMaxDuration, workers)}`);
|
|
334
444
|
console.log(` Imbalance: ${formatDuration(playwrightMaxDuration - playwrightMinDuration, workers)}`);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
445
|
+
let finalShards;
|
|
446
|
+
let optimizedMaxDuration;
|
|
447
|
+
let optimizedMinDuration;
|
|
448
|
+
if (strategy === "lpt") {
|
|
449
|
+
console.log(`\n--- Optimized Bin Packing (LPT) ---`);
|
|
450
|
+
const testGroups = buildTestGroups(allTests);
|
|
451
|
+
console.log(` Built ${testGroups.length} test groups from ${allTests.length} tests`);
|
|
452
|
+
const lptShards = packGroupsIntoShards(testGroups, shardCount);
|
|
453
|
+
finalShards = lptShards.map((s) => ({
|
|
454
|
+
index: s.index,
|
|
455
|
+
tests: s.tests,
|
|
456
|
+
totalDuration: s.totalDuration,
|
|
457
|
+
projectDurations: buildProjectDurations(s.tests),
|
|
458
|
+
}));
|
|
459
|
+
for (const shard of finalShards) {
|
|
460
|
+
console.log(` Shard ${shard.index}/${shardCount}: ${shard.tests.length} tests, ~${formatDuration(shard.totalDuration, workers)}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
console.log(`\n--- Incremental Project-aware Rebalancing ---`);
|
|
465
|
+
const shardsToRebalance = defaultShards.map((s) => ({
|
|
466
|
+
index: s.index,
|
|
467
|
+
tests: [...s.tests],
|
|
468
|
+
totalDuration: s.totalDuration,
|
|
469
|
+
projectDurations: new Map(s.projectDurations),
|
|
470
|
+
}));
|
|
471
|
+
finalShards = rebalanceShardsIncrementally(shardsToRebalance, {
|
|
472
|
+
minImbalanceMs: workers * 5000,
|
|
473
|
+
maxMoves: 1000,
|
|
474
|
+
});
|
|
475
|
+
for (const shard of finalShards) {
|
|
476
|
+
console.log(` Shard ${shard.index}/${shardCount}: ${shard.tests.length} tests, ~${formatDuration(shard.totalDuration, workers)}`);
|
|
477
|
+
}
|
|
341
478
|
}
|
|
342
|
-
|
|
343
|
-
|
|
479
|
+
optimizedMaxDuration = Math.max(...finalShards.map((s) => s.totalDuration));
|
|
480
|
+
optimizedMinDuration = Math.min(...finalShards.map((s) => s.totalDuration));
|
|
344
481
|
console.log(` Makespan (max shard): ${formatDuration(optimizedMaxDuration, workers)}`);
|
|
345
482
|
console.log(` Imbalance: ${formatDuration(optimizedMaxDuration - optimizedMinDuration, workers)}`);
|
|
346
483
|
const improvement = ((playwrightMaxDuration - optimizedMaxDuration) /
|
|
@@ -354,18 +491,24 @@ function registerOptimizeShardsCommand(program) {
|
|
|
354
491
|
fs_1.default.mkdirSync(absoluteOutputDir, { recursive: true });
|
|
355
492
|
}
|
|
356
493
|
console.log(`\n--- Writing Test List Files ---`);
|
|
357
|
-
for (const shard of
|
|
358
|
-
const
|
|
494
|
+
for (const shard of finalShards) {
|
|
495
|
+
const shardForContent = {
|
|
496
|
+
index: shard.index,
|
|
497
|
+
groups: [],
|
|
498
|
+
tests: shard.tests,
|
|
499
|
+
totalDuration: shard.totalDuration,
|
|
500
|
+
};
|
|
501
|
+
const content = generateTestListContent(shardForContent, workers);
|
|
359
502
|
const filePath = path_1.default.join(absoluteOutputDir, `shard-${shard.index}.txt`);
|
|
360
503
|
fs_1.default.writeFileSync(filePath, content, "utf-8");
|
|
361
504
|
console.log(` Written: ${filePath}`);
|
|
362
505
|
}
|
|
363
|
-
const comparison =
|
|
364
|
-
const pw =
|
|
506
|
+
const comparison = finalShards.map((os) => {
|
|
507
|
+
const pw = defaultShards.find((p) => p.index === os.index);
|
|
365
508
|
return {
|
|
366
509
|
shardIndex: os.index,
|
|
367
|
-
playwrightTestCount: pw.tests,
|
|
368
|
-
playwrightEstimatedMs: Math.round(pw.
|
|
510
|
+
playwrightTestCount: pw.tests.length,
|
|
511
|
+
playwrightEstimatedMs: Math.round(pw.totalDuration / workers),
|
|
369
512
|
optimizedTestCount: os.tests.length,
|
|
370
513
|
optimizedEstimatedMs: Math.round(os.totalDuration / workers),
|
|
371
514
|
};
|
|
@@ -375,6 +518,7 @@ function registerOptimizeShardsCommand(program) {
|
|
|
375
518
|
generatedAt: new Date().toISOString(),
|
|
376
519
|
shardCount,
|
|
377
520
|
workers,
|
|
521
|
+
strategy,
|
|
378
522
|
totalTests: allTests.length,
|
|
379
523
|
testsWithHistory,
|
|
380
524
|
testsWithoutHistory: allTests.length - testsWithHistory,
|
package/dist/cmd.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cmd.d.ts","sourceRoot":"","sources":["../src/cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/cmd.js
ADDED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
export interface TestRunResponse {
|
|
2
|
-
data: {
|
|
3
|
-
test_run: {
|
|
4
|
-
project: {
|
|
5
|
-
repo_name: string;
|
|
6
|
-
};
|
|
7
|
-
testRun: {
|
|
8
|
-
summary_url: string | null;
|
|
9
|
-
run_id: number | null;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
} | null;
|
|
13
|
-
error?: {
|
|
14
|
-
message: string;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
1
|
export interface FailedTest {
|
|
18
2
|
projectName: string;
|
|
19
3
|
file: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failed-test-list.d.ts","sourceRoot":"","sources":["../src/failed-test-list.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"failed-test-list.d.ts","sourceRoot":"","sources":["../src/failed-test-list.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAgPD,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAyG9B"}
|
package/dist/failed-test-list.js
CHANGED
|
@@ -10,11 +10,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const utils_1 = require("./utils");
|
|
11
11
|
const DOMAIN = process.env.DASHBOARD_DOMAIN || "https://dash.empirical.run";
|
|
12
12
|
const SUITES_DELIMITER = " › ";
|
|
13
|
-
const REPORTS_BASE_URL = "https://reports.empirical.run";
|
|
14
|
-
function buildSummaryUrl(repoName, runId) {
|
|
15
|
-
const projectSlug = repoName.replace("-tests", "");
|
|
16
|
-
return `${REPORTS_BASE_URL}/${projectSlug}/${runId}/summary.json`;
|
|
17
|
-
}
|
|
18
13
|
async function fetchTestRun(runId, options) {
|
|
19
14
|
if (!process.env.EMPIRICALRUN_API_KEY) {
|
|
20
15
|
throw new Error("EMPIRICALRUN_API_KEY environment variable is required");
|
|
@@ -192,20 +187,14 @@ async function buildTestListFromFailedTestRun(runId, options = {}) {
|
|
|
192
187
|
if (!testRunResponse.data) {
|
|
193
188
|
throw new Error(testRunResponse.error?.message || "Failed to fetch test run");
|
|
194
189
|
}
|
|
195
|
-
const { testRun
|
|
196
|
-
|
|
197
|
-
if (!summaryUrl
|
|
198
|
-
|
|
199
|
-
if (verbose) {
|
|
200
|
-
console.log(`Summary URL not in DB, built from run_id: ${summaryUrl}`);
|
|
201
|
-
}
|
|
190
|
+
const { testRun } = testRunResponse.data.test_run;
|
|
191
|
+
const summaryUrl = testRun.summary_url;
|
|
192
|
+
if (!summaryUrl) {
|
|
193
|
+
throw new Error("Test run does not have a summary URL");
|
|
202
194
|
}
|
|
203
195
|
if (verbose) {
|
|
204
196
|
console.log(`Summary URL: ${summaryUrl}`);
|
|
205
197
|
}
|
|
206
|
-
if (!summaryUrl) {
|
|
207
|
-
throw new Error("Test run does not have a summary URL and cannot be built (missing run_id)");
|
|
208
|
-
}
|
|
209
198
|
if (verbose) {
|
|
210
199
|
console.log(`Fetching report from: ${summaryUrl}`);
|
|
211
200
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-reports/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAC1C,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-reports/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAC1C,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AA0DlE,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA2B/B;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAoCjC;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,IAAI,CAAC,CAsDf;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA4DhC"}
|
|
@@ -20,6 +20,28 @@ var html_2 = require("./html");
|
|
|
20
20
|
Object.defineProperty(exports, "patchMergedHtmlReport", { enumerable: true, get: function () { return html_2.patchMergedHtmlReport; } });
|
|
21
21
|
var json_2 = require("./json");
|
|
22
22
|
Object.defineProperty(exports, "patchSummaryJson", { enumerable: true, get: function () { return json_2.patchSummaryJson; } });
|
|
23
|
+
function getStorageConfig(enableS3) {
|
|
24
|
+
if (enableS3) {
|
|
25
|
+
const bucket = process.env.S3_UPLOAD_BUCKET;
|
|
26
|
+
const region = process.env.S3_REGION;
|
|
27
|
+
if (!bucket || !region) {
|
|
28
|
+
throw new Error("S3_UPLOAD_BUCKET and S3_REGION must be set for S3 uploads");
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
baseUrl: `https://${bucket}.s3.${region}.amazonaws.com`,
|
|
32
|
+
uploadBucket: bucket,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const accountId = process.env.R2_ACCOUNT_ID;
|
|
36
|
+
if (!accountId) {
|
|
37
|
+
throw new Error("R2_ACCOUNT_ID must be set for R2 uploads");
|
|
38
|
+
}
|
|
39
|
+
const bucket = "test-report";
|
|
40
|
+
return {
|
|
41
|
+
baseUrl: `https://reports-r2.empirical.run`,
|
|
42
|
+
uploadBucket: bucket,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
23
45
|
function getCredentialsFromEnv(enableS3) {
|
|
24
46
|
if (enableS3) {
|
|
25
47
|
if (process.env.S3_REGION && process.env.S3_UPLOAD_BUCKET) {
|
|
@@ -170,13 +192,11 @@ async function mergeReports(options) {
|
|
|
170
192
|
const enableS3 = process.env.ENABLE_S3_UPLOADS === "true";
|
|
171
193
|
const credentials = getCredentialsFromEnv(enableS3);
|
|
172
194
|
if (credentials) {
|
|
173
|
-
const uploadBucket = enableS3
|
|
174
|
-
? process.env.S3_UPLOAD_BUCKET
|
|
175
|
-
: "test-report";
|
|
195
|
+
const { baseUrl, uploadBucket } = getStorageConfig(enableS3);
|
|
176
196
|
await uploadMergedReports(cwd, outputDir, {
|
|
177
197
|
projectName,
|
|
178
198
|
runId,
|
|
179
|
-
baseUrl
|
|
199
|
+
baseUrl,
|
|
180
200
|
uploadBucket,
|
|
181
201
|
credentials,
|
|
182
202
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/test-run",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"types": "./dist/glob-matcher.d.ts",
|
|
15
15
|
"default": "./dist/glob-matcher.js"
|
|
16
16
|
},
|
|
17
|
+
"./cmd": {
|
|
18
|
+
"types": "./dist/cmd.d.ts",
|
|
19
|
+
"default": "./dist/cmd.js"
|
|
20
|
+
},
|
|
17
21
|
".": {
|
|
18
22
|
"types": "./dist/index.d.ts",
|
|
19
23
|
"default": "./dist/index.js"
|
|
@@ -32,10 +36,10 @@
|
|
|
32
36
|
"minimatch": "^10.0.1",
|
|
33
37
|
"ts-morph": "^23.0.0",
|
|
34
38
|
"@empiricalrun/r2-uploader": "^0.9.0",
|
|
35
|
-
"@empiricalrun/reporter": "^0.
|
|
39
|
+
"@empiricalrun/reporter": "^0.27.0"
|
|
36
40
|
},
|
|
37
41
|
"devDependencies": {
|
|
38
|
-
"@playwright/test": "1.
|
|
42
|
+
"@playwright/test": "1.57.0",
|
|
39
43
|
"@types/async-retry": "^1.4.8",
|
|
40
44
|
"@types/console-log-level": "^1.4.5",
|
|
41
45
|
"@types/node": "^22.5.5",
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/dashboard.ts","./src/failed-test-list.ts","./src/glob-matcher.ts","./src/index.ts","./src/logger.ts","./src/bin/index.ts","./src/bin/commands/estimate-time-shard.ts","./src/bin/commands/failed-list.ts","./src/bin/commands/merge.ts","./src/bin/commands/optimize-shards.ts","./src/bin/commands/run.ts","./src/lib/cancellation-watcher.ts","./src/lib/cmd.ts","./src/lib/run-all-tests.ts","./src/lib/run-specific-test.ts","./src/lib/memfs/read-hello-world.ts","./src/lib/merge-reports/html.ts","./src/lib/merge-reports/index.ts","./src/lib/merge-reports/json.ts","./src/lib/merge-reports/types.ts","./src/stdout-parser/index.ts","./src/types/index.ts","./src/utils/config-parser.ts","./src/utils/config.ts","./src/utils/index.ts"],"version":"5.8.3"}
|
|
1
|
+
{"root":["./src/cmd.ts","./src/dashboard.ts","./src/failed-test-list.ts","./src/glob-matcher.ts","./src/index.ts","./src/logger.ts","./src/bin/index.ts","./src/bin/commands/estimate-time-shard.ts","./src/bin/commands/failed-list.ts","./src/bin/commands/merge.ts","./src/bin/commands/optimize-shards.ts","./src/bin/commands/run.ts","./src/lib/cancellation-watcher.ts","./src/lib/cmd.ts","./src/lib/run-all-tests.ts","./src/lib/run-specific-test.ts","./src/lib/memfs/read-hello-world.ts","./src/lib/merge-reports/html.ts","./src/lib/merge-reports/index.ts","./src/lib/merge-reports/json.ts","./src/lib/merge-reports/types.ts","./src/stdout-parser/index.ts","./src/types/index.ts","./src/utils/config-parser.ts","./src/utils/config.ts","./src/utils/index.ts"],"version":"5.8.3"}
|