flowstride 1.0.8 → 1.0.10
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/README.md +12 -4
- package/dist/cli/index.js +11 -10
- package/dist/reporting/pdf_generator.d.ts +4 -0
- package/dist/reporting/pdf_generator.js +187 -0
- package/dist/runner/flow_orchestrator.d.ts +2 -0
- package/dist/runner/flow_orchestrator.js +37 -3
- package/package.json +2 -1
- package/src/ui/dist/assets/{index-BrbAbAha.js → index-BCdexgt5.js} +19 -19
- package/src/ui/dist/index.html +1 -1
package/README.md
CHANGED
|
@@ -24,13 +24,13 @@ Flowstride requires Node.js 18 or higher.
|
|
|
24
24
|
**1. Install globally (or as a dev dependency in your project):**
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npm
|
|
27
|
+
npm i flowstride --save-dev
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
**2. Initialize a new Flowstride workspace:**
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
flowstride init
|
|
33
|
+
npx flowstride init
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
_(This creates the default directory structure (`/flows`, `/reports`, `/plugins`) and generates a `example.flow` sample file.)_
|
|
@@ -38,15 +38,23 @@ _(This creates the default directory structure (`/flows`, `/reports`, `/plugins`
|
|
|
38
38
|
**3. Run your first test:**
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
flowstride run
|
|
41
|
+
npx flowstride run
|
|
42
42
|
|
|
43
43
|
OR
|
|
44
44
|
|
|
45
|
-
flowstride run flows/example.flow
|
|
45
|
+
npx flowstride run flows/example.flow
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
_(This command executes your test while instantly booting up the local Flowstride Dashboard.)_
|
|
49
49
|
|
|
50
|
+
**4. Login to Flowstride Cloud:**
|
|
51
|
+
|
|
52
|
+
Authenticate your local project with your Cloud account to unlock Cloud features after signing up at [cloud.flowstride.io](https://cloud.flowstride.io).
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx flowstride login
|
|
56
|
+
```
|
|
57
|
+
|
|
50
58
|
---
|
|
51
59
|
|
|
52
60
|
## Privacy & Data Security
|
package/dist/cli/index.js
CHANGED
|
@@ -53,7 +53,7 @@ const API_BASE = process.env.FLOWSTRIDE_API_URL || "https://cloud.flowstride.io"
|
|
|
53
53
|
program
|
|
54
54
|
.name("flowstride")
|
|
55
55
|
.description("Flowstride: The Enterprise Flow-First Automation CLI")
|
|
56
|
-
.version("1.0.
|
|
56
|
+
.version("1.0.10");
|
|
57
57
|
program
|
|
58
58
|
.command("init")
|
|
59
59
|
.description("Initialise a new Flowstride workspace in the current directory")
|
|
@@ -158,11 +158,11 @@ program
|
|
|
158
158
|
}
|
|
159
159
|
catch (installError) {
|
|
160
160
|
installSpinner.stop();
|
|
161
|
+
console.error(chalk_1.default.bgRed.white("\n[RAW SYSTEM ERROR]:"), installError);
|
|
161
162
|
console.log(chalk_1.default.red("\n✖ Failed to install the Enterprise Engine. Please check your internet connection or verify your project configuration."));
|
|
162
163
|
if (fs.existsSync(nodeModulesPath)) {
|
|
163
164
|
fs.rmSync(nodeModulesPath, { recursive: true, force: true });
|
|
164
165
|
}
|
|
165
|
-
// THE FIX: Strict Rollback on Failure
|
|
166
166
|
console.log(chalk_1.default.yellow("⚠️ Reverting login: Enterprise Engine is required to sync."));
|
|
167
167
|
if (fs.existsSync(credentialsPath))
|
|
168
168
|
fs.unlinkSync(credentialsPath);
|
|
@@ -175,7 +175,6 @@ program
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
else {
|
|
178
|
-
// THE FIX: Strict Rollback on Decline
|
|
179
178
|
console.log(chalk_1.default.yellow(`\n⚠️ Installation skipped.`));
|
|
180
179
|
console.log(chalk_1.default.red(`✖ Reverting login: You must install the Enterprise Engine to sync this workspace.`));
|
|
181
180
|
if (fs.existsSync(credentialsPath))
|
|
@@ -184,7 +183,6 @@ program
|
|
|
184
183
|
}
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
|
-
// Only print this if the engine was successfully verified or installed
|
|
188
186
|
console.log(chalk_1.default.bold.green(`\n✅ Machine synchronized with Flowstride Enterprise.`));
|
|
189
187
|
}
|
|
190
188
|
catch (error) {
|
|
@@ -206,7 +204,6 @@ program
|
|
|
206
204
|
}
|
|
207
205
|
catch (err) { }
|
|
208
206
|
}
|
|
209
|
-
// THE FIX: Defensive Self-Healing. If login exists but engine is missing, drop to OS.
|
|
210
207
|
if (!process.env.FLOWSTRIDE_API_KEY &&
|
|
211
208
|
token &&
|
|
212
209
|
!fs.existsSync(nodeModulesPath)) {
|
|
@@ -247,13 +244,17 @@ program
|
|
|
247
244
|
}
|
|
248
245
|
});
|
|
249
246
|
program
|
|
250
|
-
.command("run")
|
|
247
|
+
.command("run [targetPath]")
|
|
251
248
|
.description("Execute all .flow automation journeys in the ./flows directory")
|
|
252
249
|
.option("-b, --bail", "Stop execution on first failure", false)
|
|
253
250
|
.option("--headed", "Run browser in headed mode", false)
|
|
254
251
|
.option("--headless <boolean>", "Run browser in headless mode", (val) => val === "true", true)
|
|
255
252
|
.option("--workers <number>", "Number of parallel workers to spawn (Pro feature)")
|
|
256
|
-
.action(async (options) => {
|
|
253
|
+
.action(async (targetPath, options) => {
|
|
254
|
+
if (typeof targetPath === "object") {
|
|
255
|
+
options = targetPath;
|
|
256
|
+
targetPath = undefined;
|
|
257
|
+
}
|
|
257
258
|
let token = process.env.FLOWSTRIDE_API_KEY || null;
|
|
258
259
|
let forceOS = false;
|
|
259
260
|
const credentialsPath = path.join(process.cwd(), ".flowstride", "login", "login.json");
|
|
@@ -265,7 +266,6 @@ program
|
|
|
265
266
|
}
|
|
266
267
|
catch (err) { }
|
|
267
268
|
}
|
|
268
|
-
// THE FIX: Defensive Self-Healing
|
|
269
269
|
if (!process.env.FLOWSTRIDE_API_KEY &&
|
|
270
270
|
token &&
|
|
271
271
|
!fs.existsSync(nodeModulesPath)) {
|
|
@@ -310,10 +310,11 @@ program
|
|
|
310
310
|
if (options.headed) {
|
|
311
311
|
options.headless = false;
|
|
312
312
|
}
|
|
313
|
-
const
|
|
313
|
+
const resolvedTargetPath = targetPath ? targetPath : "./flows";
|
|
314
|
+
const strictUiFlowsDir = path.resolve(process.cwd(), resolvedTargetPath);
|
|
314
315
|
if (!fs.existsSync(strictUiFlowsDir)) {
|
|
315
316
|
console.error(`\n[ERROR]: Could not find ${strictUiFlowsDir}.`);
|
|
316
|
-
console.error(`Flowstride requires all UI tests to be inside a
|
|
317
|
+
console.error(`Flowstride requires all UI tests to be inside a valid directory.`);
|
|
317
318
|
process.exit(1);
|
|
318
319
|
}
|
|
319
320
|
// Inject our DRM state into options before passing to runner
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PdfGenerator = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const playwright_1 = require("playwright");
|
|
40
|
+
class PdfGenerator {
|
|
41
|
+
async generateTestcasePdf(data, customOutputPath) {
|
|
42
|
+
let outputDir = "";
|
|
43
|
+
if (customOutputPath) {
|
|
44
|
+
outputDir = customOutputPath;
|
|
45
|
+
if (!fs.existsSync(outputDir)) {
|
|
46
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
outputDir = path.join(process.cwd(), ".flowstride", "artifacts");
|
|
51
|
+
if (!fs.existsSync(outputDir)) {
|
|
52
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const fileName = `Flowstride_Test_Log_${Date.now()}.pdf`;
|
|
56
|
+
const pdfPath = path.join(outputDir, fileName);
|
|
57
|
+
const htmlContent = this.generateReportHtml(data);
|
|
58
|
+
const browser = await playwright_1.chromium.launch({ headless: true });
|
|
59
|
+
const page = await browser.newPage();
|
|
60
|
+
await page.setContent(htmlContent, { waitUntil: "networkidle" });
|
|
61
|
+
await page.pdf({
|
|
62
|
+
path: pdfPath,
|
|
63
|
+
format: "A4",
|
|
64
|
+
printBackground: true,
|
|
65
|
+
margin: { top: "30px", right: "30px", bottom: "30px", left: "30px" },
|
|
66
|
+
});
|
|
67
|
+
await browser.close();
|
|
68
|
+
return pdfPath;
|
|
69
|
+
}
|
|
70
|
+
generateReportHtml(data) {
|
|
71
|
+
let scenariosToProcess = [];
|
|
72
|
+
if (data && data.files && Array.isArray(data.files)) {
|
|
73
|
+
data.files.forEach((fileObj) => {
|
|
74
|
+
if (fileObj.scenarios && Array.isArray(fileObj.scenarios)) {
|
|
75
|
+
scenariosToProcess.push(...fileObj.scenarios);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else if (Array.isArray(data)) {
|
|
80
|
+
scenariosToProcess = data;
|
|
81
|
+
}
|
|
82
|
+
const groupedScenarios = {};
|
|
83
|
+
scenariosToProcess.forEach((scenario) => {
|
|
84
|
+
const key = scenario.fileName || "General";
|
|
85
|
+
if (!groupedScenarios[key])
|
|
86
|
+
groupedScenarios[key] = [];
|
|
87
|
+
groupedScenarios[key].push(scenario);
|
|
88
|
+
});
|
|
89
|
+
let totalSteps = 0;
|
|
90
|
+
let passedSteps = 0;
|
|
91
|
+
let failedSteps = 0;
|
|
92
|
+
let tablesHtml = "";
|
|
93
|
+
for (const [fileName, fileScenarios] of Object.entries(groupedScenarios)) {
|
|
94
|
+
const flatSteps = fileScenarios.flatMap((s) => s.steps || []);
|
|
95
|
+
let fileRows = "";
|
|
96
|
+
flatSteps.forEach((step, idx) => {
|
|
97
|
+
totalSteps++;
|
|
98
|
+
const status = (step.status || "PENDING").toUpperCase();
|
|
99
|
+
const isSuccess = status === "PASS" || status === "PASSED" || status === "SUCCESS";
|
|
100
|
+
const isFail = status === "FAILED" || status === "FAIL";
|
|
101
|
+
if (isSuccess)
|
|
102
|
+
passedSteps++;
|
|
103
|
+
else if (isFail)
|
|
104
|
+
failedSteps++;
|
|
105
|
+
const color = isSuccess ? "#10b981" : isFail ? "#ef4444" : "#64748b";
|
|
106
|
+
const actualResultText = isFail
|
|
107
|
+
? "Validation Failed"
|
|
108
|
+
: "Action verified successfully.";
|
|
109
|
+
const actualResultColor = isFail ? "#ef4444" : "#64748b";
|
|
110
|
+
fileRows += `
|
|
111
|
+
<tr>
|
|
112
|
+
<td style="padding: 16px 14px; border-bottom: 1px solid #e2e8f0; color: #64748b; font-weight: 500; width: 50px;">${idx + 1}</td>
|
|
113
|
+
<td style="padding: 16px 14px; border-bottom: 1px solid #e2e8f0; font-weight: 700; color: #1e293b;">${step.command || "action"}</td>
|
|
114
|
+
<td style="padding: 16px 14px; border-bottom: 1px solid #e2e8f0; color: #475569; font-style: italic;">${step.type || ""} ${step.description || "-"}</td>
|
|
115
|
+
<td style="padding: 16px 14px; border-bottom: 1px solid #e2e8f0; color: ${actualResultColor};">${actualResultText}</td>
|
|
116
|
+
<td style="padding: 16px 14px; border-bottom: 1px solid #e2e8f0; font-weight: 900; font-size: 10px; color: ${color};">${status}</td>
|
|
117
|
+
</tr>
|
|
118
|
+
`;
|
|
119
|
+
});
|
|
120
|
+
tablesHtml += `
|
|
121
|
+
<div class="file-separator">
|
|
122
|
+
<span>📄 ${fileName.toUpperCase()}</span>
|
|
123
|
+
</div>
|
|
124
|
+
<table>
|
|
125
|
+
<thead>
|
|
126
|
+
<tr>
|
|
127
|
+
<th>S/N</th>
|
|
128
|
+
<th>Action</th>
|
|
129
|
+
<th>Expected Result</th>
|
|
130
|
+
<th>Actual Result</th>
|
|
131
|
+
<th>Status</th>
|
|
132
|
+
</tr>
|
|
133
|
+
</thead>
|
|
134
|
+
<tbody>
|
|
135
|
+
${fileRows || '<tr><td colspan="5" style="text-align:center;padding:20px;color:#64748b;">No test steps recorded.</td></tr>'}
|
|
136
|
+
</tbody>
|
|
137
|
+
</table>
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
return `
|
|
141
|
+
<!DOCTYPE html>
|
|
142
|
+
<html>
|
|
143
|
+
<head>
|
|
144
|
+
<style>
|
|
145
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; padding: 20px; color: #1e293b; }
|
|
146
|
+
.header { border-bottom: 4px solid #0f172a; padding-bottom: 24px; margin-bottom: 40px; }
|
|
147
|
+
.title { font-size: 36px; font-weight: 900; text-transform: uppercase; margin: 0 0 8px 0; color: #0f172a; }
|
|
148
|
+
.subtitle { font-size: 14px; color: #64748b; font-weight: 500; margin: 0; }
|
|
149
|
+
.summary { display: flex; gap: 15px; margin-bottom: 20px; }
|
|
150
|
+
.stat-box { padding: 15px; border-radius: 8px; background: #f8fafc; flex: 1; text-align: center; border: 1px solid #e2e8f0; }
|
|
151
|
+
.stat-value { font-size: 22px; font-weight: bold; }
|
|
152
|
+
|
|
153
|
+
.file-separator { text-align: center; margin: 50px 0 20px 0; border-bottom: 1px solid rgba(124, 58, 237, 0.3); line-height: 0.1em; }
|
|
154
|
+
.file-separator span { background: #fff; padding: 0 15px; color: #7c3aed; font-weight: 900; font-size: 11px; letter-spacing: 0.1em; }
|
|
155
|
+
|
|
156
|
+
table { width: 100%; border-collapse: collapse; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; font-size: 13px; }
|
|
157
|
+
th { background: #0f172a; color: white; text-align: left; padding: 14px; font-weight: 600; font-size: 11px; }
|
|
158
|
+
</style>
|
|
159
|
+
</head>
|
|
160
|
+
<body>
|
|
161
|
+
<div class="header">
|
|
162
|
+
<h1 class="title">Test Case Log</h1>
|
|
163
|
+
<p class="subtitle">Project: Flowstride Community Edition</p>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="summary">
|
|
166
|
+
<div class="stat-box">
|
|
167
|
+
<div class="stat-value" style="color: #1e293b;">${totalSteps}</div>
|
|
168
|
+
<div style="font-size:10px;color:#64748b;text-transform:uppercase;margin-top:4px;font-weight:700;">Total Steps</div>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="stat-box">
|
|
171
|
+
<div class="stat-value" style="color:#10b981;">${passedSteps}</div>
|
|
172
|
+
<div style="font-size:10px;color:#64748b;text-transform:uppercase;margin-top:4px;font-weight:700;">Passed</div>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="stat-box">
|
|
175
|
+
<div class="stat-value" style="color:#ef4444;">${failedSteps}</div>
|
|
176
|
+
<div style="font-size:10px;color:#64748b;text-transform:uppercase;margin-top:4px;font-weight:700;">Failed</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
${tablesHtml}
|
|
181
|
+
|
|
182
|
+
</body>
|
|
183
|
+
</html>
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
exports.PdfGenerator = PdfGenerator;
|
|
@@ -8,8 +8,10 @@ export declare class FlowOrchestrator {
|
|
|
8
8
|
private httpServer;
|
|
9
9
|
private globalTestScenarios;
|
|
10
10
|
private stepStatusRegistry;
|
|
11
|
+
private pdfGenerator;
|
|
11
12
|
constructor(worker: FlowWorker, workerCount?: number, config?: FlowstrideConfig | undefined);
|
|
12
13
|
private forwardToDashboard;
|
|
13
14
|
private executeTestRun;
|
|
14
15
|
start(targetPath: string): Promise<void>;
|
|
16
|
+
private handleExportPdfRequest;
|
|
15
17
|
}
|
|
@@ -41,7 +41,9 @@ const fs = __importStar(require("fs"));
|
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const http = __importStar(require("http"));
|
|
43
43
|
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
const ora_1 = __importDefault(require("ora"));
|
|
44
45
|
const ws_1 = require("ws");
|
|
46
|
+
const pdf_generator_1 = require("../reporting/pdf_generator");
|
|
45
47
|
class FlowOrchestrator {
|
|
46
48
|
worker;
|
|
47
49
|
workerCount;
|
|
@@ -50,10 +52,12 @@ class FlowOrchestrator {
|
|
|
50
52
|
httpServer = null;
|
|
51
53
|
globalTestScenarios = [];
|
|
52
54
|
stepStatusRegistry = new Map();
|
|
55
|
+
pdfGenerator;
|
|
53
56
|
constructor(worker, workerCount = 1, config) {
|
|
54
57
|
this.worker = worker;
|
|
55
58
|
this.workerCount = workerCount;
|
|
56
59
|
this.config = config;
|
|
60
|
+
this.pdfGenerator = new pdf_generator_1.PdfGenerator();
|
|
57
61
|
this.worker.on("worker_event", (data) => {
|
|
58
62
|
this.forwardToDashboard(data.type, data.payload);
|
|
59
63
|
});
|
|
@@ -221,14 +225,28 @@ class FlowOrchestrator {
|
|
|
221
225
|
ws.on("message", async (message) => {
|
|
222
226
|
const data = JSON.parse(message.toString());
|
|
223
227
|
if (data.type === "EXPORT_PDF_REQ") {
|
|
224
|
-
|
|
225
|
-
|
|
228
|
+
let scenariosToExport = data.payload || [];
|
|
229
|
+
if (scenariosToExport.length === 0) {
|
|
230
|
+
scenariosToExport = this.globalTestScenarios;
|
|
231
|
+
}
|
|
232
|
+
await this.handleExportPdfRequest(scenariosToExport);
|
|
226
233
|
}
|
|
227
234
|
else if (data.type === "REPLAY_REQ") {
|
|
228
235
|
await this.worker.handleReplayRequest(data.payload);
|
|
229
236
|
}
|
|
230
237
|
else if (data.type === "RERUN_REQ") {
|
|
231
|
-
|
|
238
|
+
const requestedFile = data.payload?.filePath;
|
|
239
|
+
let specificTarget = targetPath;
|
|
240
|
+
if (requestedFile) {
|
|
241
|
+
const targetStat = fs.statSync(targetPath);
|
|
242
|
+
if (targetStat.isDirectory()) {
|
|
243
|
+
specificTarget = path.join(targetPath, requestedFile);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
specificTarget = path.join(path.dirname(targetPath), requestedFile);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
await this.executeTestRun(specificTarget);
|
|
232
250
|
}
|
|
233
251
|
else if (data.type === "STOP_REQ") {
|
|
234
252
|
console.log(chalk_1.default.yellow("\nReceived STOP signal from Dashboard. Halting execution..."));
|
|
@@ -265,5 +283,21 @@ class FlowOrchestrator {
|
|
|
265
283
|
throw e;
|
|
266
284
|
}
|
|
267
285
|
}
|
|
286
|
+
async handleExportPdfRequest(testScenariosFromUI) {
|
|
287
|
+
try {
|
|
288
|
+
const spinner = (0, ora_1.default)(chalk_1.default.blue("Compiling high-fidelity PDF report...")).start();
|
|
289
|
+
const pdfPath = await this.pdfGenerator.generateTestcasePdf(testScenariosFromUI);
|
|
290
|
+
// Convert the saved PDF to base64 to trigger the native browser download UI
|
|
291
|
+
const pdfBuffer = fs.readFileSync(pdfPath);
|
|
292
|
+
const base64Data = pdfBuffer.toString("base64");
|
|
293
|
+
this.forwardToDashboard("EXPORT_PDF_RES", { base64: base64Data });
|
|
294
|
+
this.forwardToDashboard("EXPORT_PDF_SUCCESS", { path: pdfPath });
|
|
295
|
+
spinner.succeed(chalk_1.default.green(`PDF Exported Successfully: ${pdfPath}`));
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
console.error(chalk_1.default.red(`\n[Report Error]: Failed to generate PDF - ${e.message}`));
|
|
299
|
+
this.forwardToDashboard("EXPORT_PDF_ERROR", { error: e.message });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
268
302
|
}
|
|
269
303
|
exports.FlowOrchestrator = FlowOrchestrator;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowstride",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "An opensource flowfirst test automation framework",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/flowstride_report"
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@inquirer/prompts": "^7.10.1",
|
|
36
|
+
"axios": "^1.18.0",
|
|
36
37
|
"chalk": "^4.1.2",
|
|
37
38
|
"commander": "^11.0.0",
|
|
38
39
|
"dotenv": "^17.4.2",
|