@testrigor/testrigor-cli 0.0.51-beta → 1.0.1

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.
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const core_1 = require("@oclif/core");
5
+ const node_fetch_commonjs_1 = tslib_1.__importDefault(require("node-fetch-commonjs"));
6
+ const command_base_1 = require("../../command-base");
7
+ const environments_1 = require("../../config/environments");
8
+ const parser_1 = require("../../util/parser");
9
+ const core_2 = require("@oclif/core");
10
+ class ValidateSyntax extends command_base_1.CommandBase {
11
+ async run() {
12
+ const flags = this.flags;
13
+ const args = this.args;
14
+ const baseUrlApi = flags["base-url-api"] || environments_1.environments.prod.TESTRIGOR_API_URL;
15
+ this.TESTRIGOR_API2_URL = `${baseUrlApi.replace(/\/$/, "")}/api/v1`;
16
+ if (!flags.steps && !flags["test-cases-path"]) {
17
+ this.error("Provide either --steps or --test-cases-path");
18
+ }
19
+ const token = await this.resolveToken();
20
+ const testSuiteId = await this.resolveTestSuiteId(args.testSuiteId);
21
+ const casesToValidate = await this.resolveStepsToValidate(flags.steps, flags["test-cases-path"]);
22
+ if (casesToValidate.length === 0) {
23
+ this.error("No test case files matched --test-cases-path");
24
+ }
25
+ const rules = flags["rules-path"]
26
+ ? await (0, parser_1.parseRules)(flags["rules-path"])
27
+ : [];
28
+ const explicitMutations = flags["explicit-mutations"];
29
+ await this.reportValidationResults(testSuiteId, token, casesToValidate, rules, explicitMutations);
30
+ }
31
+ async resolveStepsToValidate(steps, testCasesPath) {
32
+ if (steps) {
33
+ return [{ description: "provided steps", customSteps: steps }];
34
+ }
35
+ const testCases = await (0, parser_1.parseTestCases)(testCasesPath);
36
+ return testCases.map((testCase) => ({
37
+ description: testCase.description,
38
+ customSteps: testCase.customSteps,
39
+ }));
40
+ }
41
+ async reportValidationResults(testSuiteId, token, cases, rules, explicitMutations) {
42
+ let hasAnyInvalidTestCases = false;
43
+ for (const testCase of cases) {
44
+ const response = await this.callValidateSyntax(testSuiteId, testCase.customSteps, token, rules, explicitMutations);
45
+ if (response.data && response.data.length > 0) {
46
+ const parserErrors = response.data;
47
+ core_2.ux.info(`${testCase.description}: invalid`);
48
+ for (const parserError of parserErrors) {
49
+ core_2.ux.info(` ${parserError.command} - ${parserError.errorMessage}`);
50
+ }
51
+ hasAnyInvalidTestCases = true;
52
+ }
53
+ else {
54
+ core_2.ux.info(`${testCase.description}: valid`);
55
+ }
56
+ }
57
+ if (hasAnyInvalidTestCases) {
58
+ this.exit(1);
59
+ }
60
+ core_2.ux.info("All provided steps are valid.");
61
+ this.exit(0);
62
+ }
63
+ async callValidateSyntax(testSuiteId, steps, token, rules, explicitMutations) {
64
+ const body = { steps };
65
+ if (rules.length > 0) {
66
+ body.rules = rules;
67
+ }
68
+ if (explicitMutations) {
69
+ body.explicitMutations = explicitMutations;
70
+ }
71
+ const response = await (0, node_fetch_commonjs_1.default)(`${this.TESTRIGOR_API2_URL}/apps/${testSuiteId}/validate-syntax`, {
72
+ method: "POST",
73
+ body: JSON.stringify(body),
74
+ headers: {
75
+ "Content-Type": "application/json",
76
+ Accept: "application/json",
77
+ "auth-token": token,
78
+ },
79
+ });
80
+ if (!response.ok) {
81
+ throw new Error(`Failed to validate syntax: ${response.status} ${response.statusText}`);
82
+ }
83
+ return (await response.json());
84
+ }
85
+ }
86
+ ValidateSyntax.description = "Validate step syntax against a test suite's reusable rules, without running the steps";
87
+ ValidateSyntax.examples = [
88
+ 'testrigor test-suite validate-syntax <test-suite-id> --steps "click \\"Login\\""',
89
+ 'testrigor test-suite validate-syntax <test-suite-id> --test-cases-path "test_cases/**/*.{yaml,txt}"',
90
+ ];
91
+ ValidateSyntax.flags = {
92
+ steps: core_1.Flags.string({
93
+ description: "Raw step text to validate directly",
94
+ exclusive: ["test-cases-path"],
95
+ }),
96
+ "test-cases-path": core_1.Flags.string({
97
+ description: "Test case files to validate (same glob format as `test-suite run --test-cases-path`)",
98
+ exclusive: ["steps"],
99
+ }),
100
+ "base-url-api": core_1.Flags.string({
101
+ description: "Base URL for API2 (e.g., https://api2.testrigor.com)",
102
+ }),
103
+ "rules-path": core_1.Flags.string({
104
+ description: "Ad hoc reusable rules to validate against, for rules not yet saved to this suite (same glob format as `test-suite run --rules-path`)",
105
+ }),
106
+ "explicit-mutations": core_1.Flags.boolean({
107
+ description: "Validate using only the supplied rules instead of merging them with the suite's saved rules",
108
+ default: false,
109
+ }),
110
+ };
111
+ ValidateSyntax.args = {
112
+ testSuiteId: core_1.Args.string({
113
+ name: "testSuiteId",
114
+ description: "The test suite id (optional if set in config)",
115
+ required: false,
116
+ }),
117
+ };
118
+ exports.default = ValidateSyntax;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChromeExtensionFlag = void 0;
4
+ const tslib_1 = require("tslib");
4
5
  const core_1 = require("@oclif/core");
