donobu 2.25.1 → 2.26.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/dist/apis/ToolsApi.d.ts +1 -6
- package/dist/apis/ToolsApi.d.ts.map +1 -1
- package/dist/apis/ToolsApi.js +3 -6
- package/dist/apis/ToolsApi.js.map +1 -1
- package/dist/assets/generated/version +1 -1
- package/dist/esm/apis/ToolsApi.d.ts +1 -6
- package/dist/esm/apis/ToolsApi.d.ts.map +1 -1
- package/dist/esm/apis/ToolsApi.js +3 -6
- package/dist/esm/apis/ToolsApi.js.map +1 -1
- package/dist/esm/assets/generated/version +1 -1
- package/dist/esm/lib/fixtures/gptClients.js +1 -1
- package/dist/esm/lib/testExtension.d.ts.map +1 -1
- package/dist/esm/lib/testExtension.js +8 -1
- package/dist/esm/lib/testExtension.js.map +1 -1
- package/dist/esm/main.d.ts.map +1 -1
- package/dist/esm/main.js +5 -0
- package/dist/esm/main.js.map +1 -1
- package/dist/esm/managers/AdminApiController.d.ts.map +1 -1
- package/dist/esm/managers/AdminApiController.js +1 -2
- package/dist/esm/managers/AdminApiController.js.map +1 -1
- package/dist/esm/managers/CodeGenerator.d.ts.map +1 -1
- package/dist/esm/managers/CodeGenerator.js +2 -1
- package/dist/esm/managers/CodeGenerator.js.map +1 -1
- package/dist/esm/managers/DonobuFlowsManager.d.ts.map +1 -1
- package/dist/esm/managers/DonobuFlowsManager.js +15 -15
- package/dist/esm/managers/DonobuFlowsManager.js.map +1 -1
- package/dist/esm/managers/GptConfigsManager.d.ts.map +1 -1
- package/dist/esm/managers/GptConfigsManager.js +3 -6
- package/dist/esm/managers/GptConfigsManager.js.map +1 -1
- package/dist/esm/managers/PluginLoader.d.ts +4 -0
- package/dist/esm/managers/PluginLoader.d.ts.map +1 -0
- package/dist/esm/managers/PluginLoader.js +103 -0
- package/dist/esm/managers/PluginLoader.js.map +1 -0
- package/dist/esm/managers/ToolManager.d.ts +3 -1
- package/dist/esm/managers/ToolManager.d.ts.map +1 -1
- package/dist/esm/managers/ToolManager.js +12 -1
- package/dist/esm/managers/ToolManager.js.map +1 -1
- package/dist/lib/fixtures/gptClients.js +1 -1
- package/dist/lib/testExtension.d.ts.map +1 -1
- package/dist/lib/testExtension.js +8 -1
- package/dist/lib/testExtension.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -0
- package/dist/main.js.map +1 -1
- package/dist/managers/AdminApiController.d.ts.map +1 -1
- package/dist/managers/AdminApiController.js +1 -2
- package/dist/managers/AdminApiController.js.map +1 -1
- package/dist/managers/CodeGenerator.d.ts.map +1 -1
- package/dist/managers/CodeGenerator.js +2 -1
- package/dist/managers/CodeGenerator.js.map +1 -1
- package/dist/managers/DonobuFlowsManager.d.ts.map +1 -1
- package/dist/managers/DonobuFlowsManager.js +15 -15
- package/dist/managers/DonobuFlowsManager.js.map +1 -1
- package/dist/managers/GptConfigsManager.d.ts.map +1 -1
- package/dist/managers/GptConfigsManager.js +3 -6
- package/dist/managers/GptConfigsManager.js.map +1 -1
- package/dist/managers/PluginLoader.d.ts +4 -0
- package/dist/managers/PluginLoader.d.ts.map +1 -0
- package/dist/managers/PluginLoader.js +104 -0
- package/dist/managers/PluginLoader.js.map +1 -0
- package/dist/managers/ToolManager.d.ts +3 -1
- package/dist/managers/ToolManager.d.ts.map +1 -1
- package/dist/managers/ToolManager.js +12 -1
- package/dist/managers/ToolManager.js.map +1 -1
- package/dist/models/FlowMetadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/esm/lib/utils/SelfHealingDebugReporter.d.ts +0 -69
- package/dist/esm/lib/utils/SelfHealingDebugReporter.d.ts.map +0 -1
- package/dist/esm/lib/utils/SelfHealingDebugReporter.js +0 -486
- package/dist/esm/lib/utils/SelfHealingDebugReporter.js.map +0 -1
- package/dist/esm/lib/utils/SelfHealingTestUtils.d.ts +0 -55
- package/dist/esm/lib/utils/SelfHealingTestUtils.d.ts.map +0 -1
- package/dist/esm/lib/utils/SelfHealingTestUtils.js +0 -335
- package/dist/esm/lib/utils/SelfHealingTestUtils.js.map +0 -1
- package/dist/esm/lib/utils/VideoArtifactManager.d.ts +0 -57
- package/dist/esm/lib/utils/VideoArtifactManager.d.ts.map +0 -1
- package/dist/esm/lib/utils/VideoArtifactManager.js +0 -343
- package/dist/esm/lib/utils/VideoArtifactManager.js.map +0 -1
- package/dist/esm/plugin/PluginLoader.d.ts +0 -3
- package/dist/esm/plugin/PluginLoader.d.ts.map +0 -1
- package/dist/esm/plugin/PluginLoader.js +0 -44
- package/dist/esm/plugin/PluginLoader.js.map +0 -1
- package/dist/esm/plugin/PluginModule.d.ts +0 -9
- package/dist/esm/plugin/PluginModule.d.ts.map +0 -1
- package/dist/esm/plugin/PluginModule.js +0 -3
- package/dist/esm/plugin/PluginModule.js.map +0 -1
- package/dist/lib/utils/SelfHealingDebugReporter.d.ts +0 -69
- package/dist/lib/utils/SelfHealingDebugReporter.d.ts.map +0 -1
- package/dist/lib/utils/SelfHealingDebugReporter.js +0 -486
- package/dist/lib/utils/SelfHealingDebugReporter.js.map +0 -1
- package/dist/lib/utils/SelfHealingTestUtils.d.ts +0 -55
- package/dist/lib/utils/SelfHealingTestUtils.d.ts.map +0 -1
- package/dist/lib/utils/SelfHealingTestUtils.js +0 -335
- package/dist/lib/utils/SelfHealingTestUtils.js.map +0 -1
- package/dist/lib/utils/VideoArtifactManager.d.ts +0 -57
- package/dist/lib/utils/VideoArtifactManager.d.ts.map +0 -1
- package/dist/lib/utils/VideoArtifactManager.js +0 -343
- package/dist/lib/utils/VideoArtifactManager.js.map +0 -1
- package/dist/plugin/PluginLoader.d.ts +0 -3
- package/dist/plugin/PluginLoader.d.ts.map +0 -1
- package/dist/plugin/PluginLoader.js +0 -44
- package/dist/plugin/PluginLoader.js.map +0 -1
- package/dist/plugin/PluginModule.d.ts +0 -9
- package/dist/plugin/PluginModule.d.ts.map +0 -1
- package/dist/plugin/PluginModule.js +0 -3
- package/dist/plugin/PluginModule.js.map +0 -1
|
@@ -1,343 +0,0 @@
|
|
|
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.VideoArtifactManager = void 0;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
|
-
const path = __importStar(require("path"));
|
|
39
|
-
const Logger_1 = require("../../utils/Logger");
|
|
40
|
-
/**
|
|
41
|
-
* Manages video artifacts for self-healing scenarios, providing linking and metadata capabilities
|
|
42
|
-
*/
|
|
43
|
-
class VideoArtifactManager {
|
|
44
|
-
/**
|
|
45
|
-
* Links video artifacts between original test and self-healing session
|
|
46
|
-
*/
|
|
47
|
-
static async linkSelfHealingVideos(testInfo, originalFlowMetadata, selfHealingFlowMetadata) {
|
|
48
|
-
try {
|
|
49
|
-
// Try to find existing Playwright video for original test
|
|
50
|
-
const originalVideo = await this.findPlaywrightVideo(testInfo);
|
|
51
|
-
// Try to find self-healing video (should be in flow persistence)
|
|
52
|
-
const selfHealingVideo = await this.findSelfHealingVideo(selfHealingFlowMetadata);
|
|
53
|
-
if (!originalVideo && !selfHealingVideo) {
|
|
54
|
-
Logger_1.appLogger.info('No video artifacts found to link');
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
const linkingMetadata = {
|
|
58
|
-
originalFlowId: originalFlowMetadata.id,
|
|
59
|
-
selfHealingFlowId: selfHealingFlowMetadata.id,
|
|
60
|
-
testName: testInfo.title,
|
|
61
|
-
relationship: 'self-healing-pair',
|
|
62
|
-
createdAt: Date.now(),
|
|
63
|
-
};
|
|
64
|
-
const linkedArtifacts = {
|
|
65
|
-
originalVideo,
|
|
66
|
-
selfHealingVideo,
|
|
67
|
-
linkingMetadata,
|
|
68
|
-
};
|
|
69
|
-
// Create a comprehensive video linking manifest
|
|
70
|
-
await this.createVideoLinkingManifest(testInfo, linkedArtifacts);
|
|
71
|
-
// Attach video metadata as test artifacts
|
|
72
|
-
if (originalVideo) {
|
|
73
|
-
await this.attachVideoMetadata(testInfo, originalVideo, 'original');
|
|
74
|
-
}
|
|
75
|
-
if (selfHealingVideo) {
|
|
76
|
-
await this.attachVideoMetadata(testInfo, selfHealingVideo, 'self-healing');
|
|
77
|
-
}
|
|
78
|
-
Logger_1.appLogger.info('Successfully linked video artifacts for self-healing session');
|
|
79
|
-
return linkedArtifacts;
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
Logger_1.appLogger.error('Failed to link video artifacts:', error);
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Attempts to find the Playwright-generated video for the test
|
|
88
|
-
*/
|
|
89
|
-
static async findPlaywrightVideo(testInfo) {
|
|
90
|
-
try {
|
|
91
|
-
// Playwright typically saves videos in the test-results directory
|
|
92
|
-
const outputDir = testInfo.outputDir;
|
|
93
|
-
if (!outputDir || !fs.existsSync(outputDir)) {
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
// Look for video files in the test output directory
|
|
97
|
-
const files = fs.readdirSync(outputDir);
|
|
98
|
-
const videoFile = files.find((file) => file.endsWith('.webm') || file.endsWith('.mp4'));
|
|
99
|
-
if (!videoFile) {
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
const filePath = path.join(outputDir, videoFile);
|
|
103
|
-
const stats = fs.statSync(filePath);
|
|
104
|
-
return {
|
|
105
|
-
filePath,
|
|
106
|
-
fileName: videoFile,
|
|
107
|
-
flowId: 'playwright-test',
|
|
108
|
-
flowName: testInfo.title,
|
|
109
|
-
size: stats.size,
|
|
110
|
-
createdAt: stats.mtimeMs,
|
|
111
|
-
type: 'original-test',
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
Logger_1.appLogger.warn('Failed to find Playwright video:', error);
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Attempts to find the self-healing flow video
|
|
121
|
-
*/
|
|
122
|
-
static async findSelfHealingVideo(flowMetadata) {
|
|
123
|
-
try {
|
|
124
|
-
// The self-healing video should be available through the flow persistence
|
|
125
|
-
// For now, we'll create a placeholder since the actual video retrieval
|
|
126
|
-
// would depend on the persistence implementation
|
|
127
|
-
return {
|
|
128
|
-
filePath: `flow-video-${flowMetadata.id}`, // This would be the actual path from persistence
|
|
129
|
-
fileName: `self-healing-${flowMetadata.id}.webm`,
|
|
130
|
-
flowId: flowMetadata.id,
|
|
131
|
-
flowName: flowMetadata.name || 'Self-Healing Flow',
|
|
132
|
-
size: 0, // Would be set from actual file
|
|
133
|
-
createdAt: flowMetadata.completedAt || Date.now(),
|
|
134
|
-
type: 'self-healing',
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
Logger_1.appLogger.warn('Failed to find self-healing video:', error);
|
|
139
|
-
return undefined;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Creates a comprehensive manifest linking videos and providing navigation aids
|
|
144
|
-
*/
|
|
145
|
-
static async createVideoLinkingManifest(testInfo, linkedArtifacts) {
|
|
146
|
-
const manifest = this.generateVideoManifestHtml(linkedArtifacts);
|
|
147
|
-
await testInfo.attach('video-linking-manifest.html', {
|
|
148
|
-
body: manifest,
|
|
149
|
-
contentType: 'text/html',
|
|
150
|
-
});
|
|
151
|
-
// Also create a simple JSON manifest for programmatic use
|
|
152
|
-
const jsonManifest = {
|
|
153
|
-
...linkedArtifacts.linkingMetadata,
|
|
154
|
-
videos: {
|
|
155
|
-
original: linkedArtifacts.originalVideo
|
|
156
|
-
? {
|
|
157
|
-
fileName: linkedArtifacts.originalVideo.fileName,
|
|
158
|
-
size: linkedArtifacts.originalVideo.size,
|
|
159
|
-
type: linkedArtifacts.originalVideo.type,
|
|
160
|
-
}
|
|
161
|
-
: null,
|
|
162
|
-
selfHealing: linkedArtifacts.selfHealingVideo
|
|
163
|
-
? {
|
|
164
|
-
fileName: linkedArtifacts.selfHealingVideo.fileName,
|
|
165
|
-
size: linkedArtifacts.selfHealingVideo.size,
|
|
166
|
-
type: linkedArtifacts.selfHealingVideo.type,
|
|
167
|
-
}
|
|
168
|
-
: null,
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
await testInfo.attach('video-linking-manifest.json', {
|
|
172
|
-
body: JSON.stringify(jsonManifest, null, 2),
|
|
173
|
-
contentType: 'application/json',
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Generates an HTML manifest for video navigation
|
|
178
|
-
*/
|
|
179
|
-
static generateVideoManifestHtml(linkedArtifacts) {
|
|
180
|
-
const { originalVideo, selfHealingVideo, linkingMetadata } = linkedArtifacts;
|
|
181
|
-
return `
|
|
182
|
-
<!DOCTYPE html>
|
|
183
|
-
<html lang="en">
|
|
184
|
-
<head>
|
|
185
|
-
<meta charset="UTF-8">
|
|
186
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
187
|
-
<title>Video Artifacts - ${linkingMetadata.testName}</title>
|
|
188
|
-
<style>
|
|
189
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
|
|
190
|
-
.container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
|
191
|
-
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
|
|
192
|
-
.content { padding: 20px; }
|
|
193
|
-
.video-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0; }
|
|
194
|
-
.video-card { background: #f8f9fa; padding: 20px; border-radius: 8px; border: 2px solid #e9ecef; }
|
|
195
|
-
.video-card.original { border-color: #dc3545; }
|
|
196
|
-
.video-card.healing { border-color: #28a745; }
|
|
197
|
-
.video-card h3 { margin-top: 0; color: #2c3e50; }
|
|
198
|
-
.video-info { display: grid; gap: 8px; margin: 15px 0; }
|
|
199
|
-
.info-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #dee2e6; }
|
|
200
|
-
.info-row:last-child { border-bottom: none; }
|
|
201
|
-
.info-label { font-weight: 600; color: #495057; }
|
|
202
|
-
.info-value { color: #6c757d; }
|
|
203
|
-
.navigation-tips { background: #e8f4f8; border: 1px solid #bee5eb; padding: 15px; border-radius: 6px; margin: 20px 0; }
|
|
204
|
-
.navigation-tips h4 { margin-top: 0; color: #0c5460; }
|
|
205
|
-
.tip-list { margin: 10px 0; padding-left: 20px; }
|
|
206
|
-
.tip-list li { margin: 5px 0; color: #0c5460; }
|
|
207
|
-
.file-link { color: #007bff; text-decoration: none; font-family: monospace; }
|
|
208
|
-
.file-link:hover { text-decoration: underline; }
|
|
209
|
-
.no-video { color: #6c757d; font-style: italic; }
|
|
210
|
-
.comparison-note { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 6px; margin: 20px 0; }
|
|
211
|
-
</style>
|
|
212
|
-
</head>
|
|
213
|
-
<body>
|
|
214
|
-
<div class="container">
|
|
215
|
-
<div class="header">
|
|
216
|
-
<h1>Video Artifacts</h1>
|
|
217
|
-
<p><strong>Test:</strong> ${linkingMetadata.testName}</p>
|
|
218
|
-
<p><strong>Generated:</strong> ${new Date(linkingMetadata.createdAt).toLocaleString()}</p>
|
|
219
|
-
</div>
|
|
220
|
-
|
|
221
|
-
<div class="content">
|
|
222
|
-
<div class="comparison-note">
|
|
223
|
-
<h4>📹 Video Comparison Guide</h4>
|
|
224
|
-
<p>These videos show the original failed test execution and the successful self-healing session. Use them together with the debug report to understand what changes were made.</p>
|
|
225
|
-
</div>
|
|
226
|
-
|
|
227
|
-
<div class="video-grid">
|
|
228
|
-
<div class="video-card original">
|
|
229
|
-
<h3>🔴 Original Test (Failed)</h3>
|
|
230
|
-
${originalVideo
|
|
231
|
-
? `
|
|
232
|
-
<div class="video-info">
|
|
233
|
-
<div class="info-row">
|
|
234
|
-
<span class="info-label">File:</span>
|
|
235
|
-
<span class="info-value">
|
|
236
|
-
<a href="${originalVideo.fileName}" class="file-link">${originalVideo.fileName}</a>
|
|
237
|
-
</span>
|
|
238
|
-
</div>
|
|
239
|
-
<div class="info-row">
|
|
240
|
-
<span class="info-label">Size:</span>
|
|
241
|
-
<span class="info-value">${this.formatFileSize(originalVideo.size)}</span>
|
|
242
|
-
</div>
|
|
243
|
-
<div class="info-row">
|
|
244
|
-
<span class="info-label">Created:</span>
|
|
245
|
-
<span class="info-value">${new Date(originalVideo.createdAt).toLocaleString()}</span>
|
|
246
|
-
</div>
|
|
247
|
-
<div class="info-row">
|
|
248
|
-
<span class="info-label">Type:</span>
|
|
249
|
-
<span class="info-value">Playwright Test Recording</span>
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
`
|
|
253
|
-
: '<div class="no-video">No original test video found</div>'}
|
|
254
|
-
</div>
|
|
255
|
-
|
|
256
|
-
<div class="video-card healing">
|
|
257
|
-
<h3>🟢 Self-Healing Session</h3>
|
|
258
|
-
${selfHealingVideo
|
|
259
|
-
? `
|
|
260
|
-
<div class="video-info">
|
|
261
|
-
<div class="info-row">
|
|
262
|
-
<span class="info-label">File:</span>
|
|
263
|
-
<span class="info-value">
|
|
264
|
-
<a href="${selfHealingVideo.fileName}" class="file-link">${selfHealingVideo.fileName}</a>
|
|
265
|
-
</span>
|
|
266
|
-
</div>
|
|
267
|
-
<div class="info-row">
|
|
268
|
-
<span class="info-label">Size:</span>
|
|
269
|
-
<span class="info-value">${this.formatFileSize(selfHealingVideo.size)}</span>
|
|
270
|
-
</div>
|
|
271
|
-
<div class="info-row">
|
|
272
|
-
<span class="info-label">Created:</span>
|
|
273
|
-
<span class="info-value">${new Date(selfHealingVideo.createdAt).toLocaleString()}</span>
|
|
274
|
-
</div>
|
|
275
|
-
<div class="info-row">
|
|
276
|
-
<span class="info-label">Flow ID:</span>
|
|
277
|
-
<span class="info-value">${selfHealingVideo.flowId}</span>
|
|
278
|
-
</div>
|
|
279
|
-
</div>
|
|
280
|
-
`
|
|
281
|
-
: '<div class="no-video">No self-healing video found</div>'}
|
|
282
|
-
</div>
|
|
283
|
-
</div>
|
|
284
|
-
|
|
285
|
-
<div class="navigation-tips">
|
|
286
|
-
<h4>💡 Video Analysis Tips</h4>
|
|
287
|
-
<ul class="tip-list">
|
|
288
|
-
<li><strong>Start with the debug report:</strong> Review \`self-healing-debug-report.html\` first to understand what changed</li>
|
|
289
|
-
<li><strong>Use timestamps:</strong> The debug report includes video timestamps for each action</li>
|
|
290
|
-
<li><strong>Compare side-by-side:</strong> Open both videos and use the timestamp links to jump to specific actions</li>
|
|
291
|
-
<li><strong>Focus on differences:</strong> Pay attention to actions marked as "modified" or "failed vs success"</li>
|
|
292
|
-
<li><strong>Check the generated fix:</strong> Review \`fixed-test.ts\` to see the updated test code</li>
|
|
293
|
-
</ul>
|
|
294
|
-
</div>
|
|
295
|
-
|
|
296
|
-
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #dee2e6; color: #6c757d; font-size: 14px;">
|
|
297
|
-
<p><strong>Related Artifacts:</strong></p>
|
|
298
|
-
<ul>
|
|
299
|
-
<li>\`self-healing-debug-report.html\` - Detailed comparison analysis</li>
|
|
300
|
-
<li>\`self-healing-summary.md\` - Quick overview of changes</li>
|
|
301
|
-
<li>\`self-healing-metadata.json\` - Technical metadata</li>
|
|
302
|
-
<li>\`fixed-test.ts\` - Updated test code</li>
|
|
303
|
-
<li>\`video-linking-manifest.json\` - Video linking metadata</li>
|
|
304
|
-
</ul>
|
|
305
|
-
</div>
|
|
306
|
-
</div>
|
|
307
|
-
</div>
|
|
308
|
-
</body>
|
|
309
|
-
</html>`;
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Attaches video metadata as a test artifact
|
|
313
|
-
*/
|
|
314
|
-
static async attachVideoMetadata(testInfo, video, prefix) {
|
|
315
|
-
const metadata = {
|
|
316
|
-
fileName: video.fileName,
|
|
317
|
-
flowId: video.flowId,
|
|
318
|
-
flowName: video.flowName,
|
|
319
|
-
size: video.size,
|
|
320
|
-
sizeFormatted: this.formatFileSize(video.size),
|
|
321
|
-
createdAt: video.createdAt,
|
|
322
|
-
createdAtFormatted: new Date(video.createdAt).toLocaleString(),
|
|
323
|
-
type: video.type,
|
|
324
|
-
};
|
|
325
|
-
await testInfo.attach(`${prefix}-video-metadata.json`, {
|
|
326
|
-
body: JSON.stringify(metadata, null, 2),
|
|
327
|
-
contentType: 'application/json',
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Formats file size in human-readable format
|
|
332
|
-
*/
|
|
333
|
-
static formatFileSize(bytes) {
|
|
334
|
-
if (bytes === 0)
|
|
335
|
-
return '0 Bytes';
|
|
336
|
-
const k = 1024;
|
|
337
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
338
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
339
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
exports.VideoArtifactManager = VideoArtifactManager;
|
|
343
|
-
//# sourceMappingURL=VideoArtifactManager.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"VideoArtifactManager.js","sourceRoot":"","sources":["../../../src/lib/utils/VideoArtifactManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PluginLoader.d.ts","sourceRoot":"","sources":["../../src/plugin/PluginLoader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAIrC,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,CAqC1E"}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.loadAllCustomToolPlugins = loadAllCustomToolPlugins;
|
|
7
|
-
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const node_url_1 = require("node:url");
|
|
10
|
-
const MiscUtils_1 = require("../utils/MiscUtils");
|
|
11
|
-
async function loadAllCustomToolPlugins() {
|
|
12
|
-
const pluginsDir = node_path_1.default.join(MiscUtils_1.MiscUtils.baseWorkingDirectory(), 'plugins');
|
|
13
|
-
// Collect candidate *.mjs / *.cjs / *.js / *.ts files (shallow-scan).
|
|
14
|
-
let entries = [];
|
|
15
|
-
try {
|
|
16
|
-
const dirents = await promises_1.default.readdir(pluginsDir, { withFileTypes: true });
|
|
17
|
-
entries = dirents
|
|
18
|
-
.filter((d) => d.isFile())
|
|
19
|
-
.filter((d) => /\.[cm]?js$|\.ts$/i.test(d.name))
|
|
20
|
-
.map((d) => node_path_1.default.join(pluginsDir, d.name));
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
// directory missing is not fatal – just return empty plugin list
|
|
24
|
-
return [];
|
|
25
|
-
}
|
|
26
|
-
const tools = [];
|
|
27
|
-
for (const file of entries) {
|
|
28
|
-
try {
|
|
29
|
-
// Dynamic import via file URL keeps Windows & *nix happy.
|
|
30
|
-
const mod = await import((0, node_url_1.pathToFileURL)(file).href);
|
|
31
|
-
if (mod &&
|
|
32
|
-
typeof mod.loadCustomTools === 'function') {
|
|
33
|
-
const pluginTools = await mod.loadCustomTools();
|
|
34
|
-
pluginTools.forEach((t) => tools.push(t));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
console.error(`Failed to load plugin "${file}":`, err);
|
|
39
|
-
// Keep going; a broken plugin should not stop startup.
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return tools;
|
|
43
|
-
}
|
|
44
|
-
//# sourceMappingURL=PluginLoader.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PluginLoader.js","sourceRoot":"","sources":["../../src/plugin/PluginLoader.ts"],"names":[],"mappings":";;;;;AAOA,4DAqCC;AA5CD,gEAAkC;AAClC,0DAA6B;AAC7B,uCAAyC;AAGzC,kDAA+C;AAExC,KAAK,UAAU,wBAAwB;IAC5C,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,qBAAS,CAAC,oBAAoB,EAAE,EAAE,SAAS,CAAC,CAAC;IAE1E,sEAAsE;IACtE,IAAI,OAAO,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,OAAO,GAAG,OAAO;aACd,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACzB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aAC/C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,iEAAiE;QACjE,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAqB,EAAE,CAAC;IAEnC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,0DAA0D;YAC1D,MAAM,GAAG,GAAY,MAAM,MAAM,CAAC,IAAA,wBAAa,EAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;YAE5D,IACE,GAAG;gBACH,OAAQ,GAA6B,CAAC,eAAe,KAAK,UAAU,EACpE,CAAC;gBACD,MAAM,WAAW,GAAG,MAAO,GAAoB,CAAC,eAAe,EAAE,CAAC;gBAClE,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,0BAA0B,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;YACvD,uDAAuD;QACzD,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Tool } from '../tools/Tool';
|
|
2
|
-
/**
|
|
3
|
-
* Every plugin must export ONE async function with this exact name.
|
|
4
|
-
* Donobu will call it on startup.
|
|
5
|
-
*/
|
|
6
|
-
export interface PluginModule {
|
|
7
|
-
loadCustomTools(): Promise<Tool<any, any>[]>;
|
|
8
|
-
}
|
|
9
|
-
//# sourceMappingURL=PluginModule.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PluginModule.d.ts","sourceRoot":"","sources":["../../src/plugin/PluginModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;CAC9C"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PluginModule.js","sourceRoot":"","sources":["../../src/plugin/PluginModule.ts"],"names":[],"mappings":""}
|