@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.
Files changed (46) hide show
  1. package/lib/cli.js +12 -34
  2. package/lib/common/compilationCache.js +4 -2
  3. package/lib/common/configLoader.js +4 -1
  4. package/lib/common/fixtures.js +31 -2
  5. package/lib/common/globals.js +9 -0
  6. package/lib/common/process.js +15 -3
  7. package/lib/common/testType.js +2 -1
  8. package/lib/common/transform.js +0 -1
  9. package/lib/index.js +95 -56
  10. package/lib/isomorphic/folders.js +25 -0
  11. package/lib/isomorphic/teleReceiver.js +368 -0
  12. package/lib/matchers/expect.js +35 -16
  13. package/lib/matchers/matchers.js +48 -48
  14. package/lib/matchers/toBeTruthy.js +1 -1
  15. package/lib/matchers/toEqual.js +1 -4
  16. package/lib/matchers/toMatchSnapshot.js +3 -4
  17. package/lib/matchers/toMatchText.js +1 -1
  18. package/lib/plugins/vitePlugin.js +3 -2
  19. package/lib/reporters/base.js +6 -9
  20. package/lib/reporters/html.js +7 -2
  21. package/lib/reporters/json.js +2 -2
  22. package/lib/reporters/junit.js +2 -2
  23. package/lib/reporters/teleEmitter.js +209 -0
  24. package/lib/runner/dispatcher.js +15 -2
  25. package/lib/runner/loadUtils.js +168 -96
  26. package/lib/runner/loaderHost.js +2 -2
  27. package/lib/runner/processHost.js +16 -4
  28. package/lib/runner/reporters.js +3 -2
  29. package/lib/runner/runner.js +9 -1
  30. package/lib/runner/taskRunner.js +6 -8
  31. package/lib/runner/tasks.js +55 -46
  32. package/lib/runner/testGroups.js +15 -10
  33. package/lib/runner/uiMode.js +279 -0
  34. package/lib/runner/workerHost.js +7 -6
  35. package/lib/store.js +99 -0
  36. package/lib/util.js +23 -39
  37. package/lib/worker/fixtureRunner.js +3 -3
  38. package/lib/worker/testInfo.js +8 -3
  39. package/lib/worker/timeoutManager.js +11 -5
  40. package/lib/worker/workerMain.js +6 -3
  41. package/package.json +2 -2
  42. package/types/component.d.ts +1 -1
  43. package/types/reporterPrivate.ts +22 -0
  44. package/types/test.d.ts +18 -22
  45. package/types/testReporter.d.ts +5 -1
  46. 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 _profiler = require("./common/profiler");
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
- command.option('--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`);
42
- command.option('--headed', `Run tests in headed browsers (default: headless)`);
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 --browser=webkit`);
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, _profiler.startProfiling)();
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
- const status = process.env.PWTEST_WATCH ? await runner.watchAllTests() : await runner.runAllTests();
162
- await (0, _profiler.stopProfiling)(undefined);
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, '.playwright-store');
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);
@@ -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 = [];
@@ -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
  }
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.ProcessRunner = void 0;
7
- var _profiler = require("./profiler");
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, _profiler.startProfiling)();
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, _profiler.stopProfiling)(processName).catch(() => {});
116
+ await (0, _utils.stopProfiling)(processName).catch(() => {});
105
117
  process.exit(0);
106
118
  }
107
119
  function sendMessageToParent(message) {
@@ -187,7 +187,8 @@ class TestTypeImpl {
187
187
  title,
188
188
  location,
189
189
  canHaveChildren: true,
190
- forceNoParent: false
190
+ forceNoParent: false,
191
+ wallTime: Date.now()
191
192
  });
192
193
  try {
193
194
  const result = await body();
@@ -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.default = exports._baseTest = void 0;
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.addStackIgnoreFilter)(frame => frame.file.startsWith(path.dirname(require.resolve('../package.json'))));
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
- const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT;
91
- if (!wsEndpoint) return use(undefined);
92
- let headers = process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : undefined;
93
- if (process.env.PW_TEST_REUSE_CONTEXT) {
94
- headers = {
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, '.playwright-artifacts-' + workerInfo.workerIndex);
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 stopTraceChunkOnContextClosure = async tracing => {
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 stopTraceChunkOnContextClosure(context.tracing);
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 stopTraceChunkOnContextClosure(tracing);
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
- if (_reuseContext) existingContexts.forEach(c => reusedContexts.add(c));else await Promise.all(existingContexts.map(onDidCreateBrowserContext));
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 stopTraceChunkOnTestFinish(context.tracing);
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 stopTraceChunkOnTestFinish(tracing);
620
+ await stopTracing(tracing);
609
621
  })));
610
622
 
611
- // 6. Either remove or attach temporary traces and screenshots for contexts closed
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
- await Promise.all(temporaryTraceFiles.map(async file => {
614
- if (preserveTrace) await fs.promises.rename(file, addTraceAttachment()).catch(() => {});else await fs.promises.unlink(file).catch(() => {});
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(_combinedContextOptions);
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
+ }