@percy/core 1.12.0 → 1.14.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/dist/api.js CHANGED
@@ -3,112 +3,118 @@ import path from 'path';
3
3
  import { createRequire } from 'module';
4
4
  import logger from '@percy/logger';
5
5
  import { normalize } from '@percy/config/utils';
6
- import { getPackageJSON, Server } from './utils.js'; // need require.resolve until import.meta.resolve can be transpiled
6
+ import { getPackageJSON, Server } from './utils.js';
7
7
 
8
- export const PERCY_DOM = createRequire(import.meta.url).resolve('@percy/dom'); // Returns a URL encoded string of nested query params
8
+ // need require.resolve until import.meta.resolve can be transpiled
9
+ export const PERCY_DOM = createRequire(import.meta.url).resolve('@percy/dom');
9
10
 
11
+ // Returns a URL encoded string of nested query params
10
12
  function encodeURLSearchParams(subj, prefix) {
11
13
  return typeof subj === 'object' ? Object.entries(subj).map(([key, value]) => encodeURLSearchParams(value, prefix ? `${prefix}[${key}]` : key)).join('&') : `${prefix}=${encodeURIComponent(subj)}`;
12
- } // Create a Percy CLI API server instance
13
-
14
+ }
14
15
 
16
+ // Create a Percy CLI API server instance
15
17
  export function createPercyServer(percy, port) {
16
18
  let pkg = getPackageJSON(import.meta.url);
17
19
  let server = Server.createServer({
18
20
  port
19
- }) // general middleware
21
+ })
22
+ // general middleware
20
23
  .route((req, res, next) => {
21
24
  var _percy$testing, _percy$testing4, _percy$testing4$api, _percy$testing5, _percy$testing5$api;
22
-
23
25
  // treat all request bodies as json
24
26
  if (req.body) try {
25
27
  req.body = JSON.parse(req.body);
26
- } catch {} // add version header
28
+ } catch {}
27
29
 
28
- res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version'); // skip or change api version header in testing mode
30
+ // add version header
31
+ res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version');
29
32
 
33
+ // skip or change api version header in testing mode
30
34
  if (((_percy$testing = percy.testing) === null || _percy$testing === void 0 ? void 0 : _percy$testing.version) !== false) {
31
35
  var _percy$testing2;
32
-
33
36
  res.setHeader('X-Percy-Core-Version', ((_percy$testing2 = percy.testing) === null || _percy$testing2 === void 0 ? void 0 : _percy$testing2.version) ?? pkg.version);
34
- } // track all api reqeusts in testing mode
35
-
37
+ }
36
38
 
39
+ // track all api reqeusts in testing mode
37
40
  if (percy.testing && !req.url.pathname.startsWith('/test/')) {
38
41
  var _percy$testing3;
39
-
40
42
  ((_percy$testing3 = percy.testing).requests || (_percy$testing3.requests = [])).push({
41
43
  url: `${req.url.pathname}${req.url.search}`,
42
44
  method: req.method,
43
45
  body: req.body
44
46
  });
45
- } // support sabotaging requests in testing mode
46
-
47
+ }
47
48
 
49
+ // support sabotaging requests in testing mode
48
50
  if (((_percy$testing4 = percy.testing) === null || _percy$testing4 === void 0 ? void 0 : (_percy$testing4$api = _percy$testing4.api) === null || _percy$testing4$api === void 0 ? void 0 : _percy$testing4$api[req.url.pathname]) === 'error') {
49
51
  next = () => {
50
52
  var _percy$testing$build;
51
-
52
53
  return Promise.reject(new Error(((_percy$testing$build = percy.testing.build) === null || _percy$testing$build === void 0 ? void 0 : _percy$testing$build.error) || 'testing'));
53
54
  };
54
55
  } else if (((_percy$testing5 = percy.testing) === null || _percy$testing5 === void 0 ? void 0 : (_percy$testing5$api = _percy$testing5.api) === null || _percy$testing5$api === void 0 ? void 0 : _percy$testing5$api[req.url.pathname]) === 'disconnect') {
55
56
  next = () => req.connection.destroy();
56
- } // return json errors
57
-
57
+ }
58
58
 
59
+ // return json errors
59
60
  return next().catch(e => {
60
61
  var _percy$testing6;
61
-
62
62
  return res.json(e.status ?? 500, {
63
63
  build: ((_percy$testing6 = percy.testing) === null || _percy$testing6 === void 0 ? void 0 : _percy$testing6.build) || percy.build,
64
64
  error: e.message,
65
65
  success: false
66
66
  });
67
67
  });
