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,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executionQueue = exports.ExecutionQueue = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const TestExecutor_1 = require("../core/TestExecutor");
|
|
6
|
+
const sseManager_1 = require("../utils/sseManager");
|
|
7
|
+
const logger_1 = require("../utils/logger");
|
|
8
|
+
const log = logger_1.Logger.create("ExecutionQueue");
|
|
9
|
+
class ExecutionQueue {
|
|
10
|
+
constructor(maxConcurrent = 3) {
|
|
11
|
+
this.jobs = new Map();
|
|
12
|
+
this.running = new Set();
|
|
13
|
+
this.maxConcurrent = maxConcurrent;
|
|
14
|
+
}
|
|
15
|
+
// ─── Enqueue ───────────────────────────────────────────────────────────────
|
|
16
|
+
enqueue(payload) {
|
|
17
|
+
const executionId = (0, uuid_1.v4)();
|
|
18
|
+
const job = {
|
|
19
|
+
executionId,
|
|
20
|
+
status: "queued",
|
|
21
|
+
payload,
|
|
22
|
+
queuedAt: new Date().toISOString(),
|
|
23
|
+
cancelRequested: false,
|
|
24
|
+
progress: {
|
|
25
|
+
total_scripts: payload.scripts.length,
|
|
26
|
+
completed_scripts: 0,
|
|
27
|
+
total_steps: payload.scripts.reduce((s, sc) => s + sc.steps.length, 0),
|
|
28
|
+
completed_steps: 0,
|
|
29
|
+
passed_steps: 0,
|
|
30
|
+
failed_steps: 0,
|
|
31
|
+
skipped_steps: 0,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
this.jobs.set(executionId, job);
|
|
35
|
+
log.info(`Queued: ${executionId} | scripts=${payload.scripts.length}`);
|
|
36
|
+
this.broadcastQueueUpdate();
|
|
37
|
+
this.processNext();
|
|
38
|
+
return executionId;
|
|
39
|
+
}
|
|
40
|
+
// ─── Cancel ────────────────────────────────────────────────────────────────
|
|
41
|
+
cancel(executionId) {
|
|
42
|
+
const job = this.jobs.get(executionId);
|
|
43
|
+
if (!job)
|
|
44
|
+
return { success: false, message: "Execution not found" };
|
|
45
|
+
if (job.status === "done" || job.status === "failed" || job.status === "cancelled") {
|
|
46
|
+
return { success: false, message: `Cannot cancel — already ${job.status}` };
|
|
47
|
+
}
|
|
48
|
+
job.cancelRequested = true;
|
|
49
|
+
if (job.status === "queued") {
|
|
50
|
+
// Not started yet — cancel immediately
|
|
51
|
+
job.status = "cancelled";
|
|
52
|
+
job.cancelledAt = new Date().toISOString();
|
|
53
|
+
sseManager_1.sseManager.emit(executionId, "execution_cancelled", {
|
|
54
|
+
message: "Execution cancelled before it started",
|
|
55
|
+
executionId,
|
|
56
|
+
});
|
|
57
|
+
this.broadcastQueueUpdate();
|
|
58
|
+
log.info(`Cancelled (queued): ${executionId}`);
|
|
59
|
+
return { success: true, message: "Execution cancelled" };
|
|
60
|
+
}
|
|
61
|
+
// Running — signal the executor to stop after current step
|
|
62
|
+
log.info(`Cancel requested (running): ${executionId}`);
|
|
63
|
+
sseManager_1.sseManager.emit(executionId, "log", {
|
|
64
|
+
message: "⏹ Cancellation requested — stopping after current step...",
|
|
65
|
+
});
|
|
66
|
+
return { success: true, message: "Cancellation requested — will stop after current step" };
|
|
67
|
+
}
|
|
68
|
+
// ─── Cancel All ────────────────────────────────────────────────────────────
|
|
69
|
+
cancelAll() {
|
|
70
|
+
let count = 0;
|
|
71
|
+
this.jobs.forEach((job) => {
|
|
72
|
+
if (job.status === "queued" || job.status === "running") {
|
|
73
|
+
this.cancel(job.executionId);
|
|
74
|
+
count++;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return { cancelled: count };
|
|
78
|
+
}
|
|
79
|
+
// ─── Process ───────────────────────────────────────────────────────────────
|
|
80
|
+
processNext() {
|
|
81
|
+
if (this.running.size >= this.maxConcurrent)
|
|
82
|
+
return;
|
|
83
|
+
const next = Array.from(this.jobs.values()).find(j => j.status === "queued");
|
|
84
|
+
if (!next)
|
|
85
|
+
return;
|
|
86
|
+
this.running.add(next.executionId);
|
|
87
|
+
next.status = "running";
|
|
88
|
+
next.startedAt = new Date().toISOString();
|
|
89
|
+
this.broadcastQueueUpdate();
|
|
90
|
+
this.runJob(next).catch((err) => {
|
|
91
|
+
log.error(`Job crashed: ${next.executionId} — ${err.message}`);
|
|
92
|
+
next.status = "failed";
|
|
93
|
+
next.error = err.message;
|
|
94
|
+
this.finalize(next.executionId);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async runJob(job) {
|
|
98
|
+
log.info(`Starting: ${job.executionId}`);
|
|
99
|
+
try {
|
|
100
|
+
const result = await TestExecutor_1.TestExecutor.run(job.payload, job.executionId,
|
|
101
|
+
// Progress callback
|
|
102
|
+
(update) => {
|
|
103
|
+
Object.assign(job.progress, update);
|
|
104
|
+
this.broadcastQueueUpdate();
|
|
105
|
+
},
|
|
106
|
+
// Cancel check — TestExecutor polls this
|
|
107
|
+
() => job.cancelRequested);
|
|
108
|
+
if (job.cancelRequested) {
|
|
109
|
+
job.status = "cancelled";
|
|
110
|
+
job.cancelledAt = new Date().toISOString();
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
job.result = result;
|
|
114
|
+
job.status = result.overall_status === "PASS" ? "done" : "failed";
|
|
115
|
+
}
|
|
116
|
+
job.completedAt = new Date().toISOString();
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
job.status = "failed";
|
|
120
|
+
job.error = error.message;
|
|
121
|
+
job.completedAt = new Date().toISOString();
|
|
122
|
+
sseManager_1.sseManager.emit(job.executionId, "error", { message: error.message });
|
|
123
|
+
}
|
|
124
|
+
this.finalize(job.executionId);
|
|
125
|
+
}
|
|
126
|
+
finalize(executionId) {
|
|
127
|
+
this.running.delete(executionId);
|
|
128
|
+
this.broadcastQueueUpdate();
|
|
129
|
+
log.info(`Finalized: ${executionId} | running=${this.running.size}/${this.maxConcurrent}`);
|
|
130
|
+
// Keep jobs in memory 1 hour for replay
|
|
131
|
+
setTimeout(() => this.jobs.delete(executionId), 60 * 60 * 1000);
|
|
132
|
+
this.processNext();
|
|
133
|
+
}
|
|
134
|
+
// ─── Query ─────────────────────────────────────────────────────────────────
|
|
135
|
+
getJob(executionId) {
|
|
136
|
+
return this.jobs.get(executionId);
|
|
137
|
+
}
|
|
138
|
+
isCancelRequested(executionId) {
|
|
139
|
+
return this.jobs.get(executionId)?.cancelRequested ?? false;
|
|
140
|
+
}
|
|
141
|
+
getAllJobs() {
|
|
142
|
+
return Array.from(this.jobs.values())
|
|
143
|
+
.sort((a, b) => new Date(b.queuedAt).getTime() - new Date(a.queuedAt).getTime());
|
|
144
|
+
}
|
|
145
|
+
getQueueStats() {
|
|
146
|
+
const jobs = this.getAllJobs();
|
|
147
|
+
return {
|
|
148
|
+
total: jobs.length,
|
|
149
|
+
queued: jobs.filter(j => j.status === "queued").length,
|
|
150
|
+
running: jobs.filter(j => j.status === "running").length,
|
|
151
|
+
done: jobs.filter(j => j.status === "done").length,
|
|
152
|
+
failed: jobs.filter(j => j.status === "failed").length,
|
|
153
|
+
cancelled: jobs.filter(j => j.status === "cancelled").length,
|
|
154
|
+
maxConcurrent: this.maxConcurrent,
|
|
155
|
+
jobs: jobs.map(j => ({
|
|
156
|
+
executionId: j.executionId,
|
|
157
|
+
status: j.status,
|
|
158
|
+
queuedAt: j.queuedAt,
|
|
159
|
+
startedAt: j.startedAt,
|
|
160
|
+
completedAt: j.completedAt,
|
|
161
|
+
cancelledAt: j.cancelledAt,
|
|
162
|
+
progress: j.progress,
|
|
163
|
+
error: j.error,
|
|
164
|
+
overall_status: j.result?.overall_status,
|
|
165
|
+
cancelRequested: j.cancelRequested,
|
|
166
|
+
})),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
broadcastQueueUpdate() {
|
|
170
|
+
sseManager_1.sseManager.broadcast("queue_update", this.getQueueStats());
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.ExecutionQueue = ExecutionQueue;
|
|
174
|
+
exports.executionQueue = new ExecutionQueue(3);
|
|
175
|
+
//# sourceMappingURL=ExecutionQueue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExecutionQueue.js","sourceRoot":"","sources":["../../src/queue/ExecutionQueue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const controller_1 = require("../controllers/controller");
|
|
5
|
+
const router = (0, express_1.Router)();
|
|
6
|
+
router.post("/run-test", controller_1.ApiController.runTest);
|
|
7
|
+
router.get("/stream/:executionId", controller_1.ApiController.stream);
|
|
8
|
+
router.get("/result/:executionId", controller_1.ApiController.getResult); // ← full formatted result
|
|
9
|
+
router.get("/status/:executionId", controller_1.ApiController.getStatus); // ← lightweight status
|
|
10
|
+
router.delete("/cancel/:executionId", controller_1.ApiController.cancelExecution);
|
|
11
|
+
router.delete("/cancel-all", controller_1.ApiController.cancelAll);
|
|
12
|
+
router.get("/queue", controller_1.ApiController.getQueue);
|
|
13
|
+
router.get("/actions", controller_1.ApiController.listActions);
|
|
14
|
+
router.get("/health", controller_1.ApiController.health);
|
|
15
|
+
exports.default = router;
|
|
16
|
+
//# sourceMappingURL=api.routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.routes.js","sourceRoot":"","sources":["../../src/routes/api.routes.ts"],"names":[],"mappings":";;AAAA,qCAAiC;AACjC,0DAA0D;AAE1D,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAExB,MAAM,CAAC,IAAI,CAAG,WAAW,EAAe,0BAAa,CAAC,OAAO,CAAC,CAAC;AAC/D,MAAM,CAAC,GAAG,CAAI,sBAAsB,EAAI,0BAAa,CAAC,MAAM,CAAC,CAAC;AAC9D,MAAM,CAAC,GAAG,CAAI,sBAAsB,EAAI,0BAAa,CAAC,SAAS,CAAC,CAAC,CAAI,0BAA0B;AAC/F,MAAM,CAAC,GAAG,CAAI,sBAAsB,EAAI,0BAAa,CAAC,SAAS,CAAC,CAAC,CAAI,uBAAuB;AAC5F,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAI,0BAAa,CAAC,eAAe,CAAC,CAAC;AACvE,MAAM,CAAC,MAAM,CAAC,aAAa,EAAa,0BAAa,CAAC,SAAS,CAAC,CAAC;AACjE,MAAM,CAAC,GAAG,CAAI,QAAQ,EAAkB,0BAAa,CAAC,QAAQ,CAAC,CAAC;AAChE,MAAM,CAAC,GAAG,CAAI,UAAU,EAAgB,0BAAa,CAAC,WAAW,CAAC,CAAC;AACnE,MAAM,CAAC,GAAG,CAAI,SAAS,EAAiB,0BAAa,CAAC,MAAM,CAAC,CAAC;AAE9D,kBAAe,MAAM,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "dotenv/config";
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
require("dotenv/config");
|
|
7
|
+
const app_1 = __importDefault(require("./app"));
|
|
8
|
+
const PORT = parseInt(process.env.PORT || "3000", 10);
|
|
9
|
+
const server = app_1.default.listen(PORT, () => {
|
|
10
|
+
console.log("\n" + "═".repeat(60));
|
|
11
|
+
console.log(" 🎭 Playwright Automation Framework v2.0");
|
|
12
|
+
console.log("═".repeat(60));
|
|
13
|
+
console.log(` API : http://localhost:${PORT}/api`);
|
|
14
|
+
console.log(` Queue Monitor: http://localhost:${PORT}/queue-monitor`);
|
|
15
|
+
console.log(` Health : http://localhost:${PORT}/api/health`);
|
|
16
|
+
console.log(` Actions : http://localhost:${PORT}/api/actions`);
|
|
17
|
+
console.log("═".repeat(60) + "\n");
|
|
18
|
+
});
|
|
19
|
+
server.keepAliveTimeout = 65000;
|
|
20
|
+
server.headersTimeout = 66000;
|
|
21
|
+
const shutdown = (signal) => {
|
|
22
|
+
console.log(`\n[SHUTDOWN] ${signal} received`);
|
|
23
|
+
server.close(() => process.exit(0));
|
|
24
|
+
setTimeout(() => process.exit(1), 10000);
|
|
25
|
+
};
|
|
26
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
27
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
28
|
+
process.on("uncaughtException", (e) => { console.error("[FATAL]", e); shutdown("uncaughtException"); });
|
|
29
|
+
process.on("unhandledRejection", (r) => console.error("[UNHANDLED]", r));
|
|
30
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;;;;AAAA,yBAAuB;AACvB,gDAAwB;AAExB,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AAEtD,MAAM,MAAM,GAAG,aAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACnC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5B,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,MAAM,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,gBAAgB,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,aAAa,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,cAAc,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,gBAAgB,GAAG,KAAK,CAAC;AAChC,MAAM,CAAC,cAAc,GAAK,KAAK,CAAC;AAEhC,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAE,EAAE;IAClC,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,WAAW,CAAC,CAAC;IAC/C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC,CAAC;AAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAa,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;AAC5D,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAc,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC3D,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAG,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACzG,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export type ScreenshotMode = "always" | "on_failure" | "never";
|
|
2
|
+
export type StepStatus = "PASS" | "FAIL" | "SKIP" | "RUNNING" | "PENDING";
|
|
3
|
+
export type ScriptStatus = "PASS" | "FAIL" | "RUNNING" | "PENDING";
|
|
4
|
+
export type QueueStatus = "queued" | "running" | "done" | "failed" | "cancelled";
|
|
5
|
+
export type SSEEventType = "execution_start" | "script_start" | "step_start" | "step_complete" | "script_complete" | "execution_complete" | "execution_cancelled" | "queue_update" | "log" | "error" | "health";
|
|
6
|
+
export interface ITestData {
|
|
7
|
+
set(key: string, value: unknown): void;
|
|
8
|
+
get(key: string): unknown;
|
|
9
|
+
has(key: string): boolean;
|
|
10
|
+
resolve(value: unknown): unknown;
|
|
11
|
+
getAll(): Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
export interface TestCaseConfig {
|
|
14
|
+
testData: ITestData;
|
|
15
|
+
screenshot_mode: ScreenshotMode;
|
|
16
|
+
record_video: boolean;
|
|
17
|
+
headless: boolean;
|
|
18
|
+
executionId: string;
|
|
19
|
+
timeout: number;
|
|
20
|
+
slowMo?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface RequestPayload {
|
|
23
|
+
scripts: Script[];
|
|
24
|
+
stop_on_failure?: boolean;
|
|
25
|
+
parallel?: boolean;
|
|
26
|
+
max_parallel?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface Script {
|
|
29
|
+
test_script_uid: string;
|
|
30
|
+
test_case_name?: string;
|
|
31
|
+
app_id?: string;
|
|
32
|
+
headless?: boolean;
|
|
33
|
+
screenshot_mode?: ScreenshotMode;
|
|
34
|
+
record_video?: boolean;
|
|
35
|
+
stop_on_failure?: boolean;
|
|
36
|
+
steps: Step[];
|
|
37
|
+
browser?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface Step {
|
|
40
|
+
uid?: string;
|
|
41
|
+
step_name: string;
|
|
42
|
+
step_script: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
obj_uid?: string;
|
|
45
|
+
page_uid?: string | null;
|
|
46
|
+
value?: string;
|
|
47
|
+
continue_on_failure?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface StepResult {
|
|
50
|
+
uid?: string;
|
|
51
|
+
obj_uid?: string;
|
|
52
|
+
step_name: string;
|
|
53
|
+
step_script?: string;
|
|
54
|
+
status: StepStatus;
|
|
55
|
+
page_uid?: string | null;
|
|
56
|
+
comments: string;
|
|
57
|
+
screenshot?: string;
|
|
58
|
+
duration_ms: number;
|
|
59
|
+
start_time: string;
|
|
60
|
+
expected_result?: string;
|
|
61
|
+
skip_reason?: string;
|
|
62
|
+
label?: string;
|
|
63
|
+
value?: string;
|
|
64
|
+
comparison_code?: any;
|
|
65
|
+
}
|
|
66
|
+
export interface ScriptResult {
|
|
67
|
+
test_script_uid: string;
|
|
68
|
+
test_case_name: string;
|
|
69
|
+
step_results: StepResult[];
|
|
70
|
+
overall_status: ScriptStatus;
|
|
71
|
+
app_id?: string;
|
|
72
|
+
duration_ms: number;
|
|
73
|
+
start_time: string;
|
|
74
|
+
browser?: string;
|
|
75
|
+
passed_steps: number;
|
|
76
|
+
failed_steps: number;
|
|
77
|
+
skipped_steps: number;
|
|
78
|
+
total_steps: number;
|
|
79
|
+
}
|
|
80
|
+
export interface ExecutionResult {
|
|
81
|
+
executionId: string;
|
|
82
|
+
overall_status: "PASS" | "FAIL" | "CANCELLED";
|
|
83
|
+
total_scripts: number;
|
|
84
|
+
passed: number;
|
|
85
|
+
failed: number;
|
|
86
|
+
duration_ms: number;
|
|
87
|
+
results: ScriptResult[];
|
|
88
|
+
}
|
|
89
|
+
export interface QueueJob {
|
|
90
|
+
executionId: string;
|
|
91
|
+
status: QueueStatus;
|
|
92
|
+
payload: RequestPayload;
|
|
93
|
+
queuedAt: string;
|
|
94
|
+
startedAt?: string;
|
|
95
|
+
completedAt?: string;
|
|
96
|
+
cancelledAt?: string;
|
|
97
|
+
result?: ExecutionResult;
|
|
98
|
+
error?: string;
|
|
99
|
+
cancelRequested: boolean;
|
|
100
|
+
progress: {
|
|
101
|
+
total_scripts: number;
|
|
102
|
+
completed_scripts: number;
|
|
103
|
+
total_steps: number;
|
|
104
|
+
completed_steps: number;
|
|
105
|
+
passed_steps: number;
|
|
106
|
+
failed_steps: number;
|
|
107
|
+
skipped_steps: number;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export interface LogEntry {
|
|
111
|
+
level: LogLevel;
|
|
112
|
+
message: string;
|
|
113
|
+
timestamp?: string;
|
|
114
|
+
}
|
|
115
|
+
export interface ActionResponse {
|
|
116
|
+
status: "Pass" | "Fail";
|
|
117
|
+
comments: string;
|
|
118
|
+
screenshot?: string;
|
|
119
|
+
data?: any;
|
|
120
|
+
}
|
|
121
|
+
export interface ParsedStep {
|
|
122
|
+
action: string;
|
|
123
|
+
args: string[];
|
|
124
|
+
}
|
|
125
|
+
export type ActionHandler = (args: string[]) => Promise<ActionResponse | undefined>;
|
|
126
|
+
export interface IActionHandler {
|
|
127
|
+
getActions(): Record<string, ActionHandler>;
|
|
128
|
+
}
|
|
129
|
+
export interface SSEEvent {
|
|
130
|
+
type: SSEEventType;
|
|
131
|
+
executionId: string;
|
|
132
|
+
timestamp: string;
|
|
133
|
+
data: any;
|
|
134
|
+
}
|
|
135
|
+
export type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":";AAAA,gFAAgF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Page } from "playwright";
|
|
2
|
+
/**
|
|
3
|
+
* elementHighlight.ts
|
|
4
|
+
* ===================
|
|
5
|
+
* Injects a visual highlight into the browser window BEFORE the framework
|
|
6
|
+
* interacts with an element. This lets the user watching the automation
|
|
7
|
+
* clearly see which field is being targeted.
|
|
8
|
+
*
|
|
9
|
+
* Visual effects:
|
|
10
|
+
* • Glowing border (blue for input/click, orange for verify/assertion)
|
|
11
|
+
* • Ripple pulse animation
|
|
12
|
+
* • Small floating label showing the step action + value
|
|
13
|
+
* • Auto-removes after the interaction completes (or on timeout)
|
|
14
|
+
*
|
|
15
|
+
* Usage (in handlers):
|
|
16
|
+
* await highlightElement(this.page, locatorString, 'input', 'Ravi');
|
|
17
|
+
* // ... do the actual interaction ...
|
|
18
|
+
* await removeHighlight(this.page);
|
|
19
|
+
*/
|
|
20
|
+
type HighlightType = "input" | "click" | "verify" | "select" | "checkbox";
|
|
21
|
+
/**
|
|
22
|
+
* Highlights an element in the browser before interacting with it.
|
|
23
|
+
*
|
|
24
|
+
* @param page Playwright Page
|
|
25
|
+
* @param locator CSS/XPath locator string (already built)
|
|
26
|
+
* @param type Visual type: input | click | verify | select | checkbox
|
|
27
|
+
* @param labelText Short text shown in the floating label (e.g. the value being entered)
|
|
28
|
+
*/
|
|
29
|
+
export declare function highlightElement(page: Page, locator: string, type?: HighlightType, labelText?: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Removes the visual highlight from the page.
|
|
32
|
+
* Called after the interaction completes (or on failure).
|
|
33
|
+
*/
|
|
34
|
+
export declare function removeHighlight(page: Page): Promise<void>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.highlightElement = highlightElement;
|
|
4
|
+
exports.removeHighlight = removeHighlight;
|
|
5
|
+
const COLORS = {
|
|
6
|
+
input: { border: "#3b82f6", bg: "rgba(59,130,246,0.08)", label: "#3b82f6" },
|
|
7
|
+
click: { border: "#22c55e", bg: "rgba(34,197,94,0.08)", label: "#22c55e" },
|
|
8
|
+
verify: { border: "#a855f7", bg: "rgba(168,85,247,0.08)", label: "#a855f7" },
|
|
9
|
+
select: { border: "#f59e0b", bg: "rgba(245,158,11,0.08)", label: "#f59e0b" },
|
|
10
|
+
checkbox: { border: "#ec4899", bg: "rgba(236,72,153,0.08)", label: "#ec4899" },
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Highlights an element in the browser before interacting with it.
|
|
14
|
+
*
|
|
15
|
+
* @param page Playwright Page
|
|
16
|
+
* @param locator CSS/XPath locator string (already built)
|
|
17
|
+
* @param type Visual type: input | click | verify | select | checkbox
|
|
18
|
+
* @param labelText Short text shown in the floating label (e.g. the value being entered)
|
|
19
|
+
*/
|
|
20
|
+
async function highlightElement(page, locator, type = "input", labelText = "") {
|
|
21
|
+
const c = COLORS[type];
|
|
22
|
+
try {
|
|
23
|
+
await page.evaluate(({ locator, border, bg, labelColor, labelText }) => {
|
|
24
|
+
// ── Find element ──────────────────────────────────────────────────────
|
|
25
|
+
let el = null;
|
|
26
|
+
if (locator.startsWith("//") || locator.startsWith("(//") || locator.startsWith("./")) {
|
|
27
|
+
// XPath
|
|
28
|
+
const result = document.evaluate(locator, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
29
|
+
el = result.singleNodeValue;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// CSS
|
|
33
|
+
try {
|
|
34
|
+
el = document.querySelector(locator);
|
|
35
|
+
}
|
|
36
|
+
catch { /* bad selector */ }
|
|
37
|
+
}
|
|
38
|
+
if (!el || !(el instanceof HTMLElement))
|
|
39
|
+
return;
|
|
40
|
+
// ── Remove any previous highlight ─────────────────────────────────────
|
|
41
|
+
document.querySelectorAll("[data-pw-highlight]").forEach(n => {
|
|
42
|
+
const orig = n.dataset.pwOrigStyle ?? "";
|
|
43
|
+
n.style.cssText = orig;
|
|
44
|
+
delete n.dataset.pwHighlight;
|
|
45
|
+
delete n.dataset.pwOrigStyle;
|
|
46
|
+
});
|
|
47
|
+
document.querySelectorAll("#pw-overlay,#pw-label,#pw-ripple-style").forEach(n => n.remove());
|
|
48
|
+
// ── Store original style ───────────────────────────────────────────────
|
|
49
|
+
el.dataset.pwHighlight = "1";
|
|
50
|
+
el.dataset.pwOrigStyle = el.style.cssText;
|
|
51
|
+
// ── Apply highlight styles ─────────────────────────────────────────────
|
|
52
|
+
el.style.outline = `2px solid ${border}`;
|
|
53
|
+
el.style.outlineOffset = "2px";
|
|
54
|
+
el.style.boxShadow = `0 0 0 4px ${bg}, 0 0 16px ${border}55`;
|
|
55
|
+
el.style.transition = "outline .15s, box-shadow .15s";
|
|
56
|
+
el.style.zIndex = "9999";
|
|
57
|
+
// ── Scroll element into view (smooth) ─────────────────────────────────
|
|
58
|
+
el.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" });
|
|
59
|
+
// ── Ripple pulse animation ─────────────────────────────────────────────
|
|
60
|
+
const style = document.createElement("style");
|
|
61
|
+
style.id = "pw-ripple-style";
|
|
62
|
+
style.textContent = `
|
|
63
|
+
@keyframes pwRipple {
|
|
64
|
+
0% { box-shadow: 0 0 0 0 ${border}66, 0 0 0 0 ${border}33; }
|
|
65
|
+
50% { box-shadow: 0 0 0 6px ${border}33, 0 0 0 12px ${border}11; }
|
|
66
|
+
100% { box-shadow: 0 0 0 0 ${border}00, 0 0 0 0 ${border}00; }
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
document.head.appendChild(style);
|
|
70
|
+
el.style.animation = "pwRipple .7s ease-out";
|
|
71
|
+
// ── Floating label ─────────────────────────────────────────────────────
|
|
72
|
+
if (labelText) {
|
|
73
|
+
const rect = el.getBoundingClientRect();
|
|
74
|
+
const label = document.createElement("div");
|
|
75
|
+
label.id = "pw-label";
|
|
76
|
+
const scrollX = window.scrollX || document.documentElement.scrollLeft;
|
|
77
|
+
const scrollY = window.scrollY || document.documentElement.scrollTop;
|
|
78
|
+
// Position above the element; clamp to viewport
|
|
79
|
+
const top = Math.max(scrollY + rect.top - 36, scrollY + 4);
|
|
80
|
+
const left = Math.min(Math.max(scrollX + rect.left, scrollX + 4), scrollX + document.documentElement.clientWidth - 240);
|
|
81
|
+
Object.assign(label.style, {
|
|
82
|
+
position: "absolute",
|
|
83
|
+
top: `${top}px`,
|
|
84
|
+
left: `${left}px`,
|
|
85
|
+
zIndex: "2147483647",
|
|
86
|
+
background: "#0a0d14",
|
|
87
|
+
color: labelColor,
|
|
88
|
+
border: `1px solid ${border}`,
|
|
89
|
+
borderRadius: "6px",
|
|
90
|
+
padding: "3px 10px",
|
|
91
|
+
fontSize: "12px",
|
|
92
|
+
fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif",
|
|
93
|
+
fontWeight: "600",
|
|
94
|
+
pointerEvents: "none",
|
|
95
|
+
whiteSpace: "nowrap",
|
|
96
|
+
maxWidth: "260px",
|
|
97
|
+
overflow: "hidden",
|
|
98
|
+
textOverflow: "ellipsis",
|
|
99
|
+
boxShadow: "0 4px 14px rgba(0,0,0,.5)",
|
|
100
|
+
animation: "pwFadeIn .15s ease",
|
|
101
|
+
});
|
|
102
|
+
const styleTag = document.createElement("style");
|
|
103
|
+
styleTag.textContent = "@keyframes pwFadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}";
|
|
104
|
+
document.head.appendChild(styleTag);
|
|
105
|
+
label.textContent = `⚡ ${labelText.length > 40 ? labelText.slice(0, 37) + "…" : labelText}`;
|
|
106
|
+
document.body.appendChild(label);
|
|
107
|
+
}
|
|
108
|
+
}, { locator, border: c.border, bg: c.bg, labelColor: c.label, labelText });
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Highlight is purely cosmetic — never throw
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Removes the visual highlight from the page.
|
|
116
|
+
* Called after the interaction completes (or on failure).
|
|
117
|
+
*/
|
|
118
|
+
async function removeHighlight(page) {
|
|
119
|
+
try {
|
|
120
|
+
await page.evaluate(() => {
|
|
121
|
+
document.querySelectorAll("[data-pw-highlight]").forEach(n => {
|
|
122
|
+
const el = n;
|
|
123
|
+
el.style.cssText = el.dataset.pwOrigStyle ?? "";
|
|
124
|
+
el.style.animation = "";
|
|
125
|
+
delete el.dataset.pwHighlight;
|
|
126
|
+
delete el.dataset.pwOrigStyle;
|
|
127
|
+
});
|
|
128
|
+
document.querySelectorAll("#pw-overlay,#pw-label,#pw-ripple-style").forEach(n => n.remove());
|
|
129
|
+
document.querySelectorAll("style[textContent*='pwFadeIn'],style[textContent*='pwRipple']").forEach(n => n.remove());
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Cosmetic — never throw
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=elementHighlight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elementHighlight.js","sourceRoot":"","sources":["../../src/utils/elementHighlight.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildLocatorString = buildLocatorString;
|
|
4
|
+
/**
|
|
5
|
+
* buildLocatorString
|
|
6
|
+
* ==================
|
|
7
|
+
* Converts strategy + value → Playwright selector string.
|
|
8
|
+
* Supports all standard and Playwright-native strategies.
|
|
9
|
+
*/
|
|
10
|
+
function buildLocatorString(strategy, value) {
|
|
11
|
+
switch (strategy.toLowerCase().replace(/[_\s-]/g, "")) {
|
|
12
|
+
case "xpath":
|
|
13
|
+
return `xpath=${value}`;
|
|
14
|
+
case "css":
|
|
15
|
+
case "cssselector":
|
|
16
|
+
return value;
|
|
17
|
+
case "id":
|
|
18
|
+
return `#${value}`;
|
|
19
|
+
case "name":
|
|
20
|
+
return `[name="${value}"]`;
|
|
21
|
+
case "placeholder":
|
|
22
|
+
return `[placeholder="${value}"]`;
|
|
23
|
+
case "text":
|
|
24
|
+
case "linktext":
|
|
25
|
+
return `text=${value}`;
|
|
26
|
+
case "partiallinktext":
|
|
27
|
+
case "partialtext":
|
|
28
|
+
return `text=${value}`;
|
|
29
|
+
case "classname":
|
|
30
|
+
case "class":
|
|
31
|
+
return `.${value}`;
|
|
32
|
+
case "tagname":
|
|
33
|
+
case "tag":
|
|
34
|
+
return value;
|
|
35
|
+
case "testid":
|
|
36
|
+
case "datatestid":
|
|
37
|
+
return `[data-testid="${value}"]`;
|
|
38
|
+
case "label":
|
|
39
|
+
return `label=${value}`;
|
|
40
|
+
case "arialabel":
|
|
41
|
+
return `[aria-label="${value}"]`;
|
|
42
|
+
case "role":
|
|
43
|
+
return `[role="${value}"]`;
|
|
44
|
+
case "title":
|
|
45
|
+
return `[title="${value}"]`;
|
|
46
|
+
case "value":
|
|
47
|
+
return `[value="${value}"]`;
|
|
48
|
+
default:
|
|
49
|
+
console.warn(`[WARN] Unknown strategy "${strategy}", treating as CSS`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=locatorHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locatorHelper.js","sourceRoot":"","sources":["../../src/utils/locatorHelper.ts"],"names":[],"mappings":";;AAMA,gDA0CC;AAhDD;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,QAAgB,EAAE,KAAa;IAChE,QAAQ,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC;QACtD,KAAK,OAAO;YACV,OAAO,SAAS,KAAK,EAAE,CAAC;QAC1B,KAAK,KAAK,CAAC;QACX,KAAK,aAAa;YAChB,OAAO,KAAK,CAAC;QACf,KAAK,IAAI;YACP,OAAO,IAAI,KAAK,EAAE,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,UAAU,KAAK,IAAI,CAAC;QAC7B,KAAK,aAAa;YAChB,OAAO,iBAAiB,KAAK,IAAI,CAAC;QACpC,KAAK,MAAM,CAAC;QACZ,KAAK,UAAU;YACb,OAAO,QAAQ,KAAK,EAAE,CAAC;QACzB,KAAK,iBAAiB,CAAC;QACvB,KAAK,aAAa;YAChB,OAAO,QAAQ,KAAK,EAAE,CAAC;QACzB,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO;YACV,OAAO,IAAI,KAAK,EAAE,CAAC;QACrB,KAAK,SAAS,CAAC;QACf,KAAK,KAAK;YACR,OAAO,KAAK,CAAC;QACf,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY;YACf,OAAO,iBAAiB,KAAK,IAAI,CAAC;QACpC,KAAK,OAAO;YACV,OAAO,SAAS,KAAK,EAAE,CAAC;QAC1B,KAAK,WAAW;YACd,OAAO,gBAAgB,KAAK,IAAI,CAAC;QACnC,KAAK,MAAM;YACT,OAAO,UAAU,KAAK,IAAI,CAAC;QAC7B,KAAK,OAAO;YACV,OAAO,WAAW,KAAK,IAAI,CAAC;QAC9B,KAAK,OAAO;YACV,OAAO,WAAW,KAAK,IAAI,CAAC;QAC9B;YACE,OAAO,CAAC,IAAI,CAAC,4BAA4B,QAAQ,oBAAoB,CAAC,CAAC;YACvE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC"}
|