debugController.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.DebugController = void 0;
  6. var _processLauncher = require("../utils/processLauncher");
  7. var _instrumentation = require("./instrumentation");
  8. var _recorder = require("./recorder");
  9. var _recorderApp = require("./recorder/recorderApp");
  10. var _locatorGenerators = require("../utils/isomorphic/locatorGenerators");
  11. /**
  12. * Copyright (c) Microsoft Corporation.
  13. *
  14. * Licensed under the Apache License, Version 2.0 (the "License");
  15. * you may not use this file except in compliance with the License.
  16. * You may obtain a copy of the License at
  17. *
  18. * http://www.apache.org/licenses/LICENSE-2.0
  19. *
  20. * Unless required by applicable law or agreed to in writing, software
  21. * distributed under the License is distributed on an "AS IS" BASIS,
  22. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23. * See the License for the specific language governing permissions and
  24. * limitations under the License.
  25. */
  26. const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
  27. class DebugController extends _instrumentation.SdkObject {
  28. constructor(playwright) {
  29. super({
  30. attribution: {
  31. isInternalPlaywright: true
  32. },
  33. instrumentation: (0, _instrumentation.createInstrumentation)()
  34. }, undefined, 'DebugController');
  35. this._autoCloseTimer = void 0;
  36. // TODO: remove in 1.27
  37. this._autoCloseAllowed = false;
  38. this._trackHierarchyListener = void 0;
  39. this._playwright = void 0;
  40. this._sdkLanguage = 'javascript';
  41. this._codegenId = 'playwright-test';
  42. this._playwright = playwright;
  43. }
  44. initialize(codegenId, sdkLanguage) {
  45. this._codegenId = codegenId;
  46. this._sdkLanguage = sdkLanguage;
  47. _recorder.Recorder.setAppFactory(async () => new InspectingRecorderApp(this));
  48. }
  49. setAutoCloseAllowed(allowed) {
  50. this._autoCloseAllowed = allowed;
  51. }
  52. dispose() {
  53. this.setReportStateChanged(false);
  54. this.setAutoCloseAllowed(false);
  55. _recorder.Recorder.setAppFactory(undefined);
  56. }
  57. setReportStateChanged(enabled) {
  58. if (enabled && !this._trackHierarchyListener) {
  59. this._trackHierarchyListener = {
  60. onPageOpen: () => this._emitSnapshot(),
  61. onPageClose: () => this._emitSnapshot()
  62. };
  63. this._playwright.instrumentation.addListener(this._trackHierarchyListener, null);
  64. } else if (!enabled && this._trackHierarchyListener) {
  65. this._playwright.instrumentation.removeListener(this._trackHierarchyListener);
  66. this._trackHierarchyListener = undefined;
  67. }
  68. }
  69. async resetForReuse() {
  70. const contexts = new Set();
  71. for (const page of this._playwright.allPages()) contexts.add(page.context());
  72. for (const context of contexts) await context.resetForReuse(internalMetadata, null);
  73. }
  74. async navigate(url) {
  75. for (const p of this._playwright.allPages()) await p.mainFrame().goto(internalMetadata, url);
  76. }
  77. async setRecorderMode(params) {
  78. // TODO: |file| is only used in the legacy mode.
  79. await this._closeBrowsersWithoutPages();
  80. if (params.mode === 'none') {
  81. for (const recorder of await this._allRecorders()) {
  82. recorder.hideHighlightedSelector();
  83. recorder.setMode('none');
  84. }
  85. this.setAutoCloseEnabled(true);
  86. return;
  87. }
  88. if (!this._playwright.allBrowsers().length) await this._playwright.chromium.launch(internalMetadata, {
  89. headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS
  90. });
  91. // Create page if none.
  92. const pages = this._playwright.allPages();
  93. if (!pages.length) {
  94. const [browser] = this._playwright.allBrowsers();
  95. const {
  96. context
  97. } = await browser.newContextForReuse({}, internalMetadata);
  98. await context.newPage(internalMetadata);
  99. }
  100. // Update test id attribute.
  101. if (params.testIdAttributeName) {
  102. for (const page of this._playwright.allPages()) page.context().selectors().setTestIdAttributeName(params.testIdAttributeName);
  103. }
  104. // Toggle the mode.
  105. for (const recorder of await this._allRecorders()) {
  106. recorder.hideHighlightedSelector();
  107. if (params.mode !== 'inspecting') recorder.setOutput(this._codegenId, params.file);
  108. recorder.setMode(params.mode);
  109. }
  110. this.setAutoCloseEnabled(true);
  111. }
  112. async setAutoCloseEnabled(enabled) {
  113. if (!this._autoCloseAllowed) return;
  114. if (this._autoCloseTimer) clearTimeout(this._autoCloseTimer);
  115. if (!enabled) return;
  116. const heartBeat = () => {
  117. if (!this._playwright.allPages().length) (0, _processLauncher.gracefullyProcessExitDoNotHang)(0);else this._autoCloseTimer = setTimeout(heartBeat, 5000);
  118. };
  119. this._autoCloseTimer = setTimeout(heartBeat, 30000);
  120. }
  121. async highlight(selector) {
  122. for (const recorder of await this._allRecorders()) recorder.setHighlightedSelector(this._sdkLanguage, selector);
  123. }
  124. async hideHighlight() {
  125. // Hide all active recorder highlights.
  126. for (const recorder of await this._allRecorders()) recorder.hideHighlightedSelector();
  127. // Hide all locator.highlight highlights.
  128. await this._playwright.hideHighlight();
  129. }
  130. allBrowsers() {
  131. return [...this._playwright.allBrowsers()];
  132. }
  133. async resume() {
  134. for (const recorder of await this._allRecorders()) recorder.resume();
  135. }
  136. async kill() {
  137. (0, _processLauncher.gracefullyProcessExitDoNotHang)(0);
  138. }
  139. async closeAllBrowsers() {
  140. await Promise.all(this.allBrowsers().map(browser => browser.close({
  141. reason: 'Close all browsers requested'
  142. })));
  143. }
  144. _emitSnapshot() {
  145. const browsers = [];
  146. let pageCount = 0;
  147. for (const browser of this._playwright.allBrowsers()) {
  148. const b = {
  149. contexts: []
  150. };
  151. browsers.push(b);
  152. for (const context of browser.contexts()) {
  153. const c = {
  154. pages: []
  155. };
  156. b.contexts.push(c);
  157. for (const page of context.pages()) c.pages.push(page.mainFrame().url());
  158. pageCount += context.pages().length;
  159. }
  160. }
  161. // TODO: browsers is deprecated, remove it.
  162. this.emit(DebugController.Events.BrowsersChanged, browsers);
  163. this.emit(DebugController.Events.StateChanged, {
  164. pageCount
  165. });
  166. }
  167. async _allRecorders() {
  168. const contexts = new Set();
  169. for (const page of this._playwright.allPages()) contexts.add(page.context());
  170. const result = await Promise.all([...contexts].map(c => _recorder.Recorder.show(c, {
  171. omitCallTracking: true
  172. })));
  173. return result.filter(Boolean);
  174. }
  175. async _closeBrowsersWithoutPages() {
  176. for (const browser of this._playwright.allBrowsers()) {
  177. for (const context of browser.contexts()) {
  178. if (!context.pages().length) await context.close({
  179. reason: 'Browser collected'
  180. });
  181. }
  182. if (!browser.contexts()) await browser.close({
  183. reason: 'Browser collected'
  184. });
  185. }
  186. }
  187. }
  188. exports.DebugController = DebugController;
  189. DebugController.Events = {
  190. BrowsersChanged: 'browsersChanged',
  191. StateChanged: 'stateChanged',
  192. InspectRequested: 'inspectRequested',
  193. SourceChanged: 'sourceChanged',
  194. Paused: 'paused',
  195. SetModeRequested: 'setModeRequested'
  196. };
  197. class InspectingRecorderApp extends _recorderApp.EmptyRecorderApp {
  198. constructor(debugController) {
  199. super();
  200. this._debugController = void 0;
  201. this._debugController = debugController;
  202. }
  203. async setSelector(selector) {
  204. const locator = (0, _locatorGenerators.asLocator)(this._debugController._sdkLanguage, selector);
  205. this._debugController.emit(DebugController.Events.InspectRequested, {
  206. selector,
  207. locator
  208. });
  209. }
  210. async setSources(sources) {
  211. const source = sources.find(s => s.id === this._debugController._codegenId);
  212. const {
  213. text,
  214. header,
  215. footer,
  216. actions
  217. } = source || {
  218. text: ''
  219. };
  220. this._debugController.emit(DebugController.Events.SourceChanged, {
  221. text,
  222. header,
  223. footer,
  224. actions
  225. });
  226. }
  227. async setPaused(paused) {
  228. this._debugController.emit(DebugController.Events.Paused, {
  229. paused
  230. });
  231. }
  232. async setMode(mode) {
  233. this._debugController.emit(DebugController.Events.SetModeRequested, {
  234. mode
  235. });
  236. }
  237. }