68
- }) // healthcheck returns basic information
68
+ })
69
+ // healthcheck returns basic information
69
70
  .route('get', '/percy/healthcheck', (req, res) => {
70
71
  var _percy$testing7;
71
-
72
72
  return res.json(200, {
73
73
  build: ((_percy$testing7 = percy.testing) === null || _percy$testing7 === void 0 ? void 0 : _percy$testing7.build) ?? percy.build,
74
74
  loglevel: percy.loglevel(),
75
75
  config: percy.config,
76
76
  success: true
77
77
  });
78
- }) // get or set config options
78
+ })
79
+ // get or set config options
79
80
  .route(['get', 'post'], '/percy/config', async (req, res) => res.json(200, {
80
81
  config: req.body ? percy.set(req.body) : percy.config,
81
82
  success: true
82
- })) // responds once idle (may take a long time)
83
+ }))
84
+ // responds once idle (may take a long time)
83
85
  .route('get', '/percy/idle', async (req, res) => res.json(200, {
84
86
  success: await percy.idle().then(() => true)
85
- })) // convenient @percy/dom bundle
87
+ }))
88
+ // convenient @percy/dom bundle
86
89
  .route('get', '/percy/dom.js', (req, res) => {
87
90
  return res.file(200, PERCY_DOM);
88
- }) // legacy agent wrapper for @percy/dom
91
+ })
92
+ // legacy agent wrapper for @percy/dom
89
93
  .route('get', '/percy-agent.js', async (req, res) => {
90
94
  logger('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(' '));
91
95
  let content = await fs.promises.readFile(PERCY_DOM, 'utf-8');
92
96
  let wrapper = '(window.PercyAgent = class { snapshot(n, o) { return PercyDOM.serialize(o); } });';
93
97
  return res.send(200, 'applicaton/javascript', content.concat(wrapper));
94
- }) // post one or more snapshots, optionally async
98
+ })
99
+ // post one or more snapshots, optionally async
95
100
  .route('post', '/percy/snapshot', async (req, res) => {
96
101
  let snapshot = percy.snapshot(req.body);
97
102
  if (!req.url.searchParams.has('async')) await snapshot;
98
103
  return res.json(200, {
99
104
  success: true
100
105
  });
101
- }) // post one or more comparisons, optionally waiting
106
+ })
107
+ // post one or more comparisons, optionally waiting
102
108
  .route('post', '/percy/comparison', async (req, res) => {
103
109
  let upload = percy.upload(req.body);
104
- if (req.url.searchParams.has('await')) await upload; // generate and include one or more redirect links to comparisons
110
+ if (req.url.searchParams.has('await')) await upload;
105
111
 
112
+ // generate and include one or more redirect links to comparisons
106
113
  let link = ({
107
114
  name,
108
115
  tag
109
116
  }) => {
110
117
  var _percy$build;
111
-
112
118
  return [percy.client.apiUrl, '/comparisons/redirect?', encodeURLSearchParams(normalize({
113
119
  buildId: (_percy$build = percy.build) === null || _percy$build === void 0 ? void 0 : _percy$build.id,
114
120
  snapshot: {
@@ -119,7 +125,6 @@ export function createPercyServer(percy, port) {
119
125
  snake: true
120
126
  }))].join('');
121
127
  };
122
-
123
128
  return res.json(200, Object.assign({
124
129
  success: true
125
130
  }, req.body ? Array.isArray(req.body) ? {
@@ -127,18 +132,22 @@ export function createPercyServer(percy, port) {
127
132
  } : {
128
133
  link: link(req.body)
129
134
  } : {}));
130
- }) // flushes one or more snapshots from the internal queue
135
+ })
136
+ // flushes one or more snapshots from the internal queue
131
137
  .route('post', '/percy/flush', async (req, res) => res.json(200, {
132
138
  success: await percy.flush(req.body).then(() => true)
133
- })) // stops percy at the end of the current event loop
139
+ }))
140
+ // stops percy at the end of the current event loop
134
141
  .route('/percy/stop', (req, res) => {
135
142
  setImmediate(() => percy.stop());
136
143
  return res.json(200, {
137
144
  success: true
138
145
  });
139
- }); // add test endpoints only in testing mode
146
+ });
140
147
 
