@playwright/test 1.31.2 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cli.js +12 -34
- package/lib/common/compilationCache.js +4 -2
- package/lib/common/configLoader.js +4 -1
- package/lib/common/fixtures.js +31 -2
- package/lib/common/globals.js +9 -0
- package/lib/common/process.js +15 -3
- package/lib/common/testType.js +2 -1
- package/lib/common/transform.js +0 -1
- package/lib/index.js +95 -56
- package/lib/isomorphic/folders.js +25 -0
- package/lib/isomorphic/teleReceiver.js +368 -0
- package/lib/matchers/expect.js +35 -16
- package/lib/matchers/matchers.js +48 -48
- package/lib/matchers/toBeTruthy.js +1 -1
- package/lib/matchers/toEqual.js +1 -4
- package/lib/matchers/toMatchSnapshot.js +3 -4
- package/lib/matchers/toMatchText.js +1 -1
- package/lib/plugins/vitePlugin.js +3 -2
- package/lib/reporters/base.js +6 -9
- package/lib/reporters/html.js +7 -2
- package/lib/reporters/json.js +2 -2
- package/lib/reporters/junit.js +2 -2
- package/lib/reporters/teleEmitter.js +209 -0
- package/lib/runner/dispatcher.js +15 -2
- package/lib/runner/loadUtils.js +168 -96
- package/lib/runner/loaderHost.js +2 -2
- package/lib/runner/processHost.js +16 -4
- package/lib/runner/reporters.js +3 -2
- package/lib/runner/runner.js +9 -1
- package/lib/runner/taskRunner.js +6 -8
- package/lib/runner/tasks.js +55 -46
- package/lib/runner/testGroups.js +15 -10
- package/lib/runner/uiMode.js +279 -0
- package/lib/runner/workerHost.js +7 -6
- package/lib/store.js +99 -0
- package/lib/util.js +23 -39
- package/lib/worker/fixtureRunner.js +3 -3
- package/lib/worker/testInfo.js +8 -3
- package/lib/worker/timeoutManager.js +11 -5
- package/lib/worker/workerMain.js +6 -3
- package/package.json +2 -2
- package/types/component.d.ts +1 -1
- package/types/reporterPrivate.ts +22 -0
- package/types/test.d.ts +18 -22
- package/types/testReporter.d.ts +5 -1
- package/lib/common/profiler.js +0 -53
package/lib/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.addTestCommands = addTestCommands;
|
|
|
7
7
|
var _fs = _interopRequireDefault(require("fs"));
|
|
8
8
|
var _path = _interopRequireDefault(require("path"));
|
|
9
9
|
var _runner = require("./runner/runner");
|
|
10
|
-
var
|
|
10
|
+
var _utils = require("playwright-core/lib/utils");
|
|
11
11
|
var _util = require("./util");
|
|
12
12
|
var _html = require("./reporters/html");
|
|
13
13
|
var _configLoader = require("./common/configLoader");
|
|
@@ -38,32 +38,8 @@ function addTestCommands(program) {
|
|
|
38
38
|
function addTestCommand(program) {
|
|
39
39
|
const command = program.command('test [test-filter...]');
|
|
40
40
|
command.description('run tests with Playwright Test');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
command.option('--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options`);
|
|
44
|
-
command.option('-c, --config <file>', `Configuration file, or a test directory with optional ${_configLoader.kDefaultConfigFiles.map(file => `"${file}"`).join('/')}`);
|
|
45
|
-
command.option('--forbid-only', `Fail if test.only is called (default: false)`);
|
|
46
|
-
command.option('--fully-parallel', `Run all tests in parallel (default: false)`);
|
|
47
|
-
command.option('-g, --grep <grep>', `Only run tests matching this regular expression (default: ".*")`);
|
|
48
|
-
command.option('-gv, --grep-invert <grep>', `Only run tests that do not match this regular expression`);
|
|
49
|
-
command.option('--global-timeout <timeout>', `Maximum time this test suite can run in milliseconds (default: unlimited)`);
|
|
50
|
-
command.option('--ignore-snapshots', `Ignore screenshot and snapshot expectations`);
|
|
51
|
-
command.option('-j, --workers <workers>', `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)`);
|
|
52
|
-
command.option('--list', `Collect all the tests and report them, but do not run`);
|
|
53
|
-
command.option('--max-failures <N>', `Stop after the first N failures`);
|
|
54
|
-
command.option('--no-deps', 'Do not run project dependencies');
|
|
55
|
-
command.option('--output <dir>', `Folder for output artifacts (default: "test-results")`);
|
|
56
|
-
command.option('--pass-with-no-tests', `Makes test run succeed even if no tests were found`);
|
|
57
|
-
command.option('--quiet', `Suppress stdio`);
|
|
58
|
-
command.option('--repeat-each <N>', `Run each test N times (default: 1)`);
|
|
59
|
-
command.option('--reporter <reporter>', `Reporter to use, comma-separated, can be ${_configLoader.builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${_configLoader.baseFullConfig.reporter[0]}")`);
|
|
60
|
-
command.option('--retries <retries>', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`);
|
|
61
|
-
command.option('--shard <shard>', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`);
|
|
62
|
-
command.option('--project <project-name...>', `Only run tests from the specified list of projects (default: run all projects)`);
|
|
63
|
-
command.option('--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${_configLoader.defaultTimeout})`);
|
|
64
|
-
command.option('--trace <mode>', `Force tracing mode, can be ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`);
|
|
65
|
-
command.option('-u, --update-snapshots', `Update snapshots with actual results (default: only create missing snapshots)`);
|
|
66
|
-
command.option('-x', `Stop after the first failure`);
|
|
41
|
+
const options = testOptions.sort((a, b) => a[0].replace(/-/g, '').localeCompare(b[0].replace(/-/g, '')));
|
|
42
|
+
options.forEach(([name, description]) => command.option(name, description));
|
|
67
43
|
command.action(async (args, opts) => {
|
|
68
44
|
try {
|
|
69
45
|
await runTests(args, opts);
|
|
@@ -80,7 +56,7 @@ Examples:
|
|
|
80
56
|
$ npx playwright test my.spec.ts
|
|
81
57
|
$ npx playwright test some.spec.ts:42
|
|
82
58
|
$ npx playwright test --headed
|
|
83
|
-
$ npx playwright test --
|
|
59
|
+
$ npx playwright test --project=webkit`);
|
|
84
60
|
}
|
|
85
61
|
function addListFilesCommand(program) {
|
|
86
62
|
const command = program.command('list-files [file-filter...]', {
|
|
@@ -113,7 +89,7 @@ Examples:
|
|
|
113
89
|
$ npx playwright show-report playwright-report`);
|
|
114
90
|
}
|
|
115
91
|
async function runTests(args, opts) {
|
|
116
|
-
await (0,
|
|
92
|
+
await (0, _utils.startProfiling)();
|
|
117
93
|
const overrides = overridesFromOptions(opts);
|
|
118
94
|
if (opts.browser) {
|
|
119
95
|
const browserOpt = opts.browser.toLowerCase();
|
|
@@ -131,13 +107,13 @@ async function runTests(args, opts) {
|
|
|
131
107
|
if (opts.headed || opts.debug) overrides.use = {
|
|
132
108
|
headless: false
|
|
133
109
|
};
|
|
134
|
-
if (opts.debug) {
|
|
110
|
+
if (!opts.ui && opts.debug) {
|
|
135
111
|
overrides.maxFailures = 1;
|
|
136
112
|
overrides.timeout = 0;
|
|
137
113
|
overrides.workers = 1;
|
|
138
114
|
process.env.PWDEBUG = '1';
|
|
139
115
|
}
|
|
140
|
-
if (opts.trace) {
|
|
116
|
+
if (!opts.ui && opts.trace) {
|
|
141
117
|
if (!kTraceModes.includes(opts.trace)) throw new Error(`Unsupported trace mode "${opts.trace}", must be one of ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`);
|
|
142
118
|
overrides.use = overrides.use || {};
|
|
143
119
|
overrides.use.trace = opts.trace;
|
|
@@ -158,8 +134,9 @@ async function runTests(args, opts) {
|
|
|
158
134
|
config._internal.cliProjectFilter = opts.project || undefined;
|
|
159
135
|
config._internal.passWithNoTests = !!opts.passWithNoTests;
|
|
160
136
|
const runner = new _runner.Runner(config);
|
|
161
|
-
|
|
162
|
-
await (
|
|
137
|
+
let status;
|
|
138
|
+
if (opts.ui) status = await runner.uiAllTests();else if (process.env.PWTEST_WATCH) status = await runner.watchAllTests();else status = await runner.runAllTests();
|
|
139
|
+
await (0, _utils.stopProfiling)('runner');
|
|
163
140
|
if (status === 'interrupted') process.exit(130);
|
|
164
141
|
process.exit(status === 'passed' ? 0 : 1);
|
|
165
142
|
}
|
|
@@ -229,4 +206,5 @@ function restartWithExperimentalTsEsm(configFile) {
|
|
|
229
206
|
});
|
|
230
207
|
return true;
|
|
231
208
|
}
|
|
232
|
-
const kTraceModes = ['on', 'off', 'on-first-retry', 'retain-on-failure'];
|
|
209
|
+
const kTraceModes = ['on', 'off', 'on-first-retry', 'retain-on-failure'];
|
|
210
|
+
const testOptions = [['--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`], ['-c, --config <file>', `Configuration file, or a test directory with optional ${_configLoader.kDefaultConfigFiles.map(file => `"${file}"`).join('/')}`], ['--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options`], ['--forbid-only', `Fail if test.only is called (default: false)`], ['--fully-parallel', `Run all tests in parallel (default: false)`], ['--global-timeout <timeout>', `Maximum time this test suite can run in milliseconds (default: unlimited)`], ['-g, --grep <grep>', `Only run tests matching this regular expression (default: ".*")`], ['-gv, --grep-invert <grep>', `Only run tests that do not match this regular expression`], ['--headed', `Run tests in headed browsers (default: headless)`], ['--ignore-snapshots', `Ignore screenshot and snapshot expectations`], ['--list', `Collect all the tests and report them, but do not run`], ['--max-failures <N>', `Stop after the first N failures`], ['--no-deps', 'Do not run project dependencies'], ['--output <dir>', `Folder for output artifacts (default: "test-results")`], ['--pass-with-no-tests', `Makes test run succeed even if no tests were found`], ['--project <project-name...>', `Only run tests from the specified list of projects (default: run all projects)`], ['--quiet', `Suppress stdio`], ['--repeat-each <N>', `Run each test N times (default: 1)`], ['--reporter <reporter>', `Reporter to use, comma-separated, can be ${_configLoader.builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${_configLoader.baseFullConfig.reporter[0]}")`], ['--retries <retries>', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`], ['--shard <shard>', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`], ['--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${_configLoader.defaultTimeout})`], ['--trace <mode>', `Force tracing mode, can be ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`], ['--ui', `Run tests in interactive UI mode`], ['-u, --update-snapshots', `Update snapshots with actual results (default: only create missing snapshots)`], ['-j, --workers <workers>', `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)`], ['-x', `Stop after the first failure`]];
|
|
@@ -8,6 +8,7 @@ exports.belongsToNodeModules = belongsToNodeModules;
|
|
|
8
8
|
exports.clearCompilationCache = clearCompilationCache;
|
|
9
9
|
exports.collectAffectedTestFiles = collectAffectedTestFiles;
|
|
10
10
|
exports.currentFileDepsCollector = currentFileDepsCollector;
|
|
11
|
+
exports.dependenciesForTestFile = dependenciesForTestFile;
|
|
11
12
|
exports.fileDependenciesForTest = fileDependenciesForTest;
|
|
12
13
|
exports.getFromCompilationCache = getFromCompilationCache;
|
|
13
14
|
exports.serializeCompilationCache = serializeCompilationCache;
|
|
@@ -19,7 +20,6 @@ var _fs = _interopRequireDefault(require("fs"));
|
|
|
19
20
|
var _os = _interopRequireDefault(require("os"));
|
|
20
21
|
var _path = _interopRequireDefault(require("path"));
|
|
21
22
|
var _utilsBundle = require("../utilsBundle");
|
|
22
|
-
var _globals = require("./globals");
|
|
23
23
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
24
24
|
/**
|
|
25
25
|
* Copyright (c) Microsoft Corporation.
|
|
@@ -70,7 +70,6 @@ function getFromCompilationCache(filename, code, moduleUrl) {
|
|
|
70
70
|
if (cache !== null && cache !== void 0 && cache.codePath) return {
|
|
71
71
|
cachedCode: _fs.default.readFileSync(cache.codePath, 'utf-8')
|
|
72
72
|
};
|
|
73
|
-
if ((0, _globals.isWorkerProcess)()) throw new Error('Internal error: unexpected file imported in the worker: ' + filename);
|
|
74
73
|
|
|
75
74
|
// Then do the disk cache, this cache works between the Playwright Test runs.
|
|
76
75
|
const isModule = !!moduleUrl;
|
|
@@ -160,6 +159,9 @@ function collectAffectedTestFiles(dependency, testFileCollector) {
|
|
|
160
159
|
if (deps.has(dependency)) testFileCollector.add(testFile);
|
|
161
160
|
}
|
|
162
161
|
}
|
|
162
|
+
function dependenciesForTestFile(filename) {
|
|
163
|
+
return fileDependencies.get(filename) || new Set();
|
|
164
|
+
}
|
|
163
165
|
|
|
164
166
|
// These two are only used in the dev mode, they are specifically excluding
|
|
165
167
|
// files from packages/playwright*. In production mode, node_modules covers
|
|
@@ -12,6 +12,7 @@ var path = _interopRequireWildcard(require("path"));
|
|
|
12
12
|
var _utils = require("playwright-core/lib/utils");
|
|
13
13
|
var _transform = require("./transform");
|
|
14
14
|
var _util = require("../util");
|
|
15
|
+
var _globals = require("./globals");
|
|
15
16
|
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); }
|
|
16
17
|
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; }
|
|
17
18
|
/**
|
|
@@ -55,10 +56,12 @@ class ConfigLoader {
|
|
|
55
56
|
if (this._fullConfig.configFile) throw new Error('Cannot load two config files');
|
|
56
57
|
const config = await requireOrImportDefaultObject(file);
|
|
57
58
|
await this._processConfigObject(config, path.dirname(file), file);
|
|
59
|
+
(0, _globals.setCurrentConfig)(this._fullConfig);
|
|
58
60
|
return this._fullConfig;
|
|
59
61
|
}
|
|
60
62
|
async loadEmptyConfig(configDir) {
|
|
61
63
|
await this._processConfigObject({}, configDir);
|
|
64
|
+
(0, _globals.setCurrentConfig)(this._fullConfig);
|
|
62
65
|
return {};
|
|
63
66
|
}
|
|
64
67
|
async _processConfigObject(config, configDir, configFile) {
|
|
@@ -99,7 +102,7 @@ class ConfigLoader {
|
|
|
99
102
|
if (config.outputDir !== undefined) config.outputDir = path.resolve(configDir, config.outputDir);
|
|
100
103
|
if (config.snapshotDir !== undefined) config.snapshotDir = path.resolve(configDir, config.snapshotDir);
|
|
101
104
|
this._fullConfig._internal.configDir = configDir;
|
|
102
|
-
this._fullConfig._internal.storeDir = path.resolve(configDir, '
|
|
105
|
+
this._fullConfig._internal.storeDir = path.resolve(configDir, config._storeDir || 'playwright');
|
|
103
106
|
this._fullConfig.configFile = configFile;
|
|
104
107
|
this._fullConfig.rootDir = config.testDir || configDir;
|
|
105
108
|
this._fullConfig._internal.globalOutputDir = takeFirst(config.outputDir, throwawayArtifactsPath, baseFullConfig._internal.globalOutputDir);
|
package/lib/common/fixtures.js
CHANGED
|
@@ -204,7 +204,7 @@ function fixtureParameterNames(fn, location, onError) {
|
|
|
204
204
|
return fn[signatureSymbol];
|
|
205
205
|
}
|
|
206
206
|
function innerFixtureParameterNames(fn, location, onError) {
|
|
207
|
-
const text = fn.toString();
|
|
207
|
+
const text = filterOutComments(fn.toString());
|
|
208
208
|
const match = text.match(/(?:async)?(?:\s+function)?[^(]*\(([^)]*)/);
|
|
209
209
|
if (!match) return [];
|
|
210
210
|
const trimmedParams = match[1].trim();
|
|
@@ -219,10 +219,39 @@ function innerFixtureParameterNames(fn, location, onError) {
|
|
|
219
219
|
}
|
|
220
220
|
const props = splitByComma(firstParam.substring(1, firstParam.length - 1)).map(prop => {
|
|
221
221
|
const colon = prop.indexOf(':');
|
|
222
|
-
return colon === -1 ? prop : prop.substring(0, colon).trim();
|
|
222
|
+
return colon === -1 ? prop.trim() : prop.substring(0, colon).trim();
|
|
223
223
|
});
|
|
224
|
+
const restProperty = props.find(prop => prop.startsWith('...'));
|
|
225
|
+
if (restProperty) {
|
|
226
|
+
onError({
|
|
227
|
+
message: `Rest property "${restProperty}" is not supported. List all used fixtures explicitly, separated by comma.`,
|
|
228
|
+
location
|
|
229
|
+
});
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
224
232
|
return props;
|
|
225
233
|
}
|
|
234
|
+
function filterOutComments(s) {
|
|
235
|
+
const result = [];
|
|
236
|
+
let commentState = 'none';
|
|
237
|
+
for (let i = 0; i < s.length; ++i) {
|
|
238
|
+
if (commentState === 'singleline') {
|
|
239
|
+
if (s[i] === '\n') commentState = 'none';
|
|
240
|
+
} else if (commentState === 'multiline') {
|
|
241
|
+
if (s[i - 1] === '*' && s[i] === '/') commentState = 'none';
|
|
242
|
+
} else if (commentState === 'none') {
|
|
243
|
+
if (s[i] === '/' && s[i + 1] === '/') {
|
|
244
|
+
commentState = 'singleline';
|
|
245
|
+
} else if (s[i] === '/' && s[i + 1] === '*') {
|
|
246
|
+
commentState = 'multiline';
|
|
247
|
+
i += 2;
|
|
248
|
+
} else {
|
|
249
|
+
result.push(s[i]);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return result.join('');
|
|
254
|
+
}
|
|
226
255
|
function splitByComma(s) {
|
|
227
256
|
const result = [];
|
|
228
257
|
const stack = [];
|
package/lib/common/globals.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.currentConfig = currentConfig;
|
|
6
7
|
exports.currentExpectTimeout = currentExpectTimeout;
|
|
7
8
|
exports.currentTestInfo = currentTestInfo;
|
|
8
9
|
exports.currentlyLoadingFileSuite = currentlyLoadingFileSuite;
|
|
9
10
|
exports.isWorkerProcess = isWorkerProcess;
|
|
11
|
+
exports.setCurrentConfig = setCurrentConfig;
|
|
10
12
|
exports.setCurrentTestInfo = setCurrentTestInfo;
|
|
11
13
|
exports.setCurrentlyLoadingFileSuite = setCurrentlyLoadingFileSuite;
|
|
12
14
|
exports.setIsWorkerProcess = setIsWorkerProcess;
|
|
@@ -54,4 +56,11 @@ function setIsWorkerProcess() {
|
|
|
54
56
|
}
|
|
55
57
|
function isWorkerProcess() {
|
|
56
58
|
return _isWorkerProcess;
|
|
59
|
+
}
|
|
60
|
+
let currentConfigValue = null;
|
|
61
|
+
function setCurrentConfig(config) {
|
|
62
|
+
currentConfigValue = config;
|
|
63
|
+
}
|
|
64
|
+
function currentConfig() {
|
|
65
|
+
return currentConfigValue;
|
|
57
66
|
}
|
package/lib/common/process.js
CHANGED
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.ProcessRunner = void 0;
|
|
7
|
-
var
|
|
7
|
+
var _utils = require("playwright-core/lib/utils");
|
|
8
8
|
var _util = require("../util");
|
|
9
9
|
/**
|
|
10
10
|
* Copyright Microsoft Corporation. All rights reserved.
|
|
@@ -45,6 +45,9 @@ process.on('SIGINT', () => {});
|
|
|
45
45
|
process.on('SIGTERM', () => {});
|
|
46
46
|
let processRunner;
|
|
47
47
|
let processName;
|
|
48
|
+
const startingEnv = {
|
|
49
|
+
...process.env
|
|
50
|
+
};
|
|
48
51
|
process.on('message', async message => {
|
|
49
52
|
if (message.method === '__init__') {
|
|
50
53
|
const {
|
|
@@ -54,7 +57,7 @@ process.on('message', async message => {
|
|
|
54
57
|
} = message.params;
|
|
55
58
|
setTtyParams(process.stdout, processParams.stdoutParams);
|
|
56
59
|
setTtyParams(process.stderr, processParams.stderrParams);
|
|
57
|
-
(0,
|
|
60
|
+
(0, _utils.startProfiling)();
|
|
58
61
|
const {
|
|
59
62
|
create
|
|
60
63
|
} = require(runnerScript);
|
|
@@ -63,6 +66,15 @@ process.on('message', async message => {
|
|
|
63
66
|
return;
|
|
64
67
|
}
|
|
65
68
|
if (message.method === '__stop__') {
|
|
69
|
+
const keys = new Set([...Object.keys(process.env), ...Object.keys(startingEnv)]);
|
|
70
|
+
const producedEnv = [...keys].filter(key => startingEnv[key] !== process.env[key]).map(key => {
|
|
71
|
+
var _process$env$key;
|
|
72
|
+
return [key, (_process$env$key = process.env[key]) !== null && _process$env$key !== void 0 ? _process$env$key : null];
|
|
73
|
+
});
|
|
74
|
+
sendMessageToParent({
|
|
75
|
+
method: '__env_produced__',
|
|
76
|
+
params: producedEnv
|
|
77
|
+
});
|
|
66
78
|
await gracefullyCloseAndExit();
|
|
67
79
|
return;
|
|
68
80
|
}
|
|
@@ -101,7 +113,7 @@ async function gracefullyCloseAndExit() {
|
|
|
101
113
|
setTimeout(() => process.exit(0), 30000);
|
|
102
114
|
// Meanwhile, try to gracefully shutdown.
|
|
103
115
|
await processRunner.gracefullyClose().catch(() => {});
|
|
104
|
-
await (0,
|
|
116
|
+
await (0, _utils.stopProfiling)(processName).catch(() => {});
|
|
105
117
|
process.exit(0);
|
|
106
118
|
}
|
|
107
119
|
function sendMessageToParent(message) {
|
package/lib/common/testType.js
CHANGED
package/lib/common/transform.js
CHANGED
|
@@ -127,7 +127,6 @@ function js2ts(resolved) {
|
|
|
127
127
|
if (_fs.default.existsSync(tsxResolved)) return tsxResolved;
|
|
128
128
|
}
|
|
129
129
|
function transformHook(code, filename, moduleUrl) {
|
|
130
|
-
// If we are not TypeScript and there is no applicable preprocessor - bail out.
|
|
131
130
|
const {
|
|
132
131
|
cachedCode,
|
|
133
132
|
addToCache
|
package/lib/index.js
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports._baseTest = void 0;
|
|
7
|
+
Object.defineProperty(exports, "_store", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () {
|
|
10
|
+
return _store.store;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
exports.default = void 0;
|
|
7
14
|
Object.defineProperty(exports, "expect", {
|
|
8
15
|
enumerable: true,
|
|
9
16
|
get: function () {
|
|
@@ -18,7 +25,9 @@ var path = _interopRequireWildcard(require("path"));
|
|
|
18
25
|
var playwrightLibrary = _interopRequireWildcard(require("playwright-core"));
|
|
19
26
|
var _utils = require("playwright-core/lib/utils");
|
|
20
27
|
var _testType = require("./common/testType");
|
|
28
|
+
var _folders = require("./isomorphic/folders");
|
|
21
29
|
var _expect = require("./matchers/expect");
|
|
30
|
+
var _store = require("./store");
|
|
22
31
|
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); }
|
|
23
32
|
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; }
|
|
24
33
|
/**
|
|
@@ -39,7 +48,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
|
|
|
39
48
|
|
|
40
49
|
const _baseTest = _testType.rootTestType.test;
|
|
41
50
|
exports._baseTest = _baseTest;
|
|
42
|
-
(0, _utils.
|
|
51
|
+
(0, _utils.addInternalStackPrefix)(path.dirname(require.resolve('../package.json')));
|
|
43
52
|
if (process['__pw_initiator__']) {
|
|
44
53
|
const originalStackTraceLimit = Error.stackTraceLimit;
|
|
45
54
|
Error.stackTraceLimit = 200;
|
|
@@ -87,20 +96,11 @@ const playwrightFixtures = {
|
|
|
87
96
|
option: true
|
|
88
97
|
}],
|
|
89
98
|
connectOptions: [({}, use) => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
...headers,
|
|
96
|
-
'x-playwright-reuse-context': '1'
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
return use({
|
|
100
|
-
wsEndpoint,
|
|
101
|
-
headers,
|
|
102
|
-
_exposeNetwork: process.env.PW_TEST_CONNECT_EXPOSE_NETWORK
|
|
103
|
-
});
|
|
99
|
+
// Usually, when connect options are specified (e.g, in the config or in the environment),
|
|
100
|
+
// all launch() calls are turned into connect() calls.
|
|
101
|
+
// However, when running in "reuse browser" mode and connecting to the reusable server,
|
|
102
|
+
// only the default "browser" fixture should turn into reused browser.
|
|
103
|
+
use(process.env.PW_TEST_REUSE_CONTEXT ? undefined : connectOptionsFromEnv());
|
|
104
104
|
}, {
|
|
105
105
|
scope: 'worker',
|
|
106
106
|
option: true
|
|
@@ -121,7 +121,7 @@ const playwrightFixtures = {
|
|
|
121
121
|
let dir;
|
|
122
122
|
await use(() => {
|
|
123
123
|
if (!dir) {
|
|
124
|
-
dir = path.join(workerInfo.project.outputDir,
|
|
124
|
+
dir = path.join(workerInfo.project.outputDir, (0, _folders.artifactsFolderName)(workerInfo.workerIndex));
|
|
125
125
|
fs.mkdirSync(dir, {
|
|
126
126
|
recursive: true
|
|
127
127
|
});
|
|
@@ -138,7 +138,8 @@ const playwrightFixtures = {
|
|
|
138
138
|
headless,
|
|
139
139
|
channel,
|
|
140
140
|
launchOptions,
|
|
141
|
-
connectOptions
|
|
141
|
+
connectOptions,
|
|
142
|
+
_artifactsDir
|
|
142
143
|
}, use) => {
|
|
143
144
|
const options = {
|
|
144
145
|
handleSIGINT: false,
|
|
@@ -147,6 +148,7 @@ const playwrightFixtures = {
|
|
|
147
148
|
};
|
|
148
149
|
if (headless !== undefined) options.headless = headless;
|
|
149
150
|
if (channel !== undefined) options.channel = channel;
|
|
151
|
+
options.tracesDir = path.join(_artifactsDir(), 'traces');
|
|
150
152
|
for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
|
|
151
153
|
browserType._defaultLaunchOptions = options;
|
|
152
154
|
browserType._defaultConnectOptions = connectOptions;
|
|
@@ -162,9 +164,26 @@ const playwrightFixtures = {
|
|
|
162
164
|
}],
|
|
163
165
|
browser: [async ({
|
|
164
166
|
playwright,
|
|
165
|
-
browserName
|
|
167
|
+
browserName,
|
|
168
|
+
_browserOptions
|
|
166
169
|
}, use, testInfo) => {
|
|
167
170
|
if (!['chromium', 'firefox', 'webkit'].includes(browserName)) throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
|
|
171
|
+
|
|
172
|
+
// Support for "reuse browser" mode.
|
|
173
|
+
const connectOptions = connectOptionsFromEnv();
|
|
174
|
+
if (connectOptions && process.env.PW_TEST_REUSE_CONTEXT) {
|
|
175
|
+
const browser = await playwright[browserName].connect({
|
|
176
|
+
...connectOptions,
|
|
177
|
+
headers: {
|
|
178
|
+
'x-playwright-reuse-context': '1',
|
|
179
|
+
'x-playwright-launch-options': JSON.stringify(_browserOptions),
|
|
180
|
+
...connectOptions.headers
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
await use(browser);
|
|
184
|
+
await browser.close();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
168
187
|
const browser = await playwright[browserName].launch();
|
|
169
188
|
await use(browser);
|
|
170
189
|
await browser.close();
|
|
@@ -377,7 +396,6 @@ const playwrightFixtures = {
|
|
|
377
396
|
playwright,
|
|
378
397
|
_snapshotSuffix,
|
|
379
398
|
_combinedContextOptions,
|
|
380
|
-
_reuseContext,
|
|
381
399
|
_artifactsDir,
|
|
382
400
|
trace,
|
|
383
401
|
screenshot,
|
|
@@ -406,9 +424,10 @@ const playwrightFixtures = {
|
|
|
406
424
|
const temporaryScreenshots = [];
|
|
407
425
|
const testInfoImpl = testInfo;
|
|
408
426
|
const reusedContexts = new Set();
|
|
427
|
+
let traceOrdinal = 0;
|
|
409
428
|
const createInstrumentationListener = context => {
|
|
410
429
|
return {
|
|
411
|
-
onApiCallBegin: (apiCall, stackTrace, userData) => {
|
|
430
|
+
onApiCallBegin: (apiCall, stackTrace, wallTime, userData) => {
|
|
412
431
|
if (apiCall.startsWith('expect.')) return {
|
|
413
432
|
userObject: null
|
|
414
433
|
};
|
|
@@ -422,7 +441,8 @@ const playwrightFixtures = {
|
|
|
422
441
|
category: 'pw:api',
|
|
423
442
|
title: apiCall,
|
|
424
443
|
canHaveChildren: false,
|
|
425
|
-
forceNoParent: false
|
|
444
|
+
forceNoParent: false,
|
|
445
|
+
wallTime
|
|
426
446
|
});
|
|
427
447
|
userData.userObject = step;
|
|
428
448
|
},
|
|
@@ -438,9 +458,14 @@ const playwrightFixtures = {
|
|
|
438
458
|
if (captureTrace) {
|
|
439
459
|
const title = [path.relative(testInfo.project.testDir, testInfo.file) + ':' + testInfo.line, ...testInfo.titlePath.slice(1)].join(' › ');
|
|
440
460
|
if (!tracing[kTracingStarted]) {
|
|
461
|
+
const ordinalSuffix = traceOrdinal ? `-${traceOrdinal}` : '';
|
|
462
|
+
++traceOrdinal;
|
|
463
|
+
const retrySuffix = testInfo.retry ? `-${testInfo.retry}` : '';
|
|
464
|
+
const name = `${testInfo.testId}${retrySuffix}${ordinalSuffix}`;
|
|
441
465
|
await tracing.start({
|
|
442
466
|
...traceOptions,
|
|
443
|
-
title
|
|
467
|
+
title,
|
|
468
|
+
name
|
|
444
469
|
});
|
|
445
470
|
tracing[kTracingStarted] = true;
|
|
446
471
|
} else {
|
|
@@ -469,7 +494,8 @@ const playwrightFixtures = {
|
|
|
469
494
|
context._instrumentation.addListener(createInstrumentationListener());
|
|
470
495
|
};
|
|
471
496
|
const startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
|
|
472
|
-
const
|
|
497
|
+
const stopTracing = async tracing => {
|
|
498
|
+
if (tracing[startedCollectingArtifacts]) return;
|
|
473
499
|
tracing[startedCollectingArtifacts] = true;
|
|
474
500
|
if (captureTrace) {
|
|
475
501
|
// Export trace for now. We'll know whether we have to preserve it
|
|
@@ -505,7 +531,7 @@ const playwrightFixtures = {
|
|
|
505
531
|
// When reusing context, we get all previous contexts closed at the start of next test.
|
|
506
532
|
// Do not record empty traces and useless screenshots for them.
|
|
507
533
|
if (reusedContexts.has(context)) return;
|
|
508
|
-
await
|
|
534
|
+
await stopTracing(context.tracing);
|
|
509
535
|
if (screenshotMode === 'on' || screenshotMode === 'only-on-failure') {
|
|
510
536
|
// Capture screenshot for now. We'll know whether we have to preserve them
|
|
511
537
|
// after the test finishes.
|
|
@@ -514,7 +540,7 @@ const playwrightFixtures = {
|
|
|
514
540
|
};
|
|
515
541
|
const onWillCloseRequestContext = async context => {
|
|
516
542
|
const tracing = context._tracing;
|
|
517
|
-
await
|
|
543
|
+
await stopTracing(tracing);
|
|
518
544
|
};
|
|
519
545
|
|
|
520
546
|
// 1. Setup instrumentation and process existing contexts.
|
|
@@ -522,12 +548,17 @@ const playwrightFixtures = {
|
|
|
522
548
|
browserType._onDidCreateContext = onDidCreateBrowserContext;
|
|
523
549
|
browserType._onWillCloseContext = onWillCloseContext;
|
|
524
550
|
browserType._defaultContextOptions = _combinedContextOptions;
|
|
551
|
+
const promises = [];
|
|
525
552
|
const existingContexts = Array.from(browserType._contexts);
|
|
526
|
-
|
|
553
|
+
for (const context of existingContexts) {
|
|
554
|
+
if (context[kIsReusedContext]) reusedContexts.add(context);else promises.push(onDidCreateBrowserContext(context));
|
|
555
|
+
}
|
|
556
|
+
await Promise.all(promises);
|
|
527
557
|
}
|
|
528
558
|
{
|
|
529
559
|
playwright.request._onDidCreateContext = onDidCreateRequestContext;
|
|
530
560
|
playwright.request._onWillCloseContext = onWillCloseRequestContext;
|
|
561
|
+
playwright.request._defaultContextOptions = _combinedContextOptions;
|
|
531
562
|
const existingApiRequests = Array.from(playwright.request._contexts);
|
|
532
563
|
await Promise.all(existingApiRequests.map(onDidCreateRequestContext));
|
|
533
564
|
}
|
|
@@ -540,17 +571,6 @@ const playwrightFixtures = {
|
|
|
540
571
|
const testFailed = testInfo.status !== testInfo.expectedStatus;
|
|
541
572
|
const preserveTrace = captureTrace && (traceMode === 'on' || testFailed && traceMode === 'retain-on-failure' || traceMode === 'on-first-retry' && testInfo.retry === 1);
|
|
542
573
|
const captureScreenshots = screenshotMode === 'on' || screenshotMode === 'only-on-failure' && testFailed;
|
|
543
|
-
const traceAttachments = [];
|
|
544
|
-
const addTraceAttachment = () => {
|
|
545
|
-
const tracePath = testInfo.outputPath(`trace${traceAttachments.length ? '-' + traceAttachments.length : ''}.zip`);
|
|
546
|
-
traceAttachments.push(tracePath);
|
|
547
|
-
testInfo.attachments.push({
|
|
548
|
-
name: 'trace',
|
|
549
|
-
path: tracePath,
|
|
550
|
-
contentType: 'application/zip'
|
|
551
|
-
});
|
|
552
|
-
return tracePath;
|
|
553
|
-
};
|
|
554
574
|
const screenshotAttachments = [];
|
|
555
575
|
const addScreenshotAttachment = () => {
|
|
556
576
|
const screenshotPath = testInfo.outputPath(`test-${testFailed ? 'failed' : 'finished'}-${screenshotAttachments.length + 1}.png`);
|
|
@@ -576,20 +596,12 @@ const playwrightFixtures = {
|
|
|
576
596
|
const leftoverApiRequests = Array.from(playwright.request._contexts);
|
|
577
597
|
playwright.request._onDidCreateContext = undefined;
|
|
578
598
|
playwright.request._onWillCloseContext = undefined;
|
|
599
|
+
playwright.request._defaultContextOptions = undefined;
|
|
579
600
|
testInfoImpl._onTestFailureImmediateCallbacks.delete(screenshotOnTestFailure);
|
|
580
|
-
const stopTraceChunkOnTestFinish = async tracing => {
|
|
581
|
-
// When we timeout during context.close(), we might end up with context still alive
|
|
582
|
-
// but artifacts being already collected. In this case, do not collect artifacts
|
|
583
|
-
// for the second time.
|
|
584
|
-
if (tracing[startedCollectingArtifacts]) return;
|
|
585
|
-
if (preserveTrace) await tracing.stopChunk({
|
|
586
|
-
path: addTraceAttachment()
|
|
587
|
-
});else if (captureTrace) await tracing.stopChunk();
|
|
588
|
-
};
|
|
589
601
|
|
|
590
602
|
// 5. Collect artifacts from any non-closed contexts.
|
|
591
603
|
await Promise.all(leftoverContexts.map(async context => {
|
|
592
|
-
await
|
|
604
|
+
await stopTracing(context.tracing);
|
|
593
605
|
if (captureScreenshots) {
|
|
594
606
|
await Promise.all(context.pages().map(async page => {
|
|
595
607
|
if (page[screenshottedSymbol]) return;
|
|
@@ -605,14 +617,30 @@ const playwrightFixtures = {
|
|
|
605
617
|
}
|
|
606
618
|
}).concat(leftoverApiRequests.map(async context => {
|
|
607
619
|
const tracing = context._tracing;
|
|
608
|
-
await
|
|
620
|
+
await stopTracing(tracing);
|
|
609
621
|
})));
|
|
610
622
|
|
|
611
|
-
// 6.
|
|
623
|
+
// 6. Save test trace.
|
|
624
|
+
if (preserveTrace) {
|
|
625
|
+
const events = testInfo._traceEvents;
|
|
626
|
+
if (events.length) {
|
|
627
|
+
const tracePath = path.join(_artifactsDir(), (0, _utils.createGuid)() + '.zip');
|
|
628
|
+
temporaryTraceFiles.push(tracePath);
|
|
629
|
+
await (0, _utils.saveTraceFile)(tracePath, events, traceOptions.sources);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// 7. Either remove or attach temporary traces and screenshots for contexts closed
|
|
612
634
|
// before the test has finished.
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
635
|
+
if (preserveTrace && temporaryTraceFiles.length) {
|
|
636
|
+
const tracePath = testInfo.outputPath(`trace.zip`);
|
|
637
|
+
await (0, _utils.mergeTraceFiles)(tracePath, temporaryTraceFiles);
|
|
638
|
+
testInfo.attachments.push({
|
|
639
|
+
name: 'trace',
|
|
640
|
+
path: tracePath,
|
|
641
|
+
contentType: 'application/zip'
|
|
642
|
+
});
|
|
643
|
+
}
|
|
616
644
|
await Promise.all(temporaryScreenshots.map(async file => {
|
|
617
645
|
if (captureScreenshots) await fs.promises.rename(file, addScreenshotAttachment()).catch(() => {});else await fs.promises.unlink(file).catch(() => {});
|
|
618
646
|
}));
|
|
@@ -709,6 +737,7 @@ const playwrightFixtures = {
|
|
|
709
737
|
}
|
|
710
738
|
const defaultContextOptions = playwright.chromium._defaultContextOptions;
|
|
711
739
|
const context = await browser._newContextForReuse(defaultContextOptions);
|
|
740
|
+
context[kIsReusedContext] = true;
|
|
712
741
|
await use(context);
|
|
713
742
|
},
|
|
714
743
|
page: async ({
|
|
@@ -726,10 +755,9 @@ const playwrightFixtures = {
|
|
|
726
755
|
await use(page);
|
|
727
756
|
},
|
|
728
757
|
request: async ({
|
|
729
|
-
playwright
|
|
730
|
-
_combinedContextOptions
|
|
758
|
+
playwright
|
|
731
759
|
}, use) => {
|
|
732
|
-
const request = await playwright.request.newContext(
|
|
760
|
+
const request = await playwright.request.newContext();
|
|
733
761
|
await use(request);
|
|
734
762
|
await request.dispose();
|
|
735
763
|
}
|
|
@@ -773,6 +801,17 @@ function normalizeScreenshotMode(screenshot) {
|
|
|
773
801
|
return typeof screenshot === 'string' ? screenshot : screenshot.mode;
|
|
774
802
|
}
|
|
775
803
|
const kTracingStarted = Symbol('kTracingStarted');
|
|
804
|
+
const kIsReusedContext = Symbol('kReusedContext');
|
|
805
|
+
function connectOptionsFromEnv() {
|
|
806
|
+
const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT;
|
|
807
|
+
if (!wsEndpoint) return undefined;
|
|
808
|
+
const headers = process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : undefined;
|
|
809
|
+
return {
|
|
810
|
+
wsEndpoint,
|
|
811
|
+
headers,
|
|
812
|
+
_exposeNetwork: process.env.PW_TEST_CONNECT_EXPOSE_NETWORK
|
|
813
|
+
};
|
|
814
|
+
}
|
|
776
815
|
const test = _baseTest.extend(playwrightFixtures);
|
|
777
816
|
exports.test = test;
|
|
778
817
|
var _default = test;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.artifactsFolderName = artifactsFolderName;
|
|
7
|
+
/**
|
|
8
|
+
* Copyright (c) Microsoft Corporation.
|
|
9
|
+
*
|
|
10
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
11
|
+
* you may not use this file except in compliance with the License.
|
|
12
|
+
* You may obtain a copy of the License at
|
|
13
|
+
*
|
|
14
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
15
|
+
*
|
|
16
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
* See the License for the specific language governing permissions and
|
|
20
|
+
* limitations under the License.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
function artifactsFolderName(workerIndex) {
|
|
24
|
+
return `.playwright-artifacts-${workerIndex}`;
|
|
25
|
+
}
|