@percy/core 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/discovery.js CHANGED
@@ -1,23 +1,24 @@
1
1
  import logger from '@percy/logger';
2
2
  import Queue from './queue.js';
3
- import { normalizeURL, hostnameMatches, createRootResource, createPercyCSSResource, createLogResource, yieldAll } from './utils.js'; // Logs verbose debug logs detailing various snapshot options.
3
+ import { normalizeURL, hostnameMatches, createRootResource, createPercyCSSResource, createLogResource, yieldAll } from './utils.js';
4
4
 
5
+ // Logs verbose debug logs detailing various snapshot options.
5
6
  function debugSnapshotOptions(snapshot) {
6
- let log = logger('core:snapshot'); // log snapshot info
7
+ let log = logger('core:snapshot');
7
8
 
9
+ // log snapshot info
8
10
  log.debug('---------', snapshot.meta);
9
- log.debug(`Received snapshot: ${snapshot.name}`, snapshot.meta); // will log debug info for an object property if its value is defined
11
+ log.debug(`Received snapshot: ${snapshot.name}`, snapshot.meta);
10
12
 
13
+ // will log debug info for an object property if its value is defined
11
14
  let debugProp = (obj, prop, format = String) => {
12
15
  let val = prop.split('.').reduce((o, k) => o === null || o === void 0 ? void 0 : o[k], obj);
13
-
14
16
  if (val != null) {
15
17
  // join formatted array values with a space
16
18
  val = [].concat(val).map(format).join(', ');
17
19
  log.debug(`- ${prop}: ${val}`, snapshot.meta);
18
20
  }
19
21
  };
20
-
21
22
  debugProp(snapshot, 'url');
22
23
  debugProp(snapshot, 'scope');
23
24
  debugProp(snapshot, 'widths', v => `${v}px`);
@@ -39,65 +40,62 @@ function debugSnapshotOptions(snapshot) {
39
40
  debugProp(snapshot, 'clientInfo');
40
41
  debugProp(snapshot, 'environmentInfo');
41
42
  debugProp(snapshot, 'domSnapshot', Boolean);
42
-
43
43
  for (let added of snapshot.additionalSnapshots || []) {
44
44
  log.debug(`Additional snapshot: ${added.name}`, snapshot.meta);
45
45
  debugProp(added, 'waitForTimeout');
46
46
  debugProp(added, 'waitForSelector');
47
47
  debugProp(added, 'execute');
48
48
  }
49
- } // Wait for a page's asset discovery network to idle
50
-
49
+ }
51
50
 
51
+ // Wait for a page's asset discovery network to idle
52
52
  function waitForDiscoveryNetworkIdle(page, options) {
53
53
  let {
54
54
  allowedHostnames,
55
55
  networkIdleTimeout
56
56
  } = options;
57
-
58
57
  let filter = r => hostnameMatches(allowedHostnames, r.url);
59
-
60
58
  return page.network.idle(filter, networkIdleTimeout);
61
- } // Calls the provided callback with additional resources
62
-
59
+ }
63
60
 
61
+ // Calls the provided callback with additional resources
64
62
  function processSnapshotResources({
65
63
  domSnapshot,
66
64
  resources,
67
65
  ...snapshot
68
66
  }) {
69
67
  var _resources;
68
+ resources = [...(((_resources = resources) === null || _resources === void 0 ? void 0 : _resources.values()) ?? [])];
70
69
 
71
- resources = [...(((_resources = resources) === null || _resources === void 0 ? void 0 : _resources.values()) ?? [])]; // find or create a root resource if one does not exist
72
-
70
+ // find or create a root resource if one does not exist
73
71
  let root = resources.find(r => r.content === domSnapshot);
74
-
75
72
  if (!root) {
76
73
  root = createRootResource(snapshot.url, domSnapshot);
77
74
  resources.unshift(root);
78
- } // inject Percy CSS
79
-
75
+ }
80
76
 
77
+ // inject Percy CSS
81
78
  if (snapshot.percyCSS) {
82
79
  let css = createPercyCSSResource(root.url, snapshot.percyCSS);
83
- resources.push(css); // replace root contents and associated properties
80
+ resources.push(css);
84
81
 
82
+ // replace root contents and associated properties
85
83
  Object.assign(root, createRootResource(root.url, root.content.replace(/(<\/body>)(?!.*\1)/is, `<link data-percy-specific-css rel="stylesheet" href="${css.pathname}"/>` + '$&')));
86
- } // include associated snapshot logs matched by meta information
87
-
84
+ }
88
85
 