141
- return !percy.testing ? server : server // manipulates testing mode configuration to trigger specific scenarios
148
+ // add test endpoints only in testing mode
149
+ return !percy.testing ? server : server
150
+ // manipulates testing mode configuration to trigger specific scenarios
142
151
  .route('/test/api/:cmd', ({
143
152
  body,
144
153
  params: {
@@ -146,7 +155,6 @@ export function createPercyServer(percy, port) {
146
155
  }
147
156
  }, res) => {
148
157
  body = Buffer.isBuffer(body) ? body.toString() : body;
149
-
150
158
  if (cmd === 'reset') {
151
159
  // the reset command will reset testing mode and clear any logs
152
160
  percy.testing = {};
@@ -156,7 +164,6 @@ export function createPercyServer(percy, port) {
156
164
  percy.testing.version = body;
157
165
  } else if (cmd === 'error' || cmd === 'disconnect') {
158
166
  var _percy$testing8;
159
-
160
167
  // the error or disconnect commands will cause specific endpoints to fail
161
168
  ((_percy$testing8 = percy.testing).api || (_percy$testing8.api = {}))[body] = cmd;
162
169
  } else if (cmd === 'build-failure') {
@@ -169,35 +176,43 @@ export function createPercyServer(percy, port) {
169
176
  // 404 for unknown commands
170
177
  return res.send(404);
171
178
  }
172
-
173
179
  return res.json(200, {
174
180
  success: true
175
181
  });
176
- }) // returns an array of raw requests made to the api
182
+ })
183
+ // returns an array of raw requests made to the api
177
184
  .route('get', '/test/requests', (req, res) => res.json(200, {
178
185
  requests: percy.testing.requests
179
- })) // returns an array of raw logs from the logger
186
+ }))
187
+ // returns an array of raw logs from the logger
180
188
  .route('get', '/test/logs', (req, res) => res.json(200, {
181
189
  logs: Array.from(logger.instance.messages)
182
- })) // serves a very basic html page for testing snapshots
190
+ }))
191
+ // serves a very basic html page for testing snapshots
183
192
  .route('get', '/test/snapshot', (req, res) => {
184
193
  return res.send(200, 'text/html', '<p>Snapshot Me!</p>');
185
194
  });
186
- } // Create a static server instance with an automatic sitemap
195
+ }
187
196
 
