@percy/core 1.26.3-beta.1 → 1.26.3-beta.2

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 CHANGED
@@ -5,6 +5,7 @@ import { normalizeURL, hostnameMatches, createResource, waitFor } from './utils.
5
5
  const MAX_RESOURCE_SIZE = 25 * 1024 ** 2; // 25MB
6
6
  const ALLOWED_STATUSES = [200, 201, 301, 302, 304, 307, 308];
7
7
  const ALLOWED_RESOURCES = ['Document', 'Stylesheet', 'Image', 'Media', 'Font', 'Other'];
8
+ const ABORTED_MESSAGE = 'Request was aborted by browser';
8
9
 
9
10
  // The Interceptor class creates common handlers for dealing with intercepting asset requests
10
11
  // for a given page using various devtools protocol events and commands.
@@ -15,6 +16,7 @@ export class Network {
15
16
  #requests = new Map();
16
17
  #intercepts = new Map();
17
18
  #authentications = new Set();
19
+ #aborted = new Set();
18
20
  constructor(page, options) {
19
21
  this.page = page;
20
22
  this.timeout = options.networkIdleTimeout ?? 100;
@@ -77,6 +79,23 @@ export class Network {
77
79
  });
78
80
  }
79
81
 
82
+ // Validates that requestId is still valid as sometimes request gets cancelled and we have already executed
83
+ // _forgetRequest for the same, but we still attempt to make a call for it and it fails
84
+ // with Protocol error (Fetch.failRequest): Invalid InterceptionId.
85
+ async send(session, method, params) {
86
+ /* istanbul ignore else: currently all send have requestId */
87
+ if (params.requestId) {
88
+ /* istanbul ignore if: race condition, very hard to mock this */
89
+ if (this.isAborted(params.requestId)) {
90
+ throw new Error(ABORTED_MESSAGE);
91
+ }
92
+ }
93
+ return await session.send(method, params);
94
+ }
95
+ isAborted(requestId) {
96
+ return this.#aborted.has(requestId);
97
+ }
98
+
80
99
  // Throw a better network timeout error
