@dionlarson/playwright-orchestrator-core 1.3.1 → 1.3.3

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.
@@ -1,3 +1,4 @@
1
+ import { writeFileSync } from 'node:fs';
1
2
  export default class TestResultReporter {
2
3
  testResults = [];
3
4
  testCases = [];
@@ -58,6 +59,13 @@ export default class TestResultReporter {
58
59
  retry,
59
60
  })),
60
61
  };
61
- console.log(JSON.stringify(output));
62
+ const outputFile = process.env.PLAYWRIGHT_ORCHESTRATOR_RESULT_FILE;
63
+ if (outputFile) {
64
+ writeFileSync(outputFile, JSON.stringify(output));
65
+ }
66
+ else {
67
+ // Fallback to stdout if no file specified
68
+ console.log(JSON.stringify(output));
69
+ }
62
70
  }
63
71
  }
@@ -12,7 +12,6 @@ export declare class TestRunner {
12
12
  private runTestsUntilAvailable;
13
13
  private removePreviousReports;
14
14
  private runTest;
15
- private parseTestResult;
16
15
  private buildParams;
17
16
  private createTempConfig;
18
17
  }
@@ -1,11 +1,22 @@
1
1
  import { createHash } from 'node:crypto';
2
- import child_process from 'node:child_process';
2
+ import child_process, { spawn } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
- import { rm, writeFile } from 'node:fs/promises';
4
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
5
5
  import { TestExecutionReporter } from './reporters/test-execution-reporter.js';
6
6
  import path from 'node:path';
7
7
  import * as uuid from 'uuid';
8
8
  const exec = promisify(child_process.exec);
