tab.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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 tab_exports = {};
  20. __export(tab_exports, {
  21. Tab: () => Tab,
  22. TabEvents: () => TabEvents,
  23. renderModalStates: () => renderModalStates
  24. });
  25. module.exports = __toCommonJS(tab_exports);
  26. var import_events = require("events");
  27. var import_utils = require("playwright-core/lib/utils");
  28. var import_utils2 = require("./tools/utils");
  29. var import_log = require("../log");
  30. var import_dialogs = require("./tools/dialogs");
  31. var import_files = require("./tools/files");
  32. var import_transform = require("../../transform/transform");
  33. const TabEvents = {
  34. modalState: "modalState"
  35. };
  36. class Tab extends import_events.EventEmitter {
  37. constructor(context, page, onPageClose) {
  38. super();
  39. this._lastTitle = "about:blank";
  40. this._consoleMessages = [];
  41. this._recentConsoleMessages = [];
  42. this._requests = /* @__PURE__ */ new Set();
  43. this._modalStates = [];
  44. this._downloads = [];
  45. this._needsFullSnapshot = false;
  46. this.context = context;
  47. this.page = page;
  48. this._onPageClose = onPageClose;
  49. page.on("console", (event) => this._handleConsoleMessage(messageToConsoleMessage(event)));
  50. page.on("pageerror", (error) => this._handleConsoleMessage(pageErrorToConsoleMessage(error)));
  51. page.on("request", (request) => this._requests.add(request));
  52. page.on("close", () => this._onClose());
  53. page.on("filechooser", (chooser) => {
  54. this.setModalState({
  55. type: "fileChooser",
  56. description: "File chooser",
  57. fileChooser: chooser,
  58. clearedBy: import_files.uploadFile.schema.name
  59. });
  60. });
  61. page.on("dialog", (dialog) => this._dialogShown(dialog));
  62. page.on("download", (download) => {
  63. void this._downloadStarted(download);
  64. });
  65. page.setDefaultNavigationTimeout(this.context.config.timeouts.navigation);
  66. page.setDefaultTimeout(this.context.config.timeouts.action);
  67. page[tabSymbol] = this;
  68. this.initializedPromise = this._initialize();
  69. }
  70. static forPage(page) {
  71. return page[tabSymbol];
  72. }
  73. static async collectConsoleMessages(page) {
  74. const result = [];
  75. const messages = await page.consoleMessages().catch(() => []);
  76. for (const message of messages)
  77. result.push(messageToConsoleMessage(message));
  78. const errors = await page.pageErrors().catch(() => []);
  79. for (const error of errors)
  80. result.push(pageErrorToConsoleMessage(error));
  81. return result;
  82. }
  83. async _initialize() {
  84. for (const message of await Tab.collectConsoleMessages(this.page))
  85. this._handleConsoleMessage(message);
  86. const requests = await this.page.requests().catch(() => []);
  87. for (const request of requests)
  88. this._requests.add(request);
  89. for (const initPage of this.context.config.browser.initPage || []) {
  90. try {
  91. const { default: func } = await (0, import_transform.requireOrImport)(initPage);
  92. await func({ page: this.page });
  93. } catch (e) {
  94. (0, import_log.logUnhandledError)(e);
  95. }
  96. }
  97. }
  98. modalStates() {
  99. return this._modalStates;
  100. }
  101. setModalState(modalState) {
  102. this._modalStates.push(modalState);
  103. this.emit(TabEvents.modalState, modalState);
  104. }
  105. clearModalState(modalState) {
  106. this._modalStates = this._modalStates.filter((state) => state !== modalState);
  107. }
  108. modalStatesMarkdown() {
  109. return renderModalStates(this.context, this.modalStates());
  110. }
  111. _dialogShown(dialog) {
  112. this.setModalState({
  113. type: "dialog",
  114. description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
  115. dialog,
  116. clearedBy: import_dialogs.handleDialog.schema.name
  117. });
  118. }
  119. async _downloadStarted(download) {
  120. const entry = {
  121. download,
  122. finished: false,
  123. outputFile: await this.context.outputFile(download.suggestedFilename(), { origin: "web", reason: "Saving download" })
  124. };
  125. this._downloads.push(entry);
  126. await download.saveAs(entry.outputFile);
  127. entry.finished = true;
  128. }
  129. _clearCollectedArtifacts() {
  130. this._consoleMessages.length = 0;
  131. this._recentConsoleMessages.length = 0;
  132. this._requests.clear();
  133. }
  134. _handleConsoleMessage(message) {
  135. this._consoleMessages.push(message);
  136. this._recentConsoleMessages.push(message);
  137. }
  138. _onClose() {
  139. this._clearCollectedArtifacts();
  140. this._onPageClose(this);
  141. }
  142. async updateTitle() {
  143. await this._raceAgainstModalStates(async () => {
  144. this._lastTitle = await (0, import_utils2.callOnPageNoTrace)(this.page, (page) => page.title());
  145. });
  146. }
  147. lastTitle() {
  148. return this._lastTitle;
  149. }
  150. isCurrentTab() {
  151. return this === this.context.currentTab();
  152. }
  153. async waitForLoadState(state, options) {
  154. await (0, import_utils2.callOnPageNoTrace)(this.page, (page) => page.waitForLoadState(state, options).catch(import_log.logUnhandledError));
  155. }
  156. async navigate(url) {
  157. this._clearCollectedArtifacts();
  158. const downloadEvent = (0, import_utils2.callOnPageNoTrace)(this.page, (page) => page.waitForEvent("download").catch(import_log.logUnhandledError));
  159. try {
  160. await this.page.goto(url, { waitUntil: "domcontentloaded" });
  161. } catch (_e) {
  162. const e = _e;
  163. const mightBeDownload = e.message.includes("net::ERR_ABORTED") || e.message.includes("Download is starting");
  164. if (!mightBeDownload)
  165. throw e;
  166. const download = await Promise.race([
  167. downloadEvent,
  168. new Promise((resolve) => setTimeout(resolve, 3e3))
  169. ]);
  170. if (!download)
  171. throw e;
  172. await new Promise((resolve) => setTimeout(resolve, 500));
  173. return;
  174. }
  175. await this.waitForLoadState("load", { timeout: 5e3 });
  176. }
  177. async consoleMessages(type) {
  178. await this.initializedPromise;
  179. return this._consoleMessages.filter((message) => type ? message.type === type : true);
  180. }
  181. async requests() {
  182. await this.initializedPromise;
  183. return this._requests;
  184. }
  185. async captureSnapshot() {
  186. let tabSnapshot;
  187. const modalStates = await this._raceAgainstModalStates(async () => {
  188. const snapshot = await this.page._snapshotForAI({ track: "response" });
  189. tabSnapshot = {
  190. url: this.page.url(),
  191. title: await this.page.title(),
  192. ariaSnapshot: snapshot.full,
  193. ariaSnapshotDiff: this._needsFullSnapshot ? void 0 : snapshot.incremental,
  194. modalStates: [],
  195. consoleMessages: [],
  196. downloads: this._downloads
  197. };
  198. });
  199. if (tabSnapshot) {
  200. tabSnapshot.consoleMessages = this._recentConsoleMessages;
  201. this._recentConsoleMessages = [];
  202. }
  203. this._needsFullSnapshot = !tabSnapshot;
  204. return tabSnapshot ?? {
  205. url: this.page.url(),
  206. title: "",
  207. ariaSnapshot: "",
  208. modalStates,
  209. consoleMessages: [],
  210. downloads: []
  211. };
  212. }
  213. _javaScriptBlocked() {
  214. return this._modalStates.some((state) => state.type === "dialog");
  215. }
  216. async _raceAgainstModalStates(action) {
  217. if (this.modalStates().length)
  218. return this.modalStates();
  219. const promise = new import_utils.ManualPromise();
  220. const listener = (modalState) => promise.resolve([modalState]);
  221. this.once(TabEvents.modalState, listener);
  222. return await Promise.race([
  223. action().then(() => {
  224. this.off(TabEvents.modalState, listener);
  225. return [];
  226. }),
  227. promise
  228. ]);
  229. }
  230. async waitForCompletion(callback) {
  231. await this._raceAgainstModalStates(() => (0, import_utils2.waitForCompletion)(this, callback));
  232. }
  233. async refLocator(params) {
  234. return (await this.refLocators([params]))[0];
  235. }
  236. async refLocators(params) {
  237. return Promise.all(params.map(async (param) => {
  238. try {
  239. const locator = this.page.locator(`aria-ref=${param.ref}`).describe(param.element);
  240. const { resolvedSelector } = await locator._resolveSelector();
  241. return { locator, resolved: (0, import_utils.asLocator)("javascript", resolvedSelector) };
  242. } catch (e) {
  243. throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`);
  244. }
  245. }));
  246. }
  247. async waitForTimeout(time) {
  248. if (this._javaScriptBlocked()) {
  249. await new Promise((f) => setTimeout(f, time));
  250. return;
  251. }
  252. await (0, import_utils2.callOnPageNoTrace)(this.page, (page) => {
  253. return page.evaluate(() => new Promise((f) => setTimeout(f, 1e3)));
  254. });
  255. }
  256. }
  257. function messageToConsoleMessage(message) {
  258. return {
  259. type: message.type(),
  260. text: message.text(),
  261. toString: () => `[${message.type().toUpperCase()}] ${message.text()} @ ${message.location().url}:${message.location().lineNumber}`
  262. };
  263. }
  264. function pageErrorToConsoleMessage(errorOrValue) {
  265. if (errorOrValue instanceof Error) {
  266. return {
  267. type: "error",
  268. text: errorOrValue.message,
  269. toString: () => errorOrValue.stack || errorOrValue.message
  270. };
  271. }
  272. return {
  273. type: "error",
  274. text: String(errorOrValue),
  275. toString: () => String(errorOrValue)
  276. };
  277. }
  278. function renderModalStates(context, modalStates) {
  279. const result = ["### Modal state"];
  280. if (modalStates.length === 0)
  281. result.push("- There is no modal state present");
  282. for (const state of modalStates)
  283. result.push(`- [${state.description}]: can be handled by the "${state.clearedBy}" tool`);
  284. return result;
  285. }
  286. const tabSymbol = Symbol("tabSymbol");
  287. // Annotate the CommonJS export names for ESM import in node:
  288. 0 && (module.exports = {
  289. Tab,
  290. TabEvents,
  291. renderModalStates
  292. });