playwright/test-runner/src/testRunner.ts

216 lines
6.4 KiB
TypeScript
Raw Normal View History

/**
* 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.
*/
import { FixturePool, rerunRegistrations, setParameters, TestInfo } from './fixtures';
import { EventEmitter } from 'events';
import { setCurrentTestFile } from './expect';
import { Test, Suite, Configuration, serializeError, TestResult } from './test';
import { spec } from './spec';
import { RunnerConfig } from './runnerConfig';
import * as util from 'util';
export const fixturePool = new FixturePool();
export type TestRunnerEntry = {
file: string;
ordinals: number[];
configurationString: string;
configuration: Configuration;
2020-08-22 17:46:45 +02:00
hash: string;
};
function chunkToParams(chunk: Buffer | string): { text?: string, buffer?: string } {
if (chunk instanceof Buffer)
return { buffer: chunk.toString('base64') };
if (typeof chunk !== 'string')
return { text: util.inspect(chunk) };
return { text: chunk };
}
export class TestRunner extends EventEmitter {
private _failedTestId: string | undefined;
2020-08-22 17:46:45 +02:00
private _fatalError: any | undefined;
private _file: any;
private _ordinals: Set<number>;
private _remaining: Set<number>;
private _trialRun: any;
private _configuredFile: any;
private _parsedGeneratorConfiguration: any = {};
private _config: RunnerConfig;
private _timeout: number;
private _testId: string | null;
private _stdOutBuffer: (string | Buffer)[] = [];
private _stdErrBuffer: (string | Buffer)[] = [];
private _testResult: TestResult | null = null;
constructor(entry: TestRunnerEntry, config: RunnerConfig, workerId: number) {
super();
2020-08-18 23:12:31 +02:00
this._file = entry.file;
this._ordinals = new Set(entry.ordinals);
this._remaining = new Set(entry.ordinals);
this._trialRun = config.trialRun;
this._timeout = config.timeout;
this._config = config;
this._configuredFile = entry.file + `::[${entry.configurationString}]`;
for (const {name, value} of entry.configuration)
this._parsedGeneratorConfiguration[name] = value;
this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
setCurrentTestFile(this._file);
}
2020-08-22 09:05:24 +02:00
stop() {
this._trialRun = true;
}
fatalError(error: Error | any) {
this._fatalError = serializeError(error);
if (this._testResult) {
this._testResult.error = this._fatalError;
this.emit('testEnd', {
id: this._testId,
result: this._testResult
});
}
this._reportDone();
}
stdout(chunk: string | Buffer) {
this._stdOutBuffer.push(chunk);
if (!this._testId)
return;
for (const c of this._stdOutBuffer)
this.emit('testStdOut', { id: this._testId, ...chunkToParams(c) });
this._stdOutBuffer = [];
}
stderr(chunk: string | Buffer) {
this._stdErrBuffer.push(chunk);
if (!this._testId)
return;
for (const c of this._stdErrBuffer)
this.emit('testStdErr', { id: this._testId, ...chunkToParams(c) });
this._stdErrBuffer = [];
}
async run() {
setParameters(this._parsedGeneratorConfiguration);
2020-08-22 09:05:24 +02:00
const suite = new Suite('');
const revertBabelRequire = spec(suite, this._file, this._timeout);
2020-08-22 09:05:24 +02:00
require(this._file);
revertBabelRequire();
suite._renumber();
2020-08-22 09:05:24 +02:00
rerunRegistrations(this._file, 'test');
await this._runSuite(suite);
this._reportDone();
}
2020-08-22 09:05:24 +02:00
private async _runSuite(suite: Suite) {
try {
await this._runHooks(suite, 'beforeAll', 'before');
} catch (e) {
2020-08-22 17:46:45 +02:00
this._fatalError = serializeError(e);
2020-08-22 09:05:24 +02:00
this._reportDone();
}
for (const entry of suite._entries) {
if (entry instanceof Suite)
2020-08-22 09:05:24 +02:00
await this._runSuite(entry);
else
2020-08-22 09:05:24 +02:00
await this._runTest(entry);
2020-08-22 09:05:24 +02:00
}
try {
await this._runHooks(suite, 'afterAll', 'after');
} catch (e) {
2020-08-22 17:46:45 +02:00
this._fatalError = serializeError(e);
2020-08-22 09:05:24 +02:00
this._reportDone();
}
}
2020-08-22 09:05:24 +02:00
private async _runTest(test: Test) {
if (this._failedTestId)
2020-08-22 09:05:24 +02:00
return false;
if (this._ordinals.size && !this._ordinals.has(test._ordinal))
2020-08-22 09:05:24 +02:00
return;
this._remaining.delete(test._ordinal);
const id = `${test._ordinal}@${this._configuredFile}`;
this._testId = id;
this.emit('testBegin', { id });
const result: TestResult = {
duration: 0,
status: 'none',
stdout: [],
stderr: [],
data: {}
};
this._testResult = result;
if (test._skipped || test.suite._isSkipped()) {
result.status = 'skipped';
this.emit('testEnd', { id, result });
2020-08-22 09:05:24 +02:00
return;
}
const startTime = Date.now();
2020-08-22 09:05:24 +02:00
try {
const testInfo = { config: this._config, test, result };
if (!this._trialRun) {
await this._runHooks(test.suite, 'beforeEach', 'before', testInfo);
const timeout = test.slow ? this._timeout * 3 : this._timeout;
await fixturePool.runTestWithFixtures(test.fn, timeout, testInfo);
await this._runHooks(test.suite, 'afterEach', 'after', testInfo);
} else {
result.status = 'passed';
}
result.duration = Date.now() - startTime;
this.emit('testEnd', { id, result });
2020-08-22 09:05:24 +02:00
} catch (error) {
result.error = serializeError(error);
result.status = 'failed';
result.duration = Date.now() - startTime;
this._failedTestId = this._testId;
this.emit('testEnd', { id, result });
2020-08-22 09:05:24 +02:00
}
this._testResult = null;
this._testId = null;
}
private async _runHooks(suite: Suite, type: string, dir: 'before' | 'after', testInfo?: TestInfo) {
2020-08-22 09:05:24 +02:00
if (!suite._hasTestsToRun())
return;
const all = [];
for (let s = suite; s; s = s.parent) {
const funcs = s._hooks.filter(e => e.type === type).map(e => e.fn);
all.push(...funcs.reverse());
}
2020-08-22 09:05:24 +02:00
if (dir === 'before')
all.reverse();
for (const hook of all)
await fixturePool.resolveParametersAndRun(hook, this._config, testInfo);
}
2020-08-22 09:05:24 +02:00
private _reportDone() {
this.emit('done', {
failedTestId: this._failedTestId,
2020-08-22 09:05:24 +02:00
fatalError: this._fatalError,
remaining: [...this._remaining],
});
2020-08-18 23:12:31 +02:00
}
}