@lambdatest/smartui-cli 2.0.8 → 3.0.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.
Files changed (2) hide show
  1. package/dist/index.cjs +615 -236
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -7,7 +7,7 @@ var listr2 = require('listr2');
7
7
  var chalk = require('chalk');
8
8
  var path2 = require('path');
9
9
  var fastify = require('fastify');
10
- var fs2 = require('fs');
10
+ var fs4 = require('fs');
11
11
  var test = require('@playwright/test');
12
12
  var Ajv = require('ajv');
13
13
  var addErrors = require('ajv-errors');
@@ -23,7 +23,7 @@ var which__default = /*#__PURE__*/_interopDefault(which);
23
23
  var chalk__default = /*#__PURE__*/_interopDefault(chalk);
24
24
  var path2__default = /*#__PURE__*/_interopDefault(path2);
25
25
  var fastify__default = /*#__PURE__*/_interopDefault(fastify);
26
- var fs2__default = /*#__PURE__*/_interopDefault(fs2);
26
+ var fs4__default = /*#__PURE__*/_interopDefault(fs4);
27
27
  var Ajv__default = /*#__PURE__*/_interopDefault(Ajv);
28
28
  var addErrors__default = /*#__PURE__*/_interopDefault(addErrors);
29
29
  var FormData__default = /*#__PURE__*/_interopDefault(FormData);
@@ -66,9 +66,241 @@ var __async = (__this, __arguments, generator) => {
66
66
  step((generator = generator.apply(__this, __arguments)).next());
67
67
  });
68
68
  };
