mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(testrunner): expose test and runner config to fixtures (#3580)
This commit is contained in:
parent
f4e8f34c96
commit
4025f9f1ef
@ -17,6 +17,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import program from 'commander';
|
||||
import { reporters } from './reporters';
|
||||
import { installTransform } from './transform';
|
||||
import { Runner } from './runner';
|
||||
import { TestCollector } from './testCollector';
|
||||
@ -90,13 +91,15 @@ program
|
||||
grep: command.grep,
|
||||
jobs,
|
||||
outputDir: command.output,
|
||||
reporter: command.reporter,
|
||||
snapshotDir: path.join(testDir, '__snapshots__'),
|
||||
testDir,
|
||||
timeout: command.timeout,
|
||||
trialRun: command.trialRun,
|
||||
updateSnapshots: command.updateSnapshots
|
||||
});
|
||||
const reporterFactory = reporters[command.reporter || 'dot'];
|
||||
new reporterFactory(runner);
|
||||
|
||||
try {
|
||||
if (beforeFunction)
|
||||
await beforeFunction();
|
||||
|
@ -39,32 +39,21 @@ export function setParameters(params: any) {
|
||||
registerWorkerFixture(name as keyof WorkerState, async ({}, test) => await test(parameters[name] as never));
|
||||
}
|
||||
|
||||
type TestConfig = {
|
||||
outputDir: string;
|
||||
testDir: string;
|
||||
};
|
||||
|
||||
type TestResult = {
|
||||
success: boolean;
|
||||
test: Test;
|
||||
config: TestConfig;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
class Fixture {
|
||||
pool: FixturePool;
|
||||
class Fixture<Config> {
|
||||
pool: FixturePool<Config>;
|
||||
name: string;
|
||||
scope: any;
|
||||
fn: any;
|
||||
deps: any;
|
||||
usages: Set<unknown>;
|
||||
scope: string;
|
||||
fn: Function;
|
||||
deps: string[];
|
||||
usages: Set<string>;
|
||||
hasGeneratorValue: boolean;
|
||||
value: any;
|
||||
_teardownFenceCallback: (value?: unknown) => void;
|
||||
_tearDownComplete: any;
|
||||
_setup: boolean;
|
||||
_teardown: any;
|
||||
constructor(pool: FixturePool, name: string, scope: any, fn: any) {
|
||||
_tearDownComplete: Promise<void>;
|
||||
_setup = false;
|
||||
_teardown = false;
|
||||
|
||||
constructor(pool: FixturePool<Config>, name: string, scope: string, fn: any) {
|
||||
this.pool = pool;
|
||||
this.name = name;
|
||||
this.scope = scope;
|
||||
@ -75,11 +64,11 @@ class Fixture {
|
||||
this.value = this.hasGeneratorValue ? parameters[name] : null;
|
||||
}
|
||||
|
||||
async setup() {
|
||||
async setup(config: Config, test?: Test) {
|
||||
if (this.hasGeneratorValue)
|
||||
return;
|
||||
for (const name of this.deps) {
|
||||
await this.pool.setupFixture(name);
|
||||
await this.pool.setupFixture(name, config, test);
|
||||
this.pool.instances.get(name).usages.add(this.name);
|
||||
}
|
||||
|
||||
@ -95,12 +84,12 @@ class Fixture {
|
||||
this.value = value;
|
||||
setupFenceFulfill();
|
||||
return await teardownFence;
|
||||
}).catch((e: any) => setupFenceReject(e));
|
||||
}, config, test).catch((e: any) => setupFenceReject(e));
|
||||
await setupFence;
|
||||
this._setup = true;
|
||||
}
|
||||
|
||||
async teardown(testResult: TestResult) {
|
||||
async teardown() {
|
||||
if (this.hasGeneratorValue)
|
||||
return;
|
||||
if (this._teardown)
|
||||
@ -110,24 +99,24 @@ class Fixture {
|
||||
const fixture = this.pool.instances.get(name);
|
||||
if (!fixture)
|
||||
continue;
|
||||
await fixture.teardown(testResult);
|
||||
await fixture.teardown();
|
||||
}
|
||||
if (this._setup) {
|
||||
debug('pw:test:hook')(`teardown "${this.name}"`);
|
||||
this._teardownFenceCallback(testResult);
|
||||
this._teardownFenceCallback();
|
||||
}
|
||||
await this._tearDownComplete;
|
||||
this.pool.instances.delete(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
export class FixturePool {
|
||||
instances: Map<any, any>;
|
||||
export class FixturePool<Config> {
|
||||
instances: Map<string, Fixture<Config>>;
|
||||
constructor() {
|
||||
this.instances = new Map();
|
||||
}
|
||||
|
||||
async setupFixture(name: string) {
|
||||
async setupFixture(name: string, config: Config, test?: Test) {
|
||||
let fixture = this.instances.get(name);
|
||||
if (fixture)
|
||||
return fixture;
|
||||
@ -137,21 +126,21 @@ export class FixturePool {
|
||||
const { scope, fn } = registrations.get(name);
|
||||
fixture = new Fixture(this, name, scope, fn);
|
||||
this.instances.set(name, fixture);
|
||||
await fixture.setup();
|
||||
await fixture.setup(config, test);
|
||||
return fixture;
|
||||
}
|
||||
|
||||
async teardownScope(scope: string, testResult?: TestResult) {
|
||||
async teardownScope(scope: string) {
|
||||
for (const [name, fixture] of this.instances) {
|
||||
if (fixture.scope === scope)
|
||||
await fixture.teardown(testResult);
|
||||
await fixture.teardown();
|
||||
}
|
||||
}
|
||||
|
||||
async resolveParametersAndRun(fn: (arg0: {}) => any, timeout: number) {
|
||||
async resolveParametersAndRun(fn: (arg0: {}) => any, timeout: number, config: Config, test?: Test) {
|
||||
const names = fixtureParameterNames(fn);
|
||||
for (const name of names)
|
||||
await this.setupFixture(name);
|
||||
await this.setupFixture(name, config, test);
|
||||
const params = {};
|
||||
for (const n of names)
|
||||
params[n] = this.instances.get(n).value;
|
||||
@ -167,19 +156,14 @@ export class FixturePool {
|
||||
]);
|
||||
}
|
||||
|
||||
wrapTestCallback(callback: any, timeout: number, test: Test, config: TestConfig) {
|
||||
wrapTestCallback(callback: any, timeout: number, config: Config, test: Test) {
|
||||
if (!callback)
|
||||
return callback;
|
||||
const testResult: TestResult = { success: true, test, config };
|
||||
return async() => {
|
||||
try {
|
||||
await this.resolveParametersAndRun(callback, timeout);
|
||||
} catch (e) {
|
||||
testResult.success = false;
|
||||
testResult.error = e;
|
||||
throw e;
|
||||
await this.resolveParametersAndRun(callback, timeout, config, test);
|
||||
} finally {
|
||||
await this.teardownScope('test', testResult);
|
||||
await this.teardownScope('test');
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -205,7 +189,7 @@ export function fixturesForCallback(callback: any): string[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
function fixtureParameterNames(fn: { toString: () => any; }) {
|
||||
function fixtureParameterNames(fn: { toString: () => any; }): string[] {
|
||||
const text = fn.toString();
|
||||
const match = text.match(/async(?:\s+function)?\s*\(\s*{\s*([^}]*)\s*}/);
|
||||
if (!match || !match[1].trim())
|
||||
@ -214,10 +198,10 @@ function fixtureParameterNames(fn: { toString: () => any; }) {
|
||||
return signature.split(',').map((t: string) => t.trim());
|
||||
}
|
||||
|
||||
function innerRegisterFixture(name: any, scope: string, fn: any, caller: Function) {
|
||||
function innerRegisterFixture(name: string, scope: string, fn: Function, caller: Function) {
|
||||
const obj = {stack: ''};
|
||||
Error.captureStackTrace(obj, caller);
|
||||
const stackFrame = obj.stack.split('\n')[1];
|
||||
const stackFrame = obj.stack.split('\n')[2];
|
||||
const location = stackFrame.replace(/.*at Object.<anonymous> \((.*)\)/, '$1');
|
||||
const file = location.replace(/^(.+):\d+:\d+$/, '$1');
|
||||
const registration = { name, scope, fn, file, location };
|
||||
@ -227,11 +211,11 @@ function innerRegisterFixture(name: any, scope: string, fn: any, caller: Functio
|
||||
registrationsByFile.get(file).push(registration);
|
||||
};
|
||||
|
||||
export function registerFixture<T extends keyof TestState>(name: T, fn: (params: FixtureParameters & WorkerState & TestState, test: (arg: TestState[T]) => Promise<TestResult>) => Promise<void>) {
|
||||
export function registerFixture<Config, T extends keyof TestState>(name: T, fn: (params: FixtureParameters & WorkerState & TestState, runTest: (arg: TestState[T]) => Promise<void>, config: Config, test: Test) => Promise<void>) {
|
||||
innerRegisterFixture(name, 'test', fn, registerFixture);
|
||||
};
|
||||
|
||||
export function registerWorkerFixture<T extends keyof (WorkerState & FixtureParameters)>(name: T, fn: (params: FixtureParameters & WorkerState, test: (arg: (WorkerState & FixtureParameters)[T]) => Promise<void>) => Promise<void>) {
|
||||
export function registerWorkerFixture<Config, T extends keyof (WorkerState & FixtureParameters)>(name: T, fn: (params: FixtureParameters & WorkerState, runTest: (arg: (WorkerState & FixtureParameters)[T]) => Promise<void>, config: Config) => Promise<void>) {
|
||||
innerRegisterFixture(name, 'worker', fn, registerWorkerFixture);
|
||||
};
|
||||
|
||||
|
@ -17,4 +17,15 @@
|
||||
|
||||
import './builtin.fixtures';
|
||||
import './expect';
|
||||
export {registerFixture, registerWorkerFixture, registerParameter, parameters} from './fixtures';
|
||||
import { registerFixture as registerFixtureT, registerWorkerFixture as registerWorkerFixtureT } from './fixtures';
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
import { Test } from './test';
|
||||
export { parameters, registerParameter } from './fixtures';
|
||||
|
||||
export function registerFixture<T extends keyof TestState>(name: T, fn: (params: FixtureParameters & WorkerState & TestState, runTest: (arg: TestState[T]) => Promise<void>, config: RunnerConfig, test: Test) => Promise<void>) {
|
||||
registerFixtureT<RunnerConfig, T>(name, fn);
|
||||
};
|
||||
|
||||
export function registerWorkerFixture<T extends keyof (WorkerState & FixtureParameters)>(name: T, fn: (params: FixtureParameters & WorkerState, runTest: (arg: (WorkerState & FixtureParameters)[T]) => Promise<void>, config: RunnerConfig) => Promise<void>) {
|
||||
registerWorkerFixtureT<RunnerConfig, T>(name, fn);
|
||||
};
|
||||
|
@ -22,8 +22,9 @@ import fs from 'fs';
|
||||
import os from 'os';
|
||||
import terminalLink from 'terminal-link';
|
||||
import StackUtils from 'stack-utils';
|
||||
import { Test } from './test';
|
||||
import { Test, Suite } from './test';
|
||||
import { EventEmitter } from 'ws';
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
|
||||
const stackUtils = new StackUtils();
|
||||
|
||||
@ -33,6 +34,8 @@ class BaseReporter {
|
||||
failures: Test[] = [];
|
||||
duration = 0;
|
||||
startTime: number;
|
||||
config: RunnerConfig;
|
||||
suite: Suite;
|
||||
|
||||
constructor(runner: EventEmitter) {
|
||||
process.on('SIGINT', async () => {
|
||||
@ -52,8 +55,10 @@ class BaseReporter {
|
||||
this.failures.push(test);
|
||||
});
|
||||
|
||||
runner.once('begin', () => {
|
||||
runner.once('begin', (options: { config: RunnerConfig, suite: Suite }) => {
|
||||
this.startTime = Date.now();
|
||||
this.config = options.config;
|
||||
this.suite = options.suite;
|
||||
});
|
||||
|
||||
runner.once('end', () => {
|
||||
@ -167,6 +172,44 @@ export class ListReporter extends BaseReporter {
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONReporter extends BaseReporter {
|
||||
constructor(runner: EventEmitter) {
|
||||
super(runner);
|
||||
|
||||
runner.once('end', () => {
|
||||
const result = {
|
||||
config: this.config,
|
||||
tests: this.suite.tests.map(test => this._serializeTest(test)),
|
||||
suites: this.suite.suites.map(suite => this._serializeSuite(suite))
|
||||
};
|
||||
console.log(JSON.stringify(result, undefined, 2));
|
||||
});
|
||||
}
|
||||
|
||||
private _serializeSuite(suite: Suite): any {
|
||||
return {
|
||||
title: suite.title,
|
||||
file: suite.file,
|
||||
configuration: suite.configuration,
|
||||
tests: suite.tests.map(test => this._serializeTest(test)),
|
||||
suites: suite.suites.map(suite => this._serializeSuite(suite))
|
||||
};
|
||||
}
|
||||
|
||||
private _serializeTest(test: Test): any {
|
||||
return {
|
||||
title: test.title,
|
||||
file: test.file,
|
||||
only: test.only,
|
||||
pending: test.pending,
|
||||
slow: test.slow,
|
||||
duration: test.duration,
|
||||
timeout: test.timeout,
|
||||
error: test.error
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function indent(lines: string, tab: string) {
|
||||
return lines.replace(/^/gm, tab);
|
||||
}
|
||||
@ -181,3 +224,9 @@ function positionInFile(stack: string, file: string): { column: number; line: nu
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const reporters = {
|
||||
'dot': DotReporter,
|
||||
'list': ListReporter,
|
||||
'json': JSONReporter
|
||||
};
|
||||
|
@ -18,27 +18,12 @@ import child_process from 'child_process';
|
||||
import crypto from 'crypto';
|
||||
import path from 'path';
|
||||
import { EventEmitter } from 'events';
|
||||
import { DotReporter, ListReporter} from './reporters';
|
||||
import { lookupRegistrations, FixturePool } from './fixtures';
|
||||
import { Suite } from './test';
|
||||
import { TestRunnerEntry } from './testRunner';
|
||||
|
||||
type RunnerOptions = {
|
||||
jobs: number;
|
||||
reporter: any;
|
||||
outputDir: string;
|
||||
snapshotDir: string;
|
||||
testDir: string;
|
||||
timeout: number;
|
||||
debug?: boolean;
|
||||
quiet?: boolean;
|
||||
grep?: string;
|
||||
trialRun?: boolean;
|
||||
updateSnapshots?: boolean;
|
||||
};
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
|
||||
export class Runner extends EventEmitter {
|
||||
readonly _options: RunnerOptions;
|
||||
private _workers = new Set<Worker>();
|
||||
private _freeWorkers: Worker[] = [];
|
||||
private _workerClaimers: (() => void)[] = [];
|
||||
@ -48,10 +33,13 @@ export class Runner extends EventEmitter {
|
||||
private _testsByConfiguredFile = new Map<any, any>();
|
||||
private _queue: TestRunnerEntry[] = [];
|
||||
private _stopCallback: () => void;
|
||||
readonly _config: RunnerConfig;
|
||||
private _suite: Suite;
|
||||
|
||||
constructor(suite: Suite, total: number, options: RunnerOptions) {
|
||||
constructor(suite: Suite, total: number, config: RunnerConfig) {
|
||||
super();
|
||||
this._options = options;
|
||||
|
||||
this._config = config;
|
||||
this.stats = {
|
||||
duration: 0,
|
||||
failures: 0,
|
||||
@ -59,13 +47,11 @@ export class Runner extends EventEmitter {
|
||||
pending: 0,
|
||||
tests: 0,
|
||||
};
|
||||
const reporterFactory = options.reporter === 'list' ? ListReporter : DotReporter;
|
||||
new reporterFactory(this);
|
||||
|
||||
this._testById = new Map();
|
||||
this._testsByConfiguredFile = new Map();
|
||||
|
||||
suite.eachTest(test => {
|
||||
this._suite = suite;
|
||||
this._suite.eachTest(test => {
|
||||
const configuredFile = `${test.file}::[${test._configurationString}]`;
|
||||
if (!this._testsByConfiguredFile.has(configuredFile)) {
|
||||
this._testsByConfiguredFile.set(configuredFile, {
|
||||
@ -83,7 +69,7 @@ export class Runner extends EventEmitter {
|
||||
|
||||
if (process.stdout.isTTY) {
|
||||
console.log();
|
||||
const jobs = Math.min(options.jobs, this._testsByConfiguredFile.size);
|
||||
const jobs = Math.min(config.jobs, this._testsByConfiguredFile.size);
|
||||
console.log(`Running ${total} test${ total > 1 ? 's' : '' } using ${jobs} worker${ jobs > 1 ? 's' : ''}`);
|
||||
}
|
||||
}
|
||||
@ -97,7 +83,7 @@ export class Runner extends EventEmitter {
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.emit('begin', {});
|
||||
this.emit('begin', { config: this._config, suite: this._suite });
|
||||
this._queue = this._filesSortedByWorkerHash();
|
||||
// Loop in case job schedules more jobs
|
||||
while (this._queue.length)
|
||||
@ -144,7 +130,7 @@ export class Runner extends EventEmitter {
|
||||
if (this._freeWorkers.length)
|
||||
return this._freeWorkers.pop();
|
||||
// If we can create worker, create it.
|
||||
if (this._workers.size < this._options.jobs)
|
||||
if (this._workers.size < this._config.jobs)
|
||||
this._createWorker();
|
||||
// Wait for the next available worker.
|
||||
await new Promise(f => this._workerClaimers.push(f));
|
||||
@ -160,7 +146,7 @@ export class Runner extends EventEmitter {
|
||||
}
|
||||
|
||||
_createWorker() {
|
||||
const worker = this._options.debug ? new InProcessWorker(this) : new OopWorker(this);
|
||||
const worker = this._config.debug ? new InProcessWorker(this) : new OopWorker(this);
|
||||
worker.on('test', params => {
|
||||
++this.stats.tests;
|
||||
this.emit('test', this._updateTest(params.test));
|
||||
@ -216,7 +202,7 @@ export class Runner extends EventEmitter {
|
||||
let lastWorkerId = 0;
|
||||
|
||||
class Worker extends EventEmitter {
|
||||
runner: any;
|
||||
runner: Runner;
|
||||
hash: string;
|
||||
|
||||
constructor(runner) {
|
||||
@ -254,26 +240,26 @@ class OopWorker extends Worker {
|
||||
this.stderr = [];
|
||||
this.on('stdout', params => {
|
||||
const chunk = chunkFromParams(params);
|
||||
if (!runner._options.quiet)
|
||||
if (!runner._config.quiet)
|
||||
process.stdout.write(chunk);
|
||||
this.stdout.push(chunk);
|
||||
});
|
||||
this.on('stderr', params => {
|
||||
const chunk = chunkFromParams(params);
|
||||
if (!runner._options.quiet)
|
||||
if (!runner._config.quiet)
|
||||
process.stderr.write(chunk);
|
||||
this.stderr.push(chunk);
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.process.send({ method: 'init', params: { workerId: lastWorkerId++, ...this.runner._options } });
|
||||
this.process.send({ method: 'init', params: { workerId: lastWorkerId++, ...this.runner._config } });
|
||||
await new Promise(f => this.process.once('message', f)); // Ready ack
|
||||
}
|
||||
|
||||
run(entry) {
|
||||
this.hash = entry.hash;
|
||||
this.process.send({ method: 'run', params: { entry, options: this.runner._options } });
|
||||
this.process.send({ method: 'run', params: { entry, config: this.runner._config } });
|
||||
}
|
||||
|
||||
stop() {
|
||||
@ -294,22 +280,22 @@ class OopWorker extends Worker {
|
||||
}
|
||||
|
||||
class InProcessWorker extends Worker {
|
||||
fixturePool: FixturePool;
|
||||
fixturePool: FixturePool<RunnerConfig>;
|
||||
|
||||
constructor(runner: Runner) {
|
||||
super(runner);
|
||||
this.fixturePool = require('./testRunner').fixturePool;
|
||||
this.fixturePool = require('./testRunner').fixturePool as FixturePool<RunnerConfig>;
|
||||
}
|
||||
|
||||
async init() {
|
||||
const { initializeImageMatcher } = require('./expect');
|
||||
initializeImageMatcher(this.runner._options);
|
||||
initializeImageMatcher(this.runner._config);
|
||||
}
|
||||
|
||||
async run(entry) {
|
||||
delete require.cache[entry.file];
|
||||
const { TestRunner } = require('./testRunner');
|
||||
const testRunner = new TestRunner(entry, this.runner._options, 0);
|
||||
const testRunner = new TestRunner(entry, this.runner._config, 0);
|
||||
for (const event of ['test', 'pending', 'pass', 'fail', 'done'])
|
||||
testRunner.on(event, this.emit.bind(this, event));
|
||||
testRunner.run();
|
||||
|
28
test-runner/src/runnerConfig.ts
Normal file
28
test-runner/src/runnerConfig.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type RunnerConfig = {
|
||||
jobs: number;
|
||||
outputDir: string;
|
||||
snapshotDir: string;
|
||||
testDir: string;
|
||||
timeout: number;
|
||||
debug?: boolean;
|
||||
quiet?: boolean;
|
||||
grep?: string;
|
||||
trialRun?: boolean;
|
||||
updateSnapshots?: boolean;
|
||||
};
|
@ -22,6 +22,7 @@ export class Test {
|
||||
file: string;
|
||||
only = false;
|
||||
pending = false;
|
||||
slow = false;
|
||||
duration = 0;
|
||||
timeout = 0;
|
||||
fn: Function;
|
||||
@ -56,10 +57,6 @@ export class Test {
|
||||
fullTitle(): string {
|
||||
return this.titlePath().join(' ');
|
||||
}
|
||||
|
||||
slow(): number {
|
||||
return 10000;
|
||||
}
|
||||
}
|
||||
|
||||
export class Suite {
|
||||
@ -70,6 +67,7 @@ export class Suite {
|
||||
only = false;
|
||||
pending = false;
|
||||
file: string;
|
||||
configuration: Configuration;
|
||||
|
||||
_hooks: { type: string, fn: Function } [] = [];
|
||||
_entries: (Suite | Test)[] = [];
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { fixturesForCallback, registerWorkerFixture } from './fixtures';
|
||||
import { fixturesForCallback } from './fixtures';
|
||||
import { Configuration, Test, Suite } from './test';
|
||||
import { fixturesUI } from './fixturesUI';
|
||||
|
||||
@ -29,9 +29,6 @@ export class TestCollector {
|
||||
|
||||
constructor(files: string[], matrix: { [key: string] : string }, options) {
|
||||
this._matrix = matrix;
|
||||
for (const name of Object.keys(matrix))
|
||||
//@ts-ignore
|
||||
registerWorkerFixture(name, async ({}, test) => test());
|
||||
this._options = options;
|
||||
this.suite = new Suite('');
|
||||
if (options.grep) {
|
||||
@ -106,6 +103,7 @@ export class TestCollector {
|
||||
_cloneSuite(suite: Suite, configurationObject: Configuration, configurationString: string, tests: Set<Test>) {
|
||||
const copy = suite.clone();
|
||||
copy.only = suite.only;
|
||||
copy.configuration = configurationObject;
|
||||
for (const entry of suite._entries) {
|
||||
if (entry instanceof Suite) {
|
||||
copy.addSuite(this._cloneSuite(entry, configurationObject, configurationString, tests));
|
||||
|
@ -15,13 +15,14 @@
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { FixturePool, registerWorkerFixture, rerunRegistrations, setParameters } from './fixtures';
|
||||
import { FixturePool, rerunRegistrations, setParameters } from './fixtures';
|
||||
import { EventEmitter } from 'events';
|
||||
import { setCurrentTestFile } from './expect';
|
||||
import { Test, Suite } from './test';
|
||||
import { fixturesUI } from './fixturesUI';
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
|
||||
export const fixturePool = new FixturePool();
|
||||
export const fixturePool = new FixturePool<RunnerConfig>();
|
||||
|
||||
export type TestRunnerEntry = {
|
||||
file: string;
|
||||
@ -40,30 +41,23 @@ export class TestRunner extends EventEmitter {
|
||||
private _remaining: Set<number>;
|
||||
private _trialRun: any;
|
||||
private _configuredFile: any;
|
||||
private _configurationObject: any;
|
||||
private _parsedGeneratorConfiguration: any = {};
|
||||
private _outDir: string;
|
||||
private _config: RunnerConfig;
|
||||
private _timeout: number;
|
||||
private _testDir: string;
|
||||
|
||||
constructor(entry: TestRunnerEntry, options, workerId) {
|
||||
constructor(entry: TestRunnerEntry, config: RunnerConfig, workerId: number) {
|
||||
super();
|
||||
this._file = entry.file;
|
||||
this._ordinals = new Set(entry.ordinals);
|
||||
this._remaining = new Set(entry.ordinals);
|
||||
this._trialRun = options.trialRun;
|
||||
this._timeout = options.timeout;
|
||||
this._testDir = options.testDir;
|
||||
this._outDir = options.outputDir;
|
||||
this._trialRun = config.trialRun;
|
||||
this._timeout = config.timeout;
|
||||
this._config = config;
|
||||
this._configuredFile = entry.configuredFile;
|
||||
this._configurationObject = entry.configurationObject;
|
||||
for (const {name, value} of this._configurationObject) {
|
||||
for (const {name, value} of entry.configurationObject)
|
||||
this._parsedGeneratorConfiguration[name] = value;
|
||||
// @ts-ignore
|
||||
registerWorkerFixture(name, async ({}, test) => await test(value));
|
||||
}
|
||||
this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
|
||||
setCurrentTestFile(path.relative(options.testDir, this._file));
|
||||
setCurrentTestFile(path.relative(config.testDir, this._file));
|
||||
}
|
||||
|
||||
stop() {
|
||||
@ -146,7 +140,7 @@ export class TestRunner extends EventEmitter {
|
||||
if (dir === 'before')
|
||||
all.reverse();
|
||||
for (const hook of all)
|
||||
await fixturePool.resolveParametersAndRun(hook, 0);
|
||||
await fixturePool.resolveParametersAndRun(hook, 0, this._config);
|
||||
}
|
||||
|
||||
private _reportDone() {
|
||||
@ -159,10 +153,7 @@ export class TestRunner extends EventEmitter {
|
||||
|
||||
private _testWrapper(test: Test) {
|
||||
const timeout = test.slow ? this._timeout * 3 : this._timeout;
|
||||
return fixturePool.wrapTestCallback(test.fn, timeout, test, {
|
||||
outputDir: this._outDir,
|
||||
testDir: this._testDir,
|
||||
});
|
||||
return fixturePool.wrapTestCallback(test.fn, timeout, { ...this._config }, test);
|
||||
}
|
||||
|
||||
private _serializeTest(test) {
|
||||
|
@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { initializeImageMatcher } = require('./expect');
|
||||
const { TestRunner, fixturePool } = require('./testRunner');
|
||||
|
||||
const util = require('util');
|
||||
import { initializeImageMatcher } from './expect';
|
||||
import { TestRunner, fixturePool } from './testRunner';
|
||||
import * as util from 'util';
|
||||
|
||||
let closed = false;
|
||||
|
||||
@ -45,8 +44,8 @@ process.on('disconnect', gracefullyCloseAndExit);
|
||||
process.on('SIGINT',() => {});
|
||||
process.on('SIGTERM',() => {});
|
||||
|
||||
let workerId;
|
||||
let testRunner;
|
||||
let workerId: number;
|
||||
let testRunner: TestRunner;
|
||||
|
||||
process.on('message', async message => {
|
||||
if (message.method === 'init') {
|
||||
@ -59,7 +58,7 @@ process.on('message', async message => {
|
||||
return;
|
||||
}
|
||||
if (message.method === 'run') {
|
||||
testRunner = new TestRunner(message.params.entry, message.params.options, workerId);
|
||||
testRunner = new TestRunner(message.params.entry, message.params.config, workerId);
|
||||
for (const event of ['test', 'pending', 'pass', 'fail', 'done'])
|
||||
testRunner.on(event, sendMessageToParent.bind(null, event));
|
||||
await testRunner.run();
|
@ -25,12 +25,16 @@ import { Transport } from '../lib/protocol/transport';
|
||||
import { setUnderTest } from '../lib/utils/utils';
|
||||
import { installCoverageHooks } from './coverage';
|
||||
import { parameters, registerFixture, registerWorkerFixture } from '../test-runner';
|
||||
|
||||
import {mkdtempAsync, removeFolderAsync} from './utils';
|
||||
|
||||
setUnderTest(); // Note: we must call setUnderTest before requiring Playwright
|
||||
|
||||
const platform = os.platform();
|
||||
export const options = {
|
||||
CHROMIUM: parameters.browserName === 'chromium',
|
||||
FIREFOX: parameters.browserName === 'firefox',
|
||||
WEBKIT: parameters.browserName === 'webkit',
|
||||
HEADLESS : !!valueFromEnv('HEADLESS', true),
|
||||
WIRE: !!process.env.PWWIRE,
|
||||
SLOW_MO: valueFromEnv('SLOW_MO', 0),
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface WorkerState {
|
||||
@ -52,9 +56,6 @@ declare global {
|
||||
}
|
||||
interface FixtureParameters {
|
||||
browserName: string;
|
||||
headless: boolean;
|
||||
wire: boolean;
|
||||
slowMo: number;
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +64,7 @@ declare global {
|
||||
const LINUX: boolean;
|
||||
const WIN: boolean;
|
||||
}
|
||||
const platform = os.platform();
|
||||
global['MAC'] = platform === 'darwin';
|
||||
global['LINUX'] = platform === 'linux';
|
||||
global['WIN'] = platform === 'win32';
|
||||
@ -96,22 +98,24 @@ const getExecutablePath = (browserName) => {
|
||||
return process.env.WKPATH;
|
||||
}
|
||||
|
||||
registerWorkerFixture('defaultBrowserOptions', async({browserName, headless, slowMo}, test) => {
|
||||
registerWorkerFixture('defaultBrowserOptions', async({browserName}, test) => {
|
||||
let executablePath = getExecutablePath(browserName);
|
||||
|
||||
if (executablePath)
|
||||
console.error(`Using executable at ${executablePath}`);
|
||||
await test({
|
||||
handleSIGINT: false,
|
||||
slowMo,
|
||||
headless,
|
||||
slowMo: options.SLOW_MO,
|
||||
headless: options.HEADLESS,
|
||||
executablePath
|
||||
});
|
||||
});
|
||||
|
||||
registerWorkerFixture('playwright', async({browserName, wire}, test) => {
|
||||
registerWorkerFixture('playwright', async({browserName}, test) => {
|
||||
setUnderTest(); // Note: we must call setUnderTest before requiring Playwright
|
||||
|
||||
const {coverage, uninstall} = installCoverageHooks(browserName);
|
||||
if (wire) {
|
||||
if (options.WIRE) {
|
||||
const connection = new Connection();
|
||||
const spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'rpc', 'server'), [], {
|
||||
stdio: 'pipe',
|
||||
@ -184,10 +188,10 @@ registerFixture('context', async ({browser}, test) => {
|
||||
await context.close();
|
||||
});
|
||||
|
||||
registerFixture('page', async ({context}, runTest) => {
|
||||
registerFixture('page', async ({context}, runTest, config, test) => {
|
||||
const page = await context.newPage();
|
||||
const { success, test, config } = await runTest(page);
|
||||
if (!success) {
|
||||
await runTest(page);
|
||||
if (test.error) {
|
||||
const relativePath = path.relative(config.testDir, test.file).replace(/\.spec\.[jt]s/, '');
|
||||
const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_');
|
||||
const assetPath = path.join(config.outputDir, relativePath, sanitizedTitle) + '-failed.png';
|
||||
@ -211,10 +215,8 @@ registerFixture('tmpDir', async ({}, test) => {
|
||||
await removeFolderAsync(tmpDir).catch(e => {});
|
||||
});
|
||||
|
||||
export const options = {
|
||||
CHROMIUM: parameters.browserName === 'chromium',
|
||||
FIREFOX: parameters.browserName === 'firefox',
|
||||
WEBKIT: parameters.browserName === 'webkit',
|
||||
HEADLESS : parameters.headless,
|
||||
WIRE: parameters.wire,
|
||||
function valueFromEnv(name, defaultValue) {
|
||||
if (!(name in process.env))
|
||||
return defaultValue;
|
||||
return JSON.parse(process.env[name]);
|
||||
}
|
||||
|
@ -20,9 +20,6 @@ declare const matrix: (m: any) => void;
|
||||
|
||||
matrix({
|
||||
'browserName': process.env.BROWSER ? [process.env.BROWSER] : ['chromium', 'webkit', 'firefox'],
|
||||
'headless': [!!valueFromEnv('HEADLESS', true)],
|
||||
'wire': [!!process.env.PWWIRE],
|
||||
'slowMo': [valueFromEnv('SLOW_MO', 0)]
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
@ -30,9 +27,3 @@ before(async () => {
|
||||
|
||||
after(async () => {
|
||||
});
|
||||
|
||||
function valueFromEnv(name, defaultValue) {
|
||||
if (!(name in process.env))
|
||||
return defaultValue;
|
||||
return JSON.parse(process.env[name]);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user