pw-automation-framework 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -0
- package/bin/lexxit-automation-framework.js +427 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +26 -0
- package/dist/app.js.map +1 -0
- package/dist/controllers/controller.d.ts +57 -0
- package/dist/controllers/controller.js +263 -0
- package/dist/controllers/controller.js.map +1 -0
- package/dist/core/BrowserManager.d.ts +46 -0
- package/dist/core/BrowserManager.js +377 -0
- package/dist/core/BrowserManager.js.map +1 -0
- package/dist/core/PlaywrightEngine.d.ts +16 -0
- package/dist/core/PlaywrightEngine.js +246 -0
- package/dist/core/PlaywrightEngine.js.map +1 -0
- package/dist/core/ScreenshotManager.d.ts +10 -0
- package/dist/core/ScreenshotManager.js +28 -0
- package/dist/core/ScreenshotManager.js.map +1 -0
- package/dist/core/TestData.d.ts +12 -0
- package/dist/core/TestData.js +29 -0
- package/dist/core/TestData.js.map +1 -0
- package/dist/core/TestExecutor.d.ts +16 -0
- package/dist/core/TestExecutor.js +355 -0
- package/dist/core/TestExecutor.js.map +1 -0
- package/dist/core/handlers/AllHandlers.d.ts +116 -0
- package/dist/core/handlers/AllHandlers.js +648 -0
- package/dist/core/handlers/AllHandlers.js.map +1 -0
- package/dist/core/handlers/BaseHandler.d.ts +16 -0
- package/dist/core/handlers/BaseHandler.js +27 -0
- package/dist/core/handlers/BaseHandler.js.map +1 -0
- package/dist/core/handlers/ClickHandler.d.ts +34 -0
- package/dist/core/handlers/ClickHandler.js +359 -0
- package/dist/core/handlers/ClickHandler.js.map +1 -0
- package/dist/core/handlers/CustomCodeHandler.d.ts +35 -0
- package/dist/core/handlers/CustomCodeHandler.js +102 -0
- package/dist/core/handlers/CustomCodeHandler.js.map +1 -0
- package/dist/core/handlers/DropdownHandler.d.ts +43 -0
- package/dist/core/handlers/DropdownHandler.js +304 -0
- package/dist/core/handlers/DropdownHandler.js.map +1 -0
- package/dist/core/handlers/InputHandler.d.ts +24 -0
- package/dist/core/handlers/InputHandler.js +197 -0
- package/dist/core/handlers/InputHandler.js.map +1 -0
- package/dist/core/registry/ActionRegistry.d.ts +8 -0
- package/dist/core/registry/ActionRegistry.js +35 -0
- package/dist/core/registry/ActionRegistry.js.map +1 -0
- package/dist/installer/frameworkLauncher.d.ts +31 -0
- package/dist/installer/frameworkLauncher.js +198 -0
- package/dist/installer/frameworkLauncher.js.map +1 -0
- package/dist/queue/ExecutionQueue.d.ts +52 -0
- package/dist/queue/ExecutionQueue.js +175 -0
- package/dist/queue/ExecutionQueue.js.map +1 -0
- package/dist/routes/api.routes.d.ts +2 -0
- package/dist/routes/api.routes.js +16 -0
- package/dist/routes/api.routes.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +30 -0
- package/dist/server.js.map +1 -0
- package/dist/types/types.d.ts +135 -0
- package/dist/types/types.js +4 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/elementHighlight.d.ts +35 -0
- package/dist/utils/elementHighlight.js +136 -0
- package/dist/utils/elementHighlight.js.map +1 -0
- package/dist/utils/locatorHelper.d.ts +7 -0
- package/dist/utils/locatorHelper.js +53 -0
- package/dist/utils/locatorHelper.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +35 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/response.d.ts +4 -0
- package/dist/utils/response.js +25 -0
- package/dist/utils/response.js.map +1 -0
- package/dist/utils/responseFormatter.d.ts +78 -0
- package/dist/utils/responseFormatter.js +123 -0
- package/dist/utils/responseFormatter.js.map +1 -0
- package/dist/utils/sseManager.d.ts +32 -0
- package/dist/utils/sseManager.js +122 -0
- package/dist/utils/sseManager.js.map +1 -0
- package/lexxit-automation-framework-2.0.0.tgz +0 -0
- package/npmignore +5 -0
- package/package.json +36 -0
- package/scripts/postinstall.js +52 -0
- package/src/app.ts +27 -0
- package/src/controllers/controller.ts +282 -0
- package/src/core/BrowserManager.ts +398 -0
- package/src/core/PlaywrightEngine.ts +371 -0
- package/src/core/ScreenshotManager.ts +25 -0
- package/src/core/TestData.ts +25 -0
- package/src/core/TestExecutor.ts +436 -0
- package/src/core/handlers/AllHandlers.ts +626 -0
- package/src/core/handlers/BaseHandler.ts +41 -0
- package/src/core/handlers/ClickHandler.ts +482 -0
- package/src/core/handlers/CustomCodeHandler.ts +123 -0
- package/src/core/handlers/DropdownHandler.ts +438 -0
- package/src/core/handlers/InputHandler.ts +192 -0
- package/src/core/registry/ActionRegistry.ts +31 -0
- package/src/installer/frameworkLauncher.ts +242 -0
- package/src/installer/install.sh +107 -0
- package/src/public/dashboard.html +540 -0
- package/src/public/queue-monitor.html +190 -0
- package/src/queue/ExecutionQueue.ts +200 -0
- package/src/routes/api.routes.ts +16 -0
- package/src/server.ts +29 -0
- package/src/types/types.ts +169 -0
- package/src/utils/elementHighlight.ts +174 -0
- package/src/utils/locatorHelper.ts +49 -0
- package/src/utils/logger.ts +40 -0
- package/src/utils/response.ts +27 -0
- package/src/utils/responseFormatter.ts +167 -0
- package/src/utils/sseManager.ts +127 -0
- package/tsconfig.json +18 -0
- package/videos/fb1b94b6-6639-4c9a-82bb-63572606f403/page@5bd5c6c8b62baa700e9810cdd64f5c49.webm +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class Logger {
|
|
2
|
+
private executionId;
|
|
3
|
+
private context;
|
|
4
|
+
constructor(context: string, executionId?: string);
|
|
5
|
+
private log;
|
|
6
|
+
debug(msg: string, ctx?: Record<string, any>): void;
|
|
7
|
+
info(msg: string, ctx?: Record<string, any>): void;
|
|
8
|
+
warn(msg: string, ctx?: Record<string, any>): void;
|
|
9
|
+
error(msg: string, ctx?: Record<string, any>): void;
|
|
10
|
+
child(context: string): Logger;
|
|
11
|
+
static create(context: string, executionId?: string): Logger;
|
|
12
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Logger = void 0;
|
|
4
|
+
const COLORS = {
|
|
5
|
+
DEBUG: "\x1b[36m", // cyan
|
|
6
|
+
INFO: "\x1b[32m", // green
|
|
7
|
+
WARN: "\x1b[33m", // yellow
|
|
8
|
+
ERROR: "\x1b[31m", // red
|
|
9
|
+
};
|
|
10
|
+
const RESET = "\x1b[0m";
|
|
11
|
+
class Logger {
|
|
12
|
+
constructor(context, executionId = "global") {
|
|
13
|
+
this.context = context;
|
|
14
|
+
this.executionId = executionId;
|
|
15
|
+
}
|
|
16
|
+
log(level, message, extra) {
|
|
17
|
+
const ts = new Date().toISOString();
|
|
18
|
+
const color = COLORS[level];
|
|
19
|
+
const tag = `[${level}][${this.context}]`;
|
|
20
|
+
const extra_str = extra ? ` ${JSON.stringify(extra)}` : "";
|
|
21
|
+
console.log(`${color}${ts} ${tag}${RESET} ${message}${extra_str}`);
|
|
22
|
+
}
|
|
23
|
+
debug(msg, ctx) { this.log("DEBUG", msg, ctx); }
|
|
24
|
+
info(msg, ctx) { this.log("INFO", msg, ctx); }
|
|
25
|
+
warn(msg, ctx) { this.log("WARN", msg, ctx); }
|
|
26
|
+
error(msg, ctx) { this.log("ERROR", msg, ctx); }
|
|
27
|
+
child(context) {
|
|
28
|
+
return new Logger(`${this.context}:${context}`, this.executionId);
|
|
29
|
+
}
|
|
30
|
+
static create(context, executionId) {
|
|
31
|
+
return new Logger(context, executionId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.Logger = Logger;
|
|
35
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;AAEA,MAAM,MAAM,GAA6B;IACvC,KAAK,EAAE,UAAU,EAAG,OAAO;IAC3B,IAAI,EAAG,UAAU,EAAG,QAAQ;IAC5B,IAAI,EAAG,UAAU,EAAG,SAAS;IAC7B,KAAK,EAAE,UAAU,EAAG,MAAM;CAC3B,CAAC;AACF,MAAM,KAAK,GAAG,SAAS,CAAC;AAExB,MAAa,MAAM;IAIjB,YAAY,OAAe,EAAE,cAAsB,QAAQ;QACzD,IAAI,CAAC,OAAO,GAAO,OAAO,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,KAA2B;QACvE,MAAM,EAAE,GAAM,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,GAAG,GAAK,IAAI,KAAK,KAAK,IAAI,CAAC,OAAO,GAAG,CAAC;QAC5C,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,EAAE,IAAI,GAAG,GAAG,KAAK,IAAI,OAAO,GAAG,SAAS,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,GAAW,EAAE,GAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9E,IAAI,CAAE,GAAW,EAAE,GAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9E,IAAI,CAAE,GAAW,EAAE,GAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9E,KAAK,CAAC,GAAW,EAAE,GAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAE9E,KAAK,CAAC,OAAe;QACnB,OAAO,IAAI,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,OAAO,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,OAAe,EAAE,WAAoB;QACjD,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC1C,CAAC;CACF;AA7BD,wBA6BC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ActionResponse } from "../types/types";
|
|
2
|
+
export declare function pass(comments: string, screenshot?: string, data?: any): ActionResponse;
|
|
3
|
+
export declare function fail(comments: string, errorDetail?: string, screenshot?: string, data?: any): ActionResponse;
|
|
4
|
+
export declare function failElementNotFound(displayLabel: string, screenshot?: string, extraData?: Record<string, any>): ActionResponse;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pass = pass;
|
|
4
|
+
exports.fail = fail;
|
|
5
|
+
exports.failElementNotFound = failElementNotFound;
|
|
6
|
+
function pass(comments, screenshot, data) {
|
|
7
|
+
return { status: "Pass", comments, screenshot, data };
|
|
8
|
+
}
|
|
9
|
+
function fail(comments, errorDetail, screenshot, data) {
|
|
10
|
+
const message = errorDetail ? `${comments} — ${errorDetail}` : comments;
|
|
11
|
+
return { status: "Fail", comments: message, screenshot, data };
|
|
12
|
+
}
|
|
13
|
+
function failElementNotFound(displayLabel, screenshot, extraData) {
|
|
14
|
+
return {
|
|
15
|
+
status: "Fail",
|
|
16
|
+
comments: `"${displayLabel}" not found`,
|
|
17
|
+
screenshot,
|
|
18
|
+
data: {
|
|
19
|
+
expected_result: `"${displayLabel}" found and interacted with`,
|
|
20
|
+
failure_type: "ELEMENT_NOT_FOUND",
|
|
21
|
+
...extraData,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/utils/response.ts"],"names":[],"mappings":";;AAEA,oBAEC;AAED,oBAGC;AACD,kDAeC;AAvBD,SAAgB,IAAI,CAAC,QAAgB,EAAE,UAAmB,EAAE,IAAU;IACpE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,SAAgB,IAAI,CAAC,QAAgB,EAAE,WAAoB,EAAE,UAAmB,EAAE,IAAU;IAC1F,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,QAAQ,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACjE,CAAC;AACD,SAAgB,mBAAmB,CACjC,YAAoB,EACpB,UAAmB,EACnB,SAA+B;IAE/B,OAAO;QACL,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,IAAI,YAAY,aAAa;QACvC,UAAU;QACV,IAAI,EAAE;YACJ,eAAe,EAAE,IAAI,YAAY,6BAA6B;YAC9D,YAAY,EAAE,mBAAmB;YACjC,GAAG,SAAS;SACb;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* responseFormatter.ts
|
|
3
|
+
* ====================
|
|
4
|
+
* Converts internal framework types → the standard API response format
|
|
5
|
+
* that matches what the main application expects.
|
|
6
|
+
*
|
|
7
|
+
* Standard format example:
|
|
8
|
+
* {
|
|
9
|
+
* "status": "fail",
|
|
10
|
+
* "results": [
|
|
11
|
+
* {
|
|
12
|
+
* "test_script_uid": "...",
|
|
13
|
+
* "app_id": "...",
|
|
14
|
+
* "status": "fail",
|
|
15
|
+
* "results": [
|
|
16
|
+
* {
|
|
17
|
+
* "uid": null,
|
|
18
|
+
* "obj_uid": "...",
|
|
19
|
+
* "step_name": "...",
|
|
20
|
+
* "page_uid": null,
|
|
21
|
+
* "comments": "...",
|
|
22
|
+
* "screenshot": "base64...",
|
|
23
|
+
* "duration": "8 seconds",
|
|
24
|
+
* "start_time": "2026-04-04 20:25:34",
|
|
25
|
+
* "expected_result": "...",
|
|
26
|
+
* "status": "pass"
|
|
27
|
+
* }
|
|
28
|
+
* ],
|
|
29
|
+
* "duration": "42 seconds",
|
|
30
|
+
* "script_start_time": "2026-04-04 20:25:34"
|
|
31
|
+
* }
|
|
32
|
+
* ]
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
import { ExecutionResult, ScriptResult, StepResult } from "../types/types";
|
|
36
|
+
export interface FormattedStepResult {
|
|
37
|
+
uid: string | null;
|
|
38
|
+
obj_uid: string | null;
|
|
39
|
+
step_name: string;
|
|
40
|
+
step_script?: string;
|
|
41
|
+
page_uid: string | null;
|
|
42
|
+
comments: string;
|
|
43
|
+
screenshot?: string;
|
|
44
|
+
duration: string;
|
|
45
|
+
start_time: string;
|
|
46
|
+
expected_result: string;
|
|
47
|
+
status: "pass" | "fail" | "skip";
|
|
48
|
+
comparison_code?: any;
|
|
49
|
+
}
|
|
50
|
+
export interface FormattedScriptResult {
|
|
51
|
+
test_script_uid: string;
|
|
52
|
+
app_id: string | undefined;
|
|
53
|
+
status: "pass" | "fail";
|
|
54
|
+
results: FormattedStepResult[];
|
|
55
|
+
duration: string;
|
|
56
|
+
script_start_time: string;
|
|
57
|
+
comparison_code?: any;
|
|
58
|
+
}
|
|
59
|
+
export interface FormattedExecutionResponse {
|
|
60
|
+
status: "pass" | "fail";
|
|
61
|
+
executionId: string;
|
|
62
|
+
results: FormattedScriptResult[];
|
|
63
|
+
total_scripts: number;
|
|
64
|
+
passed_scripts: number;
|
|
65
|
+
failed_scripts: number;
|
|
66
|
+
total_duration: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Format milliseconds → "X seconds" or "less than 1 second"
|
|
70
|
+
*/
|
|
71
|
+
export declare function formatDuration(ms: number): string;
|
|
72
|
+
/**
|
|
73
|
+
* Format ISO timestamp → "YYYY-MM-DD HH:MM:SS"
|
|
74
|
+
*/
|
|
75
|
+
export declare function formatDateTime(isoString: string): string;
|
|
76
|
+
export declare function formatStepResult(step: StepResult): FormattedStepResult;
|
|
77
|
+
export declare function formatScriptResult(script: ScriptResult): FormattedScriptResult;
|
|
78
|
+
export declare function formatExecutionResult(result: ExecutionResult): FormattedExecutionResponse;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* responseFormatter.ts
|
|
4
|
+
* ====================
|
|
5
|
+
* Converts internal framework types → the standard API response format
|
|
6
|
+
* that matches what the main application expects.
|
|
7
|
+
*
|
|
8
|
+
* Standard format example:
|
|
9
|
+
* {
|
|
10
|
+
* "status": "fail",
|
|
11
|
+
* "results": [
|
|
12
|
+
* {
|
|
13
|
+
* "test_script_uid": "...",
|
|
14
|
+
* "app_id": "...",
|
|
15
|
+
* "status": "fail",
|
|
16
|
+
* "results": [
|
|
17
|
+
* {
|
|
18
|
+
* "uid": null,
|
|
19
|
+
* "obj_uid": "...",
|
|
20
|
+
* "step_name": "...",
|
|
21
|
+
* "page_uid": null,
|
|
22
|
+
* "comments": "...",
|
|
23
|
+
* "screenshot": "base64...",
|
|
24
|
+
* "duration": "8 seconds",
|
|
25
|
+
* "start_time": "2026-04-04 20:25:34",
|
|
26
|
+
* "expected_result": "...",
|
|
27
|
+
* "status": "pass"
|
|
28
|
+
* }
|
|
29
|
+
* ],
|
|
30
|
+
* "duration": "42 seconds",
|
|
31
|
+
* "script_start_time": "2026-04-04 20:25:34"
|
|
32
|
+
* }
|
|
33
|
+
* ]
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.formatDuration = formatDuration;
|
|
38
|
+
exports.formatDateTime = formatDateTime;
|
|
39
|
+
exports.formatStepResult = formatStepResult;
|
|
40
|
+
exports.formatScriptResult = formatScriptResult;
|
|
41
|
+
exports.formatExecutionResult = formatExecutionResult;
|
|
42
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
43
|
+
/**
|
|
44
|
+
* Format milliseconds → "X seconds" or "less than 1 second"
|
|
45
|
+
*/
|
|
46
|
+
function formatDuration(ms) {
|
|
47
|
+
if (ms < 1000)
|
|
48
|
+
return "less than 1 second";
|
|
49
|
+
const secs = Math.round(ms / 1000);
|
|
50
|
+
return `${secs} second${secs !== 1 ? "s" : ""}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Format ISO timestamp → "YYYY-MM-DD HH:MM:SS"
|
|
54
|
+
*/
|
|
55
|
+
function formatDateTime(isoString) {
|
|
56
|
+
try {
|
|
57
|
+
const d = new Date(isoString);
|
|
58
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
59
|
+
return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
|
|
60
|
+
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return isoString;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Map internal StepStatus to lowercase API status
|
|
68
|
+
*/
|
|
69
|
+
function mapStepStatus(status) {
|
|
70
|
+
switch (status?.toUpperCase()) {
|
|
71
|
+
case "PASS": return "pass";
|
|
72
|
+
case "FAIL": return "fail";
|
|
73
|
+
case "SKIP":
|
|
74
|
+
case "RUNNING":
|
|
75
|
+
case "PENDING": return "skip";
|
|
76
|
+
default: return "skip";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ─── Formatters ──────────────────────────────────────────────────────────────
|
|
80
|
+
function formatStepResult(step) {
|
|
81
|
+
return {
|
|
82
|
+
uid: step.uid ?? null,
|
|
83
|
+
obj_uid: step.obj_uid ?? null,
|
|
84
|
+
step_name: step.step_name,
|
|
85
|
+
step_script: step.step_script,
|
|
86
|
+
page_uid: step.page_uid ?? null,
|
|
87
|
+
comments: step.skip_reason
|
|
88
|
+
? `Skipped — ${step.skip_reason}`
|
|
89
|
+
: (step.comments || ""),
|
|
90
|
+
screenshot: step.screenshot,
|
|
91
|
+
duration: formatDuration(step.duration_ms),
|
|
92
|
+
start_time: formatDateTime(step.start_time),
|
|
93
|
+
expected_result: step.expected_result || "",
|
|
94
|
+
status: mapStepStatus(step.status),
|
|
95
|
+
// comparison_code: step.comparison_code,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function formatScriptResult(script) {
|
|
99
|
+
const failedStep = script.step_results.find(s => s.comparison_code); // ← this line
|
|
100
|
+
return {
|
|
101
|
+
test_script_uid: script.test_script_uid,
|
|
102
|
+
app_id: script.app_id,
|
|
103
|
+
status: script.overall_status === "PASS" ? "pass" : "fail",
|
|
104
|
+
results: script.step_results.map(formatStepResult),
|
|
105
|
+
duration: formatDuration(script.duration_ms),
|
|
106
|
+
script_start_time: formatDateTime(script.start_time),
|
|
107
|
+
comparison_code: failedStep?.comparison_code,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function formatExecutionResult(result) {
|
|
111
|
+
const formattedResults = result.results.map(formatScriptResult);
|
|
112
|
+
const overallPass = formattedResults.every(r => r.status === "pass");
|
|
113
|
+
return {
|
|
114
|
+
status: overallPass ? "pass" : "fail",
|
|
115
|
+
executionId: result.executionId,
|
|
116
|
+
results: formattedResults,
|
|
117
|
+
total_scripts: result.total_scripts,
|
|
118
|
+
passed_scripts: result.passed,
|
|
119
|
+
failed_scripts: result.failed,
|
|
120
|
+
total_duration: formatDuration(result.duration_ms),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=responseFormatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responseFormatter.js","sourceRoot":"","sources":["../../src/utils/responseFormatter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Response } from "express";
|
|
2
|
+
import { SSEEvent, SSEEventType } from "../types/types";
|
|
3
|
+
/**
|
|
4
|
+
* SSEManager — v2 with Event Replay Buffer
|
|
5
|
+
* =========================================
|
|
6
|
+
* ROOT CAUSE FIX:
|
|
7
|
+
* The dashboard opens AFTER POST /api/run-test returns. Since execution is
|
|
8
|
+
* now non-blocking, it often completes before the browser even renders the
|
|
9
|
+
* dashboard page and the SSE connection is established. This means the
|
|
10
|
+
* dashboard misses all events and shows "Waiting for execution to begin..."
|
|
11
|
+
* even though the run is already done.
|
|
12
|
+
*
|
|
13
|
+
* FIX: Store every emitted event in a per-execution replay buffer.
|
|
14
|
+
* When a new SSE client connects, immediately replay all past events.
|
|
15
|
+
* This way late-connecting dashboards see the full execution history.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SSEManager {
|
|
18
|
+
private clients;
|
|
19
|
+
private eventBuffer;
|
|
20
|
+
private readonly MAX_BUFFER;
|
|
21
|
+
private readonly BUFFER_TTL;
|
|
22
|
+
addClient(executionId: string, res: Response): void;
|
|
23
|
+
emit(executionId: string, type: SSEEventType, data: any): void;
|
|
24
|
+
/** Broadcast to ALL connections (e.g. queue updates, global health) */
|
|
25
|
+
broadcast(type: SSEEventType, data: any): void;
|
|
26
|
+
/** Replay buffer for a given executionId (used by status endpoint) */
|
|
27
|
+
getBuffer(executionId: string): SSEEvent[];
|
|
28
|
+
hasClients(executionId: string): boolean;
|
|
29
|
+
private sendToOne;
|
|
30
|
+
private removeClient;
|
|
31
|
+
}
|
|
32
|
+
export declare const sseManager: SSEManager;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sseManager = exports.SSEManager = void 0;
|
|
4
|
+
const logger_1 = require("./logger");
|
|
5
|
+
const log = logger_1.Logger.create("SSEManager");
|
|
6
|
+
/**
|
|
7
|
+
* SSEManager — v2 with Event Replay Buffer
|
|
8
|
+
* =========================================
|
|
9
|
+
* ROOT CAUSE FIX:
|
|
10
|
+
* The dashboard opens AFTER POST /api/run-test returns. Since execution is
|
|
11
|
+
* now non-blocking, it often completes before the browser even renders the
|
|
12
|
+
* dashboard page and the SSE connection is established. This means the
|
|
13
|
+
* dashboard misses all events and shows "Waiting for execution to begin..."
|
|
14
|
+
* even though the run is already done.
|
|
15
|
+
*
|
|
16
|
+
* FIX: Store every emitted event in a per-execution replay buffer.
|
|
17
|
+
* When a new SSE client connects, immediately replay all past events.
|
|
18
|
+
* This way late-connecting dashboards see the full execution history.
|
|
19
|
+
*/
|
|
20
|
+
class SSEManager {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.clients = new Map();
|
|
23
|
+
// ✅ Replay buffer — stores all events per execution
|
|
24
|
+
this.eventBuffer = new Map();
|
|
25
|
+
// Max events to buffer per execution (prevents memory leak)
|
|
26
|
+
this.MAX_BUFFER = 500;
|
|
27
|
+
// How long to keep buffer after execution completes (ms)
|
|
28
|
+
this.BUFFER_TTL = 2 * 60 * 60 * 1000; // 2 hours
|
|
29
|
+
}
|
|
30
|
+
addClient(executionId, res) {
|
|
31
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
32
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
33
|
+
res.setHeader("Connection", "keep-alive");
|
|
34
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
35
|
+
res.flushHeaders();
|
|
36
|
+
if (!this.clients.has(executionId))
|
|
37
|
+
this.clients.set(executionId, new Set());
|
|
38
|
+
this.clients.get(executionId).add(res);
|
|
39
|
+
log.info(`SSE client connected: ${executionId}`);
|
|
40
|
+
// ✅ REPLAY: Send all past events immediately to this late-joining client
|
|
41
|
+
const pastEvents = this.eventBuffer.get(executionId) ?? [];
|
|
42
|
+
if (pastEvents.length > 0) {
|
|
43
|
+
log.info(`Replaying ${pastEvents.length} past events to late client: ${executionId}`);
|
|
44
|
+
pastEvents.forEach((ev) => this.sendToOne(res, ev));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Only send connect message if no replay (otherwise connect msg mid-replay is confusing)
|
|
48
|
+
this.sendToOne(res, {
|
|
49
|
+
type: "log", executionId,
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
data: { message: "Connected to execution stream" },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Heartbeat
|
|
55
|
+
const hb = setInterval(() => {
|
|
56
|
+
if (!res.writableEnded)
|
|
57
|
+
res.write(": heartbeat\n\n");
|
|
58
|
+
else
|
|
59
|
+
clearInterval(hb);
|
|
60
|
+
}, 20000);
|
|
61
|
+
res.on("close", () => {
|
|
62
|
+
clearInterval(hb);
|
|
63
|
+
this.removeClient(executionId, res);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
emit(executionId, type, data) {
|
|
67
|
+
const event = {
|
|
68
|
+
type, executionId,
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
data,
|
|
71
|
+
};
|
|
72
|
+
// ✅ Buffer event for replay
|
|
73
|
+
if (!this.eventBuffer.has(executionId)) {
|
|
74
|
+
this.eventBuffer.set(executionId, []);
|
|
75
|
+
}
|
|
76
|
+
const buf = this.eventBuffer.get(executionId);
|
|
77
|
+
if (buf.length < this.MAX_BUFFER)
|
|
78
|
+
buf.push(event);
|
|
79
|
+
// Broadcast to all currently connected clients
|
|
80
|
+
const clients = this.clients.get(executionId);
|
|
81
|
+
if (clients && clients.size > 0) {
|
|
82
|
+
clients.forEach((res) => this.sendToOne(res, event));
|
|
83
|
+
}
|
|
84
|
+
if (type === "execution_complete") {
|
|
85
|
+
// Clean up connections, keep buffer for replay
|
|
86
|
+
setTimeout(() => this.clients.delete(executionId), 5000);
|
|
87
|
+
// Clean up buffer after TTL
|
|
88
|
+
setTimeout(() => this.eventBuffer.delete(executionId), this.BUFFER_TTL);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Broadcast to ALL connections (e.g. queue updates, global health) */
|
|
92
|
+
broadcast(type, data) {
|
|
93
|
+
const event = {
|
|
94
|
+
type, executionId: "global",
|
|
95
|
+
timestamp: new Date().toISOString(),
|
|
96
|
+
data,
|
|
97
|
+
};
|
|
98
|
+
this.clients.forEach((clients) => clients.forEach((res) => this.sendToOne(res, event)));
|
|
99
|
+
}
|
|
100
|
+
/** Replay buffer for a given executionId (used by status endpoint) */
|
|
101
|
+
getBuffer(executionId) {
|
|
102
|
+
return this.eventBuffer.get(executionId) ?? [];
|
|
103
|
+
}
|
|
104
|
+
hasClients(executionId) {
|
|
105
|
+
return (this.clients.get(executionId)?.size ?? 0) > 0;
|
|
106
|
+
}
|
|
107
|
+
sendToOne(res, event) {
|
|
108
|
+
try {
|
|
109
|
+
if (!res.writableEnded)
|
|
110
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
111
|
+
}
|
|
112
|
+
catch { /* client gone */ }
|
|
113
|
+
}
|
|
114
|
+
removeClient(executionId, res) {
|
|
115
|
+
this.clients.get(executionId)?.delete(res);
|
|
116
|
+
if (this.clients.get(executionId)?.size === 0)
|
|
117
|
+
this.clients.delete(executionId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.SSEManager = SSEManager;
|
|
121
|
+
exports.sseManager = new SSEManager();
|
|
122
|
+
//# sourceMappingURL=sseManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sseManager.js","sourceRoot":"","sources":["../../src/utils/sseManager.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pw-automation-framework",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Enterprise Playwright Automation Framework — Parallel, Non-blocking, Self-installing",
|
|
5
|
+
"main": "dist/server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lexxit-automation-framework": "./bin/lexxit-automation-framework.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/server.js",
|
|
12
|
+
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
|
13
|
+
"install:browsers": "npx playwright install chromium firefox webkit",
|
|
14
|
+
"clean": "rimraf dist",
|
|
15
|
+
"postinstall": "node scripts/postinstall.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"cors": "^2.8.5",
|
|
19
|
+
"dotenv": "^16.3.1",
|
|
20
|
+
"express": "^4.18.2",
|
|
21
|
+
"open": "^9.1.0",
|
|
22
|
+
"p-limit": "^4.0.0",
|
|
23
|
+
"playwright": "^1.40.0",
|
|
24
|
+
"say": "^0.16.0",
|
|
25
|
+
"uuid": "^9.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/cors": "^2.8.17",
|
|
29
|
+
"@types/express": "^4.17.21",
|
|
30
|
+
"@types/node": "^20.10.0",
|
|
31
|
+
"@types/uuid": "^9.0.7",
|
|
32
|
+
"rimraf": "^5.0.5",
|
|
33
|
+
"ts-node-dev": "^2.0.0",
|
|
34
|
+
"typescript": "^5.3.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* postinstall.js
|
|
5
|
+
* Runs automatically after: npm install -g lexxit-automation-framework
|
|
6
|
+
* Installs Playwright Chromium silently.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { execSync } = require("child_process");
|
|
10
|
+
|
|
11
|
+
const COLORS = {
|
|
12
|
+
reset: "\x1b[0m",
|
|
13
|
+
green: "\x1b[32m",
|
|
14
|
+
yellow: "\x1b[33m",
|
|
15
|
+
red: "\x1b[31m",
|
|
16
|
+
cyan: "\x1b[36m",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function log(color, prefix, msg) {
|
|
20
|
+
console.log(`${color}${prefix}${COLORS.reset} ${msg}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log(`
|
|
24
|
+
${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
25
|
+
lexxit-automation-framework — Post Install Setup
|
|
26
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}
|
|
27
|
+
`);
|
|
28
|
+
|
|
29
|
+
// ─── INSTALL PLAYWRIGHT CHROMIUM ─────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
log(COLORS.cyan, "[1/1]", "Installing Playwright Chromium browser...");
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
execSync("npx playwright install chromium", {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
timeout: 120000, // 2 minutes
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
log(COLORS.green, "✔", "Playwright Chromium installed successfully.\n");
|
|
40
|
+
} catch (err) {
|
|
41
|
+
log(
|
|
42
|
+
COLORS.yellow,
|
|
43
|
+
"⚠",
|
|
44
|
+
"Playwright Chromium install failed. You can run it manually:"
|
|
45
|
+
);
|
|
46
|
+
console.log(" npx playwright install chromium\n");
|
|
47
|
+
// Don't exit(1) — don't block the npm install itself
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
51
|
+
console.log(` Setup complete. Run: lexxit-automation-framework start`);
|
|
52
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}\n`);
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import apiRoutes from "./routes/api.routes";
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
|
|
8
|
+
app.use(cors({ origin: "*" }));
|
|
9
|
+
app.use(express.json({ limit: "20mb" }));
|
|
10
|
+
app.use(express.urlencoded({ extended: true }));
|
|
11
|
+
app.use(express.static(path.join(__dirname, "public")));
|
|
12
|
+
|
|
13
|
+
// Live dashboard — serves the HTML shell; JS reads executionId from URL
|
|
14
|
+
app.get("/dashboard/:executionId", (_req, res) => {
|
|
15
|
+
res.sendFile(path.join(__dirname, "public", "dashboard.html"));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Multi-execution queue monitor
|
|
19
|
+
app.get("/queue-monitor", (_req, res) => {
|
|
20
|
+
res.sendFile(path.join(__dirname, "public", "queue-monitor.html"));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
app.use("/api", apiRoutes);
|
|
24
|
+
|
|
25
|
+
app.use((_req, res) => res.status(404).json({ error: "Not found" }));
|
|
26
|
+
|
|
27
|
+
export default app;
|