@percy/core 1.4.0 → 1.6.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 CHANGED
@@ -8,7 +8,7 @@ export const PERCY_DOM = createRequire(import.meta.url).resolve('@percy/dom'); /
8
8
 
9
9
  export function createPercyServer(percy, port) {
10
10
  let pkg = getPackageJSON(import.meta.url);
11
- return Server.createServer({
11
+ let server = Server.createServer({
12
12
  port
13
13
  }) // facilitate logger websocket connections
14
14
  .websocket('/(logger)?', ws => {
@@ -29,13 +29,31 @@ export function createPercyServer(percy, port) {
29
29
  }));
30
30
  }) // general middleware
31
31
  .route((req, res, next) => {
32
+ var _percy$testing, _percy$testing3, _percy$testing3$api, _percy$testing4, _percy$testing4$api;
33
+
32
34
  // treat all request bodies as json
33
35
  if (req.body) try {
34
36
  req.body = JSON.parse(req.body);
35
37
  } catch {} // add version header
36
38
 
37
- res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version');
38
- res.setHeader('X-Percy-Core-Version', pkg.version); // return json errors
39
+ res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version'); // skip or change api version header in testing mode
40
+
41
+ if (((_percy$testing = percy.testing) === null || _percy$testing === void 0 ? void 0 : _percy$testing.version) !== false) {
42
+ var _percy$testing2;
43
+
44
+ res.setHeader('X-Percy-Core-Version', ((_percy$testing2 = percy.testing) === null || _percy$testing2 === void 0 ? void 0 : _percy$testing2.version) ?? pkg.version);
45
+ } // support sabotaging requests in testing mode
46
+
47
+
48
+ if (((_percy$testing3 = percy.testing) === null || _percy$testing3 === void 0 ? void 0 : (_percy$testing3$api = _percy$testing3.api) === null || _percy$testing3$api === void 0 ? void 0 : _percy$testing3$api[req.url.pathname]) === 'error') {
49
+ return res.json(500, {
50
+ success: false,
51
+ error: 'Error: testing'
52
+ });
53
+ } else if (((_percy$testing4 = percy.testing) === null || _percy$testing4 === void 0 ? void 0 : (_percy$testing4$api = _percy$testing4.api) === null || _percy$testing4$api === void 0 ? void 0 : _percy$testing4$api[req.url.pathname]) === 'disconnect') {
54
+ return req.connection.destroy();
55
+ } // return json errors
56
+
39
57
 
40
58
  return next().catch(e => res.json(e.status ?? 500, {
41
59
  build: percy.build,
@@ -77,6 +95,42 @@ export function createPercyServer(percy, port) {
77
95
  return res.json(200, {
78
96
  success: true
79
97
  });
98
+ }); // add test endpoints only in testing mode
99
+
100
+ return !percy.testing ? server : server // manipulates testing mode configuration to trigger specific scenarios
101
+ .route('/test/api/:cmd', ({
102
+ body,
103
+ params: {
104
+ cmd
105
+ }
106
+ }, res) => {
107
+ body = Buffer.isBuffer(body) ? body.toString() : body;
108
+
109
+ if (cmd === 'reset') {
110
+ // the reset command will reset testing mode to its default options
111
+ percy.testing = {};
112
+ } else if (cmd === 'version') {
113
+ // the version command will update the api version header for testing
114
+ percy.testing.version = body;
115
+ } else if (cmd === 'error' || cmd === 'disconnect') {
116
+ // the error or disconnect commands will cause specific endpoints to fail
117
+ percy.testing.api = { ...percy.testing.api,
118
+ [body]: cmd
119
+ };
120
+ } else {
121
+ // 404 for unknown commands
122
+ return res.send(404);
123
+ }
124
+
125
+ return res.json(200, {
126
+ testing: percy.testing
127
+ });
128
+ }) // returns an array of raw logs from the logger
129
+ .route('get', '/test/logs', (req, res) => res.json(200, {
130
+ logs: Array.from(logger.instance.messages)
131
+ })) // serves a very basic html page for testing snapshots
132
+ .route('get', '/test/snapshot', (req, res) => {
133
+ return res.send(200, 'text/html', '<p>Snapshot Me!</p>');
80
134
  });
81
135
  } // Create a static server instance with an automatic sitemap
82
136
 
package/dist/config.js CHANGED
@@ -28,6 +28,9 @@ export const configSchema = {
28
28
  },
29
29
  scope: {
30
30
  type: 'string'
31
+ },
32
+ devicePixelRatio: {
33
+ type: 'integer'
31
34
  }
32
35
  }
33
36
  },
@@ -177,6 +180,9 @@ export const snapshotSchema = {
177
180
  enableJavaScript: {
178
181
  $ref: '/config/snapshot#/properties/enableJavaScript'
179
182
  },
183
+ devicePixelRatio: {
184
+ $ref: '/config/snapshot#/properties/devicePixelRatio'
185
+ },
180
186
  discovery: {
181
187
  type: 'object',
182
188
  additionalProperties: false,
package/dist/discovery.js CHANGED
@@ -89,8 +89,10 @@ export function createRequestFinishedHandler(network, {
89
89
 
90
90
  if (mimeType !== null && mimeType !== void 0 && mimeType.includes('font')) {
91
91
  // font responses from the browser may not be properly encoded, so request them directly
92
+ log.debug('- Requesting asset directly');
92
93
  body = await makeRequest(response.url, {
93
- buffer: true
94
+ buffer: true,
95
+ headers: request.headers
94
96
  });
95
97
  }
96
98
 
package/dist/page.js CHANGED
@@ -29,12 +29,14 @@ export class Page {
29
29
 
30
30
  async resize({
31
31
  width,
32
- height
32
+ height,
33
+ deviceScaleFactor = 1,
34
+ mobile = false
33
35
  }) {
34
- this.log.debug(`Resize page to ${width}x${height}`);
36
+ this.log.debug(`Resize page to ${width}x${height} @${deviceScaleFactor}x`);
35
37
  await this.session.send('Emulation.setDeviceMetricsOverride', {
36
- deviceScaleFactor: 1,
37
- mobile: false,
38
+ deviceScaleFactor,
39
+ mobile,
38
40
  height,
39
41
  width
40
42
  });
package/dist/percy.js CHANGED
@@ -31,6 +31,8 @@ export class Percy {
31
31
  skipUploads,
32
32
  // implies `skipUploads` and also skips asset discovery
33
33
  dryRun,
34
+ // implies `dryRun`, silent logs, and adds extra api endpoints
35
+ testing,
34
36
  // configuration filepath
35
37
  config,
36
38
  // provided to @percy/client
@@ -44,8 +46,10 @@ export class Percy {
44
46
  // options which will become accessible via the `.config` property
45
47
  ...options
46
48
  } = {}) {
49
+ if (testing) loglevel = 'silent';
47
50
  if (loglevel) this.loglevel(loglevel);
48
- this.dryRun = !!dryRun;
51
+ this.testing = testing ? {} : null;
52
+ this.dryRun = !!testing || !!dryRun;
49
53
  this.skipUploads = this.dryRun || !!skipUploads;
50
54
  this.deferUploads = this.skipUploads || !!deferUploads;
51
55
  if (this.deferUploads) this.#uploads.stop();
package/dist/snapshot.js CHANGED
@@ -260,6 +260,7 @@ function debugSnapshotConfig(snapshot, showInfo) {
260
260
  debugProp(snapshot, 'widths', v => `${v}px`);
261
261
  debugProp(snapshot, 'minHeight', v => `${v}px`);
262
262
  debugProp(snapshot, 'enableJavaScript');
263
+ debugProp(snapshot, 'deviceScaleFactor');
263
264
  debugProp(snapshot, 'waitForTimeout');
264
265
  debugProp(snapshot, 'waitForSelector');
265
266
  debugProp(snapshot, 'execute.afterNavigation');
@@ -320,20 +321,55 @@ function waitForDiscoveryNetworkIdle(page, options) {
320
321
  } // Used to cache resources across core instances
321
322
 
322
323
 
323
- const RESOURCE_CACHE_KEY = Symbol('resource-cache'); // Discovers resources for a snapshot using a browser page to intercept requests. The callback
324
+ const RESOURCE_CACHE_KEY = Symbol('resource-cache'); // Trigger resource requests for a page by iterating over snapshot widths and calling any provided
325
+ // execute options. Additional resize options may be provided to capture resources mobile resources
326
+
327
+ function* triggerResourceRequests(page, snapshot, options) {
328
+ // copy widths to prevent mutation later
329
+ let [initialWidth, ...widths] = snapshot.widths; // set the initial page size
330
+
331
+ yield page.resize({
332
+ width: initialWidth,
333
+ height: snapshot.minHeight,
334
+ ...options
335
+ }); // navigate to the url
336
+
337
+ yield page.goto(snapshot.url);
338
+
339
+ if (snapshot.execute) {
340
+ // when any execute options are provided, inject snapshot options
341
+
342
+ /* istanbul ignore next: cannot detect coverage of injected code */
343
+ yield page.eval((_, s) => window.__PERCY__.snapshot = s, snapshot);
344
+ yield page.evaluate(snapshot.execute.afterNavigation);
345
+ } // trigger resize events for other widths
346
+
347
+
348
+ for (let width of widths) {
349
+ var _snapshot$execute, _snapshot$execute2;
350
+
351
+ yield page.evaluate((_snapshot$execute = snapshot.execute) === null || _snapshot$execute === void 0 ? void 0 : _snapshot$execute.beforeResize);
352
+ yield waitForDiscoveryNetworkIdle(page, snapshot.discovery);
353
+ yield page.resize({
354
+ width,
355
+ height: snapshot.minHeight,
356
+ ...options
357
+ });
358
+ yield page.evaluate((_snapshot$execute2 = snapshot.execute) === null || _snapshot$execute2 === void 0 ? void 0 : _snapshot$execute2.afterResize);
359
+ }
360
+ } // Discovers resources for a snapshot using a browser page to intercept requests. The callback
324
361
  // function will be called with the snapshot name (for additional snapshots) and an array of
325
362
  // discovered resources. When additional snapshots are provided, the callback will be called once
326
363
  // for each snapshot.
327
364
 
365
+
328
366
  export async function* discoverSnapshotResources(percy, snapshot, callback) {
329
367
  debugSnapshotConfig(snapshot, percy.dryRun); // when dry-running, invoke the callback for each snapshot and immediately return
330
368
 
331
369
  let allSnapshots = [snapshot, ...(snapshot.additionalSnapshots || [])];
332
370
  if (percy.dryRun) return allSnapshots.map(s => callback(s)); // keep a global resource cache across snapshots
333
371
 
334
- let cache = percy[RESOURCE_CACHE_KEY] || (percy[RESOURCE_CACHE_KEY] = new Map()); // copy widths to prevent mutation later
335
-
336
- let widths = snapshot.widths.slice(); // preload the root resource for existing dom snapshots
372
+ let cache = percy[RESOURCE_CACHE_KEY] || (percy[RESOURCE_CACHE_KEY] = new Map()); // preload the root resource for existing dom snapshots
337
373
 
338
374
  let resources = new Map(snapshot.domSnapshot && [createRootResource(snapshot.url, snapshot.domSnapshot)].map(resource => [resource.url, resource])); // open a new browser page
339
375
 
@@ -356,33 +392,15 @@ export async function* discoverSnapshotResources(percy, snapshot, callback) {
356
392
  });
357
393
 
358
394
  try {
359
- // set the initial page size
360
- yield page.resize({
361
- width: widths.shift(),
362
- height: snapshot.minHeight
363
- }); // navigate to the url
364
-
365
- yield page.goto(snapshot.url);
366
-
367
- if (snapshot.execute) {
368
- // when any execute options are provided, inject snapshot options
369
-
370
- /* istanbul ignore next: cannot detect coverage of injected code */
371
- yield page.eval((_, s) => window.__PERCY__.snapshot = s, snapshot);
372
- yield page.evaluate(snapshot.execute.afterNavigation);
373
- } // trigger resize events for other widths
374
-
395
+ yield* triggerResourceRequests(page, snapshot); // trigger resource requests for any alternate device pixel ratio
375
396
 
376
- for (let width of widths) {
377
- var _snapshot$execute, _snapshot$execute2;
378
-
379
- yield page.evaluate((_snapshot$execute = snapshot.execute) === null || _snapshot$execute === void 0 ? void 0 : _snapshot$execute.beforeResize);
397
+ if (snapshot.devicePixelRatio) {
398
+ // wait for any existing pending resource requests first
380
399
  yield waitForDiscoveryNetworkIdle(page, snapshot.discovery);
381
- yield page.resize({
382
- width,
383
- height: snapshot.minHeight
400
+ yield* triggerResourceRequests(page, snapshot, {
401
+ deviceScaleFactor: snapshot.devicePixelRatio,
402
+ mobile: true
384
403
  });
385
- yield page.evaluate((_snapshot$execute2 = snapshot.execute) === null || _snapshot$execute2 === void 0 ? void 0 : _snapshot$execute2.afterResize);
386
404
  }
387
405
 
388
406
  if (snapshot.domSnapshot) {
@@ -413,12 +431,8 @@ export async function* discoverSnapshotResources(percy, snapshot, callback) {
413
431
 
414
432
  resources.delete(root.url);
415
433
  }
416
- } // page clean up
417
-
418
-
419
- await page.close();
420
- } catch (error) {
434
+ }
435
+ } finally {
421
436
  await page.close();
422
- throw error;
423
437
  }
424
438
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.4.0",
3
+ "version": "1.6.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.4.0",
43
- "@percy/config": "1.4.0",
44
- "@percy/dom": "1.4.0",
45
- "@percy/logger": "1.4.0",
42
+ "@percy/client": "1.6.0",
43
+ "@percy/config": "1.6.0",
44
+ "@percy/dom": "1.6.0",
45
+ "@percy/logger": "1.6.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": "341362f3cf526db57412414aa3a9743c1a014fe1"
56
+ "gitHead": "dd03aec4a68a26425cda3afd10fa142011198b5f"
57
57
  }
@@ -7,7 +7,7 @@ export async function request(url, method = 'GET', handle) {
7
7
  try {
8
8
  return await request(url, options, cb);
9
9
  } catch (error) {
10
- if (typeof handle !== 'boolean') throw error;
10
+ if (!error.response || typeof handle !== 'boolean') throw error;
11
11
  return handle ? [error.response.body, error.response] : error.response;
12
12
  }
13
13
  }