|
1
|
"use strict"; |
|
2
|
var __create = Object.create; |
|
3
|
var __defProp = Object.defineProperty; |
|
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; |
|
5
|
var __getOwnPropNames = Object.getOwnPropertyNames; |
|
6
|
var __getProtoOf = Object.getPrototypeOf; |
|
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty; |
|
8
|
var __export = (target, all) => { |
|
9
|
for (var name in all) |
|
10
|
__defProp(target, name, { get: all[name], enumerable: true }); |
|
11
|
}; |
|
12
|
var __copyProps = (to, from, except, desc) => { |
|
13
|
if (from && typeof from === "object" || typeof from === "function") { |
|
14
|
for (let key of __getOwnPropNames(from)) |
|
15
|
if (!__hasOwnProp.call(to, key) && key !== except) |
|
16
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); |
|
17
|
} |
|
18
|
return to; |
|
19
|
}; |
|
20
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( |
|
21
|
// If the importer is in node compatibility mode or this is not an ESM |
|
22
|
// file that has been converted to a CommonJS file using a Babel- |
|
23
|
// compatible transform (i.e. "__esModule" has not been set), then set |
|
24
|
// "default" to the CommonJS "module.exports" for node compatibility. |
|
25
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, |
|
26
|
mod |
|
27
|
)); |
|
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); |
|
29
|
var loadUtils_exports = {}; |
|
30
|
__export(loadUtils_exports, { |
|
31
|
collectProjectsAndTestFiles: () => collectProjectsAndTestFiles, |
|
32
|
createRootSuite: () => createRootSuite, |
|
33
|
loadFileSuites: () => loadFileSuites, |
|
34
|
loadGlobalHook: () => loadGlobalHook, |
|
35
|
loadReporter: () => loadReporter, |
|
36
|
loadTestList: () => loadTestList |
|
37
|
}); |
|
38
|
module.exports = __toCommonJS(loadUtils_exports); |
|
39
|
var import_path = __toESM(require("path")); |
|
40
|
var import_fs = __toESM(require("fs")); |
|
41
|
var import_utils = require("playwright-core/lib/utils"); |
|
42
|
var import_loaderHost = require("./loaderHost"); |
|
43
|
var import_util = require("../util"); |
|
44
|
var import_projectUtils = require("./projectUtils"); |
|
45
|
var import_testGroups = require("./testGroups"); |
|
46
|
var import_suiteUtils = require("../common/suiteUtils"); |
|
47
|
var import_test = require("../common/test"); |
|
48
|
var import_compilationCache = require("../transform/compilationCache"); |
|
49
|
var import_transform = require("../transform/transform"); |
|
50
|
var import_utilsBundle = require("../utilsBundle"); |
|
51
|
async function collectProjectsAndTestFiles(testRun, doNotRunTestsOutsideProjectFilter) { |
|
52
|
const config = testRun.config; |
|
53
|
const fsCache = /* @__PURE__ */ new Map(); |
|
54
|
const sourceMapCache = /* @__PURE__ */ new Map(); |
|
55
|
const cliFileMatcher = config.cliArgs.length ? (0, import_util.createFileMatcherFromArguments)(config.cliArgs) : null; |
|
56
|
const allFilesForProject = /* @__PURE__ */ new Map(); |
|
57
|
const filteredProjects = (0, import_projectUtils.filterProjects)(config.projects, config.cliProjectFilter); |
|
58
|
for (const project of filteredProjects) { |
|
59
|
const files = await (0, import_projectUtils.collectFilesForProject)(project, fsCache); |
|
60
|
allFilesForProject.set(project, files); |
|
61
|
} |
|
62
|
const filesToRunByProject = /* @__PURE__ */ new Map(); |
|
63
|
for (const [project, files] of allFilesForProject) { |
|
64
|
const matchedFiles = files.filter((file) => { |
|
65
|
const hasMatchingSources = sourceMapSources(file, sourceMapCache).some((source) => { |
|
66
|
if (cliFileMatcher && !cliFileMatcher(source)) |
|
67
|
return false; |
|
68
|
return true; |
|
69
|
}); |
|
70
|
return hasMatchingSources; |
|
71
|
}); |
|
72
|
const filteredFiles = matchedFiles.filter(Boolean); |
|
73
|
filesToRunByProject.set(project, filteredFiles); |
|
74
|
} |
|
75
|
const projectClosure = (0, import_projectUtils.buildProjectsClosure)([...filesToRunByProject.keys()]); |
|
76
|
for (const [project, type] of projectClosure) { |
|
77
|
if (type === "dependency") { |
|
78
|
const treatProjectAsEmpty = doNotRunTestsOutsideProjectFilter && !filteredProjects.includes(project); |
|
79
|
const files = treatProjectAsEmpty ? [] : allFilesForProject.get(project) || await (0, import_projectUtils.collectFilesForProject)(project, fsCache); |
|
80
|
filesToRunByProject.set(project, files); |
|
81
|
} |
|
82
|
} |
|
83
|
testRun.projectFiles = filesToRunByProject; |
|
84
|
testRun.projectSuites = /* @__PURE__ */ new Map(); |
|
85
|
} |
|
86
|
async function loadFileSuites(testRun, mode, errors) { |
|
87
|
const config = testRun.config; |
|
88
|
const allTestFiles = /* @__PURE__ */ new Set(); |
|
89
|
for (const files of testRun.projectFiles.values()) |
|
90
|
files.forEach((file) => allTestFiles.add(file)); |
|
91
|
const fileSuiteByFile = /* @__PURE__ */ new Map(); |
|
92
|
const loaderHost = mode === "out-of-process" ? new import_loaderHost.OutOfProcessLoaderHost(config) : new import_loaderHost.InProcessLoaderHost(config); |
|
93
|
if (await loaderHost.start(errors)) { |
|
94
|
for (const file of allTestFiles) { |
|
95
|
const fileSuite = await loaderHost.loadTestFile(file, errors); |
|
96
|
fileSuiteByFile.set(file, fileSuite); |
|
97
|
errors.push(...createDuplicateTitlesErrors(config, fileSuite)); |
|
98
|
} |
|
99
|
await loaderHost.stop(); |
|
100
|
} |
|
101
|
for (const file of allTestFiles) { |
|
102
|
for (const dependency of (0, import_compilationCache.dependenciesForTestFile)(file)) { |
|
103
|
if (allTestFiles.has(dependency)) { |
|
104
|
const importer = import_path.default.relative(config.config.rootDir, file); |
|
105
|
const importee = import_path.default.relative(config.config.rootDir, dependency); |
|
106
|
errors.push({ |
|
107
|
message: `Error: test file "${importer}" should not import test file "${importee}"`, |
|
108
|
location: { file, line: 1, column: 1 } |
|
109
|
}); |
|
110
|
} |
|
111
|
} |
|
112
|
} |
|
113
|
for (const [project, files] of testRun.projectFiles) { |
|
114
|
const suites = files.map((file) => fileSuiteByFile.get(file)).filter(Boolean); |
|
115
|
testRun.projectSuites.set(project, suites); |
|
116
|
} |
|
117
|
} |
|
118
|
async function createRootSuite(testRun, errors, shouldFilterOnly) { |
|
119
|
const config = testRun.config; |
|
120
|
const rootSuite = new import_test.Suite("", "root"); |
|
121
|
const projectSuites = /* @__PURE__ */ new Map(); |
|
122
|
const filteredProjectSuites = /* @__PURE__ */ new Map(); |
|
123
|
{ |
|
124
|
const cliFileFilters = (0, import_util.createFileFiltersFromArguments)(config.cliArgs); |
|
125
|
const grepMatcher = config.cliGrep ? (0, import_util.createTitleMatcher)((0, import_util.forceRegExp)(config.cliGrep)) : () => true; |
|
126
|
const grepInvertMatcher = config.cliGrepInvert ? (0, import_util.createTitleMatcher)((0, import_util.forceRegExp)(config.cliGrepInvert)) : () => false; |
|
127
|
const cliTitleMatcher = (title) => !grepInvertMatcher(title) && grepMatcher(title); |
|
128
|
for (const [project, fileSuites] of testRun.projectSuites) { |
|
129
|
const projectSuite = createProjectSuite(project, fileSuites); |
|
130
|
projectSuites.set(project, projectSuite); |
|
131
|
const filteredProjectSuite = filterProjectSuite(projectSuite, { cliFileFilters, cliTitleMatcher, testFilters: config.preOnlyTestFilters }); |
|
132
|
filteredProjectSuites.set(project, filteredProjectSuite); |
|
133
|
} |
|
134
|
} |
|
135
|
if (shouldFilterOnly) { |
|
136
|
const filteredRoot = new import_test.Suite("", "root"); |
|
137
|
for (const filteredProjectSuite of filteredProjectSuites.values()) |
|
138
|
filteredRoot._addSuite(filteredProjectSuite); |
|
139
|
(0, import_suiteUtils.filterOnly)(filteredRoot); |
|
140
|
for (const [project, filteredProjectSuite] of filteredProjectSuites) { |
|
141
|
if (!filteredRoot.suites.includes(filteredProjectSuite)) |
|
142
|
filteredProjectSuites.delete(project); |
|
143
|
} |
|
144
|
} |
|
145
|
const projectClosure = (0, import_projectUtils.buildProjectsClosure)([...filteredProjectSuites.keys()], (project) => filteredProjectSuites.get(project)._hasTests()); |
|
146
|
for (const [project, type] of projectClosure) { |
|
147
|
if (type === "top-level") { |
|
148
|
project.project.repeatEach = project.fullConfig.configCLIOverrides.repeatEach ?? project.project.repeatEach; |
|
149
|
rootSuite._addSuite(buildProjectSuite(project, filteredProjectSuites.get(project))); |
|
150
|
} |
|
151
|
} |
|
152
|
if (config.config.forbidOnly) { |
|
153
|
const onlyTestsAndSuites = rootSuite._getOnlyItems(); |
|
154
|
if (onlyTestsAndSuites.length > 0) { |
|
155
|
const configFilePath = config.config.configFile ? import_path.default.relative(config.config.rootDir, config.config.configFile) : void 0; |
|
156
|
errors.push(...createForbidOnlyErrors(onlyTestsAndSuites, config.configCLIOverrides.forbidOnly, configFilePath)); |
|
157
|
} |
|
158
|
} |
|
159
|
if (config.config.shard) { |
|
160
|
const testGroups = []; |
|
161
|
for (const projectSuite of rootSuite.suites) { |
|
162
|
for (const group of (0, import_testGroups.createTestGroups)(projectSuite, config.config.shard.total)) |
|
163
|
testGroups.push(group); |
|
164
|
} |
|
165
|
const testGroupsInThisShard = (0, import_testGroups.filterForShard)(config.config.shard, config.configCLIOverrides.shardWeights, testGroups); |
|
166
|
const testsInThisShard = /* @__PURE__ */ new Set(); |
|
167
|
for (const group of testGroupsInThisShard) { |
|
168
|
for (const test of group.tests) |
|
169
|
testsInThisShard.add(test); |
|
170
|
} |
|
171
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => testsInThisShard.has(test)); |
|
172
|
} |
|
173
|
if (config.postShardTestFilters.length) |
|
174
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => config.postShardTestFilters.every((filter) => filter(test))); |
|
175
|
const topLevelProjects = []; |
|
176
|
{ |
|
177
|
const projectClosure2 = new Map((0, import_projectUtils.buildProjectsClosure)(rootSuite.suites.map((suite) => suite._fullProject))); |
|
178
|
for (const [project, level] of projectClosure2.entries()) { |
|
179
|
if (level === "dependency") |
|
180
|
rootSuite._prependSuite(buildProjectSuite(project, projectSuites.get(project))); |
|
181
|
else |
|
182
|
topLevelProjects.push(project); |
|
183
|
} |
|
184
|
} |
|
185
|
return { rootSuite, topLevelProjects }; |
|
186
|
} |
|
187
|
function createProjectSuite(project, fileSuites) { |
|
188
|
const projectSuite = new import_test.Suite(project.project.name, "project"); |
|
189
|
for (const fileSuite of fileSuites) |
|
190
|
projectSuite._addSuite((0, import_suiteUtils.bindFileSuiteToProject)(project, fileSuite)); |
|
191
|
const grepMatcher = (0, import_util.createTitleMatcher)(project.project.grep); |
|
192
|
const grepInvertMatcher = project.project.grepInvert ? (0, import_util.createTitleMatcher)(project.project.grepInvert) : null; |
|
193
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(projectSuite, (test) => { |
|
194
|
const grepTitle = test._grepTitleWithTags(); |
|
195
|
if (grepInvertMatcher?.(grepTitle)) |
|
196
|
return false; |
|
197
|
return grepMatcher(grepTitle); |
|
198
|
}); |
|
199
|
return projectSuite; |
|
200
|
} |
|
201
|
function filterProjectSuite(projectSuite, options) { |
|
202
|
if (!options.cliFileFilters.length && !options.cliTitleMatcher && !options.testFilters.length) |
|
203
|
return projectSuite; |
|
204
|
const result = projectSuite._deepClone(); |
|
205
|
if (options.cliFileFilters.length) |
|
206
|
(0, import_suiteUtils.filterByFocusedLine)(result, options.cliFileFilters); |
|
207
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(result, (test) => { |
|
208
|
if (!options.testFilters.every((filter) => filter(test))) |
|
209
|
return false; |
|
210
|
if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags())) |
|
211
|
return false; |
|
212
|
return true; |
|
213
|
}); |
|
214
|
return result; |
|
215
|
} |
|
216
|
function buildProjectSuite(project, projectSuite) { |
|
217
|
const result = new import_test.Suite(project.project.name, "project"); |
|
218
|
result._fullProject = project; |
|
219
|
if (project.fullyParallel) |
|
220
|
result._parallelMode = "parallel"; |
|
221
|
for (const fileSuite of projectSuite.suites) { |
|
222
|
result._addSuite(fileSuite); |
|
223
|
for (let repeatEachIndex = 1; repeatEachIndex < project.project.repeatEach; repeatEachIndex++) { |
|
224
|
const clone = fileSuite._deepClone(); |
|
225
|
(0, import_suiteUtils.applyRepeatEachIndex)(project, clone, repeatEachIndex); |
|
226
|
result._addSuite(clone); |
|
227
|
} |
|
228
|
} |
|
229
|
return result; |
|
230
|
} |
|
231
|
function createForbidOnlyErrors(onlyTestsAndSuites, forbidOnlyCLIFlag, configFilePath) { |
|
232
|
const errors = []; |
|
233
|
for (const testOrSuite of onlyTestsAndSuites) { |
|
234
|
const title = testOrSuite.titlePath().slice(2).join(" "); |
|
235
|
const configFilePathName = configFilePath ? `'${configFilePath}'` : "the Playwright configuration file"; |
|
236
|
const forbidOnlySource = forbidOnlyCLIFlag ? `'--forbid-only' CLI flag` : `'forbidOnly' option in ${configFilePathName}`; |
|
237
|
const error = { |
|
238
|
message: `Error: item focused with '.only' is not allowed due to the ${forbidOnlySource}: "${title}"`, |
|
239
|
location: testOrSuite.location |
|
240
|
}; |
|
241
|
errors.push(error); |
|
242
|
} |
|
243
|
return errors; |
|
244
|
} |
|
245
|
function createDuplicateTitlesErrors(config, fileSuite) { |
|
246
|
const errors = []; |
|
247
|
const testsByFullTitle = /* @__PURE__ */ new Map(); |
|
248
|
for (const test of fileSuite.allTests()) { |
|
249
|
const fullTitle = test.titlePath().slice(1).join(" \u203A "); |
|
250
|
const existingTest = testsByFullTitle.get(fullTitle); |
|
251
|
if (existingTest) { |
|
252
|
const error = { |
|
253
|
message: `Error: duplicate test title "${fullTitle}", first declared in ${buildItemLocation(config.config.rootDir, existingTest)}`, |
|
254
|
location: test.location |
|
255
|
}; |
|
256
|
errors.push(error); |
|
257
|
} |
|
258
|
testsByFullTitle.set(fullTitle, test); |
|
259
|
} |
|
260
|
return errors; |
|
261
|
} |
|
262
|
function buildItemLocation(rootDir, testOrSuite) { |
|
263
|
if (!testOrSuite.location) |
|
264
|
return ""; |
|
265
|
return `${import_path.default.relative(rootDir, testOrSuite.location.file)}:${testOrSuite.location.line}`; |
|
266
|
} |
|
267
|
async function requireOrImportDefaultFunction(file, expectConstructor) { |
|
268
|
let func = await (0, import_transform.requireOrImport)(file); |
|
269
|
if (func && typeof func === "object" && "default" in func) |
|
270
|
func = func["default"]; |
|
271
|
if (typeof func !== "function") |
|
272
|
throw (0, import_util.errorWithFile)(file, `file must export a single ${expectConstructor ? "class" : "function"}.`); |
|
273
|
return func; |
|
274
|
} |
|
275
|
function loadGlobalHook(config, file) { |
|
276
|
return requireOrImportDefaultFunction(import_path.default.resolve(config.config.rootDir, file), false); |
|
277
|
} |
|
278
|
function loadReporter(config, file) { |
|
279
|
return requireOrImportDefaultFunction(config ? import_path.default.resolve(config.config.rootDir, file) : file, true); |
|
280
|
} |
|
281
|
function sourceMapSources(file, cache) { |
|
282
|
let sources = [file]; |
|
283
|
if (!file.endsWith(".js")) |
|
284
|
return sources; |
|
285
|
if (cache.has(file)) |
|
286
|
return cache.get(file); |
|
287
|
try { |
|
288
|
const sourceMap = import_utilsBundle.sourceMapSupport.retrieveSourceMap(file); |
|
289
|
const sourceMapData = typeof sourceMap?.map === "string" ? JSON.parse(sourceMap.map) : sourceMap?.map; |
|
290
|
if (sourceMapData?.sources) |
|
291
|
sources = sourceMapData.sources.map((source) => import_path.default.resolve(import_path.default.dirname(file), source)); |
|
292
|
} finally { |
|
293
|
cache.set(file, sources); |
|
294
|
return sources; |
|
295
|
} |
|
296
|
} |
|
297
|
async function loadTestList(config, filePath) { |
|
298
|
try { |
|
299
|
const content = await import_fs.default.promises.readFile(filePath, "utf-8"); |
|
300
|
const lines = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")); |
|
301
|
const descriptions = lines.map((line) => { |
|
302
|
const delimiter = line.includes("\u203A") ? "\u203A" : ">"; |
|
303
|
const tokens = line.split(delimiter).map((token) => token.trim()); |
|
304
|
let project; |
|
305
|
if (tokens[0].startsWith("[")) { |
|
306
|
if (!tokens[0].endsWith("]")) |
|
307
|
throw new Error(`Malformed test description: ${line}`); |
|
308
|
project = tokens[0].substring(1, tokens[0].length - 1); |
|
309
|
tokens.shift(); |
|
310
|
} |
|
311
|
return { project, file: (0, import_utils.toPosixPath)((0, import_util.parseLocationArg)(tokens[0]).file), titlePath: tokens.slice(1) }; |
|
312
|
}); |
|
313
|
return (test) => descriptions.some((d) => { |
|
314
|
const [projectName, , ...titles] = test.titlePath(); |
|
315
|
if (d.project !== void 0 && d.project !== projectName) |
|
316
|
return false; |
|
317
|
const relativeFile = (0, import_utils.toPosixPath)(import_path.default.relative(config.config.rootDir, test.location.file)); |
|
318
|
if (relativeFile !== d.file) |
|
319
|
return false; |
|
320
|
return d.titlePath.length <= titles.length && d.titlePath.every((_, index) => titles[index] === d.titlePath[index]); |
|
321
|
}); |
|
322
|
} catch (e) { |
|
323
|
throw (0, import_util.errorWithFile)(filePath, "Cannot read test list file: " + e.message); |
|
324
|
} |
|
325
|
} |
|
326
|
// Annotate the CommonJS export names for ESM import in node: |
|
327
|
0 && (module.exports = { |
|
328
|
collectProjectsAndTestFiles, |
|
329
|
createRootSuite, |
|
330
|
loadFileSuites, |
|
331
|
loadGlobalHook, |
|
332
|
loadReporter, |
|
333
|
loadTestList |
|
334
|
}); |
|
335
|
|