197
+ // Create a static server instance with an automatic sitemap
188
198
  export function createStaticServer(options) {
189
199
  let {
190
200
  serve: dir,
191
201
  baseUrl = ''
192
202
  } = options;
193
- let server = Server.createServer(options); // remove trailing slashes so the base snapshot name matches other snapshots
203
+ let server = Server.createServer(options);
194
204
 
195
- baseUrl = baseUrl.replace(/\/$/, ''); // used when generating an automatic sitemap
205
+ // remove trailing slashes so the base snapshot name matches other snapshots
206
+ baseUrl = baseUrl.replace(/\/$/, '');
196
207
 
197
- let toURL = Server.createRewriter( // reverse rewrites' src, dest, & order
198
- Object.entries((options === null || options === void 0 ? void 0 : options.rewrites) ?? {}).reduce((acc, rw) => [rw.reverse(), ...acc], []), (filename, rewrite) => new URL(path.posix.join('/', baseUrl, // cleanUrls will trim trailing .html/index.html from paths
199
- !options.cleanUrls ? rewrite(filename) : rewrite(filename).replace(/(\/index)?\.html$/, '')), server.address())); // include automatic sitemap route
208
+ // used when generating an automatic sitemap
209
+ let toURL = Server.createRewriter(
210
+ // reverse rewrites' src, dest, & order
211
+ Object.entries((options === null || options === void 0 ? void 0 : options.rewrites) ?? {}).reduce((acc, rw) => [rw.reverse(), ...acc], []), (filename, rewrite) => new URL(path.posix.join('/', baseUrl,
212
+ // cleanUrls will trim trailing .html/index.html from paths
213
+ !options.cleanUrls ? rewrite(filename) : rewrite(filename).replace(/(\/index)?\.html$/, '')), server.address()));
200
214
 
215
+ // include automatic sitemap route
201
216
  server.route('get', `${baseUrl}/sitemap.xml`, async (req, res) => {
202
217
  let {
203
218
  default: glob
package/dist/browser.js CHANGED
@@ -16,35 +16,56 @@ export class Browser extends EventEmitter {
16
16
  closed = false;
17
17
  #callbacks = new Map();
18
18
  #lastid = 0;
19
- args = [// disable the translate popup
20
- '--disable-features=Translate', // disable several subsystems which run network requests in the background
21
- '--disable-background-networking', // disable task throttling of timer tasks from background pages
22
- '--disable-background-timer-throttling', // disable backgrounding renderer processes
23
- '--disable-renderer-backgrounding', // disable backgrounding renderers for occluded windows (reduce nondeterminism)
24
- '--disable-backgrounding-occluded-windows', // disable crash reporting
25
- '--disable-breakpad', // disable client side phishing detection
26
- '--disable-client-side-phishing-detection', // disable default component extensions with background pages for performance
27
- '--disable-component-extensions-with-background-pages', // disable installation of default apps on first run
28
- '--disable-default-apps', // work-around for environments where a small /dev/shm partition causes crashes
29
- '--disable-dev-shm-usage', // disable extensions
30
- '--disable-extensions', // disable hang monitor dialogs in renderer processes
31
- '--disable-hang-monitor', // disable inter-process communication flooding protection for javascript
32
- '--disable-ipc-flooding-protection', // disable web notifications and the push API
33
- '--disable-notifications', // disable the prompt when a POST request causes page navigation
34
- '--disable-prompt-on-repost', // disable syncing browser data with google accounts
35
- '--disable-sync', // disable site-isolation to make network requests easier to intercept
36
- '--disable-site-isolation-trials', // disable the first run tasks, whether or not it's actually the first run
37
- '--no-first-run', // disable the sandbox for all process types that are normally sandboxed
38
- '--no-sandbox', // enable indication that browser is controlled by automation
39
- '--enable-automation', // specify a consistent encryption backend across platforms
40
- '--password-store=basic', // use a mock keychain on Mac to prevent blocking permissions dialogs
41
- '--use-mock-keychain', // enable remote debugging on the first available port
19
+ args = [
20
+ // disable the translate popup
21
+ '--disable-features=Translate',
22
+ // disable several subsystems which run network requests in the background
23
+ '--disable-background-networking',
24
+ // disable task throttling of timer tasks from background pages
25
+ '--disable-background-timer-throttling',
26
+ // disable backgrounding renderer processes
27
+ '--disable-renderer-backgrounding',
28
+ // disable backgrounding renderers for occluded windows (reduce nondeterminism)
29
+ '--disable-backgrounding-occluded-windows',
30
+ // disable crash reporting
31
+ '--disable-breakpad',
32
+ // disable client side phishing detection
33
+ '--disable-client-side-phishing-detection',
34
+ // disable default component extensions with background pages for performance
35
+ '--disable-component-extensions-with-background-pages',
36
+ // disable installation of default apps on first run
37
+ '--disable-default-apps',
38
+ // work-around for environments where a small /dev/shm partition causes crashes
39
+ '--disable-dev-shm-usage',
40
+ // disable extensions
41
+ '--disable-extensions',
42
+ // disable hang monitor dialogs in renderer processes
43
+ '--disable-hang-monitor',
44
+ // disable inter-process communication flooding protection for javascript
45
+ '--disable-ipc-flooding-protection',
46
+ // disable web notifications and the push API
47
+ '--disable-notifications',
48
+ // disable the prompt when a POST request causes page navigation
49
+ '--disable-prompt-on-repost',
50
+ // disable syncing browser data with google accounts
51
+ '--disable-sync',
52
+ // disable site-isolation to make network requests easier to intercept
53
+ '--disable-site-isolation-trials',
54
+ // disable the first run tasks, whether or not it's actually the first run
55
+ '--no-first-run',
56
+ // disable the sandbox for all process types that are normally sandboxed
57
+ '--no-sandbox',
58
+ // enable indication that browser is controlled by automation
59
+ '--enable-automation',
60
+ // specify a consistent encryption backend across platforms
61
+ '--password-store=basic',
62
+ // use a mock keychain on Mac to prevent blocking permissions dialogs
63
+ '--use-mock-keychain',
64
+ // enable remote debugging on the first available port
42
65
  '--remote-debugging-port=0'];
43
-
44
66
  constructor(percy) {
45
67
  super().percy = percy;
46
68
  }
47
-
48
69
  async launch() {
49
70
  // already launching or launched
50
71
  if (this.readyState != null) return;
@@ -59,57 +80,57 @@ export class Browser extends EventEmitter {
59
80
  args = [],
60
81
  timeout
61
82
  } = launchOptions;
62
- executable ?? (executable = process.env.PERCY_BROWSER_EXECUTABLE); // transform cookies object to an array of cookie params
83
+ executable ?? (executable = process.env.PERCY_BROWSER_EXECUTABLE);
63
84
 
85
+ // transform cookies object to an array of cookie params
64
86
  this.cookies = Array.isArray(cookies) ? cookies : Object.entries(cookies).map(([name, value]) => ({
65
87
  name,
66
88
  value
67
- })); // check if any provided executable exists
89
+ }));
68
90
 
91
+ // check if any provided executable exists
69
92
  if (executable && !fs.existsSync(executable)) {
70
93
  this.log.error(`Browser executable not found: ${executable}`);
71
94
  executable = null;
72
- } // download and install the browser if not already present
73
-
95
+ }
74
96
 
97
+ // download and install the browser if not already present
75
98
  this.executable = executable || (await install.chromium());
76
- this.log.debug('Launching browser'); // create a temporary profile directory and collect additional launch arguments
99
+ this.log.debug('Launching browser');
77
100
 
101
+ // create a temporary profile directory and collect additional launch arguments
78
102
  this.profile = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'percy-browser-'));
79
103
  /* istanbul ignore next: only false for debugging */
80
-
81
104
  if (headless) this.args.push('--headless', '--hide-scrollbars', '--mute-audio');
82
-
83
105
  for (let a of args) if (!this.args.includes(a)) this.args.push(a);
106
+ this.args.push(`--user-data-dir=${this.profile}`);
84
107
 
85
- this.args.push(`--user-data-dir=${this.profile}`); // spawn the browser process and connect a websocket to the devtools address
86
-
108
+ // spawn the browser process and connect a websocket to the devtools address
87
109
  this.ws = new WebSocket(await this.spawn(timeout), {
88
110
  perMessageDeflate: false
89
- }); // wait until the websocket has connected
111
+ });
90
112
 
