lexxit-automation-framework 3.0.3 → 3.0.4
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-obf/actions/baseHandler.js +1 -1
- package/dist-obf/actions/browserManager.js +1 -1
- package/dist-obf/actions/checkboxHandler.js +1 -1
- package/dist-obf/actions/clickHandler.js +1 -1
- package/dist-obf/actions/customcodehandler.js +1 -1
- package/dist-obf/actions/dropdownHandler.js +1 -1
- package/dist-obf/actions/radiobuttonHandler.js +1 -1
- package/dist-obf/actions/textHandler.js +1 -1
- package/dist-obf/api/server.js +1 -1
- package/dist-obf/config.js +1 -1
- package/dist-obf/executor/functionMap.js +1 -1
- package/dist-obf/executor/mapping.js +1 -1
- package/dist-obf/executor/scriptExecutor.js +1 -1
- package/dist-obf/executor/stepExecutor.js +1 -1
- package/dist-obf/runner/testRunner.js +1 -1
- package/dist-obf/sse/sseManager.js +1 -1
- package/dist-obf/store/executionStore.js +1 -1
- package/dist-obf/store/testDataStore.js +1 -1
- package/dist-obf/types/types.js +1 -1
- package/dist-obf/utils/healingService.js +1 -1
- package/dist-obf/utils/locatorService.js +1 -1
- package/dist-obf/utils/logger.js +1 -1
- package/dist-obf/utils/metadataService.js +1 -1
- package/dist-obf/utils/waitConditions.js +1 -1
- package/dist-obf/validator/validator.js +1 -1
- package/package.json +5 -2
- package/npmignore +0 -8
- package/public/dashboard.html +0 -591
- package/src/actions/baseHandler.ts +0 -351
- package/src/actions/browserManager.ts +0 -432
- package/src/actions/checkboxHandler.ts +0 -276
- package/src/actions/clickHandler.ts +0 -513
- package/src/actions/customcodehandler.ts +0 -251
- package/src/actions/dropdownHandler.ts +0 -501
- package/src/actions/radiobuttonHandler.ts +0 -286
- package/src/actions/textHandler.ts +0 -498
- package/src/api/server.ts +0 -210
- package/src/config.ts +0 -7
- package/src/executor/functionMap.ts +0 -153
- package/src/executor/mapping.ts +0 -70
- package/src/executor/scriptExecutor.ts +0 -162
- package/src/executor/stepExecutor.ts +0 -289
- package/src/runner/testRunner.ts +0 -78
- package/src/sse/sseManager.ts +0 -130
- package/src/store/executionStore.ts +0 -152
- package/src/store/testDataStore.ts +0 -46
- package/src/types/types.ts +0 -159
- package/src/utils/healingService.ts +0 -210
- package/src/utils/locatorService.ts +0 -73
- package/src/utils/logger.ts +0 -27
- package/src/utils/metadataService.ts +0 -137
- package/src/utils/waitConditions.ts +0 -141
- package/src/validator/validator.ts +0 -140
- package/tsconfig.json +0 -16
package/src/api/server.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import express, { Request, Response } from 'express';
|
|
2
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
-
import { exec } from 'child_process';
|
|
4
|
-
import cors from 'cors';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { testRunner } from '../runner/testRunner';
|
|
7
|
-
import { validatePayload } from '../validator/validator';
|
|
8
|
-
import { executionStore } from '../store/executionStore';
|
|
9
|
-
import { sseManager } from '../sse/sseManager';
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
|
|
12
|
-
const app = express();
|
|
13
|
-
const PORT = 5500;
|
|
14
|
-
|
|
15
|
-
let dashboardOpened = false;
|
|
16
|
-
|
|
17
|
-
app.use(express.json());
|
|
18
|
-
app.use(cors({
|
|
19
|
-
origin: '*',
|
|
20
|
-
methods: ['GET', 'POST', 'OPTIONS'],
|
|
21
|
-
allowedHeaders: ['Content-Type', 'Authorization']
|
|
22
|
-
}));
|
|
23
|
-
app.use(express.static(path.join(__dirname, '../../public')));
|
|
24
|
-
|
|
25
|
-
// ─── POST /api/run-test ──────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
app.post('/api/run-test', async (req: Request, res: Response) => {
|
|
28
|
-
try {
|
|
29
|
-
const requestPayload = req.body;
|
|
30
|
-
|
|
31
|
-
const errors = validatePayload(requestPayload);
|
|
32
|
-
if (errors.length > 0) {
|
|
33
|
-
return res.status(400).json({ status: 'fail', errors });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const executionId = uuidv4();
|
|
37
|
-
executionStore.createExecution(executionId, requestPayload.test_set_name);
|
|
38
|
-
|
|
39
|
-
res.status(202).json({
|
|
40
|
-
status: 'started',
|
|
41
|
-
execution_id: executionId,
|
|
42
|
-
dashboard_url: `http://localhost:${PORT}/dashboard?execution_id=${executionId}`,
|
|
43
|
-
result_url: `http://localhost:${PORT}/api/status/${executionId}`
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (requestPayload.open_dashboard && !dashboardOpened) {
|
|
47
|
-
dashboardOpened = true;
|
|
48
|
-
console.log(`Opening dashboard for execution: ${executionId}`);
|
|
49
|
-
openInBrowser(`http://localhost:${PORT}/dashboard?execution_id=${executionId}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
testRunner(requestPayload, executionId).catch((error: any) => {
|
|
53
|
-
console.error(`Execution ${executionId} failed:`, error.message || error);
|
|
54
|
-
});
|
|
55
|
-
} catch (error: any) {
|
|
56
|
-
res.status(500).json({
|
|
57
|
-
status: 'fail',
|
|
58
|
-
message: error.message || 'Internal server error'
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// ─── GET /video/:execution_id/:test_script_uid ───────────────────────────────
|
|
64
|
-
|
|
65
|
-
app.get('/video/:execution_id/:test_script_uid', (req: Request, res: Response) => {
|
|
66
|
-
const execution = executionStore.getExecution(req.params.execution_id);
|
|
67
|
-
if (!execution) {
|
|
68
|
-
return res.status(404).json({ message: 'Execution not found' });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const script = execution.scripts.find((s) => s.test_script_uid === req.params.test_script_uid);
|
|
72
|
-
if (!script || !script.video_path) {
|
|
73
|
-
return res.status(404).json({ message: 'Video not available yet' });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const videoPath = script.video_path;
|
|
77
|
-
if (!fs.existsSync(videoPath)) {
|
|
78
|
-
return res.status(404).json({ message: 'Video file not found on disk' });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const stat = fs.statSync(videoPath);
|
|
82
|
-
res.setHeader('Content-Type', 'video/webm');
|
|
83
|
-
res.setHeader('Content-Length', stat.size);
|
|
84
|
-
fs.createReadStream(videoPath).pipe(res);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// ─── GET /api/status/:execution_id ──────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
app.get('/api/status/:execution_id', (req: Request, res: Response) => {
|
|
90
|
-
const execution = executionStore.getExecution(req.params.execution_id);
|
|
91
|
-
|
|
92
|
-
if (!execution) {
|
|
93
|
-
return res.status(404).json({ status: 'fail', message: 'Execution not found' });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!execution.final_result) {
|
|
97
|
-
return res.status(202).json({ status: 'RUNNING', message: 'Execution still in progress' });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
res.status(200).json({
|
|
101
|
-
type: 'execution_complete',
|
|
102
|
-
executionId: req.params.execution_id,
|
|
103
|
-
timestamp: new Date().toISOString(),
|
|
104
|
-
data: execution.final_result
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// ─── GET /api/live/:execution_id ─────────────────────────────────────────────
|
|
109
|
-
|
|
110
|
-
app.get('/api/live/:execution_id', (req: Request, res: Response) => {
|
|
111
|
-
const execution = executionStore.getExecution(req.params.execution_id);
|
|
112
|
-
|
|
113
|
-
if (!execution) {
|
|
114
|
-
return res.status(404).json({ status: 'fail', message: 'Execution not found' });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
res.status(200).json(execution);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// ─── GET /api/stream/:execution_id ───────────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
app.get('/api/stream/:execution_id', (req: Request, res: Response) => {
|
|
123
|
-
const execution = executionStore.getExecution(req.params.execution_id);
|
|
124
|
-
|
|
125
|
-
if (!execution) {
|
|
126
|
-
return res.status(404).json({ status: 'fail', message: 'Execution not found' });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
sseManager.addClient(req.params.execution_id, res);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// ─── GET /api/executions ─────────────────────────────────────────────────────
|
|
133
|
-
|
|
134
|
-
app.get('/api/executions', (req: Request, res: Response) => {
|
|
135
|
-
res.status(200).json(executionStore.listExecutions());
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// ─── GET /dashboard ──────────────────────────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
app.get('/dashboard', (req: Request, res: Response) => {
|
|
141
|
-
res.sendFile(path.join(__dirname, '../../public/dashboard.html'));
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// ─── GET /api/health ─────────────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
app.get('/api/health', (req: Request, res: Response) => {
|
|
147
|
-
res.status(200).json({
|
|
148
|
-
status: 'ok',
|
|
149
|
-
version: '2.0.0',
|
|
150
|
-
ts: new Date().toISOString(),
|
|
151
|
-
queue: executionStore.getQueueStats()
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// ─── GET / ───────────────────────────────────────────────────────────────────
|
|
156
|
-
|
|
157
|
-
app.get('/', (req: Request, res: Response) => {
|
|
158
|
-
res.status(200).json({
|
|
159
|
-
message: 'Playwright Framework API',
|
|
160
|
-
endpoints: [
|
|
161
|
-
{ method: 'POST', path: '/api/run-test', description: 'Submit a test set payload for execution' },
|
|
162
|
-
{ method: 'GET', path: '/api/status/:execution_id', description: 'Get final result of an execution' },
|
|
163
|
-
{ method: 'GET', path: '/api/live/:execution_id', description: 'Get live step-by-step status' },
|
|
164
|
-
{ method: 'GET', path: '/api/executions', description: 'List all executions latest first' },
|
|
165
|
-
{ method: 'GET', path: '/video/:execution_id/:test_script_uid', description: 'Stream recorded video' },
|
|
166
|
-
{ method: 'GET', path: '/dashboard', description: 'Live execution dashboard' },
|
|
167
|
-
{ method: 'GET', path: '/api/health', description: 'Health check' },
|
|
168
|
-
{ method: 'GET', path: '/api/stream/:execution_id', description: 'SSE live event stream' }
|
|
169
|
-
]
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// ─── Helper: open URL in default browser ─────────────────────────────────────
|
|
174
|
-
|
|
175
|
-
function openInBrowser(url: string): void {
|
|
176
|
-
const platform = process.platform;
|
|
177
|
-
let command: string;
|
|
178
|
-
|
|
179
|
-
if (platform === 'win32') {
|
|
180
|
-
command = `start "" "${url}"`;
|
|
181
|
-
} else if (platform === 'darwin') {
|
|
182
|
-
command = `open "${url}"`;
|
|
183
|
-
} else {
|
|
184
|
-
command = `xdg-open "${url}"`;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
exec(command, (error, stdout, stderr) => {
|
|
188
|
-
if (error) {
|
|
189
|
-
console.error('Failed to open dashboard automatically:', error.message);
|
|
190
|
-
console.error('stderr:', stderr);
|
|
191
|
-
console.error('Try opening manually:', url);
|
|
192
|
-
} else {
|
|
193
|
-
console.log(`Dashboard opened: ${url}`);
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
app.listen(PORT, () => {
|
|
199
|
-
console.log(`Playwright framework running on port ${PORT}`);
|
|
200
|
-
console.log(`API URL: http://localhost:${PORT}/api/run-test`);
|
|
201
|
-
console.log('Available endpoints:');
|
|
202
|
-
console.log(` POST http://localhost:${PORT}/api/run-test`);
|
|
203
|
-
console.log(` GET http://localhost:${PORT}/api/status/:execution_id`);
|
|
204
|
-
console.log(` GET http://localhost:${PORT}/api/live/:execution_id`);
|
|
205
|
-
console.log(` GET http://localhost:${PORT}/video/:execution_id/:test_script_uid`);
|
|
206
|
-
console.log(` GET http://localhost:${PORT}/api/executions`);
|
|
207
|
-
console.log(` GET http://localhost:${PORT}/dashboard`);
|
|
208
|
-
console.log(` GET http://localhost:${PORT}/api/health`);
|
|
209
|
-
console.log(` GET http://localhost:${PORT}/api/stream/:execution_id`);
|
|
210
|
-
});
|
package/src/config.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { ScreenshotMode } from './types/types';
|
|
3
|
-
export const DEFAULT_SCREENSHOT_MODE: ScreenshotMode = 'on_failure';
|
|
4
|
-
// export const TRIAGE: string = 'http://0.0.0.0:5005/api/v1/triage';
|
|
5
|
-
// export const COMPARE: string = 'http://localhost:5005/api/compare';
|
|
6
|
-
export const TRIAGE: string = 'https://ai-service.dev.lexxit.ai/api/v1/triage';
|
|
7
|
-
export const COMPARE: string = 'https://ai-service.dev.lexxit.ai/api/compare';
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
// export interface HandlerInfo {
|
|
2
|
-
// handler: string;
|
|
3
|
-
// method: string;
|
|
4
|
-
// }
|
|
5
|
-
|
|
6
|
-
// export const FUNCTION_MAP: Record<string, HandlerInfo> = {
|
|
7
|
-
// // ─── Text Handler ────────────────────────────────────────────────────────
|
|
8
|
-
// enterText: { handler: 'textHandler', method: 'enterText' },
|
|
9
|
-
// typeText: { handler: 'textHandler', method: 'enterText' },
|
|
10
|
-
// clearText: { handler: 'textHandler', method: 'clearText' },
|
|
11
|
-
// getInputValue: { handler: 'textHandler', method: 'getValue' },
|
|
12
|
-
// appendText: { handler: 'textHandler', method: 'appendText' },
|
|
13
|
-
// setInputValue: { handler: 'textHandler', method: 'enterText' },
|
|
14
|
-
// verifyText: { handler: 'textHandler', method: 'verifyText' },
|
|
15
|
-
// verifyValue: { handler: 'textHandler', method: 'verifyText' },
|
|
16
|
-
// getText: { handler: 'textHandler', method: 'getText' },
|
|
17
|
-
|
|
18
|
-
// // mapped in future
|
|
19
|
-
// // verifyTextContains: not yet implemented in textHandler
|
|
20
|
-
// // verifyTextNotEqual: not yet implemented in textHandler
|
|
21
|
-
// // getAttribute: not yet implemented in textHandler
|
|
22
|
-
|
|
23
|
-
// // ─── Click Handler ───────────────────────────────────────────────────────
|
|
24
|
-
// clickElement: { handler: 'clickHandler', method: 'click' },
|
|
25
|
-
// doubleClick: { handler: 'clickHandler', method: 'doubleClick' },
|
|
26
|
-
// rightClick: { handler: 'clickHandler', method: 'rightClick' },
|
|
27
|
-
// hover: { handler: 'clickHandler', method: 'hover' },
|
|
28
|
-
|
|
29
|
-
// // mapped in future
|
|
30
|
-
// // clickByJS: not yet implemented in clickHandler
|
|
31
|
-
|
|
32
|
-
// // ─── Checkbox Handler ────────────────────────────────────────────────────
|
|
33
|
-
// check_checkbox: { handler: 'checkboxHandler', method: 'check' },
|
|
34
|
-
// uncheck_checkbox: { handler: 'checkboxHandler', method: 'uncheck' },
|
|
35
|
-
// verifyChecked: { handler: 'checkboxHandler', method: 'verifyChecked' },
|
|
36
|
-
// verifyUnchecked: { handler: 'checkboxHandler', method: 'verifyUnchecked' },
|
|
37
|
-
// verifyEnabled: { handler: 'checkboxHandler', method: 'isEnabled' },
|
|
38
|
-
// verifyDisabled: { handler: 'checkboxHandler', method: 'isEnabled' },
|
|
39
|
-
// verifyVisible: { handler: 'checkboxHandler', method: 'isVisible' },
|
|
40
|
-
// verifyHidden: { handler: 'checkboxHandler', method: 'isVisible' },
|
|
41
|
-
|
|
42
|
-
// // ─── Radiobutton Handler ─────────────────────────────────────────────────
|
|
43
|
-
// selectRadioButton: { handler: 'radiobuttonHandler', method: 'select' },
|
|
44
|
-
|
|
45
|
-
// // ─── Dropdown Handler ────────────────────────────────────────────────────
|
|
46
|
-
// selectDropdown: { handler: 'dropdownHandler', method: 'selectOption' },
|
|
47
|
-
|
|
48
|
-
// // ─── Browser Manager ─────────────────────────────────────────────────────
|
|
49
|
-
// openBrowser: { handler: 'browserManager', method: 'openBrowser' },
|
|
50
|
-
// closeBrowser: { handler: 'browserManager', method: 'closeBrowser' },
|
|
51
|
-
// navigateToURL: { handler: 'browserManager', method: 'navigateToURL' },
|
|
52
|
-
// navigateBack: { handler: 'browserManager', method: 'goBack' },
|
|
53
|
-
// navigateForward: { handler: 'browserManager', method: 'goForward' },
|
|
54
|
-
// refreshPage: { handler: 'browserManager', method: 'reloadPage' },
|
|
55
|
-
// getTitle: { handler: 'browserManager', method: 'getTitle' },
|
|
56
|
-
// getCurrentURL: { handler: 'browserManager', method: 'getCurrentURL' },
|
|
57
|
-
|
|
58
|
-
// // mapped in future
|
|
59
|
-
// // verifyTitle: not yet implemented in browserManager
|
|
60
|
-
// // verifyPartialTitle: not yet implemented in browserManager
|
|
61
|
-
// // verifyURL: not yet implemented in browserManager
|
|
62
|
-
// // verifyPartialURL: not yet implemented in browserManager
|
|
63
|
-
// // getPageSource: not yet implemented in browserManager
|
|
64
|
-
|
|
65
|
-
// // mapped in future - no handler class created yet
|
|
66
|
-
// // dragAndDrop: dragDropHandler not yet created
|
|
67
|
-
// // file_upload: fileUploadHandler not yet created
|
|
68
|
-
// // enterTextInFrame: frameHandler not yet created
|
|
69
|
-
// // verifyElementCount: elementHandler not yet created
|
|
70
|
-
// // verifyAttribute: attributeHandler not yet created
|
|
71
|
-
// // acceptAlert: alertHandler not yet created
|
|
72
|
-
// // dismissAlert: alertHandler not yet created
|
|
73
|
-
// // getAlertText: alertHandler not yet created
|
|
74
|
-
// };
|
|
75
|
-
|
|
76
|
-
export interface HandlerInfo {
|
|
77
|
-
handler: string;
|
|
78
|
-
method: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export const FUNCTION_MAP: Record<string, HandlerInfo> = {
|
|
82
|
-
// ─── Text Handler ────────────────────────────────────────────────────────
|
|
83
|
-
enterText: { handler: 'textHandler', method: 'enterText' },
|
|
84
|
-
typeText: { handler: 'textHandler', method: 'enterText' },
|
|
85
|
-
clearText: { handler: 'textHandler', method: 'clearText' },
|
|
86
|
-
getInputValue: { handler: 'textHandler', method: 'getValue' },
|
|
87
|
-
appendText: { handler: 'textHandler', method: 'appendText' },
|
|
88
|
-
setInputValue: { handler: 'textHandler', method: 'enterText' },
|
|
89
|
-
verifyText: { handler: 'textHandler', method: 'verifyText' },
|
|
90
|
-
verifyValue: { handler: 'textHandler', method: 'verifyText' },
|
|
91
|
-
getText: { handler: 'textHandler', method: 'getText' },
|
|
92
|
-
file_upload: { handler: 'textHandler', method: 'file_upload' },
|
|
93
|
-
setSlider: { handler: 'textHandler', method: 'setSlider' },
|
|
94
|
-
// mapped in future
|
|
95
|
-
// verifyTextContains: not yet implemented in textHandler
|
|
96
|
-
// verifyTextNotEqual: not yet implemented in textHandler
|
|
97
|
-
// getAttribute: not yet implemented in textHandler
|
|
98
|
-
|
|
99
|
-
// ─── Click Handler ───────────────────────────────────────────────────────
|
|
100
|
-
clickElement: { handler: 'clickHandler', method: 'click' },
|
|
101
|
-
doubleClick: { handler: 'clickHandler', method: 'doubleClick' },
|
|
102
|
-
rightClick: { handler: 'clickHandler', method: 'rightClick' },
|
|
103
|
-
hover: { handler: 'clickHandler', method: 'hover' },
|
|
104
|
-
|
|
105
|
-
// mapped in future
|
|
106
|
-
// clickByJS: not yet implemented in clickHandler
|
|
107
|
-
|
|
108
|
-
// ─── Checkbox Handler ────────────────────────────────────────────────────
|
|
109
|
-
check_checkbox: { handler: 'checkboxHandler', method: 'check' },
|
|
110
|
-
uncheck_checkbox: { handler: 'checkboxHandler', method: 'uncheck' },
|
|
111
|
-
verifyChecked: { handler: 'checkboxHandler', method: 'verifyChecked' },
|
|
112
|
-
verifyUnchecked: { handler: 'checkboxHandler', method: 'verifyUnchecked' },
|
|
113
|
-
verifyEnabled: { handler: 'checkboxHandler', method: 'isEnabled' },
|
|
114
|
-
verifyDisabled: { handler: 'checkboxHandler', method: 'isEnabled' },
|
|
115
|
-
verifyVisible: { handler: 'checkboxHandler', method: 'isVisible' },
|
|
116
|
-
verifyHidden: { handler: 'checkboxHandler', method: 'isVisible' },
|
|
117
|
-
|
|
118
|
-
// ─── Radiobutton Handler ─────────────────────────────────────────────────
|
|
119
|
-
selectRadioButton: { handler: 'radiobuttonHandler', method: 'select' },
|
|
120
|
-
|
|
121
|
-
// ─── Dropdown Handler ────────────────────────────────────────────────────
|
|
122
|
-
selectDropdown: { handler: 'dropdownHandler', method: 'selectOption' },
|
|
123
|
-
|
|
124
|
-
// ─── Browser Manager ─────────────────────────────────────────────────────
|
|
125
|
-
openBrowser: { handler: 'browserManager', method: 'openBrowser' },
|
|
126
|
-
closeBrowser: { handler: 'browserManager', method: 'closeBrowser' },
|
|
127
|
-
navigateToURL: { handler: 'browserManager', method: 'navigateToURL' },
|
|
128
|
-
navigateBack: { handler: 'browserManager', method: 'goBack' },
|
|
129
|
-
navigateForward: { handler: 'browserManager', method: 'goForward' },
|
|
130
|
-
refreshPage: { handler: 'browserManager', method: 'reloadPage' },
|
|
131
|
-
getTitle: { handler: 'browserManager', method: 'getTitle' },
|
|
132
|
-
getCurrentURL: { handler: 'browserManager', method: 'getCurrentURL' },
|
|
133
|
-
|
|
134
|
-
// ─── Custom Code Handler ─────────────────────────────────────────────────
|
|
135
|
-
runCustomCode: { handler: 'customCodeHandler', method: 'runCustomCode' },
|
|
136
|
-
|
|
137
|
-
// mapped in future
|
|
138
|
-
// verifyTitle: not yet implemented in browserManager
|
|
139
|
-
// verifyPartialTitle: not yet implemented in browserManager
|
|
140
|
-
// verifyURL: not yet implemented in browserManager
|
|
141
|
-
// verifyPartialURL: not yet implemented in browserManager
|
|
142
|
-
// getPageSource: not yet implemented in browserManager
|
|
143
|
-
|
|
144
|
-
// mapped in future - no handler class created yet
|
|
145
|
-
// dragAndDrop: dragDropHandler not yet created
|
|
146
|
-
// file_upload: fileUploadHandler not yet created
|
|
147
|
-
// enterTextInFrame: frameHandler not yet created
|
|
148
|
-
// verifyElementCount: elementHandler not yet created
|
|
149
|
-
// verifyAttribute: attributeHandler not yet created
|
|
150
|
-
// acceptAlert: alertHandler not yet created
|
|
151
|
-
// dismissAlert: alertHandler not yet created
|
|
152
|
-
// getAlertText: alertHandler not yet created
|
|
153
|
-
};
|
package/src/executor/mapping.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
export interface ParsedStep {
|
|
2
|
-
functionName: string;
|
|
3
|
-
args: string[];
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export class Mapping {
|
|
7
|
-
/**
|
|
8
|
-
* Parses a step_script string like:
|
|
9
|
-
* tSetup.enterText('id', 'fname', 'mahesh')
|
|
10
|
-
* into { functionName: 'enterText', args: ['id', 'fname', 'mahesh'] }
|
|
11
|
-
*/
|
|
12
|
-
static parse(stepScript: string): ParsedStep {
|
|
13
|
-
if (!stepScript || typeof stepScript !== 'string') {
|
|
14
|
-
throw new Error('step_script is required and must be a string');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const match = stepScript.match(/^tSetup\.(\w+)\((.*)\)$/s);
|
|
18
|
-
|
|
19
|
-
if (!match) {
|
|
20
|
-
throw new Error(`Invalid step_script format: ${stepScript}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const functionName = match[1];
|
|
24
|
-
const rawArgs = match[2].trim();
|
|
25
|
-
|
|
26
|
-
const args = rawArgs.length === 0 ? [] : this.splitArgs(rawArgs).map((arg) => this.stripQuotes(arg));
|
|
27
|
-
|
|
28
|
-
return { functionName, args };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private static splitArgs(rawArgs: string): string[] {
|
|
32
|
-
const result: string[] = [];
|
|
33
|
-
let current = '';
|
|
34
|
-
let inSingleQuote = false;
|
|
35
|
-
let inDoubleQuote = false;
|
|
36
|
-
|
|
37
|
-
for (let i = 0; i < rawArgs.length; i++) {
|
|
38
|
-
const char = rawArgs[i];
|
|
39
|
-
|
|
40
|
-
if (char === "'" && !inDoubleQuote) {
|
|
41
|
-
inSingleQuote = !inSingleQuote;
|
|
42
|
-
current += char;
|
|
43
|
-
} else if (char === '"' && !inSingleQuote) {
|
|
44
|
-
inDoubleQuote = !inDoubleQuote;
|
|
45
|
-
current += char;
|
|
46
|
-
} else if (char === ',' && !inSingleQuote && !inDoubleQuote) {
|
|
47
|
-
result.push(current.trim());
|
|
48
|
-
current = '';
|
|
49
|
-
} else {
|
|
50
|
-
current += char;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (current.trim().length > 0) {
|
|
55
|
-
result.push(current.trim());
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private static stripQuotes(value: string): string {
|
|
62
|
-
if (
|
|
63
|
-
(value.startsWith("'") && value.endsWith("'")) ||
|
|
64
|
-
(value.startsWith('"') && value.endsWith('"'))
|
|
65
|
-
) {
|
|
66
|
-
return value.slice(1, -1);
|
|
67
|
-
}
|
|
68
|
-
return value;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { TestScriptRequest, TestScriptResult, StepResult } from '../types/types';
|
|
2
|
-
import { BrowserManager } from '../actions/browserManager';
|
|
3
|
-
import { StepExecutor } from './stepExecutor';
|
|
4
|
-
import { executionStore } from '../store/executionStore';
|
|
5
|
-
import { sseManager } from '../sse/sseManager';
|
|
6
|
-
|
|
7
|
-
export class ScriptExecutor {
|
|
8
|
-
async executeScript(
|
|
9
|
-
script: TestScriptRequest,
|
|
10
|
-
videoEnabled: boolean,
|
|
11
|
-
executionId: string,
|
|
12
|
-
delayMs: number = 0
|
|
13
|
-
): Promise<TestScriptResult> {
|
|
14
|
-
const startMs = Date.now();
|
|
15
|
-
const startTime = new Date().toISOString();
|
|
16
|
-
|
|
17
|
-
const stepNames = script.steps.map((s) => s.step_name);
|
|
18
|
-
executionStore.registerScript(executionId, script.test_script_uid, script.test_case_name, stepNames);
|
|
19
|
-
|
|
20
|
-
sseManager.emit(executionId, 'script_start', {
|
|
21
|
-
test_script_uid: script.test_script_uid,
|
|
22
|
-
test_case_name: script.test_case_name
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const browserManager = new BrowserManager();
|
|
26
|
-
const stepExecutor = new StepExecutor(browserManager);
|
|
27
|
-
const results: StepResult[] = [];
|
|
28
|
-
let scriptFailed = false;
|
|
29
|
-
|
|
30
|
-
const context = {
|
|
31
|
-
browser: script.browser,
|
|
32
|
-
headless: script.headless,
|
|
33
|
-
videoEnabled,
|
|
34
|
-
testScriptUid: script.test_script_uid,
|
|
35
|
-
screenshotMode: script.screenshot_mode
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
for (let i = 0; i < script.steps.length; i++) {
|
|
40
|
-
const step = script.steps[i];
|
|
41
|
-
const skip = scriptFailed;
|
|
42
|
-
|
|
43
|
-
// ─── Mark step as running/skip ─────────────────────────────────────
|
|
44
|
-
executionStore.updateStep(executionId, script.test_script_uid, i, skip ? 'SKIP' : 'RUNNING');
|
|
45
|
-
|
|
46
|
-
sseManager.emit(executionId, 'step_start', {
|
|
47
|
-
test_script_uid: script.test_script_uid,
|
|
48
|
-
step_index: i,
|
|
49
|
-
step_name: step.step_name,
|
|
50
|
-
status: skip ? 'SKIP' : 'RUNNING'
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// ─── Execute step ──────────────────────────────────────────────────
|
|
54
|
-
const stepResult = await stepExecutor.executeStep(step, skip, context);
|
|
55
|
-
|
|
56
|
-
// ─── Attach step_script to result ──────────────────────────────────
|
|
57
|
-
stepResult.step_script = step.step_script;
|
|
58
|
-
|
|
59
|
-
results.push(stepResult);
|
|
60
|
-
|
|
61
|
-
// ─── Update store ──────────────────────────────────────────────────
|
|
62
|
-
executionStore.updateStep(
|
|
63
|
-
executionId,
|
|
64
|
-
script.test_script_uid,
|
|
65
|
-
i,
|
|
66
|
-
stepResult.status,
|
|
67
|
-
stepResult.expected_result,
|
|
68
|
-
stepResult.comments,
|
|
69
|
-
stepResult.duration
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// ─── Emit step complete ────────────────────────────────────────────
|
|
73
|
-
sseManager.emit(executionId, 'step_complete', {
|
|
74
|
-
test_script_uid: script.test_script_uid,
|
|
75
|
-
step_index: i,
|
|
76
|
-
step_name: step.step_name,
|
|
77
|
-
step_script: step.step_script,
|
|
78
|
-
status: stepResult.status,
|
|
79
|
-
expected_result: stepResult.expected_result,
|
|
80
|
-
comments: stepResult.comments,
|
|
81
|
-
duration: stepResult.duration,
|
|
82
|
-
start_time: stepResult.start_time,
|
|
83
|
-
screenshot: stepResult.screenshot,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (stepResult.status === 'FAIL' && script.stop_on_failure) {
|
|
87
|
-
scriptFailed = true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (delayMs > 0 && !scriptFailed) {
|
|
91
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
} catch (error: any) {
|
|
95
|
-
scriptFailed = true;
|
|
96
|
-
const errResult: StepResult = {
|
|
97
|
-
uid: null,
|
|
98
|
-
obj_uid: null,
|
|
99
|
-
page_uid: null,
|
|
100
|
-
step_name: 'Script execution error',
|
|
101
|
-
step_script: '',
|
|
102
|
-
status: 'FAIL',
|
|
103
|
-
expected_result: 'Script executed successfully',
|
|
104
|
-
comments: `Unexpected error: ${error.message || error}`,
|
|
105
|
-
duration: 0,
|
|
106
|
-
start_time: new Date().toISOString()
|
|
107
|
-
};
|
|
108
|
-
results.push(errResult);
|
|
109
|
-
|
|
110
|
-
sseManager.emit(executionId, 'step_complete', {
|
|
111
|
-
test_script_uid: script.test_script_uid,
|
|
112
|
-
step_index: results.length - 1,
|
|
113
|
-
step_name: 'Script execution error',
|
|
114
|
-
step_script: '',
|
|
115
|
-
status: 'FAIL',
|
|
116
|
-
expected_result: errResult.expected_result,
|
|
117
|
-
comments: errResult.comments,
|
|
118
|
-
duration: errResult.duration,
|
|
119
|
-
start_time: errResult.start_time
|
|
120
|
-
});
|
|
121
|
-
} finally {
|
|
122
|
-
try {
|
|
123
|
-
await browserManager.closeBrowser(script.test_script_uid);
|
|
124
|
-
const videoPath = browserManager.getSavedVideoPath();
|
|
125
|
-
if (videoPath) {
|
|
126
|
-
executionStore.setVideoPath(executionId, script.test_script_uid, videoPath);
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
// ignore close errors
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const passed = results.filter((r) => r.status === 'PASS').length;
|
|
134
|
-
const failed = results.filter((r) => r.status === 'FAIL').length;
|
|
135
|
-
const skipped = results.filter((r) => r.status === 'SKIP').length;
|
|
136
|
-
|
|
137
|
-
const finalStatus = failed > 0 ? 'FAIL' : 'PASS';
|
|
138
|
-
executionStore.updateScriptStatus(executionId, script.test_script_uid, finalStatus);
|
|
139
|
-
|
|
140
|
-
// ─── Emit script complete ──────────────────────────────────────────────
|
|
141
|
-
sseManager.emit(executionId, 'script_complete', {
|
|
142
|
-
test_script_uid: script.test_script_uid,
|
|
143
|
-
test_case_name: script.test_case_name,
|
|
144
|
-
status: finalStatus
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
test_script_uid: script.test_script_uid,
|
|
149
|
-
test_case_name: script.test_case_name,
|
|
150
|
-
app_id: script.app_id,
|
|
151
|
-
browser: script.browser,
|
|
152
|
-
status: finalStatus,
|
|
153
|
-
results,
|
|
154
|
-
total_steps: results.length,
|
|
155
|
-
passed_steps: passed,
|
|
156
|
-
failed_steps: failed,
|
|
157
|
-
skipped_steps: skipped,
|
|
158
|
-
duration: Date.now() - startMs,
|
|
159
|
-
start_time: startTime
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
}
|