@crawlee/puppeteer 4.0.0-beta.15 → 4.0.0-beta.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/internals/enqueue-links/click-elements.d.ts +39 -65
- package/internals/enqueue-links/click-elements.js +59 -64
- package/internals/puppeteer-browser-pool.d.ts +55 -0
- package/internals/puppeteer-browser-pool.js +48 -0
- package/internals/puppeteer-crawler.d.ts +162 -115
- package/internals/puppeteer-crawler.js +52 -67
- package/internals/puppeteer-launcher.d.ts +31 -19
- package/internals/puppeteer-launcher.js +18 -14
- package/internals/utils/puppeteer_request_interception.d.ts +0 -1
- package/internals/utils/puppeteer_request_interception.js +8 -8
- package/internals/utils/puppeteer_utils.d.ts +10 -28
- package/internals/utils/puppeteer_utils.js +83 -91
- package/package.json +10 -15
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/internals/enqueue-links/click-elements.d.ts.map +0 -1
- package/internals/enqueue-links/click-elements.js.map +0 -1
- package/internals/puppeteer-crawler.d.ts.map +0 -1
- package/internals/puppeteer-crawler.js.map +0 -1
- package/internals/puppeteer-launcher.d.ts.map +0 -1
- package/internals/puppeteer-launcher.js.map +0 -1
- package/internals/utils/puppeteer_request_interception.d.ts.map +0 -1
- package/internals/utils/puppeteer_request_interception.js.map +0 -1
- package/internals/utils/puppeteer_utils.d.ts.map +0 -1
- package/internals/utils/puppeteer_utils.js.map +0 -1
|
@@ -3,6 +3,7 @@ import { BrowserLauncher, Configuration } from '@crawlee/browser';
|
|
|
3
3
|
import { PuppeteerPlugin } from '@crawlee/browser-pool';
|
|
4
4
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
5
5
|
import type { Browser } from 'puppeteer';
|
|
6
|
+
import { z } from 'zod';
|
|
6
7
|
/**
|
|
7
8
|
* Apify extends the launch options of Puppeteer.
|
|
8
9
|
* You can use any of the Puppeteer compatible
|
|
@@ -66,30 +67,42 @@ export interface PuppeteerLaunchContext extends BrowserLaunchContext<PuppeteerPl
|
|
|
66
67
|
* @ignore
|
|
67
68
|
*/
|
|
68
69
|
export declare class PuppeteerLauncher extends BrowserLauncher<PuppeteerPlugin, unknown> {
|
|
69
|
-
readonly
|
|
70
|
+
readonly configuration: Configuration;
|
|
71
|
+
/**
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
70
74
|
protected static optionsShape: {
|
|
75
|
+
proxyUrl: z.ZodOptional<z.ZodURL>;
|
|
76
|
+
useChrome: z.ZodOptional<z.ZodBoolean>;
|
|
77
|
+
useIncognitoPages: z.ZodOptional<z.ZodBoolean>;
|
|
78
|
+
browserPerProxy: z.ZodOptional<z.ZodBoolean>;
|
|
79
|
+
ignoreProxyCertificate: z.ZodOptional<z.ZodBoolean>;
|
|
80
|
+
userDataDir: z.ZodOptional<z.ZodString>;
|
|
71
81
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
proxyUrl: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
|
|
75
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
76
|
-
useChrome: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
|
|
82
|
+
launchOptions: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
83
|
+
userAgent: z.ZodOptional<z.ZodString>;
|
|
77
84
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
launcher: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
86
|
+
};
|
|
87
|
+
/** @internal */
|
|
88
|
+
protected static optionsSchema: z.ZodObject<{
|
|
89
|
+
proxyUrl: z.ZodOptional<z.ZodURL>;
|
|
90
|
+
useChrome: z.ZodOptional<z.ZodBoolean>;
|
|
91
|
+
useIncognitoPages: z.ZodOptional<z.ZodBoolean>;
|
|
92
|
+
browserPerProxy: z.ZodOptional<z.ZodBoolean>;
|
|
93
|
+
ignoreProxyCertificate: z.ZodOptional<z.ZodBoolean>;
|
|
94
|
+
userDataDir: z.ZodOptional<z.ZodString>;
|
|
83
95
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
84
|
-
launchOptions: import("
|
|
96
|
+
launchOptions: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
97
|
+
userAgent: z.ZodOptional<z.ZodString>;
|
|
85
98
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
86
|
-
|
|
87
|
-
}
|
|
99
|
+
launcher: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
100
|
+
}, z.core.$strict>;
|
|
88
101
|
/**
|
|
89
102
|
* All `PuppeteerLauncher` parameters are passed via an launchContext object.
|
|
90
103
|
*/
|
|
91
|
-
constructor(launchContext?: PuppeteerLaunchContext,
|
|
92
|
-
protected
|
|
104
|
+
constructor(launchContext?: PuppeteerLaunchContext, configuration?: Configuration);
|
|
105
|
+
protected getDefaultHeadlessOption(): boolean;
|
|
93
106
|
}
|
|
94
107
|
/**
|
|
95
108
|
* Launches headless Chrome using Puppeteer pre-configured to work within the Apify platform.
|
|
@@ -119,9 +132,8 @@ export declare class PuppeteerLauncher extends BrowserLauncher<PuppeteerPlugin,
|
|
|
119
132
|
* @param [launchContext]
|
|
120
133
|
* All `PuppeteerLauncher` parameters are passed via an launchContext object.
|
|
121
134
|
* If you want to pass custom `puppeteer.launch(options)` options you can use the `PuppeteerLaunchContext.launchOptions` property.
|
|
122
|
-
* @param [
|
|
135
|
+
* @param [configuration]
|
|
123
136
|
* @returns
|
|
124
137
|
* Promise that resolves to Puppeteer's `Browser` instance.
|
|
125
138
|
*/
|
|
126
|
-
export declare function launchPuppeteer(launchContext?: PuppeteerLaunchContext,
|
|
127
|
-
//# sourceMappingURL=puppeteer-launcher.d.ts.map
|
|
139
|
+
export declare function launchPuppeteer(launchContext?: PuppeteerLaunchContext, configuration?: Configuration): Promise<Browser>;
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
import { BrowserLauncher, Configuration } from '@crawlee/browser';
|
|
2
2
|
import { PuppeteerPlugin } from '@crawlee/browser-pool';
|
|
3
|
-
import
|
|
3
|
+
import { parseArgument, schemas } from '@crawlee/utils/internal';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
/**
|
|
5
6
|
* `PuppeteerLauncher` is based on the `BrowserLauncher`. It launches `puppeteer` browser instance.
|
|
6
7
|
* @ignore
|
|
7
8
|
*/
|
|
8
9
|
export class PuppeteerLauncher extends BrowserLauncher {
|
|
9
|
-
|
|
10
|
+
configuration;
|
|
11
|
+
/**
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
10
14
|
static optionsShape = {
|
|
11
15
|
...BrowserLauncher.optionsShape,
|
|
12
|
-
launcher:
|
|
16
|
+
launcher: schemas.anyObject.optional(),
|
|
13
17
|
};
|
|
18
|
+
/** @internal */
|
|
19
|
+
static optionsSchema = z.strictObject(PuppeteerLauncher.optionsShape);
|
|
14
20
|
/**
|
|
15
21
|
* All `PuppeteerLauncher` parameters are passed via an launchContext object.
|
|
16
22
|
*/
|
|
17
|
-
constructor(launchContext = {},
|
|
18
|
-
|
|
19
|
-
const { launcher = BrowserLauncher.requireLauncherOrThrow('puppeteer', 'apify/actor-node-puppeteer-chrome'), ...browserLauncherOptions } = launchContext;
|
|
23
|
+
constructor(launchContext = {}, configuration = Configuration.getGlobalConfiguration()) {
|
|
24
|
+
const { launcher = BrowserLauncher.requireLauncherOrThrow('puppeteer', 'apify/actor-node-puppeteer-chrome'), ...browserLauncherOptions } = parseArgument(launchContext, PuppeteerLauncher.optionsSchema, 'PuppeteerLaunchContext');
|
|
20
25
|
super({
|
|
21
26
|
...browserLauncherOptions,
|
|
22
27
|
launcher,
|
|
23
|
-
},
|
|
24
|
-
this.
|
|
28
|
+
}, configuration);
|
|
29
|
+
this.configuration = configuration;
|
|
25
30
|
this.Plugin = PuppeteerPlugin;
|
|
26
31
|
}
|
|
27
|
-
|
|
28
|
-
const headless = super.
|
|
32
|
+
getDefaultHeadlessOption() {
|
|
33
|
+
const headless = super.getDefaultHeadlessOption();
|
|
29
34
|
return headless ? 'new' : headless;
|
|
30
35
|
}
|
|
31
36
|
}
|
|
@@ -57,12 +62,11 @@ export class PuppeteerLauncher extends BrowserLauncher {
|
|
|
57
62
|
* @param [launchContext]
|
|
58
63
|
* All `PuppeteerLauncher` parameters are passed via an launchContext object.
|
|
59
64
|
* If you want to pass custom `puppeteer.launch(options)` options you can use the `PuppeteerLaunchContext.launchOptions` property.
|
|
60
|
-
* @param [
|
|
65
|
+
* @param [configuration]
|
|
61
66
|
* @returns
|
|
62
67
|
* Promise that resolves to Puppeteer's `Browser` instance.
|
|
63
68
|
*/
|
|
64
|
-
export async function launchPuppeteer(launchContext,
|
|
65
|
-
const puppeteerLauncher = new PuppeteerLauncher(launchContext,
|
|
69
|
+
export async function launchPuppeteer(launchContext, configuration = Configuration.getGlobalConfiguration()) {
|
|
70
|
+
const puppeteerLauncher = new PuppeteerLauncher(launchContext, configuration);
|
|
66
71
|
return puppeteerLauncher.launch();
|
|
67
72
|
}
|
|
68
|
-
//# sourceMappingURL=puppeteer-launcher.js.map
|
|
@@ -58,4 +58,3 @@ export declare function addInterceptRequestHandler(page: Page, handler: Intercep
|
|
|
58
58
|
* @param handler Request interception handler.
|
|
59
59
|
*/
|
|
60
60
|
export declare function removeInterceptRequestHandler(page: Page, handler: InterceptHandler): Promise<void>;
|
|
61
|
-
//# sourceMappingURL=puppeteer_request_interception.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { serviceLocator } from '@crawlee/browser';
|
|
3
|
+
import { parseArgument, schemas } from '@crawlee/utils/internal';
|
|
4
|
+
const pageSchema = schemas.objectWithKeys(['goto', 'evaluate']);
|
|
4
5
|
// We use weak maps here so that the content gets discarded after page gets closed.
|
|
5
6
|
const pageInterceptRequestHandlersMap = new WeakMap(); // Maps page to an array of request interception handlers.
|
|
6
7
|
const pageInterceptRequestMasterHandlerMap = new WeakMap(); // Maps page to master request interception handler.
|
|
@@ -137,8 +138,8 @@ async function handleRequest(request, interceptRequestHandlers) {
|
|
|
137
138
|
* @param handler Request interception handler.
|
|
138
139
|
*/
|
|
139
140
|
export async function addInterceptRequestHandler(page, handler) {
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
parseArgument(page, pageSchema);
|
|
142
|
+
parseArgument(handler, schemas.anyFunction);
|
|
142
143
|
if (!pageInterceptRequestHandlersMap.has(page)) {
|
|
143
144
|
pageInterceptRequestHandlersMap.set(page, []);
|
|
144
145
|
}
|
|
@@ -174,8 +175,8 @@ export async function addInterceptRequestHandler(page, handler) {
|
|
|
174
175
|
* @param handler Request interception handler.
|
|
175
176
|
*/
|
|
176
177
|
export async function removeInterceptRequestHandler(page, handler) {
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
parseArgument(page, pageSchema);
|
|
179
|
+
parseArgument(handler, schemas.anyFunction);
|
|
179
180
|
const handlersArray = pageInterceptRequestHandlersMap.get(page).filter((item) => item !== handler);
|
|
180
181
|
pageInterceptRequestHandlersMap.set(page, handlersArray);
|
|
181
182
|
if (handlersArray.length === 0) {
|
|
@@ -194,7 +195,7 @@ export async function removeInterceptRequestHandler(page, handler) {
|
|
|
194
195
|
interceptedRequestsInProgress.removeListener('delete', onDelete);
|
|
195
196
|
}
|
|
196
197
|
catch (error) {
|
|
197
|
-
|
|
198
|
+
serviceLocator.getLogger().debug('Error while disabling request interception', { error });
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
};
|
|
@@ -207,4 +208,3 @@ async function disableRequestInterception(page) {
|
|
|
207
208
|
const requestHandler = pageInterceptRequestMasterHandlerMap.get(page);
|
|
208
209
|
page.off('request', requestHandler);
|
|
209
210
|
}
|
|
210
|
-
//# sourceMappingURL=puppeteer_request_interception.js.map
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import type { Request } from '@crawlee/browser';
|
|
21
21
|
import { Configuration } from '@crawlee/browser';
|
|
22
22
|
import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
|
|
23
|
-
import {
|
|
23
|
+
import type { CheerioAPI } from 'cheerio';
|
|
24
24
|
import type { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js';
|
|
25
25
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
26
26
|
import type { HTTPResponse, Page, ResponseForRequest } from 'puppeteer';
|
|
@@ -129,7 +129,7 @@ export declare function injectJQuery(page: Page, options?: {
|
|
|
129
129
|
* @param page Puppeteer [`Page`](https://pptr.dev/api/puppeteer.page) object.
|
|
130
130
|
* @param ignoreShadowRoots
|
|
131
131
|
*/
|
|
132
|
-
export declare function parseWithCheerio(page: Page, ignoreShadowRoots?: boolean, ignoreIframes?: boolean): Promise<
|
|
132
|
+
export declare function parseWithCheerio(page: Page, ignoreShadowRoots?: boolean, ignoreIframes?: boolean): Promise<CheerioAPI>;
|
|
133
133
|
/**
|
|
134
134
|
* Forces the Puppeteer browser tab to block loading URLs that match a provided pattern.
|
|
135
135
|
* This is useful to speed up crawling of websites, since it reduces the amount
|
|
@@ -305,9 +305,9 @@ export interface SaveSnapshotOptions {
|
|
|
305
305
|
keyValueStoreName?: string | null;
|
|
306
306
|
/**
|
|
307
307
|
* Configuration of the crawler that will be used to save the snapshot.
|
|
308
|
-
* @default Configuration.
|
|
308
|
+
* @default Configuration.getGlobalConfiguration()
|
|
309
309
|
*/
|
|
310
|
-
|
|
310
|
+
configuration?: Configuration;
|
|
311
311
|
}
|
|
312
312
|
/**
|
|
313
313
|
* Saves a full screenshot and HTML of the current page into a Key-Value store.
|
|
@@ -315,8 +315,6 @@ export interface SaveSnapshotOptions {
|
|
|
315
315
|
* @param [options]
|
|
316
316
|
*/
|
|
317
317
|
export declare function saveSnapshot(page: Page, options?: SaveSnapshotOptions): Promise<void>;
|
|
318
|
-
export declare function closeCookieModals(page: Page): Promise<void>;
|
|
319
|
-
/** @internal */
|
|
320
318
|
export interface PuppeteerContextUtils {
|
|
321
319
|
/**
|
|
322
320
|
* Injects a JavaScript file into current `page`.
|
|
@@ -378,7 +376,7 @@ export interface PuppeteerContextUtils {
|
|
|
378
376
|
* });
|
|
379
377
|
* ```
|
|
380
378
|
*/
|
|
381
|
-
parseWithCheerio(selector?: string, timeoutMs?: number): Promise<
|
|
379
|
+
parseWithCheerio(selector?: string, timeoutMs?: number): Promise<CheerioAPI>;
|
|
382
380
|
/**
|
|
383
381
|
* The function finds elements matching a specific CSS selector in a Puppeteer page,
|
|
384
382
|
* clicks all those elements using a mouse move and a left mouse button click and intercepts
|
|
@@ -388,8 +386,7 @@ export interface PuppeteerContextUtils {
|
|
|
388
386
|
* in `href` elements, but rather navigations are triggered in click handlers.
|
|
389
387
|
* If you're looking to find URLs in `href` attributes of the page, see {@link enqueueLinks}.
|
|
390
388
|
*
|
|
391
|
-
* Optionally, the function allows you to filter the target links' URLs using an array of
|
|
392
|
-
* and override settings of the enqueued {@link Request} objects.
|
|
389
|
+
* Optionally, the function allows you to filter the target links' URLs using an array of glob or regexp patterns.
|
|
393
390
|
*
|
|
394
391
|
* **IMPORTANT**: To be able to do this, this function uses various mutations on the page,
|
|
395
392
|
* such as changing the Z-index of elements being clicked and their visibility. Therefore,
|
|
@@ -410,9 +407,9 @@ export interface PuppeteerContextUtils {
|
|
|
410
407
|
* async requestHandler({ enqueueLinksByClickingElements }) {
|
|
411
408
|
* await enqueueLinksByClickingElements({
|
|
412
409
|
* selector: 'a.product-detail',
|
|
413
|
-
*
|
|
414
|
-
* 'https://www.example.com/handbags/**'
|
|
415
|
-
* 'https://www.example.com/purses/**'
|
|
410
|
+
* include: [
|
|
411
|
+
* 'https://www.example.com/handbags/**',
|
|
412
|
+
* 'https://www.example.com/purses/**',
|
|
416
413
|
* ],
|
|
417
414
|
* });
|
|
418
415
|
* });
|
|
@@ -420,7 +417,7 @@ export interface PuppeteerContextUtils {
|
|
|
420
417
|
*
|
|
421
418
|
* @returns Promise that resolves to {@link BatchAddRequestsResult} object.
|
|
422
419
|
*/
|
|
423
|
-
enqueueLinksByClickingElements(options: Omit<EnqueueLinksByClickingElementsOptions, 'page' | '
|
|
420
|
+
enqueueLinksByClickingElements(options: Omit<EnqueueLinksByClickingElementsOptions, 'page' | 'requestManager'>): Promise<BatchAddRequestsResult>;
|
|
424
421
|
/**
|
|
425
422
|
* Forces the Puppeteer browser tab to block loading URLs that match a provided pattern.
|
|
426
423
|
* This is useful to speed up crawling of websites, since it reduces the amount
|
|
@@ -550,19 +547,6 @@ export interface PuppeteerContextUtils {
|
|
|
550
547
|
* Saves a full screenshot and HTML of the current page into a Key-Value store.
|
|
551
548
|
*/
|
|
552
549
|
saveSnapshot(options?: SaveSnapshotOptions): Promise<void>;
|
|
553
|
-
/**
|
|
554
|
-
* Tries to close cookie consent modals on the page. Based on the I Don't Care About Cookies browser extension.
|
|
555
|
-
*
|
|
556
|
-
* Note that this method requires the idcac-playwright package to be installed.
|
|
557
|
-
* Crawlee does not include it by default due to licensing issues.
|
|
558
|
-
*
|
|
559
|
-
* To use this method, please install the package manually by running:
|
|
560
|
-
*
|
|
561
|
-
* ```bash
|
|
562
|
-
* npm install idcac-playwright
|
|
563
|
-
* ```
|
|
564
|
-
*/
|
|
565
|
-
closeCookieModals(): Promise<void>;
|
|
566
550
|
}
|
|
567
551
|
export { enqueueLinksByClickingElements, addInterceptRequestHandler, removeInterceptRequestHandler };
|
|
568
552
|
/** @internal */
|
|
@@ -578,6 +562,4 @@ export declare const puppeteerUtils: {
|
|
|
578
562
|
infiniteScroll: typeof infiniteScroll;
|
|
579
563
|
saveSnapshot: typeof saveSnapshot;
|
|
580
564
|
parseWithCheerio: typeof parseWithCheerio;
|
|
581
|
-
closeCookieModals: typeof closeCookieModals;
|
|
582
565
|
};
|
|
583
|
-
//# sourceMappingURL=puppeteer_utils.d.ts.map
|
|
@@ -20,19 +20,49 @@
|
|
|
20
20
|
import { readFile } from 'node:fs/promises';
|
|
21
21
|
import { createRequire } from 'node:module';
|
|
22
22
|
import vm from 'node:vm';
|
|
23
|
-
import { Configuration, KeyValueStore, validators } from '@crawlee/browser';
|
|
23
|
+
import { Configuration, KeyValueStore, serviceLocator, validators } from '@crawlee/browser';
|
|
24
24
|
import { expandShadowRoots, sleep } from '@crawlee/utils';
|
|
25
|
-
import
|
|
26
|
-
import
|
|
25
|
+
import { parseArgument, schemas } from '@crawlee/utils/internal';
|
|
26
|
+
import { z } from 'zod';
|
|
27
27
|
import { LruCache } from '@apify/datastructures';
|
|
28
|
-
import log_ from '@apify/log';
|
|
29
28
|
import { enqueueLinksByClickingElements } from '../enqueue-links/click-elements.js';
|
|
30
29
|
import { addInterceptRequestHandler, removeInterceptRequestHandler } from './puppeteer_request_interception.js';
|
|
31
30
|
const require = createRequire(import.meta.url);
|
|
32
31
|
const jqueryPath = require.resolve('jquery');
|
|
33
32
|
const MAX_INJECT_FILE_CACHE_SIZE = 10;
|
|
34
33
|
const DEFAULT_BLOCK_REQUEST_URL_PATTERNS = ['.css', '.jpg', '.jpeg', '.png', '.svg', '.gif', '.woff', '.pdf', '.zip'];
|
|
35
|
-
const
|
|
34
|
+
const filePathSchema = z.string();
|
|
35
|
+
const injectFileOptionsSchema = z.strictObject({
|
|
36
|
+
surviveNavigations: z.boolean().optional(),
|
|
37
|
+
});
|
|
38
|
+
const responseUrlRulesSchema = schemas.arrayOf(z.union([z.string(), z.instanceof(RegExp)]), 'strings or RegExps');
|
|
39
|
+
const gotoExtendedRequestSchema = z.looseObject({
|
|
40
|
+
url: z.url(),
|
|
41
|
+
method: z.string().optional(),
|
|
42
|
+
headers: schemas.anyObject.optional(),
|
|
43
|
+
payload: z.union([z.string(), z.instanceof(Uint8Array)]).optional(),
|
|
44
|
+
});
|
|
45
|
+
const blockRequestsOptionsSchema = z.strictObject({
|
|
46
|
+
urlPatterns: schemas.arrayOf(z.string(), 'strings').default(DEFAULT_BLOCK_REQUEST_URL_PATTERNS),
|
|
47
|
+
extraUrlPatterns: schemas.arrayOf(z.string(), 'strings').default(() => []),
|
|
48
|
+
});
|
|
49
|
+
const infiniteScrollOptionsSchema = z.strictObject({
|
|
50
|
+
timeoutSecs: schemas.anyNumber.default(0),
|
|
51
|
+
maxScrollHeight: schemas.anyNumber.default(0),
|
|
52
|
+
waitForSecs: schemas.anyNumber.default(4),
|
|
53
|
+
scrollDownAndUp: z.boolean().default(false),
|
|
54
|
+
buttonSelector: z.string().optional(),
|
|
55
|
+
stopScrollCallback: schemas.anyFunction.optional(),
|
|
56
|
+
});
|
|
57
|
+
const saveSnapshotOptionsSchema = z.strictObject({
|
|
58
|
+
key: z.string().min(1).default('SNAPSHOT'),
|
|
59
|
+
screenshotQuality: schemas.anyNumber.default(50),
|
|
60
|
+
saveScreenshot: z.boolean().default(true),
|
|
61
|
+
saveHtml: z.boolean().default(true),
|
|
62
|
+
keyValueStoreName: z.string().optional(),
|
|
63
|
+
configuration: schemas.anyObject.optional(),
|
|
64
|
+
});
|
|
65
|
+
const getLog = () => serviceLocator.getChildLog('Puppeteer Utils');
|
|
36
66
|
/**
|
|
37
67
|
* Cache contents of previously injected files to limit file system access.
|
|
38
68
|
*/
|
|
@@ -49,21 +79,19 @@ const injectedFilesCache = new LruCache({ maxLength: MAX_INJECT_FILE_CACHE_SIZE
|
|
|
49
79
|
* @param [options]
|
|
50
80
|
*/
|
|
51
81
|
export async function injectFile(page, filePath, options = {}) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
surviveNavigations: ow.optional.boolean,
|
|
56
|
-
}));
|
|
82
|
+
parseArgument(page, validators.browserPage);
|
|
83
|
+
parseArgument(filePath, filePathSchema);
|
|
84
|
+
const { surviveNavigations } = parseArgument(options, injectFileOptionsSchema);
|
|
57
85
|
let contents = injectedFilesCache.get(filePath);
|
|
58
86
|
if (!contents) {
|
|
59
87
|
contents = await readFile(filePath, 'utf8');
|
|
60
88
|
injectedFilesCache.add(filePath, contents);
|
|
61
89
|
}
|
|
62
90
|
const evalP = page.evaluate(contents);
|
|
63
|
-
if (
|
|
91
|
+
if (surviveNavigations) {
|
|
64
92
|
page.on('framenavigated', async () => page
|
|
65
93
|
.evaluate(contents)
|
|
66
|
-
.catch((error) =>
|
|
94
|
+
.catch((error) => getLog().warning('An error occurred during the script injection!', { error })));
|
|
67
95
|
}
|
|
68
96
|
return evalP;
|
|
69
97
|
}
|
|
@@ -94,7 +122,7 @@ export async function injectFile(page, filePath, options = {}) {
|
|
|
94
122
|
* @param [options.surviveNavigations] Opt-out option to disable the JQuery reinjection after navigation.
|
|
95
123
|
*/
|
|
96
124
|
export async function injectJQuery(page, options) {
|
|
97
|
-
|
|
125
|
+
parseArgument(page, validators.browserPage);
|
|
98
126
|
return injectFile(page, jqueryPath, { surviveNavigations: options?.surviveNavigations ?? true });
|
|
99
127
|
}
|
|
100
128
|
/**
|
|
@@ -110,7 +138,7 @@ export async function injectJQuery(page, options) {
|
|
|
110
138
|
* @param ignoreShadowRoots
|
|
111
139
|
*/
|
|
112
140
|
export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIframes = false) {
|
|
113
|
-
|
|
141
|
+
parseArgument(page, validators.browserPage);
|
|
114
142
|
if (page.frames().length > 1 && !ignoreIframes) {
|
|
115
143
|
const frames = await page.$$('iframe');
|
|
116
144
|
await Promise.all(frames.map(async (frame) => {
|
|
@@ -135,7 +163,7 @@ export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIf
|
|
|
135
163
|
}
|
|
136
164
|
}
|
|
137
165
|
catch (error) {
|
|
138
|
-
|
|
166
|
+
getLog().warning(`Failed to extract iframe content: ${error}`);
|
|
139
167
|
}
|
|
140
168
|
}));
|
|
141
169
|
}
|
|
@@ -143,7 +171,8 @@ export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIf
|
|
|
143
171
|
? null
|
|
144
172
|
: (await page.evaluate(`(${expandShadowRoots.toString()})(document)`));
|
|
145
173
|
const pageContent = html || (await page.content());
|
|
146
|
-
|
|
174
|
+
const { load } = await import('cheerio');
|
|
175
|
+
return load(pageContent);
|
|
147
176
|
}
|
|
148
177
|
/**
|
|
149
178
|
* Forces the Puppeteer browser tab to block loading URLs that match a provided pattern.
|
|
@@ -188,12 +217,8 @@ export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIf
|
|
|
188
217
|
* @param [options]
|
|
189
218
|
*/
|
|
190
219
|
export async function blockRequests(page, options = {}) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
urlPatterns: ow.optional.array.ofType(ow.string),
|
|
194
|
-
extraUrlPatterns: ow.optional.array.ofType(ow.string),
|
|
195
|
-
}));
|
|
196
|
-
const { urlPatterns = DEFAULT_BLOCK_REQUEST_URL_PATTERNS, extraUrlPatterns = [] } = options;
|
|
220
|
+
parseArgument(page, validators.browserPage);
|
|
221
|
+
const { urlPatterns, extraUrlPatterns } = parseArgument(options, blockRequestsOptionsSchema);
|
|
197
222
|
const patternsToBlock = [...urlPatterns, ...extraUrlPatterns];
|
|
198
223
|
// We use CDP commands instead of request interception as the latter disables caching, which is not ideal
|
|
199
224
|
await sendCDPCommand(page, 'Network.setBlockedURLs', { urls: patternsToBlock });
|
|
@@ -221,7 +246,9 @@ export async function sendCDPCommand(page, command, ...args) {
|
|
|
221
246
|
* @deprecated
|
|
222
247
|
*/
|
|
223
248
|
export const blockResources = async (page, resourceTypes = ['stylesheet', 'font', 'image', 'media']) => {
|
|
224
|
-
|
|
249
|
+
serviceLocator
|
|
250
|
+
.getLogger()
|
|
251
|
+
.deprecated('utils.puppeteer.blockResources() has a high impact on performance in recent versions of Puppeteer. ' +
|
|
225
252
|
'Until this resolves, please use utils.puppeteer.blockRequests()');
|
|
226
253
|
await addInterceptRequestHandler(page, async (request) => {
|
|
227
254
|
const type = request.resourceType();
|
|
@@ -248,10 +275,12 @@ export const blockResources = async (page, resourceTypes = ['stylesheet', 'font'
|
|
|
248
275
|
* @deprecated
|
|
249
276
|
*/
|
|
250
277
|
export async function cacheResponses(page, cache, responseUrlRules) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
278
|
+
parseArgument(page, validators.browserPage);
|
|
279
|
+
parseArgument(cache, schemas.anyObject);
|
|
280
|
+
parseArgument(responseUrlRules, responseUrlRulesSchema);
|
|
281
|
+
serviceLocator
|
|
282
|
+
.getLogger()
|
|
283
|
+
.deprecated('utils.puppeteer.cacheResponses() has a high impact on performance ' +
|
|
255
284
|
"in recent versions of Puppeteer so it's use is discouraged until this issue resolves.");
|
|
256
285
|
await addInterceptRequestHandler(page, async (request) => {
|
|
257
286
|
const url = request.url();
|
|
@@ -321,7 +350,7 @@ export function compileScript(scriptString, context = Object.create(null)) {
|
|
|
321
350
|
func = vm.runInNewContext(funcString, context); // "Secure" the context by removing prototypes, unless custom context is provided.
|
|
322
351
|
}
|
|
323
352
|
catch (err) {
|
|
324
|
-
|
|
353
|
+
getLog().exception(err, 'Cannot compile script!');
|
|
325
354
|
throw err;
|
|
326
355
|
}
|
|
327
356
|
if (typeof func !== 'function')
|
|
@@ -341,23 +370,20 @@ export function compileScript(scriptString, context = Object.create(null)) {
|
|
|
341
370
|
* @param [gotoOptions] Custom options for `page.goto()`.
|
|
342
371
|
*/
|
|
343
372
|
export async function gotoExtended(page, request, gotoOptions = {}) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
method: ow.optional.string,
|
|
348
|
-
headers: ow.optional.object,
|
|
349
|
-
payload: ow.optional.any(ow.string, ow.uint8Array),
|
|
350
|
-
}));
|
|
351
|
-
ow(gotoOptions, ow.object);
|
|
373
|
+
parseArgument(page, validators.browserPage);
|
|
374
|
+
parseArgument(request, gotoExtendedRequestSchema);
|
|
375
|
+
parseArgument(gotoOptions, schemas.anyObject);
|
|
352
376
|
gotoOptions = { ...gotoOptions };
|
|
353
377
|
if (gotoOptions.waitUntil === 'networkidle') {
|
|
354
378
|
gotoOptions.waitUntil = 'networkidle0';
|
|
355
379
|
}
|
|
356
380
|
const { url, method, headers, payload } = request;
|
|
357
381
|
const isEmpty = (o) => !o || Object.keys(o).length === 0;
|
|
358
|
-
if (method !== 'GET' || payload
|
|
382
|
+
if (method !== 'GET' || payload) {
|
|
359
383
|
// This is not deprecated, we use it to log only once.
|
|
360
|
-
|
|
384
|
+
serviceLocator
|
|
385
|
+
.getLogger()
|
|
386
|
+
.deprecated('Using other request methods than GET, rewriting headers and adding payloads has a high impact on performance ' +
|
|
361
387
|
'in recent versions of Puppeteer. Use only when necessary.');
|
|
362
388
|
let wasCalled = false;
|
|
363
389
|
const interceptRequestHandler = async (interceptedRequest) => {
|
|
@@ -380,6 +406,19 @@ export async function gotoExtended(page, request, gotoOptions = {}) {
|
|
|
380
406
|
};
|
|
381
407
|
await addInterceptRequestHandler(page, interceptRequestHandler);
|
|
382
408
|
}
|
|
409
|
+
else if (!isEmpty(headers)) {
|
|
410
|
+
const extraHeaders = { ...headers };
|
|
411
|
+
// Chrome bundled with Puppeteer 25+ ignores a `User-Agent` passed via `setExtraHTTPHeaders()`, it has to be set explicitly.
|
|
412
|
+
for (const name of Object.keys(extraHeaders)) {
|
|
413
|
+
if (name.toLowerCase() === 'user-agent') {
|
|
414
|
+
await page.setUserAgent(extraHeaders[name]);
|
|
415
|
+
delete extraHeaders[name];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!isEmpty(extraHeaders)) {
|
|
419
|
+
await page.setExtraHTTPHeaders(extraHeaders);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
383
422
|
return page.goto(url, gotoOptions);
|
|
384
423
|
}
|
|
385
424
|
/**
|
|
@@ -389,16 +428,8 @@ export async function gotoExtended(page, request, gotoOptions = {}) {
|
|
|
389
428
|
* @param [options]
|
|
390
429
|
*/
|
|
391
430
|
export async function infiniteScroll(page, options = {}) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
timeoutSecs: ow.optional.number,
|
|
395
|
-
maxScrollHeight: ow.optional.number,
|
|
396
|
-
waitForSecs: ow.optional.number,
|
|
397
|
-
scrollDownAndUp: ow.optional.boolean,
|
|
398
|
-
buttonSelector: ow.optional.string,
|
|
399
|
-
stopScrollCallback: ow.optional.function,
|
|
400
|
-
}));
|
|
401
|
-
const { timeoutSecs = 0, maxScrollHeight = 0, waitForSecs = 4, scrollDownAndUp = false, buttonSelector, stopScrollCallback, } = options;
|
|
431
|
+
parseArgument(page, validators.browserPage);
|
|
432
|
+
const { timeoutSecs, maxScrollHeight, waitForSecs, scrollDownAndUp, buttonSelector, stopScrollCallback } = parseArgument(options, infiniteScrollOptionsSchema);
|
|
402
433
|
let finished;
|
|
403
434
|
const startTime = Date.now();
|
|
404
435
|
const CHECK_INTERVAL_MILLIS = 1000;
|
|
@@ -490,19 +521,11 @@ export async function infiniteScroll(page, options = {}) {
|
|
|
490
521
|
* @param [options]
|
|
491
522
|
*/
|
|
492
523
|
export async function saveSnapshot(page, options = {}) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
key: ow.optional.string.nonEmpty,
|
|
496
|
-
screenshotQuality: ow.optional.number,
|
|
497
|
-
saveScreenshot: ow.optional.boolean,
|
|
498
|
-
saveHtml: ow.optional.boolean,
|
|
499
|
-
keyValueStoreName: ow.optional.string,
|
|
500
|
-
config: ow.optional.object,
|
|
501
|
-
}));
|
|
502
|
-
const { key = 'SNAPSHOT', screenshotQuality = 50, saveScreenshot = true, saveHtml = true, keyValueStoreName, config, } = options;
|
|
524
|
+
parseArgument(page, validators.browserPage);
|
|
525
|
+
const { key, screenshotQuality, saveScreenshot, saveHtml, keyValueStoreName, configuration } = parseArgument(options, saveSnapshotOptionsSchema);
|
|
503
526
|
try {
|
|
504
|
-
const store = await KeyValueStore.open(keyValueStoreName, {
|
|
505
|
-
|
|
527
|
+
const store = await KeyValueStore.open(keyValueStoreName ? { name: keyValueStoreName } : null, {
|
|
528
|
+
configuration: configuration ?? Configuration.getGlobalConfiguration(),
|
|
506
529
|
});
|
|
507
530
|
if (saveScreenshot) {
|
|
508
531
|
const screenshotName = `${key}.jpg`;
|
|
@@ -523,35 +546,6 @@ export async function saveSnapshot(page, options = {}) {
|
|
|
523
546
|
throw new Error(`saveSnapshot with key ${key} failed.\nCause:${err.message}`);
|
|
524
547
|
}
|
|
525
548
|
}
|
|
526
|
-
let idcacPlaywright = null;
|
|
527
|
-
async function getIdcacPlaywright() {
|
|
528
|
-
if (idcacPlaywright)
|
|
529
|
-
return idcacPlaywright;
|
|
530
|
-
try {
|
|
531
|
-
idcacPlaywright = await import('idcac-playwright');
|
|
532
|
-
}
|
|
533
|
-
catch (error) {
|
|
534
|
-
log.warning(`Failed to import 'idcac-playwright'.
|
|
535
|
-
|
|
536
|
-
We recently made idcac-playwright an optional dependency due to licensing issues.
|
|
537
|
-
To use this feature, please install it manually by running
|
|
538
|
-
|
|
539
|
-
npm install idcac-playwright
|
|
540
|
-
|
|
541
|
-
Original error message follows:
|
|
542
|
-
|
|
543
|
-
${error.message}
|
|
544
|
-
`);
|
|
545
|
-
}
|
|
546
|
-
return idcacPlaywright;
|
|
547
|
-
}
|
|
548
|
-
export async function closeCookieModals(page) {
|
|
549
|
-
ow(page, ow.object.validate(validators.browserPage));
|
|
550
|
-
const idcac = await getIdcacPlaywright();
|
|
551
|
-
if (idcac?.getInjectableScript()) {
|
|
552
|
-
await page.evaluate(idcac.getInjectableScript());
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
549
|
export { enqueueLinksByClickingElements, addInterceptRequestHandler, removeInterceptRequestHandler };
|
|
556
550
|
/** @internal */
|
|
557
551
|
export const puppeteerUtils = {
|
|
@@ -566,6 +560,4 @@ export const puppeteerUtils = {
|
|
|
566
560
|
infiniteScroll,
|
|
567
561
|
saveSnapshot,
|
|
568
562
|
parseWithCheerio,
|
|
569
|
-
closeCookieModals,
|
|
570
563
|
};
|
|
571
|
-
//# sourceMappingURL=puppeteer_utils.js.map
|