uiMode.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.runUIMode = runUIMode;
  6. var _server = require("playwright-core/lib/server");
  7. var _utils = require("playwright-core/lib/utils");
  8. var _compilationCache = require("../transform/compilationCache");
  9. var _internalReporter = require("../reporters/internalReporter");
  10. var _teleEmitter = require("../reporters/teleEmitter");
  11. var _reporters = require("./reporters");
  12. var _tasks = require("./tasks");
  13. var _utilsBundle = require("../utilsBundle");
  14. var _utilsBundle2 = require("playwright-core/lib/utilsBundle");
  15. var _list = _interopRequireDefault(require("../reporters/list"));
  16. var _multiplexer = require("../reporters/multiplexer");
  17. var _sigIntWatcher = require("./sigIntWatcher");
  18. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  19. /**
  20. * Copyright Microsoft Corporation. All rights reserved.
  21. *
  22. * Licensed under the Apache License, Version 2.0 (the "License");
  23. * you may not use this file except in compliance with the License.
  24. * You may obtain a copy of the License at
  25. *
  26. * http://www.apache.org/licenses/LICENSE-2.0
  27. *
  28. * Unless required by applicable law or agreed to in writing, software
  29. * distributed under the License is distributed on an "AS IS" BASIS,
  30. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31. * See the License for the specific language governing permissions and
  32. * limitations under the License.
  33. */
  34. class UIMode {
  35. constructor(config) {
  36. this._config = void 0;
  37. this._transport = void 0;
  38. this._testRun = void 0;
  39. this.globalCleanup = void 0;
  40. this._globalWatcher = void 0;
  41. this._testWatcher = void 0;
  42. this._originalStdoutWrite = void 0;
  43. this._originalStderrWrite = void 0;
  44. this._config = config;
  45. process.env.PW_LIVE_TRACE_STACKS = '1';
  46. config.cliListOnly = false;
  47. config.cliPassWithNoTests = true;
  48. config.config.preserveOutput = 'always';
  49. for (const p of config.projects) {
  50. p.project.retries = 0;
  51. p.project.repeatEach = 1;
  52. }
  53. config.configCLIOverrides.use = config.configCLIOverrides.use || {};
  54. config.configCLIOverrides.use.trace = {
  55. mode: 'on',
  56. sources: false,
  57. _live: true
  58. };
  59. this._originalStdoutWrite = process.stdout.write;
  60. this._originalStderrWrite = process.stderr.write;
  61. this._globalWatcher = new Watcher('deep', () => this._dispatchEvent('listChanged', {}));
  62. this._testWatcher = new Watcher('flat', events => {
  63. const collector = new Set();
  64. events.forEach(f => (0, _compilationCache.collectAffectedTestFiles)(f.file, collector));
  65. this._dispatchEvent('testFilesChanged', {
  66. testFileNames: [...collector]
  67. });
  68. });
  69. }
  70. async runGlobalSetup() {
  71. const reporter = new _internalReporter.InternalReporter(new _list.default());
  72. const taskRunner = (0, _tasks.createTaskRunnerForWatchSetup)(this._config, reporter);
  73. reporter.onConfigure(this._config.config);
  74. const testRun = new _tasks.TestRun(this._config, reporter);
  75. const {
  76. status,
  77. cleanup: globalCleanup
  78. } = await taskRunner.runDeferCleanup(testRun, 0);
  79. await reporter.onEnd({
  80. status
  81. });
  82. await reporter.onExit();
  83. if (status !== 'passed') {
  84. await globalCleanup();
  85. return status;
  86. }
  87. this.globalCleanup = globalCleanup;
  88. return status;
  89. }
  90. async showUI(options, cancelPromise) {
  91. let queue = Promise.resolve();
  92. this._transport = {
  93. dispatch: async (method, params) => {
  94. if (method === 'ping') return;
  95. if (method === 'watch') {
  96. this._watchFiles(params.fileNames);
  97. return;
  98. }
  99. if (method === 'open' && params.location) {
  100. (0, _utilsBundle2.open)('vscode://file/' + params.location).catch(e => this._originalStderrWrite.call(process.stderr, String(e)));
  101. return;
  102. }
  103. if (method === 'resizeTerminal') {
  104. process.stdout.columns = params.cols;
  105. process.stdout.rows = params.rows;
  106. process.stderr.columns = params.cols;
  107. process.stderr.columns = params.rows;
  108. return;
  109. }
  110. if (method === 'stop') {
  111. void this._stopTests();
  112. return;
  113. }
  114. if (method === 'checkBrowsers') return {
  115. hasBrowsers: hasSomeBrowsers()
  116. };
  117. if (method === 'installBrowsers') {
  118. await installBrowsers();
  119. return;
  120. }
  121. queue = queue.then(() => this._queueListOrRun(method, params));
  122. await queue;
  123. },
  124. onclose: () => {}
  125. };
  126. const openOptions = {
  127. app: 'uiMode.html',
  128. headless: (0, _utils.isUnderTest)() && process.env.PWTEST_HEADED_FOR_TEST !== '1',
  129. transport: this._transport,
  130. host: options.host,
  131. port: options.port,
  132. persistentContextOptions: {
  133. handleSIGINT: false
  134. }
  135. };
  136. if (options.host !== undefined || options.port !== undefined) {
  137. await (0, _server.openTraceInBrowser)([], openOptions);
  138. } else {
  139. const page = await (0, _server.openTraceViewerApp)([], 'chromium', openOptions);
  140. page.on('close', () => cancelPromise.resolve());
  141. }
  142. if (!process.env.PWTEST_DEBUG) {
  143. process.stdout.write = chunk => {
  144. this._dispatchEvent('stdio', chunkToPayload('stdout', chunk));
  145. return true;
  146. };
  147. process.stderr.write = chunk => {
  148. this._dispatchEvent('stdio', chunkToPayload('stderr', chunk));
  149. return true;
  150. };
  151. }
  152. await cancelPromise;
  153. if (!process.env.PWTEST_DEBUG) {
  154. process.stdout.write = this._originalStdoutWrite;
  155. process.stderr.write = this._originalStderrWrite;
  156. }
  157. }
  158. async _queueListOrRun(method, params) {
  159. if (method === 'list') await this._listTests();
  160. if (method === 'run') await this._runTests(params.testIds, params.projects);
  161. }
  162. _dispatchEvent(method, params) {
  163. var _this$_transport$send, _this$_transport;
  164. (_this$_transport$send = (_this$_transport = this._transport).sendEvent) === null || _this$_transport$send === void 0 ? void 0 : _this$_transport$send.call(_this$_transport, method, params);
  165. }
  166. async _listTests() {
  167. const reporter = new _internalReporter.InternalReporter(new _teleEmitter.TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params), true));
  168. this._config.cliListOnly = true;
  169. this._config.testIdMatcher = undefined;
  170. const taskRunner = (0, _tasks.createTaskRunnerForList)(this._config, reporter, 'out-of-process', {
  171. failOnLoadErrors: false
  172. });
  173. const testRun = new _tasks.TestRun(this._config, reporter);
  174. (0, _compilationCache.clearCompilationCache)();
  175. reporter.onConfigure(this._config.config);
  176. const status = await taskRunner.run(testRun, 0);
  177. await reporter.onEnd({
  178. status
  179. });
  180. await reporter.onExit();
  181. const projectDirs = new Set();
  182. const projectOutputs = new Set();
  183. for (const p of this._config.projects) {
  184. projectDirs.add(p.project.testDir);
  185. projectOutputs.add(p.project.outputDir);
  186. }
  187. this._globalWatcher.update([...projectDirs], [...projectOutputs], false);
  188. }
  189. async _runTests(testIds, projects) {
  190. await this._stopTests();
  191. const testIdSet = testIds ? new Set(testIds) : null;
  192. this._config.cliListOnly = false;
  193. this._config.cliProjectFilter = projects.length ? projects : undefined;
  194. this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id);
  195. const reporters = await (0, _reporters.createReporters)(this._config, 'ui');
  196. reporters.push(new _teleEmitter.TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params), true));
  197. const reporter = new _internalReporter.InternalReporter(new _multiplexer.Multiplexer(reporters));
  198. const taskRunner = (0, _tasks.createTaskRunnerForWatch)(this._config, reporter);
  199. const testRun = new _tasks.TestRun(this._config, reporter);
  200. (0, _compilationCache.clearCompilationCache)();
  201. reporter.onConfigure(this._config.config);
  202. const stop = new _utils.ManualPromise();
  203. const run = taskRunner.run(testRun, 0, stop).then(async status => {
  204. await reporter.onEnd({
  205. status
  206. });
  207. await reporter.onExit();
  208. this._testRun = undefined;
  209. this._config.testIdMatcher = undefined;
  210. return status;
  211. });
  212. this._testRun = {
  213. run,
  214. stop
  215. };
  216. await run;
  217. }
  218. _watchFiles(fileNames) {
  219. const files = new Set();
  220. for (const fileName of fileNames) {
  221. files.add(fileName);
  222. (0, _compilationCache.dependenciesForTestFile)(fileName).forEach(file => files.add(file));
  223. }
  224. this._testWatcher.update([...files], [], true);
  225. }
  226. async _stopTests() {
  227. var _this$_testRun, _this$_testRun$stop, _this$_testRun2;
  228. (_this$_testRun = this._testRun) === null || _this$_testRun === void 0 ? void 0 : (_this$_testRun$stop = _this$_testRun.stop) === null || _this$_testRun$stop === void 0 ? void 0 : _this$_testRun$stop.resolve();
  229. await ((_this$_testRun2 = this._testRun) === null || _this$_testRun2 === void 0 ? void 0 : _this$_testRun2.run);
  230. }
  231. }
  232. async function runUIMode(config, options) {
  233. var _uiMode$globalCleanup;
  234. const uiMode = new UIMode(config);
  235. const globalSetupStatus = await uiMode.runGlobalSetup();
  236. if (globalSetupStatus !== 'passed') return globalSetupStatus;
  237. const cancelPromise = new _utils.ManualPromise();
  238. const sigintWatcher = new _sigIntWatcher.SigIntWatcher();
  239. void sigintWatcher.promise().then(() => cancelPromise.resolve());
  240. try {
  241. await uiMode.showUI(options, cancelPromise);
  242. } finally {
  243. sigintWatcher.disarm();
  244. }
  245. return (await ((_uiMode$globalCleanup = uiMode.globalCleanup) === null || _uiMode$globalCleanup === void 0 ? void 0 : _uiMode$globalCleanup.call(uiMode))) || (sigintWatcher.hadSignal() ? 'interrupted' : 'passed');
  246. }
  247. function chunkToPayload(type, chunk) {
  248. if (chunk instanceof Buffer) return {
  249. type,
  250. buffer: chunk.toString('base64')
  251. };
  252. return {
  253. type,
  254. text: chunk
  255. };
  256. }
  257. class Watcher {
  258. constructor(mode, onChange) {
  259. this._onChange = void 0;
  260. this._watchedFiles = [];
  261. this._ignoredFolders = [];
  262. this._collector = [];
  263. this._fsWatcher = void 0;
  264. this._throttleTimer = void 0;
  265. this._mode = void 0;
  266. this._mode = mode;
  267. this._onChange = onChange;
  268. }
  269. update(watchedFiles, ignoredFolders, reportPending) {
  270. var _this$_fsWatcher;
  271. if (JSON.stringify([this._watchedFiles, this._ignoredFolders]) === JSON.stringify(watchedFiles, ignoredFolders)) return;
  272. if (reportPending) this._reportEventsIfAny();
  273. this._watchedFiles = watchedFiles;
  274. this._ignoredFolders = ignoredFolders;
  275. void ((_this$_fsWatcher = this._fsWatcher) === null || _this$_fsWatcher === void 0 ? void 0 : _this$_fsWatcher.close());
  276. this._fsWatcher = undefined;
  277. this._collector.length = 0;
  278. clearTimeout(this._throttleTimer);
  279. this._throttleTimer = undefined;
  280. if (!this._watchedFiles.length) return;
  281. this._fsWatcher = _utilsBundle.chokidar.watch(watchedFiles, {
  282. ignoreInitial: true,
  283. ignored: this._ignoredFolders
  284. }).on('all', async (event, file) => {
  285. if (this._throttleTimer) clearTimeout(this._throttleTimer);
  286. if (this._mode === 'flat' && event !== 'add' && event !== 'change') return;
  287. if (this._mode === 'deep' && event !== 'add' && event !== 'change' && event !== 'unlink' && event !== 'addDir' && event !== 'unlinkDir') return;
  288. this._collector.push({
  289. event,
  290. file
  291. });
  292. this._throttleTimer = setTimeout(() => this._reportEventsIfAny(), 250);
  293. });
  294. }
  295. _reportEventsIfAny() {
  296. if (this._collector.length) this._onChange(this._collector.slice());
  297. this._collector.length = 0;
  298. }
  299. }
  300. function hasSomeBrowsers() {
  301. for (const browserName of ['chromium', 'webkit', 'firefox']) {
  302. try {
  303. _server.registry.findExecutable(browserName).executablePathOrDie('javascript');
  304. return true;
  305. } catch {}
  306. }
  307. return false;
  308. }
  309. async function installBrowsers() {
  310. const executables = _server.registry.defaultExecutables();
  311. await _server.registry.install(executables, false);
  312. }