@percy/core 1.31.2-beta.0 → 1.31.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/README.md CHANGED
@@ -56,12 +56,12 @@ The following options can also be defined within a Percy config file
56
56
  - `userAgent` — Custom user-agent string used when requesting assets
57
57
  - `cookies` — Browser cookies to use when requesting assets
58
58
  - `networkIdleTimeout` — Milliseconds to wait for the network to idle (**default** `100`)
59
- - `concurrency` — Asset discovery concerrency (**default** `5`)
59
+ - `concurrency` — Asset discovery concurrency (**default** `5`)
60
60
  - `launchOptions` — Asset discovery browser launch options
61
61
  - `executable` — Browser executable path (**default** `process.env.PERCY_BROWSER_EXECUTABLE`)
62
62
  - `timeout` — Discovery launch timeout, in milliseconds (**default** `30000`)
63
63
  - `args` — Additional browser process arguments
64
- - `headless` — Runs the browser headlessy (**default** `true`)
64
+ - `headless` — Runs the browser headlessly (**default** `true`)
65
65
 
66
66
  Additional Percy config file options are also allowed and will override any options defined by a
67
67
  local config file. These config file options are also made available to SDKs via the local API
@@ -282,7 +282,7 @@ environment variable.
282
282
 
283
283
  - Vist https://chromiumdash.appspot.com/releases?platform=Mac and check for current version in use.
284
284
  - Get Branch Base Position for that release.
285
- - Visit https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Mac/ and search for exact/neareast Branch Base Position.
285
+ - Visit https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Mac/ and search for exact/nearest Branch Base Position.
286
286
 
287
287
  ### Excerpt
288
288
 
package/dist/api.js CHANGED
@@ -54,7 +54,7 @@ export function createPercyServer(percy, port) {
54
54
  res.setHeader('X-Percy-Core-Version', ((_percy$testing2 = percy.testing) === null || _percy$testing2 === void 0 ? void 0 : _percy$testing2.version) ?? pkg.version);
55
55
  }
56
56
 
