@empiricalrun/test-run 0.16.0 → 0.17.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/bin/commands/failed-list.js +1 -1
  3. package/dist/bin/commands/run.d.ts.map +1 -1
  4. package/dist/bin/commands/run.js +13 -14
  5. package/dist/bin/index.js +0 -4
  6. package/dist/dashboard.d.ts +1 -1
  7. package/dist/dashboard.d.ts.map +1 -1
  8. package/dist/dashboard.js +4 -6
  9. package/dist/failed-test-list.d.ts +25 -2
  10. package/dist/failed-test-list.d.ts.map +1 -1
  11. package/dist/failed-test-list.js +115 -17
  12. package/dist/index.d.ts +1 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +6 -3
  15. package/dist/lib/cmd.d.ts.map +1 -1
  16. package/dist/lib/cmd.js +10 -39
  17. package/dist/lib/merge-reports/index.d.ts +4 -1
  18. package/dist/lib/merge-reports/index.d.ts.map +1 -1
  19. package/dist/lib/merge-reports/index.js +17 -9
  20. package/dist/lib/merge-reports/types.d.ts +1 -1
  21. package/dist/lib/run-all-tests.d.ts.map +1 -1
  22. package/dist/lib/run-all-tests.js +11 -5
  23. package/dist/lib/run-specific-test.d.ts.map +1 -1
  24. package/dist/lib/run-specific-test.js +13 -22
  25. package/dist/utils/index.d.ts +0 -2
  26. package/dist/utils/index.d.ts.map +1 -1
  27. package/dist/utils/index.js +1 -12
  28. package/package.json +5 -5
  29. package/tsconfig.tsbuildinfo +1 -1
  30. package/vitest.config.ts +7 -0
  31. package/dist/bin/commands/estimate-time-shard.d.ts +0 -3
  32. package/dist/bin/commands/estimate-time-shard.d.ts.map +0 -1
  33. package/dist/bin/commands/estimate-time-shard.js +0 -122
  34. package/dist/bin/commands/optimize-shards.d.ts +0 -3
  35. package/dist/bin/commands/optimize-shards.d.ts.map +0 -1
  36. package/dist/bin/commands/optimize-shards.js +0 -544
  37. package/dist/lib/cancellation-watcher.d.ts +0 -5
  38. package/dist/lib/cancellation-watcher.d.ts.map +0 -1
  39. package/dist/lib/cancellation-watcher.js +0 -49
  40. package/test-data/blob-report/report-1.zip +0 -0
  41. package/test-data/blob-report/report-2.zip +0 -0
