debugController.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. "use strict";
  2. var __defProp = Object.defineProperty;
  3. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  4. var __getOwnPropNames = Object.getOwnPropertyNames;
  5. var __hasOwnProp = Object.prototype.hasOwnProperty;
  6. var __export = (target, all) => {
  7. for (var name in all)
  8. __defProp(target, name, { get: all[name], enumerable: true });
  9. };
  10. var __copyProps = (to, from, except, desc) => {
  11. if (from && typeof from === "object" || typeof from === "function") {
  12. for (let key of __getOwnPropNames(from))
  13. if (!__hasOwnProp.call(to, key) && key !== except)
  14. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  15. }
  16. return to;
  17. };
  18. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  19. var debugController_exports = {};
  20. __export(debugController_exports, {
  21. DebugController: () => DebugController
  22. });
  23. module.exports = __toCommonJS(debugController_exports);
  24. var import_instrumentation = require("./instrumentation");
  25. var import_processLauncher = require("./utils/processLauncher");
  26. var import_recorder = require("./recorder");
  27. var import_utils = require("../utils");
  28. var import_ariaSnapshot = require("../utils/isomorphic/ariaSnapshot");
  29. var import_utilsBundle = require("../utilsBundle");
  30. var import_locatorParser = require("../utils/isomorphic/locatorParser");
  31. var import_language = require("./codegen/language");
  32. var import_recorderUtils = require("./recorder/recorderUtils");
  33. var import_javascript = require("./codegen/javascript");
  34. var import_frames = require("./frames");
  35. var import_page = require("./page");
  36. class DebugController extends import_instrumentation.SdkObject {
  37. constructor(playwright) {
  38. super({ attribution: { isInternalPlaywright: true }, instrumentation: (0, import_instrumentation.createInstrumentation)() }, void 0, "DebugController");
  39. this._reportState = false;
  40. this._disposeListeners = /* @__PURE__ */ new Set();
  41. this._sdkLanguage = "javascript";
  42. this._generateAutoExpect = false;
  43. this._playwright = playwright;
  44. }
  45. static {
  46. this.Events = {
  47. StateChanged: "stateChanged",
  48. InspectRequested: "inspectRequested",
  49. SourceChanged: "sourceChanged",
  50. Paused: "paused",
  51. SetModeRequested: "setModeRequested"
  52. };
  53. }
  54. initialize(codegenId, sdkLanguage) {
  55. this._sdkLanguage = sdkLanguage;
  56. }
  57. dispose() {
  58. this.setReportStateChanged(false);
  59. }
  60. setReportStateChanged(enabled) {
  61. if (this._reportState === enabled)
  62. return;
  63. this._reportState = enabled;
  64. if (enabled) {
  65. const listener = {
  66. onPageOpen: (page) => {
  67. this._emitSnapshot(false);
  68. const handleNavigation = () => this._emitSnapshot(false);
  69. page.mainFrame().on(import_frames.Frame.Events.InternalNavigation, handleNavigation);
  70. const dispose = () => page.mainFrame().off(import_frames.Frame.Events.InternalNavigation, handleNavigation);
  71. this._disposeListeners.add(dispose);
  72. page.on(import_page.Page.Events.Close, () => this._disposeListeners.delete(dispose));
  73. },
  74. onPageClose: () => this._emitSnapshot(false)
  75. };
  76. this._playwright.instrumentation.addListener(listener, null);
  77. this._disposeListeners.add(() => this._playwright.instrumentation.removeListener(listener));
  78. this._emitSnapshot(true);
  79. } else {
  80. for (const dispose of this._disposeListeners)
  81. dispose();
  82. this._disposeListeners.clear();
  83. }
  84. }
  85. async setRecorderMode(progress, params) {
  86. await progress.race(this._closeBrowsersWithoutPages());
  87. this._generateAutoExpect = !!params.generateAutoExpect;
  88. if (params.mode === "none") {
  89. for (const recorder of await progress.race(this._allRecorders())) {
  90. recorder.hideHighlightedSelector();
  91. recorder.setMode("none");
  92. }
  93. return;
  94. }
  95. if (!this._playwright.allBrowsers().length)
  96. await this._playwright.chromium.launch(progress, { headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS });
  97. const pages = this._playwright.allPages();
  98. if (!pages.length) {
  99. const [browser] = this._playwright.allBrowsers();
  100. const context = await browser.newContextForReuse(progress, {});
  101. await context.newPage(progress);
  102. }
  103. if (params.testIdAttributeName) {
  104. for (const page of this._playwright.allPages())
  105. page.browserContext.selectors().setTestIdAttributeName(params.testIdAttributeName);
  106. }
  107. for (const recorder of await progress.race(this._allRecorders())) {
  108. recorder.hideHighlightedSelector();
  109. recorder.setMode(params.mode);
  110. }
  111. }
  112. async highlight(progress, params) {
  113. if (params.selector)
  114. (0, import_locatorParser.unsafeLocatorOrSelectorAsSelector)(this._sdkLanguage, params.selector, "data-testid");
  115. const ariaTemplate = params.ariaTemplate ? (0, import_ariaSnapshot.parseAriaSnapshotUnsafe)(import_utilsBundle.yaml, params.ariaTemplate) : void 0;
  116. for (const recorder of await progress.race(this._allRecorders())) {
  117. if (ariaTemplate)
  118. recorder.setHighlightedAriaTemplate(ariaTemplate);
  119. else if (params.selector)
  120. recorder.setHighlightedSelector(params.selector);
  121. }
  122. }
  123. async hideHighlight(progress) {
  124. for (const recorder of await progress.race(this._allRecorders()))
  125. recorder.hideHighlightedSelector();
  126. await Promise.all(this._playwright.allPages().map((p) => p.hideHighlight().catch(() => {
  127. })));
  128. }
  129. async resume(progress) {
  130. for (const recorder of await progress.race(this._allRecorders()))
  131. recorder.resume();
  132. }
  133. kill() {
  134. (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0);
  135. }
  136. _emitSnapshot(initial) {
  137. const pageCount = this._playwright.allPages().length;
  138. if (initial && !pageCount)
  139. return;
  140. this.emit(DebugController.Events.StateChanged, {
  141. pageCount,
  142. browsers: this._playwright.allBrowsers().map((browser) => ({
  143. id: browser.guid,
  144. name: browser.options.name,
  145. channel: browser.options.channel,
  146. contexts: browser.contexts().map((context) => ({
  147. pages: context.pages().map((page) => ({
  148. url: page.mainFrame().url()
  149. }))
  150. }))
  151. }))
  152. });
  153. }
  154. async _allRecorders() {
  155. const contexts = /* @__PURE__ */ new Set();
  156. for (const page of this._playwright.allPages())
  157. contexts.add(page.browserContext);
  158. const recorders = await Promise.all([...contexts].map((c) => import_recorder.Recorder.forContext(c, { omitCallTracking: true })));
  159. const nonNullRecorders = recorders.filter(Boolean);
  160. for (const recorder of recorders)
  161. wireListeners(recorder, this);
  162. return nonNullRecorders;
  163. }
  164. async _closeBrowsersWithoutPages() {
  165. for (const browser of this._playwright.allBrowsers()) {
  166. for (const context of browser.contexts()) {
  167. if (!context.pages().length)
  168. await context.close({ reason: "Browser collected" });
  169. }
  170. if (!browser.contexts())
  171. await browser.close({ reason: "Browser collected" });
  172. }
  173. }
  174. }
  175. const wiredSymbol = Symbol("wired");
  176. function wireListeners(recorder, debugController) {
  177. if (recorder[wiredSymbol])
  178. return;
  179. recorder[wiredSymbol] = true;
  180. const actions = [];
  181. const languageGenerator = new import_javascript.JavaScriptLanguageGenerator(
  182. /* isPlaywrightTest */
  183. true
  184. );
  185. const actionsChanged = () => {
  186. const aa = (0, import_recorderUtils.collapseActions)(actions);
  187. const { header, footer, text, actionTexts } = (0, import_language.generateCode)(aa, languageGenerator, {
  188. browserName: "chromium",
  189. launchOptions: {},
  190. contextOptions: {},
  191. generateAutoExpect: debugController._generateAutoExpect
  192. });
  193. debugController.emit(DebugController.Events.SourceChanged, { text, header, footer, actions: actionTexts });
  194. };
  195. recorder.on(import_recorder.RecorderEvent.ElementPicked, (elementInfo) => {
  196. const locator = (0, import_utils.asLocator)(debugController._sdkLanguage, elementInfo.selector);
  197. debugController.emit(DebugController.Events.InspectRequested, { selector: elementInfo.selector, locator, ariaSnapshot: elementInfo.ariaSnapshot });
  198. });
  199. recorder.on(import_recorder.RecorderEvent.PausedStateChanged, (paused) => {
  200. debugController.emit(DebugController.Events.Paused, { paused });
  201. });
  202. recorder.on(import_recorder.RecorderEvent.ModeChanged, (mode) => {
  203. debugController.emit(DebugController.Events.SetModeRequested, { mode });
  204. });
  205. recorder.on(import_recorder.RecorderEvent.ActionAdded, (action) => {
  206. actions.push(action);
  207. actionsChanged();
  208. });
  209. recorder.on(import_recorder.RecorderEvent.SignalAdded, (signal) => {
  210. const lastAction = actions.findLast((a) => a.frame.pageGuid === signal.frame.pageGuid);
  211. if (lastAction)
  212. lastAction.action.signals.push(signal.signal);
  213. actionsChanged();
  214. });
  215. }
  216. // Annotate the CommonJS export names for ESM import in node:
  217. 0 && (module.exports = {
  218. DebugController
  219. });