lexxit-automation-framework 3.0.2 → 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 +7 -1
  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
@@ -1,289 +0,0 @@
1
- // import { StepRequest, StepResult } from '../types/types';
2
- // import { FUNCTION_MAP } from './functionMap';
3
- // import { Mapping } from './mapping';
4
- // import { TextHandler } from '../actions/textHandler';
5
- // import { ClickHandler } from '../actions/clickHandler';
6
- // import { CheckboxHandler } from '../actions/checkboxHandler';
7
- // import { RadiobuttonHandler } from '../actions/radiobuttonHandler';
8
- // import { DropdownHandler } from '../actions/dropdownHandler';
9
- // import { BrowserManager } from '../actions/browserManager';
10
-
11
- // export class StepExecutor {
12
- // private browserManager: BrowserManager;
13
-
14
- // constructor(browserManager: BrowserManager) {
15
- // this.browserManager = browserManager;
16
- // }
17
-
18
- // private getHandlerInstance(handlerName: string): any {
19
- // if (handlerName === 'browserManager') {
20
- // return this.browserManager;
21
- // }
22
-
23
- // const page = this.browserManager.getPage();
24
- // const handlerMap: Record<string, any> = {
25
- // textHandler: new TextHandler(page),
26
- // clickHandler: new ClickHandler(page),
27
- // checkboxHandler: new CheckboxHandler(page),
28
- // radiobuttonHandler: new RadiobuttonHandler(page),
29
- // dropdownHandler: new DropdownHandler(page)
30
- // };
31
-
32
- // return handlerMap[handlerName];
33
- // }
34
-
35
- // private buildSkippedResult(step: StepRequest): StepResult {
36
- // return {
37
- // uid: null,
38
- // obj_uid: step.obj_uid ?? null,
39
- // page_uid: step.page_uid ?? null,
40
- // step_name: step.step_name,
41
- // status: 'skip',
42
- // expected_result: 'Step skipped',
43
- // comments: 'Skipped due to previous step failure',
44
- // duration: '0 seconds',
45
- // start_time: new Date().toISOString().replace('T', ' ').split('.')[0]
46
- // };
47
- // }
48
-
49
- // private buildErrorResult(step: StepRequest, message: string): StepResult {
50
- // return {
51
- // uid: null,
52
- // obj_uid: step.obj_uid ?? null,
53
- // page_uid: step.page_uid ?? null,
54
- // step_name: step.step_name,
55
- // status: 'fail',
56
- // expected_result: 'Step executed successfully',
57
- // comments: message,
58
- // duration: '0 seconds',
59
- // start_time: new Date().toISOString().replace('T', ' ').split('.')[0]
60
- // };
61
- // }
62
-
63
- // async executeStep(
64
- // step: StepRequest,
65
- // skip: boolean = false,
66
- // context: { browser?: string; headless?: boolean; videoEnabled?: boolean; testScriptUid?: string } = {}
67
- // ): Promise<StepResult> {
68
- // if (skip) {
69
- // return this.buildSkippedResult(step);
70
- // }
71
-
72
- // try {
73
- // const parsed = Mapping.parse(step.step_script);
74
- // const handlerInfo = FUNCTION_MAP[parsed.functionName];
75
-
76
- // if (!handlerInfo) {
77
- // return this.buildErrorResult(step, `No handler mapped for function: ${parsed.functionName}`);
78
- // }
79
-
80
- // const handlerInstance = this.getHandlerInstance(handlerInfo.handler);
81
-
82
- // if (!handlerInstance) {
83
- // return this.buildErrorResult(step, `Handler instance not found: ${handlerInfo.handler}`);
84
- // }
85
-
86
- // const method = handlerInstance[handlerInfo.method];
87
-
88
- // if (typeof method !== 'function') {
89
- // return this.buildErrorResult(step, `Method '${handlerInfo.method}' not found on handler '${handlerInfo.handler}'`);
90
- // }
91
-
92
- // const callArgs = this.buildCallArgs(handlerInfo.handler, parsed, step, context);
93
- // const result: StepResult = await method.apply(handlerInstance, callArgs);
94
-
95
- // result.step_name = step.step_name;
96
- // result.obj_uid = step.obj_uid ?? null;
97
- // result.page_uid = step.page_uid ?? null;
98
-
99
- // return result;
100
- // } catch (error: any) {
101
- // return this.buildErrorResult(step, `Unexpected error executing step: ${error.message || error}`);
102
- // }
103
- // }
104
-
105
- // private buildCallArgs(
106
- // handlerName: string,
107
- // parsed: { functionName: string; args: string[] },
108
- // step: StepRequest,
109
- // context: { browser?: string; headless?: boolean; videoEnabled?: boolean; testScriptUid?: string }
110
- // ): any[] {
111
- // if (handlerName === 'browserManager') {
112
- // if (parsed.functionName === 'openBrowser') {
113
- // return [context.browser, context.headless, context.videoEnabled];
114
- // }
115
- // if (parsed.functionName === 'closeBrowser') {
116
- // return [context.testScriptUid];
117
- // }
118
- // return [...parsed.args, step.step_name];
119
- // }
120
-
121
- // const label = step.label || '';
122
- // const locators = step.locators || [];
123
- // return [label, locators, ...parsed.args.slice(2), step.step_name];
124
- // }
125
- // }
126
-
127
-
128
- import { StepRequest, StepResult } from '../types/types';
129
- import { FUNCTION_MAP } from './functionMap';
130
- import { Mapping } from './mapping';
131
- import { TextHandler } from '../actions/textHandler';
132
- import { ClickHandler } from '../actions/clickHandler';
133
- import { CheckboxHandler } from '../actions/checkboxHandler';
134
- import { RadiobuttonHandler } from '../actions/radiobuttonHandler';
135
- import { DropdownHandler } from '../actions/dropdownHandler';
136
- import { BrowserManager } from '../actions/browserManager';
137
- import { CustomCodeHandler } from '../actions/customcodehandler';
138
- import { TestDataStore } from '../store/testDataStore';
139
- import { ScreenshotMode } from '../types/types';
140
- import { DEFAULT_SCREENSHOT_MODE } from '../config';
141
-
142
- type StepContext = {
143
- browser?: string;
144
- headless?: boolean;
145
- videoEnabled?: boolean;
146
- testScriptUid?: string;
147
- screenshotMode?: ScreenshotMode;
148
- };
149
-
150
- export class StepExecutor {
151
- private browserManager: BrowserManager;
152
- private testData: TestDataStore;
153
-
154
- constructor(browserManager: BrowserManager, testData?: TestDataStore) {
155
- this.browserManager = browserManager;
156
- this.testData = testData ?? new TestDataStore();
157
- }
158
-
159
- private getHandlerInstance(handlerName: string, screenshotMode: ScreenshotMode): any {
160
- if (handlerName === 'browserManager') {
161
- return this.browserManager;
162
- }
163
-
164
- const page = this.browserManager.getPage();
165
- const handlerMap: Record<string, any> = {
166
- textHandler: new TextHandler(page),
167
- clickHandler: new ClickHandler(page, screenshotMode),
168
- checkboxHandler: new CheckboxHandler(page),
169
- radiobuttonHandler: new RadiobuttonHandler(page),
170
- dropdownHandler: new DropdownHandler(page),
171
- customCodeHandler: new CustomCodeHandler(page, this.testData)
172
- };
173
-
174
- return handlerMap[handlerName];
175
- }
176
-
177
- private buildSkippedResult(step: StepRequest): StepResult {
178
- return {
179
- uid: null,
180
- obj_uid: step.obj_uid ?? null,
181
- page_uid: step.page_uid ?? null,
182
- step_name: step.step_name,
183
- step_script: step.step_script,
184
- status: 'SKIP',
185
- expected_result: 'Step skipped',
186
- comments: 'Skipped due to previous step failure',
187
- duration: 0,
188
- start_time: new Date().toISOString().replace('T', ' ').split('.')[0]
189
- };
190
- }
191
-
192
- private buildErrorResult(step: StepRequest, message: string): StepResult {
193
- return {
194
- uid: null,
195
- obj_uid: step.obj_uid ?? null,
196
- page_uid: step.page_uid ?? null,
197
- step_name: step.step_name,
198
- step_script: step.step_script,
199
- status: 'FAIL',
200
- expected_result: 'Step executed successfully',
201
- comments: message,
202
- duration: 0,
203
- start_time: new Date().toISOString().replace('T', ' ').split('.')[0]
204
- };
205
- }
206
-
207
- async executeStep(
208
- step: StepRequest,
209
- skip: boolean = false,
210
- context: StepContext = {}
211
- ): Promise<StepResult> {
212
- if (skip) {
213
- return this.buildSkippedResult(step);
214
- }
215
-
216
- const screenshotMode: ScreenshotMode = context.screenshotMode ?? DEFAULT_SCREENSHOT_MODE;
217
-
218
- try {
219
- const parsed = Mapping.parse(step.step_script);
220
- const handlerInfo = FUNCTION_MAP[parsed.functionName];
221
-
222
- if (!handlerInfo) {
223
- return this.buildErrorResult(step, `No handler mapped for function: ${parsed.functionName}`);
224
- }
225
-
226
- const handlerInstance = this.getHandlerInstance(handlerInfo.handler, screenshotMode);
227
-
228
- if (!handlerInstance) {
229
- return this.buildErrorResult(step, `Handler instance not found: ${handlerInfo.handler}`);
230
- }
231
-
232
- const method = handlerInstance[handlerInfo.method];
233
-
234
- if (typeof method !== 'function') {
235
- return this.buildErrorResult(step, `Method '${handlerInfo.method}' not found on handler '${handlerInfo.handler}'`);
236
- }
237
-
238
- const callArgs = this.buildCallArgs(handlerInfo.handler, parsed, step, context);
239
- const result: StepResult = await method.apply(handlerInstance, callArgs);
240
- const screenshot = await this.browserManager.captureScreenshot(
241
- context.screenshotMode || 'never',
242
- result.status === 'FAIL'
243
- );
244
- result.screenshot = screenshot;
245
-
246
- result.step_name = step.step_name;
247
- result.obj_uid = step.obj_uid ?? null;
248
- result.page_uid = step.page_uid ?? null;
249
-
250
- return result;
251
- } catch (error: any) {
252
- return this.buildErrorResult(step, `Unexpected error executing step: ${error.message || error}`);
253
- }
254
- }
255
-
256
- private buildCallArgs(
257
- handlerName: string,
258
- parsed: { functionName: string; args: string[] },
259
- step: StepRequest,
260
- context: StepContext
261
- ): any[] {
262
- if (handlerName === 'browserManager') {
263
- if (parsed.functionName === 'openBrowser') {
264
- return [context.browser, context.headless, context.videoEnabled];
265
- }
266
- if (parsed.functionName === 'closeBrowser') {
267
- return [context.testScriptUid];
268
- }
269
- return [...parsed.args, step.step_name];
270
- }
271
-
272
- // customCodeHandler: runCustomCode(codeStr, returnVariable?, xpath?)
273
- // args come straight from the step_script — no label/locators prepended
274
- // if (handlerName === 'customCodeHandler') {
275
- // return [...parsed.args, step.step_name];
276
- // }
277
- if (handlerName === 'customCodeHandler') {
278
- const [codeStr, returnVariable, xpath] = parsed.args;
279
- return [codeStr, returnVariable, xpath, step.step_name];
280
- }
281
-
282
- const label = step.label || '';
283
- const locators = step.locators || [];
284
- const resolvedArgs = parsed.args.slice(2).map(arg =>
285
- typeof arg === 'string' ? this.testData.resolve(arg) : arg
286
- );
287
- return [label, locators, ...resolvedArgs, step.step_name];
288
- }
289
- }
@@ -1,78 +0,0 @@
1
- import { TestSetRequest, TestSetResult, TestScriptResult } from '../types/types';
2
- import { ScriptExecutor } from '../executor/scriptExecutor';
3
- import { executionStore } from '../store/executionStore';
4
- import { sseManager } from '../sse/sseManager';
5
-
6
- const MAX_PARALLEL = 10;
7
-
8
- export async function testRunner(payload: TestSetRequest, executionId: string): Promise<TestSetResult> {
9
- const startMs = Date.now();
10
-
11
- const execMode = payload.exec_mode?.mode || 'fast';
12
- const delayMs = execMode === 'fast' ? 0 : (payload.exec_mode?.delay_ms || (execMode === 'medium' ? 1000 : 3000));
13
- const effectiveVideoEnabled = execMode === 'fast' ? false : payload.video_enabled;
14
-
15
- // ─── Emit execution start ──────────────────────────────────────────────────
16
- sseManager.emit(executionId, 'execution_start', {
17
- test_set_name: payload.test_set_name,
18
- total_scripts: payload.scripts.length,
19
- parallel: payload.parallel,
20
- exec_mode: payload.exec_mode
21
- });
22
-
23
- const scriptExecutor = new ScriptExecutor();
24
- let results: TestScriptResult[] = [];
25
-
26
- if (payload.parallel) {
27
- results = await runInBatches(payload, scriptExecutor, MAX_PARALLEL, executionId, effectiveVideoEnabled, delayMs);
28
- } else {
29
- for (const script of payload.scripts) {
30
- const result = await scriptExecutor.executeScript(script, effectiveVideoEnabled, executionId, delayMs);
31
- results.push(result);
32
- }
33
- }
34
-
35
- const passed = results.filter((r) => r.status === 'PASS').length;
36
- const failed = results.filter((r) => r.status === 'FAIL').length;
37
- const overallStatus = failed > 0 ? 'FAIL' : 'PASS';
38
-
39
- const finalResult: TestSetResult = {
40
- executionId,
41
- status: overallStatus,
42
- total_scripts: results.length,
43
- passed,
44
- failed,
45
- duration: Date.now() - startMs,
46
- results
47
- };
48
-
49
- executionStore.setFinalResult(executionId, finalResult);
50
- executionStore.completeExecution(executionId, overallStatus);
51
-
52
- // ─── Emit execution complete ───────────────────────────────────────────────
53
- sseManager.emit(executionId, 'execution_complete', finalResult);
54
-
55
- return finalResult;
56
- }
57
-
58
- async function runInBatches(
59
- payload: TestSetRequest,
60
- scriptExecutor: ScriptExecutor,
61
- maxParallel: number,
62
- executionId: string,
63
- videoEnabled: boolean,
64
- delayMs: number
65
- ): Promise<TestScriptResult[]> {
66
- const results: TestScriptResult[] = [];
67
- const scripts = payload.scripts;
68
-
69
- for (let i = 0; i < scripts.length; i += maxParallel) {
70
- const batch = scripts.slice(i, i + maxParallel);
71
- const batchResults = await Promise.all(
72
- batch.map((script) => scriptExecutor.executeScript(script, videoEnabled, executionId, delayMs))
73
- );
74
- results.push(...batchResults);
75
- }
76
-
77
- return results;
78
- }
@@ -1,130 +0,0 @@
1
- import { Response } from 'express';
2
- import { SSEEvent, SSEEventType } from '../types/types';
3
- import { Logger } from '../utils/logger';
4
-
5
- const log = Logger.create('SSEManager');
6
-
7
- export class SSEManager {
8
- private clients: Map<string, Set<Response>> = new Map();
9
- private eventBuffer: Map<string, SSEEvent[]> = new Map();
10
- private readonly MAX_BUFFER = 500;
11
- private readonly BUFFER_TTL = 2 * 60 * 60 * 1000;
12
-
13
- // ─── Add Client ────────────────────────────────────────────────────────────
14
-
15
- addClient(executionId: string, res: Response): void {
16
- res.setHeader('Content-Type', 'text/event-stream');
17
- res.setHeader('Cache-Control', 'no-cache');
18
- res.setHeader('Connection', 'keep-alive');
19
- res.setHeader('X-Accel-Buffering', 'no');
20
- res.flushHeaders();
21
-
22
- if (!this.clients.has(executionId)) {
23
- this.clients.set(executionId, new Set());
24
- }
25
- this.clients.get(executionId)!.add(res);
26
-
27
- log.info(`SSE client connected: ${executionId}`);
28
-
29
- const pastEvents = this.eventBuffer.get(executionId) ?? [];
30
- if (pastEvents.length > 0) {
31
- log.info(`Replaying ${pastEvents.length} past events to late client: ${executionId}`);
32
- pastEvents.forEach((ev) => this.sendToOne(res, ev));
33
- } else {
34
- this.sendToOne(res, {
35
- type: 'log',
36
- executionId,
37
- timestamp: new Date().toISOString(),
38
- data: { message: 'Connected to execution stream' }
39
- });
40
- }
41
-
42
- const hb = setInterval(() => {
43
- if (!res.writableEnded) res.write(': heartbeat\n\n');
44
- else clearInterval(hb);
45
- }, 20000);
46
-
47
- res.on('close', () => {
48
- clearInterval(hb);
49
- this.removeClient(executionId, res);
50
- });
51
- }
52
-
53
- // ─── Emit ──────────────────────────────────────────────────────────────────
54
-
55
- emit(executionId: string, type: SSEEventType, data: any): void {
56
- const event: SSEEvent = {
57
- type,
58
- executionId,
59
- timestamp: new Date().toISOString(),
60
- data
61
- };
62
-
63
- if (!this.eventBuffer.has(executionId)) {
64
- this.eventBuffer.set(executionId, []);
65
- }
66
- const buf = this.eventBuffer.get(executionId)!;
67
- if (buf.length < this.MAX_BUFFER) {
68
- buf.push(event);
69
- }
70
-
71
- const clients = this.clients.get(executionId);
72
- if (clients && clients.size > 0) {
73
- clients.forEach((res) => this.sendToOne(res, event));
74
- }
75
-
76
- if (type === 'execution_complete') {
77
- setTimeout(() => this.clients.delete(executionId), 5000);
78
- setTimeout(() => this.eventBuffer.delete(executionId), this.BUFFER_TTL);
79
- }
80
- }
81
-
82
- // ─── Broadcast ─────────────────────────────────────────────────────────────
83
-
84
- broadcast(type: SSEEventType, data: any): void {
85
- const event: SSEEvent = {
86
- type,
87
- executionId: 'global',
88
- timestamp: new Date().toISOString(),
89
- data
90
- };
91
- this.clients.forEach((clients) => {
92
- clients.forEach((res) => this.sendToOne(res, event));
93
- });
94
- }
95
-
96
- // ─── Get Buffer ────────────────────────────────────────────────────────────
97
-
98
- getBuffer(executionId: string): SSEEvent[] {
99
- return this.eventBuffer.get(executionId) ?? [];
100
- }
101
-
102
- // ─── Has Clients ───────────────────────────────────────────────────────────
103
-
104
- hasClients(executionId: string): boolean {
105
- return (this.clients.get(executionId)?.size ?? 0) > 0;
106
- }
107
-
108
- // ─── Private: Send To One ──────────────────────────────────────────────────
109
-
110
- private sendToOne(res: Response, event: SSEEvent): void {
111
- try {
112
- if (!res.writableEnded) {
113
- res.write(`data: ${JSON.stringify(event)}\n\n`);
114
- }
115
- } catch {
116
- /* client gone */
117
- }
118
- }
119
-
120
- // ─── Private: Remove Client ────────────────────────────────────────────────
121
-
122
- private removeClient(executionId: string, res: Response): void {
123
- this.clients.get(executionId)?.delete(res);
124
- if (this.clients.get(executionId)?.size === 0) {
125
- this.clients.delete(executionId);
126
- }
127
- }
128
- }
129
-
130
- export const sseManager = new SSEManager();
@@ -1,152 +0,0 @@
1
- import { ExecutionState, LiveScriptState, LiveStepState, ExecutionOverallStatus, StepLiveStatus, TestSetResult } from '../types/types';
2
-
3
- class ExecutionStore {
4
- private executions: Map<string, ExecutionState> = new Map();
5
-
6
- // ─── Create ────────────────────────────────────────────────────────────────
7
-
8
- createExecution(executionId: string, testSetName: string): void {
9
- const execution: ExecutionState = {
10
- execution_id: executionId,
11
- test_set_name: testSetName,
12
- status: 'RUNNING',
13
- start_time: new Date().toISOString(),
14
- end_time: null,
15
- scripts: [],
16
- final_result: null
17
- };
18
-
19
- this.executions.set(executionId, execution);
20
- }
21
-
22
- // ─── Register Script ───────────────────────────────────────────────────────
23
-
24
- registerScript(executionId: string, testScriptUid: string, testCaseName: string, stepNames: string[]): void {
25
- const execution = this.executions.get(executionId);
26
- if (!execution) return;
27
-
28
- const scriptState: LiveScriptState = {
29
- test_script_uid: testScriptUid,
30
- test_case_name: testCaseName,
31
- status: 'RUNNING',
32
- video_path: null,
33
- steps: stepNames.map((name) => ({
34
- step_name: name,
35
- status: 'PENDING' as StepLiveStatus,
36
- expected_result: null,
37
- comments: null,
38
- duration: null
39
- }))
40
- };
41
-
42
- execution.scripts.push(scriptState);
43
- }
44
-
45
- // ─── Set Video Path ────────────────────────────────────────────────────────
46
-
47
- setVideoPath(executionId: string, testScriptUid: string, videoPath: string): void {
48
- const execution = this.executions.get(executionId);
49
- if (!execution) return;
50
-
51
- const script = execution.scripts.find((s) => s.test_script_uid === testScriptUid);
52
- if (!script) return;
53
-
54
- script.video_path = videoPath;
55
- }
56
-
57
- // ─── Update Step ───────────────────────────────────────────────────────────
58
-
59
- updateStep(
60
- executionId: string,
61
- testScriptUid: string,
62
- stepIndex: number,
63
- status: StepLiveStatus,
64
- expected_result: string | null = null,
65
- comments: string | null = null,
66
- duration: number | null = null
67
- ): void {
68
- const execution = this.executions.get(executionId);
69
- if (!execution) return;
70
-
71
- const script = execution.scripts.find((s) => s.test_script_uid === testScriptUid);
72
- if (!script || !script.steps[stepIndex]) return;
73
-
74
- script.steps[stepIndex].status = status;
75
- if (expected_result !== null) script.steps[stepIndex].expected_result = expected_result;
76
- if (comments !== null) script.steps[stepIndex].comments = comments;
77
- if (duration !== null) script.steps[stepIndex].duration = duration;
78
- }
79
-
80
- // ─── Update Script Status ──────────────────────────────────────────────────
81
-
82
- updateScriptStatus(executionId: string, testScriptUid: string, status: ExecutionOverallStatus): void {
83
- const execution = this.executions.get(executionId);
84
- if (!execution) return;
85
-
86
- const script = execution.scripts.find((s) => s.test_script_uid === testScriptUid);
87
- if (!script) return;
88
-
89
- script.status = status;
90
- }
91
-
92
- // ─── Complete Execution ────────────────────────────────────────────────────
93
-
94
- completeExecution(executionId: string, status: ExecutionOverallStatus): void {
95
- const execution = this.executions.get(executionId);
96
- if (!execution) return;
97
-
98
- execution.status = status;
99
- execution.end_time = new Date().toISOString();
100
- }
101
-
102
- // ─── Get Execution ─────────────────────────────────────────────────────────
103
-
104
- getExecution(executionId: string): ExecutionState | undefined {
105
- return this.executions.get(executionId);
106
- }
107
-
108
- // ─── Set Final Result ──────────────────────────────────────────────────────
109
-
110
- setFinalResult(executionId: string, result: TestSetResult): void {
111
- const execution = this.executions.get(executionId);
112
- if (!execution) return;
113
-
114
- execution.final_result = result;
115
- }
116
-
117
- // ─── List Executions (latest first) ───────────────────────────────────────
118
-
119
- listExecutions(): { execution_id: string; test_set_name: string; status: ExecutionOverallStatus; start_time: string }[] {
120
- return Array.from(this.executions.values())
121
- .map((e) => ({
122
- execution_id: e.execution_id,
123
- test_set_name: e.test_set_name,
124
- status: e.status,
125
- start_time: e.start_time
126
- }))
127
- .reverse();
128
- }
129
-
130
- // ─── Queue Stats ───────────────────────────────────────────────────────────
131
-
132
- getQueueStats(): { total: number; queued: number; running: number; done: number; failed: number; cancelled: number; maxConcurrent: number; jobs: { execution_id: string; test_set_name: string; status: string }[] } {
133
- const all = Array.from(this.executions.values());
134
-
135
- return {
136
- total: all.length,
137
- queued: 0,
138
- running: all.filter((e) => e.status === 'RUNNING').length,
139
- done: all.filter((e) => e.status === 'PASS').length,
140
- failed: all.filter((e) => e.status === 'FAIL').length,
141
- cancelled: 0,
142
- maxConcurrent: 10,
143
- jobs: all.map((e) => ({
144
- execution_id: e.execution_id,
145
- test_set_name: e.test_set_name,
146
- status: e.status
147
- }))
148
- };
149
- }
150
- }
151
-
152
- export const executionStore = new ExecutionStore();
@@ -1,46 +0,0 @@
1
- /**
2
- * TestDataStore
3
- * -------------
4
- * A simple key-value store shared across all steps of a single script execution.
5
- * runCustomCode writes values into it; any subsequent step arg that contains
6
- * testData["key"] has the token replaced with the stored value at call time.
7
- */
8
- export class TestDataStore {
9
- private store = new Map<string, string>();
10
-
11
- set(key: string, value: string): void {
12
- this.store.set(key, String(value));
13
- }
14
-
15
- get(key: string): string | undefined {
16
- return this.store.get(key);
17
- }
18
-
19
- has(key: string): boolean {
20
- return this.store.has(key);
21
- }
22
-
23
- /**
24
- * Resolves a step arg string that may contain testData["key"] tokens.
25
- * e.g. 'testData["RN_randomEmail"]' → 'abc@xyz.com'
26
- * Plain strings pass through unchanged.
27
- */
28
- resolve(value: string): string {
29
- return value.replace(/testData\["([^"]+)"\]/g, (_, key) => {
30
- const resolved = this.store.get(key);
31
- if (resolved === undefined) {
32
- console.warn(`[TestDataStore] Key "${key}" not found in store`);
33
- return value; // return original token so the error is visible
34
- }
35
- return resolved;
36
- });
37
- }
38
-
39
- getAll(): Record<string, string> {
40
- return Object.fromEntries(this.store);
41
- }
42
-
43
- clear(): void {
44
- this.store.clear();
45
- }
46
- }