@percy/client 1.31.0-alpha.3 → 1.31.1-beta.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/client.js CHANGED
@@ -3,13 +3,13 @@ import PercyEnv from '@percy/env';
3
3
  import { git } from '@percy/env/utils';
4
4
  import logger from '@percy/logger';
5
5
  import Pako from 'pako';
6
- import { pool, request, formatBytes, sha256hash, base64encode, getPackageJSON, waitForTimeout, validateTiles, formatLogErrors, tagsList } from './utils.js';
6
+ import { pool, request, formatBytes, sha256hash, base64encode, getPackageJSON, waitForTimeout, validateTiles, formatLogErrors, tagsList, normalizeBrowsers } from './utils.js';
7
7
 
8
8
  // Default client API URL can be set with an env var for API development
9
9
  const {
10
10
  PERCY_CLIENT_API_URL = 'https://percy.io/api/v1'
11
11
  } = process.env;
12
- const pkg = getPackageJSON(import.meta.url);
12
+ let pkg = getPackageJSON(import.meta.url);
13
13
  // minimum polling interval milliseconds
14
14
  const MIN_POLLING_INTERVAL = 1_000;
15
15
  const INVALID_TOKEN_ERROR_MESSAGE = 'Unable to retrieve snapshot details with write access token. Kindly use a full access token for retrieving snapshot details with Synchronous CLI.';
@@ -21,6 +21,25 @@ function validateId(type, id) {
21
21
  throw new Error(`Invalid ${type} ID`);
22
22
  }
23
23
  }
24
+ function makeRegions(regions, algorithm, algorithmConfiguration) {
25
+ let regionObj;
26
+ if (algorithm) {
27
+ regionObj = {};
28
+ regionObj.algorithm = algorithm;
29
+ regionObj.configuration = algorithmConfiguration;
30
+ }
31
+ if (!Array.isArray(regions) && !regionObj) return null;
32
+ if (regionObj) {
33
+ regions || (regions = []);
34
+ regions.push(regionObj);
35
+ }
36
+ return regions.map(region => ({
37
+ ...region,
38
+ elementSelector: region.elementSelector || {
39
+ fullpage: true
40
+ }
41
+ }));
42
+ }
24
43
 
25
44
  // Validate project path arguments
