@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 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;AAgZzC,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,QAyQ7D"}
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 playwrightShardEstimates = [];
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
- playwrightShardEstimates.push({
325
- shard: i,
326
- tests: shardTests.length,
327
- durationMs: totalDuration,
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(...playwrightShardEstimates.map((s) => s.durationMs));
332
- const playwrightMinDuration = Math.min(...playwrightShardEstimates.map((s) => s.durationMs));
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
- console.log(`\n--- Optimized Bin Packing (LPT) ---`);
336
- const testGroups = buildTestGroups(allTests);
337
- console.log(` Built ${testGroups.length} test groups from ${allTests.length} tests`);
338
- const optimizedShards = packGroupsIntoShards(testGroups, shardCount);
339
- for (const shard of optimizedShards) {
340
- console.log(` Shard ${shard.index}/${shardCount}: ${shard.tests.length} tests, ~${formatDuration(shard.totalDuration, workers)}`);
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
- const optimizedMaxDuration = Math.max(...optimizedShards.map((s) => s.totalDuration));
343
- const optimizedMinDuration = Math.min(...optimizedShards.map((s) => s.totalDuration));
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 optimizedShards) {
358
- const content = generateTestListContent(shard, workers);
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 = optimizedShards.map((os) => {
364
- const pw = playwrightShardEstimates.find((p) => p.shard === os.index);
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.durationMs / workers),
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,2 @@
1
+ export { spawnCmd } from "./lib/cmd";
2
+ //# sourceMappingURL=cmd.d.ts.map
@@ -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
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.spawnCmd = void 0;
4
+ var cmd_1 = require("./lib/cmd");
5
+ Object.defineProperty(exports, "spawnCmd", { enumerable: true, get: function () { return cmd_1.spawnCmd; } });
@@ -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":"AAmBA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QACJ,QAAQ,EAAE;YACR,OAAO,EAAE;gBACP,SAAS,EAAE,MAAM,CAAC;aACnB,CAAC;YACF,OAAO,EAAE;gBACP,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;gBAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;aACvB,CAAC;SACH,CAAC;KACH,GAAG,IAAI,CAAC;IACT,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AASD,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,CAiH9B"}
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"}
@@ -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, project } = testRunResponse.data.test_run;
196
- let summaryUrl = testRun.summary_url;
197
- if (!summaryUrl && testRun.run_id && project.repo_name) {
198
- summaryUrl = buildSummaryUrl(project.repo_name, testRun.run_id);
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;AA6BlE,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,CA8DhC"}
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: "https://reports.empirical.run",
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.13.2",
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.25.4"
39
+ "@empiricalrun/reporter": "^0.27.0"
36
40
  },
37
41
  "devDependencies": {
38
- "@playwright/test": "1.53.2",
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",
@@ -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"}