69
+
70
+ // src/lib/constants.ts
71
+ var constants_default = {
72
+ // default configs
73
+ DEFAULT_CONFIG: {
74
+ web: {
75
+ browsers: [
76
+ "chrome",
77
+ "firefox",
78
+ "safari",
79
+ "edge"
80
+ ],
81
+ viewports: [
82
+ [1920],
83
+ [1366],
84
+ [1028]
85
+ ]
86
+ },
87
+ mobile: {
88
+ devices: [
89
+ "iPhone 14",
90
+ "Galaxy S24"
91
+ ],
92
+ fullPage: true,
93
+ orientation: "portrait"
94
+ },
95
+ waitForTimeout: 1e3,
96
+ enableJavaScript: false,
97
+ allowedHostnames: []
98
+ },
99
+ DEFAULT_WEB_STATIC_CONFIG: [
100
+ {
101
+ "name": "lambdatest-home-page",
102
+ "url": "https://www.lambdatest.com",
103
+ "waitForTimeout": 1e3
104
+ },
105
+ {
106
+ "name": "example-page",
107
+ "url": "https://example.com/"
108
+ }
109
+ ],
110
+ // browsers
111
+ CHROME: "chrome",
112
+ SAFARI: "safari",
113
+ FIREFOX: "firefox",
114
+ EDGE: "edge",
115
+ EDGE_CHANNEL: "msedge",
116
+ PW_WEBKIT: "webkit",
117
+ // viewports
118
+ MIN_VIEWPORT_HEIGHT: 1080,
119
+ // mobile
120
+ MOBILE_OS_ANDROID: "android",
121
+ MOBILE_OS_IOS: "ios",
122
+ MOBILE_ORIENTATION_PORTRAIT: "portrait",
123
+ MOBILE_ORIENTATION_LANDSCAPE: "landscape",
124
+ // CI
125
+ GITHUB_API_HOST: "https://api.github.com",
126
+ SUPPORTED_MOBILE_DEVICES: {
127
+ "Blackberry KEY2 LE": { os: "android", viewport: { width: 412, height: 618 } },
128
+ "Galaxy A12": { os: "android", viewport: { width: 360, height: 800 } },
129
+ "Galaxy A21s": { os: "android", viewport: { width: 412, height: 915 } },
130
+ "Galaxy A22": { os: "android", viewport: { width: 358, height: 857 } },
131
+ "Galaxy A31": { os: "android", viewport: { width: 412, height: 915 } },
132
+ "Galaxy A32": { os: "android", viewport: { width: 412, height: 915 } },
133
+ "Galaxy A51": { os: "android", viewport: { width: 412, height: 915 } },
134
+ "Galaxy A7": { os: "android", viewport: { width: 412, height: 846 } },
135
+ "Galaxy A70": { os: "android", viewport: { width: 412, height: 915 } },
136
+ "Galaxy A8": { os: "android", viewport: { width: 360, height: 740 } },
137
+ "Galaxy A8 Plus": { os: "android", viewport: { width: 412, height: 846 } },
138
+ "Galaxy J7 Prime": { os: "android", viewport: { width: 360, height: 640 } },
139
+ "Galaxy M12": { os: "android", viewport: { width: 412, height: 915 } },
140
+ "Galaxy M31": { os: "android", viewport: { width: 412, height: 892 } },
141
+ "Galaxy Note10": { os: "android", viewport: { width: 412, height: 869 } },
142
+ "Galaxy Note10 Plus": { os: "android", viewport: { width: 412, height: 869 } },
143
+ "Galaxy Note20": { os: "android", viewport: { width: 412, height: 915 } },
144
+ "Galaxy Note20 Ultra": { os: "android", viewport: { width: 412, height: 869 } },
145
+ "Galaxy S10": { os: "android", viewport: { width: 360, height: 760 } },
146
+ "Galaxy S10 Plus": { os: "android", viewport: { width: 412, height: 869 } },
147
+ "Galaxy S10e": { os: "android", viewport: { width: 412, height: 740 } },
148
+ "Galaxy S20": { os: "android", viewport: { width: 360, height: 800 } },
149
+ "Galaxy S20 FE": { os: "android", viewport: { width: 412, height: 914 } },
150
+ "Galaxy S20 Ultra": { os: "android", viewport: { width: 412, height: 915 } },
151
+ "Galaxy S20 Plus": { os: "android", viewport: { width: 384, height: 854 } },
152
+ "Galaxy S21": { os: "android", viewport: { width: 360, height: 800 } },
153
+ "Galaxy S21 FE": { os: "android", viewport: { width: 360, height: 780 } },
154
+ "Galaxy S21 Ultra": { os: "android", viewport: { width: 384, height: 854 } },
155
+ "Galaxy S21 Plus": { os: "android", viewport: { width: 360, height: 800 } },
156
+ "Galaxy S22": { os: "android", viewport: { width: 360, height: 780 } },
157
+ "Galaxy S22 Ultra": { os: "android", viewport: { width: 384, height: 854 } },
158
+ "Galaxy S23": { os: "android", viewport: { width: 360, height: 645 } },
159
+ "Galaxy S23 Plus": { os: "android", viewport: { width: 360, height: 648 } },
160
+ "Galaxy S23 Ultra": { os: "android", viewport: { width: 384, height: 689 } },
161
+ "Galaxy S24": { os: "android", viewport: { width: 360, height: 780 } },
162
+ "Galaxy S24 Plus": { os: "android", viewport: { width: 384, height: 832 } },
163
+ "Galaxy S24 Ultra": { os: "android", viewport: { width: 384, height: 832 } },
164
+ "Galaxy S7": { os: "android", viewport: { width: 360, height: 640 } },
165
+ "Galaxy S7 Edge": { os: "android", viewport: { width: 360, height: 640 } },
166
+ "Galaxy S8": { os: "android", viewport: { width: 360, height: 740 } },
167
+ "Galaxy S8 Plus": { os: "android", viewport: { width: 360, height: 740 } },
168
+ "Galaxy S9": { os: "android", viewport: { width: 360, height: 740 } },
169
+ "Galaxy S9 Plus": { os: "android", viewport: { width: 360, height: 740 } },
170
+ "Galaxy Tab A7 Lite": { os: "android", viewport: { width: 534, height: 894 } },
171
+ "Galaxy Tab A8": { os: "android", viewport: { width: 800, height: 1280 } },
172
+ "Galaxy Tab S3": { os: "android", viewport: { width: 1024, height: 768 } },
173
+ "Galaxy Tab S4": { os: "android", viewport: { width: 712, height: 1138 } },
174
+ "Galaxy Tab S7": { os: "android", viewport: { width: 800, height: 1192 } },
175
+ "Galaxy Tab S8": { os: "android", viewport: { width: 753, height: 1205 } },
176
+ "Galaxy Tab S8 Plus": { os: "android", viewport: { width: 825, height: 1318 } },
177
+ "Huawei Mate 20 Pro": { os: "android", viewport: { width: 360, height: 780 } },
178
+ "Huawei P20 Pro": { os: "android", viewport: { width: 360, height: 747 } },
179
+ "Huawei P30": { os: "android", viewport: { width: 360, height: 780 } },
180
+ "Huawei P30 Pro": { os: "android", viewport: { width: 360, height: 780 } },
181
+ "Microsoft Surface Duo": { os: "android", viewport: { width: 1114, height: 705 } },
182
+ "Moto G7 Play": { os: "android", viewport: { width: 360, height: 760 } },
183
+ "Moto G9 Play": { os: "android", viewport: { width: 393, height: 786 } },
184
+ "Moto G Stylus (2022)": { os: "android", viewport: { width: 432, height: 984 } },
185
+ "Nexus 5": { os: "android", viewport: { width: 360, height: 640 } },
186
+ "Nexus 5X": { os: "android", viewport: { width: 412, height: 732 } },
187
+ "Nokia 5": { os: "android", viewport: { width: 360, height: 640 } },
188
+ "Nothing Phone (1)": { os: "android", viewport: { width: 412, height: 915 } },
189
+ "OnePlus 10 Pro": { os: "android", viewport: { width: 412, height: 919 } },
190
+ "OnePlus 11": { os: "android", viewport: { width: 360, height: 804 } },
191
+ "OnePlus 6": { os: "android", viewport: { width: 412, height: 869 } },
192
+ "OnePlus 6T": { os: "android", viewport: { width: 412, height: 892 } },
193
+ "OnePlus 7": { os: "android", viewport: { width: 412, height: 892 } },
194
+ "OnePlus 7T": { os: "android", viewport: { width: 412, height: 914 } },
195
+ "OnePlus 8": { os: "android", viewport: { width: 412, height: 915 } },
196
+ "OnePlus 9": { os: "android", viewport: { width: 411, height: 915 } },
197
+ "OnePlus 9 Pro": { os: "android", viewport: { width: 412, height: 919 } },
198
+ "OnePlus Nord": { os: "android", viewport: { width: 412, height: 914 } },
199
+ "OnePlus Nord 2": { os: "android", viewport: { width: 412, height: 915 } },
200
+ "OnePlus Nord CE": { os: "android", viewport: { width: 412, height: 915 } },
201
+ "Oppo A12": { os: "android", viewport: { width: 360, height: 760 } },
202
+ "Oppo A15": { os: "android", viewport: { width: 360, height: 800 } },
203
+ "Oppo A54": { os: "android", viewport: { width: 360, height: 800 } },
204
+ "Oppo A5s": { os: "android", viewport: { width: 360, height: 760 } },
205
+ "Oppo F17": { os: "android", viewport: { width: 360, height: 800 } },
206
+ "Oppo K10": { os: "android", viewport: { width: 360, height: 804 } },
207
+ "Pixel 3": { os: "android", viewport: { width: 412, height: 823 } },
208
+ "Pixel 3 XL": { os: "android", viewport: { width: 412, height: 846 } },
209
+ "Pixel 3a": { os: "android", viewport: { width: 412, height: 823 } },
210
+ "Pixel 4": { os: "android", viewport: { width: 392, height: 830 } },
211
+ "Pixel 4 XL": { os: "android", viewport: { width: 412, height: 823 } },
212
+ "Pixel 4a": { os: "android", viewport: { width: 393, height: 851 } },
213
+ "Pixel 5": { os: "android", viewport: { width: 393, height: 851 } },
214
+ "Pixel 6": { os: "android", viewport: { width: 393, height: 786 } },
215
+ "Pixel 6 Pro": { os: "android", viewport: { width: 412, height: 892 } },
216
+ "Pixel 7": { os: "android", viewport: { width: 412, height: 915 } },
217
+ "Pixel 7 Pro": { os: "android", viewport: { width: 412, height: 892 } },
218
+ "Pixel 8": { os: "android", viewport: { width: 412, height: 915 } },
219
+ "Pixel 8 Pro": { os: "android", viewport: { width: 448, height: 998 } },
220
+ "Poco M2 Pro": { os: "android", viewport: { width: 393, height: 873 } },
221
+ "POCO X3 Pro": { os: "android", viewport: { width: 393, height: 873 } },
222
+ "Realme 5i": { os: "android", viewport: { width: 360, height: 800 } },
223
+ "Realme 7i": { os: "android", viewport: { width: 360, height: 800 } },
224
+ "Realme 8i": { os: "android", viewport: { width: 360, height: 804 } },
225
+ "Realme C21Y": { os: "android", viewport: { width: 360, height: 800 } },
226
+ "Realme C21": { os: "android", viewport: { width: 360, height: 800 } },
227
+ "Realme GT2 Pro": { os: "android", viewport: { width: 360, height: 804 } },
228
+ "Redmi 8": { os: "android", viewport: { width: 360, height: 760 } },
229
+ "Redmi 9": { os: "android", viewport: { width: 360, height: 800 } },
230
+ "Redmi 9C": { os: "android", viewport: { width: 360, height: 800 } },
231
+ "Redmi Note 10 Pro": { os: "android", viewport: { width: 393, height: 873 } },
232
+ "Redmi Note 8": { os: "android", viewport: { width: 393, height: 851 } },
233
+ "Redmi Note 8 Pro": { os: "android", viewport: { width: 393, height: 851 } },
234
+ "Redmi Note 9": { os: "android", viewport: { width: 393, height: 851 } },
235
+ "Redmi Note 9 Pro Max": { os: "android", viewport: { width: 393, height: 873 } },
236
+ "Redmi Y2": { os: "android", viewport: { width: 360, height: 720 } },
237
+ "Tecno Spark 7": { os: "android", viewport: { width: 360, height: 800 } },
238
+ "Vivo Y22": { os: "android", viewport: { width: 385, height: 860 } },
239
+ "Vivo T1": { os: "android", viewport: { width: 393, height: 873 } },
240
+ "Vivo V7": { os: "android", viewport: { width: 360, height: 720 } },
241
+ "Vivo Y11": { os: "android", viewport: { width: 360, height: 722 } },
242
+ "Vivo Y12": { os: "android", viewport: { width: 360, height: 722 } },
243
+ "Vivo Y20g": { os: "android", viewport: { width: 385, height: 854 } },
244
+ "Vivo Y50": { os: "android", viewport: { width: 393, height: 786 } },
245
+ "Xiaomi 12 Pro": { os: "android", viewport: { width: 412, height: 915 } },
246
+ "Xperia Z5": { os: "android", viewport: { width: 360, height: 640 } },
247
+ "Xperia Z5 Dual": { os: "android", viewport: { width: 360, height: 640 } },
248
+ "Zenfone 6": { os: "android", viewport: { width: 412, height: 892 } },
249
+ "iPad 10.2 (2019)": { os: "ios", viewport: { width: 810, height: 1080 } },
250
+ "iPad 10.2 (2020)": { os: "ios", viewport: { width: 834, height: 1194 } },
251
+ "iPad 10.2 (2021)": { os: "ios", viewport: { width: 810, height: 1080 } },
252
+ "iPad 9.7 (2017)": { os: "ios", viewport: { width: 768, height: 1024 } },
253
+ "iPad Air (2019)": { os: "ios", viewport: { width: 834, height: 1112 } },
254
+ "iPad Air (2020)": { os: "ios", viewport: { width: 820, height: 1180 } },
255
+ "iPad Air (2022)": { os: "ios", viewport: { width: 820, height: 1180 } },
256
+ "iPad mini (2019)": { os: "ios", viewport: { width: 768, height: 1024 } },
257
+ "iPad mini (2021)": { os: "ios", viewport: { width: 744, height: 1133 } },
258
+ "iPad Pro 11 (2021)": { os: "ios", viewport: { width: 834, height: 1194 } },
259
+ "iPad Pro 11 (2022)": { os: "ios", viewport: { width: 834, height: 1194 } },
260
+ "iPad Pro 12.9 (2018)": { os: "ios", viewport: { width: 1024, height: 1366 } },
261
+ "iPad Pro 12.9 (2020)": { os: "ios", viewport: { width: 1024, height: 1366 } },
262
+ "iPad Pro 12.9 (2021)": { os: "ios", viewport: { width: 1024, height: 1366 } },
263
+ "iPad Pro 12.9 (2022)": { os: "ios", viewport: { width: 1024, height: 1366 } },
264
+ "iPhone 11": { os: "ios", viewport: { width: 375, height: 812 } },
265
+ "iPhone 11 Pro": { os: "ios", viewport: { width: 375, height: 812 } },
266
+ "iPhone 11 Pro Max": { os: "ios", viewport: { width: 414, height: 896 } },
267
+ "iPhone 12": { os: "ios", viewport: { width: 390, height: 844 } },
268
+ "iPhone 12 Mini": { os: "ios", viewport: { width: 375, height: 812 } },
269
+ "iPhone 12 Pro": { os: "ios", viewport: { width: 390, height: 844 } },
270
+ "iPhone 12 Pro Max": { os: "ios", viewport: { width: 428, height: 926 } },
271
+ "iPhone 13": { os: "ios", viewport: { width: 390, height: 844 } },
272
+ "iPhone 13 Mini": { os: "ios", viewport: { width: 390, height: 844 } },
273
+ "iPhone 13 Pro": { os: "ios", viewport: { width: 390, height: 844 } },
274
+ "iPhone 13 Pro Max": { os: "ios", viewport: { width: 428, height: 926 } },
275
+ "iPhone 14": { os: "ios", viewport: { width: 390, height: 844 } },
276
+ "iPhone 14 Plus": { os: "ios", viewport: { width: 428, height: 926 } },
277
+ "iPhone 14 Pro": { os: "ios", viewport: { width: 390, height: 844 } },
278
+ "iPhone 14 Pro Max": { os: "ios", viewport: { width: 428, height: 928 } },
279
+ "iPhone 15": { os: "ios", viewport: { width: 393, height: 852 } },
280
+ "iPhone 15 Plus": { os: "ios", viewport: { width: 430, height: 932 } },
281
+ "iPhone 15 Pro": { os: "ios", viewport: { width: 393, height: 852 } },
282
+ "iPhone 15 Pro Max": { os: "ios", viewport: { width: 430, height: 932 } },
283
+ "iPhone 6": { os: "ios", viewport: { width: 375, height: 667 } },
284
+ "iPhone 6s": { os: "ios", viewport: { width: 375, height: 667 } },
285
+ "iPhone 6s Plus": { os: "ios", viewport: { width: 414, height: 736 } },
286
+ "iPhone 7": { os: "ios", viewport: { width: 375, height: 667 } },
287
+ "iPhone 7 Plus": { os: "ios", viewport: { width: 414, height: 736 } },
288
+ "iPhone 8": { os: "ios", viewport: { width: 375, height: 667 } },
289
+ "iPhone 8 Plus": { os: "ios", viewport: { width: 414, height: 736 } },
290
+ "iPhone SE (2016)": { os: "ios", viewport: { width: 320, height: 568 } },
291
+ "iPhone SE (2020)": { os: "ios", viewport: { width: 375, height: 667 } },
292
+ "iPhone SE (2022)": { os: "ios", viewport: { width: 375, height: 667 } },
293
+ "iPhone X": { os: "ios", viewport: { width: 375, height: 812 } },
294
+ "iPhone XR": { os: "ios", viewport: { width: 414, height: 896 } },
295
+ "iPhone XS": { os: "ios", viewport: { width: 375, height: 812 } },
296
+ "iPhone XS Max": { os: "ios", viewport: { width: 414, height: 896 } }
297
+ }
298
+ };
299
+
300
+ // src/lib/utils.ts
69
301
  function delDir(dir) {
70
- if (fs2__default.default.existsSync(dir)) {
71
- fs2__default.default.rmSync(dir, { recursive: true });
302
+ if (fs4__default.default.existsSync(dir)) {
303
+ fs4__default.default.rmSync(dir, { recursive: true });
72
304
  }
73
305
  }
74
306
  function scrollToBottomAndBackToTop({
@@ -97,6 +329,68 @@ function scrollToBottomAndBackToTop({
97
329
  })();
98
330
  });
99
331
  }
