screenshot/test_1
This commit is contained in:
264
node_modules/playwright/lib/worker/fixtureRunner.js
generated
vendored
Normal file
264
node_modules/playwright/lib/worker/fixtureRunner.js
generated
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.FixtureRunner = void 0;
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _fixtures = require("../common/fixtures");
|
||||
var _util = require("../util");
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Fixture {
|
||||
constructor(runner, registration) {
|
||||
this.runner = void 0;
|
||||
this.registration = void 0;
|
||||
this.value = void 0;
|
||||
this.failed = false;
|
||||
this._useFuncFinished = void 0;
|
||||
this._selfTeardownComplete = void 0;
|
||||
this._setupDescription = void 0;
|
||||
this._teardownDescription = void 0;
|
||||
this._stepInfo = void 0;
|
||||
this._deps = new Set();
|
||||
this._usages = new Set();
|
||||
this.runner = runner;
|
||||
this.registration = registration;
|
||||
this.value = null;
|
||||
const shouldGenerateStep = !this.registration.box && !this.registration.option;
|
||||
const isUserFixture = this.registration.location && (0, _util.filterStackFile)(this.registration.location.file);
|
||||
const title = this.registration.customTitle || this.registration.name;
|
||||
const location = isUserFixture ? this.registration.location : undefined;
|
||||
this._stepInfo = shouldGenerateStep ? {
|
||||
title: `fixture: ${title}`,
|
||||
category: 'fixture',
|
||||
location
|
||||
} : undefined;
|
||||
this._setupDescription = {
|
||||
title,
|
||||
phase: 'setup',
|
||||
location,
|
||||
slot: this.registration.timeout === undefined ? undefined : {
|
||||
timeout: this.registration.timeout,
|
||||
elapsed: 0
|
||||
}
|
||||
};
|
||||
this._teardownDescription = {
|
||||
...this._setupDescription,
|
||||
phase: 'teardown'
|
||||
};
|
||||
}
|
||||
async setup(testInfo, runnable) {
|
||||
this.runner.instanceForId.set(this.registration.id, this);
|
||||
if (typeof this.registration.fn !== 'function') {
|
||||
this.value = this.registration.fn;
|
||||
return;
|
||||
}
|
||||
const run = () => testInfo._runWithTimeout({
|
||||
...runnable,
|
||||
fixture: this._setupDescription
|
||||
}, () => this._setupInternal(testInfo));
|
||||
if (this._stepInfo) await testInfo._runAsStep(this._stepInfo, run);else await run();
|
||||
}
|
||||
async _setupInternal(testInfo) {
|
||||
const params = {};
|
||||
for (const name of this.registration.deps) {
|
||||
const registration = this.runner.pool.resolve(name, this.registration);
|
||||
const dep = this.runner.instanceForId.get(registration.id);
|
||||
if (!dep) {
|
||||
this.failed = true;
|
||||
return;
|
||||
}
|
||||
// Fixture teardown is root => leaves, when we need to teardown a fixture,
|
||||
// it recursively tears down its usages first.
|
||||
dep._usages.add(this);
|
||||
// Don't forget to decrement all usages when fixture goes.
|
||||
// Otherwise worker-scope fixtures will retain test-scope fixtures forever.
|
||||
this._deps.add(dep);
|
||||
params[name] = dep.value;
|
||||
if (dep.failed) {
|
||||
this.failed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
let called = false;
|
||||
const useFuncStarted = new _utils.ManualPromise();
|
||||
const useFunc = async value => {
|
||||
if (called) throw new Error(`Cannot provide fixture value for the second time`);
|
||||
called = true;
|
||||
this.value = value;
|
||||
this._useFuncFinished = new _utils.ManualPromise();
|
||||
useFuncStarted.resolve();
|
||||
await this._useFuncFinished;
|
||||
};
|
||||
const workerInfo = {
|
||||
config: testInfo.config,
|
||||
parallelIndex: testInfo.parallelIndex,
|
||||
workerIndex: testInfo.workerIndex,
|
||||
project: testInfo.project
|
||||
};
|
||||
const info = this.registration.scope === 'worker' ? workerInfo : testInfo;
|
||||
this._selfTeardownComplete = (async () => {
|
||||
try {
|
||||
await this.registration.fn(params, useFunc, info);
|
||||
} catch (error) {
|
||||
this.failed = true;
|
||||
if (!useFuncStarted.isDone()) useFuncStarted.reject(error);else throw error;
|
||||
}
|
||||
})();
|
||||
await useFuncStarted;
|
||||
}
|
||||
async teardown(testInfo, runnable) {
|
||||
try {
|
||||
const fixtureRunnable = {
|
||||
...runnable,
|
||||
fixture: this._teardownDescription
|
||||
};
|
||||
// Do not even start the teardown for a fixture that does not have any
|
||||
// time remaining in the time slot. This avoids cascading timeouts.
|
||||
if (!testInfo._timeoutManager.isTimeExhaustedFor(fixtureRunnable)) {
|
||||
const run = () => testInfo._runWithTimeout(fixtureRunnable, () => this._teardownInternal());
|
||||
if (this._stepInfo) await testInfo._runAsStep(this._stepInfo, run);else await run();
|
||||
}
|
||||
} finally {
|
||||
// To preserve fixtures integrity, forcefully cleanup fixtures
|
||||
// that cannnot teardown due to a timeout or an error.
|
||||
for (const dep of this._deps) dep._usages.delete(this);
|
||||
this.runner.instanceForId.delete(this.registration.id);
|
||||
}
|
||||
}
|
||||
async _teardownInternal() {
|
||||
if (typeof this.registration.fn !== 'function') return;
|
||||
if (this._usages.size !== 0) {
|
||||
// TODO: replace with assert.
|
||||
console.error('Internal error: fixture integrity at', this._teardownDescription.title); // eslint-disable-line no-console
|
||||
this._usages.clear();
|
||||
}
|
||||
if (this._useFuncFinished) {
|
||||
this._useFuncFinished.resolve();
|
||||
this._useFuncFinished = undefined;
|
||||
await this._selfTeardownComplete;
|
||||
}
|
||||
}
|
||||
_collectFixturesInTeardownOrder(scope, collector) {
|
||||
if (this.registration.scope !== scope) return;
|
||||
for (const fixture of this._usages) fixture._collectFixturesInTeardownOrder(scope, collector);
|
||||
collector.add(this);
|
||||
}
|
||||
}
|
||||
class FixtureRunner {
|
||||
constructor() {
|
||||
this.testScopeClean = true;
|
||||
this.pool = void 0;
|
||||
this.instanceForId = new Map();
|
||||
}
|
||||
setPool(pool) {
|
||||
if (!this.testScopeClean) throw new Error('Did not teardown test scope');
|
||||
if (this.pool && pool.digest !== this.pool.digest) {
|
||||
throw new Error([`Playwright detected inconsistent test.use() options.`, `Most common mistakes that lead to this issue:`, ` - Calling test.use() outside of the test file, for example in a common helper.`, ` - One test file imports from another test file.`].join('\n'));
|
||||
}
|
||||
this.pool = pool;
|
||||
}
|
||||
_collectFixturesInSetupOrder(registration, collector) {
|
||||
if (collector.has(registration)) return;
|
||||
for (const name of registration.deps) {
|
||||
const dep = this.pool.resolve(name, registration);
|
||||
this._collectFixturesInSetupOrder(dep, collector);
|
||||
}
|
||||
collector.add(registration);
|
||||
}
|
||||
async teardownScope(scope, testInfo, runnable) {
|
||||
// Teardown fixtures in the reverse order.
|
||||
const fixtures = Array.from(this.instanceForId.values()).reverse();
|
||||
const collector = new Set();
|
||||
for (const fixture of fixtures) fixture._collectFixturesInTeardownOrder(scope, collector);
|
||||
let firstError;
|
||||
for (const fixture of collector) {
|
||||
try {
|
||||
await fixture.teardown(testInfo, runnable);
|
||||
} catch (error) {
|
||||
firstError = firstError !== null && firstError !== void 0 ? firstError : error;
|
||||
}
|
||||
}
|
||||
if (scope === 'test') this.testScopeClean = true;
|
||||
if (firstError) throw firstError;
|
||||
}
|
||||
async resolveParametersForFunction(fn, testInfo, autoFixtures, runnable) {
|
||||
const collector = new Set();
|
||||
|
||||
// Collect automatic fixtures.
|
||||
const auto = [];
|
||||
for (const registration of this.pool.autoFixtures()) {
|
||||
let shouldRun = true;
|
||||
if (autoFixtures === 'all-hooks-only') shouldRun = registration.scope === 'worker' || registration.auto === 'all-hooks-included';else if (autoFixtures === 'worker') shouldRun = registration.scope === 'worker';
|
||||
if (shouldRun) auto.push(registration);
|
||||
}
|
||||
auto.sort((r1, r2) => (r1.scope === 'worker' ? 0 : 1) - (r2.scope === 'worker' ? 0 : 1));
|
||||
for (const registration of auto) this._collectFixturesInSetupOrder(registration, collector);
|
||||
|
||||
// Collect used fixtures.
|
||||
const names = getRequiredFixtureNames(fn);
|
||||
for (const name of names) this._collectFixturesInSetupOrder(this.pool.resolve(name), collector);
|
||||
|
||||
// Setup fixtures.
|
||||
for (const registration of collector) await this._setupFixtureForRegistration(registration, testInfo, runnable);
|
||||
|
||||
// Create params object.
|
||||
const params = {};
|
||||
for (const name of names) {
|
||||
const registration = this.pool.resolve(name);
|
||||
const fixture = this.instanceForId.get(registration.id);
|
||||
if (!fixture || fixture.failed) return null;
|
||||
params[name] = fixture.value;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
async resolveParametersAndRunFunction(fn, testInfo, autoFixtures, runnable) {
|
||||
const params = await this.resolveParametersForFunction(fn, testInfo, autoFixtures, runnable);
|
||||
if (params === null) {
|
||||
// Do not run the function when fixture setup has already failed.
|
||||
return null;
|
||||
}
|
||||
await testInfo._runWithTimeout(runnable, () => fn(params, testInfo));
|
||||
}
|
||||
async _setupFixtureForRegistration(registration, testInfo, runnable) {
|
||||
if (registration.scope === 'test') this.testScopeClean = false;
|
||||
let fixture = this.instanceForId.get(registration.id);
|
||||
if (fixture) return fixture;
|
||||
fixture = new Fixture(this, registration);
|
||||
await fixture.setup(testInfo, runnable);
|
||||
return fixture;
|
||||
}
|
||||
dependsOnWorkerFixturesOnly(fn, location) {
|
||||
const names = getRequiredFixtureNames(fn, location);
|
||||
for (const name of names) {
|
||||
const registration = this.pool.resolve(name);
|
||||
if (registration.scope !== 'worker') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
exports.FixtureRunner = FixtureRunner;
|
||||
function getRequiredFixtureNames(fn, location) {
|
||||
return (0, _fixtures.fixtureParameterNames)(fn, location !== null && location !== void 0 ? location : {
|
||||
file: '<unknown>',
|
||||
line: 1,
|
||||
column: 1
|
||||
}, e => {
|
||||
throw new Error(`${(0, _util.formatLocation)(e.location)}: ${e.message}`);
|
||||
});
|
||||
}
|
||||
57
node_modules/playwright/lib/worker/floatingPromiseScope.js
generated
vendored
Normal file
57
node_modules/playwright/lib/worker/floatingPromiseScope.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.FloatingPromiseScope = void 0;
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class FloatingPromiseScope {
|
||||
constructor() {
|
||||
this._floatingCalls = new Set();
|
||||
}
|
||||
/**
|
||||
* Enables a promise API call to be tracked by the test, alerting if unawaited.
|
||||
*
|
||||
* **NOTE:** Returning from an async function wraps the result in a promise, regardless of whether the return value is a promise. This will automatically mark the promise as awaited. Avoid this.
|
||||
*/
|
||||
wrapPromiseAPIResult(promise) {
|
||||
if (process.env.PW_DISABLE_FLOATING_PROMISES_WARNING) return promise;
|
||||
const promiseProxy = new Proxy(promise, {
|
||||
get: (target, prop, receiver) => {
|
||||
if (prop === 'then') {
|
||||
return (...args) => {
|
||||
this._floatingCalls.delete(promise);
|
||||
const originalThen = Reflect.get(target, prop, receiver);
|
||||
return originalThen.call(target, ...args);
|
||||
};
|
||||
} else {
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
}
|
||||
});
|
||||
this._floatingCalls.add(promise);
|
||||
return promiseProxy;
|
||||
}
|
||||
clear() {
|
||||
this._floatingCalls.clear();
|
||||
}
|
||||
hasFloatingPromises() {
|
||||
return this._floatingCalls.size > 0;
|
||||
}
|
||||
}
|
||||
exports.FloatingPromiseScope = FloatingPromiseScope;
|
||||
452
node_modules/playwright/lib/worker/testInfo.js
generated
vendored
Normal file
452
node_modules/playwright/lib/worker/testInfo.js
generated
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.TestStepInfoImpl = exports.TestSkipError = exports.TestInfoImpl = exports.StepSkipError = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _timeoutManager = require("./timeoutManager");
|
||||
var _util = require("../util");
|
||||
var _testTracing = require("./testTracing");
|
||||
var _util2 = require("./util");
|
||||
var _floatingPromiseScope = require("./floatingPromiseScope");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class TestInfoImpl {
|
||||
get error() {
|
||||
return this.errors[0];
|
||||
}
|
||||
set error(e) {
|
||||
if (e === undefined) throw new Error('Cannot assign testInfo.error undefined value!');
|
||||
this.errors[0] = e;
|
||||
}
|
||||
get timeout() {
|
||||
return this._timeoutManager.defaultSlot().timeout;
|
||||
}
|
||||
set timeout(timeout) {
|
||||
// Ignored.
|
||||
}
|
||||
_deadlineForMatcher(timeout) {
|
||||
const startTime = (0, _utils.monotonicTime)();
|
||||
const matcherDeadline = timeout ? startTime + timeout : _timeoutManager.kMaxDeadline;
|
||||
const testDeadline = this._timeoutManager.currentSlotDeadline() - 250;
|
||||
const matcherMessage = `Timeout ${timeout}ms exceeded while waiting on the predicate`;
|
||||
const testMessage = `Test timeout of ${this.timeout}ms exceeded`;
|
||||
return {
|
||||
deadline: Math.min(testDeadline, matcherDeadline),
|
||||
timeoutMessage: testDeadline < matcherDeadline ? testMessage : matcherMessage
|
||||
};
|
||||
}
|
||||
static _defaultDeadlineForMatcher(timeout) {
|
||||
return {
|
||||
deadline: timeout ? (0, _utils.monotonicTime)() + timeout : 0,
|
||||
timeoutMessage: `Timeout ${timeout}ms exceeded while waiting on the predicate`
|
||||
};
|
||||
}
|
||||
constructor(configInternal, projectInternal, workerParams, test, retry, onStepBegin, onStepEnd, onAttach) {
|
||||
var _test$id, _test$_requireFile, _test$title, _test$titlePath, _test$location$file, _test$location$line, _test$location$column, _test$tags, _test$fn, _test$expectedStatus;
|
||||
this._onStepBegin = void 0;
|
||||
this._onStepEnd = void 0;
|
||||
this._onAttach = void 0;
|
||||
this._timeoutManager = void 0;
|
||||
this._startTime = void 0;
|
||||
this._startWallTime = void 0;
|
||||
this._tracing = void 0;
|
||||
this._floatingPromiseScope = new _floatingPromiseScope.FloatingPromiseScope();
|
||||
this._wasInterrupted = false;
|
||||
this._lastStepId = 0;
|
||||
this._requireFile = void 0;
|
||||
this._projectInternal = void 0;
|
||||
this._configInternal = void 0;
|
||||
this._steps = [];
|
||||
this._stepMap = new Map();
|
||||
this._onDidFinishTestFunction = void 0;
|
||||
this._hasNonRetriableError = false;
|
||||
this._hasUnhandledError = false;
|
||||
this._allowSkips = false;
|
||||
// ------------ TestInfo fields ------------
|
||||
this.testId = void 0;
|
||||
this.repeatEachIndex = void 0;
|
||||
this.retry = void 0;
|
||||
this.workerIndex = void 0;
|
||||
this.parallelIndex = void 0;
|
||||
this.project = void 0;
|
||||
this.config = void 0;
|
||||
this.title = void 0;
|
||||
this.titlePath = void 0;
|
||||
this.file = void 0;
|
||||
this.line = void 0;
|
||||
this.tags = void 0;
|
||||
this.column = void 0;
|
||||
this.fn = void 0;
|
||||
this.expectedStatus = void 0;
|
||||
this.duration = 0;
|
||||
this.annotations = [];
|
||||
this.attachments = [];
|
||||
this.status = 'passed';
|
||||
this.snapshotSuffix = '';
|
||||
this.outputDir = void 0;
|
||||
this.snapshotDir = void 0;
|
||||
this.errors = [];
|
||||
this._attachmentsPush = void 0;
|
||||
this.testId = (_test$id = test === null || test === void 0 ? void 0 : test.id) !== null && _test$id !== void 0 ? _test$id : '';
|
||||
this._onStepBegin = onStepBegin;
|
||||
this._onStepEnd = onStepEnd;
|
||||
this._onAttach = onAttach;
|
||||
this._startTime = (0, _utils.monotonicTime)();
|
||||
this._startWallTime = Date.now();
|
||||
this._requireFile = (_test$_requireFile = test === null || test === void 0 ? void 0 : test._requireFile) !== null && _test$_requireFile !== void 0 ? _test$_requireFile : '';
|
||||
this.repeatEachIndex = workerParams.repeatEachIndex;
|
||||
this.retry = retry;
|
||||
this.workerIndex = workerParams.workerIndex;
|
||||
this.parallelIndex = workerParams.parallelIndex;
|
||||
this._projectInternal = projectInternal;
|
||||
this.project = projectInternal.project;
|
||||
this._configInternal = configInternal;
|
||||
this.config = configInternal.config;
|
||||
this.title = (_test$title = test === null || test === void 0 ? void 0 : test.title) !== null && _test$title !== void 0 ? _test$title : '';
|
||||
this.titlePath = (_test$titlePath = test === null || test === void 0 ? void 0 : test.titlePath()) !== null && _test$titlePath !== void 0 ? _test$titlePath : [];
|
||||
this.file = (_test$location$file = test === null || test === void 0 ? void 0 : test.location.file) !== null && _test$location$file !== void 0 ? _test$location$file : '';
|
||||
this.line = (_test$location$line = test === null || test === void 0 ? void 0 : test.location.line) !== null && _test$location$line !== void 0 ? _test$location$line : 0;
|
||||
this.column = (_test$location$column = test === null || test === void 0 ? void 0 : test.location.column) !== null && _test$location$column !== void 0 ? _test$location$column : 0;
|
||||
this.tags = (_test$tags = test === null || test === void 0 ? void 0 : test.tags) !== null && _test$tags !== void 0 ? _test$tags : [];
|
||||
this.fn = (_test$fn = test === null || test === void 0 ? void 0 : test.fn) !== null && _test$fn !== void 0 ? _test$fn : () => {};
|
||||
this.expectedStatus = (_test$expectedStatus = test === null || test === void 0 ? void 0 : test.expectedStatus) !== null && _test$expectedStatus !== void 0 ? _test$expectedStatus : 'skipped';
|
||||
this._timeoutManager = new _timeoutManager.TimeoutManager(this.project.timeout);
|
||||
if (configInternal.configCLIOverrides.debug) this._setDebugMode();
|
||||
this.outputDir = (() => {
|
||||
const relativeTestFilePath = _path.default.relative(this.project.testDir, this._requireFile.replace(/\.(spec|test)\.(js|ts|jsx|tsx|mjs|mts|cjs|cts)$/, ''));
|
||||
const sanitizedRelativePath = relativeTestFilePath.replace(process.platform === 'win32' ? new RegExp('\\\\', 'g') : new RegExp('/', 'g'), '-');
|
||||
const fullTitleWithoutSpec = this.titlePath.slice(1).join(' ');
|
||||
let testOutputDir = (0, _util.trimLongString)(sanitizedRelativePath + '-' + (0, _utils.sanitizeForFilePath)(fullTitleWithoutSpec), _util.windowsFilesystemFriendlyLength);
|
||||
if (projectInternal.id) testOutputDir += '-' + (0, _utils.sanitizeForFilePath)(projectInternal.id);
|
||||
if (this.retry) testOutputDir += '-retry' + this.retry;
|
||||
if (this.repeatEachIndex) testOutputDir += '-repeat' + this.repeatEachIndex;
|
||||
return _path.default.join(this.project.outputDir, testOutputDir);
|
||||
})();
|
||||
this.snapshotDir = (() => {
|
||||
const relativeTestFilePath = _path.default.relative(this.project.testDir, this._requireFile);
|
||||
return _path.default.join(this.project.snapshotDir, relativeTestFilePath + '-snapshots');
|
||||
})();
|
||||
this._attachmentsPush = this.attachments.push.bind(this.attachments);
|
||||
this.attachments.push = (...attachments) => {
|
||||
for (const a of attachments) {
|
||||
var _this$_parentStep;
|
||||
this._attach(a, (_this$_parentStep = this._parentStep()) === null || _this$_parentStep === void 0 ? void 0 : _this$_parentStep.stepId);
|
||||
}
|
||||
return this.attachments.length;
|
||||
};
|
||||
this._tracing = new _testTracing.TestTracing(this, workerParams.artifactsDir);
|
||||
}
|
||||
_modifier(type, modifierArgs) {
|
||||
if (typeof modifierArgs[1] === 'function') {
|
||||
throw new Error(['It looks like you are calling test.skip() inside the test and pass a callback.', 'Pass a condition instead and optional description instead:', `test('my test', async ({ page, isMobile }) => {`, ` test.skip(isMobile, 'This test is not applicable on mobile');`, `});`].join('\n'));
|
||||
}
|
||||
if (modifierArgs.length >= 1 && !modifierArgs[0]) return;
|
||||
const description = modifierArgs[1];
|
||||
this.annotations.push({
|
||||
type,
|
||||
description
|
||||
});
|
||||
if (type === 'slow') {
|
||||
this._timeoutManager.slow();
|
||||
} else if (type === 'skip' || type === 'fixme') {
|
||||
this.expectedStatus = 'skipped';
|
||||
throw new TestSkipError('Test is skipped: ' + (description || ''));
|
||||
} else if (type === 'fail') {
|
||||
if (this.expectedStatus !== 'skipped') this.expectedStatus = 'failed';
|
||||
}
|
||||
}
|
||||
_findLastPredefinedStep(steps) {
|
||||
// Find the deepest predefined step that has not finished yet.
|
||||
for (let i = steps.length - 1; i >= 0; i--) {
|
||||
const child = this._findLastPredefinedStep(steps[i].steps);
|
||||
if (child) return child;
|
||||
if ((steps[i].category === 'hook' || steps[i].category === 'fixture') && !steps[i].endWallTime) return steps[i];
|
||||
}
|
||||
}
|
||||
_parentStep() {
|
||||
var _currentZone$data;
|
||||
return (_currentZone$data = (0, _utils.currentZone)().data('stepZone')) !== null && _currentZone$data !== void 0 ? _currentZone$data : this._findLastPredefinedStep(this._steps);
|
||||
}
|
||||
_addStep(data, parentStep) {
|
||||
var _parentStep, _parentStep2;
|
||||
const stepId = `${data.category}@${++this._lastStepId}`;
|
||||
if (data.category === 'hook' || data.category === 'fixture') {
|
||||
// Predefined steps form a fixed hierarchy - use the current one as parent.
|
||||
parentStep = this._findLastPredefinedStep(this._steps);
|
||||
} else {
|
||||
if (!parentStep) parentStep = this._parentStep();
|
||||
}
|
||||
const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)());
|
||||
data.boxedStack = (_parentStep = parentStep) === null || _parentStep === void 0 ? void 0 : _parentStep.boxedStack;
|
||||
if (!data.boxedStack && data.box) {
|
||||
data.boxedStack = filteredStack.slice(1);
|
||||
data.location = data.location || data.boxedStack[0];
|
||||
}
|
||||
data.location = data.location || filteredStack[0];
|
||||
const attachmentIndices = [];
|
||||
const step = {
|
||||
stepId,
|
||||
...data,
|
||||
steps: [],
|
||||
attachmentIndices,
|
||||
info: new TestStepInfoImpl(this, stepId),
|
||||
complete: result => {
|
||||
if (step.endWallTime) return;
|
||||
step.endWallTime = Date.now();
|
||||
if (result.error) {
|
||||
var _result$error;
|
||||
if (typeof result.error === 'object' && !((_result$error = result.error) !== null && _result$error !== void 0 && _result$error[stepSymbol])) result.error[stepSymbol] = step;
|
||||
const error = (0, _util2.testInfoError)(result.error);
|
||||
if (data.boxedStack) error.stack = `${error.message}\n${(0, _utils.stringifyStackFrames)(data.boxedStack).join('\n')}`;
|
||||
step.error = error;
|
||||
}
|
||||
if (!step.error) {
|
||||
// Soft errors inside try/catch will make the test fail.
|
||||
// In order to locate the failing step, we are marking all the parent
|
||||
// steps as failing unconditionally.
|
||||
for (const childStep of step.steps) {
|
||||
if (childStep.error && childStep.infectParentStepsWithError) {
|
||||
step.error = childStep.error;
|
||||
step.infectParentStepsWithError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
testId: this.testId,
|
||||
stepId,
|
||||
wallTime: step.endWallTime,
|
||||
error: step.error,
|
||||
suggestedRebaseline: result.suggestedRebaseline,
|
||||
annotations: step.info.annotations
|
||||
};
|
||||
this._onStepEnd(payload);
|
||||
const errorForTrace = step.error ? {
|
||||
name: '',
|
||||
message: step.error.message || '',
|
||||
stack: step.error.stack
|
||||
} : undefined;
|
||||
const attachments = attachmentIndices.map(i => this.attachments[i]);
|
||||
this._tracing.appendAfterActionForStep(stepId, errorForTrace, attachments, step.info.annotations);
|
||||
}
|
||||
};
|
||||
const parentStepList = parentStep ? parentStep.steps : this._steps;
|
||||
parentStepList.push(step);
|
||||
this._stepMap.set(stepId, step);
|
||||
const payload = {
|
||||
testId: this.testId,
|
||||
stepId,
|
||||
parentStepId: parentStep ? parentStep.stepId : undefined,
|
||||
title: data.title,
|
||||
category: data.category,
|
||||
wallTime: Date.now(),
|
||||
location: data.location
|
||||
};
|
||||
this._onStepBegin(payload);
|
||||
this._tracing.appendBeforeActionForStep(stepId, (_parentStep2 = parentStep) === null || _parentStep2 === void 0 ? void 0 : _parentStep2.stepId, data.category, data.apiName || data.title, data.params, data.location ? [data.location] : []);
|
||||
return step;
|
||||
}
|
||||
_interrupt() {
|
||||
// Mark as interrupted so we can ignore TimeoutError thrown by interrupt() call.
|
||||
this._wasInterrupted = true;
|
||||
this._timeoutManager.interrupt();
|
||||
// Do not overwrite existing failure (for example, unhandled rejection) with "interrupted".
|
||||
if (this.status === 'passed') this.status = 'interrupted';
|
||||
}
|
||||
_failWithError(error) {
|
||||
if (this.status === 'passed' || this.status === 'skipped') this.status = error instanceof _timeoutManager.TimeoutManagerError ? 'timedOut' : 'failed';
|
||||
const serialized = (0, _util2.testInfoError)(error);
|
||||
const step = typeof error === 'object' ? error === null || error === void 0 ? void 0 : error[stepSymbol] : undefined;
|
||||
if (step && step.boxedStack) serialized.stack = `${error.name}: ${error.message}\n${(0, _utils.stringifyStackFrames)(step.boxedStack).join('\n')}`;
|
||||
this.errors.push(serialized);
|
||||
this._tracing.appendForError(serialized);
|
||||
}
|
||||
async _runAsStep(stepInfo, cb) {
|
||||
const step = this._addStep(stepInfo);
|
||||
try {
|
||||
await cb();
|
||||
step.complete({});
|
||||
} catch (error) {
|
||||
step.complete({
|
||||
error
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async _runWithTimeout(runnable, cb) {
|
||||
try {
|
||||
await this._timeoutManager.withRunnable(runnable, async () => {
|
||||
try {
|
||||
await cb();
|
||||
} catch (e) {
|
||||
if (this._allowSkips && e instanceof TestSkipError) {
|
||||
if (this.status === 'passed') this.status = 'skipped';
|
||||
} else {
|
||||
// Unfortunately, we have to handle user errors and timeout errors differently.
|
||||
// Consider the following scenario:
|
||||
// - locator.click times out
|
||||
// - all steps containing the test function finish with TimeoutManagerError
|
||||
// - test finishes, the page is closed and this triggers locator.click error
|
||||
// - we would like to present the locator.click error to the user
|
||||
// - therefore, we need a try/catch inside the "run with timeout" block and capture the error
|
||||
this._failWithError(e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// When interrupting, we arrive here with a TimeoutManagerError, but we should not
|
||||
// consider it a timeout.
|
||||
if (!this._wasInterrupted && error instanceof _timeoutManager.TimeoutManagerError) this._failWithError(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
_isFailure() {
|
||||
return this.status !== 'skipped' && this.status !== this.expectedStatus;
|
||||
}
|
||||
_currentHookType() {
|
||||
const type = this._timeoutManager.currentSlotType();
|
||||
return ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'].includes(type) ? type : undefined;
|
||||
}
|
||||
_setDebugMode() {
|
||||
this._timeoutManager.setIgnoreTimeouts();
|
||||
}
|
||||
|
||||
// ------------ TestInfo methods ------------
|
||||
|
||||
async attach(name, options = {}) {
|
||||
const step = this._addStep({
|
||||
title: `attach "${name}"`,
|
||||
category: 'attach'
|
||||
});
|
||||
this._attach(await (0, _util.normalizeAndSaveAttachment)(this.outputPath(), name, options), step.stepId);
|
||||
step.complete({});
|
||||
}
|
||||
_attach(attachment, stepId) {
|
||||
var _attachment$body;
|
||||
const index = this._attachmentsPush(attachment) - 1;
|
||||
if (stepId) this._stepMap.get(stepId).attachmentIndices.push(index);else this._tracing.appendTopLevelAttachment(attachment);
|
||||
this._onAttach({
|
||||
testId: this.testId,
|
||||
name: attachment.name,
|
||||
contentType: attachment.contentType,
|
||||
path: attachment.path,
|
||||
body: (_attachment$body = attachment.body) === null || _attachment$body === void 0 ? void 0 : _attachment$body.toString('base64'),
|
||||
stepId
|
||||
});
|
||||
}
|
||||
outputPath(...pathSegments) {
|
||||
const outputPath = this._getOutputPath(...pathSegments);
|
||||
_fs.default.mkdirSync(this.outputDir, {
|
||||
recursive: true
|
||||
});
|
||||
return outputPath;
|
||||
}
|
||||
_getOutputPath(...pathSegments) {
|
||||
const joinedPath = _path.default.join(...pathSegments);
|
||||
const outputPath = (0, _util.getContainedPath)(this.outputDir, joinedPath);
|
||||
if (outputPath) return outputPath;
|
||||
throw new Error(`The outputPath is not allowed outside of the parent directory. Please fix the defined path.\n\n\toutputPath: ${joinedPath}`);
|
||||
}
|
||||
_fsSanitizedTestName() {
|
||||
const fullTitleWithoutSpec = this.titlePath.slice(1).join(' ');
|
||||
return (0, _utils.sanitizeForFilePath)((0, _util.trimLongString)(fullTitleWithoutSpec));
|
||||
}
|
||||
_resolveSnapshotPath(template, defaultTemplate, pathSegments, extension) {
|
||||
const subPath = _path.default.join(...pathSegments);
|
||||
const dir = _path.default.dirname(subPath);
|
||||
const ext = extension !== null && extension !== void 0 ? extension : _path.default.extname(subPath);
|
||||
const name = _path.default.basename(subPath, ext);
|
||||
const relativeTestFilePath = _path.default.relative(this.project.testDir, this._requireFile);
|
||||
const parsedRelativeTestFilePath = _path.default.parse(relativeTestFilePath);
|
||||
const projectNamePathSegment = (0, _utils.sanitizeForFilePath)(this.project.name);
|
||||
const actualTemplate = template || this._projectInternal.snapshotPathTemplate || defaultTemplate;
|
||||
const snapshotPath = actualTemplate.replace(/\{(.)?testDir\}/g, '$1' + this.project.testDir).replace(/\{(.)?snapshotDir\}/g, '$1' + this.project.snapshotDir).replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? '$1' + this.snapshotSuffix : '').replace(/\{(.)?testFileDir\}/g, '$1' + parsedRelativeTestFilePath.dir).replace(/\{(.)?platform\}/g, '$1' + process.platform).replace(/\{(.)?projectName\}/g, projectNamePathSegment ? '$1' + projectNamePathSegment : '').replace(/\{(.)?testName\}/g, '$1' + this._fsSanitizedTestName()).replace(/\{(.)?testFileName\}/g, '$1' + parsedRelativeTestFilePath.base).replace(/\{(.)?testFilePath\}/g, '$1' + relativeTestFilePath).replace(/\{(.)?arg\}/g, '$1' + _path.default.join(dir, name)).replace(/\{(.)?ext\}/g, ext ? '$1' + ext : '');
|
||||
return _path.default.normalize(_path.default.resolve(this._configInternal.configDir, snapshotPath));
|
||||
}
|
||||
snapshotPath(...pathSegments) {
|
||||
const legacyTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}';
|
||||
return this._resolveSnapshotPath(undefined, legacyTemplate, pathSegments);
|
||||
}
|
||||
skip(...args) {
|
||||
this._modifier('skip', args);
|
||||
}
|
||||
fixme(...args) {
|
||||
this._modifier('fixme', args);
|
||||
}
|
||||
fail(...args) {
|
||||
this._modifier('fail', args);
|
||||
}
|
||||
slow(...args) {
|
||||
this._modifier('slow', args);
|
||||
}
|
||||
setTimeout(timeout) {
|
||||
this._timeoutManager.setTimeout(timeout);
|
||||
}
|
||||
}
|
||||
exports.TestInfoImpl = TestInfoImpl;
|
||||
class TestStepInfoImpl {
|
||||
constructor(testInfo, stepId) {
|
||||
this.annotations = [];
|
||||
this._testInfo = void 0;
|
||||
this._stepId = void 0;
|
||||
this._testInfo = testInfo;
|
||||
this._stepId = stepId;
|
||||
}
|
||||
async _runStepBody(skip, body) {
|
||||
if (skip) {
|
||||
this.annotations.push({
|
||||
type: 'skip'
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return await body(this);
|
||||
} catch (e) {
|
||||
if (e instanceof StepSkipError) return undefined;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
_attachToStep(attachment) {
|
||||
this._testInfo._attach(attachment, this._stepId);
|
||||
}
|
||||
async attach(name, options) {
|
||||
this._attachToStep(await (0, _util.normalizeAndSaveAttachment)(this._testInfo.outputPath(), name, options));
|
||||
}
|
||||
skip(...args) {
|
||||
// skip();
|
||||
// skip(condition: boolean, description: string);
|
||||
if (args.length > 0 && !args[0]) return;
|
||||
const description = args[1];
|
||||
this.annotations.push({
|
||||
type: 'skip',
|
||||
description
|
||||
});
|
||||
throw new StepSkipError(description);
|
||||
}
|
||||
}
|
||||
exports.TestStepInfoImpl = TestStepInfoImpl;
|
||||
class TestSkipError extends Error {}
|
||||
exports.TestSkipError = TestSkipError;
|
||||
class StepSkipError extends Error {}
|
||||
exports.StepSkipError = StepSkipError;
|
||||
const stepSymbol = Symbol('step');
|
||||
346
node_modules/playwright/lib/worker/testTracing.js
generated
vendored
Normal file
346
node_modules/playwright/lib/worker/testTracing.js
generated
vendored
Normal file
@@ -0,0 +1,346 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.testTraceEntryName = exports.TestTracing = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _zipBundle = require("playwright-core/lib/zipBundle");
|
||||
var _util = require("../isomorphic/util");
|
||||
var _util2 = require("../util");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const testTraceEntryName = exports.testTraceEntryName = 'test.trace';
|
||||
const version = 7;
|
||||
let traceOrdinal = 0;
|
||||
class TestTracing {
|
||||
constructor(testInfo, artifactsDir) {
|
||||
this._testInfo = void 0;
|
||||
this._options = void 0;
|
||||
this._liveTraceFile = void 0;
|
||||
this._traceEvents = [];
|
||||
this._temporaryTraceFiles = [];
|
||||
this._artifactsDir = void 0;
|
||||
this._tracesDir = void 0;
|
||||
this._contextCreatedEvent = void 0;
|
||||
this._didFinishTestFunctionAndAfterEachHooks = false;
|
||||
this._lastActionId = 0;
|
||||
this._testInfo = testInfo;
|
||||
this._artifactsDir = artifactsDir;
|
||||
this._tracesDir = _path.default.join(this._artifactsDir, 'traces');
|
||||
this._contextCreatedEvent = {
|
||||
version,
|
||||
type: 'context-options',
|
||||
origin: 'testRunner',
|
||||
browserName: '',
|
||||
options: {},
|
||||
platform: process.platform,
|
||||
wallTime: Date.now(),
|
||||
monotonicTime: (0, _utils.monotonicTime)(),
|
||||
sdkLanguage: 'javascript'
|
||||
};
|
||||
this._appendTraceEvent(this._contextCreatedEvent);
|
||||
}
|
||||
_shouldCaptureTrace() {
|
||||
var _this$_options, _this$_options2, _this$_options3, _this$_options4, _this$_options5;
|
||||
if (process.env.PW_TEST_DISABLE_TRACING) return false;
|
||||
if (((_this$_options = this._options) === null || _this$_options === void 0 ? void 0 : _this$_options.mode) === 'on') return true;
|
||||
if (((_this$_options2 = this._options) === null || _this$_options2 === void 0 ? void 0 : _this$_options2.mode) === 'retain-on-failure') return true;
|
||||
if (((_this$_options3 = this._options) === null || _this$_options3 === void 0 ? void 0 : _this$_options3.mode) === 'on-first-retry' && this._testInfo.retry === 1) return true;
|
||||
if (((_this$_options4 = this._options) === null || _this$_options4 === void 0 ? void 0 : _this$_options4.mode) === 'on-all-retries' && this._testInfo.retry > 0) return true;
|
||||
if (((_this$_options5 = this._options) === null || _this$_options5 === void 0 ? void 0 : _this$_options5.mode) === 'retain-on-first-failure' && this._testInfo.retry === 0) return true;
|
||||
return false;
|
||||
}
|
||||
async startIfNeeded(value) {
|
||||
const defaultTraceOptions = {
|
||||
screenshots: true,
|
||||
snapshots: true,
|
||||
sources: true,
|
||||
attachments: true,
|
||||
_live: false,
|
||||
mode: 'off'
|
||||
};
|
||||
if (!value) {
|
||||
this._options = defaultTraceOptions;
|
||||
} else if (typeof value === 'string') {
|
||||
this._options = {
|
||||
...defaultTraceOptions,
|
||||
mode: value === 'retry-with-trace' ? 'on-first-retry' : value
|
||||
};
|
||||
} else {
|
||||
const mode = value.mode || 'off';
|
||||
this._options = {
|
||||
...defaultTraceOptions,
|
||||
...value,
|
||||
mode: mode === 'retry-with-trace' ? 'on-first-retry' : mode
|
||||
};
|
||||
}
|
||||
if (!this._shouldCaptureTrace()) {
|
||||
this._options = undefined;
|
||||
return;
|
||||
}
|
||||
if (!this._liveTraceFile && this._options._live) {
|
||||
// Note that trace name must start with testId for live tracing to work.
|
||||
this._liveTraceFile = {
|
||||
file: _path.default.join(this._tracesDir, `${this._testInfo.testId}-test.trace`),
|
||||
fs: new _utils.SerializedFS()
|
||||
};
|
||||
this._liveTraceFile.fs.mkdir(_path.default.dirname(this._liveTraceFile.file));
|
||||
const data = this._traceEvents.map(e => JSON.stringify(e)).join('\n') + '\n';
|
||||
this._liveTraceFile.fs.writeFile(this._liveTraceFile.file, data);
|
||||
}
|
||||
}
|
||||
didFinishTestFunctionAndAfterEachHooks() {
|
||||
this._didFinishTestFunctionAndAfterEachHooks = true;
|
||||
}
|
||||
artifactsDir() {
|
||||
return this._artifactsDir;
|
||||
}
|
||||
tracesDir() {
|
||||
return this._tracesDir;
|
||||
}
|
||||
traceTitle() {
|
||||
return [_path.default.relative(this._testInfo.project.testDir, this._testInfo.file) + ':' + this._testInfo.line, ...this._testInfo.titlePath.slice(1)].join(' › ');
|
||||
}
|
||||
generateNextTraceRecordingName() {
|
||||
const ordinalSuffix = traceOrdinal ? `-recording${traceOrdinal}` : '';
|
||||
++traceOrdinal;
|
||||
const retrySuffix = this._testInfo.retry ? `-retry${this._testInfo.retry}` : '';
|
||||
// Note that trace name must start with testId for live tracing to work.
|
||||
return `${this._testInfo.testId}${retrySuffix}${ordinalSuffix}`;
|
||||
}
|
||||
_generateNextTraceRecordingPath() {
|
||||
const file = _path.default.join(this._artifactsDir, (0, _utils.createGuid)() + '.zip');
|
||||
this._temporaryTraceFiles.push(file);
|
||||
return file;
|
||||
}
|
||||
traceOptions() {
|
||||
return this._options;
|
||||
}
|
||||
maybeGenerateNextTraceRecordingPath() {
|
||||
// Forget about traces that should be saved on failure, when no failure happened
|
||||
// during the test and beforeEach/afterEach hooks.
|
||||
// This avoids downloading traces over the wire when not really needed.
|
||||
if (this._didFinishTestFunctionAndAfterEachHooks && this._shouldAbandonTrace()) return;
|
||||
return this._generateNextTraceRecordingPath();
|
||||
}
|
||||
_shouldAbandonTrace() {
|
||||
if (!this._options) return true;
|
||||
const testFailed = this._testInfo.status !== this._testInfo.expectedStatus;
|
||||
return !testFailed && (this._options.mode === 'retain-on-failure' || this._options.mode === 'retain-on-first-failure');
|
||||
}
|
||||
async stopIfNeeded() {
|
||||
var _this$_liveTraceFile, _this$_options6, _this$_options7;
|
||||
if (!this._options) return;
|
||||
const error = await ((_this$_liveTraceFile = this._liveTraceFile) === null || _this$_liveTraceFile === void 0 ? void 0 : _this$_liveTraceFile.fs.syncAndGetError());
|
||||
if (error) throw error;
|
||||
if (this._shouldAbandonTrace()) {
|
||||
for (const file of this._temporaryTraceFiles) await _fs.default.promises.unlink(file).catch(() => {});
|
||||
return;
|
||||
}
|
||||
const zipFile = new _zipBundle.yazl.ZipFile();
|
||||
if (!((_this$_options6 = this._options) !== null && _this$_options6 !== void 0 && _this$_options6.attachments)) {
|
||||
for (const event of this._traceEvents) {
|
||||
if (event.type === 'after') delete event.attachments;
|
||||
}
|
||||
}
|
||||
if ((_this$_options7 = this._options) !== null && _this$_options7 !== void 0 && _this$_options7.sources) {
|
||||
const sourceFiles = new Set();
|
||||
for (const event of this._traceEvents) {
|
||||
if (event.type === 'before') {
|
||||
for (const frame of event.stack || []) sourceFiles.add(frame.file);
|
||||
}
|
||||
}
|
||||
for (const sourceFile of sourceFiles) {
|
||||
await _fs.default.promises.readFile(sourceFile, 'utf8').then(source => {
|
||||
zipFile.addBuffer(Buffer.from(source), 'resources/src@' + (0, _utils.calculateSha1)(sourceFile) + '.txt');
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
const sha1s = new Set();
|
||||
for (const event of this._traceEvents.filter(e => e.type === 'after')) {
|
||||
for (const attachment of event.attachments || []) {
|
||||
let contentPromise;
|
||||
if (attachment.path) contentPromise = _fs.default.promises.readFile(attachment.path).catch(() => undefined);else if (attachment.base64) contentPromise = Promise.resolve(Buffer.from(attachment.base64, 'base64'));
|
||||
const content = await contentPromise;
|
||||
if (content === undefined) continue;
|
||||
const sha1 = (0, _utils.calculateSha1)(content);
|
||||
attachment.sha1 = sha1;
|
||||
delete attachment.path;
|
||||
delete attachment.base64;
|
||||
if (sha1s.has(sha1)) continue;
|
||||
sha1s.add(sha1);
|
||||
zipFile.addBuffer(content, 'resources/' + sha1);
|
||||
}
|
||||
}
|
||||
const traceContent = Buffer.from(this._traceEvents.map(e => JSON.stringify(e)).join('\n'));
|
||||
zipFile.addBuffer(traceContent, testTraceEntryName);
|
||||
await new Promise(f => {
|
||||
zipFile.end(undefined, () => {
|
||||
zipFile.outputStream.pipe(_fs.default.createWriteStream(this._generateNextTraceRecordingPath())).on('close', f);
|
||||
});
|
||||
});
|
||||
const tracePath = this._testInfo.outputPath('trace.zip');
|
||||
await mergeTraceFiles(tracePath, this._temporaryTraceFiles);
|
||||
this._testInfo.attachments.push({
|
||||
name: 'trace',
|
||||
path: tracePath,
|
||||
contentType: 'application/zip'
|
||||
});
|
||||
}
|
||||
appendForError(error) {
|
||||
var _error$stack;
|
||||
const rawStack = ((_error$stack = error.stack) === null || _error$stack === void 0 ? void 0 : _error$stack.split('\n')) || [];
|
||||
const stack = rawStack ? (0, _util2.filteredStackTrace)(rawStack) : [];
|
||||
this._appendTraceEvent({
|
||||
type: 'error',
|
||||
message: this._formatError(error),
|
||||
stack
|
||||
});
|
||||
}
|
||||
_formatError(error) {
|
||||
const parts = [error.message || String(error.value)];
|
||||
if (error.cause) parts.push('[cause]: ' + this._formatError(error.cause));
|
||||
return parts.join('\n');
|
||||
}
|
||||
appendStdioToTrace(type, chunk) {
|
||||
this._appendTraceEvent({
|
||||
type,
|
||||
timestamp: (0, _utils.monotonicTime)(),
|
||||
text: typeof chunk === 'string' ? chunk : undefined,
|
||||
base64: typeof chunk === 'string' ? undefined : chunk.toString('base64')
|
||||
});
|
||||
}
|
||||
appendBeforeActionForStep(callId, parentId, category, apiName, params, stack) {
|
||||
this._appendTraceEvent({
|
||||
type: 'before',
|
||||
callId,
|
||||
parentId,
|
||||
startTime: (0, _utils.monotonicTime)(),
|
||||
class: 'Test',
|
||||
method: 'step',
|
||||
apiName,
|
||||
params: Object.fromEntries(Object.entries(params || {}).map(([name, value]) => [name, generatePreview(value)])),
|
||||
stack
|
||||
});
|
||||
}
|
||||
appendAfterActionForStep(callId, error, attachments = [], annotations) {
|
||||
this._appendTraceEvent({
|
||||
type: 'after',
|
||||
callId,
|
||||
endTime: (0, _utils.monotonicTime)(),
|
||||
attachments: serializeAttachments(attachments),
|
||||
annotations,
|
||||
error
|
||||
});
|
||||
}
|
||||
appendTopLevelAttachment(attachment) {
|
||||
// trace viewer has no means of representing attachments outside of a step,
|
||||
// so we create an artificial action that's hidden in the UI
|
||||
// the alternative would be to have one hidden step at the end with all top-level attachments,
|
||||
// but that would delay useful information in live traces.
|
||||
const callId = `${_util.kTopLevelAttachmentPrefix}@${++this._lastActionId}`;
|
||||
this.appendBeforeActionForStep(callId, undefined, _util.kTopLevelAttachmentPrefix, `${_util.kTopLevelAttachmentPrefix} "${attachment.name}"`, undefined, []);
|
||||
this.appendAfterActionForStep(callId, undefined, [attachment]);
|
||||
}
|
||||
_appendTraceEvent(event) {
|
||||
this._traceEvents.push(event);
|
||||
if (this._liveTraceFile) this._liveTraceFile.fs.appendFile(this._liveTraceFile.file, JSON.stringify(event) + '\n', true);
|
||||
}
|
||||
}
|
||||
exports.TestTracing = TestTracing;
|
||||
function serializeAttachments(attachments) {
|
||||
if (attachments.length === 0) return undefined;
|
||||
return attachments.filter(a => a.name !== 'trace').map(a => {
|
||||
var _a$body;
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: a.path,
|
||||
base64: (_a$body = a.body) === null || _a$body === void 0 ? void 0 : _a$body.toString('base64')
|
||||
};
|
||||
});
|
||||
}
|
||||
function generatePreview(value, visited = new Set()) {
|
||||
if (visited.has(value)) return '';
|
||||
visited.add(value);
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number') return value.toString();
|
||||
if (typeof value === 'boolean') return value.toString();
|
||||
if (value === null) return 'null';
|
||||
if (value === undefined) return 'undefined';
|
||||
if (Array.isArray(value)) return '[' + value.map(v => generatePreview(v, visited)).join(', ') + ']';
|
||||
if (typeof value === 'object') return 'Object';
|
||||
return String(value);
|
||||
}
|
||||
async function mergeTraceFiles(fileName, temporaryTraceFiles) {
|
||||
temporaryTraceFiles = temporaryTraceFiles.filter(file => _fs.default.existsSync(file));
|
||||
if (temporaryTraceFiles.length === 1) {
|
||||
await _fs.default.promises.rename(temporaryTraceFiles[0], fileName);
|
||||
return;
|
||||
}
|
||||
const mergePromise = new _utils.ManualPromise();
|
||||
const zipFile = new _zipBundle.yazl.ZipFile();
|
||||
const entryNames = new Set();
|
||||
zipFile.on('error', error => mergePromise.reject(error));
|
||||
for (let i = temporaryTraceFiles.length - 1; i >= 0; --i) {
|
||||
const tempFile = temporaryTraceFiles[i];
|
||||
const promise = new _utils.ManualPromise();
|
||||
_zipBundle.yauzl.open(tempFile, (err, inZipFile) => {
|
||||
if (err) {
|
||||
promise.reject(err);
|
||||
return;
|
||||
}
|
||||
let pendingEntries = inZipFile.entryCount;
|
||||
inZipFile.on('entry', entry => {
|
||||
let entryName = entry.fileName;
|
||||
if (entry.fileName === testTraceEntryName) {
|
||||
// Keep the name for test traces so that the last test trace
|
||||
// that contains most of the information is kept in the trace.
|
||||
// Note the reverse order of the iteration (from new traces to old).
|
||||
} else if (entry.fileName.match(/trace\.[a-z]*$/)) {
|
||||
entryName = i + '-' + entry.fileName;
|
||||
}
|
||||
if (entryNames.has(entryName)) {
|
||||
if (--pendingEntries === 0) promise.resolve();
|
||||
return;
|
||||
}
|
||||
entryNames.add(entryName);
|
||||
inZipFile.openReadStream(entry, (err, readStream) => {
|
||||
if (err) {
|
||||
promise.reject(err);
|
||||
return;
|
||||
}
|
||||
zipFile.addReadStream(readStream, entryName);
|
||||
if (--pendingEntries === 0) promise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
await promise;
|
||||
}
|
||||
zipFile.end(undefined, () => {
|
||||
zipFile.outputStream.pipe(_fs.default.createWriteStream(fileName)).on('close', () => {
|
||||
void Promise.all(temporaryTraceFiles.map(tempFile => _fs.default.promises.unlink(tempFile))).then(() => {
|
||||
mergePromise.resolve();
|
||||
}).catch(error => mergePromise.reject(error));
|
||||
}).on('error', error => mergePromise.reject(error));
|
||||
});
|
||||
await mergePromise;
|
||||
}
|
||||
159
node_modules/playwright/lib/worker/timeoutManager.js
generated
vendored
Normal file
159
node_modules/playwright/lib/worker/timeoutManager.js
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.kMaxDeadline = exports.TimeoutManagerError = exports.TimeoutManager = void 0;
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _util = require("../util");
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const kMaxDeadline = exports.kMaxDeadline = 2147483647; // 2^31-1
|
||||
|
||||
class TimeoutManager {
|
||||
constructor(timeout) {
|
||||
this._defaultSlot = void 0;
|
||||
this._running = void 0;
|
||||
this._ignoreTimeouts = false;
|
||||
this._defaultSlot = {
|
||||
timeout,
|
||||
elapsed: 0
|
||||
};
|
||||
}
|
||||
setIgnoreTimeouts() {
|
||||
this._ignoreTimeouts = true;
|
||||
if (this._running) this._updateTimeout(this._running);
|
||||
}
|
||||
interrupt() {
|
||||
if (this._running) this._running.timeoutPromise.reject(this._createTimeoutError(this._running));
|
||||
}
|
||||
isTimeExhaustedFor(runnable) {
|
||||
var _runnable$fixture;
|
||||
const slot = ((_runnable$fixture = runnable.fixture) === null || _runnable$fixture === void 0 ? void 0 : _runnable$fixture.slot) || runnable.slot || this._defaultSlot;
|
||||
// Note: the "-1" here matches the +1 in _updateTimeout.
|
||||
return slot.timeout > 0 && slot.elapsed >= slot.timeout - 1;
|
||||
}
|
||||
async withRunnable(runnable, cb) {
|
||||
var _runnable$fixture2;
|
||||
if (this._running) throw new Error(`Internal error: duplicate runnable`);
|
||||
const running = this._running = {
|
||||
runnable,
|
||||
slot: ((_runnable$fixture2 = runnable.fixture) === null || _runnable$fixture2 === void 0 ? void 0 : _runnable$fixture2.slot) || runnable.slot || this._defaultSlot,
|
||||
start: (0, _utils.monotonicTime)(),
|
||||
deadline: kMaxDeadline,
|
||||
timer: undefined,
|
||||
timeoutPromise: new _utils.ManualPromise()
|
||||
};
|
||||
let debugTitle = '';
|
||||
try {
|
||||
if (_util.debugTest.enabled) {
|
||||
debugTitle = runnable.fixture ? `${runnable.fixture.phase} "${runnable.fixture.title}"` : runnable.type;
|
||||
const location = runnable.location ? ` at "${(0, _util.formatLocation)(runnable.location)}"` : ``;
|
||||
(0, _util.debugTest)(`started ${debugTitle}${location}`);
|
||||
}
|
||||
this._updateTimeout(running);
|
||||
return await Promise.race([cb(), running.timeoutPromise]);
|
||||
} finally {
|
||||
if (running.timer) clearTimeout(running.timer);
|
||||
running.timer = undefined;
|
||||
running.slot.elapsed += (0, _utils.monotonicTime)() - running.start;
|
||||
this._running = undefined;
|
||||
if (_util.debugTest.enabled) (0, _util.debugTest)(`finished ${debugTitle}`);
|
||||
}
|
||||
}
|
||||
_updateTimeout(running) {
|
||||
if (running.timer) clearTimeout(running.timer);
|
||||
running.timer = undefined;
|
||||
if (this._ignoreTimeouts || !running.slot.timeout) {
|
||||
running.deadline = kMaxDeadline;
|
||||
return;
|
||||
}
|
||||
running.deadline = running.start + (running.slot.timeout - running.slot.elapsed);
|
||||
// Compensate for Node.js troubles with timeouts that can fire too early.
|
||||
// We add an extra millisecond which seems to be enough.
|
||||
// See https://github.com/nodejs/node/issues/26578.
|
||||
const timeout = running.deadline - (0, _utils.monotonicTime)() + 1;
|
||||
if (timeout <= 0) running.timeoutPromise.reject(this._createTimeoutError(running));else running.timer = setTimeout(() => running.timeoutPromise.reject(this._createTimeoutError(running)), timeout);
|
||||
}
|
||||
defaultSlot() {
|
||||
return this._defaultSlot;
|
||||
}
|
||||
slow() {
|
||||
const slot = this._running ? this._running.slot : this._defaultSlot;
|
||||
slot.timeout = slot.timeout * 3;
|
||||
if (this._running) this._updateTimeout(this._running);
|
||||
}
|
||||
setTimeout(timeout) {
|
||||
const slot = this._running ? this._running.slot : this._defaultSlot;
|
||||
slot.timeout = timeout;
|
||||
if (this._running) this._updateTimeout(this._running);
|
||||
}
|
||||
currentSlotDeadline() {
|
||||
return this._running ? this._running.deadline : kMaxDeadline;
|
||||
}
|
||||
currentSlotType() {
|
||||
return this._running ? this._running.runnable.type : 'test';
|
||||
}
|
||||
_createTimeoutError(running) {
|
||||
var _runnable$fixture3;
|
||||
let message = '';
|
||||
const timeout = running.slot.timeout;
|
||||
const runnable = running.runnable;
|
||||
switch (runnable.type) {
|
||||
case 'test':
|
||||
{
|
||||
if (runnable.fixture) {
|
||||
if (runnable.fixture.phase === 'setup') message = `Test timeout of ${timeout}ms exceeded while setting up "${runnable.fixture.title}".`;else message = `Tearing down "${runnable.fixture.title}" exceeded the test timeout of ${timeout}ms.`;
|
||||
} else {
|
||||
message = `Test timeout of ${timeout}ms exceeded.`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'afterEach':
|
||||
case 'beforeEach':
|
||||
message = `Test timeout of ${timeout}ms exceeded while running "${runnable.type}" hook.`;
|
||||
break;
|
||||
case 'beforeAll':
|
||||
case 'afterAll':
|
||||
message = `"${runnable.type}" hook timeout of ${timeout}ms exceeded.`;
|
||||
break;
|
||||
case 'teardown':
|
||||
{
|
||||
if (runnable.fixture) message = `Worker teardown timeout of ${timeout}ms exceeded while ${runnable.fixture.phase === 'setup' ? 'setting up' : 'tearing down'} "${runnable.fixture.title}".`;else message = `Worker teardown timeout of ${timeout}ms exceeded.`;
|
||||
break;
|
||||
}
|
||||
case 'skip':
|
||||
case 'slow':
|
||||
case 'fixme':
|
||||
case 'fail':
|
||||
message = `"${runnable.type}" modifier timeout of ${timeout}ms exceeded.`;
|
||||
break;
|
||||
}
|
||||
const fixtureWithSlot = (_runnable$fixture3 = runnable.fixture) !== null && _runnable$fixture3 !== void 0 && _runnable$fixture3.slot ? runnable.fixture : undefined;
|
||||
if (fixtureWithSlot) message = `Fixture "${fixtureWithSlot.title}" timeout of ${timeout}ms exceeded during ${fixtureWithSlot.phase}.`;
|
||||
message = _utils.colors.red(message);
|
||||
const location = (fixtureWithSlot || runnable).location;
|
||||
const error = new TimeoutManagerError(message);
|
||||
error.name = '';
|
||||
// Include location for hooks, modifiers and fixtures to distinguish between them.
|
||||
error.stack = message + (location ? `\n at ${location.file}:${location.line}:${location.column}` : '');
|
||||
return error;
|
||||
}
|
||||
}
|
||||
exports.TimeoutManager = TimeoutManager;
|
||||
class TimeoutManagerError extends Error {}
|
||||
exports.TimeoutManagerError = TimeoutManagerError;
|
||||
29
node_modules/playwright/lib/worker/util.js
generated
vendored
Normal file
29
node_modules/playwright/lib/worker/util.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.testInfoError = testInfoError;
|
||||
var _matcherHint = require("../matchers/matcherHint");
|
||||
var _util = require("../util");
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
function testInfoError(error) {
|
||||
const result = (0, _util.serializeError)(error);
|
||||
if (error instanceof _matcherHint.ExpectError) result.matcherResult = error.matcherResult;
|
||||
return result;
|
||||
}
|
||||
620
node_modules/playwright/lib/worker/workerMain.js
generated
vendored
Normal file
620
node_modules/playwright/lib/worker/workerMain.js
generated
vendored
Normal file
@@ -0,0 +1,620 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.create = exports.WorkerMain = void 0;
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _configLoader = require("../common/configLoader");
|
||||
var _globals = require("../common/globals");
|
||||
var _ipc = require("../common/ipc");
|
||||
var _util = require("../util");
|
||||
var _fixtureRunner = require("./fixtureRunner");
|
||||
var _testInfo = require("./testInfo");
|
||||
var _util2 = require("./util");
|
||||
var _fixtures = require("../common/fixtures");
|
||||
var _poolBuilder = require("../common/poolBuilder");
|
||||
var _process = require("../common/process");
|
||||
var _suiteUtils = require("../common/suiteUtils");
|
||||
var _testLoader = require("../common/testLoader");
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class WorkerMain extends _process.ProcessRunner {
|
||||
constructor(params) {
|
||||
super();
|
||||
this._params = void 0;
|
||||
this._config = void 0;
|
||||
this._project = void 0;
|
||||
this._poolBuilder = void 0;
|
||||
this._fixtureRunner = void 0;
|
||||
// Accumulated fatal errors that cannot be attributed to a test.
|
||||
this._fatalErrors = [];
|
||||
// Whether we should skip running remaining tests in this suite because
|
||||
// of a setup error, usually beforeAll hook.
|
||||
this._skipRemainingTestsInSuite = void 0;
|
||||
// The stage of the full cleanup. Once "finished", we can safely stop running anything.
|
||||
this._didRunFullCleanup = false;
|
||||
// Whether the worker was requested to stop.
|
||||
this._isStopped = false;
|
||||
// This promise resolves once the single "run test group" call finishes.
|
||||
this._runFinished = new _utils.ManualPromise();
|
||||
this._currentTest = null;
|
||||
this._lastRunningTests = [];
|
||||
this._totalRunningTests = 0;
|
||||
// Suites that had their beforeAll hooks, but not afterAll hooks executed.
|
||||
// These suites still need afterAll hooks to be executed for the proper cleanup.
|
||||
// Contains dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`.
|
||||
this._activeSuites = new Map();
|
||||
process.env.TEST_WORKER_INDEX = String(params.workerIndex);
|
||||
process.env.TEST_PARALLEL_INDEX = String(params.parallelIndex);
|
||||
(0, _globals.setIsWorkerProcess)();
|
||||
this._params = params;
|
||||
this._fixtureRunner = new _fixtureRunner.FixtureRunner();
|
||||
|
||||
// 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();
|
||||
process.on('unhandledRejection', reason => this.unhandledError(reason));
|
||||
process.on('uncaughtException', error => this.unhandledError(error));
|
||||
process.stdout.write = (chunk, cb) => {
|
||||
var _this$_currentTest;
|
||||
this.dispatchEvent('stdOut', (0, _ipc.stdioChunkToParams)(chunk));
|
||||
(_this$_currentTest = this._currentTest) === null || _this$_currentTest === void 0 || _this$_currentTest._tracing.appendStdioToTrace('stdout', chunk);
|
||||
if (typeof cb === 'function') process.nextTick(cb);
|
||||
return true;
|
||||
};
|
||||
if (!process.env.PW_RUNNER_DEBUG) {
|
||||
process.stderr.write = (chunk, cb) => {
|
||||
var _this$_currentTest2;
|
||||
this.dispatchEvent('stdErr', (0, _ipc.stdioChunkToParams)(chunk));
|
||||
(_this$_currentTest2 = this._currentTest) === null || _this$_currentTest2 === void 0 || _this$_currentTest2._tracing.appendStdioToTrace('stderr', chunk);
|
||||
if (typeof cb === 'function') process.nextTick(cb);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
_stop() {
|
||||
if (!this._isStopped) {
|
||||
var _this$_currentTest3;
|
||||
this._isStopped = true;
|
||||
(_this$_currentTest3 = this._currentTest) === null || _this$_currentTest3 === void 0 || _this$_currentTest3._interrupt();
|
||||
}
|
||||
return this._runFinished;
|
||||
}
|
||||
async gracefullyClose() {
|
||||
try {
|
||||
await this._stop();
|
||||
if (!this._config) {
|
||||
// We never set anything up and we can crash on attempting cleanup
|
||||
return;
|
||||
}
|
||||
// Ignore top-level errors, they are already inside TestInfo.errors.
|
||||
const fakeTestInfo = new _testInfo.TestInfoImpl(this._config, this._project, this._params, undefined, 0, () => {}, () => {}, () => {});
|
||||
const runnable = {
|
||||
type: 'teardown'
|
||||
};
|
||||
// We have to load the project to get the right deadline below.
|
||||
await fakeTestInfo._runWithTimeout(runnable, () => this._loadIfNeeded()).catch(() => {});
|
||||
await this._fixtureRunner.teardownScope('test', fakeTestInfo, runnable).catch(() => {});
|
||||
await this._fixtureRunner.teardownScope('worker', fakeTestInfo, runnable).catch(() => {});
|
||||
// Close any other browsers launched in this process. This includes anything launched
|
||||
// manually in the test/hooks and internal browsers like Playwright Inspector.
|
||||
await fakeTestInfo._runWithTimeout(runnable, () => (0, _utils.gracefullyCloseAll)()).catch(() => {});
|
||||
this._fatalErrors.push(...fakeTestInfo.errors);
|
||||
} catch (e) {
|
||||
this._fatalErrors.push((0, _util2.testInfoError)(e));
|
||||
}
|
||||
if (this._fatalErrors.length) {
|
||||
this._appendProcessTeardownDiagnostics(this._fatalErrors[this._fatalErrors.length - 1]);
|
||||
const payload = {
|
||||
fatalErrors: this._fatalErrors
|
||||
};
|
||||
this.dispatchEvent('teardownErrors', payload);
|
||||
}
|
||||
}
|
||||
_appendProcessTeardownDiagnostics(error) {
|
||||
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 = ['', '', _utils.colors.red(`Failed worker ran ${count}${lastMessage}:`), ...this._lastRunningTests.map(test => formatTestTitle(test, this._project.project.name))].join('\n');
|
||||
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;
|
||||
}
|
||||
}
|
||||
unhandledError(error) {
|
||||
// No current test - fatal error.
|
||||
if (!this._currentTest) {
|
||||
if (!this._fatalErrors.length) this._fatalErrors.push((0, _util2.testInfoError)(error));
|
||||
void this._stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (!this._currentTest._hasUnhandledError) {
|
||||
this._currentTest._hasUnhandledError = true;
|
||||
this._currentTest._failWithError(error);
|
||||
}
|
||||
|
||||
// For tests marked with test.fail(), this might be a problem when unhandled error
|
||||
// is not coming from the user test code (legit failure), but from fixtures or test runner.
|
||||
//
|
||||
// Ideally, we would mark this test as "failed unexpectedly" and show that in the report.
|
||||
// However, we do not have such a special test status, so the test will be considered ok (failed as expected).
|
||||
//
|
||||
// To avoid messing up future tests, we forcefully stop the worker, unless it is
|
||||
// an expect() error which we know does not mess things up.
|
||||
const isExpectError = error instanceof Error && !!error.matcherResult;
|
||||
const shouldContinueInThisWorker = this._currentTest.expectedStatus === 'failed' && isExpectError;
|
||||
if (!shouldContinueInThisWorker) void this._stop();
|
||||
}
|
||||
async _loadIfNeeded() {
|
||||
if (this._config) return;
|
||||
const config = await (0, _configLoader.deserializeConfig)(this._params.config);
|
||||
const project = config.projects.find(p => p.id === this._params.projectId);
|
||||
if (!project) throw new Error(`Project "${this._params.projectId}" not found in the worker process. Make sure project name does not change.`);
|
||||
this._config = config;
|
||||
this._project = project;
|
||||
this._poolBuilder = _poolBuilder.PoolBuilder.createForWorker(this._project);
|
||||
}
|
||||
async runTestGroup(runPayload) {
|
||||
this._runFinished = new _utils.ManualPromise();
|
||||
const entries = new Map(runPayload.entries.map(e => [e.testId, e]));
|
||||
let fatalUnknownTestIds;
|
||||
try {
|
||||
await this._loadIfNeeded();
|
||||
const fileSuite = await (0, _testLoader.loadTestFile)(runPayload.file, this._config.config.rootDir);
|
||||
const suite = (0, _suiteUtils.bindFileSuiteToProject)(this._project, fileSuite);
|
||||
if (this._params.repeatEachIndex) (0, _suiteUtils.applyRepeatEachIndex)(this._project, suite, this._params.repeatEachIndex);
|
||||
const hasEntries = (0, _suiteUtils.filterTestsRemoveEmptySuites)(suite, test => entries.has(test.id));
|
||||
if (hasEntries) {
|
||||
this._poolBuilder.buildPools(suite);
|
||||
this._activeSuites = new Map();
|
||||
this._didRunFullCleanup = false;
|
||||
const tests = suite.allTests();
|
||||
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;
|
||||
const entry = entries.get(tests[i].id);
|
||||
entries.delete(tests[i].id);
|
||||
(0, _util.debugTest)(`test started "${tests[i].title}"`);
|
||||
await this._runTest(tests[i], entry.retry, tests[i + 1]);
|
||||
(0, _util.debugTest)(`test finished "${tests[i].title}"`);
|
||||
}
|
||||
} else {
|
||||
fatalUnknownTestIds = runPayload.entries.map(e => e.testId);
|
||||
void this._stop();
|
||||
}
|
||||
} 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.
|
||||
this._fatalErrors.push((0, _util2.testInfoError)(e));
|
||||
void this._stop();
|
||||
} finally {
|
||||
const donePayload = {
|
||||
fatalErrors: this._fatalErrors,
|
||||
skipTestsDueToSetupFailure: [],
|
||||
fatalUnknownTestIds
|
||||
};
|
||||
for (const test of ((_this$_skipRemainingT = this._skipRemainingTestsInSuite) === null || _this$_skipRemainingT === void 0 ? void 0 : _this$_skipRemainingT.allTests()) || []) {
|
||||
var _this$_skipRemainingT;
|
||||
if (entries.has(test.id)) donePayload.skipTestsDueToSetupFailure.push(test.id);
|
||||
}
|
||||
this.dispatchEvent('done', donePayload);
|
||||
this._fatalErrors = [];
|
||||
this._skipRemainingTestsInSuite = undefined;
|
||||
this._runFinished.resolve();
|
||||
}
|
||||
}
|
||||
async _runTest(test, retry, nextTest) {
|
||||
const testInfo = new _testInfo.TestInfoImpl(this._config, this._project, this._params, test, retry, stepBeginPayload => this.dispatchEvent('stepBegin', stepBeginPayload), stepEndPayload => this.dispatchEvent('stepEnd', stepEndPayload), attachment => this.dispatchEvent('attach', attachment));
|
||||
const processAnnotation = annotation => {
|
||||
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':
|
||||
testInfo._timeoutManager.slow();
|
||||
break;
|
||||
}
|
||||
};
|
||||
if (!this._isStopped) this._fixtureRunner.setPool(test._pool);
|
||||
const suites = getSuites(test);
|
||||
const reversedSuites = suites.slice().reverse();
|
||||
const nextSuites = new Set(getSuites(nextTest));
|
||||
testInfo._timeoutManager.setTimeout(test.timeout);
|
||||
for (const annotation of test.annotations) processAnnotation(annotation);
|
||||
|
||||
// Process existing annotations dynamically set for parent suites.
|
||||
for (const suite of suites) {
|
||||
const extraAnnotations = this._activeSuites.get(suite) || [];
|
||||
for (const annotation of extraAnnotations) processAnnotation(annotation);
|
||||
}
|
||||
this._currentTest = testInfo;
|
||||
(0, _globals.setCurrentTestInfo)(testInfo);
|
||||
this.dispatchEvent('testBegin', buildTestBeginPayload(testInfo));
|
||||
const isSkipped = testInfo.expectedStatus === 'skipped';
|
||||
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.
|
||||
testInfo.status = 'skipped';
|
||||
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo));
|
||||
return;
|
||||
}
|
||||
this._totalRunningTests++;
|
||||
this._lastRunningTests.push(test);
|
||||
if (this._lastRunningTests.length > 10) this._lastRunningTests.shift();
|
||||
let shouldRunAfterEachHooks = false;
|
||||
testInfo._allowSkips = true;
|
||||
|
||||
// Create warning if any of the async calls were not awaited in various stages.
|
||||
const checkForFloatingPromises = functionDescription => {
|
||||
if (process.env.PW_DISABLE_FLOATING_PROMISES_WARNING) return;
|
||||
if (!testInfo._floatingPromiseScope.hasFloatingPromises()) return;
|
||||
// TODO: 1.52: Actually build annotations
|
||||
// testInfo.annotations.push({ type: 'warning', description: `Some async calls were not awaited by the end of ${functionDescription}. This can cause flakiness.` });
|
||||
testInfo._floatingPromiseScope.clear();
|
||||
};
|
||||
await (async () => {
|
||||
await testInfo._runWithTimeout({
|
||||
type: 'test'
|
||||
}, async () => {
|
||||
// Ideally, "trace" would be an config-level option belonging to the
|
||||
// test runner instead of a fixture belonging to Playwright.
|
||||
// However, for backwards compatibility, we have to read it from a fixture today.
|
||||
// We decided to not introduce the config-level option just yet.
|
||||
const traceFixtureRegistration = test._pool.resolve('trace');
|
||||
if (!traceFixtureRegistration) return;
|
||||
if (typeof traceFixtureRegistration.fn === 'function') throw new Error(`"trace" option cannot be a function`);
|
||||
await testInfo._tracing.startIfNeeded(traceFixtureRegistration.fn);
|
||||
});
|
||||
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.
|
||||
testInfo.status = 'skipped';
|
||||
return;
|
||||
}
|
||||
await (0, _utils.removeFolders)([testInfo.outputDir]);
|
||||
let testFunctionParams = null;
|
||||
await testInfo._runAsStep({
|
||||
title: 'Before Hooks',
|
||||
category: 'hook'
|
||||
}, async () => {
|
||||
// Run "beforeAll" hooks, unless already run during previous tests.
|
||||
for (const suite of suites) await this._runBeforeAllHooksForSuite(suite, testInfo);
|
||||
|
||||
// Run "beforeEach" hooks. Once started with "beforeEach", we must run all "afterEach" hooks as well.
|
||||
shouldRunAfterEachHooks = true;
|
||||
await this._runEachHooksForSuites(suites, 'beforeEach', testInfo);
|
||||
|
||||
// Setup fixtures required by the test.
|
||||
testFunctionParams = await this._fixtureRunner.resolveParametersForFunction(test.fn, testInfo, 'test', {
|
||||
type: 'test'
|
||||
});
|
||||
});
|
||||
checkForFloatingPromises('beforeAll/beforeEach hooks');
|
||||
if (testFunctionParams === null) {
|
||||
// Fixture setup failed or was skipped, we should not run the test now.
|
||||
return;
|
||||
}
|
||||
await testInfo._runWithTimeout({
|
||||
type: 'test'
|
||||
}, async () => {
|
||||
// Now run the test itself.
|
||||
const fn = test.fn; // Extract a variable to get a better stack trace ("myTest" vs "TestCase.myTest [as fn]").
|
||||
await fn(testFunctionParams, testInfo);
|
||||
checkForFloatingPromises('the test');
|
||||
});
|
||||
})().catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
|
||||
|
||||
// Update duration, so it is available in fixture teardown and afterEach hooks.
|
||||
testInfo.duration = testInfo._timeoutManager.defaultSlot().elapsed | 0;
|
||||
|
||||
// No skips in after hooks.
|
||||
testInfo._allowSkips = true;
|
||||
|
||||
// After hooks get an additional timeout.
|
||||
const afterHooksTimeout = calculateMaxTimeout(this._project.project.timeout, testInfo.timeout);
|
||||
const afterHooksSlot = {
|
||||
timeout: afterHooksTimeout,
|
||||
elapsed: 0
|
||||
};
|
||||
await testInfo._runAsStep({
|
||||
title: 'After Hooks',
|
||||
category: 'hook'
|
||||
}, async () => {
|
||||
let firstAfterHooksError;
|
||||
try {
|
||||
// Run "immediately upon test function finish" callback.
|
||||
await testInfo._runWithTimeout({
|
||||
type: 'test',
|
||||
slot: afterHooksSlot
|
||||
}, async () => {
|
||||
var _testInfo$_onDidFinis;
|
||||
return (_testInfo$_onDidFinis = testInfo._onDidFinishTestFunction) === null || _testInfo$_onDidFinis === void 0 ? void 0 : _testInfo$_onDidFinis.call(testInfo);
|
||||
});
|
||||
} catch (error) {
|
||||
firstAfterHooksError = firstAfterHooksError !== null && firstAfterHooksError !== void 0 ? firstAfterHooksError : error;
|
||||
}
|
||||
try {
|
||||
// Run "afterEach" hooks, unless we failed at beforeAll stage.
|
||||
if (shouldRunAfterEachHooks) await this._runEachHooksForSuites(reversedSuites, 'afterEach', testInfo, afterHooksSlot);
|
||||
} catch (error) {
|
||||
firstAfterHooksError = firstAfterHooksError !== null && firstAfterHooksError !== void 0 ? firstAfterHooksError : error;
|
||||
}
|
||||
testInfo._tracing.didFinishTestFunctionAndAfterEachHooks();
|
||||
try {
|
||||
// Teardown test-scoped fixtures. Attribute to 'test' so that users understand
|
||||
// they should probably increase the test timeout to fix this issue.
|
||||
await this._fixtureRunner.teardownScope('test', testInfo, {
|
||||
type: 'test',
|
||||
slot: afterHooksSlot
|
||||
});
|
||||
} catch (error) {
|
||||
firstAfterHooksError = firstAfterHooksError !== null && firstAfterHooksError !== void 0 ? firstAfterHooksError : error;
|
||||
}
|
||||
|
||||
// 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
|
||||
// hooks run before worker fixtures teardown.
|
||||
for (const suite of reversedSuites) {
|
||||
if (!nextSuites.has(suite) || testInfo._isFailure()) {
|
||||
try {
|
||||
await this._runAfterAllHooksForSuite(suite, testInfo);
|
||||
} catch (error) {
|
||||
// Continue running "afterAll" hooks even after some of them timeout.
|
||||
firstAfterHooksError = firstAfterHooksError !== null && firstAfterHooksError !== void 0 ? firstAfterHooksError : error;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (firstAfterHooksError) throw firstAfterHooksError;
|
||||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
|
||||
|
||||
checkForFloatingPromises('afterAll/afterEach hooks');
|
||||
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;
|
||||
await testInfo._runAsStep({
|
||||
title: 'Worker Cleanup',
|
||||
category: 'hook'
|
||||
}, async () => {
|
||||
let firstWorkerCleanupError;
|
||||
|
||||
// Give it more time for the full cleanup.
|
||||
const teardownSlot = {
|
||||
timeout: this._project.project.timeout,
|
||||
elapsed: 0
|
||||
};
|
||||
try {
|
||||
// Attribute to 'test' so that users understand they should probably increate the test timeout to fix this issue.
|
||||
await this._fixtureRunner.teardownScope('test', testInfo, {
|
||||
type: 'test',
|
||||
slot: teardownSlot
|
||||
});
|
||||
} catch (error) {
|
||||
firstWorkerCleanupError = firstWorkerCleanupError !== null && firstWorkerCleanupError !== void 0 ? firstWorkerCleanupError : error;
|
||||
}
|
||||
for (const suite of reversedSuites) {
|
||||
try {
|
||||
await this._runAfterAllHooksForSuite(suite, testInfo);
|
||||
} catch (error) {
|
||||
firstWorkerCleanupError = firstWorkerCleanupError !== null && firstWorkerCleanupError !== void 0 ? firstWorkerCleanupError : error;
|
||||
}
|
||||
}
|
||||
try {
|
||||
// Attribute to 'teardown' because worker fixtures are not perceived as a part of a test.
|
||||
await this._fixtureRunner.teardownScope('worker', testInfo, {
|
||||
type: 'teardown',
|
||||
slot: teardownSlot
|
||||
});
|
||||
} catch (error) {
|
||||
firstWorkerCleanupError = firstWorkerCleanupError !== null && firstWorkerCleanupError !== void 0 ? firstWorkerCleanupError : error;
|
||||
}
|
||||
if (firstWorkerCleanupError) throw firstWorkerCleanupError;
|
||||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
|
||||
}
|
||||
const tracingSlot = {
|
||||
timeout: this._project.project.timeout,
|
||||
elapsed: 0
|
||||
};
|
||||
await testInfo._runWithTimeout({
|
||||
type: 'test',
|
||||
slot: tracingSlot
|
||||
}, async () => {
|
||||
await testInfo._tracing.stopIfNeeded();
|
||||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
|
||||
|
||||
testInfo.duration = testInfo._timeoutManager.defaultSlot().elapsed + afterHooksSlot.elapsed | 0;
|
||||
this._currentTest = null;
|
||||
(0, _globals.setCurrentTestInfo)(null);
|
||||
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo));
|
||||
const preserveOutput = this._config.config.preserveOutput === 'always' || this._config.config.preserveOutput === 'failures-only' && testInfo._isFailure();
|
||||
if (!preserveOutput) await (0, _utils.removeFolders)([testInfo.outputDir]);
|
||||
}
|
||||
_collectHooksAndModifiers(suite, type, testInfo) {
|
||||
const runnables = [];
|
||||
for (const modifier of suite._modifiers) {
|
||||
const modifierType = this._fixtureRunner.dependsOnWorkerFixturesOnly(modifier.fn, modifier.location) ? 'beforeAll' : 'beforeEach';
|
||||
if (modifierType !== type) continue;
|
||||
const fn = async fixtures => {
|
||||
const result = await modifier.fn(fixtures);
|
||||
testInfo[modifier.type](!!result, modifier.description);
|
||||
};
|
||||
(0, _fixtures.inheritFixtureNames)(modifier.fn, fn);
|
||||
runnables.push({
|
||||
title: `${modifier.type} modifier`,
|
||||
location: modifier.location,
|
||||
type: modifier.type,
|
||||
fn
|
||||
});
|
||||
}
|
||||
// Modifiers first, then hooks.
|
||||
runnables.push(...suite._hooks.filter(hook => hook.type === type));
|
||||
return runnables;
|
||||
}
|
||||
async _runBeforeAllHooksForSuite(suite, testInfo) {
|
||||
if (this._activeSuites.has(suite)) return;
|
||||
const extraAnnotations = [];
|
||||
this._activeSuites.set(suite, extraAnnotations);
|
||||
await this._runAllHooksForSuite(suite, testInfo, 'beforeAll', extraAnnotations);
|
||||
}
|
||||
async _runAllHooksForSuite(suite, testInfo, type, extraAnnotations) {
|
||||
// Always run all the hooks, and capture the first error.
|
||||
let firstError;
|
||||
for (const hook of this._collectHooksAndModifiers(suite, type, testInfo)) {
|
||||
try {
|
||||
await testInfo._runAsStep({
|
||||
title: hook.title,
|
||||
category: 'hook',
|
||||
location: hook.location
|
||||
}, async () => {
|
||||
// Separate time slot for each beforeAll/afterAll hook.
|
||||
const timeSlot = {
|
||||
timeout: this._project.project.timeout,
|
||||
elapsed: 0
|
||||
};
|
||||
const runnable = {
|
||||
type: hook.type,
|
||||
slot: timeSlot,
|
||||
location: hook.location
|
||||
};
|
||||
const existingAnnotations = new Set(testInfo.annotations);
|
||||
try {
|
||||
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only', runnable);
|
||||
} finally {
|
||||
if (extraAnnotations) {
|
||||
// Inherit all annotations defined in the beforeAll/modifer to all tests in the suite.
|
||||
const newAnnotations = testInfo.annotations.filter(a => !existingAnnotations.has(a));
|
||||
extraAnnotations.push(...newAnnotations);
|
||||
}
|
||||
// Each beforeAll/afterAll hook has its own scope for test fixtures. Attribute to the same runnable and timeSlot.
|
||||
// Note: we must teardown even after hook fails, because we'll run more hooks.
|
||||
await this._fixtureRunner.teardownScope('test', testInfo, runnable);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
firstError = firstError !== null && firstError !== void 0 ? firstError : error;
|
||||
// Skip in beforeAll/modifier prevents others from running.
|
||||
if (type === 'beforeAll' && error instanceof _testInfo.TestSkipError) break;
|
||||
if (type === 'beforeAll' && !this._skipRemainingTestsInSuite) {
|
||||
// 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.
|
||||
this._skipRemainingTestsInSuite = suite;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (firstError) throw firstError;
|
||||
}
|
||||
async _runAfterAllHooksForSuite(suite, testInfo) {
|
||||
if (!this._activeSuites.has(suite)) return;
|
||||
this._activeSuites.delete(suite);
|
||||
await this._runAllHooksForSuite(suite, testInfo, 'afterAll');
|
||||
}
|
||||
async _runEachHooksForSuites(suites, type, testInfo, slot) {
|
||||
// Always run all the hooks, unless one of the times out, and capture the first error.
|
||||
let firstError;
|
||||
const hooks = suites.map(suite => this._collectHooksAndModifiers(suite, type, testInfo)).flat();
|
||||
for (const hook of hooks) {
|
||||
const runnable = {
|
||||
type: hook.type,
|
||||
location: hook.location,
|
||||
slot
|
||||
};
|
||||
if (testInfo._timeoutManager.isTimeExhaustedFor(runnable)) {
|
||||
// Do not run hooks that will timeout right away.
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await testInfo._runAsStep({
|
||||
title: hook.title,
|
||||
category: 'hook',
|
||||
location: hook.location
|
||||
}, async () => {
|
||||
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'test', runnable);
|
||||
});
|
||||
} catch (error) {
|
||||
firstError = firstError !== null && firstError !== void 0 ? firstError : error;
|
||||
// Skip in modifier prevents others from running.
|
||||
if (error instanceof _testInfo.TestSkipError) break;
|
||||
}
|
||||
}
|
||||
if (firstError) throw firstError;
|
||||
}
|
||||
}
|
||||
exports.WorkerMain = WorkerMain;
|
||||
function buildTestBeginPayload(testInfo) {
|
||||
return {
|
||||
testId: testInfo.testId,
|
||||
startWallTime: testInfo._startWallTime
|
||||
};
|
||||
}
|
||||
function buildTestEndPayload(testInfo) {
|
||||
return {
|
||||
testId: testInfo.testId,
|
||||
duration: testInfo.duration,
|
||||
status: testInfo.status,
|
||||
errors: testInfo.errors,
|
||||
hasNonRetriableError: testInfo._hasNonRetriableError,
|
||||
expectedStatus: testInfo.expectedStatus,
|
||||
annotations: testInfo.annotations,
|
||||
timeout: testInfo.timeout
|
||||
};
|
||||
}
|
||||
function getSuites(test) {
|
||||
const suites = [];
|
||||
for (let suite = test === null || test === void 0 ? void 0 : test.parent; suite; suite = suite.parent) suites.push(suite);
|
||||
suites.reverse(); // Put root suite first.
|
||||
return suites;
|
||||
}
|
||||
function formatTestTitle(test, projectName) {
|
||||
// file, ...describes, test
|
||||
const [, ...titles] = test.titlePath();
|
||||
const location = `${(0, _util.relativeFilePath)(test.location.file)}:${test.location.line}:${test.location.column}`;
|
||||
const projectTitle = projectName ? `[${projectName}] › ` : '';
|
||||
return `${projectTitle}${location} › ${titles.join(' › ')}`;
|
||||
}
|
||||
function calculateMaxTimeout(t1, t2) {
|
||||
// Zero means "no timeout".
|
||||
return !t1 || !t2 ? 0 : Math.max(t1, t2);
|
||||
}
|
||||
const create = params => new WorkerMain(params);
|
||||
exports.create = create;
|
||||
Reference in New Issue
Block a user