@empiricalrun/test-run 0.16.0 → 0.16.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,12 @@
1
1
  # @empiricalrun/test-run
2
2
 
3
+ ## 0.16.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [3a315aa]
8
+ - @empiricalrun/reporter@0.28.1
9
+
3
10
  ## 0.16.0
4
11
 
5
12
  ### Minor Changes
@@ -23,7 +23,7 @@ function registerFailedListCommand(program) {
23
23
  repoPath: options.repoPath,
24
24
  verbose: options.verbose,
25
25
  });
26
- console.log(`Found ${result.failedTests.length} failed tests`);
26
+ console.log(`Found ${result.testsToRun.length} failed tests`);
27
27
  console.log(`Test list written to: ${result.outputPath}`);
28
28
  }
29
29
  catch (error) {
@@ -1,9 +1,28 @@
1
- export interface FailedTest {
1
+ /**
2
+ * Normalize a test line for comparison between manifest and ran tests.
3
+ * Strips `:line:col` suffixes from file paths.
4
+ *
5
+ * e.g. `[chromium] › foo/bar.spec.ts:4:5 › has title`
6
+ * → `[chromium] › foo/bar.spec.ts › has title`
7
+ */
8
+ export declare function normalizeTestLine(line: string): string;
9
+ export declare function parseTestListLine(line: string): TestList | null;
10
+ /**
11
+ * Runs `npx playwright test --list <file>` and returns all test lines for the file.
12
+ */
13
+ export declare function getAllTestsForFile(file: string, repoPath: string): Promise<string[]>;
14
+ export interface TestList {
2
15
  projectName: string;
3
16
  file: string;
4
17
  title: string;
5
18
  suites: string[];
6
19
  }
20
+ export interface SerialBlockInfo {
21
+ file: string;
22
+ serialDescribeName: string | null;
23
+ isFileSerial: boolean;
24
+ }
25
+ export declare function getSerialBlockInfo(test: TestList, repoPath: string, verbose: boolean): Promise<SerialBlockInfo | null>;
7
26
  export interface BuildTestListOptions {
8
27
  outputPath?: string;
9
28
  verbose?: boolean;
@@ -11,9 +30,13 @@ export interface BuildTestListOptions {
11
30
  repoPath?: string;
12
31
  }
13
32
  export interface BuildTestListResult {
14
- failedTests: FailedTest[];
33
+ testsToRun: TestList[];
15
34
  testListContent: string;
16
35
  outputPath: string;
17
36
  }
37
+ export declare function buildTestList(testsToRun: TestList[], allTests: TestList[], options: BuildTestListOptions & {
38
+ verbose: boolean;
39
+ }): Promise<BuildTestListResult>;
18
40
  export declare function buildTestListFromFailedTestRun(runId: string, options?: BuildTestListOptions): Promise<BuildTestListResult>;
41
+ export declare function expandSerialDependencies(testListContent: string, outputPath: string, repoPath: string): Promise<string>;
19
42
  //# sourceMappingURL=failed-test-list.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"failed-test-list.d.ts","sourceRoot":"","sources":["../src/failed-test-list.ts"],"names":[],"mappings":"AAsBA;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAgB/D;AAID;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,EAAE,CAAC,CAsBnB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAmED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC;CACvB;AAoBD,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA+CjC;AA0FD,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,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AA0DD,wBAAsB,aAAa,CACjC,UAAU,EAAE,QAAQ,EAAE,EACtB,QAAQ,EAAE,QAAQ,EAAE,EACpB,OAAO,EAAE,oBAAoB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GACnD,OAAO,CAAC,mBAAmB,CAAC,CA0D9B;AAED,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAa9B;AAED,wBAAsB,wBAAwB,CAC5C,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAmCjB"}
@@ -3,13 +3,65 @@ 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.normalizeTestLine = normalizeTestLine;
7
+ exports.parseTestListLine = parseTestListLine;
8
+ exports.getAllTestsForFile = getAllTestsForFile;
9
+ exports.getSerialBlockInfo = getSerialBlockInfo;
10
+ exports.buildTestList = buildTestList;
6
11
  exports.buildTestListFromFailedTestRun = buildTestListFromFailedTestRun;
12
+ exports.expandSerialDependencies = expandSerialDependencies;
13
+ const node_child_process_1 = require("node:child_process");
14
+ const node_util_1 = require("node:util");
7
15
  const reporter_1 = require("@empiricalrun/reporter");
8
16
  const fs_1 = __importDefault(require("fs"));
9
17
  const path_1 = __importDefault(require("path"));
10
18
  const utils_1 = require("./utils");
11
19
  const DOMAIN = process.env.DASHBOARD_DOMAIN || "https://dash.empirical.run";
12
20
  const SUITES_DELIMITER = " › ";
21
+ /**
22
+ * Normalize a test line for comparison between manifest and ran tests.
23
+ * Strips `:line:col` suffixes from file paths.
24
+ *
25
+ * e.g. `[chromium] › foo/bar.spec.ts:4:5 › has title`
26
+ * → `[chromium] › foo/bar.spec.ts › has title`
27
+ */
28
+ function normalizeTestLine(line) {
29
+ return line.replace(/(\.spec\.ts|\.test\.ts|\.ts|\.js):\d+:\d+/g, "$1");
30
+ }
31
+ function parseTestListLine(line) {
32
+ const trimmed = normalizeTestLine(line.trim());
33
+ if (!trimmed || trimmed.startsWith("#"))
34
+ return null;
35
+ const match = trimmed.match(/^\[(.*?)\]\s*[›>]\s*(.*)/);
36
+ if (!match?.[1] || !match[2])
37
+ return null;
38
+ const projectName = match[1];
39
+ const parts = match[2].split(/\s*[›>]\s*/);
40
+ if (parts.length < 2)
41
+ return null;
42
+ const file = parts[0].trim();
43
+ const suites = parts.slice(1, -1);
44
+ const title = parts[parts.length - 1].trim();
45
+ return { projectName, file, suites, title };
46
+ }
47
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
48
+ /**
49
+ * Runs `npx playwright test --list <file>` and returns all test lines for the file.
50
+ */
51
+ async function getAllTestsForFile(file, repoPath) {
52
+ try {
53
+ const playwrightCli = path_1.default.join(repoPath, "node_modules", "@playwright", "test", "cli.js");
54
+ const { stdout } = await execFileAsync("node", [playwrightCli, "test", "--list", file], { cwd: repoPath });
55
+ return stdout
56
+ .split("\n")
57
+ .map((line) => line.trim())
58
+ .filter((line) => line.includes(" › "));
59
+ }
60
+ catch (error) {
61
+ console.error(`Failed to list tests for file: ${file}`, error);
62
+ throw error;
63
+ }
64
+ }
13
65
  async function fetchTestRun(runId, options) {
14
66
  if (!process.env.EMPIRICALRUN_API_KEY) {
15
67
  throw new Error("EMPIRICALRUN_API_KEY environment variable is required");
@@ -45,8 +97,12 @@ function getFailedTests(specs) {
45
97
  const failedTests = [];
46
98
  for (const spec of specs) {
47
99
  for (const test of spec.tests) {
48
- const status = (0, reporter_1.deriveTestStatus)(test.results);
49
- if (status === "fail") {
100
+ const retries = test.results.map((r) => ({
101
+ status: r.status ?? "unknown",
102
+ expectedStatus: test.expectedStatus,
103
+ }));
104
+ const status = (0, reporter_1.getTestStatusFromRetries)(retries);
105
+ if (status === "failed") {
50
106
  const suites = spec.nesting.slice(1, -1);
51
107
  failedTests.push({
52
108
  projectName: test.projectName,
@@ -141,17 +197,12 @@ function isTestInSerialBlock(test, serialBlock) {
141
197
  }
142
198
  return false;
143
199
  }
144
- function generateTestListContent(failedTests, allTests, serialBlocks = []) {
145
- const lines = [
146
- `# Failed tests from test run`,
147
- `# Generated: ${new Date().toISOString()}`,
148
- `# Total failed tests: ${failedTests.length}`,
149
- "",
150
- ];
200
+ function generateTestListContent(testsToRun, allTests, serialBlocks = []) {
201
+ const lines = [];
151
202
  const addedEntries = new Set();
152
203
  for (const serialBlock of serialBlocks) {
153
204
  const projectNames = [
154
- ...new Set(failedTests
205
+ ...new Set(testsToRun
155
206
  .filter((t) => t.file === serialBlock.file)
156
207
  .map((t) => t.projectName)),
157
208
  ];
@@ -166,7 +217,7 @@ function generateTestListContent(failedTests, allTests, serialBlocks = []) {
166
217
  }
167
218
  }
168
219
  }
169
- for (const test of failedTests) {
220
+ for (const test of testsToRun) {
170
221
  const isInSerialBlock = serialBlocks.some((s) => s.file === test.file);
171
222
  if (!isInSerialBlock) {
172
223
  const line = formatTestListLine(test);
@@ -178,8 +229,8 @@ function generateTestListContent(failedTests, allTests, serialBlocks = []) {
178
229
  }
179
230
  return lines.join("\n");
180
231
  }
181
- async function buildTestListFromFailedTestRun(runId, options = {}) {
182
- const verbose = options.verbose ?? false;
232
+ async function fetchFailedTestList(runId, options) {
233
+ const { verbose } = options;
183
234
  const testRunResponse = await fetchTestRun(runId, {
184
235
  verbose,
185
236
  repoName: options.repoName,
@@ -214,12 +265,16 @@ async function buildTestListFromFailedTestRun(runId, options = {}) {
214
265
  if (verbose) {
215
266
  console.log(`Found ${failedTests.length} failed tests, ${allTests.length} total tests`);
216
267
  }
268
+ return { failedTests, allTests };
269
+ }
270
+ async function buildTestList(testsToRun, allTests, options) {
271
+ const { verbose } = options;
217
272
  const serialBlocks = [];
218
273
  if (options.repoPath) {
219
274
  if (verbose) {
220
275
  console.log(`Checking for serial blocks in repo: ${options.repoPath}`);
221
276
  }
222
- for (const test of failedTests) {
277
+ for (const test of testsToRun) {
223
278
  try {
224
279
  const serialInfo = await getSerialBlockInfo(test, options.repoPath, verbose);
225
280
  if (serialInfo) {
@@ -241,16 +296,59 @@ async function buildTestListFromFailedTestRun(runId, options = {}) {
241
296
  console.log(`Found ${serialBlocks.length} serial blocks to expand`);
242
297
  }
243
298
  }
244
- const testListContent = generateTestListContent(failedTests, allTests, serialBlocks);
245
- const outputPath = options.outputPath || path_1.default.join(process.cwd(), `failed-tests-${runId}.txt`);
299
+ const testListContent = generateTestListContent(testsToRun, allTests, serialBlocks);
300
+ const outputPath = options.outputPath || path_1.default.join(process.cwd(), "failed-tests.txt");
246
301
  const outputDir = path_1.default.dirname(outputPath);
247
302
  if (!fs_1.default.existsSync(outputDir)) {
248
303
  fs_1.default.mkdirSync(outputDir, { recursive: true });
249
304
  }
250
305
  fs_1.default.writeFileSync(outputPath, testListContent, "utf-8");
251
306
  return {
252
- failedTests,
307
+ testsToRun,
253
308
  testListContent,
254
309
  outputPath,
255
310
  };
256
311
  }
312
+ async function buildTestListFromFailedTestRun(runId, options = {}) {
313
+ const verbose = options.verbose ?? false;
314
+ const { failedTests, allTests } = await fetchFailedTestList(runId, {
315
+ verbose,
316
+ repoName: options.repoName,
317
+ });
318
+ return buildTestList(failedTests, allTests, {
319
+ ...options,
320
+ verbose,
321
+ outputPath: options.outputPath ||
322
+ path_1.default.join(process.cwd(), `failed-tests-${runId}.txt`),
323
+ });
324
+ }
325
+ async function expandSerialDependencies(testListContent, outputPath, repoPath) {
326
+ const lines = testListContent.split("\n");
327
+ const testsToRun = [];
328
+ for (const line of lines) {
329
+ const parsed = parseTestListLine(line);
330
+ if (parsed)
331
+ testsToRun.push(parsed);
332
+ }
333
+ if (testsToRun.length === 0) {
334
+ console.log("testListContent could not be parsed. Returning original tests");
335
+ fs_1.default.writeFileSync(outputPath, testListContent, "utf-8");
336
+ return testListContent;
337
+ }
338
+ const uniqueFiles = [...new Set(testsToRun.map((t) => t.file))];
339
+ const allTests = [];
340
+ for (const file of uniqueFiles) {
341
+ const allLines = await getAllTestsForFile(file, repoPath);
342
+ for (const line of allLines) {
343
+ const parsed = parseTestListLine(line);
344
+ if (parsed)
345
+ allTests.push(parsed);
346
+ }
347
+ }
348
+ const result = await buildTestList(testsToRun, allTests, {
349
+ repoPath,
350
+ outputPath,
351
+ verbose: false,
352
+ });
353
+ return result.testListContent;
354
+ }
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import { parseTestListOutput } from "./stdout-parser";
5
5
  import { TestCase } from "./types";
6
6
  import { getProjectsFromPlaywrightConfig } from "./utils/config";
7
7
  export { getProjectsFromPlaywrightConfig, parseTestListOutput, runSpecificTestsCmd, spawnCmd, };
8
- export { type BuildTestListOptions, type BuildTestListResult, buildTestListFromFailedTestRun, type FailedTest, } from "./failed-test-list";
8
+ export { type BuildTestListOptions, type BuildTestListResult, buildTestList, buildTestListFromFailedTestRun, expandSerialDependencies, getAllTestsForFile, normalizeTestLine, parseTestListLine, type TestList, } from "./failed-test-list";
9
9
  export * from "./glob-matcher";
10
10
  export { type CancellationWatcher, startCancellationWatcher, } from "./lib/cancellation-watcher";
11
11
  export { filterArrayByGlobMatchersSet, generateProjectFilters, generateProjectFiltersV2, } from "./utils";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAI/E,OAAO,EAAkB,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EACL,+BAA+B,EAC/B,mBAAmB,EACnB,mBAAmB,EACnB,QAAQ,GACT,CAAC;AACF,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,8BAA8B,EAC9B,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,KAAK,mBAAmB,EACxB,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,SAAS,CAAC;AAQjB,wBAAsB,aAAa,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC;IACV,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,oBAAoB,CAAC;CACnC,CAAC,CAmBD;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;CACjD,CAAC,CAeD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAI/E,OAAO,EAAkB,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EACL,+BAA+B,EAC/B,mBAAmB,EACnB,mBAAmB,EACnB,QAAQ,GACT,CAAC;AACF,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,aAAa,EACb,8BAA8B,EAC9B,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,QAAQ,GACd,MAAM,oBAAoB,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,KAAK,mBAAmB,EACxB,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,SAAS,CAAC;AAQjB,wBAAsB,aAAa,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC;IACV,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,oBAAoB,CAAC;CACnC,CAAC,CAmBD;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;CACjD,CAAC,CAeD"}
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.generateProjectFiltersV2 = exports.generateProjectFilters = exports.filterArrayByGlobMatchersSet = exports.startCancellationWatcher = exports.buildTestListFromFailedTestRun = exports.spawnCmd = exports.runSpecificTestsCmd = exports.parseTestListOutput = exports.getProjectsFromPlaywrightConfig = void 0;
20
+ exports.generateProjectFiltersV2 = exports.generateProjectFilters = exports.filterArrayByGlobMatchersSet = exports.startCancellationWatcher = exports.parseTestListLine = exports.normalizeTestLine = exports.getAllTestsForFile = exports.expandSerialDependencies = exports.buildTestListFromFailedTestRun = exports.buildTestList = exports.spawnCmd = exports.runSpecificTestsCmd = exports.parseTestListOutput = exports.getProjectsFromPlaywrightConfig = void 0;
21
21
  exports.runSingleTest = runSingleTest;
22
22
  exports.listProjectsAndTests = listProjectsAndTests;
23
23
  const fs_1 = __importDefault(require("fs"));
@@ -31,7 +31,12 @@ Object.defineProperty(exports, "parseTestListOutput", { enumerable: true, get: f
31
31
  const config_1 = require("./utils/config");
32
32
  Object.defineProperty(exports, "getProjectsFromPlaywrightConfig", { enumerable: true, get: function () { return config_1.getProjectsFromPlaywrightConfig; } });
33
33
  var failed_test_list_1 = require("./failed-test-list");
34
+ Object.defineProperty(exports, "buildTestList", { enumerable: true, get: function () { return failed_test_list_1.buildTestList; } });
34
35
  Object.defineProperty(exports, "buildTestListFromFailedTestRun", { enumerable: true, get: function () { return failed_test_list_1.buildTestListFromFailedTestRun; } });
36
+ Object.defineProperty(exports, "expandSerialDependencies", { enumerable: true, get: function () { return failed_test_list_1.expandSerialDependencies; } });
37
+ Object.defineProperty(exports, "getAllTestsForFile", { enumerable: true, get: function () { return failed_test_list_1.getAllTestsForFile; } });
38
+ Object.defineProperty(exports, "normalizeTestLine", { enumerable: true, get: function () { return failed_test_list_1.normalizeTestLine; } });
39
+ Object.defineProperty(exports, "parseTestListLine", { enumerable: true, get: function () { return failed_test_list_1.parseTestListLine; } });
35
40
  __exportStar(require("./glob-matcher"), exports);
36
41
  var cancellation_watcher_1 = require("./lib/cancellation-watcher");
37
42
  Object.defineProperty(exports, "startCancellationWatcher", { enumerable: true, get: function () { return cancellation_watcher_1.startCancellationWatcher; } });
package/dist/lib/cmd.js CHANGED
@@ -122,9 +122,16 @@ async function spawnCmd(command, args, options) {
122
122
  function setupProcessSignalHandlers(proc) {
123
123
  const handleSignal = async (signal) => {
124
124
  logger_1.logger.debug(`\nReceived ${signal}, gracefully shutting down...`);
125
- if (proc && !proc.killed) {
126
- // Forward the signal to the child process
127
- proc.kill(signal);
125
+ if (proc && !proc.killed && proc.pid) {
126
+ // Forward the signal to the entire process group so all children
127
+ // (including Playwright workers and reporters) receive it
128
+ try {
129
+ process.kill(-proc.pid, signal);
130
+ }
131
+ catch {
132
+ // Process group may have already exited
133
+ proc.kill(signal);
134
+ }
128
135
  // Wait for the child process to exit
129
136
  await new Promise((resolve) => {
130
137
  proc.once("exit", () => {
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-run",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -37,7 +37,7 @@
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.28.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@playwright/test": "1.57.0",
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ testDir: ".",
5
+ projects: [{ name: "chromium" }],
6
+ });
@@ -0,0 +1,17 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test.describe("Serial Tests", () => {
4
+ test.describe.configure({ mode: "serial" });
5
+
6
+ test("Test 1 should pass", async () => {
7
+ expect(true).toBe(true);
8
+ });
9
+
10
+ test("Test 2 should pass", async () => {
11
+ expect(1 + 1).toBe(2);
12
+ });
13
+
14
+ test("Test 3 should fail", async () => {
15
+ expect(true).toBe(true);
16
+ });
17
+ });
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["src/**/*.test.ts"],
6
+ },
7
+ });