@empiricalrun/test-run 0.15.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 +13 -0
- package/dist/bin/commands/failed-list.js +1 -1
- package/dist/failed-test-list.d.ts +25 -2
- package/dist/failed-test-list.d.ts.map +1 -1
- package/dist/failed-test-list.js +115 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -6
- package/dist/lib/cmd.js +10 -3
- package/dist/lib/merge-reports/html.d.ts.map +1 -1
- package/dist/lib/merge-reports/html.js +5 -1
- package/dist/lib/merge-reports/index.d.ts.map +1 -1
- package/dist/lib/merge-reports/index.js +4 -4
- package/dist/lib/run-specific-test.d.ts.map +1 -1
- package/dist/lib/run-specific-test.js +13 -22
- package/package.json +2 -2
- package/test-data/expand-serial-fixture/playwright.config.ts +6 -0
- package/test-data/expand-serial-fixture/serial.spec.ts +17 -0
- package/vitest.config.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
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
|
+
|
|
10
|
+
## 0.16.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- ab403b8: fix: move json report location to avoid accidental agent commits
|
|
15
|
+
|
|
3
16
|
## 0.15.0
|
|
4
17
|
|
|
5
18
|
### 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.
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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"}
|
package/dist/failed-test-list.js
CHANGED
|
@@ -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
|
|
49
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
182
|
-
const verbose = options
|
|
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
|
|
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(
|
|
245
|
-
const outputPath = options.outputPath || path_1.default.join(process.cwd(),
|
|
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
|
-
|
|
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
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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; } });
|
|
@@ -40,11 +45,9 @@ Object.defineProperty(exports, "filterArrayByGlobMatchersSet", { enumerable: tru
|
|
|
40
45
|
Object.defineProperty(exports, "generateProjectFilters", { enumerable: true, get: function () { return utils_1.generateProjectFilters; } });
|
|
41
46
|
Object.defineProperty(exports, "generateProjectFiltersV2", { enumerable: true, get: function () { return utils_1.generateProjectFiltersV2; } });
|
|
42
47
|
function getSummaryJsonPath(repoDir) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
return fs_1.default.existsSync(
|
|
46
|
-
? pathForPlaywright147
|
|
47
|
-
: pathForOtherPlaywrightVersions;
|
|
48
|
+
const newPath = path_1.default.join(repoDir, "playwright-report", "summary.json");
|
|
49
|
+
const legacyPath = path_1.default.join(repoDir, "summary.json");
|
|
50
|
+
return fs_1.default.existsSync(newPath) ? newPath : legacyPath;
|
|
48
51
|
}
|
|
49
52
|
async function runSingleTest({ testName, suites, filePath, projects, envOverrides, repoDir, stdout, stderr, }) {
|
|
50
53
|
const testDir = "tests";
|
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
|
|
127
|
-
|
|
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":"html.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-reports/html.ts"],"names":[],"mappings":"AAaA,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-reports/html.ts"],"names":[],"mappings":"AAaA,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,IAAI,CAAC,CAsIf"}
|
|
@@ -24,7 +24,11 @@ async function patchMergedHtmlReport(htmlFilePath, summaryJsonFilePath) {
|
|
|
24
24
|
// Ensure attachment.name includes a file extension so that
|
|
25
25
|
// Playwright's downloadFileNameForAttachment() uses it as-is
|
|
26
26
|
// instead of parsing the (now URL) path for an extension.
|
|
27
|
-
|
|
27
|
+
// Exception: keep "trace" as-is because the HTML reporter checks
|
|
28
|
+
// `attachment.name === 'trace'` to show the "View Trace" button.
|
|
29
|
+
if (attachment.path &&
|
|
30
|
+
attachment.name !== "trace" &&
|
|
31
|
+
!attachment.name.includes(".")) {
|
|
28
32
|
const ext = attachment.path.match(/\.[^/.]+$/)?.[0];
|
|
29
33
|
if (ext) {
|
|
30
34
|
return { ...attachment, name: attachment.name + ext };
|
|
@@ -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;AA0DlE,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,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,CA+B/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,CA+EhC"}
|
|
@@ -75,7 +75,7 @@ async function runPlaywrightMergeReports(options) {
|
|
|
75
75
|
envOverrides: {
|
|
76
76
|
PLAYWRIGHT_HTML_OPEN: "never",
|
|
77
77
|
PLAYWRIGHT_HTML_OUTPUT_DIR: outputDir,
|
|
78
|
-
PLAYWRIGHT_JSON_OUTPUT_NAME: path_1.default.join(cwd, "summary.json"),
|
|
78
|
+
PLAYWRIGHT_JSON_OUTPUT_NAME: path_1.default.join(cwd, "playwright-report", "summary.json"),
|
|
79
79
|
},
|
|
80
80
|
captureOutput: false,
|
|
81
81
|
throwOnError: true,
|
|
@@ -116,7 +116,7 @@ async function uploadMergedReports(cwd, outputDir, uploadOptions) {
|
|
|
116
116
|
const { projectName, runId, baseUrl, uploadBucket, credentials } = uploadOptions;
|
|
117
117
|
const destinationDir = path_1.default.join(projectName, runId);
|
|
118
118
|
const htmlFilePath = path_1.default.join(outputDir, "index.html");
|
|
119
|
-
const jsonFilePath = path_1.default.join(
|
|
119
|
+
const jsonFilePath = path_1.default.join(outputDir, "summary.json");
|
|
120
120
|
if (fs_1.default.existsSync(htmlFilePath)) {
|
|
121
121
|
logger_1.logger.debug(`[Merge Reports] Uploading HTML report`);
|
|
122
122
|
const task = (0, r2_uploader_1.createUploadTask)({
|
|
@@ -132,7 +132,7 @@ async function uploadMergedReports(cwd, outputDir, uploadOptions) {
|
|
|
132
132
|
if (fs_1.default.existsSync(jsonFilePath)) {
|
|
133
133
|
logger_1.logger.debug(`[Merge Reports] Uploading summary.json`);
|
|
134
134
|
const task = (0, r2_uploader_1.createUploadTask)({
|
|
135
|
-
sourceDir:
|
|
135
|
+
sourceDir: outputDir,
|
|
136
136
|
fileList: [jsonFilePath],
|
|
137
137
|
destinationDir,
|
|
138
138
|
uploadBucket,
|
|
@@ -193,7 +193,7 @@ async function mergeReports(options) {
|
|
|
193
193
|
return { success: false };
|
|
194
194
|
}
|
|
195
195
|
const htmlFilePath = path_1.default.join(outputDir, "index.html");
|
|
196
|
-
const jsonFilePath = path_1.default.join(
|
|
196
|
+
const jsonFilePath = path_1.default.join(outputDir, "summary.json");
|
|
197
197
|
// Patch summary.json first (replaces local paths with URLs),
|
|
198
198
|
// then patch the HTML report (reads the patched summary.json to build an attachment map).
|
|
199
199
|
await (0, json_1.patchSummaryJson)(jsonFilePath, urlMappings);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-specific-test.d.ts","sourceRoot":"","sources":["../../src/lib/run-specific-test.ts"],"names":[],"mappings":"
|
|
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
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
suites: testCase.suites,
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
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.
|
|
40
|
+
"@empiricalrun/reporter": "^0.28.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@playwright/test": "1.57.0",
|
|
@@ -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
|
+
});
|