flowstride 1.0.9 → 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 +10 -14
- 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 +1 -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,16 +158,11 @@ program
|
|
|
158
158
|
}
|
|
159
159
|
catch (installError) {
|
|
160
160
|
installSpinner.stop();
|
|
161
|
-
// ==========================================
|
|
162
|
-
// TEMPORARY DEBUG LOG TO UNMASK THE ERROR
|
|
163
|
-
// ==========================================
|
|
164
161
|
console.error(chalk_1.default.bgRed.white("\n[RAW SYSTEM ERROR]:"), installError);
|
|
165
|
-
// ==========================================
|
|
166
162
|
console.log(chalk_1.default.red("\n✖ Failed to install the Enterprise Engine. Please check your internet connection or verify your project configuration."));
|
|
167
163
|
if (fs.existsSync(nodeModulesPath)) {
|
|
168
164
|
fs.rmSync(nodeModulesPath, { recursive: true, force: true });
|
|
169
165
|
}
|
|
170
|
-
// THE FIX: Strict Rollback on Failure
|
|
171
166
|
console.log(chalk_1.default.yellow("⚠️ Reverting login: Enterprise Engine is required to sync."));
|
|
172
167
|
if (fs.existsSync(credentialsPath))
|
|
173
168
|
fs.unlinkSync(credentialsPath);
|
|
@@ -180,7 +175,6 @@ program
|
|
|
180
175
|
}
|
|
181
176
|
}
|
|
182
177
|
else {
|
|
183
|
-
// THE FIX: Strict Rollback on Decline
|
|
184
178
|
console.log(chalk_1.default.yellow(`\n⚠️ Installation skipped.`));
|
|
185
179
|
console.log(chalk_1.default.red(`✖ Reverting login: You must install the Enterprise Engine to sync this workspace.`));
|
|
186
180
|
if (fs.existsSync(credentialsPath))
|
|
@@ -189,7 +183,6 @@ program
|
|
|
189
183
|
}
|
|
190
184
|
}
|
|
191
185
|
}
|
|
192
|
-
// Only print this if the engine was successfully verified or installed
|
|
193
186
|
console.log(chalk_1.default.bold.green(`\n✅ Machine synchronized with Flowstride Enterprise.`));
|
|
194
187
|
}
|
|
195
188
|
catch (error) {
|
|
@@ -211,7 +204,6 @@ program
|
|
|
211
204
|
}
|
|
212
205
|
catch (err) { }
|
|
213
206
|
}
|
|
214
|
-
// THE FIX: Defensive Self-Healing. If login exists but engine is missing, drop to OS.
|
|
215
207
|
if (!process.env.FLOWSTRIDE_API_KEY &&
|
|
216
208
|
token &&
|
|
217
209
|
!fs.existsSync(nodeModulesPath)) {
|
|
@@ -252,13 +244,17 @@ program
|
|
|
252
244
|
}
|
|
253
245
|
});
|
|
254
246
|
program
|
|
255
|
-
.command("run")
|
|
247
|
+
.command("run [targetPath]")
|
|
256
248
|
.description("Execute all .flow automation journeys in the ./flows directory")
|
|
257
249
|
.option("-b, --bail", "Stop execution on first failure", false)
|
|
258
250
|
.option("--headed", "Run browser in headed mode", false)
|
|
259
251
|
.option("--headless <boolean>", "Run browser in headless mode", (val) => val === "true", true)
|
|
260
252
|
.option("--workers <number>", "Number of parallel workers to spawn (Pro feature)")
|
|
261
|
-
.action(async (options) => {
|
|
253
|
+
.action(async (targetPath, options) => {
|
|
254
|
+
if (typeof targetPath === "object") {
|
|
255
|
+
options = targetPath;
|
|
256
|
+
targetPath = undefined;
|
|
257
|
+
}
|
|
262
258
|
let token = process.env.FLOWSTRIDE_API_KEY || null;
|
|
263
259
|
let forceOS = false;
|
|
264
260
|
const credentialsPath = path.join(process.cwd(), ".flowstride", "login", "login.json");
|
|
@@ -270,7 +266,6 @@ program
|
|
|
270
266
|
}
|
|
271
267
|
catch (err) { }
|
|
272
268
|
}
|
|
273
|
-
// THE FIX: Defensive Self-Healing
|
|
274
269
|
if (!process.env.FLOWSTRIDE_API_KEY &&
|
|
275
270
|
token &&
|
|
276
271
|
!fs.existsSync(nodeModulesPath)) {
|
|
@@ -315,10 +310,11 @@ program
|
|
|
315
310
|
if (options.headed) {
|
|
316
311
|
options.headless = false;
|
|
317
312
|
}
|
|
318
|
-
const
|
|
313
|
+
const resolvedTargetPath = targetPath ? targetPath : "./flows";
|
|
314
|
+
const strictUiFlowsDir = path.resolve(process.cwd(), resolvedTargetPath);
|
|
319
315
|
if (!fs.existsSync(strictUiFlowsDir)) {
|
|
320
316
|
console.error(`\n[ERROR]: Could not find ${strictUiFlowsDir}.`);
|
|
321
|
-
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.`);
|
|
322
318
|
process.exit(1);
|
|
323
319
|
}
|
|
324
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;
|