@percy/core 1.0.0-beta.76 → 1.0.0

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  The core component of Percy's CLI and SDKs that handles creating builds, discovering snapshot
4
4
  assets, uploading snapshots, and finalizing builds. Uses `@percy/client` for API communication, a
5
- Puppeteer browser for asset discovery, and starts a local API server for posting snapshots from
5
+ Chromium browser for asset discovery, and starts a local API server for posting snapshots from
6
6
  other processes.
7
7
 
8
8
  - [Usage](#usage)
@@ -49,6 +49,7 @@ The following options can also be defined within a Percy config file
49
49
  - `enableJavaScript` — Enable JavaScript for screenshots (**default** `false`)
50
50
  - `discovery` — Asset discovery options
51
51
  - `allowedHostnames` — Array of allowed hostnames to capture assets from
52
+ - `disallowedHostnames` — Array of hostnames where requests will be aborted
52
53
  - `requestHeaders` — Request headers used when discovering snapshot assets
53
54
  - `authorization` — Basic auth `username` and `password` for protected snapshot assets
54
55
  - `disableCache` — Disable asset caching (**default** `false`)
@@ -127,10 +128,13 @@ await percy.idle()
127
128
 
128
129
  ### `#snapshot(options)`
129
130
 
130
- Performs asset discovery for a snapshot and queues uploading the snapshot to the associated Percy
131
- build. When an existing DOM snapshot is provided, it is served as the root resource during asset
132
- discovery. When no existing DOM snapshot is provided, a new one will be captured using any provided
133
- snapshot capture options.
131
+ Takes one or more snapshots of a page while discovering resources to upload with the snapshot. Once
132
+ asset discovery has completed, the queued snapshot will resolve and an upload task will be queued
133
+ separately. Accepts several different syntaxes for taking snapshots in various ways.
134
+
135
+ All available syntaxes will push snapshots into the snapshot queue without the need to await on the
136
+ method directly. This method resolves after the snapshot upload is queued, but does not await on the
137
+ upload to complete.
134
138
 
135
139
  ``` js
136
140
  // snapshots can be handled concurrently, no need to await
@@ -140,37 +144,61 @@ percy.snapshot({
140
144
  domSnapshot: domSnapshot,
141
145
  clientInfo: 'my-sdk',
142
146
  environmentInfo: 'my-lib'
143
- ...snapshotOptions
144
147
  })
145
148
 
149
+ // without a domSnapshot, capture options will be used to take one
146
150
  percy.snapshot({
147
151
  name: 'Snapshot 2',
148
- url: 'http://localhost:3000/',
149
- ...snapshotOptions,
150
-
151
- // without a domSnapshot, capture options will be used to take one
152
+ url: 'http://localhost:3000',
152
153
  waitForTimeout: 1000,
153
154
  waitForSelector: '.done-loading',
154
155
  execute: async () => {},
155
156
  additionalSnapshots: [{
156
157
  name: 'Snapshot 2.1',
157
- execute: () => {},
158
- ...snapshotOptions
158
+ execute: () => {}
159
159
  }]
160
160
  })
161
+
162
+ // alternate shorthand syntax
163
+ percy.snapshot({
164
+ baseUrl: 'http://localhost:3000',
165
+ snapshots: ['/', '/about', '/contact'],
166
+ options: {
167
+ widths: [600, 1200]
168
+ }
169
+ })
170
+
171
+ // gather snapshots from an external sitemap
172
+ percy.snapshot({
173
+ sitemap: 'https://example.com/sitemap.xml',
174
+ exclude: ['/blog/*']
175
+ })
176
+
177
+ // start a server and take static snapshots
178
+ percy.snapshot({
179
+ serve: './public',
180
+ cleanUrls: true,
181
+ })
161
182
  ```
162
183
 
163
184
  #### Options
164
185
 
186
+ When capturing a single snapshot, the snapshot URL may be provided as the only argument rather than
187
+ a snapshot options object. When providing an options object, a few alternate syntaxes are available
188
+ depending on the provided properties ([see alternate syntaxes below](#alternate-syntaxes)).
189
+
190
+ **Common options** accepted for each snapshot:
191
+
165
192
  - `url` — Snapshot URL (**required**)
166
193
  - `name` — Snapshot name
167
194
  - `domSnapshot` — Snapshot DOM string
168
- - `clientInfo` — Additional client info
169
- - `environmentInfo` — Additional environment info
170
195
  - `discovery` - Limited snapshot specific discovery options
171
- - `allowedHostnames`, `requestHeaders`, `authorization`, `disableCache`, `userAgent`
196
+ - `allowedHostnames`, `disallowedHostnames`, `requestHeaders`, `authorization`, `disableCache`, `userAgent`
172
197
 
173
- **Capture options** can only be provided when `domSnapshot` is missing.
198
+ Common snapshot options are also accepted and will override instance snapshot options. [See instance
199
+ options](#options) for common snapshot and discovery options.
200
+
201
+ **Capture options** can only be provided when `domSnapshot` is missing:
174
202
 
175
203
  - `waitForTimeout` — Milliseconds to wait before taking a snapshot
176
204
  - `waitForSelector` — CSS selector to wait for before taking a snapshot
@@ -180,9 +208,49 @@ percy.snapshot({
180
208
  - `prefix` — Snapshot name prefix (**required** if no `name` or `suffix`)
181
209
  - `suffix` — Snapshot name suffix (**required** if no `name` or `prefix`)
182
210
  - `waitForTimeout`, `waitForSelector`, `execute` — See above
211
+
212
+ #### Alternate syntaxes
183
213
 
184
- Common snapshot options are also accepted and will override instance snapshot options. [See instance
185
- options](#options) for common snapshot and discovery options.
214
+ All snapshot syntaxes can be provided as items within an array. For example, a single method call
215
+ can upload multiple DOM snapshots, capture multiple external snapshots, crawl a sitemap for
216
+ snapshots, and host a local static server for snapshots.
217
+
218
+ **Shared options** accepted by all syntaxes:
219
+
220
+ - `clientInfo` — Client info to include with the build
221
+ - `environmentInfo` — Environment info to include with the build
222
+
223
+ The following alternate syntaxes may **not** be combined with snapshot options, but rather offer
224
+ alternate methods for taking multiple snapshots.
225
+
226
+ **List options** can only be provided when a top-level `snapshots` is present:
227
+
228
+ - `snapshots` — An array of snapshot URLs or snapshot options (**required**)
229
+ - `baseUrl` — The full base URL (including protocol) used when snapshot URLs only include a pathname
230
+ - `include`/`exclude` — Include and exclude matching snapshot names
231
+ - `options` — Additional options to apply to snapshots
232
+ - `include`/`exclude` — Include and exclude snapshots to apply these options to
233
+ - [Common snapshot and capture options](#options) (**excluding** `url`, `domSnapshot`)
234
+
235
+ **Sitemap options** can only be provided when a top-level `sitemap` is present:
236
+
237
+ - `sitemap` — The URL where an XML sitemap can be located (**required**)
238
+ - `include`/`exclude` — Include and exclude matching snapshot names
239
+ - `options` — Additional options to apply to snapshots
240
+ - `include`/`exclude` — Include and exclude snapshots to apply these options to
241
+ - [Common snapshot and capture options](#options) (**excluding** `url`, `domSnapshot`)
242
+
243
+ **Server options** can only be provided when a top-level `serve` is present:
244
+
245
+ - `serve` — The static directory to serve relative to the current working directory (**required**)
246
+ - `baseUrl` — The base URL to serve the directory at, starting with a forward slash (/)
247
+ - `cleanUrls` — Set to `true` to strip `.html` and `index.html` from served URLs
248
+ - `rewrites` — A source-destination map for rewriting source URLs into destination pathnames
249
+ - `snapshots` — An array of specific snapshots to take while serving the static directory
250
+ - `include`/`exclude` — Include and exclude matching snapshot names
251
+ - `options` — Additional options to apply to snapshots
252
+ - `include`/`exclude` — Include and exclude snapshots to apply these options to
253
+ - [Common snapshot and capture options](#options) (**excluding** `url`, `domSnapshot`)
186
254
 
187
255
  ## Advanced
188
256
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.0.0-beta.76",
3
+ "version": "1.0.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,16 +10,24 @@
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
- "main": "dist/index.js",
14
- "types": "types/index.d.ts",
13
+ "engines": {
14
+ "node": ">=14"
15
+ },
15
16
  "files": [
16
- "dist",
17
- "post-install.js",
18
- "types/index.d.ts",
19
- "test/helpers/server.js"
17
+ "./dist",
18
+ "./post-install.js",
19
+ "./types/index.d.ts",
20
+ "./test/helpers/server.js"
20
21
  ],
21
- "engines": {
22
- "node": ">=12"
22
+ "main": "./dist/index.js",
23
+ "types": "types/index.d.ts",
24
+ "type": "module",
25
+ "exports": {
26
+ ".": "./dist/index.js",
27
+ "./utils": "./dist/utils.js",
28
+ "./config": "./dist/config.js",
29
+ "./install": "./dist/install.js",
30
+ "./test/helpers": "./test/helpers/index.js"
23
31
  },
24
32
  "scripts": {
25
33
  "build": "node ../../scripts/build",
@@ -30,17 +38,19 @@
30
38
  "test:types": "tsd"
31
39
  },
32
40
  "dependencies": {
33
- "@percy/client": "1.0.0-beta.76",
34
- "@percy/config": "1.0.0-beta.76",
35
- "@percy/dom": "1.0.0-beta.76",
36
- "@percy/logger": "1.0.0-beta.76",
41
+ "@percy/client": "1.0.0",
42
+ "@percy/config": "1.0.0",
43
+ "@percy/dom": "1.0.0",
44
+ "@percy/logger": "1.0.0",
37
45
  "content-disposition": "^0.5.4",
38
46
  "cross-spawn": "^7.0.3",
39
47
  "extract-zip": "^2.0.1",
48
+ "fast-glob": "^3.2.11",
49
+ "micromatch": "^4.0.4",
40
50
  "mime-types": "^2.1.34",
41
51
  "path-to-regexp": "^6.2.0",
42
52
  "rimraf": "^3.0.2",
43
53
  "ws": "^8.0.0"
44
54
  },
45
- "gitHead": "445af68d8e270e2a35fc74e26422ed5d3c91d2ae"
55
+ "gitHead": "6df509421a60144e4f9f5d59dc57a5675372a0b2"
46
56
  }
package/dist/api.js DELETED
@@ -1,76 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.createPercyServer = createPercyServer;
7
-
8
- var _fs = _interopRequireDefault(require("fs"));
9
-
10
- var _logger = _interopRequireDefault(require("@percy/logger"));
11
-
12
- var _server = _interopRequireDefault(require("./server"));
13
-
14
- var _package = _interopRequireDefault(require("../package.json"));
15
-
16
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
-
18
- function createPercyServer(percy, port) {
19
- return new _server.default({
20
- port
21
- }) // facilitate logger websocket connections
22
- .websocket(ws => _logger.default.connect(ws)) // general middleware
23
- .route((req, res, next) => {
24
- // treat all request bodies as json
25
- if (req.body) try {
26
- req.body = JSON.parse(req.body);
27
- } catch {} // add version header
28
-
29
- res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version');
30
- res.setHeader('X-Percy-Core-Version', _package.default.version); // return json errors
31
-
32
- return next().catch(e => {
33
- var _e$status;
34
-
35
- return res.json((_e$status = e.status) !== null && _e$status !== void 0 ? _e$status : 500, {
36
- error: e.message,
37
- success: false
38
- });
39
- });
40
- }) // healthcheck returns basic information
41
- .route('get', '/percy/healthcheck', (req, res) => res.json(200, {
42
- loglevel: percy.loglevel(),
43
- config: percy.config,
44
- build: percy.build,
45
- success: true
46
- })) // get or set config options
47
- .route(['get', 'post'], '/percy/config', async (req, res) => res.json(200, {
48
- config: req.body ? await percy.setConfig(req.body) : percy.config,
49
- success: true
50
- })) // responds once idle (may take a long time)
51
- .route('get', '/percy/idle', async (req, res) => res.json(200, {
52
- success: await percy.idle().then(() => true)
53
- })) // convenient @percy/dom bundle
54
- .route('get', '/percy/dom.js', (req, res) => {
55
- return res.file(200, require.resolve('@percy/dom'));
56
- }) // legacy agent wrapper for @percy/dom
57
- .route('get', '/percy-agent.js', async (req, res) => {
58
- (0, _logger.default)('core:server').deprecated(['It looks like you’re using @percy/cli with an older SDK.', 'Please upgrade to the latest version to fix this warning.', 'See these docs for more info: https:docs.percy.io/docs/migrating-to-percy-cli'].join(' '));
59
- let content = await _fs.default.promises.readFile(require.resolve('@percy/dom'), 'utf-8');
60
- let wrapper = '(window.PercyAgent = class { snapshot(n, o) { return PercyDOM.serialize(o); } });';
61
- return res.send(200, 'applicaton/javascript', content.concat(wrapper));
62
- }) // post one or more snapshots
63
- .route('post', '/percy/snapshot', async (req, res) => {
64
- let snapshot = percy.snapshot(req.body);
65
- if (!req.url.searchParams.has('async')) await snapshot;
66
- return res.json(200, {
67
- success: true
68
- });
69
- }) // stops percy at the end of the current event loop
70
- .route('/percy/stop', (req, res) => {
71
- setImmediate(() => percy.stop());
72
- return res.json(200, {
73
- success: true
74
- });
75
- });
76
- }
package/dist/browser.js DELETED
@@ -1,363 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = exports.Browser = void 0;
7
-
8
- var _os = _interopRequireDefault(require("os"));
9
-
10
- var _path = _interopRequireDefault(require("path"));
11
-
12
- var _fs = require("fs");
13
-
14
- var _crossSpawn = _interopRequireDefault(require("cross-spawn"));
15
-
16
- var _events = _interopRequireDefault(require("events"));
17
-
18
- var _ws = _interopRequireDefault(require("ws"));
19
-
20
- var _rimraf = _interopRequireDefault(require("rimraf"));
21
-
22
- var _logger = _interopRequireDefault(require("@percy/logger"));
23
-
24
- var _install = _interopRequireDefault(require("./install"));
25
-
26
- var _session = _interopRequireDefault(require("./session"));
27
-
28
- var _page = _interopRequireDefault(require("./page"));
29
-
30
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
31
-
32
- function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
33
-
34
- function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
35
-
36
- function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
37
-
38
- function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
39
-
40
- function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
41
-
42
- function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
43
-
44
- function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
45
-
46
- function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
47
-
48
- var _callbacks = /*#__PURE__*/new WeakMap();
49
-
50
- var _lastid = /*#__PURE__*/new WeakMap();
51
-
52
- class Browser extends _events.default {
53
- constructor({
54
- executable = process.env.PERCY_BROWSER_EXECUTABLE,
55
- headless = true,
56
- cookies = [],
57
- args = [],
58
- timeout
59
- }) {
60
- super();
61
-
62
- _defineProperty(this, "log", (0, _logger.default)('core:browser'));
63
-
64
- _defineProperty(this, "sessions", new Map());
65
-
66
- _defineProperty(this, "readyState", null);
67
-
68
- _defineProperty(this, "closed", false);
69
-
70
- _classPrivateFieldInitSpec(this, _callbacks, {
71
- writable: true,
72
- value: new Map()
73
- });
74
-
75
- _classPrivateFieldInitSpec(this, _lastid, {
76
- writable: true,
77
- value: 0
78
- });
79
-
80
- _defineProperty(this, "args", [// disable the translate popup
81
- '--disable-features=Translate', // disable several subsystems which run network requests in the background
82
- '--disable-background-networking', // disable task throttling of timer tasks from background pages
83
- '--disable-background-timer-throttling', // disable backgrounding renderer processes
84
- '--disable-renderer-backgrounding', // disable backgrounding renderers for occluded windows (reduce nondeterminism)
85
- '--disable-backgrounding-occluded-windows', // disable crash reporting
86
- '--disable-breakpad', // disable client side phishing detection
87
- '--disable-client-side-phishing-detection', // disable default component extensions with background pages for performance
88
- '--disable-component-extensions-with-background-pages', // disable installation of default apps on first run
89
- '--disable-default-apps', // work-around for environments where a small /dev/shm partition causes crashes
90
- '--disable-dev-shm-usage', // disable extensions
91
- '--disable-extensions', // disable hang monitor dialogs in renderer processes
92
- '--disable-hang-monitor', // disable inter-process communication flooding protection for javascript
93
- '--disable-ipc-flooding-protection', // disable web notifications and the push API
94
- '--disable-notifications', // disable the prompt when a POST request causes page navigation
95
- '--disable-prompt-on-repost', // disable syncing browser data with google accounts
96
- '--disable-sync', // disable site-isolation to make network requests easier to intercept
97
- '--disable-site-isolation-trials', // disable the first run tasks, whether or not it's actually the first run
98
- '--no-first-run', // disable the sandbox for all process types that are normally sandboxed
99
- '--no-sandbox', // enable indication that browser is controlled by automation
100
- '--enable-automation', // specify a consistent encryption backend across platforms
101
- '--password-store=basic', // use a mock keychain on Mac to prevent blocking permissions dialogs
102
- '--use-mock-keychain', // enable remote debugging on the first available port
103
- '--remote-debugging-port=0']);
104
-
105
- this.launchTimeout = timeout;
106
- this.executable = executable;
107
- this.headless = headless;
108
- /* istanbul ignore next: only false for debugging */
109
-
110
- if (this.headless) this.args.push('--headless', '--hide-scrollbars', '--mute-audio');
111
-
112
- for (let a of args) if (!this.args.includes(a)) this.args.push(a); // transform cookies object to an array of cookie params
113
-
114
-
115
- this.cookies = Array.isArray(cookies) ? cookies : Object.entries(cookies).map(([name, value]) => ({
116
- name,
117
- value
118
- }));
119
- }
120
-
121
- async launch() {
122
- // already launching or launched
123
- if (this.readyState != null) return;
124
- this.readyState = 0; // check if any provided executable exists
125
-
126
- if (this.executable && !(0, _fs.existsSync)(this.executable)) {
127
- this.log.error(`Browser executable not found: ${this.executable}`);
128
- this.executable = null;
129
- } // download and install the browser if not already present
130
-
131
-
132
- this.executable || (this.executable = await _install.default.chromium()); // create a temporary profile directory
133
-
134
- this.profile = await _fs.promises.mkdtemp(_path.default.join(_os.default.tmpdir(), 'percy-browser-')); // spawn the browser process detached in its own group and session
135
-
136
- let args = this.args.concat(`--user-data-dir=${this.profile}`);
137
- this.log.debug('Launching browser');
138
- this.process = (0, _crossSpawn.default)(this.executable, args, {
139
- detached: process.platform !== 'win32'
140
- }); // connect a websocket to the devtools address
141
-
142
- let addr = await this.address(this.launchTimeout);
143
- this.ws = new _ws.default(addr, {
144
- perMessageDeflate: false
145
- }); // wait until the websocket has connected
146
-
147
- await new Promise(resolve => this.ws.once('open', resolve));
148
- this.ws.on('message', data => this._handleMessage(data)); // get version information
149
-
150
- this.version = await this.send('Browser.getVersion');
151
- this.log.debug(`Browser connected [${this.process.pid}]: ${this.version.product}`);
152
- this.readyState = 1;
153
- }
154
-
155
- isConnected() {
156
- var _this$ws;
157
-
158
- return ((_this$ws = this.ws) === null || _this$ws === void 0 ? void 0 : _this$ws.readyState) === _ws.default.OPEN;
159
- }
160
-
161
- async close() {
162
- var _this$process4, _this$ws2;
163
-
164
- // not running, already closed, or closing
165
- if (this._closed) return this._closed;
166
- this.readyState = 2;
167
- this.log.debug('Closing browser'); // resolves when the browser has closed
168
-
169
- this._closed = Promise.all([new Promise(resolve => {
170
- /* istanbul ignore next: race condition paranoia */
171
- if (!this.process || this.process.exitCode) resolve();else this.process.on('exit', resolve);
172
- }), new Promise(resolve => {
173
- /* istanbul ignore next: race condition paranoia */
174
- if (!this.isConnected()) resolve();else this.ws.on('close', resolve);
175
- })]).then(() => {
176
- var _this$process, _this$process2, _this$process3;
177
-
178
- // needed due to a bug in Node 12 - https://github.com/nodejs/node/issues/27097
179
- (_this$process = this.process) === null || _this$process === void 0 ? void 0 : _this$process.stdin.end();
180
- (_this$process2 = this.process) === null || _this$process2 === void 0 ? void 0 : _this$process2.stdout.end();
181
- (_this$process3 = this.process) === null || _this$process3 === void 0 ? void 0 : _this$process3.stderr.end();
182
- /* istanbul ignore next:
183
- * this might fail on some systems but ultimately it is just a temp file */
184
-
185
- if (this.profile) {
186
- // attempt to clean up the profile directory
187
- return new Promise((resolve, reject) => {
188
- (0, _rimraf.default)(this.profile, e => e ? reject(e) : resolve());
189
- }).catch(error => {
190
- this.log.debug('Could not clean up temporary browser profile directory.');
191
- this.log.debug(error);
192
- });
193
- }
194
- }).then(() => {
195
- this.log.debug('Browser closed');
196
- this.readyState = 3;
197
- }); // reject any pending callbacks
198
-
199
- for (let callback of _classPrivateFieldGet(this, _callbacks).values()) {
200
- callback.reject(Object.assign(callback.error, {
201
- message: `Protocol error (${callback.method}): Browser closed.`
202
- }));
203
- } // trigger rejecting pending session callbacks
204
-
205
-
206
- for (let session of this.sessions.values()) {
207
- session._handleClose();
208
- } // clear own callbacks and sessions
209
-
210
-
211
- _classPrivateFieldGet(this, _callbacks).clear();
212
-
213
- this.sessions.clear();
214
- /* istanbul ignore next:
215
- * difficult to test failure here without mocking private properties */
216
-
217
- if ((_this$process4 = this.process) !== null && _this$process4 !== void 0 && _this$process4.pid && !this.process.killed) {
218
- // always force close the browser process
219
- try {
220
- this.process.kill('SIGKILL');
221
- } catch (error) {
222
- throw new Error(`Unable to close the browser: ${error.stack}`);
223
- }
224
- } // close the socket connection
225
-
226
-
227
- (_this$ws2 = this.ws) === null || _this$ws2 === void 0 ? void 0 : _this$ws2.close(); // wait for the browser to close
228
-
229
- return this._closed;
230
- }
231
-
232
- async page(options = {}) {
233
- let {
234
- targetId
235
- } = await this.send('Target.createTarget', {
236
- url: ''
237
- });
238
- let {
239
- sessionId
240
- } = await this.send('Target.attachToTarget', {
241
- targetId,
242
- flatten: true
243
- });
244
- let page = new _page.default(this.sessions.get(sessionId), options);
245
- await page._handleAttachedToTarget();
246
- return page;
247
- }
248
-
249
- async send(method, params) {
250
- /* istanbul ignore next:
251
- * difficult to test failure here without mocking private properties */
252
- if (!this.isConnected()) throw new Error('Browser not connected'); // every command needs a unique id
253
-
254
- let id = _classPrivateFieldSet(this, _lastid, +_classPrivateFieldGet(this, _lastid) + 1);
255
-
256
- if (!params && typeof method === 'object') {
257
- // allow providing a raw message as the only argument and return the id
258
- this.ws.send(JSON.stringify({ ...method,
259
- id
260
- }));
261
- return id;
262
- } else {
263
- // send the message payload
264
- this.ws.send(JSON.stringify({
265
- id,
266
- method,
267
- params
268
- })); // will resolve or reject when a matching response is received
269
-
270
- return new Promise((resolve, reject) => {
271
- _classPrivateFieldGet(this, _callbacks).set(id, {
272
- error: new Error(),
273
- resolve,
274
- reject,
275
- method
276
- });
277
- });
278
- }
279
- } // Returns the devtools websocket address. If not already known, will watch the browser's
280
- // stderr and resolves when it emits the devtools protocol address or rejects if the process
281
- // exits for any reason or if the address does not appear after the timeout.
282
-
283
-
284
- async address(timeout = 30000) {
285
- this._address || (this._address = await new Promise((resolve, reject) => {
286
- let stderr = '';
287
-
288
- let handleData = chunk => {
289
- stderr += chunk = chunk.toString();
290
- let match = chunk.match(/^DevTools listening on (ws:\/\/.*)$/m);
291
- if (match) cleanup(() => resolve(match[1]));
292
- };
293
-
294
- let handleExitClose = () => handleError();
295
-
296
- let handleError = error => cleanup(() => {
297
- var _error$message;
298
-
299
- return reject(new Error(`Failed to launch browser. ${(_error$message = error === null || error === void 0 ? void 0 : error.message) !== null && _error$message !== void 0 ? _error$message : ''}\n${stderr}'\n\n`));
300
- });
301
-
302
- let cleanup = callback => {
303
- clearTimeout(timeoutId);
304
- this.process.stderr.off('data', handleData);
305
- this.process.stderr.off('close', handleExitClose);
306
- this.process.off('exit', handleExitClose);
307
- this.process.off('error', handleError);
308
- callback();
309
- };
310
-
311
- let timeoutId = setTimeout(() => handleError(new Error(`Timed out after ${timeout}ms`)), timeout);
312
- this.process.stderr.on('data', handleData);
313
- this.process.stderr.on('close', handleExitClose);
314
- this.process.on('exit', handleExitClose);
315
- this.process.on('error', handleError);
316
- }));
317
- return this._address;
318
- }
319
-
320
- _handleMessage(data) {
321
- data = JSON.parse(data);
322
-
323
- if (data.method === 'Target.attachedToTarget') {
324
- // create a new session reference when attached to a target
325
- let session = new _session.default(this, data);
326
- this.sessions.set(session.sessionId, session);
327
- } else if (data.method === 'Target.detachedFromTarget') {
328
- // remove the old session reference when detached from a target
329
- let session = this.sessions.get(data.params.sessionId);
330
- this.sessions.delete(data.params.sessionId);
331
- session === null || session === void 0 ? void 0 : session._handleClose();
332
- }
333
-
334
- if (data.sessionId) {
335
- // message was for a specific session that sent it
336
- let session = this.sessions.get(data.sessionId);
337
- session === null || session === void 0 ? void 0 : session._handleMessage(data);
338
- } else if (data.id && _classPrivateFieldGet(this, _callbacks).has(data.id)) {
339
- // resolve or reject a pending promise created with #send()
340
- let callback = _classPrivateFieldGet(this, _callbacks).get(data.id);
341
-
342
- _classPrivateFieldGet(this, _callbacks).delete(data.id);
343
- /* istanbul ignore next: races with page._handleMessage() */
344
-
345
-
346
- if (data.error) {
347
- callback.reject(Object.assign(callback.error, {
348
- message: `Protocol error (${callback.method}): ${data.error.message}` + ('data' in data.error ? `: ${data.error.data}` : '')
349
- }));
350
- } else {
351
- callback.resolve(data.result);
352
- }
353
- } else {
354
- // emit the message as an event
355
- this.emit(data.method, data.params);
356
- }
357
- }
358
-
359
- }
360
-
361
- exports.Browser = Browser;
362
- var _default = Browser;
363
- exports.default = _default;