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 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 `/ERR_NETWORK_CHANGED/i`
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.goto('http://example.net')
40
- let lines: string[] = await page.evaluate(() =>
41
- Array.from(document.querySelectorAll('a'), a => a.href),
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/i),
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graceful-playwright",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Gracefully handle timeout and network error with auto retry.",
5
5
  "keywords": [
6
6
  "graceful",