Skip to content

Commit cd02fe3

Browse files
authored
Merge pull request #56 from angiejones/feat/new-tools
fix: improve switch_to_frame API, add missing docs/tests, fix test reliability
2 parents 671791c + 6237c21 commit cd02fe3

3 files changed

Lines changed: 60 additions & 22 deletions

File tree

README.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ Executes JavaScript in the browser and returns the result.
505505
| Parameter | Type | Required | Description |
506506
|-----------|------|----------|-------------|
507507
| script | string | Yes | JavaScript code to execute |
508+
| args | array | No | Arguments to pass to the script (accessible via `arguments[0]`, `arguments[1]`, etc.) |
508509

509510
**Example:**
510511
```json
@@ -516,6 +517,17 @@ Executes JavaScript in the browser and returns the result.
516517
}
517518
```
518519

520+
**Example with arguments:**
521+
```json
522+
{
523+
"tool": "execute_script",
524+
"parameters": {
525+
"script": "return arguments[0] + arguments[1];",
526+
"args": [10, 32]
527+
}
528+
}
529+
```
530+
519531
### get_window_handles
520532
Returns a list of all window/tab handles in the current session.
521533

@@ -577,18 +589,17 @@ None required
577589
```
578590

579591
### switch_to_frame
580-
Switches focus to an iframe within the page.
592+
Switches focus to an iframe or frame within the page. Provide either `by`/`value` to locate the frame by element, or `index` to switch by position.
581593

582594
**Parameters:**
583595
| Parameter | Type | Required | Description |
584596
|-----------|------|----------|-------------|
585597
| by | string | No | Locator strategy (id, css, xpath, name, tag, class) |
586598
| value | string | No | Value for the locator strategy |
587599
| index | number | No | Frame index (0-based) |
600+
| timeout | number | No | Max wait time in ms (default: 10000) |
588601

589-
Provide either `by`/`value` to locate the frame by element, or `index` to switch by position. Omit all parameters to switch to the parent frame.
590-
591-
**Example:**
602+
**Example (by locator):**
592603
```json
593604
{
594605
"tool": "switch_to_frame",
@@ -599,6 +610,16 @@ Provide either `by`/`value` to locate the frame by element, or `index` to switch
599610
}
600611
```
601612

613+
**Example (by index):**
614+
```json
615+
{
616+
"tool": "switch_to_frame",
617+
"parameters": {
618+
"index": 0
619+
}
620+
}
621+
```
622+
602623
### switch_to_default_content
603624
Switches focus back to the main page from an iframe.
604625

src/lib/server.js

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -652,8 +652,17 @@ server.tool(
652652
content: [{ type: 'text', text: `Window closed. Switched to: ${handles[0]}` }]
653653
};
654654
}
655+
// Last window closed — quit the driver and clean up the session
656+
const sessionId = state.currentSession;
657+
try {
658+
await driver.quit();
659+
} catch (quitError) {
660+
console.error(`Error quitting driver for session ${sessionId}:`, quitError);
661+
}
662+
state.drivers.delete(sessionId);
663+
state.currentSession = null;
655664
return {
656-
content: [{ type: 'text', text: 'Last window closed' }]
665+
content: [{ type: 'text', text: 'Last window closed. Session ended.' }]
657666
};
658667
} catch (e) {
659668
return {
@@ -667,25 +676,24 @@ server.tool(
667676
// Frame Management Tools
668677
server.tool(
669678
"switch_to_frame",
670-
"switches focus to an iframe or frame within the page",
679+
"switches focus to an iframe or frame within the page. Provide either by/value to locate by element, or index to switch by position.",
671680
{
672-
by: z.enum(["id", "css", "xpath", "name", "tag", "class", "index"]).describe("Locator strategy or 'index' to find frame"),
673-
value: z.string().describe("Value for the locator strategy, or frame index number"),
681+
by: z.enum(["id", "css", "xpath", "name", "tag", "class"]).optional().describe("Locator strategy to find frame element"),
682+
value: z.string().optional().describe("Value for the locator strategy"),
683+
index: z.number().optional().describe("Frame index (0-based) to switch to by position"),
674684
timeout: z.number().optional().describe("Maximum time to wait for frame in milliseconds")
675685
},
676-
async ({ by, value, timeout = 10000 }) => {
686+
async ({ by, value, index, timeout = 10000 }) => {
677687
try {
678688
const driver = getDriver();
679-
if (by === 'index') {
680-
const index = parseInt(value, 10);
681-
if (isNaN(index)) {
682-
throw new Error(`Invalid frame index: ${value}`);
683-
}
689+
if (index !== undefined) {
684690
await driver.switchTo().frame(index);
685-
} else {
691+
} else if (by && value) {
686692
const locator = getLocator(by, value);
687693
const element = await driver.wait(until.elementLocated(locator), timeout);
688694
await driver.switchTo().frame(element);
695+
} else {
696+
throw new Error('Provide either by/value to locate frame by element, or index to switch by position');
689697
}
690698
return {
691699
content: [{ type: 'text', text: `Switched to frame` }]
@@ -794,7 +802,7 @@ server.tool(
794802

795803
server.tool(
796804
"send_alert_text",
797-
"types text into a browser prompt dialog",
805+
"types text into a browser prompt dialog and accepts it",
798806
{
799807
text: z.string().describe("Text to enter into the prompt"),
800808
timeout: z.number().optional().describe("Maximum time to wait for alert in milliseconds")

test/new-tools.test.mjs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ describe('execute_script', () => {
142142
assert.deepEqual(parsed, { a: 1, b: 2 });
143143
});
144144

145+
it('executes script with arguments', async () => {
146+
const result = await client.callTool('execute_script', { script: 'return arguments[0] + arguments[1];', args: [10, 32] });
147+
assert.equal(getResponseText(result), '42');
148+
});
149+
145150
it('returns error for invalid script', async () => {
146151
const result = await client.callTool('execute_script', { script: 'return undefinedVariable.property;' });
147152
assert.equal(result.isError, true);
@@ -266,7 +271,7 @@ describe('frame management', () => {
266271
});
267272

268273
it('switch_to_frame by index', async () => {
269-
let result = await client.callTool('switch_to_frame', { by: 'index', value: '0' });
274+
let result = await client.callTool('switch_to_frame', { index: 0 });
270275
assert.equal(getResponseText(result), 'Switched to frame');
271276

272277
result = await client.callTool('get_element_text', { by: 'id', value: 'frame-text' });
@@ -276,8 +281,8 @@ describe('frame management', () => {
276281
await client.callTool('switch_to_default_content');
277282
});
278283

279-
it('switch_to_frame returns error for invalid index', async () => {
280-
const result = await client.callTool('switch_to_frame', { by: 'index', value: 'abc' });
284+
it('switch_to_frame returns error when no locator or index provided', async () => {
285+
const result = await client.callTool('switch_to_frame', {});
281286
assert.equal(result.isError, true);
282287
});
283288
});
@@ -299,14 +304,18 @@ describe('alert handling', () => {
299304
await client.stop();
300305
});
301306

302-
it('get_alert_text reads alert message', async () => {
307+
it('get_alert_text reads alert message and accept_alert closes it', async () => {
303308
await client.callTool('click_element', { by: 'id', value: 'alert-btn' });
304309
const result = await client.callTool('get_alert_text');
305310
assert.equal(getResponseText(result), 'Hello from alert!');
311+
312+
// Accept the alert to close it
313+
const acceptResult = await client.callTool('accept_alert');
314+
assert.equal(getResponseText(acceptResult), 'Alert accepted');
306315
});
307316

308-
it('accept_alert dismisses the alert', async () => {
309-
// Alert is still open from previous test
317+
it('accept_alert accepts a fresh alert', async () => {
318+
await client.callTool('click_element', { by: 'id', value: 'alert-btn' });
310319
const result = await client.callTool('accept_alert');
311320
assert.equal(getResponseText(result), 'Alert accepted');
312321
});

0 commit comments

Comments
 (0)