113
+ // wait until the websocket has connected
91
114
  await new Promise(resolve => this.ws.once('open', resolve));
92
- this.ws.on('message', data => this._handleMessage(data)); // get version information
115
+ this.ws.on('message', data => this._handleMessage(data));
93
116
 
117
+ // get version information
94
118
  this.version = await this.send('Browser.getVersion');
95
119
  this.log.debug(`Browser connected [${this.process.pid}]: ${this.version.product}`);
96
120
  this.readyState = 1;
97
121
  }
98
-
99
122
  isConnected() {
100
123
  var _this$ws;
101
-
102
124
  return ((_this$ws = this.ws) === null || _this$ws === void 0 ? void 0 : _this$ws.readyState) === WebSocket.OPEN;
103
125
  }
104
-
105
126
  async close() {
106
127
  var _this$process, _this$ws2;
107
-
108
128
  // not running, already closed, or closing
109
129
  if (this._closed) return this._closed;
110
130
  this.readyState = 2;
111
- this.log.debug('Closing browser'); // resolves when the browser has closed
131
+ this.log.debug('Closing browser');
112
132
 
133
+ // resolves when the browser has closed
113
134
  this._closed = Promise.all([new Promise(resolve => {
114
135
  /* istanbul ignore next: race condition paranoia */
115
136
  if (!this.process || this.process.exitCode) resolve();else this.process.on('exit', resolve);
@@ -131,25 +152,26 @@ export class Browser extends EventEmitter {
131
152
  }).then(() => {
132
153
  this.log.debug('Browser closed');
133
154
  this.readyState = 3;
134
- }); // reject any pending callbacks
155
+ });
135
156
 
157
+ // reject any pending callbacks
136
158
  for (let callback of this.#callbacks.values()) {
137
159
  callback.reject(Object.assign(callback.error, {
138
160
  message: `Protocol error (${callback.method}): Browser closed.`
139
161
  }));
140
- } // trigger rejecting pending session callbacks
141
-
162
+ }
142
163
 
164
+ // trigger rejecting pending session callbacks
143
165
  for (let session of this.sessions.values()) {
144
166
  session._handleClose();
145
- } // clear own callbacks and sessions
146
-
167
+ }
147
168
 
169
+ // clear own callbacks and sessions
148
170
  this.#callbacks.clear();
149
171
  this.sessions.clear();
