artes 1.4.13 → 1.5.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.
@@ -172,6 +172,15 @@ module.exports = {
172
172
  worldParameters: artesConfig.worldParameters || {}, // Custom world parameters
173
173
  },
174
174
  report: {
175
+ logo: process.env.LOGO
176
+ ? process.env.LOGO
177
+ : artesConfig?.logo || "./logo.png",
178
+ brandName: process.env.BRAND_NAME
179
+ ? process.env.BRAND_NAME
180
+ : artesConfig?.brandName || "ARTES",
181
+ reportName: process.env.REPORT_NAME
182
+ ? process.env.REPORT_NAME
183
+ : artesConfig?.reportName || "ARTES REPORT",
175
184
  singleFileReport:
176
185
  process.env.SINGLE_FILE_REPORT == "true"
177
186
  ? true
@@ -0,0 +1,198 @@
1
+ # CI/CD Executor Integration guide
2
+
3
+ This document describes the environment variables used to identify which CI/CD runner is executing your tests. When these variables are detected, the corresponding runner will automatically appear in your report.
4
+
5
+ ---
6
+
7
+ ## How It Works
8
+
9
+ The reporter inspects environment variables at runtime to detect the current CI/CD platform. **No configuration is needed** — simply ensure the variables below are present in your environment, and the correct runner will be shown in the report automatically.
10
+
11
+ ### ✅ Supported Platforms
12
+
13
+ | Platform | Auto-detected via |
14
+ | --------------------- | ---------------------------------- |
15
+ | **GitHub Actions** | `GITHUB_RUN_ID` |
16
+ | **Jenkins** | `JENKINS_HOME` |
17
+ | **GitLab CI** | `CI_PIPELINE_ID` |
18
+ | **Bitbucket Pipelines** | `BITBUCKET_BUILD_NUMBER` |
19
+ | **CircleCI** | `CIRCLE_WORKFLOW_ID` |
20
+ | **Azure Pipelines** | `BUILD_BUILDID` |
21
+ | **TeamCity** | `BUILD_NUMBER` + `TEAMCITY_VERSION`|
22
+ | **Travis CI** | `TRAVIS_BUILD_NUMBER` |
23
+ | **Bamboo** | `bamboo_buildNumber` |
24
+ | **Local / Other** | Fallback — shown as "Local Run" |
25
+
26
+ When none of the above variables are present, the executor is shown as **Manual Execution** in the report.
27
+
28
+ ### Quick Reference — All Variables
29
+
30
+ | Platform | Required (Detection) | Optional (Enrichment) |
31
+ |---|---|---|
32
+ | GitHub Actions | `GITHUB_RUN_ID` | `GITHUB_RUN_NUMBER`, `GITHUB_SERVER_URL`, `GITHUB_REPOSITORY` |
33
+ | Jenkins | `JENKINS_HOME` | `JOB_NAME`, `BUILD_NUMBER`, `BUILD_URL` |
34
+ | GitLab CI | `CI_PIPELINE_ID` | `CI_PIPELINE_IID`, `CI_PIPELINE_URL` |
35
+ | Bitbucket | `BITBUCKET_BUILD_NUMBER` | `BITBUCKET_BUILD_URL` |
36
+ | CircleCI | `CIRCLE_WORKFLOW_ID` | `CIRCLE_BUILD_NUM`, `CIRCLE_BUILD_URL` |
37
+ | Azure Pipelines | `BUILD_BUILDID` | `BUILD_BUILDURI` |
38
+ | TeamCity | `BUILD_NUMBER` + `TEAMCITY_VERSION` | `BUILD_URL` |
39
+ | Travis CI | `TRAVIS_BUILD_NUMBER` | `TRAVIS_BUILD_WEB_URL` |
40
+ | Bamboo | `bamboo_buildNumber` | `bamboo_resultsUrl` |
41
+ ---
42
+
43
+ ## Supported CI/CD Platforms
44
+
45
+ ### ✅ GitHub Actions
46
+
47
+ Automatically detected when running inside a GitHub Actions workflow.
48
+
49
+ | Variable | Description | Example |
50
+ |---|---|---|
51
+ | `GITHUB_RUN_ID` | Unique ID of the workflow run *(required for detection)* | `9876543210` |
52
+ | `GITHUB_RUN_NUMBER` | Sequential run number for the workflow | `42` |
53
+ | `GITHUB_SERVER_URL` | Base URL of the GitHub server | `https://github.com` |
54
+ | `GITHUB_REPOSITORY` | Owner and repository name | `my-org/my-repo` |
55
+
56
+ > These variables are set automatically by GitHub Actions. No manual setup needed.
57
+
58
+ ---
59
+
60
+ ### ✅ Jenkins
61
+
62
+ Detected when `JENKINS_HOME` is set in the environment.
63
+
64
+ | Variable | Description | Example |
65
+ |---|---|---|
66
+ | `JENKINS_HOME` | Jenkins home directory path *(required for detection)* | `/var/jenkins_home` |
67
+ | `JOB_NAME` | Name of the Jenkins job | `my-pipeline` |
68
+ | `BUILD_NUMBER` | Sequential build number | `101` |
69
+ | `BUILD_URL` | Full URL to the build page | `http://jenkins.example.com/job/my-pipeline/101/` |
70
+
71
+ > These variables are set automatically by Jenkins. No manual setup needed.
72
+
73
+ ---
74
+
75
+ ### ✅ GitLab CI
76
+
77
+ Detected when `CI_PIPELINE_ID` is set in the environment.
78
+
79
+ | Variable | Description | Example |
80
+ |---|---|---|
81
+ | `CI_PIPELINE_ID` | Unique pipeline ID *(required for detection)* | `123456789` |
82
+ | `CI_PIPELINE_IID` | Project-scoped pipeline number (used for display) | `55` |
83
+ | `CI_PIPELINE_URL` | Full URL to the pipeline | `https://gitlab.com/my-org/my-repo/-/pipelines/55` |
84
+
85
+ > These variables are set automatically by GitLab CI/CD. No manual setup needed.
86
+
87
+ ---
88
+
89
+ ### ✅ Bitbucket Pipelines
90
+
91
+ Detected when `BITBUCKET_BUILD_NUMBER` is set in the environment.
92
+
93
+ | Variable | Description | Example |
94
+ |---|---|---|
95
+ | `BITBUCKET_BUILD_NUMBER` | Unique build number *(required for detection)* | `77` |
96
+ | `BITBUCKET_BUILD_URL` | URL to the Bitbucket pipeline build | `https://bitbucket.org/my-org/my-repo/addon/pipelines/home#!/results/77` |
97
+
98
+ > These variables are set automatically by Bitbucket Pipelines. No manual setup needed.
99
+
100
+ ---
101
+
102
+ ### ✅ CircleCI
103
+
104
+ Detected when `CIRCLE_WORKFLOW_ID` is set in the environment.
105
+
106
+ | Variable | Description | Example |
107
+ |---|---|---|
108
+ | `CIRCLE_WORKFLOW_ID` | Unique workflow ID *(required for detection)* | `abc-123-def-456` |
109
+ | `CIRCLE_BUILD_NUM` | Sequential build number | `88` |
110
+ | `CIRCLE_BUILD_URL` | URL to the CircleCI build | `https://circleci.com/gh/my-org/my-repo/88` |
111
+
112
+ > These variables are set automatically by CircleCI. No manual setup needed.
113
+
114
+ ---
115
+
116
+ ### ✅ Azure Pipelines
117
+
118
+ Detected when `BUILD_BUILDID` is set in the environment.
119
+
120
+ | Variable | Description | Example |
121
+ |---|---|---|
122
+ | `BUILD_BUILDID` | Unique build ID *(required for detection)* | `5001` |
123
+ | `BUILD_BUILDURI` | URI to the Azure DevOps build | `vstfs:///Build/Build/5001` |
124
+
125
+ > These variables are set automatically by Azure Pipelines. No manual setup needed.
126
+
127
+ ---
128
+
129
+ ### ✅ TeamCity
130
+
131
+ Detected when **both** `BUILD_NUMBER` and `TEAMCITY_VERSION` are set.
132
+
133
+ | Variable | Description | Example |
134
+ |---|---|---|
135
+ | `TEAMCITY_VERSION` | TeamCity server version *(required for detection)* | `2023.11` |
136
+ | `BUILD_NUMBER` | Sequential build number *(required for detection)* | `99` |
137
+ | `BUILD_URL` | URL to the TeamCity build | `http://teamcity.example.com/build/99` |
138
+
139
+ > These variables are set automatically by TeamCity. No manual setup needed.
140
+
141
+ ---
142
+
143
+ ### ✅ Travis CI
144
+
145
+ Detected when `TRAVIS_BUILD_NUMBER` is set in the environment.
146
+
147
+ | Variable | Description | Example |
148
+ |---|---|---|
149
+ | `TRAVIS_BUILD_NUMBER` | Sequential build number *(required for detection)* | `123` |
150
+ | `TRAVIS_BUILD_WEB_URL` | URL to the Travis CI build | `https://travis-ci.com/my-org/my-repo/builds/123` |
151
+
152
+ > These variables are set automatically by Travis CI. No manual setup needed.
153
+
154
+ ---
155
+
156
+ ### ✅ Bamboo
157
+
158
+ Detected when `bamboo_buildNumber` is set in the environment.
159
+
160
+ | Variable | Description | Example |
161
+ |---|---|---|
162
+ | `bamboo_buildNumber` | Sequential build number *(required for detection)* | `56` |
163
+ | `bamboo_resultsUrl` | URL to the Bamboo build results | `http://bamboo.example.com/browse/MY-PROJECT-56` |
164
+
165
+ > These variables are set automatically by Bamboo. No manual setup needed.
166
+
167
+ ---
168
+
169
+ ### 🖥️ Local / Manual Run
170
+
171
+ If **none** of the above variables are detected, the report will show **"Local Run"** with build name `"Manual Execution"`. This is the default fallback for local development.
172
+
173
+ ---
174
+
175
+ ## Running in a Custom or Self-Hosted Environment
176
+
177
+ If you're using a custom CI system or a Docker container and want a specific runner to appear in the report, manually export the required variables before running your tests.
178
+
179
+ **Example — simulating a Jenkins build in a Docker container:**
180
+
181
+ ```bash
182
+ docker run \
183
+ -e JENKINS_HOME=/var/jenkins_home \
184
+ -e JOB_NAME="my-test-suite" \
185
+ -e BUILD_NUMBER=42 \
186
+ -e BUILD_URL="http://jenkins.example.com/job/my-test-suite/42/" \
187
+ my-test-image npm test
188
+ ```
189
+
190
+ **Example — simulating a GitHub Actions build locally:**
191
+
192
+ ```bash
193
+ export GITHUB_RUN_ID=9999999999
194
+ export GITHUB_RUN_NUMBER=1
195
+ export GITHUB_SERVER_URL=https://github.com
196
+ export GITHUB_REPOSITORY=my-org/my-repo
197
+
198
+ ```
package/executer.js CHANGED
@@ -37,6 +37,9 @@ const flags = {
37
37
  trace: args.includes("-t") || args.includes("--trace"),
38
38
  reportWithTrace: args.includes("-rwt") || args.includes("--reportWithTrace"),
39
39
  singleFileReport: args.includes("--singleFileReport"),
40
+ customLogo: args.includes("--logo"),
41
+ customBrandName:args.includes("--brandName"),
42
+ customReportName:args.includes("--reportName"),
40
43
  zip: args.includes("--zip"),
41
44
  features: args.includes("--features"),
42
45
  stepDef: args.includes("--stepDef"),
@@ -63,6 +66,9 @@ const flags = {
63
66
 
64
67
  const env = getArgValue("--env");
65
68
  const vars = getArgValue("--saveVar");
69
+ const logo = getArgValue("--logo");
70
+ const brandName = getArgValue("--brandName");
71
+ const reportName = getArgValue("--reportName");
66
72
  const featureFiles = getArgValue("--features");
67
73
  const features = flags.features && featureFiles;
68
74
  const stepDef = getArgValue("--stepDef");
@@ -93,6 +99,10 @@ artesConfig.report
93
99
  ]))