81
100
  _throwTimeoutError(msg, filter = () => true) {
82
101
  if (this.log.shouldLog('debug')) {
@@ -116,7 +135,7 @@ export class Network {
116
135
  response = 'ProvideCredentials';
117
136
  this.#authentications.add(requestId);
118
137
  }
119
- await session.send('Fetch.continueWithAuth', {
138
+ await this.send(session, 'Fetch.continueWithAuth', {
120
139
  requestId: event.requestId,
121
140
  authChallengeResponse: {
122
141
  response,
@@ -222,7 +241,7 @@ export class Network {
222
241
  if (!request) return;
223
242
  request.response = response;
224
243
  request.response.buffer = async () => {
225
- let result = await session.send('Network.getResponseBody', {
244
+ let result = await this.send(session, 'Network.getResponseBody', {
226
245
  requestId
227
246
  });
228
247
  return Buffer.from(result.body, result.base64Encoded ? 'base64' : 'utf-8');
@@ -253,8 +272,18 @@ export class Network {
253
272
  /* istanbul ignore if: race condition paranioa */
254
273
  if (!request) return;
255
274
 
256
- // do not log generic messages since the real error was likely logged elsewhere
257
- if (event.errorText !== 'net::ERR_FAILED') {
275
+ // If request was aborted, keep track of it as we need to cancel any in process callbacks for
276
+ // such a request to avoid Invalid InterceptionId errors
277
+ // Note: 404s also show up under ERR_ABORTED and not ERR_FAILED
278
+ if (event.errorText === 'net::ERR_ABORTED') {
279
+ let message = `Request aborted for ${request.url}: ${event.errorText}`;
280
+ this.log.debug(message, {
281
+ ...this.meta,
282
+ url: request.url
283
+ });
284
+ this.#aborted.add(request.requestId);
285
+ } else if (event.errorText !== 'net::ERR_FAILED') {
286
+ // do not log generic messages since the real error was likely logged elsewhere
258
287
  let message = `Request failed for ${request.url}: ${event.errorText}`;
259
288
  this.log.debug(message, {
260
289
  ...this.meta,
@@ -289,18 +318,19 @@ async function sendResponseResource(network, request, session) {
289
318
  ...network.meta,
290
319
  url
291
320
  };
321
+ let send = (method, params) => network.send(session, method, params);
292
322
  try {
293
323
  let resource = network.intercept.getResource(url);
294
324
  network.log.debug(`Handling request: ${url}`, meta);
295
325
  if (!(resource !== null && resource !== void 0 && resource.root) && hostnameMatches(disallowedHostnames, url)) {
296
326
  log.debug('- Skipping disallowed hostname', meta);
297
- await session.send('Fetch.failRequest', {
327
+ await send('Fetch.failRequest', {
298
328
  requestId: request.interceptId,
299
329
  errorReason: 'Aborted'
300
330
  });
301
331
  } else if (resource && (resource.root || resource.provided || !disableCache)) {
302
332
  log.debug(resource.root ? '- Serving root resource' : '- Resource cache hit', meta);
303
- await session.send('Fetch.fulfillRequest', {
333
+ await send('Fetch.fulfillRequest', {
304
334
  requestId: request.interceptId,
305
335
  responseCode: resource.status || 200,
306
336
  body: Buffer.from(resource.content).toString('base64'),
@@ -310,18 +340,35 @@ async function sendResponseResource(network, request, session) {
310
340
  }))
311
341
  });
312
342
  } else {
313
- await session.send('Fetch.continueRequest', {
343
+ await send('Fetch.continueRequest', {
314
344
  requestId: request.interceptId
315
345
  });
316
346
  }
317
347
  } catch (error) {
318
348
  /* istanbul ignore next: too hard to test (create race condition) */
319
349
  if (session.closing && error.message.includes('close')) return;
350
+
351
+ // if failure is due to an already aborted request, ignore it
352
+ // due to race condition we might get aborted event later and see a `Invalid InterceptionId`
353
+ // error before, in which case we should wait for a tick and check again
354
+ // Note: its not a necessity that we would get aborted callback in a tick, its just that if we
355
+ // already have it then we can safely ignore this error
356
+ // Its very hard to test it as this function should be called and request should get cancelled before
357
+ if (error.message === ABORTED_MESSAGE || error.message.includes('Invalid InterceptionId')) {
358
+ // defer this to the end of queue to make sure that any incoming aborted messages were
359
+ // handled and network.#aborted is updated
360
+ await new Promise((res, _) => process.nextTick(res));
361
+ /* istanbul ignore else: too hard to create race where abortion event is delayed */
362
+ if (network.isAborted(request.requestId)) {
363
+ log.debug(`Ignoring further steps for ${url} as request was aborted by the browser.`);
364
+ return;
365
+ }
366
+ }
320
367
  log.debug(`Encountered an error handling request: ${url}`, meta);
321
368
  log.debug(error);
322
369
 
323
370
  /* istanbul ignore next: catch race condition */
324
- await session.send('Fetch.failRequest', {
371
+ await send('Fetch.failRequest', {
325
372
  requestId: request.interceptId,
326
373
  errorReason: 'Failed'
327
374
  }).catch(e => log.debug(e, meta));
package/dist/snapshot.js CHANGED
@@ -78,7 +78,7 @@ function mapSnapshotOptions(snapshots, context) {
78
78
  // assign additional options to included snaphots
79
79
  snapshotMatches(snap, include, exclude) ? Object.assign(snap, opts) : snap), snap => getSnapshotOptions(snap, context));
80
80
 
81
- // reduce snapshots with overrides
81
+ // reduce snapshots with options
82
82
  return snapshots.reduce((acc, snapshot) => {
83
83
  var _snapshot;
84
84
  // transform snapshot URL shorthand into an object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.26.3-beta.1",
3
+ "version": "1.26.3-beta.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,10 +43,10 @@
43
43
  "test:types": "tsd"
44
44
  },
45
45
  "dependencies": {
46
- "@percy/client": "1.26.3-beta.1",
47
- "@percy/config": "1.26.3-beta.1",
48
- "@percy/dom": "1.26.3-beta.1",
49
- "@percy/logger": "1.26.3-beta.1",
46
+ "@percy/client": "1.26.3-beta.2",
47
+ "@percy/config": "1.26.3-beta.2",
48
+ "@percy/dom": "1.26.3-beta.2",
49
+ "@percy/logger": "1.26.3-beta.2",
50
50
  "content-disposition": "^0.5.4",
51
51
  "cross-spawn": "^7.0.3",
52
52
  "extract-zip": "^2.0.1",
@@ -57,5 +57,5 @@
57
57
  "rimraf": "^3.0.2",
58
58
  "ws": "^8.0.0"
59
59
  },
60
- "gitHead": "6571d4ad016bc8b3b32d49bf8a7da1393c1a5eba"
60
+ "gitHead": "5e402f5ff469cc521506fef6a347e04a1a2e1434"
61
61
  }