Skip to content

Commit ddc583b

Browse files
author
rocketraccoon
committed
feat(mcp-tools): rewrite isActive method + add correct tests
1 parent 33c4a85 commit ddc583b

File tree

11 files changed

+127
-31
lines changed

11 files changed

+127
-31
lines changed

src/browser-context.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,16 @@ export class BrowserContext {
2222

2323
if (this._session) {
2424
console.error("Attach to browser");
25+
2526
this._browser = await attachToBrowser(this._session);
27+
28+
// try {
29+
// this._browser = await attachToBrowser(this._session);
30+
// await this._browser.getUrl();
31+
// console.error("Attached to browser successfully");
32+
// } catch (e) {
33+
// console.error("Can't attach to browser", e);
34+
// }
2635
} else {
2736
console.error("Launch browser");
2837
this._browser = await launchBrowser({
@@ -51,7 +60,17 @@ export class BrowserContext {
5160
}
5261
}
5362

54-
isActive(): boolean {
55-
return this._browser !== null;
63+
async isActive(): Promise<boolean> {
64+
try {
65+
if (this._browser) {
66+
await this._browser.getUrl();
67+
return true;
68+
}
69+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
70+
} catch (_) {
71+
/* empty */
72+
}
73+
return false;
74+
// return this._browser !== null;
5675
}
5776
}

src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export async function startServer(options: ServerOptions = {}): Promise<McpServe
6565

6666
Promise.resolve()
6767
.then(async () => {
68-
if (context.browser.isActive()) {
68+
if (await context.browser.isActive()) {
6969
await context.browser.close();
7070
}
7171
})

src/tools/attach-to-browser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const attachToBrowserCb: ToolCallback<typeof attachToBrowserSchema> = async args
1919

2020
const context = contextProvider.getContext();
2121

22-
if (!context.browser.isActive()) {
23-
return createSimpleResponse("Can not attach to browser using existing session options");
22+
if (!(await context.browser.isActive())) {
23+
return createErrorResponse("Can not attach to browser using existing session options");
2424
}
2525

2626
return createSimpleResponse("Successfully attached to existing browser session");

src/tools/close-browser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const closeBrowserCb: ToolCallback<typeof closeBrowserSchema> = async () => {
99
try {
1010
const context = contextProvider.getContext();
1111

12-
if (!context.browser.isActive()) {
12+
if (!(await context.browser.isActive())) {
1313
return createSimpleResponse("No active browser session to close");
1414
}
1515

src/tools/close-tab.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const closeTabCb: ToolCallback<typeof closeTabSchema> = async args => {
1919
const { tabNumber } = args;
2020
const context = contextProvider.getContext();
2121

22-
if (!context.browser.isActive()) {
22+
if (!(await context.browser.isActive())) {
2323
return createErrorResponse(
2424
"Cannot close tab — browser is not launched yet. Try opening a tab or navigating to URL.",
2525
);

src/tools/list-tabs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const listTabsCb: ToolCallback<typeof listTabsSchema> = async () => {
99
try {
1010
const context = contextProvider.getContext();
1111

12-
if (!context.browser.isActive()) {
12+
if (!(await context.browser.isActive())) {
1313
return createErrorResponse(
1414
"No opened tabs — browser is not launched yet. Try opening a tab or navigating to URL.",
1515
);

src/tools/open-new-tab.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const openNewTabCb: ToolCallback<typeof openNewTabSchema> = async args => {
1313
const { url } = args;
1414
const context = contextProvider.getContext();
1515

16-
const browserWasActive = context.browser.isActive();
16+
const browserWasActive = await context.browser.isActive();
1717
const browser = await context.browser.get();
1818

1919
let actionMessage = "Opened new tab";

src/tools/switch-to-tab.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const switchToTabCb: ToolCallback<typeof switchToTabSchema> = async args => {
1414
const { tabNumber } = args;
1515
const context = contextProvider.getContext();
1616

17-
if (!context.browser.isActive()) {
17+
if (!(await context.browser.isActive())) {
1818
return createErrorResponse(
1919
"Cannot switch to tab — browser is not launched yet. Try opening a tab or navigating to URL.",
2020
);

src/tools/utils/attach-to-browser-schema.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ export const attachToBrowserSchema = {
44
session: z
55
.object({
66
sessionId: z.string().describe("Unique identifier for the session"),
7-
pid: z.number().describe("Pid of webdriver process, need for close browser correct"),
7+
driverPid: z
8+
.number()
9+
.describe(
10+
"Pid of webdriver process, need for close browser correct, necessarily provide it if it exist",
11+
)
12+
.optional(),
813
sessionCaps: z
914
.object({
1015
acceptInsecureCerts: z
@@ -70,12 +75,14 @@ export const attachToBrowserSchema = {
7075
browserName: z.string().describe("Requested browser name"),
7176
"wdio:enforceWebDriverClassic": z
7277
.boolean()
73-
.describe("Flag to enforce classic WebDriver protocol"),
78+
.describe("Flag to enforce classic WebDriver protocol")
79+
.optional(),
7480
"goog:chromeOptions": z
7581
.object({
76-
binary: z.string().describe("Path to Chrome binary"),
82+
binary: z.string().describe("Path to Chrome binary").optional(),
7783
})
78-
.describe("Chrome-specific options"),
84+
.describe("Chrome-specific options")
85+
.optional(),
7986
})
8087
.describe("Requested capabilities")
8188
.optional(),
@@ -89,10 +96,13 @@ export const attachToBrowserSchema = {
8996
browserName: z.string().describe("Originally requested browser name"),
9097
"wdio:enforceWebDriverClassic": z
9198
.boolean()
92-
.describe("Originally requested protocol enforcement"),
93-
"goog:chromeOptions": z.object({
94-
binary: z.string().describe("Originally requested Chrome binary path"),
95-
}),
99+
.describe("Originally requested protocol enforcement")
100+
.optional(),
101+
"goog:chromeOptions": z
102+
.object({
103+
binary: z.string().describe("Originally requested Chrome binary path").optional(),
104+
})
105+
.optional(),
96106
})
97107
.describe("Originally requested capabilities")
98108
.optional(),

test/tools/attach-to-browser.test.ts

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,29 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
22
import { describe, it, expect, beforeEach, afterEach } from "vitest";
33
import { startClient } from "../utils";
44
import { INTEGRATION_TEST_TIMEOUT } from "../constants";
5+
import { launchBrowser } from "testplane/unstable";
56

6-
const sessionMinimalMock = {
7-
sessionId: "ffe4129f99be1125e304242500121efa",
8-
sessionCaps: {
9-
browserName: "chrome",
10-
browserVersion: "137.0.7151.119",
11-
setWindowRect: true,
7+
export const BROWSER_NAME = (process.env.BROWSER || "chrome").toLowerCase() as string;
8+
9+
export const BROWSER_CONFIG = {
10+
desiredCapabilities: {
11+
browserName: BROWSER_NAME,
1212
},
13-
sessionOpts: {
14-
protocol: "http",
15-
hostname: "127.0.0.1",
16-
port: 49426,
17-
path: "/",
18-
strictSSL: true,
13+
headless: true,
14+
system: {
15+
debug: Boolean(process.env.DEBUG) || false,
1916
},
2017
};
18+
19+
const checkProcessExists = (pid: number): boolean => {
20+
try {
21+
process.kill(pid, 0);
22+
return true;
23+
} catch {
24+
return false;
25+
}
26+
};
27+
2128
describe.only(
2229
"tools/attachToBrowser",
2330
() => {
@@ -45,11 +52,55 @@ describe.only(
4552
});
4653

4754
describe("attachToBrowser tool execution", () => {
55+
it("error if connect to unexisting session", async () => {
56+
const result = await client.callTool({
57+
name: "attachToBrowser",
58+
arguments: {
59+
session: {
60+
sessionId: "ffe4129f99be1125e304242500121efa",
61+
sessionCaps: {
62+
browserName: "chrome",
63+
browserVersion: "137.0.7151.119",
64+
setWindowRect: true,
65+
},
66+
sessionOpts: {
67+
protocol: "http",
68+
hostname: "127.0.0.1",
69+
port: 49426,
70+
path: "/",
71+
strictSSL: true,
72+
},
73+
},
74+
},
75+
});
76+
77+
expect(result.isError).toBe(true);
78+
expect(result.content).toBeDefined();
79+
80+
const content = result.content as Array<{ type: string; text: string }>;
81+
82+
expect(content).toHaveLength(1);
83+
expect(content[0].type).toBe("text");
84+
expect(content[0].text).toBe("❌ Can not attach to browser using existing session options");
85+
});
86+
4887
it("should attach to existing browser session", async () => {
88+
const browser: WebdriverIO.Browser & { getDriverPid?: () => number | undefined } =
89+
await launchBrowser(BROWSER_CONFIG);
90+
const driverPid = (await browser.getDriverPid!()) as number;
91+
4992
const result = await client.callTool({
5093
name: "attachToBrowser",
5194
arguments: {
52-
session: sessionMinimalMock,
95+
session: {
96+
sessionId: browser.sessionId,
97+
sessionCaps: browser.capabilities,
98+
sessionOpts: {
99+
capabilities: browser.capabilities,
100+
...browser.options,
101+
},
102+
driverPid,
103+
},
53104
},
54105
});
55106

@@ -60,6 +111,20 @@ describe.only(
60111
expect(content).toHaveLength(1);
61112
expect(content[0].type).toBe("text");
62113
expect(content[0].text).toBe("Successfully attached to existing browser session");
114+
115+
// Check that browser process exist
116+
expect(checkProcessExists(driverPid)).toBe(true);
117+
118+
// Call closeBrowser tool
119+
await client.callTool({
120+
name: "closeBrowser",
121+
arguments: {},
122+
});
123+
124+
await browser.pause(100);
125+
126+
// Check that browser process doesn't exist
127+
expect(checkProcessExists(driverPid)).toBe(false);
63128
});
64129
});
65130
},

0 commit comments

Comments
 (0)