86
+ // include associated snapshot logs matched by meta information
89
87
  resources.push(createLogResource(logger.query(log => {
90
88
  var _log$meta$snapshot;
91
-
92
89
  return ((_log$meta$snapshot = log.meta.snapshot) === null || _log$meta$snapshot === void 0 ? void 0 : _log$meta$snapshot.name) === snapshot.meta.snapshot.name;
93
90
  })));
94
- return { ...snapshot,
91
+ return {
92
+ ...snapshot,
95
93
  resources
96
94
  };
97
- } // Triggers the capture of resource requests for a page by iterating over snapshot widths to resize
98
- // the page and calling any provided execute options.
99
-
95
+ }
100
96
 
97
+ // Triggers the capture of resource requests for a page by iterating over snapshot widths to resize
98
+ // the page and calling any provided execute options.
101
99
  async function* captureSnapshotResources(page, snapshot, options) {
102
100
  let {
103
101
  discovery,
@@ -109,50 +107,52 @@ async function* captureSnapshotResources(page, snapshot, options) {
109
107
  captureWidths,
110
108
  deviceScaleFactor,
111
109
  mobile
112
- } = options; // used to take snapshots and remove any discovered root resource
110
+ } = options;
113
111
 
112
+ // used to take snapshots and remove any discovered root resource
114
113
  let takeSnapshot = async (options, width) => {
115
- if (captureWidths) options = { ...options,
114
+ if (captureWidths) options = {
115
+ ...options,
116
116
  width
117
117
  };
118
118
  let captured = await page.snapshot(options);
119
119
  captured.resources.delete(normalizeURL(captured.url));
120
120
  capture(processSnapshotResources(captured));
121
121
  return captured;
122
- }; // used to resize the using capture options
123
-
122
+ };
124
123
 
124
+ // used to resize the using capture options
125
125
  let resizePage = width => page.resize({
126
126
  height: snapshot.minHeight,
127
127
  deviceScaleFactor,
128
128
  mobile,
129
129
  width
130
- }); // navigate to the url
131
-
130
+ });
132
131
 
132
+ // navigate to the url
133
133
  yield resizePage(snapshot.widths[0]);
134
134
  yield page.goto(snapshot.url);
135
-
136
135
  if (snapshot.execute) {
137
136
  // when any execute options are provided, inject snapshot options
138
-
139
137
  /* istanbul ignore next: cannot detect coverage of injected code */
140
138
  yield page.eval((_, s) => window.__PERCY__.snapshot = s, snapshot);
141
139
  yield page.evaluate(snapshot.execute.afterNavigation);
142
- } // iterate over additional snapshots for proper DOM capturing
143
-
140
+ }
144
141
 
142
+ // iterate over additional snapshots for proper DOM capturing
145
143
  for (let additionalSnapshot of [baseSnapshot, ...additionalSnapshots]) {
146
144
  let isBaseSnapshot = additionalSnapshot === baseSnapshot;
147
- let snap = { ...baseSnapshot,
145
+ let snap = {
146
+ ...baseSnapshot,
148
147
  ...additionalSnapshot
149
148
  };
150
149
  let {
151
150
  widths,
152
151
  execute
153
152
  } = snap;
154
- let [width] = widths; // iterate over widths to trigger reqeusts and capture other widths
153
+ let [width] = widths;
155
154
 
155
+ // iterate over widths to trigger reqeusts and capture other widths
156
156
  if (isBaseSnapshot || captureWidths) {
157
157
  for (let i = 0; i < widths.length - 1; i++) {
158
158
  if (captureWidths) yield takeSnapshot(snap, width);
@@ -162,38 +162,38 @@ async function* captureSnapshotResources(page, snapshot, options) {
162
162
  yield page.evaluate(execute === null || execute === void 0 ? void 0 : execute.afterResize);
163
163
  }
164
164
  }
165
-
166
165
  if (capture && !snapshot.domSnapshot) {
167
166
  // capture this snapshot and update the base snapshot after capture
168
167
  let captured = yield takeSnapshot(snap, width);
169
- if (isBaseSnapshot) baseSnapshot = captured; // resize back to the initial width when capturing additional snapshot widths
168
+ if (isBaseSnapshot) baseSnapshot = captured;
170
169
 
170
+ // resize back to the initial width when capturing additional snapshot widths
171
171
  if (captureWidths && additionalSnapshots.length) {
172
172
  let l = additionalSnapshots.indexOf(additionalSnapshot) + 1;
173
173
  if (l < additionalSnapshots.length) yield resizePage(snapshot.widths[0]);
174
174
  }
175
175
  }
176
- } // recursively trigger resource requests for any alternate device pixel ratio
177
-
176
+ }
178
177
 
178
+ // recursively trigger resource requests for any alternate device pixel ratio
179
179
  if (deviceScaleFactor !== discovery.devicePixelRatio) {
180
180
  yield waitForDiscoveryNetworkIdle(page, discovery);
181
181
  yield* captureSnapshotResources(page, snapshot, {
182
182
  deviceScaleFactor: discovery.devicePixelRatio,
183
183
  mobile: true
184
184
  });
185
- } // wait for final network idle when not capturing DOM
186
-
185
+ }
187
186
 
