@percy/core 1.29.4 → 1.29.5-beta.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
@@ -74,6 +74,12 @@ export function createPercyServer(percy, port) {
74
74
  build: ((_percy$testing7 = percy.testing) === null || _percy$testing7 === void 0 ? void 0 : _percy$testing7.build) ?? percy.build,
75
75
  loglevel: percy.loglevel(),
76
76
  config: percy.config,
77
+ widths: {
78
+ // This is always needed even if width is passed
79
+ mobile: percy.deviceDetails ? percy.deviceDetails.map(d => d.width) : [],
80
+ // This will only be used if width is not passed in options
81
+ config: percy.config.snapshot.widths
82
+ },
77
83
  success: true,
78
84
  type: percy.client.tokenType()
79
85
  });
@@ -223,6 +229,16 @@ export function createPercyServer(percy, port) {
223
229
  } else if (cmd === 'version') {
224
230
  // the version command will update the api version header for testing
225
231
  percy.testing.version = body;
232
+ } else if (cmd === 'config') {
233
+ var _body$mobile;
234
+ percy.config.snapshot.widths = body.config;
235
+ percy.deviceDetails = (_body$mobile = body.mobile) === null || _body$mobile === void 0 ? void 0 : _body$mobile.map(w => {
236
+ return {
237
+ width: w
238
+ };
239
+ });
240
+ percy.config.snapshot.responsiveSnapshotCapture = !!body.responsive;
241
+ percy.config.percy.deferUploads = !!body.deferUploads;
226
242
  } else if (cmd === 'error' || cmd === 'disconnect') {
227
243
  var _percy$testing8;
228
244
  // the error or disconnect commands will cause specific endpoints to fail
package/dist/config.js CHANGED
@@ -78,6 +78,10 @@ export const configSchema = {
78
78
  sync: {
79
79
  type: 'boolean'
80
80
  },
81
+ responsiveSnapshotCapture: {
82
+ type: 'boolean',
83
+ default: false
84
+ },
81
85
  testCase: {
82
86
  type: 'string'
83
87
  },
@@ -343,6 +347,9 @@ export const snapshotSchema = {
343
347
  sync: {
344
348
  $ref: '/config/snapshot#/properties/sync'
345
349
  },
350
+ responsiveSnapshotCapture: {
351
+ $ref: '/config/snapshot#/properties/responsiveSnapshotCapture'
352
+ },
346
353
  testCase: {
347
354
  $ref: '/config/snapshot#/properties/testCase'
348
355
  },
@@ -603,8 +610,21 @@ export const snapshotSchema = {
603
610
  }
604
611
  },
605
612
  cookies: {
613
+ oneOf: [{
614
+ type: 'string'
615
+ }, {
616
+ type: 'array',
617
+ items: {
618
+ type: 'string'
619
+ }
620
+ }]
621
+ },
622
+ userAgent: {
606
623
  type: 'string'
607
624
  },
625
+ width: {
626
+ $ref: '/config/snapshot#/properties/widths/items'
627
+ },
608
628
  resources: {
609
629
  type: 'array',
610
630
  items: {
@@ -631,6 +651,11 @@ export const snapshotSchema = {
631
651
  }
632
652
  }
633
653
  }
654
+ }, {
655
+ type: 'array',
656
+ items: {
657
+ $ref: '/snapshot#/$defs/dom/properties/domSnapshot/oneOf/1'
658
+ }
634
659
  }]
635
660
  }
636
661
  },
