@percy/core 1.10.4 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +42 -4
- package/dist/config.js +92 -2
- package/dist/discovery.js +288 -126
- package/dist/network.js +165 -54
- package/dist/page.js +18 -9
- package/dist/percy.js +156 -279
- package/dist/queue.js +335 -99
- package/dist/snapshot.js +243 -277
- package/dist/utils.js +4 -1
- package/package.json +6 -6
package/dist/snapshot.js
CHANGED
|
@@ -2,9 +2,10 @@ import logger from '@percy/logger';
|
|
|
2
2
|
import PercyConfig from '@percy/config';
|
|
3
3
|
import micromatch from 'micromatch';
|
|
4
4
|
import { configSchema } from './config.js';
|
|
5
|
-
import
|
|
5
|
+
import Queue from './queue.js';
|
|
6
|
+
import { request, hostnameMatches, yieldTo } from './utils.js'; // Throw a better error message for missing or invalid urls
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
function validURL(url, base) {
|
|
8
9
|
if (!url) {
|
|
9
10
|
throw new Error('Missing required URL for snapshot');
|
|
10
11
|
}
|
|
@@ -16,10 +17,11 @@ export function validURL(url, base) {
|
|
|
16
17
|
}
|
|
17
18
|
} // used to deserialize regular expression strings
|
|
18
19
|
|
|
20
|
+
|
|
19
21
|
const RE_REGEXP = /^\/(.+)\/(\w+)?$/; // Returns true or false if a snapshot matches the provided include and exclude predicates. A
|
|
20
22
|
// predicate can be an array of predicates, a regular expression, a glob pattern, or a function.
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
function snapshotMatches(snapshot, include, exclude) {
|
|
23
25
|
var _include, _include2;
|
|
24
26
|
|
|
25
27
|
// support an options object as the second argument
|
|
@@ -62,15 +64,16 @@ export function snapshotMatches(snapshot, include, exclude) {
|
|
|
62
64
|
return !test(exclude, false) && test(include, true);
|
|
63
65
|
} // Accepts an array of snapshots to filter and map with matching options.
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
|
|
68
|
+
function mapSnapshotOptions(snapshots, context) {
|
|
66
69
|
if (!(snapshots !== null && snapshots !== void 0 && snapshots.length)) return []; // reduce options into a single function
|
|
67
70
|
|
|
68
|
-
let applyOptions = [].concat((
|
|
71
|
+
let applyOptions = [].concat((context === null || context === void 0 ? void 0 : context.options) || []).reduceRight((next, {
|
|
69
72
|
include,
|
|
70
73
|
exclude,
|
|
71
74
|
...opts
|
|
72
75
|
}) => snap => next( // assign additional options to included snaphots
|
|
73
|
-
snapshotMatches(snap, include, exclude) ? Object.assign(snap, opts) : snap),
|
|
76
|
+
snapshotMatches(snap, include, exclude) ? Object.assign(snap, opts) : snap), snap => getSnapshotOptions(snap, context)); // reduce snapshots with overrides
|
|
74
77
|
|
|
75
78
|
return snapshots.reduce((acc, snapshot) => {
|
|
76
79
|
var _snapshot;
|
|
@@ -80,42 +83,84 @@ export function mapSnapshotOptions(percy, snapshots, config) {
|
|
|
80
83
|
url: snapshot
|
|
81
84
|
}; // normalize the snapshot url and use it for the default name
|
|
82
85
|
|
|
83
|
-
let url = validURL(snapshot.url,
|
|
86
|
+
let url = validURL(snapshot.url, context === null || context === void 0 ? void 0 : context.baseUrl);
|
|
84
87
|
(_snapshot = snapshot).name || (_snapshot.name = `${url.pathname}${url.search}${url.hash}`);
|
|
85
88
|
snapshot.url = url.href; // use the snapshot when matching include/exclude
|
|
86
89
|
|
|
87
|
-
if (snapshotMatches(snapshot,
|
|
90
|
+
if (snapshotMatches(snapshot, context)) {
|
|
88
91
|
acc.push(applyOptions(snapshot));
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
return acc;
|
|
92
95
|
}, []);
|
|
93
|
-
} //
|
|
96
|
+
} // Return snapshot options merged with defaults and global config.
|
|
94
97
|
|
|
95
|
-
export async function* gatherSnapshots(percy, options) {
|
|
96
|
-
let {
|
|
97
|
-
baseUrl,
|
|
98
|
-
snapshots
|
|
99
|
-
} = options;
|
|
100
|
-
if ('url' in options) snapshots = [options];
|
|
101
|
-
if ('sitemap' in options) snapshots = yield getSitemapSnapshots(options); // validate evaluated snapshots
|
|
102
98
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
function getSnapshotOptions(options, {
|
|
100
|
+
config,
|
|
101
|
+
meta
|
|
102
|
+
}) {
|
|
103
|
+
return PercyConfig.merge([{
|
|
104
|
+
widths: configSchema.snapshot.properties.widths.default,
|
|
105
|
+
discovery: {
|
|
106
|
+
allowedHostnames: [validURL(options.url).hostname]
|
|
107
|
+
},
|
|
108
|
+
meta: { ...meta,
|
|
109
|
+
snapshot: {
|
|
110
|
+
name: options.name
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}, config.snapshot, {
|
|
114
|
+
// only specific discovery options are used per-snapshot
|
|
115
|
+
discovery: {
|
|
116
|
+
allowedHostnames: config.discovery.allowedHostnames,
|
|
117
|
+
disallowedHostnames: config.discovery.disallowedHostnames,
|
|
118
|
+
networkIdleTimeout: config.discovery.networkIdleTimeout,
|
|
119
|
+
requestHeaders: config.discovery.requestHeaders,
|
|
120
|
+
authorization: config.discovery.authorization,
|
|
121
|
+
disableCache: config.discovery.disableCache,
|
|
122
|
+
userAgent: config.discovery.userAgent
|
|
123
|
+
}
|
|
124
|
+
}, options], (path, prev, next) => {
|
|
125
|
+
var _next, _next2;
|
|
126
|
+
|
|
127
|
+
switch (path.map(k => k.toString()).join('.')) {
|
|
128
|
+
case 'widths':
|
|
129
|
+
// dedup, sort, and override widths when not empty
|
|
130
|
+
return [path, !((_next = next) !== null && _next !== void 0 && _next.length) ? prev : [...new Set(next)].sort((a, b) => a - b)];
|
|
110
131
|
|
|
132
|
+
case 'percyCSS':
|
|
133
|
+
// concatenate percy css
|
|
134
|
+
return [path, [prev, next].filter(Boolean).join('\n')];
|
|
111
135
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
136
|
+
case 'execute':
|
|
137
|
+
// shorthand for execute.beforeSnapshot
|
|
138
|
+
return Array.isArray(next) || typeof next !== 'object' ? [path.concat('beforeSnapshot'), next] : [path];
|
|
139
|
+
|
|
140
|
+
case 'discovery.disallowedHostnames':
|
|
141
|
+
// prevent disallowing the root hostname
|
|
142
|
+
return [path, !((_next2 = next) !== null && _next2 !== void 0 && _next2.length) ? prev : (prev ?? []).concat(next).filter(h => !hostnameMatches(h, options.url))];
|
|
143
|
+
} // ensure additional snapshots have complete names
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if (path[0] === 'additionalSnapshots' && path.length === 2) {
|
|
147
|
+
let {
|
|
148
|
+
prefix = '',
|
|
149
|
+
suffix = '',
|
|
150
|
+
...n
|
|
151
|
+
} = next;
|
|
152
|
+
next = {
|
|
153
|
+
name: `${prefix}${options.name}${suffix}`,
|
|
154
|
+
...n
|
|
155
|
+
};
|
|
156
|
+
return [path, next];
|
|
157
|
+
}
|
|
158
|
+
});
|
|
115
159
|
} // Validates and migrates snapshot options against the correct schema based on provided
|
|
116
160
|
// properties. Eagerly throws an error when missing a URL for any snapshot, and warns about all
|
|
117
161
|
// other invalid options which are also scrubbed from the returned migrated options.
|
|
118
162
|
|
|
163
|
+
|
|
119
164
|
export function validateSnapshotOptions(options) {
|
|
120
165
|
var _migrated$baseUrl;
|
|
121
166
|
|
|
@@ -161,7 +206,7 @@ export function validateSnapshotOptions(options) {
|
|
|
161
206
|
} // Fetches a sitemap and parses it into a list of URLs for taking snapshots. Duplicate URLs,
|
|
162
207
|
// including a trailing slash, are removed from the resulting list.
|
|
163
208
|
|
|
164
|
-
|
|
209
|
+
async function getSitemapSnapshots(options) {
|
|
165
210
|
return request(options.sitemap, (body, res) => {
|
|
166
211
|
// validate sitemap content-type
|
|
167
212
|
let [contentType] = res.headers['content-type'].split(';');
|
|
@@ -178,271 +223,192 @@ export async function getSitemapSnapshots(options) {
|
|
|
178
223
|
return match === -1 || match === i;
|
|
179
224
|
});
|
|
180
225
|
});
|
|
181
|
-
} //
|
|
182
|
-
|
|
183
|
-
export function getSnapshotConfig(percy, options) {
|
|
184
|
-
return PercyConfig.merge([{
|
|
185
|
-
widths: configSchema.snapshot.properties.widths.default,
|
|
186
|
-
discovery: {
|
|
187
|
-
allowedHostnames: [validURL(options.url).hostname]
|
|
188
|
-
},
|
|
189
|
-
meta: {
|
|
190
|
-
snapshot: {
|
|
191
|
-
name: options.name
|
|
192
|
-
},
|
|
193
|
-
build: percy.build
|
|
194
|
-
}
|
|
195
|
-
}, percy.config.snapshot, {
|
|
196
|
-
// only specific discovery options are used per-snapshot
|
|
197
|
-
discovery: {
|
|
198
|
-
allowedHostnames: percy.config.discovery.allowedHostnames,
|
|
199
|
-
disallowedHostnames: percy.config.discovery.disallowedHostnames,
|
|
200
|
-
networkIdleTimeout: percy.config.discovery.networkIdleTimeout,
|
|
201
|
-
requestHeaders: percy.config.discovery.requestHeaders,
|
|
202
|
-
authorization: percy.config.discovery.authorization,
|
|
203
|
-
disableCache: percy.config.discovery.disableCache,
|
|
204
|
-
userAgent: percy.config.discovery.userAgent
|
|
205
|
-
}
|
|
206
|
-
}, options], (path, prev, next) => {
|
|
207
|
-
var _next, _next2;
|
|
208
|
-
|
|
209
|
-
switch (path.map(k => k.toString()).join('.')) {
|
|
210
|
-
case 'widths':
|
|
211
|
-
// dedup, sort, and override widths when not empty
|
|
212
|
-
return [path, !((_next = next) !== null && _next !== void 0 && _next.length) ? prev : Array.from(new Set(next)).sort((a, b) => a - b)];
|
|
226
|
+
} // Returns an array of derived snapshot options
|
|
213
227
|
|
|
214
|
-
case 'percyCSS':
|
|
215
|
-
// concatenate percy css
|
|
216
|
-
return [path, [prev, next].filter(Boolean).join('\n')];
|
|
217
228
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
229
|
+
export async function* gatherSnapshots(options, context) {
|
|
230
|
+
let {
|
|
231
|
+
baseUrl,
|
|
232
|
+
snapshots
|
|
233
|
+
} = options;
|
|
234
|
+
if ('url' in options) [snapshots, options] = [[options], {}];
|
|
235
|
+
if ('sitemap' in options) snapshots = yield getSitemapSnapshots(options); // validate evaluated snapshots
|
|
221
236
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
237
|
+
if (typeof snapshots === 'function') {
|
|
238
|
+
snapshots = yield* yieldTo(snapshots(baseUrl));
|
|
239
|
+
snapshots = validateSnapshotOptions({
|
|
240
|
+
baseUrl,
|
|
241
|
+
snapshots
|
|
242
|
+
}).snapshots;
|
|
243
|
+
} // map snapshots with snapshot options
|
|
226
244
|
|
|
227
245
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
prefix = '',
|
|
231
|
-
suffix = '',
|
|
232
|
-
...n
|
|
233
|
-
} = next;
|
|
234
|
-
next = {
|
|
235
|
-
name: `${prefix}${options.name}${suffix}`,
|
|
236
|
-
...n
|
|
237
|
-
};
|
|
238
|
-
return [path, next];
|
|
239
|
-
}
|
|
246
|
+
snapshots = mapSnapshotOptions(snapshots, { ...options,
|
|
247
|
+
...context
|
|
240
248
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
function debugSnapshotConfig(snapshot, showInfo) {
|
|
246
|
-
let log = logger('core:snapshot'); // log snapshot info
|
|
247
|
-
|
|
248
|
-
log.debug('---------', snapshot.meta);
|
|
249
|
-
if (showInfo) log.info(`Snapshot found: ${snapshot.name}`, snapshot.meta);else log.debug(`Handling snapshot: ${snapshot.name}`, snapshot.meta); // will log debug info for an object property if its value is defined
|
|
250
|
-
|
|
251
|
-
let debugProp = (obj, prop, format = String) => {
|
|
252
|
-
let val = prop.split('.').reduce((o, k) => o === null || o === void 0 ? void 0 : o[k], obj);
|
|
249
|
+
if (!snapshots.length) throw new Error('No snapshots found');
|
|
250
|
+
return snapshots;
|
|
251
|
+
} // Merges snapshots and deduplicates resource arrays. Duplicate log resources are replaced, root
|
|
252
|
+
// resources are deduplicated by widths, and all other resources are deduplicated by their URL.
|
|
253
253
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
function mergeSnapshotOptions(prev = {}, next) {
|
|
255
|
+
let {
|
|
256
|
+
resources: oldResources = [],
|
|
257
|
+
...existing
|
|
258
|
+
} = prev;
|
|
259
|
+
let {
|
|
260
|
+
resources: newResources = [],
|
|
261
|
+
widths = [],
|
|
262
|
+
width,
|
|
263
|
+
...incoming
|
|
264
|
+
} = next; // prioritize singular widths over mutilple widths
|
|
265
|
+
|
|
266
|
+
widths = width ? [width] : widths; // deduplicate resources by associated widths and url
|
|
267
|
+
|
|
268
|
+
let resources = oldResources.reduce((all, resource) => {
|
|
269
|
+
if (resource.log || resource.widths.every(w => widths.includes(w))) return all;
|
|
270
|
+
if (!resource.root && all.some(r => r.url === resource.url)) return all;
|
|
271
|
+
resource.widths = resource.widths.filter(w => !widths.includes(w));
|
|
272
|
+
return all.concat(resource);
|
|
273
|
+
}, newResources.map(r => ({ ...r,
|
|
274
|
+
widths
|
|
275
|
+
}))); // sort resources after merging; roots first by min-width & logs last
|
|
276
|
+
|
|
277
|
+
resources.sort((a, b) => {
|
|
278
|
+
if (a.root && b.root) return Math.min(...b.widths) - Math.min(...a.widths);
|
|
279
|
+
return a.root || b.log ? -1 : a.log || b.root ? 1 : 0;
|
|
280
|
+
}); // overwrite resources and ensure unique widths
|
|
281
|
+
|
|
282
|
+
return PercyConfig.merge([existing, incoming, {
|
|
283
|
+
widths,
|
|
284
|
+
resources
|
|
285
|
+
}], (path, prev, next) => {
|
|
286
|
+
if (path[0] === 'resources') return [path, next];
|
|
287
|
+
|
|
288
|
+
if (path[0] === 'widths' && prev && next) {
|
|
289
|
+
return [path, [...new Set([...prev, ...next])]];
|
|
258
290
|
}
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
debugProp(snapshot, 'url');
|
|
262
|
-
debugProp(snapshot, 'scope');
|
|
263
|
-
debugProp(snapshot, 'widths', v => `${v}px`);
|
|
264
|
-
debugProp(snapshot, 'minHeight', v => `${v}px`);
|
|
265
|
-
debugProp(snapshot, 'enableJavaScript');
|
|
266
|
-
debugProp(snapshot, 'deviceScaleFactor');
|
|
267
|
-
debugProp(snapshot, 'waitForTimeout');
|
|
268
|
-
debugProp(snapshot, 'waitForSelector');
|
|
269
|
-
debugProp(snapshot, 'execute.afterNavigation');
|
|
270
|
-
debugProp(snapshot, 'execute.beforeResize');
|
|
271
|
-
debugProp(snapshot, 'execute.afterResize');
|
|
272
|
-
debugProp(snapshot, 'execute.beforeSnapshot');
|
|
273
|
-
debugProp(snapshot, 'discovery.allowedHostnames');
|
|
274
|
-
debugProp(snapshot, 'discovery.disallowedHostnames');
|
|
275
|
-
debugProp(snapshot, 'discovery.requestHeaders', JSON.stringify);
|
|
276
|
-
debugProp(snapshot, 'discovery.authorization', JSON.stringify);
|
|
277
|
-
debugProp(snapshot, 'discovery.disableCache');
|
|
278
|
-
debugProp(snapshot, 'discovery.userAgent');
|
|
279
|
-
debugProp(snapshot, 'clientInfo');
|
|
280
|
-
debugProp(snapshot, 'environmentInfo');
|
|
281
|
-
debugProp(snapshot, 'domSnapshot', Boolean);
|
|
282
|
-
|
|
283
|
-
for (let added of snapshot.additionalSnapshots || []) {
|
|
284
|
-
if (showInfo) log.info(`Snapshot found: ${added.name}`, snapshot.meta);else log.debug(`Additional snapshot: ${added.name}`, snapshot.meta);
|
|
285
|
-
debugProp(added, 'waitForTimeout');
|
|
286
|
-
debugProp(added, 'waitForSelector');
|
|
287
|
-
debugProp(added, 'execute');
|
|
288
|
-
}
|
|
289
|
-
} // Calls the provided callback with additional resources
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
function handleSnapshotResources(snapshot, map, callback) {
|
|
293
|
-
let resources = [...map.values()]; // sort the root resource first
|
|
294
|
-
|
|
295
|
-
let [root] = resources.splice(resources.findIndex(r => r.root), 1);
|
|
296
|
-
resources.unshift(root); // inject Percy CSS
|
|
297
|
-
|
|
298
|
-
if (snapshot.percyCSS) {
|
|
299
|
-
let css = createPercyCSSResource(root.url, snapshot.percyCSS);
|
|
300
|
-
resources.push(css); // replace root contents and associated properties
|
|
301
|
-
|
|
302
|
-
Object.assign(root, createRootResource(root.url, root.content.replace(/(<\/body>)(?!.*\1)/is, `<link data-percy-specific-css rel="stylesheet" href="${css.pathname}"/>` + '$&')));
|
|
303
|
-
} // include associated snapshot logs matched by meta information
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
resources.push(createLogResource(logger.query(log => {
|
|
307
|
-
var _log$meta$snapshot;
|
|
308
|
-
|
|
309
|
-
return ((_log$meta$snapshot = log.meta.snapshot) === null || _log$meta$snapshot === void 0 ? void 0 : _log$meta$snapshot.name) === snapshot.meta.snapshot.name;
|
|
310
|
-
})));
|
|
311
|
-
return callback(snapshot, resources);
|
|
312
|
-
} // Wait for a page's asset discovery network to idle
|
|
291
|
+
});
|
|
292
|
+
} // Creates a snapshots queue that manages a Percy build and uploads snapshots.
|
|
313
293
|
|
|
314
294
|
|
|
315
|
-
function
|
|
295
|
+
export function createSnapshotsQueue(percy) {
|
|
316
296
|
let {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
} // trigger resize events for other widths
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
for (let width of widths) {
|
|
352
|
-
var _snapshot$execute, _snapshot$execute2;
|
|
353
|
-
|
|
354
|
-
yield page.evaluate((_snapshot$execute = snapshot.execute) === null || _snapshot$execute === void 0 ? void 0 : _snapshot$execute.beforeResize);
|
|
355
|
-
yield waitForDiscoveryNetworkIdle(page, snapshot.discovery);
|
|
356
|
-
yield page.resize({
|
|
357
|
-
width,
|
|
358
|
-
height: snapshot.minHeight,
|
|
359
|
-
...options
|
|
360
|
-
});
|
|
361
|
-
yield page.evaluate((_snapshot$execute2 = snapshot.execute) === null || _snapshot$execute2 === void 0 ? void 0 : _snapshot$execute2.afterResize);
|
|
362
|
-
}
|
|
363
|
-
} // Discovers resources for a snapshot using a browser page to intercept requests. The callback
|
|
364
|
-
// function will be called with the snapshot name (for additional snapshots) and an array of
|
|
365
|
-
// discovered resources. When additional snapshots are provided, the callback will be called once
|
|
366
|
-
// for each snapshot.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
export async function* discoverSnapshotResources(percy, snapshot, callback) {
|
|
370
|
-
debugSnapshotConfig(snapshot, percy.dryRun); // when dry-running, invoke the callback for each snapshot and immediately return
|
|
371
|
-
|
|
372
|
-
let allSnapshots = [snapshot, ...(snapshot.additionalSnapshots || [])];
|
|
373
|
-
if (percy.dryRun) return allSnapshots.map(s => callback(s)); // keep a global resource cache across snapshots
|
|
374
|
-
|
|
375
|
-
let cache = percy[RESOURCE_CACHE_KEY] || (percy[RESOURCE_CACHE_KEY] = new Map()); // preload the root resource for existing dom snapshots
|
|
376
|
-
|
|
377
|
-
let resources = new Map(snapshot.domSnapshot && [createRootResource(snapshot.url, snapshot.domSnapshot)].map(resource => [resource.url, resource])); // when no discovery browser is available, do not attempt to discover other resources
|
|
378
|
-
|
|
379
|
-
if (percy.skipDiscovery && !snapshot.domSnapshot) {
|
|
380
|
-
throw new Error('Cannot capture DOM snapshot when asset discovery is disabled');
|
|
381
|
-
} else if (percy.skipDiscovery) {
|
|
382
|
-
return handleSnapshotResources(snapshot, resources, callback);
|
|
383
|
-
} // open a new browser page
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
let page = yield percy.browser.page({
|
|
387
|
-
enableJavaScript: snapshot.enableJavaScript ?? !snapshot.domSnapshot,
|
|
388
|
-
networkIdleTimeout: snapshot.discovery.networkIdleTimeout,
|
|
389
|
-
requestHeaders: snapshot.discovery.requestHeaders,
|
|
390
|
-
authorization: snapshot.discovery.authorization,
|
|
391
|
-
userAgent: snapshot.discovery.userAgent,
|
|
392
|
-
meta: snapshot.meta,
|
|
393
|
-
// enable network inteception
|
|
394
|
-
intercept: {
|
|
395
|
-
enableJavaScript: snapshot.enableJavaScript,
|
|
396
|
-
disableCache: snapshot.discovery.disableCache,
|
|
397
|
-
allowedHostnames: snapshot.discovery.allowedHostnames,
|
|
398
|
-
disallowedHostnames: snapshot.discovery.disallowedHostnames,
|
|
399
|
-
getResource: u => resources.get(u) || cache.get(u),
|
|
400
|
-
saveResource: r => resources.set(r.url, r) && cache.set(r.url, r)
|
|
297
|
+
concurrency
|
|
298
|
+
} = percy.config.discovery;
|
|
299
|
+
let queue = new Queue();
|
|
300
|
+
let build;
|
|
301
|
+
return queue.set({
|
|
302
|
+
concurrency
|
|
303
|
+
}) // on start, create a new Percy build
|
|
304
|
+
.handle('start', async () => {
|
|
305
|
+
try {
|
|
306
|
+
build = percy.build = {};
|
|
307
|
+
let {
|
|
308
|
+
data
|
|
309
|
+
} = await percy.client.createBuild();
|
|
310
|
+
let url = data.attributes['web-url'];
|
|
311
|
+
let number = data.attributes['build-number'];
|
|
312
|
+
Object.assign(build, {
|
|
313
|
+
id: data.id,
|
|
314
|
+
url,
|
|
315
|
+
number
|
|
316
|
+
}); // immediately run the queue if not delayed or deferred
|
|
317
|
+
|
|
318
|
+
if (!percy.delayUploads && !percy.deferUploads) queue.run();
|
|
319
|
+
} catch (err) {
|
|
320
|
+
// immediately throw the error if not delayed or deferred
|
|
321
|
+
if (!percy.delayUploads && !percy.deferUploads) throw err;
|
|
322
|
+
Object.assign(build, {
|
|
323
|
+
error: 'Failed to create build'
|
|
324
|
+
});
|
|
325
|
+
percy.log.error(build.error);
|
|
326
|
+
percy.log.error(err);
|
|
327
|
+
queue.close(true);
|
|
401
328
|
}
|
|
402
|
-
})
|
|
329
|
+
}) // on end, maybe finalize the build and log about build info
|
|
330
|
+
.handle('end', async () => {
|
|
331
|
+
var _build, _build2;
|
|
403
332
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
333
|
+
if (!percy.readyState) return;
|
|
334
|
+
|
|
335
|
+
if ((_build = build) !== null && _build !== void 0 && _build.failed) {
|
|
336
|
+
percy.log.warn(`Build #${build.number} failed: ${build.url}`, {
|
|
337
|
+
build
|
|
338
|
+
});
|
|
339
|
+
} else if ((_build2 = build) !== null && _build2 !== void 0 && _build2.id) {
|
|
340
|
+
await percy.client.finalizeBuild(build.id);
|
|
341
|
+
percy.log.info(`Finalized build #${build.number}: ${build.url}`, {
|
|
342
|
+
build
|
|
343
|
+
});
|
|
344
|
+
} else {
|
|
345
|
+
percy.log.warn('Build not created', {
|
|
346
|
+
build
|
|
413
347
|
});
|
|
414
348
|
}
|
|
349
|
+
}) // snapshots are unique by name alone
|
|
350
|
+
.handle('find', ({
|
|
351
|
+
name
|
|
352
|
+
}, snapshot) => snapshot.name === name) // when pushed, maybe flush old snapshots or possibly merge with existing snapshots
|
|
353
|
+
.handle('push', (snapshot, existing) => {
|
|
354
|
+
let {
|
|
355
|
+
name,
|
|
356
|
+
meta
|
|
357
|
+
} = snapshot; // log immediately when not deferred or dry-running
|
|
358
|
+
|
|
359
|
+
if (!percy.deferUploads) percy.log.info(`Snapshot taken: ${name}`, meta);
|
|
360
|
+
if (percy.dryRun) percy.log.info(`Snapshot found: ${name}`, meta); // immediately flush when uploads are delayed but not skipped
|
|
361
|
+
|
|
362
|
+
if (percy.delayUploads && !percy.deferUploads) queue.flush(); // overwrite any existing snapshot when not deferred or when resources is a function
|
|
363
|
+
|
|
364
|
+
if (!percy.deferUploads || typeof snapshot.resources === 'function') return snapshot; // merge snapshot options when uploads are deferred
|
|
365
|
+
|
|
366
|
+
return mergeSnapshotOptions(existing, snapshot);
|
|
367
|
+
}) // send snapshots to be uploaded to the build
|
|
368
|
+
.handle('task', async function* ({
|
|
369
|
+
resources,
|
|
370
|
+
...snapshot
|
|
371
|
+
}) {
|
|
372
|
+
let {
|
|
373
|
+
name,
|
|
374
|
+
meta
|
|
375
|
+
} = snapshot; // yield to evaluated snapshot resources
|
|
376
|
+
|
|
377
|
+
snapshot.resources = typeof resources === 'function' ? yield* yieldTo(resources()) : resources; // upload the snapshot and log when deferred
|
|
378
|
+
|
|
379
|
+
let send = 'tag' in snapshot ? 'sendComparison' : 'sendSnapshot';
|
|
380
|
+
let response = yield percy.client[send](build.id, snapshot);
|
|
381
|
+
if (percy.deferUploads) percy.log.info(`Snapshot uploaded: ${name}`, meta);
|
|
382
|
+
return { ...snapshot,
|
|
383
|
+
response
|
|
384
|
+
};
|
|
385
|
+
}) // handle possible build errors returned by the API
|
|
386
|
+
.handle('error', (snapshot, error) => {
|
|
387
|
+
var _error$response;
|
|
388
|
+
|
|
389
|
+
let result = { ...snapshot,
|
|
390
|
+
error
|
|
391
|
+
};
|
|
392
|
+
let {
|
|
393
|
+
name,
|
|
394
|
+
meta
|
|
395
|
+
} = snapshot;
|
|
396
|
+
if (error.name === 'QueueClosedError') return result;
|
|
397
|
+
if (error.name === 'AbortError') return result;
|
|
398
|
+
let failed = ((_error$response = error.response) === null || _error$response === void 0 ? void 0 : _error$response.statusCode) === 422 && error.response.body.errors.find(e => {
|
|
399
|
+
var _e$source;
|
|
400
|
+
|
|
401
|
+
return ((_e$source = e.source) === null || _e$source === void 0 ? void 0 : _e$source.pointer) === '/data/attributes/build';
|
|
402
|
+
});
|
|
415
403
|
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
} else {
|
|
421
|
-
let {
|
|
422
|
-
enableJavaScript
|
|
423
|
-
} = snapshot; // capture snapshots sequentially
|
|
424
|
-
|
|
425
|
-
for (let snap of allSnapshots) {
|
|
426
|
-
// will wait for timeouts, selectors, and additional network activity
|
|
427
|
-
let {
|
|
428
|
-
url,
|
|
429
|
-
dom
|
|
430
|
-
} = yield page.snapshot({
|
|
431
|
-
enableJavaScript,
|
|
432
|
-
...snap
|
|
433
|
-
});
|
|
434
|
-
let root = createRootResource(url, dom); // use the normalized root url to prevent duplicates
|
|
435
|
-
|
|
436
|
-
resources.set(root.url, root); // shallow merge with root snapshot options
|
|
437
|
-
|
|
438
|
-
handleSnapshotResources({ ...snapshot,
|
|
439
|
-
...snap
|
|
440
|
-
}, resources, callback); // remove the previously captured dom snapshot
|
|
441
|
-
|
|
442
|
-
resources.delete(root.url);
|
|
443
|
-
}
|
|
404
|
+
if (failed) {
|
|
405
|
+
build.error = error.message = failed.detail;
|
|
406
|
+
build.failed = true;
|
|
407
|
+
queue.close(true);
|
|
444
408
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
409
|
+
|
|
410
|
+
percy.log.error(`Encountered an error uploading snapshot: ${name}`, meta);
|
|
411
|
+
percy.log.error(error, meta);
|
|
412
|
+
return result;
|
|
413
|
+
});
|
|
448
414
|
}
|
package/dist/utils.js
CHANGED
|
@@ -45,7 +45,10 @@ export function createPercyCSSResource(url, css) {
|
|
|
45
45
|
} // Creates a log resource object.
|
|
46
46
|
|
|
47
47
|
export function createLogResource(logs) {
|
|
48
|
-
|
|
48
|
+
let [url, content] = [`/percy.${Date.now()}.log`, JSON.stringify(logs)];
|
|
49
|
+
return createResource(url, content, 'text/plain', {
|
|
50
|
+
log: true
|
|
51
|
+
});
|
|
49
52
|
} // Returns true or false if the provided object is a generator or not
|
|
50
53
|
|
|
51
54
|
export function isGenerator(subject) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"test:types": "tsd"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@percy/client": "1.
|
|
43
|
-
"@percy/config": "1.
|
|
44
|
-
"@percy/dom": "1.
|
|
45
|
-
"@percy/logger": "1.
|
|
42
|
+
"@percy/client": "1.11.0",
|
|
43
|
+
"@percy/config": "1.11.0",
|
|
44
|
+
"@percy/dom": "1.11.0",
|
|
45
|
+
"@percy/logger": "1.11.0",
|
|
46
46
|
"content-disposition": "^0.5.4",
|
|
47
47
|
"cross-spawn": "^7.0.3",
|
|
48
48
|
"extract-zip": "^2.0.1",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"rimraf": "^3.0.2",
|
|
54
54
|
"ws": "^8.0.0"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "0a5043cd677266390889063924f342af9b347055"
|
|
57
57
|
}
|