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,28 +18,13 @@ 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 _workers = new Set<Worker>(); | ||||
|   private _freeWorkers: Worker[] = []; | ||||
|   private _workerClaimers: (() => void)[] = []; | ||||
|   stats: { duration: number; failures: number; passes: number; pending: number; tests: number; }; | ||||
| @ -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,19 +20,10 @@ 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 () => { | ||||
| }); | ||||
| 
 | ||||
| after(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
	 Pavel Feldman
						Pavel Feldman