configLoader.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.defineConfig = exports.ConfigLoader = void 0;
  6. exports.resolveConfigFile = resolveConfigFile;
  7. var fs = _interopRequireWildcard(require("fs"));
  8. var path = _interopRequireWildcard(require("path"));
  9. var _utils = require("playwright-core/lib/utils");
  10. var _transform = require("../transform/transform");
  11. var _util = require("../util");
  12. var _globals = require("./globals");
  13. var _config = require("./config");
  14. var _compilationCache = require("../transform/compilationCache");
  15. var _esmLoaderHost = require("./esmLoaderHost");
  16. function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  17. function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  18. /**
  19. * Copyright Microsoft Corporation. All rights reserved.
  20. *
  21. * Licensed under the Apache License, Version 2.0 (the "License");
  22. * you may not use this file except in compliance with the License.
  23. * You may obtain a copy of the License at
  24. *
  25. * http://www.apache.org/licenses/LICENSE-2.0
  26. *
  27. * Unless required by applicable law or agreed to in writing, software
  28. * distributed under the License is distributed on an "AS IS" BASIS,
  29. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30. * See the License for the specific language governing permissions and
  31. * limitations under the License.
  32. */
  33. const kDefineConfigWasUsed = Symbol('defineConfigWasUsed');
  34. const defineConfig = (...configs) => {
  35. let result = configs[0];
  36. for (let i = 1; i < configs.length; ++i) {
  37. const config = configs[i];
  38. result = {
  39. ...result,
  40. ...config,
  41. expect: {
  42. ...result.expect,
  43. ...config.expect
  44. },
  45. use: {
  46. ...result.use,
  47. ...config.use
  48. },
  49. build: {
  50. ...result.build,
  51. ...config.build
  52. },
  53. webServer: [...(Array.isArray(result.webServer) ? result.webServer : result.webServer ? [result.webServer] : []), ...(Array.isArray(config.webServer) ? config.webServer : config.webServer ? [config.webServer] : [])]
  54. };
  55. if (!result.projects && !config.projects) continue;
  56. const projectOverrides = new Map();
  57. for (const project of config.projects || []) projectOverrides.set(project.name, project);
  58. const projects = [];
  59. for (const project of result.projects || []) {
  60. const projectOverride = projectOverrides.get(project.name);
  61. if (projectOverride) {
  62. projects.push({
  63. ...project,
  64. ...projectOverride,
  65. use: {
  66. ...project.use,
  67. ...projectOverride.use
  68. }
  69. });
  70. projectOverrides.delete(project.name);
  71. } else {
  72. projects.push(project);
  73. }
  74. }
  75. projects.push(...projectOverrides.values());
  76. result.projects = projects;
  77. }
  78. result[kDefineConfigWasUsed] = true;
  79. return result;
  80. };
  81. exports.defineConfig = defineConfig;
  82. class ConfigLoader {
  83. constructor(configCLIOverrides) {
  84. this._configCLIOverrides = void 0;
  85. this._fullConfig = void 0;
  86. this._configCLIOverrides = configCLIOverrides || {};
  87. }
  88. static async deserialize(data) {
  89. (0, _compilationCache.addToCompilationCache)(data.compilationCache);
  90. const loader = new ConfigLoader(data.configCLIOverrides);
  91. const config = data.configFile ? await loader.loadConfigFile(data.configFile) : await loader.loadEmptyConfig(data.configDir);
  92. await (0, _esmLoaderHost.initializeEsmLoader)();
  93. return config;
  94. }
  95. async loadConfigFile(file, ignoreProjectDependencies = false) {
  96. if (this._fullConfig) throw new Error('Cannot load two config files');
  97. const config = await requireOrImportDefaultObject(file);
  98. const fullConfig = await this._loadConfig(config, path.dirname(file), file);
  99. (0, _globals.setCurrentConfig)(fullConfig);
  100. if (ignoreProjectDependencies) {
  101. for (const project of fullConfig.projects) {
  102. project.deps = [];
  103. project.teardown = undefined;
  104. }
  105. }
  106. this._fullConfig = fullConfig;
  107. return fullConfig;
  108. }
  109. async loadEmptyConfig(configDir) {
  110. const fullConfig = await this._loadConfig({}, configDir);
  111. (0, _globals.setCurrentConfig)(fullConfig);
  112. return fullConfig;
  113. }
  114. async _loadConfig(config, configDir, configFile) {
  115. // 1. Validate data provided in the config file.
  116. validateConfig(configFile || '<default config>', config);
  117. const fullConfig = new _config.FullConfigInternal(configDir, configFile, config, this._configCLIOverrides);
  118. fullConfig.defineConfigWasUsed = !!config[kDefineConfigWasUsed];
  119. return fullConfig;
  120. }
  121. }
  122. exports.ConfigLoader = ConfigLoader;
  123. async function requireOrImportDefaultObject(file) {
  124. let object = await (0, _transform.requireOrImport)(file);
  125. if (object && typeof object === 'object' && 'default' in object) object = object['default'];
  126. return object;
  127. }
  128. function validateConfig(file, config) {
  129. if (typeof config !== 'object' || !config) throw (0, _util.errorWithFile)(file, `Configuration file must export a single object`);
  130. validateProject(file, config, 'config');
  131. if ('forbidOnly' in config && config.forbidOnly !== undefined) {
  132. if (typeof config.forbidOnly !== 'boolean') throw (0, _util.errorWithFile)(file, `config.forbidOnly must be a boolean`);
  133. }
  134. if ('globalSetup' in config && config.globalSetup !== undefined) {
  135. if (typeof config.globalSetup !== 'string') throw (0, _util.errorWithFile)(file, `config.globalSetup must be a string`);
  136. }
  137. if ('globalTeardown' in config && config.globalTeardown !== undefined) {
  138. if (typeof config.globalTeardown !== 'string') throw (0, _util.errorWithFile)(file, `config.globalTeardown must be a string`);
  139. }
  140. if ('globalTimeout' in config && config.globalTimeout !== undefined) {
  141. if (typeof config.globalTimeout !== 'number' || config.globalTimeout < 0) throw (0, _util.errorWithFile)(file, `config.globalTimeout must be a non-negative number`);
  142. }
  143. if ('grep' in config && config.grep !== undefined) {
  144. if (Array.isArray(config.grep)) {
  145. config.grep.forEach((item, index) => {
  146. if (!(0, _utils.isRegExp)(item)) throw (0, _util.errorWithFile)(file, `config.grep[${index}] must be a RegExp`);
  147. });
  148. } else if (!(0, _utils.isRegExp)(config.grep)) {
  149. throw (0, _util.errorWithFile)(file, `config.grep must be a RegExp`);
  150. }
  151. }
  152. if ('grepInvert' in config && config.grepInvert !== undefined) {
  153. if (Array.isArray(config.grepInvert)) {
  154. config.grepInvert.forEach((item, index) => {
  155. if (!(0, _utils.isRegExp)(item)) throw (0, _util.errorWithFile)(file, `config.grepInvert[${index}] must be a RegExp`);
  156. });
  157. } else if (!(0, _utils.isRegExp)(config.grepInvert)) {
  158. throw (0, _util.errorWithFile)(file, `config.grepInvert must be a RegExp`);
  159. }
  160. }
  161. if ('maxFailures' in config && config.maxFailures !== undefined) {
  162. if (typeof config.maxFailures !== 'number' || config.maxFailures < 0) throw (0, _util.errorWithFile)(file, `config.maxFailures must be a non-negative number`);
  163. }
  164. if ('preserveOutput' in config && config.preserveOutput !== undefined) {
  165. if (typeof config.preserveOutput !== 'string' || !['always', 'never', 'failures-only'].includes(config.preserveOutput)) throw (0, _util.errorWithFile)(file, `config.preserveOutput must be one of "always", "never" or "failures-only"`);
  166. }
  167. if ('projects' in config && config.projects !== undefined) {
  168. if (!Array.isArray(config.projects)) throw (0, _util.errorWithFile)(file, `config.projects must be an array`);
  169. config.projects.forEach((project, index) => {
  170. validateProject(file, project, `config.projects[${index}]`);
  171. });
  172. }
  173. if ('quiet' in config && config.quiet !== undefined) {
  174. if (typeof config.quiet !== 'boolean') throw (0, _util.errorWithFile)(file, `config.quiet must be a boolean`);
  175. }
  176. if ('reporter' in config && config.reporter !== undefined) {
  177. if (Array.isArray(config.reporter)) {
  178. config.reporter.forEach((item, index) => {
  179. if (!Array.isArray(item) || item.length <= 0 || item.length > 2 || typeof item[0] !== 'string') throw (0, _util.errorWithFile)(file, `config.reporter[${index}] must be a tuple [name, optionalArgument]`);
  180. });
  181. } else if (typeof config.reporter !== 'string') {
  182. throw (0, _util.errorWithFile)(file, `config.reporter must be a string`);
  183. }
  184. }
  185. if ('reportSlowTests' in config && config.reportSlowTests !== undefined && config.reportSlowTests !== null) {
  186. if (!config.reportSlowTests || typeof config.reportSlowTests !== 'object') throw (0, _util.errorWithFile)(file, `config.reportSlowTests must be an object`);
  187. if (!('max' in config.reportSlowTests) || typeof config.reportSlowTests.max !== 'number' || config.reportSlowTests.max < 0) throw (0, _util.errorWithFile)(file, `config.reportSlowTests.max must be a non-negative number`);
  188. if (!('threshold' in config.reportSlowTests) || typeof config.reportSlowTests.threshold !== 'number' || config.reportSlowTests.threshold < 0) throw (0, _util.errorWithFile)(file, `config.reportSlowTests.threshold must be a non-negative number`);
  189. }
  190. if ('shard' in config && config.shard !== undefined && config.shard !== null) {
  191. if (!config.shard || typeof config.shard !== 'object') throw (0, _util.errorWithFile)(file, `config.shard must be an object`);
  192. if (!('total' in config.shard) || typeof config.shard.total !== 'number' || config.shard.total < 1) throw (0, _util.errorWithFile)(file, `config.shard.total must be a positive number`);
  193. if (!('current' in config.shard) || typeof config.shard.current !== 'number' || config.shard.current < 1 || config.shard.current > config.shard.total) throw (0, _util.errorWithFile)(file, `config.shard.current must be a positive number, not greater than config.shard.total`);
  194. }
  195. if ('ignoreSnapshots' in config && config.ignoreSnapshots !== undefined) {
  196. if (typeof config.ignoreSnapshots !== 'boolean') throw (0, _util.errorWithFile)(file, `config.ignoreSnapshots must be a boolean`);
  197. }
  198. if ('updateSnapshots' in config && config.updateSnapshots !== undefined) {
  199. if (typeof config.updateSnapshots !== 'string' || !['all', 'none', 'missing'].includes(config.updateSnapshots)) throw (0, _util.errorWithFile)(file, `config.updateSnapshots must be one of "all", "none" or "missing"`);
  200. }
  201. if ('workers' in config && config.workers !== undefined) {
  202. if (typeof config.workers === 'number' && config.workers <= 0) throw (0, _util.errorWithFile)(file, `config.workers must be a positive number`);else if (typeof config.workers === 'string' && !config.workers.endsWith('%')) throw (0, _util.errorWithFile)(file, `config.workers must be a number or percentage`);
  203. }
  204. }
  205. function validateProject(file, project, title) {
  206. if (typeof project !== 'object' || !project) throw (0, _util.errorWithFile)(file, `${title} must be an object`);
  207. if ('name' in project && project.name !== undefined) {
  208. if (typeof project.name !== 'string') throw (0, _util.errorWithFile)(file, `${title}.name must be a string`);
  209. }
  210. if ('outputDir' in project && project.outputDir !== undefined) {
  211. if (typeof project.outputDir !== 'string') throw (0, _util.errorWithFile)(file, `${title}.outputDir must be a string`);
  212. }
  213. if ('repeatEach' in project && project.repeatEach !== undefined) {
  214. if (typeof project.repeatEach !== 'number' || project.repeatEach < 0) throw (0, _util.errorWithFile)(file, `${title}.repeatEach must be a non-negative number`);
  215. }
  216. if ('retries' in project && project.retries !== undefined) {
  217. if (typeof project.retries !== 'number' || project.retries < 0) throw (0, _util.errorWithFile)(file, `${title}.retries must be a non-negative number`);
  218. }
  219. if ('testDir' in project && project.testDir !== undefined) {
  220. if (typeof project.testDir !== 'string') throw (0, _util.errorWithFile)(file, `${title}.testDir must be a string`);
  221. }
  222. for (const prop of ['testIgnore', 'testMatch']) {
  223. if (prop in project && project[prop] !== undefined) {
  224. const value = project[prop];
  225. if (Array.isArray(value)) {
  226. value.forEach((item, index) => {
  227. if (typeof item !== 'string' && !(0, _utils.isRegExp)(item)) throw (0, _util.errorWithFile)(file, `${title}.${prop}[${index}] must be a string or a RegExp`);
  228. });
  229. } else if (typeof value !== 'string' && !(0, _utils.isRegExp)(value)) {
  230. throw (0, _util.errorWithFile)(file, `${title}.${prop} must be a string or a RegExp`);
  231. }
  232. }
  233. }
  234. if ('timeout' in project && project.timeout !== undefined) {
  235. if (typeof project.timeout !== 'number' || project.timeout < 0) throw (0, _util.errorWithFile)(file, `${title}.timeout must be a non-negative number`);
  236. }
  237. if ('use' in project && project.use !== undefined) {
  238. if (!project.use || typeof project.use !== 'object') throw (0, _util.errorWithFile)(file, `${title}.use must be an object`);
  239. }
  240. }
  241. function resolveConfigFile(configFileOrDirectory) {
  242. const resolveConfig = configFile => {
  243. if (fs.existsSync(configFile)) return configFile;
  244. };
  245. const resolveConfigFileFromDirectory = directory => {
  246. for (const ext of ['.ts', '.js', '.mts', '.mjs', '.cts', '.cjs']) {
  247. const configFile = resolveConfig(path.resolve(directory, 'playwright.config' + ext));
  248. if (configFile) return configFile;
  249. }
  250. };
  251. if (!fs.existsSync(configFileOrDirectory)) throw new Error(`${configFileOrDirectory} does not exist`);
  252. if (fs.statSync(configFileOrDirectory).isDirectory()) {
  253. // When passed a directory, look for a config file inside.
  254. const configFile = resolveConfigFileFromDirectory(configFileOrDirectory);
  255. if (configFile) return configFile;
  256. // If there is no config, assume this as a root testing directory.
  257. return null;
  258. } else {
  259. // When passed a file, it must be a config file.
  260. const configFile = resolveConfig(configFileOrDirectory);
  261. return configFile;
  262. }
  263. }