@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 +35 -27
- package/dist/config.js +5 -4
- package/dist/index.js +8 -6
- package/dist/network.js +14 -7
- package/dist/page.js +16 -6
- package/dist/percy.js +206 -152
- package/dist/queue.js +55 -39
- package/dist/server.js +12 -5
- package/dist/session.js +9 -3
- package/dist/snapshot.js +15 -9
- package/dist/utils.js +91 -27
- package/package.json +14 -14
- package/test/helpers/server.js +5 -1
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
|
-
|
|
70
|
+
_classPrivateFieldInitSpec(this, _callbacks, {
|
|
65
71
|
writable: true,
|
|
66
72
|
value: new Map()
|
|
67
73
|
});
|
|
68
74
|
|
|
69
|
-
|
|
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
|
|
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
|
-
|
|
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-')); //
|
|
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
|
|
296
|
+
let handleError = error => cleanup(() => {
|
|
297
|
+
var _error$message;
|
|
285
298
|
|
|
286
|
-
|
|
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',
|
|
300
|
-
this.process.off('exit',
|
|
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',
|
|
308
|
-
this.process.on('exit',
|
|
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.
|
|
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
|
-
}; //
|
|
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
|
-
} //
|
|
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
|
-
|
|
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); //
|
|
12
|
+
PercyConfig.addMigration(CoreConfig.migrations); // export the Percy class with commonjs compatibility
|
|
12
13
|
|
|
13
|
-
module.exports =
|
|
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
|
-
|
|
44
|
+
_classPrivateFieldInitSpec(this, _pending, {
|
|
41
45
|
writable: true,
|
|
42
46
|
value: new Map()
|
|
43
47
|
});
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
_classPrivateFieldInitSpec(this, _requests, {
|
|
46
50
|
writable: true,
|
|
47
51
|
value: new Map()
|
|
48
52
|
});
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
_classPrivateFieldInitSpec(this, _intercepts, {
|
|
51
55
|
writable: true,
|
|
52
56
|
value: new Map()
|
|
53
57
|
});
|
|
54
58
|
|
|
55
|
-
|
|
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.
|
|
360
|
+
exports.Network = Network;
|
|
361
|
+
|
|
362
|
+
_defineProperty(Network, "TIMEOUT", 30000);
|
|
357
363
|
|
|
358
|
-
|
|
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() {' +
|
|
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(
|
|
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 === '
|
|
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.
|
|
288
|
+
exports.Page = Page;
|
|
289
|
+
|
|
290
|
+
_defineProperty(Page, "TIMEOUT", 30000);
|
|
282
291
|
|
|
283
|
-
|
|
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
|
-
...
|
|
76
|
+
..._options
|
|
71
77
|
} = {}) {
|
|
72
78
|
_defineProperty(this, "log", (0, _logger.default)('core'));
|
|
73
79
|
|
|
74
80
|
_defineProperty(this, "readyState", null);
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
_classPrivateFieldInitSpec(this, _uploads, {
|
|
77
83
|
writable: true,
|
|
78
84
|
value: new _queue.default()
|
|
79
85
|
});
|
|
80
86
|
|
|
81
|
-
|
|
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:
|
|
256
|
+
overrides: _options,
|
|
92
257
|
path: config
|
|
93
258
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
377
|
+
this._scheduleUpload(snap.name, { ...snap,
|
|
378
|
+
resources
|
|
379
|
+
});
|
|
328
380
|
});
|
|
329
381
|
} catch (error) {
|
|
330
|
-
if (error.
|
|
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
|
|
394
|
+
} // Queues a snapshot upload with the provided options
|
|
343
395
|
|
|
344
396
|
|
|
345
|
-
_scheduleUpload(
|
|
346
|
-
_classPrivateFieldGet(this, _uploads).push(`upload/${
|
|
397
|
+
_scheduleUpload(name, options) {
|
|
398
|
+
return _classPrivateFieldGet(this, _uploads).push(`upload/${name}`, async () => {
|
|
347
399
|
try {
|
|
348
|
-
|
|
349
|
-
|
|
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: ${
|
|
360
|
-
this.log.error((_failed$detail = failed === null || failed === void 0 ? void 0 : failed.detail) !== null && _failed$detail !== void 0 ? _failed$detail : error,
|
|
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.
|
|
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
|
-
|
|
32
|
+
_classPrivateFieldInitSpec(this, _queued, {
|
|
42
33
|
writable: true,
|
|
43
34
|
value: new Map()
|
|
44
35
|
});
|
|
45
36
|
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
70
|
+
var _classPrivateFieldGet2, _classPrivateFieldGet3;
|
|
77
71
|
|
|
78
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 =
|
|
158
|
-
|
|
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
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
188
|
+
done(task.reject)(err);
|
|
175
189
|
}
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
}
|
|
179
193
|
|
|
180
|
-
exports.
|
|
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 =
|
|
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': () =>
|
|
159
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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(
|
|
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
|
-
} //
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
99
|
+
_defineProperty(this, "canceled", true);
|
|
100
|
+
}
|
|
77
101
|
|
|
102
|
+
} // recursively runs the generator, maybe throwing an error when canceled
|
|
78
103
|
|
|
79
|
-
|
|
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(
|
|
85
|
-
timeout:
|
|
86
|
-
} :
|
|
87
|
-
return
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
}
|
|
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.
|
|
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.
|
|
29
|
-
"@percy/config": "1.0.0-beta.
|
|
30
|
-
"@percy/dom": "1.0.0-beta.
|
|
31
|
-
"@percy/logger": "1.0.0-beta.
|
|
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
|
-
"
|
|
38
|
-
"type": "git",
|
|
39
|
-
"url": "https://github.com/percy/cli",
|
|
40
|
-
"directory": "packages/core"
|
|
41
|
-
},
|
|
42
|
-
"gitHead": "34f37a98ff71281cebadd39e53bb55a65b0d3456"
|
|
42
|
+
"gitHead": "f8d1e38e93e5d731b8519d3fa8637e8a478efcde"
|
|
43
43
|
}
|
package/test/helpers/server.js
CHANGED
|
@@ -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
|
-
|
|
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;
|