@percy/core 1.0.0-beta.70 → 1.0.0-beta.74

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/browser.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.default = exports.Browser = void 0;
7
7
 
8
8
  var _os = _interopRequireDefault(require("os"));
9
9
 
@@ -29,6 +29,10 @@ var _page = _interopRequireDefault(require("./page"));
29
29
 
30
30
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
31
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
+
32
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; }
33
37
 
34
38
  function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
@@ -59,14 +63,16 @@ class Browser extends _events.default {
59
63
 
60
64
  _defineProperty(this, "sessions", new Map());
61
65
 
66
+ _defineProperty(this, "readyState", null);
67
+
62
68
  _defineProperty(this, "closed", false);
63
69
 
64
- _callbacks.set(this, {
70
+ _classPrivateFieldInitSpec(this, _callbacks, {
65
71
  writable: true,
66
72
  value: new Map()
67
73
  });
68
74
 
69
- _lastid.set(this, {
75
+ _classPrivateFieldInitSpec(this, _lastid, {
70
76
  writable: true,
71
77
  value: 0
72
78
  });
@@ -74,7 +80,8 @@ class Browser extends _events.default {
74
80
  _defineProperty(this, "args", [// disable the translate popup
75
81
  '--disable-features=Translate', // disable several subsystems which run network requests in the background
76
82
  '--disable-background-networking', // disable task throttling of timer tasks from background pages
77
- '--disable-background-timer-throttling', // disable backgrounding renderers for occluded windows (reduce nondeterminism)
83
+ '--disable-background-timer-throttling', // disable backgrounding renderer processes
84
+ '--disable-renderer-backgrounding', // disable backgrounding renderers for occluded windows (reduce nondeterminism)
78
85
  '--disable-backgrounding-occluded-windows', // disable crash reporting
79
86
  '--disable-breakpad', // disable client side phishing detection
80
87
  '--disable-client-side-phishing-detection', // disable default component extensions with background pages for performance
@@ -112,7 +119,9 @@ class Browser extends _events.default {
112
119
  }
113
120
 
114
121
  async launch() {
115
- if (this.isConnected()) return; // check if any provided executable exists
122
+ // already launching or launched
123
+ if (this.readyState != null) return;
124
+ this.readyState = 0; // check if any provided executable exists
116
125
 
117
126
  if (this.executable && !(0, _fs.existsSync)(this.executable)) {
118
127
  this.log.error(`Browser executable not found: ${this.executable}`);
@@ -122,11 +131,10 @@ class Browser extends _events.default {
122
131
 
123
132
  this.executable || (this.executable = await _install.default.chromium()); // create a temporary profile directory
124
133
 
125
- this.profile = await _fs.promises.mkdtemp(_path.default.join(_os.default.tmpdir(), 'percy-browser-')); // collect args to pass to the browser process
126
-
127
- let args = [...this.args, `--user-data-dir=${this.profile}`];
128
- this.log.debug('Launching browser'); // spawn the browser process detached in its own group and session
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
129
135
 
136
+ let args = this.args.concat(`--user-data-dir=${this.profile}`);
137
+ this.log.debug('Launching browser');
130
138
  this.process = (0, _crossSpawn.default)(this.executable, args, {
131
139
  detached: process.platform !== 'win32'
132
140
  }); // connect a websocket to the devtools address
@@ -140,7 +148,8 @@ class Browser extends _events.default {
140
148
  this.ws.on('message', data => this._handleMessage(data)); // get version information
141
149
 
142
150
  this.version = await this.send('Browser.getVersion');
143
- this.log.debug(`Browser connected: ${this.version.product}`);
151
+ this.log.debug(`Browser connected [${this.process.pid}]: ${this.version.product}`);
152
+ this.readyState = 1;
144
153
  }
145
154
 
146
155
  isConnected() {
@@ -152,7 +161,9 @@ class Browser extends _events.default {
152
161
  async close() {
153
162
  var _this$process4, _this$ws2;
154
163
 
164
+ // not running, already closed, or closing
155
165
  if (this._closed) return this._closed;
166
+ this.readyState = 2;
156
167
  this.log.debug('Closing browser'); // resolves when the browser has closed
157
168
 
158
169
  this._closed = Promise.all([new Promise(resolve => {
@@ -180,8 +191,9 @@ class Browser extends _events.default {
180
191
  this.log.debug(error);
181
192
  });
182
193
  }
183
-
194
+ }).then(() => {
184
195
  this.log.debug('Browser closed');
196
+ this.readyState = 3;
185
197
  }); // reject any pending callbacks
186
198
 
187
199
  for (let callback of _classPrivateFieldGet(this, _callbacks).values()) {
@@ -278,34 +290,28 @@ class Browser extends _events.default {
278
290
  let match = chunk.match(/^DevTools listening on (ws:\/\/.*)$/m);
279
291
  if (match) cleanup(() => resolve(match[1]));
280
292
  };
281
- /* istanbul ignore next: for sanity */
282
293
 
294
+ let handleExitClose = () => handleError();
283
295
 
284
- let handleExit = () => handleError();
296
+ let handleError = error => cleanup(() => {
297
+ var _error$message;
285
298
 
286
- let handleClose = () => handleError();
287
-
288
- let handleError = error => {
289
- cleanup(() => {
290
- var _error$message;
291
-
292
- 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`));
293
- });
294
- };
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
+ });
295
301
 
296
302
  let cleanup = callback => {
297
303
  clearTimeout(timeoutId);
298
304
  this.process.stderr.off('data', handleData);
299
- this.process.stderr.off('close', handleClose);
300
- this.process.off('exit', handleExit);
305
+ this.process.stderr.off('close', handleExitClose);
306
+ this.process.off('exit', handleExitClose);
301
307
  this.process.off('error', handleError);
302
308
  callback();
303
309
  };
304
310
 
305
311
  let timeoutId = setTimeout(() => handleError(new Error(`Timed out after ${timeout}ms`)), timeout);
306
312
  this.process.stderr.on('data', handleData);
307
- this.process.stderr.on('close', handleClose);
308
- this.process.on('exit', handleExit);
313
+ this.process.stderr.on('close', handleExitClose);
314
+ this.process.on('exit', handleExitClose);
309
315
  this.process.on('error', handleError);
310
316
  }));
311
317
  return this._address;
@@ -352,4 +358,6 @@ class Browser extends _events.default {
352
358
 
353
359
  }
354
360
 
355
- exports.default = Browser;
361
+ exports.Browser = Browser;
362
+ var _default = Browser;
363
+ exports.default = _default;
package/dist/config.js CHANGED
@@ -296,9 +296,12 @@ const snapshotDOMSchema = {
296
296
  // schemas have no concept of inheritance, but we can leverage JS for brevity
297
297
  ...snapshotSchema.properties
298
298
  }
299
- }; // Config migrate function
299
+ }; // Grouped schemas for easier registration
300
300
 
301
301
  exports.snapshotDOMSchema = snapshotDOMSchema;
302
+ const schemas = [configSchema, snapshotSchema, snapshotDOMSchema]; // Config migrate function
303
+
304
+ exports.schemas = schemas;
302
305
 
303
306
  function configMigration(config, util) {
304
307
  /* eslint-disable curly */
@@ -348,10 +351,8 @@ function snapshotMigration(config, util) {
348
351
  map: 'additionalSnapshots',
349
352
  ...notice
350
353
  });
351
- } // Convinient references for schema registrations
354
+ } // Grouped migrations for easier registration
352
355
 
353
356
 
354
- const schemas = [configSchema, snapshotSchema, snapshotDOMSchema];
355
- exports.schemas = schemas;
356
357
  const migrations = [['/config', configMigration], ['/snapshot', snapshotMigration]];
357
358
  exports.migrations = migrations;
package/dist/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  "use strict";
2
2
 
3
- // Register core config options
4
- const {
5
- default: PercyConfig
6
- } = require('@percy/config');
3
+ const PercyConfig = require('@percy/config');
7
4
 
8
5
  const CoreConfig = require('./config');
9
6
 
7
+ const {
8
+ Percy
9
+ } = require('./percy');
10
+
10
11
  PercyConfig.addSchema(CoreConfig.schemas);
11
- PercyConfig.addMigration(CoreConfig.migrations); // Export the Percy class with commonjs compatibility
12
+ PercyConfig.addMigration(CoreConfig.migrations); // export the Percy class with commonjs compatibility
12
13
 
13
- module.exports = require('./percy').default;
14
+ module.exports = Percy;
15
+ module.exports.Percy = Percy;
package/dist/network.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.default = exports.Network = void 0;
7
7
 
8
8
  var _logger = _interopRequireDefault(require("@percy/logger"));
9
9
 
@@ -13,6 +13,10 @@ var _discovery = require("./discovery");
13
13
 
14
14
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
15
 
16
+ function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
17
+
18
+ function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
19
+
16
20
  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; }
17
21
 
18
22
  function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
@@ -37,22 +41,22 @@ class Network {
37
41
 
38
42
  _defineProperty(this, "log", (0, _logger.default)('core:network'));
39
43
 
40
- _pending.set(this, {
44
+ _classPrivateFieldInitSpec(this, _pending, {
41
45
  writable: true,
42
46
  value: new Map()
43
47
  });
44
48
 
45
- _requests.set(this, {
49
+ _classPrivateFieldInitSpec(this, _requests, {
46
50
  writable: true,
47
51
  value: new Map()
48
52
  });
49
53
 
50
- _intercepts.set(this, {
54
+ _classPrivateFieldInitSpec(this, _intercepts, {
51
55
  writable: true,
52
56
  value: new Map()
53
57
  });
54
58
 
55
- _authentications.set(this, {
59
+ _classPrivateFieldInitSpec(this, _authentications, {
56
60
  writable: true,
57
61
  value: new Set()
58
62
  });
@@ -353,6 +357,9 @@ class Network {
353
357
 
354
358
  }
355
359
 
356
- exports.default = Network;
360
+ exports.Network = Network;
361
+
362
+ _defineProperty(Network, "TIMEOUT", 30000);
357
363
 
358
- _defineProperty(Network, "TIMEOUT", 30000);
364
+ var _default = Network;
365
+ exports.default = _default;
package/dist/page.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.default = exports.Page = void 0;
7
7
 
8
8
  var _fs = require("fs");
9
9
 
@@ -179,7 +179,14 @@ class Page {
179
179
  } // wrap the function body with percy helpers
180
180
 
181
181
 
182
- fnbody = 'function withPercyHelpers() {' + (`return (${fnbody})({` + `waitFor: ${_utils.waitFor}` + '}, ...arguments)') + '}'; // send the call function command
182
+ fnbody = 'function withPercyHelpers() {\n' + [`return (${fnbody})({ generatePromise, waitFor }, ...arguments);`, `${_utils.generatePromise}`, `${_utils.waitFor}`].join('\n\n') + '}';
183
+ /* istanbul ignore else: ironic. */
184
+
185
+ if (fnbody.includes('cov_')) {
186
+ // remove coverage statements during testing
187
+ fnbody = fnbody.replace(/cov_.*?(;\n?|,)\s*/g, '');
188
+ } // send the call function command
189
+
183
190
 
184
191
  let {
185
192
  result,
@@ -214,7 +221,7 @@ class Page {
214
221
 
215
222
  for (let script of scripts) {
216
223
  if (typeof script === 'string') {
217
- script = `async eval({ waitFor }) {\n${script}\n}`;
224
+ script = `async eval() {\n${script}\n}`;
218
225
  }
219
226
 
220
227
  await this.eval(script);
@@ -250,7 +257,7 @@ class Page {
250
257
  } // execute any javascript
251
258
 
252
259
 
253
- await this.evaluate(typeof execute === 'function' ? execute : execute === null || execute === void 0 ? void 0 : execute.beforeSnapshot); // wait for any final network activity before capturing the dom snapshot
260
+ await this.evaluate(typeof execute === 'object' && !Array.isArray(execute) ? execute.beforeSnapshot : execute); // wait for any final network activity before capturing the dom snapshot
254
261
 
255
262
  await this.network.idle(); // inject @percy/dom for serialization by evaluating the file contents which adds a global
256
263
  // PercyDOM object that we can later check against
@@ -278,6 +285,9 @@ class Page {
278
285
 
279
286
  }
280
287
 
281
- exports.default = Page;
288
+ exports.Page = Page;
289
+
290
+ _defineProperty(Page, "TIMEOUT", 30000);
282
291
 
283
- _defineProperty(Page, "TIMEOUT", 30000);
292
+ var _default = Page;
293
+ exports.default = _default;
package/dist/percy.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.default = exports.Percy = void 0;
7
7
 
8
8
  var _client = _interopRequireDefault(require("@percy/client"));
9
9
 
@@ -21,8 +21,14 @@ var _server = _interopRequireDefault(require("./server"));
21
21
 
22
22
  var _snapshot = require("./snapshot");
23
23
 
24
+ var _utils2 = require("./utils");
25
+
24
26
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
25
27
 
28
+ function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
29
+
30
+ function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
31
+
26
32
  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; }
27
33
 
28
34
  function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
@@ -67,35 +73,198 @@ class Percy {
67
73
  port = 5338,
68
74
  // options such as `snapshot` and `discovery` that are valid Percy config
69
75
  // options which will become accessible via the `.config` property
70
- ...options
76
+ ..._options
71
77
  } = {}) {
72
78
  _defineProperty(this, "log", (0, _logger.default)('core'));
73
79
 
74
80
  _defineProperty(this, "readyState", null);
75
81
 
76
- _uploads.set(this, {
82
+ _classPrivateFieldInitSpec(this, _uploads, {
77
83
  writable: true,
78
84
  value: new _queue.default()
79
85
  });
80
86
 
81
- _snapshots.set(this, {
87
+ _classPrivateFieldInitSpec(this, _snapshots, {
82
88
  writable: true,
83
89
  value: new _queue.default()
84
90
  });
85
91
 
92
+ _defineProperty(this, "idle", () => (0, _utils2.generatePromise)(async function* () {
93
+ yield* _classPrivateFieldGet(this, _snapshots).idle();
94
+ yield* _classPrivateFieldGet(this, _uploads).idle();
95
+ }.bind(this)));
96
+
97
+ _defineProperty(this, "start", options => (0, _utils2.generatePromise)(async function* () {
98
+ // already starting or started
99
+ if (this.readyState != null) return;
100
+ this.readyState = 0; // create a percy build as the first immediately queued task
101
+
102
+ let buildTask = _classPrivateFieldGet(this, _uploads).push('build/create', () => {
103
+ // pause other queued tasks until after the build is created
104
+ _classPrivateFieldGet(this, _uploads).stop();
105
+
106
+ return this.client.createBuild().then(({
107
+ data: {
108
+ id,
109
+ attributes
110
+ }
111
+ }) => {
112
+ this.build = {
113
+ id
114
+ };
115
+ this.build.number = attributes['build-number'];
116
+ this.build.url = attributes['web-url'];
117
+
118
+ _classPrivateFieldGet(this, _uploads).run();
119
+ });
120
+ }, 0); // handle deferred build errors
121
+
122
+
123
+ if (this.deferUploads) {
124
+ buildTask.catch(err => {
125
+ this.log.error('Failed to create build');
126
+ this.log.error(err);
127
+ this.close();
128
+ });
129
+ }
130
+
131
+ try {
132
+ var _this$server;
133
+
134
+ // when not deferred, wait until the build is created first
135
+ if (!this.deferUploads) await buildTask; // maybe launch the discovery browser
136
+
137
+ if (!this.dryRun && (options === null || options === void 0 ? void 0 : options.browser) !== false) {
138
+ yield this.browser.launch();
139
+ } // start the server after everything else is ready
140
+
141
+
142
+ yield (_this$server = this.server) === null || _this$server === void 0 ? void 0 : _this$server.listen(this.port); // mark instance as started
143
+
144
+ this.log.info('Percy has started!');
145
+ this.readyState = 1;
146
+ } catch (error) {
147
+ var _this$server2;
148
+
149
+ // on error, close any running server and browser
150
+ await ((_this$server2 = this.server) === null || _this$server2 === void 0 ? void 0 : _this$server2.close());
151
+ await this.browser.close(); // mark instance as closed
152
+
153
+ this.readyState = 3; // when uploads are deferred, cancel build creation
154
+
155
+ if (error.canceled && this.deferUploads) {
156
+ _classPrivateFieldGet(this, _uploads).cancel('build/create');
157
+
158
+ this.readyState = null;
159
+ } // throw an easier-to-understand error when the port is taken
160
+
161
+
162
+ if (error.code === 'EADDRINUSE') {
163
+ throw new Error('Percy is already running or the port is in use');
164
+ } else {
165
+ throw error;
166
+ }
167
+ }
168
+ }.bind(this)));
169
+
170
+ _defineProperty(this, "flush", close => (0, _utils2.generatePromise)(async function* () {
171
+ // close the snapshot queue and wait for it to empty
172
+ if (_classPrivateFieldGet(this, _snapshots).size) {
173
+ if (close) _classPrivateFieldGet(this, _snapshots).close();
174
+ yield* _classPrivateFieldGet(this, _snapshots).flush(s => {
175
+ // do not log a count when not closing or while dry-running
176
+ if (!close || this.dryRun) return;
177
+ this.log.progress(`Processing ${s} snapshot${s !== 1 ? 's' : ''}...`, !!s);
178
+ });
179
+ } // run, close, and wait for the upload queue to empty
180
+
181
+
182
+ if (!this.skipUploads && _classPrivateFieldGet(this, _uploads).size) {
183
+ if (close) _classPrivateFieldGet(this, _uploads).close();
184
+ yield* _classPrivateFieldGet(this, _uploads).flush(s => {
185
+ // do not log a count when not closing or while creating a build
186
+ if (!close || _classPrivateFieldGet(this, _uploads).has('build/create')) return;
187
+ this.log.progress(`Uploading ${s} snapshot${s !== 1 ? 's' : ''}...`, !!s);
188
+ });
189
+ }
190
+ }.bind(this)).canceled(() => {
191
+ // reopen closed queues when canceled
192
+ _classPrivateFieldGet(this, _snapshots).open();
193
+
194
+ _classPrivateFieldGet(this, _uploads).open();
195
+ }));
196
+
197
+ _defineProperty(this, "stop", force => (0, _utils2.generatePromise)(async function* () {
198
+ var _this$server3, _this$build;
199
+
200
+ // not started, but the browser was launched
201
+ if (!this.readyState && this.browser.isConnected()) {
202
+ await this.browser.close();
203
+ } // not started or already stopped
204
+
205
+
206
+ if (!this.readyState || this.readyState > 2) return; // close queues asap
207
+
208
+ if (force) this.close(); // already stopping
209
+
210
+ if (this.readyState === 2) return;
211
+ this.readyState = 2; // log when force stopping
212
+
213
+ if (force) this.log.info('Stopping percy...'); // process uploads and close queues
214
+
215
+ yield* this.flush(true); // if dry-running, log the total number of snapshots
216
+
217
+ if (this.dryRun && _classPrivateFieldGet(this, _uploads).size) {
218
+ let total = _classPrivateFieldGet(this, _uploads).size - 1; // subtract the build task
219
+
220
+ this.log.info(`Found ${total} snapshot${total !== 1 ? 's' : ''}`);
221
+ } // close any running server and browser
222
+
223
+
224
+ await ((_this$server3 = this.server) === null || _this$server3 === void 0 ? void 0 : _this$server3.close());
225
+ await this.browser.close(); // finalize and log build info
226
+
227
+ let meta = {
228
+ build: this.build
229
+ };
230
+
231
+ if ((_this$build = this.build) !== null && _this$build !== void 0 && _this$build.failed) {
232
+ // do not finalize failed builds
233
+ this.log.warn(`Build #${this.build.number} failed: ${this.build.url}`, meta);
234
+ } else if (this.build) {
235
+ // finalize the build
236
+ await this.client.finalizeBuild(this.build.id);
237
+ this.log.info(`Finalized build #${this.build.number}: ${this.build.url}`, meta);
238
+ } else {
239
+ // no build was ever created (likely failed while deferred)
240
+ this.log.warn('Build not created', meta);
241
+ } // mark instance as stopped
242
+
243
+
244
+ this.readyState = 3;
245
+ }.bind(this)).canceled(() => {
246
+ // reset ready state when canceled
247
+ this.readyState = 1;
248
+ }));
249
+
86
250
  if (loglevel) this.loglevel(loglevel);
87
251
  this.dryRun = !!dryRun;
88
252
  this.skipUploads = this.dryRun || !!skipUploads;
89
253
  this.deferUploads = this.skipUploads || !!deferUploads;
254
+ if (this.deferUploads) _classPrivateFieldGet(this, _uploads).stop();
90
255
  this.config = _config.default.load({
91
- overrides: options,
256
+ overrides: _options,
92
257
  path: config
93
258
  });
94
- let {
95
- concurrency
96
- } = this.config.discovery;
97
- if (concurrency) _classPrivateFieldGet(this, _snapshots).concurrency = concurrency;
98
- if (this.deferUploads) _classPrivateFieldGet(this, _uploads).stop();
259
+
260
+ if (this.config.discovery.concurrency) {
261
+ let {
262
+ concurrency
263
+ } = this.config.discovery;
264
+ _classPrivateFieldGet(this, _uploads).concurrency = concurrency;
265
+ _classPrivateFieldGet(this, _snapshots).concurrency = concurrency;
266
+ }
267
+
99
268
  this.client = new _client.default({
100
269
  token,
101
270
  clientInfo,
@@ -147,23 +316,21 @@ class Percy {
147
316
  this.config = (0, _utils.merge)([this.config, config], (path, prev, next) => {
148
317
  // replace arrays instead of merging
149
318
  return Array.isArray(next) && [path, next];
150
- });
319
+ }); // adjust concurrency if necessary
320
+
321
+ if (this.config.discovery.concurrency) {
322
+ let {
323
+ concurrency
324
+ } = this.config.discovery;
325
+ _classPrivateFieldGet(this, _uploads).concurrency = concurrency;
326
+ _classPrivateFieldGet(this, _snapshots).concurrency = concurrency;
327
+ }
328
+
151
329
  return this.config;
152
330
  } // Resolves once snapshot and upload queues are idle
153
331
 
154
332
 
155
- async idle() {
156
- await _classPrivateFieldGet(this, _snapshots).idle();
157
- await _classPrivateFieldGet(this, _uploads).idle();
158
- } // Waits for snapshot idle and flushes the upload queue
159
-
160
-
161
- async dispatch() {
162
- await _classPrivateFieldGet(this, _snapshots).idle();
163
- if (!this.skipUploads) await _classPrivateFieldGet(this, _uploads).flush();
164
- } // Immediately stops all queues, preventing any more tasks from running
165
-
166
-
333
+ // Immediately stops all queues, preventing any more tasks from running
167
334
  close() {
168
335
  _classPrivateFieldGet(this, _snapshots).close(true);
169
336
 
@@ -172,124 +339,7 @@ class Percy {
172
339
  // at a later time when uploads are deferred, or run immediately when not deferred.
173
340
 
174
341
 
175
- async start() {
176
- // already starting or started
177
- if (this.readyState != null) return;
178
- this.readyState = 0; // create a percy build as the first immediately queued task
179
-
180
- let buildTask = _classPrivateFieldGet(this, _uploads).push('build/create', () => {
181
- // pause other queued tasks until after the build is created
182
- _classPrivateFieldGet(this, _uploads).stop();
183
-
184
- return this.client.createBuild().then(({
185
- data: {
186
- id,
187
- attributes
188
- }
189
- }) => {
190
- this.build = {
191
- id
192
- };
193
- this.build.number = attributes['build-number'];
194
- this.build.url = attributes['web-url'];
195
-
196
- _classPrivateFieldGet(this, _uploads).run();
197
- });
198
- }, 0); // handle deferred build errors
199
-
200
-
201
- if (this.deferUploads) {
202
- buildTask.catch(err => {
203
- this.log.error('Failed to create build');
204
- this.log.error(err);
205
- this.close();
206
- });
207
- }
208
-
209
- try {
210
- var _this$server;
211
-
212
- // when not deferred, wait until the build is created first
213
- if (!this.deferUploads) await buildTask; // launch the discovery browser
214
-
215
- if (!this.dryRun) await this.browser.launch(); // if there is a server, start listening
216
-
217
- await ((_this$server = this.server) === null || _this$server === void 0 ? void 0 : _this$server.listen(this.port)); // mark this process as running
218
-
219
- this.log.info('Percy has started!');
220
- this.readyState = 1;
221
- } catch (error) {
222
- var _this$server2;
223
-
224
- // on error, close any running server and browser
225
- await ((_this$server2 = this.server) === null || _this$server2 === void 0 ? void 0 : _this$server2.close());
226
- await this.browser.close();
227
- this.readyState = 3; // throw an easier-to-understand error when the port is taken
228
-
229
- if (error.code === 'EADDRINUSE') {
230
- throw new Error('Percy is already running or the port is in use');
231
- } else {
232
- throw error;
233
- }
234
- }
235
- } // Stops the local API server and browser once snapshots have completed and finalizes the Percy
236
- // build. Does nothing if not running. When `force` is true, any queued tasks are cleared.
237
-
238
-
239
- async stop(force) {
240
- var _this$server3, _this$build;
241
-
242
- // not started or already stopped
243
- if (!this.readyState || this.readyState > 2) return; // close queues asap
244
-
245
- if (force) this.close(); // already stopping
246
-
247
- if (this.readyState === 2) return;
248
- this.readyState = 2; // log when force stopping
249
-
250
- let meta = {
251
- build: this.build
252
- };
253
- if (force) this.log.info('Stopping percy...', meta); // close the snapshot queue and wait for it to empty
254
-
255
- if (_classPrivateFieldGet(this, _snapshots).close().size) {
256
- await _classPrivateFieldGet(this, _snapshots).empty(s => !this.dryRun && this.log.progress(`Processing ${s} snapshot${s !== 1 ? 's' : ''}...`, !!s));
257
- } // run, close, and wait for the upload queue to empty
258
-
259
-
260
- if (!this.skipUploads && _classPrivateFieldGet(this, _uploads).run().close().size) {
261
- await _classPrivateFieldGet(this, _uploads).empty(s => {
262
- this.log.progress(`Uploading ${s} snapshot${s !== 1 ? 's' : ''}...`, !!s);
263
- });
264
- } // if dry-running, print the total number of snapshots that would be uploaded
265
-
266
-
267
- if (this.dryRun && _classPrivateFieldGet(this, _uploads).size) {
268
- let total = _classPrivateFieldGet(this, _uploads).size - 1; // subtract the build task
269
-
270
- this.log.info(`Found ${total} snapshot${total !== 1 ? 's' : ''}`);
271
- } // close the any running server and browser
272
-
273
-
274
- await ((_this$server3 = this.server) === null || _this$server3 === void 0 ? void 0 : _this$server3.close());
275
- await this.browser.close();
276
-
277
- if ((_this$build = this.build) !== null && _this$build !== void 0 && _this$build.failed) {
278
- // do not finalize failed builds
279
- this.log.warn(`Build #${this.build.number} failed: ${this.build.url}`, meta);
280
- } else if (this.build) {
281
- // finalize the build
282
- await this.client.finalizeBuild(this.build.id);
283
- this.log.info(`Finalized build #${this.build.number}: ${this.build.url}`, meta);
284
- } else {
285
- // no build was ever created (likely failed while deferred)
286
- this.log.warn('Build not created', meta);
287
- }
288
-
289
- this.readyState = 3;
290
- } // Deprecated capture method
291
-
292
-
342
+ // Deprecated capture method
293
343
  capture(options) {
294
344
  this.log.deprecated('The #capture() method will be ' + 'removed in 1.0.0. Use #snapshot() instead.');
295
345
  return this.snapshot(options);
@@ -324,10 +374,12 @@ class Percy {
324
374
  yield* (0, _snapshot.discoverSnapshotResources)(this, snapshot, (snap, resources) => {
325
375
  if (!this.dryRun) this.log.info(`Snapshot taken: ${snap.name}`, snap.meta);
326
376
 
327
- this._scheduleUpload(snap, resources);
377
+ this._scheduleUpload(snap.name, { ...snap,
378
+ resources
379
+ });
328
380
  });
329
381
  } catch (error) {
330
- if (error.message === 'Canceled') {
382
+ if (error.canceled) {
331
383
  this.log.error('Received a duplicate snapshot name, ' + `the previous snapshot was canceled: ${snapshot.name}`);
332
384
  } else {
333
385
  this.log.error(`Encountered an error taking snapshot: ${snapshot.name}`, snapshot.meta);
@@ -339,15 +391,15 @@ class Percy {
339
391
 
340
392
  return; // eslint-disable-line no-useless-return
341
393
  }.bind(this));
342
- } // Queues a snapshot upload with the provided configuration options and resources
394
+ } // Queues a snapshot upload with the provided options
343
395
 
344
396
 
345
- _scheduleUpload(snapshot, resources) {
346
- _classPrivateFieldGet(this, _uploads).push(`upload/${snapshot.name}`, async () => {
397
+ _scheduleUpload(name, options) {
398
+ return _classPrivateFieldGet(this, _uploads).push(`upload/${name}`, async () => {
347
399
  try {
348
- await this.client.sendSnapshot(this.build.id, { ...snapshot,
349
- resources
350
- });
400
+ /* istanbul ignore if: useful for other internal packages */
401
+ if (typeof options === 'function') options = await options();
402
+ await this.client.sendSnapshot(this.build.id, options);
351
403
  } catch (error) {
352
404
  var _error$response, _failed$detail;
353
405
 
@@ -356,8 +408,8 @@ class Percy {
356
408
 
357
409
  return ((_e$source = e.source) === null || _e$source === void 0 ? void 0 : _e$source.pointer) === '/data/attributes/build';
358
410
  });
359
- this.log.error(`Encountered an error uploading snapshot: ${snapshot.name}`, snapshot.meta);
360
- this.log.error((_failed$detail = failed === null || failed === void 0 ? void 0 : failed.detail) !== null && _failed$detail !== void 0 ? _failed$detail : error, snapshot.meta); // build failed at some point, stop accepting snapshots
411
+ this.log.error(`Encountered an error uploading snapshot: ${name}`, options.meta);
412
+ this.log.error((_failed$detail = failed === null || failed === void 0 ? void 0 : failed.detail) !== null && _failed$detail !== void 0 ? _failed$detail : error, options.meta); // build failed at some point, stop accepting snapshots
361
413
 
362
414
  if (failed) {
363
415
  this.build.failed = true;
@@ -369,4 +421,6 @@ class Percy {
369
421
 
370
422
  }
371
423
 
372
- exports.default = Percy;
424
+ exports.Percy = Percy;
425
+ var _default = Percy;
426
+ exports.default = _default;
package/dist/queue.js CHANGED
@@ -3,10 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.default = exports.Queue = void 0;
7
7
 
8
8
  var _utils = require("./utils");
9
9
 
10
+ function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
11
+
12
+ function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
13
+
10
14
  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; }
11
15
 
12
16
  function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
@@ -15,19 +19,6 @@ function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!priva
15
19
 
16
20
  function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
17
21
 
18
- function isGenerator(obj) {
19
- return typeof obj.next === 'function' && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function');
20
- }
21
-
22
- async function runGeneratorTask(task, arg) {
23
- if (task.canceled) await task.generator.throw(new Error('Canceled'));
24
- let {
25
- done,
26
- value
27
- } = await task.generator.next(arg);
28
- return done ? value : runGeneratorTask(task, value);
29
- }
30
-
31
22
  var _queued = /*#__PURE__*/new WeakMap();
32
23
 
33
24
  var _pending = /*#__PURE__*/new WeakMap();
@@ -38,12 +29,12 @@ class Queue {
38
29
 
39
30
  _defineProperty(this, "closed", false);
40
31
 
41
- _queued.set(this, {
32
+ _classPrivateFieldInitSpec(this, _queued, {
42
33
  writable: true,
43
34
  value: new Map()
44
35
  });
45
36
 
46
- _pending.set(this, {
37
+ _classPrivateFieldInitSpec(this, _pending, {
47
38
  writable: true,
48
39
  value: new Map()
49
40
  });
@@ -52,7 +43,10 @@ class Queue {
52
43
  }
53
44
 
54
45
  push(id, callback, priority) {
55
- if (this.closed) throw new Error('Closed');
46
+ if (this.closed && !id.startsWith('@@/')) {
47
+ throw new Error('Closed');
48
+ }
49
+
56
50
  this.cancel(id);
57
51
  let task = {
58
52
  id,
@@ -73,15 +67,19 @@ class Queue {
73
67
  }
74
68
 
75
69
  cancel(id) {
76
- let pending = _classPrivateFieldGet(this, _pending).get(id);
70
+ var _classPrivateFieldGet2, _classPrivateFieldGet3;
77
71
 
78
- if (pending) pending.canceled = true;
72
+ (_classPrivateFieldGet2 = _classPrivateFieldGet(this, _pending).get(id)) === null || _classPrivateFieldGet2 === void 0 ? void 0 : (_classPrivateFieldGet3 = _classPrivateFieldGet2.cancel) === null || _classPrivateFieldGet3 === void 0 ? void 0 : _classPrivateFieldGet3.call(_classPrivateFieldGet2);
79
73
 
80
74
  _classPrivateFieldGet(this, _pending).delete(id);
81
75
 
82
76
  _classPrivateFieldGet(this, _queued).delete(id);
83
77
  }
84
78
 
79
+ has(id) {
80
+ return _classPrivateFieldGet(this, _queued).has(id) || _classPrivateFieldGet(this, _pending).has(id);
81
+ }
82
+
85
83
  clear() {
86
84
  _classPrivateFieldGet(this, _queued).clear();
87
85
 
@@ -105,32 +103,48 @@ class Queue {
105
103
  return this;
106
104
  }
107
105
 
106
+ open() {
107
+ this.closed = false;
108
+ return this;
109
+ }
110
+
108
111
  close(abort) {
109
112
  if (abort) this.stop().clear();
110
113
  this.closed = true;
111
114
  return this;
112
115
  }
113
116
 
114
- async idle() {
115
- await (0, _utils.waitFor)(() => {
117
+ idle(callback) {
118
+ return (0, _utils.waitFor)(() => {
119
+ callback === null || callback === void 0 ? void 0 : callback(_classPrivateFieldGet(this, _pending).size);
116
120
  return !_classPrivateFieldGet(this, _pending).size;
117
121
  }, {
118
122
  idle: 10
119
123
  });
120
124
  }
121
125
 
122
- async empty(onCheck) {
123
- await (0, _utils.waitFor)(() => {
124
- onCheck === null || onCheck === void 0 ? void 0 : onCheck(this.size);
126
+ empty(callback) {
127
+ return (0, _utils.waitFor)(() => {
128
+ callback === null || callback === void 0 ? void 0 : callback(this.size);
125
129
  return !this.size;
126
130
  }, {
127
131
  idle: 10
128
132
  });
129
133
  }
130
134
 
131
- async flush() {
132
- this.push('@@/flush', () => this.stop());
133
- await this.run().idle();
135
+ flush(callback) {
136
+ let stopped = !this.running;
137
+ this.run().push('@@/flush', () => {
138
+ if (stopped) this.stop();
139
+ });
140
+ return this.idle(pend => {
141
+ let left = [..._classPrivateFieldGet(this, _queued).keys()].indexOf('@@/flush');
142
+ if (!~left && !_classPrivateFieldGet(this, _pending).has('@@/flush')) left = 0;
143
+ callback === null || callback === void 0 ? void 0 : callback(pend + left);
144
+ }).canceled(() => {
145
+ if (stopped) this.stop();
146
+ this.cancel('@@/flush');
147
+ });
134
148
  }
135
149
 
136
150
  next() {
@@ -154,27 +168,29 @@ class Queue {
154
168
 
155
169
  _classPrivateFieldGet(this, _pending).set(task.id, task);
156
170
 
157
- let done = (callback, arg) => {
158
- if (!task.canceled) _classPrivateFieldGet(this, _pending).delete(task.id);
171
+ let done = callback => arg => {
172
+ var _task$cancel;
173
+
174
+ if (!((_task$cancel = task.cancel) !== null && _task$cancel !== void 0 && _task$cancel.triggered)) {
175
+ _classPrivateFieldGet(this, _pending).delete(task.id);
176
+ }
177
+
159
178
  callback(arg);
160
179
 
161
180
  this._dequeue();
162
181
  };
163
182
 
164
183
  try {
165
- let result = task.callback();
166
-
167
- if (isGenerator(result)) {
168
- task.generator = result;
169
- result = runGeneratorTask(task);
170
- }
171
-
172
- return Promise.resolve(result).then(done.bind(null, task.resolve)).catch(done.bind(null, task.reject));
184
+ let gen = (0, _utils.generatePromise)(task.callback);
185
+ task.cancel = gen.cancel;
186
+ return gen.then(done(task.resolve), done(task.reject));
173
187
  } catch (err) {
174
- done(task.reject, err);
188
+ done(task.reject)(err);
175
189
  }
176
190
  }
177
191
 
178
192
  }
179
193
 
180
- exports.default = Queue;
194
+ exports.Queue = Queue;
195
+ var _default = Queue;
196
+ exports.default = _default;
package/dist/server.js CHANGED
@@ -3,8 +3,9 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.createPercyServer = createPercyServer;
6
7
  exports.createServer = createServer;
7
- exports.default = createPercyServer;
8
+ exports.default = void 0;
8
9
 
9
10
  var _fs = _interopRequireDefault(require("fs"));
10
11
 
@@ -155,9 +156,12 @@ function createPercyServer(percy) {
155
156
  }];
156
157
  },
157
158
  // stops the instance async at the end of the event loop
158
- '/percy/stop': () => setImmediate(() => percy.stop()) && [200, 'application/json', {
159
- success: true
160
- }],
159
+ '/percy/stop': () => {
160
+ setImmediate(async () => await percy.stop());
161
+ return [200, 'application/json', {
162
+ success: true
163
+ }];
164
+ },
161
165
  // other routes 404
162
166
  default: () => [404, 'application/json', {
163
167
  error: 'Not found',
@@ -185,4 +189,7 @@ function createPercyServer(percy) {
185
189
  });
186
190
  });
187
191
  return context;
188
- }
192
+ }
193
+
194
+ var _default = createPercyServer;
195
+ exports.default = _default;
package/dist/session.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.default = exports.Session = void 0;
7
7
 
8
8
  var _events = _interopRequireDefault(require("events"));
9
9
 
@@ -13,6 +13,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
13
13
 
14
14
  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; }
15
15
 
16
+ function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
17
+
18
+ function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
19
+
16
20
  function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
17
21
 
18
22
  function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
@@ -30,7 +34,7 @@ class Session extends _events.default {
30
34
 
31
35
  super();
32
36
 
33
- _callbacks.set(this, {
37
+ _classPrivateFieldInitSpec(this, _callbacks, {
34
38
  writable: true,
35
39
  value: new Map()
36
40
  });
@@ -131,4 +135,6 @@ class Session extends _events.default {
131
135
 
132
136
  }
133
137
 
134
- exports.default = Session;
138
+ exports.Session = Session;
139
+ var _default = Session;
140
+ exports.default = _default;
package/dist/snapshot.js CHANGED
@@ -63,6 +63,7 @@ function getSnapshotConfig(percy, options) {
63
63
  // only specific discovery options are used per-snapshot
64
64
  discovery: {
65
65
  allowedHostnames: [uri.hostname, ...config.discovery.allowedHostnames],
66
+ networkIdleTimeout: config.discovery.networkIdleTimeout,
66
67
  requestHeaders: config.discovery.requestHeaders,
67
68
  authorization: config.discovery.authorization,
68
69
  disableCache: config.discovery.disableCache,
@@ -246,20 +247,25 @@ async function* discoverSnapshotResources(percy, snapshot, callback) {
246
247
  yield waitForDiscoveryNetworkIdle(page, snapshot.discovery);
247
248
  handleSnapshotResources(snapshot, resources, callback);
248
249
  } else {
249
- // capture snapshots sequentially
250
- for (let snap of allSnapshots) {
251
- // shallow merge snapshot options
252
- let options = { ...snapshot,
253
- ...snap
254
- }; // will wait for timeouts, selectors, and additional network activity
250
+ let {
251
+ enableJavaScript
252
+ } = snapshot; // capture snapshots sequentially
255
253
 
254
+ for (let snap of allSnapshots) {
255
+ // will wait for timeouts, selectors, and additional network activity
256
256
  let {
257
257
  url,
258
258
  dom
259
- } = yield page.snapshot(options); // handle resources and remove previously captured dom snapshots
259
+ } = yield page.snapshot({
260
+ enableJavaScript,
261
+ ...snap
262
+ });
263
+ resources.set(url, (0, _utils2.createRootResource)(url, dom)); // shallow merge with root snapshot options
264
+
265
+ handleSnapshotResources({ ...snapshot,
266
+ ...snap
267
+ }, resources, callback); // remove the previously captured dom snapshot
260
268
 
261
- resources.set(url, (0, _utils2.createRootResource)(url, dom));
262
- handleSnapshotResources(options, resources, callback);
263
269
  resources.delete(url);
264
270
  }
265
271
  } // page clean up
package/dist/utils.js CHANGED
@@ -7,6 +7,7 @@ exports.createLogResource = createLogResource;
7
7
  exports.createPercyCSSResource = createPercyCSSResource;
8
8
  exports.createResource = createResource;
9
9
  exports.createRootResource = createRootResource;
10
+ exports.generatePromise = generatePromise;
10
11
  exports.hostname = hostname;
11
12
  Object.defineProperty(exports, "hostnameMatches", {
12
13
  enumerable: true,
@@ -15,10 +16,20 @@ Object.defineProperty(exports, "hostnameMatches", {
15
16
  }
16
17
  });
17
18
  exports.normalizeURL = normalizeURL;
19
+ Object.defineProperty(exports, "request", {
20
+ enumerable: true,
21
+ get: function () {
22
+ return _request.request;
23
+ }
24
+ });
18
25
  exports.waitFor = waitFor;
19
26
 
20
27
  var _utils = require("@percy/client/dist/utils");
21
28
 
29
+ var _request = require("@percy/client/dist/request");
30
+
31
+ 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; }
32
+
22
33
  // Returns the hostname portion of a URL.
23
34
  function hostname(url) {
24
35
  return new URL(url).hostname;
@@ -68,39 +79,92 @@ function createPercyCSSResource(url, css) {
68
79
 
69
80
  function createLogResource(logs) {
70
81
  return createResource(`/percy.${Date.now()}.log`, JSON.stringify(logs), 'text/plain');
71
- } // Polls for the predicate to be truthy within a timeout or the returned promise rejects. If
72
- // the second argument is an options object and `idle` is provided, the predicate will be
73
- // checked again after the idle period. This helper is injected as an argument when using
74
- // the `page#eval()` method, such as for the snapshot `execute` option.
82
+ } // Creates a thennable, cancelable, generator instance
83
+
84
+
85
+ function generatePromise(gen) {
86
+ var _gen, _gen2;
87
+
88
+ // ensure a generator is provided
89
+ if (typeof gen === 'function') gen = gen();
90
+ if (typeof ((_gen = gen) === null || _gen === void 0 ? void 0 : _gen.then) === 'function') return gen;
91
+ if (typeof ((_gen2 = gen) === null || _gen2 === void 0 ? void 0 : _gen2.next) !== 'function' || !(typeof gen[Symbol.iterator] === 'function' || typeof gen[Symbol.asyncIterator] === 'function')) return Promise.resolve(gen); // used to trigger cancelation
92
+
93
+ class Canceled extends Error {
94
+ constructor(...args) {
95
+ super(...args);
96
+
97
+ _defineProperty(this, "name", 'Canceled');
75
98
 
76
- /* istanbul ignore next: no instrumenting injected code */
99
+ _defineProperty(this, "canceled", true);
100
+ }
77
101
 
102
+ } // recursively runs the generator, maybe throwing an error when canceled
78
103
 
79
- function waitFor(predicate, timeoutOrOptions) {
104
+
105
+ let handleNext = async (g, last) => {
106
+ let canceled = g.cancel.triggered;
107
+ let {
108
+ done,
109
+ value
110
+ } = canceled ? await g.throw(canceled) : await g.next(last);
111
+ if (canceled) delete g.cancel.triggered;
112
+ return done ? value : handleNext(g, value);
113
+ }; // handle cancelation errors by calling any cancel handlers
114
+
115
+
116
+ let cancelable = async function* () {
117
+ try {
118
+ return yield* gen;
119
+ } catch (error) {
120
+ if (error.canceled) {
121
+ let cancelers = cancelable.cancelers || [];
122
+
123
+ for (let c of cancelers) await c(error);
124
+ }
125
+
126
+ throw error;
127
+ }
128
+ }(); // augment the cancelable generator with promise-like and cancel methods
129
+
130
+
131
+ return Object.assign(cancelable, {
132
+ run: () => cancelable.promise || (cancelable.promise = handleNext(cancelable)),
133
+ then: (resolve, reject) => cancelable.run().then(resolve, reject),
134
+ catch: reject => cancelable.run().catch(reject),
135
+ cancel: message => {
136
+ cancelable.cancel.triggered = new Canceled(message);
137
+ return cancelable;
138
+ },
139
+ canceled: handler => {
140
+ (cancelable.cancelers || (cancelable.cancelers = [])).push(handler);
141
+ return cancelable;
142
+ }
143
+ });
144
+ } // Resolves when the predicate function returns true within the timeout. If an idle option is
145
+ // provided, the predicate will be checked again before resolving, after the idle period. The poll
146
+ // option determines how often the predicate check will be run.
147
+
148
+
149
+ function waitFor(predicate, options) {
80
150
  let {
81
151
  poll = 10,
82
152
  timeout,
83
153
  idle
84
- } = Number.isInteger(timeoutOrOptions) ? {
85
- timeout: timeoutOrOptions
86
- } : timeoutOrOptions || {};
87
- return new Promise((resolve, reject) => {
88
- return function check(start, done) {
89
- try {
90
- if (timeout && Date.now() - start >= timeout) {
91
- throw new Error(`Timeout of ${timeout}ms exceeded.`);
92
- } else if (predicate()) {
93
- if (idle && !done) {
94
- setTimeout(check, idle, start, true);
95
- } else {
96
- resolve();
97
- }
98
- } else {
99
- setTimeout(check, poll, start);
100
- }
101
- } catch (error) {
102
- reject(error);
154
+ } = Number.isInteger(options) ? {
155
+ timeout: options
156
+ } : options || {};
157
+ return generatePromise(async function* check(start, done) {
158
+ while (true) {
159
+ if (timeout && Date.now() - start >= timeout) {
160
+ throw new Error(`Timeout of ${timeout}ms exceeded.`);
161
+ } else if (!predicate()) {
162
+ yield new Promise(r => setTimeout(r, poll, done = false));
163
+ } else if (idle && !done) {
164
+ yield new Promise(r => setTimeout(r, idle, done = true));
165
+ } else {
166
+ return;
103
167
  }
104
- }(Date.now());
105
- });
168
+ }
169
+ }(Date.now()));
106
170
  }
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.0.0-beta.70",
3
+ "version": "1.0.0-beta.74",
4
4
  "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/percy/cli",
8
+ "directory": "packages/core"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
5
13
  "main": "dist/index.js",
6
14
  "types": "types/index.d.ts",
7
15
  "files": [
@@ -21,23 +29,15 @@
21
29
  "test:coverage": "yarn test --coverage",
22
30
  "test:types": "tsd"
23
31
  },
24
- "publishConfig": {
25
- "access": "public"
26
- },
27
32
  "dependencies": {
28
- "@percy/client": "1.0.0-beta.70",
29
- "@percy/config": "1.0.0-beta.70",
30
- "@percy/dom": "1.0.0-beta.70",
31
- "@percy/logger": "1.0.0-beta.70",
33
+ "@percy/client": "1.0.0-beta.74",
34
+ "@percy/config": "1.0.0-beta.74",
35
+ "@percy/dom": "1.0.0-beta.74",
36
+ "@percy/logger": "1.0.0-beta.74",
32
37
  "cross-spawn": "^7.0.3",
33
38
  "extract-zip": "^2.0.1",
34
39
  "rimraf": "^3.0.2",
35
40
  "ws": "^8.0.0"
36
41
  },
37
- "repository": {
38
- "type": "git",
39
- "url": "https://github.com/percy/cli",
40
- "directory": "packages/core"
41
- },
42
- "gitHead": "34f37a98ff71281cebadd39e53bb55a65b0d3456"
42
+ "gitHead": "f8d1e38e93e5d731b8519d3fa8637e8a478efcde"
43
43
  }
@@ -1,7 +1,7 @@
1
1
  // aliased to src for coverage during tests without needing to compile this file
2
2
  const { createServer } = require('@percy/core/dist/server');
3
3
 
4
- module.exports = function createTestServer(routes, port = 8000) {
4
+ function createTestServer(routes, port = 8000) {
5
5
  let context = createServer(routes);
6
6
 
7
7
  // handle route errors
@@ -16,3 +16,7 @@ module.exports = function createTestServer(routes, port = 8000) {
16
16
  // automatically listen
17
17
  return context.listen(port);
18
18
  };
19
+
20
+ // support commonjs environments
21
+ module.exports = createTestServer;
22
+ module.exports.createTestServer = createTestServer;