UNPKG

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