package/dist/discovery.js CHANGED
@@ -53,6 +53,11 @@ function debugSnapshotOptions(snapshot) {
53
53
  debugProp(snapshot, 'clientInfo');
54
54
  debugProp(snapshot, 'environmentInfo');
55
55
  debugProp(snapshot, 'domSnapshot', Boolean);
56
+ if (Array.isArray(snapshot.domSnapshot)) {
57
+ debugProp(snapshot, 'domSnapshot.0.userAgent');
58
+ } else {
59
+ debugProp(snapshot, 'domSnapshot.userAgent');
60
+ }
56
61
  for (let added of snapshot.additionalSnapshots || []) {
57
62
  log.debug(`Additional snapshot: ${added.name}`, snapshot.meta);
58
63
  debugProp(added, 'waitForTimeout');
@@ -62,9 +67,21 @@ function debugSnapshotOptions(snapshot) {
62
67
  }
63
68
 
64
69
  // parse browser cookies in correct format if flag is enabled
65
- // it assumes that cookiesStr is string returned by document.cookie
66
- function parseCookies(cookiesStr) {
67
- if (process.env.PERCY_DO_NOT_USE_CAPTURED_COOKIES === 'true' || !(typeof cookiesStr === 'string' && cookiesStr !== '')) return null;
70
+ function parseCookies(cookies) {
71
+ if (process.env.PERCY_DO_NOT_USE_CAPTURED_COOKIES === 'true') return null;
72
+
73
+ // If cookies is collected via SDK
74
+ if (Array.isArray(cookies) && cookies.every(item => typeof item === 'object' && 'name' in item && 'value' in item)) {
75
+ // omit other fields reason sometimes expiry comes as actual date where we expect it to be double
76
+ return cookies.map(c => ({
77
+ name: c.name,
78
+ value: c.value,
79
+ secure: c.secure
80
+ }));
81
+ }
82
+ if (!(typeof cookies === 'string' && cookies !== '')) return null;
83
+ // it assumes that cookiesStr is string returned by document.cookie
84
+ const cookiesStr = cookies;
68
85
  return cookiesStr.split('; ').map(c => {
69
86
  const eqIdx = c.indexOf('=');
70
87
  const name = c.substring(0, eqIdx);
@@ -96,18 +113,34 @@ function parseDomResources({
96
113
  url,
97
114
  domSnapshot
98
115
  }) {
99
- if (!domSnapshot) return new Map();
100
- let isHTML = typeof domSnapshot === 'string';
101
- let {
102
- html,
103
- resources = []
104
- } = isHTML ? {
105
- html: domSnapshot
106
- } : domSnapshot;
107
- let rootResource = createRootResource(url, html);
116
+ const map = new Map();
117
+ if (!domSnapshot) return map;
118
+ let allRootResources = new Set();
119
+ let allResources = new Set();
120
+ if (!Array.isArray(domSnapshot)) {
121
+ domSnapshot = [domSnapshot];
122
+ }
123
+ for (let dom of domSnapshot) {
124
+ let isHTML = typeof dom === 'string';
125
+ let {
126
+ html,
127
+ resources = []
128
+ } = isHTML ? {
129
+ html: dom
130
+ } : dom;
131
+ resources.forEach(r => allResources.add(r));
132
+ const attrs = dom.width ? {
133
+ widths: [dom.width]
134
+ } : {};
135
+ let rootResource = createRootResource(url, html, attrs);
136
+ allRootResources.add(rootResource);
137
+ }
138
+ allRootResources = Array.from(allRootResources);
139
+ map.set(allRootResources[0].url, allRootResources);
140
+ allResources = Array.from(allResources);
108
141
 
109
142
  // reduce the array of resources into a keyed map
110
- return resources.reduce((map, {
143
+ return allResources.reduce((map, {
111
144
  url,
112
145
  content,
113
146
  mimetype
@@ -121,7 +154,19 @@ function parseDomResources({
121
154
  // key the resource by its url and return the map
122
155
  return map.set(resource.url, resource);
123
156
  // the initial map is created with at least a root resource
124
- }, new Map([[rootResource.url, rootResource]]));
157
+ }, map);
158
+ }
159
+ function createAndApplyPercyCSS({
160
+ percyCSS,
161
+ roots
162
+ }) {
163
+ let css = createPercyCSSResource(roots[0].url, percyCSS);
164
+
165
+ // replace root contents and associated properties
166
+ roots.forEach(root => {
167
+ Object.assign(root, createRootResource(root.url, root.content.replace(/(<\/body>)(?!.*\1)/is, `<link data-percy-specific-css rel="stylesheet" href="${css.pathname}"/>` + '$&')));
168
+ });
169
+ return css;
125
170
  }
126
171
 
127
172
  // Calls the provided callback with additional resources
@@ -135,17 +180,17 @@ function processSnapshotResources({
135
180
  resources = [...(((_resources = resources) === null || _resources === void 0 ? void 0 : _resources.values()) ?? [])];
136
181
 
137
182
  // find any root resource matching the provided dom snapshot
138
- let rootContent = (domSnapshot === null || domSnapshot === void 0 ? void 0 : domSnapshot.html) ?? domSnapshot;
139
- let root = resources.find(r => r.content === rootContent);
183
+ // since root resources are stored as array
184
+ let roots = resources.find(r => Array.isArray(r));
140
185
 
141
186
  // initialize root resources if needed
142
- if (!root) {
187
+ if (!roots) {
143
188
  let domResources = parseDomResources({
144
189
  ...snapshot,
145
190
  domSnapshot
146
191
  });
147
192
  resources = [...domResources.values(), ...resources];
148
- root = resources[0];
193
+ roots = resources.find(r => Array.isArray(r));
149
194
  }
150
195
 
151
196
  // inject Percy CSS
@@ -155,13 +200,16 @@ function processSnapshotResources({
155
200
  if (domSnapshotHints.includes('DOM elements found outside </body>')) {
156
201
  log.warn('DOM elements found outside </body>, percyCSS might not work');
157
202
  }
158
- let css = createPercyCSSResource(root.url, snapshot.percyCSS);
159
- resources.push(css);
160
-
161
- // replace root contents and associated properties
162
- Object.assign(root, createRootResource(root.url, root.content.replace(/(<\/body>)(?!.*\1)/is, `<link data-percy-specific-css rel="stylesheet" href="${css.pathname}"/>` + '$&')));
203
+ const percyCSSReource = createAndApplyPercyCSS({
204
+ percyCSS: snapshot.percyCSS,
205
+ roots
206
+ });
207
+ resources.push(percyCSSReource);
163
208
  }
164
209
 
210
+ // For multi dom root resources are stored as array
211
+ resources = resources.flat();
212
+
165
213
  // include associated snapshot logs matched by meta information
166
214
  resources.push(createLogResource(logger.query(log => {
167
215
  var _log$meta$snapshot, _log$meta$snapshot2;
@@ -182,7 +230,7 @@ function processSnapshotResources({
182
230
  // Triggers the capture of resource requests for a page by iterating over snapshot widths to resize
183
231
  // the page and calling any provided execute options.
184
232
  async function* captureSnapshotResources(page, snapshot, options) {
185
- var _snapshot$domSnapshot;
233
+ var _snapshot$domSnapshot, _snapshot$domSnapshot2;
186
234
  const log = logger('core:discovery');
187
235
  let {
188
236
  discovery,
@@ -196,7 +244,8 @@ async function* captureSnapshotResources(page, snapshot, options) {
196
244
  mobile,
197
245
  captureForDevices
198
246
  } = options;
199
- let cookies = parseCookies(snapshot === null || snapshot === void 0 || (_snapshot$domSnapshot = snapshot.domSnapshot) === null || _snapshot$domSnapshot === void 0 ? void 0 : _snapshot$domSnapshot.cookies);
247
+ let cookies = (snapshot === null || snapshot === void 0 || (_snapshot$domSnapshot = snapshot.domSnapshot) === null || _snapshot$domSnapshot === void 0 ? void 0 : _snapshot$domSnapshot.cookies) || (snapshot === null || snapshot === void 0 || (_snapshot$domSnapshot2 = snapshot.domSnapshot) === null || _snapshot$domSnapshot2 === void 0 || (_snapshot$domSnapshot2 = _snapshot$domSnapshot2[0]) === null || _snapshot$domSnapshot2 === void 0 ? void 0 : _snapshot$domSnapshot2.cookies);
248
+ cookies = parseCookies(cookies);
200
249
 
201
250
  // iterate over device to trigger reqeusts and capture other dpr width
202
251
  async function* captureResponsiveAssets() {
@@ -234,17 +283,21 @@ async function* captureSnapshotResources(page, snapshot, options) {
234
283
  ;
235
284
 
236
285
  // used to resize the using capture options
237
- let resizePage = width => page.resize({
238
- height: snapshot.minHeight,
239
- deviceScaleFactor,
240
- mobile,
241
- width
242
- });
286
+ let resizePage = width => {
287
+ page.network.intercept.currentWidth = width;
288
+ return page.resize({
289
+ height: snapshot.minHeight,
290
+ deviceScaleFactor,
291
+ mobile,
292
+ width
293
+ });
294
+ };
243
295
 
244
296
  // navigate to the url
245
297
  yield resizePage(snapshot.widths[0]);
246
298
  yield page.goto(snapshot.url, {
247
- cookies
299
+ cookies,
300
+ forceReload: discovery.captureResponsiveAssetsEnabled
248
301
  });
249
302
 
250
303
  // wait for any specified timeout
@@ -268,7 +321,8 @@ async function* captureSnapshotResources(page, snapshot, options) {
268
321
  // Running before page idle since this will trigger many network calls
269
322
  // so need to run as early as possible. plus it is just reading urls from dom srcset
270
323
  // which will be already loaded after navigation complete
271
- if (discovery.captureSrcset) {
324
+ // Don't run incase of responsiveSnapshotCapture since we are running discovery for all widths so images will get captured in all required widths
325
+ if (!snapshot.responsiveSnapshotCapture && discovery.captureSrcset) {
272
326
  await page.insertPercyDom();
273
327
  yield page.eval('window.PercyDOM.loadAllSrcsetLinks()');
274
328
  }
@@ -293,6 +347,12 @@ async function* captureSnapshotResources(page, snapshot, options) {
293
347
  yield page.evaluate(execute === null || execute === void 0 ? void 0 : execute.beforeResize);
294
348
  yield waitForDiscoveryNetworkIdle(page, discovery);
295
349
  yield resizePage(width = widths[i + 1]);
350
+ if (snapshot.responsiveSnapshotCapture) {
351
+ yield page.goto(snapshot.url, {
352
+ cookies,
353
+ forceReload: true
354
+ });
355
+ }
296
356
  yield page.evaluate(execute === null || execute === void 0 ? void 0 : execute.afterResize);
297
357
  }
298
358
  }
@@ -419,12 +479,20 @@ export function createDiscoveryQueue(percy) {
419
479
  disableCache: snapshot.discovery.disableCache,
420
480
  allowedHostnames: snapshot.discovery.allowedHostnames,
421
481
  disallowedHostnames: snapshot.discovery.disallowedHostnames,
422
- getResource: u => snapshot.resources.get(u) || cache.get(u),
482
+ getResource: (u, width = null) => {
483
+ let resource = snapshot.resources.get(u) || cache.get(u);
484
+ if (resource && Array.isArray(resource) && resource[0].root) {
485
+ const rootResource = resource.find(r => {
486
+ var _r$widths;
487
+ return (_r$widths = r.widths) === null || _r$widths === void 0 ? void 0 : _r$widths.includes(width);
488
+ });
489
+ resource = rootResource || resource[0];
490
+ }
491
+ return resource;
492
+ },
423
493
  saveResource: r => {
424
494
  snapshot.resources.set(r.url, r);
425
- if (!r.root) {
426
- cache.set(r.url, r);
427
- }
495
+ cache.set(r.url, r);
428
496
  }
429
497
  }
430
498
  });
package/dist/network.js CHANGED
@@ -384,7 +384,7 @@ async function sendResponseResource(network, request, session) {
384
384
  };
385
385
  let send = (method, params) => network.send(session, method, params);
386
386
  try {
387
- let resource = network.intercept.getResource(url);
387
+ let resource = network.intercept.getResource(url, network.intercept.currentWidth);
388
388
  network.log.debug(`Handling request: ${url}`, meta);
389
389
  if (!(resource !== null && resource !== void 0 && resource.root) && hostnameMatches(disallowedHostnames, url)) {
390
390
  log.debug('- Skipping disallowed hostname', meta);
@@ -532,7 +532,7 @@ async function saveResponseResource(network, request) {
532
532
  log.debug(error, meta);
533
533
  }
534
534
  }
535
- if (resource) {
535
+ if (resource && !resource.root) {
536
536
  network.intercept.saveResource(resource);
537
537
  }
538
538
  }
package/dist/page.js CHANGED
@@ -61,13 +61,21 @@ export class Page {
61
61
  // Go to a URL and wait for navigation to occur
62
62
  async goto(url, {
63
63
  waitUntil = 'load',
64
- cookies
64
+ cookies,
65
+ forceReload,
66
+ skipCookies = false
65
67
  } = {}) {
66
68
  this.log.debug(`Navigate to: ${url}`, this.meta);
69
+ if (forceReload) {
70
+ this.log.debug('Navigating to blank page', this.meta);
71
+ await this.goto('about:blank', {
72
+ skipCookies: true
73
+ });
74
+ }
67
75
  let navigate = async () => {
68
76
  const userPassedCookie = this.session.browser.cookies;
69
77
  // set cookies before navigation so we can default the domain to this hostname
70
- if (userPassedCookie.length || cookies) {
78
+ if (!skipCookies && (userPassedCookie.length || cookies)) {
71
79
  let defaultDomain = hostname(url);
72
80
  cookies = this.mergeCookies(userPassedCookie, cookies);
73
81
  await this.session.send('Network.setCookies', {
package/dist/utils.js CHANGED
@@ -112,8 +112,9 @@ export function createResource(url, content, mimetype, attrs) {
112
112
 
113
113
  // Creates a root resource object with an additional `root: true` property. The URL is normalized
114
114
  // here as a convenience since root resources are usually created outside of asset discovery.
115
- export function createRootResource(url, content) {
115
+ export function createRootResource(url, content, attrs = {}) {
116
116
  return createResource(normalizeURL(url), content, 'text/html', {
117
+ ...attrs,
117
118
  root: true
118
119
  });
119
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.29.4",
3
+ "version": "1.29.5-beta.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "publishConfig": {
11
11
  "access": "public",
12
- "tag": "latest"
12
+ "tag": "beta"
13
13
  },
14
14
  "engines": {
15
15
  "node": ">=14"
@@ -43,11 +43,11 @@
43
43
  "test:types": "tsd"
44
44
  },
45
45
  "dependencies": {
46
- "@percy/client": "1.29.4",
47
- "@percy/config": "1.29.4",
48
- "@percy/dom": "1.29.4",
49
- "@percy/logger": "1.29.4",
50
- "@percy/webdriver-utils": "1.29.4",
46
+ "@percy/client": "1.29.5-beta.0",
47
+ "@percy/config": "1.29.5-beta.0",
48
+ "@percy/dom": "1.29.5-beta.0",
49
+ "@percy/logger": "1.29.5-beta.0",
50
+ "@percy/webdriver-utils": "1.29.5-beta.0",
51
51
  "content-disposition": "^0.5.4",
52
52
  "cross-spawn": "^7.0.3",
53
53
  "extract-zip": "^2.0.1",
@@ -60,5 +60,5 @@
60
60
  "ws": "^8.17.1",
61
61
  "yaml": "^2.4.1"
62
62
  },
63
- "gitHead": "17468058cf55d75557451a81e82b0d6fd5c0e26b"
63
+ "gitHead": "1edb0e95570ca077e8ef931b53399c04d71a95bd"
64
64
  }