187
+ // wait for final network idle when not capturing DOM
188
188
  if (capture && snapshot.domSnapshot) {
189
189
  yield waitForDiscoveryNetworkIdle(page, discovery);
190
190
  capture(processSnapshotResources(snapshot));
191
191
  }
192
- } // Pushes all provided snapshots to a discovery queue with the provided callback, yielding to each
192
+ }
193
+
194
+ // Pushes all provided snapshots to a discovery queue with the provided callback, yielding to each
193
195
  // one concurrently. When skipping asset discovery, the callback is called immediately for each
194
196
  // snapshot, also processing snapshot resources when not dry-running.
195
-
196
-
197
197
  export async function* discoverSnapshotResources(queue, options, callback) {
198
198
  let {
199
199
  snapshots,
@@ -202,28 +202,27 @@ export async function* discoverSnapshotResources(queue, options, callback) {
202
202
  } = options;
203
203
  yield* yieldAll(snapshots.reduce((all, snapshot) => {
204
204
  debugSnapshotOptions(snapshot);
205
-
206
205
  if (skipDiscovery) {
207
206
  let {
208
207
  additionalSnapshots,
209
208
  ...baseSnapshot
210
209
  } = snapshot;
211
210
  additionalSnapshots = dryRun && additionalSnapshots || [];
212
-
213
211
  for (let snap of [baseSnapshot, ...additionalSnapshots]) {
214
212
  callback(dryRun ? snap : processSnapshotResources(snap));
215
213
  }
216
214
  } else {
217
215
  all.push(queue.push(snapshot, callback));
218
216
  }
219
-
220
217
  return all;
221
218
  }, []));
222
- } // Used to cache resources across core instances
219
+ }
223
220
 
224
- const RESOURCE_CACHE_KEY = Symbol('resource-cache'); // Creates an asset discovery queue that uses the percy browser instance to create a page for each
225
- // snapshot which is used to intercept and capture snapshot resource requests.
221
+ // Used to cache resources across core instances
222
+ const RESOURCE_CACHE_KEY = Symbol('resource-cache');
226
223
 
