@lambdatest/smartui-cli 2.0.4 → 2.0.5

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 +280 -109
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -8,14 +8,14 @@ var chalk = require('chalk');
8
8
  var path2 = require('path');
9
9
  var fastify = require('fastify');
10
10
  var fs = require('fs');
11
- var winston = require('winston');
11
+ var test = require('@playwright/test');
12
12
  var Ajv = require('ajv');
13
13
  var addErrors = require('ajv-errors');
14
+ var winston = require('winston');
14
15
  var FormData = require('form-data');
15
16
  var axios = require('axios');
16
17
  var child_process = require('child_process');
17
18
  var spawn = require('cross-spawn');
18
- var test = require('@playwright/test');
19
19
 
20
20
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
21
 
@@ -66,6 +66,271 @@ var __async = (__this, __arguments, generator) => {
66
66
  step((generator = generator.apply(__this, __arguments)).next());
67
67
  });
68
68
  };
69
+ var MIN_VIEWPORT_HEIGHT = 1080;
70
+ var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function* () {
71
+ let options = snapshot.options;
72
+ let warnings = [];
73
+ let processedOptions = {};
74
+ if (options && Object.keys(options).length !== 0) {
75
+ ctx.log.debug(`Processing options: ${JSON.stringify(options)}`);
76
+ if (options.ignoreDOM && Object.keys(options.ignoreDOM).length !== 0 || options.selectDOM && Object.keys(options.selectDOM).length !== 0) {
77
+ if (!ctx.browser)
78
+ ctx.browser = yield test.chromium.launch({ headless: true });
79
+ let ignoreOrSelectDOM;
80
+ let ignoreOrSelectBoxes;
81
+ if (options.ignoreDOM && Object.keys(options.ignoreDOM).length !== 0) {
82
+ processedOptions.ignoreBoxes = {};
83
+ ignoreOrSelectDOM = "ignoreDOM";
84
+ ignoreOrSelectBoxes = "ignoreBoxes";
85
+ } else {
86
+ processedOptions.selectBoxes = {};
87
+ ignoreOrSelectDOM = "selectDOM";
88
+ ignoreOrSelectBoxes = "selectBoxes";
89
+ }
90
+ let selectors = [];
91
+ for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
92
+ switch (key) {
93
+ case "id":
94
+ selectors.push(...value.map((e) => "#" + e));
95
+ break;
96
+ case "class":
97
+ selectors.push(...value.map((e) => "." + e));
98
+ break;
99
+ case "xpath":
100
+ selectors.push(...value.map((e) => "xpath=" + e));
101
+ break;
102
+ case "cssSelector":
103
+ selectors.push(...value);
104
+ break;
105
+ }
106
+ }
107
+ for (const vp of ctx.webConfig.viewports) {
108
+ const page = yield ctx.browser.newPage({ viewport: { width: vp.width, height: vp.height || MIN_VIEWPORT_HEIGHT } });
109
+ yield page.setContent(snapshot.dom.html);
110
+ let viewport = `${vp.width}${vp.height ? "x" + vp.height : ""}`;
111
+ if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewport]))
112
+ processedOptions[ignoreOrSelectBoxes][viewport] = [];
113
+ let locators = [];
114
+ let boxes = [];
115
+ for (const selector of selectors) {
116
+ let l = yield page.locator(selector).all();
117
+ if (l.length === 0) {
118
+ warnings.push(`For snapshot ${snapshot.name}, no element found for selector ${selector}`);
119
+ continue;
120
+ }
121
+ locators.push(...l);
122
+ }
123
+ for (const locator of locators) {
124
+ let bb = yield locator.boundingBox();
125
+ if (bb)
126
+ boxes.push({
127
+ left: bb.x,
128
+ top: bb.y,
129
+ right: bb.x + bb.width,
130
+ bottom: bb.y + bb.height
131
+ });
132
+ }
133
+ processedOptions[ignoreOrSelectBoxes][viewport].push(...boxes);
134
+ yield page.close();
135
+ }
136
+ }
137
+ }
138
+ warnings.push(...snapshot.dom.warnings);
139
+ return {
140
+ processedSnapshot: {
141
+ name: snapshot.name,
142
+ url: snapshot.url,
143
+ dom: Buffer.from(snapshot.dom.html).toString("base64"),
144
+ options: processedOptions
145
+ },
146
+ warnings
147
+ };
148
+ });
149
+ var ajv = new Ajv__default.default({ allErrors: true });
150
+ ajv.addFormat("web-url", {
151
+ type: "string",
152
+ validate: (url) => {
153
+ const urlPattern = new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$", "i");
154
+ return urlPattern.test(url.trim());
155
+ }
156
+ });
157
+ addErrors__default.default(ajv);
158
+ var ConfigSchema = {
159
+ type: "object",
160
+ properties: {
161
+ web: {
162
+ type: "object",
163
+ properties: {
164
+ browsers: {
165
+ type: "array",
166
+ items: { type: "string", enum: ["chrome", "firefox", "edge", "safari"] },
167
+ uniqueItems: true,
168
+ maxItems: 4,
169
+ errorMessage: "Invalid config; allowed browsers - chrome, firefox, edge, safari"
170
+ },
171
+ viewports: {
172
+ type: "array",
173
+ items: {
174
+ type: "array",
175
+ oneOf: [
176
+ {
177
+ items: [{ type: "number", minimum: 320, maximum: 7680 }],
178
+ minItems: 1,
179
+ maxItems: 1
180
+ },
181
+ {
182
+ items: [
183
+ { type: "number", minimum: 320, maximum: 7680 },
184
+ { type: "number", minimum: 320, maximum: 7680 }
185
+ ],
186
+ minItems: 2,
187
+ maxItems: 2
188
+ }
189
+ ],
190
+ errorMessage: "Invalid config; width/height must be >= 320 and <= 7680"
191
+ },
192
+ uniqueItems: true,
193
+ maxItems: 5,
194
+ errorMessage: "Invalid config; max unique viewports allowed - 5"
195
+ },
196
+ waitForPageRender: {
197
+ type: "number",
198
+ minimum: 0,
199
+ maximum: 3e5,
200
+ errorMessage: "Invalid config; waitForPageRender must be > 0 and <= 300000"
201
+ },
202
+ waitForTimeout: {
203
+ type: "number",
204
+ minimum: 0,
205
+ maximum: 3e4,
206
+ errorMessage: "Invalid config; waitForTimeout must be > 0 and <= 30000"
207
+ }
208
+ },
209
+ required: ["browsers", "viewports"],
210
+ additionalProperties: false
211
+ }
212
+ },
213
+ required: ["web"],
214
+ additionalProperties: false
215
+ };
216
+ var WebStaticConfigSchema = {
217
+ type: "array",
218
+ items: {
219
+ type: "object",
220
+ properties: {
221
+ name: {
222
+ type: "string",
223
+ minLength: 1,
224
+ errorMessage: "name is mandatory and cannot be empty"
225
+ },
226
+ url: {
227
+ type: "string",
228
+ format: "web-url",
229
+ errorMessage: "url is mandatory and must be a valid web URL"
230
+ },
231
+ waitForTimeout: {
232
+ type: "number",
233
+ nullable: true,
234
+ minimum: 0,
235
+ maximum: 3e4,
236
+ errorMessage: "waitForTimeout must be > 0 and <= 30000"
237
+ }
238
+ },
239
+ required: ["name", "url"],
240
+ additionalProperties: false
241
+ },
242
+ uniqueItems: true
243
+ };
244
+ var SnapshotSchema = {
245
+ type: "object",
246
+ properties: {
247
+ name: {
248
+ type: "string",
249
+ minLength: 1,
250
+ errorMessage: "Invalid snapshot; name is mandatory and cannot be empty"
251
+ },
252
+ url: {
253
+ type: "string",
254
+ format: "web-url",
255
+ errorMessage: "Invalid snapshot; url is mandatory and must be a valid web URL"
256
+ },
257
+ dom: {
258
+ type: "object"
259
+ },
260
+ options: {
261
+ type: "object",
262
+ properties: {
263
+ ignoreDOM: {
264
+ type: "object",
265
+ properties: {
266
+ id: {
267
+ type: "array",
268
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; id cannot be empty or have semicolon" },
269
+ uniqueItems: true,
270
+ errorMessage: "Invalid snapshot options; id array must have unique items"
271
+ },
272
+ class: {
273
+ type: "array",
274
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; class cannot be empty or have semicolon" },
275
+ uniqueItems: true,
276
+ errorMessage: "Invalid snapshot options; class array must have unique items"
277
+ },
278
+ cssSelector: {
279
+ type: "array",
280
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; cssSelector cannot be empty or have semicolon" },
281
+ uniqueItems: true,
282
+ errorMessage: "Invalid snapshot options; cssSelector array must have unique items"
283
+ },
284
+ xpath: {
285
+ type: "array",
286
+ items: { type: "string", minLength: 1 },
287
+ uniqueItems: true,
288
+ errorMessage: "Invalid snapshot options; xpath array must have unique and non-empty items"
289
+ }
290
+ }
291
+ },
292
+ selectDOM: {
293
+ type: "object",
294
+ properties: {
295
+ id: {
296
+ type: "array",
297
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; id cannot be empty or have semicolon" },
298
+ uniqueItems: true,
299
+ errorMessage: "Invalid snapshot options; id array must have unique items"
300
+ },
301
+ class: {
302
+ type: "array",
303
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; class cannot be empty or have semicolon" },
304
+ uniqueItems: true,
305
+ errorMessage: "Invalid snapshot options; class array must have unique items"
306
+ },
307
+ cssSelector: {
308
+ type: "array",
309
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; cssSelector cannot be empty or have semicolon" },
310
+ uniqueItems: true,
311
+ errorMessage: "Invalid snapshot options; cssSelector array must have unique items"
312
+ },
313
+ xpath: {
314
+ type: "array",
315
+ items: { type: "string", minLength: 1 },
316
+ uniqueItems: true,
317
+ errorMessage: "Invalid snapshot options; xpath array must have unique and non-empty items"
318
+ }
319
+ }
320
+ }
321
+ },
322
+ additionalProperties: false
323
+ }
324
+ },
325
+ required: ["name", "url", "dom", "options"],
326
+ additionalProperties: false,
327
+ errorMessage: "Invalid snapshot"
328
+ };
329
+ var validateConfig = ajv.compile(ConfigSchema);
330
+ var validateWebStaticConfig = ajv.compile(WebStaticConfigSchema);
331
+ var validateSnapshot = ajv.compile(SnapshotSchema);
332
+
333
+ // src/lib/server.ts
69
334
  var server_default = (ctx) => __async(void 0, null, function* () {
70
335
  const server = fastify__default.default({ logger: false, bodyLimit: 1e7 });
71
336
  const opts = {};
@@ -77,15 +342,17 @@ var server_default = (ctx) => __async(void 0, null, function* () {
77
342
  reply.code(200).send({ data: { dom: SMARTUI_DOM } });
78
343
  });
79
344
  server.post("/snapshot", opts, (request, reply) => __async(void 0, null, function* () {
80
- let { snapshot, testType } = request.body;
81
- snapshot.dom = Buffer.from(snapshot.dom).toString("base64");
82
345
  try {
83
- yield ctx.client.uploadSnapshot(ctx.build.id, snapshot, testType, ctx.log);
346
+ let { snapshot, testType } = request.body;
347
+ if (!validateSnapshot(snapshot))
348
+ throw new Error(validateSnapshot.errors[0].message);
349
+ let { processedSnapshot, warnings } = yield processSnapshot_default(snapshot, ctx);
350
+ yield ctx.client.uploadSnapshot(ctx.build.id, processedSnapshot, testType, ctx.log);
351
+ ctx.totalSnapshots++;
352
+ reply.code(200).send({ data: { message: "success", warnings } });
84
353
  } catch (error) {
85
- reply.code(500).send({ error: { message: error.message } });
354
+ return reply.code(500).send({ error: { message: error.message } });
86
355
  }
87
- ctx.totalSnapshots++;
88
- reply.code(200).send({ data: { message: "success" } });
89
356
  }));
90
357
  yield server.listen({ port: 8080 });
91
358
  let { port } = server.addresses()[0];
@@ -237,104 +504,7 @@ function createWebStaticConfig(filepath) {
237
504
  }
238
505
 
239
506
  // package.json
240
- var version = "2.0.4";
241
- var ajv = new Ajv__default.default({ allErrors: true });
242
- ajv.addFormat("web-url", {
243
- type: "string",
244
- validate: (url) => {
245
- const urlPattern = new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$", "i");
246
- return urlPattern.test(url.trim());
247
- }
248
- });
249
- addErrors__default.default(ajv);
250
- var ConfigSchema = {
251
- type: "object",
252
- properties: {
253
- web: {
254
- type: "object",
255
- properties: {
256
- browsers: {
257
- type: "array",
258
- items: { type: "string", enum: ["chrome", "firefox", "edge", "safari"] },
259
- uniqueItems: true,
260
- maxItems: 4,
261
- errorMessage: "Invalid config; allowed browsers - chrome, firefox, edge, safari"
262
- },
263
- viewports: {
264
- type: "array",
265
- items: {
266
- type: "array",
267
- oneOf: [
268
- {
269
- items: [{ type: "number", minimum: 320, maximum: 7680 }],
270
- minItems: 1,
271
- maxItems: 1
272
- },
273
- {
274
- items: [
275
- { type: "number", minimum: 320, maximum: 7680 },
276
- { type: "number", minimum: 320, maximum: 7680 }
277
- ],
278
- minItems: 2,
279
- maxItems: 2
280
- }
281
- ],
282
- errorMessage: "Invalid config; width/height must be >= 320 and <= 7680"
283
- },
284
- uniqueItems: true,
285
- maxItems: 5,
286
- errorMessage: "Invalid config; max unique viewports allowed - 5"
287
- },
288
- waitForPageRender: {
289
- type: "number",
290
- minimum: 0,
291
- maximum: 3e5,
292
- errorMessage: "Invalid config; waitForPageRender must be > 0 and <= 300000"
293
- },
294
- waitForTimeout: {
295
- type: "number",
296
- minimum: 0,
297
- maximum: 3e4,
298
- errorMessage: "Invalid config; waitForTimeout must be > 0 and <= 30000"
299
- }
300
- },
301
- required: ["browsers", "viewports"],
302
- additionalProperties: false
303
- }
304
- },
305
- required: ["web"],
306
- additionalProperties: false
307
- };
308
- var WebStaticConfigSchema = {
309
- type: "array",
310
- items: {
311
- type: "object",
312
- properties: {
313
- name: {
314
- type: "string",
315
- minLength: 1,
316
- errorMessage: "name is mandatory and cannot be empty"
317
- },
318
- url: {
319
- type: "string",
320
- format: "web-url",
321
- errorMessage: "url is mandatory and must be a valid web URL"
322
- },
323
- waitForTimeout: {
324
- type: "number",
325
- nullable: true,
326
- minimum: 0,
327
- maximum: 3e4,
328
- errorMessage: "waitForTimeout must be > 0 and <= 30000"
329
- }
330
- },
331
- required: ["name", "url"],
332
- additionalProperties: false
333
- },
334
- uniqueItems: true
335
- };
336
- var validateConfig = ajv.compile(ConfigSchema);
337
- var validateWebStaticConfig = ajv.compile(WebStaticConfigSchema);
507
+ var version = "2.0.5";
338
508
  var HTTP_SCHEME = "https:";
339
509
  var HTTP_SCHEME_PREFIX = "https://";
340
510
  var WWW = "www.";
@@ -682,7 +852,7 @@ var finalizeBuild_default = (ctx) => {
682
852
  var command = new commander.Command();
683
853
  command.name("exec").description("Run test commands around SmartUI").argument("<command...>", "Command supplied for running tests").action(function(execCommand, _, command3) {
684
854
  return __async(this, null, function* () {
685
- var _a;
855
+ var _a, _b;
686
856
  let ctx = ctx_default(command3.optsWithGlobals());
687
857
  if (!which__default.default.sync(execCommand[0], { nothrow: true })) {
688
858
  console.log(`Error: Command not found "${execCommand[0]}"`);
@@ -716,6 +886,7 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
716
886
  console.log("\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/");
717
887
  } finally {
718
888
  yield (_a = ctx.server) == null ? void 0 : _a.close();
889
+ yield (_b = ctx.browser) == null ? void 0 : _b.close();
719
890
  }
720
891
  });
721
892
  });
@@ -738,7 +909,7 @@ var BROWSER_FIREFOX = "firefox";
738
909
  var BROWSER_EDGE = "edge";
739
910
  var EDGE_CHANNEL = "msedge";
740
911
  var PW_WEBKIT = "webkit";
741
- var MIN_RESOLUTION_HEIGHT = 320;
912
+ var MIN_VIEWPORT_HEIGHT2 = 1080;
742
913
  function captureScreenshots(ctx, screenshots) {
743
914
  return __async(this, null, function* () {
744
915
  var _a;
@@ -782,7 +953,7 @@ function captureScreenshots(ctx, screenshots) {
782
953
  let { width, height } = ctx.webConfig.viewports[k];
783
954
  let ssName = `${browserName}-${width}x${height}-${screenshotId}.png`;
784
955
  let ssPath = `screenshots/${screenshotId}/${ssName}.png`;
785
- yield page.setViewportSize({ width, height: height || MIN_RESOLUTION_HEIGHT });
956
+ yield page.setViewportSize({ width, height: height || MIN_VIEWPORT_HEIGHT2 });
786
957
  if (height === 0)
787
958
  yield page.evaluate(scrollToBottomAndBackToTop);
788
959
  yield page.waitForTimeout(screenshot.waitForTimeout || 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdatest/smartui-cli",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
5
5
  "files": [
6
6
  "dist/**/*"