94
100
  : "";
95
101
 
102
+ flags.customLogo ? (process.env.LOGO = logo) : "";
103
+ flags.customBrandName ? (process.env.BRAND_NAME = brandName) : "";
104
+ flags.customReportName ? (process.env.REPORT_NAME = reportName) : "";
105
+
96
106
  flags.reportSuccess ? (process.env.REPORT_SUCCESS = true) : "";
97
107
 
98
108
  flags.tags && console.log("Running tags:", tags);
@@ -443,10 +453,12 @@ if (fs.existsSync(source)) {
443
453
  ){
444
454
  const executor = getExecutor();
445
455
 
456
+ if(fs.existsSync(path.join(process.cwd(), "node_modules", "artes",'allure-result'))){
446
457
  fs.writeFileSync(
447
458
  path.join(process.cwd(), "node_modules", "artes",'allure-result',"executor.json"),
448
459
  JSON.stringify(executor, null, 2)
449
460
  );
461
+ }
450
462
 
451
463
  generateReport();
452
464
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artes",
3
- "version": "1.4.13",
3
+ "version": "1.5.1",
4
4
  "description": "The simplest way to automate UI and API tests using Cucumber-style steps.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -28,7 +28,9 @@
28
28
  "archiver": "^7.0.1",
29
29
  "dayjs": "1.11.13",
30
30
  "deasync": "^0.1.31",
31
- "playwright": "^1.58.2"
31
+ "playwright": "^1.58.2",
32
+ "ffmpeg-static": "^5.3.0",
33
+ "ffprobe-static": "^3.1.0"
32
34
  },
