Skip to content

Commit b82aa49

Browse files
authored
chore: merge FailureTracker into TestRun, pull more into TestRunOptions (#40150)
1 parent 654bd19 commit b82aa49

8 files changed

Lines changed: 102 additions & 182 deletions

File tree

packages/playwright/src/cli/testActions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export async function runTests(args: string[], opts: { [key: string]: any }) {
4141
lastFailed: !!opts.lastFailed,
4242
testList: opts.testList ? path.resolve(process.cwd(), opts.testList) : undefined,
4343
testListInvert: opts.testListInvert ? path.resolve(process.cwd(), opts.testListInvert) : undefined,
44+
shardWeights: resolveShardWeightsOption(),
4445
};
4546

4647
// Evaluate project filters against config before starting execution. This enables a consistent error message across run modes
@@ -124,7 +125,6 @@ function overridesFromOptions(options: { [key: string]: any }): ipc.ConfigCLIOve
124125
retries: options.retries ? parseInt(options.retries, 10) : undefined,
125126
reporter: resolveReporterOption(options.reporter),
126127
shard: resolveShardOption(options.shard),
127-
shardWeights: resolveShardWeightsOption(),
128128
timeout: options.timeout ? parseInt(options.timeout, 10) : undefined,
129129
tsconfig: options.tsconfig ? path.resolve(process.cwd(), options.tsconfig) : undefined,
130130
ignoreSnapshots: options.ignoreSnapshots ? !!options.ignoreSnapshots : undefined,
@@ -195,7 +195,7 @@ function resolveShardOption(shard?: string): ipc.ConfigCLIOverrides['shard'] {
195195
return { current, total };
196196
}
197197

198-
function resolveShardWeightsOption(): ipc.ConfigCLIOverrides['shardWeights'] {
198+
function resolveShardWeightsOption(): TestRunOptions['shardWeights'] {
199199
const shardWeights = process.env.PWTEST_SHARD_WEIGHTS;
200200
if (!shardWeights)
201201
return undefined;

packages/playwright/src/common/ipc.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,12 @@ export type ConfigCLIOverrides = {
3030
globalTimeout?: number;
3131
maxFailures?: number;
3232
outputDir?: string;
33-
preserveOutputDir?: boolean;
3433
pause?: boolean;
3534
quiet?: boolean;
3635
repeatEach?: number;
3736
retries?: number;
3837
reporter?: ReporterDescription[];
39-
additionalReporters?: ReporterDescription[];
4038
shard?: { current: number, total: number };
41-
shardWeights?: number[];
4239
timeout?: number;
4340
tsconfig?: string;
4441
ignoreSnapshots?: boolean;

packages/playwright/src/runner/dispatcher.ts

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,15 @@ import { eventsHelper } from '@utils/eventsHelper';
2020

2121
import { addSuggestedRebaseline } from './rebase';
2222
import { WorkerHost } from './workerHost';
23-
import { FullConfigInternal, ipc, test as testNs } from '../common';
23+
import { ipc, test as testNs } from '../common';
2424
import { addLocationAndSnippetToError } from '../reporters/internalReporter';
2525
import { serializeError } from '../util';
2626

2727
import type { RegisteredListener } from '@utils/eventsHelper';
28-
import type { FailureTracker } from './failureTracker';
2928
import type { ProcessExitData } from './processHost';
29+
import type { TestRun } from './tasks';
3030
import type { TestGroup } from './testGroups';
3131
import type { TestError, TestResult, TestStep } from '../../types/testReporter';
32-
import type { ReporterV2 } from '../reporters/reporterV2';
3332

3433
export type EnvByProjectId = Map<string, Record<string, string | undefined>>;
3534

@@ -42,18 +41,14 @@ export class Dispatcher {
4241
private _finished = new ManualPromise<void>();
4342
private _isStopped = true;
4443

45-
private _config: FullConfigInternal;
46-
private _reporter: ReporterV2;
47-
private _failureTracker: FailureTracker;
44+
private _testRun: TestRun;
4845

4946
private _extraEnvByProjectId: EnvByProjectId = new Map();
5047
private _producedEnvByProjectId: EnvByProjectId = new Map();
5148

52-
constructor(config: FullConfigInternal, reporter: ReporterV2, failureTracker: FailureTracker) {
53-
this._config = config;
54-
this._reporter = reporter;
55-
this._failureTracker = failureTracker;
56-
for (const project of config.projects) {
49+
constructor(testRun: TestRun) {
50+
this._testRun = testRun;
51+
for (const project of testRun.config.projects) {
5752
if (project.workers)
5853
this._workerLimitPerProjectId.set(project.id, project.workers);
5954
}
@@ -97,7 +92,7 @@ export class Dispatcher {
9792

9893
// 3. Claim both the job and the worker slot.
9994
this._queue.splice(jobIndex, 1);
100-
const jobDispatcher = new JobDispatcher(job, this._config, this._reporter, this._failureTracker, () => this.stop().catch(() => {}));
95+
const jobDispatcher = new JobDispatcher(job, this._testRun, () => this.stop().catch(() => {}));
10196
this._workerSlots[workerIndex].jobDispatcher = jobDispatcher;
10297

10398
// 4. Run the job. This is the only async operation.
@@ -132,7 +127,7 @@ export class Dispatcher {
132127
// 2. Start the worker if it is down.
133128
let startError;
134129
if (!worker) {
135-
worker = this._createWorker(job, index, ipc.serializeConfig(this._config, true));
130+
worker = this._createWorker(job, index, ipc.serializeConfig(this._testRun.config, true));
136131
this._workerSlots[index].worker = worker;
137132
worker.on('exit', () => this._workerSlots[index].worker = undefined);
138133
startError = await worker.start();
@@ -198,10 +193,10 @@ export class Dispatcher {
198193
this._isStopped = false;
199194
this._workerSlots = [];
200195
// 0. Stop right away if we have reached max failures.
201-
if (this._failureTracker.hasReachedMaxFailures())
196+
if (this._testRun.hasReachedMaxFailures())
202197
void this.stop();
203198
// 1. Allocate workers.
204-
for (let i = 0; i < this._config.config.workers; i++)
199+
for (let i = 0; i < this._testRun.config.config.workers; i++)
205200
this._workerSlots.push({});
206201
// 2. Schedule enough jobs.
207202
for (let i = 0; i < this._workerSlots.length; i++)
@@ -213,15 +208,15 @@ export class Dispatcher {
213208
}
214209

215210
_createWorker(testGroup: TestGroup, parallelIndex: number, loaderData: ipc.SerializedConfig) {
216-
const projectConfig = this._config.projects.find(p => p.id === testGroup.projectId)!;
217-
const outputDir = projectConfig.project.outputDir;
211+
const project = this._testRun.config.projects.find(p => p.id === testGroup.projectId)!;
212+
const pauseAtEnd = this._testRun.topLevelProjects.includes(project) && !!this._testRun.options.pauseAtEnd;
218213
const worker = new WorkerHost(testGroup, {
219214
parallelIndex,
220215
config: loaderData,
221216
extraEnv: this._extraEnvByProjectId.get(testGroup.projectId) || {},
222-
outputDir,
223-
pauseOnError: this._failureTracker.pauseOnError(),
224-
pauseAtEnd: this._failureTracker.pauseAtEnd(projectConfig),
217+
outputDir: project.project.outputDir,
218+
pauseOnError: !!this._testRun.options.pauseOnError,
219+
pauseAtEnd,
225220
});
226221
const handleOutput = (params: ipc.TestOutputPayload) => {
227222
const chunk = chunkFromParams(params);
@@ -239,17 +234,17 @@ export class Dispatcher {
239234
worker.on('stdOut', (params: ipc.TestOutputPayload) => {
240235
const { chunk, test, result } = handleOutput(params);
241236
result?.stdout.push(chunk);
242-
this._reporter.onStdOut?.(chunk, test, result);
237+
this._testRun.reporter.onStdOut?.(chunk, test, result);
243238
});
244239
worker.on('stdErr', (params: ipc.TestOutputPayload) => {
245240
const { chunk, test, result } = handleOutput(params);
246241
result?.stderr.push(chunk);
247-
this._reporter.onStdErr?.(chunk, test, result);
242+
this._testRun.reporter.onStdErr?.(chunk, test, result);
248243
});
249244
worker.on('teardownErrors', (params: ipc.TeardownErrorsPayload) => {
250-
this._failureTracker.onWorkerError();
245+
this._testRun.hasWorkerErrors = true;
251246
for (const error of params.fatalErrors)
252-
this._reporter.onError?.(error);
247+
this._testRun.reporter.onError?.(error);
253248
});
254249
worker.on('exit', () => {
255250
const producedEnv = this._producedEnvByProjectId.get(testGroup.projectId) || {};
@@ -275,9 +270,7 @@ class JobDispatcher {
275270
jobResult = new ManualPromise<{ newJob?: TestGroup, didFail: boolean }>();
276271

277272
readonly job: TestGroup;
278-
private _config: FullConfigInternal;
279-
private _reporter: ReporterV2;
280-
private _failureTracker: FailureTracker;
273+
private _testRun: TestRun;
281274
private _stopCallback: () => void;
282275
private _listeners: RegisteredListener[] = [];
283276
private _failedTests = new Set<testNs.TestCase>();
@@ -288,11 +281,9 @@ class JobDispatcher {
288281
private _workerIndex = 0;
289282
private _currentlyRunning: { test: testNs.TestCase, result: TestResult } | undefined;
290283

291-
constructor(job: TestGroup, config: FullConfigInternal, reporter: ReporterV2, failureTracker: FailureTracker, stopCallback: () => void) {
284+
constructor(job: TestGroup, testRun: TestRun, stopCallback: () => void) {
292285
this.job = job;
293-
this._config = config;
294-
this._reporter = reporter;
295-
this._failureTracker = failureTracker;
286+
this._testRun = testRun;
296287
this._stopCallback = stopCallback;
297288
this._remainingByTestId = new Map(this.job.tests.map(e => [e.id, e]));
298289
}
@@ -308,12 +299,12 @@ class JobDispatcher {
308299
result.parallelIndex = this._parallelIndex;
309300
result.workerIndex = this._workerIndex;
310301
result.startTime = new Date(params.startWallTime);
311-
this._reporter.onTestBegin?.(test, result);
302+
this._testRun.reporter.onTestBegin?.(test, result);
312303
this._currentlyRunning = { test, result };
313304
}
314305

315306
private _onTestEnd(params: ipc.TestEndPayload) {
316-
if (this._failureTracker.hasReachedMaxFailures()) {
307+
if (this._testRun.hasReachedMaxFailures()) {
317308
// Do not show more than one error to avoid confusion, but report
318309
// as interrupted to indicate that we did actually start the test.
319310
params.status = 'interrupted';
@@ -377,7 +368,7 @@ class JobDispatcher {
377368
};
378369
steps.set(params.stepId, step);
379370
(parentStep || result).steps.push(step);
380-
this._reporter.onStepBegin?.(test, result, step);
371+
this._testRun.reporter.onStepBegin?.(test, result, step);
381372
}
382373

383374
private _onStepEnd(params: ipc.StepEndPayload) {
@@ -389,7 +380,7 @@ class JobDispatcher {
389380
const { result, steps, test } = data;
390381
const step = steps.get(params.stepId);
391382
if (!step) {
392-
this._reporter.onStdErr?.('Internal error: step end without step begin: ' + params.stepId, test, result);
383+
this._testRun.reporter.onStdErr?.('Internal error: step end without step begin: ' + params.stepId, test, result);
393384
return;
394385
}
395386
step.duration = params.wallTime - step.startTime.getTime();
@@ -399,7 +390,7 @@ class JobDispatcher {
399390
addSuggestedRebaseline(step.location!, params.suggestedRebaseline);
400391
step.annotations = params.annotations;
401392
steps.delete(params.stepId);
402-
this._reporter.onStepEnd?.(test, result, step);
393+
this._testRun.reporter.onStepEnd?.(test, result, step);
403394
}
404395

405396
private _onAttach(params: ipc.AttachmentPayload) {
@@ -420,7 +411,7 @@ class JobDispatcher {
420411
if (step)
421412
step.attachments.push(attachment);
422413
else
423-
this._reporter.onStdErr?.('Internal error: step id not found: ' + params.stepId);
414+
this._testRun.reporter.onStdErr?.('Internal error: step id not found: ' + params.stepId);
424415
}
425416
}
426417

@@ -432,7 +423,7 @@ class JobDispatcher {
432423
result = runData.result;
433424
} else {
434425
result = test._appendTestResult();
435-
this._reporter.onTestBegin?.(test, result);
426+
this._testRun.reporter.onTestBegin?.(test, result);
436427
}
437428
result.errors = [...errors];
438429
result.error = result.errors[0];
@@ -445,7 +436,7 @@ class JobDispatcher {
445436
for (const test of this._remainingByTestId.values()) {
446437
if (!testIds.has(test.id))
447438
continue;
448-
if (!this._failureTracker.hasReachedMaxFailures()) {
439+
if (!this._testRun.hasReachedMaxFailures()) {
449440
this._failTestWithErrors(test, errors);
450441
errors = []; // Only report errors for the first test.
451442
}
@@ -454,9 +445,9 @@ class JobDispatcher {
454445
if (errors.length) {
455446
// We had fatal errors after all tests have passed - most likely in some teardown.
456447
// Let's just fail the test run.
457-
this._failureTracker.onWorkerError();
448+
this._testRun.hasWorkerErrors = true;
458449
for (const error of errors)
459-
this._reporter.onError?.(error);
450+
this._testRun.reporter.onError?.(error);
460451
}
461452
}
462453

@@ -592,11 +583,11 @@ class JobDispatcher {
592583
throw new Error('Test has already stopped');
593584
const response = await worker.sendCustomMessage({ testId: test.id, request: message.request });
594585
if (response.error)
595-
addLocationAndSnippetToError(this._config.config, response.error);
586+
addLocationAndSnippetToError(this._testRun.config.config, response.error);
596587
return response;
597588
} catch (e) {
598589
const error = serializeError(e);
599-
addLocationAndSnippetToError(this._config.config, error);
590+
addLocationAndSnippetToError(this._testRun.config.config, error);
600591
return { response: undefined, error };
601592
}
602593
};
@@ -605,10 +596,10 @@ class JobDispatcher {
605596
result.errors = params.errors;
606597
result.error = result.errors[0];
607598

608-
void this._reporter.onTestPaused?.(test, result).then(() => {
599+
void this._testRun.reporter.onTestPaused?.(test, result).then(() => {
609600
worker.sendResume({});
610601
});
611-
this._failureTracker.onTestPaused?.({ ...params, sendMessage });
602+
this._testRun.onTestPaused({ ...params, sendMessage });
612603
}
613604

614605
skipWholeJob(): boolean {
@@ -620,10 +611,10 @@ class JobDispatcher {
620611
// with skipped tests mixed in-between non-skipped. This makes
621612
// for a better reporter experience.
622613
const allTestsSkipped = this.job.tests.every(test => test.expectedStatus === 'skipped');
623-
if (allTestsSkipped && !this._failureTracker.hasReachedMaxFailures()) {
614+
if (allTestsSkipped && !this._testRun.hasReachedMaxFailures()) {
624615
for (const test of this.job.tests) {
625616
const result = test._appendTestResult();
626-
this._reporter.onTestBegin?.(test, result);
617+
this._testRun.reporter.onTestBegin?.(test, result);
627618
result.status = 'skipped';
628619
// This must mirror _onTestEnd() above
629620
result.annotations = [...test.annotations];
@@ -639,13 +630,14 @@ class JobDispatcher {
639630
}
640631

641632
private _reportTestEnd(test: testNs.TestCase, result: TestResult) {
642-
this._reporter.onTestEnd?.(test, result);
643-
const hadMaxFailures = this._failureTracker.hasReachedMaxFailures();
644-
this._failureTracker.onTestEnd(test, result);
645-
if (this._failureTracker.hasReachedMaxFailures()) {
633+
this._testRun.reporter.onTestEnd?.(test, result);
634+
const hadMaxFailures = this._testRun.hasReachedMaxFailures();
635+
// Test is considered failing after the last retry.
636+
if (test.outcome() === 'unexpected' && test.results.length > test.retries)
637+
++this._testRun.failedTestCount;
638+
if (!hadMaxFailures && this._testRun.hasReachedMaxFailures()) {
646639
this._stopCallback();
647-
if (!hadMaxFailures)
648-
this._reporter.onError?.({ message: colors.red(`Testing stopped early after ${this._failureTracker.maxFailures()} maximum allowed failures.`) });
640+
this._testRun.reporter.onError?.({ message: colors.red(`Testing stopped early after ${this._testRun.config.config.maxFailures} maximum allowed failures.`) });
649641
}
650642
}
651643
}

0 commit comments

Comments
 (0)