332
+ function launchBrowsers(ctx) {
333
+ return __async(this, null, function* () {
334
+ let browsers = {};
335
+ let launchOptions = { headless: true };
336
+ if (ctx.config.web) {
337
+ for (const browser of ctx.config.web.browsers) {
338
+ switch (browser) {
339
+ case constants_default.CHROME:
340
+ browsers[constants_default.CHROME] = yield test.chromium.launch(launchOptions);
341
+ break;
342
+ case constants_default.SAFARI:
343
+ browsers[constants_default.SAFARI] = yield test.webkit.launch(launchOptions);
344
+ break;
345
+ case constants_default.FIREFOX:
346
+ browsers[constants_default.FIREFOX] = yield test.firefox.launch(launchOptions);
347
+ break;
348
+ case constants_default.EDGE:
349
+ browsers[constants_default.EDGE] = yield test.chromium.launch(__spreadValues({ channel: constants_default.EDGE_CHANNEL }, launchOptions));
350
+ break;
351
+ }
352
+ }
353
+ }
354
+ if (ctx.config.mobile) {
355
+ for (const device of ctx.config.mobile.devices) {
356
+ if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === "android" && !browsers[constants_default.CHROME])
357
+ browsers[constants_default.CHROME] = yield test.chromium.launch(launchOptions);
358
+ else if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === "ios" && !browsers[constants_default.SAFARI])
359
+ browsers[constants_default.SAFARI] = yield test.webkit.launch(launchOptions);
360
+ }
361
+ }
362
+ return browsers;
363
+ });
364
+ }
365
+ function closeBrowsers(browsers) {
366
+ return __async(this, null, function* () {
367
+ var _a;
368
+ for (const browserName of Object.keys(browsers))
369
+ yield (_a = browsers[browserName]) == null ? void 0 : _a.close();
370
+ });
371
+ }
372
+ function getRenderViewports(ctx) {
373
+ let renderViewports = [];
374
+ if (ctx.config.web) {
375
+ for (const viewport of ctx.config.web.viewports) {
376
+ renderViewports.push({
377
+ viewport,
378
+ viewportString: `${viewport.width}${viewport.height ? "x" + viewport.height : ""}`,
379
+ fullPage: viewport.height ? false : true
380
+ });
381
+ }
382
+ }
383
+ if (ctx.config.mobile) {
384
+ for (const device of ctx.config.mobile.devices) {
385
+ renderViewports.push({
386
+ viewport: constants_default.SUPPORTED_MOBILE_DEVICES[device].viewport,
387
+ viewportString: `${device} (${ctx.config.mobile.orientation})`,
388
+ fullPage: ctx.config.mobile.fullPage
389
+ });
390
+ }
391
+ }
392
+ return renderViewports;
393
+ }
100
394
  var MAX_RESOURCE_SIZE = 5 * 1024 ** 2;
101
395
  var ALLOWED_RESOURCES = ["document", "stylesheet", "image", "media", "font", "other"];
102
396
  var ALLOWED_STATUSES = [200, 201];
@@ -110,13 +404,13 @@ var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function*
110
404
  let cache = {};
111
405
  yield page.route("**/*", (route, request) => __async(void 0, null, function* () {
112
406
  const requestUrl = request.url();
113
- const snapshotHostname = new URL(snapshot.url).hostname;
114
407
  const requestHostname = new URL(requestUrl).hostname;
115
408
  try {
409
+ ctx.config.allowedHostnames.push(new URL(snapshot.url).hostname);
410
+ if (ctx.config.enableJavaScript)
411
+ ALLOWED_RESOURCES.push("script");
116
412
  const response = yield page.request.fetch(request);
117
413
  const body = yield response.body();
118
- if (ctx.webConfig.enableJavaScript)
119
- ALLOWED_RESOURCES.push("script");
120
414
  if (!body) {
121
415
  ctx.log.debug(`Handling request ${requestUrl}
122
416
  - skipping no response`);
@@ -126,7 +420,7 @@ var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function*
126
420
  } else if (requestUrl === snapshot.url) {
127
421
  ctx.log.debug(`Handling request ${requestUrl}
128
422
  - skipping root resource`);
129
- } else if (requestHostname !== snapshotHostname) {
423
+ } else if (!ctx.config.allowedHostnames.includes(requestHostname)) {
130
424
  ctx.log.debug(`Handling request ${requestUrl}
131
425
  - skipping remote resource`);
132
426
  } else if (cache[requestUrl]) {
@@ -138,7 +432,7 @@ var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function*
138
432
  } else if (!ALLOWED_STATUSES.includes(response.status())) {
139
433
  ctx.log.debug(`Handling request ${requestUrl}
140
434
  - skipping disallowed status [${response.status()}]`);
141
- } else if (!ctx.webConfig.enableJavaScript && !ALLOWED_RESOURCES.includes(request.resourceType())) {
435
+ } else if (!ALLOWED_RESOURCES.includes(request.resourceType())) {
142
436
  ctx.log.debug(`Handling request ${requestUrl}
143
437
  - skipping disallowed resource type [${request.resourceType()}]`);
144
438
  } else {
@@ -155,48 +449,66 @@ var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function*
155
449
  body
156
450
  });
157
451
  } catch (error) {
158
- ctx.log.debug(`Handling request ${requestUrl} - aborted`);
452
+ ctx.log.debug(`Handling request ${requestUrl}
453
+ - aborted due to ${error.message}`);
159
454
  route.abort();
160
455
  }
161
456
  }));
