@percy/core 1.28.2-beta.0 → 1.28.2-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/config.js CHANGED
@@ -255,6 +255,10 @@ export const configSchema = {
255
255
  type: 'integer',
256
256
  minimum: 1
257
257
  },
258
+ retry: {
259
+ type: 'boolean',
260
+ default: false
261
+ },
258
262
  launchOptions: {
259
263
  type: 'object',
260
264
  additionalProperties: false,
@@ -357,6 +361,9 @@ export const snapshotSchema = {
357
361
  },
358
362
  devicePixelRatio: {
359
363
  $ref: '/config/discovery#/properties/devicePixelRatio'
364
+ },
365
+ retry: {
366
+ $ref: '/config/discovery#/properties/retry'
360
367
  }
361
368
  }
362
369
  }
package/dist/discovery.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import logger from '@percy/logger';
2
2
  import Queue from './queue.js';
3
- import { normalizeURL, hostnameMatches, createResource, createRootResource, createPercyCSSResource, createLogResource, yieldAll, snapshotLogName } from './utils.js';
3
+ import { normalizeURL, hostnameMatches, createResource, createRootResource, createPercyCSSResource, createLogResource, yieldAll, snapshotLogName, withRetries } from './utils.js';
4
4
 
5
5
  // Logs verbose debug logs detailing various snapshot options.
