@lambdatest/smartui-cli 4.1.33 → 4.1.34

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.
Files changed (2) hide show
  1. package/dist/index.cjs +211 -55
  2. package/package.json +2 -1
package/dist/index.cjs CHANGED
@@ -61,6 +61,7 @@ var sharp__default = /*#__PURE__*/_interopDefault(sharp);
61
61
  var http__namespace = /*#__PURE__*/_interopNamespace(http);
62
62
 
63
63
  var __defProp = Object.defineProperty;
64
+ var __getOwnPropNames = Object.getOwnPropertyNames;
64
65
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
65
66
  var __hasOwnProp = Object.prototype.hasOwnProperty;
66
67
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -83,6 +84,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
83
84
  return require.apply(this, arguments);
84
85
  throw Error('Dynamic require of "' + x + '" is not supported');
85
86
  });
87
+ var __commonJS = (cb, mod) => function __require2() {
88
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
89
+ };
86
90
  var __async = (__this, __arguments, generator) => {
87
91
  return new Promise((resolve, reject) => {
88
92
  var fulfilled = (value) => {
@@ -104,6 +108,73 @@ var __async = (__this, __arguments, generator) => {
104
108
  });
105
109
  };
106
110
 
111
+ // node_modules/.pnpm/find-free-port@2.0.0/node_modules/find-free-port/index.js
112
+ var require_find_free_port = __commonJS({
113
+ "node_modules/.pnpm/find-free-port@2.0.0/node_modules/find-free-port/index.js"(exports, module) {
114
+ var net = __require("net");
115
+ function findFreePort(beg, ...rest) {
116
+ const p = rest.slice(0, rest.length - 1), cb = rest[rest.length - 1];
117
+ let [end, ip, cnt] = Array.from(p);
118
+ if (!ip && end && !/^\d+$/.test(end)) {
119
+ ip = end;
120
+ end = 65534;
121
+ } else {
122
+ if (end == null) {
123
+ end = 65534;
124
+ }
125
+ }
126
+ if (cnt == null) {
127
+ cnt = 1;
128
+ }
129
+ const retcb = cb;
130
+ const res = [];
131
+ const probe = function(ip2, port, cb2) {
132
+ const s = net.createConnection({ port, host: ip2 });
133
+ s.on("connect", function() {
134
+ s.end();
135
+ cb2(null, port + 1);
136
+ });
137
+ s.on("error", (err) => {
138
+ cb2(port);
139
+ });
140
+ };
141
+ var onprobe = function(port, nextPort) {
142
+ if (port) {
143
+ res.push(port);
144
+ if (res.length >= cnt) {
145
+ retcb(null, ...res);
146
+ } else {
147
+ setImmediate(() => probe(ip, port + 1, onprobe));
148
+ }
149
+ } else {
150
+ if (nextPort >= end) {
151
+ retcb(new Error("No available ports"));
152
+ } else {
153
+ setImmediate(() => probe(ip, nextPort, onprobe));
154
+ }
155
+ }
156
+ };
157
+ return probe(ip, beg, onprobe);
158
+ }
159
+ function findFreePortPmfy(beg, ...rest) {
160
+ const last = rest[rest.length - 1];
161
+ if (typeof last === "function") {
162
+ findFreePort(beg, ...rest);
163
+ } else {
164
+ return new Promise((resolve, reject) => {
165
+ findFreePort(beg, ...rest, (err, ...ports) => {
166
+ if (err)
167
+ reject(err);
168
+ else
169
+ resolve(ports);
170
+ });
171
+ });
172
+ }
173
+ }
174
+ module.exports = findFreePortPmfy;
175
+ }
176
+ });
177
+
107
178
  // src/lib/constants.ts
108
179
  var constants_default = {
109
180
  // default configs
@@ -152,6 +223,8 @@ var constants_default = {
152
223
  EDGE: "edge",
153
224
  EDGE_CHANNEL: "msedge",
154
225
  WEBKIT: "webkit",
226
+ MIN_PORT_RANGE: 49100,
227
+ MAX_PORT_RANGE: 6e4,
155
228
  // discovery browser launch arguments
156
229
  LAUNCH_ARGS: [
157
230
  // disable the translate popup and optimization downloads
@@ -785,6 +858,10 @@ var ConfigSchema = {
785
858
  type: "boolean",
786
859
  errorMessage: "Invalid config; useLambdaInternal must be true/false"
787
860
  },
861
+ useRemoteDiscovery: {
862
+ type: "boolean",
863
+ errorMessage: "Invalid config; useRemoteDiscovery must be true/false"
864
+ },
788
865
  useExtendedViewport: {
789
866
  type: "boolean",
790
867
  errorMessage: "Invalid config; useExtendedViewport must be true/false"
@@ -1971,7 +2048,32 @@ function validateCoordinates(coordString, pageHeight, pageWidth, snapshotName) {
1971
2048
  }
1972
2049
 
1973
2050
  // src/lib/server.ts
2051
+ var fp = require_find_free_port();
1974
2052
  var uploadDomToS3ViaEnv = process.env.USE_LAMBDA_INTERNAL || false;
2053
+ function findAvailablePort(server, startPort, log2) {
2054
+ return __async(this, null, function* () {
2055
+ let currentPort = startPort;
2056
+ try {
2057
+ yield server.listen({ port: currentPort });
2058
+ return currentPort;
2059
+ } catch (error) {
2060
+ if (error.code === "EADDRINUSE") {
2061
+ log2.debug(`Port ${currentPort} is in use, finding available port in range 49100-60000`);
2062
+ const availablePorts = yield fp(constants_default.MIN_PORT_RANGE, constants_default.MAX_PORT_RANGE);
2063
+ if (availablePorts.length > 0) {
2064
+ const freePort = availablePorts[0];
2065
+ yield server.listen({ port: freePort });
2066
+ log2.debug(`Found and started server on port ${freePort}`);
2067
+ return freePort;
2068
+ } else {
2069
+ throw new Error("No available ports found in range 49100-60000");
2070
+ }
2071
+ } else {
2072
+ throw error;
2073
+ }
2074
+ }
2075
+ });
2076
+ }
1975
2077
  var server_default = (ctx) => __async(void 0, null, function* () {
1976
2078
  const server = fastify__default.default({
1977
2079
  logger: {
@@ -2221,10 +2323,18 @@ var server_default = (ctx) => __async(void 0, null, function* () {
2221
2323
  return reply.code(replyCode).send(replyBody);
2222
2324
  }
2223
2325
  }));
2224
- yield server.listen({ port: ctx.options.port });
2225
- let { port } = server.addresses()[0];
2226
- process.env.SMARTUI_SERVER_ADDRESS = `http://localhost:${port}`;
2227
- process.env.CYPRESS_SMARTUI_SERVER_ADDRESS = `http://localhost:${port}`;
2326
+ if (ctx.sourceCommand && ctx.sourceCommand === "exec-start") {
2327
+ yield server.listen({ port: ctx.options.port });
2328
+ let { port } = server.addresses()[0];
2329
+ process.env.SMARTUI_SERVER_ADDRESS = `http://localhost:${port}`;
2330
+ process.env.CYPRESS_SMARTUI_SERVER_ADDRESS = `http://localhost:${port}`;
2331
+ ctx.log.debug(`Server started successfully on port ${port}`);
2332
+ } else {
2333
+ const actualPort = yield findAvailablePort(server, ctx.options.port, ctx.log);
2334
+ process.env.SMARTUI_SERVER_ADDRESS = `http://localhost:${actualPort}`;
2335
+ process.env.CYPRESS_SMARTUI_SERVER_ADDRESS = `http://localhost:${actualPort}`;
2336
+ ctx.log.debug(`Server started successfully on port ${actualPort}`);
2337
+ }
2228
2338
  return server;
2229
2339
  });
2230
2340
 
@@ -2369,7 +2479,7 @@ var authExec_default = (ctx) => {
2369
2479
  };
2370
2480
 
2371
2481
  // package.json
2372
- var version = "4.1.33";
2482
+ var version = "4.1.34";
2373
2483
  var package_default = {
2374
2484
  name: "@lambdatest/smartui-cli",
2375
2485
  version,
@@ -2425,6 +2535,7 @@ var package_default = {
2425
2535
  "simple-swizzle": "0.2.2"
2426
2536
  },
2427
2537
  devDependencies: {
2538
+ "find-free-port": "^2.0.0",
2428
2539
  typescript: "^5.3.2"
2429
2540
  }
2430
2541
  };
@@ -3110,6 +3221,7 @@ var ctx_default = (options) => {
3110
3221
  let buildNameObj;
3111
3222
  let allowDuplicateSnapshotNames = false;
3112
3223
  let useLambdaInternal = false;
3224
+ let useRemoteDiscovery = false;
3113
3225
  let useExtendedViewport = false;
3114
3226
  let loadDomContent = false;
3115
3227
  try {
@@ -3184,6 +3296,9 @@ var ctx_default = (options) => {
3184
3296
  if (config.useLambdaInternal) {
3185
3297
  useLambdaInternal = true;
3186
3298
  }
3299
+ if (config.useRemoteDiscovery) {
3300
+ useRemoteDiscovery = true;
3301
+ }
3187
3302
  if (config.useExtendedViewport) {
3188
3303
  useExtendedViewport = true;
3189
3304
  }
@@ -3219,6 +3334,7 @@ var ctx_default = (options) => {
3219
3334
  requestHeaders: config.requestHeaders || {},
3220
3335
  allowDuplicateSnapshotNames,
3221
3336
  useLambdaInternal,
3337
+ useRemoteDiscovery,
3222
3338
  useExtendedViewport,
3223
3339
  loadDomContent,
3224
3340
  approvalThreshold: config.approvalThreshold,
@@ -3268,6 +3384,7 @@ var ctx_default = (options) => {
3268
3384
  isSnapshotCaptured: false,
3269
3385
  sessionCapabilitiesMap: /* @__PURE__ */ new Map(),
3270
3386
  buildToSnapshotCountMap: /* @__PURE__ */ new Map(),
3387
+ sessionIdToSnapshotNameMap: /* @__PURE__ */ new Map(),
3271
3388
  fetchResultsForBuild: new Array(),
3272
3389
  orgId: 0,
3273
3390
  userId: 0,
@@ -3425,7 +3542,7 @@ var createBuildExec_default = (ctx) => {
3425
3542
  }
3426
3543
  task.output = chalk__default.default.gray(`build id: ${resp.data.buildId}`);
3427
3544
  task.title = "SmartUI build created";
3428
- if (ctx2.env.USE_REMOTE_DISCOVERY) {
3545
+ if (ctx2.env.USE_REMOTE_DISCOVERY || ctx2.config.useRemoteDiscovery) {
3429
3546
  task.output += chalk__default.default.gray(`
3430
3547
  Using remote discovery for this build`);
3431
3548
  }
@@ -4118,13 +4235,13 @@ function processSnapshot(snapshot, ctx) {
4118
4235
  for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
4119
4236
  switch (key) {
4120
4237
  case "id":
4121
- selectors.push(...value.map((e) => "#" + e));
4238
+ selectors.push(...value.map((e) => e.startsWith("#") ? e : "#" + e));
4122
4239
  break;
4123
4240
  case "class":
4124
- selectors.push(...value.map((e) => "." + e));
4241
+ selectors.push(...value.map((e) => e.startsWith(".") ? e : "." + e));
4125
4242
  break;
4126
4243
  case "xpath":
4127
- selectors.push(...value.map((e) => "xpath=" + e));
4244
+ selectors.push(...value.map((e) => e.startsWith("xpath=") ? e : "xpath=" + e));
4128
4245
  break;
4129
4246
  case "cssSelector":
4130
4247
  selectors.push(...value);
@@ -4239,14 +4356,7 @@ function processSnapshot(snapshot, ctx) {
4239
4356
  }
4240
4357
  }
4241
4358
  }
4242
- if (processedOptions.element) {
4243
- let l = yield page.locator(processedOptions.element).all();
4244
- if (l.length === 0) {
4245
- throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${processedOptions.element}`);
4246
- } else if (l.length > 1) {
4247
- throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, multiple elements found for selector ${processedOptions.element}`);
4248
- }
4249
- } else if (selectors.length) {
4359
+ if (selectors.length) {
4250
4360
  let height = 0;
4251
4361
  height = yield page.evaluate(() => {
4252
4362
  const DEFAULT_HEIGHT = 16384;
@@ -4271,7 +4381,6 @@ function processSnapshot(snapshot, ctx) {
4271
4381
  return Math.max(...measurements);
4272
4382
  });
4273
4383
  ctx.log.debug(`Calculated content height: ${height}`);
4274
- let locators = [];
4275
4384
  if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewportString]))
4276
4385
  processedOptions[ignoreOrSelectBoxes][viewportString] = [];
4277
4386
  for (const selector of selectors) {
@@ -4294,44 +4403,75 @@ function processSnapshot(snapshot, ctx) {
4294
4403
  if (renderViewports.length > 1) {
4295
4404
  optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, coordinates may not be accurate for multiple viewports`);
4296
4405
  }
4297
- const coordinateElement = __spreadValues({
4406
+ __spreadValues({
4298
4407
  type: "coordinates"
4299
4408
  }, validation.coords);
4300
- locators.push(coordinateElement);
4301
- continue;
4302
- }
4303
- let l = yield page.locator(selector).all();
4304
- if (l.length === 0) {
4305
- optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
4306
4409
  continue;
4307
- }
4308
- locators.push(...l);
4309
- }
4310
- for (const locator of locators) {
4311
- if (locator && typeof locator === "object" && locator.hasOwnProperty("type") && locator.type === "coordinates") {
4312
- const coordLocator = locator;
4313
- const { top, bottom, left, right } = coordLocator;
4314
- processedOptions[ignoreOrSelectBoxes][viewportString].push({
4315
- left,
4316
- top,
4317
- right,
4318
- bottom
4319
- });
4320
- continue;
4321
- }
4322
- let bb = yield locator.boundingBox();
4323
- if (bb) {
4324
- const top = bb.y;
4325
- const bottom = bb.y + bb.height;
4326
- if (top <= height && bottom <= height) {
4327
- processedOptions[ignoreOrSelectBoxes][viewportString].push({
4328
- left: bb.x,
4329
- top,
4330
- right: bb.x + bb.width,
4331
- bottom
4332
- });
4410
+ } else {
4411
+ const isXPath = selector.startsWith("xpath=");
4412
+ const selectorValue = isXPath ? selector.substring(6) : selector;
4413
+ const boxes = yield page.evaluate(({ selectorValue: selectorValue2, isXPath: isXPath2 }) => {
4414
+ try {
4415
+ const DEFAULT_HEIGHT = 16384;
4416
+ const DEFAULT_WIDTH = 7680;
4417
+ const body = document.body;
4418
+ const html = document.documentElement;
4419
+ let pageHeight;
4420
+ let pageWidth;
4421
+ if (!body || !html) {
4422
+ pageHeight = DEFAULT_HEIGHT;
4423
+ pageWidth = DEFAULT_WIDTH;
4424
+ } else {
4425
+ const measurements = [
4426
+ (body == null ? void 0 : body.scrollHeight) || 0,
4427
+ (body == null ? void 0 : body.offsetHeight) || 0,
4428
+ (html == null ? void 0 : html.clientHeight) || 0,
4429
+ (html == null ? void 0 : html.scrollHeight) || 0,
4430
+ (html == null ? void 0 : html.offsetHeight) || 0
4431
+ ];
4432
+ const allMeasurementsInvalid = measurements.every((measurement) => !measurement);
4433
+ if (allMeasurementsInvalid) {
4434
+ pageHeight = DEFAULT_HEIGHT;
4435
+ } else {
4436
+ pageHeight = Math.max(...measurements);
4437
+ }
4438
+ const measurementsWidth = [
4439
+ (body == null ? void 0 : body.scrollWidth) || 0,
4440
+ (body == null ? void 0 : body.offsetWidth) || 0,
4441
+ (html == null ? void 0 : html.clientWidth) || 0,
4442
+ (html == null ? void 0 : html.scrollWidth) || 0,
4443
+ (html == null ? void 0 : html.offsetWidth) || 0
4444
+ ];
4445
+ const allMeasurementsInvalidWidth = measurementsWidth.every((measurement) => !measurement);
4446
+ if (allMeasurementsInvalidWidth) {
4447
+ pageWidth = DEFAULT_WIDTH;
4448
+ } else {
4449
+ pageWidth = Math.max(...measurementsWidth);
4450
+ }
4451
+ }
4452
+ let elements = [];
4453
+ if (isXPath2) {
4454
+ const xpathResult = document.evaluate(
4455
+ selectorValue2,
4456
+ document,
4457
+ null,
4458
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
4459
+ null
4460
+ );
4461
+ for (let i = 0; i < xpathResult.snapshotLength; i++) {
4462
+ elements.push(xpathResult.snapshotItem(i));
4463
+ }
4464
+ } else {
4465
+ elements = Array.from(document.querySelectorAll(selectorValue2));
4466
+ }
4467
+ return elements;
4468
+ } catch (error) {
4469
+ }
4470
+ }, { selectorValue, isXPath });
4471
+ if (boxes && boxes.length >= 1) {
4472
+ processedOptions[ignoreOrSelectBoxes][viewportString].push(...boxes);
4333
4473
  } else {
4334
- ctx.log.debug(`Bounding box for selector skipped due to exceeding height: ${JSON.stringify({ top, bottom, height })}`);
4474
+ optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
4335
4475
  }
4336
4476
  }
4337
4477
  }
@@ -4616,8 +4756,24 @@ var Queue = class {
4616
4756
  this.ctx.log.info(`Processing Snapshot: ${snapshot == null ? void 0 : snapshot.name}`);
4617
4757
  }
4618
4758
  if (!this.ctx.config.delayedUpload && snapshot && snapshot.name && this.snapshotNames.includes(snapshot.name) && !this.ctx.config.allowDuplicateSnapshotNames) {
4619
- drop = true;
4620
- this.ctx.log.info(`Skipping duplicate SmartUI snapshot '${snapshot.name}'. To capture duplicate screenshots, please set the 'allowDuplicateSnapshotNames' or 'delayedUpload' configuration as true in your config file.`);
4759
+ if (this.ctx.sessionIdToSnapshotNameMap && snapshot.options && snapshot.options.sessionId) {
4760
+ if (this.ctx.sessionIdToSnapshotNameMap.has(snapshot.options.sessionId)) {
4761
+ console.log(`snapshot.options.sessionId`, snapshot.options.sessionId, `this.ctx.sessionIdToSnapshotNameMap`, JSON.stringify([...this.ctx.sessionIdToSnapshotNameMap]));
4762
+ const existingNames = this.ctx.sessionIdToSnapshotNameMap.get(snapshot.options.sessionId) || [];
4763
+ if (existingNames.includes(snapshot.name)) {
4764
+ drop = true;
4765
+ this.ctx.log.info(`Skipping123 duplicate SmartUI snapshot '${snapshot.name}'. To capture duplicate screenshots, please set the 'allowDuplicateSnapshotNames' or 'delayedUpload' configuration as true in your config file.`);
4766
+ } else {
4767
+ existingNames.push(snapshot.name);
4768
+ this.ctx.sessionIdToSnapshotNameMap.set(snapshot.options.sessionId, existingNames);
4769
+ }
4770
+ } else {
4771
+ this.ctx.sessionIdToSnapshotNameMap.set(snapshot.options.sessionId, [snapshot.name]);
4772
+ }
4773
+ } else {
4774
+ drop = true;
4775
+ this.ctx.log.info(`Skipping duplicate SmartUI snapshot '${snapshot.name}'. To capture duplicate screenshots, please set the 'allowDuplicateSnapshotNames' or 'delayedUpload' configuration as true in your config file.`);
4776
+ }
4621
4777
  }
4622
4778
  if (this.ctx.config.delayedUpload && snapshot && snapshot.name && this.snapshotNames.includes(snapshot.name)) {
4623
4779
  drop = this.filterExistingVariants(snapshot, this.ctx.config);
@@ -4644,7 +4800,7 @@ var Queue = class {
4644
4800
  }
4645
4801
  }
4646
4802
  let processedSnapshot, warnings, discoveryErrors;
4647
- if (this.ctx.env.USE_REMOTE_DISCOVERY) {
4803
+ if (this.ctx.env.USE_REMOTE_DISCOVERY || this.ctx.config.useRemoteDiscovery) {
4648
4804
  this.ctx.log.debug(`Using remote discovery`);
4649
4805
  let result = yield prepareSnapshot(snapshot, this.ctx);
4650
4806
  processedSnapshot = result.processedSnapshot;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdatest/smartui-cli",
3
- "version": "4.1.33",
3
+ "version": "4.1.34",
4
4
  "description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
5
5
  "files": [
6
6
  "dist/**/*"
@@ -47,6 +47,7 @@
47
47
  "simple-swizzle": "0.2.2"
48
48
  },
49
49
  "devDependencies": {
50
+ "find-free-port": "^2.0.0",
50
51
  "typescript": "^5.3.2"
51
52
  },
52
53
  "scripts": {