224
+ // Creates an asset discovery queue that uses the percy browser instance to create a page for each
225
+ // snapshot which is used to intercept and capture snapshot resource requests.
227
226
  export function createDiscoveryQueue(percy) {
228
227
  let {
229
228
  concurrency
@@ -232,19 +231,23 @@ export function createDiscoveryQueue(percy) {
232
231
  let cache;
233
232
  return queue.set({
234
233
  concurrency
235
- }) // on start, launch the browser and run the queue
234
+ })
235
+ // on start, launch the browser and run the queue
236
236
  .handle('start', async () => {
237
237
  cache = percy[RESOURCE_CACHE_KEY] = new Map();
238
238
  await percy.browser.launch();
239
239
  queue.run();
240
- }) // on end, close the browser
240
+ })
241
+ // on end, close the browser
241
242
  .handle('end', async () => {
242
243
  await percy.browser.close();
243
- }) // snapshots are unique by name; when deferred also by widths
244
+ })
245
+ // snapshots are unique by name; when deferred also by widths
244
246
  .handle('find', ({
245
247
  name,
246
248
  widths
247
- }, snapshot) => snapshot.name === name && (!percy.deferUploads || !widths || widths.join() === snapshot.widths.join())) // initialize the root resource for DOM snapshots
249
+ }, snapshot) => snapshot.name === name && (!percy.deferUploads || !widths || widths.join() === snapshot.widths.join()))
250
+ // initialize the root resource for DOM snapshots
248
251
  .handle('push', snapshot => {
249
252
  let {
250
253
  url,
@@ -252,13 +255,16 @@ export function createDiscoveryQueue(percy) {
252
255
  } = snapshot;
253
256
  let root = domSnapshot && createRootResource(url, domSnapshot);
254
257
  let resources = new Map(root ? [[root.url, root]] : []);
255
- return { ...snapshot,
258
+ return {
259
+ ...snapshot,
256
260
  resources
257
261
  };
258
- }) // discovery resources for snapshots and call the callback for each discovered snapshot
262
+ })
263
+ // discovery resources for snapshots and call the callback for each discovered snapshot
259
264
  .handle('task', async function* (snapshot, callback) {
260
- percy.log.debug(`Discovering resources: ${snapshot.name}`, snapshot.meta); // create a new browser page
265
+ percy.log.debug(`Discovering resources: ${snapshot.name}`, snapshot.meta);
261
266
 
267
+ // create a new browser page
262
268
  let page = yield percy.browser.page({
263
269
  enableJavaScript: snapshot.enableJavaScript ?? !snapshot.domSnapshot,
264
270
  networkIdleTimeout: snapshot.discovery.networkIdleTimeout,
@@ -276,7 +282,6 @@ export function createDiscoveryQueue(percy) {
276
282
  saveResource: r => snapshot.resources.set(r.url, r) && cache.set(r.url, r)
277
283
  }
278
284
  });
279
-
280
285
  try {
281
286
  yield* captureSnapshotResources(page, snapshot, {
282
287
  captureWidths: !snapshot.domSnapshot && percy.deferUploads,
package/dist/install.js CHANGED
@@ -3,27 +3,26 @@ import url from 'url';
3
3
  import path from 'path';
4
4
  import https from 'https';
5
5
  import logger from '@percy/logger';
6
- import { ProxyHttpsAgent } from '@percy/client/utils'; // Formats a raw byte integer as a string
6
+ import { ProxyHttpsAgent } from '@percy/client/utils';
7
7
 
8
+ // Formats a raw byte integer as a string
8
9
  function formatBytes(int) {
9
10
  let units = ['kB', 'MB', 'GB'];
10
11
  let base = 1024;
11
12
  let u = -1;
12
13
  if (Math.abs(int) < base) return `${int}B`;
13
-
14
14
  while (Math.abs(int) >= base && u++ < 2) int /= base;
15
-
16
15
  return `${int.toFixed(1)}${units[u]}`;
17
- } // Formats milleseconds as "MM:SS"
18
-
16
+ }
19
17
 
18
+ // Formats milleseconds as "MM:SS"
20
19
  function formatTime(ms) {
21
20
  let minutes = (ms / 1000 / 60).toString().split('.')[0].padStart(2, '0');
22
21
  let seconds = (ms / 1000 % 60).toFixed().padStart(2, '0');
23
22
  return `${minutes}:${seconds}`;
24
- } // Formats progress as ":prefix [:bar] :ratio :percent :eta"
25
-
23
+ }
26
24
 
25
+ // Formats progress as ":prefix [:bar] :ratio :percent :eta"
27
26
  function formatProgress(prefix, total, start, progress) {
28
27
  let width = 20;
29
28
  let ratio = progress === total ? 1 : Math.min(Math.max(progress / total, 0), 1);
@@ -33,9 +32,9 @@ function formatProgress(prefix, total, start, progress) {
33
32
  let elapsed = Date.now() - start;
34
33
  let eta = ratio >= 1 ? 0 : elapsed * (total / progress - 1);
35
34
  return `${prefix} [${barContent}] ` + `${formatBytes(progress)}/${formatBytes(total)} ` + `${percent}% ${formatTime(eta)}`;
36
- } // Returns an item from the map keyed by the current platform
37
-
35
+ }
38
36
 
37
+ // Returns an item from the map keyed by the current platform
39
38
  export function selectByPlatform(map) {
40
39
  let {
41
40
  platform,
@@ -44,9 +43,10 @@ export function selectByPlatform(map) {
44
43
  if (platform === 'win32' && arch === 'x64') platform = 'win64';
45
44
  if (platform === 'darwin' && arch === 'arm64') platform = 'darwinArm';
46
45
  return map[platform];
47
- } // Downloads and extracts an executable from a url into a local directory, returning the full path
48
- // to the extracted binary. Skips installation if the executable already exists at the binary path.
46
+ }
49
47
 
48
+ // Downloads and extracts an executable from a url into a local directory, returning the full path
49
+ // to the extracted binary. Skips installation if the executable already exists at the binary path.
50
50
  export async function download({
51
51
  name,
52
52
  revision,
@@ -58,30 +58,28 @@ export async function download({
58
58
  let outdir = path.join(directory, revision);
59
59
  let archive = path.join(outdir, decodeURIComponent(url.split('/').pop()));
60
60
  let exec = path.join(outdir, executable);
61
-
62
61
  if (!fs.existsSync(exec)) {
63
62
  let log = logger('core:install');
64
63
  let premsg = `Downloading ${name} ${revision}`;
65
64
  log.progress(`${premsg}...`);
66
-
67
65
  try {
68
66
  // ensure the out directory exists
69
67
  await fs.promises.mkdir(outdir, {
70
68
  recursive: true
71
- }); // download the file at the given URL
69
+ });
72
70
 
71
+ // download the file at the given URL
73
72
  await new Promise((resolve, reject) => https.get(url, {
74
73
  agent: new ProxyHttpsAgent() // allow proxied requests
75
-
76
74
  }, response => {
77
75
  // on failure, resume the response before rejecting
78
76
  if (response.statusCode !== 200) {
79
77
  response.resume();
80
78
  reject(new Error(`Download failed: ${response.statusCode} - ${url}`));
81
79
  return;
82
- } // log progress
83
-
80
+ }
84
81
 
82
+ // log progress
85
83
  if (log.shouldLog('info') && logger.stdout.isTTY) {
86
84
  let total = parseInt(response.headers['content-length'], 10);
87
85
  let start, progress;
@@ -90,14 +88,16 @@ export async function download({
90
88
  progress = (progress ?? 0) + chunk.length;
91
89
  log.progress(formatProgress(premsg, total, start, progress));
92
90
  });
93
- } // pipe the response directly to a file
94
-
91
+ }
95
92
 
93
+ // pipe the response directly to a file
96
94
  response.pipe(fs.createWriteStream(archive).on('finish', resolve).on('error', reject));
97
- }).on('error', reject)); // extract the downloaded file
95
+ }).on('error', reject));
98
96
 
99
- await extract(archive, outdir); // log success
97
+ // extract the downloaded file
98
+ await extract(archive, outdir);
100
99
 
100
+ // log success
101
101
  log.info(`Successfully downloaded ${name} ${revision}`);
102
102
  } finally {
103
103
  // always cleanup the archive
@@ -105,12 +105,13 @@ export async function download({
105
105
  await fs.promises.unlink(archive);
106
106
  }
107
107
  }
108
- } // return the path to the executable
109
-
108
+ }
110
109
 
110
+ // return the path to the executable
111
111
  return exec;
112
- } // Installs a revision of Chromium to a local directory
112
+ }
113
113
 