33
35
  "repository": {
34
36
  "type": "git",
@@ -0,0 +1,193 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const https = require("https");
4
+ const http = require("http");
5
+
6
+ const CSS_START_MARKER = "/* ARTES_DYNAMIC_START */";
7
+ const CSS_END_MARKER = "/* ARTES_DYNAMIC_END */";
8
+
9
+ function reportCustomizer() {
10
+ delete require.cache[require.resolve("../../../cucumber.config.js")];
11
+ const cucumberConfig = require("../../../cucumber.config.js");
12
+
13
+ const report = cucumberConfig.report;
14
+ const today = new Date().toLocaleDateString("en-US", { month: "numeric", day: "numeric", year: "numeric" });
15
+ const reportName = typeof report.reportName === "string" ? report.reportName : report.reportName.name || "";
16
+
17
+ const { buffer: faviconBuffer, mime: faviconMime } = readFileAsBuffer(defaultLogoPath());
18
+ const faviconDataUrl = `data:${faviconMime};base64,${faviconBuffer.toString("base64")}`;
19
+
20
+ if (isRemoteUrl(report.logo)) {
21
+ return fetchRemoteLogo(report.logo)
22
+ .catch((err) => {
23
+ console.warn(`[artes] Warning: failed to fetch logo from "${report.logo}": ${err.message}. Falling back to default logo.`);
24
+ return readFileAsBuffer(defaultLogoPath());
25
+ })
26
+ .then(({ buffer, mime, filename }) => {
27
+ applyLogo(cucumberConfig, report, today, reportName, buffer, mime, filename, faviconDataUrl);
28
+ });
29
+ }
30
+
31
+ const logoSrc = resolveLogoPath(report.logo);
32
+ const logoExt = path.extname(logoSrc).replace(".", "").toLowerCase();
33
+ const logoMime = logoExt === "svg" ? "image/svg+xml" : `image/${logoExt}`;
34
+ const logoBuffer = fs.readFileSync(logoSrc);
35
+ applyLogo(cucumberConfig, report, today, reportName, logoBuffer, logoMime, path.basename(logoSrc), faviconDataUrl);
36
+ }
37
+
38
+ function applyLogo(cucumberConfig, report, today, reportName, logoBuffer, logoMime, logoFilename, faviconDataUrl) {
39
+ const logoBase64 = logoBuffer.toString("base64");
40
+ const logoDataUrl = `data:${logoMime};base64,${logoBase64}`;
41
+
42
+ if (cucumberConfig.report.singleFileReport) {
43
+ const htmlPath = path.resolve(__dirname, "../../../../../report/index.html");
44
+ const srcCssPath = path.resolve(__dirname, "../../../assets/styles.css");
45
+
46
+ const dynamicCss = generateCss(report, today, reportName, logoDataUrl);
47
+ const modifiedCss = injectCssAndReturn(srcCssPath, dynamicCss);
48
+ const cssBase64 = Buffer.from(modifiedCss).toString("base64");
49
+ const cssDataUrl = `data:text/css;base64,${cssBase64}`;
50
+
51
+
52
+ updateSingleFileHtml(htmlPath, report, reportName, faviconDataUrl, cssDataUrl);
53
+
54
+ } else {
55
+ const htmlPath = path.resolve(__dirname, "../../../../../report/index.html");
56
+ const srcCssPath = path.resolve(__dirname, "../../../assets/styles.css");
57
+ const reportDir = path.resolve(__dirname, "../../../../../report");
58
+ const reportCssPath = path.join(reportDir, "styles.css");
59
+
60
+
61
+ const logoDest = path.join(reportDir, logoFilename);
62
+ fs.writeFileSync(logoDest, logoBuffer);
63
+
64
+
65
+ const dynamicCss = generateCss(report, today, reportName, logoFilename);
66
+
67
+ const modifiedCss = injectCssAndReturn(srcCssPath, dynamicCss);
68
+ fs.writeFileSync(reportCssPath, modifiedCss, "utf8");
69
+
70
+ updateHtml(htmlPath, report, reportName, faviconDataUrl);
71
+ }
72
+ }
73
+
74
+ function resolveLogoPath(logoConfig) {
75
+ if (!logoConfig) {
76
+ return defaultLogoPath();
77
+ }
78
+
79
+ const resolved = path.isAbsolute(logoConfig)
80
+ ? logoConfig
81
+ : path.resolve(process.cwd(), logoConfig);
82
+
83
+ if (!fs.existsSync(resolved)) {
84
+ console.warn(`[artes] Warning: logo not found at "${resolved}". Falling back to default logo.`);
85
+ return defaultLogoPath();
86
+ }
87
+
88
+ return resolved;
89
+ }
90
+
91
+ function defaultLogoPath() {
92
+ return path.resolve(process.cwd(), "node_modules", "artes", "assets", "logo.png");
93
+ }
94
+
95
+ function isRemoteUrl(logoConfig) {
96
+ return typeof logoConfig === "string" && (logoConfig.startsWith("http://") || logoConfig.startsWith("https://"));
97
+ }
98
+
99
+ function readFileAsBuffer(filePath) {
100
+ const buffer = fs.readFileSync(filePath);
101
+ const ext = path.extname(filePath).replace(".", "").toLowerCase();
102
+ const mime = ext === "svg" ? "image/svg+xml" : `image/${ext}`;
103
+ const filename = path.basename(filePath);
104
+ return { buffer, mime, filename };
105
+ }
106
+
107
+ function fetchRemoteLogo(url, redirectCount = 0) {
108
+ return new Promise((resolve, reject) => {
109
+ if (redirectCount > 5) return reject(new Error("Too many redirects"));
110
+
111
+ const client = url.startsWith("https://") ? https : http;
112
+
113
+ client.get(url, (res) => {
114
+
115
+ if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
116
+ return fetchRemoteLogo(res.headers.location, redirectCount + 1).then(resolve).catch(reject);
117
+ }
118
+
119
+ if (res.statusCode !== 200) {
120
+ res.resume();
121
+ return reject(new Error(`HTTP ${res.statusCode}`));
122
+ }
123
+
124
+ const contentType = res.headers["content-type"] || "";
125
+ const mime = contentType.split(";")[0].trim();
126
+
127
+ if (!mime.startsWith("image/")) {
128
+ res.resume();
129
+ return reject(new Error(`URL did not return an image (Content-Type: "${mime || "unknown"}"). Make sure the URL points directly to an image file.`));
130
+ }
131
+
132
+ const urlPath = new URL(url).pathname;
133
+ const ext = path.extname(urlPath) || inferExtFromMime(mime);
134
+ const filename = path.basename(urlPath) || `logo${ext}`;
135
+
136
+ const chunks = [];
137
+ res.on("data", (chunk) => chunks.push(chunk));
138
+ res.on("end", () => resolve({ buffer: Buffer.concat(chunks), mime, filename }));
139
+ res.on("error", reject);
140
+ }).on("error", reject);
141
+ });
142
+ }
143
+
144
+ function inferExtFromMime(mime) {
145
+ const map = { "image/png": ".png", "image/jpeg": ".jpg", "image/gif": ".gif", "image/svg+xml": ".svg", "image/webp": ".webp" };
146
+ return map[mime] || ".png";
147
+ }
148
+
149
+ function generateCss(report, today, reportName, logoUrl) {
150
+ return `.side-nav__brand{background:url('${logoUrl}') no-repeat center left !important;background-size:70px 70px !important;height:80px;width:200px;display:flex !important;align-items:center;padding-left:65px}.side-nav__brand img,.side-nav__brand svg{display:none !important}.side-nav__brand-text{font-size:0 !important;display:block !important;padding: 0 8px;}.side-nav__brand-text::after{content:'${report.brandName}';font-size:26px;color:white;}.widget__title{font-weight:lighter;margin-bottom:15px;margin-top:0;text-transform:uppercase}.widget__flex-line:first-child .widget__title{font-size:0}.widget__flex-line:first-child .widget__title::before{content:'${reportName} ${today}';font-size:18px;font-weight:lighter;text-transform:uppercase}.widget__flex-line:first-child .widget__subtitle{font-size:14px}.widget__flex-line:not(:first-child) .widget__title{font-size:inherit;font-weight:lighter}`;
151
+ }
152
+
153
+ function injectCssAndReturn(cssPath, dynamicCss) {
154
+ let css = fs.readFileSync(cssPath, "utf8");
155
+
156
+ const startIdx = css.indexOf(CSS_START_MARKER);
157
+ const endIdx = css.indexOf(CSS_END_MARKER);
158
+ if (startIdx !== -1 && endIdx !== -1) {
159
+ css = css.slice(0, startIdx) + css.slice(endIdx + CSS_END_MARKER.length);
160
+ }
161
+
162
+ css = css.trimEnd();
163
+ css += `\n${CSS_START_MARKER}\n${dynamicCss}\n${CSS_END_MARKER}\n`;
164
+ return css;
165
+ }
166
+
167
+ function updateSingleFileHtml(htmlPath, report, reportName, faviconDataUrl, cssDataUrl) {
168
+ let html = fs.readFileSync(htmlPath, "utf8");
169
+
170
+ html = html.replace(/<title>.*?<\/title>/, `<title>ARTES REPORT</title>`);
171
+
172
+ html = html.replace(
173
+ /<link rel="icon" href="data:image\/[^"]+"/,
174
+ `<link rel="icon" href="${faviconDataUrl}"`
175
+ );
176
+
177
+ html = html.replace(
178
+ /<link rel="stylesheet" type="text\/css" href="data:text\/css;base64,[^"]+"/,
179
+ `<link rel="stylesheet" type="text/css" href="${cssDataUrl}"`
180
+ );
181
+
182
+ fs.writeFileSync(htmlPath, html, "utf8");
183
+ }
184
+
185
+ function updateHtml(htmlPath, report, reportName, faviconDataUrl) {
186
+ let html = fs.readFileSync(htmlPath, "utf8");
187
+ html = html.replace(/<title>.*?<\/title>/, `<title>ARTES REPORT</title>`);
188
+
189
+ html = html.replace(/<link rel="icon" href=".*?">/, `<link rel="icon" href="${faviconDataUrl}">`);
190
+ fs.writeFileSync(htmlPath, html, "utf8");
191
+ }
192
+
193
+ module.exports = { reportCustomizer };
@@ -42,6 +42,15 @@ function showHelp() {
42
42
 
43
43
  🗜️ --zip Zip the report folder after generation
44
44
  Usage: artes -r --zip
45
+
46
+ 🖼️ --logo Set a custom logo in the report sidebar
47
+ Usage: artes --logo logo.png
48
+
49
+ 🏢 --brandName Set the brand name displayed next to the logo in the report sidebar
50
+ Usage: artes --brandName 'My Company'
51
+
52
+ 📄 --reportName Set the report name displayed on the summary widget
53
+ Usage: artes --reportName 'Alma UI'
45
54
 
46
55
  📁 --features Specify one or more feature files' relative paths to run (comma-separated)
47
56
  Usage: artes --features "tests/features/Alma, tests/features/Banan.feature"
@@ -58,6 +58,9 @@ function createProject(createYes, noDeps) {
58
58
  // parallel: 0, // number - Number of parallel workers
59
59
  // report: true // boolean - Generate report
60
60
  // zip: false // boolean - Generate zip of report
61
+ // logo: "" // string - Custom logo for the report sidebar. Accepts an absolute path, a relative path, or a direct image URL
62
+ // brandName: "" // string - Brand name displayed next to the logo in the report sidebar
63
+ // reportName: "" // string - Report name displayed on the summary widget
61
64
  // reportSuccess: false, // boolean - Add screenshots and video records to report also for success test cases
62
65
  // trace: false, // boolean - Enable tracing
63
66
  // reportWithTrace: false, // boolean - Include trace in report
@@ -3,6 +3,7 @@ const path = require("path");
3
3
  const archiver = require("archiver");
4
4
  const { spawnSync } = require("child_process");
5
5
  const { moduleConfig } = require("../imports/commons");
6
+ const { reportCustomizer } = require("../../helper/controller/reportCustomizer");
6
7
 
7
8
  function generateReport() {
8
9
  try {
@@ -29,6 +30,17 @@ function generateReport() {
29
30
  `📋 Report generated successfully in ${moduleConfig.reportPath}!`,
30
31
  );
31
32
 
33
+ let customizerDone = false;
34
+ let customizerError = null;
35
+
36
+ Promise.resolve(reportCustomizer())
37
+ .then(() => { customizerDone = true; })
38
+ .catch((err) => { customizerError = err; customizerDone = true; });
39
+
40
+ require("deasync").loopWhile(() => !customizerDone);
41
+
42
+ if (customizerError) throw customizerError;
43
+
32
44
  if (fs.existsSync(moduleConfig.reportPath) && process.env.ZIP === "true") {
33
45
  console.log(`🗜️ Zipping report folder...`);
34
46
 
@@ -20,6 +20,9 @@ const path = require("path");
20
20
  const { moduleConfig, saveVar } = require("artes/src/helper/imports/commons");
21
21
  require("allure-cucumberjs");
22
22
  const allure = require("allure-js-commons");
23
+ const ffprobe = require('ffprobe-static');
24
+ const ffmpegPath = require('ffmpeg-static');
25
+ const { execSync } = require('child_process');
23
26
 
24
27
  const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"];
25
28
 
@@ -147,7 +150,8 @@ After(async function ({result, pickle}) {
147
150
 
148
151
  await attachResponse(allure.attachment);
149
152
  context.response = await {};
150
- allure.attachment('Variables', JSON.stringify(context.vars, null, 2), 'application/json')
153
+
154
+ Object.keys(context.vars).length > 0 && allure.attachment('Variables', JSON.stringify(context.vars, null, 2), 'application/json')
151
155
 
152
156
  const shouldReport =
153
157
  cucumberConfig.default.successReport || result?.status !== Status.PASSED;
@@ -199,9 +203,30 @@ After(async function ({result, pickle}) {
199
203
  await new Promise((resolve) => setTimeout(resolve, 1000));
200
204
 
201
205
  if (fs.existsSync(videoPath)) {
206
+ const trimmedPath = videoPath.replace('.webm', '-trimmed.webm');
207
+
208
+ const isTimeoutError = result.message?.includes('Error: function timed out, ensure the promise resolves within')
202
209
  const webmBuffer = fs.readFileSync(videoPath);
203
210
  await allure.attachment("Screenrecord", webmBuffer, "video/webm");
211
+ if (isTimeoutError) {
212
+ const duration = parseFloat(
213
+ execSync(`"${ffprobe.path}" -v error -show_entries format=duration -of csv=p=0 "${videoPath}"`).toString().trim()
214
+ );
215
+
216
+ const timeoutSeconds = cucumberConfig.default.timeout / 1000;
217
+ const newDuration = Math.max(duration - timeoutSeconds + 3, 1);
218
+
219
+ execSync(`"${ffmpegPath}" -loglevel quiet -i "${videoPath}" -t ${newDuration} -c copy "${trimmedPath}" -y`);
220
+
221
+ const webmBuffer = fs.readFileSync(trimmedPath);
222
+ await allure.attachment("Screenrecord", webmBuffer, "video/webm");
223
+ } else {
224
+
225
+ const webmBuffer = fs.readFileSync(videoPath);
226
+ await allure.attachment("Screenrecord", webmBuffer, "video/webm");
227
+ }
204
228
  }
229
+
205
230
  }
206
231
  }
207
232
  });