@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 +16 -0
- package/dist/config.js +25 -0
- package/dist/discovery.js +105 -37
- package/dist/network.js +2 -2
- package/dist/page.js +10 -2
- package/dist/utils.js +2 -1
- package/package.json +8 -8
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
let
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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
|
-
},
|
|
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
|
-
|
|
139
|
-
let
|
|
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 (!
|
|
187
|
+
if (!roots) {
|
|
143
188
|
let domResources = parseDomResources({
|
|
144
189
|
...snapshot,
|
|
145
190
|
domSnapshot
|
|
146
191
|
});
|
|
147
192
|
resources = [...domResources.values(), ...resources];
|
|
148
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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 =
|
|
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 =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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.
|
|
47
|
-
"@percy/config": "1.29.
|
|
48
|
-
"@percy/dom": "1.29.
|
|
49
|
-
"@percy/logger": "1.29.
|
|
50
|
-
"@percy/webdriver-utils": "1.29.
|
|
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": "
|
|
63
|
+
"gitHead": "1edb0e95570ca077e8ef931b53399c04d71a95bd"
|
|
64
64
|
}
|