@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
|
@@ -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');
|
package/lib/runner/dispatcher.js
CHANGED
|
@@ -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;
|
package/lib/runner/loadUtils.js
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
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
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
//
|
|
91
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
147
|
+
// Filter only for top-level projects.
|
|
119
148
|
if (shouldFilterOnly) (0, _suiteUtils.filterOnly)(rootSuite);
|
|
120
149
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
155
|
-
return
|
|
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,
|
|
218
|
+
function createDuplicateTitlesErrors(config, fileSuite) {
|
|
171
219
|
const errors = [];
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
}
|
package/lib/runner/loaderHost.js
CHANGED
|
@@ -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
|
|
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
|
-
...
|
|
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 === '
|
|
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);
|