@lambdatest/smartui-cli 1.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.
- package/package.json +28 -0
- package/src/capture.js +15 -0
- package/src/config.js +79 -0
- package/src/constant.js +9 -0
- package/src/index.js +49 -0
- package/src/validate.js +134 -0
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lambdatest/smartui-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/Lambdatest/smartui-cli"
|
|
13
|
+
},
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@lambdatest/smartui-logger": "1.0.0",
|
|
18
|
+
"@lambdatest/smartui-core": "1.0.0",
|
|
19
|
+
"commander": "^11.0.0",
|
|
20
|
+
"fs": "^0.0.1-security",
|
|
21
|
+
"path": "^0.12.7",
|
|
22
|
+
"url": "^0.11.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@lambdatest/smartui-logger": "file:../logger",
|
|
26
|
+
"@lambdatest/smartui-core": "file:../core"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/capture.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import SmartUI from "@lambdatest/smartui-core"
|
|
2
|
+
import { parse } from './validate.js';
|
|
3
|
+
|
|
4
|
+
async function capture(file, options, log) {
|
|
5
|
+
let screenshots = parse(file);
|
|
6
|
+
log.debug(screenshots);
|
|
7
|
+
options.screenshots = screenshots
|
|
8
|
+
const smartui = new SmartUI();
|
|
9
|
+
await smartui.createBuild(options, log);
|
|
10
|
+
await smartui.capture(options, log);
|
|
11
|
+
await smartui.cleanup(true, log);
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { capture };
|
package/src/config.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { ABNORMAL_EXIT } from './constant.js';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export const defaultScreenshotConfig = [
|
|
6
|
+
{
|
|
7
|
+
"name": "lambdatest-home-page",
|
|
8
|
+
"url": "https://www.lambdatest.com",
|
|
9
|
+
"waitForTimeout": 1000
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "example-page",
|
|
13
|
+
"url": "https://example.com/"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export const defaultSmartUIWebConfig = {
|
|
18
|
+
web: {
|
|
19
|
+
browsers: [
|
|
20
|
+
'chrome',
|
|
21
|
+
'firefox',
|
|
22
|
+
'safari',
|
|
23
|
+
'edge'
|
|
24
|
+
],
|
|
25
|
+
resolutions: [
|
|
26
|
+
[1920, 1080],
|
|
27
|
+
[1366, 768],
|
|
28
|
+
[360, 640],
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function createWebConfig(filepath, log) {
|
|
34
|
+
// default filepath
|
|
35
|
+
filepath = filepath || 'smartui-web.json';
|
|
36
|
+
let filetype = path.extname(filepath);
|
|
37
|
+
if (filetype != '.json') {
|
|
38
|
+
log.error(`Error: Web Config file must have .json extension`);
|
|
39
|
+
process.exitCode = ABNORMAL_EXIT;
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// verify the file does not already exist
|
|
44
|
+
if (fs.existsSync(filepath)) {
|
|
45
|
+
log.error(`Error: SmartUI Web Config already exists: ${filepath}`);
|
|
46
|
+
log.error(`To create a new file, please specify the file name like: 'smartui config:create-web webConfig.json'`);
|
|
47
|
+
process.exitCode = ABNORMAL_EXIT;
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// write stringified default config options to the filepath
|
|
52
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
53
|
+
fs.writeFileSync(filepath, JSON.stringify(defaultSmartUIWebConfig, null, 2) + '\n');
|
|
54
|
+
log.info(`Created SmartUI Web Config: ${filepath}`);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function createWebStaticConfig(filepath, log) {
|
|
58
|
+
// default filepath
|
|
59
|
+
filepath = filepath || 'url.json';
|
|
60
|
+
let filetype = path.extname(filepath);
|
|
61
|
+
if (filetype != '.json') {
|
|
62
|
+
log.error(`Error: Config file must have .json extension`);
|
|
63
|
+
process.exitCode = ABNORMAL_EXIT;
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// verify the file does not already exist
|
|
68
|
+
if (fs.existsSync(filepath)) {
|
|
69
|
+
log.error(`Error: web-static config already exists: ${filepath}`);
|
|
70
|
+
log.error(`To create a new file, please specify the file name like: 'smartui config:create-web links.json'`);
|
|
71
|
+
process.exitCode = ABNORMAL_EXIT;
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// write stringified default config options to the filepath
|
|
76
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
77
|
+
fs.writeFileSync(filepath, JSON.stringify(defaultScreenshotConfig, null, 2) + '\n');
|
|
78
|
+
log.info(`Created web-static config: ${filepath}`);
|
|
79
|
+
};
|
package/src/constant.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
// Error codes
|
|
3
|
+
export const ABNORMAL_EXIT = 1;
|
|
4
|
+
export const MAX_RESOLUTIONS = 5
|
|
5
|
+
export const MIN_RESOLUTION_WIDTH = 320
|
|
6
|
+
export const MIN_RESOLUTION_HEIGHT = 320
|
|
7
|
+
export const MAX_RESOLUTION_WIDTH = 7680
|
|
8
|
+
export const MAX_RESOLUTION_HEIGHT = 7680
|
|
9
|
+
export const VALID_BROWSERS = ['chrome', 'safari', 'firefox', 'edge'];
|
package/src/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
import { capture } from './capture.js';
|
|
3
|
+
import { logger } from '@lambdatest/smartui-logger';
|
|
4
|
+
import { validateScreenshotConfig } from './validate.js';
|
|
5
|
+
import packageJson from '../package.json' assert { type: 'json' };
|
|
6
|
+
import { createWebConfig, createWebStaticConfig } from './config.js';
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('smartui')
|
|
12
|
+
.description('CLI to help you to take screenshot SmartUI on LambdaTest platform')
|
|
13
|
+
.addOption(new Option('--env <prod|stage>', 'Runtime environment option').choices(['prod', 'stage']));
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('capture <file>')
|
|
17
|
+
.description('capture')
|
|
18
|
+
.option('-c --config <file>', 'Config file path')
|
|
19
|
+
.action(async function (file, options) {
|
|
20
|
+
const log = await logger();
|
|
21
|
+
options.env = program.opts().env || 'prod';
|
|
22
|
+
log.info('SmartUI Capture CLI v' + packageJson.version);
|
|
23
|
+
options.config = validateScreenshotConfig(file, options, log);
|
|
24
|
+
log.info('config validation done');
|
|
25
|
+
log.debug(options);
|
|
26
|
+
capture(file, options, log);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
program.command('config:create-web')
|
|
30
|
+
.description('Create SmartUI Web config file')
|
|
31
|
+
.argument('[filepath]', 'Optional config filepath')
|
|
32
|
+
.action(async function (filepath, options) {
|
|
33
|
+
const log = await logger();
|
|
34
|
+
log.info('SmartUI Config CLI v' + packageJson.version);
|
|
35
|
+
log.info('\n');
|
|
36
|
+
createWebConfig(filepath, log);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
program.command('config:web-static')
|
|
40
|
+
.description('Create Web Static config file')
|
|
41
|
+
.argument('[filepath]', 'Optional config filepath')
|
|
42
|
+
.action(async function (filepath, options) {
|
|
43
|
+
const log = await logger();
|
|
44
|
+
log.info('SmartUI Config CLI v' + packageJson.version);
|
|
45
|
+
log.info('\n');
|
|
46
|
+
createWebStaticConfig(filepath, log);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
program.parse();
|
package/src/validate.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { ABNORMAL_EXIT, VALID_BROWSERS, MAX_RESOLUTIONS, MAX_RESOLUTION_WIDTH, MAX_RESOLUTION_HEIGHT, MIN_RESOLUTION_WIDTH, MIN_RESOLUTION_HEIGHT } from './constant.js';
|
|
3
|
+
import { URL } from 'url';
|
|
4
|
+
import { defaultSmartUIWebConfig } from './config.js'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// Parse the JSON data
|
|
8
|
+
export function parse(file) {
|
|
9
|
+
const data = fs.readFileSync(file, 'utf-8');
|
|
10
|
+
return JSON.parse(data);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function validateScreenshotConfig(configFile, options, log) {
|
|
14
|
+
log.info(`file : ${configFile}`)
|
|
15
|
+
|
|
16
|
+
// Check if file exists
|
|
17
|
+
if (Object.keys(configFile).length) {
|
|
18
|
+
try {
|
|
19
|
+
fs.promises.access(configFile);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
log.error(`Error: Either File does not exist ${configFile} or doesn't have read permissions. Trace : ${error}`);
|
|
22
|
+
log.debug(error)
|
|
23
|
+
process.exit(ABNORMAL_EXIT);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
let screenshots = {};
|
|
29
|
+
// Check JSON Parse Error
|
|
30
|
+
if (Object.keys(configFile).length) {
|
|
31
|
+
try {
|
|
32
|
+
screenshots = parse(configFile)
|
|
33
|
+
} catch (error) {
|
|
34
|
+
log.error('Error: Invalid file, capture command only supports json file');
|
|
35
|
+
process.exit(ABNORMAL_EXIT);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
log.debug(screenshots)
|
|
40
|
+
|
|
41
|
+
//Check for URLs should not be empty
|
|
42
|
+
for (const screenshot of screenshots) {
|
|
43
|
+
if (!screenshot.name || screenshot.name == '') {
|
|
44
|
+
log.error(`Error: Missing screenshot name in ${configFile}`);
|
|
45
|
+
process.exit(ABNORMAL_EXIT);
|
|
46
|
+
}
|
|
47
|
+
if (!screenshot.url || screenshot.url == '') {
|
|
48
|
+
log.error('Error: Missing required URL for screenshot : '+screenshot.name);
|
|
49
|
+
process.exit(ABNORMAL_EXIT);
|
|
50
|
+
}
|
|
51
|
+
//Check for URLs should valid (like abcd in URL)
|
|
52
|
+
try {
|
|
53
|
+
new URL(screenshot.url);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
log.error('Error: Invalid screenshot URL: ' + screenshot.url);
|
|
56
|
+
process.exit(ABNORMAL_EXIT);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log.debug(options)
|
|
61
|
+
|
|
62
|
+
//Check for smartui-web.json
|
|
63
|
+
let webConfigFile = options.config
|
|
64
|
+
// Verify config file exists
|
|
65
|
+
if (!fs.existsSync(webConfigFile)) {
|
|
66
|
+
log.error(`Error: Config file not found, will use default configs`);
|
|
67
|
+
return defaultSmartUIWebConfig
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Parse JSON
|
|
71
|
+
let webConfig;
|
|
72
|
+
try {
|
|
73
|
+
webConfig = JSON.parse(fs.readFileSync(webConfigFile));
|
|
74
|
+
} catch (error) {
|
|
75
|
+
log.error('Error: ', error.message);
|
|
76
|
+
process.exit(ABNORMAL_EXIT);
|
|
77
|
+
}
|
|
78
|
+
if (webConfig && webConfig.web) {
|
|
79
|
+
try {
|
|
80
|
+
validateConfigBrowsers(webConfig.web.browsers);
|
|
81
|
+
webConfig.web.resolutions = validateConfigResolutions(webConfig.web.resolutions);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
log.error(`Error: Invalid webConfig, ${error.message}`);
|
|
84
|
+
process.exit(ABNORMAL_EXIT);
|
|
85
|
+
}
|
|
86
|
+
return webConfig
|
|
87
|
+
} else {
|
|
88
|
+
log.error(`No webConfig found in ${webConfigFile} file, will use default configs`);
|
|
89
|
+
return defaultSmartUIWebConfig
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
function validateConfigBrowsers(browsers) {
|
|
95
|
+
if (browsers.length == 0) {
|
|
96
|
+
throw new ValidationError('empty browsers list.');
|
|
97
|
+
}
|
|
98
|
+
const set = new Set();
|
|
99
|
+
for (let element of browsers) {
|
|
100
|
+
if (!VALID_BROWSERS.includes(element.toLowerCase()) || set.has(element)) {
|
|
101
|
+
throw new ValidationError(`invalid or duplicate value for browser. Accepted browsers are ${VALID_BROWSERS.join(',')}`);
|
|
102
|
+
}
|
|
103
|
+
set.add(element);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function validateConfigResolutions(resolutions) {
|
|
108
|
+
if (!Array.isArray(resolutions)) {
|
|
109
|
+
throw new ValidationError('invalid resolutions.');
|
|
110
|
+
}
|
|
111
|
+
if (resolutions.length == 0) {
|
|
112
|
+
throw new ValidationError('empty resolutions list in config.');
|
|
113
|
+
}
|
|
114
|
+
if (resolutions.length > 5) {
|
|
115
|
+
throw new ValidationError(`max resolutions: ${MAX_RESOLUTIONS}`);
|
|
116
|
+
}
|
|
117
|
+
let res = [];
|
|
118
|
+
resolutions.forEach(element => {
|
|
119
|
+
if (!Array.isArray(element) || element.length == 0 || element.length > 2) {
|
|
120
|
+
throw new ValidationError('invalid resolutions.');
|
|
121
|
+
}
|
|
122
|
+
let width = element[0];
|
|
123
|
+
let height = element[1];
|
|
124
|
+
if (typeof width != 'number' || width < MIN_RESOLUTION_WIDTH || width > MAX_RESOLUTION_WIDTH) {
|
|
125
|
+
throw new ValidationError(`width must be > ${MIN_RESOLUTION_WIDTH}, < ${MAX_RESOLUTION_WIDTH}`);
|
|
126
|
+
}
|
|
127
|
+
if (height && (typeof height != 'number' || height < MIN_RESOLUTION_WIDTH || height > MAX_RESOLUTION_WIDTH)) {
|
|
128
|
+
throw new ValidationError(`height must be > ${MIN_RESOLUTION_HEIGHT}, < ${MAX_RESOLUTION_HEIGHT}`);
|
|
129
|
+
}
|
|
130
|
+
res.push([width, height || 0]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return res
|
|
134
|
+
}
|