feat(test runner): expose FullConfig.actualWorkers
This property is used by reporters, so stop plumbing it through metadata.
This commit is contained in:
parent
ce2b138eeb
commit
f474eab1e4
|
|
@ -4,6 +4,14 @@
|
|||
|
||||
Resolved configuration which is accessible via [`property: TestInfo.config`] and is passed to the test reporters. To see the format of Playwright configuration file, please see [TestConfig] instead.
|
||||
|
||||
## property: FullConfig.actualWorkers
|
||||
* since: v1.46
|
||||
- type: <[int]>
|
||||
|
||||
The actual number of worker processes used for running tests. This number depends on the maximum number of workers specified in [`property: TestConfig.workers`], the list of running tests and their mode of parallel or sequential execution.
|
||||
|
||||
This property is only available after tests have started running.
|
||||
|
||||
## property: FullConfig.configFile
|
||||
* since: v1.20
|
||||
- type: ?<[string]>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export class FullConfigInternal {
|
|||
this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p }));
|
||||
|
||||
this.config = {
|
||||
actualWorkers: 0,
|
||||
configFile: resolvedConfigFile,
|
||||
rootDir: pathResolve(configDir, userConfig.testDir) || configDir,
|
||||
forbidOnly: takeFirst(configCLIOverrides.forbidOnly, userConfig.forbidOnly, false),
|
||||
|
|
@ -134,6 +135,10 @@ export class FullConfigInternal {
|
|||
this.config.projects = this.projects.map(p => p.project);
|
||||
}
|
||||
|
||||
setActualWorkers(actualWorkers: number) {
|
||||
this.config.actualWorkers = actualWorkers;
|
||||
}
|
||||
|
||||
private _assignUniqueProjectIds(projects: FullProjectInternal[]) {
|
||||
const usedNames = new Set();
|
||||
for (const p of projects) {
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export async function deserializeConfig(data: SerializedConfig): Promise<FullCon
|
|||
addToCompilationCache(data.compilationCache);
|
||||
|
||||
const config = await loadConfig(data.location, data.configCLIOverrides);
|
||||
config.setActualWorkers(data.actualWorkers);
|
||||
await initializeEsmLoader();
|
||||
return config;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ export type ConfigCLIOverrides = {
|
|||
export type SerializedConfig = {
|
||||
location: ConfigLocation;
|
||||
configCLIOverrides: ConfigCLIOverrides;
|
||||
// TODO: create a struct to hold this and more properties like cliProjectFilter.
|
||||
actualWorkers: number;
|
||||
compilationCache?: SerializedCompilationCache;
|
||||
};
|
||||
|
||||
|
|
@ -131,6 +133,7 @@ export function serializeConfig(config: FullConfigInternal, passCompilationCache
|
|||
const result: SerializedConfig = {
|
||||
location: { configDir: config.configDir, resolvedConfigFile: config.config.configFile },
|
||||
configCLIOverrides: config.configCLIOverrides,
|
||||
actualWorkers: config.config.actualWorkers,
|
||||
compilationCache: passCompilationCache ? serializeCompilationCache() : undefined,
|
||||
};
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export type JsonStackFrame = { file: string, line: number, column: number };
|
|||
export type JsonStdIOType = 'stdout' | 'stderr';
|
||||
|
||||
export type JsonConfig = Pick<reporterTypes.FullConfig, 'configFile' | 'globalTimeout' | 'maxFailures' | 'metadata' | 'rootDir' | 'version' | 'workers'>;
|
||||
export type JsonBeginParams = { actualWorkers: number };
|
||||
|
||||
export type JsonPattern = {
|
||||
s?: string;
|
||||
|
|
@ -162,7 +163,7 @@ export class TeleReporterReceiver {
|
|||
return;
|
||||
}
|
||||
if (method === 'onBegin') {
|
||||
this._onBegin();
|
||||
this._onBegin(params);
|
||||
return;
|
||||
}
|
||||
if (method === 'onTestBegin') {
|
||||
|
|
@ -213,7 +214,8 @@ export class TeleReporterReceiver {
|
|||
this._mergeSuiteInto(suite, projectSuite);
|
||||
}
|
||||
|
||||
private _onBegin() {
|
||||
private _onBegin(params: JsonBeginParams) {
|
||||
this._config.actualWorkers = params.actualWorkers;
|
||||
this._reporter.onBegin?.(this._rootSuite);
|
||||
}
|
||||
|
||||
|
|
@ -578,6 +580,7 @@ export class TeleTestResult implements reporterTypes.TestResult {
|
|||
export type TeleFullProject = reporterTypes.FullProject;
|
||||
|
||||
export const baseFullConfig: reporterTypes.FullConfig = {
|
||||
actualWorkers: 0,
|
||||
forbidOnly: false,
|
||||
fullyParallel: false,
|
||||
globalSetup: null,
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ export class BaseReporter implements ReporterV2 {
|
|||
}
|
||||
|
||||
protected generateStartingMessage() {
|
||||
const jobs = this.config.metadata.actualWorkers ?? this.config.workers;
|
||||
const jobs = this.config.actualWorkers ?? this.config.workers;
|
||||
const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : '';
|
||||
if (!this.totalTestCount)
|
||||
return '';
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ type BlobReporterOptions = {
|
|||
_commandHash: string;
|
||||
};
|
||||
|
||||
export const currentBlobReportVersion = 2;
|
||||
export const currentBlobReportVersion = 3;
|
||||
|
||||
export type BlobReportMetadata = {
|
||||
version: number;
|
||||
|
|
@ -49,7 +49,6 @@ export class BlobReporter extends TeleReporterEmitter {
|
|||
private readonly _attachments: { originalPath: string, zipEntryPath: string }[] = [];
|
||||
private readonly _options: BlobReporterOptions;
|
||||
private readonly _salt: string;
|
||||
private _config!: FullConfig;
|
||||
|
||||
constructor(options: BlobReporterOptions) {
|
||||
super(message => this._messages.push(message));
|
||||
|
|
@ -72,7 +71,6 @@ export class BlobReporter extends TeleReporterEmitter {
|
|||
params: metadata
|
||||
});
|
||||
|
||||
this._config = config;
|
||||
super.onConfigure(config);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import type { ReporterDescription } from '../../types/test';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import type { JsonConfig, JsonEvent, JsonFullResult, JsonLocation, JsonProject, JsonSuite, JsonTestCase, JsonTestResultEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||
import type { JsonBeginParams, JsonConfig, JsonEvent, JsonFullResult, JsonLocation, JsonProject, JsonSuite, JsonTestCase, JsonTestResultEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||
import { TeleReporterReceiver } from '../isomorphic/teleReceiver';
|
||||
import { JsonStringInternalizer, StringInternPool } from '../isomorphic/stringInternPool';
|
||||
import { createReporters } from '../runner/reporters';
|
||||
|
|
@ -172,6 +172,7 @@ async function mergeEvents(dir: string, shardReportFiles: string[], stringPool:
|
|||
|
||||
const configureEvents: JsonEvent[] = [];
|
||||
const projectEvents: JsonEvent[] = [];
|
||||
const beginEvents: JsonEvent[] = [];
|
||||
const endEvents: JsonEvent[] = [];
|
||||
|
||||
const blobs = await extractAndParseReports(dir, shardReportFiles, internalizer, printStatus);
|
||||
|
|
@ -214,6 +215,8 @@ async function mergeEvents(dir: string, shardReportFiles: string[], stringPool:
|
|||
for (const event of parsedEvents) {
|
||||
if (event.method === 'onConfigure')
|
||||
configureEvents.push(event);
|
||||
else if (event.method === 'onBegin')
|
||||
beginEvents.push(event);
|
||||
else if (event.method === 'onProject')
|
||||
projectEvents.push(event);
|
||||
else if (event.method === 'onEnd')
|
||||
|
|
@ -232,7 +235,7 @@ async function mergeEvents(dir: string, shardReportFiles: string[], stringPool:
|
|||
prologue: [
|
||||
mergeConfigureEvents(configureEvents, rootDirOverride),
|
||||
...projectEvents,
|
||||
{ method: 'onBegin', params: undefined },
|
||||
mergeBeginEvents(beginEvents),
|
||||
],
|
||||
reports,
|
||||
epilogue: [
|
||||
|
|
@ -288,6 +291,20 @@ function mergeConfigureEvents(configureEvents: JsonEvent[], rootDirOverride: str
|
|||
};
|
||||
}
|
||||
|
||||
function mergeBeginEvents(beginEvents: JsonEvent[]): JsonEvent {
|
||||
let actualWorkers = 0;
|
||||
for (const event of beginEvents) {
|
||||
const params = event.params as JsonBeginParams;
|
||||
actualWorkers += params.actualWorkers;
|
||||
}
|
||||
return {
|
||||
method: 'onBegin',
|
||||
params: {
|
||||
actualWorkers,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mergeConfigs(to: JsonConfig, from: JsonConfig): JsonConfig {
|
||||
return {
|
||||
...to,
|
||||
|
|
@ -295,7 +312,6 @@ function mergeConfigs(to: JsonConfig, from: JsonConfig): JsonConfig {
|
|||
metadata: {
|
||||
...to.metadata,
|
||||
...from.metadata,
|
||||
actualWorkers: (to.metadata.actualWorkers || 0) + (from.metadata.actualWorkers || 0),
|
||||
},
|
||||
workers: to.workers + from.workers,
|
||||
};
|
||||
|
|
@ -548,6 +564,8 @@ class JsonEventPatchers {
|
|||
}
|
||||
|
||||
class BlobModernizer {
|
||||
private _2to3actualWorkers?: number;
|
||||
|
||||
modernize(fromVersion: number, events: JsonEvent[]): JsonEvent[] {
|
||||
const result = [];
|
||||
for (const event of events)
|
||||
|
|
@ -577,6 +595,23 @@ class BlobModernizer {
|
|||
return event;
|
||||
});
|
||||
}
|
||||
|
||||
_modernize_2_to_3(events: JsonEvent[]): JsonEvent[] {
|
||||
// Migrate from onConfigure.config.metadata.actualWorkers to onBegin.actualWorkers.
|
||||
return events.map(event => {
|
||||
if (event.method === 'onConfigure') {
|
||||
const config = event.params.config as JsonConfig;
|
||||
this._2to3actualWorkers = config.metadata.actualWorkers;
|
||||
delete config.metadata.actualWorkers;
|
||||
}
|
||||
if (event.method === 'onBegin') {
|
||||
event.params = event.params || {};
|
||||
if (event.params.actualWorkers === undefined && this._2to3actualWorkers !== undefined)
|
||||
(event.params as JsonBeginParams).actualWorkers = this._2to3actualWorkers;
|
||||
}
|
||||
return event;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const modernizer = new BlobModernizer();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export type TeleReporterEmitterOptions = {
|
|||
|
||||
export class TeleReporterEmitter implements ReporterV2 {
|
||||
private _messageSink: (message: teleReceiver.JsonEvent) => void;
|
||||
private _rootDir!: string;
|
||||
protected _config!: reporterTypes.FullConfig;
|
||||
private _emitterOptions: TeleReporterEmitterOptions;
|
||||
// In case there is blob reporter and UI mode, make sure one does override
|
||||
// the id assigned by the other.
|
||||
|
|
@ -46,7 +46,7 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
}
|
||||
|
||||
onConfigure(config: reporterTypes.FullConfig) {
|
||||
this._rootDir = config.rootDir;
|
||||
this._config = config;
|
||||
this._messageSink({ method: 'onConfigure', params: { config: this._serializeConfig(config) } });
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +54,8 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite));
|
||||
for (const project of projects)
|
||||
this._messageSink({ method: 'onProject', params: { project } });
|
||||
this._messageSink({ method: 'onBegin', params: undefined });
|
||||
const beginParams: teleReceiver.JsonBeginParams = { actualWorkers: this._config.actualWorkers };
|
||||
this._messageSink({ method: 'onBegin', params: beginParams });
|
||||
}
|
||||
|
||||
onTestBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult): void {
|
||||
|
|
@ -280,6 +281,6 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
private _relativePath(absolutePath?: string): string | undefined {
|
||||
if (!absolutePath)
|
||||
return absolutePath;
|
||||
return path.relative(this._rootDir, absolutePath);
|
||||
return path.relative(this._config.rootDir, absolutePath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ function createPhasesTask(): Task<TestRun> {
|
|||
}
|
||||
}
|
||||
|
||||
testRun.config.config.metadata.actualWorkers = Math.min(testRun.config.config.workers, maxConcurrentTestGroups);
|
||||
testRun.config.setActualWorkers(Math.min(testRun.config.config.workers, maxConcurrentTestGroups));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
9
packages/playwright/types/test.d.ts
vendored
9
packages/playwright/types/test.d.ts
vendored
|
|
@ -1707,6 +1707,15 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
|
|||
* See [testConfig.webServer](https://playwright.dev/docs/api/class-testconfig#test-config-web-server).
|
||||
*/
|
||||
webServer: TestConfigWebServer | null;
|
||||
/**
|
||||
* The actual number of worker processes used for running tests. This number depends on the maximum number of workers
|
||||
* specified in [testConfig.workers](https://playwright.dev/docs/api/class-testconfig#test-config-workers), the list
|
||||
* of running tests and their mode of parallel or sequential execution.
|
||||
*
|
||||
* This property is only available after tests have started running.
|
||||
*/
|
||||
actualWorkers: number;
|
||||
|
||||
/**
|
||||
* Path to the configuration file used to run the tests. The value is an empty string if no config file was used.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -661,3 +661,28 @@ test('should merge ct configs', async ({ runInlineTest }) => {
|
|||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should expose actualWorkers', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'playwright.config.ts': `
|
||||
module.exports = { workers: 42 };
|
||||
`,
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('pass', async ({}) => {
|
||||
expect(test.info().config.actualWorkers).toBe(2);
|
||||
});
|
||||
`,
|
||||
'b.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('pass', async ({}) => {
|
||||
expect(test.info().config.actualWorkers).toBe(2);
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(2);
|
||||
expect(result.output).toContain('Running 2 tests using 2 workers');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1421,7 +1421,7 @@ test('blob report should include version', async ({ runInlineTest }) => {
|
|||
|
||||
const events = await extractReport(test.info().outputPath('blob-report', 'report.zip'), test.info().outputPath('tmp'));
|
||||
const metadataEvent = events.find(e => e.method === 'onBlobReportMetadata');
|
||||
expect(metadataEvent.params.version).toBe(2);
|
||||
expect(metadataEvent.params.version).toBe(3);
|
||||
expect(metadataEvent.params.userAgent).toBe(getUserAgent());
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue