testRunner.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  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 testRunner_exports = {};
  30. __export(testRunner_exports, {
  31. TestRunner: () => TestRunner,
  32. TestRunnerEvent: () => TestRunnerEvent,
  33. runAllTestsWithConfig: () => runAllTestsWithConfig
  34. });
  35. module.exports = __toCommonJS(testRunner_exports);
  36. var import_events = __toESM(require("events"));
  37. var import_fs = __toESM(require("fs"));
  38. var import_path = __toESM(require("path"));
  39. var import_server = require("playwright-core/lib/server");
  40. var import_utils = require("playwright-core/lib/utils");
  41. var import_configLoader = require("../common/configLoader");
  42. var import_fsWatcher = require("../fsWatcher");
  43. var import_teleReceiver = require("../isomorphic/teleReceiver");
  44. var import_gitCommitInfoPlugin = require("../plugins/gitCommitInfoPlugin");
  45. var import_webServerPlugin = require("../plugins/webServerPlugin");
  46. var import_base = require("../reporters/base");
  47. var import_internalReporter = require("../reporters/internalReporter");
  48. var import_compilationCache = require("../transform/compilationCache");
  49. var import_util = require("../util");
  50. var import_reporters = require("./reporters");
  51. var import_tasks = require("./tasks");
  52. var import_lastRun = require("./lastRun");
  53. const TestRunnerEvent = {
  54. TestFilesChanged: "testFilesChanged",
  55. RecoverFromStepError: "recoverFromStepError"
  56. };
  57. class TestRunner extends import_events.default {
  58. constructor(configLocation, configCLIOverrides) {
  59. super();
  60. this._watchedProjectDirs = /* @__PURE__ */ new Set();
  61. this._ignoredProjectOutputs = /* @__PURE__ */ new Set();
  62. this._watchedTestDependencies = /* @__PURE__ */ new Set();
  63. this._queue = Promise.resolve();
  64. this._watchTestDirs = false;
  65. this._populateDependenciesOnList = false;
  66. this._recoverFromStepErrors = false;
  67. this._resumeAfterStepErrors = /* @__PURE__ */ new Map();
  68. this.configLocation = configLocation;
  69. this._configCLIOverrides = configCLIOverrides;
  70. this._watcher = new import_fsWatcher.Watcher((events) => {
  71. const collector = /* @__PURE__ */ new Set();
  72. events.forEach((f) => (0, import_compilationCache.collectAffectedTestFiles)(f.file, collector));
  73. this.emit(TestRunnerEvent.TestFilesChanged, [...collector]);
  74. });
  75. }
  76. async initialize(params) {
  77. this._watchTestDirs = !!params.watchTestDirs;
  78. this._populateDependenciesOnList = !!params.populateDependenciesOnList;
  79. this._recoverFromStepErrors = !!params.recoverFromStepErrors;
  80. }
  81. resizeTerminal(params) {
  82. process.stdout.columns = params.cols;
  83. process.stdout.rows = params.rows;
  84. process.stderr.columns = params.cols;
  85. process.stderr.rows = params.rows;
  86. }
  87. hasSomeBrowsers() {
  88. for (const browserName of ["chromium", "webkit", "firefox"]) {
  89. try {
  90. import_server.registry.findExecutable(browserName).executablePathOrDie("javascript");
  91. return true;
  92. } catch {
  93. }
  94. }
  95. return false;
  96. }
  97. async installBrowsers() {
  98. const executables = import_server.registry.defaultExecutables();
  99. await import_server.registry.install(executables, false);
  100. }
  101. async runGlobalSetup(userReporters) {
  102. await this.runGlobalTeardown();
  103. const reporter = new import_internalReporter.InternalReporter(userReporters);
  104. const config = await this._loadConfigOrReportError(reporter, this._configCLIOverrides);
  105. if (!config)
  106. return { status: "failed" };
  107. const { status, cleanup } = await (0, import_tasks.runTasksDeferCleanup)(new import_tasks.TestRun(config, reporter), [
  108. ...(0, import_tasks.createGlobalSetupTasks)(config)
  109. ]);
  110. if (status !== "passed")
  111. await cleanup();
  112. else
  113. this._globalSetup = { cleanup };
  114. return { status };
  115. }
  116. async runGlobalTeardown() {
  117. const globalSetup = this._globalSetup;
  118. const status = await globalSetup?.cleanup();
  119. this._globalSetup = void 0;
  120. return { status };
  121. }
  122. async startDevServer(userReporter, mode) {
  123. await this.stopDevServer();
  124. const reporter = new import_internalReporter.InternalReporter([userReporter]);
  125. const config = await this._loadConfigOrReportError(reporter);
  126. if (!config)
  127. return { status: "failed" };
  128. const { status, cleanup } = await (0, import_tasks.runTasksDeferCleanup)(new import_tasks.TestRun(config, reporter), [
  129. ...(0, import_tasks.createPluginSetupTasks)(config),
  130. (0, import_tasks.createLoadTask)(mode, { failOnLoadErrors: true, filterOnly: false }),
  131. (0, import_tasks.createStartDevServerTask)()
  132. ]);
  133. if (status !== "passed")
  134. await cleanup();
  135. else
  136. this._devServer = { cleanup };
  137. return { status };
  138. }
  139. async stopDevServer() {
  140. const devServer = this._devServer;
  141. const status = await devServer?.cleanup();
  142. this._devServer = void 0;
  143. return { status };
  144. }
  145. async clearCache(userReporter) {
  146. const reporter = new import_internalReporter.InternalReporter(userReporter ? [userReporter] : []);
  147. const config = await this._loadConfigOrReportError(reporter);
  148. if (!config)
  149. return { status: "failed" };
  150. const status = await (0, import_tasks.runTasks)(new import_tasks.TestRun(config, reporter), [
  151. ...(0, import_tasks.createPluginSetupTasks)(config),
  152. (0, import_tasks.createClearCacheTask)(config)
  153. ]);
  154. return { status };
  155. }
  156. async listFiles(userReporter, projects) {
  157. const reporter = new import_internalReporter.InternalReporter([userReporter]);
  158. const config = await this._loadConfigOrReportError(reporter);
  159. if (!config)
  160. return { status: "failed" };
  161. config.cliProjectFilter = projects?.length ? projects : void 0;
  162. const status = await (0, import_tasks.runTasks)(new import_tasks.TestRun(config, reporter), [
  163. (0, import_tasks.createListFilesTask)(),
  164. (0, import_tasks.createReportBeginTask)()
  165. ]);
  166. return { status };
  167. }
  168. async listTests(userReporter, params) {
  169. let result;
  170. this._queue = this._queue.then(async () => {
  171. const { config, status } = await this._innerListTests(userReporter, params);
  172. if (config)
  173. await this._updateWatchedDirs(config);
  174. result = { status };
  175. }).catch(printInternalError);
  176. await this._queue;
  177. return result;
  178. }
  179. async _innerListTests(userReporter, params) {
  180. const overrides = {
  181. ...this._configCLIOverrides,
  182. repeatEach: 1,
  183. retries: 0
  184. };
  185. const reporter = new import_internalReporter.InternalReporter([userReporter]);
  186. const config = await this._loadConfigOrReportError(reporter, overrides);
  187. if (!config)
  188. return { status: "failed" };
  189. config.cliArgs = params.locations || [];
  190. config.cliGrep = params.grep;
  191. config.cliGrepInvert = params.grepInvert;
  192. config.cliProjectFilter = params.projects?.length ? params.projects : void 0;
  193. config.cliListOnly = true;
  194. const status = await (0, import_tasks.runTasks)(new import_tasks.TestRun(config, reporter), [
  195. (0, import_tasks.createLoadTask)("out-of-process", { failOnLoadErrors: false, filterOnly: false, populateDependencies: this._populateDependenciesOnList }),
  196. (0, import_tasks.createReportBeginTask)()
  197. ]);
  198. return { config, status };
  199. }
  200. async _updateWatchedDirs(config) {
  201. this._watchedProjectDirs = /* @__PURE__ */ new Set();
  202. this._ignoredProjectOutputs = /* @__PURE__ */ new Set();
  203. for (const p of config.projects) {
  204. this._watchedProjectDirs.add(p.project.testDir);
  205. this._ignoredProjectOutputs.add(p.project.outputDir);
  206. }
  207. const result = await resolveCtDirs(config);
  208. if (result) {
  209. this._watchedProjectDirs.add(result.templateDir);
  210. this._ignoredProjectOutputs.add(result.outDir);
  211. }
  212. if (this._watchTestDirs)
  213. await this._updateWatcher(false);
  214. }
  215. async _updateWatcher(reportPending) {
  216. await this._watcher.update([...this._watchedProjectDirs, ...this._watchedTestDependencies], [...this._ignoredProjectOutputs], reportPending);
  217. }
  218. async runTests(userReporter, params) {
  219. let result = { status: "passed" };
  220. this._queue = this._queue.then(async () => {
  221. result = await this._innerRunTests(userReporter, params).catch((e) => {
  222. printInternalError(e);
  223. return { status: "failed" };
  224. });
  225. });
  226. await this._queue;
  227. return result;
  228. }
  229. async _innerRunTests(userReporter, params) {
  230. await this.stopTests();
  231. const overrides = {
  232. ...this._configCLIOverrides,
  233. repeatEach: 1,
  234. retries: 0,
  235. preserveOutputDir: true,
  236. reporter: params.reporters ? params.reporters.map((r) => [r]) : void 0,
  237. use: {
  238. ...this._configCLIOverrides.use,
  239. ...params.trace === "on" ? { trace: { mode: "on", sources: false, _live: true } } : {},
  240. ...params.trace === "off" ? { trace: "off" } : {},
  241. ...params.video === "on" || params.video === "off" ? { video: params.video } : {},
  242. ...params.headed !== void 0 ? { headless: !params.headed } : {},
  243. _optionContextReuseMode: params.reuseContext ? "when-possible" : void 0,
  244. _optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : void 0
  245. },
  246. ...params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {},
  247. ...params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {},
  248. ...params.workers ? { workers: params.workers } : {}
  249. };
  250. if (params.trace === "on")
  251. process.env.PW_LIVE_TRACE_STACKS = "1";
  252. else
  253. process.env.PW_LIVE_TRACE_STACKS = void 0;
  254. const config = await this._loadConfigOrReportError(new import_internalReporter.InternalReporter([userReporter]), overrides);
  255. if (!config)
  256. return { status: "failed" };
  257. config.cliListOnly = false;
  258. config.cliPassWithNoTests = true;
  259. config.cliArgs = params.locations || [];
  260. config.cliGrep = params.grep;
  261. config.cliGrepInvert = params.grepInvert;
  262. config.cliProjectFilter = params.projects?.length ? params.projects : void 0;
  263. config.preOnlyTestFilters = [];
  264. if (params.testIds) {
  265. const testIdSet = new Set(params.testIds);
  266. config.preOnlyTestFilters.push((test) => testIdSet.has(test.id));
  267. }
  268. const configReporters = await (0, import_reporters.createReporters)(config, "test", true);
  269. const reporter = new import_internalReporter.InternalReporter([...configReporters, userReporter]);
  270. const stop = new import_utils.ManualPromise();
  271. const tasks = [
  272. (0, import_tasks.createApplyRebaselinesTask)(),
  273. (0, import_tasks.createLoadTask)("out-of-process", { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }),
  274. ...(0, import_tasks.createRunTestsTasks)(config)
  275. ];
  276. const testRun = new import_tasks.TestRun(config, reporter);
  277. testRun.failureTracker.setRecoverFromStepErrorHandler(this._recoverFromStepError.bind(this));
  278. const run = (0, import_tasks.runTasks)(testRun, tasks, 0, stop).then(async (status) => {
  279. this._testRun = void 0;
  280. return status;
  281. });
  282. this._testRun = { run, stop };
  283. return { status: await run };
  284. }
  285. async _recoverFromStepError(stepId, error) {
  286. if (!this._recoverFromStepErrors)
  287. return { stepId, status: "failed" };
  288. const recoveryPromise = new import_utils.ManualPromise();
  289. this._resumeAfterStepErrors.set(stepId, recoveryPromise);
  290. if (!error?.message || !error?.location)
  291. return { stepId, status: "failed" };
  292. this.emit(TestRunnerEvent.RecoverFromStepError, stepId, error.message, error.location);
  293. const recoveredResult = await recoveryPromise;
  294. if (recoveredResult.stepId !== stepId)
  295. return { stepId, status: "failed" };
  296. return recoveredResult;
  297. }
  298. async resumeAfterStepError(params) {
  299. const recoveryPromise = this._resumeAfterStepErrors.get(params.stepId);
  300. if (recoveryPromise)
  301. recoveryPromise.resolve(params);
  302. }
  303. async watch(fileNames) {
  304. this._watchedTestDependencies = /* @__PURE__ */ new Set();
  305. for (const fileName of fileNames) {
  306. this._watchedTestDependencies.add(fileName);
  307. (0, import_compilationCache.dependenciesForTestFile)(fileName).forEach((file) => this._watchedTestDependencies.add(file));
  308. }
  309. await this._updateWatcher(true);
  310. }
  311. async findRelatedTestFiles(files, userReporter) {
  312. const errorReporter = (0, import_reporters.createErrorCollectingReporter)(import_base.internalScreen);
  313. const reporter = new import_internalReporter.InternalReporter(userReporter ? [userReporter, errorReporter] : [errorReporter]);
  314. const config = await this._loadConfigOrReportError(reporter);
  315. if (!config)
  316. return { errors: errorReporter.errors(), testFiles: [] };
  317. const status = await (0, import_tasks.runTasks)(new import_tasks.TestRun(config, reporter), [
  318. ...(0, import_tasks.createPluginSetupTasks)(config),
  319. (0, import_tasks.createLoadTask)("out-of-process", { failOnLoadErrors: true, filterOnly: false, populateDependencies: true })
  320. ]);
  321. if (status !== "passed")
  322. return { errors: errorReporter.errors(), testFiles: [] };
  323. return { testFiles: (0, import_compilationCache.affectedTestFiles)(files) };
  324. }
  325. async stopTests() {
  326. this._testRun?.stop?.resolve();
  327. await this._testRun?.run;
  328. this._resumeAfterStepErrors.clear();
  329. }
  330. async closeGracefully() {
  331. (0, import_utils.gracefullyProcessExitDoNotHang)(0);
  332. }
  333. async _loadConfig(overrides) {
  334. try {
  335. const config = await (0, import_configLoader.loadConfig)(this.configLocation, overrides);
  336. if (!this._plugins) {
  337. (0, import_webServerPlugin.webServerPluginsForConfig)(config).forEach((p) => config.plugins.push({ factory: p }));
  338. (0, import_gitCommitInfoPlugin.addGitCommitInfoPlugin)(config);
  339. this._plugins = config.plugins || [];
  340. } else {
  341. config.plugins.splice(0, config.plugins.length, ...this._plugins);
  342. }
  343. return { config };
  344. } catch (e) {
  345. return { config: null, error: (0, import_util.serializeError)(e) };
  346. }
  347. }
  348. async _loadConfigOrReportError(reporter, overrides) {
  349. const { config, error } = await this._loadConfig(overrides);
  350. if (config)
  351. return config;
  352. reporter.onConfigure(import_teleReceiver.baseFullConfig);
  353. reporter.onError(error);
  354. await reporter.onEnd({ status: "failed" });
  355. await reporter.onExit();
  356. return null;
  357. }
  358. }
  359. function printInternalError(e) {
  360. console.error("Internal error:", e);
  361. }
  362. async function resolveCtDirs(config) {
  363. const use = config.config.projects[0].use;
  364. const relativeTemplateDir = use.ctTemplateDir || "playwright";
  365. const templateDir = await import_fs.default.promises.realpath(import_path.default.normalize(import_path.default.join(config.configDir, relativeTemplateDir))).catch(() => void 0);
  366. if (!templateDir)
  367. return null;
  368. const outDir = use.ctCacheDir ? import_path.default.resolve(config.configDir, use.ctCacheDir) : import_path.default.resolve(templateDir, ".cache");
  369. return {
  370. outDir,
  371. templateDir
  372. };
  373. }
  374. async function runAllTestsWithConfig(config) {
  375. const listOnly = config.cliListOnly;
  376. (0, import_gitCommitInfoPlugin.addGitCommitInfoPlugin)(config);
  377. (0, import_webServerPlugin.webServerPluginsForConfig)(config).forEach((p) => config.plugins.push({ factory: p }));
  378. const reporters = await (0, import_reporters.createReporters)(config, listOnly ? "list" : "test", false);
  379. const lastRun = new import_lastRun.LastRunReporter(config);
  380. if (config.cliLastFailed)
  381. await lastRun.filterLastFailed();
  382. const reporter = new import_internalReporter.InternalReporter([...reporters, lastRun]);
  383. const tasks = listOnly ? [
  384. (0, import_tasks.createLoadTask)("in-process", { failOnLoadErrors: true, filterOnly: false }),
  385. (0, import_tasks.createReportBeginTask)()
  386. ] : [
  387. (0, import_tasks.createApplyRebaselinesTask)(),
  388. ...(0, import_tasks.createGlobalSetupTasks)(config),
  389. (0, import_tasks.createLoadTask)("in-process", { filterOnly: true, failOnLoadErrors: true }),
  390. ...(0, import_tasks.createRunTestsTasks)(config)
  391. ];
  392. const status = await (0, import_tasks.runTasks)(new import_tasks.TestRun(config, reporter), tasks, config.config.globalTimeout);
  393. await new Promise((resolve) => process.stdout.write("", () => resolve()));
  394. await new Promise((resolve) => process.stderr.write("", () => resolve()));
  395. return status;
  396. }
  397. // Annotate the CommonJS export names for ESM import in node:
  398. 0 && (module.exports = {
  399. TestRunner,
  400. TestRunnerEvent,
  401. runAllTestsWithConfig
  402. });