172
+
150
173
  /* istanbul ignore next:
151
174
  * difficult to test failure here without mocking private properties */
152
-
153
175
  if ((_this$process = this.process) !== null && _this$process !== void 0 && _this$process.pid && !this.process.killed) {
154
176
  // always force close the browser process
155
177
  try {
@@ -157,14 +179,14 @@ export class Browser extends EventEmitter {
157
179
  } catch (error) {
158
180
  throw new Error(`Unable to close the browser: ${error.stack}`);
159
181
  }
160
- } // close the socket connection
161
-
182
+ }
162
183
 
163
- (_this$ws2 = this.ws) === null || _this$ws2 === void 0 ? void 0 : _this$ws2.close(); // wait for the browser to close
184
+ // close the socket connection
185
+ (_this$ws2 = this.ws) === null || _this$ws2 === void 0 ? void 0 : _this$ws2.close();
164
186
 
187
+ // wait for the browser to close
165
188
  return this._closed;
166
189
  }
167
-
168
190
  async page(options = {}) {
169
191
  let {
170
192
  targetId
@@ -181,17 +203,17 @@ export class Browser extends EventEmitter {
181
203
  await page._handleAttachedToTarget();
182
204
  return page;
183
205
  }
184
-
185
206
  async send(method, params) {
186
207
  /* istanbul ignore next:
187
208
  * difficult to test failure here without mocking private properties */
188
- if (!this.isConnected()) throw new Error('Browser not connected'); // every command needs a unique id
209
+ if (!this.isConnected()) throw new Error('Browser not connected');
189
210
 
211
+ // every command needs a unique id
190
212
  let id = ++this.#lastid;
191
-
192
213
  if (!params && typeof method === 'object') {
193
214
  // allow providing a raw message as the only argument and return the id
194
- this.ws.send(JSON.stringify({ ...method,
215
+ this.ws.send(JSON.stringify({
216
+ ...method,
195
217
  id
196
218
  }));
197
219
  return id;
@@ -201,8 +223,9 @@ export class Browser extends EventEmitter {
201
223
  id,
202
224
  method,
203
225
  params
204
- })); // will resolve or reject when a matching response is received
226
+ }));
205
227
 
228
+ // will resolve or reject when a matching response is received
206
229
  return new Promise((resolve, reject) => {
207
230
  this.#callbacks.set(id, {
208
231
  error: new Error(),
@@ -213,26 +236,22 @@ export class Browser extends EventEmitter {
213
236
  });
214
237
  }
215
238
  }
216
-
217
239
  async spawn(timeout = 30000) {
218
240
  // spawn the browser process detached in its own group and session
219
241
  this.process = spawn(this.executable, this.args, {
220
242
  detached: process.platform !== 'win32'
221
- }); // watch the process stderr and resolve when it emits the devtools protocol address
243
+ });
222
244
 
