config.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.defaultTimeout = exports.defaultReporter = exports.defaultGrep = exports.builtInReporters = exports.FullProjectInternal = exports.FullConfigInternal = void 0;
  6. exports.getProjectId = getProjectId;
  7. exports.takeFirst = takeFirst;
  8. exports.toReporters = toReporters;
  9. var _fs = _interopRequireDefault(require("fs"));
  10. var _path = _interopRequireDefault(require("path"));
  11. var _os = _interopRequireDefault(require("os"));
  12. var _util = require("../util");
  13. var _transform = require("../transform/transform");
  14. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  15. /**
  16. * Copyright Microsoft Corporation. All rights reserved.
  17. *
  18. * Licensed under the Apache License, Version 2.0 (the "License");
  19. * you may not use this file except in compliance with the License.
  20. * You may obtain a copy of the License at
  21. *
  22. * http://www.apache.org/licenses/LICENSE-2.0
  23. *
  24. * Unless required by applicable law or agreed to in writing, software
  25. * distributed under the License is distributed on an "AS IS" BASIS,
  26. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27. * See the License for the specific language governing permissions and
  28. * limitations under the License.
  29. */
  30. const defaultTimeout = exports.defaultTimeout = 30000;
  31. class FullConfigInternal {
  32. // TODO: when merging reports, there could be no internal config. This is very unfortunate.
  33. static from(config) {
  34. return config[configInternalSymbol];
  35. }
  36. constructor(configDir, configFile, config, configCLIOverrides) {
  37. var _build, _config$build;
  38. this.config = void 0;
  39. this.globalOutputDir = void 0;
  40. this.configDir = void 0;
  41. this.configCLIOverrides = void 0;
  42. this.storeDir = void 0;
  43. this.ignoreSnapshots = void 0;
  44. this.webServers = void 0;
  45. this.plugins = void 0;
  46. this.projects = [];
  47. this.cliArgs = [];
  48. this.cliGrep = void 0;
  49. this.cliGrepInvert = void 0;
  50. this.cliProjectFilter = void 0;
  51. this.cliListOnly = false;
  52. this.cliPassWithNoTests = void 0;
  53. this.testIdMatcher = void 0;
  54. this.defineConfigWasUsed = false;
  55. if (configCLIOverrides.projects && config.projects) throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`);
  56. const packageJsonPath = (0, _util.getPackageJsonPath)(configDir);
  57. const packageJsonDir = packageJsonPath ? _path.default.dirname(packageJsonPath) : undefined;
  58. const throwawayArtifactsPath = packageJsonDir || process.cwd();
  59. this.configDir = configDir;
  60. this.configCLIOverrides = configCLIOverrides;
  61. this.storeDir = _path.default.resolve(configDir, config._storeDir || 'playwright');
  62. this.globalOutputDir = takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, config.outputDir), throwawayArtifactsPath, _path.default.resolve(process.cwd()));
  63. this.ignoreSnapshots = takeFirst(configCLIOverrides.ignoreSnapshots, config.ignoreSnapshots, false);
  64. this.plugins = (config._plugins || []).map(p => ({
  65. factory: p
  66. }));
  67. this.config = {
  68. configFile,
  69. rootDir: pathResolve(configDir, config.testDir) || configDir,
  70. forbidOnly: takeFirst(configCLIOverrides.forbidOnly, config.forbidOnly, false),
  71. fullyParallel: takeFirst(configCLIOverrides.fullyParallel, config.fullyParallel, false),
  72. globalSetup: takeFirst(resolveScript(config.globalSetup, configDir), null),
  73. globalTeardown: takeFirst(resolveScript(config.globalTeardown, configDir), null),
  74. globalTimeout: takeFirst(configCLIOverrides.globalTimeout, config.globalTimeout, 0),
  75. grep: takeFirst(config.grep, defaultGrep),
  76. grepInvert: takeFirst(config.grepInvert, null),
  77. maxFailures: takeFirst(configCLIOverrides.maxFailures, config.maxFailures, 0),
  78. metadata: takeFirst(config.metadata, {}),
  79. preserveOutput: takeFirst(config.preserveOutput, 'always'),
  80. reporter: takeFirst(configCLIOverrides.reporter, resolveReporters(config.reporter, configDir), [[defaultReporter]]),
  81. reportSlowTests: takeFirst(config.reportSlowTests, {
  82. max: 5,
  83. threshold: 15000
  84. }),
  85. quiet: takeFirst(configCLIOverrides.quiet, config.quiet, false),
  86. projects: [],
  87. shard: takeFirst(configCLIOverrides.shard, config.shard, null),
  88. updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, config.updateSnapshots, 'missing'),
  89. version: require('../../package.json').version,
  90. workers: 0,
  91. webServer: null
  92. };
  93. this.config[configInternalSymbol] = this;
  94. const workers = takeFirst(configCLIOverrides.workers, config.workers, '50%');
  95. if (typeof workers === 'string') {
  96. if (workers.endsWith('%')) {
  97. const cpus = _os.default.cpus().length;
  98. this.config.workers = Math.max(1, Math.floor(cpus * (parseInt(workers, 10) / 100)));
  99. } else {
  100. this.config.workers = parseInt(workers, 10);
  101. }
  102. } else {
  103. this.config.workers = workers;
  104. }
  105. const webServers = takeFirst(config.webServer, null);
  106. if (Array.isArray(webServers)) {
  107. // multiple web server mode
  108. // Due to previous choices, this value shows up to the user in globalSetup as part of FullConfig. Arrays are not supported by the old type.
  109. this.config.webServer = null;
  110. this.webServers = webServers;
  111. } else if (webServers) {
  112. // legacy singleton mode
  113. this.config.webServer = webServers;
  114. this.webServers = [webServers];
  115. } else {
  116. this.webServers = [];
  117. }
  118. const projectConfigs = configCLIOverrides.projects || config.projects || [config];
  119. this.projects = projectConfigs.map(p => new FullProjectInternal(configDir, config, this, p, this.configCLIOverrides, throwawayArtifactsPath));
  120. resolveProjectDependencies(this.projects);
  121. this._assignUniqueProjectIds(this.projects);
  122. (0, _transform.setTransformConfig)({
  123. babelPlugins: ((_build = config.build) === null || _build === void 0 ? void 0 : _build.babelPlugins) || [],
  124. external: ((_config$build = config.build) === null || _config$build === void 0 ? void 0 : _config$build.external) || []
  125. });
  126. this.config.projects = this.projects.map(p => p.project);
  127. }
  128. _assignUniqueProjectIds(projects) {
  129. const usedNames = new Set();
  130. for (const p of projects) {
  131. const name = p.project.name || '';
  132. for (let i = 0; i < projects.length; ++i) {
  133. const candidate = name + (i ? i : '');
  134. if (usedNames.has(candidate)) continue;
  135. p.id = candidate;
  136. p.project.__projectId = p.id;
  137. usedNames.add(candidate);
  138. break;
  139. }
  140. }
  141. }
  142. }
  143. exports.FullConfigInternal = FullConfigInternal;
  144. class FullProjectInternal {
  145. constructor(configDir, config, fullConfig, projectConfig, configCLIOverrides, throwawayArtifactsPath) {
  146. var _this$expect$toHaveSc;
  147. this.project = void 0;
  148. this.fullConfig = void 0;
  149. this.fullyParallel = void 0;
  150. this.expect = void 0;
  151. this.respectGitIgnore = void 0;
  152. this.snapshotPathTemplate = void 0;
  153. this.id = '';
  154. this.deps = [];
  155. this.teardown = void 0;
  156. this.fullConfig = fullConfig;
  157. const testDir = takeFirst(pathResolve(configDir, projectConfig.testDir), pathResolve(configDir, config.testDir), fullConfig.configDir);
  158. const defaultSnapshotPathTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}';
  159. this.snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate, defaultSnapshotPathTemplate);
  160. this.project = {
  161. grep: takeFirst(projectConfig.grep, config.grep, defaultGrep),
  162. grepInvert: takeFirst(projectConfig.grepInvert, config.grepInvert, null),
  163. outputDir: takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, projectConfig.outputDir), pathResolve(configDir, config.outputDir), _path.default.join(throwawayArtifactsPath, 'test-results')),
  164. // Note: we either apply the cli override for repeatEach or not, depending on whether the
  165. // project is top-level vs dependency. See collectProjectsAndTestFiles in loadUtils.
  166. repeatEach: takeFirst(projectConfig.repeatEach, config.repeatEach, 1),
  167. retries: takeFirst(configCLIOverrides.retries, projectConfig.retries, config.retries, 0),
  168. metadata: takeFirst(projectConfig.metadata, config.metadata, undefined),
  169. name: takeFirst(projectConfig.name, config.name, ''),
  170. testDir,
  171. snapshotDir: takeFirst(pathResolve(configDir, projectConfig.snapshotDir), pathResolve(configDir, config.snapshotDir), testDir),
  172. testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []),
  173. testMatch: takeFirst(projectConfig.testMatch, config.testMatch, '**/*.@(spec|test).?(c|m)[jt]s?(x)'),
  174. timeout: takeFirst(configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
  175. use: (0, _util.mergeObjects)(config.use, projectConfig.use, configCLIOverrides.use),
  176. dependencies: projectConfig.dependencies || [],
  177. teardown: projectConfig.teardown
  178. };
  179. this.fullyParallel = takeFirst(configCLIOverrides.fullyParallel, projectConfig.fullyParallel, config.fullyParallel, undefined);
  180. this.expect = takeFirst(projectConfig.expect, config.expect, {});
  181. if ((_this$expect$toHaveSc = this.expect.toHaveScreenshot) !== null && _this$expect$toHaveSc !== void 0 && _this$expect$toHaveSc.stylePath) {
  182. const stylePaths = Array.isArray(this.expect.toHaveScreenshot.stylePath) ? this.expect.toHaveScreenshot.stylePath : [this.expect.toHaveScreenshot.stylePath];
  183. this.expect.toHaveScreenshot.stylePath = stylePaths.map(stylePath => _path.default.resolve(configDir, stylePath));
  184. }
  185. this.respectGitIgnore = !projectConfig.testDir && !config.testDir;
  186. }
  187. }
  188. exports.FullProjectInternal = FullProjectInternal;
  189. function takeFirst(...args) {
  190. for (const arg of args) {
  191. if (arg !== undefined) return arg;
  192. }
  193. return undefined;
  194. }
  195. function pathResolve(baseDir, relative) {
  196. if (!relative) return undefined;
  197. return _path.default.resolve(baseDir, relative);
  198. }
  199. function resolveReporters(reporters, rootDir) {
  200. var _toReporters;
  201. return (_toReporters = toReporters(reporters)) === null || _toReporters === void 0 ? void 0 : _toReporters.map(([id, arg]) => {
  202. if (builtInReporters.includes(id)) return [id, arg];
  203. return [require.resolve(id, {
  204. paths: [rootDir]
  205. }), arg];
  206. });
  207. }
  208. function resolveProjectDependencies(projects) {
  209. const teardownSet = new Set();
  210. for (const project of projects) {
  211. for (const dependencyName of project.project.dependencies) {
  212. const dependencies = projects.filter(p => p.project.name === dependencyName);
  213. if (!dependencies.length) throw new Error(`Project '${project.project.name}' depends on unknown project '${dependencyName}'`);
  214. if (dependencies.length > 1) throw new Error(`Project dependencies should have unique names, reading ${dependencyName}`);
  215. project.deps.push(...dependencies);
  216. }
  217. if (project.project.teardown) {
  218. const teardowns = projects.filter(p => p.project.name === project.project.teardown);
  219. if (!teardowns.length) throw new Error(`Project '${project.project.name}' has unknown teardown project '${project.project.teardown}'`);
  220. if (teardowns.length > 1) throw new Error(`Project teardowns should have unique names, reading ${project.project.teardown}`);
  221. const teardown = teardowns[0];
  222. project.teardown = teardown;
  223. teardownSet.add(teardown);
  224. }
  225. }
  226. for (const teardown of teardownSet) {
  227. if (teardown.deps.length) throw new Error(`Teardown project ${teardown.project.name} must not have dependencies`);
  228. }
  229. for (const project of projects) {
  230. for (const dep of project.deps) {
  231. if (teardownSet.has(dep)) throw new Error(`Project ${project.project.name} must not depend on a teardown project ${dep.project.name}`);
  232. }
  233. }
  234. }
  235. function toReporters(reporters) {
  236. if (!reporters) return;
  237. if (typeof reporters === 'string') return [[reporters]];
  238. return reporters;
  239. }
  240. const builtInReporters = exports.builtInReporters = ['list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html', 'blob', 'markdown'];
  241. function resolveScript(id, rootDir) {
  242. if (!id) return undefined;
  243. const localPath = _path.default.resolve(rootDir, id);
  244. if (_fs.default.existsSync(localPath)) return localPath;
  245. return require.resolve(id, {
  246. paths: [rootDir]
  247. });
  248. }
  249. const defaultGrep = exports.defaultGrep = /.*/;
  250. const defaultReporter = exports.defaultReporter = process.env.CI ? 'dot' : 'list';
  251. const configInternalSymbol = Symbol('configInternalSymbol');
  252. function getProjectId(project) {
  253. return project.__projectId;
  254. }