chromeflow 0.1.38 → 0.1.39
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/CLAUDE.md +21 -7
- package/dist/setup.js +3 -1
- package/dist/tools/capture.js +34 -3
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -155,14 +155,31 @@ screenshot to check what happened.
|
|
|
155
155
|
|
|
156
156
|
**React Select / custom styled dropdowns** (e.g. "Select..." components on DataAnnotation):
|
|
157
157
|
`click_element` and `fill_input` do NOT work on these — they intercept native events. Use
|
|
158
|
-
`execute_script`
|
|
158
|
+
`execute_script` with the hidden combobox input approach (most reliable):
|
|
159
159
|
|
|
160
160
|
```js
|
|
161
|
-
// 1.
|
|
161
|
+
// 1. Find the hidden combobox input (each React Select has one: input[id*="react-select-N-input"])
|
|
162
|
+
var input = document.querySelector('input[id*="react-select-3-input"]');
|
|
163
|
+
input.focus();
|
|
164
|
+
|
|
165
|
+
// 2. Set value via native setter to trigger React's onChange
|
|
166
|
+
var setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
|
|
167
|
+
setter.call(input, 'Target Option');
|
|
168
|
+
input.dispatchEvent(new Event('input', {bubbles: true}));
|
|
169
|
+
|
|
170
|
+
// 3. Wait 300ms for the dropdown to filter, then click the first matching option
|
|
171
|
+
// (run this as a separate execute_script call after a brief pause)
|
|
172
|
+
var option = document.querySelector('[id*="react-select-3-option-0"]');
|
|
173
|
+
if (option) option.click();
|
|
174
|
+
|
|
175
|
+
// 4. Verify — the control div should show the selected value
|
|
176
|
+
document.querySelector('[class*="singleValue"]').textContent.trim();
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Fallback if the combobox approach doesn't work (older React Select versions):
|
|
180
|
+
```js
|
|
162
181
|
var controls = document.querySelectorAll('[class*="control"]');
|
|
163
182
|
controls[N].click();
|
|
164
|
-
|
|
165
|
-
// 2. Pick an option by exact text
|
|
166
183
|
var allEls = document.querySelectorAll('*');
|
|
167
184
|
for (var i = 0; i < allEls.length; i++) {
|
|
168
185
|
if (allEls[i].textContent.trim() === 'Target Option' && allEls[i].children.length === 0) {
|
|
@@ -171,9 +188,6 @@ for (var i = 0; i < allEls.length; i++) {
|
|
|
171
188
|
break;
|
|
172
189
|
}
|
|
173
190
|
}
|
|
174
|
-
|
|
175
|
-
// 3. Verify
|
|
176
|
-
controls[N].textContent.trim(); // should show selected value
|
|
177
191
|
```
|
|
178
192
|
|
|
179
193
|
**Page text with large embedded content** (e.g. uploaded log files previewed inline): full-page `get_page_text()` pagination becomes unwieldy. Scope to a specific section instead:
|
package/dist/setup.js
CHANGED
|
@@ -169,7 +169,9 @@ const CHROMEFLOW_TOOLS = [
|
|
|
169
169
|
// v0.1.32+
|
|
170
170
|
"fill_form",
|
|
171
171
|
// v0.1.36+
|
|
172
|
-
"set_file_input"
|
|
172
|
+
"set_file_input",
|
|
173
|
+
// v0.1.39+
|
|
174
|
+
"get_console_logs"
|
|
173
175
|
].map((t) => `mcp__chromeflow__${t}`);
|
|
174
176
|
function patchSettingsLocalJson(cwd) {
|
|
175
177
|
const claudeDir = join(cwd, ".claude");
|
package/dist/tools/capture.js
CHANGED
|
@@ -12,10 +12,11 @@ DO NOT use for: email address, password, payment/billing info, phone number \u20
|
|
|
12
12
|
After filling, call wait_for_click only if the user needs to review/confirm; otherwise proceed directly to the next step.`,
|
|
13
13
|
{
|
|
14
14
|
textHint: z.string().describe("The label, placeholder, or nearby text identifying the input (e.g. 'Product name', 'Amount', 'Description')"),
|
|
15
|
-
value: z.string().describe("The value to fill in")
|
|
15
|
+
value: z.string().describe("The value to fill in"),
|
|
16
|
+
nth: z.number().int().min(1).optional().describe("Which match to fill when multiple inputs share the same label (1 = first/topmost, default 1)")
|
|
16
17
|
},
|
|
17
|
-
async ({ textHint, value }) => {
|
|
18
|
-
const response = await bridge.request({ type: "fill_input", textHint, value });
|
|
18
|
+
async ({ textHint, value, nth }) => {
|
|
19
|
+
const response = await bridge.request({ type: "fill_input", textHint, value, nth });
|
|
19
20
|
if (response.type !== "fill_response") throw new Error("Unexpected response");
|
|
20
21
|
const r = response;
|
|
21
22
|
return {
|
|
@@ -117,6 +118,36 @@ The snapshot is read from the local temp file written by save_page_state.`,
|
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
);
|
|
121
|
+
server.tool(
|
|
122
|
+
"get_console_logs",
|
|
123
|
+
`Read the browser console output (log, warn, error, info) captured since the page loaded.
|
|
124
|
+
Returns the last 200 messages with their level and timestamp.
|
|
125
|
+
Use this to check for JavaScript errors, debug React issues, or verify that an action produced the expected console output.
|
|
126
|
+
Pass level="error" to see only errors, or omit to see all levels.`,
|
|
127
|
+
{
|
|
128
|
+
level: z.enum(["log", "warn", "error", "info"]).optional().describe('Filter by log level (e.g. "error" to see only errors). Omit for all levels.')
|
|
129
|
+
},
|
|
130
|
+
async ({ level }) => {
|
|
131
|
+
const response = await bridge.request({ type: "execute_script", code: `JSON.stringify(window._consoleLogs || [])` });
|
|
132
|
+
if (response.type !== "script_response") throw new Error("Unexpected response");
|
|
133
|
+
let logs;
|
|
134
|
+
try {
|
|
135
|
+
logs = JSON.parse(response.result);
|
|
136
|
+
} catch {
|
|
137
|
+
return { content: [{ type: "text", text: "No console logs captured (console capture may not be injected on this page yet \u2014 navigate first)." }] };
|
|
138
|
+
}
|
|
139
|
+
if (level) logs = logs.filter((l) => l.level === level);
|
|
140
|
+
if (logs.length === 0) {
|
|
141
|
+
return { content: [{ type: "text", text: level ? `No ${level}-level console messages.` : "No console messages captured." }] };
|
|
142
|
+
}
|
|
143
|
+
const lines = logs.map((l) => {
|
|
144
|
+
const time = new Date(l.time).toISOString().slice(11, 23);
|
|
145
|
+
return `[${time}] ${l.level.toUpperCase()}: ${l.message.slice(0, 500)}`;
|
|
146
|
+
});
|
|
147
|
+
return { content: [{ type: "text", text: `Console logs (${logs.length} entries):
|
|
148
|
+
${lines.join("\n")}` }] };
|
|
149
|
+
}
|
|
150
|
+
);
|
|
120
151
|
server.tool(
|
|
121
152
|
"write_to_env",
|
|
122
153
|
"Write a key=value pair to a .env file. Use this after capturing an API key or ID from the page.",
|
package/package.json
CHANGED