File: /var/www/quadcode.com/node_modules/@playwright/test/lib/index.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports._baseTest = void 0;
Object.defineProperty(exports, "_store", {
enumerable: true,
get: function () {
return _store.store;
}
});
exports.default = void 0;
Object.defineProperty(exports, "expect", {
enumerable: true,
get: function () {
return _expect.expect;
}
});
exports.test = void 0;
var fs = _interopRequireWildcard(require("fs"));
var path = _interopRequireWildcard(require("path"));
var playwrightLibrary = _interopRequireWildcard(require("playwright-core"));
var _utils = require("playwright-core/lib/utils");
var _testType = require("./common/testType");
var _folders = require("./isomorphic/folders");
var _globals = require("./common/globals");
var _expect = require("./matchers/expect");
var _store = require("./store");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* 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.
*/
const _baseTest = _testType.rootTestType.test;
exports._baseTest = _baseTest;
(0, _utils.addInternalStackPrefix)(path.dirname(require.resolve('../package.json')));
if (process['__pw_initiator__']) {
const originalStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 200;
try {
throw new Error('Requiring @playwright/test second time, \nFirst:\n' + process['__pw_initiator__'] + '\n\nSecond: ');
} finally {
Error.stackTraceLimit = originalStackTraceLimit;
}
} else {
process['__pw_initiator__'] = new Error().stack;
}
const playwrightFixtures = {
defaultBrowserType: ['chromium', {
scope: 'worker',
option: true
}],
browserName: [({
defaultBrowserType
}, use) => use(defaultBrowserType), {
scope: 'worker',
option: true
}],
playwright: [async ({}, use) => {
await use(require('playwright-core'));
}, {
scope: 'worker',
_hideStep: true
}],
headless: [({
launchOptions
}, use) => {
var _launchOptions$headle;
return use((_launchOptions$headle = launchOptions.headless) !== null && _launchOptions$headle !== void 0 ? _launchOptions$headle : true);
}, {
scope: 'worker',
option: true
}],
channel: [({
launchOptions
}, use) => use(launchOptions.channel), {
scope: 'worker',
option: true
}],
launchOptions: [{}, {
scope: 'worker',
option: true
}],
connectOptions: [async ({}, use) => {
// Usually, when connect options are specified (e.g, in the config or in the environment),
// all launch() calls are turned into connect() calls.
// However, when running in "reuse browser" mode and connecting to the reusable server,
// only the default "browser" fixture should turn into reused browser.
await use(process.env.PW_TEST_REUSE_CONTEXT ? undefined : connectOptionsFromEnv());
}, {
scope: 'worker',
option: true
}],
screenshot: ['off', {
scope: 'worker',
option: true
}],
video: ['off', {
scope: 'worker',
option: true
}],
trace: ['off', {
scope: 'worker',
option: true
}],
_artifactsDir: [async ({}, use, workerInfo) => {
let dir;
await use(() => {
if (!dir) {
dir = path.join(workerInfo.project.outputDir, (0, _folders.artifactsFolderName)(workerInfo.workerIndex));
fs.mkdirSync(dir, {
recursive: true
});
}
return dir;
});
if (dir) await (0, _utils.removeFolders)([dir]);
}, {
scope: 'worker',
_title: 'playwright configuration'
}],
_browserOptions: [async ({
playwright,
headless,
channel,
launchOptions,
connectOptions,
_artifactsDir
}, use) => {
const options = {
handleSIGINT: false,
...launchOptions
};
if (headless !== undefined) options.headless = headless;
if (channel !== undefined) options.channel = channel;
options.tracesDir = path.join(_artifactsDir(), 'traces');
for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
browserType._defaultLaunchOptions = options;
browserType._defaultConnectOptions = connectOptions;
}
await use(options);
for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
browserType._defaultLaunchOptions = undefined;
browserType._defaultConnectOptions = undefined;
}
}, {
scope: 'worker',
auto: true
}],
browser: [async ({
playwright,
browserName,
_browserOptions
}, use, testInfo) => {
if (!['chromium', 'firefox', 'webkit'].includes(browserName)) throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
// Support for "reuse browser" mode.
const connectOptions = connectOptionsFromEnv();
if (connectOptions && process.env.PW_TEST_REUSE_CONTEXT) {
const browser = await playwright[browserName].connect({
...connectOptions,
headers: {
'x-playwright-reuse-context': '1',
// HTTP headers are ASCII only (not UTF-8).
'x-playwright-launch-options': (0, _utils.jsonStringifyForceASCII)(_browserOptions),
...connectOptions.headers
}
});
await use(browser);
await browser._wrapApiCall(async () => {
await browser.close();
}, true);
return;
}
const browser = await playwright[browserName].launch();
await use(browser);
await browser._wrapApiCall(async () => {
await browser.close();
}, true);
}, {
scope: 'worker',
timeout: 0
}],
acceptDownloads: [({
contextOptions
}, use) => {
var _contextOptions$accep;
return use((_contextOptions$accep = contextOptions.acceptDownloads) !== null && _contextOptions$accep !== void 0 ? _contextOptions$accep : true);
}, {
option: true
}],
bypassCSP: [({
contextOptions
}, use) => {
var _contextOptions$bypas;
return use((_contextOptions$bypas = contextOptions.bypassCSP) !== null && _contextOptions$bypas !== void 0 ? _contextOptions$bypas : false);
}, {
option: true
}],
colorScheme: [({
contextOptions
}, use) => use(contextOptions.colorScheme === undefined ? 'light' : contextOptions.colorScheme), {
option: true
}],
deviceScaleFactor: [({
contextOptions
}, use) => use(contextOptions.deviceScaleFactor), {
option: true
}],
extraHTTPHeaders: [({
contextOptions
}, use) => use(contextOptions.extraHTTPHeaders), {
option: true
}],
geolocation: [({
contextOptions
}, use) => use(contextOptions.geolocation), {
option: true
}],
hasTouch: [({
contextOptions
}, use) => {
var _contextOptions$hasTo;
return use((_contextOptions$hasTo = contextOptions.hasTouch) !== null && _contextOptions$hasTo !== void 0 ? _contextOptions$hasTo : false);
}, {
option: true
}],
httpCredentials: [({
contextOptions
}, use) => use(contextOptions.httpCredentials), {
option: true
}],
ignoreHTTPSErrors: [({
contextOptions
}, use) => {
var _contextOptions$ignor;
return use((_contextOptions$ignor = contextOptions.ignoreHTTPSErrors) !== null && _contextOptions$ignor !== void 0 ? _contextOptions$ignor : false);
}, {
option: true
}],
isMobile: [({
contextOptions
}, use) => {
var _contextOptions$isMob;
return use((_contextOptions$isMob = contextOptions.isMobile) !== null && _contextOptions$isMob !== void 0 ? _contextOptions$isMob : false);
}, {
option: true
}],
javaScriptEnabled: [({
contextOptions
}, use) => {
var _contextOptions$javaS;
return use((_contextOptions$javaS = contextOptions.javaScriptEnabled) !== null && _contextOptions$javaS !== void 0 ? _contextOptions$javaS : true);
}, {
option: true
}],
locale: [({
contextOptions
}, use) => {
var _contextOptions$local;
return use((_contextOptions$local = contextOptions.locale) !== null && _contextOptions$local !== void 0 ? _contextOptions$local : 'en-US');
}, {
option: true
}],
offline: [({
contextOptions
}, use) => {
var _contextOptions$offli;
return use((_contextOptions$offli = contextOptions.offline) !== null && _contextOptions$offli !== void 0 ? _contextOptions$offli : false);
}, {
option: true
}],
permissions: [({
contextOptions
}, use) => use(contextOptions.permissions), {
option: true
}],
proxy: [({
contextOptions
}, use) => use(contextOptions.proxy), {
option: true
}],
storageState: [({
contextOptions
}, use) => use(contextOptions.storageState), {
option: true
}],
timezoneId: [({
contextOptions
}, use) => use(contextOptions.timezoneId), {
option: true
}],
userAgent: [({
contextOptions
}, use) => use(contextOptions.userAgent), {
option: true
}],
viewport: [({
contextOptions
}, use) => use(contextOptions.viewport === undefined ? {
width: 1280,
height: 720
} : contextOptions.viewport), {
option: true
}],
actionTimeout: [0, {
option: true
}],
testIdAttribute: ['data-testid', {
option: true
}],
navigationTimeout: [0, {
option: true
}],
baseURL: [async ({}, use) => {
await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
}, {
option: true
}],
serviceWorkers: [({
contextOptions
}, use) => {
var _contextOptions$servi;
return use((_contextOptions$servi = contextOptions.serviceWorkers) !== null && _contextOptions$servi !== void 0 ? _contextOptions$servi : 'allow');
}, {
option: true
}],
contextOptions: [{}, {
option: true
}],
_combinedContextOptions: async ({
acceptDownloads,
bypassCSP,
colorScheme,
deviceScaleFactor,
extraHTTPHeaders,
hasTouch,
geolocation,
httpCredentials,
ignoreHTTPSErrors,
isMobile,
javaScriptEnabled,
locale,
offline,
permissions,
proxy,
storageState,
viewport,
timezoneId,
userAgent,
baseURL,
contextOptions,
serviceWorkers
}, use) => {
const options = {};
if (acceptDownloads !== undefined) options.acceptDownloads = acceptDownloads;
if (bypassCSP !== undefined) options.bypassCSP = bypassCSP;
if (colorScheme !== undefined) options.colorScheme = colorScheme;
if (deviceScaleFactor !== undefined) options.deviceScaleFactor = deviceScaleFactor;
if (extraHTTPHeaders !== undefined) options.extraHTTPHeaders = extraHTTPHeaders;
if (geolocation !== undefined) options.geolocation = geolocation;
if (hasTouch !== undefined) options.hasTouch = hasTouch;
if (httpCredentials !== undefined) options.httpCredentials = httpCredentials;
if (ignoreHTTPSErrors !== undefined) options.ignoreHTTPSErrors = ignoreHTTPSErrors;
if (isMobile !== undefined) options.isMobile = isMobile;
if (javaScriptEnabled !== undefined) options.javaScriptEnabled = javaScriptEnabled;
if (locale !== undefined) options.locale = locale;
if (offline !== undefined) options.offline = offline;
if (permissions !== undefined) options.permissions = permissions;
if (proxy !== undefined) options.proxy = proxy;
if (storageState !== undefined) options.storageState = storageState;
if (timezoneId !== undefined) options.timezoneId = timezoneId;
if (userAgent !== undefined) options.userAgent = userAgent;
if (viewport !== undefined) options.viewport = viewport;
if (baseURL !== undefined) options.baseURL = baseURL;
if (serviceWorkers !== undefined) options.serviceWorkers = serviceWorkers;
await use({
...contextOptions,
...options
});
},
_setupContextOptions: [async ({
playwright,
_combinedContextOptions,
_artifactsDir,
actionTimeout,
navigationTimeout,
testIdAttribute
}, use, testInfo) => {
if (testIdAttribute) playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
testInfo.snapshotSuffix = process.platform;
if ((0, _utils.debugMode)()) testInfo.setTimeout(0);
for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
browserType._defaultContextOptions = _combinedContextOptions;
browserType._defaultContextTimeout = actionTimeout || 0;
browserType._defaultContextNavigationTimeout = navigationTimeout || 0;
}
playwright.request._defaultContextOptions = {
..._combinedContextOptions
};
playwright.request._defaultContextOptions.tracesDir = path.join(_artifactsDir(), 'traces');
playwright.request._defaultContextOptions.timeout = actionTimeout || 0;
await use();
playwright.request._defaultContextOptions = undefined;
for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
browserType._defaultContextOptions = undefined;
browserType._defaultContextTimeout = undefined;
browserType._defaultContextNavigationTimeout = undefined;
}
}, {
auto: 'all-hooks-included',
_title: 'context configuration'
}],
_setupArtifacts: [async ({
playwright,
_artifactsDir,
trace,
screenshot
}, use, testInfo) => {
const artifactsRecorder = new ArtifactsRecorder(playwright, _artifactsDir(), trace, screenshot);
await artifactsRecorder.willStartTest(testInfo);
const csiListener = {
onApiCallBegin: (apiName, params, stackTrace, wallTime, userData) => {
const testInfo = (0, _globals.currentTestInfo)();
if (!testInfo || apiName.startsWith('expect.') || apiName.includes('setTestIdAttribute')) return {
userObject: null
};
const step = testInfo._addStep({
location: stackTrace === null || stackTrace === void 0 ? void 0 : stackTrace.frames[0],
category: 'pw:api',
title: renderApiCall(apiName, params),
apiName,
params,
wallTime,
laxParent: true
});
userData.userObject = step;
},
onApiCallEnd: (userData, error) => {
const step = userData.userObject;
step === null || step === void 0 ? void 0 : step.complete({
error
});
},
onWillPause: () => {
var _currentTestInfo;
(_currentTestInfo = (0, _globals.currentTestInfo)()) === null || _currentTestInfo === void 0 ? void 0 : _currentTestInfo.setTimeout(0);
},
onDidCreateBrowserContext: async context => {
await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.didCreateBrowserContext(context));
const testInfo = (0, _globals.currentTestInfo)();
if (testInfo) attachConnectedHeaderIfNeeded(testInfo, context.browser());
},
onDidCreateRequestContext: async context => {
await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.didCreateRequestContext(context));
},
onWillCloseBrowserContext: async context => {
await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.willCloseBrowserContext(context));
},
onWillCloseRequestContext: async context => {
await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.willCloseRequestContext(context));
}
};
const clientInstrumentation = playwright._instrumentation;
clientInstrumentation.addListener(csiListener);
await use();
clientInstrumentation.removeListener(csiListener);
await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.didFinishTest());
}, {
auto: 'all-hooks-included',
_title: 'trace recording'
}],
_contextFactory: [async ({
browser,
video,
_artifactsDir,
_reuseContext
}, use, testInfo) => {
const testInfoImpl = testInfo;
const videoMode = normalizeVideoMode(video);
const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext;
const contexts = new Map();
await use(async options => {
const hook = hookType(testInfoImpl);
if (hook) {
throw new Error([`"context" and "page" fixtures are not supported in "${hook}" since they are created on a per-test basis.`, `If you would like to reuse a single page between tests, create context manually with browser.newContext(). See https://aka.ms/playwright/reuse-page for details.`, `If you would like to configure your page before each test, do that in beforeEach hook instead.`].join('\n'));
}
const videoOptions = captureVideo ? {
recordVideo: {
dir: _artifactsDir(),
size: typeof video === 'string' ? undefined : video.size
}
} : {};
const context = await browser.newContext({
...videoOptions,
...options
});
const contextData = {
pagesWithVideo: []
};
contexts.set(context, contextData);
if (captureVideo) context.on('page', page => contextData.pagesWithVideo.push(page));
return context;
});
const prependToError = testInfoImpl._didTimeout ? formatPendingCalls(browser._connection.pendingProtocolCalls()) : '';
let counter = 0;
await Promise.all([...contexts.keys()].map(async context => {
context[kStartedContextTearDown] = true;
await context._wrapApiCall(async () => {
await context.close();
}, true);
const testFailed = testInfo.status !== testInfo.expectedStatus;
const preserveVideo = captureVideo && (videoMode === 'on' || testFailed && videoMode === 'retain-on-failure' || videoMode === 'on-first-retry' && testInfo.retry === 1);
if (preserveVideo) {
const {
pagesWithVideo: pagesForVideo
} = contexts.get(context);
const videos = pagesForVideo.map(p => p.video()).filter(Boolean);
await Promise.all(videos.map(async v => {
try {
const savedPath = testInfo.outputPath(`video${counter ? '-' + counter : ''}.webm`);
++counter;
await v.saveAs(savedPath);
testInfo.attachments.push({
name: 'video',
path: savedPath,
contentType: 'video/webm'
});
} catch (e) {
// Silent catch empty videos.
}
}));
}
}));
if (prependToError) testInfo.errors.push({
message: prependToError
});
}, {
scope: 'test',
_title: 'context'
}],
_contextReuseMode: process.env.PW_TEST_REUSE_CONTEXT === 'when-possible' ? 'when-possible' : process.env.PW_TEST_REUSE_CONTEXT ? 'force' : 'none',
_reuseContext: [async ({
video,
_contextReuseMode
}, use, testInfo) => {
const reuse = _contextReuseMode === 'force' || _contextReuseMode === 'when-possible' && !shouldCaptureVideo(normalizeVideoMode(video), testInfo);
await use(reuse);
}, {
scope: 'test',
_title: 'context'
}],
context: async ({
playwright,
browser,
_reuseContext,
_contextFactory
}, use, testInfo) => {
attachConnectedHeaderIfNeeded(testInfo, browser);
if (!_reuseContext) {
await use(await _contextFactory());
return;
}
const defaultContextOptions = playwright.chromium._defaultContextOptions;
const context = await browser._newContextForReuse(defaultContextOptions);
context[kIsReusedContext] = true;
await use(context);
},
page: async ({
context,
_reuseContext
}, use) => {
if (!_reuseContext) {
await use(await context.newPage());
return;
}
// First time we are reusing the context, we should create the page.
let [page] = context.pages();
if (!page) page = await context.newPage();
await use(page);
},
request: async ({
playwright
}, use) => {
const request = await playwright.request.newContext();
await use(request);
request[kStartedContextTearDown] = true;
await request.dispose();
}
};
function formatPendingCalls(calls) {
calls = calls.filter(call => !!call.apiName);
if (!calls.length) return '';
return 'Pending operations:\n' + calls.map(call => {
const frame = call.frames && call.frames[0] ? ' at ' + formatStackFrame(call.frames[0]) : '';
return ` - ${call.apiName}${frame}\n`;
}).join('');
}
function formatStackFrame(frame) {
const file = path.relative(process.cwd(), frame.file) || path.basename(frame.file);
return `${file}:${frame.line || 1}:${frame.column || 1}`;
}
function hookType(testInfo) {
const type = testInfo._timeoutManager.currentRunnableType();
if (type === 'beforeAll' || type === 'afterAll') return type;
}
function normalizeVideoMode(video) {
if (!video) return 'off';
let videoMode = typeof video === 'string' ? video : video.mode;
if (videoMode === 'retry-with-video') videoMode = 'on-first-retry';
return videoMode;
}
function shouldCaptureVideo(videoMode, testInfo) {
return videoMode === 'on' || videoMode === 'retain-on-failure' || videoMode === 'on-first-retry' && testInfo.retry === 1;
}
function normalizeTraceMode(trace) {
if (!trace) return 'off';
let traceMode = typeof trace === 'string' ? trace : trace.mode;
if (traceMode === 'retry-with-trace') traceMode = 'on-first-retry';
return traceMode;
}
function shouldCaptureTrace(traceMode, testInfo) {
return traceMode === 'on' || traceMode === 'retain-on-failure' || traceMode === 'on-first-retry' && testInfo.retry === 1 || traceMode === 'on-all-retries' && testInfo.retry > 0;
}
function normalizeScreenshotMode(screenshot) {
if (!screenshot) return 'off';
return typeof screenshot === 'string' ? screenshot : screenshot.mode;
}
function attachConnectedHeaderIfNeeded(testInfo, browser) {
const connectHeaders = browser === null || browser === void 0 ? void 0 : browser._connectHeaders;
if (!connectHeaders) return;
for (const header of connectHeaders) {
if (header.name !== 'x-playwright-attachment') continue;
const [name, value] = header.value.split('=');
if (!name || !value) continue;
if (testInfo.attachments.some(attachment => attachment.name === name)) continue;
testInfo.attachments.push({
name,
contentType: 'text/plain',
body: Buffer.from(value)
});
}
}
const kTracingStarted = Symbol('kTracingStarted');
const kIsReusedContext = Symbol('kReusedContext');
const kStartedContextTearDown = Symbol('kStartedContextTearDown');
function connectOptionsFromEnv() {
const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT;
if (!wsEndpoint) return undefined;
const headers = process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : undefined;
return {
wsEndpoint,
headers,
exposeNetwork: process.env.PW_TEST_CONNECT_EXPOSE_NETWORK
};
}
class ArtifactsRecorder {
constructor(playwright, artifactsDir, trace, screenshot) {
this._testInfo = void 0;
this._playwright = void 0;
this._artifactsDir = void 0;
this._screenshotMode = void 0;
this._traceMode = void 0;
this._captureTrace = false;
this._screenshotOptions = void 0;
this._traceOptions = void 0;
this._temporaryTraceFiles = [];
this._temporaryScreenshots = [];
this._reusedContexts = new Set();
this._traceOrdinal = 0;
this._screenshotOrdinal = 0;
this._screenshottedSymbol = void 0;
this._startedCollectingArtifacts = void 0;
this._playwright = playwright;
this._artifactsDir = artifactsDir;
this._screenshotMode = normalizeScreenshotMode(screenshot);
this._screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
this._traceMode = normalizeTraceMode(trace);
const defaultTraceOptions = {
screenshots: true,
snapshots: true,
sources: true,
attachments: true
};
this._traceOptions = typeof trace === 'string' ? defaultTraceOptions : {
...defaultTraceOptions,
...trace,
mode: undefined
};
this._screenshottedSymbol = Symbol('screenshotted');
this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
}
async willStartTest(testInfo) {
this._testInfo = testInfo;
testInfo._onDidFinishTestFunction = () => this.didFinishTestFunction();
this._captureTrace = shouldCaptureTrace(this._traceMode, testInfo) && !process.env.PW_TEST_DISABLE_TRACING;
// Since beforeAll(s), test and afterAll(s) reuse the same TestInfo, make sure we do not
// overwrite previous screenshots.
this._screenshotOrdinal = testInfo.attachments.filter(a => a.name === 'screenshot').length;
// Process existing contexts.
for (const browserType of [this._playwright.chromium, this._playwright.firefox, this._playwright.webkit]) {
const promises = [];
const existingContexts = Array.from(browserType._contexts);
for (const context of existingContexts) {
if (context[kIsReusedContext]) this._reusedContexts.add(context);else promises.push(this.didCreateBrowserContext(context));
}
await Promise.all(promises);
}
{
const existingApiRequests = Array.from(this._playwright.request._contexts);
await Promise.all(existingApiRequests.map(c => this.didCreateRequestContext(c)));
}
}
async didCreateBrowserContext(context) {
await this._startTraceChunkOnContextCreation(context.tracing);
}
async willCloseBrowserContext(context) {
// When reusing context, we get all previous contexts closed at the start of next test.
// Do not record empty traces and useless screenshots for them.
if (this._reusedContexts.has(context)) return;
await this._stopTracing(context.tracing, context[kStartedContextTearDown]);
if (this._screenshotMode === 'on' || this._screenshotMode === 'only-on-failure') {
// Capture screenshot for now. We'll know whether we have to preserve them
// after the test finishes.
await Promise.all(context.pages().map(page => this._screenshotPage(page)));
}
}
async didCreateRequestContext(context) {
const tracing = context._tracing;
await this._startTraceChunkOnContextCreation(tracing);
}
async willCloseRequestContext(context) {
const tracing = context._tracing;
await this._stopTracing(tracing, context[kStartedContextTearDown]);
}
async didFinishTestFunction() {
if (this._testInfo._isFailure() && (this._screenshotMode === 'on' || this._screenshotMode === 'only-on-failure')) await this._screenshotOnTestFailure();
}
async didFinishTest() {
const captureScreenshots = this._screenshotMode === 'on' || this._screenshotMode === 'only-on-failure' && this._testInfo.status !== this._testInfo.expectedStatus;
const leftoverContexts = [];
for (const browserType of [this._playwright.chromium, this._playwright.firefox, this._playwright.webkit]) leftoverContexts.push(...browserType._contexts);
const leftoverApiRequests = Array.from(this._playwright.request._contexts);
// Collect traces/screenshots for remaining contexts.
await Promise.all(leftoverContexts.map(async context => {
await this._stopTracing(context.tracing, true);
if (captureScreenshots) {
await Promise.all(context.pages().map(async page => {
if (page[this._screenshottedSymbol]) return;
try {
const screenshotPath = this._createScreenshotAttachmentPath();
// Pass caret=initial to avoid any evaluations that might slow down the screenshot
// and let the page modify itself from the problematic state it had at the moment of failure.
await page.screenshot({
...this._screenshotOptions,
timeout: 5000,
path: screenshotPath,
caret: 'initial'
});
this._testInfo.attachments.push({
name: 'screenshot',
path: screenshotPath,
contentType: 'image/png'
});
} catch {
// Screenshot may fail, just ignore.
}
}));
}
}).concat(leftoverApiRequests.map(async context => {
const tracing = context._tracing;
await this._stopTracing(tracing, true);
})));
// Either remove or attach temporary screenshots for contexts closed before
// collecting the test trace.
await Promise.all(this._temporaryScreenshots.map(async file => {
if (!captureScreenshots) {
await fs.promises.unlink(file).catch(() => {});
return;
}
try {
const screenshotPath = this._createScreenshotAttachmentPath();
await fs.promises.rename(file, screenshotPath);
this._testInfo.attachments.push({
name: 'screenshot',
path: screenshotPath,
contentType: 'image/png'
});
} catch {}
}));
// Collect test trace.
if (this._preserveTrace()) {
const events = this._testInfo._traceEvents;
if (events.length) {
if (!this._traceOptions.attachments) {
for (const event of events) {
if (event.type === 'after') delete event.attachments;
}
}
const tracePath = path.join(this._artifactsDir, (0, _utils.createGuid)() + '.zip');
this._temporaryTraceFiles.push(tracePath);
await (0, _utils.saveTraceFile)(tracePath, events, this._traceOptions.sources);
}
}
// Either remove or attach temporary traces for contexts closed before the
// test has finished.
if (this._preserveTrace() && this._temporaryTraceFiles.length) {
const tracePath = this._testInfo.outputPath(`trace.zip`);
// This could be: beforeHooks, or beforeHooks + test, etc.
const beforeHooksHadTrace = fs.existsSync(tracePath);
if (beforeHooksHadTrace) {
await fs.promises.rename(tracePath, tracePath + '.tmp');
this._temporaryTraceFiles.unshift(tracePath + '.tmp');
}
await (0, _utils.mergeTraceFiles)(tracePath, this._temporaryTraceFiles);
// Do not add attachment twice.
if (!beforeHooksHadTrace) this._testInfo.attachments.push({
name: 'trace',
path: tracePath,
contentType: 'application/zip'
});
}
}
_createScreenshotAttachmentPath() {
const testFailed = this._testInfo.status !== this._testInfo.expectedStatus;
const index = this._screenshotOrdinal + 1;
++this._screenshotOrdinal;
const screenshotPath = this._testInfo.outputPath(`test-${testFailed ? 'failed' : 'finished'}-${index}.png`);
return screenshotPath;
}
async _screenshotPage(page) {
if (page[this._screenshottedSymbol]) return;
page[this._screenshottedSymbol] = true;
try {
const screenshotPath = path.join(this._artifactsDir, (0, _utils.createGuid)() + '.png');
// Pass caret=initial to avoid any evaluations that might slow down the screenshot
// and let the page modify itself from the problematic state it had at the moment of failure.
await page.screenshot({
...this._screenshotOptions,
timeout: 5000,
path: screenshotPath,
caret: 'initial'
}).catch(() => {});
this._temporaryScreenshots.push(screenshotPath);
} catch {
// Screenshot may fail, just ignore.
}
}
async _screenshotOnTestFailure() {
const contexts = [];
for (const browserType of [this._playwright.chromium, this._playwright.firefox, this._playwright.webkit]) contexts.push(...browserType._contexts);
await Promise.all(contexts.map(ctx => Promise.all(ctx.pages().map(page => this._screenshotPage(page)))));
}
async _startTraceChunkOnContextCreation(tracing) {
if (this._captureTrace) {
const title = [path.relative(this._testInfo.project.testDir, this._testInfo.file) + ':' + this._testInfo.line, ...this._testInfo.titlePath.slice(1)].join(' › ');
const ordinalSuffix = this._traceOrdinal ? `-context${this._traceOrdinal}` : '';
++this._traceOrdinal;
const retrySuffix = this._testInfo.retry ? `-retry${this._testInfo.retry}` : '';
const name = `${this._testInfo.testId}${retrySuffix}${ordinalSuffix}`;
if (!tracing[kTracingStarted]) {
await tracing.start({
...this._traceOptions,
title,
name
});
tracing[kTracingStarted] = true;
} else {
await tracing.startChunk({
title,
name
});
}
} else {
if (tracing[kTracingStarted]) {
tracing[kTracingStarted] = false;
await tracing.stop();
}
}
}
_preserveTrace() {
const testFailed = this._testInfo.status !== this._testInfo.expectedStatus;
return this._captureTrace && (this._traceMode === 'on' || testFailed && this._traceMode === 'retain-on-failure' || this._traceMode === 'on-first-retry' && this._testInfo.retry === 1 || this._traceMode === 'on-all-retries' && this._testInfo.retry > 0);
}
async _stopTracing(tracing, contextTearDownStarted) {
if (tracing[this._startedCollectingArtifacts]) return;
tracing[this._startedCollectingArtifacts] = true;
if (this._captureTrace) {
let tracePath;
// Create a trace file if we know that:
// - it is's going to be used due to the config setting and the test status or
// - we are inside a test or afterEach and the user manually closed the context.
if (this._preserveTrace() || !contextTearDownStarted) {
tracePath = path.join(this._artifactsDir, (0, _utils.createGuid)() + '.zip');
this._temporaryTraceFiles.push(tracePath);
}
await tracing.stopChunk({
path: tracePath
});
}
}
}
const paramsToRender = ['url', 'selector', 'text', 'key'];
function renderApiCall(apiName, params) {
const paramsArray = [];
if (params) {
for (const name of paramsToRender) {
if (!(name in params)) continue;
let value;
if (name === 'selector' && (0, _utils.isString)(params[name]) && params[name].startsWith('internal:')) {
const getter = (0, _utils.asLocator)('javascript', params[name], false, true);
apiName = apiName.replace(/^locator\./, 'locator.' + getter + '.');
apiName = apiName.replace(/^page\./, 'page.' + getter + '.');
apiName = apiName.replace(/^frame\./, 'frame.' + getter + '.');
} else {
value = params[name];
paramsArray.push(value);
}
}
}
const paramsText = paramsArray.length ? '(' + paramsArray.join(', ') + ')' : '';
return apiName + paramsText;
}
const test = _baseTest.extend(playwrightFixtures);
exports.test = test;
var _default = test;
exports.default = _default;