graceful-playwright 1.0.0 → 1.1.1
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 +61 -5
- package/core.d.ts +4 -2
- package/core.js +31 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,10 +6,12 @@ Gracefully handle timeout and network error with auto retry.
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- auto retry when `page.goto()` timeout or encountered
|
|
9
|
+
- auto retry when `page.goto()` timeout or encountered `ERR_NETWORK_CHANGED`
|
|
10
10
|
|
|
11
11
|
- auto restart page when `page.goto()` crashed with `/page crashed/i` error
|
|
12
12
|
|
|
13
|
+
- helper method to auto retry when failed with `The object has been collected to prevent unbounded heap growth` error
|
|
14
|
+
|
|
13
15
|
- support restarting page from `Browser` or `BrowserContext` instance
|
|
14
16
|
|
|
15
17
|
- support wrapping existing `Page` instance
|
|
@@ -36,16 +38,70 @@ import { GracefulPage } from 'graceful-playwright'
|
|
|
36
38
|
let browser = await chromium.launch()
|
|
37
39
|
let page = new GracefulPage({ from: browser })
|
|
38
40
|
|
|
39
|
-
await page.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
41
|
+
let lines: string[] = await page.autoRetryWhenFailed(async () => {
|
|
42
|
+
await page.goto('http://example.net')
|
|
43
|
+
return await page.evaluate(() =>
|
|
44
|
+
Array.from(document.querySelectorAll('a'), a => a.href),
|
|
45
|
+
)
|
|
46
|
+
})
|
|
43
47
|
console.log('lines:', lines)
|
|
44
48
|
|
|
45
49
|
await page.close()
|
|
46
50
|
await browser.close()
|
|
47
51
|
```
|
|
48
52
|
|
|
53
|
+
## Typescript Signature
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { Browser, BrowserContext, Page, Response } from 'playwright'
|
|
57
|
+
|
|
58
|
+
export class GracefulPage {
|
|
59
|
+
constructor(
|
|
60
|
+
public options: {
|
|
61
|
+
from: Browser | BrowserContext
|
|
62
|
+
page?: Page | Promise<Page>
|
|
63
|
+
/**
|
|
64
|
+
* @default 5000 ms
|
|
65
|
+
*/
|
|
66
|
+
retryInterval?: number
|
|
67
|
+
/**
|
|
68
|
+
* @default error => console.error(error)
|
|
69
|
+
*/
|
|
70
|
+
onError?: (error: unknown) => void
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
fork(): GracefulPage
|
|
75
|
+
|
|
76
|
+
getPage(): Page | Promise<Page>
|
|
77
|
+
|
|
78
|
+
restart(options?: Parameters<Page['close']>[0]): Promise<Page>
|
|
79
|
+
|
|
80
|
+
/** @description optimized version of page.close() */
|
|
81
|
+
close: Page['close']
|
|
82
|
+
|
|
83
|
+
/** @description graceful version of page.goto() */
|
|
84
|
+
goto(
|
|
85
|
+
url: string,
|
|
86
|
+
/**
|
|
87
|
+
* @default { waitUtil: "domcontentloaded" }
|
|
88
|
+
*/
|
|
89
|
+
options?: Parameters<Page['goto']>[1],
|
|
90
|
+
): Promise<Response | null>
|
|
91
|
+
|
|
92
|
+
autoRetryWhenFailed<T>(f: () => T | Promise<T>): Promise<T>
|
|
93
|
+
|
|
94
|
+
/** @description proxy method to (await this.getPage())[method] */
|
|
95
|
+
evaluate: Page['evaluate']
|
|
96
|
+
fill: Page['fill']
|
|
97
|
+
click: Page['click']
|
|
98
|
+
content: Page['content']
|
|
99
|
+
title: Page['title']
|
|
100
|
+
innerHTML: Page['innerHTML']
|
|
101
|
+
innerText: Page['innerText']
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
49
105
|
## License
|
|
50
106
|
|
|
51
107
|
This project is licensed with [BSD-2-Clause](./LICENSE)
|
package/core.d.ts
CHANGED
|
@@ -29,11 +29,15 @@ export declare class GracefulPage {
|
|
|
29
29
|
getOnError(): (error: unknown) => void;
|
|
30
30
|
getPage(): Page | Promise<Page>;
|
|
31
31
|
restart(options?: Parameters<Page['close']>[0]): Promise<Page>;
|
|
32
|
+
/** @description optimized version of page.close() */
|
|
33
|
+
close: Page['close'];
|
|
34
|
+
/** @description graceful version of page.goto() */
|
|
32
35
|
goto(url: string,
|
|
33
36
|
/**
|
|
34
37
|
* @default { waitUtil: "domcontentloaded" }
|
|
35
38
|
*/
|
|
36
39
|
options?: Parameters<Page['goto']>[1]): Promise<import("playwright").Response | null>;
|
|
40
|
+
autoRetryWhenFailed<T>(f: () => T | Promise<T>): Promise<T>;
|
|
37
41
|
/** @description proxy method to (await this.getPage()).evaluate */
|
|
38
42
|
evaluate: Page['evaluate'];
|
|
39
43
|
/** @description proxy method to (await this.getPage()).fill */
|
|
@@ -48,6 +52,4 @@ export declare class GracefulPage {
|
|
|
48
52
|
innerHTML: Page['innerHTML'];
|
|
49
53
|
/** @description proxy method to (await this.getPage()).innerText */
|
|
50
54
|
innerText: Page['innerText'];
|
|
51
|
-
/** @description proxy method to (await this.getPage()).click */
|
|
52
|
-
close: Page['close'];
|
|
53
55
|
}
|
package/core.js
CHANGED
|
@@ -24,6 +24,15 @@ class GracefulPage {
|
|
|
24
24
|
await this.close(options);
|
|
25
25
|
return this.getPage();
|
|
26
26
|
}
|
|
27
|
+
/** @description optimized version of page.close() */
|
|
28
|
+
close = async (options) => {
|
|
29
|
+
let promise = Promise.resolve(this.options.page)
|
|
30
|
+
.then(page => page?.close(options))
|
|
31
|
+
.catch(this.getOnError());
|
|
32
|
+
this.options.page = undefined;
|
|
33
|
+
return promise;
|
|
34
|
+
};
|
|
35
|
+
/** @description graceful version of page.goto() */
|
|
27
36
|
async goto(url,
|
|
28
37
|
/**
|
|
29
38
|
* @default { waitUtil: "domcontentloaded" }
|
|
@@ -41,10 +50,11 @@ class GracefulPage {
|
|
|
41
50
|
catch (error) {
|
|
42
51
|
let message = String(error);
|
|
43
52
|
let flags = {
|
|
44
|
-
retry: message.match(/timeout/i) || message.match(/ERR_NETWORK_CHANGED/
|
|
53
|
+
retry: message.match(/timeout/i) || message.match(/ERR_NETWORK_CHANGED/),
|
|
45
54
|
restart: message.match(/page crashed/i),
|
|
46
55
|
};
|
|
47
56
|
if (flags.retry || flags.restart) {
|
|
57
|
+
this.getOnError()(error);
|
|
48
58
|
if (flags.restart) {
|
|
49
59
|
await this.restart();
|
|
50
60
|
}
|
|
@@ -55,6 +65,26 @@ class GracefulPage {
|
|
|
55
65
|
}
|
|
56
66
|
}
|
|
57
67
|
}
|
|
68
|
+
async autoRetryWhenFailed(f) {
|
|
69
|
+
for (;;) {
|
|
70
|
+
try {
|
|
71
|
+
return await f();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
let message = String(error);
|
|
75
|
+
let flags = {
|
|
76
|
+
restart: message.match(/The object has been collected to prevent unbounded heap growth/),
|
|
77
|
+
};
|
|
78
|
+
if (flags.restart) {
|
|
79
|
+
this.getOnError()(error);
|
|
80
|
+
await this.restart();
|
|
81
|
+
await sleep(this.getRetryInterval());
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
58
88
|
/** @description proxy method to (await this.getPage()).evaluate */
|
|
59
89
|
evaluate = async (pageFunction, arg) => {
|
|
60
90
|
let page = await this.getPage();
|
|
@@ -90,14 +120,6 @@ class GracefulPage {
|
|
|
90
120
|
let page = await this.getPage();
|
|
91
121
|
return await page.innerText(selector, options);
|
|
92
122
|
};
|
|
93
|
-
/** @description proxy method to (await this.getPage()).click */
|
|
94
|
-
close = async (options) => {
|
|
95
|
-
let promise = Promise.resolve(this.options.page)
|
|
96
|
-
.then(page => page?.close(options))
|
|
97
|
-
.catch(this.getOnError());
|
|
98
|
-
this.options.page = undefined;
|
|
99
|
-
return promise;
|
|
100
|
-
};
|
|
101
123
|
}
|
|
102
124
|
exports.GracefulPage = GracefulPage;
|
|
103
125
|
function sleep(ms) {
|