162
457
  let options = snapshot.options;
163
458
  let optionWarnings = /* @__PURE__ */ new Set();
164
459
  let processedOptions = {};
165
- let selectors2 = [];
460
+ let selectors = [];
166
461
  let ignoreOrSelectDOM;
167
462
  let ignoreOrSelectBoxes;
168
463
  if (options && Object.keys(options).length) {
169
464
  ctx.log.debug(`Snapshot options: ${JSON.stringify(options)}`);
170
- if (options.ignoreDOM && Object.keys(options.ignoreDOM).length || options.selectDOM && Object.keys(options.selectDOM).length) {
171
- if (options.ignoreDOM && Object.keys(options.ignoreDOM).length) {
172
- processedOptions.ignoreBoxes = {};
173
- ignoreOrSelectDOM = "ignoreDOM";
174
- ignoreOrSelectBoxes = "ignoreBoxes";
175
- } else {
176
- processedOptions.selectBoxes = {};
177
- ignoreOrSelectDOM = "selectDOM";
178
- ignoreOrSelectBoxes = "selectBoxes";
179
- }
465
+ const isNotAllEmpty = (obj) => {
466
+ var _a;
467
+ for (let key in obj)
468
+ if ((_a = obj[key]) == null ? void 0 : _a.length)
469
+ return true;
470
+ return false;
471
+ };
472
+ if (options.element && Object.keys(options.element).length) {
473
+ if (options.element.id)
474
+ processedOptions.element = "#" + options.element.id;
475
+ else if (options.element.class)
476
+ processedOptions.element = "." + options.element.class;
477
+ else if (options.element.cssSelector)
478
+ processedOptions.element = options.element.cssSelector;
479
+ else if (options.element.xpath)
480
+ processedOptions.element = "xpath=" + options.element.xpath;
481
+ } else if (options.ignoreDOM && Object.keys(options.ignoreDOM).length && isNotAllEmpty(options.ignoreDOM)) {
482
+ processedOptions.ignoreBoxes = {};
483
+ ignoreOrSelectDOM = "ignoreDOM";
484
+ ignoreOrSelectBoxes = "ignoreBoxes";
485
+ } else if (options.selectDOM && Object.keys(options.selectDOM).length && isNotAllEmpty(options.selectDOM)) {
486
+ processedOptions.selectBoxes = {};
487
+ ignoreOrSelectDOM = "selectDOM";
488
+ ignoreOrSelectBoxes = "selectBoxes";
489
+ }
490
+ if (ignoreOrSelectDOM) {
180
491
  for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
181
492
  switch (key) {
182
493
  case "id":
183
- selectors2.push(...value.map((e) => "#" + e));
494
+ selectors.push(...value.map((e) => "#" + e));
184
495
  break;
185
496
  case "class":
186
- selectors2.push(...value.map((e) => "." + e));
497
+ selectors.push(...value.map((e) => "." + e));
187
498
  break;
188
499
  case "xpath":
189
- selectors2.push(...value.map((e) => "xpath=" + e));
500
+ selectors.push(...value.map((e) => "xpath=" + e));
190
501
  break;
191
502
  case "cssSelector":
192
- selectors2.push(...value);
503
+ selectors.push(...value);
193
504
  break;
194
505
  }
195
506
  }
196
507
  }
197
508
  }
198
509
  let navigated = false;
199
- for (const viewport of ctx.webConfig.viewports) {
510
+ let renderViewports = getRenderViewports(ctx);
511
+ for (const { viewport, viewportString, fullPage } of renderViewports) {
200
512
  yield page.setViewportSize({ width: viewport.width, height: viewport.height || MIN_VIEWPORT_HEIGHT });
201
513
  ctx.log.debug(`Page resized to ${viewport.width}x${viewport.height || MIN_VIEWPORT_HEIGHT}`);
202
514
  if (!navigated) {
@@ -204,20 +516,25 @@ var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function*
204
516
  navigated = true;
205
517
  ctx.log.debug(`Navigated to ${snapshot.url}`);
206
518
  }
207
- if (!viewport.height)
519
+ if (fullPage)
208
520
  yield page.evaluate(scrollToBottomAndBackToTop);
209
521
  yield page.waitForLoadState("networkidle");
210
522
  ctx.log.debug("Network idle 500ms");
211
- if (selectors2.length) {
212
- let viewportString = `${viewport.width}${viewport.height ? "x" + viewport.height : ""}`;
523
+ if (processedOptions.element) {
524
+ let l = yield page.locator(processedOptions.element).all();
525
+ if (l.length === 0) {
526
+ throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${processedOptions.element}`);
527
+ } else if (l.length > 1) {
528
+ throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, multiple elements found for selector ${processedOptions.element}`);
529
+ }
530
+ } else if (selectors.length) {
531
+ let locators = [];
213
532
  if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewportString]))
214
533
  processedOptions[ignoreOrSelectBoxes][viewportString] = [];
