tasks.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.TestRun = void 0;
  6. exports.createTaskRunner = createTaskRunner;
  7. exports.createTaskRunnerForList = createTaskRunnerForList;
  8. exports.createTaskRunnerForWatch = createTaskRunnerForWatch;
  9. exports.createTaskRunnerForWatchSetup = createTaskRunnerForWatchSetup;
  10. var _fs = _interopRequireDefault(require("fs"));
  11. var _path = _interopRequireDefault(require("path"));
  12. var _util = require("util");
  13. var _utilsBundle = require("playwright-core/lib/utilsBundle");
  14. var _utils = require("playwright-core/lib/utils");
  15. var _dispatcher = require("./dispatcher");
  16. var _testGroups = require("../runner/testGroups");
  17. var _taskRunner = require("./taskRunner");
  18. var _loadUtils = require("./loadUtils");
  19. var _projectUtils = require("./projectUtils");
  20. var _failureTracker = require("./failureTracker");
  21. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  22. /**
  23. * Copyright Microsoft Corporation. All rights reserved.
  24. *
  25. * Licensed under the Apache License, Version 2.0 (the "License");
  26. * you may not use this file except in compliance with the License.
  27. * You may obtain a copy of the License at
  28. *
  29. * http://www.apache.org/licenses/LICENSE-2.0
  30. *
  31. * Unless required by applicable law or agreed to in writing, software
  32. * distributed under the License is distributed on an "AS IS" BASIS,
  33. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  34. * See the License for the specific language governing permissions and
  35. * limitations under the License.
  36. */
  37. const readDirAsync = (0, _util.promisify)(_fs.default.readdir);
  38. class TestRun {
  39. constructor(config, reporter) {
  40. this.reporter = void 0;
  41. this.config = void 0;
  42. this.failureTracker = void 0;
  43. this.rootSuite = undefined;
  44. this.phases = [];
  45. this.projects = [];
  46. this.projectFiles = new Map();
  47. this.projectSuites = new Map();
  48. this.config = config;
  49. this.reporter = reporter;
  50. this.failureTracker = new _failureTracker.FailureTracker(config);
  51. }
  52. }
  53. exports.TestRun = TestRun;
  54. function createTaskRunner(config, reporter) {
  55. const taskRunner = new _taskRunner.TaskRunner(reporter, config.config.globalTimeout);
  56. addGlobalSetupTasks(taskRunner, config);
  57. taskRunner.addTask('load tests', createLoadTask('in-process', {
  58. filterOnly: true,
  59. failOnLoadErrors: true
  60. }));
  61. addRunTasks(taskRunner, config);
  62. return taskRunner;
  63. }
  64. function createTaskRunnerForWatchSetup(config, reporter) {
  65. const taskRunner = new _taskRunner.TaskRunner(reporter, 0);
  66. addGlobalSetupTasks(taskRunner, config);
  67. return taskRunner;
  68. }
  69. function createTaskRunnerForWatch(config, reporter, additionalFileMatcher) {
  70. const taskRunner = new _taskRunner.TaskRunner(reporter, 0);
  71. taskRunner.addTask('load tests', createLoadTask('out-of-process', {
  72. filterOnly: true,
  73. failOnLoadErrors: false,
  74. doNotRunTestsOutsideProjectFilter: true,
  75. additionalFileMatcher
  76. }));
  77. addRunTasks(taskRunner, config);
  78. return taskRunner;
  79. }
  80. function addGlobalSetupTasks(taskRunner, config) {
  81. for (const plugin of config.plugins) taskRunner.addTask('plugin setup', createPluginSetupTask(plugin));
  82. if (config.config.globalSetup || config.config.globalTeardown) taskRunner.addTask('global setup', createGlobalSetupTask());
  83. taskRunner.addTask('clear output', createRemoveOutputDirsTask());
  84. }
  85. function addRunTasks(taskRunner, config) {
  86. taskRunner.addTask('create phases', createPhasesTask());
  87. taskRunner.addTask('report begin', createReportBeginTask());
  88. for (const plugin of config.plugins) taskRunner.addTask('plugin begin', createPluginBeginTask(plugin));
  89. taskRunner.addTask('test suite', createRunTestsTask());
  90. return taskRunner;
  91. }
  92. function createTaskRunnerForList(config, reporter, mode, options) {
  93. const taskRunner = new _taskRunner.TaskRunner(reporter, config.config.globalTimeout);
  94. taskRunner.addTask('load tests', createLoadTask(mode, {
  95. ...options,
  96. filterOnly: false
  97. }));
  98. taskRunner.addTask('report begin', createReportBeginTask());
  99. return taskRunner;
  100. }
  101. function createReportBeginTask() {
  102. return {
  103. setup: async ({
  104. reporter,
  105. rootSuite
  106. }) => {
  107. reporter.onBegin(rootSuite);
  108. },
  109. teardown: async ({}) => {}
  110. };
  111. }
  112. function createPluginSetupTask(plugin) {
  113. return {
  114. setup: async ({
  115. config,
  116. reporter
  117. }) => {
  118. var _plugin$instance, _plugin$instance$setu;
  119. if (typeof plugin.factory === 'function') plugin.instance = await plugin.factory();else plugin.instance = plugin.factory;
  120. await ((_plugin$instance = plugin.instance) === null || _plugin$instance === void 0 ? void 0 : (_plugin$instance$setu = _plugin$instance.setup) === null || _plugin$instance$setu === void 0 ? void 0 : _plugin$instance$setu.call(_plugin$instance, config.config, config.configDir, reporter));
  121. },
  122. teardown: async () => {
  123. var _plugin$instance2, _plugin$instance2$tea;
  124. await ((_plugin$instance2 = plugin.instance) === null || _plugin$instance2 === void 0 ? void 0 : (_plugin$instance2$tea = _plugin$instance2.teardown) === null || _plugin$instance2$tea === void 0 ? void 0 : _plugin$instance2$tea.call(_plugin$instance2));
  125. }
  126. };
  127. }
  128. function createPluginBeginTask(plugin) {
  129. return {
  130. setup: async ({
  131. rootSuite
  132. }) => {
  133. var _plugin$instance3, _plugin$instance3$beg;
  134. await ((_plugin$instance3 = plugin.instance) === null || _plugin$instance3 === void 0 ? void 0 : (_plugin$instance3$beg = _plugin$instance3.begin) === null || _plugin$instance3$beg === void 0 ? void 0 : _plugin$instance3$beg.call(_plugin$instance3, rootSuite));
  135. },
  136. teardown: async () => {
  137. var _plugin$instance4, _plugin$instance4$end;
  138. await ((_plugin$instance4 = plugin.instance) === null || _plugin$instance4 === void 0 ? void 0 : (_plugin$instance4$end = _plugin$instance4.end) === null || _plugin$instance4$end === void 0 ? void 0 : _plugin$instance4$end.call(_plugin$instance4));
  139. }
  140. };
  141. }
  142. function createGlobalSetupTask() {
  143. let globalSetupResult;
  144. let globalSetupFinished = false;
  145. let teardownHook;
  146. return {
  147. setup: async ({
  148. config
  149. }) => {
  150. const setupHook = config.config.globalSetup ? await (0, _loadUtils.loadGlobalHook)(config, config.config.globalSetup) : undefined;
  151. teardownHook = config.config.globalTeardown ? await (0, _loadUtils.loadGlobalHook)(config, config.config.globalTeardown) : undefined;
  152. globalSetupResult = setupHook ? await setupHook(config.config) : undefined;
  153. globalSetupFinished = true;
  154. },
  155. teardown: async ({
  156. config
  157. }) => {
  158. var _teardownHook;
  159. if (typeof globalSetupResult === 'function') await globalSetupResult();
  160. if (globalSetupFinished) await ((_teardownHook = teardownHook) === null || _teardownHook === void 0 ? void 0 : _teardownHook(config.config));
  161. }
  162. };
  163. }
  164. function createRemoveOutputDirsTask() {
  165. return {
  166. setup: async ({
  167. config
  168. }) => {
  169. if (process.env.PW_TEST_NO_REMOVE_OUTPUT_DIRS) return;
  170. const outputDirs = new Set();
  171. for (const p of config.projects) {
  172. if (!config.cliProjectFilter || config.cliProjectFilter.includes(p.project.name)) outputDirs.add(p.project.outputDir);
  173. }
  174. await Promise.all(Array.from(outputDirs).map(outputDir => (0, _utils.removeFolders)([outputDir]).then(async ([error]) => {
  175. if (!error) return;
  176. if (error.code === 'EBUSY') {
  177. // We failed to remove folder, might be due to the whole folder being mounted inside a container:
  178. // https://github.com/microsoft/playwright/issues/12106
  179. // Do a best-effort to remove all files inside of it instead.
  180. const entries = await readDirAsync(outputDir).catch(e => []);
  181. await Promise.all(entries.map(entry => (0, _utils.removeFolders)([_path.default.join(outputDir, entry)])));
  182. } else {
  183. throw error;
  184. }
  185. })));
  186. }
  187. };
  188. }
  189. function createLoadTask(mode, options) {
  190. return {
  191. setup: async (testRun, errors, softErrors) => {
  192. await (0, _loadUtils.collectProjectsAndTestFiles)(testRun, !!options.doNotRunTestsOutsideProjectFilter, options.additionalFileMatcher);
  193. await (0, _loadUtils.loadFileSuites)(testRun, mode, options.failOnLoadErrors ? errors : softErrors);
  194. testRun.rootSuite = await (0, _loadUtils.createRootSuite)(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly);
  195. testRun.failureTracker.onRootSuite(testRun.rootSuite);
  196. // Fail when no tests.
  197. if (options.failOnLoadErrors && !testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard) {
  198. if (testRun.config.cliArgs.length) {
  199. throw new Error([`No tests found.`, `Make sure that arguments are regular expressions matching test files.`, `You may need to escape symbols like "$" or "*" and quote the arguments.`].join('\n'));
  200. }
  201. throw new Error(`No tests found`);
  202. }
  203. }
  204. };
  205. }
  206. function createPhasesTask() {
  207. return {
  208. setup: async testRun => {
  209. let maxConcurrentTestGroups = 0;
  210. const processed = new Set();
  211. const projectToSuite = new Map(testRun.rootSuite.suites.map(suite => [suite._fullProject, suite]));
  212. const allProjects = [...projectToSuite.keys()];
  213. const teardownToSetups = (0, _projectUtils.buildTeardownToSetupsMap)(allProjects);
  214. const teardownToSetupsDependents = new Map();
  215. for (const [teardown, setups] of teardownToSetups) {
  216. const closure = (0, _projectUtils.buildDependentProjects)(setups, allProjects);
  217. closure.delete(teardown);
  218. teardownToSetupsDependents.set(teardown, [...closure]);
  219. }
  220. for (let i = 0; i < projectToSuite.size; i++) {
  221. // Find all projects that have all their dependencies processed by previous phases.
  222. const phaseProjects = [];
  223. for (const project of projectToSuite.keys()) {
  224. if (processed.has(project)) continue;
  225. const projectsThatShouldFinishFirst = [...project.deps, ...(teardownToSetupsDependents.get(project) || [])];
  226. if (projectsThatShouldFinishFirst.find(p => !processed.has(p))) continue;
  227. phaseProjects.push(project);
  228. }
  229. // Create a new phase.
  230. for (const project of phaseProjects) processed.add(project);
  231. if (phaseProjects.length) {
  232. let testGroupsInPhase = 0;
  233. const phase = {
  234. dispatcher: new _dispatcher.Dispatcher(testRun.config, testRun.reporter, testRun.failureTracker),
  235. projects: []
  236. };
  237. testRun.phases.push(phase);
  238. for (const project of phaseProjects) {
  239. const projectSuite = projectToSuite.get(project);
  240. const testGroups = (0, _testGroups.createTestGroups)(projectSuite, testRun.config.config.workers);
  241. phase.projects.push({
  242. project,
  243. projectSuite,
  244. testGroups
  245. });
  246. testGroupsInPhase += testGroups.length;
  247. }
  248. (0, _utilsBundle.debug)('pw:test:task')(`created phase #${testRun.phases.length} with ${phase.projects.map(p => p.project.project.name).sort()} projects, ${testGroupsInPhase} testGroups`);
  249. maxConcurrentTestGroups = Math.max(maxConcurrentTestGroups, testGroupsInPhase);
  250. }
  251. }
  252. testRun.config.config.metadata.actualWorkers = Math.min(testRun.config.config.workers, maxConcurrentTestGroups);
  253. }
  254. };
  255. }
  256. function createRunTestsTask() {
  257. return {
  258. setup: async ({
  259. phases,
  260. failureTracker
  261. }) => {
  262. const successfulProjects = new Set();
  263. const extraEnvByProjectId = new Map();
  264. const teardownToSetups = (0, _projectUtils.buildTeardownToSetupsMap)(phases.map(phase => phase.projects.map(p => p.project)).flat());
  265. for (const {
  266. dispatcher,
  267. projects
  268. } of phases) {
  269. // Each phase contains dispatcher and a set of test groups.
  270. // We don't want to run the test groups belonging to the projects
  271. // that depend on the projects that failed previously.
  272. const phaseTestGroups = [];
  273. for (const {
  274. project,
  275. testGroups
  276. } of projects) {
  277. // Inherit extra environment variables from dependencies.
  278. let extraEnv = {};
  279. for (const dep of project.deps) extraEnv = {
  280. ...extraEnv,
  281. ...extraEnvByProjectId.get(dep.id)
  282. };
  283. for (const setup of teardownToSetups.get(project) || []) extraEnv = {
  284. ...extraEnv,
  285. ...extraEnvByProjectId.get(setup.id)
  286. };
  287. extraEnvByProjectId.set(project.id, extraEnv);
  288. const hasFailedDeps = project.deps.some(p => !successfulProjects.has(p));
  289. if (!hasFailedDeps) phaseTestGroups.push(...testGroups);
  290. }
  291. if (phaseTestGroups.length) {
  292. await dispatcher.run(phaseTestGroups, extraEnvByProjectId);
  293. await dispatcher.stop();
  294. for (const [projectId, envProduced] of dispatcher.producedEnvByProjectId()) {
  295. const extraEnv = extraEnvByProjectId.get(projectId) || {};
  296. extraEnvByProjectId.set(projectId, {
  297. ...extraEnv,
  298. ...envProduced
  299. });
  300. }
  301. }
  302. // If the worker broke, fail everything, we have no way of knowing which
  303. // projects failed.
  304. if (!failureTracker.hasWorkerErrors()) {
  305. for (const {
  306. project,
  307. projectSuite
  308. } of projects) {
  309. const hasFailedDeps = project.deps.some(p => !successfulProjects.has(p));
  310. if (!hasFailedDeps && !projectSuite.allTests().some(test => !test.ok())) successfulProjects.add(project);
  311. }
  312. }
  313. }
  314. },
  315. teardown: async ({
  316. phases
  317. }) => {
  318. for (const {
  319. dispatcher
  320. } of phases.reverse()) await dispatcher.stop();
  321. }
  322. };
  323. }