9
+ function spawnPassthrough(command, env) {
10
+ return new Promise((resolve, reject) => {
11
+ const proc = spawn(command, {
12
+ env,
13
+ shell: true,
14
+ stdio: 'inherit' // Full passthrough - all output goes directly to terminal
15
+ });
16
+ proc.on('close', (code) => resolve(code ?? 1));
17
+ proc.on('error', reject);
18
+ });
19
+ }
9
20
  export class TestRunner {
10
21
  adapter;
11
22
  runId;
@@ -17,11 +28,14 @@ export class TestRunner {
17
28
  this.outputFolder = options.output;
18
29
  }
19
30
  async runTests() {
31
+ console.log(`[orchestrator] Starting shard for run ${this.runId}`);
20
32
  await this.removePreviousReports();
21
33
  const config = await this.adapter.startShard(this.runId);
34
+ console.log(`[orchestrator] Shard started. Workers: ${config.workers}, Config: ${config.configFile || 'default'}`);
22
35
  config.configFile = await this.createTempConfig(config.configFile);
23
36
  try {
24
37
  await this.runTestsUntilAvailable(config);
38
+ console.log(`[orchestrator] All tests completed, finishing shard`);
25
39
  await this.adapter.finishShard(this.runId);
26
40
  await this.adapter.dispose();
27
41
  this.reporter.printSummary();
@@ -33,9 +47,14 @@ export class TestRunner {
33
47
  }
34
48
  async runTestsUntilAvailable(config) {
35
49
  const runningTests = new Set();
50
+ console.log(`[orchestrator] Fetching first test...`);
36
51
  let next = await this.adapter.getNextTest(this.runId, config);
52
+ console.log(`[orchestrator] First test: ${next ? `[${next.project}] ${next.file}:${next.position}` : 'none available'}`);
53
+ let testCount = 0;
37
54
  while (next || runningTests.size > 0) {
38
55
  if (next && runningTests.size < config.workers) {
56
+ testCount++;
57
+ console.log(`[orchestrator] Running test #${testCount}: [${next.project}] ${next.file}:${next.position}`);
39
58
  const testPromise = this.runTest(next, config).then(() => {
40
59
  runningTests.delete(testPromise);
41
60
  });
@@ -46,49 +65,61 @@ export class TestRunner {
46
65
  await Promise.race(runningTests);
47
66
  }
48
67
  }
68
+ console.log(`[orchestrator] Finished ${testCount} tests`);
49
69
  await Promise.all(runningTests);
50
70
  }
51
71
  async removePreviousReports() {
52
72
  await rm(`./${this.outputFolder}`, { recursive: true, force: true });
73
+ await mkdir(`./${this.outputFolder}`, { recursive: true });
53
74
  }
54
75
  async runTest(test, config) {
55
76
  const testPosition = `${test.file}:${test.position}`;
56
77
  const testName = `[${test.project}] > ${testPosition}`;
57
78
  const testHash = createHash('md5').update(testName).digest('hex');
79
+ const resultFile = `${this.outputFolder}/${testHash}.result.json`;
80
+ const args = [testPosition, ...this.buildParams(test, config, testHash).split(' ')];
81
+ const env = {
82
+ ...process.env,
83
+ PLAYWRIGHT_BLOB_OUTPUT_FILE: `${this.outputFolder}/${testHash}.zip`,
84
+ PLAYWRIGHT_ORCHESTRATOR_RESULT_FILE: resultFile,
85
+ };
86
+ const fullCommand = `npx playwright test ${args.join(' ')}`;
87
+ const exitCode = await spawnPassthrough(fullCommand, env);
88
+ let testResult;
58
89
  try {
59
- const run = exec(`npx playwright test ${testPosition} ${this.buildParams(test, config, testHash)}`, {
60
- env: {
61
- ...process.env,
62
- PLAYWRIGHT_BLOB_OUTPUT_FILE: `${this.outputFolder}/${testHash}.zip`,
63
- },
64
- });
65
- this.reporter.addTest(test, run);
66
- const { stdout } = await run;
90
+ const resultJson = await readFile(resultFile, 'utf-8');
91
+ testResult = JSON.parse(resultJson);
92
+ await rm(resultFile, { force: true });
93
+ }
94
+ catch (e) {
95
+ console.error(`[orchestrator] Failed to read test result for [${test.project}] ${test.file}:${test.position}`, e);
96
+ throw e;
97
+ }
98
+ if (exitCode === 0) {
99
+ console.log(`[orchestrator] Test passed: [${test.project}] ${test.file}:${test.position}`);
100
+ this.reporter.finishTest(test);
67
101
  await this.adapter.finishTest({
68
102
  runId: this.runId,
69
103
  test,
70
- testResult: this.parseTestResult(stdout),
104
+ testResult,
71
105
  config,
72
106
  });
73
107
  }
74
- catch (error) {
75
- if (!error.stdout)
76
- throw error;
108
+ else {
109
+ console.log(`[orchestrator] Test failed: [${test.project}] ${test.file}:${test.position}`);
110
+ this.reporter.failTest(test);
77
111
  await this.adapter.failTest({
78
112
  runId: this.runId,
79
113
  test,
80
- testResult: this.parseTestResult(error.stdout),
114
+ testResult,
81
115
  config,
82
116
  });
83
117
  }
84
118
  }
85
- parseTestResult(stdout) {
86
- return JSON.parse(stdout);
87
- }
88
119
  buildParams(test, config, testHash) {
89
120
  const args = [...config.args];
90
121
  args.push('--workers', '1');
91
- args.push('--reporter', 'blob,@dionlarson/playwright-orchestrator-core/test-result-reporter');
122
+ args.push('--reporter', 'list,blob,@dionlarson/playwright-orchestrator-core/test-result-reporter');
92
123
  args.push('--project', `"${test.project}"`);
93
124
  args.push('--output', `"${this.outputFolder}/${testHash}"`);
94
125
  if (config.configFile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dionlarson/playwright-orchestrator-core",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "Core lib and cli for Playwright test orchestration",
5
5
  "keywords": [
6
6
  "playwright",