config.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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 config_exports = {};
  30. __export(config_exports, {
  31. FullConfigInternal: () => FullConfigInternal,
  32. FullProjectInternal: () => FullProjectInternal,
  33. builtInReporters: () => builtInReporters,
  34. defaultGrep: () => defaultGrep,
  35. defaultReporter: () => defaultReporter,
  36. defaultTimeout: () => defaultTimeout,
  37. getProjectId: () => getProjectId,
  38. takeFirst: () => takeFirst,
  39. toReporters: () => toReporters
  40. });
  41. module.exports = __toCommonJS(config_exports);
  42. var import_fs = __toESM(require("fs"));
  43. var import_os = __toESM(require("os"));
  44. var import_path = __toESM(require("path"));
  45. var import_util = require("../util");
  46. const defaultTimeout = 3e4;
  47. class FullConfigInternal {
  48. constructor(location, userConfig, configCLIOverrides, metadata) {
  49. this.projects = [];
  50. this.cliArgs = [];
  51. this.cliListOnly = false;
  52. this.preOnlyTestFilters = [];
  53. this.postShardTestFilters = [];
  54. this.defineConfigWasUsed = false;
  55. this.globalSetups = [];
  56. this.globalTeardowns = [];
  57. if (configCLIOverrides.projects && userConfig.projects)
  58. throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`);
  59. const { resolvedConfigFile, configDir } = location;
  60. const packageJsonPath = (0, import_util.getPackageJsonPath)(configDir);
  61. const packageJsonDir = packageJsonPath ? import_path.default.dirname(packageJsonPath) : process.cwd();
  62. this.configDir = configDir;
  63. this.configCLIOverrides = configCLIOverrides;
  64. const privateConfiguration = userConfig["@playwright/test"];
  65. this.plugins = (privateConfiguration?.plugins || []).map((p) => ({ factory: p }));
  66. this.singleTSConfigPath = pathResolve(configDir, userConfig.tsconfig);
  67. this.captureGitInfo = userConfig.captureGitInfo;
  68. this.failOnFlakyTests = takeFirst(configCLIOverrides.failOnFlakyTests, userConfig.failOnFlakyTests, false);
  69. this.globalSetups = (Array.isArray(userConfig.globalSetup) ? userConfig.globalSetup : [userConfig.globalSetup]).map((s) => resolveScript(s, configDir)).filter((script) => script !== void 0);
  70. this.globalTeardowns = (Array.isArray(userConfig.globalTeardown) ? userConfig.globalTeardown : [userConfig.globalTeardown]).map((s) => resolveScript(s, configDir)).filter((script) => script !== void 0);
  71. userConfig.metadata = userConfig.metadata || {};
  72. const globalTags = Array.isArray(userConfig.tag) ? userConfig.tag : userConfig.tag ? [userConfig.tag] : [];
  73. for (const tag of globalTags) {
  74. if (tag[0] !== "@")
  75. throw new Error(`Tag must start with "@" symbol, got "${tag}" instead.`);
  76. }
  77. this.config = {
  78. configFile: resolvedConfigFile,
  79. rootDir: pathResolve(configDir, userConfig.testDir) || configDir,
  80. forbidOnly: takeFirst(configCLIOverrides.forbidOnly, userConfig.forbidOnly, false),
  81. fullyParallel: takeFirst(configCLIOverrides.fullyParallel, userConfig.fullyParallel, false),
  82. globalSetup: this.globalSetups[0] ?? null,
  83. globalTeardown: this.globalTeardowns[0] ?? null,
  84. globalTimeout: takeFirst(configCLIOverrides.debug ? 0 : void 0, configCLIOverrides.globalTimeout, userConfig.globalTimeout, 0),
  85. grep: takeFirst(userConfig.grep, defaultGrep),
  86. grepInvert: takeFirst(userConfig.grepInvert, null),
  87. maxFailures: takeFirst(configCLIOverrides.debug ? 1 : void 0, configCLIOverrides.maxFailures, userConfig.maxFailures, 0),
  88. metadata: metadata ?? userConfig.metadata,
  89. preserveOutput: takeFirst(userConfig.preserveOutput, "always"),
  90. reporter: takeFirst(configCLIOverrides.reporter, resolveReporters(userConfig.reporter, configDir), [[defaultReporter]]),
  91. reportSlowTests: takeFirst(userConfig.reportSlowTests, {
  92. max: 5,
  93. threshold: 3e5
  94. /* 5 minutes */
  95. }),
  96. quiet: takeFirst(configCLIOverrides.quiet, userConfig.quiet, false),
  97. projects: [],
  98. shard: takeFirst(configCLIOverrides.shard, userConfig.shard, null),
  99. tags: globalTags,
  100. updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, userConfig.updateSnapshots, "missing"),
  101. updateSourceMethod: takeFirst(configCLIOverrides.updateSourceMethod, userConfig.updateSourceMethod, "patch"),
  102. version: require("../../package.json").version,
  103. workers: resolveWorkers(takeFirst(configCLIOverrides.debug ? 1 : void 0, configCLIOverrides.workers, userConfig.workers, "50%")),
  104. webServer: null
  105. };
  106. for (const key in userConfig) {
  107. if (key.startsWith("@"))
  108. this.config[key] = userConfig[key];
  109. }
  110. this.config[configInternalSymbol] = this;
  111. const webServers = takeFirst(userConfig.webServer, null);
  112. if (Array.isArray(webServers)) {
  113. this.config.webServer = null;
  114. this.webServers = webServers;
  115. } else if (webServers) {
  116. this.config.webServer = webServers;
  117. this.webServers = [webServers];
  118. } else {
  119. this.webServers = [];
  120. }
  121. const projectConfigs = configCLIOverrides.projects || userConfig.projects || [{ ...userConfig, workers: void 0 }];
  122. this.projects = projectConfigs.map((p) => new FullProjectInternal(configDir, userConfig, this, p, this.configCLIOverrides, packageJsonDir));
  123. resolveProjectDependencies(this.projects);
  124. this._assignUniqueProjectIds(this.projects);
  125. this.config.projects = this.projects.map((p) => p.project);
  126. }
  127. _assignUniqueProjectIds(projects) {
  128. const usedNames = /* @__PURE__ */ new Set();
  129. for (const p of projects) {
  130. const name = p.project.name || "";
  131. for (let i = 0; i < projects.length; ++i) {
  132. const candidate = name + (i ? i : "");
  133. if (usedNames.has(candidate))
  134. continue;
  135. p.id = candidate;
  136. p.project.__projectId = p.id;
  137. usedNames.add(candidate);
  138. break;
  139. }
  140. }
  141. }
  142. }
  143. class FullProjectInternal {
  144. constructor(configDir, config, fullConfig, projectConfig, configCLIOverrides, packageJsonDir) {
  145. this.id = "";
  146. this.deps = [];
  147. this.fullConfig = fullConfig;
  148. const testDir = takeFirst(pathResolve(configDir, projectConfig.testDir), pathResolve(configDir, config.testDir), fullConfig.configDir);
  149. this.snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate);
  150. this.project = {
  151. grep: takeFirst(projectConfig.grep, config.grep, defaultGrep),
  152. grepInvert: takeFirst(projectConfig.grepInvert, config.grepInvert, null),
  153. outputDir: takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, projectConfig.outputDir), pathResolve(configDir, config.outputDir), import_path.default.join(packageJsonDir, "test-results")),
  154. // Note: we either apply the cli override for repeatEach or not, depending on whether the
  155. // project is top-level vs dependency. See collectProjectsAndTestFiles in loadUtils.
  156. repeatEach: takeFirst(projectConfig.repeatEach, config.repeatEach, 1),
  157. retries: takeFirst(configCLIOverrides.retries, projectConfig.retries, config.retries, 0),
  158. metadata: takeFirst(projectConfig.metadata, config.metadata, {}),
  159. name: takeFirst(projectConfig.name, config.name, ""),
  160. testDir,
  161. snapshotDir: takeFirst(pathResolve(configDir, projectConfig.snapshotDir), pathResolve(configDir, config.snapshotDir), testDir),
  162. testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []),
  163. testMatch: takeFirst(projectConfig.testMatch, config.testMatch, "**/*.@(spec|test).?(c|m)[jt]s?(x)"),
  164. timeout: takeFirst(configCLIOverrides.debug ? 0 : void 0, configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
  165. use: (0, import_util.mergeObjects)(config.use, projectConfig.use, configCLIOverrides.use),
  166. dependencies: projectConfig.dependencies || [],
  167. teardown: projectConfig.teardown
  168. };
  169. this.fullyParallel = takeFirst(configCLIOverrides.fullyParallel, projectConfig.fullyParallel, config.fullyParallel, void 0);
  170. this.expect = takeFirst(projectConfig.expect, config.expect, {});
  171. if (this.expect.toHaveScreenshot?.stylePath) {
  172. const stylePaths = Array.isArray(this.expect.toHaveScreenshot.stylePath) ? this.expect.toHaveScreenshot.stylePath : [this.expect.toHaveScreenshot.stylePath];
  173. this.expect.toHaveScreenshot.stylePath = stylePaths.map((stylePath) => import_path.default.resolve(configDir, stylePath));
  174. }
  175. this.respectGitIgnore = takeFirst(projectConfig.respectGitIgnore, config.respectGitIgnore, !projectConfig.testDir && !config.testDir);
  176. this.ignoreSnapshots = takeFirst(configCLIOverrides.ignoreSnapshots, projectConfig.ignoreSnapshots, config.ignoreSnapshots, false);
  177. this.workers = projectConfig.workers ? resolveWorkers(projectConfig.workers) : void 0;
  178. if (configCLIOverrides.debug && this.workers)
  179. this.workers = 1;
  180. }
  181. }
  182. function takeFirst(...args) {
  183. for (const arg of args) {
  184. if (arg !== void 0)
  185. return arg;
  186. }
  187. return void 0;
  188. }
  189. function pathResolve(baseDir, relative) {
  190. if (!relative)
  191. return void 0;
  192. return import_path.default.resolve(baseDir, relative);
  193. }
  194. function resolveReporters(reporters, rootDir) {
  195. return toReporters(reporters)?.map(([id, arg]) => {
  196. if (builtInReporters.includes(id))
  197. return [id, arg];
  198. return [require.resolve(id, { paths: [rootDir] }), arg];
  199. });
  200. }
  201. function resolveWorkers(workers) {
  202. if (typeof workers === "string") {
  203. if (workers.endsWith("%")) {
  204. const cpus = import_os.default.cpus().length;
  205. return Math.max(1, Math.floor(cpus * (parseInt(workers, 10) / 100)));
  206. }
  207. const parsedWorkers = parseInt(workers, 10);
  208. if (isNaN(parsedWorkers))
  209. throw new Error(`Workers ${workers} must be a number or percentage.`);
  210. return parsedWorkers;
  211. }
  212. return workers;
  213. }
  214. function resolveProjectDependencies(projects) {
  215. const teardownSet = /* @__PURE__ */ new Set();
  216. for (const project of projects) {
  217. for (const dependencyName of project.project.dependencies) {
  218. const dependencies = projects.filter((p) => p.project.name === dependencyName);
  219. if (!dependencies.length)
  220. throw new Error(`Project '${project.project.name}' depends on unknown project '${dependencyName}'`);
  221. if (dependencies.length > 1)
  222. throw new Error(`Project dependencies should have unique names, reading ${dependencyName}`);
  223. project.deps.push(...dependencies);
  224. }
  225. if (project.project.teardown) {
  226. const teardowns = projects.filter((p) => p.project.name === project.project.teardown);
  227. if (!teardowns.length)
  228. throw new Error(`Project '${project.project.name}' has unknown teardown project '${project.project.teardown}'`);
  229. if (teardowns.length > 1)
  230. throw new Error(`Project teardowns should have unique names, reading ${project.project.teardown}`);
  231. const teardown = teardowns[0];
  232. project.teardown = teardown;
  233. teardownSet.add(teardown);
  234. }
  235. }
  236. for (const teardown of teardownSet) {
  237. if (teardown.deps.length)
  238. throw new Error(`Teardown project ${teardown.project.name} must not have dependencies`);
  239. }
  240. for (const project of projects) {
  241. for (const dep of project.deps) {
  242. if (teardownSet.has(dep))
  243. throw new Error(`Project ${project.project.name} must not depend on a teardown project ${dep.project.name}`);
  244. }
  245. }
  246. }
  247. function toReporters(reporters) {
  248. if (!reporters)
  249. return;
  250. if (typeof reporters === "string")
  251. return [[reporters]];
  252. return reporters;
  253. }
  254. const builtInReporters = ["list", "line", "dot", "json", "junit", "null", "github", "html", "blob"];
  255. function resolveScript(id, rootDir) {
  256. if (!id)
  257. return void 0;
  258. const localPath = import_path.default.resolve(rootDir, id);
  259. if (import_fs.default.existsSync(localPath))
  260. return localPath;
  261. return require.resolve(id, { paths: [rootDir] });
  262. }
  263. const defaultGrep = /.*/;
  264. const defaultReporter = process.env.CI ? "dot" : "list";
  265. const configInternalSymbol = Symbol("configInternalSymbol");
  266. function getProjectId(project) {
  267. return project.__projectId;
  268. }
  269. // Annotate the CommonJS export names for ESM import in node:
  270. 0 && (module.exports = {
  271. FullConfigInternal,
  272. FullProjectInternal,
  273. builtInReporters,
  274. defaultGrep,
  275. defaultReporter,
  276. defaultTimeout,
  277. getProjectId,
  278. takeFirst,
  279. toReporters
  280. });