configLoader.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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 configLoader_exports = {};
  30. __export(configLoader_exports, {
  31. defineConfig: () => defineConfig,
  32. deserializeConfig: () => deserializeConfig,
  33. loadConfig: () => loadConfig,
  34. loadConfigFromFile: () => loadConfigFromFile,
  35. loadEmptyConfigForMergeReports: () => loadEmptyConfigForMergeReports,
  36. resolveConfigLocation: () => resolveConfigLocation
  37. });
  38. module.exports = __toCommonJS(configLoader_exports);
  39. var import_fs = __toESM(require("fs"));
  40. var import_path = __toESM(require("path"));
  41. var import_utils = require("playwright-core/lib/utils");
  42. var import_transform = require("../transform/transform");
  43. var import_util = require("../util");
  44. var import_config = require("./config");
  45. var import_esmLoaderHost = require("./esmLoaderHost");
  46. var import_compilationCache = require("../transform/compilationCache");
  47. const kDefineConfigWasUsed = Symbol("defineConfigWasUsed");
  48. const defineConfig = (...configs) => {
  49. let result = configs[0];
  50. for (let i = 1; i < configs.length; ++i) {
  51. const config = configs[i];
  52. const prevProjects = result.projects;
  53. result = {
  54. ...result,
  55. ...config,
  56. expect: {
  57. ...result.expect,
  58. ...config.expect
  59. },
  60. use: {
  61. ...result.use,
  62. ...config.use
  63. },
  64. build: {
  65. ...result.build,
  66. ...config.build
  67. },
  68. webServer: [
  69. ...Array.isArray(result.webServer) ? result.webServer : result.webServer ? [result.webServer] : [],
  70. ...Array.isArray(config.webServer) ? config.webServer : config.webServer ? [config.webServer] : []
  71. ]
  72. };
  73. if (!result.projects && !config.projects)
  74. continue;
  75. const projectOverrides = /* @__PURE__ */ new Map();
  76. for (const project of config.projects || [])
  77. projectOverrides.set(project.name, project);
  78. const projects = [];
  79. for (const project of prevProjects || []) {
  80. const projectOverride = projectOverrides.get(project.name);
  81. if (projectOverride) {
  82. projects.push({
  83. ...project,
  84. ...projectOverride,
  85. use: {
  86. ...project.use,
  87. ...projectOverride.use
  88. }
  89. });
  90. projectOverrides.delete(project.name);
  91. } else {
  92. projects.push(project);
  93. }
  94. }
  95. projects.push(...projectOverrides.values());
  96. result.projects = projects;
  97. }
  98. result[kDefineConfigWasUsed] = true;
  99. return result;
  100. };
  101. async function deserializeConfig(data) {
  102. if (data.compilationCache)
  103. (0, import_compilationCache.addToCompilationCache)(data.compilationCache);
  104. return await loadConfig(data.location, data.configCLIOverrides, void 0, data.metadata ? JSON.parse(data.metadata) : void 0);
  105. }
  106. async function loadUserConfig(location) {
  107. let object = location.resolvedConfigFile ? await (0, import_transform.requireOrImport)(location.resolvedConfigFile) : {};
  108. if (object && typeof object === "object" && "default" in object)
  109. object = object["default"];
  110. return object;
  111. }
  112. async function loadConfig(location, overrides, ignoreProjectDependencies = false, metadata) {
  113. if (!(0, import_esmLoaderHost.registerESMLoader)()) {
  114. if (location.resolvedConfigFile && (0, import_util.fileIsModule)(location.resolvedConfigFile))
  115. throw (0, import_util.errorWithFile)(location.resolvedConfigFile, `Playwright requires Node.js 18.19 or higher to load esm modules. Please update your version of Node.js.`);
  116. }
  117. (0, import_transform.setSingleTSConfig)(overrides?.tsconfig);
  118. await (0, import_esmLoaderHost.configureESMLoader)();
  119. const userConfig = await loadUserConfig(location);
  120. validateConfig(location.resolvedConfigFile || "<default config>", userConfig);
  121. const fullConfig = new import_config.FullConfigInternal(location, userConfig, overrides || {}, metadata);
  122. fullConfig.defineConfigWasUsed = !!userConfig[kDefineConfigWasUsed];
  123. if (ignoreProjectDependencies) {
  124. for (const project of fullConfig.projects) {
  125. project.deps = [];
  126. project.teardown = void 0;
  127. }
  128. }
  129. const babelPlugins = userConfig["@playwright/test"]?.babelPlugins || [];
  130. const external = userConfig.build?.external || [];
  131. (0, import_transform.setTransformConfig)({ babelPlugins, external });
  132. if (!overrides?.tsconfig)
  133. (0, import_transform.setSingleTSConfig)(fullConfig?.singleTSConfigPath);
  134. await (0, import_esmLoaderHost.configureESMLoaderTransformConfig)();
  135. return fullConfig;
  136. }
  137. function validateConfig(file, config) {
  138. if (typeof config !== "object" || !config)
  139. throw (0, import_util.errorWithFile)(file, `Configuration file must export a single object`);
  140. validateProject(file, config, "config");
  141. if ("forbidOnly" in config && config.forbidOnly !== void 0) {
  142. if (typeof config.forbidOnly !== "boolean")
  143. throw (0, import_util.errorWithFile)(file, `config.forbidOnly must be a boolean`);
  144. }
  145. if ("globalSetup" in config && config.globalSetup !== void 0) {
  146. if (Array.isArray(config.globalSetup)) {
  147. config.globalSetup.forEach((item, index) => {
  148. if (typeof item !== "string")
  149. throw (0, import_util.errorWithFile)(file, `config.globalSetup[${index}] must be a string`);
  150. });
  151. } else if (typeof config.globalSetup !== "string") {
  152. throw (0, import_util.errorWithFile)(file, `config.globalSetup must be a string`);
  153. }
  154. }
  155. if ("globalTeardown" in config && config.globalTeardown !== void 0) {
  156. if (Array.isArray(config.globalTeardown)) {
  157. config.globalTeardown.forEach((item, index) => {
  158. if (typeof item !== "string")
  159. throw (0, import_util.errorWithFile)(file, `config.globalTeardown[${index}] must be a string`);
  160. });
  161. } else if (typeof config.globalTeardown !== "string") {
  162. throw (0, import_util.errorWithFile)(file, `config.globalTeardown must be a string`);
  163. }
  164. }
  165. if ("globalTimeout" in config && config.globalTimeout !== void 0) {
  166. if (typeof config.globalTimeout !== "number" || config.globalTimeout < 0)
  167. throw (0, import_util.errorWithFile)(file, `config.globalTimeout must be a non-negative number`);
  168. }
  169. if ("grep" in config && config.grep !== void 0) {
  170. if (Array.isArray(config.grep)) {
  171. config.grep.forEach((item, index) => {
  172. if (!(0, import_utils.isRegExp)(item))
  173. throw (0, import_util.errorWithFile)(file, `config.grep[${index}] must be a RegExp`);
  174. });
  175. } else if (!(0, import_utils.isRegExp)(config.grep)) {
  176. throw (0, import_util.errorWithFile)(file, `config.grep must be a RegExp`);
  177. }
  178. }
  179. if ("grepInvert" in config && config.grepInvert !== void 0) {
  180. if (Array.isArray(config.grepInvert)) {
  181. config.grepInvert.forEach((item, index) => {
  182. if (!(0, import_utils.isRegExp)(item))
  183. throw (0, import_util.errorWithFile)(file, `config.grepInvert[${index}] must be a RegExp`);
  184. });
  185. } else if (!(0, import_utils.isRegExp)(config.grepInvert)) {
  186. throw (0, import_util.errorWithFile)(file, `config.grepInvert must be a RegExp`);
  187. }
  188. }
  189. if ("maxFailures" in config && config.maxFailures !== void 0) {
  190. if (typeof config.maxFailures !== "number" || config.maxFailures < 0)
  191. throw (0, import_util.errorWithFile)(file, `config.maxFailures must be a non-negative number`);
  192. }
  193. if ("preserveOutput" in config && config.preserveOutput !== void 0) {
  194. if (typeof config.preserveOutput !== "string" || !["always", "never", "failures-only"].includes(config.preserveOutput))
  195. throw (0, import_util.errorWithFile)(file, `config.preserveOutput must be one of "always", "never" or "failures-only"`);
  196. }
  197. if ("projects" in config && config.projects !== void 0) {
  198. if (!Array.isArray(config.projects))
  199. throw (0, import_util.errorWithFile)(file, `config.projects must be an array`);
  200. config.projects.forEach((project, index) => {
  201. validateProject(file, project, `config.projects[${index}]`);
  202. });
  203. }
  204. if ("quiet" in config && config.quiet !== void 0) {
  205. if (typeof config.quiet !== "boolean")
  206. throw (0, import_util.errorWithFile)(file, `config.quiet must be a boolean`);
  207. }
  208. if ("reporter" in config && config.reporter !== void 0) {
  209. if (Array.isArray(config.reporter)) {
  210. config.reporter.forEach((item, index) => {
  211. if (!Array.isArray(item) || item.length <= 0 || item.length > 2 || typeof item[0] !== "string")
  212. throw (0, import_util.errorWithFile)(file, `config.reporter[${index}] must be a tuple [name, optionalArgument]`);
  213. });
  214. } else if (typeof config.reporter !== "string") {
  215. throw (0, import_util.errorWithFile)(file, `config.reporter must be a string`);
  216. }
  217. }
  218. if ("reportSlowTests" in config && config.reportSlowTests !== void 0 && config.reportSlowTests !== null) {
  219. if (!config.reportSlowTests || typeof config.reportSlowTests !== "object")
  220. throw (0, import_util.errorWithFile)(file, `config.reportSlowTests must be an object`);
  221. if (!("max" in config.reportSlowTests) || typeof config.reportSlowTests.max !== "number" || config.reportSlowTests.max < 0)
  222. throw (0, import_util.errorWithFile)(file, `config.reportSlowTests.max must be a non-negative number`);
  223. if (!("threshold" in config.reportSlowTests) || typeof config.reportSlowTests.threshold !== "number" || config.reportSlowTests.threshold < 0)
  224. throw (0, import_util.errorWithFile)(file, `config.reportSlowTests.threshold must be a non-negative number`);
  225. }
  226. if ("shard" in config && config.shard !== void 0 && config.shard !== null) {
  227. if (!config.shard || typeof config.shard !== "object")
  228. throw (0, import_util.errorWithFile)(file, `config.shard must be an object`);
  229. if (!("total" in config.shard) || typeof config.shard.total !== "number" || config.shard.total < 1)
  230. throw (0, import_util.errorWithFile)(file, `config.shard.total must be a positive number`);
  231. if (!("current" in config.shard) || typeof config.shard.current !== "number" || config.shard.current < 1 || config.shard.current > config.shard.total)
  232. throw (0, import_util.errorWithFile)(file, `config.shard.current must be a positive number, not greater than config.shard.total`);
  233. }
  234. if ("updateSnapshots" in config && config.updateSnapshots !== void 0) {
  235. if (typeof config.updateSnapshots !== "string" || !["all", "changed", "missing", "none"].includes(config.updateSnapshots))
  236. throw (0, import_util.errorWithFile)(file, `config.updateSnapshots must be one of "all", "changed", "missing" or "none"`);
  237. }
  238. if ("tsconfig" in config && config.tsconfig !== void 0) {
  239. if (typeof config.tsconfig !== "string")
  240. throw (0, import_util.errorWithFile)(file, `config.tsconfig must be a string`);
  241. if (!import_fs.default.existsSync(import_path.default.resolve(file, "..", config.tsconfig)))
  242. throw (0, import_util.errorWithFile)(file, `config.tsconfig does not exist`);
  243. }
  244. }
  245. function validateProject(file, project, title) {
  246. if (typeof project !== "object" || !project)
  247. throw (0, import_util.errorWithFile)(file, `${title} must be an object`);
  248. if ("name" in project && project.name !== void 0) {
  249. if (typeof project.name !== "string")
  250. throw (0, import_util.errorWithFile)(file, `${title}.name must be a string`);
  251. }
  252. if ("outputDir" in project && project.outputDir !== void 0) {
  253. if (typeof project.outputDir !== "string")
  254. throw (0, import_util.errorWithFile)(file, `${title}.outputDir must be a string`);
  255. }
  256. if ("repeatEach" in project && project.repeatEach !== void 0) {
  257. if (typeof project.repeatEach !== "number" || project.repeatEach < 0)
  258. throw (0, import_util.errorWithFile)(file, `${title}.repeatEach must be a non-negative number`);
  259. }
  260. if ("retries" in project && project.retries !== void 0) {
  261. if (typeof project.retries !== "number" || project.retries < 0)
  262. throw (0, import_util.errorWithFile)(file, `${title}.retries must be a non-negative number`);
  263. }
  264. if ("testDir" in project && project.testDir !== void 0) {
  265. if (typeof project.testDir !== "string")
  266. throw (0, import_util.errorWithFile)(file, `${title}.testDir must be a string`);
  267. }
  268. for (const prop of ["testIgnore", "testMatch"]) {
  269. if (prop in project && project[prop] !== void 0) {
  270. const value = project[prop];
  271. if (Array.isArray(value)) {
  272. value.forEach((item, index) => {
  273. if (typeof item !== "string" && !(0, import_utils.isRegExp)(item))
  274. throw (0, import_util.errorWithFile)(file, `${title}.${prop}[${index}] must be a string or a RegExp`);
  275. });
  276. } else if (typeof value !== "string" && !(0, import_utils.isRegExp)(value)) {
  277. throw (0, import_util.errorWithFile)(file, `${title}.${prop} must be a string or a RegExp`);
  278. }
  279. }
  280. }
  281. if ("timeout" in project && project.timeout !== void 0) {
  282. if (typeof project.timeout !== "number" || project.timeout < 0)
  283. throw (0, import_util.errorWithFile)(file, `${title}.timeout must be a non-negative number`);
  284. }
  285. if ("use" in project && project.use !== void 0) {
  286. if (!project.use || typeof project.use !== "object")
  287. throw (0, import_util.errorWithFile)(file, `${title}.use must be an object`);
  288. }
  289. if ("ignoreSnapshots" in project && project.ignoreSnapshots !== void 0) {
  290. if (typeof project.ignoreSnapshots !== "boolean")
  291. throw (0, import_util.errorWithFile)(file, `${title}.ignoreSnapshots must be a boolean`);
  292. }
  293. if ("workers" in project && project.workers !== void 0) {
  294. if (typeof project.workers === "number" && project.workers <= 0)
  295. throw (0, import_util.errorWithFile)(file, `${title}.workers must be a positive number`);
  296. else if (typeof project.workers === "string" && !project.workers.endsWith("%"))
  297. throw (0, import_util.errorWithFile)(file, `${title}.workers must be a number or percentage`);
  298. }
  299. }
  300. function resolveConfigLocation(configFile) {
  301. const configFileOrDirectory = configFile ? import_path.default.resolve(process.cwd(), configFile) : process.cwd();
  302. const resolvedConfigFile = resolveConfigFile(configFileOrDirectory);
  303. return {
  304. resolvedConfigFile,
  305. configDir: resolvedConfigFile ? import_path.default.dirname(resolvedConfigFile) : configFileOrDirectory
  306. };
  307. }
  308. function resolveConfigFile(configFileOrDirectory) {
  309. const resolveConfig = (configFile) => {
  310. if (import_fs.default.existsSync(configFile))
  311. return configFile;
  312. };
  313. const resolveConfigFileFromDirectory = (directory) => {
  314. for (const ext of [".ts", ".js", ".mts", ".mjs", ".cts", ".cjs"]) {
  315. const configFile = resolveConfig(import_path.default.resolve(directory, "playwright.config" + ext));
  316. if (configFile)
  317. return configFile;
  318. }
  319. };
  320. if (!import_fs.default.existsSync(configFileOrDirectory))
  321. throw new Error(`${configFileOrDirectory} does not exist`);
  322. if (import_fs.default.statSync(configFileOrDirectory).isDirectory()) {
  323. const configFile = resolveConfigFileFromDirectory(configFileOrDirectory);
  324. if (configFile)
  325. return configFile;
  326. return void 0;
  327. }
  328. return configFileOrDirectory;
  329. }
  330. async function loadConfigFromFile(configFile, overrides, ignoreDeps) {
  331. return await loadConfig(resolveConfigLocation(configFile), overrides, ignoreDeps);
  332. }
  333. async function loadEmptyConfigForMergeReports() {
  334. return await loadConfig({ configDir: process.cwd() });
  335. }
  336. // Annotate the CommonJS export names for ESM import in node:
  337. 0 && (module.exports = {
  338. defineConfig,
  339. deserializeConfig,
  340. loadConfig,
  341. loadConfigFromFile,
  342. loadEmptyConfigForMergeReports,
  343. resolveConfigLocation
  344. });