114
+ // Installs a revision of Chromium to a local directory
114
115
  export function chromium({
115
116
  // default directory is within @percy/core package root
116
117
  directory = path.resolve(url.fileURLToPath(import.meta.url), '../../.local-chromium'),
@@ -120,7 +121,6 @@ export function chromium({
120
121
  let extract = (i, o) => import('extract-zip').then(ex => ex.default(i, {
121
122
  dir: o
122
123
  }));
123
-
124
124
  let url = 'https://storage.googleapis.com/chromium-browser-snapshots/' + selectByPlatform({
125
125
  linux: `Linux_x64/${revision}/chrome-linux.zip`,
126
126
  darwin: `Mac/${revision}/chrome-mac.zip`,
@@ -143,14 +143,16 @@ export function chromium({
143
143
  directory,
144
144
  executable
145
145
  });
146
- } // default chromium revisions corresponds to v96.0.4664.0
146
+ }
147
147
 
148
+ // default chromium revisions corresponds to v96.0.4664.0
148
149
  chromium.revisions = {
149
150
  linux: '929511',
150
151
  win64: '929483',
151
152
  win32: '929483',
152
153
  darwin: '929475',
153
154
  darwinArm: '929475'
154
- }; // export the namespace by default
155
+ };
155
156
 
157
+ // export the namespace by default
156
158
  export * as default from './install.js';