@percy/core 1.31.7 → 1.31.8
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/network.js +108 -0
- package/package.json +8 -8
- package/types/index.d.ts +57 -0
package/dist/network.js
CHANGED
|
@@ -348,6 +348,43 @@ export class Network {
|
|
|
348
348
|
/* istanbul ignore if: race condition paranoia */
|
|
349
349
|
if (!request) return;
|
|
350
350
|
|
|
351
|
+
// Log instrumentation for failed requests
|
|
352
|
+
let response = request.response;
|
|
353
|
+
let category, reason, details;
|
|
354
|
+
|
|
355
|
+
/* istanbul ignore next: rare edge case where loadingFailed is called with a response */
|
|
356
|
+
if (response) {
|
|
357
|
+
// Request has a response but failed (e.g., 404, 5xx)
|
|
358
|
+
if (response.status >= 500 && response.status < 600) {
|
|
359
|
+
category = 'asset_load_5xx';
|
|
360
|
+
reason = 'server_error';
|
|
361
|
+
} else if (!ALLOWED_STATUSES.includes(response.status)) {
|
|
362
|
+
category = 'asset_not_uploaded';
|
|
363
|
+
reason = 'disallowed_status';
|
|
364
|
+
}
|
|
365
|
+
if (category) {
|
|
366
|
+
details = {
|
|
367
|
+
url: request.url,
|
|
368
|
+
statusCode: response.status,
|
|
369
|
+
snapshot: this.meta.snapshot,
|
|
370
|
+
requestType: request.type
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
// Failed request without a response (network error)
|
|
374
|
+
} else if (event.errorText && event.errorText !== 'net::ERR_FAILED') {
|
|
375
|
+
category = 'asset_load_missing';
|
|
376
|
+
reason = 'network_error';
|
|
377
|
+
details = {
|
|
378
|
+
url: request.url,
|
|
379
|
+
errorText: event.errorText,
|
|
380
|
+
snapshot: this.meta.snapshot,
|
|
381
|
+
requestType: request.type
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (category) {
|
|
385
|
+
logAssetInstrumentation(this.log, category, reason, details);
|
|
386
|
+
}
|
|
387
|
+
|
|
351
388
|
// If request was aborted, keep track of it as we need to cancel any in process callbacks for
|
|
352
389
|
// such a request to avoid Invalid InterceptionId errors
|
|
353
390
|
// Note: 404s also show up under ERR_ABORTED and not ERR_FAILED
|
|
@@ -377,6 +414,33 @@ export class Network {
|
|
|
377
414
|
}
|
|
378
415
|
}
|
|
379
416
|
|
|
417
|
+
// Logs asset instrumentation for failed/skipped asset loading
|
|
418
|
+
function logAssetInstrumentation(log, category, reason, details) {
|
|
419
|
+
const categoryMap = {
|
|
420
|
+
asset_load_5xx: '[ASSET_LOAD_5XX]',
|
|
421
|
+
asset_not_uploaded: '[ASSET_NOT_UPLOADED]',
|
|
422
|
+
asset_load_missing: '[ASSET_LOAD_MISSING]'
|
|
423
|
+
};
|
|
424
|
+
const messageMap = {
|
|
425
|
+
server_error: 'Server error response',
|
|
426
|
+
disallowed_status: 'Disallowed status code',
|
|
427
|
+
network_error: 'Network error',
|
|
428
|
+
disallowed_hostname: 'Disallowed hostname',
|
|
429
|
+
resource_too_large: 'Resource too large',
|
|
430
|
+
no_response: 'No response received',
|
|
431
|
+
empty_response: 'Empty response',
|
|
432
|
+
disallowed_resource_type: 'Disallowed resource type'
|
|
433
|
+
};
|
|
434
|
+
const prefix = categoryMap[category];
|
|
435
|
+
/* istanbul ignore next: fallback for unknown reason */
|
|
436
|
+
const message = messageMap[reason] || reason;
|
|
437
|
+
log.debug(`${prefix} ${message}`, {
|
|
438
|
+
...details,
|
|
439
|
+
reason,
|
|
440
|
+
instrumentationCategory: category
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
380
444
|
// Returns the normalized origin URL of a request
|
|
381
445
|
function originURL(request) {
|
|
382
446
|
return normalizeURL((request.redirectChain[0] || request).url);
|
|
@@ -399,6 +463,11 @@ async function sendResponseResource(network, request, session) {
|
|
|
399
463
|
let resource = network.intercept.getResource(url, network.intercept.currentWidth);
|
|
400
464
|
network.log.debug(`Handling request: ${url}`, meta);
|
|
401
465
|
if (!(resource !== null && resource !== void 0 && resource.root) && hostnameMatches(disallowedHostnames, url)) {
|
|
466
|
+
logAssetInstrumentation(log, 'asset_not_uploaded', 'disallowed_hostname', {
|
|
467
|
+
url,
|
|
468
|
+
hostname: new URL(url).hostname,
|
|
469
|
+
snapshot: meta.snapshot
|
|
470
|
+
});
|
|
402
471
|
log.debug('- Skipping disallowed hostname', meta);
|
|
403
472
|
await send('Fetch.failRequest', {
|
|
404
473
|
requestId: request.interceptId,
|
|
@@ -512,6 +581,11 @@ async function saveResponseResource(network, request, session) {
|
|
|
512
581
|
let contentLength = (_response$headers = response.headers) === null || _response$headers === void 0 ? void 0 : _response$headers[Object.keys(response.headers).find(key => key.toLowerCase() === 'content-length')];
|
|
513
582
|
contentLength = parseInt(contentLength);
|
|
514
583
|
if (contentLength > MAX_RESOURCE_SIZE) {
|
|
584
|
+
logAssetInstrumentation(log, 'asset_not_uploaded', 'resource_too_large', {
|
|
585
|
+
url,
|
|
586
|
+
size: contentLength,
|
|
587
|
+
snapshot: meta.snapshot
|
|
588
|
+
});
|
|
515
589
|
return log.debug('- Skipping resource larger than 25MB', meta);
|
|
516
590
|
}
|
|
517
591
|
let resource = network.intercept.getResource(url);
|
|
@@ -526,17 +600,51 @@ async function saveResponseResource(network, request, session) {
|
|
|
526
600
|
// Don't rename the below log line as it is used in getting network logs in api
|
|
527
601
|
/* istanbul ignore if: first check is a sanity check */
|
|
528
602
|
if (!response) {
|
|
603
|
+
logAssetInstrumentation(log, 'asset_load_missing', 'no_response', {
|
|
604
|
+
url,
|
|
605
|
+
snapshot: meta.snapshot,
|
|
606
|
+
requestType: request.type
|
|
607
|
+
});
|
|
529
608
|
return log.debug('- Skipping no response', meta);
|
|
530
609
|
} else if (!shouldCapture) {
|
|
610
|
+
logAssetInstrumentation(log, 'asset_not_uploaded', 'disallowed_hostname', {
|
|
611
|
+
url,
|
|
612
|
+
hostname: new URL(url).hostname,
|
|
613
|
+
snapshot: meta.snapshot
|
|
614
|
+
});
|
|
531
615
|
return log.debug('- Skipping remote resource', meta);
|
|
532
616
|
} else if (!body.length) {
|
|
617
|
+
logAssetInstrumentation(log, 'asset_not_uploaded', 'empty_response', {
|
|
618
|
+
url,
|
|
619
|
+
snapshot: meta.snapshot
|
|
620
|
+
});
|
|
533
621
|
return log.debug('- Skipping empty response', meta);
|
|
534
622
|
} else if (body.length > MAX_RESOURCE_SIZE) {
|
|
623
|
+
logAssetInstrumentation(log, 'asset_not_uploaded', 'resource_too_large', {
|
|
624
|
+
url,
|
|
625
|
+
size: body.length,
|
|
626
|
+
snapshot: meta.snapshot
|
|
627
|
+
});
|
|
535
628
|
log.debug('- Missing headers for the requested resource.', meta);
|
|
536
629
|
return log.debug('- Skipping resource larger than 25MB', meta);
|
|
537
630
|
} else if (!ALLOWED_STATUSES.includes(response.status)) {
|
|
631
|
+
/* istanbul ignore next: ternary branches tested separately */
|
|
632
|
+
const category = response.status >= 500 && response.status < 600 ? 'asset_load_5xx' : 'asset_not_uploaded';
|
|
633
|
+
/* istanbul ignore next: ternary branches tested separately */
|
|
634
|
+
const reason = response.status >= 500 && response.status < 600 ? 'server_error' : 'disallowed_status';
|
|
635
|
+
logAssetInstrumentation(log, category, reason, {
|
|
636
|
+
url,
|
|
637
|
+
statusCode: response.status,
|
|
638
|
+
snapshot: meta.snapshot,
|
|
639
|
+
requestType: request.type
|
|
640
|
+
});
|
|
538
641
|
return log.debug(`- Skipping disallowed status [${response.status}]`, meta);
|
|
539
642
|
} else if (!enableJavaScript && !ALLOWED_RESOURCES.includes(request.type)) {
|
|
643
|
+
logAssetInstrumentation(log, 'asset_not_uploaded', 'disallowed_resource_type', {
|
|
644
|
+
url,
|
|
645
|
+
resourceType: request.type,
|
|
646
|
+
snapshot: meta.snapshot
|
|
647
|
+
});
|
|
540
648
|
return log.debug(`- Skipping disallowed resource type [${request.type}]`, meta);
|
|
541
649
|
}
|
|
542
650
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/core",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"test:types": "tsd"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@percy/client": "1.31.
|
|
47
|
-
"@percy/config": "1.31.
|
|
48
|
-
"@percy/dom": "1.31.
|
|
49
|
-
"@percy/logger": "1.31.
|
|
50
|
-
"@percy/monitoring": "1.31.
|
|
51
|
-
"@percy/webdriver-utils": "1.31.
|
|
46
|
+
"@percy/client": "1.31.8",
|
|
47
|
+
"@percy/config": "1.31.8",
|
|
48
|
+
"@percy/dom": "1.31.8",
|
|
49
|
+
"@percy/logger": "1.31.8",
|
|
50
|
+
"@percy/monitoring": "1.31.8",
|
|
51
|
+
"@percy/webdriver-utils": "1.31.8",
|
|
52
52
|
"content-disposition": "^0.5.4",
|
|
53
53
|
"cross-spawn": "^7.0.3",
|
|
54
54
|
"extract-zip": "^2.0.1",
|
|
@@ -61,5 +61,5 @@
|
|
|
61
61
|
"ws": "^8.17.1",
|
|
62
62
|
"yaml": "^2.4.1"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "f5c77f4e541aca84042d7249105428c55303e809"
|
|
65
65
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -59,9 +59,66 @@ interface CommonSnapshotOptions {
|
|
|
59
59
|
scopeOptions?: ScopeOptions;
|
|
60
60
|
browsers?: string[];
|
|
61
61
|
}
|
|
62
|
+
// Region support for TypeScript
|
|
63
|
+
interface BoundingBox {
|
|
64
|
+
x: number;
|
|
65
|
+
y: number;
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface Padding {
|
|
71
|
+
top?: number;
|
|
72
|
+
left?: number;
|
|
73
|
+
right?: number;
|
|
74
|
+
bottom?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface ElementSelector {
|
|
78
|
+
boundingBox?: BoundingBox;
|
|
79
|
+
elementXpath?: string;
|
|
80
|
+
elementCSS?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface RegionConfiguration {
|
|
84
|
+
diffSensitivity?: number;
|
|
85
|
+
imageIgnoreThreshold?: number;
|
|
86
|
+
carouselsEnabled?: boolean;
|
|
87
|
+
bannersEnabled?: boolean;
|
|
88
|
+
adsEnabled?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface RegionAssertion {
|
|
92
|
+
diffIgnoreThreshold?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface Region {
|
|
96
|
+
algorithm: string;
|
|
97
|
+
elementSelector: ElementSelector;
|
|
98
|
+
padding?: Padding;
|
|
99
|
+
configuration?: RegionConfiguration;
|
|
100
|
+
assertion?: RegionAssertion;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface CreateRegionOptions {
|
|
104
|
+
boundingBox?: BoundingBox;
|
|
105
|
+
elementXpath?: string;
|
|
106
|
+
elementCSS?: string;
|
|
107
|
+
padding?: Padding;
|
|
108
|
+
algorithm?: string;
|
|
109
|
+
diffSensitivity?: number;
|
|
110
|
+
imageIgnoreThreshold?: number;
|
|
111
|
+
carouselsEnabled?: boolean;
|
|
112
|
+
bannersEnabled?: boolean;
|
|
113
|
+
adsEnabled?: boolean;
|
|
114
|
+
diffIgnoreThreshold?: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function createRegion(options: CreateRegionOptions): Region;
|
|
62
118
|
|
|
63
119
|
export interface SnapshotOptions extends CommonSnapshotOptions {
|
|
64
120
|
discovery?: DiscoveryOptions;
|
|
121
|
+
regions?: Region[];
|
|
65
122
|
}
|
|
66
123
|
|
|
67
124
|
type ClientEnvInfo = {
|