@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
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.TeleReporterEmitter = void 0;
7
+ var _utils = require("playwright-core/lib/utils");
8
+ var _teleReceiver = require("../isomorphic/teleReceiver");
9
+ /**
10
+ * Copyright (c) Microsoft Corporation.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+
25
+ class TeleReporterEmitter {
26
+ constructor(messageSink) {
27
+ this._messageSink = void 0;
28
+ this._messageSink = messageSink;
29
+ }
30
+ onBegin(config, suite) {
31
+ const projects = [];
32
+ for (const projectSuite of suite.suites) {
33
+ const report = this._serializeProject(projectSuite);
34
+ projects.push(report);
35
+ }
36
+ this._messageSink({
37
+ method: 'onBegin',
38
+ params: {
39
+ config: this._serializeConfig(config),
40
+ projects
41
+ }
42
+ });
43
+ }
44
+ onTestBegin(test, result) {
45
+ result[idSymbol] = (0, _utils.createGuid)();
46
+ this._messageSink({
47
+ method: 'onTestBegin',
48
+ params: {
49
+ testId: test.id,
50
+ result: this._serializeResultStart(result)
51
+ }
52
+ });
53
+ }
54
+ onTestEnd(test, result) {
55
+ this._messageSink({
56
+ method: 'onTestEnd',
57
+ params: {
58
+ testId: test.id,
59
+ result: this._serializeResultEnd(result)
60
+ }
61
+ });
62
+ }
63
+ onStepBegin(test, result, step) {
64
+ step[idSymbol] = (0, _utils.createGuid)();
65
+ this._messageSink({
66
+ method: 'onStepBegin',
67
+ params: {
68
+ testId: test.id,
69
+ resultId: result[idSymbol],
70
+ step: this._serializeStepStart(step)
71
+ }
72
+ });
73
+ }
74
+ onStepEnd(test, result, step) {
75
+ this._messageSink({
76
+ method: 'onStepEnd',
77
+ params: {
78
+ testId: test.id,
79
+ resultId: result[idSymbol],
80
+ step: this._serializeStepEnd(step)
81
+ }
82
+ });
83
+ }
84
+ onError(error) {
85
+ this._messageSink({
86
+ method: 'onError',
87
+ params: {
88
+ error
89
+ }
90
+ });
91
+ }
92
+ onStdOut(chunk, test, result) {
93
+ this._onStdIO('stdio', chunk, test, result);
94
+ }
95
+ onStdErr(chunk, test, result) {
96
+ this._onStdIO('stderr', chunk, test, result);
97
+ }
98
+ _onStdIO(type, chunk, test, result) {
99
+ const isBase64 = typeof chunk !== 'string';
100
+ const data = isBase64 ? chunk.toString('base64') : chunk;
101
+ this._messageSink({
102
+ method: 'onStdIO',
103
+ params: {
104
+ testId: test === null || test === void 0 ? void 0 : test.id,
105
+ resultId: result ? result[idSymbol] : undefined,
106
+ type,
107
+ data,
108
+ isBase64
109
+ }
110
+ });
111
+ }
112
+ async onEnd(result) {
113
+ this._messageSink({
114
+ method: 'onEnd',
115
+ params: {
116
+ result
117
+ }
118
+ });
119
+ }
120
+ _serializeConfig(config) {
121
+ return {
122
+ rootDir: config.rootDir,
123
+ configFile: config.configFile,
124
+ listOnly: config._internal.listOnly
125
+ };
126
+ }
127
+ _serializeProject(suite) {
128
+ const project = suite.project();
129
+ const report = {
130
+ id: project._internal.id,
131
+ metadata: project.metadata,
132
+ name: project.name,
133
+ outputDir: project.outputDir,
134
+ repeatEach: project.repeatEach,
135
+ retries: project.retries,
136
+ testDir: project.testDir,
137
+ testIgnore: (0, _teleReceiver.serializeRegexPatterns)(project.testIgnore),
138
+ testMatch: (0, _teleReceiver.serializeRegexPatterns)(project.testMatch),
139
+ timeout: project.timeout,
140
+ suites: suite.suites.map(fileSuite => {
141
+ return this._serializeSuite(fileSuite);
142
+ }),
143
+ grep: (0, _teleReceiver.serializeRegexPatterns)(project.grep),
144
+ grepInvert: (0, _teleReceiver.serializeRegexPatterns)(project.grepInvert || []),
145
+ dependencies: project.dependencies,
146
+ snapshotDir: project.snapshotDir
147
+ };
148
+ return report;
149
+ }
150
+ _serializeSuite(suite) {
151
+ const result = {
152
+ type: suite._type,
153
+ title: suite.title,
154
+ fileId: suite._fileId,
155
+ parallelMode: suite._parallelMode,
156
+ location: suite.location,
157
+ suites: suite.suites.map(s => this._serializeSuite(s)),
158
+ tests: suite.tests.map(t => this._serializeTest(t))
159
+ };
160
+ return result;
161
+ }
162
+ _serializeTest(test) {
163
+ return {
164
+ testId: test.id,
165
+ title: test.title,
166
+ location: test.location,
167
+ expectedStatus: test.expectedStatus,
168
+ timeout: test.timeout,
169
+ annotations: test.annotations,
170
+ retries: test.retries
171
+ };
172
+ }
173
+ _serializeResultStart(result) {
174
+ return {
175
+ id: result[idSymbol],
176
+ retry: result.retry,
177
+ workerIndex: result.workerIndex,
178
+ parallelIndex: result.parallelIndex,
179
+ startTime: result.startTime.toISOString()
180
+ };
181
+ }
182
+ _serializeResultEnd(result) {
183
+ return {
184
+ id: result[idSymbol],
185
+ duration: result.duration,
186
+ status: result.status,
187
+ errors: result.errors,
188
+ attachments: result.attachments
189
+ };
190
+ }
191
+ _serializeStepStart(step) {
192
+ return {
193
+ id: step[idSymbol],
194
+ title: step.title,
195
+ category: step.category,
196
+ startTime: step.startTime.toISOString(),
197
+ location: step.location
198
+ };
199
+ }
200
+ _serializeStepEnd(step) {
201
+ return {
202
+ id: step[idSymbol],
203
+ duration: step.duration,
204
+ error: step.error
205
+ };
206
+ }
207
+ }
208
+ exports.TeleReporterEmitter = TeleReporterEmitter;
209
+ const idSymbol = Symbol('id');
@@ -35,6 +35,8 @@ class Dispatcher {
35
35
  this._reporter = void 0;
36
36
  this._hasWorkerErrors = false;
37
37
  this._failureCount = 0;
38
+ this._extraEnvByProjectId = new Map();
39
+ this._producedEnvByProjectId = new Map();
38
40
  this._config = config;
39
41
  this._reporter = reporter;
40
42
  }
@@ -138,7 +140,8 @@ class Dispatcher {
138
140
  }
139
141
  return workersWithSameHash > this._queuedOrRunningHashCount.get(worker.hash());
140
142
  }
141
- async run(testGroups) {
143
+ async run(testGroups, extraEnvByProjectId) {
144
+ this._extraEnvByProjectId = extraEnvByProjectId;
142
145
  this._queue = testGroups;
143
146
  for (const group of testGroups) {
144
147
  this._queuedOrRunningHashCount.set(group.workerHash, 1 + (this._queuedOrRunningHashCount.get(group.workerHash) || 0));
@@ -431,7 +434,7 @@ class Dispatcher {
431
434
  return result;
432
435
  }
433
436
  _createWorker(testGroup, parallelIndex, loaderData) {
434
- const worker = new _workerHost.WorkerHost(testGroup, parallelIndex, loaderData);
437
+ const worker = new _workerHost.WorkerHost(testGroup, parallelIndex, loaderData, this._extraEnvByProjectId.get(testGroup.projectId) || {});
435
438
  const handleOutput = params => {
436
439
  var _data$resultByWorkerI;
437
440
  const chunk = chunkFromParams(params);
@@ -480,8 +483,18 @@ class Dispatcher {
480
483
  (_this$_reporter$onErr2 = (_this$_reporter11 = this._reporter).onError) === null || _this$_reporter$onErr2 === void 0 ? void 0 : _this$_reporter$onErr2.call(_this$_reporter11, error);
481
484
  }
482
485
  });
486
+ worker.on('exit', () => {
487
+ const producedEnv = this._producedEnvByProjectId.get(testGroup.projectId) || {};
488
+ this._producedEnvByProjectId.set(testGroup.projectId, {
489
+ ...producedEnv,
490
+ ...worker.producedEnv()
491
+ });
492
+ });
483
493
  return worker;
484
494
  }
495
+ producedEnvByProjectId() {
496
+ return this._producedEnvByProjectId;
497
+ }
485
498
  async stop() {
486
499
  if (this._isStopped) return;
487
500
  this._isStopped = true;
@@ -3,10 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.loadAllTests = loadAllTests;
6
+ exports.collectProjectsAndTestFiles = collectProjectsAndTestFiles;
7
+ exports.createRootSuite = createRootSuite;
8
+ exports.loadFileSuites = loadFileSuites;
7
9
  exports.loadGlobalHook = loadGlobalHook;
8
10
  exports.loadReporter = loadReporter;
11
+ var _fs = _interopRequireDefault(require("fs"));
9
12
  var _path = _interopRequireDefault(require("path"));
13
+ var _readline = _interopRequireDefault(require("readline"));
10
14
  var _loaderHost = require("./loaderHost");
11
15
  var _test = require("../common/test");
12
16
  var _util = require("../util");
@@ -14,6 +18,7 @@ var _projectUtils = require("./projectUtils");
14
18
  var _transform = require("../common/transform");
15
19
  var _suiteUtils = require("../common/suiteUtils");
16
20
  var _testGroups = require("./testGroups");
21
+ var _compilationCache = require("../common/compilationCache");
17
22
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18
23
  /**
19
24
  * Copyright Microsoft Corporation. All rights reserved.
@@ -31,82 +36,106 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
31
36
  * limitations under the License.
32
37
  */
