flowstride 1.0.9 → 1.0.11
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 +25 -5
- package/dist/cli/index.js +18 -17
- 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,29 +24,49 @@ 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.)_
|
|
37
37
|
|
|
38
|
-
**3.
|
|
38
|
+
**3. Install VS Code Extension (Highly Recommended):**
|
|
39
|
+
|
|
40
|
+
To get the best developer experience, including syntax highlighting, auto-completion, and native IDE support for .flow files, install our official VS Code extension directly via the VS Code CLI.
|
|
41
|
+
|
|
42
|
+
Run the command below:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
code --install-extension flowstride.flowstride-vscode
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
_Alternatively, you can open the Extensions tab in VS Code and search for Flowstride Automation_
|
|
49
|
+
|
|
50
|
+
**4. Run your first test:**
|
|
39
51
|
|
|
40
52
|
```bash
|
|
41
|
-
flowstride run
|
|
53
|
+
npx flowstride run
|
|
42
54
|
|
|
43
55
|
OR
|
|
44
56
|
|
|
45
|
-
flowstride run flows/example.flow
|
|
57
|
+
npx flowstride run flows/example.flow
|
|
46
58
|
```
|
|
47
59
|
|
|
48
60
|
_(This command executes your test while instantly booting up the local Flowstride Dashboard.)_
|
|
49
61
|
|
|
62
|
+
**5. Login to Flowstride Cloud:**
|
|
63
|
+
|
|
64
|
+
Authenticate your local project with your Cloud account to unlock Cloud features after signing up at [cloud.flowstride.io](https://cloud.flowstride.io).
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx flowstride login
|
|
68
|
+
```
|
|
69
|
+
|
|
50
70
|
---
|
|
51
71
|
|
|
52
72
|
## Privacy & Data Security
|
package/dist/cli/index.js
CHANGED
|
@@ -45,6 +45,7 @@ const fs = __importStar(require("fs"));
|
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
46
|
const os = __importStar(require("os"));
|
|
47
47
|
const tar = __importStar(require("tar"));
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
48
49
|
const prompts_1 = require("@inquirer/prompts");
|
|
49
50
|
const chalk_1 = __importDefault(require("chalk"));
|
|
50
51
|
const ora_1 = __importDefault(require("ora"));
|
|
@@ -53,7 +54,7 @@ const API_BASE = process.env.FLOWSTRIDE_API_URL || "https://cloud.flowstride.io"
|
|
|
53
54
|
program
|
|
54
55
|
.name("flowstride")
|
|
55
56
|
.description("Flowstride: The Enterprise Flow-First Automation CLI")
|
|
56
|
-
.version("1.0.
|
|
57
|
+
.version("1.0.11");
|
|
57
58
|
program
|
|
58
59
|
.command("init")
|
|
59
60
|
.description("Initialise a new Flowstride workspace in the current directory")
|
|
@@ -154,20 +155,20 @@ program
|
|
|
154
155
|
cwd: nodeModulesPath,
|
|
155
156
|
strip: 1,
|
|
156
157
|
});
|
|
158
|
+
installSpinner.text = "Wiring up Enterprise dependencies...";
|
|
159
|
+
(0, child_process_1.execSync)("npm install --omit=dev", {
|
|
160
|
+
cwd: nodeModulesPath,
|
|
161
|
+
stdio: "ignore",
|
|
162
|
+
});
|
|
157
163
|
installSpinner.succeed(chalk_1.default.green("Enterprise Engine installed successfully!"));
|
|
158
164
|
}
|
|
159
165
|
catch (installError) {
|
|
160
166
|
installSpinner.stop();
|
|
161
|
-
// ==========================================
|
|
162
|
-
// TEMPORARY DEBUG LOG TO UNMASK THE ERROR
|
|
163
|
-
// ==========================================
|
|
164
167
|
console.error(chalk_1.default.bgRed.white("\n[RAW SYSTEM ERROR]:"), installError);
|
|
165
|
-
// ==========================================
|
|
166
168
|
console.log(chalk_1.default.red("\n✖ Failed to install the Enterprise Engine. Please check your internet connection or verify your project configuration."));
|
|
167
169
|
if (fs.existsSync(nodeModulesPath)) {
|
|
168
170
|
fs.rmSync(nodeModulesPath, { recursive: true, force: true });
|
|
169
171
|
}
|
|
170
|
-
// THE FIX: Strict Rollback on Failure
|
|
171
172
|
console.log(chalk_1.default.yellow("⚠️ Reverting login: Enterprise Engine is required to sync."));
|
|
172
173
|
if (fs.existsSync(credentialsPath))
|
|
173
174
|
fs.unlinkSync(credentialsPath);
|
|
@@ -180,7 +181,6 @@ program
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
else {
|
|
183
|
-
// THE FIX: Strict Rollback on Decline
|
|
184
184
|
console.log(chalk_1.default.yellow(`\n⚠️ Installation skipped.`));
|
|
185
185
|
console.log(chalk_1.default.red(`✖ Reverting login: You must install the Enterprise Engine to sync this workspace.`));
|
|
186
186
|
if (fs.existsSync(credentialsPath))
|
|
@@ -189,7 +189,6 @@ program
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
-
// Only print this if the engine was successfully verified or installed
|
|
193
192
|
console.log(chalk_1.default.bold.green(`\n✅ Machine synchronized with Flowstride Enterprise.`));
|
|
194
193
|
}
|
|
195
194
|
catch (error) {
|
|
@@ -211,7 +210,6 @@ program
|
|
|
211
210
|
}
|
|
212
211
|
catch (err) { }
|
|
213
212
|
}
|
|
214
|
-
// THE FIX: Defensive Self-Healing. If login exists but engine is missing, drop to OS.
|
|
215
213
|
if (!process.env.FLOWSTRIDE_API_KEY &&
|
|
216
214
|
token &&
|
|
217
215
|
!fs.existsSync(nodeModulesPath)) {
|
|
@@ -252,13 +250,17 @@ program
|
|
|
252
250
|
}
|
|
253
251
|
});
|
|
254
252
|
program
|
|
255
|
-
.command("run")
|
|
253
|
+
.command("run [targetPath]")
|
|
256
254
|
.description("Execute all .flow automation journeys in the ./flows directory")
|
|
257
255
|
.option("-b, --bail", "Stop execution on first failure", false)
|
|
258
256
|
.option("--headed", "Run browser in headed mode", false)
|
|
259
257
|
.option("--headless <boolean>", "Run browser in headless mode", (val) => val === "true", true)
|
|
260
258
|
.option("--workers <number>", "Number of parallel workers to spawn (Pro feature)")
|
|
261
|
-
.action(async (options) => {
|
|
259
|
+
.action(async (targetPath, options) => {
|
|
260
|
+
if (typeof targetPath === "object") {
|
|
261
|
+
options = targetPath;
|
|
262
|
+
targetPath = undefined;
|
|
263
|
+
}
|
|
262
264
|
let token = process.env.FLOWSTRIDE_API_KEY || null;
|
|
263
265
|
let forceOS = false;
|
|
264
266
|
const credentialsPath = path.join(process.cwd(), ".flowstride", "login", "login.json");
|
|
@@ -270,7 +272,6 @@ program
|
|
|
270
272
|
}
|
|
271
273
|
catch (err) { }
|
|
272
274
|
}
|
|
273
|
-
// THE FIX: Defensive Self-Healing
|
|
274
275
|
if (!process.env.FLOWSTRIDE_API_KEY &&
|
|
275
276
|
token &&
|
|
276
277
|
!fs.existsSync(nodeModulesPath)) {
|
|
@@ -315,13 +316,13 @@ program
|
|
|
315
316
|
if (options.headed) {
|
|
316
317
|
options.headless = false;
|
|
317
318
|
}
|
|
318
|
-
const
|
|
319
|
+
const resolvedTargetPath = targetPath ? targetPath : "./flows";
|
|
320
|
+
const strictUiFlowsDir = path.resolve(process.cwd(), resolvedTargetPath);
|
|
319
321
|
if (!fs.existsSync(strictUiFlowsDir)) {
|
|
320
322
|
console.error(`\n[ERROR]: Could not find ${strictUiFlowsDir}.`);
|
|
321
|
-
console.error(`Flowstride requires all UI tests to be inside a
|
|
323
|
+
console.error(`Flowstride requires all UI tests to be inside a valid directory.`);
|
|
322
324
|
process.exit(1);
|
|
323
325
|
}
|
|
324
|
-
// Inject our DRM state into options before passing to runner
|
|
325
326
|
options.forceOS = forceOS;
|
|
326
327
|
await (0, run_1.runAction)(strictUiFlowsDir, options);
|
|
327
328
|
});
|
|
@@ -329,7 +330,7 @@ async function handleOfflinePrompt() {
|
|
|
329
330
|
console.log(chalk_1.default.yellow("🛜 OFFLINE: Unable to reach Flowstride Cloud. Enterprise Sync paused."));
|
|
330
331
|
if (process.env.CI || process.env.FLOWSTRIDE_API_KEY) {
|
|
331
332
|
console.log(chalk_1.default.yellow("⚠️ CI/CD Environment: Automatically falling back to OS Engine."));
|
|
332
|
-
return true;
|
|
333
|
+
return true;
|
|
333
334
|
}
|
|
334
335
|
const runAnyway = await (0, prompts_1.confirm)({
|
|
335
336
|
message: "⚠️ Execute a local OS run anyway? (Pro features are disabled on OS)",
|
|
@@ -338,7 +339,7 @@ async function handleOfflinePrompt() {
|
|
|
338
339
|
if (!runAnyway) {
|
|
339
340
|
process.exit(0);
|
|
340
341
|
}
|
|
341
|
-
return true;
|
|
342
|
+
return true;
|
|
342
343
|
}
|
|
343
344
|
program
|
|
344
345
|
.command("report")
|
|
@@ -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;
|