2021-06-06 17:09:53 -07:00
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-04-18 16:50:25 -08:00
|
|
|
|
import { colors, rimraf } from 'playwright-core/lib/utilsBundle';
|
2022-02-23 15:10:11 -07:00
|
|
|
|
import util from 'util';
|
2023-01-26 17:26:47 -08:00
|
|
|
|
import { debugTest, formatLocation, relativeFilePath, serializeError } from '../util';
|
|
|
|
|
import type { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerInitParams, TeardownErrorsPayload, TestOutputPayload } from '../common/ipc';
|
2023-05-23 09:36:35 -07:00
|
|
|
|
import { setCurrentTestInfo, setIsWorkerProcess } from '../common/globals';
|
2023-01-26 17:26:47 -08:00
|
|
|
|
import { ConfigLoader } from '../common/configLoader';
|
|
|
|
|
import type { Suite, TestCase } from '../common/test';
|
2023-04-07 09:54:01 -07:00
|
|
|
|
import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config';
|
2023-02-09 11:31:54 -08:00
|
|
|
|
import { FixtureRunner } from './fixtureRunner';
|
2023-01-13 13:50:38 -08:00
|
|
|
|
import { ManualPromise } from 'playwright-core/lib/utils';
|
2023-02-09 19:22:17 -08:00
|
|
|
|
import { TestInfoImpl } from './testInfo';
|
|
|
|
|
import { TimeoutManager, type TimeSlot } from './timeoutManager';
|
2023-01-26 17:26:47 -08:00
|
|
|
|
import { ProcessRunner } from '../common/process';
|
2023-02-01 12:33:42 -08:00
|
|
|
|
import { loadTestFile } from '../common/testLoader';
|
2023-01-26 17:26:47 -08:00
|
|
|
|
import { buildFileSuiteForProject, filterTestsRemoveEmptySuites } from '../common/suiteUtils';
|
|
|
|
|
import { PoolBuilder } from '../common/poolBuilder';
|
2023-04-07 09:54:01 -07:00
|
|
|
|
import type { TestInfoError } from '../../types/test';
|
2021-06-06 17:09:53 -07:00
|
|
|
|
|
2022-02-23 15:10:11 -07:00
|
|
|
|
const removeFolderAsync = util.promisify(rimraf);
|
|
|
|
|
|
2023-01-22 15:04:29 -08:00
|
|
|
|
export class WorkerMain extends ProcessRunner {
|
2021-06-06 17:09:53 -07:00
|
|
|
|
private _params: WorkerInitParams;
|
2023-01-27 12:44:15 -08:00
|
|
|
|
private _config!: FullConfigInternal;
|
2022-05-03 13:36:24 +01:00
|
|
|
|
private _project!: FullProjectInternal;
|
2023-01-19 15:56:57 -08:00
|
|
|
|
private _poolBuilder!: PoolBuilder;
|
2021-06-06 17:09:53 -07:00
|
|
|
|
private _fixtureRunner: FixtureRunner;
|
|
|
|
|
|
2022-03-08 16:35:14 -08:00
|
|
|
|
// Accumulated fatal errors that cannot be attributed to a test.
|
2022-12-21 09:36:59 -08:00
|
|
|
|
private _fatalErrors: TestInfoError[] = [];
|
2022-03-08 20:29:31 -08:00
|
|
|
|
// Whether we should skip running remaining tests in this suite because
|
2022-03-08 16:35:14 -08:00
|
|
|
|
// of a setup error, usually beforeAll hook.
|
2022-03-08 20:29:31 -08:00
|
|
|
|
private _skipRemainingTestsInSuite: Suite | undefined;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
// The stage of the full cleanup. Once "finished", we can safely stop running anything.
|
|
|
|
|
private _didRunFullCleanup = false;
|
|
|
|
|
// Whether the worker was requested to stop.
|
2021-07-28 15:43:37 -07:00
|
|
|
|
private _isStopped = false;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
// This promise resolves once the single "run test group" call finishes.
|
|
|
|
|
private _runFinished = new ManualPromise<void>();
|
2023-01-17 12:43:51 -08:00
|
|
|
|
private _currentTest: TestInfoImpl | null = null;
|
2022-03-28 19:58:24 -07:00
|
|
|
|
private _lastRunningTests: TestInfoImpl[] = [];
|
|
|
|
|
private _totalRunningTests = 0;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
// Dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`.
|
|
|
|
|
private _extraSuiteAnnotations = new Map<Suite, Annotation[]>();
|
|
|
|
|
// Suites that had their beforeAll hooks, but not afterAll hooks executed.
|
|
|
|
|
// These suites still need afterAll hooks to be executed for the proper cleanup.
|
|
|
|
|
private _activeSuites = new Set<Suite>();
|
2021-06-06 17:09:53 -07:00
|
|
|
|
|
|
|
|
|
constructor(params: WorkerInitParams) {
|
|
|
|
|
super();
|
2023-01-17 12:43:51 -08:00
|
|
|
|
process.env.TEST_WORKER_INDEX = String(params.workerIndex);
|
|
|
|
|
process.env.TEST_PARALLEL_INDEX = String(params.parallelIndex);
|
2023-02-02 16:46:54 -08:00
|
|
|
|
setIsWorkerProcess();
|
2023-01-17 12:43:51 -08:00
|
|
|
|
|
2021-06-06 17:09:53 -07:00
|
|
|
|
this._params = params;
|
|
|
|
|
this._fixtureRunner = new FixtureRunner();
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
|
|
|
|
// Resolve this promise, so worker does not stall waiting for the non-existent run to finish,
|
|
|
|
|
// when it was sopped before running any test group.
|
|
|
|
|
this._runFinished.resolve();
|
2023-01-17 14:53:11 -08:00
|
|
|
|
|
|
|
|
|
process.on('unhandledRejection', reason => this.unhandledError(reason));
|
|
|
|
|
process.on('uncaughtException', error => this.unhandledError(error));
|
2023-01-20 18:24:15 -08:00
|
|
|
|
process.stdout.write = (chunk: string | Buffer) => {
|
|
|
|
|
const outPayload: TestOutputPayload = {
|
|
|
|
|
...chunkToParams(chunk)
|
|
|
|
|
};
|
|
|
|
|
this.dispatchEvent('stdOut', outPayload);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!process.env.PW_RUNNER_DEBUG) {
|
|
|
|
|
process.stderr.write = (chunk: string | Buffer) => {
|
|
|
|
|
const outPayload: TestOutputPayload = {
|
|
|
|
|
...chunkToParams(chunk)
|
|
|
|
|
};
|
|
|
|
|
this.dispatchEvent('stdErr', outPayload);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 14:53:11 -08:00
|
|
|
|
private _stop(): Promise<void> {
|
2021-08-10 10:54:05 -07:00
|
|
|
|
if (!this._isStopped) {
|
|
|
|
|
this._isStopped = true;
|
2023-03-24 15:03:49 -07:00
|
|
|
|
this._currentTest?._interrupt();
|
2021-07-30 13:12:49 -07:00
|
|
|
|
}
|
2021-08-10 10:54:05 -07:00
|
|
|
|
return this._runFinished;
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 14:53:11 -08:00
|
|
|
|
override async gracefullyClose() {
|
2023-01-22 15:04:29 -08:00
|
|
|
|
try {
|
|
|
|
|
await this._stop();
|
|
|
|
|
// We have to load the project to get the right deadline below.
|
|
|
|
|
await this._loadIfNeeded();
|
|
|
|
|
await this._teardownScopes();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this._fatalErrors.push(serializeError(e));
|
|
|
|
|
}
|
2023-01-17 14:53:11 -08:00
|
|
|
|
|
2022-02-23 12:32:12 -08:00
|
|
|
|
if (this._fatalErrors.length) {
|
2023-01-22 15:04:29 -08:00
|
|
|
|
this._appendProcessTeardownDiagnostics(this._fatalErrors[this._fatalErrors.length - 1]);
|
2022-02-23 12:32:12 -08:00
|
|
|
|
const payload: TeardownErrorsPayload = { fatalErrors: this._fatalErrors };
|
2023-01-17 12:43:51 -08:00
|
|
|
|
this.dispatchEvent('teardownErrors', payload);
|
2022-02-23 12:32:12 -08:00
|
|
|
|
}
|
2021-09-28 10:56:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-22 15:04:29 -08:00
|
|
|
|
private _appendProcessTeardownDiagnostics(error: TestInfoError) {
|
2022-03-28 19:58:24 -07:00
|
|
|
|
if (!this._lastRunningTests.length)
|
|
|
|
|
return;
|
|
|
|
|
const count = this._totalRunningTests === 1 ? '1 test' : `${this._totalRunningTests} tests`;
|
|
|
|
|
let lastMessage = '';
|
|
|
|
|
if (this._lastRunningTests.length < this._totalRunningTests)
|
|
|
|
|
lastMessage = `, last ${this._lastRunningTests.length} tests were`;
|
|
|
|
|
const message = [
|
2022-07-28 14:46:21 -07:00
|
|
|
|
'',
|
|
|
|
|
'',
|
|
|
|
|
colors.red(`Failed worker ran ${count}${lastMessage}:`),
|
2022-03-28 19:58:24 -07:00
|
|
|
|
...this._lastRunningTests.map(testInfo => formatTestTitle(testInfo._test, testInfo.project.name)),
|
|
|
|
|
].join('\n');
|
2022-07-28 14:46:21 -07:00
|
|
|
|
if (error.message) {
|
|
|
|
|
if (error.stack) {
|
|
|
|
|
let index = error.stack.indexOf(error.message);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
index += error.message.length;
|
|
|
|
|
error.stack = error.stack.substring(0, index) + message + error.stack.substring(index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
error.message += message;
|
|
|
|
|
} else if (error.value) {
|
|
|
|
|
error.value += message;
|
|
|
|
|
}
|
2022-03-28 19:58:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-28 10:56:50 -07:00
|
|
|
|
private async _teardownScopes() {
|
2021-06-06 17:09:53 -07:00
|
|
|
|
// TODO: separate timeout for teardown?
|
2023-04-07 09:54:01 -07:00
|
|
|
|
const timeoutManager = new TimeoutManager(this._project.project.timeout);
|
2022-03-17 09:36:03 -07:00
|
|
|
|
timeoutManager.setCurrentRunnable({ type: 'teardown' });
|
|
|
|
|
const timeoutError = await timeoutManager.runWithTimeout(async () => {
|
|
|
|
|
await this._fixtureRunner.teardownScope('test', timeoutManager);
|
|
|
|
|
await this._fixtureRunner.teardownScope('worker', timeoutManager);
|
|
|
|
|
});
|
|
|
|
|
if (timeoutError)
|
|
|
|
|
this._fatalErrors.push(timeoutError);
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 14:53:11 -08:00
|
|
|
|
unhandledError(error: Error | any) {
|
2021-10-29 13:36:12 -07:00
|
|
|
|
// Usually, we do not differentiate between errors in the control flow
|
|
|
|
|
// and unhandled errors - both lead to the test failing. This is good for regular tests,
|
|
|
|
|
// so that you can, e.g. expect() from inside an event handler. The test fails,
|
|
|
|
|
// and we restart the worker.
|
|
|
|
|
//
|
|
|
|
|
// However, for tests marked with test.fail(), this is a problem. Unhandled error
|
|
|
|
|
// could come either from the user test code (legit failure), or from a fixture or
|
|
|
|
|
// a test runner. In the latter case, the worker state could be messed up,
|
|
|
|
|
// and continuing to run tests in the same worker is problematic. Therefore,
|
|
|
|
|
// we turn this into a fatal error and restart the worker anyway.
|
2022-02-03 17:14:12 -08:00
|
|
|
|
// The only exception is the expect() error that we still consider ok.
|
|
|
|
|
const isExpectError = (error instanceof Error) && !!(error as any).matcherResult;
|
|
|
|
|
const isCurrentTestExpectedToFail = this._currentTest?.expectedStatus === 'failed';
|
|
|
|
|
const shouldConsiderAsTestError = isExpectError || !isCurrentTestExpectedToFail;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
if (this._currentTest && shouldConsiderAsTestError) {
|
2022-02-02 19:33:51 -07:00
|
|
|
|
this._currentTest._failWithError(serializeError(error), true /* isHardError */);
|
2021-06-06 17:09:53 -07:00
|
|
|
|
} else {
|
|
|
|
|
// No current test - fatal error.
|
2022-02-23 12:32:12 -08:00
|
|
|
|
if (!this._fatalErrors.length)
|
|
|
|
|
this._fatalErrors.push(serializeError(error));
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
2023-06-05 17:45:56 +02:00
|
|
|
|
void this._stop();
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-12 11:59:58 -05:00
|
|
|
|
private async _loadIfNeeded() {
|
2023-01-27 12:44:15 -08:00
|
|
|
|
if (this._config)
|
2021-06-06 17:09:53 -07:00
|
|
|
|
return;
|
|
|
|
|
|
2023-04-07 09:54:01 -07:00
|
|
|
|
this._config = await ConfigLoader.deserialize(this._params.config);
|
|
|
|
|
this._project = this._config.projects.find(p => p.id === this._params.projectId)!;
|
2023-01-20 08:36:31 -08:00
|
|
|
|
this._poolBuilder = PoolBuilder.createForWorker(this._project);
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 16:35:14 -08:00
|
|
|
|
async runTestGroup(runPayload: RunPayload) {
|
|
|
|
|
this._runFinished = new ManualPromise<void>();
|
2022-08-18 20:12:33 +02:00
|
|
|
|
const entries = new Map(runPayload.entries.map(e => [e.testId, e]));
|
2022-04-25 09:05:40 -07:00
|
|
|
|
let fatalUnknownTestIds;
|
2021-08-10 10:54:05 -07:00
|
|
|
|
try {
|
|
|
|
|
await this._loadIfNeeded();
|
2023-04-07 09:54:01 -07:00
|
|
|
|
const fileSuite = await loadTestFile(runPayload.file, this._config.config.rootDir);
|
2023-01-19 15:56:57 -08:00
|
|
|
|
const suite = buildFileSuiteForProject(this._project, fileSuite, this._params.repeatEachIndex);
|
2023-01-20 18:24:15 -08:00
|
|
|
|
const hasEntries = filterTestsRemoveEmptySuites(suite, test => entries.has(test.id));
|
2023-01-19 15:56:57 -08:00
|
|
|
|
if (hasEntries) {
|
2023-01-20 08:36:31 -08:00
|
|
|
|
this._poolBuilder.buildPools(suite);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
this._extraSuiteAnnotations = new Map();
|
|
|
|
|
this._activeSuites = new Set();
|
|
|
|
|
this._didRunFullCleanup = false;
|
2022-07-29 11:40:33 -07:00
|
|
|
|
const tests = suite.allTests();
|
2022-03-08 20:29:31 -08:00
|
|
|
|
for (let i = 0; i < tests.length; i++) {
|
|
|
|
|
// Do not run tests after full cleanup, because we are entirely done.
|
|
|
|
|
if (this._isStopped && this._didRunFullCleanup)
|
|
|
|
|
break;
|
2022-07-27 20:17:19 -07:00
|
|
|
|
const entry = entries.get(tests[i].id)!;
|
|
|
|
|
entries.delete(tests[i].id);
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`test started "${tests[i].title}"`);
|
2022-03-08 20:29:31 -08:00
|
|
|
|
await this._runTest(tests[i], entry.retry, tests[i + 1]);
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`test finished "${tests[i].title}"`);
|
2022-03-08 20:29:31 -08:00
|
|
|
|
}
|
2022-04-25 09:05:40 -07:00
|
|
|
|
} else {
|
|
|
|
|
fatalUnknownTestIds = runPayload.entries.map(e => e.testId);
|
2023-06-05 17:45:56 +02:00
|
|
|
|
void this._stop();
|
2021-08-10 10:54:05 -07:00
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// In theory, we should run above code without any errors.
|
|
|
|
|
// However, in the case we screwed up, or loadTestFile failed in the worker
|
|
|
|
|
// but not in the runner, let's do a fatal error.
|
2023-02-21 19:24:17 -08:00
|
|
|
|
this._fatalErrors.push(serializeError(e));
|
2023-06-05 17:45:56 +02:00
|
|
|
|
void this._stop();
|
2021-08-10 10:54:05 -07:00
|
|
|
|
} finally {
|
2022-03-08 20:29:31 -08:00
|
|
|
|
const donePayload: DonePayload = {
|
|
|
|
|
fatalErrors: this._fatalErrors,
|
|
|
|
|
skipTestsDueToSetupFailure: [],
|
2022-04-25 09:05:40 -07:00
|
|
|
|
fatalUnknownTestIds
|
2022-03-08 20:29:31 -08:00
|
|
|
|
};
|
|
|
|
|
for (const test of this._skipRemainingTestsInSuite?.allTests() || []) {
|
2022-07-27 20:17:19 -07:00
|
|
|
|
if (entries.has(test.id))
|
|
|
|
|
donePayload.skipTestsDueToSetupFailure.push(test.id);
|
2022-03-08 20:29:31 -08:00
|
|
|
|
}
|
2023-01-17 12:43:51 -08:00
|
|
|
|
this.dispatchEvent('done', donePayload);
|
2022-03-08 20:29:31 -08:00
|
|
|
|
this._fatalErrors = [];
|
|
|
|
|
this._skipRemainingTestsInSuite = undefined;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
this._runFinished.resolve();
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 16:35:14 -08:00
|
|
|
|
private async _runTest(test: TestCase, retry: number, nextTest: TestCase | undefined) {
|
2023-01-27 12:44:15 -08:00
|
|
|
|
const testInfo = new TestInfoImpl(this._config, this._project, this._params, test, retry,
|
2023-01-17 12:43:51 -08:00
|
|
|
|
stepBeginPayload => this.dispatchEvent('stepBegin', stepBeginPayload),
|
2023-06-01 20:29:32 -07:00
|
|
|
|
stepEndPayload => this.dispatchEvent('stepEnd', stepEndPayload),
|
|
|
|
|
attachment => this.dispatchEvent('attach', attachment));
|
2021-06-29 13:33:13 -07:00
|
|
|
|
|
2022-03-08 16:35:14 -08:00
|
|
|
|
const processAnnotation = (annotation: Annotation) => {
|
2021-07-02 15:49:05 -07:00
|
|
|
|
testInfo.annotations.push(annotation);
|
|
|
|
|
switch (annotation.type) {
|
|
|
|
|
case 'fixme':
|
|
|
|
|
case 'skip':
|
|
|
|
|
testInfo.expectedStatus = 'skipped';
|
|
|
|
|
break;
|
|
|
|
|
case 'fail':
|
|
|
|
|
if (testInfo.expectedStatus !== 'skipped')
|
|
|
|
|
testInfo.expectedStatus = 'failed';
|
|
|
|
|
break;
|
|
|
|
|
case 'slow':
|
2022-03-17 09:36:03 -07:00
|
|
|
|
testInfo.slow();
|
2021-07-02 15:49:05 -07:00
|
|
|
|
break;
|
|
|
|
|
}
|
2022-03-08 16:35:14 -08:00
|
|
|
|
};
|
|
|
|
|
|
2023-01-17 17:38:44 -08:00
|
|
|
|
if (!this._isStopped)
|
2022-03-08 16:35:14 -08:00
|
|
|
|
this._fixtureRunner.setPool(test._pool!);
|
|
|
|
|
|
|
|
|
|
const suites = getSuites(test);
|
|
|
|
|
const reversedSuites = suites.slice().reverse();
|
2023-01-04 14:13:49 -08:00
|
|
|
|
const nextSuites = new Set(getSuites(nextTest));
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
2023-01-25 12:54:50 -08:00
|
|
|
|
testInfo._timeoutManager.setTimeout(test.timeout);
|
2023-01-24 12:49:47 -08:00
|
|
|
|
for (const annotation of test._staticAnnotations)
|
|
|
|
|
processAnnotation(annotation);
|
2023-01-25 12:54:50 -08:00
|
|
|
|
|
2023-01-24 12:49:47 -08:00
|
|
|
|
// Process existing annotations dynamically set for parent suites.
|
2022-03-08 16:35:14 -08:00
|
|
|
|
for (const suite of suites) {
|
|
|
|
|
const extraAnnotations = this._extraSuiteAnnotations.get(suite) || [];
|
|
|
|
|
for (const annotation of extraAnnotations)
|
|
|
|
|
processAnnotation(annotation);
|
2021-07-02 15:49:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 17:39:42 -08:00
|
|
|
|
this._currentTest = testInfo;
|
2021-08-10 10:54:05 -07:00
|
|
|
|
setCurrentTestInfo(testInfo);
|
2023-01-17 12:43:51 -08:00
|
|
|
|
this.dispatchEvent('testBegin', buildTestBeginPayload(testInfo));
|
2021-06-06 17:09:53 -07:00
|
|
|
|
|
2022-07-11 21:33:56 -07:00
|
|
|
|
const isSkipped = testInfo.expectedStatus === 'skipped';
|
2023-01-04 14:13:49 -08:00
|
|
|
|
const hasAfterAllToRunBeforeNextTest = reversedSuites.some(suite => {
|
|
|
|
|
return this._activeSuites.has(suite) && !nextSuites.has(suite) && suite._hooks.some(hook => hook.type === 'afterAll');
|
|
|
|
|
});
|
|
|
|
|
if (isSkipped && nextTest && !hasAfterAllToRunBeforeNextTest) {
|
|
|
|
|
// Fast path - this test is skipped, and there are more tests that will handle cleanup.
|
2021-06-06 17:09:53 -07:00
|
|
|
|
testInfo.status = 'skipped';
|
2023-01-17 12:43:51 -08:00
|
|
|
|
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo));
|
2021-06-06 17:09:53 -07:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-28 19:58:24 -07:00
|
|
|
|
this._totalRunningTests++;
|
|
|
|
|
this._lastRunningTests.push(testInfo);
|
|
|
|
|
if (this._lastRunningTests.length > 10)
|
|
|
|
|
this._lastRunningTests.shift();
|
2022-03-08 20:29:31 -08:00
|
|
|
|
let didFailBeforeAllForSuite: Suite | undefined;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
let shouldRunAfterEachHooks = false;
|
|
|
|
|
|
|
|
|
|
await testInfo._runWithTimeout(async () => {
|
2022-07-11 21:33:56 -07:00
|
|
|
|
if (this._isStopped || isSkipped) {
|
|
|
|
|
// Two reasons to get here:
|
|
|
|
|
// - Last test is skipped, so we should not run the test, but run the cleanup.
|
|
|
|
|
// - Worker is requested to stop, but was not able to run full cleanup yet.
|
|
|
|
|
// We should skip the test, but run the cleanup.
|
2022-03-08 16:35:14 -08:00
|
|
|
|
testInfo.status = 'skipped';
|
2022-03-08 20:29:31 -08:00
|
|
|
|
didFailBeforeAllForSuite = undefined;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-30 16:03:50 -07:00
|
|
|
|
await removeFolderAsync(testInfo.outputDir).catch(() => {});
|
|
|
|
|
|
2023-04-19 17:31:07 -07:00
|
|
|
|
let testFunctionParams: object | null = null;
|
|
|
|
|
await testInfo._runAsStep({ category: 'hook', title: 'Before Hooks' }, async step => {
|
2023-05-06 10:25:32 -07:00
|
|
|
|
testInfo._beforeHooksStep = step;
|
2023-04-19 17:31:07 -07:00
|
|
|
|
// Note: wrap all preparation steps together, because failure/skip in any of them
|
|
|
|
|
// prevents further setup and/or test from running.
|
|
|
|
|
const beforeHooksError = await testInfo._runAndFailOnError(async () => {
|
|
|
|
|
// Run "beforeAll" modifiers on parent suites, unless already run during previous tests.
|
|
|
|
|
for (const suite of suites) {
|
|
|
|
|
if (this._extraSuiteAnnotations.has(suite))
|
|
|
|
|
continue;
|
|
|
|
|
const extraAnnotations: Annotation[] = [];
|
|
|
|
|
this._extraSuiteAnnotations.set(suite, extraAnnotations);
|
|
|
|
|
didFailBeforeAllForSuite = suite; // Assume failure, unless reset below.
|
|
|
|
|
// Separate timeout for each "beforeAll" modifier.
|
|
|
|
|
const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
|
|
|
|
await this._runModifiersForSuite(suite, testInfo, 'worker', timeSlot, extraAnnotations);
|
|
|
|
|
}
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
2023-04-19 17:31:07 -07:00
|
|
|
|
// Run "beforeAll" hooks, unless already run during previous tests.
|
|
|
|
|
for (const suite of suites) {
|
|
|
|
|
didFailBeforeAllForSuite = suite; // Assume failure, unless reset below.
|
|
|
|
|
await this._runBeforeAllHooksForSuite(suite, testInfo);
|
|
|
|
|
}
|
2022-03-08 20:29:31 -08:00
|
|
|
|
|
2023-04-19 17:31:07 -07:00
|
|
|
|
// Running "beforeAll" succeeded for all suites!
|
|
|
|
|
didFailBeforeAllForSuite = undefined;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
2023-04-19 17:31:07 -07:00
|
|
|
|
// Run "beforeEach" modifiers.
|
|
|
|
|
for (const suite of suites)
|
|
|
|
|
await this._runModifiersForSuite(suite, testInfo, 'test', undefined);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
2023-04-19 17:31:07 -07:00
|
|
|
|
// Run "beforeEach" hooks. Once started with "beforeEach", we must run all "afterEach" hooks as well.
|
|
|
|
|
shouldRunAfterEachHooks = true;
|
|
|
|
|
await this._runEachHooksForSuites(suites, 'beforeEach', testInfo, undefined);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
2023-04-19 17:31:07 -07:00
|
|
|
|
// Setup fixtures required by the test.
|
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'test' });
|
|
|
|
|
testFunctionParams = await this._fixtureRunner.resolveParametersForFunction(test.fn, testInfo, 'test');
|
|
|
|
|
}, 'allowSkips');
|
|
|
|
|
if (beforeHooksError)
|
|
|
|
|
step.complete({ error: beforeHooksError });
|
2023-03-30 21:05:07 -07:00
|
|
|
|
});
|
|
|
|
|
|
2023-04-19 17:31:07 -07:00
|
|
|
|
if (testFunctionParams === null) {
|
2023-03-30 21:05:07 -07:00
|
|
|
|
// Fixture setup failed, we should not run the test now.
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-06-06 17:09:53 -07:00
|
|
|
|
|
2023-04-19 17:31:07 -07:00
|
|
|
|
await testInfo._runAndFailOnError(async () => {
|
2022-03-08 16:35:14 -08:00
|
|
|
|
// Now run the test itself.
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`test function started`);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
const fn = test.fn; // Extract a variable to get a better stack trace ("myTest" vs "TestCase.myTest [as fn]").
|
2023-04-19 17:31:07 -07:00
|
|
|
|
await fn(testFunctionParams, testInfo);
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`test function finished`);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
}, 'allowSkips');
|
|
|
|
|
});
|
|
|
|
|
|
2022-03-08 20:29:31 -08:00
|
|
|
|
if (didFailBeforeAllForSuite) {
|
2022-03-08 16:35:14 -08:00
|
|
|
|
// This will inform dispatcher that we should not run more tests from this group
|
|
|
|
|
// because we had a beforeAll error.
|
|
|
|
|
// This behavior avoids getting the same common error for each test.
|
2022-03-08 20:29:31 -08:00
|
|
|
|
this._skipRemainingTestsInSuite = didFailBeforeAllForSuite;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-17 09:36:03 -07:00
|
|
|
|
let afterHooksSlot: TimeSlot | undefined;
|
2022-10-25 12:34:15 -07:00
|
|
|
|
if (testInfo._didTimeout) {
|
2021-08-10 10:54:05 -07:00
|
|
|
|
// A timed-out test gets a full additional timeout to run after hooks.
|
2023-04-07 09:54:01 -07:00
|
|
|
|
afterHooksSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
2022-05-09 20:38:20 +01:00
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'afterEach', slot: afterHooksSlot });
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
2023-04-19 17:31:07 -07:00
|
|
|
|
await testInfo._runAsStep({ category: 'hook', title: 'After Hooks' }, async step => {
|
2023-05-06 10:25:32 -07:00
|
|
|
|
testInfo._afterHooksStep = step;
|
2023-03-30 21:05:07 -07:00
|
|
|
|
let firstAfterHooksError: TestInfoError | undefined;
|
|
|
|
|
await testInfo._runWithTimeout(async () => {
|
|
|
|
|
// Note: do not wrap all teardown steps together, because failure in any of them
|
|
|
|
|
// does not prevent further teardown steps from running.
|
|
|
|
|
|
2023-05-01 13:53:15 -07:00
|
|
|
|
// Run "immediately upon test function finish" callback.
|
|
|
|
|
debugTest(`on-test-function-finish callback started`);
|
2023-05-23 09:36:35 -07:00
|
|
|
|
const didFinishTestFunctionError = await testInfo._runAndFailOnError(async () => testInfo._onDidFinishTestFunction?.());
|
2023-05-01 13:53:15 -07:00
|
|
|
|
firstAfterHooksError = firstAfterHooksError || didFinishTestFunctionError;
|
|
|
|
|
debugTest(`on-test-function-finish callback finished`);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
2023-03-30 21:05:07 -07:00
|
|
|
|
// Run "afterEach" hooks, unless we failed at beforeAll stage.
|
|
|
|
|
if (shouldRunAfterEachHooks) {
|
2023-04-19 17:31:07 -07:00
|
|
|
|
const afterEachError = await testInfo._runAndFailOnError(() => this._runEachHooksForSuites(reversedSuites, 'afterEach', testInfo, afterHooksSlot));
|
2023-03-30 21:05:07 -07:00
|
|
|
|
firstAfterHooksError = firstAfterHooksError || afterEachError;
|
|
|
|
|
}
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
2023-05-02 11:04:51 -07:00
|
|
|
|
// Teardown test-scoped fixtures. Attribute to 'test' so that users understand
|
|
|
|
|
// they should probably increase the test timeout to fix this issue.
|
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'test', slot: afterHooksSlot });
|
|
|
|
|
debugTest(`tearing down test scope started`);
|
|
|
|
|
const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager));
|
|
|
|
|
debugTest(`tearing down test scope finished`);
|
|
|
|
|
firstAfterHooksError = firstAfterHooksError || testScopeError;
|
|
|
|
|
|
2023-03-30 21:05:07 -07:00
|
|
|
|
// Run "afterAll" hooks for suites that are not shared with the next test.
|
|
|
|
|
// In case of failure the worker will be stopped and we have to make sure that afterAll
|
2023-05-02 11:04:51 -07:00
|
|
|
|
// hooks run before worker fixtures teardown.
|
2022-03-08 16:35:14 -08:00
|
|
|
|
for (const suite of reversedSuites) {
|
2023-03-30 21:05:07 -07:00
|
|
|
|
if (!nextSuites.has(suite) || testInfo._isFailure()) {
|
|
|
|
|
const afterAllError = await this._runAfterAllHooksForSuite(suite, testInfo);
|
|
|
|
|
firstAfterHooksError = firstAfterHooksError || afterAllError;
|
|
|
|
|
}
|
2022-03-08 16:35:14 -08:00
|
|
|
|
}
|
|
|
|
|
});
|
2021-09-28 10:56:50 -07:00
|
|
|
|
|
2023-03-30 21:05:07 -07:00
|
|
|
|
if (testInfo._isFailure())
|
|
|
|
|
this._isStopped = true;
|
|
|
|
|
|
|
|
|
|
if (this._isStopped) {
|
|
|
|
|
// Run all remaining "afterAll" hooks and teardown all fixtures when worker is shutting down.
|
|
|
|
|
// Mark as "cleaned up" early to avoid running cleanup twice.
|
|
|
|
|
this._didRunFullCleanup = true;
|
|
|
|
|
|
|
|
|
|
// Give it more time for the full cleanup.
|
|
|
|
|
await testInfo._runWithTimeout(async () => {
|
|
|
|
|
debugTest(`running full cleanup after the failure`);
|
2023-05-02 11:04:51 -07:00
|
|
|
|
|
2023-04-07 09:54:01 -07:00
|
|
|
|
const teardownSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
2023-03-30 21:05:07 -07:00
|
|
|
|
// Attribute to 'test' so that users understand they should probably increate the test timeout to fix this issue.
|
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'test', slot: teardownSlot });
|
|
|
|
|
debugTest(`tearing down test scope started`);
|
2023-04-19 17:31:07 -07:00
|
|
|
|
const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager));
|
2023-03-30 21:05:07 -07:00
|
|
|
|
debugTest(`tearing down test scope finished`);
|
|
|
|
|
firstAfterHooksError = firstAfterHooksError || testScopeError;
|
2023-05-02 11:04:51 -07:00
|
|
|
|
|
|
|
|
|
for (const suite of reversedSuites) {
|
|
|
|
|
const afterAllError = await this._runAfterAllHooksForSuite(suite, testInfo);
|
|
|
|
|
firstAfterHooksError = firstAfterHooksError || afterAllError;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-30 21:05:07 -07:00
|
|
|
|
// Attribute to 'teardown' because worker fixtures are not perceived as a part of a test.
|
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'teardown', slot: teardownSlot });
|
|
|
|
|
debugTest(`tearing down worker scope started`);
|
2023-04-19 17:31:07 -07:00
|
|
|
|
const workerScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('worker', testInfo._timeoutManager));
|
2023-03-30 21:05:07 -07:00
|
|
|
|
debugTest(`tearing down worker scope finished`);
|
|
|
|
|
firstAfterHooksError = firstAfterHooksError || workerScopeError;
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-05-01 13:53:15 -07:00
|
|
|
|
|
2023-03-30 21:05:07 -07:00
|
|
|
|
if (firstAfterHooksError)
|
|
|
|
|
step.complete({ error: firstAfterHooksError });
|
|
|
|
|
});
|
|
|
|
|
|
2022-03-08 16:35:14 -08:00
|
|
|
|
this._currentTest = null;
|
|
|
|
|
setCurrentTestInfo(null);
|
2023-01-17 12:43:51 -08:00
|
|
|
|
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo));
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
2023-04-07 09:54:01 -07:00
|
|
|
|
const preserveOutput = this._config.config.preserveOutput === 'always' ||
|
|
|
|
|
(this._config.config.preserveOutput === 'failures-only' && testInfo._isFailure());
|
2021-09-28 10:56:50 -07:00
|
|
|
|
if (!preserveOutput)
|
2023-05-30 16:03:50 -07:00
|
|
|
|
await removeFolderAsync(testInfo.outputDir).catch(() => {});
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-17 09:36:03 -07:00
|
|
|
|
private async _runModifiersForSuite(suite: Suite, testInfo: TestInfoImpl, scope: 'worker' | 'test', timeSlot: TimeSlot | undefined, extraAnnotations?: Annotation[]) {
|
2022-03-08 16:35:14 -08:00
|
|
|
|
for (const modifier of suite._modifiers) {
|
|
|
|
|
const actualScope = this._fixtureRunner.dependsOnWorkerFixturesOnly(modifier.fn, modifier.location) ? 'worker' : 'test';
|
|
|
|
|
if (actualScope !== scope)
|
|
|
|
|
continue;
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`modifier at "${formatLocation(modifier.location)}" started`);
|
2022-03-17 09:36:03 -07:00
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type: modifier.type, location: modifier.location, slot: timeSlot });
|
2023-04-19 17:31:07 -07:00
|
|
|
|
const result = await testInfo._runAsStep({
|
2022-03-17 19:33:01 -07:00
|
|
|
|
category: 'hook',
|
|
|
|
|
title: `${modifier.type} modifier`,
|
|
|
|
|
location: modifier.location,
|
2023-04-19 17:31:07 -07:00
|
|
|
|
}, () => this._fixtureRunner.resolveParametersAndRunFunction(modifier.fn, testInfo, scope));
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`modifier at "${formatLocation(modifier.location)}" finished`);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
if (result && extraAnnotations)
|
|
|
|
|
extraAnnotations.push({ type: modifier.type, description: modifier.description });
|
|
|
|
|
testInfo[modifier.type](!!result, modifier.description);
|
|
|
|
|
}
|
2021-06-06 17:09:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 16:35:14 -08:00
|
|
|
|
private async _runBeforeAllHooksForSuite(suite: Suite, testInfo: TestInfoImpl) {
|
|
|
|
|
if (this._activeSuites.has(suite))
|
|
|
|
|
return;
|
|
|
|
|
this._activeSuites.add(suite);
|
|
|
|
|
let beforeAllError: Error | undefined;
|
|
|
|
|
for (const hook of suite._hooks) {
|
|
|
|
|
if (hook.type !== 'beforeAll')
|
|
|
|
|
continue;
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`${hook.type} hook at "${formatLocation(hook.location)}" started`);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
try {
|
2022-03-17 09:36:03 -07:00
|
|
|
|
// Separate time slot for each "beforeAll" hook.
|
2023-04-07 09:54:01 -07:00
|
|
|
|
const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
2022-03-17 09:36:03 -07:00
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'beforeAll', location: hook.location, slot: timeSlot });
|
2023-04-19 17:31:07 -07:00
|
|
|
|
await testInfo._runAsStep({
|
2022-03-17 19:33:01 -07:00
|
|
|
|
category: 'hook',
|
|
|
|
|
title: `${hook.type} hook`,
|
|
|
|
|
location: hook.location,
|
2023-05-02 11:04:51 -07:00
|
|
|
|
}, async () => {
|
|
|
|
|
try {
|
|
|
|
|
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only');
|
|
|
|
|
} finally {
|
|
|
|
|
// Each beforeAll hook has its own scope for test fixtures. Attribute to the same runnable and timeSlot.
|
|
|
|
|
// Note: we must teardown even after beforeAll fails, because we'll run more beforeAlls.
|
|
|
|
|
await this._fixtureRunner.teardownScope('test', testInfo._timeoutManager);
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-03-08 16:35:14 -08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
// Always run all the hooks, and capture the first error.
|
|
|
|
|
beforeAllError = beforeAllError || e;
|
|
|
|
|
}
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`${hook.type} hook at "${formatLocation(hook.location)}" finished`);
|
2022-03-08 16:35:14 -08:00
|
|
|
|
}
|
|
|
|
|
if (beforeAllError)
|
|
|
|
|
throw beforeAllError;
|
2022-02-28 11:42:47 -08:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 16:35:14 -08:00
|
|
|
|
private async _runAfterAllHooksForSuite(suite: Suite, testInfo: TestInfoImpl) {
|
|
|
|
|
if (!this._activeSuites.has(suite))
|
|
|
|
|
return;
|
|
|
|
|
this._activeSuites.delete(suite);
|
2022-12-21 09:36:59 -08:00
|
|
|
|
let firstError: TestInfoError | undefined;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
for (const hook of suite._hooks) {
|
|
|
|
|
if (hook.type !== 'afterAll')
|
|
|
|
|
continue;
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`${hook.type} hook at "${formatLocation(hook.location)}" started`);
|
2023-04-19 17:31:07 -07:00
|
|
|
|
const afterAllError = await testInfo._runAndFailOnError(async () => {
|
2022-03-17 09:36:03 -07:00
|
|
|
|
// Separate time slot for each "afterAll" hook.
|
2023-04-07 09:54:01 -07:00
|
|
|
|
const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
2022-03-17 09:36:03 -07:00
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'afterAll', location: hook.location, slot: timeSlot });
|
2023-04-19 17:31:07 -07:00
|
|
|
|
await testInfo._runAsStep({
|
2022-03-17 19:33:01 -07:00
|
|
|
|
category: 'hook',
|
|
|
|
|
title: `${hook.type} hook`,
|
|
|
|
|
location: hook.location,
|
2023-05-02 11:04:51 -07:00
|
|
|
|
}, async () => {
|
|
|
|
|
try {
|
|
|
|
|
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only');
|
|
|
|
|
} finally {
|
|
|
|
|
// Each afterAll hook has its own scope for test fixtures. Attribute to the same runnable and timeSlot.
|
|
|
|
|
// Note: we must teardown even after afterAll fails, because we'll run more afterAlls.
|
|
|
|
|
await this._fixtureRunner.teardownScope('test', testInfo._timeoutManager);
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-03-08 16:35:14 -08:00
|
|
|
|
});
|
|
|
|
|
firstError = firstError || afterAllError;
|
2022-08-04 18:49:23 -07:00
|
|
|
|
debugTest(`${hook.type} hook at "${formatLocation(hook.location)}" finished`);
|
2022-03-01 09:11:17 -08:00
|
|
|
|
}
|
2022-03-08 16:35:14 -08:00
|
|
|
|
return firstError;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-17 09:36:03 -07:00
|
|
|
|
private async _runEachHooksForSuites(suites: Suite[], type: 'beforeEach' | 'afterEach', testInfo: TestInfoImpl, timeSlot: TimeSlot | undefined) {
|
2022-03-08 16:35:14 -08:00
|
|
|
|
const hooks = suites.map(suite => suite._hooks.filter(hook => hook.type === type)).flat();
|
2021-06-06 17:09:53 -07:00
|
|
|
|
let error: Error | undefined;
|
2022-03-08 16:35:14 -08:00
|
|
|
|
for (const hook of hooks) {
|
2021-06-06 17:09:53 -07:00
|
|
|
|
try {
|
2022-03-17 09:36:03 -07:00
|
|
|
|
testInfo._timeoutManager.setCurrentRunnable({ type, location: hook.location, slot: timeSlot });
|
2023-04-19 17:31:07 -07:00
|
|
|
|
await testInfo._runAsStep({
|
2022-03-17 19:33:01 -07:00
|
|
|
|
category: 'hook',
|
|
|
|
|
title: `${hook.type} hook`,
|
|
|
|
|
location: hook.location,
|
2023-04-19 17:31:07 -07:00
|
|
|
|
}, () => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'test'));
|
2021-06-06 17:09:53 -07:00
|
|
|
|
} catch (e) {
|
|
|
|
|
// Always run all the hooks, and capture the first error.
|
|
|
|
|
error = error || e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (error)
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 17:39:42 -08:00
|
|
|
|
function buildTestBeginPayload(testInfo: TestInfoImpl): TestBeginPayload {
|
2021-06-06 17:09:53 -07:00
|
|
|
|
return {
|
2022-07-27 20:17:19 -07:00
|
|
|
|
testId: testInfo._test.id,
|
2022-01-28 17:39:42 -08:00
|
|
|
|
startWallTime: testInfo._startWallTime,
|
2021-06-06 17:09:53 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 17:39:42 -08:00
|
|
|
|
function buildTestEndPayload(testInfo: TestInfoImpl): TestEndPayload {
|
2021-06-06 17:09:53 -07:00
|
|
|
|
return {
|
2022-07-27 20:17:19 -07:00
|
|
|
|
testId: testInfo._test.id,
|
2021-06-06 17:09:53 -07:00
|
|
|
|
duration: testInfo.duration,
|
|
|
|
|
status: testInfo.status!,
|
2022-02-02 19:33:51 -07:00
|
|
|
|
errors: testInfo.errors,
|
2021-06-06 17:09:53 -07:00
|
|
|
|
expectedStatus: testInfo.expectedStatus,
|
|
|
|
|
annotations: testInfo.annotations,
|
|
|
|
|
timeout: testInfo.timeout,
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-03-08 16:35:14 -08:00
|
|
|
|
|
|
|
|
|
function getSuites(test: TestCase | undefined): Suite[] {
|
|
|
|
|
const suites: Suite[] = [];
|
|
|
|
|
for (let suite: Suite | undefined = test?.parent; suite; suite = suite.parent)
|
|
|
|
|
suites.push(suite);
|
|
|
|
|
suites.reverse(); // Put root suite first.
|
|
|
|
|
return suites;
|
|
|
|
|
}
|
2022-03-28 19:58:24 -07:00
|
|
|
|
|
|
|
|
|
function formatTestTitle(test: TestCase, projectName: string) {
|
|
|
|
|
// file, ...describes, test
|
|
|
|
|
const [, ...titles] = test.titlePath();
|
|
|
|
|
const location = `${relativeFilePath(test.location.file)}:${test.location.line}:${test.location.column}`;
|
|
|
|
|
const projectTitle = projectName ? `[${projectName}] › ` : '';
|
|
|
|
|
return `${projectTitle}${location} › ${titles.join(' › ')}`;
|
|
|
|
|
}
|
2023-01-17 12:43:51 -08:00
|
|
|
|
|
2023-01-20 18:24:15 -08:00
|
|
|
|
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 };
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-22 15:04:29 -08:00
|
|
|
|
export const create = (params: WorkerInitParams) => new WorkerMain(params);
|