33
38
 
34
- async function loadAllTests(mode, config, projectsToIgnore, fileMatcher, errors, shouldFilterOnly) {
35
- const projects = (0, _projectUtils.filterProjects)(config.projects, config._internal.cliProjectFilter);
36
- let filesToRunByProject = new Map();
37
- let topLevelProjects;
38
- let dependencyProjects;
39
- // Collect files, categorize top level and dependency projects.
40
- {
41
- const fsCache = new Map();
42
-
43
- // First collect all files for the projects in the command line, don't apply any file filters.
44
- const allFilesForProject = new Map();
45
- for (const project of projects) {
46
- if (projectsToIgnore.has(project)) continue;
47
- const files = await (0, _projectUtils.collectFilesForProject)(project, fsCache);
48
- allFilesForProject.set(project, files);
49
- }
39
+ async function collectProjectsAndTestFiles(config, projectsToIgnore, additionalFileMatcher) {
40
+ const fsCache = new Map();
41
+ const sourceMapCache = new Map();
42
+ const cliFileMatcher = config._internal.cliArgs.length ? (0, _util.createFileMatcherFromArguments)(config._internal.cliArgs) : null;
50
43
 
51
- // Filter files based on the file filters, eliminate the empty projects.
52
- for (const [project, files] of allFilesForProject) {
53
- const filteredFiles = files.filter(fileMatcher);
54
- if (filteredFiles.length) filesToRunByProject.set(project, filteredFiles);
55
- }
56
- const projectClosure = (0, _projectUtils.buildProjectsClosure)([...filesToRunByProject.keys()]);
57
- // Remove files for dependency projects, they'll be added back later.
58
- for (const project of projectClosure.filter(p => p._internal.type === 'dependency')) filesToRunByProject.delete(project);
59
-
60
- // Shard only the top-level projects.
61
- if (config.shard) filesToRunByProject = (0, _testGroups.filterForShard)(config.shard, filesToRunByProject);
62
-
63
- // Re-build the closure, project set might have changed.
64
- const filteredProjectClosure = (0, _projectUtils.buildProjectsClosure)([...filesToRunByProject.keys()]);
65
- topLevelProjects = filteredProjectClosure.filter(p => p._internal.type === 'top-level');
66
- dependencyProjects = filteredProjectClosure.filter(p => p._internal.type === 'dependency');
67
- topLevelProjects = topLevelProjects.filter(p => !projectsToIgnore.has(p));
68
- dependencyProjects = dependencyProjects.filter(p => !projectsToIgnore.has(p));
69
-
70
- // (Re-)add all files for dependent projects, disregard filters.
71
- for (const project of dependencyProjects) {
44
+ // First collect all files for the projects in the command line, don't apply any file filters.
45
+ const allFilesForProject = new Map();
46
+ for (const project of (0, _projectUtils.filterProjects)(config.projects, config._internal.cliProjectFilter)) {
47
+ if (projectsToIgnore.has(project)) continue;
48
+ const files = await (0, _projectUtils.collectFilesForProject)(project, fsCache);
49
+ allFilesForProject.set(project, files);
50
+ }
51
+
52
+ // Filter files based on the file filters, eliminate the empty projects.
53
+ const filesToRunByProject = new Map();
54
+ for (const [project, files] of allFilesForProject) {
55
+ const matchedFiles = await Promise.all(files.map(async file => {
56
+ if (additionalFileMatcher && !additionalFileMatcher(file)) return;
57
+ if (cliFileMatcher) {
58
+ if (!cliFileMatcher(file) && !(await isPotentiallyJavaScriptFileWithSourceMap(file, sourceMapCache))) return;
59
+ }
60
+ return file;
61
+ }));
62
+ const filteredFiles = matchedFiles.filter(Boolean);
63
+ if (filteredFiles.length) filesToRunByProject.set(project, filteredFiles);
64
+ }
65
+
66
+ // (Re-)add all files for dependent projects, disregard filters.
67
+ const projectClosure = (0, _projectUtils.buildProjectsClosure)([...filesToRunByProject.keys()]).filter(p => !projectsToIgnore.has(p));
68
+ for (const project of projectClosure) {
69
+ if (project._internal.type === 'dependency') {
70
+ filesToRunByProject.delete(project);
72
71
  const files = allFilesForProject.get(project) || (await (0, _projectUtils.collectFilesForProject)(project, fsCache));
73
72
  filesToRunByProject.set(project, files);
74
73
  }
75
74
  }
75
+ return filesToRunByProject;
76
+ }
77
+ async function loadFileSuites(mode, config, filesToRunByProject, errors) {
78
+ // Determine all files to load.
79
+ const allTestFiles = new Set();
80
+ for (const files of filesToRunByProject.values()) files.forEach(file => allTestFiles.add(file));
76
81
 
77
- // Load all test files and create a preprocessed root. Child suites are files there.
78
- const fileSuits = [];
79
- {
80
- const loaderHost = mode === 'out-of-process' ? new _loaderHost.OutOfProcessLoaderHost(config) : new _loaderHost.InProcessLoaderHost(config);
81
- const allTestFiles = new Set();
82
- for (const files of filesToRunByProject.values()) files.forEach(file => allTestFiles.add(file));
83
- for (const file of allTestFiles) {
84
- const fileSuite = await loaderHost.loadTestFile(file, errors);
85
- fileSuits.push(fileSuite);
86
- }
87
- await loaderHost.stop();
82
+ // Load test files.
83
+ const fileSuiteByFile = new Map();
84
+ const loaderHost = mode === 'out-of-process' ? new _loaderHost.OutOfProcessLoaderHost(config) : new _loaderHost.InProcessLoaderHost(config);
85
+ for (const file of allTestFiles) {
86
+ const fileSuite = await loaderHost.loadTestFile(file, errors);
87
+ fileSuiteByFile.set(file, fileSuite);
88
+ errors.push(...createDuplicateTitlesErrors(config, fileSuite));
88
89
  }
90
+ await loaderHost.stop();
89
91
 
90
- // Complain about duplicate titles.
91
- errors.push(...createDuplicateTitlesErrors(config, fileSuits));
92
+ // Check that no test file imports another test file.
93
+ // Loader must be stopped first, since it popuplates the dependency tree.
94
+ for (const file of allTestFiles) {
95
+ for (const dependency of (0, _compilationCache.dependenciesForTestFile)(file)) {
96
+ if (allTestFiles.has(dependency)) {
97
+ const importer = _path.default.relative(config.rootDir, file);
98
+ const importee = _path.default.relative(config.rootDir, dependency);
99
+ errors.push({
100
+ message: `Error: test file "${importer}" should not import test file "${importee}"`,
101
+ location: {
102
+ file,
103
+ line: 1,
104
+ column: 1
105
+ }
106
+ });
107
+ }
108
+ }
109
+ }
92
110
 
93
- // Create root suites with clones for the projects.
111
+ // Collect file suites for each project.
112
+ const fileSuitesByProject = new Map();
113
+ for (const [project, files] of filesToRunByProject) {
114
+ const suites = files.map(file => fileSuiteByFile.get(file)).filter(Boolean);
115
+ fileSuitesByProject.set(project, suites);
116
+ }
117
+ return fileSuitesByProject;
118
+ }
119
+ async function createRootSuite(config, fileSuitesByProject, errors, shouldFilterOnly) {
120
+ // Create root suite, where each child will be a project suite with cloned file suites inside it.
94
121
  const rootSuite = new _test.Suite('', 'root');
95
122
 
96
- // Interpret cli parameters.
97
- const cliFileFilters = (0, _util.createFileFiltersFromArguments)(config._internal.cliArgs);
98
- const grepMatcher = config._internal.cliGrep ? (0, _util.createTitleMatcher)((0, _util.forceRegExp)(config._internal.cliGrep)) : () => true;
99
- const grepInvertMatcher = config._internal.cliGrepInvert ? (0, _util.createTitleMatcher)((0, _util.forceRegExp)(config._internal.cliGrepInvert)) : () => false;
100
- const cliTitleMatcher = title => !grepInvertMatcher(title) && grepMatcher(title);
101
-
102
- // First iterate leaf projects to focus only, then add all other projects.
103
- for (const project of topLevelProjects) {
104
- const projectSuite = await createProjectSuite(fileSuits, project, {
105
- cliFileFilters,
106
- cliTitleMatcher,
107
- testIdMatcher: config._internal.testIdMatcher
108
- }, filesToRunByProject.get(project));
109
- if (projectSuite) rootSuite._addSuite(projectSuite);
123
+ // First add top-level projects, so that we can filterOnly and shard just top-level.
124
+ {
125
+ // Interpret cli parameters.
126
+ const cliFileFilters = (0, _util.createFileFiltersFromArguments)(config._internal.cliArgs);
127
+ const grepMatcher = config._internal.cliGrep ? (0, _util.createTitleMatcher)((0, _util.forceRegExp)(config._internal.cliGrep)) : () => true;
128
+ const grepInvertMatcher = config._internal.cliGrepInvert ? (0, _util.createTitleMatcher)((0, _util.forceRegExp)(config._internal.cliGrepInvert)) : () => false;
129
+ const cliTitleMatcher = title => !grepInvertMatcher(title) && grepMatcher(title);
130
+
131
+ // Clone file suites for top-level projects.
132
+ for (const [project, fileSuites] of fileSuitesByProject) {
133
+ if (project._internal.type === 'top-level') rootSuite._addSuite(await createProjectSuite(fileSuites, project, {
134
+ cliFileFilters,
135
+ cliTitleMatcher,
136
+ testIdMatcher: config._internal.testIdMatcher
137
+ }));
138
+ }
110
139
  }
111
140
 
112
141
  // Complain about only.
@@ -115,28 +144,47 @@ async function loadAllTests(mode, config, projectsToIgnore, fileMatcher, errors,
115
144
  if (onlyTestsAndSuites.length > 0) errors.push(...createForbidOnlyErrors(onlyTestsAndSuites));
116
145
  }
117
146
 
118
- // Filter only for leaf projects.
147
+ // Filter only for top-level projects.
119
148
  if (shouldFilterOnly) (0, _suiteUtils.filterOnly)(rootSuite);
120
149
 
121
- // Prepend the projects that are dependencies.
122
- for (const project of dependencyProjects) {
123
- const projectSuite = await createProjectSuite(fileSuits, project, {
124
- cliFileFilters: [],
125
- cliTitleMatcher: undefined
126
- }, filesToRunByProject.get(project));
127
- if (projectSuite) rootSuite._prependSuite(projectSuite);
150
+ // Shard only the top-level projects.
151
+ if (config.shard) {
152
+ // Create test groups for top-level projects.
153
+ const testGroups = [];
154
+ for (const projectSuite of rootSuite.suites) testGroups.push(...(0, _testGroups.createTestGroups)(projectSuite, config.workers));
155
+
156
+ // Shard test groups.
157
+ const testGroupsInThisShard = (0, _testGroups.filterForShard)(config.shard, testGroups);
158
+ const testsInThisShard = new Set();
159
+ for (const group of testGroupsInThisShard) {
160
+ for (const test of group.tests) testsInThisShard.add(test);
161
+ }
162
+
163
+ // Update project suites, removing empty ones.
164
+ (0, _suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, test => testsInThisShard.has(test));
165
+ }
166
+
167
+ // Now prepend dependency projects.
168
+ {
169
+ // Filtering only and sharding might have reduced the number of top-level projects.
170
+ // Build the project closure to only include dependencies that are still needed.
171
+ const projectClosure = new Set((0, _projectUtils.buildProjectsClosure)(rootSuite.suites.map(suite => suite.project())));
172
+
173
+ // Clone file suites for dependency projects.
174
+ for (const [project, fileSuites] of fileSuitesByProject) {
175
+ if (project._internal.type === 'dependency' && projectClosure.has(project)) rootSuite._prependSuite(await createProjectSuite(fileSuites, project, {
176
+ cliFileFilters: [],
177
+ cliTitleMatcher: undefined
178
+ }));
179
+ }
128
180
  }
129
181
  return rootSuite;
130
182
  }
131
- async function createProjectSuite(fileSuits, project, options, files) {
132
- const fileSuitesMap = new Map();
133
- for (const fileSuite of fileSuits) fileSuitesMap.set(fileSuite._requireFile, fileSuite);
183
+ async function createProjectSuite(fileSuites, project, options) {
134
184
  const projectSuite = new _test.Suite(project.name, 'project');
135
185
  projectSuite._projectConfig = project;
136
186
  if (project._internal.fullyParallel) projectSuite._parallelMode = 'parallel';
137
- for (const file of files) {
138
- const fileSuite = fileSuitesMap.get(file);
139
- if (!fileSuite) continue;
187
+ for (const fileSuite of fileSuites) {
140
188
  for (let repeatEachIndex = 0; repeatEachIndex < project.repeatEach; repeatEachIndex++) {
141
189
  const builtSuite = (0, _suiteUtils.buildFileSuiteForProject)(project, fileSuite, repeatEachIndex);
142
190
  projectSuite._addSuite(builtSuite);
@@ -151,8 +199,8 @@ async function createProjectSuite(fileSuits, project, options, files) {
151
199
  if (grepInvertMatcher !== null && grepInvertMatcher !== void 0 && grepInvertMatcher(grepTitle)) return false;
152
200
  return grepMatcher(grepTitle) && (!options.cliTitleMatcher || options.cliTitleMatcher(grepTitle));
153
201
  };
154
- if ((0, _suiteUtils.filterTestsRemoveEmptySuites)(projectSuite, titleMatcher)) return projectSuite;
155
- return null;
202
+ (0, _suiteUtils.filterTestsRemoveEmptySuites)(projectSuite, titleMatcher);
203
+ return projectSuite;
156
204
  }
157
205
  function createForbidOnlyErrors(onlyTestsAndSuites) {
158
206
  const errors = [];
@@ -167,22 +215,20 @@ function createForbidOnlyErrors(onlyTestsAndSuites) {
167
215
  }
168
216
  return errors;
169
217
  }
170
- function createDuplicateTitlesErrors(config, fileSuites) {
218
+ function createDuplicateTitlesErrors(config, fileSuite) {
171
219
  const errors = [];
172
- for (const fileSuite of fileSuites) {
173
- const testsByFullTitle = new Map();
174
- for (const test of fileSuite.allTests()) {
175
- const fullTitle = test.titlePath().slice(1).join(' › ');
176
- const existingTest = testsByFullTitle.get(fullTitle);
177
- if (existingTest) {
178
- const error = {
179
- message: `Error: duplicate test title "${fullTitle}", first declared in ${buildItemLocation(config.rootDir, existingTest)}`,
180
- location: test.location
181
- };
182
- errors.push(error);
183
- }
184
- testsByFullTitle.set(fullTitle, test);
220
+ const testsByFullTitle = new Map();
221
+ for (const test of fileSuite.allTests()) {
222
+ const fullTitle = test.titlePath().slice(1).join(' › ');
223
+ const existingTest = testsByFullTitle.get(fullTitle);
224
+ if (existingTest) {
225
+ const error = {
226
+ message: `Error: duplicate test title "${fullTitle}", first declared in ${buildItemLocation(config.rootDir, existingTest)}`,
227
+ location: test.location
228
+ };
229
+ errors.push(error);
185
230
  }
231
+ testsByFullTitle.set(fullTitle, test);
186
232
  }
187
233
  return errors;
188
234
  }
@@ -201,4 +247,30 @@ function loadGlobalHook(config, file) {
201
247
  }
202
248
  function loadReporter(config, file) {
203
249
  return requireOrImportDefaultFunction(_path.default.resolve(config.rootDir, file), true);
250
+ }
251
+ async function isPotentiallyJavaScriptFileWithSourceMap(file, cache) {
252
+ if (!file.endsWith('.js')) return false;
253
+ if (cache.has(file)) return cache.get(file);
254
+ try {
255
+ const stream = _fs.default.createReadStream(file);
256
+ const rl = _readline.default.createInterface({
257
+ input: stream,
258
+ crlfDelay: Infinity
259
+ });
260
+ let lastLine;
261
+ rl.on('line', line => {
262
+ lastLine = line;
263
+ });
264
+ await new Promise((fulfill, reject) => {
265
+ rl.on('close', fulfill);
266
+ rl.on('error', reject);
267
+ stream.on('error', reject);
268
+ });
269
+ const hasSourceMap = !!lastLine && lastLine.startsWith('//# sourceMappingURL=');
270
+ cache.set(file, hasSourceMap);
271
+ return hasSourceMap;
272
+ } catch (e) {
273
+ cache.set(file, true);
274
+ return true;
275
+ }
204
276
  }
@@ -45,8 +45,8 @@ class OutOfProcessLoaderHost {
45
45
  constructor(config) {
46
46
  this._startPromise = void 0;
47
47
  this._processHost = void 0;
48
- this._processHost = new _processHost.ProcessHost(require.resolve('../loader/loaderMain.js'), 'loader');
49
- this._startPromise = this._processHost.startRunner((0, _ipc.serializeConfig)(config), true, {});
48
+ this._processHost = new _processHost.ProcessHost(require.resolve('../loader/loaderMain.js'), 'loader', {});
49
+ this._startPromise = this._processHost.startRunner((0, _ipc.serializeConfig)(config), true);
50
50
  }
51
51
  async loadTestFile(file, testErrors) {
52
52
  await this._startPromise;
@@ -25,7 +25,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
25
25
  */
26
26
 
27
27
  class ProcessHost extends _events.EventEmitter {
28
- constructor(runnerScript, processName) {
28
+ constructor(runnerScript, processName, env) {
29
29
  super();
30
30
  this.process = void 0;
31
31
  this._didSendStop = false;
@@ -35,16 +35,19 @@ class ProcessHost extends _events.EventEmitter {
35
35
  this._lastMessageId = 0;
36
36
  this._callbacks = new Map();
37
37
  this._processName = void 0;
38
+ this._producedEnv = {};
39
+ this._extraEnv = void 0;
38
40
  this._runnerScript = runnerScript;
39
41
  this._processName = processName;
42
+ this._extraEnv = env;
40
43
  }
41
- async startRunner(runnerParams, inheritStdio, env) {
44
+ async startRunner(runnerParams, inheritStdio) {
42
45
  var _process$stdout$getCo, _process$stdout, _process$stderr$getCo, _process$stderr;
43
46
  this.process = _child_process.default.fork(require.resolve('../common/process'), {
44
47
  detached: false,
45
48
  env: {
46
49
  ...process.env,
47
- ...env
50
+ ...this._extraEnv
48
51
  },
49
52
  stdio: inheritStdio ? ['ignore', 'inherit', 'inherit', 'ipc'] : ['ignore', 'ignore', process.env.PW_RUNNER_DEBUG ? 'inherit' : 'ignore', 'ipc']
50
53
  });
@@ -59,7 +62,13 @@ class ProcessHost extends _events.EventEmitter {
59
62
  this.process.on('error', e => {}); // do not yell at a send to dead process.
60
63
  this.process.on('message', message => {
61
64
  if (_utilsBundle.debug.enabled('pw:test:protocol')) (0, _utilsBundle.debug)('pw:test:protocol')('◀ RECV ' + JSON.stringify(message));
62
- if (message.method === '__dispatch__') {
65
+ if (message.method === '__env_produced__') {
66
+ const producedEnv = message.params;
67
+ this._producedEnv = Object.fromEntries(producedEnv.map(e => {
68
+ var _e$;
69
+ return [e[0], (_e$ = e[1]) !== null && _e$ !== void 0 ? _e$ : undefined];
70
+ }));
71
+ } else if (message.method === '__dispatch__') {
63
72
  const {
64
73
  id,
65
74
  error,
@@ -149,6 +158,9 @@ class ProcessHost extends _events.EventEmitter {
149
158
  didSendStop() {
150
159
  return this._didSendStop;
151
160
  }
161
+ producedEnv() {
162
+ return this._producedEnv;
163
+ }
152
164
  send(message) {
153
165
  if (_utilsBundle.debug.enabled('pw:test:protocol')) (0, _utilsBundle.debug)('pw:test:protocol')('SEND ► ' + JSON.stringify(message));
154
166
  this.process.send(message);