@ticktockbent/charlotte 0.6.0 → 0.6.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/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/dist/browser/browser-manager.d.ts +3 -0
- package/dist/browser/browser-manager.d.ts.map +1 -1
- package/dist/browser/browser-manager.js +13 -2
- package/dist/browser/browser-manager.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +41 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -6
- package/dist/index.js.map +1 -1
- package/dist/renderer/interactive-extractor.d.ts.map +1 -1
- package/dist/renderer/interactive-extractor.js +10 -2
- package/dist/renderer/interactive-extractor.js.map +1 -1
- package/dist/renderer/layout-extractor.d.ts.map +1 -1
- package/dist/renderer/layout-extractor.js +10 -2
- package/dist/renderer/layout-extractor.js.map +1 -1
- package/dist/renderer/renderer-pipeline.d.ts +2 -2
- package/dist/renderer/renderer-pipeline.d.ts.map +1 -1
- package/dist/renderer/renderer-pipeline.js +5 -3
- package/dist/renderer/renderer-pipeline.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -4
- package/dist/server.js.map +1 -1
- package/dist/state/snapshot-store.d.ts +4 -0
- package/dist/state/snapshot-store.d.ts.map +1 -1
- package/dist/state/snapshot-store.js +15 -3
- package/dist/state/snapshot-store.js.map +1 -1
- package/dist/tools/dev-mode.d.ts.map +1 -1
- package/dist/tools/dev-mode.js +10 -10
- package/dist/tools/dev-mode.js.map +1 -1
- package/dist/tools/dialog.js +5 -5
- package/dist/tools/dialog.js.map +1 -1
- package/dist/tools/evaluate.d.ts +1 -1
- package/dist/tools/evaluate.d.ts.map +1 -1
- package/dist/tools/evaluate.js +3 -2
- package/dist/tools/evaluate.js.map +1 -1
- package/dist/tools/interaction-helpers.d.ts +55 -0
- package/dist/tools/interaction-helpers.d.ts.map +1 -0
- package/dist/tools/interaction-helpers.js +312 -0
- package/dist/tools/interaction-helpers.js.map +1 -0
- package/dist/tools/interaction.d.ts +1 -17
- package/dist/tools/interaction.d.ts.map +1 -1
- package/dist/tools/interaction.js +120 -500
- package/dist/tools/interaction.js.map +1 -1
- package/dist/tools/meta-tool.d.ts +2 -2
- package/dist/tools/meta-tool.js +3 -3
- package/dist/tools/monitoring.js +9 -9
- package/dist/tools/monitoring.js.map +1 -1
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tools/navigation.js +36 -76
- package/dist/tools/navigation.js.map +1 -1
- package/dist/tools/observation.d.ts.map +1 -1
- package/dist/tools/observation.js +94 -98
- package/dist/tools/observation.js.map +1 -1
- package/dist/tools/session.d.ts.map +1 -1
- package/dist/tools/session.js +135 -184
- package/dist/tools/session.js.map +1 -1
- package/dist/tools/tool-groups.d.ts +8 -8
- package/dist/tools/tool-groups.d.ts.map +1 -1
- package/dist/tools/tool-groups.js +113 -142
- package/dist/tools/tool-groups.js.map +1 -1
- package/dist/tools/tool-helpers.d.ts +18 -0
- package/dist/tools/tool-helpers.d.ts.map +1 -1
- package/dist/tools/tool-helpers.js +69 -2
- package/dist/tools/tool-helpers.js.map +1 -1
- package/dist/tools/wait-for.d.ts +5 -0
- package/dist/tools/wait-for.d.ts.map +1 -0
- package/dist/tools/wait-for.js +169 -0
- package/dist/tools/wait-for.js.map +1 -0
- package/dist/types/config.d.ts +16 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +15 -0
- package/dist/types/config.js.map +1 -1
- package/dist/utils/wait.js +11 -5
- package/dist/utils/wait.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,9 +5,13 @@ const MAX_DEPTH = 500;
|
|
|
5
5
|
* Ring-buffer store for page snapshots. Snapshots have monotonically
|
|
6
6
|
* increasing integer IDs and are evicted oldest-first when the buffer
|
|
7
7
|
* reaches its configured depth.
|
|
8
|
+
*
|
|
9
|
+
* A secondary Map index provides O(1) lookup by snapshot ID alongside
|
|
10
|
+
* the ring buffer used for ordered access (latest, previous, oldest).
|
|
8
11
|
*/
|
|
9
12
|
export class SnapshotStore {
|
|
10
13
|
buffer = [];
|
|
14
|
+
index = new Map();
|
|
11
15
|
nextSnapshotId = 1;
|
|
12
16
|
maxDepth;
|
|
13
17
|
constructor(depth = DEFAULT_DEPTH) {
|
|
@@ -29,9 +33,13 @@ export class SnapshotStore {
|
|
|
29
33
|
representation,
|
|
30
34
|
};
|
|
31
35
|
this.buffer.push(snapshot);
|
|
36
|
+
this.index.set(snapshotId, snapshot);
|
|
32
37
|
// Evict oldest if over capacity
|
|
33
38
|
while (this.buffer.length > this.maxDepth) {
|
|
34
|
-
this.buffer.shift();
|
|
39
|
+
const evicted = this.buffer.shift();
|
|
40
|
+
if (evicted !== undefined) {
|
|
41
|
+
this.index.delete(evicted.id);
|
|
42
|
+
}
|
|
35
43
|
}
|
|
36
44
|
return snapshotId;
|
|
37
45
|
}
|
|
@@ -39,7 +47,7 @@ export class SnapshotStore {
|
|
|
39
47
|
* Retrieve a snapshot by its ID. Returns null if evicted or never existed.
|
|
40
48
|
*/
|
|
41
49
|
get(snapshotId) {
|
|
42
|
-
return this.
|
|
50
|
+
return this.index.get(snapshotId) ?? null;
|
|
43
51
|
}
|
|
44
52
|
/**
|
|
45
53
|
* Get the most recent snapshot, or null if the store is empty.
|
|
@@ -84,7 +92,10 @@ export class SnapshotStore {
|
|
|
84
92
|
setDepth(newDepth) {
|
|
85
93
|
this.maxDepth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, newDepth));
|
|
86
94
|
while (this.buffer.length > this.maxDepth) {
|
|
87
|
-
this.buffer.shift();
|
|
95
|
+
const evicted = this.buffer.shift();
|
|
96
|
+
if (evicted !== undefined) {
|
|
97
|
+
this.index.delete(evicted.id);
|
|
98
|
+
}
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
101
|
/**
|
|
@@ -92,6 +103,7 @@ export class SnapshotStore {
|
|
|
92
103
|
*/
|
|
93
104
|
clear() {
|
|
94
105
|
this.buffer = [];
|
|
106
|
+
this.index.clear();
|
|
95
107
|
this.nextSnapshotId = 1;
|
|
96
108
|
}
|
|
97
109
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-store.js","sourceRoot":"","sources":["../../src/state/snapshot-store.ts"],"names":[],"mappings":"AAGA,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,SAAS,GAAG,CAAC,CAAC;AACpB,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB
|
|
1
|
+
{"version":3,"file":"snapshot-store.js","sourceRoot":"","sources":["../../src/state/snapshot-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-mode.d.ts","sourceRoot":"","sources":["../../src/tools/dev-mode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAIzF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"dev-mode.d.ts","sourceRoot":"","sources":["../../src/tools/dev-mode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAIzF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAS1D,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,gBAAgB,GACrB,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAqMhC"}
|
package/dist/tools/dev-mode.js
CHANGED
|
@@ -4,11 +4,11 @@ import * as path from "node:path";
|
|
|
4
4
|
import { CharlotteError, CharlotteErrorCode } from "../types/errors.js";
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
6
|
import { Auditor } from "../dev/auditor.js";
|
|
7
|
-
import { renderActivePage, renderAfterAction, formatPageResponse, handleToolError, } from "./tool-helpers.js";
|
|
7
|
+
import { ensureReady, renderActivePage, renderAfterAction, formatPageResponse, handleToolError, } from "./tool-helpers.js";
|
|
8
8
|
export function registerDevModeTools(server, deps) {
|
|
9
9
|
const tools = {};
|
|
10
|
-
// ───
|
|
11
|
-
tools["
|
|
10
|
+
// ─── charlotte_dev_serve ───
|
|
11
|
+
tools["charlotte_dev_serve"] = server.registerTool("charlotte_dev_serve", {
|
|
12
12
|
description: "Serve a local directory as a static website and optionally watch for file changes. Navigates to the served URL and returns the page representation. File changes trigger automatic reloads and surface as reload_event on the next tool response.",
|
|
13
13
|
inputSchema: {
|
|
14
14
|
path: z.string().describe("Local directory to serve"),
|
|
@@ -17,7 +17,7 @@ export function registerDevModeTools(server, deps) {
|
|
|
17
17
|
},
|
|
18
18
|
}, async ({ path: directoryPath, port, watch }) => {
|
|
19
19
|
try {
|
|
20
|
-
await deps
|
|
20
|
+
await ensureReady(deps);
|
|
21
21
|
if (!deps.devModeState) {
|
|
22
22
|
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Dev mode is not available.", "DevModeState was not initialized.");
|
|
23
23
|
}
|
|
@@ -72,8 +72,8 @@ export function registerDevModeTools(server, deps) {
|
|
|
72
72
|
return handleToolError(error);
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
|
-
// ───
|
|
76
|
-
tools["
|
|
75
|
+
// ─── charlotte_dev_inject ───
|
|
76
|
+
tools["charlotte_dev_inject"] = server.registerTool("charlotte_dev_inject", {
|
|
77
77
|
description: "Inject CSS or JavaScript into the current page for testing modifications without editing files. Returns the page representation with a delta showing changes.",
|
|
78
78
|
inputSchema: {
|
|
79
79
|
css: z.string().optional().describe("CSS to inject into the page"),
|
|
@@ -81,7 +81,7 @@ export function registerDevModeTools(server, deps) {
|
|
|
81
81
|
},
|
|
82
82
|
}, async ({ css, js }) => {
|
|
83
83
|
try {
|
|
84
|
-
await deps
|
|
84
|
+
await ensureReady(deps);
|
|
85
85
|
if (!css && !js) {
|
|
86
86
|
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "At least one of 'css' or 'js' must be provided.", "Provide CSS to inject, JS to execute, or both.");
|
|
87
87
|
}
|
|
@@ -107,8 +107,8 @@ export function registerDevModeTools(server, deps) {
|
|
|
107
107
|
return handleToolError(error);
|
|
108
108
|
}
|
|
109
109
|
});
|
|
110
|
-
// ───
|
|
111
|
-
tools["
|
|
110
|
+
// ─── charlotte_dev_audit ───
|
|
111
|
+
tools["charlotte_dev_audit"] = server.registerTool("charlotte_dev_audit", {
|
|
112
112
|
description: "Run accessibility and quality audits on the current page. Returns findings with severity levels and actionable recommendations.",
|
|
113
113
|
inputSchema: {
|
|
114
114
|
checks: z
|
|
@@ -118,7 +118,7 @@ export function registerDevModeTools(server, deps) {
|
|
|
118
118
|
},
|
|
119
119
|
}, async ({ checks }) => {
|
|
120
120
|
try {
|
|
121
|
-
await deps
|
|
121
|
+
await ensureReady(deps);
|
|
122
122
|
const page = deps.pageManager.getActivePage();
|
|
123
123
|
const session = await page.createCDPSession();
|
|
124
124
|
const auditor = new Auditor();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-mode.js","sourceRoot":"","sources":["../../src/tools/dev-mode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAsB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,UAAU,oBAAoB,CAClC,MAAiB,EACjB,IAAsB;IAEtB,MAAM,KAAK,GAAmC,EAAE,CAAC;IAEjD,8BAA8B;IAC9B,KAAK,CAAC,qBAAqB,CAAC,GAAG,MAAM,CAAC,YAAY,CAChD,qBAAqB,EACrB;QACE,WAAW,EACT,mPAAmP;QACrP,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;YACrD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;YAC/E,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;SACtF;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;QAC7C,IAAI,CAAC;YACH,MAAM,
|
|
1
|
+
{"version":3,"file":"dev-mode.js","sourceRoot":"","sources":["../../src/tools/dev-mode.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/dialog.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CharlotteError, CharlotteErrorCode } from "../types/errors.js";
|
|
3
3
|
import { logger } from "../utils/logger.js";
|
|
4
|
-
import { renderAfterAction, stripEmptyFields, handleToolError } from "./tool-helpers.js";
|
|
4
|
+
import { ensureReady, renderAfterAction, stripEmptyFields, handleToolError } from "./tool-helpers.js";
|
|
5
5
|
export function registerDialogTools(server, deps) {
|
|
6
6
|
const tools = {};
|
|
7
|
-
// ───
|
|
8
|
-
tools["
|
|
7
|
+
// ─── charlotte_dialog ───
|
|
8
|
+
tools["charlotte_dialog"] = server.registerTool("charlotte_dialog", {
|
|
9
9
|
description: "Handle a pending JavaScript dialog (alert, confirm, prompt, beforeunload). Accept or dismiss the dialog. Returns page representation after the dialog is resolved.",
|
|
10
10
|
inputSchema: {
|
|
11
11
|
accept: z.boolean().describe("true to accept/OK the dialog, false to dismiss/Cancel"),
|
|
@@ -16,11 +16,11 @@ export function registerDialogTools(server, deps) {
|
|
|
16
16
|
},
|
|
17
17
|
}, async ({ accept, prompt_text }) => {
|
|
18
18
|
try {
|
|
19
|
-
await deps
|
|
19
|
+
await ensureReady(deps);
|
|
20
20
|
const pendingDialog = deps.pageManager.getPendingDialog();
|
|
21
21
|
const pendingDialogInfo = deps.pageManager.getPendingDialogInfo();
|
|
22
22
|
if (!pendingDialog || !pendingDialogInfo) {
|
|
23
|
-
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "No pending dialog to handle.", "Call
|
|
23
|
+
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "No pending dialog to handle.", "Call charlotte_observe to check page state. Dialogs appear as pending_dialog in the response.");
|
|
24
24
|
}
|
|
25
25
|
// Capture dialog info before clearing for the response metadata
|
|
26
26
|
const dialogHandled = {
|
package/dist/tools/dialog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","sourceRoot":"","sources":["../../src/tools/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"dialog.js","sourceRoot":"","sources":["../../src/tools/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEtG,MAAM,UAAU,mBAAmB,CACjC,MAAiB,EACjB,IAAsB;IAEtB,MAAM,KAAK,GAAmC,EAAE,CAAC;IAEjD,2BAA2B;IAC3B,KAAK,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC,YAAY,CAC7C,kBAAkB,EAClB;QACE,WAAW,EACT,oKAAoK;QACtK,WAAW,EAAE;YACX,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;YACrF,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,sFAAsF,CACvF;SACJ;KACF,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE;QAChC,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;YAExB,MAAM,aAAa,GAAG,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC;YAC1D,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC;YAElE,IAAI,CAAC,aAAa,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACzC,MAAM,IAAI,cAAc,CACtB,kBAAkB,CAAC,aAAa,EAChC,8BAA8B,EAC9B,+FAA+F,CAChG,CAAC;YACJ,CAAC;YAED,gEAAgE;YAChE,MAAM,aAAa,GAAG;gBACpB,IAAI,EAAE,iBAAiB,CAAC,IAAI;gBAC5B,OAAO,EAAE,iBAAiB,CAAC,OAAO;gBAClC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW;aAC1C,CAAC;YAEF,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE;gBAC7B,IAAI,EAAE,iBAAiB,CAAC,IAAI;gBAC5B,MAAM;gBACN,WAAW;aACZ,CAAC,CAAC;YAEH,+BAA+B;YAC/B,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAC1C,CAAC;iBAAM,CAAC;gBACN,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC;YAChC,CAAC;YAED,iCAAiC;YACjC,IAAI,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC;YAEtC,iDAAiD;YACjD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;YAExD,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAErD,8CAA8C;YAC9C,MAAM,eAAe,GAAG;gBACtB,cAAc,EAAE,aAAa;gBAC7B,IAAI,EAAE,gBAAgB,CAAC,cAAc,CAAC;aACvC,CAAC;YAEF,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC;qBACtC;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/tools/evaluate.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { BrowserManager } from "../browser/browser-manager.js";
|
|
|
3
3
|
import type { PageManager } from "../browser/page-manager.js";
|
|
4
4
|
export interface EvaluateDeps {
|
|
5
5
|
browserManager: BrowserManager;
|
|
6
|
-
pageManager
|
|
6
|
+
pageManager: PageManager;
|
|
7
7
|
getActivePage: () => import("puppeteer").Page;
|
|
8
8
|
}
|
|
9
9
|
export declare function registerEvaluateTools(server: McpServer, deps: EvaluateDeps): Record<string, RegisteredTool>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/tools/evaluate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/tools/evaluate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAK9D,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,aAAa,EAAE,MAAM,OAAO,WAAW,EAAE,IAAI,CAAC;CAC/C;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAqHhC"}
|
package/dist/tools/evaluate.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CharlotteError, CharlotteErrorCode } from "../types/errors.js";
|
|
3
3
|
import { logger } from "../utils/logger.js";
|
|
4
|
+
import { ensureReady } from "./tool-helpers.js";
|
|
4
5
|
export function registerEvaluateTools(server, deps) {
|
|
5
6
|
const tools = {};
|
|
6
|
-
tools["
|
|
7
|
+
tools["charlotte_evaluate"] = server.registerTool("charlotte_evaluate", {
|
|
7
8
|
description: "Execute JavaScript in page context. Supports single expressions and multi-statement code. Returns the completion value of the last expression-statement.",
|
|
8
9
|
inputSchema: {
|
|
9
10
|
expression: z.string().describe("JS expression or multi-statement code to evaluate"),
|
|
@@ -14,7 +15,7 @@ export function registerEvaluateTools(server, deps) {
|
|
|
14
15
|
.describe("If the expression returns a Promise, await it before returning (default: true)"),
|
|
15
16
|
},
|
|
16
17
|
}, async ({ expression, timeout, await_promise }) => {
|
|
17
|
-
await deps
|
|
18
|
+
await ensureReady(deps);
|
|
18
19
|
const page = deps.getActivePage();
|
|
19
20
|
const evaluationTimeout = timeout ?? 5000;
|
|
20
21
|
const shouldAwaitPromise = await_promise ?? true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/tools/evaluate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/tools/evaluate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Page, KeyInput } from "puppeteer";
|
|
2
|
+
/** Maps short modifier names to Puppeteer KeyInput values. */
|
|
3
|
+
export declare const MODIFIER_KEY_MAP: Record<string, KeyInput>;
|
|
4
|
+
/**
|
|
5
|
+
* Click an element by backend node ID using CDP to get coordinates, then click at those coords.
|
|
6
|
+
*/
|
|
7
|
+
export declare function clickElementByBackendNodeId(page: Page, backendNodeId: number, clickType?: "left" | "right" | "double", modifiers?: Array<"ctrl" | "shift" | "alt" | "meta">): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Wait for any navigation triggered by an action, or fall back to a brief settle pause.
|
|
10
|
+
*
|
|
11
|
+
* Listens for the `framenavigated` CDP event to detect if a click caused navigation.
|
|
12
|
+
* If navigation is detected within `detectionWindowMs`, waits for the page load event
|
|
13
|
+
* (up to `loadTimeoutMs`). If no navigation fires, returns after `settleMs`.
|
|
14
|
+
*
|
|
15
|
+
* Also races against dialog events — if the action triggers a JavaScript dialog
|
|
16
|
+
* (alert, confirm, prompt, beforeunload), the action promise will block indefinitely.
|
|
17
|
+
* This function detects that and returns early so the caller can surface `pending_dialog`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function waitForPossibleNavigation(page: Page, action: () => Promise<void>, { detectionWindowMs, loadTimeoutMs, settleMs }?: {
|
|
20
|
+
detectionWindowMs?: number | undefined;
|
|
21
|
+
loadTimeoutMs?: number | undefined;
|
|
22
|
+
settleMs?: number | undefined;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Focus an element by backend node ID using CDP.
|
|
26
|
+
*/
|
|
27
|
+
export declare function focusElementByBackendNodeId(page: Page, backendNodeId: number): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Hover over an element by backend node ID.
|
|
30
|
+
*/
|
|
31
|
+
export declare function hoverElementByBackendNodeId(page: Page, backendNodeId: number): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Drag one element to another using mouse primitives.
|
|
34
|
+
* Sequence: move to source → mousedown → move to target → mouseup
|
|
35
|
+
* Includes intermediate move steps and delays to ensure drag events fire reliably.
|
|
36
|
+
*/
|
|
37
|
+
export declare function dragElementToElement(page: Page, sourceBackendNodeId: number, targetBackendNodeId: number): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Type text into an input element. Uses CDP to focus, optionally clears, then types via keyboard.
|
|
40
|
+
*/
|
|
41
|
+
export declare function typeIntoElement(page: Page, backendNodeId: number, text: string, clearFirst: boolean, pressEnter: boolean, characterDelay?: number): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Select a value in a <select> element using CDP to set the value and dispatch change events.
|
|
44
|
+
*/
|
|
45
|
+
export declare function selectOptionByBackendNodeId(page: Page, backendNodeId: number, value: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Submit a form by backend node ID — calls form.submit() via CDP.
|
|
48
|
+
*/
|
|
49
|
+
export declare function submitFormByBackendNodeId(page: Page, backendNodeId: number): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Set files on a file input element using CDP DOM.setFileInputFiles.
|
|
52
|
+
* Validates that the target element is actually an <input type="file">.
|
|
53
|
+
*/
|
|
54
|
+
export declare function setFileInputFiles(page: Page, backendNodeId: number, filePaths: string[]): Promise<void>;
|
|
55
|
+
//# sourceMappingURL=interaction-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-helpers.d.ts","sourceRoot":"","sources":["../../src/tools/interaction-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAIhD,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAKrD,CAAC;AAEF;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,MAAM,GAAG,OAAO,GAAG,QAAiB,EAC/C,SAAS,GAAE,KAAK,CAAC,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAM,GACvD,OAAO,CAAC,IAAI,CAAC,CAiDf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAC3B,EAAE,iBAAuB,EAAE,aAAqB,EAAE,QAAa,EAAE;;;;CAAK,GACrE,OAAO,CAAC,IAAI,CAAC,CAoEf;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAmCD;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,mBAAmB,EAAE,MAAM,EAC3B,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,OAAO,EACnB,UAAU,EAAE,OAAO,EACnB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAoCf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,IAAI,CAAC,CAuBf"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { CharlotteError, CharlotteErrorCode } from "../types/errors.js";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
/** Maps short modifier names to Puppeteer KeyInput values. */
|
|
4
|
+
export const MODIFIER_KEY_MAP = {
|
|
5
|
+
ctrl: "Control",
|
|
6
|
+
shift: "Shift",
|
|
7
|
+
alt: "Alt",
|
|
8
|
+
meta: "Meta",
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Click an element by backend node ID using CDP to get coordinates, then click at those coords.
|
|
12
|
+
*/
|
|
13
|
+
export async function clickElementByBackendNodeId(page, backendNodeId, clickType = "left", modifiers = []) {
|
|
14
|
+
// Get the element's box model to find clickable coordinates
|
|
15
|
+
const cdpSession = await page.createCDPSession();
|
|
16
|
+
try {
|
|
17
|
+
// First, scroll the element into view
|
|
18
|
+
await cdpSession.send("DOM.scrollIntoViewIfNeeded", { backendNodeId });
|
|
19
|
+
// Get box model for coordinates
|
|
20
|
+
const { model } = await cdpSession.send("DOM.getBoxModel", {
|
|
21
|
+
backendNodeId,
|
|
22
|
+
});
|
|
23
|
+
if (!model) {
|
|
24
|
+
throw new CharlotteError(CharlotteErrorCode.ELEMENT_NOT_FOUND, "Element has no visible box model — it may be hidden or zero-sized.", "Call charlotte_observe to check the element's state.");
|
|
25
|
+
}
|
|
26
|
+
// content quad: [x1,y1, x2,y2, x3,y3, x4,y4]
|
|
27
|
+
const contentQuad = model.content;
|
|
28
|
+
const centerX = (contentQuad[0] + contentQuad[2] + contentQuad[4] + contentQuad[6]) / 4;
|
|
29
|
+
const centerY = (contentQuad[1] + contentQuad[3] + contentQuad[5] + contentQuad[7]) / 4;
|
|
30
|
+
// Hold down modifier keys before the click
|
|
31
|
+
for (const modifier of modifiers) {
|
|
32
|
+
const modifierKey = MODIFIER_KEY_MAP[modifier];
|
|
33
|
+
await page.keyboard.down(modifierKey);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
if (clickType === "right") {
|
|
37
|
+
await page.mouse.click(centerX, centerY, { button: "right" });
|
|
38
|
+
}
|
|
39
|
+
else if (clickType === "double") {
|
|
40
|
+
await page.mouse.click(centerX, centerY, { clickCount: 2 });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
await page.mouse.click(centerX, centerY);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
// Release modifier keys in reverse order (always release even if click fails)
|
|
48
|
+
for (const modifier of [...modifiers].reverse()) {
|
|
49
|
+
const modifierKey = MODIFIER_KEY_MAP[modifier];
|
|
50
|
+
await page.keyboard.up(modifierKey);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await cdpSession.detach();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wait for any navigation triggered by an action, or fall back to a brief settle pause.
|
|
60
|
+
*
|
|
61
|
+
* Listens for the `framenavigated` CDP event to detect if a click caused navigation.
|
|
62
|
+
* If navigation is detected within `detectionWindowMs`, waits for the page load event
|
|
63
|
+
* (up to `loadTimeoutMs`). If no navigation fires, returns after `settleMs`.
|
|
64
|
+
*
|
|
65
|
+
* Also races against dialog events — if the action triggers a JavaScript dialog
|
|
66
|
+
* (alert, confirm, prompt, beforeunload), the action promise will block indefinitely.
|
|
67
|
+
* This function detects that and returns early so the caller can surface `pending_dialog`.
|
|
68
|
+
*/
|
|
69
|
+
export async function waitForPossibleNavigation(page, action, { detectionWindowMs = 500, loadTimeoutMs = 10000, settleMs = 50 } = {}) {
|
|
70
|
+
let navigationDetected = false;
|
|
71
|
+
let dialogDetected = false;
|
|
72
|
+
// Listen for navigation start via page event (fires on any navigation)
|
|
73
|
+
const navigationStartPromise = new Promise((resolve) => {
|
|
74
|
+
const handler = () => {
|
|
75
|
+
navigationDetected = true;
|
|
76
|
+
page.off("framenavigated", handler);
|
|
77
|
+
resolve();
|
|
78
|
+
};
|
|
79
|
+
page.on("framenavigated", handler);
|
|
80
|
+
// Clean up listener if no navigation fires within detection window
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
page.off("framenavigated", handler);
|
|
83
|
+
resolve();
|
|
84
|
+
}, detectionWindowMs);
|
|
85
|
+
});
|
|
86
|
+
// Listen for dialog (blocks the action from completing)
|
|
87
|
+
const dialogPromise = new Promise((resolve) => {
|
|
88
|
+
const handler = () => {
|
|
89
|
+
dialogDetected = true;
|
|
90
|
+
page.off("dialog", handler);
|
|
91
|
+
resolve();
|
|
92
|
+
};
|
|
93
|
+
page.on("dialog", handler);
|
|
94
|
+
// Clean up on timeout — if no dialog fires, we don't need this listener
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
page.off("dialog", handler);
|
|
97
|
+
resolve();
|
|
98
|
+
}, detectionWindowMs);
|
|
99
|
+
});
|
|
100
|
+
// Race: action vs dialog
|
|
101
|
+
const actionPromise = action();
|
|
102
|
+
await Promise.race([
|
|
103
|
+
actionPromise.then(() => "action"),
|
|
104
|
+
dialogPromise.then(() => "dialog"),
|
|
105
|
+
]);
|
|
106
|
+
if (dialogDetected) {
|
|
107
|
+
// Dialog is blocking the action. Don't await actionPromise — it will
|
|
108
|
+
// resolve later when the dialog is handled via charlotte_dialog.
|
|
109
|
+
// Guard against unhandled rejection from the fire-and-forget promise.
|
|
110
|
+
actionPromise.catch(() => {
|
|
111
|
+
logger.debug("Post-dialog action promise rejected (expected)");
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Action completed normally — check for navigation
|
|
116
|
+
await navigationStartPromise;
|
|
117
|
+
if (navigationDetected) {
|
|
118
|
+
// Navigation occurred — wait for the page to finish loading
|
|
119
|
+
try {
|
|
120
|
+
await page.waitForNavigation({ waitUntil: "load", timeout: loadTimeoutMs });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Page may have already finished loading before we called waitForNavigation,
|
|
124
|
+
// or the load timed out. Either way, render what we have.
|
|
125
|
+
logger.debug("Post-navigation load wait ended (page may already be loaded)");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// No navigation — brief settle for in-page DOM updates
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Focus an element by backend node ID using CDP.
|
|
135
|
+
*/
|
|
136
|
+
export async function focusElementByBackendNodeId(page, backendNodeId) {
|
|
137
|
+
const cdpSession = await page.createCDPSession();
|
|
138
|
+
try {
|
|
139
|
+
await cdpSession.send("DOM.focus", { backendNodeId });
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
await cdpSession.detach();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Hover over an element by backend node ID.
|
|
147
|
+
*/
|
|
148
|
+
export async function hoverElementByBackendNodeId(page, backendNodeId) {
|
|
149
|
+
const cdpSession = await page.createCDPSession();
|
|
150
|
+
try {
|
|
151
|
+
await cdpSession.send("DOM.scrollIntoViewIfNeeded", { backendNodeId });
|
|
152
|
+
const { model } = await cdpSession.send("DOM.getBoxModel", {
|
|
153
|
+
backendNodeId,
|
|
154
|
+
});
|
|
155
|
+
if (!model) {
|
|
156
|
+
throw new CharlotteError(CharlotteErrorCode.ELEMENT_NOT_FOUND, "Element has no visible box model for hover.");
|
|
157
|
+
}
|
|
158
|
+
const contentQuad = model.content;
|
|
159
|
+
const centerX = (contentQuad[0] + contentQuad[2] + contentQuad[4] + contentQuad[6]) / 4;
|
|
160
|
+
const centerY = (contentQuad[1] + contentQuad[3] + contentQuad[5] + contentQuad[7]) / 4;
|
|
161
|
+
await page.mouse.move(centerX, centerY);
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
await cdpSession.detach();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get the center coordinates of an element by backend node ID.
|
|
169
|
+
* Scrolls the element into view first.
|
|
170
|
+
*/
|
|
171
|
+
async function getElementCenter(page, backendNodeId) {
|
|
172
|
+
const cdpSession = await page.createCDPSession();
|
|
173
|
+
try {
|
|
174
|
+
await cdpSession.send("DOM.scrollIntoViewIfNeeded", { backendNodeId });
|
|
175
|
+
const { model } = await cdpSession.send("DOM.getBoxModel", {
|
|
176
|
+
backendNodeId,
|
|
177
|
+
});
|
|
178
|
+
if (!model) {
|
|
179
|
+
throw new CharlotteError(CharlotteErrorCode.ELEMENT_NOT_FOUND, "Element has no visible box model — it may be hidden or zero-sized.", "Call charlotte_observe to check the element's state.");
|
|
180
|
+
}
|
|
181
|
+
const contentQuad = model.content;
|
|
182
|
+
return {
|
|
183
|
+
x: (contentQuad[0] + contentQuad[2] + contentQuad[4] + contentQuad[6]) / 4,
|
|
184
|
+
y: (contentQuad[1] + contentQuad[3] + contentQuad[5] + contentQuad[7]) / 4,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
await cdpSession.detach();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Drag one element to another using mouse primitives.
|
|
193
|
+
* Sequence: move to source → mousedown → move to target → mouseup
|
|
194
|
+
* Includes intermediate move steps and delays to ensure drag events fire reliably.
|
|
195
|
+
*/
|
|
196
|
+
export async function dragElementToElement(page, sourceBackendNodeId, targetBackendNodeId) {
|
|
197
|
+
const sourceCenter = await getElementCenter(page, sourceBackendNodeId);
|
|
198
|
+
const targetCenter = await getElementCenter(page, targetBackendNodeId);
|
|
199
|
+
// Move to source and press down
|
|
200
|
+
await page.mouse.move(sourceCenter.x, sourceCenter.y);
|
|
201
|
+
await page.mouse.down();
|
|
202
|
+
// Intermediate move to trigger dragstart (some browsers need movement to begin a drag)
|
|
203
|
+
await page.mouse.move(sourceCenter.x + (targetCenter.x - sourceCenter.x) * 0.25, sourceCenter.y + (targetCenter.y - sourceCenter.y) * 0.25, { steps: 5 });
|
|
204
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
205
|
+
// Move to target
|
|
206
|
+
await page.mouse.move(targetCenter.x, targetCenter.y, { steps: 10 });
|
|
207
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
208
|
+
// Release
|
|
209
|
+
await page.mouse.up();
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Type text into an input element. Uses CDP to focus, optionally clears, then types via keyboard.
|
|
213
|
+
*/
|
|
214
|
+
export async function typeIntoElement(page, backendNodeId, text, clearFirst, pressEnter, characterDelay) {
|
|
215
|
+
// Focus the element
|
|
216
|
+
await focusElementByBackendNodeId(page, backendNodeId);
|
|
217
|
+
if (clearFirst) {
|
|
218
|
+
// Select all text then delete — works cross-platform
|
|
219
|
+
await page.keyboard.down("Control");
|
|
220
|
+
await page.keyboard.press("a");
|
|
221
|
+
await page.keyboard.up("Control");
|
|
222
|
+
await page.keyboard.press("Backspace");
|
|
223
|
+
}
|
|
224
|
+
// Type the text — with optional per-character delay for sites with
|
|
225
|
+
// key-by-key event handlers (autocomplete, search-as-you-type, etc.)
|
|
226
|
+
await page.keyboard.type(text, characterDelay ? { delay: characterDelay } : undefined);
|
|
227
|
+
if (pressEnter) {
|
|
228
|
+
await page.keyboard.press("Enter");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Select a value in a <select> element using CDP to set the value and dispatch change events.
|
|
233
|
+
*/
|
|
234
|
+
export async function selectOptionByBackendNodeId(page, backendNodeId, value) {
|
|
235
|
+
const cdpSession = await page.createCDPSession();
|
|
236
|
+
try {
|
|
237
|
+
// Resolve the node to get a remote object reference
|
|
238
|
+
const { object } = await cdpSession.send("DOM.resolveNode", {
|
|
239
|
+
backendNodeId,
|
|
240
|
+
});
|
|
241
|
+
if (!object?.objectId) {
|
|
242
|
+
throw new CharlotteError(CharlotteErrorCode.ELEMENT_NOT_FOUND, "Could not resolve select element.");
|
|
243
|
+
}
|
|
244
|
+
// Use Runtime.callFunctionOn to set the value and fire events
|
|
245
|
+
await cdpSession.send("Runtime.callFunctionOn", {
|
|
246
|
+
objectId: object.objectId,
|
|
247
|
+
functionDeclaration: `function(targetValue) {
|
|
248
|
+
const options = Array.from(this.options);
|
|
249
|
+
const matchByValue = options.find(o => o.value === targetValue);
|
|
250
|
+
const matchByText = options.find(o => o.textContent.trim() === targetValue);
|
|
251
|
+
const match = matchByValue || matchByText;
|
|
252
|
+
if (match) {
|
|
253
|
+
this.value = match.value;
|
|
254
|
+
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
255
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
256
|
+
} else {
|
|
257
|
+
throw new Error('Option "' + targetValue + '" not found');
|
|
258
|
+
}
|
|
259
|
+
}`,
|
|
260
|
+
arguments: [{ value }],
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
await cdpSession.detach();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Submit a form by backend node ID — calls form.submit() via CDP.
|
|
269
|
+
*/
|
|
270
|
+
export async function submitFormByBackendNodeId(page, backendNodeId) {
|
|
271
|
+
const cdpSession = await page.createCDPSession();
|
|
272
|
+
try {
|
|
273
|
+
const { object } = await cdpSession.send("DOM.resolveNode", {
|
|
274
|
+
backendNodeId,
|
|
275
|
+
});
|
|
276
|
+
if (!object?.objectId) {
|
|
277
|
+
throw new CharlotteError(CharlotteErrorCode.ELEMENT_NOT_FOUND, "Could not resolve form element.");
|
|
278
|
+
}
|
|
279
|
+
await cdpSession.send("Runtime.callFunctionOn", {
|
|
280
|
+
objectId: object.objectId,
|
|
281
|
+
functionDeclaration: `function() {
|
|
282
|
+
this.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
|
283
|
+
}`,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
await cdpSession.detach();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Set files on a file input element using CDP DOM.setFileInputFiles.
|
|
292
|
+
* Validates that the target element is actually an <input type="file">.
|
|
293
|
+
*/
|
|
294
|
+
export async function setFileInputFiles(page, backendNodeId, filePaths) {
|
|
295
|
+
const cdpSession = await page.createCDPSession();
|
|
296
|
+
try {
|
|
297
|
+
const { node } = await cdpSession.send("DOM.describeNode", { backendNodeId });
|
|
298
|
+
const isFileInput = node.nodeName === "INPUT" &&
|
|
299
|
+
(node.attributes ?? []).some((attr, i, arr) => attr === "type" && arr[i + 1] === "file");
|
|
300
|
+
if (!isFileInput) {
|
|
301
|
+
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Element is not a file input.", "Use charlotte_find to locate file_input elements.");
|
|
302
|
+
}
|
|
303
|
+
await cdpSession.send("DOM.setFileInputFiles", {
|
|
304
|
+
files: filePaths,
|
|
305
|
+
backendNodeId,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
await cdpSession.detach();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=interaction-helpers.js.map
|