@percy/core 1.0.0-beta.71 → 1.0.0-beta.72

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
 
@@ -63,6 +63,8 @@ class Browser extends _events.default {
63
63
 
64
64
  _defineProperty(this, "sessions", new Map());
65
65
 
66
+ _defineProperty(this, "readyState", null);
67
+
66
68
  _defineProperty(this, "closed", false);
67
69
 
68
70
  _classPrivateFieldInitSpec(this, _callbacks, {
@@ -78,7 +80,8 @@ class Browser extends _events.default {
78
80
  _defineProperty(this, "args", [// disable the translate popup
79
81
  '--disable-features=Translate', // disable several subsystems which run network requests in the background
80
82
  '--disable-background-networking', // disable task throttling of timer tasks from background pages
81
- '--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)
82
85
  '--disable-backgrounding-occluded-windows', // disable crash reporting
83
86
  '--disable-breakpad', // disable client side phishing detection
84
87
  '--disable-client-side-phishing-detection', // disable default component extensions with background pages for performance
@@ -116,7 +119,9 @@ class Browser extends _events.default {
116
119
  }
117
120
 
118
121
  async launch() {
119
- 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
120
125
 
121
126
  if (this.executable && !(0, _fs.existsSync)(this.executable)) {
122
127
  this.log.error(`Browser executable not found: ${this.executable}`);
@@ -126,11 +131,10 @@ class Browser extends _events.default {
126
131
 
127
132
  this.executable || (this.executable = await _install.default.chromium()); // create a temporary profile directory
128
133
 
129
- this.profile = await _fs.promises.mkdtemp(_path.default.join(_os.default.tmpdir(), 'percy-browser-')); // collect args to pass to the browser process
130
-
131
- let args = [...this.args, `--user-data-dir=${this.profile}`];
132
- 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
133
135
 
136
+ let args = this.args.concat(`--user-data-dir=${this.profile}`);
137
+ this.log.debug('Launching browser');
134
138
  this.process = (0, _crossSpawn.default)(this.executable, args, {
135
139
  detached: process.platform !== 'win32'
136
140
  }); // connect a websocket to the devtools address
@@ -144,7 +148,8 @@ class Browser extends _events.default {
144
148
  this.ws.on('message', data => this._handleMessage(data)); // get version information
145
149
 
146
150
  this.version = await this.send('Browser.getVersion');
147
- this.log.debug(`Browser connected: ${this.version.product}`);
151
+ this.log.debug(`Browser connected [${this.process.pid}]: ${this.version.product}`);
152
+ this.readyState = 1;
148
153
  }
149
154
 
150
155
  isConnected() {
@@ -156,7 +161,9 @@ class Browser extends _events.default {
156
161
  async close() {
157
162
  var _this$process4, _this$ws2;
158
163
 
164
+ // not running, already closed, or closing
159
165
  if (this._closed) return this._closed;
166
+ this.readyState = 2;
160
167
  this.log.debug('Closing browser'); // resolves when the browser has closed
161
168
 
162
169
  this._closed = Promise.all([new Promise(resolve => {
@@ -184,8 +191,9 @@ class Browser extends _events.default {
184
191
  this.log.debug(error);
185
192
  });
186
193
  }
187
-
194
+ }).then(() => {
188
195
  this.log.debug('Browser closed');
196
+ this.readyState = 3;
189
197
  }); // reject any pending callbacks
190
198
 
191
199
  for (let callback of _classPrivateFieldGet(this, _callbacks).values()) {
@@ -282,34 +290,28 @@ class Browser extends _events.default {
282
290
  let match = chunk.match(/^DevTools listening on (ws:\/\/.*)$/m);
283
291
  if (match) cleanup(() => resolve(match[1]));
284
292
  };
285
- /* istanbul ignore next: for sanity */
286
-
287
293
 
288
- let handleExit = () => handleError();
294
+ let handleExitClose = () => handleError();
289
295
 
290
- let handleClose = () => handleError();
296
+ let handleError = error => cleanup(() => {
297
+ var _error$message;
291
298
 
292
- let handleError = error => {
293
- cleanup(() => {
294
- var _error$message;
295
-
296
- 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`));
297
- });
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
+ });
299
301
 
300
302
  let cleanup = callback => {
301
303
  clearTimeout(timeoutId);
302
304
  this.process.stderr.off('data', handleData);
303
- this.process.stderr.off('close', handleClose);
304
- this.process.off('exit', handleExit);
305
+ this.process.stderr.off('close', handleExitClose);
306
+ this.process.off('exit', handleExitClose);
305
307
  this.process.off('error', handleError);
306
308
  callback();
307
309
  };
308
310
 
309
311
  let timeoutId = setTimeout(() => handleError(new Error(`Timed out after ${timeout}ms`)), timeout);
310
312
  this.process.stderr.on('data', handleData);
311
- this.process.stderr.on('close', handleClose);
312
- this.process.on('exit', handleExit);
313
+ this.process.stderr.on('close', handleExitClose);
314
+ this.process.on('exit', handleExitClose);
313
315
  this.process.on('error', handleError);
314
316
  }));
315
317
  return this._address;
@@ -356,4 +358,6 @@ class Browser extends _events.default {
356
358
 
357
359
  }
358
360
 
359
- 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
 
@@ -357,6 +357,9 @@ class Network {
357
357
 
358
358
  }
359
359
 
360
- exports.default = Network;
360
+ exports.Network = Network;
361
361
 
362
- _defineProperty(Network, "TIMEOUT", 30000);
362
+ _defineProperty(Network, "TIMEOUT", 30000);
363
+
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,6 +21,8 @@ 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
 
26
28
  function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
@@ -71,7 +73,7 @@ class Percy {
71
73
  port = 5338,
72
74
  // options such as `snapshot` and `discovery` that are valid Percy config
73
75
  // options which will become accessible via the `.config` property
74
- ...options
76
+ ..._options
75
77
  } = {}) {
76
78
  _defineProperty(this, "log", (0, _logger.default)('core'));
77
79
 
@@ -87,19 +89,182 @@ class Percy {
87
89
  value: new _queue.default()
88
90
  });
89
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
+
90
250
  if (loglevel) this.loglevel(loglevel);
91
251
  this.dryRun = !!dryRun;
92
252
  this.skipUploads = this.dryRun || !!skipUploads;
93
253
  this.deferUploads = this.skipUploads || !!deferUploads;
254
+ if (this.deferUploads) _classPrivateFieldGet(this, _uploads).stop();
94
255
  this.config = _config.default.load({
95
- overrides: options,
256
+ overrides: _options,
96
257
  path: config
97
258
  });
98
- let {
99
- concurrency
100
- } = this.config.discovery;
101
- if (concurrency) _classPrivateFieldGet(this, _snapshots).concurrency = concurrency;
102
- 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
+
103
268
  this.client = new _client.default({
104
269
  token,
105
270
  clientInfo,
@@ -151,23 +316,21 @@ class Percy {
151
316
  this.config = (0, _utils.merge)([this.config, config], (path, prev, next) => {
152
317
  // replace arrays instead of merging
153
318
  return Array.isArray(next) && [path, next];
154
- });
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
+
155
329
  return this.config;
156
330
  } // Resolves once snapshot and upload queues are idle
157
331
 
158
332
 
159
- async idle() {
160
- await _classPrivateFieldGet(this, _snapshots).idle();
161
- await _classPrivateFieldGet(this, _uploads).idle();
162
- } // Waits for snapshot idle and flushes the upload queue
163
-
164
-
165
- async dispatch() {
166
- await _classPrivateFieldGet(this, _snapshots).idle();
167
- if (!this.skipUploads) await _classPrivateFieldGet(this, _uploads).flush();
168
- } // Immediately stops all queues, preventing any more tasks from running
169
-
170
-
333
+ // Immediately stops all queues, preventing any more tasks from running
171
334
  close() {
172
335
  _classPrivateFieldGet(this, _snapshots).close(true);
173
336
 
@@ -176,124 +339,7 @@ class Percy {
176
339
  // at a later time when uploads are deferred, or run immediately when not deferred.
177
340
 
178
341
 
179
- async start() {
180
- // already starting or started
181
- if (this.readyState != null) return;
182
- this.readyState = 0; // create a percy build as the first immediately queued task
183
-
184
- let buildTask = _classPrivateFieldGet(this, _uploads).push('build/create', () => {
185
- // pause other queued tasks until after the build is created
186
- _classPrivateFieldGet(this, _uploads).stop();
187
-
188
- return this.client.createBuild().then(({
189
- data: {
190
- id,
191
- attributes
192
- }
193
- }) => {
194
- this.build = {
195
- id
196
- };
197
- this.build.number = attributes['build-number'];
198
- this.build.url = attributes['web-url'];
199
-
200
- _classPrivateFieldGet(this, _uploads).run();
201
- });
202
- }, 0); // handle deferred build errors
203
-
204
-
205
- if (this.deferUploads) {
206
- buildTask.catch(err => {
207
- this.log.error('Failed to create build');
208
- this.log.error(err);
209
- this.close();
210
- });
211
- }
212
-
213
- try {
214
- var _this$server;
215
-
216
- // when not deferred, wait until the build is created first
217
- if (!this.deferUploads) await buildTask; // launch the discovery browser
218
-
219
- if (!this.dryRun) await this.browser.launch(); // if there is a server, start listening
220
-
221
- await ((_this$server = this.server) === null || _this$server === void 0 ? void 0 : _this$server.listen(this.port)); // mark this process as running
222
-
223
- this.log.info('Percy has started!');
224
- this.readyState = 1;
225
- } catch (error) {
226
- var _this$server2;
227
-
228
- // on error, close any running server and browser
229
- await ((_this$server2 = this.server) === null || _this$server2 === void 0 ? void 0 : _this$server2.close());
230
- await this.browser.close();
231
- this.readyState = 3; // throw an easier-to-understand error when the port is taken
232
-
233
- if (error.code === 'EADDRINUSE') {
234
- throw new Error('Percy is already running or the port is in use');
235
- } else {
236
- throw error;
237
- }
238
- }
239
- } // Stops the local API server and browser once snapshots have completed and finalizes the Percy
240
- // build. Does nothing if not running. When `force` is true, any queued tasks are cleared.
241
-
242
-
243
- async stop(force) {
244
- var _this$server3, _this$build;
245
-
246
- // not started or already stopped
247
- if (!this.readyState || this.readyState > 2) return; // close queues asap
248
-
249
- if (force) this.close(); // already stopping
250
-
251
- if (this.readyState === 2) return;
252
- this.readyState = 2; // log when force stopping
253
-
254
- let meta = {
255
- build: this.build
256
- };
257
- if (force) this.log.info('Stopping percy...', meta); // close the snapshot queue and wait for it to empty
258
-
259
- if (_classPrivateFieldGet(this, _snapshots).close().size) {
260
- await _classPrivateFieldGet(this, _snapshots).empty(s => !this.dryRun && this.log.progress(`Processing ${s} snapshot${s !== 1 ? 's' : ''}...`, !!s));
261
- } // run, close, and wait for the upload queue to empty
262
-
263
-
264
- if (!this.skipUploads && _classPrivateFieldGet(this, _uploads).run().close().size) {
265
- await _classPrivateFieldGet(this, _uploads).empty(s => {
266
- this.log.progress(`Uploading ${s} snapshot${s !== 1 ? 's' : ''}...`, !!s);
267
- });
268
- } // if dry-running, print the total number of snapshots that would be uploaded
269
-
270
-
271
- if (this.dryRun && _classPrivateFieldGet(this, _uploads).size) {
272
- let total = _classPrivateFieldGet(this, _uploads).size - 1; // subtract the build task
273
-
274
- this.log.info(`Found ${total} snapshot${total !== 1 ? 's' : ''}`);
275
- } // close the any running server and browser
276
-
277
-
278
- await ((_this$server3 = this.server) === null || _this$server3 === void 0 ? void 0 : _this$server3.close());
279
- await this.browser.close();
280
-
281
- if ((_this$build = this.build) !== null && _this$build !== void 0 && _this$build.failed) {
282
- // do not finalize failed builds
283
- this.log.warn(`Build #${this.build.number} failed: ${this.build.url}`, meta);
284
- } else if (this.build) {
285
- // finalize the build
286
- await this.client.finalizeBuild(this.build.id);
287
- this.log.info(`Finalized build #${this.build.number}: ${this.build.url}`, meta);
288
- } else {
289
- // no build was ever created (likely failed while deferred)
290
- this.log.warn('Build not created', meta);
291
- }
292
-
293
- this.readyState = 3;
294
- } // Deprecated capture method
295
-
296
-
342
+ // Deprecated capture method
297
343
  capture(options) {
298
344
  this.log.deprecated('The #capture() method will be ' + 'removed in 1.0.0. Use #snapshot() instead.');
299
345
  return this.snapshot(options);
@@ -328,10 +374,12 @@ class Percy {
328
374
  yield* (0, _snapshot.discoverSnapshotResources)(this, snapshot, (snap, resources) => {
329
375
  if (!this.dryRun) this.log.info(`Snapshot taken: ${snap.name}`, snap.meta);
330
376
 
331
- this._scheduleUpload(snap, resources);
377
+ this._scheduleUpload(snap.name, { ...snap,
378
+ resources
379
+ });
332
380
  });
333
381
  } catch (error) {
334
- if (error.message === 'Canceled') {
382
+ if (error.canceled) {
335
383
  this.log.error('Received a duplicate snapshot name, ' + `the previous snapshot was canceled: ${snapshot.name}`);
336
384
  } else {
337
385
  this.log.error(`Encountered an error taking snapshot: ${snapshot.name}`, snapshot.meta);
@@ -343,15 +391,15 @@ class Percy {
343
391
 
344
392
  return; // eslint-disable-line no-useless-return
345
393
  }.bind(this));
346
- } // Queues a snapshot upload with the provided configuration options and resources
394
+ } // Queues a snapshot upload with the provided options
347
395
 
348
396
 
349
- _scheduleUpload(snapshot, resources) {
350
- _classPrivateFieldGet(this, _uploads).push(`upload/${snapshot.name}`, async () => {
397
+ _scheduleUpload(name, options) {
398
+ return _classPrivateFieldGet(this, _uploads).push(`upload/${name}`, async () => {
351
399
  try {
352
- await this.client.sendSnapshot(this.build.id, { ...snapshot,
353
- resources
354
- });
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);
355
403
  } catch (error) {
356
404
  var _error$response, _failed$detail;
357
405
 
@@ -360,8 +408,8 @@ class Percy {
360
408
 
361
409
  return ((_e$source = e.source) === null || _e$source === void 0 ? void 0 : _e$source.pointer) === '/data/attributes/build';
362
410
  });
363
- this.log.error(`Encountered an error uploading snapshot: ${snapshot.name}`, snapshot.meta);
364
- 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
365
413
 
366
414
  if (failed) {
367
415
  this.build.failed = true;
@@ -373,4 +421,6 @@ class Percy {
373
421
 
374
422
  }
375
423
 
376
- exports.default = Percy;
424
+ exports.Percy = Percy;
425
+ var _default = Percy;
426
+ exports.default = _default;
package/dist/queue.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.Queue = void 0;
7
7
 
8
8
  var _utils = require("./utils");
9
9
 
@@ -19,19 +19,6 @@ function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!priva
19
19
 
20
20
  function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
21
21
 
22
- function isGenerator(obj) {
23
- return typeof obj.next === 'function' && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function');
24
- }
25
-
26
- async function runGeneratorTask(task, arg) {
27
- if (task.canceled) await task.generator.throw(new Error('Canceled'));
28
- let {
29
- done,
30
- value
31
- } = await task.generator.next(arg);
32
- return done ? value : runGeneratorTask(task, value);
33
- }
34
-
35
22
  var _queued = /*#__PURE__*/new WeakMap();
36
23
 
37
24
  var _pending = /*#__PURE__*/new WeakMap();
@@ -56,7 +43,10 @@ class Queue {
56
43
  }
57
44
 
58
45
  push(id, callback, priority) {
59
- if (this.closed) throw new Error('Closed');
46
+ if (this.closed && !id.startsWith('@@/')) {
47
+ throw new Error('Closed');
48
+ }
49
+
60
50
  this.cancel(id);
61
51
  let task = {
62
52
  id,
@@ -77,15 +67,19 @@ class Queue {
77
67
  }
78
68
 
79
69
  cancel(id) {
80
- let pending = _classPrivateFieldGet(this, _pending).get(id);
70
+ var _classPrivateFieldGet2, _classPrivateFieldGet3;
81
71
 
82
- 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);
83
73
 
84
74
  _classPrivateFieldGet(this, _pending).delete(id);
85
75
 
86
76
  _classPrivateFieldGet(this, _queued).delete(id);
87
77
  }
88
78
 
79
+ has(id) {
80
+ return _classPrivateFieldGet(this, _queued).has(id) || _classPrivateFieldGet(this, _pending).has(id);
81
+ }
82
+
89
83
  clear() {
90
84
  _classPrivateFieldGet(this, _queued).clear();
91
85
 
@@ -109,32 +103,48 @@ class Queue {
109
103
  return this;
110
104
  }
111
105
 
106
+ open() {
107
+ this.closed = false;
108
+ return this;
109
+ }
110
+
112
111
  close(abort) {
113
112
  if (abort) this.stop().clear();
114
113
  this.closed = true;
115
114
  return this;
116
115
  }
117
116
 
118
- async idle() {
119
- 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);
120
120
  return !_classPrivateFieldGet(this, _pending).size;
121
121
  }, {
122
122
  idle: 10
123
123
  });
124
124
  }
125
125
 
126
- async empty(onCheck) {
127
- await (0, _utils.waitFor)(() => {
128
- 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);
129
129
  return !this.size;
130
130
  }, {
131
131
  idle: 10
132
132
  });
133
133
  }
134
134
 
135
- async flush() {
136
- this.push('@@/flush', () => this.stop());
137
- 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
+ });
138
148
  }
139
149
 
140
150
  next() {
@@ -158,27 +168,29 @@ class Queue {
158
168
 
159
169
  _classPrivateFieldGet(this, _pending).set(task.id, task);
160
170
 
161
- let done = (callback, arg) => {
162
- 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
+
163
178
  callback(arg);
164
179
 
165
180
  this._dequeue();
166
181
  };
167
182
 
168
183
  try {
169
- let result = task.callback();
170
-
171
- if (isGenerator(result)) {
172
- task.generator = result;
173
- result = runGeneratorTask(task);
174
- }
175
-
176
- 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));
177
187
  } catch (err) {
178
- done(task.reject, err);
188
+ done(task.reject)(err);
179
189
  }
180
190
  }
181
191
 
182
192
  }
183
193
 
184
- 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
 
@@ -135,4 +135,6 @@ class Session extends _events.default {
135
135
 
136
136
  }
137
137
 
138
- exports.default = Session;
138
+ exports.Session = Session;
139
+ var _default = Session;
140
+ exports.default = _default;
package/dist/snapshot.js CHANGED
@@ -247,20 +247,25 @@ async function* discoverSnapshotResources(percy, snapshot, callback) {
247
247
  yield waitForDiscoveryNetworkIdle(page, snapshot.discovery);
248
248
  handleSnapshotResources(snapshot, resources, callback);
249
249
  } else {
250
- // capture snapshots sequentially
251
- for (let snap of allSnapshots) {
252
- // shallow merge snapshot options
253
- let options = { ...snapshot,
254
- ...snap
255
- }; // will wait for timeouts, selectors, and additional network activity
250
+ let {
251
+ enableJavaScript
252
+ } = snapshot; // capture snapshots sequentially
256
253
 
254
+ for (let snap of allSnapshots) {
255
+ // will wait for timeouts, selectors, and additional network activity
257
256
  let {
258
257
  url,
259
258
  dom
260
- } = 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
261
268
 
262
- resources.set(url, (0, _utils2.createRootResource)(url, dom));
263
- handleSnapshotResources(options, resources, callback);
264
269
  resources.delete(url);
265
270
  }
266
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,90 @@ 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);
103
- }
104
- }(Date.now());
105
- });
154
+ } = Number.isInteger(options) ? {
155
+ timeout: options
156
+ } : options || {};
157
+ return generatePromise(async function* check(start, done) {
158
+ if (timeout && Date.now() - start >= timeout) {
159
+ throw new Error(`Timeout of ${timeout}ms exceeded.`);
160
+ } else if (!predicate()) {
161
+ yield new Promise(r => setTimeout(r, poll));
162
+ return yield* check(start);
163
+ } else if (idle && !done) {
164
+ yield new Promise(r => setTimeout(r, idle));
165
+ return yield* check(start, true);
166
+ }
167
+ }(Date.now()));
106
168
  }
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.0.0-beta.71",
3
+ "version": "1.0.0-beta.72",
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.71",
29
- "@percy/config": "1.0.0-beta.71",
30
- "@percy/dom": "1.0.0-beta.71",
31
- "@percy/logger": "1.0.0-beta.71",
33
+ "@percy/client": "1.0.0-beta.72",
34
+ "@percy/config": "1.0.0-beta.72",
35
+ "@percy/dom": "1.0.0-beta.72",
36
+ "@percy/logger": "1.0.0-beta.72",
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": "364d1df717fb19a26ccb024458df6e78a9c11f99"
42
+ "gitHead": "6219287e18a0cacb609d0c2696a5785abc9009b9"
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;