Frame.ts (36896B)
1 /** 2 * @license 3 * Copyright 2023 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import type Protocol from 'devtools-protocol'; 8 9 import type {ClickOptions, ElementHandle} from '../api/ElementHandle.js'; 10 import type {HTTPResponse} from '../api/HTTPResponse.js'; 11 import type { 12 Page, 13 QueryOptions, 14 WaitForSelectorOptions, 15 WaitTimeoutOptions, 16 } from '../api/Page.js'; 17 import type {Accessibility} from '../cdp/Accessibility.js'; 18 import type {DeviceRequestPrompt} from '../cdp/DeviceRequestPrompt.js'; 19 import type {PuppeteerLifeCycleEvent} from '../cdp/LifecycleWatcher.js'; 20 import {EventEmitter, type EventType} from '../common/EventEmitter.js'; 21 import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js'; 22 import {transposeIterableHandle} from '../common/HandleIterator.js'; 23 import type { 24 Awaitable, 25 EvaluateFunc, 26 EvaluateFuncWith, 27 HandleFor, 28 NodeFor, 29 } from '../common/types.js'; 30 import {withSourcePuppeteerURLIfNone} from '../common/util.js'; 31 import {environment} from '../environment.js'; 32 import {assert} from '../util/assert.js'; 33 import {throwIfDisposed} from '../util/decorators.js'; 34 35 import type {CDPSession} from './CDPSession.js'; 36 import type {KeyboardTypeOptions} from './Input.js'; 37 import { 38 FunctionLocator, 39 NodeLocator, 40 type Locator, 41 } from './locators/locators.js'; 42 import type {Realm} from './Realm.js'; 43 44 /** 45 * @public 46 */ 47 export interface WaitForOptions { 48 /** 49 * Maximum wait time in milliseconds. Pass 0 to disable the timeout. 50 * 51 * The default value can be changed by using the 52 * {@link Page.setDefaultTimeout} or {@link Page.setDefaultNavigationTimeout} 53 * methods. 54 * 55 * @defaultValue `30000` 56 */ 57 timeout?: number; 58 /** 59 * When to consider waiting succeeds. Given an array of event strings, waiting 60 * is considered to be successful after all events have been fired. 61 * 62 * @defaultValue `'load'` 63 */ 64 waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; 65 /** 66 * @internal 67 */ 68 ignoreSameDocumentNavigation?: boolean; 69 /** 70 * A signal object that allows you to cancel the call. 71 */ 72 signal?: AbortSignal; 73 } 74 75 /** 76 * @public 77 */ 78 export interface GoToOptions extends WaitForOptions { 79 /** 80 * If provided, it will take preference over the referer header value set by 81 * {@link Page.setExtraHTTPHeaders | page.setExtraHTTPHeaders()}. 82 */ 83 referer?: string; 84 /** 85 * If provided, it will take preference over the referer-policy header value 86 * set by {@link Page.setExtraHTTPHeaders | page.setExtraHTTPHeaders()}. 87 */ 88 referrerPolicy?: string; 89 } 90 91 /** 92 * @public 93 */ 94 export interface FrameWaitForFunctionOptions { 95 /** 96 * An interval at which the `pageFunction` is executed, defaults to `raf`. If 97 * `polling` is a number, then it is treated as an interval in milliseconds at 98 * which the function would be executed. If `polling` is a string, then it can 99 * be one of the following values: 100 * 101 * - `raf` - to constantly execute `pageFunction` in `requestAnimationFrame` 102 * callback. This is the tightest polling mode which is suitable to observe 103 * styling changes. 104 * 105 * - `mutation` - to execute `pageFunction` on every DOM mutation. 106 */ 107 polling?: 'raf' | 'mutation' | number; 108 /** 109 * Maximum time to wait in milliseconds. Defaults to `30000` (30 seconds). 110 * Pass `0` to disable the timeout. Puppeteer's default timeout can be changed 111 * using {@link Page.setDefaultTimeout}. 112 */ 113 timeout?: number; 114 /** 115 * A signal object that allows you to cancel a waitForFunction call. 116 */ 117 signal?: AbortSignal; 118 } 119 120 /** 121 * @public 122 */ 123 export interface FrameAddScriptTagOptions { 124 /** 125 * URL of the script to be added. 126 */ 127 url?: string; 128 /** 129 * Path to a JavaScript file to be injected into the frame. 130 * 131 * @remarks 132 * If `path` is a relative path, it is resolved relative to the current 133 * working directory (`process.cwd()` in Node.js). 134 */ 135 path?: string; 136 /** 137 * JavaScript to be injected into the frame. 138 */ 139 content?: string; 140 /** 141 * Sets the `type` of the script. Use `module` in order to load an ES2015 module. 142 */ 143 type?: string; 144 /** 145 * Sets the `id` of the script. 146 */ 147 id?: string; 148 } 149 150 /** 151 * @public 152 */ 153 export interface FrameAddStyleTagOptions { 154 /** 155 * the URL of the CSS file to be added. 156 */ 157 url?: string; 158 /** 159 * The path to a CSS file to be injected into the frame. 160 * @remarks 161 * If `path` is a relative path, it is resolved relative to the current 162 * working directory (`process.cwd()` in Node.js). 163 */ 164 path?: string; 165 /** 166 * Raw CSS content to be injected into the frame. 167 */ 168 content?: string; 169 } 170 171 /** 172 * @public 173 */ 174 export interface FrameEvents extends Record<EventType, unknown> { 175 /** @internal */ 176 [FrameEvent.FrameNavigated]: Protocol.Page.NavigationType; 177 /** @internal */ 178 [FrameEvent.FrameSwapped]: undefined; 179 /** @internal */ 180 [FrameEvent.LifecycleEvent]: undefined; 181 /** @internal */ 182 [FrameEvent.FrameNavigatedWithinDocument]: undefined; 183 /** @internal */ 184 [FrameEvent.FrameDetached]: Frame; 185 /** @internal */ 186 [FrameEvent.FrameSwappedByActivation]: undefined; 187 } 188 189 /** 190 * We use symbols to prevent external parties listening to these events. 191 * They are internal to Puppeteer. 192 * 193 * @internal 194 */ 195 // eslint-disable-next-line @typescript-eslint/no-namespace 196 export namespace FrameEvent { 197 export const FrameNavigated = Symbol('Frame.FrameNavigated'); 198 export const FrameSwapped = Symbol('Frame.FrameSwapped'); 199 export const LifecycleEvent = Symbol('Frame.LifecycleEvent'); 200 export const FrameNavigatedWithinDocument = Symbol( 201 'Frame.FrameNavigatedWithinDocument', 202 ); 203 export const FrameDetached = Symbol('Frame.FrameDetached'); 204 export const FrameSwappedByActivation = Symbol( 205 'Frame.FrameSwappedByActivation', 206 ); 207 } 208 209 /** 210 * @internal 211 */ 212 export const throwIfDetached = throwIfDisposed<Frame>(frame => { 213 return `Attempted to use detached Frame '${frame._id}'.`; 214 }); 215 216 /** 217 * Represents a DOM frame. 218 * 219 * To understand frames, you can think of frames as `<iframe>` elements. Just 220 * like iframes, frames can be nested, and when JavaScript is executed in a 221 * frame, the JavaScript does not affect frames inside the ambient frame the 222 * JavaScript executes in. 223 * 224 * @example 225 * At any point in time, {@link Page | pages} expose their current frame 226 * tree via the {@link Page.mainFrame} and {@link Frame.childFrames} methods. 227 * 228 * @example 229 * An example of dumping frame tree: 230 * 231 * ```ts 232 * import puppeteer from 'puppeteer'; 233 * 234 * (async () => { 235 * const browser = await puppeteer.launch(); 236 * const page = await browser.newPage(); 237 * await page.goto('https://www.google.com/chrome/browser/canary.html'); 238 * dumpFrameTree(page.mainFrame(), ''); 239 * await browser.close(); 240 * 241 * function dumpFrameTree(frame, indent) { 242 * console.log(indent + frame.url()); 243 * for (const child of frame.childFrames()) { 244 * dumpFrameTree(child, indent + ' '); 245 * } 246 * } 247 * })(); 248 * ``` 249 * 250 * @example 251 * An example of getting text from an iframe element: 252 * 253 * ```ts 254 * const frames = page.frames(); 255 * let frame = null; 256 * for (const currentFrame of frames) { 257 * const frameElement = await currentFrame.frameElement(); 258 * const name = await frameElement.evaluate(el => el.getAttribute('name')); 259 * if (name === 'myframe') { 260 * frame = currentFrame; 261 * break; 262 * } 263 * } 264 * if (frame) { 265 * const text = await frame.$eval( 266 * '.selector', 267 * element => element.textContent, 268 * ); 269 * console.log(text); 270 * } else { 271 * console.error('Frame with name "myframe" not found.'); 272 * } 273 * ``` 274 * 275 * @remarks 276 * Frame lifecycles are controlled by three events that are all dispatched on 277 * the parent {@link Frame.page | page}: 278 * 279 * - {@link PageEvent.FrameAttached} 280 * - {@link PageEvent.FrameNavigated} 281 * - {@link PageEvent.FrameDetached} 282 * 283 * @public 284 */ 285 export abstract class Frame extends EventEmitter<FrameEvents> { 286 /** 287 * @internal 288 */ 289 _id!: string; 290 /** 291 * @internal 292 */ 293 _parentId?: string; 294 295 /** 296 * @internal 297 */ 298 _name?: string; 299 300 /** 301 * @internal 302 */ 303 _hasStartedLoading = false; 304 305 /** 306 * @internal 307 */ 308 constructor() { 309 super(); 310 } 311 312 /** 313 * The page associated with the frame. 314 */ 315 abstract page(): Page; 316 317 /** 318 * Navigates the frame or page to the given `url`. 319 * 320 * @remarks 321 * Navigation to `about:blank` or navigation to the same URL with a different 322 * hash will succeed and return `null`. 323 * 324 * :::warning 325 * 326 * Headless shell mode doesn't support navigation to a PDF document. See the 327 * {@link https://crbug.com/761295 | upstream issue}. 328 * 329 * ::: 330 * 331 * In headless shell, this method will not throw an error when any valid HTTP 332 * status code is returned by the remote server, including 404 "Not Found" and 333 * 500 "Internal Server Error". The status code for such responses can be 334 * retrieved by calling {@link HTTPResponse.status}. 335 * 336 * @param url - URL to navigate the frame to. The URL should include scheme, 337 * e.g. `https://` 338 * @param options - Options to configure waiting behavior. 339 * @returns A promise which resolves to the main resource response. In case of 340 * multiple redirects, the navigation will resolve with the response of the 341 * last redirect. 342 * @throws If: 343 * 344 * - there's an SSL error (e.g. in case of self-signed certificates). 345 * 346 * - target URL is invalid. 347 * 348 * - the timeout is exceeded during navigation. 349 * 350 * - the remote server does not respond or is unreachable. 351 * 352 * - the main resource failed to load. 353 */ 354 abstract goto( 355 url: string, 356 options?: GoToOptions, 357 ): Promise<HTTPResponse | null>; 358 359 /** 360 * Waits for the frame to navigate. It is useful for when you run code which 361 * will indirectly cause the frame to navigate. 362 * 363 * Usage of the 364 * {@link https://developer.mozilla.org/en-US/docs/Web/API/History_API | History API} 365 * to change the URL is considered a navigation. 366 * 367 * @example 368 * 369 * ```ts 370 * const [response] = await Promise.all([ 371 * // The navigation promise resolves after navigation has finished 372 * frame.waitForNavigation(), 373 * // Clicking the link will indirectly cause a navigation 374 * frame.click('a.my-link'), 375 * ]); 376 * ``` 377 * 378 * @param options - Options to configure waiting behavior. 379 * @returns A promise which resolves to the main resource response. 380 */ 381 abstract waitForNavigation( 382 options?: WaitForOptions, 383 ): Promise<HTTPResponse | null>; 384 385 /** 386 * @internal 387 */ 388 abstract get client(): CDPSession; 389 390 /** 391 * @internal 392 */ 393 abstract get accessibility(): Accessibility; 394 395 /** 396 * @internal 397 */ 398 abstract mainRealm(): Realm; 399 400 /** 401 * @internal 402 */ 403 abstract isolatedRealm(): Realm; 404 405 #_document: Promise<ElementHandle<Document>> | undefined; 406 407 /** 408 * @internal 409 */ 410 #document(): Promise<ElementHandle<Document>> { 411 if (!this.#_document) { 412 this.#_document = this.mainRealm().evaluateHandle(() => { 413 return document; 414 }); 415 } 416 return this.#_document; 417 } 418 419 /** 420 * Used to clear the document handle that has been destroyed. 421 * 422 * @internal 423 */ 424 clearDocumentHandle(): void { 425 this.#_document = undefined; 426 } 427 428 /** 429 * @returns The frame element associated with this frame (if any). 430 */ 431 @throwIfDetached 432 async frameElement(): Promise<HandleFor<HTMLIFrameElement> | null> { 433 const parentFrame = this.parentFrame(); 434 if (!parentFrame) { 435 return null; 436 } 437 using list = await parentFrame.isolatedRealm().evaluateHandle(() => { 438 return document.querySelectorAll('iframe,frame'); 439 }); 440 for await (using iframe of transposeIterableHandle(list)) { 441 const frame = await iframe.contentFrame(); 442 if (frame?._id === this._id) { 443 return (await parentFrame 444 .mainRealm() 445 .adoptHandle(iframe)) as HandleFor<HTMLIFrameElement>; 446 } 447 } 448 return null; 449 } 450 451 /** 452 * Behaves identically to {@link Page.evaluateHandle} except it's run within 453 * the context of this frame. 454 * 455 * See {@link Page.evaluateHandle} for details. 456 */ 457 @throwIfDetached 458 async evaluateHandle< 459 Params extends unknown[], 460 Func extends EvaluateFunc<Params> = EvaluateFunc<Params>, 461 >( 462 pageFunction: Func | string, 463 ...args: Params 464 ): Promise<HandleFor<Awaited<ReturnType<Func>>>> { 465 pageFunction = withSourcePuppeteerURLIfNone( 466 this.evaluateHandle.name, 467 pageFunction, 468 ); 469 return await this.mainRealm().evaluateHandle(pageFunction, ...args); 470 } 471 472 /** 473 * Behaves identically to {@link Page.evaluate} except it's run within 474 * the context of this frame. 475 * 476 * See {@link Page.evaluate} for details. 477 */ 478 @throwIfDetached 479 async evaluate< 480 Params extends unknown[], 481 Func extends EvaluateFunc<Params> = EvaluateFunc<Params>, 482 >( 483 pageFunction: Func | string, 484 ...args: Params 485 ): Promise<Awaited<ReturnType<Func>>> { 486 pageFunction = withSourcePuppeteerURLIfNone( 487 this.evaluate.name, 488 pageFunction, 489 ); 490 return await this.mainRealm().evaluate(pageFunction, ...args); 491 } 492 493 /** 494 * Creates a locator for the provided selector. See {@link Locator} for 495 * details and supported actions. 496 * 497 * @param selector - 498 * {@link https://pptr.dev/guides/page-interactions#selectors | selector} 499 * to query the page for. 500 * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors} 501 * can be passed as-is and a 502 * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax} 503 * allows querying by 504 * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text}, 505 * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name}, 506 * and 507 * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath} 508 * and 509 * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}. 510 * Alternatively, you can specify the selector type using a 511 * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}. 512 */ 513 locator<Selector extends string>( 514 selector: Selector, 515 ): Locator<NodeFor<Selector>>; 516 517 /** 518 * Creates a locator for the provided function. See {@link Locator} for 519 * details and supported actions. 520 */ 521 locator<Ret>(func: () => Awaitable<Ret>): Locator<Ret>; 522 523 /** 524 * @internal 525 */ 526 @throwIfDetached 527 locator<Selector extends string, Ret>( 528 selectorOrFunc: Selector | (() => Awaitable<Ret>), 529 ): Locator<NodeFor<Selector>> | Locator<Ret> { 530 if (typeof selectorOrFunc === 'string') { 531 return NodeLocator.create(this, selectorOrFunc); 532 } else { 533 return FunctionLocator.create(this, selectorOrFunc); 534 } 535 } 536 /** 537 * Queries the frame for an element matching the given selector. 538 * 539 * @param selector - 540 * {@link https://pptr.dev/guides/page-interactions#selectors | selector} 541 * to query the page for. 542 * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors} 543 * can be passed as-is and a 544 * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax} 545 * allows querying by 546 * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text}, 547 * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name}, 548 * and 549 * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath} 550 * and 551 * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}. 552 * Alternatively, you can specify the selector type using a 553 * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}. 554 * 555 * @returns A {@link ElementHandle | element handle} to the first element 556 * matching the given selector. Otherwise, `null`. 557 */ 558 @throwIfDetached 559 async $<Selector extends string>( 560 selector: Selector, 561 ): Promise<ElementHandle<NodeFor<Selector>> | null> { 562 // eslint-disable-next-line rulesdir/use-using -- This is cached. 563 const document = await this.#document(); 564 return await document.$(selector); 565 } 566 567 /** 568 * Queries the frame for all elements matching the given selector. 569 * 570 * @param selector - 571 * {@link https://pptr.dev/guides/page-interactions#selectors | selector} 572 * to query the page for. 573 * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors} 574 * can be passed as-is and a 575 * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax} 576 * allows querying by 577 * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text}, 578 * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name}, 579 * and 580 * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath} 581 * and 582 * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}. 583 * Alternatively, you can specify the selector type using a 584 * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}. 585 * 586 * @returns An array of {@link ElementHandle | element handles} that point to 587 * elements matching the given selector. 588 */ 589 @throwIfDetached 590 async $$<Selector extends string>( 591 selector: Selector, 592 options?: QueryOptions, 593 ): Promise<Array<ElementHandle<NodeFor<Selector>>>> { 594 // eslint-disable-next-line rulesdir/use-using -- This is cached. 595 const document = await this.#document(); 596 return await document.$$(selector, options); 597 } 598 599 /** 600 * Runs the given function on the first element matching the given selector in 601 * the frame. 602 * 603 * If the given function returns a promise, then this method will wait till 604 * the promise resolves. 605 * 606 * @example 607 * 608 * ```ts 609 * const searchValue = await frame.$eval('#search', el => el.value); 610 * ``` 611 * 612 * @param selector - 613 * {@link https://pptr.dev/guides/page-interactions#selectors | selector} 614 * to query the page for. 615 * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors} 616 * can be passed as-is and a 617 * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax} 618 * allows querying by 619 * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text}, 620 * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name}, 621 * and 622 * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath} 623 * and 624 * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}. 625 * Alternatively, you can specify the selector type using a 626 * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}. 627 * @param pageFunction - The function to be evaluated in the frame's context. 628 * The first element matching the selector will be passed to the function as 629 * its first argument. 630 * @param args - Additional arguments to pass to `pageFunction`. 631 * @returns A promise to the result of the function. 632 */ 633 @throwIfDetached 634 async $eval< 635 Selector extends string, 636 Params extends unknown[], 637 Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith< 638 NodeFor<Selector>, 639 Params 640 >, 641 >( 642 selector: Selector, 643 pageFunction: string | Func, 644 ...args: Params 645 ): Promise<Awaited<ReturnType<Func>>> { 646 pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction); 647 // eslint-disable-next-line rulesdir/use-using -- This is cached. 648 const document = await this.#document(); 649 return await document.$eval(selector, pageFunction, ...args); 650 } 651 652 /** 653 * Runs the given function on an array of elements matching the given selector 654 * in the frame. 655 * 656 * If the given function returns a promise, then this method will wait till 657 * the promise resolves. 658 * 659 * @example 660 * 661 * ```ts 662 * const divsCounts = await frame.$$eval('div', divs => divs.length); 663 * ``` 664 * 665 * @param selector - 666 * {@link https://pptr.dev/guides/page-interactions#selectors | selector} 667 * to query the page for. 668 * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | CSS selectors} 669 * can be passed as-is and a 670 * {@link https://pptr.dev/guides/page-interactions#non-css-selectors | Puppeteer-specific selector syntax} 671 * allows querying by 672 * {@link https://pptr.dev/guides/page-interactions#text-selectors--p-text | text}, 673 * {@link https://pptr.dev/guides/page-interactions#aria-selectors--p-aria | a11y role and name}, 674 * and 675 * {@link https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath | xpath} 676 * and 677 * {@link https://pptr.dev/guides/page-interactions#querying-elements-in-shadow-dom | combining these queries across shadow roots}. 678 * Alternatively, you can specify the selector type using a 679 * {@link https://pptr.dev/guides/page-interactions#prefixed-selector-syntax | prefix}. 680 * @param pageFunction - The function to be evaluated in the frame's context. 681 * An array of elements matching the given selector will be passed to the 682 * function as its first argument. 683 * @param args - Additional arguments to pass to `pageFunction`. 684 * @returns A promise to the result of the function. 685 */ 686 @throwIfDetached 687 async $$eval< 688 Selector extends string, 689 Params extends unknown[], 690 Func extends EvaluateFuncWith< 691 Array<NodeFor<Selector>>, 692 Params 693 > = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>, 694 >( 695 selector: Selector, 696 pageFunction: string | Func, 697 ...args: Params 698 ): Promise<Awaited<ReturnType<Func>>> { 699 pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction); 700 // eslint-disable-next-line rulesdir/use-using -- This is cached. 701 const document = await this.#document(); 702 return await document.$$eval(selector, pageFunction, ...args); 703 } 704 705 /** 706 * Waits for an element matching the given selector to appear in the frame. 707 * 708 * This method works across navigations. 709 * 710 * @example 711 * 712 * ```ts 713 * import puppeteer from 'puppeteer'; 714 * 715 * (async () => { 716 * const browser = await puppeteer.launch(); 717 * const page = await browser.newPage(); 718 * let currentURL; 719 * page 720 * .mainFrame() 721 * .waitForSelector('img') 722 * .then(() => console.log('First URL with image: ' + currentURL)); 723 * 724 * for (currentURL of [ 725 * 'https://example.com', 726 * 'https://google.com', 727 * 'https://bbc.com', 728 * ]) { 729 * await page.goto(currentURL); 730 * } 731 * await browser.close(); 732 * })(); 733 * ``` 734 * 735 * @param selector - The selector to query and wait for. 736 * @param options - Options for customizing waiting behavior. 737 * @returns An element matching the given selector. 738 * @throws Throws if an element matching the given selector doesn't appear. 739 */ 740 @throwIfDetached 741 async waitForSelector<Selector extends string>( 742 selector: Selector, 743 options: WaitForSelectorOptions = {}, 744 ): Promise<ElementHandle<NodeFor<Selector>> | null> { 745 const {updatedSelector, QueryHandler, polling} = 746 getQueryHandlerAndSelector(selector); 747 return (await QueryHandler.waitFor(this, updatedSelector, { 748 polling, 749 ...options, 750 })) as ElementHandle<NodeFor<Selector>> | null; 751 } 752 753 /** 754 * @example 755 * The `waitForFunction` can be used to observe viewport size change: 756 * 757 * ```ts 758 * import puppeteer from 'puppeteer'; 759 * 760 * (async () => { 761 * . const browser = await puppeteer.launch(); 762 * . const page = await browser.newPage(); 763 * . const watchDog = page.mainFrame().waitForFunction('window.innerWidth < 100'); 764 * . page.setViewport({width: 50, height: 50}); 765 * . await watchDog; 766 * . await browser.close(); 767 * })(); 768 * ``` 769 * 770 * To pass arguments from Node.js to the predicate of `page.waitForFunction` function: 771 * 772 * ```ts 773 * const selector = '.foo'; 774 * await frame.waitForFunction( 775 * selector => !!document.querySelector(selector), 776 * {}, // empty options object 777 * selector, 778 * ); 779 * ``` 780 * 781 * @param pageFunction - the function to evaluate in the frame context. 782 * @param options - options to configure the polling method and timeout. 783 * @param args - arguments to pass to the `pageFunction`. 784 * @returns the promise which resolve when the `pageFunction` returns a truthy value. 785 */ 786 @throwIfDetached 787 async waitForFunction< 788 Params extends unknown[], 789 Func extends EvaluateFunc<Params> = EvaluateFunc<Params>, 790 >( 791 pageFunction: Func | string, 792 options: FrameWaitForFunctionOptions = {}, 793 ...args: Params 794 ): Promise<HandleFor<Awaited<ReturnType<Func>>>> { 795 return await (this.mainRealm().waitForFunction( 796 pageFunction, 797 options, 798 ...args, 799 ) as Promise<HandleFor<Awaited<ReturnType<Func>>>>); 800 } 801 /** 802 * The full HTML contents of the frame, including the DOCTYPE. 803 */ 804 @throwIfDetached 805 async content(): Promise<string> { 806 return await this.evaluate(() => { 807 let content = ''; 808 for (const node of document.childNodes) { 809 switch (node) { 810 case document.documentElement: 811 content += document.documentElement.outerHTML; 812 break; 813 default: 814 content += new XMLSerializer().serializeToString(node); 815 break; 816 } 817 } 818 819 return content; 820 }); 821 } 822 823 /** 824 * Set the content of the frame. 825 * 826 * @param html - HTML markup to assign to the page. 827 * @param options - Options to configure how long before timing out and at 828 * what point to consider the content setting successful. 829 */ 830 abstract setContent(html: string, options?: WaitForOptions): Promise<void>; 831 832 /** 833 * @internal 834 */ 835 async setFrameContent(content: string): Promise<void> { 836 return await this.evaluate(html => { 837 document.open(); 838 document.write(html); 839 document.close(); 840 }, content); 841 } 842 843 /** 844 * The frame's `name` attribute as specified in the tag. 845 * 846 * @remarks 847 * If the name is empty, it returns the `id` attribute instead. 848 * 849 * @remarks 850 * This value is calculated once when the frame is created, and will not 851 * update if the attribute is changed later. 852 * 853 * @deprecated Use 854 * 855 * ```ts 856 * const element = await frame.frameElement(); 857 * const nameOrId = await element.evaluate(frame => frame.name ?? frame.id); 858 * ``` 859 */ 860 name(): string { 861 return this._name || ''; 862 } 863 864 /** 865 * The frame's URL. 866 */ 867 abstract url(): string; 868 869 /** 870 * The parent frame, if any. Detached and main frames return `null`. 871 */ 872 abstract parentFrame(): Frame | null; 873 874 /** 875 * An array of child frames. 876 */ 877 abstract childFrames(): Frame[]; 878 879 /** 880 * @returns `true` if the frame has detached. `false` otherwise. 881 */ 882 abstract get detached(): boolean; 883 884 /** 885 * Is`true` if the frame has been detached. Otherwise, `false`. 886 * 887 * @deprecated Use the `detached` getter. 888 */ 889 isDetached(): boolean { 890 return this.detached; 891 } 892 893 /** 894 * @internal 895 */ 896 get disposed(): boolean { 897 return this.detached; 898 } 899 900 /** 901 * Adds a `<script>` tag into the page with the desired url or content. 902 * 903 * @param options - Options for the script. 904 * @returns An {@link ElementHandle | element handle} to the injected 905 * `<script>` element. 906 */ 907 @throwIfDetached 908 async addScriptTag( 909 options: FrameAddScriptTagOptions, 910 ): Promise<ElementHandle<HTMLScriptElement>> { 911 let {content = '', type} = options; 912 const {path} = options; 913 if (+!!options.url + +!!path + +!!content !== 1) { 914 throw new Error( 915 'Exactly one of `url`, `path`, or `content` must be specified.', 916 ); 917 } 918 919 if (path) { 920 content = await environment.value.fs.promises.readFile(path, 'utf8'); 921 content += `//# sourceURL=${path.replace(/\n/g, '')}`; 922 } 923 924 type = type ?? 'text/javascript'; 925 926 return await this.mainRealm().transferHandle( 927 await this.isolatedRealm().evaluateHandle( 928 async ({url, id, type, content}) => { 929 return await new Promise<HTMLScriptElement>((resolve, reject) => { 930 const script = document.createElement('script'); 931 script.type = type; 932 script.text = content; 933 script.addEventListener( 934 'error', 935 event => { 936 reject(new Error(event.message ?? 'Could not load script')); 937 }, 938 {once: true}, 939 ); 940 if (id) { 941 script.id = id; 942 } 943 if (url) { 944 script.src = url; 945 script.addEventListener( 946 'load', 947 () => { 948 resolve(script); 949 }, 950 {once: true}, 951 ); 952 document.head.appendChild(script); 953 } else { 954 document.head.appendChild(script); 955 resolve(script); 956 } 957 }); 958 }, 959 {...options, type, content}, 960 ), 961 ); 962 } 963 964 /** 965 * Adds a `HTMLStyleElement` into the frame with the desired URL 966 * 967 * @returns An {@link ElementHandle | element handle} to the loaded `<style>` 968 * element. 969 */ 970 async addStyleTag( 971 options: Omit<FrameAddStyleTagOptions, 'url'>, 972 ): Promise<ElementHandle<HTMLStyleElement>>; 973 974 /** 975 * Adds a `HTMLLinkElement` into the frame with the desired URL 976 * 977 * @returns An {@link ElementHandle | element handle} to the loaded `<link>` 978 * element. 979 */ 980 async addStyleTag( 981 options: FrameAddStyleTagOptions, 982 ): Promise<ElementHandle<HTMLLinkElement>>; 983 984 /** 985 * @internal 986 */ 987 @throwIfDetached 988 async addStyleTag( 989 options: FrameAddStyleTagOptions, 990 ): Promise<ElementHandle<HTMLStyleElement | HTMLLinkElement>> { 991 let {content = ''} = options; 992 const {path} = options; 993 if (+!!options.url + +!!path + +!!content !== 1) { 994 throw new Error( 995 'Exactly one of `url`, `path`, or `content` must be specified.', 996 ); 997 } 998 999 if (path) { 1000 content = await environment.value.fs.promises.readFile(path, 'utf8'); 1001 content += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/'; 1002 options.content = content; 1003 } 1004 1005 return await this.mainRealm().transferHandle( 1006 await this.isolatedRealm().evaluateHandle(async ({url, content}) => { 1007 return await new Promise<HTMLStyleElement | HTMLLinkElement>( 1008 (resolve, reject) => { 1009 let element: HTMLStyleElement | HTMLLinkElement; 1010 if (!url) { 1011 element = document.createElement('style'); 1012 element.appendChild(document.createTextNode(content!)); 1013 } else { 1014 const link = document.createElement('link'); 1015 link.rel = 'stylesheet'; 1016 link.href = url; 1017 element = link; 1018 } 1019 element.addEventListener( 1020 'load', 1021 () => { 1022 resolve(element); 1023 }, 1024 {once: true}, 1025 ); 1026 element.addEventListener( 1027 'error', 1028 event => { 1029 reject( 1030 new Error( 1031 (event as ErrorEvent).message ?? 'Could not load style', 1032 ), 1033 ); 1034 }, 1035 {once: true}, 1036 ); 1037 document.head.appendChild(element); 1038 return element; 1039 }, 1040 ); 1041 }, options), 1042 ); 1043 } 1044 1045 /** 1046 * Clicks the first element found that matches `selector`. 1047 * 1048 * @remarks 1049 * If `click()` triggers a navigation event and there's a separate 1050 * `page.waitForNavigation()` promise to be resolved, you may end up with a 1051 * race condition that yields unexpected results. The correct pattern for 1052 * click and wait for navigation is the following: 1053 * 1054 * ```ts 1055 * const [response] = await Promise.all([ 1056 * page.waitForNavigation(waitOptions), 1057 * frame.click(selector, clickOptions), 1058 * ]); 1059 * ``` 1060 * 1061 * @param selector - The selector to query for. 1062 */ 1063 @throwIfDetached 1064 async click( 1065 selector: string, 1066 options: Readonly<ClickOptions> = {}, 1067 ): Promise<void> { 1068 using handle = await this.$(selector); 1069 assert(handle, `No element found for selector: ${selector}`); 1070 await handle.click(options); 1071 await handle.dispose(); 1072 } 1073 1074 /** 1075 * Focuses the first element that matches the `selector`. 1076 * 1077 * @param selector - The selector to query for. 1078 * @throws Throws if there's no element matching `selector`. 1079 */ 1080 @throwIfDetached 1081 async focus(selector: string): Promise<void> { 1082 using handle = await this.$(selector); 1083 assert(handle, `No element found for selector: ${selector}`); 1084 await handle.focus(); 1085 } 1086 1087 /** 1088 * Hovers the pointer over the center of the first element that matches the 1089 * `selector`. 1090 * 1091 * @param selector - The selector to query for. 1092 * @throws Throws if there's no element matching `selector`. 1093 */ 1094 @throwIfDetached 1095 async hover(selector: string): Promise<void> { 1096 using handle = await this.$(selector); 1097 assert(handle, `No element found for selector: ${selector}`); 1098 await handle.hover(); 1099 } 1100 1101 /** 1102 * Selects a set of value on the first `<select>` element that matches the 1103 * `selector`. 1104 * 1105 * @example 1106 * 1107 * ```ts 1108 * frame.select('select#colors', 'blue'); // single selection 1109 * frame.select('select#colors', 'red', 'green', 'blue'); // multiple selections 1110 * ``` 1111 * 1112 * @param selector - The selector to query for. 1113 * @param values - The array of values to select. If the `<select>` has the 1114 * `multiple` attribute, all values are considered, otherwise only the first 1115 * one is taken into account. 1116 * @returns the list of values that were successfully selected. 1117 * @throws Throws if there's no `<select>` matching `selector`. 1118 */ 1119 @throwIfDetached 1120 async select(selector: string, ...values: string[]): Promise<string[]> { 1121 using handle = await this.$(selector); 1122 assert(handle, `No element found for selector: ${selector}`); 1123 return await handle.select(...values); 1124 } 1125 1126 /** 1127 * Taps the first element that matches the `selector`. 1128 * 1129 * @param selector - The selector to query for. 1130 * @throws Throws if there's no element matching `selector`. 1131 */ 1132 @throwIfDetached 1133 async tap(selector: string): Promise<void> { 1134 using handle = await this.$(selector); 1135 assert(handle, `No element found for selector: ${selector}`); 1136 await handle.tap(); 1137 } 1138 1139 /** 1140 * Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character 1141 * in the text. 1142 * 1143 * @remarks 1144 * To press a special key, like `Control` or `ArrowDown`, use 1145 * {@link Keyboard.press}. 1146 * 1147 * @example 1148 * 1149 * ```ts 1150 * await frame.type('#mytextarea', 'Hello'); // Types instantly 1151 * await frame.type('#mytextarea', 'World', {delay: 100}); // Types slower, like a user 1152 * ``` 1153 * 1154 * @param selector - the selector for the element to type into. If there are 1155 * multiple the first will be used. 1156 * @param text - text to type into the element 1157 * @param options - takes one option, `delay`, which sets the time to wait 1158 * between key presses in milliseconds. Defaults to `0`. 1159 */ 1160 @throwIfDetached 1161 async type( 1162 selector: string, 1163 text: string, 1164 options?: Readonly<KeyboardTypeOptions>, 1165 ): Promise<void> { 1166 using handle = await this.$(selector); 1167 assert(handle, `No element found for selector: ${selector}`); 1168 await handle.type(text, options); 1169 } 1170 1171 /** 1172 * The frame's title. 1173 */ 1174 @throwIfDetached 1175 async title(): Promise<string> { 1176 return await this.isolatedRealm().evaluate(() => { 1177 return document.title; 1178 }); 1179 } 1180 1181 /** 1182 * This method is typically coupled with an action that triggers a device 1183 * request from an api such as WebBluetooth. 1184 * 1185 * :::caution 1186 * 1187 * This must be called before the device request is made. It will not return a 1188 * currently active device prompt. 1189 * 1190 * ::: 1191 * 1192 * @example 1193 * 1194 * ```ts 1195 * const [devicePrompt] = Promise.all([ 1196 * frame.waitForDevicePrompt(), 1197 * frame.click('#connect-bluetooth'), 1198 * ]); 1199 * await devicePrompt.select( 1200 * await devicePrompt.waitForDevice(({name}) => name.includes('My Device')), 1201 * ); 1202 * ``` 1203 * 1204 * @internal 1205 */ 1206 abstract waitForDevicePrompt( 1207 options?: WaitTimeoutOptions, 1208 ): Promise<DeviceRequestPrompt>; 1209 }