245
+ // watch the process stderr and resolve when it emits the devtools protocol address
223
246
  this.address = await new Promise((resolve, reject) => {
224
247
  let stderr = '';
225
-
226
248
  let handleData = chunk => {
227
249
  stderr += chunk = chunk.toString();
228
250
  let match = chunk.match(/^DevTools listening on (ws:\/\/.*)$/m);
229
251
  if (match) cleanup(() => resolve(match[1]));
230
252
  };
231
-
232
253
  let handleExitClose = () => handleError();
233
-
234
254
  let handleError = error => cleanup(() => reject(new Error(`Failed to launch browser. ${(error === null || error === void 0 ? void 0 : error.message) ?? ''}\n${stderr}'\n\n`)));
235
-
236
255
  let cleanup = callback => {
237
256
  clearTimeout(timeoutId);
238
257
  this.process.stderr.off('data', handleData);
@@ -241,7 +260,6 @@ export class Browser extends EventEmitter {
241
260
  this.process.off('error', handleError);
242
261
  callback();
243
262
  };
244
-
245
263
  let timeoutId = setTimeout(() => handleError(new Error(`Timed out after ${timeout}ms`)), timeout);
246
264
  this.process.stderr.on('data', handleData);
247
265
  this.process.stderr.on('close', handleExitClose);
@@ -250,10 +268,8 @@ export class Browser extends EventEmitter {
250
268
  });
251
269
  return this.address;
252
270
  }
253
-
254
271
  _handleMessage(data) {
255
272
  data = JSON.parse(data);
256
-
257
273
  if (data.method === 'Target.attachedToTarget') {
258
274
  // create a new session reference when attached to a target
259
275
  let session = new Session(this, data);
@@ -264,7 +280,6 @@ export class Browser extends EventEmitter {
264
280
  this.sessions.delete(data.params.sessionId);
265
281
  session === null || session === void 0 ? void 0 : session._handleClose();
266
282
  }
267
-
268
283
  if (data.sessionId) {
269
284
  // message was for a specific session that sent it
270
285
  let session = this.sessions.get(data.sessionId);
@@ -273,8 +288,8 @@ export class Browser extends EventEmitter {
273
288
  // resolve or reject a pending promise created with #send()
274
289
  let callback = this.#callbacks.get(data.id);
275
290
  this.#callbacks.delete(data.id);
276
- /* istanbul ignore next: races with page._handleMessage() */
277
291
 
292
+ /* istanbul ignore next: races with page._handleMessage() */
278
293
  if (data.error) {
279
294
  callback.reject(Object.assign(callback.error, {
280
295
  message: `Protocol error (${callback.method}): ${data.error.message}` + ('data' in data.error ? `: ${data.error.data}` : '')
@@ -287,6 +302,5 @@ export class Browser extends EventEmitter {
287
302
  this.emit(data.method, data.params);
288
303
  }
289
304
  }
290
-
291
305
  }
292
306
  export default Browser;
package/dist/config.js CHANGED
@@ -165,8 +165,9 @@ export const configSchema = {
165
165
  }
166
166
  }
167
167
  }
168
- }; // Common per-snapshot capture options
168
+ };
169
169
 
170
+ // Common per-snapshot capture options
170
171
  export const snapshotSchema = {
171
172
  $id: '/snapshot',
172
173
  $ref: '#/$defs/snapshot',
@@ -482,8 +483,9 @@ export const snapshotSchema = {
482
483
  }
483
484
  }
484
485
  }
485
- }; // Comparison upload options
486
+ };
486
487
 
488
+ // Comparison upload options
487
489
  export const comparisonSchema = {
488
490
  type: 'object',
489
491
  $id: '/comparison',
@@ -560,10 +562,12 @@ export const comparisonSchema = {
560
562
  }
561
563
  }
562
564
  }
563
- }; // Grouped schemas for easier registration
565
+ };
564
566
 
565
- export const schemas = [configSchema, snapshotSchema, comparisonSchema]; // Config migrate function
567
+ // Grouped schemas for easier registration
568
+ export const schemas = [configSchema, snapshotSchema, comparisonSchema];
566
569
 
570
+ // Config migrate function
567
571
  export function configMigration(config, util) {
568
572
  /* eslint-disable curly */
569
573
  if (config.version < 2) {
@@ -581,8 +585,9 @@ export function configMigration(config, util) {
581
585
  until: '2.0.0'
582
586
  });
583
587
  }
584
- } // Snapshot option migrate function
588
+ }
585
589
 
590
+ // Snapshot option migrate function
586
591
  export function snapshotMigration(config, util, root = '') {
587
592
  // discovery options have moved
588
593
  util.deprecate(`${root}.devicePixelRatio`, {
@@ -591,8 +596,9 @@ export function snapshotMigration(config, util, root = '') {
591
596
  until: '2.0.0',
592
597
  warn: true
593
598
  });
594
- } // Snapshot list options migrate function
599
+ }
595
600
 
601
+ // Snapshot list options migrate function
596
602
  export function snapshotListMigration(config, util) {
597
603
  if (config.snapshots) {
598
604
  // migrate each snapshot options
@@ -601,9 +607,9 @@ export function snapshotListMigration(config, util) {
601
607
  snapshotMigration(config, util, `snapshots[${i}]`);
602
608
  }
603
609
  }
604
- } // migrate options
605
-
610
+ }
606
611
 
612
+ // migrate options
607
613
  if (Array.isArray(config.options)) {
608
614
  for (let i in config.options) {
609
615
  snapshotMigration(config, util, `options[${i}]`);
@@ -611,8 +617,9 @@ export function snapshotListMigration(config, util) {
611
617
  } else {
612
618
  snapshotMigration(config, util, 'options');
613
619
  }
614
- } // Grouped migrations for easier registration
620
+ }
615
621
 
622
+ // Grouped migrations for easier registration
616
623
  export const migrations = {
617
624
  '/config': configMigration,
618
625
  '/snapshot': snapshotMigration,