5
- const fs = require("fs");
6
- const path = require("path");
6
+ const fs = tslib_1.__importStar(require("fs"));
7
+ const path = tslib_1.__importStar(require("path"));
7
8
  const utils_1 = require("../util/utils");
8
9
  let totalChromeExtensionSize = 0;
9
10
  exports.ChromeExtensionFlag = core_1.Flags.custom({
package/dist/types.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import { ReadStream } from "fs";
3
2
  export type RetestResponse = {
4
3
  result: string;
@@ -51,6 +50,13 @@ export type Rule = {
51
50
  name: string;
52
51
  labels: string[];
53
52
  };
53
+ export type ParserError = {
54
+ command?: string;
55
+ errorMessage?: string;
56
+ };
57
+ export type ValidateSyntaxApiResponse = Api2Wrapper & {
58
+ data?: ParserError[];
59
+ };
54
60
  export type ChromeExtensionInput = {
55
61
  name: string;
56
62
  file: string;
@@ -1,12 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.stopAppium = exports.startAppium = exports.getRunningAppiumPid = exports.isProcessRunning = exports.ensureDriver = exports.getDriverStatus = exports.checkAppiumInstalled = exports.PID_FILE = exports.PID_DIR = exports.REQUIRED_DRIVERS = exports.DEFAULT_BASE_PATH = exports.DEFAULT_PORT = void 0;
4
- const childProcess = require("child_process");
5
- const fs = require("fs");
6
- const util = require("util");
7
- const path = require("path");
8
- const os = require("os");
9
- const Fs = require("@supercharge/fs");
3
+ exports.PID_FILE = exports.PID_DIR = exports.REQUIRED_DRIVERS = exports.DEFAULT_BASE_PATH = exports.DEFAULT_PORT = void 0;
4
+ exports.checkAppiumInstalled = checkAppiumInstalled;
5
+ exports.getDriverStatus = getDriverStatus;
6
+ exports.ensureDriver = ensureDriver;
7
+ exports.isProcessRunning = isProcessRunning;
8
+ exports.getRunningAppiumPid = getRunningAppiumPid;
9
+ exports.startAppium = startAppium;
10
+ exports.stopAppium = stopAppium;
11
+ const tslib_1 = require("tslib");
12
+ const childProcess = tslib_1.__importStar(require("child_process"));
13
+ const fs = tslib_1.__importStar(require("fs"));
14
+ const util = tslib_1.__importStar(require("util"));
15
+ const path = tslib_1.__importStar(require("path"));
16
+ const os = tslib_1.__importStar(require("os"));
17
+ const fs_1 = tslib_1.__importDefault(require("@supercharge/fs"));
10
18
  const execFile = util.promisify(childProcess.execFile);
11
19
  const PROJECT_ROOT = path.resolve(__dirname, "..", "..");
12
20
  const APPIUM_HOME = path.join(PROJECT_ROOT, ".appium");
@@ -36,7 +44,7 @@ function getAppiumEnv() {
36
44
  }
37
45
  async function checkAppiumInstalled() {
38
46
  const appiumBin = getAppiumBin();
39
- if (!(await Fs.exists(appiumBin))) {
47
+ if (!(await fs_1.default.exists(appiumBin))) {
40
48
  throw new Error(`Appium binary not found at ${appiumBin}. Try reinstalling testrigor-cli.`);
41
49
  }
42
50
  try {
@@ -46,7 +54,6 @@ async function checkAppiumInstalled() {
46
54
  throw new Error(`Appium binary at ${appiumBin} is not working. Try reinstalling testrigor-cli.`);
47
55
  }
48
56
  }
49
- exports.checkAppiumInstalled = checkAppiumInstalled;
50
57
  async function getDriverStatus(driverName, requiredVersion) {
51
58
  const appiumBin = getAppiumBin();
52
59
  try {
@@ -66,7 +73,6 @@ async function getDriverStatus(driverName, requiredVersion) {
66
73
  return { installed: false, upToDate: false };
67
74
  }
68
75
  }
69
- exports.getDriverStatus = getDriverStatus;
70
76
  async function ensureDriver(driverName, requiredVersion) {
71
77
  const appiumBin = getAppiumBin();
72
78
  const env = getAppiumEnv();
@@ -86,10 +92,12 @@ async function ensureDriver(driverName, requiredVersion) {
86
92
  }
87
93
  catch (error) {
88
94
  const raw = error instanceof Error ? error.message : String(error);
89
- return raw.split("\n")[0].replace(/^Command failed:.*?Error:\s*/, "").trim();
95
+ return raw
96
+ .split("\n")[0]
97
+ .replace(/^Command failed:.*?Error:\s*/, "")
98
+ .trim();
90
99
  }
91
100
  }
92
- exports.ensureDriver = ensureDriver;
93
101
  function isProcessRunning(pid) {
94
102
  try {
95
103
  process.kill(pid, 0);
@@ -99,24 +107,22 @@ function isProcessRunning(pid) {
99
107
  return false;
100
108
  }
101
109
  }
102
- exports.isProcessRunning = isProcessRunning;
103
110
  function killSafe(pid) {
104
111
  if (isProcessRunning(pid)) {
105
112
  process.kill(pid);
106
113
  }
107
114
  }
108
115
  async function getRunningAppiumPid() {
109
- if (!(await Fs.exists(PID_FILE))) {
116
+ if (!(await fs_1.default.exists(PID_FILE))) {
110
117
  return null;
111
118
  }
112
- const existingPid = Number((await Fs.content(PID_FILE)).trim());
119
+ const existingPid = Number((await fs_1.default.content(PID_FILE)).trim());
113
120
  if (isProcessRunning(existingPid)) {
114
121
  return existingPid;
115
122
  }
116
- await Fs.removeFile(PID_FILE);
123
+ await fs_1.default.removeFile(PID_FILE);
117
124
  return null;
118
125
  }
119
- exports.getRunningAppiumPid = getRunningAppiumPid;
120
126
  async function healthCheck(port, retries = 15, interval = 2000) {
121
127
  const url = `http://127.0.0.1:${port}/wd/hub/status`;
122
128
  let lastError = "";
@@ -161,29 +167,27 @@ async function startAppium(port = DEFAULT_PORT, logLevel = "debug") {
161
167
  throw new Error("Failed to start Appium server: no PID returned");
162
168
  }
163
169
  appiumProcess.unref();
164
- await Fs.ensureDir(PID_DIR);
165
- await Fs.writeFile(PID_FILE, String(pid));
170
+ await fs_1.default.ensureDir(PID_DIR);
171
+ await fs_1.default.writeFile(PID_FILE, String(pid));
166
172
  try {
167
173
  await healthCheck(port);
168
174
  }
169
175
  catch (err) {
170
176
  killSafe(pid);
171
- await Fs.removeFile(PID_FILE);
177
+ await fs_1.default.removeFile(PID_FILE);
172
178
  throw err;
173
179
  }
174
180
  return { pid, alreadyRunning: false };
175
181
  }
176
- exports.startAppium = startAppium;
177
182
  async function stopAppium() {
178
- if (!(await Fs.exists(PID_FILE))) {
183
+ if (!(await fs_1.default.exists(PID_FILE))) {
179
184
  return null;
180
185
  }
181
- const pidContent = (await Fs.content(PID_FILE)).trim();
186
+ const pidContent = (await fs_1.default.content(PID_FILE)).trim();
182
187
  const pid = Number(pidContent);
183
188
  if (!isNaN(pid) && isProcessRunning(pid)) {
184
189
  process.kill(pid, "SIGTERM");
185
190
  }
186
- await Fs.removeFile(PID_FILE);
191
+ await fs_1.default.removeFile(PID_FILE);
187
192
  return pid;
188
193
  }
189
- exports.stopAppium = stopAppium;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadClientCertificate = void 0;
4
- const fs = require("fs");
5
- const path = require("path");
6
- const node_fetch_commonjs_1 = require("node-fetch-commonjs");
3
+ exports.uploadClientCertificate = uploadClientCertificate;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const node_fetch_commonjs_1 = tslib_1.__importDefault(require("node-fetch-commonjs"));
7
8
  const type_guards_1 = require("./type-guards");
8
9
  async function uploadClientCertificate(api2BaseUrl, testSuiteId, token, localFilePath, password) {
9
10
  const fileName = path.basename(localFilePath);
@@ -29,4 +30,3 @@ async function uploadClientCertificate(api2BaseUrl, testSuiteId, token, localFil
29
30
  info: responseJson.data.info,
30
31
  };
31
32
  }
32
- exports.uploadClientCertificate = uploadClientCertificate;
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteConfigProperty = exports.updateConfigProperty = exports.readConfigProperty = exports.writeConfig = exports.readConfig = exports.getConfigPath = void 0;
3
+ exports.getConfigPath = getConfigPath;
4
+ exports.readConfig = readConfig;
5
+ exports.writeConfig = writeConfig;
6
+ exports.readConfigProperty = readConfigProperty;
7
+ exports.updateConfigProperty = updateConfigProperty;
8
+ exports.deleteConfigProperty = deleteConfigProperty;
9
+ const tslib_1 = require("tslib");
4
10
  const core_1 = require("@oclif/core");
5
- const Fs = require("@supercharge/fs");
6
- const path = require("path");
11
+ const fs_1 = tslib_1.__importDefault(require("@supercharge/fs"));
12
+ const path = tslib_1.__importStar(require("path"));
7
13
  const yaml_1 = require("yaml");
8
14
  const utils_1 = require("./utils");
9
15
  /**
@@ -14,17 +20,16 @@ function getConfigPath() {
14
20
  const configPath = path.join(homeDir, "testrigor.yml");
15
21
  return { homeDir, configPath };
16
22
  }
17
- exports.getConfigPath = getConfigPath;
18
23
  /**
19
24
  * Read testRigor configuration from ~/.testrigor/testrigor.yml
20
25
  */
21
26
  async function readConfig() {
22
27
  const { configPath } = getConfigPath();
23
- if (!(await Fs.pathExists(configPath))) {
28
+ if (!(await fs_1.default.pathExists(configPath))) {
24
29
  return {};
25
30
  }
26
31
  try {
27
- const content = await Fs.content(configPath);
32
+ const content = await fs_1.default.content(configPath);
28
33
  return (0, yaml_1.parse)(content.toString()) || {};
29
34
  }
30
35
  catch (error) {
@@ -34,26 +39,24 @@ async function readConfig() {
34
39
  return {};
35
40
  }
36
41
  }
37
- exports.readConfig = readConfig;
38
42
  /**
39
43
  * Write testRigor configuration to ~/.testrigor/testrigor.yml
40
44
  */
41
45
  async function writeConfig(config) {
42
46
  const { homeDir, configPath } = getConfigPath();
43
47
  // Ensure the config directory exists
44
- if (!(await Fs.pathExists(homeDir))) {
45
- await Fs.ensureDir(homeDir);
48
+ if (!(await fs_1.default.pathExists(homeDir))) {
49
+ await fs_1.default.ensureDir(homeDir);
46
50
  }
47
51
  // Avoid writing empty objects as "{}"
48
52
  const configContent = Object.keys(config).length > 0 ? (0, yaml_1.stringify)(config) : "";
49
53
  if (configContent.trim() === "") {
50
- await Fs.writeFile(configPath, "");
54
+ await fs_1.default.writeFile(configPath, "");
51
55
  }
52
56
  else {
53
- await Fs.writeFile(configPath, configContent);
57
+ await fs_1.default.writeFile(configPath, configContent);
54
58
  }
55
59
  }
56
- exports.writeConfig = writeConfig;
57
60
  /**
58
61
  * Read a specific property from testRigor configuration
59
62
  */
@@ -61,7 +64,6 @@ async function readConfigProperty(property) {
61
64
  const config = await readConfig();
62
65
  return config[property] || null;
63
66
  }
64
- exports.readConfigProperty = readConfigProperty;
65
67
  /**
66
68
  * Update a specific property in testRigor configuration
67
69
  */
@@ -75,7 +77,6 @@ async function updateConfigProperty(property, value) {
75
77
  }
76
78
  await writeConfig(config);
77
79
  }
78
- exports.updateConfigProperty = updateConfigProperty;
79
80
  /**
80
81
  * Delete a specific property from testRigor configuration
81
82
  */
@@ -84,4 +85,3 @@ async function deleteConfigProperty(property) {
84
85
  delete config[property];
85
86
  await writeConfig(config);
86
87
  }
87
- exports.deleteConfigProperty = deleteConfigProperty;
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseSettings = exports.parseRules = exports.parseTestCases = void 0;
4
- const Fs = require("@supercharge/fs");
3
+ exports.parseTestCases = parseTestCases;
4
+ exports.parseRules = parseRules;
5
+ exports.parseSettings = parseSettings;
6
+ const tslib_1 = require("tslib");
7
+ const fs_1 = tslib_1.__importDefault(require("@supercharge/fs"));
5
8
  const glob_1 = require("glob");
6
9
  const yaml_1 = require("yaml");
7
10
  const utils_1 = require("./utils");
@@ -10,7 +13,7 @@ async function parseTestCases(path) {
10
13
  const files = await (0, glob_1.glob)(path, { withFileTypes: true });
11
14
  for (const file of files) {
12
15
  if (file.isFile()) {
13
- const content = await Fs.content(file.fullpath());
16
+ const content = await fs_1.default.content(file.fullpath());
14
17
  if ((0, utils_1.isYamlFile)(file.name)) {
15
18
  const json = (0, yaml_1.parse)(content.toString());
16
19
  const testCase = {
@@ -39,13 +42,12 @@ async function parseTestCases(path) {
39
42
  }
40
43
  return result;
41
44
  }
42
- exports.parseTestCases = parseTestCases;
43
45
  async function parseRules(path) {
44
46
  const result = [];
45
47
  const files = await (0, glob_1.glob)(path, { withFileTypes: true });
46
48
  for (const file of files) {
47
49
  if (file.isFile()) {
48
- const content = await Fs.content(file.fullpath());
50
+ const content = await fs_1.default.content(file.fullpath());
49
51
  if ((0, utils_1.isYamlFile)(file.name)) {
50
52
  const json = (0, yaml_1.parse)(content.toString());
51
53
  const ruleName = (0, utils_1.removeExtension)(file.name);
@@ -69,9 +71,8 @@ async function parseRules(path) {
69
71
  }
70
72
  return result;
71
73
  }
72
- exports.parseRules = parseRules;
73
74
  async function parseSettings(path) {
74
- const content = await Fs.content(path);
75
+ const content = await fs_1.default.content(path);
75
76
  if ((0, utils_1.isYamlFile)(path)) {
76
77
  return (0, yaml_1.parse)(content.toString());
77
78
  }
@@ -80,4 +81,3 @@ async function parseSettings(path) {
80
81
  }
81
82
  return {};
82
83
  }
83
- exports.parseSettings = parseSettings;
@@ -1,4 +1,4 @@
1
- import * as FormData from "form-data";
1
+ import FormData from "form-data";
2
2
  import { ChromeExtensionInput } from "../types";
3
3
  export interface ChromeExtensionFormDataResult {
4
4
  form: FormData;
@@ -1,14 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildChromeExtensionFormData = void 0;
4
- const FormData = require("form-data");
5
- const Fs = require("@supercharge/fs");
6
- const path = require("path");
3
+ exports.buildChromeExtensionFormData = buildChromeExtensionFormData;
4
+ const tslib_1 = require("tslib");
5
+ const form_data_1 = tslib_1.__importDefault(require("form-data"));
6
+ const fs_1 = tslib_1.__importDefault(require("@supercharge/fs"));
7
+ const path = tslib_1.__importStar(require("path"));
7
8
  function toChromeExtensionOverrideItem(input) {
8
9
  try {
9
10
  return {
10
11
  name: input.name,
11
- file: Fs.createReadStream(input.file),
12
+ file: fs_1.default.createReadStream(input.file),
12
13
  };
13
14
  }
14
15
  catch (error) {
@@ -29,7 +30,7 @@ function toChromeExtensionOverridesRequest(extensionInputs) {
29
30
  */
30
31
  function buildChromeExtensionFormData(chromeExtensions) {
31
32
  const { request, streams } = toChromeExtensionOverridesRequest(chromeExtensions);
32
- const form = new FormData();
33
+ const form = new form_data_1.default();
33
34
  request.chromeExtensions.forEach((ext, index) => {
34
35
  form.append(`chromeExtensions[${index}].name`, ext.name);
35
36
  const fileName = path.basename(chromeExtensions[index].file);
@@ -46,4 +47,3 @@ function buildChromeExtensionFormData(chromeExtensions) {
46
47
  };
47
48
  return { form, cleanup };
48
49
  }
49
- exports.buildChromeExtensionFormData = buildChromeExtensionFormData;
@@ -1 +1,3 @@
1
+ export declare function extractZip(dir: string, stream: NodeJS.ReadableStream): Promise<string>;
2
+ export declare function extractTar(dir: string, stream: NodeJS.ReadableStream): Promise<string>;
1
3
  export declare function download(dir: string): Promise<string>;