Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,10 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
- [`hover`](docs/tool-reference.md#hover)
- [`press_key`](docs/tool-reference.md#press_key)
- [`upload_file`](docs/tool-reference.md#upload_file)
- **Navigation automation** (7 tools)
- **Navigation automation** (6 tools)
- [`close_page`](docs/tool-reference.md#close_page)
- [`list_pages`](docs/tool-reference.md#list_pages)
- [`navigate_page`](docs/tool-reference.md#navigate_page)
- [`navigate_page_history`](docs/tool-reference.md#navigate_page_history)
- [`new_page`](docs/tool-reference.md#new_page)
- [`select_page`](docs/tool-reference.md#select_page)
- [`wait_for`](docs/tool-reference.md#wait_for)
Expand Down
17 changes: 3 additions & 14 deletions docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
- [`hover`](#hover)
- [`press_key`](#press_key)
- [`upload_file`](#upload_file)
- **[Navigation automation](#navigation-automation)** (7 tools)
- **[Navigation automation](#navigation-automation)** (6 tools)
- [`close_page`](#close_page)
- [`list_pages`](#list_pages)
- [`navigate_page`](#navigate_page)
- [`navigate_page_history`](#navigate_page_history)
- [`new_page`](#new_page)
- [`select_page`](#select_page)
- [`wait_for`](#wait_for)
Expand Down Expand Up @@ -151,18 +150,8 @@
**Parameters:**

- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used.
- **url** (string) **(required)**: URL to navigate the page to

---

### `navigate_page_history`

**Description:** Navigates the currently selected page.

**Parameters:**

- **navigate** (enum: "back", "forward") **(required)**: Whether to navigate back or navigate forward in the selected pages history
- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used.
- **type** (enum: "url", "back", "forward", "reload") _(optional)_: Navigate the page by URL, back or forward in history, or reload.
- **url** (string) _(optional)_: Target URL (only type=url)

---

Expand Down
102 changes: 65 additions & 37 deletions src/tools/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,35 +105,13 @@ export const navigatePage = defineTool({
readOnlyHint: false,
},
schema: {
url: zod.string().describe('URL to navigate the page to'),
...timeoutSchema,
},
handler: async (request, response, context) => {
const page = context.getSelectedPage();

await context.waitForEventsAfterAction(async () => {
await page.goto(request.params.url, {
timeout: request.params.timeout,
});
});

response.setIncludePages(true);
},
});

export const navigatePageHistory = defineTool({
name: 'navigate_page_history',
description: `Navigates the currently selected page.`,
annotations: {
category: ToolCategory.NAVIGATION,
readOnlyHint: false,
},
schema: {
navigate: zod
.enum(['back', 'forward'])
type: zod
.enum(['url', 'back', 'forward', 'reload'])
.optional()
.describe(
'Whether to navigate back or navigate forward in the selected pages history',
'Navigate the page by URL, back or forward in history, or reload.',
),
url: zod.string().optional().describe('Target URL (only type=url)'),
...timeoutSchema,
},
handler: async (request, response, context) => {
Expand All @@ -142,18 +120,68 @@ export const navigatePageHistory = defineTool({
timeout: request.params.timeout,
};

try {
if (request.params.navigate === 'back') {
await page.goBack(options);
} else {
await page.goForward(options);
}
} catch (error) {
response.appendResponseLine(
`Unable to navigate ${request.params.navigate} in currently selected page. ${error.message}`,
);
if (!request.params.type && !request.params.url) {
throw new Error('Either URL or a type is required.');
}

if (!request.params.type) {
request.params.type = 'url';
}

await context.waitForEventsAfterAction(async () => {
switch (request.params.type) {
case 'url':
if (!request.params.url) {
throw new Error('A URL is required for navigation of type=url.');
}
try {
await page.goto(request.params.url, options);
response.appendResponseLine(
`Successfully navigated to ${request.params.url}.`,
);
} catch (error) {
response.appendResponseLine(
`Unable to navigate in the selected page: ${error.message}.`,
);
}
break;
case 'back':
try {
await page.goBack(options);
response.appendResponseLine(
`Successfully navigated back to ${page.url()}.`,
);
} catch (error) {
response.appendResponseLine(
`Unable to navigate back in the selected page: ${error.message}.`,
);
}
break;
case 'forward':
try {
await page.goForward(options);
response.appendResponseLine(
`Successfully navigated forward to ${page.url()}.`,
);
} catch (error) {
response.appendResponseLine(
`Unable to navigate forward in the selected page: ${error.message}.`,
);
}
break;
case 'reload':
try {
await page.reload(options);
response.appendResponseLine(`Successfully reloaded the page.`);
} catch (error) {
response.appendResponseLine(
`Unable to reload the selected page: ${error.message}.`,
);
}
break;
}
});

response.setIncludePages(true);
},
});
Expand Down
56 changes: 30 additions & 26 deletions tests/tools/pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
closePage,
selectPage,
navigatePage,
navigatePageHistory,
resizePage,
handleDialog,
} from '../../src/tools/pages.js';
Expand All @@ -29,7 +28,7 @@ describe('pages', () => {
});
});
});
describe('browser_new_page', () => {
describe('new_page', () => {
it('create a page', async () => {
await withBrowser(async (response, context) => {
assert.strictEqual(context.getSelectedPageIdx(), 0);
Expand All @@ -43,7 +42,7 @@ describe('pages', () => {
});
});
});
describe('browser_close_page', () => {
describe('close_page', () => {
it('closes a page', async () => {
await withBrowser(async (response, context) => {
const page = await context.newPage();
Expand All @@ -67,7 +66,7 @@ describe('pages', () => {
});
});
});
describe('browser_select_page', () => {
describe('select_page', () => {
it('selects a page', async () => {
await withBrowser(async (response, context) => {
await context.newPage();
Expand All @@ -78,7 +77,7 @@ describe('pages', () => {
});
});
});
describe('browser_navigate_page', () => {
describe('navigate_page', () => {
it('navigates to correct page', async () => {
await withBrowser(async (response, context) => {
await navigatePage.handler(
Expand Down Expand Up @@ -118,17 +117,11 @@ describe('pages', () => {
}
});
});
});
describe('browser_navigate_page_history', () => {
it('go back', async () => {
await withBrowser(async (response, context) => {
const page = context.getSelectedPage();
await page.goto('data:text/html,<div>Hello MCP</div>');
await navigatePageHistory.handler(
{params: {navigate: 'back'}},
response,
context,
);
await navigatePage.handler({params: {type: 'back'}}, response, context);

assert.equal(
await page.evaluate(() => document.location.href),
Expand All @@ -142,8 +135,8 @@ describe('pages', () => {
const page = context.getSelectedPage();
await page.goto('data:text/html,<div>Hello MCP</div>');
await page.goBack();
await navigatePageHistory.handler(
{params: {navigate: 'forward'}},
await navigatePage.handler(
{params: {type: 'forward'}},
response,
context,
);
Expand All @@ -155,42 +148,53 @@ describe('pages', () => {
assert.ok(response.includePages);
});
});
it('reload', async () => {
await withBrowser(async (response, context) => {
const page = context.getSelectedPage();
await page.goto('data:text/html,<div>Hello MCP</div>');
await navigatePage.handler(
{params: {type: 'reload'}},
response,
context,
);

assert.equal(
await page.evaluate(() => document.location.href),
'data:text/html,<div>Hello MCP</div>',
);
assert.ok(response.includePages);
});
});
it('go forward with error', async () => {
await withBrowser(async (response, context) => {
await navigatePageHistory.handler(
{params: {navigate: 'forward'}},
await navigatePage.handler(
{params: {type: 'forward'}},
response,
context,
);

assert.ok(
response.responseLines
.at(0)
?.startsWith(
'Unable to navigate forward in currently selected page.',
),
?.startsWith('Unable to navigate forward in the selected page:'),
);
assert.ok(response.includePages);
});
});
it('go back with error', async () => {
await withBrowser(async (response, context) => {
await navigatePageHistory.handler(
{params: {navigate: 'back'}},
response,
context,
);
await navigatePage.handler({params: {type: 'back'}}, response, context);

assert.ok(
response.responseLines
.at(0)
?.startsWith('Unable to navigate back in currently selected page.'),
?.startsWith('Unable to navigate back in the selected page:'),
);
assert.ok(response.includePages);
});
});
});
describe('browser_resize', () => {
describe('resize', () => {
it('create a page', async () => {
await withBrowser(async (response, context) => {
assert.strictEqual(context.getSelectedPageIdx(), 0);
Expand Down