1 |
|
2 |
|
3 |
|
4 |
|
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 |
|
46 |
|
47 | export interface WaitForOptions {
|
48 | |
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 | timeout?: number;
|
58 | |
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
|
65 | |
66 |
|
67 |
|
68 | ignoreSameDocumentNavigation?: boolean;
|
69 | |
70 |
|
71 |
|
72 | signal?: AbortSignal;
|
73 | }
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | export interface GoToOptions extends WaitForOptions {
|
79 | |
80 |
|
81 |
|
82 |
|
83 | referer?: string;
|
84 | |
85 |
|
86 |
|
87 |
|
88 | referrerPolicy?: string;
|
89 | }
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | export interface FrameWaitForFunctionOptions {
|
95 | |
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | polling?: 'raf' | 'mutation' | number;
|
108 | |
109 |
|
110 |
|
111 |
|
112 |
|
113 | timeout?: number;
|
114 | |
115 |
|
116 |
|
117 | signal?: AbortSignal;
|
118 | }
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | export interface FrameAddScriptTagOptions {
|
124 | |
125 |
|
126 |
|
127 | url?: string;
|
128 | |
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 | path?: string;
|
136 | |
137 |
|
138 |
|
139 | content?: string;
|
140 | |
141 |
|
142 |
|
143 | type?: string;
|
144 | |
145 |
|
146 |
|
147 | id?: string;
|
148 | }
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | export interface FrameAddStyleTagOptions {
|
154 | |
155 |
|
156 |
|
157 | url?: string;
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 | path?: string;
|
165 | |
166 |
|
167 |
|
168 | content?: string;
|
169 | }
|
170 |
|
171 |
|
172 |
|
173 |
|
174 | export interface FrameEvents extends Record<EventType, unknown> {
|
175 |
|
176 | [FrameEvent.FrameNavigated]: Protocol.Page.NavigationType;
|
177 |
|
178 | [FrameEvent.FrameSwapped]: undefined;
|
179 |
|
180 | [FrameEvent.LifecycleEvent]: undefined;
|
181 |
|
182 | [FrameEvent.FrameNavigatedWithinDocument]: undefined;
|
183 |
|
184 | [FrameEvent.FrameDetached]: Frame;
|
185 |
|
186 | [FrameEvent.FrameSwappedByActivation]: undefined;
|
187 | }
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
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 |
|
211 |
|
212 | export const throwIfDetached = throwIfDisposed<Frame>(frame => {
|
213 | return `Attempted to use detached Frame '${frame._id}'.`;
|
214 | });
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | export abstract class Frame extends EventEmitter<FrameEvents> {
|
270 | |
271 |
|
272 |
|
273 | _id!: string;
|
274 | |
275 |
|
276 |
|
277 | _parentId?: string;
|
278 |
|
279 | |
280 |
|
281 |
|
282 | _name?: string;
|
283 |
|
284 | |
285 |
|
286 |
|
287 | _hasStartedLoading = false;
|
288 |
|
289 | |
290 |
|
291 |
|
292 | constructor() {
|
293 | super();
|
294 | }
|
295 |
|
296 | |
297 |
|
298 |
|
299 | abstract page(): Page;
|
300 |
|
301 | |
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 | abstract goto(
|
339 | url: string,
|
340 | options?: GoToOptions,
|
341 | ): Promise<HTTPResponse | null>;
|
342 |
|
343 | |
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 | abstract waitForNavigation(
|
366 | options?: WaitForOptions,
|
367 | ): Promise<HTTPResponse | null>;
|
368 |
|
369 | |
370 |
|
371 |
|
372 | abstract get client(): CDPSession;
|
373 |
|
374 | |
375 |
|
376 |
|
377 | abstract get accessibility(): Accessibility;
|
378 |
|
379 | |
380 |
|
381 |
|
382 | abstract mainRealm(): Realm;
|
383 |
|
384 | |
385 |
|
386 |
|
387 | abstract isolatedRealm(): Realm;
|
388 |
|
389 | #_document: Promise<ElementHandle<Document>> | undefined;
|
390 |
|
391 | |
392 |
|
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 |
|
405 |
|
406 |
|
407 |
|
408 | clearDocumentHandle(): void {
|
409 | this.#_document = undefined;
|
410 | }
|
411 |
|
412 | |
413 |
|
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 |
|
437 |
|
438 |
|
439 |
|
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 |
|
458 |
|
459 |
|
460 |
|
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 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 | locator<Selector extends string>(
|
498 | selector: Selector,
|
499 | ): Locator<NodeFor<Selector>>;
|
500 |
|
501 | |
502 |
|
503 |
|
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:
|
710 | * 'https:
|
711 | * 'https:
|
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 | * {},
|
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 |