215
- let locators = [];
216
- let boxes = [];
217
- for (const selector of selectors2) {
534
+ for (const selector of selectors) {
218
535
  let l = yield page.locator(selector).all();
219
536
  if (l.length === 0) {
220
- optionWarnings.add(`For snapshot ${snapshot.name}, no element found for selector ${selector}`);
537
+ optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
221
538
  continue;
222
539
  }
223
540
  locators.push(...l);
@@ -225,14 +542,13 @@ var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function*
225
542
  for (const locator of locators) {
226
543
  let bb = yield locator.boundingBox();
227
544
  if (bb)
228
- boxes.push({
545
+ processedOptions[ignoreOrSelectBoxes][viewportString].push({
229
546
  left: bb.x,
230
547
  top: bb.y,
231
548
  right: bb.x + bb.width,
232
549
  bottom: bb.y + bb.height
233
550
  });
234
551
  }
235
- processedOptions[ignoreOrSelectBoxes][viewportString].push(...boxes);
236
552
  }
237
553
  }
238
554
  yield page.close();
@@ -269,10 +585,10 @@ var ConfigSchema = {
269
585
  properties: {
270
586
  browsers: {
271
587
  type: "array",
272
- items: { type: "string", enum: ["chrome", "firefox", "edge", "safari"] },
588
+ items: { type: "string", enum: [constants_default.CHROME, constants_default.FIREFOX, constants_default.SAFARI, constants_default.EDGE] },
273
589
  uniqueItems: true,
274
590
  maxItems: 4,
275
- errorMessage: "Invalid config; allowed browsers - chrome, firefox, edge, safari"
591
+ errorMessage: `Invalid config; allowed browsers - ${constants_default.CHROME}, ${constants_default.FIREFOX}, ${constants_default.SAFARI}, ${constants_default.EDGE}`
276
592
  },
277
593
  viewports: {
278
594
  type: "array",
@@ -298,29 +614,80 @@ var ConfigSchema = {
298
614
  uniqueItems: true,
299
615
  maxItems: 5,
300
616
  errorMessage: "Invalid config; max unique viewports allowed - 5"
617
+ }
618
+ },
619
+ required: ["browsers", "viewports"],
620
+ additionalProperties: false
621
+ },
622
+ mobile: {
623
+ type: "object",
624
+ properties: {
625
+ devices: {
626
+ type: "array",
627
+ items: {
628
+ type: "string",
629
+ enum: Object.keys(constants_default.SUPPORTED_MOBILE_DEVICES),
630
+ minLength: 1,
631
+ errorMessage: {
632
+ enum: "Invalid config; unsupported mobile devices",
633
+ minLength: "Invalid config; mobile device cannot be empty"
634
+ }
635
+ },
636
+ uniqueItems: true,
637
+ maxItems: 20,
638
+ errorMessage: {
639
+ uniqueItems: "Invalid config; duplicate mobile devices",
640
+ maxItems: "Invalid config; max mobile devices allowed - 20"
641
+ }
301
642
  },
302
- waitForPageRender: {
303
- type: "number",
304
- minimum: 0,
305
- maximum: 3e5,
306
- errorMessage: "Invalid config; waitForPageRender must be > 0 and <= 300000"
307
- },
308
- waitForTimeout: {
309
- type: "number",
310
- minimum: 0,
311
- maximum: 3e4,
312
- errorMessage: "Invalid config; waitForTimeout must be > 0 and <= 30000"
313
- },
314
- enableJavaScript: {
643
+ fullPage: {
315
644
  type: "boolean",
316
- errorMessage: "Invalid config; enableJavaScript must be true/false"
645
+ errorMessage: "Invalid config; fullPage must be true/false"
646
+ },
647
+ orientation: {
648
+ type: "string",
649
+ enum: [constants_default.MOBILE_ORIENTATION_PORTRAIT, constants_default.MOBILE_ORIENTATION_LANDSCAPE],
650
+ errorMessage: `Invalid config; orientation must be ${constants_default.MOBILE_ORIENTATION_PORTRAIT}/${constants_default.MOBILE_ORIENTATION_LANDSCAPE}`
317
651
  }
318
652
  },
319
- required: ["browsers", "viewports"],
653
+ required: ["devices"],
320
654
  additionalProperties: false
655
+ },
656
+ waitForPageRender: {
657
+ type: "number",
658
+ minimum: 0,
659
+ maximum: 3e5,
660
+ errorMessage: "Invalid config; waitForPageRender must be > 0 and <= 300000"
661
+ },
662
+ waitForTimeout: {
663
+ type: "number",
664
+ minimum: 0,
665
+ maximum: 3e4,
666
+ errorMessage: "Invalid config; waitForTimeout must be > 0 and <= 30000"
667
+ },
668
+ enableJavaScript: {
669
+ type: "boolean",
670
+ errorMessage: "Invalid config; enableJavaScript must be true/false"
671
+ },
672
+ allowedHostnames: {
673
+ type: "array",
674
+ items: {
675
+ type: "string",
676
+ minLength: 1,
677
+ errorMessage: {
678
+ minLength: "Invalid config; allowed hostname cannot be empty"
679
+ }
680
+ },
681
+ uniqueItems: true,
682
+ errorMessage: {
683
+ uniqueItems: "Invalid config; duplicates in allowedHostnames"
684
+ }
321
685
  }
322
686
  },
323
- required: ["web"],
687
+ anyOf: [
688
+ { required: ["web"] },
689
+ { required: ["mobile"] }
690
+ ],
324
691
  additionalProperties: false
325
692
  };
326
693
  var WebStaticConfigSchema = {
@@ -370,32 +737,56 @@ var SnapshotSchema = {
370
737
  options: {
371
738
  type: "object",
372
739
  properties: {
740
+ element: {
741
+ type: "object",
742
+ properties: {
743
+ id: {
744
+ type: "string",
745
+ pattern: "^[^;]*$",
746
+ errorMessage: "Invalid snapshot options; element id cannot be empty or have semicolon"
747
+ },
748
+ class: {
749
+ type: "string",
750
+ pattern: "^[^;]*$",
751
+ errorMessage: "Invalid snapshot options; element class cannot be empty or have semicolon"
752
+ },
753
+ cssSelector: {
754
+ type: "string",
755
+ pattern: "^[^;]*$",
756
+ errorMessage: "Invalid snapshot options; element cssSelector cannot be empty or have semicolon"
757
+ },
758
+ xpath: {
759
+ type: "string",
760
+ errorMessage: "Invalid snapshot options; element xpath cannot be empty"
761
+ }
762
+ }
763
+ },
373
764
  ignoreDOM: {
374
765
  type: "object",
375
766
  properties: {
376
767
  id: {
377
768
  type: "array",
378
- items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; id cannot be empty or have semicolon" },
769
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; ignoreDOM id cannot be empty or have semicolon" },
379
770
  uniqueItems: true,
380
- errorMessage: "Invalid snapshot options; id array must have unique items"
771
+ errorMessage: "Invalid snapshot options; ignoreDOM id array must have unique items"
381
772
  },
382
773
  class: {
383
774
  type: "array",
384
- items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; class cannot be empty or have semicolon" },
775
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; ignoreDOM class cannot be empty or have semicolon" },
385
776
  uniqueItems: true,
386
- errorMessage: "Invalid snapshot options; class array must have unique items"
777
+ errorMessage: "Invalid snapshot options; ignoreDOM class array must have unique items"
387
778
  },
388
779
  cssSelector: {
389
780
  type: "array",
390
- items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; cssSelector cannot be empty or have semicolon" },
781
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; ignoreDOM cssSelector cannot be empty or have semicolon" },
391
782
  uniqueItems: true,
392
- errorMessage: "Invalid snapshot options; cssSelector array must have unique items"
783
+ errorMessage: "Invalid snapshot options; ignoreDOM cssSelector array must have unique items"
393
784
  },
394
785
  xpath: {
395
786
  type: "array",
396
787
  items: { type: "string", minLength: 1 },
397
788
  uniqueItems: true,
398
- errorMessage: "Invalid snapshot options; xpath array must have unique and non-empty items"
789
+ errorMessage: "Invalid snapshot options; ignoreDOM xpath array must have unique and non-empty items"
399
790
  }
400
791
  }
401
792
  },
@@ -404,27 +795,27 @@ var SnapshotSchema = {
404
795
  properties: {
405
796
  id: {
406
797
  type: "array",
407
- items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; id cannot be empty or have semicolon" },
798
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; selectDOM id cannot be empty or have semicolon" },
408
799
  uniqueItems: true,
409
- errorMessage: "Invalid snapshot options; id array must have unique items"
800
+ errorMessage: "Invalid snapshot options; selectDOM id array must have unique items"
410
801
  },
411
802
  class: {
412
803
  type: "array",
413
- items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; class cannot be empty or have semicolon" },
804
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; selectDOM class cannot be empty or have semicolon" },
414
805
  uniqueItems: true,
415
- errorMessage: "Invalid snapshot options; class array must have unique items"
806
+ errorMessage: "Invalid snapshot options; selectDOM class array must have unique items"
416
807
  },
417
808
  cssSelector: {
418
809
  type: "array",
419
- items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; cssSelector cannot be empty or have semicolon" },
810
+ items: { type: "string", minLength: 1, pattern: "^[^;]*$", errorMessage: "Invalid snapshot options; selectDOM cssSelector cannot be empty or have semicolon" },
420
811
  uniqueItems: true,
421
- errorMessage: "Invalid snapshot options; cssSelector array must have unique items"
812
+ errorMessage: "Invalid snapshot options; selectDOM cssSelector array must have unique items"
422
813
  },
423
814
  xpath: {
424
815
  type: "array",
425
816
  items: { type: "string", minLength: 1 },
426
817
  uniqueItems: true,
427
- errorMessage: "Invalid snapshot options; xpath array must have unique and non-empty items"
818
+ errorMessage: "Invalid snapshot options; selectDOM xpath array must have unique and non-empty items"
428
819
  }
429
820
  }
430
821
  }
@@ -444,7 +835,7 @@ var validateSnapshot = ajv.compile(SnapshotSchema);
444
835
  var server_default = (ctx) => __async(void 0, null, function* () {
445
836
  const server = fastify__default.default({ logger: false, bodyLimit: 1e7 });
446
837
  const opts = {};
447
- const SMARTUI_DOM = fs2.readFileSync(path2__default.default.resolve(__dirname, "dom-serializer.js"), "utf-8");
838
+ const SMARTUI_DOM = fs4.readFileSync(path2__default.default.resolve(__dirname, "dom-serializer.js"), "utf-8");
448
839
  server.get("/healthcheck", opts, (_, reply) => {
449
840
  reply.code(200).send({ cliVersion: ctx.cliVersion });
450
841
  });
@@ -467,6 +858,7 @@ var server_default = (ctx) => __async(void 0, null, function* () {
467
858
  yield server.listen();
468
859
  let { port } = server.addresses()[0];
469
860
  process.env.SMARTUI_SERVER_ADDRESS = `http://localhost:${port}`;
861
+ process.env.CYPRESS_SMARTUI_SERVER_ADDRESS = `http://localhost:${port}`;
470
862
  return server;
471
863
  });
472
864
 
@@ -476,13 +868,15 @@ var env_default = () => {
476
868
  PROJECT_TOKEN = "",
477
869
  SMARTUI_CLIENT_API_URL = "https://api.lambdatest.com/visualui/1.0",
478
870
  LT_SDK_LOG_LEVEL,
479
- LT_SDK_DEBUG
871
+ LT_SDK_DEBUG,
872
+ GITHUB_ACTIONS
480
873
  } = process.env;
481
874
  return {
482
875
  PROJECT_TOKEN,
483
876
  SMARTUI_CLIENT_API_URL,
484
877
  LT_SDK_LOG_LEVEL,
485
- LT_SDK_DEBUG
878
+ LT_SDK_DEBUG,
879
+ GITHUB_ACTIONS
486
880
  };
487
881
  };
488
882
  var logContext = { task: "smartui-cli" };
@@ -557,69 +951,9 @@ var auth_default = (ctx) => {
557
951
  rendererOptions: { persistentOutput: true }
558
952
  };
559
953
  };
560
- var DEFAULT_WEB_STATIC_CONFIG = [
561
- {
562
- "name": "lambdatest-home-page",
563
- "url": "https://www.lambdatest.com",
564
- "waitForTimeout": 1e3
565
- },
566
- {
567
- "name": "example-page",
568
- "url": "https://example.com/"
569
- }
570
- ];
571
- var DEFAULT_CONFIG = {
572
- web: {
573
- browsers: [
574
- "chrome",
575
- "firefox",
576
- "safari",
577
- "edge"
578
- ],
579
- viewports: [
580
- [1920],
581
- [1366],
582
- [360]
583
- ],
584
- waitForTimeout: 1e3,
585
- enableJavaScript: false
586
- }
587
- };
588
- function createConfig(filepath) {
589
- filepath = filepath || ".smartui.json";
590
- let filetype = path2__default.default.extname(filepath);
591
- if (filetype != ".json") {
592
- console.log("Error: Config file must have .json extension");
593
- return;
594
- }
595
- if (fs2__default.default.existsSync(filepath)) {
596
- console.log(`Error: SmartUI Config already exists: ${filepath}`);
597
- console.log(`To create a new file, please specify the file name like: 'smartui config:create .smartui-config.json'`);
598
- return;
599
- }
600
- fs2__default.default.mkdirSync(path2__default.default.dirname(filepath), { recursive: true });
601
- fs2__default.default.writeFileSync(filepath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
602
- console.log(`Created SmartUI Config: ${filepath}`);
603
- }
604
- function createWebStaticConfig(filepath) {
605
- filepath = filepath || "url.json";
606
- let filetype = path2__default.default.extname(filepath);
607
- if (filetype != ".json") {
608
- console.log("Error: Config file must have .json extension");
609
- return;
610
- }
611
- if (fs2__default.default.existsSync(filepath)) {
612
- console.log(`Error: web-static config already exists: ${filepath}`);
613
- console.log(`To create a new file, please specify the file name like: 'smartui config:create-web-static links.json'`);
614
- return;
615
- }
616
- fs2__default.default.mkdirSync(path2__default.default.dirname(filepath), { recursive: true });
617
- fs2__default.default.writeFileSync(filepath, JSON.stringify(DEFAULT_WEB_STATIC_CONFIG, null, 2) + "\n");
618
- console.log(`Created web-static config: ${filepath}`);
619
- }
620
954
 
621
955
  // package.json
622
- var version = "2.0.8";
956
+ var version = "3.0.0";
623
957
  var package_default = {
624
958
  name: "@lambdatest/smartui-cli",
625
959
  version,
@@ -691,7 +1025,7 @@ var httpClient = class {
691
1025
  headers: error.response.headers,
692
1026
  body: error.response.data
693
1027
  })}`);
694
- throw new Error(JSON.stringify(error.response.data));
1028
+ throw new Error(error.response.data.error.message);
695
1029
  }
696
1030
  if (error.request) {
697
1031
  log.debug(`http request failed: ${error.toJSON()}`);
@@ -708,24 +1042,13 @@ var httpClient = class {
708
1042
  method: "GET"
709
1043
  }, log);
710
1044
  }
711
- createBuild({ branch, commitId, commitAuthor, commitMessage, githubURL }, config, log) {
1045
+ createBuild(git, config, log) {
712
1046
  return this.request({
713
1047
  url: "/build",
714
1048
  method: "POST",
715
1049
  data: {
716
- git: {
717
- branch,
718
- commitId,
719
- commitAuthor,
720
- commitMessage,
721
- githubURL
722
- },
723
- config: {
724
- browsers: config.browsers,
725
- resolutions: config.viewports,
726
- waitForPageRender: config.waitForPageRender,
727
- waitForTimeout: config.waitForTimeout
728
- }
1050
+ git,
1051
+ config
729
1052
  }
730
1053
  }, log);
731
1054
  }
@@ -753,28 +1076,26 @@ var httpClient = class {
753
1076
  }
754
1077
  }, log);
755
1078
  }
756
- uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport, completed) {
757
- const file = fs2__default.default.readFileSync(ssPath);
1079
+ uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport, log) {
1080
+ const file = fs4__default.default.readFileSync(ssPath);
758
1081
  const form = new FormData__default.default();
759
- form.append("screenshots", file, { filename: `${ssName}.png`, contentType: "image/png" });
1082
+ form.append("screenshot", file, { filename: `${ssName}.png`, contentType: "image/png" });
760
1083
  form.append("browser", browserName);
761
- form.append("resolution", viewport);
1084
+ form.append("viewport", viewport);
762
1085
  form.append("buildId", buildId);
763
1086
  form.append("buildName", buildName);
764
1087
  form.append("screenshotName", ssName);
765
1088
  form.append("baseline", baseline.toString());
766
- form.append("completed", completed.toString());
767
1089
  return this.axiosInstance.request({
768
1090
  url: `/screenshot`,
769
1091
  method: "POST",
770
1092
  headers: form.getHeaders(),
771
1093
  data: form
772
1094
  }).then(() => {
773
- if (completed)
774
- delDir("screenshots");
1095
+ log.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
775
1096
  }).catch((error) => {
776
1097
  if (error.response) {
777
- throw new Error(JSON.stringify(error.response.data));
1098
+ throw new Error(error.response.data.error.message);
778
1099
  }
779
1100
  if (error.request) {
780
1101
  throw new Error(error.toJSON().message);
@@ -795,35 +1116,49 @@ var httpClient = class {
795
1116
  }
796
1117
  };
797
1118
  var ctx_default = (options) => {
1119
+ var _a, _b;
798
1120
  let env = env_default();
799
- let viewports = [];
800
- let config = DEFAULT_CONFIG;
1121
+ let webConfig;
1122
+ let mobileConfig;
1123
+ let config = constants_default.DEFAULT_CONFIG;
801
1124
  try {
802
1125
  if (options.config) {
803
- config = JSON.parse(fs2__default.default.readFileSync(options.config, "utf-8"));
804
- if (config.web.resolutions) {
1126
+ config = JSON.parse(fs4__default.default.readFileSync(options.config, "utf-8"));
1127
+ if ((_a = config.web) == null ? void 0 : _a.resolutions) {
805
1128
  config.web.viewports = config.web.resolutions;
806
1129
  delete config.web.resolutions;
807
1130
  }
1131
+ if (!validateConfig(config)) {
1132
+ throw new Error(validateConfig.errors[0].message);
1133
+ }
808
1134
  }
809
- if (!validateConfig(config))
810
- throw new Error(validateConfig.errors[0].message);
811
1135
  } catch (error) {
812
1136
  console.log(`[smartui] Error: ${error.message}`);
813
1137
  process.exit();
814
1138
  }
815
- for (let viewport of config.web.viewports)
816
- viewports.push({ width: viewport[0], height: viewport[1] || 0 });
1139
+ if (config.web) {
1140
+ webConfig = { browsers: config.web.browsers, viewports: [] };
1141
+ for (let viewport of (_b = config.web) == null ? void 0 : _b.viewports)
1142
+ webConfig.viewports.push({ width: viewport[0], height: viewport[1] || 0 });
1143
+ }
1144
+ if (config.mobile) {
1145
+ mobileConfig = {
1146
+ devices: config.mobile.devices,
1147
+ fullPage: config.mobile.fullPage || true,
1148
+ orientation: config.mobile.orientation || constants_default.MOBILE_ORIENTATION_PORTRAIT
1149
+ };
1150
+ }
817
1151
  return {
818
1152
  env,
819
1153
  log: logger_default,
820
1154
  client: new httpClient(env),
821
- webConfig: {
822
- browsers: config.web.browsers,
823
- viewports,
824
- waitForPageRender: config.web.waitForPageRender || 0,
825
- waitForTimeout: config.web.waitForTimeout || 0,
826
- enableJavaScript: config.web.enableJavaScript || false
1155
+ config: {
1156
+ web: webConfig,
1157
+ mobile: mobileConfig,
1158
+ waitForPageRender: config.waitForPageRender || 0,
1159
+ waitForTimeout: config.waitForTimeout || 0,
1160
+ enableJavaScript: config.enableJavaScript || false,
1161
+ allowedHostnames: config.allowedHostnames || []
827
1162
  },
828
1163
  webStaticConfig: [],
829
1164
  git: {
@@ -865,7 +1200,7 @@ function isGitRepo() {
865
1200
  return false;
866
1201
  }
867
1202
  }
868
- var git_default = () => {
1203
+ var git_default = (ctx) => {
869
1204
  const splitCharacter = "<##>";
870
1205
  const prettyFormat = ["%h", "%H", "%s", "%f", "%b", "%at", "%ct", "%an", "%ae", "%cn", "%ce", "%N", ""];
871
1206
  const command3 = 'git log -1 --pretty=format:"' + prettyFormat.join(splitCharacter) + '" && git rev-parse --abbrev-ref HEAD && git tag --contains HEAD';
@@ -877,7 +1212,8 @@ var git_default = () => {
877
1212
  branch,
878
1213
  commitId: res[0] || "",
879
1214
  commitMessage: res[2] || "",
880
- commitAuthor: res[7] || ""
1215
+ commitAuthor: res[7] || "",
1216
+ githubURL: ctx.env.GITHUB_ACTIONS ? `${constants_default.GITHUB_API_HOST}/repos/${process.env.GITHUB_REPOSITORY}/statuses/${res[1]}` : ""
881
1217
  };
882
1218
  };
883
1219
  var getGitInfo_default = (ctx) => {
@@ -888,7 +1224,7 @@ var getGitInfo_default = (ctx) => {
888
1224
  },
889
1225
  task: (ctx2, task) => __async(void 0, null, function* () {
890
1226
  try {
891
- ctx2.git = git_default();
1227
+ ctx2.git = git_default(ctx2);
892
1228
  task.output = chalk__default.default.gray(`branch: ${ctx2.git.branch}, commit: ${ctx2.git.commitId}, author: ${ctx2.git.commitAuthor}`);
893
1229
  task.title = "Fetched git information";
894
1230
  } catch (error) {
@@ -906,13 +1242,12 @@ var createBuild_default = (ctx) => {
906
1242
  task: (ctx2, task) => __async(void 0, null, function* () {
907
1243
  updateLogContext({ task: "createBuild" });
908
1244
  try {
909
- let resp = yield ctx2.client.createBuild(ctx2.git, ctx2.webConfig, ctx2.log);
1245
+ let resp = yield ctx2.client.createBuild(ctx2.git, ctx2.config, ctx2.log);
910
1246
  ctx2.build = {
911
1247
  id: resp.data.buildId,
912
1248
  name: resp.data.buildName,
913
- url: resp.buildURL,
914
- baseline: resp.data.baseline || false,
915
- projectId: resp.data.projectId
1249
+ url: resp.data.buildURL,
1250
+ baseline: resp.data.baseline
916
1251
  };
917
1252
  task.output = chalk__default.default.gray(`build id: ${resp.data.buildId}`);
918
1253
  task.title = "SmartUI build created";
@@ -1024,6 +1359,40 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
1024
1359
  });
1025
1360
  });
1026
1361
  var exec_default2 = command;
1362
+ function createConfig(filepath) {
1363
+ filepath = filepath || ".smartui.json";
1364
+ let filetype = path2__default.default.extname(filepath);
1365
+ if (filetype != ".json") {
1366
+ console.log("Error: Config file must have .json extension");
1367
+ return;
1368
+ }
1369
+ if (fs4__default.default.existsSync(filepath)) {
1370
+ console.log(`Error: SmartUI Config already exists: ${filepath}`);
1371
+ console.log(`To create a new file, please specify the file name like: 'smartui config:create .smartui-config.json'`);
1372
+ return;
1373
+ }
1374
+ fs4__default.default.mkdirSync(path2__default.default.dirname(filepath), { recursive: true });
1375
+ fs4__default.default.writeFileSync(filepath, JSON.stringify(constants_default.DEFAULT_CONFIG, null, 2) + "\n");
1376
+ console.log(`Created SmartUI Config: ${filepath}`);
1377
+ }
1378
+ function createWebStaticConfig(filepath) {
1379
+ filepath = filepath || "url.json";
1380
+ let filetype = path2__default.default.extname(filepath);
1381
+ if (filetype != ".json") {
1382
+ console.log("Error: Config file must have .json extension");
1383
+ return;
1384
+ }
1385
+ if (fs4__default.default.existsSync(filepath)) {
1386
+ console.log(`Error: web-static config already exists: ${filepath}`);
1387
+ console.log(`To create a new file, please specify the file name like: 'smartui config:create-web-static links.json'`);
1388
+ return;
1389
+ }
1390
+ fs4__default.default.mkdirSync(path2__default.default.dirname(filepath), { recursive: true });
1391
+ fs4__default.default.writeFileSync(filepath, JSON.stringify(constants_default.DEFAULT_WEB_STATIC_CONFIG, null, 2) + "\n");
1392
+ console.log(`Created web-static config: ${filepath}`);
1393
+ }
1394
+
1395
+ // src/commander/config.ts
1027
1396
  var configWeb = new commander.Command();
1028
1397
  var configStatic = new commander.Command();
1029
1398
  configWeb.name("config:create").description("Create SmartUI config file").argument("[filepath]", "Optional config filepath").action(function(filepath, options) {
@@ -1036,73 +1405,83 @@ configStatic.name("config:create-web-static").description("Create Web Static con
1036
1405
  createWebStaticConfig(filepath);
1037
1406
  });
1038
1407
  });
1039
- var BROWSER_CHROME = "chrome";
1040
- var BROWSER_SAFARI = "safari";
1041
- var BROWSER_FIREFOX = "firefox";
1042
- var BROWSER_EDGE = "edge";
1043
- var EDGE_CHANNEL = "msedge";
1044
- var PW_WEBKIT = "webkit";
1045
- var MIN_VIEWPORT_HEIGHT2 = 1080;
1046
- function captureScreenshots(ctx, screenshots) {
1408
+ function captureScreenshots(ctx) {
1047
1409
  return __async(this, null, function* () {
1048
- var _a;
1410
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1049
1411
  delDir("screenshots");
1050
- let totalBrowsers = ctx.webConfig.browsers.length;
1051
- let totalViewports = ctx.webConfig.viewports.length;
1052
- let totalScreenshots = screenshots.length;
1412
+ let browsers = {};
1053
1413
  let capturedScreenshots = 0;
1054
- for (let i = 0; i < totalBrowsers; i++) {
1055
- let browserName = (_a = ctx.webConfig.browsers[i]) == null ? void 0 : _a.toLowerCase();
1056
- let browser;
1057
- let launchOptions = { headless: true };
1058
- let pageOptions = { waitUntil: process.env.SMARTUI_PAGE_WAIT_UNTIL_EVENT || "load" };
1059
- try {
1060
- switch (browserName) {
1061
- case BROWSER_CHROME:
1062
- browser = yield test.chromium.launch(launchOptions);
1063
- break;
1064
- case BROWSER_SAFARI:
1065
- browser = yield test.webkit.launch(launchOptions);
1066
- break;
1067
- case BROWSER_FIREFOX:
1068
- browser = yield test.firefox.launch(launchOptions);
1069
- break;
1070
- case BROWSER_EDGE:
1071
- launchOptions.channel = EDGE_CHANNEL;
1072
- browser = yield test.chromium.launch(launchOptions);
1073
- break;
1414
+ let pageOptions = { waitUntil: process.env.SMARTUI_PAGE_WAIT_UNTIL_EVENT || "load" };
1415
+ let totalScreenshots = ctx.webStaticConfig.length * ((((_b = (_a = ctx.config.web) == null ? void 0 : _a.browsers) == null ? void 0 : _b.length) * ((_d = (_c = ctx.config.web) == null ? void 0 : _c.viewports) == null ? void 0 : _d.length) || 0) + (((_f = (_e = ctx.config.mobile) == null ? void 0 : _e.devices) == null ? void 0 : _f.length) || 0));
1416
+ try {
1417
+ browsers = yield launchBrowsers(ctx);
1418
+ for (let staticConfig of ctx.webStaticConfig) {
1419
+ let screenshotId = staticConfig.name.toLowerCase().replace(/\s/g, "_");
1420
+ if (ctx.config.web) {
1421
+ for (let browserName of ctx.config.web.browsers) {
1422
+ const browser = browsers[browserName];
1423
+ const context = yield browser.newContext();
1424
+ const page = yield context.newPage();
1425
+ yield page.goto(staticConfig.url.trim(), pageOptions);
1426
+ for (let { width, height } of ctx.config.web.viewports) {
1427
+ let ssPath = `screenshots/${screenshotId}/${browserName}-${width}x${height}-${screenshotId}.png`;
1428
+ yield page.setViewportSize({ width, height: height || constants_default.MIN_VIEWPORT_HEIGHT });
1429
+ if (height === 0)
1430
+ yield page.evaluate(scrollToBottomAndBackToTop);
1431
+ yield page.waitForTimeout(staticConfig.waitForTimeout || 0);
1432
+ yield page.screenshot({ path: ssPath, fullPage: height ? false : true });
1433
+ browserName = browserName === constants_default.SAFARI ? constants_default.PW_WEBKIT : browserName;
1434
+ yield ctx.client.uploadScreenshot(ctx.build, ssPath, staticConfig.name, browserName, `${width}${height ? `x${height}` : ``}`, ctx.log);
1435
+ capturedScreenshots++;
1436
+ ctx.task.output = chalk__default.default.gray(`screenshots captured: ${capturedScreenshots}/${totalScreenshots}`);
1437
+ }
1438
+ yield page.close();
1439
+ yield context.close();
1440
+ }
1074
1441
  }
1075
- const context = yield browser.newContext();
1076
- for (let j = 0; j < totalScreenshots; j++) {
1077
- let screenshot = screenshots[j];
1078
- let screenshotId = screenshot.name.toLowerCase().replace(/\s/g, "-");
1079
- const page = yield context.newPage();
1080
- yield page.goto(screenshot.url.trim(), pageOptions);
1081
- for (let k = 0; k < totalViewports; k++) {
1082
- let { width, height } = ctx.webConfig.viewports[k];
1083
- let ssName = `${browserName}-${width}x${height}-${screenshotId}.png`;
1084
- let ssPath = `screenshots/${screenshotId}/${ssName}.png`;
1085
- yield page.setViewportSize({ width, height: height || MIN_VIEWPORT_HEIGHT2 });
1086
- if (height === 0)
1087
- yield page.evaluate(scrollToBottomAndBackToTop);
1088
- yield page.waitForTimeout(screenshot.waitForTimeout || 0);
1089
- yield page.screenshot({ path: ssPath, fullPage: height ? false : true });
1090
- let completed = i == totalBrowsers - 1 && j == totalScreenshots - 1 && k == totalViewports - 1 ? true : false;
1091
- browserName = browserName === BROWSER_SAFARI ? PW_WEBKIT : browserName;
1092
- ctx.client.uploadScreenshot(ctx.build, ssPath, screenshot.name, browserName, `${width}x${height}`, completed);
1442
+ if (ctx.config.mobile) {
1443
+ let contextChrome = yield (_g = browsers[constants_default.CHROME]) == null ? void 0 : _g.newContext();
1444
+ let contextSafari = yield (_h = browsers[constants_default.SAFARI]) == null ? void 0 : _h.newContext();
1445
+ let pageChrome = yield contextChrome == null ? void 0 : contextChrome.newPage();
1446
+ let pageSafari = yield contextSafari == null ? void 0 : contextSafari.newPage();
1447
+ yield pageChrome == null ? void 0 : pageChrome.goto(staticConfig.url.trim(), pageOptions);
1448
+ yield pageSafari == null ? void 0 : pageSafari.goto(staticConfig.url.trim(), pageOptions);
1449
+ for (let device of ctx.config.mobile.devices) {
1450
+ let ssPath = `screenshots/${screenshotId}/${device.replace(/\s/g, "_")}_${screenshotId}.png`;
1451
+ let { width, height } = constants_default.SUPPORTED_MOBILE_DEVICES[device].viewport;
1452
+ let portrait = ctx.config.mobile.orientation === constants_default.MOBILE_ORIENTATION_PORTRAIT ? true : false;
1453
+ if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === constants_default.MOBILE_OS_ANDROID) {
1454
+ yield pageChrome == null ? void 0 : pageChrome.setViewportSize({ width: portrait ? width : height, height: portrait ? height : width });
1455
+ if (ctx.config.mobile.fullPage)
1456
+ yield pageChrome == null ? void 0 : pageChrome.evaluate(scrollToBottomAndBackToTop);
1457
+ yield pageChrome == null ? void 0 : pageChrome.waitForTimeout(staticConfig.waitForTimeout || 0);
1458
+ yield pageChrome == null ? void 0 : pageChrome.screenshot({ path: ssPath, fullPage: ctx.config.mobile.fullPage });
1459
+ yield ctx.client.uploadScreenshot(ctx.build, ssPath, staticConfig.name, constants_default.CHROME, `${device} (${ctx.config.mobile.orientation})`, ctx.log);
1460
+ } else {
1461
+ yield pageSafari == null ? void 0 : pageSafari.setViewportSize({ width: portrait ? width : height, height: portrait ? height : width });
1462
+ if (ctx.config.mobile.fullPage)
1463
+ yield pageChrome == null ? void 0 : pageChrome.evaluate(scrollToBottomAndBackToTop);
1464
+ yield pageSafari == null ? void 0 : pageSafari.waitForTimeout(staticConfig.waitForTimeout || 0);
1465
+ yield pageSafari == null ? void 0 : pageSafari.screenshot({ path: ssPath, fullPage: ctx.config.mobile.fullPage });
1466
+ yield ctx.client.uploadScreenshot(ctx.build, ssPath, staticConfig.name, constants_default.PW_WEBKIT, `${device} (${ctx.config.mobile.orientation})`, ctx.log);
1467
+ }
1093
1468
  capturedScreenshots++;
1094
- ctx.task.output = chalk__default.default.gray(`screenshots captured: ${capturedScreenshots}/${totalBrowsers * totalViewports * totalScreenshots}`);
1469
+ ctx.task.output = chalk__default.default.gray(`screenshots captured: ${capturedScreenshots}/${totalScreenshots}`);
1095
1470
  }
1096
- yield page.close();
1471
+ yield pageChrome == null ? void 0 : pageChrome.close();
1472
+ yield pageSafari == null ? void 0 : pageSafari.close();
1473
+ yield contextChrome == null ? void 0 : contextChrome.close();
1474
+ yield contextSafari == null ? void 0 : contextSafari.close();
1097
1475
  }
1098
- yield browser.close();
1099
- } catch (error) {
1100
- if (browser)
1101
- yield browser.close();
1102
- throw error;
1103
1476
  }
1477
+ yield closeBrowsers(browsers);
1478
+ delDir("screenshots");
1479
+ } catch (error) {
1480
+ yield closeBrowsers(browsers);
1481
+ delDir("screenshots");
1482
+ throw error;
1104
1483
  }
1105
- return totalBrowsers * totalViewports * totalScreenshots;
1484
+ return capturedScreenshots;
1106
1485
  });
1107
1486
  }
1108
1487
  var captureScreenshots_default = (ctx) => {
@@ -1111,7 +1490,7 @@ var captureScreenshots_default = (ctx) => {
1111
1490
  task: (ctx2, task) => __async(void 0, null, function* () {
1112
1491
  try {
1113
1492
  ctx2.task = task;
1114
- let totalScreenshots = yield captureScreenshots(ctx2, ctx2.webStaticConfig);
1493
+ let totalScreenshots = yield captureScreenshots(ctx2);
1115
1494
  task.title = "Screenshots captured successfully";
1116
1495
  task.output = chalk__default.default.gray(`total screenshots: ${totalScreenshots}`);
1117
1496
  } catch (error) {
@@ -1129,12 +1508,12 @@ var command2 = new commander.Command();
1129
1508
  command2.name("capture").description("Capture screenshots of static sites").argument("<file>", "Web static config file").action(function(file, _, command3) {
1130
1509
  return __async(this, null, function* () {
1131
1510
  let ctx = ctx_default(command3.optsWithGlobals());
1132
- if (!fs2__default.default.existsSync(file)) {
1511
+ if (!fs4__default.default.existsSync(file)) {
1133
1512
  console.log(`Error: Web Static Config file ${file} not found.`);
1134
1513
  return;
1135
1514
  }
1136
1515
  try {
1137
- ctx.webStaticConfig = JSON.parse(fs2__default.default.readFileSync(file, "utf8"));
1516
+ ctx.webStaticConfig = JSON.parse(fs4__default.default.readFileSync(file, "utf8"));
1138
1517
  if (!validateWebStaticConfig(ctx.webStaticConfig))
1139
1518
  throw new Error(validateWebStaticConfig.errors[0].message);
1140
1519
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdatest/smartui-cli",
3
- "version": "2.0.8",
3
+ "version": "3.0.0",
4
4
  "description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
5
5
  "files": [
6
6
  "dist/**/*"