26
45
  function validateProjectPath(path) {
@@ -77,6 +96,9 @@ export class PercyClient {
77
96
 
78
97
  // Stringifies client and environment info.
79
98
  userAgent() {
99
+ // forcedPkgValue has been added since when percy package is bundled inside Electron app (LCNC)
100
+ // we can't read Percy's package json for package name and version, so we are passing it via env variables
101
+ if (this.env.forcedPkgValue) pkg = this.env.forcedPkgValue;
80
102
  let client = new Set([`Percy/${/\w+$/.exec(this.apiUrl)}`].concat(`${pkg.name}/${pkg.version}`, ...this.clientInfo).filter(Boolean));
81
103
  let environment = new Set([...this.environmentInfo].concat(`node/${process.version}`, this.env.info).filter(Boolean));
82
104
  return `${[...client].join(' ')} (${[...environment].join('; ')})`;
@@ -146,7 +168,9 @@ export class PercyClient {
146
168
  var _this$config$percy2;
147
169
  this.log.debug('Creating a new build...');
148
170
  let source = 'user_created';
149
- if (process.env.PERCY_AUTO_ENABLED_GROUP_BUILD === 'true') {
171
+ if (process.env.PERCY_ORIGINATED_SOURCE) {
172
+ source = 'bstack_sdk_created';
173
+ } else if (process.env.PERCY_AUTO_ENABLED_GROUP_BUILD === 'true') {
150
174
  source = 'auto_enabled_group';
151
175
  }
152
176
  let tagsArr = tagsList(this.labels);
@@ -172,7 +196,8 @@ export class PercyClient {
172
196
  tags: tagsArr,
173
197
  'cli-start-time': cliStartTime,
174
198
  source: source,
175
- 'skip-base-build': (_this$config$percy2 = this.config.percy) === null || _this$config$percy2 === void 0 ? void 0 : _this$config$percy2.skipBaseBuild
199
+ 'skip-base-build': (_this$config$percy2 = this.config.percy) === null || _this$config$percy2 === void 0 ? void 0 : _this$config$percy2.skipBaseBuild,
200
+ 'testhub-build-uuid': this.env.testhubBuildUuid
176
201
  },
177
202
  relationships: {
178
203
  resources: {
@@ -380,8 +405,10 @@ export class PercyClient {
380
405
  sha: resource.sha,
381
406
  ...meta
382
407
  };
383
- yield this.uploadResource(buildId, resource, resourceMeta);
384
- this.log.debug(`Uploaded resource ${resource.url}`, resourceMeta);
408
+ yield this.uploadResource(buildId, resource, resourceMeta).then(result => {
409
+ this.log.debug(`Uploaded resource ${resource.url}`, resourceMeta);
410
+ return result;
411
+ });
385
412
  }
386
413
  }, this, uploadConcurrency);
387
414
  }
@@ -401,6 +428,10 @@ export class PercyClient {
401
428
  testCase,
402
429
  labels,
403
430
  thTestCaseExecutionId,
431
+ browsers,
432
+ regions,
433
+ algorithm,
434
+ algorithmConfiguration,
404
435
  resources = [],
405
436
  meta
406
437
  } = {}) {
@@ -411,6 +442,7 @@ export class PercyClient {
411
442
  this.log.warn('Warning: Missing `clientInfo` and/or `environmentInfo` properties', meta);
412
443
  }
413
444
  let tagsArr = tagsList(labels);
445
+ let regionsArr = makeRegions(regions, algorithm, algorithmConfiguration);
414
446
  this.log.debug(`Validating resources: ${name}...`, meta);
415
447
  for (let resource of resources) {
416
448
  if (resource.sha || resource.content || !resource.filepath) continue;
@@ -428,10 +460,12 @@ export class PercyClient {
428
460
  'test-case': testCase || null,
429
461
  tags: tagsArr,
430
462
  'scope-options': scopeOptions || {},
463
+ regions: regionsArr || null,
431
464
  'minimum-height': minHeight || null,
432
465
  'enable-javascript': enableJavaScript || null,
433
466
  'enable-layout': enableLayout || false,
434
- 'th-test-case-execution-id': thTestCaseExecutionId || null
467
+ 'th-test-case-execution-id': thTestCaseExecutionId || null,
468
+ browsers: normalizeBrowsers(browsers) || null
435
469
  },
436
470
  relationships: {
437
471
  resources: {
@@ -497,6 +531,9 @@ export class PercyClient {
497
531
  consideredElementsData,
498
532
  metadata,
499
533
  sync,
534
+ regions,
535
+ algorithm,
536
+ algorithmConfiguration,
500
537
  meta = {}
501
538
  } = {}) {
502
539
  validateId('snapshot', snapshotId);
@@ -511,6 +548,7 @@ export class PercyClient {
511
548
  tile.content = await fs.promises.readFile(tile.filepath);
512
549
  }
513
550
  }
551
+ let regionsArr = makeRegions(regions, algorithm, algorithmConfiguration);
514
552
  this.log.debug(`${tiles.length} tiles for comparision: ${tag.name}...`, meta);
515
553
  return this.post(`snapshots/${snapshotId}/comparisons`, {
516
554
  data: {
@@ -518,6 +556,7 @@ export class PercyClient {
518
556
  attributes: {
519
557
  'external-debug-url': externalDebugUrl || null,
520
558
  'ignore-elements-data': ignoredElementsData || null,
559
+ regions: regionsArr || null,
521
560
  'consider-elements-data': consideredElementsData || null,
522
561
  'dom-info-sha': domInfoSha || null,
523
562
  sync: !!sync,
@@ -719,6 +758,8 @@ export class PercyClient {
719
758
  return 'generic';
720
759
  case 'vmw':
721
760
  return 'visual_scanner';
761
+ case 'res':
762
+ return 'responsive_scanner';
722
763
  default:
723
764
  return 'web';
724
765
  }
package/dist/proxy.js CHANGED
@@ -4,9 +4,26 @@ import http from 'http';
4
4
  import https from 'https';
5
5
  import logger from '@percy/logger';
6
6
  import { stripQuotesAndSpaces } from '@percy/env/utils';
7
+ import { PacProxyAgent } from 'pac-proxy-agent';
7
8
  const CRLF = '\r\n';
8
9
  const STATUS_REG = /^HTTP\/1.[01] (\d*)/;
9
10
 
11
+ // function to create PAC proxy agent
12
+ export function createPacAgent(pacUrl, options = {}) {
13
+ pacUrl = stripQuotesAndSpaces(pacUrl);
14
+ try {
15
+ const agent = new PacProxyAgent(pacUrl, {
16
+ keepAlive: true,
17
+ ...options
18
+ });
19
+ logger('client:proxy').info(`Successfully loaded PAC file from: ${pacUrl}`);
20
+ return agent;
21
+ } catch (error) {
22
+ logger('client:proxy').error(`Failed to load PAC file, error message: ${error.message}, stack: ${error.stack}`);
23
+ throw new Error(`Failed to initialize PAC proxy: ${error.message}`);
24
+ }
25
+ }
26
+
10
27
  // Returns true if the URL hostname matches any patterns
11
28
  export function hostnameMatches(patterns, url) {
12
29
  let subject = new URL(url);
@@ -196,8 +213,29 @@ export function proxyAgentFor(url, options) {
196
213
  hostname
197
214
  } = new URL(url);
198
215
  let cachekey = `${protocol}//${hostname}`;
199
- if (!cache.has(cachekey)) {
200
- cache.set(cachekey, protocol === 'https:' ? new ProxyHttpsAgent(options) : new ProxyHttpAgent(options));
216
+
217
+ // If we already have a cached agent, return it
218
+ if (cache.has(cachekey)) {
219
+ return cache.get(cachekey);
220
+ }
221
+ try {
222
+ let agent;
223
+ const pacUrl = process.env.PERCY_PAC_FILE_URL;
224
+
225
+ // If PAC URL is provided, use PAC proxy
226
+ if (pacUrl) {
227
+ logger('client:proxy').info(`Using PAC file from: ${pacUrl}`);
228
+ agent = createPacAgent(pacUrl, options);
229
+ } else {
230
+ // Fall back to other proxy configuration
231
+ agent = protocol === 'https:' ? new ProxyHttpsAgent(options) : new ProxyHttpAgent(options);
232
+ }
233
+
234
+ // Cache the created agent
235
+ cache.set(cachekey, agent);
236
+ return agent;
237
+ } catch (error) {
238
+ logger('client:proxy').error(`Failed to create proxy agent: ${error.message}`);
239
+ throw error;
201
240
  }
202
- return cache.get(cachekey);
203
241
  }
package/dist/utils.js CHANGED
@@ -4,6 +4,7 @@ import url from 'url';
4
4
  import path from 'path';
5
5
  import crypto from 'crypto';
6
6
  import logger from '@percy/logger';
7
+ import { snakecase } from '@percy/config/utils';
7
8
 
8
9
  // Formats a raw byte integer as a string
9
10
  export function formatBytes(int) {
@@ -141,9 +142,11 @@ export async function request(url, options = {}, callback) {
141
142
  } = new URL(url);
142
143
 
143
144
  // reference the default export so tests can mock it
145
+ // bundling cli inside electron or another package fails if we import it
146
+ // like this: await import(protocol === 'https:' ? 'https' : 'http');
144
147
  let {
145
148
  default: http
146
- } = await import(protocol === 'https:' ? 'https' : 'http');
149
+ } = protocol === 'https:' ? await import('https') : await import('http');
147
150
  let {
148
151
  proxyAgentFor
149
152
  } = await import('./proxy.js');
@@ -278,4 +281,8 @@ export function tagsList(tags) {
278
281
  }
279
282
  return tagsArr;
280
283
  }
284
+ export function normalizeBrowsers(browserValues) {
285
+ if (!browserValues) return null;
286
+ return browserValues.map(browser => snakecase(browser));
287
+ }
281
288
  export { hostnameMatches, getProxy, ProxyHttpAgent, ProxyHttpsAgent, proxyAgentFor } from './proxy.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/client",
3
- "version": "1.31.0-alpha.3",
3
+ "version": "1.31.1-beta.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "publishConfig": {
11
11
  "access": "public",
12
- "tag": "alpha"
12
+ "tag": "beta"
13
13
  },
14
14
  "engines": {
15
15
  "node": ">=14"
@@ -33,9 +33,10 @@
33
33
  "test:coverage": "yarn test --coverage"
34
34
  },
35
35
  "dependencies": {
36
- "@percy/env": "1.31.0-alpha.3",
37
- "@percy/logger": "1.31.0-alpha.3",
36
+ "@percy/env": "1.31.1-beta.0",
37
+ "@percy/logger": "1.31.1-beta.0",
38
+ "pac-proxy-agent": "^7.0.2",
38
39
  "pako": "^2.1.0"
39
40
  },
40
- "gitHead": "14ae0b4319c1c2ae28729d56cba3b159e80386a8"
41
+ "gitHead": "0bd4ee85db05f5adb8376d1ef09627c04cff5fb1"
41
42
  }