@@ -70,7 +70,14 @@ async function runPlaywrightMergeReports(options) {
70
70
  logger_1.logger.debug(`[Merge Reports] Blob dir: ${blobDir}`);
71
71
  logger_1.logger.debug(`[Merge Reports] Output dir: ${outputDir}`);
72
72
  try {
73
- await (0, cmd_1.spawnCmd)("npx", ["playwright", "merge-reports", blobDir, "--reporter", "html,json"], {
73
+ await (0, cmd_1.spawnCmd)("npx", [
74
+ "--yes",
75
+ "playwright@1.58.2",
76
+ "merge-reports",
77
+ blobDir,
78
+ "--reporter",
79
+ "html,json",
80
+ ], {
74
81
  cwd,
75
82
  envOverrides: {
76
83
  PLAYWRIGHT_HTML_OPEN: "never",
@@ -112,9 +119,9 @@ async function extractUrlMappingsFromBlobs(blobDir) {
112
119
  logger_1.logger.info(`[Merge Reports] Total URL mappings: ${Object.keys(combinedMap).length}`);
113
120
  return combinedMap;
114
121
  }
115
- async function uploadMergedReports(cwd, outputDir, uploadOptions) {
116
- const { projectName, runId, baseUrl, uploadBucket, credentials } = uploadOptions;
117
- const destinationDir = path_1.default.join(projectName, runId);
122
+ async function uploadMergedReports({ outputDir, uploadOptions, }) {
123
+ const { projectSlug, runId, baseUrl, uploadBucket, credentials } = uploadOptions;
124
+ const destinationDir = path_1.default.join(projectSlug, runId);
118
125
  const htmlFilePath = path_1.default.join(outputDir, "index.html");
119
126
  const jsonFilePath = path_1.default.join(outputDir, "summary.json");
120
127
  if (fs_1.default.existsSync(htmlFilePath)) {
@@ -164,7 +171,7 @@ async function mergeReports(options) {
164
171
  const cwd = options.cwd || process.cwd();
165
172
  const blobDir = options.blobDir || path_1.default.join(cwd, "blob-report");
166
173
  const outputDir = path_1.default.join(cwd, "playwright-report");
167
- const projectName = process.env.PROJECT_NAME;
174
+ const projectSlug = process.env.PROJECT_NAME;
168
175
  const runId = process.env.TEST_RUN_GITHUB_ACTION_ID;
169
176
  // Track peak memory usage
170
177
  let peakMemoryMb = 0;
@@ -175,7 +182,7 @@ async function mergeReports(options) {
175
182
  }
176
183
  }, 100);
177
184
  try {
178
- if (!projectName || !runId) {
185
+ if (!projectSlug || !runId) {
179
186
  logger_1.logger.error(`[Merge Reports] PROJECT_NAME and TEST_RUN_GITHUB_ACTION_ID must be set`);
180
187
  return { success: false };
181
188
  }
@@ -202,13 +209,14 @@ async function mergeReports(options) {
202
209
  const credentials = getCredentialsFromEnv(enableS3);
203
210
  if (credentials) {
204
211
  const { baseUrl, uploadBucket } = getStorageConfig(enableS3);
205
- await uploadMergedReports(cwd, outputDir, {
206
- projectName,
212
+ const options = {
213
+ projectSlug,
207
214
  runId,
208
215
  baseUrl,
209
216
  uploadBucket,
210
217
  credentials,
211
- });
218
+ };
219
+ await uploadMergedReports({ uploadOptions: options, outputDir });
212
220
  }
213
221
  else {
214
222
  const storageType = enableS3 ? "S3" : "R2";
@@ -5,7 +5,7 @@ export interface MergeReportsOptions {
5
5
  }
6
6
  import type { StorageCredentials } from "@empiricalrun/r2-uploader";
7
7
  export interface UploadOptions {
8
- projectName: string;
8
+ projectSlug: string;
9
9
  runId: string;
10
10
  baseUrl: string;
11
11
  uploadBucket: string;
@@ -1 +1 @@
1
- {"version":3,"file":"run-all-tests.d.ts","sourceRoot":"","sources":["../../src/lib/run-all-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAQlD,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,eAAe,EACf,cAAc,EACd,WAAW,EACX,YAAY,GACb,EAAE;IACD,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,GAAG,YAAY,CAsBf"}
1
+ {"version":3,"file":"run-all-tests.d.ts","sourceRoot":"","sources":["../../src/lib/run-all-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAQlD,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,eAAe,EACf,cAAc,EACd,WAAW,EACX,YAAY,GACb,EAAE;IACD,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,GAAG,YAAY,CAgCf"}
@@ -11,17 +11,23 @@ function runAllTestsCmd({ projects, passthroughArgs, patternsToGrep, filesFilter
11
11
  const projectsArg = (projects || [])
12
12
  .map((project) => `--project ${project}`)
13
13
  .join(" ");
14
- const grepArgs = (patternsToGrep || [])
15
- .map((pattern) => ` -g "${pattern}"`)
16
- .join(" ");
17
- let args = ` ${filesFilter || ""} ${grepArgs} ${projectsArg} ${passthroughArgs || ""}`;
14
+ let args = ` ${filesFilter || ""} ${projectsArg} ${passthroughArgs || ""}`;
18
15
  const envVars = Object.assign({ ...process.env, PW_TEST_HTML_REPORT_OPEN: "never" }, envOverrides);
19
16
  if (envVars.RUN_PLAYWRIGHT_HEADED === "true") {
20
17
  args = `${args} --headed`;
21
18
  }
22
19
  const testRunCmd = normalizeSpaces(`npx ${testRunner} test ${args}`);
20
+ const parsed = (0, cmd_1.getCommandFromString)(testRunCmd);
21
+ // Multiple `-g` flags collapse to the last one in Playwright's CLI, so OR
22
+ // all patterns into a single regex passed as one `-g` argument. We append
23
+ // this after `getCommandFromString` because that helper re-escapes regex
24
+ // specials inside quoted args, which would clobber the `|` alternation.
25
+ if (patternsToGrep && patternsToGrep.length > 0) {
26
+ const escapedPatterns = patternsToGrep.map((pattern) => pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
27
+ parsed.args.push("-g", escapedPatterns.join("|"));
28
+ }
23
29
  return {
24
- ...(0, cmd_1.getCommandFromString)(testRunCmd),
30
+ ...parsed,
25
31
  env: envVars,
26
32
  };
27
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"run-specific-test.d.ts","sourceRoot":"","sources":["../../src/lib/run-specific-test.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAYlD,wBAAsB,mBAAmB,CAAC,EACxC,KAAU,EACV,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,OAAO,GACR,EAAE;IACD,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,YAAY,CAAC,CAsFxB"}
1
+ {"version":3,"file":"run-specific-test.d.ts","sourceRoot":"","sources":["../../src/lib/run-specific-test.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIlD,wBAAsB,mBAAmB,CAAC,EACxC,KAAU,EACV,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,OAAO,GACR,EAAE;IACD,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkFxB"}
@@ -1,10 +1,7 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.runSpecificTestsCmd = runSpecificTestsCmd;
7
- const path_1 = __importDefault(require("path"));
4
+ const failed_test_list_1 = require("../failed-test-list");
8
5
  const utils_1 = require("../utils");
9
6
  const run_all_tests_1 = require("./run-all-tests");
10
7
  async function runSpecificTestsCmd({ tests = [], projects, passthroughArgs, envOverrides, repoDir, }) {
@@ -16,6 +13,7 @@ async function runSpecificTestsCmd({ tests = [], projects, passthroughArgs, envO
16
13
  for (const testCase of tests) {
17
14
  // TODO: Why do we have this getAllFilePaths call?
18
15
  // TODO: Can we remove `dir` from the test case entity?
16
+ // TODO: After 1.57 is present in all repo, replace grep with --test-list
19
17
  const files = await (0, utils_1.getAllFilePaths)(testCase.dir, repoDir, {
20
18
  filePath: testCase.filePath,
21
19
  });
@@ -43,24 +41,17 @@ async function runSpecificTestsCmd({ tests = [], projects, passthroughArgs, envO
43
41
  else {
44
42
  filesToRun.push(matchingFilePath);
45
43
  }
46
- const { testCaseNode } = await (0, utils_1.getTestCaseNode)({
47
- filePath: matchingFilePath,
48
- scenarioName: testCase.name,
49
- suites: testCase.suites,
50
- repoDir,
51
- });
52
- const isFileMarkedSerial = await (0, utils_1.hasTopLevelDescribeConfigureWithSerialMode)(path_1.default.join(repoDir, matchingFilePath));
53
- if (!isFileMarkedSerial && testCaseNode) {
54
- const parentDescribe = (0, utils_1.findFirstSerialDescribeBlock)(testCaseNode);
55
- if (!parentDescribe) {
56
- patternsToGrep.push(testCase.name);
57
- }
58
- else {
59
- const parentDescribeName = (0, utils_1.getDescribeBlockName)(parentDescribe);
60
- if (parentDescribeName) {
61
- patternsToGrep.push(parentDescribeName);
62
- }
63
- }
44
+ const serialInfo = await (0, failed_test_list_1.getSerialBlockInfo)({
45
+ file: matchingFilePath,
46
+ title: testCase.name,
47
+ suites: testCase.suites ?? [],
48
+ projectName: "",
49
+ }, repoDir, false);
50
+ if (!serialInfo) {
51
+ patternsToGrep.push(testCase.name);
52
+ }
53
+ else if (!serialInfo.isFileSerial && serialInfo.serialDescribeName) {
54
+ patternsToGrep.push(serialInfo.serialDescribeName);
64
55
  }
65
56
  }
66
57
  const teardownLabels = await (0, utils_1.labelTeardownProjects)(projects, repoDir);
@@ -41,8 +41,6 @@ export declare const generateProjectFiltersV2: ({ match, ignore, repoDir, }: {
41
41
  ignore: string[];
42
42
  repoDir: string;
43
43
  }) => Promise<string[]>;
44
- export declare function buildRepoName(projectName: string): string;
45
- export declare const pickNameFromPackageJson: () => Promise<string | undefined>;
46
44
  export declare const downloadBuild: (buildUrl: string) => Promise<void>;
47
45
  export declare const getTestRunner: () => string;
48
46
  export declare const handleTeardownSkipFlag: (directory: string, repoDir: string) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAW,UAAU,EAAc,MAAM,UAAU,CAAC;AAWjE,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,YAAU,EAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,MAAM,EAAE,CAAC,CA4BnB;AAED,eAAO,MAAM,gCAAgC,GAC3C,YAAY,UAAU,KACrB,MAgBF,CAAC;AAEF,wBAAsB,eAAe,CAAC,EACpC,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,YAAY,EAAE,IAAI,GAAG,SAAS,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,CAStE;AAED,wBAAsB,YAAY,CAAC,EACjC,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAWnE;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,IAAI,GAAG,SAAS,GACrB,IAAI,GAAG,SAAS,CA2BlB;AAED,wBAAsB,0CAA0C,CAC9D,QAAQ,EAAE,MAAM,oBA+BjB;AAED,wBAAsB,cAAc,CAAC,EACnC,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACtC,YAAY,EAAE,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,iBAgBA;AAED,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAE/D,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EAAE,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CACR;IACE,UAAU,EAAE,OAAO,CAAC;IACpB,yBAAyB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,EAAE,CACJ,CAkBA;AAED,eAAO,MAAM,sBAAsB,GAAU,6BAG1C;IACD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB,KAAG,OAAO,CAAC,MAAM,EAAE,CAgBnB,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,6BAI5C;IACD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,KAAG,OAAO,CAAC,MAAM,EAAE,CAgBnB,CAAC;AAEF,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,eAAO,MAAM,uBAAuB,QAAa,OAAO,CACtD,MAAM,GAAG,SAAS,CAMnB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,IAAI,CAclE,CAAC;AAEF,eAAO,MAAM,aAAa,QAAO,MAEhC,CAAC;AAqEF,eAAO,MAAM,sBAAsB,GACjC,WAAW,MAAM,EACjB,SAAS,MAAM,kBAmBhB,CAAC;AA0BF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,YAAY,EACZ,MAAM,EACN,OAAO,EACP,OAAO,GACR,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG;IACF,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,IAAI,GAAG,SAAS,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,UAAU,CAAC;CACxB,CAiDA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.handleTeardownSkipFlag = exports.getTestRunner = exports.downloadBuild = exports.pickNameFromPackageJson = exports.generateProjectFiltersV2 = exports.generateProjectFilters = exports.filterArrayByGlobMatchersSet = exports.getTestModuleAliasFromSourceFile = void 0;
6
+ exports.handleTeardownSkipFlag = exports.getTestRunner = exports.downloadBuild = exports.generateProjectFiltersV2 = exports.generateProjectFilters = exports.filterArrayByGlobMatchersSet = exports.getTestModuleAliasFromSourceFile = void 0;
7
7
  exports.getAllFilePaths = getAllFilePaths;
8
8
  exports.getTestCaseNode = getTestCaseNode;
9
9
  exports.hasTestBlock = hasTestBlock;
@@ -12,7 +12,6 @@ exports.findFirstSerialDescribeBlock = findFirstSerialDescribeBlock;
12
12
  exports.hasTopLevelDescribeConfigureWithSerialMode = hasTopLevelDescribeConfigureWithSerialMode;
13
13
  exports.markTestAsOnly = markTestAsOnly;
14
14
  exports.labelTeardownProjects = labelTeardownProjects;
15
- exports.buildRepoName = buildRepoName;
16
15
  exports.getTypescriptTestBlock = getTypescriptTestBlock;
17
16
  const fs_1 = require("fs");
18
17
  const promises_1 = __importDefault(require("fs/promises"));
@@ -207,16 +206,6 @@ const generateProjectFiltersV2 = async ({ match, ignore, repoDir, }) => {
207
206
  return filteredProjects;
208
207
  };
209
208
  exports.generateProjectFiltersV2 = generateProjectFiltersV2;
210
- function buildRepoName(projectName) {
211
- return `${projectName}-tests`;
212
- }
213
- const pickNameFromPackageJson = async () => {
214
- const packageJSONPath = "package.json";
215
- const packageJsonStr = await promises_1.default.readFile(packageJSONPath, "utf-8");
216
- const packageJSONData = JSON.parse(packageJsonStr);
217
- return packageJSONData.name && packageJSONData.name.replace("-tests", "");
218
- };
219
- exports.pickNameFromPackageJson = pickNameFromPackageJson;
220
209
  const downloadBuild = async (buildUrl) => {
221
210
  const packageJSONPath = "package.json";
222
211
  const packageJsonStr = await promises_1.default.readFile(packageJSONPath, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-run",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -37,21 +37,21 @@
37
37
  "minimatch": "^10.0.1",
38
38
  "ts-morph": "^23.0.0",
39
39
  "@empiricalrun/r2-uploader": "^0.9.1",
40
- "@empiricalrun/reporter": "^0.28.0"
40
+ "@empiricalrun/reporter": "^0.29.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@playwright/test": "1.57.0",
43
+ "@playwright/test": "1.60.0",
44
44
  "@types/async-retry": "^1.4.8",
45
45
  "@types/js-yaml": "^4.0.9",
46
46
  "@types/console-log-level": "^1.4.5",
47
47
  "@types/node": "^22.5.5",
48
48
  "memfs": "^4.17.1",
49
- "@empiricalrun/shared-types": "0.12.1"
49
+ "@empiricalrun/shared-types": "0.13.0"
50
50
  },
51
51
  "scripts": {
52
52
  "dev": "tsc --build --watch",
53
53
  "build": "tsc --build",
54
- "clean": "tsc --build --clean",
54
+ "clean": "rm -rf dist && tsc --build --clean",
55
55
  "lint": "biome check --unsafe",
56
56
  "test": "vitest run",
57
57
  "test:watch": "vitest"
@@ -1 +1 @@
1
- {"root":["./src/cmd.ts","./src/dashboard.ts","./src/env-files.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/env-files.ts","./src/failed-test-list.ts","./src/glob-matcher.ts","./src/index.ts","./src/logger.ts","./src/bin/index.ts","./src/bin/commands/failed-list.ts","./src/bin/commands/merge.ts","./src/bin/commands/run.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"}
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["src/**/*.test.ts"],
6
+ },
7
+ });
@@ -1,3 +0,0 @@
1
- import type { Command } from "commander";
2
- export declare function registerEstimateTimeShardCommand(program: Command): void;
3
- //# sourceMappingURL=estimate-time-shard.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"estimate-time-shard.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/estimate-time-shard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6DzC,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,OAAO,QA6HhE"}
@@ -1,122 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerEstimateTimeShardCommand = registerEstimateTimeShardCommand;
4
- const reporter_1 = require("@empiricalrun/reporter");
5
- const child_process_1 = require("child_process");
6
- async function fetchTestHistory(testCaseIds, dashboardUrl) {
7
- const apiKey = process.env.EMPIRICALRUN_API_KEY;
8
- if (!apiKey) {
9
- throw new Error("EMPIRICALRUN_API_KEY environment variable is required");
10
- }
11
- const url = `${dashboardUrl}/api/test-cases/history/batch`;
12
- const response = await fetch(url, {
13
- method: "POST",
14
- headers: {
15
- "Content-Type": "application/json",
16
- Authorization: `Bearer ${apiKey}`,
17
- },
18
- body: JSON.stringify({ test_case_ids: testCaseIds }),
19
- });
20
- if (!response.ok) {
21
- throw new Error(`Failed to fetch test history: ${response.status}`);
22
- }
23
- return response.json();
24
- }
25
- function calculateEstimatedDuration(history, includeRetries) {
26
- if (history.length === 0) {
27
- return 0;
28
- }
29
- const durationField = includeRetries
30
- ? "duration_total"
31
- : "duration_per_retry";
32
- const avgDuration = history.reduce((sum, r) => sum + r[durationField], 0) / history.length;
33
- return avgDuration;
34
- }
35
- function registerEstimateTimeShardCommand(program) {
36
- program
37
- .command("estimate-time-shard")
38
- .description("Estimate how long a shard will take based on historical data")
39
- .requiredOption("--shard <shard>", "Shard identifier in N/M format (e.g., 1/2)")
40
- .option("--dashboard-url <url>", "Dashboard URL for fetching test history", process.env.DASHBOARD_URL || "https://dash.empirical.run")
41
- .option("--workers <workers>", "Number of parallel workers", "8")
42
- .option("--include-retries", "Use total duration including retries (accounts for flakiness)", false)
43
- .action(async (options) => {
44
- const { shard, dashboardUrl, includeRetries } = options;
45
- const workers = parseInt(options.workers, 10);
46
- const shardMatch = shard.match(/^(\d+)\/(\d+)$/);
47
- if (!shardMatch) {
48
- console.error('Invalid shard format. Expected N/M format (e.g., "1/2")');
49
- process.exit(1);
50
- }
51
- const shardIndex = parseInt(shardMatch[1], 10);
52
- const totalShards = parseInt(shardMatch[2], 10);
53
- if (shardIndex < 1 || shardIndex > totalShards) {
54
- console.error(`Invalid shard index. Must be between 1 and ${totalShards}`);
55
- process.exit(1);
56
- }
57
- let jsonReport;
58
- try {
59
- const output = (0, child_process_1.execSync)(`npx playwright test --shard ${shard} --list --reporter=json`, {
60
- encoding: "utf-8",
61
- stdio: ["pipe", "pipe", "pipe"],
62
- });
63
- jsonReport = JSON.parse(output);
64
- }
65
- catch (error) {
66
- if (error.stdout) {
67
- try {
68
- jsonReport = JSON.parse(error.stdout);
69
- }
70
- catch {
71
- console.error("Failed to parse playwright JSON output:", error.message);
72
- process.exit(1);
73
- }
74
- }
75
- else {
76
- console.error("Failed to get test list from playwright:", error.message);
77
- process.exit(1);
78
- }
79
- }
80
- const flattenedSpecs = (0, reporter_1.getFlattenedTestList)(jsonReport.suites);
81
- if (flattenedSpecs.length === 0) {
82
- console.log("No tests found for this shard.");
83
- process.exit(0);
84
- }
85
- const testCaseIds = flattenedSpecs.map((spec) => spec.id);
86
- let historyResponse;
87
- try {
88
- historyResponse = await fetchTestHistory(testCaseIds, dashboardUrl);
89
- }
90
- catch (error) {
91
- console.error("Failed to fetch test history:", error.message);
92
- process.exit(1);
93
- }
94
- let totalEstimatedDuration = 0;
95
- let testsWithHistory = 0;
96
- let testsWithoutHistory = 0;
97
- for (const testId of testCaseIds) {
98
- const history = historyResponse[testId] || [];
99
- const estimatedDuration = calculateEstimatedDuration(history, includeRetries);
100
- if (history.length > 0) {
101
- testsWithHistory++;
102
- }
103
- else {
104
- testsWithoutHistory++;
105
- }
106
- totalEstimatedDuration += estimatedDuration;
107
- }
108
- const parallelizedDuration = totalEstimatedDuration / workers;
109
- const totalSeconds = Math.round(parallelizedDuration / 1000);
110
- const minutes = Math.floor(totalSeconds / 60);
111
- const seconds = totalSeconds % 60;
112
- console.log(JSON.stringify({
113
- shard,
114
- totalTests: testCaseIds.length,
115
- workers,
116
- testsWithHistory,
117
- testsWithoutHistory,
118
- estimatedDurationMs: Math.round(parallelizedDuration),
119
- estimatedDurationFormatted: `${minutes}m ${seconds}s`,
120
- }));
121
- });
122
- }
@@ -1,3 +0,0 @@
1
- import type { Command } from "commander";
2
- export declare function registerOptimizeShardsCommand(program: Command): void;
3
- //# sourceMappingURL=optimize-shards.d.ts.map
@@ -1 +0,0 @@
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"}