page.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  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 page_exports = {};
  30. __export(page_exports, {
  31. InitScript: () => InitScript,
  32. Page: () => Page,
  33. PageBinding: () => PageBinding,
  34. Worker: () => Worker,
  35. WorkerEvent: () => WorkerEvent
  36. });
  37. module.exports = __toCommonJS(page_exports);
  38. var import_browserContext = require("./browserContext");
  39. var import_console = require("./console");
  40. var import_errors = require("./errors");
  41. var import_fileChooser = require("./fileChooser");
  42. var frames = __toESM(require("./frames"));
  43. var import_helper = require("./helper");
  44. var input = __toESM(require("./input"));
  45. var import_instrumentation = require("./instrumentation");
  46. var js = __toESM(require("./javascript"));
  47. var import_screenshotter = require("./screenshotter");
  48. var import_utils = require("../utils");
  49. var import_utils2 = require("../utils");
  50. var import_comparators = require("./utils/comparators");
  51. var import_debugLogger = require("./utils/debugLogger");
  52. var import_selectorParser = require("../utils/isomorphic/selectorParser");
  53. var import_manualPromise = require("../utils/isomorphic/manualPromise");
  54. var import_utilityScriptSerializers = require("../utils/isomorphic/utilityScriptSerializers");
  55. var import_callLog = require("./callLog");
  56. var rawBindingsControllerSource = __toESM(require("../generated/bindingsControllerSource"));
  57. var import_screencast = require("./screencast");
  58. const PageEvent = {
  59. Close: "close",
  60. Crash: "crash",
  61. Download: "download",
  62. EmulatedSizeChanged: "emulatedsizechanged",
  63. FileChooser: "filechooser",
  64. FrameAttached: "frameattached",
  65. FrameDetached: "framedetached",
  66. InternalFrameNavigatedToNewDocument: "internalframenavigatedtonewdocument",
  67. LocatorHandlerTriggered: "locatorhandlertriggered",
  68. ScreencastFrame: "screencastframe",
  69. Video: "video",
  70. WebSocket: "websocket",
  71. Worker: "worker"
  72. };
  73. class Page extends import_instrumentation.SdkObject {
  74. constructor(delegate, browserContext) {
  75. super(browserContext, "page");
  76. this._closedState = "open";
  77. this._closedPromise = new import_manualPromise.ManualPromise();
  78. this._initializedPromise = new import_manualPromise.ManualPromise();
  79. this._consoleMessages = [];
  80. this._pageErrors = [];
  81. this._crashed = false;
  82. this.openScope = new import_utils.LongStandingScope();
  83. this._emulatedMedia = {};
  84. this._fileChooserInterceptedBy = /* @__PURE__ */ new Set();
  85. this._pageBindings = /* @__PURE__ */ new Map();
  86. this.initScripts = [];
  87. this._workers = /* @__PURE__ */ new Map();
  88. this.requestInterceptors = [];
  89. this.video = null;
  90. this._locatorHandlers = /* @__PURE__ */ new Map();
  91. this._lastLocatorHandlerUid = 0;
  92. this._locatorHandlerRunningCounter = 0;
  93. this._networkRequests = [];
  94. this.attribution.page = this;
  95. this.delegate = delegate;
  96. this.browserContext = browserContext;
  97. this.keyboard = new input.Keyboard(delegate.rawKeyboard, this);
  98. this.mouse = new input.Mouse(delegate.rawMouse, this);
  99. this.touchscreen = new input.Touchscreen(delegate.rawTouchscreen, this);
  100. this.screenshotter = new import_screenshotter.Screenshotter(this);
  101. this.frameManager = new frames.FrameManager(this);
  102. this.screencast = new import_screencast.Screencast(this);
  103. if (delegate.pdf)
  104. this.pdf = delegate.pdf.bind(delegate);
  105. this.coverage = delegate.coverage ? delegate.coverage() : null;
  106. this.isStorageStatePage = browserContext.isCreatingStorageStatePage();
  107. }
  108. static {
  109. this.Events = PageEvent;
  110. }
  111. async reportAsNew(opener, error) {
  112. if (opener) {
  113. const openerPageOrError = await opener.waitForInitializedOrError();
  114. if (openerPageOrError instanceof Page && !openerPageOrError.isClosed())
  115. this._opener = openerPageOrError;
  116. }
  117. this._markInitialized(error);
  118. }
  119. _markInitialized(error = void 0) {
  120. if (error) {
  121. if (this.browserContext.isClosingOrClosed())
  122. return;
  123. this.frameManager.createDummyMainFrameIfNeeded();
  124. }
  125. this._initialized = error || this;
  126. this.emitOnContext(import_browserContext.BrowserContext.Events.Page, this);
  127. for (const pageError of this._pageErrors)
  128. this.emitOnContext(import_browserContext.BrowserContext.Events.PageError, pageError, this);
  129. for (const message of this._consoleMessages)
  130. this.emitOnContext(import_browserContext.BrowserContext.Events.Console, message);
  131. if (this.isClosed())
  132. this.emit(Page.Events.Close);
  133. else
  134. this.instrumentation.onPageOpen(this);
  135. this._initializedPromise.resolve(this._initialized);
  136. }
  137. initializedOrUndefined() {
  138. return this._initialized ? this : void 0;
  139. }
  140. waitForInitializedOrError() {
  141. return this._initializedPromise;
  142. }
  143. emitOnContext(event, ...args) {
  144. if (this.isStorageStatePage)
  145. return;
  146. this.browserContext.emit(event, ...args);
  147. }
  148. async resetForReuse(progress) {
  149. await this.mainFrame().gotoImpl(progress, "about:blank", {});
  150. this._emulatedSize = void 0;
  151. this._emulatedMedia = {};
  152. this._extraHTTPHeaders = void 0;
  153. await Promise.all([
  154. this.delegate.updateEmulatedViewportSize(),
  155. this.delegate.updateEmulateMedia(),
  156. this.delegate.updateExtraHTTPHeaders()
  157. ]);
  158. await this.delegate.resetForReuse(progress);
  159. }
  160. _didClose() {
  161. this.frameManager.dispose();
  162. this.screencast.stopFrameThrottler();
  163. (0, import_utils.assert)(this._closedState !== "closed", "Page closed twice");
  164. this._closedState = "closed";
  165. this.emit(Page.Events.Close);
  166. this._closedPromise.resolve();
  167. this.instrumentation.onPageClose(this);
  168. this.openScope.close(new import_errors.TargetClosedError(this.closeReason()));
  169. }
  170. _didCrash() {
  171. this.frameManager.dispose();
  172. this.screencast.stopFrameThrottler();
  173. this.emit(Page.Events.Crash);
  174. this._crashed = true;
  175. this.instrumentation.onPageClose(this);
  176. this.openScope.close(new Error("Page crashed"));
  177. }
  178. async _onFileChooserOpened(handle) {
  179. let multiple;
  180. try {
  181. multiple = await handle.evaluate((element) => !!element.multiple);
  182. } catch (e) {
  183. return;
  184. }
  185. if (!this.listenerCount(Page.Events.FileChooser)) {
  186. handle.dispose();
  187. return;
  188. }
  189. const fileChooser = new import_fileChooser.FileChooser(this, handle, multiple);
  190. this.emit(Page.Events.FileChooser, fileChooser);
  191. }
  192. opener() {
  193. return this._opener;
  194. }
  195. mainFrame() {
  196. return this.frameManager.mainFrame();
  197. }
  198. frames() {
  199. return this.frameManager.frames();
  200. }
  201. async exposeBinding(progress, name, needsHandle, playwrightBinding) {
  202. if (this._pageBindings.has(name))
  203. throw new Error(`Function "${name}" has been already registered`);
  204. if (this.browserContext._pageBindings.has(name))
  205. throw new Error(`Function "${name}" has been already registered in the browser context`);
  206. await progress.race(this.browserContext.exposePlaywrightBindingIfNeeded());
  207. const binding = new PageBinding(name, playwrightBinding, needsHandle);
  208. this._pageBindings.set(name, binding);
  209. try {
  210. await progress.race(this.delegate.addInitScript(binding.initScript));
  211. await progress.race(this.safeNonStallingEvaluateInAllFrames(binding.initScript.source, "main"));
  212. return binding;
  213. } catch (error) {
  214. this._pageBindings.delete(name);
  215. throw error;
  216. }
  217. }
  218. async removeExposedBindings(bindings) {
  219. bindings = bindings.filter((binding) => this._pageBindings.get(binding.name) === binding);
  220. for (const binding of bindings)
  221. this._pageBindings.delete(binding.name);
  222. await this.delegate.removeInitScripts(bindings.map((binding) => binding.initScript));
  223. const cleanup = bindings.map((binding) => `{ ${binding.cleanupScript} };
  224. `).join("");
  225. await this.safeNonStallingEvaluateInAllFrames(cleanup, "main");
  226. }
  227. async setExtraHTTPHeaders(progress, headers) {
  228. const oldHeaders = this._extraHTTPHeaders;
  229. try {
  230. this._extraHTTPHeaders = headers;
  231. await progress.race(this.delegate.updateExtraHTTPHeaders());
  232. } catch (error) {
  233. this._extraHTTPHeaders = oldHeaders;
  234. this.delegate.updateExtraHTTPHeaders().catch(() => {
  235. });
  236. throw error;
  237. }
  238. }
  239. extraHTTPHeaders() {
  240. return this._extraHTTPHeaders;
  241. }
  242. addNetworkRequest(request) {
  243. this._networkRequests.push(request);
  244. ensureArrayLimit(this._networkRequests, 100);
  245. }
  246. networkRequests() {
  247. return this._networkRequests;
  248. }
  249. async onBindingCalled(payload, context) {
  250. if (this._closedState === "closed")
  251. return;
  252. await PageBinding.dispatch(this, payload, context);
  253. }
  254. addConsoleMessage(worker, type, args, location, text) {
  255. const message = new import_console.ConsoleMessage(this, worker, type, text, args, location);
  256. const intercepted = this.frameManager.interceptConsoleMessage(message);
  257. if (intercepted) {
  258. args.forEach((arg) => arg.dispose());
  259. return;
  260. }
  261. this._consoleMessages.push(message);
  262. ensureArrayLimit(this._consoleMessages, 200);
  263. if (this._initialized)
  264. this.emitOnContext(import_browserContext.BrowserContext.Events.Console, message);
  265. }
  266. consoleMessages() {
  267. return this._consoleMessages;
  268. }
  269. addPageError(pageError) {
  270. this._pageErrors.push(pageError);
  271. ensureArrayLimit(this._pageErrors, 200);
  272. if (this._initialized)
  273. this.emitOnContext(import_browserContext.BrowserContext.Events.PageError, pageError, this);
  274. }
  275. pageErrors() {
  276. return this._pageErrors;
  277. }
  278. async reload(progress, options) {
  279. return this.mainFrame().raceNavigationAction(progress, async () => {
  280. const [response] = await Promise.all([
  281. // Reload must be a new document, and should not be confused with a stray pushState.
  282. this.mainFrame()._waitForNavigation(progress, true, options),
  283. progress.race(this.delegate.reload())
  284. ]);
  285. return response;
  286. });
  287. }
  288. async goBack(progress, options) {
  289. return this.mainFrame().raceNavigationAction(progress, async () => {
  290. let error;
  291. const waitPromise = this.mainFrame()._waitForNavigation(progress, false, options).catch((e) => {
  292. error = e;
  293. return null;
  294. });
  295. const result = await progress.race(this.delegate.goBack());
  296. if (!result) {
  297. waitPromise.catch(() => {
  298. });
  299. return null;
  300. }
  301. const response = await waitPromise;
  302. if (error)
  303. throw error;
  304. return response;
  305. });
  306. }
  307. async goForward(progress, options) {
  308. return this.mainFrame().raceNavigationAction(progress, async () => {
  309. let error;
  310. const waitPromise = this.mainFrame()._waitForNavigation(progress, false, options).catch((e) => {
  311. error = e;
  312. return null;
  313. });
  314. const result = await progress.race(this.delegate.goForward());
  315. if (!result) {
  316. waitPromise.catch(() => {
  317. });
  318. return null;
  319. }
  320. const response = await waitPromise;
  321. if (error)
  322. throw error;
  323. return response;
  324. });
  325. }
  326. requestGC() {
  327. return this.delegate.requestGC();
  328. }
  329. registerLocatorHandler(selector, noWaitAfter) {
  330. const uid = ++this._lastLocatorHandlerUid;
  331. this._locatorHandlers.set(uid, { selector, noWaitAfter });
  332. return uid;
  333. }
  334. resolveLocatorHandler(uid, remove) {
  335. const handler = this._locatorHandlers.get(uid);
  336. if (remove)
  337. this._locatorHandlers.delete(uid);
  338. if (handler) {
  339. handler.resolved?.resolve();
  340. handler.resolved = void 0;
  341. }
  342. }
  343. unregisterLocatorHandler(uid) {
  344. this._locatorHandlers.delete(uid);
  345. }
  346. async performActionPreChecks(progress) {
  347. await this._performWaitForNavigationCheck(progress);
  348. await this._performLocatorHandlersCheckpoint(progress);
  349. await this._performWaitForNavigationCheck(progress);
  350. }
  351. async _performWaitForNavigationCheck(progress) {
  352. if (process.env.PLAYWRIGHT_SKIP_NAVIGATION_CHECK)
  353. return;
  354. const mainFrame = this.frameManager.mainFrame();
  355. if (!mainFrame || !mainFrame.pendingDocument())
  356. return;
  357. const url = mainFrame.pendingDocument()?.request?.url();
  358. const toUrl = url ? `" ${(0, import_utils.trimStringWithEllipsis)(url, 200)}"` : "";
  359. progress.log(` waiting for${toUrl} navigation to finish...`);
  360. await import_helper.helper.waitForEvent(progress, mainFrame, frames.Frame.Events.InternalNavigation, (e) => {
  361. if (!e.isPublic)
  362. return false;
  363. if (!e.error)
  364. progress.log(` navigated to "${(0, import_utils.trimStringWithEllipsis)(mainFrame.url(), 200)}"`);
  365. return true;
  366. }).promise;
  367. }
  368. async _performLocatorHandlersCheckpoint(progress) {
  369. if (this._locatorHandlerRunningCounter)
  370. return;
  371. for (const [uid, handler] of this._locatorHandlers) {
  372. if (!handler.resolved) {
  373. if (await this.mainFrame().isVisibleInternal(progress, handler.selector, { strict: true })) {
  374. handler.resolved = new import_manualPromise.ManualPromise();
  375. this.emit(Page.Events.LocatorHandlerTriggered, uid);
  376. }
  377. }
  378. if (handler.resolved) {
  379. ++this._locatorHandlerRunningCounter;
  380. progress.log(` found ${(0, import_utils2.asLocator)(this.browserContext._browser.sdkLanguage(), handler.selector)}, intercepting action to run the handler`);
  381. const promise = handler.resolved.then(async () => {
  382. if (!handler.noWaitAfter) {
  383. progress.log(` locator handler has finished, waiting for ${(0, import_utils2.asLocator)(this.browserContext._browser.sdkLanguage(), handler.selector)} to be hidden`);
  384. await this.mainFrame().waitForSelector(progress, handler.selector, false, { state: "hidden" });
  385. } else {
  386. progress.log(` locator handler has finished`);
  387. }
  388. });
  389. await progress.race(this.openScope.race(promise)).finally(() => --this._locatorHandlerRunningCounter);
  390. progress.log(` interception handler has finished, continuing`);
  391. }
  392. }
  393. }
  394. async emulateMedia(progress, options) {
  395. const oldEmulatedMedia = { ...this._emulatedMedia };
  396. if (options.media !== void 0)
  397. this._emulatedMedia.media = options.media;
  398. if (options.colorScheme !== void 0)
  399. this._emulatedMedia.colorScheme = options.colorScheme;
  400. if (options.reducedMotion !== void 0)
  401. this._emulatedMedia.reducedMotion = options.reducedMotion;
  402. if (options.forcedColors !== void 0)
  403. this._emulatedMedia.forcedColors = options.forcedColors;
  404. if (options.contrast !== void 0)
  405. this._emulatedMedia.contrast = options.contrast;
  406. try {
  407. await progress.race(this.delegate.updateEmulateMedia());
  408. } catch (error) {
  409. this._emulatedMedia = oldEmulatedMedia;
  410. this.delegate.updateEmulateMedia().catch(() => {
  411. });
  412. throw error;
  413. }
  414. }
  415. emulatedMedia() {
  416. const contextOptions = this.browserContext._options;
  417. return {
  418. media: this._emulatedMedia.media || "no-override",
  419. colorScheme: this._emulatedMedia.colorScheme !== void 0 ? this._emulatedMedia.colorScheme : contextOptions.colorScheme ?? "light",
  420. reducedMotion: this._emulatedMedia.reducedMotion !== void 0 ? this._emulatedMedia.reducedMotion : contextOptions.reducedMotion ?? "no-preference",
  421. forcedColors: this._emulatedMedia.forcedColors !== void 0 ? this._emulatedMedia.forcedColors : contextOptions.forcedColors ?? "none",
  422. contrast: this._emulatedMedia.contrast !== void 0 ? this._emulatedMedia.contrast : contextOptions.contrast ?? "no-preference"
  423. };
  424. }
  425. async setViewportSize(progress, viewportSize) {
  426. const oldEmulatedSize = this._emulatedSize;
  427. try {
  428. this._setEmulatedSize({ viewport: { ...viewportSize }, screen: { ...viewportSize } });
  429. await progress.race(this.delegate.updateEmulatedViewportSize());
  430. } catch (error) {
  431. this._emulatedSize = oldEmulatedSize;
  432. this.delegate.updateEmulatedViewportSize().catch(() => {
  433. });
  434. throw error;
  435. }
  436. }
  437. setEmulatedSizeFromWindowOpen(emulatedSize) {
  438. this._setEmulatedSize(emulatedSize);
  439. }
  440. _setEmulatedSize(emulatedSize) {
  441. this._emulatedSize = emulatedSize;
  442. this.emit(Page.Events.EmulatedSizeChanged);
  443. }
  444. emulatedSize() {
  445. if (this._emulatedSize)
  446. return this._emulatedSize;
  447. const contextOptions = this.browserContext._options;
  448. return contextOptions.viewport ? { viewport: contextOptions.viewport, screen: contextOptions.screen || contextOptions.viewport } : void 0;
  449. }
  450. async bringToFront() {
  451. await this.delegate.bringToFront();
  452. }
  453. async addInitScript(progress, source) {
  454. const initScript = new InitScript(source);
  455. this.initScripts.push(initScript);
  456. try {
  457. await progress.race(this.delegate.addInitScript(initScript));
  458. } catch (error) {
  459. this.removeInitScripts([initScript]).catch(() => {
  460. });
  461. throw error;
  462. }
  463. return initScript;
  464. }
  465. async removeInitScripts(initScripts) {
  466. const set = new Set(initScripts);
  467. this.initScripts = this.initScripts.filter((script) => !set.has(script));
  468. await this.delegate.removeInitScripts(initScripts);
  469. }
  470. needsRequestInterception() {
  471. return this.requestInterceptors.length > 0 || this.browserContext.requestInterceptors.length > 0;
  472. }
  473. async addRequestInterceptor(progress, handler, prepend) {
  474. if (prepend)
  475. this.requestInterceptors.unshift(handler);
  476. else
  477. this.requestInterceptors.push(handler);
  478. await this.delegate.updateRequestInterception();
  479. }
  480. async removeRequestInterceptor(handler) {
  481. const index = this.requestInterceptors.indexOf(handler);
  482. if (index === -1)
  483. return;
  484. this.requestInterceptors.splice(index, 1);
  485. await this.browserContext.notifyRoutesInFlightAboutRemovedHandler(handler);
  486. await this.delegate.updateRequestInterception();
  487. }
  488. async expectScreenshot(progress, options) {
  489. const locator = options.locator;
  490. const rafrafScreenshot = locator ? async (timeout) => {
  491. return await locator.frame.rafrafTimeoutScreenshotElementWithProgress(progress, locator.selector, timeout, options || {});
  492. } : async (timeout) => {
  493. await this.performActionPreChecks(progress);
  494. await this.mainFrame().rafrafTimeout(progress, timeout);
  495. return await this.screenshotter.screenshotPage(progress, options || {});
  496. };
  497. const comparator = (0, import_comparators.getComparator)("image/png");
  498. if (!options.expected && options.isNot)
  499. return { errorMessage: '"not" matcher requires expected result' };
  500. try {
  501. const format = (0, import_screenshotter.validateScreenshotOptions)(options || {});
  502. if (format !== "png")
  503. throw new Error("Only PNG screenshots are supported");
  504. } catch (error) {
  505. return { errorMessage: error.message };
  506. }
  507. let intermediateResult;
  508. const areEqualScreenshots = (actual, expected, previous) => {
  509. const comparatorResult = actual && expected ? comparator(actual, expected, options) : void 0;
  510. if (comparatorResult !== void 0 && !!comparatorResult === !!options.isNot)
  511. return true;
  512. if (comparatorResult)
  513. intermediateResult = { errorMessage: comparatorResult.errorMessage, diff: comparatorResult.diff, actual, previous };
  514. return false;
  515. };
  516. try {
  517. let actual;
  518. let previous;
  519. const pollIntervals = [0, 100, 250, 500];
  520. progress.log(`${(0, import_utils.renderTitleForCall)(progress.metadata)}${options.timeout ? ` with timeout ${options.timeout}ms` : ""}`);
  521. if (options.expected)
  522. progress.log(` verifying given screenshot expectation`);
  523. else
  524. progress.log(` generating new stable screenshot expectation`);
  525. let isFirstIteration = true;
  526. while (true) {
  527. if (this.isClosed())
  528. throw new Error("The page has closed");
  529. const screenshotTimeout = pollIntervals.shift() ?? 1e3;
  530. if (screenshotTimeout)
  531. progress.log(`waiting ${screenshotTimeout}ms before taking screenshot`);
  532. previous = actual;
  533. actual = await rafrafScreenshot(screenshotTimeout).catch((e) => {
  534. if (this.mainFrame().isNonRetriableError(e))
  535. throw e;
  536. progress.log(`failed to take screenshot - ` + e.message);
  537. return void 0;
  538. });
  539. if (!actual)
  540. continue;
  541. const expectation = options.expected && isFirstIteration ? options.expected : previous;
  542. if (areEqualScreenshots(actual, expectation, previous))
  543. break;
  544. if (intermediateResult)
  545. progress.log(intermediateResult.errorMessage);
  546. isFirstIteration = false;
  547. }
  548. if (!isFirstIteration)
  549. progress.log(`captured a stable screenshot`);
  550. if (!options.expected)
  551. return { actual };
  552. if (isFirstIteration) {
  553. progress.log(`screenshot matched expectation`);
  554. return {};
  555. }
  556. if (areEqualScreenshots(actual, options.expected, void 0)) {
  557. progress.log(`screenshot matched expectation`);
  558. return {};
  559. }
  560. throw new Error(intermediateResult.errorMessage);
  561. } catch (e) {
  562. if (js.isJavaScriptErrorInEvaluate(e) || (0, import_selectorParser.isInvalidSelectorError)(e))
  563. throw e;
  564. let errorMessage = e.message;
  565. if (e instanceof import_errors.TimeoutError && intermediateResult?.previous)
  566. errorMessage = `Failed to take two consecutive stable screenshots.`;
  567. return {
  568. log: (0, import_callLog.compressCallLog)(e.message ? [...progress.metadata.log, e.message] : progress.metadata.log),
  569. ...intermediateResult,
  570. errorMessage,
  571. timedOut: e instanceof import_errors.TimeoutError
  572. };
  573. }
  574. }
  575. async screenshot(progress, options) {
  576. return await this.screenshotter.screenshotPage(progress, options);
  577. }
  578. async close(options = {}) {
  579. if (this._closedState === "closed")
  580. return;
  581. if (options.reason)
  582. this._closeReason = options.reason;
  583. const runBeforeUnload = !!options.runBeforeUnload;
  584. if (this._closedState !== "closing") {
  585. if (!runBeforeUnload)
  586. this._closedState = "closing";
  587. await this.delegate.closePage(runBeforeUnload).catch((e) => import_debugLogger.debugLogger.log("error", e));
  588. }
  589. if (!runBeforeUnload)
  590. await this._closedPromise;
  591. }
  592. isClosed() {
  593. return this._closedState === "closed";
  594. }
  595. hasCrashed() {
  596. return this._crashed;
  597. }
  598. isClosedOrClosingOrCrashed() {
  599. return this._closedState !== "open" || this._crashed;
  600. }
  601. addWorker(workerId, worker) {
  602. this._workers.set(workerId, worker);
  603. this.emit(Page.Events.Worker, worker);
  604. }
  605. removeWorker(workerId) {
  606. const worker = this._workers.get(workerId);
  607. if (!worker)
  608. return;
  609. worker.didClose();
  610. this._workers.delete(workerId);
  611. }
  612. clearWorkers() {
  613. for (const [workerId, worker] of this._workers) {
  614. worker.didClose();
  615. this._workers.delete(workerId);
  616. }
  617. }
  618. async setFileChooserInterceptedBy(enabled, by) {
  619. const wasIntercepted = this.fileChooserIntercepted();
  620. if (enabled)
  621. this._fileChooserInterceptedBy.add(by);
  622. else
  623. this._fileChooserInterceptedBy.delete(by);
  624. if (wasIntercepted !== this.fileChooserIntercepted())
  625. await this.delegate.updateFileChooserInterception();
  626. }
  627. fileChooserIntercepted() {
  628. return this._fileChooserInterceptedBy.size > 0;
  629. }
  630. frameNavigatedToNewDocument(frame) {
  631. this.emit(Page.Events.InternalFrameNavigatedToNewDocument, frame);
  632. const origin = frame.origin();
  633. if (origin)
  634. this.browserContext.addVisitedOrigin(origin);
  635. }
  636. allInitScripts() {
  637. const bindings = [...this.browserContext._pageBindings.values(), ...this._pageBindings.values()].map((binding) => binding.initScript);
  638. if (this.browserContext.bindingsInitScript)
  639. bindings.unshift(this.browserContext.bindingsInitScript);
  640. return [...bindings, ...this.browserContext.initScripts, ...this.initScripts];
  641. }
  642. getBinding(name) {
  643. return this._pageBindings.get(name) || this.browserContext._pageBindings.get(name);
  644. }
  645. async safeNonStallingEvaluateInAllFrames(expression, world, options = {}) {
  646. await Promise.all(this.frames().map(async (frame) => {
  647. try {
  648. await frame.nonStallingEvaluateInExistingContext(expression, world);
  649. } catch (e) {
  650. if (options.throwOnJSErrors && js.isJavaScriptErrorInEvaluate(e))
  651. throw e;
  652. }
  653. }));
  654. }
  655. async hideHighlight() {
  656. await Promise.all(this.frames().map((frame) => frame.hideHighlight().catch(() => {
  657. })));
  658. }
  659. async snapshotForAI(progress, options = {}) {
  660. const snapshot = await snapshotFrameForAI(progress, this.mainFrame(), options);
  661. return { full: snapshot.full.join("\n"), incremental: snapshot.incremental?.join("\n") };
  662. }
  663. }
  664. const WorkerEvent = {
  665. Close: "close"
  666. };
  667. class Worker extends import_instrumentation.SdkObject {
  668. constructor(parent, url) {
  669. super(parent, "worker");
  670. this._executionContextPromise = new import_manualPromise.ManualPromise();
  671. this._workerScriptLoaded = false;
  672. this.existingExecutionContext = null;
  673. this.openScope = new import_utils.LongStandingScope();
  674. this.url = url;
  675. }
  676. static {
  677. this.Events = WorkerEvent;
  678. }
  679. createExecutionContext(delegate) {
  680. this.existingExecutionContext = new js.ExecutionContext(this, delegate, "worker");
  681. if (this._workerScriptLoaded)
  682. this._executionContextPromise.resolve(this.existingExecutionContext);
  683. return this.existingExecutionContext;
  684. }
  685. workerScriptLoaded() {
  686. this._workerScriptLoaded = true;
  687. if (this.existingExecutionContext)
  688. this._executionContextPromise.resolve(this.existingExecutionContext);
  689. }
  690. didClose() {
  691. if (this.existingExecutionContext)
  692. this.existingExecutionContext.contextDestroyed("Worker was closed");
  693. this.emit(Worker.Events.Close, this);
  694. this.openScope.close(new Error("Worker closed"));
  695. }
  696. async evaluateExpression(expression, isFunction, arg) {
  697. return js.evaluateExpression(await this._executionContextPromise, expression, { returnByValue: true, isFunction }, arg);
  698. }
  699. async evaluateExpressionHandle(expression, isFunction, arg) {
  700. return js.evaluateExpression(await this._executionContextPromise, expression, { returnByValue: false, isFunction }, arg);
  701. }
  702. }
  703. class PageBinding {
  704. static {
  705. this.kController = "__playwright__binding__controller__";
  706. }
  707. static {
  708. this.kBindingName = "__playwright__binding__";
  709. }
  710. static createInitScript() {
  711. return new InitScript(`
  712. (() => {
  713. const module = {};
  714. ${rawBindingsControllerSource.source}
  715. const property = '${PageBinding.kController}';
  716. if (!globalThis[property])
  717. globalThis[property] = new (module.exports.BindingsController())(globalThis, '${PageBinding.kBindingName}');
  718. })();
  719. `);
  720. }
  721. constructor(name, playwrightFunction, needsHandle) {
  722. this.name = name;
  723. this.playwrightFunction = playwrightFunction;
  724. this.initScript = new InitScript(`globalThis['${PageBinding.kController}'].addBinding(${JSON.stringify(name)}, ${needsHandle})`);
  725. this.needsHandle = needsHandle;
  726. this.cleanupScript = `globalThis['${PageBinding.kController}'].removeBinding(${JSON.stringify(name)})`;
  727. }
  728. static async dispatch(page, payload, context) {
  729. const { name, seq, serializedArgs } = JSON.parse(payload);
  730. try {
  731. (0, import_utils.assert)(context.world);
  732. const binding = page.getBinding(name);
  733. if (!binding)
  734. throw new Error(`Function "${name}" is not exposed`);
  735. let result;
  736. if (binding.needsHandle) {
  737. const handle = await context.evaluateExpressionHandle(`arg => globalThis['${PageBinding.kController}'].takeBindingHandle(arg)`, { isFunction: true }, { name, seq }).catch((e) => null);
  738. result = await binding.playwrightFunction({ frame: context.frame, page, context: page.browserContext }, handle);
  739. } else {
  740. if (!Array.isArray(serializedArgs))
  741. throw new Error(`serializedArgs is not an array. This can happen when Array.prototype.toJSON is defined incorrectly`);
  742. const args = serializedArgs.map((a) => (0, import_utilityScriptSerializers.parseEvaluationResultValue)(a));
  743. result = await binding.playwrightFunction({ frame: context.frame, page, context: page.browserContext }, ...args);
  744. }
  745. context.evaluateExpressionHandle(`arg => globalThis['${PageBinding.kController}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, result }).catch((e) => import_debugLogger.debugLogger.log("error", e));
  746. } catch (error) {
  747. context.evaluateExpressionHandle(`arg => globalThis['${PageBinding.kController}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, error }).catch((e) => import_debugLogger.debugLogger.log("error", e));
  748. }
  749. }
  750. }
  751. class InitScript {
  752. constructor(source) {
  753. this.source = `(() => {
  754. ${source}
  755. })();`;
  756. }
  757. }
  758. async function snapshotFrameForAI(progress, frame, options = {}) {
  759. const snapshot = await frame.retryWithProgressAndTimeouts(progress, [1e3, 2e3, 4e3, 8e3], async (continuePolling) => {
  760. try {
  761. const context = await progress.race(frame._utilityContext());
  762. const injectedScript = await progress.race(context.injectedScript());
  763. const snapshotOrRetry = await progress.race(injectedScript.evaluate((injected, options2) => {
  764. const node = injected.document.body;
  765. if (!node)
  766. return true;
  767. return injected.incrementalAriaSnapshot(node, { mode: "ai", ...options2 });
  768. }, { refPrefix: frame.seq ? "f" + frame.seq : "", track: options.track, doNotRenderActive: options.doNotRenderActive }));
  769. if (snapshotOrRetry === true)
  770. return continuePolling;
  771. return snapshotOrRetry;
  772. } catch (e) {
  773. if (frame.isNonRetriableError(e))
  774. throw e;
  775. return continuePolling;
  776. }
  777. });
  778. const childSnapshotPromises = snapshot.iframeRefs.map((ref) => snapshotFrameRefForAI(progress, frame, ref, options));
  779. const childSnapshots = await Promise.all(childSnapshotPromises);
  780. const full = [];
  781. let incremental;
  782. if (snapshot.incremental !== void 0) {
  783. incremental = snapshot.incremental.split("\n");
  784. for (let i = 0; i < snapshot.iframeRefs.length; i++) {
  785. const childSnapshot = childSnapshots[i];
  786. if (childSnapshot.incremental)
  787. incremental.push(...childSnapshot.incremental);
  788. else if (childSnapshot.full.length)
  789. incremental.push("- <changed> iframe [ref=" + snapshot.iframeRefs[i] + "]:", ...childSnapshot.full.map((l) => " " + l));
  790. }
  791. }
  792. for (const line of snapshot.full.split("\n")) {
  793. const match = line.match(/^(\s*)- iframe (?:\[active\] )?\[ref=([^\]]*)\]/);
  794. if (!match) {
  795. full.push(line);
  796. continue;
  797. }
  798. const leadingSpace = match[1];
  799. const ref = match[2];
  800. const childSnapshot = childSnapshots[snapshot.iframeRefs.indexOf(ref)] ?? { full: [] };
  801. full.push(childSnapshot.full.length ? line + ":" : line);
  802. full.push(...childSnapshot.full.map((l) => leadingSpace + " " + l));
  803. }
  804. return { full, incremental };
  805. }
  806. async function snapshotFrameRefForAI(progress, parentFrame, frameRef, options) {
  807. const frameSelector = `aria-ref=${frameRef} >> internal:control=enter-frame`;
  808. const frameBodySelector = `${frameSelector} >> body`;
  809. const child = await progress.race(parentFrame.selectors.resolveFrameForSelector(frameBodySelector, { strict: true }));
  810. if (!child)
  811. return { full: [] };
  812. try {
  813. return await snapshotFrameForAI(progress, child.frame, options);
  814. } catch {
  815. return { full: [] };
  816. }
  817. }
  818. function ensureArrayLimit(array, limit) {
  819. if (array.length > limit)
  820. return array.splice(0, limit / 10);
  821. return [];
  822. }
  823. // Annotate the CommonJS export names for ESM import in node:
  824. 0 && (module.exports = {
  825. InitScript,
  826. Page,
  827. PageBinding,
  828. Worker,
  829. WorkerEvent
  830. });