@msalaam/xray-qe-toolkit 1.1.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/.env.example +16 -0
- package/README.md +893 -0
- package/bin/cli.js +137 -0
- package/commands/createExecution.js +46 -0
- package/commands/editJson.js +165 -0
- package/commands/genPipeline.js +42 -0
- package/commands/genPostman.js +70 -0
- package/commands/genTests.js +138 -0
- package/commands/importResults.js +50 -0
- package/commands/init.js +141 -0
- package/commands/pushTests.js +114 -0
- package/lib/config.js +108 -0
- package/lib/index.js +31 -0
- package/lib/logger.js +43 -0
- package/lib/postmanGenerator.js +305 -0
- package/lib/testCaseBuilder.js +202 -0
- package/lib/xrayClient.js +416 -0
- package/package.json +61 -0
- package/schema/tests.schema.json +112 -0
- package/templates/README.template.md +161 -0
- package/templates/azure-pipelines.yml +65 -0
- package/templates/knowledge-README.md +121 -0
- package/templates/tests.json +72 -0
- package/templates/xray-mapping.json +1 -0
- package/ui/editor/editor.js +484 -0
- package/ui/editor/index.html +150 -0
- package/ui/editor/styles.css +550 -0
package/commands/init.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command: xray-qe init
|
|
3
|
+
*
|
|
4
|
+
* Scaffolds a new project with starter templates:
|
|
5
|
+
* - tests.json (test definitions)
|
|
6
|
+
* - xray-mapping.json (empty mapping)
|
|
7
|
+
* - .env.example (environment variable template)
|
|
8
|
+
* - .xrayrc (optional project-level config)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import logger from "../lib/logger.js";
|
|
15
|
+
import { setVerbose } from "../lib/logger.js";
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
const TEMPLATES = path.join(__dirname, "..", "templates");
|
|
20
|
+
|
|
21
|
+
export default async function init(opts = {}) {
|
|
22
|
+
if (opts.verbose) setVerbose(true);
|
|
23
|
+
|
|
24
|
+
const cwd = process.cwd();
|
|
25
|
+
logger.rocket("@msalaam/xray-qe-toolkit — Project Init\n");
|
|
26
|
+
|
|
27
|
+
let created = 0;
|
|
28
|
+
let skipped = 0;
|
|
29
|
+
|
|
30
|
+
// Create knowledge/ directory structure
|
|
31
|
+
const knowledgeDirs = [
|
|
32
|
+
{ path: "knowledge", desc: "Knowledge base folder" },
|
|
33
|
+
{ path: "knowledge/api-specs", desc: "API specifications (OpenAPI, Swagger)" },
|
|
34
|
+
{ path: "knowledge/requirements", desc: "Business requirements and docs" },
|
|
35
|
+
{ path: "knowledge/tickets", desc: "JIRA/Confluence exports" },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
for (const dir of knowledgeDirs) {
|
|
39
|
+
const dirPath = path.join(cwd, dir.path);
|
|
40
|
+
if (!fs.existsSync(dirPath)) {
|
|
41
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
42
|
+
logger.success(`${dir.path}/ created (${dir.desc})`);
|
|
43
|
+
created++;
|
|
44
|
+
} else {
|
|
45
|
+
logger.warn(`${dir.path}/ already exists — skipping`);
|
|
46
|
+
skipped++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Copy knowledge README
|
|
51
|
+
const knowledgeReadmeSrc = path.join(TEMPLATES, "knowledge-README.md");
|
|
52
|
+
const knowledgeReadmeDest = path.join(cwd, "knowledge", "README.md");
|
|
53
|
+
if (!fs.existsSync(knowledgeReadmeDest)) {
|
|
54
|
+
fs.copyFileSync(knowledgeReadmeSrc, knowledgeReadmeDest);
|
|
55
|
+
logger.success("knowledge/README.md created (Knowledge base guide)");
|
|
56
|
+
created++;
|
|
57
|
+
} else {
|
|
58
|
+
logger.warn("knowledge/README.md already exists — skipping");
|
|
59
|
+
skipped++;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Copy main README
|
|
63
|
+
const readmeSrc = path.join(TEMPLATES, "README.template.md");
|
|
64
|
+
const readmeDest = path.join(cwd, "README.md");
|
|
65
|
+
if (!fs.existsSync(readmeDest)) {
|
|
66
|
+
fs.copyFileSync(readmeSrc, readmeDest);
|
|
67
|
+
logger.success("README.md created (Getting started guide)");
|
|
68
|
+
created++;
|
|
69
|
+
} else {
|
|
70
|
+
logger.warn("README.md already exists — skipping");
|
|
71
|
+
skipped++;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create .gitkeep files in knowledge subdirs to ensure they're tracked
|
|
75
|
+
for (const subdir of ["api-specs", "requirements", "tickets"]) {
|
|
76
|
+
const gitkeepPath = path.join(cwd, "knowledge", subdir, ".gitkeep");
|
|
77
|
+
if (!fs.existsSync(gitkeepPath)) {
|
|
78
|
+
fs.writeFileSync(gitkeepPath, "");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const files = [
|
|
83
|
+
{ src: "tests.json", dest: "tests.json", desc: "Test definitions" },
|
|
84
|
+
{ src: "xray-mapping.json", dest: "xray-mapping.json", desc: "Xray mapping (empty)" },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
const destPath = path.join(cwd, file.dest);
|
|
89
|
+
if (fs.existsSync(destPath)) {
|
|
90
|
+
logger.warn(`${file.dest} already exists — skipping`);
|
|
91
|
+
skipped++;
|
|
92
|
+
} else {
|
|
93
|
+
fs.copyFileSync(path.join(TEMPLATES, file.src), destPath);
|
|
94
|
+
logger.success(`${file.dest} created (${file.desc})`);
|
|
95
|
+
created++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Copy .env.example from package root
|
|
100
|
+
const envExampleSrc = path.join(__dirname, "..", ".env.example");
|
|
101
|
+
const envExampleDest = path.join(cwd, ".env.example");
|
|
102
|
+
if (!fs.existsSync(envExampleDest)) {
|
|
103
|
+
fs.copyFileSync(envExampleSrc, envExampleDest);
|
|
104
|
+
logger.success(".env.example created");
|
|
105
|
+
created++;
|
|
106
|
+
} else {
|
|
107
|
+
logger.warn(".env.example already exists — skipping");
|
|
108
|
+
skipped++;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Create .xrayrc if it doesn't exist
|
|
112
|
+
const xrayrcPath = path.join(cwd, ".xrayrc");
|
|
113
|
+
if (!fs.existsSync(xrayrcPath)) {
|
|
114
|
+
const xrayrc = {
|
|
115
|
+
testsPath: "tests.json",
|
|
116
|
+
mappingPath: "xray-mapping.json",
|
|
117
|
+
collectionPath: "collection.postman.json",
|
|
118
|
+
knowledgePath: "knowledge",
|
|
119
|
+
playwrightDir: "playwright-tests",
|
|
120
|
+
};
|
|
121
|
+
fs.writeFileSync(xrayrcPath, JSON.stringify(xrayrc, null, 2));
|
|
122
|
+
logger.success(".xrayrc created (project config)");
|
|
123
|
+
created++;
|
|
124
|
+
} else {
|
|
125
|
+
logger.warn(".xrayrc already exists — skipping");
|
|
126
|
+
skipped++;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
logger.success(`${created} file(s)/folder(s) created, ${skipped} skipped\n`);
|
|
130
|
+
|
|
131
|
+
// Print next steps
|
|
132
|
+
console.log("📖 Next steps:");
|
|
133
|
+
console.log(" 1. Copy .env.example → .env and fill in your credentials");
|
|
134
|
+
console.log(" 2. Add API specs and docs to knowledge/ folder (see knowledge/README.md)");
|
|
135
|
+
console.log(" 3. Generate test cases: npx xray-qe gen-tests --ai (or edit tests.json manually)");
|
|
136
|
+
console.log(" 4. Review tests: npx xray-qe edit-json");
|
|
137
|
+
console.log(" 5. Push tests to Xray: npx xray-qe push-tests");
|
|
138
|
+
console.log(" 6. Generate Postman collection: npx xray-qe gen-postman --ai");
|
|
139
|
+
console.log(" 7. Generate CI pipeline: npx xray-qe gen-pipeline");
|
|
140
|
+
console.log("");
|
|
141
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command: xray-qe push-tests [--testExecKey QE-123] [--skip-exec]
|
|
3
|
+
*
|
|
4
|
+
* Validates tests.json against schema, then pushes tests to Xray Cloud.
|
|
5
|
+
* Idempotent: updates existing tests (via xray-mapping.json) and creates new ones.
|
|
6
|
+
*
|
|
7
|
+
* If --testExecKey is provided, links all tests to that existing Test Execution.
|
|
8
|
+
* If --skip-exec is set, tests are pushed without creating/linking any execution.
|
|
9
|
+
* Otherwise, creates a new Test Execution from tests.json config and links all tests.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import logger, { setVerbose } from "../lib/logger.js";
|
|
15
|
+
import { loadConfig, validateConfig } from "../lib/config.js";
|
|
16
|
+
import {
|
|
17
|
+
buildAndPush,
|
|
18
|
+
loadMapping,
|
|
19
|
+
createExecutionAndLink,
|
|
20
|
+
linkToExistingExecution,
|
|
21
|
+
saveMapping,
|
|
22
|
+
} from "../lib/testCaseBuilder.js";
|
|
23
|
+
|
|
24
|
+
export default async function pushTests(opts = {}) {
|
|
25
|
+
if (opts.verbose) setVerbose(true);
|
|
26
|
+
|
|
27
|
+
logger.rocket("@msalaam/xray-qe-toolkit — Push Tests\n");
|
|
28
|
+
|
|
29
|
+
// Load & validate config
|
|
30
|
+
const cfg = loadConfig({ envPath: opts.env });
|
|
31
|
+
validateConfig(cfg);
|
|
32
|
+
|
|
33
|
+
// Load tests.json
|
|
34
|
+
if (!fs.existsSync(cfg.testsPath)) {
|
|
35
|
+
logger.error(`tests.json not found at ${cfg.testsPath}`);
|
|
36
|
+
logger.info("Run 'npx xray-qe init' to create a starter tests.json");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const testsConfig = JSON.parse(fs.readFileSync(cfg.testsPath, "utf8"));
|
|
41
|
+
const allTests = testsConfig.tests || [];
|
|
42
|
+
|
|
43
|
+
// Filter out skipped tests
|
|
44
|
+
const tests = allTests.filter((t) => !t.skip);
|
|
45
|
+
const skippedCount = allTests.length - tests.length;
|
|
46
|
+
|
|
47
|
+
if (tests.length === 0) {
|
|
48
|
+
logger.error("No tests to push (all tests are skipped or tests array is empty)");
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logger.info(`${tests.length} test(s) to push${skippedCount > 0 ? ` (${skippedCount} skipped)` : ""}\n`);
|
|
53
|
+
|
|
54
|
+
// Validate against schema (soft validation — warn but continue)
|
|
55
|
+
try {
|
|
56
|
+
const require = createRequire(import.meta.url);
|
|
57
|
+
const Ajv = require("ajv");
|
|
58
|
+
const schema = JSON.parse(
|
|
59
|
+
fs.readFileSync(new URL("../schema/tests.schema.json", import.meta.url), "utf8")
|
|
60
|
+
);
|
|
61
|
+
const ajv = new Ajv({ allErrors: true });
|
|
62
|
+
const validate = ajv.compile(schema);
|
|
63
|
+
if (!validate(testsConfig)) {
|
|
64
|
+
logger.warn("tests.json has schema validation warnings:");
|
|
65
|
+
for (const err of validate.errors) {
|
|
66
|
+
console.log(` ⚠️ ${err.instancePath || "/"}: ${err.message}`);
|
|
67
|
+
}
|
|
68
|
+
logger.blank();
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
logger.debug("Schema validation skipped (ajv not available)");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Load existing mapping
|
|
75
|
+
const mapping = loadMapping(cfg.mappingPath);
|
|
76
|
+
|
|
77
|
+
// Build & push
|
|
78
|
+
const result = await buildAndPush(cfg, tests, mapping);
|
|
79
|
+
|
|
80
|
+
logger.blank();
|
|
81
|
+
logger.success(
|
|
82
|
+
`${result.created.length} created, ${result.updated.length} updated, ${result.failed.length} failed`
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Handle Test Execution linking
|
|
86
|
+
const allTestKeys = [
|
|
87
|
+
...result.created,
|
|
88
|
+
...result.updated,
|
|
89
|
+
...Object.values(mapping)
|
|
90
|
+
.filter((v) => v.key && !v.key.startsWith("_"))
|
|
91
|
+
.map((v) => v.key),
|
|
92
|
+
];
|
|
93
|
+
// Deduplicate
|
|
94
|
+
const testKeys = [...new Set(allTestKeys)];
|
|
95
|
+
|
|
96
|
+
if (opts.skipExec) {
|
|
97
|
+
logger.info("Skipping Test Execution (--skip-exec)");
|
|
98
|
+
} else if (opts.testExecKey) {
|
|
99
|
+
// Link to existing execution
|
|
100
|
+
logger.blank();
|
|
101
|
+
const linkResult = await linkToExistingExecution(cfg, opts.testExecKey, testKeys);
|
|
102
|
+
logger.link(`View: ${cfg.jiraUrl}/browse/${opts.testExecKey}`);
|
|
103
|
+
} else if (testsConfig.testExecution) {
|
|
104
|
+
// Create new execution and link
|
|
105
|
+
logger.blank();
|
|
106
|
+
const execResult = await createExecutionAndLink(cfg, testsConfig.testExecution, testKeys, mapping);
|
|
107
|
+
saveMapping(cfg.mappingPath, mapping);
|
|
108
|
+
logger.link(`View Test Execution: ${cfg.jiraUrl}/browse/${execResult.key}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
logger.blank();
|
|
112
|
+
logger.save(`Mapping saved to ${cfg.mappingPath}`);
|
|
113
|
+
logger.blank();
|
|
114
|
+
}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @msalaam/xray-qe-toolkit — Configuration Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads and validates environment variables from .env and optional .xrayrc file.
|
|
5
|
+
* All 6 required env vars must be present before any API call.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { config as dotenvConfig } from "dotenv";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
const REQUIRED_VARS = [
|
|
13
|
+
"XRAY_ID",
|
|
14
|
+
"XRAY_SECRET",
|
|
15
|
+
"JIRA_PROJECT_KEY",
|
|
16
|
+
"JIRA_EMAIL",
|
|
17
|
+
"JIRA_API_TOKEN",
|
|
18
|
+
"JIRA_URL",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load configuration from .env file and optional .xrayrc JSON file.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} [opts] Options
|
|
25
|
+
* @param {string} [opts.envPath] Custom path to .env file
|
|
26
|
+
* @param {string} [opts.cwd] Working directory (defaults to process.cwd())
|
|
27
|
+
* @returns {object} Frozen configuration object
|
|
28
|
+
*/
|
|
29
|
+
export function loadConfig(opts = {}) {
|
|
30
|
+
const cwd = opts.cwd || process.cwd();
|
|
31
|
+
|
|
32
|
+
// Load .env — custom path takes precedence
|
|
33
|
+
const envPath = opts.envPath || path.join(cwd, ".env");
|
|
34
|
+
if (fs.existsSync(envPath)) {
|
|
35
|
+
dotenvConfig({ path: envPath });
|
|
36
|
+
} else {
|
|
37
|
+
// Try default dotenv resolution
|
|
38
|
+
dotenvConfig();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Merge optional .xrayrc (non-sensitive project defaults)
|
|
42
|
+
const rcPath = path.join(cwd, ".xrayrc");
|
|
43
|
+
let rcConfig = {};
|
|
44
|
+
if (fs.existsSync(rcPath)) {
|
|
45
|
+
try {
|
|
46
|
+
rcConfig = JSON.parse(fs.readFileSync(rcPath, "utf8"));
|
|
47
|
+
} catch {
|
|
48
|
+
// Ignore malformed .xrayrc — env vars take precedence anyway
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const cfg = {
|
|
53
|
+
// Xray Cloud
|
|
54
|
+
xrayId: process.env.XRAY_ID || rcConfig.xrayId,
|
|
55
|
+
xraySecret: process.env.XRAY_SECRET || rcConfig.xraySecret,
|
|
56
|
+
xrayGraphqlUrl:
|
|
57
|
+
process.env.XRAY_GRAPHQL_URL ||
|
|
58
|
+
rcConfig.xrayGraphqlUrl ||
|
|
59
|
+
"https://us.xray.cloud.getxray.app/api/v2/graphql",
|
|
60
|
+
xrayAuthUrl:
|
|
61
|
+
process.env.XRAY_AUTH_URL ||
|
|
62
|
+
rcConfig.xrayAuthUrl ||
|
|
63
|
+
"https://xray.cloud.getxray.app/api/v2/authenticate",
|
|
64
|
+
|
|
65
|
+
// JIRA
|
|
66
|
+
jiraProjectKey: process.env.JIRA_PROJECT_KEY || rcConfig.jiraProjectKey,
|
|
67
|
+
jiraUrl: (process.env.JIRA_URL || rcConfig.jiraUrl || "").replace(/\/+$/, ""),
|
|
68
|
+
jiraApiToken: process.env.JIRA_API_TOKEN || rcConfig.jiraApiToken,
|
|
69
|
+
jiraEmail: process.env.JIRA_EMAIL || rcConfig.jiraEmail,
|
|
70
|
+
|
|
71
|
+
// Paths (resolved relative to consuming project)
|
|
72
|
+
testsPath: path.resolve(cwd, rcConfig.testsPath || "tests.json"),
|
|
73
|
+
mappingPath: path.resolve(cwd, rcConfig.mappingPath || "xray-mapping.json"),
|
|
74
|
+
collectionPath: path.resolve(cwd, rcConfig.collectionPath || "collection.postman.json"),
|
|
75
|
+
knowledgePath: path.resolve(cwd, rcConfig.knowledgePath || "knowledge"),
|
|
76
|
+
playwrightDir: path.resolve(cwd, rcConfig.playwrightDir || "playwright-tests"),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return Object.freeze(cfg);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validate that all required environment variables are set.
|
|
84
|
+
*
|
|
85
|
+
* @param {object} cfg Config object from loadConfig()
|
|
86
|
+
* @throws {Error} If any required variable is missing
|
|
87
|
+
*/
|
|
88
|
+
export function validateConfig(cfg) {
|
|
89
|
+
const map = {
|
|
90
|
+
XRAY_ID: cfg.xrayId,
|
|
91
|
+
XRAY_SECRET: cfg.xraySecret,
|
|
92
|
+
JIRA_PROJECT_KEY: cfg.jiraProjectKey,
|
|
93
|
+
JIRA_URL: cfg.jiraUrl,
|
|
94
|
+
JIRA_API_TOKEN: cfg.jiraApiToken,
|
|
95
|
+
JIRA_EMAIL: cfg.jiraEmail,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const missing = Object.entries(map)
|
|
99
|
+
.filter(([, v]) => !v)
|
|
100
|
+
.map(([k]) => k);
|
|
101
|
+
|
|
102
|
+
if (missing.length > 0) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Missing required configuration: ${missing.join(", ")}.\n` +
|
|
105
|
+
`Set them in your .env file or .xrayrc. See .env.example for reference.`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @msalaam/xray-qe-toolkit — Public API
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all library modules for programmatic usage.
|
|
5
|
+
* Consumers can also use the CLI (bin/cli.js) for interactive workflows.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { loadConfig, validateConfig } from "./config.js";
|
|
9
|
+
export { default as logger, setVerbose } from "./logger.js";
|
|
10
|
+
export {
|
|
11
|
+
authenticate,
|
|
12
|
+
createIssue,
|
|
13
|
+
updateIssue,
|
|
14
|
+
getIssue,
|
|
15
|
+
setTestTypeAutomated,
|
|
16
|
+
addTestStep,
|
|
17
|
+
removeAllTestSteps,
|
|
18
|
+
linkIssues,
|
|
19
|
+
linkMultiple,
|
|
20
|
+
importResults,
|
|
21
|
+
withRetry,
|
|
22
|
+
toAdf,
|
|
23
|
+
} from "./xrayClient.js";
|
|
24
|
+
export {
|
|
25
|
+
buildAndPush,
|
|
26
|
+
createExecutionAndLink,
|
|
27
|
+
linkToExistingExecution,
|
|
28
|
+
loadMapping,
|
|
29
|
+
saveMapping,
|
|
30
|
+
} from "./testCaseBuilder.js";
|
|
31
|
+
export { generate as generatePostmanCollection } from "./postmanGenerator.js";
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @msalaam/xray-qe-toolkit — Logger
|
|
3
|
+
*
|
|
4
|
+
* Emoji-prefixed, level-aware console logger.
|
|
5
|
+
* Mirrors the output style of the original @oldmutual/xray-test-automation package.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
let verbose = false;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Enable or disable verbose (debug) output.
|
|
12
|
+
* @param {boolean} flag
|
|
13
|
+
*/
|
|
14
|
+
export function setVerbose(flag) {
|
|
15
|
+
verbose = !!flag;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @returns {boolean} */
|
|
19
|
+
export function isVerbose() {
|
|
20
|
+
return verbose;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const logger = {
|
|
24
|
+
info: (...args) => console.log("📋", ...args),
|
|
25
|
+
success: (...args) => console.log("✅", ...args),
|
|
26
|
+
warn: (...args) => console.log("⚠️ ", ...args),
|
|
27
|
+
error: (...args) => console.error("❌", ...args),
|
|
28
|
+
step: (...args) => console.log(" ✅", ...args),
|
|
29
|
+
stepFail:(...args) => console.log(" ❌", ...args),
|
|
30
|
+
send: (...args) => console.log("📤", ...args),
|
|
31
|
+
link: (...args) => console.log("🔗", ...args),
|
|
32
|
+
auth: (...args) => console.log("🔐", ...args),
|
|
33
|
+
save: (...args) => console.log("💾", ...args),
|
|
34
|
+
wait: (...args) => console.log("⏳", ...args),
|
|
35
|
+
rocket: (...args) => console.log("🚀", ...args),
|
|
36
|
+
pin: (...args) => console.log("📌", ...args),
|
|
37
|
+
debug: (...args) => {
|
|
38
|
+
if (verbose) console.log("🐛", ...args);
|
|
39
|
+
},
|
|
40
|
+
blank: () => console.log(""),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default logger;
|