@percy/core 1.28.3 → 1.28.4-alpha.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/discovery.js CHANGED
@@ -3,6 +3,7 @@ import Queue from './queue.js';
3
3
  import { normalizeURL, hostnameMatches, createResource, createRootResource, createPercyCSSResource, createLogResource, yieldAll, snapshotLogName, withRetries } from './utils.js';
4
4
  import { sha256hash } from '@percy/client/utils';
5
5
  import Pako from 'pako';
6
+ import TimeIt from './timing.js';
6
7
 
7
8
  // Logs verbose debug logs detailing various snapshot options.
8
9
  function debugSnapshotOptions(snapshot) {
@@ -312,6 +313,7 @@ export function createDiscoveryQueue(percy) {
312
313
  concurrency
313
314
  } = percy.config.discovery;
314
315
  let queue = new Queue('discovery');
316
+ let timeit = new TimeIt();
315
317
  let cache;
316
318
  return queue.set({
317
319
  concurrency
@@ -342,54 +344,56 @@ export function createDiscoveryQueue(percy) {
342
344
  })
343
345
  // discovery resources for snapshots and call the callback for each discovered snapshot
344
346
  .handle('task', async function* (snapshot, callback) {
345
- percy.log.debug(`Discovering resources: ${snapshot.name}`, snapshot.meta);
347
+ await timeit.measure('asset-discovery', snapshot.name, async () => {
348
+ percy.log.debug(`Discovering resources: ${snapshot.name}`, snapshot.meta);
346
349
 
347
- // expectation explained in tests
348
- /* istanbul ignore next: tested, but coverage is stripped */
349
- let assetDiscoveryPageEnableJS = snapshot.cliEnableJavaScript && !snapshot.domSnapshot || (snapshot.enableJavaScript ?? !snapshot.domSnapshot);
350
- percy.log.debug(`Asset discovery Browser Page enable JS: ${assetDiscoveryPageEnableJS}`);
351
- await withRetries(async function* () {
352
- // create a new browser page
353
- let page = yield percy.browser.page({
354
- enableJavaScript: assetDiscoveryPageEnableJS,
355
- networkIdleTimeout: snapshot.discovery.networkIdleTimeout,
356
- requestHeaders: snapshot.discovery.requestHeaders,
357
- authorization: snapshot.discovery.authorization,
358
- userAgent: snapshot.discovery.userAgent,
359
- captureMockedServiceWorker: snapshot.discovery.captureMockedServiceWorker,
360
- meta: snapshot.meta,
361
- // enable network inteception
362
- intercept: {
363
- enableJavaScript: snapshot.enableJavaScript,
364
- disableCache: snapshot.discovery.disableCache,
365
- allowedHostnames: snapshot.discovery.allowedHostnames,
366
- disallowedHostnames: snapshot.discovery.disallowedHostnames,
367
- getResource: u => snapshot.resources.get(u) || cache.get(u),
368
- saveResource: r => {
369
- snapshot.resources.set(r.url, r);
370
- if (!r.root) {
371
- cache.set(r.url, r);
350
+ // expectation explained in tests
351
+ /* istanbul ignore next: tested, but coverage is stripped */
352
+ let assetDiscoveryPageEnableJS = snapshot.cliEnableJavaScript && !snapshot.domSnapshot || (snapshot.enableJavaScript ?? !snapshot.domSnapshot);
353
+ percy.log.debug(`Asset discovery Browser Page enable JS: ${assetDiscoveryPageEnableJS}`);
354
+ await withRetries(async function* () {
355
+ // create a new browser page
356
+ let page = yield percy.browser.page({
357
+ enableJavaScript: assetDiscoveryPageEnableJS,
358
+ networkIdleTimeout: snapshot.discovery.networkIdleTimeout,
359
+ requestHeaders: snapshot.discovery.requestHeaders,
360
+ authorization: snapshot.discovery.authorization,
361
+ userAgent: snapshot.discovery.userAgent,
362
+ captureMockedServiceWorker: snapshot.discovery.captureMockedServiceWorker,
363
+ meta: snapshot.meta,
364
+ // enable network inteception
365
+ intercept: {
366
+ enableJavaScript: snapshot.enableJavaScript,
367
+ disableCache: snapshot.discovery.disableCache,
368
+ allowedHostnames: snapshot.discovery.allowedHostnames,
369
+ disallowedHostnames: snapshot.discovery.disallowedHostnames,
370
+ getResource: u => snapshot.resources.get(u) || cache.get(u),
371
+ saveResource: r => {
372
+ snapshot.resources.set(r.url, r);
373
+ if (!r.root) {
374
+ cache.set(r.url, r);
375
+ }
372
376
  }
373
377
  }
378
+ });
379
+ try {
380
+ yield* captureSnapshotResources(page, snapshot, {
381
+ captureWidths: !snapshot.domSnapshot && percy.deferUploads,
382
+ capture: callback,
383
+ captureForDevices: percy.deviceDetails || []
384
+ });
385
+ } finally {
386
+ // always close the page when done
387
+ await page.close();
374
388
  }
389
+ }, {
390
+ count: snapshot.discovery.retry ? 3 : 1,
391
+ onRetry: () => {
392
+ percy.log.info(`Retrying snapshot: ${snapshotLogName(snapshot.name, snapshot.meta)}`, snapshot.meta);
393
+ },
394
+ signal: snapshot._ctrl.signal,
395
+ throwOn: ['AbortError']
375
396
  });
376
- try {
377
- yield* captureSnapshotResources(page, snapshot, {
378
- captureWidths: !snapshot.domSnapshot && percy.deferUploads,
379
- capture: callback,
380
- captureForDevices: percy.deviceDetails || []
381
- });
382
- } finally {
383
- // always close the page when done
384
- await page.close();
385
- }
386
- }, {
387
- count: snapshot.discovery.retry ? 3 : 1,
388
- onRetry: () => {
389
- percy.log.info(`Retrying snapshot: ${snapshotLogName(snapshot.name, snapshot.meta)}`, snapshot.meta);
390
- },
391
- signal: snapshot._ctrl.signal,
392
- throwOn: ['AbortError']
393
397
  });
394
398
  }).handle('error', ({
395
399
  name,
package/dist/percy.js CHANGED
@@ -2,10 +2,11 @@ import PercyClient from '@percy/client';
2
2
  import PercyConfig from '@percy/config';
3
3
  import logger from '@percy/logger';
4
4
  import Browser from './browser.js';
5
+ import Pako from 'pako';
6
+ import { base64encode, generatePromise, yieldAll, yieldTo, redactSecrets } from './utils.js';
5
7
  import { createPercyServer, createStaticServer } from './api.js';
6
8
  import { gatherSnapshots, createSnapshotsQueue, validateSnapshotOptions } from './snapshot.js';
7
9
  import { discoverSnapshotResources, createDiscoveryQueue } from './discovery.js';
8
- import { generatePromise, yieldAll, yieldTo } from './utils.js';
9
10
  import { WaitForJob } from './wait-for-job.js';
10
11
 
11
12
  // A Percy instance will create a new build when started, handle snapshot creation, asset discovery,
@@ -146,6 +147,9 @@ export class Percy {
146
147
  this.readyState = 0;
147
148
  try {
148
149
  var _this$build;
150
+ if (process.env.PERCY_CLIENT_ERROR_LOGS !== 'false') {
151
+ this.log.warn('Notice: Percy collects CI logs for service improvement, stored for 30 days. Opt-out anytime with export PERCY_CLIENT_ERROR_LOGS=false');
152
+ }
149
153
  // start the snapshots queue immediately when not delayed or deferred
150
154
  if (!this.delayUploads && !this.deferUploads) yield this.#snapshots.start();
151
155
  // do not start the discovery queue when not needed
@@ -206,54 +210,61 @@ export class Percy {
206
210
  // Stops the local API server and closes the browser and internal queues once snapshots have
207
211
  // completed. Does nothing if not running. When `force` is true, any queued snapshots are cleared.
208
212
  async *stop(force) {
209
- var _this$server3;
210
213
  // not started, but the browser was launched
211
- if (!this.readyState && this.browser.isConnected()) {
212
- await this.browser.close();
213
- }
214
- if (this.syncQueue) this.syncQueue.stop();
215
- // not started or already stopped
216
- if (!this.readyState || this.readyState > 2) return;
217
-
218
- // close queues asap
219
- if (force) {
220
- this.#discovery.close(true);
221
- this.#snapshots.close(true);
222
- }
214
+ try {
215
+ var _this$server3;
216
+ if (!this.readyState && this.browser.isConnected()) {
217
+ await this.browser.close();
218
+ }
219
+ if (this.syncQueue) this.syncQueue.stop();
220
+ // not started or already stopped
221
+ if (!this.readyState || this.readyState > 2) return;
222
+
223
+ // close queues asap
224
+ if (force) {
225
+ this.#discovery.close(true);
226
+ this.#snapshots.close(true);
227
+ }
223
228
 
224
- // already stopping
225
- if (this.readyState === 2) return;
226
- this.readyState = 2;
229
+ // already stopping
230
+ if (this.readyState === 2) return;
231
+ this.readyState = 2;
227
232
 
228
- // log when force stopping
229
- if (force) this.log.info('Stopping percy...');
233
+ // log when force stopping
234
+ if (force) this.log.info('Stopping percy...');
230
235
 
231
- // used to log snapshot count information
232
- let info = (state, size) => `${state} ` + `${size} snapshot${size !== 1 ? 's' : ''}`;
233
- try {
234
- // flush discovery and snapshot queues
235
- yield* this.yield.flush((state, size) => {
236
- this.log.progress(`${info(state, size)}...`, !!size);
237
- });
238
- } catch (error) {
239
- // reset ready state when aborted
240
- /* istanbul ignore else: all errors bubble */
241
- if (error.name === 'AbortError') this.readyState = 1;
242
- throw error;
243
- }
236
+ // used to log snapshot count information
237
+ let info = (state, size) => `${state} ` + `${size} snapshot${size !== 1 ? 's' : ''}`;
238
+ try {
239
+ // flush discovery and snapshot queues
240
+ yield* this.yield.flush((state, size) => {
241
+ this.log.progress(`${info(state, size)}...`, !!size);
242
+ });
243
+ } catch (error) {
244
+ // reset ready state when aborted
245
+ /* istanbul ignore else: all errors bubble */
246
+ if (error.name === 'AbortError') this.readyState = 1;
247
+ throw error;
248
+ }
244
249
 
245
- // if dry-running, log the total number of snapshots
246
- if (this.dryRun && this.#snapshots.size) {
247
- this.log.info(info('Found', this.#snapshots.size));
248
- }
250
+ // if dry-running, log the total number of snapshots
251
+ if (this.dryRun && this.#snapshots.size) {
252
+ this.log.info(info('Found', this.#snapshots.size));
253
+ }
249
254
 
250
- // close server and end queues
251
- await ((_this$server3 = this.server) === null || _this$server3 === void 0 ? void 0 : _this$server3.close());
252
- await this.#discovery.end();
253
- await this.#snapshots.end();
255
+ // close server and end queues
256
+ await ((_this$server3 = this.server) === null || _this$server3 === void 0 ? void 0 : _this$server3.close());
257
+ await this.#discovery.end();
258
+ await this.#snapshots.end();
254
259
 
255
- // mark instance as stopped
256
- this.readyState = 3;
260
+ // mark instance as stopped
261
+ this.readyState = 3;
262
+ } catch (err) {
263
+ this.log.error(err);
264
+ throw err;
265
+ } finally {
266
+ await this.sendBuildLogs();
267
+ }
257
268
  }
258
269
 
259
270
  // Takes one or more snapshots of a page while discovering resources to upload with the resulting
@@ -412,5 +423,34 @@ export class Percy {
412
423
  if (syncMode) options.sync = syncMode;
413
424
  return syncMode;
414
425
  }
426
+ async sendBuildLogs() {
427
+ if (!process.env.PERCY_TOKEN) return;
428
+ try {
429
+ var _this$build3, _this$build4, _this$build5, _this$build6;
430
+ const logsObject = {
431
+ clilogs: logger.query(() => true)
432
+ };
433
+ // Only add CI logs if not disabled voluntarily.
434
+ const sendCILogs = process.env.PERCY_CLIENT_ERROR_LOGS !== 'false';
435
+ if (sendCILogs) {
436
+ const redactedContent = redactSecrets(logger.query(() => true, true));
437
+ logsObject.cilogs = redactedContent;
438
+ }
439
+ const content = base64encode(Pako.gzip(JSON.stringify(logsObject)));
440
+ const referenceId = (_this$build3 = this.build) !== null && _this$build3 !== void 0 && _this$build3.id ? `build_${(_this$build4 = this.build) === null || _this$build4 === void 0 ? void 0 : _this$build4.id}` : (_this$build5 = this.build) === null || _this$build5 === void 0 ? void 0 : _this$build5.id;
441
+ const eventObject = {
442
+ content: content,
443
+ build_id: (_this$build6 = this.build) === null || _this$build6 === void 0 ? void 0 : _this$build6.id,
444
+ reference_id: referenceId,
445
+ service_name: 'cli',
446
+ base64encoded: true
447
+ };
448
+ // Ignore this will update once I implement logs controller.
449
+ const logsSHA = await this.client.sendBuildLogs(eventObject);
450
+ this.log.info(`Build's CLI${sendCILogs ? ' and CI' : ''} logs sent successfully. Please share this log ID with Percy team in case of any issues - ${logsSHA}`);
451
+ } catch (err) {
452
+ this.log.warn('Could not send the builds logs');
453
+ }
454
+ }
415
455
  }
416
456
  export default Percy;