@percy/core 1.28.3-beta.1 → 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
@@ -1,6 +1,9 @@
1
1
  import logger from '@percy/logger';
2
2
  import Queue from './queue.js';
3
3
  import { normalizeURL, hostnameMatches, createResource, createRootResource, createPercyCSSResource, createLogResource, yieldAll, snapshotLogName, withRetries } from './utils.js';
4
+ import { sha256hash } from '@percy/client/utils';
5
+ import Pako from 'pako';
6
+ import TimeIt from './timing.js';
4
7
 
5
8
  // Logs verbose debug logs detailing various snapshot options.
6
9
  function debugSnapshotOptions(snapshot) {
@@ -143,6 +146,12 @@ function processSnapshotResources({
143
146
  var _log$meta$snapshot, _log$meta$snapshot2;
144
147
  return ((_log$meta$snapshot = log.meta.snapshot) === null || _log$meta$snapshot === void 0 ? void 0 : _log$meta$snapshot.testCase) === snapshot.meta.snapshot.testCase && ((_log$meta$snapshot2 = log.meta.snapshot) === null || _log$meta$snapshot2 === void 0 ? void 0 : _log$meta$snapshot2.name) === snapshot.meta.snapshot.name;
145
148
  })));
149
+ if (process.env.PERCY_GZIP) {
150
+ for (let index = 0; index < resources.length; index++) {
151
+ resources[index].content = Pako.gzip(resources[index].content);
152
+ resources[index].sha = sha256hash(resources[index].content);
153
+ }
154
+ }
146
155
  return {
147
156
  ...snapshot,
148
157
  resources
@@ -304,6 +313,7 @@ export function createDiscoveryQueue(percy) {
304
313
  concurrency
305
314
  } = percy.config.discovery;
306
315
  let queue = new Queue('discovery');
316
+ let timeit = new TimeIt();
307
317
  let cache;
308
318
  return queue.set({
309
319
  concurrency
@@ -334,54 +344,56 @@ export function createDiscoveryQueue(percy) {
334
344
  })
335
345
  // discovery resources for snapshots and call the callback for each discovered snapshot
336
346
  .handle('task', async function* (snapshot, callback) {
337
- 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);
338
349
 
339
- // expectation explained in tests
340
- /* istanbul ignore next: tested, but coverage is stripped */
341
- let assetDiscoveryPageEnableJS = snapshot.cliEnableJavaScript && !snapshot.domSnapshot || (snapshot.enableJavaScript ?? !snapshot.domSnapshot);
342
- percy.log.debug(`Asset discovery Browser Page enable JS: ${assetDiscoveryPageEnableJS}`);
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);
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
+ }
364
376
  }
365
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();
366
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']
367
396
  });
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();
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']
385
397
  });
386
398
  }).handle('error', ({
387
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;