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.
Files changed (54) hide show
  1. package/dist-obf/actions/baseHandler.js +1 -1
  2. package/dist-obf/actions/browserManager.js +1 -1
  3. package/dist-obf/actions/checkboxHandler.js +1 -1
  4. package/dist-obf/actions/clickHandler.js +1 -1
  5. package/dist-obf/actions/customcodehandler.js +1 -1
  6. package/dist-obf/actions/dropdownHandler.js +1 -1
  7. package/dist-obf/actions/radiobuttonHandler.js +1 -1
  8. package/dist-obf/actions/textHandler.js +1 -1
  9. package/dist-obf/api/server.js +1 -1
  10. package/dist-obf/config.js +1 -1
  11. package/dist-obf/executor/functionMap.js +1 -1
  12. package/dist-obf/executor/mapping.js +1 -1
  13. package/dist-obf/executor/scriptExecutor.js +1 -1
  14. package/dist-obf/executor/stepExecutor.js +1 -1
  15. package/dist-obf/runner/testRunner.js +1 -1
  16. package/dist-obf/sse/sseManager.js +1 -1
  17. package/dist-obf/store/executionStore.js +1 -1
  18. package/dist-obf/store/testDataStore.js +1 -1
  19. package/dist-obf/types/types.js +1 -1
  20. package/dist-obf/utils/healingService.js +1 -1
  21. package/dist-obf/utils/locatorService.js +1 -1
  22. package/dist-obf/utils/logger.js +1 -1
  23. package/dist-obf/utils/metadataService.js +1 -1
  24. package/dist-obf/utils/waitConditions.js +1 -1
  25. package/dist-obf/validator/validator.js +1 -1
  26. package/package.json +5 -2
  27. package/npmignore +0 -8
  28. package/public/dashboard.html +0 -591
  29. package/src/actions/baseHandler.ts +0 -351
  30. package/src/actions/browserManager.ts +0 -432
  31. package/src/actions/checkboxHandler.ts +0 -276
  32. package/src/actions/clickHandler.ts +0 -513
  33. package/src/actions/customcodehandler.ts +0 -251
  34. package/src/actions/dropdownHandler.ts +0 -501
  35. package/src/actions/radiobuttonHandler.ts +0 -286
  36. package/src/actions/textHandler.ts +0 -498
  37. package/src/api/server.ts +0 -210
  38. package/src/config.ts +0 -7
  39. package/src/executor/functionMap.ts +0 -153
  40. package/src/executor/mapping.ts +0 -70
  41. package/src/executor/scriptExecutor.ts +0 -162
  42. package/src/executor/stepExecutor.ts +0 -289
  43. package/src/runner/testRunner.ts +0 -78
  44. package/src/sse/sseManager.ts +0 -130
  45. package/src/store/executionStore.ts +0 -152
  46. package/src/store/testDataStore.ts +0 -46
  47. package/src/types/types.ts +0 -159
  48. package/src/utils/healingService.ts +0 -210
  49. package/src/utils/locatorService.ts +0 -73
  50. package/src/utils/logger.ts +0 -27
  51. package/src/utils/metadataService.ts +0 -137
  52. package/src/utils/waitConditions.ts +0 -141
  53. package/src/validator/validator.ts +0 -140
  54. 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
- };
@@ -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
- }