57
- // track all api reqeusts in testing mode
57
+ // track all api requests in testing mode
58
58
  if (percy.testing && !req.url.pathname.startsWith('/test/')) {
59
59
  var _percy$testing3;
60
60
  ((_percy$testing3 = percy.testing).requests || (_percy$testing3.requests = [])).push({
@@ -119,7 +119,7 @@ export function createPercyServer(percy, port) {
119
119
  logger('core:server').deprecated(['It looks like you’re using @percy/cli with an older SDK.', 'Please upgrade to the latest version to fix this warning.', 'See these docs for more info: https://www.browserstack.com/docs/percy/migration/migrate-to-cli'].join(' '));
120
120
  let content = await fs.promises.readFile(PERCY_DOM, 'utf-8');
121
121
  let wrapper = '(window.PercyAgent = class { snapshot(n, o) { return PercyDOM.serialize(o); } });';
122
- return res.send(200, 'applicaton/javascript', content.concat(wrapper));
122
+ return res.send(200, 'application/javascript', content.concat(wrapper));
123
123
  })
124
124
  // post one or more snapshots, optionally async
125
125
  .route('post', '/percy/snapshot', async (req, res) => {
package/dist/discovery.js CHANGED
@@ -259,7 +259,7 @@ async function* captureSnapshotResources(page, snapshot, options) {
259
259
  let cookies = ((_snapshot$domSnapshot = snapshot.domSnapshot) === null || _snapshot$domSnapshot === void 0 ? void 0 : _snapshot$domSnapshot.cookies) || ((_snapshot$domSnapshot2 = snapshot.domSnapshot) === null || _snapshot$domSnapshot2 === void 0 || (_snapshot$domSnapshot2 = _snapshot$domSnapshot2[0]) === null || _snapshot$domSnapshot2 === void 0 ? void 0 : _snapshot$domSnapshot2.cookies);
260
260
  cookies = parseCookies(cookies);
261
261
 
262
- // iterate over device to trigger reqeusts and capture other dpr width
262
+ // iterate over device to trigger requests and capture other dpr width
263
263
  async function* captureResponsiveAssets() {
264
264
  for (const device of captureForDevices) {
265
265
  discovery = {
@@ -354,7 +354,7 @@ async function* captureSnapshotResources(page, snapshot, options) {
354
354
  } = snap;
355
355
  let [width] = widths;
356
356
 
357
- // iterate over widths to trigger reqeusts and capture other widths
357
+ // iterate over widths to trigger requests and capture other widths
358
358
  if (isBaseSnapshot || captureWidths) {
359
359
  for (let i = 0; i < widths.length - 1; i++) {
360
360
  if (captureWidths) yield* takeSnapshot(snap, width);
package/dist/install.js CHANGED
@@ -6,7 +6,7 @@ import logger from '@percy/logger';
6
6
  import cp from 'child_process';
7
7
  import { ProxyHttpsAgent, formatBytes } from '@percy/client/utils';
8
8
 
9
- // Formats milleseconds as "MM:SS"
9
+ // Formats milliseconds as "MM:SS"
10
10
  function formatTime(ms) {
11
11
  let minutes = (ms / 1000 / 60).toString().split('.')[0].padStart(2, '0');
12
12
  let seconds = (ms / 1000 % 60).toFixed().padStart(2, '0');
package/dist/network.js CHANGED
@@ -289,10 +289,10 @@ export class Network {
289
289
  response
290
290
  } = event;
291
291
  // await on requestWillBeSent
292
- // no explicitly wait on requestWillBePaused as we implictly wait on it, since it manipulates the lifeCycle of request using Fetch module
292
+ // no explicitly wait on requestWillBePaused as we implicitly wait on it, since it manipulates the lifeCycle of request using Fetch module
293
293
  await this.#requestsLifeCycleHandler.get(requestId).requestWillBeSent;
294
294
  let request = this.#requests.get(requestId);
295
- /* istanbul ignore if: race condition paranioa */
295
+ /* istanbul ignore if: race condition paranoia */
296
296
  if (!request) return;
297
297
  request.response = response;
298
298
  request.response.buffer = async () => {
@@ -314,7 +314,7 @@ export class Network {
314
314
  // wait for request to be sent
315
315
  await this.#requestsLifeCycleHandler.get(requestId).requestWillBeSent;
316
316
  let request = this.#requests.get(requestId);
317
- /* istanbul ignore else: race condition paranioa */
317
+ /* istanbul ignore else: race condition paranoia */
318
318
  if (request) this._forgetRequest(request);
319
319
  };
320
320
 
@@ -327,7 +327,7 @@ export class Network {
327
327
  // wait for upto 2 seconds or check if response has been sent
328
328
  await this.#requestsLifeCycleHandler.get(requestId).responseReceived;
329
329
  let request = this.#requests.get(requestId);
330
- /* istanbul ignore if: race condition paranioa */
330
+ /* istanbul ignore if: race condition paranoia */
331
331
  if (!request) return;
332
332
  await saveResponseResource(this, request, session);
333
333
  this._forgetRequest(request);
@@ -344,7 +344,7 @@ export class Network {
344
344
  // and in any case, order of processing for responseReceived and loadingFailed does not matter, as response capturing is done in loadingFinished
345
345
  await this.#requestsLifeCycleHandler.get(requestId).requestWillBeSent;
346
346
  let request = this.#requests.get(event.requestId);
347
- /* istanbul ignore if: race condition paranioa */
347
+ /* istanbul ignore if: race condition paranoia */
348
348
  if (!request) return;
349
349
 
350
350
  // If request was aborted, keep track of it as we need to cancel any in process callbacks for
@@ -489,7 +489,7 @@ async function makeDirectRequest(network, request, session) {
489
489
  });
490
490
  }
491
491
 
492
- // Save a resource from a request, skipping it if specific paramters are not met
492
+ // Save a resource from a request, skipping it if specific parameters are not met
493
493
  async function saveResponseResource(network, request, session) {
494
494
  var _response$headers;
495
495
  let {
@@ -505,7 +505,7 @@ async function saveResponseResource(network, request, session) {
505
505
  url,
506
506
  responseStatus: response === null || response === void 0 ? void 0 : response.status
507
507
  };
508
- // Checing for content length more than 100MB, to prevent websocket error which is governed by
508
+ // Checking for content length more than 100MB, to prevent websocket error which is governed by
509
509
  // maxPayload option of websocket defaulted to 100MB.
510
510
  // If content-length is more than our allowed 25MB, no need to process that resouce we can return log.
511
511
  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')];
package/dist/percy.js CHANGED
@@ -13,7 +13,7 @@ import logger from '@percy/logger';
13
13
  import { getProxy } from '@percy/client/utils';
14
14
  import Browser from './browser.js';
15
15
  import Pako from 'pako';
16
- import { base64encode, generatePromise, yieldAll, yieldTo, redactSecrets, detectSystemProxyAndLog } from './utils.js';
16
+ import { base64encode, generatePromise, yieldAll, yieldTo, redactSecrets, detectSystemProxyAndLog, checkSDKVersion } from './utils.js';
17
17
  import { createPercyServer, createStaticServer } from './api.js';
18
18
  import { gatherSnapshots, createSnapshotsQueue, validateSnapshotOptions } from './snapshot.js';
19
19
  import { discoverSnapshotResources, createDiscoveryQueue } from './discovery.js';
@@ -113,6 +113,7 @@ export class Percy {
113
113
  // if there is none, stop it
114
114
  this.resetMonitoringId = null;
115
115
  this.monitoringCheckLastExecutedAt = null;
116
+ this.sdkInfoDisplayed = false;
116
117
 
117
118
  // generator methods are wrapped to autorun and return promises
118
119
  for (let m of ['start', 'stop', 'flush', 'idle', 'snapshot', 'upload']) {
@@ -212,7 +213,7 @@ export class Percy {
212
213
  if (process.env.PERCY_CLIENT_ERROR_LOGS !== 'false') {
213
214
  this.log.warn('Notice: Percy collects CI logs to improve service and enhance your experience. These logs help us debug issues and provide insights on your dashboards, making it easier to optimize the product experience. Logs are stored securely for 30 days. You can opt out anytime with export PERCY_CLIENT_ERROR_LOGS=false, but keeping this enabled helps us offer the best support and features.');
214
215
  }
215
- // Not awaiting proxy check as this can be asyncronous when not enabled
216
+ // Not awaiting proxy check as this can be asynchronous when not enabled
216
217
  const detectProxy = detectSystemProxyAndLog(this.config.percy.useSystemProxy);
217
218
  if (this.config.percy.useSystemProxy) await detectProxy;
218
219
  // start the snapshots queue immediately when not delayed or deferred
@@ -372,7 +373,7 @@ export class Percy {
372
373
  if (cpuInfo.currentUsagePercent >= 80 || memoryUsageInfo.currentUsagePercent >= 80) {
373
374
  let currentConcurrent = _classPrivateFieldGet(_discovery, this).concurrency;
374
375
 
375
- // concurrency must be betweeen [1, (default/user defined value)]
376
+ // concurrency must be between [1, (default/user defined value)]
376
377
  let newConcurrency = Math.max(1, parseInt(currentConcurrent / 2));
377
378
  newConcurrency = Math.min(this.discoveryMaxConcurrency, newConcurrency);
378
379
  this.log.debug(`Downscaling discovery browser concurrency from ${_classPrivateFieldGet(_discovery, this).concurrency} to ${newConcurrency}`);
@@ -383,7 +384,7 @@ export class Percy {
383
384
  let currentConcurrent = _classPrivateFieldGet(_discovery, this).concurrency;
384
385
  let newConcurrency = currentConcurrent + 2;
385
386
 
386
- // concurrency must be betweeen [1, (default/user-defined value)]
387
+ // concurrency must be between [1, (default/user-defined value)]
387
388
  newConcurrency = Math.min(this.discoveryMaxConcurrency, newConcurrency);
388
389
  newConcurrency = Math.max(1, newConcurrency);
389
390
  this.log.debug(`Upscaling discovery browser concurrency from ${_classPrivateFieldGet(_discovery, this).concurrency} to ${newConcurrency}`);
@@ -429,10 +430,15 @@ export class Percy {
429
430
  throw new Error('Cannot capture DOM snapshots when asset discovery is disabled');
430
431
  }
431
432
 
432
- // return an async generator to allow cancelation
433
+ // return an async generator to allow cancellation
433
434
  return async function* () {
434
435
  let server;
435
436
  try {
437
+ // Check SDK version
438
+ if (!this.sdkInfoDisplayed && options.clientInfo) {
439
+ await checkSDKVersion(options.clientInfo);
440
+ this.sdkInfoDisplayed = true;
441
+ }
436
442
  if ('serve' in options) {
437
443
  // create and start a static server
438
444
  let {
@@ -533,7 +539,7 @@ export class Percy {
533
539
  });
534
540
  }
535
541
 
536
- // return an async generator to allow cancelation
542
+ // return an async generator to allow cancellation
537
543
  return async function* () {
538
544
  try {
539
545
  return yield* yieldTo(_classPrivateFieldGet(_snapshots, this).push(options));
@@ -615,7 +621,7 @@ export class Percy {
615
621
  // This can be due to proxy issue
616
622
  this.log.error('percy.io might not be reachable, check network connection, proxy and ensure that percy.io is whitelisted.');
617
623
  if (!_assertClassBrand(_Percy_brand, this, _proxyEnabled).call(this)) {
618
- this.log.error('If inside a proxied envirnment, please configure the following environment variables: HTTP_PROXY, [ and optionally HTTPS_PROXY if you need it ]. Refer to our documentation for more details');
624
+ this.log.error('If inside a proxied environment, please configure the following environment variables: HTTP_PROXY, [ and optionally HTTPS_PROXY if you need it ]. Refer to our documentation for more details');
619
625
  }
620
626
  }
621
627
  this.log.error('Unable to analyze error logs');
package/dist/queue.js CHANGED
@@ -211,7 +211,7 @@ export class Queue {
211
211
 
212
212
  // processes tasks using a generator promise, allowing task handlers to be cancelable
213
213
 
214
- // returns a generator that yeilds until started and no longer pending, calling the
214
+ // returns a generator that yields until started and no longer pending, calling the
215
215
  // callback every 10ms during checks with the current number of pending tasks
216
216
  idle(callback) {
217
217
  return yieldFor(() => {
package/dist/snapshot.js CHANGED
@@ -19,7 +19,7 @@ function validURL(url, base) {
19
19
  }
20
20
  function validateAndFixSnapshotUrl(snapshot) {
21
21
  let log = logger('core:snapshot');
22
- // encoding snapshot url, if contians invalid URI characters/syntax
22
+ // encoding snapshot url, if contains invalid URI characters/syntax
23
23
  let modifiedURL = decodeAndEncodeURLWithLogging(snapshot.url, log, {
24
24
  meta: {
25
25
  snapshot: {
@@ -93,7 +93,7 @@ function mapSnapshotOptions(snapshots, context) {
93
93
  exclude,
94
94
  ...opts
95
95
  }) => snap => next(
96
- // assign additional options to included snaphots
96
+ // assign additional options to included snapshots
97
97
  snapshotMatches(snap, include, exclude) ? Object.assign(snap, opts) : snap), snap => getSnapshotOptions(snap, context));
98
98
 
99
99
  // reduce snapshots with options
@@ -326,7 +326,7 @@ function mergeSnapshotOptions(prev = {}, next) {
326
326
  ...incoming
327
327
  } = next;
328
328
 
329
- // prioritize singular widths over mutilple widths
329
+ // prioritize singular widths over multiple widths
330
330
  widths = width ? [width] : widths;
331
331
 
332
332
  // deduplicate resources by associated widths and url
package/dist/utils.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from 'events';
2
- import { sha256hash } from '@percy/client/utils';
2
+ import { sha256hash, request } from '@percy/client/utils';
3
3
  import { camelcase, merge } from '@percy/config/utils';
4
4
  import YAML from 'yaml';
5
5
  import path from 'path';
@@ -576,4 +576,68 @@ export async function* maybeScrollToBottom(page, discovery) {
576
576
  if (discovery.scrollToBottom && page.enableJavaScript) {
577
577
  yield page.eval('await scrollToBottom()');
578
578
  }
579
+ }
580
+
581
+ // Package to GitHub repo mapping
582
+ const PACKAGE_TO_REPO = {
583
+ '@percy/selenium-webdriver': 'percy-selenium-js',
584
+ '@percy/playwright': 'percy-playwright',
585
+ '@percy/appium-app': 'percy-appium-js',
586
+ '@percy/storybook': 'percy-storybook',
587
+ '@percy/ember': 'percy-ember',
588
+ '@percy/cypress': 'percy-cypress',
589
+ '@percy/puppeteer': 'percy-puppeteer',
590
+ '@percy/testcafe': 'percy-testcafe',
591
+ '@percy/nightwatch': 'percy-nightwatch',
592
+ 'percy-java-selenium': 'percy-selenium-java',
593
+ 'percy-playwright-java': 'percy-playwright-java',
594
+ 'percy-appium-dotnet': 'percy-appium-dotnet',
595
+ 'percy-selenium-dotnet': 'percy-selenium-dotnet',
596
+ 'percy-playwright-dotnet': 'percy-playwright-dotnet',
597
+ 'percy-playwright-python': 'percy-playwright-python',
598
+ 'percy-selenium-python': 'percy-selenium-python',
599
+ 'percy-selenium-ruby': 'percy-selenium-ruby',
600
+ 'percy-capybara': 'percy-capybara'
601
+ };
602
+
603
+ // Utility function to check SDK version updates
604
+ export async function checkSDKVersion(clientInfo) {
605
+ const log = logger('core:sdk-version');
606
+ try {
607
+ // Split on the last '/' to get package name and version
608
+ const lastSlashIndex = clientInfo.lastIndexOf('/');
609
+ if (lastSlashIndex === -1) {
610
+ log.debug(`Invalid clientInfo format: ${clientInfo}`);
611
+ return;
612
+ }
613
+ const packageName = clientInfo.substring(0, lastSlashIndex);
614
+ const currentVersion = clientInfo.substring(lastSlashIndex + 1);
615
+
616
+ // Get GitHub repo name from mapping
617
+ const repoName = PACKAGE_TO_REPO[packageName];
618
+ if (!repoName) {
619
+ log.debug(`No repo mapping found for package: ${packageName}`);
620
+ return;
621
+ }
622
+
623
+ // Fetch latest version from GitHub releases
624
+ const githubData = await request(`https://api.github.com/repos/percy/${repoName}/releases?page=1`, {
625
+ headers: {
626
+ 'User-Agent': '@percy/cli'
627
+ },
628
+ retries: 0
629
+ });
630
+ const latestRelease = githubData.find(r => !r.prerelease);
631
+ if (!latestRelease) {
632
+ return;
633
+ }
634
+ const latestVersion = latestRelease.tag_name.replace(/^v/, ''); // Remove 'v' prefix if present
635
+
636
+ log.debug(`[SDK Version Check] Current: ${currentVersion}, Latest: ${latestVersion}`);
637
+ if (currentVersion !== latestVersion) {
638
+ log.warn(`[SDK Update Available] ${packageName}: ${currentVersion} -> ${latestVersion}`);
639
+ }
640
+ } catch (error) {
641
+ log.debug('Could not check SDK version', error);
642
+ }
579
643
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.31.2-beta.0",
3
+ "version": "1.31.2-beta.2",
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.2-beta.0",
47
- "@percy/config": "1.31.2-beta.0",
48
- "@percy/dom": "1.31.2-beta.0",
49
- "@percy/logger": "1.31.2-beta.0",
50
- "@percy/monitoring": "1.31.2-beta.0",
51
- "@percy/webdriver-utils": "1.31.2-beta.0",
46
+ "@percy/client": "1.31.2-beta.2",
47
+ "@percy/config": "1.31.2-beta.2",
48
+ "@percy/dom": "1.31.2-beta.2",
49
+ "@percy/logger": "1.31.2-beta.2",
50
+ "@percy/monitoring": "1.31.2-beta.2",
51
+ "@percy/webdriver-utils": "1.31.2-beta.2",
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": "6d36e464efaa1261bf1f1e7c7302c64cc85eccc7"
64
+ "gitHead": "3002538e11412bea43ddc5e15da7ce338ff44b4f"
65
65
  }