6
6
  function debugSnapshotOptions(snapshot) {
@@ -152,6 +152,7 @@ function processSnapshotResources({
152
152
  // Triggers the capture of resource requests for a page by iterating over snapshot widths to resize
153
153
  // the page and calling any provided execute options.
154
154
  async function* captureSnapshotResources(page, snapshot, options) {
155
+ const log = logger('core:discovery');
155
156
  let {
156
157
  discovery,
157
158
  additionalSnapshots = [],
@@ -161,7 +162,8 @@ async function* captureSnapshotResources(page, snapshot, options) {
161
162
  capture,
162
163
  captureWidths,
163
164
  deviceScaleFactor,
164
- mobile
165
+ mobile,
166
+ captureForDevices
165
167
  } = options;
166
168
 
167
169
  // used to take snapshots and remove any discovered root resource
@@ -215,6 +217,21 @@ async function* captureSnapshotResources(page, snapshot, options) {
215
217
  } = snap;
216
218
  let [width] = widths;
217
219
 
220
+ // iterate over device to trigger reqeusts and capture other dpr width
221
+ if (captureForDevices) {
222
+ for (const device of captureForDevices) {
223
+ yield waitForDiscoveryNetworkIdle(page, discovery);
224
+ // We are not adding these widths and pixels ratios in loop below because we want to explicitly reload the page after resize which we dont do below
225
+ yield* captureSnapshotResources(page, {
226
+ ...snapshot,
227
+ widths: [device.width]
228
+ }, {
229
+ deviceScaleFactor: device.deviceScaleFactor,
230
+ mobile: true
231
+ });
232
+ }
233
+ }
234
+
218
235
  // iterate over widths to trigger reqeusts and capture other widths
219
236
  if (isBaseSnapshot || captureWidths) {
220
237
  for (let i = 0; i < widths.length - 1; i++) {
@@ -239,12 +256,8 @@ async function* captureSnapshotResources(page, snapshot, options) {
239
256
  }
240
257
 
241
258
  // recursively trigger resource requests for any alternate device pixel ratio
242
- if (deviceScaleFactor !== discovery.devicePixelRatio) {
243
- yield waitForDiscoveryNetworkIdle(page, discovery);
244
- yield* captureSnapshotResources(page, snapshot, {
245
- deviceScaleFactor: discovery.devicePixelRatio,
246
- mobile: true
247
- });
259
+ if (discovery.devicePixelRatio) {
260
+ log.deprecated('discovery.devicePixelRatio is deprecated percy will now auto capture resource in all devicePixelRatio, Ignoring configuration');
248
261
  }
249
262
 
250
263
  // wait for final network idle when not capturing DOM
@@ -327,39 +340,49 @@ export function createDiscoveryQueue(percy) {
327
340
  /* istanbul ignore next: tested, but coverage is stripped */
328
341
  let assetDiscoveryPageEnableJS = snapshot.cliEnableJavaScript && !snapshot.domSnapshot || (snapshot.enableJavaScript ?? !snapshot.domSnapshot);
329
342
  percy.log.debug(`Asset discovery Browser Page enable JS: ${assetDiscoveryPageEnableJS}`);
330
- // create a new browser page
331
- let page = yield percy.browser.page({
332
- enableJavaScript: assetDiscoveryPageEnableJS,
333
- networkIdleTimeout: snapshot.discovery.networkIdleTimeout,
334
- requestHeaders: snapshot.discovery.requestHeaders,
335
- authorization: snapshot.discovery.authorization,
336
- userAgent: snapshot.discovery.userAgent,
337
- captureMockedServiceWorker: snapshot.discovery.captureMockedServiceWorker,
338
- meta: snapshot.meta,
339
- // enable network inteception
340
- intercept: {
341
- enableJavaScript: snapshot.enableJavaScript,
342
- disableCache: snapshot.discovery.disableCache,
343
- allowedHostnames: snapshot.discovery.allowedHostnames,
344
- disallowedHostnames: snapshot.discovery.disallowedHostnames,
345
- getResource: u => snapshot.resources.get(u) || cache.get(u),
346
- saveResource: r => {
347
- snapshot.resources.set(r.url, r);
348
- if (!r.root) {
349
- cache.set(r.url, r);
343
+ await withRetries(async function* () {
344
+ // create a new browser page
345
+ let page = yield percy.browser.page({
346
+ enableJavaScript: assetDiscoveryPageEnableJS,
347
+ networkIdleTimeout: snapshot.discovery.networkIdleTimeout,
348
+ requestHeaders: snapshot.discovery.requestHeaders,
349
+ authorization: snapshot.discovery.authorization,
350
+ userAgent: snapshot.discovery.userAgent,
351
+ captureMockedServiceWorker: snapshot.discovery.captureMockedServiceWorker,
352
+ meta: snapshot.meta,
353
+ // enable network inteception
354
+ intercept: {
355
+ enableJavaScript: snapshot.enableJavaScript,
356
+ disableCache: snapshot.discovery.disableCache,
357
+ allowedHostnames: snapshot.discovery.allowedHostnames,
358
+ disallowedHostnames: snapshot.discovery.disallowedHostnames,
359
+ getResource: u => snapshot.resources.get(u) || cache.get(u),
360
+ saveResource: r => {
361
+ snapshot.resources.set(r.url, r);
362
+ if (!r.root) {
363
+ cache.set(r.url, r);
364
+ }
350
365
  }
351
366
  }
367
+ });
368
+ try {
369
+ yield* captureSnapshotResources(page, snapshot, {
370
+ captureWidths: !snapshot.domSnapshot && percy.deferUploads,
371
+ capture: callback,
372
+ captureForDevices: percy.deviceDetails || []
373
+ });
374
+ } finally {
375
+ // always close the page when done
376
+ await page.close();
352
377
  }
378
+ }, {
379
+ count: snapshot.discovery.retry ? 3 : 1,
380
+ onRetry: () => {
381
+ percy.log.info(`Retrying snapshot: ${snapshotLogName(snapshot.name, snapshot.meta)}`, snapshot.meta);
382
+ },
383
+ signal: snapshot._ctrl.signal,
384
+ throwOn: ['AbortError']
353
385
  });
354
- try {
355
- yield* captureSnapshotResources(page, snapshot, {
356
- captureWidths: !snapshot.domSnapshot && percy.deferUploads,
357
- capture: callback
358
- });
359
- } finally {
360
- // always close the page when done
361
- await page.close();
362
- }
363
386
  }).handle('error', ({
364
387
  name,
365
388
  meta
package/dist/network.js CHANGED
@@ -28,6 +28,7 @@ export class Network {
28
28
  #requests = new Map();
29
29
  #authentications = new Set();
30
30
  #aborted = new Set();
31
+ #finishedUrls = new Set();
31
32
  constructor(page, options) {
32
33
  this.page = page;
33
34
  this.timeout = options.networkIdleTimeout ?? 100;
@@ -78,6 +79,12 @@ export class Network {
78
79
  throw new Error(`Network error: ${this.page.session.closedReason}`);
79
80
  }
80
81
  requests = Array.from(this.#requests.values()).filter(filter);
82
+ // remove requests which are finished at least once
83
+ // this happens when same request is made multiple times by browser in parallel and one of
84
+ // them gets stuck in pending state in browser [ need to debug why ]. So we dont receive
85
+ // loadingFinished event, causing it to show up in Active requests, but we can only store one
86
+ // response per url so as long as we have captured one, we dont care about other such requests
87
+ requests = requests.filter(req => !this.#finishedUrls.has(req.url));
81
88
  return requests.length === 0;
82
89
  }, {
83
90
  timeout: Network.TIMEOUT,
@@ -120,10 +127,12 @@ export class Network {
120
127
  // Called when a request should be removed from various trackers
121
128
  _forgetRequest({
122
129
  requestId,
123
- interceptId
130
+ interceptId,
131
+ url
124
132
  }, keepPending) {
125
133
  this.#requests.delete(requestId);
126
134
  this.#authentications.delete(interceptId);
135
+ this.#finishedUrls.add(url);
127
136
  if (!keepPending) {
128
137
  this.#pending.delete(requestId);
129
138
  }
package/dist/percy.js CHANGED
@@ -145,12 +145,14 @@ export class Percy {
145
145
  if (this.readyState != null) return;
146
146
  this.readyState = 0;
147
147
  try {
148
+ var _this$build;
148
149
  // start the snapshots queue immediately when not delayed or deferred
149
150
  if (!this.delayUploads && !this.deferUploads) yield this.#snapshots.start();
150
151
  // do not start the discovery queue when not needed
151
152
  if (!this.skipDiscovery) yield this.#discovery.start();
152
153
  // start a local API server for SDK communication
153
154
  if (this.server) yield this.server.listen();
155
+ if (this.projectType === 'web') this.deviceDetails = yield this.client.getDeviceDetails((_this$build = this.build) === null || _this$build === void 0 ? void 0 : _this$build.id);
154
156
  const snapshotType = this.projectType === 'web' ? 'snapshot' : 'comparison';
155
157
  this.syncQueue = new WaitForJob(snapshotType, this);
156
158
  // log and mark this instance as started
@@ -258,10 +260,10 @@ export class Percy {
258
260
  // snapshots. Once asset discovery has completed for the provided snapshots, the queued task will
259
261
  // resolve and an upload task will be queued separately.
260
262
  snapshot(options, snapshotPromise = {}) {
261
- var _this$build;
263
+ var _this$build2;
262
264
  if (this.readyState !== 1) {
263
265
  throw new Error('Not running');
264
- } else if ((_this$build = this.build) !== null && _this$build !== void 0 && _this$build.error) {
266
+ } else if ((_this$build2 = this.build) !== null && _this$build2 !== void 0 && _this$build2.error) {
265
267
  throw new Error(this.build.error);
266
268
  } else if (Array.isArray(options)) {
267
269
  return yieldAll(options.map(o => this.yield.snapshot(o, snapshotPromise)));
package/dist/queue.js CHANGED
@@ -122,9 +122,15 @@ export class Queue {
122
122
 
123
123
  // call or set up other handlers
124
124
  let exists = this.cancel(item);
125
+ task.ctrl = new AbortController();
126
+ // duplicate abortion controller on task, so it can can be used in further
127
+ // generators and can be cancelled internally
128
+ // TODO fix this for non object item usecase
129
+ if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
130
+ item._ctrl = task.ctrl;
131
+ }
125
132
  task.item = item = _classPrivateFieldGet(this, _handlers).push ? _classPrivateFieldGet(this, _handlers).push(item, exists) : item;
126
133
  task.handler = () => _classPrivateFieldGet(this, _handlers).task ? _classPrivateFieldGet(this, _handlers).task(item, ...args) : item;
127
- task.ctrl = new AbortController();
128
134
 
129
135
  // queue this task & maybe dequeue the next task
130
136
  _classPrivateFieldGet(this, _queued).add(task);
package/dist/snapshot.js CHANGED
@@ -129,7 +129,8 @@ function getSnapshotOptions(options, {
129
129
  disableCache: config.discovery.disableCache,
130
130
  captureMockedServiceWorker: config.discovery.captureMockedServiceWorker,
131
131
  captureSrcset: config.discovery.captureSrcset,
132
- userAgent: config.discovery.userAgent
132
+ userAgent: config.discovery.userAgent,
133
+ retry: config.discovery.retry
133
134
  }
134
135
  }, options], (path, prev, next) => {
135
136
  var _next, _next2;
package/dist/utils.js CHANGED
@@ -328,6 +328,29 @@ export function serializeFunction(fn) {
328
328
  }
329
329
  return fnbody;
330
330
  }
331
+ export async function withRetries(fn, {
332
+ count,
333
+ onRetry,
334
+ signal,
335
+ throwOn
336
+ }) {
337
+ count || (count = 1); // default a single try
338
+ let run = 0;
339
+ while (true) {
340
+ run += 1;
341
+ try {
342
+ return await generatePromise(fn, signal);
343
+ } catch (e) {
344
+ // if this error should not be retried on, we want to skip errors
345
+ let throwError = throwOn === null || throwOn === void 0 ? void 0 : throwOn.includes(e.name);
346
+ if (!throwError && run < count) {
347
+ await (onRetry === null || onRetry === void 0 ? void 0 : onRetry());
348
+ continue;
349
+ }
350
+ throw e;
351
+ }
352
+ }
353
+ }
331
354
  export function snapshotLogName(name, meta) {
332
355
  var _meta$snapshot;
333
356
  if (meta !== null && meta !== void 0 && (_meta$snapshot = meta.snapshot) !== null && _meta$snapshot !== void 0 && _meta$snapshot.testCase) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.28.2-beta.0",
3
+ "version": "1.28.2-beta.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,11 +43,11 @@
43
43
  "test:types": "tsd"
44
44
  },
45
45
  "dependencies": {
46
- "@percy/client": "1.28.2-beta.0",
47
- "@percy/config": "1.28.2-beta.0",
48
- "@percy/dom": "1.28.2-beta.0",
49
- "@percy/logger": "1.28.2-beta.0",
50
- "@percy/webdriver-utils": "1.28.2-beta.0",
46
+ "@percy/client": "1.28.2-beta.2",
47
+ "@percy/config": "1.28.2-beta.2",
48
+ "@percy/dom": "1.28.2-beta.2",
49
+ "@percy/logger": "1.28.2-beta.2",
50
+ "@percy/webdriver-utils": "1.28.2-beta.2",
51
51
  "content-disposition": "^0.5.4",
52
52
  "cross-spawn": "^7.0.3",
53
53
  "extract-zip": "^2.0.1",
@@ -58,5 +58,5 @@
58
58
  "rimraf": "^3.0.2",
59
59
  "ws": "^8.0.0"
60
60
  },
61
- "gitHead": "0519aa061bd36acd0852b961e24c05b9ad67274f"
61
+ "gitHead": "7c2bdccd896be9da7d07dd30ead802fbb4ca9fab"
62
62
  }