Skip to content

Commit eee8f17

Browse files
feat: add automate self-heal tools integration (#54)
* feat: add automate self-heal tools integration * enhance self-heal selector extraction with context information * Refactor selector extraction logic to improve mapping * Change tool description * Update src/tools/selfheal.ts Co-authored-by: Pulkit Sharma --------- Co-authored-by: Pulkit Sharma
1 parent b370760 commit eee8f17

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import addTestManagementTools from "./tools/testmanagement.js";
1515
import addAppAutomationTools from "./tools/appautomate.js";
1616
import addFailureLogsTools from "./tools/getFailureLogs.js";
1717
import addAutomateTools from "./tools/automate.js";
18+
import addSelfHealTools from "./tools/selfheal.js";
1819
import { setupOnInitialized } from "./oninitialized.js";
1920

2021
function registerTools(server: McpServer) {
@@ -26,6 +27,7 @@ function registerTools(server: McpServer) {
2627
addAppAutomationTools(server);
2728
addFailureLogsTools(server);
2829
addAutomateTools(server);
30+
addSelfHealTools(server);
2931
}
3032

3133
// Create an MCP server

src/tools/selfheal-utils/selfheal.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { assertOkResponse } from "../../lib/utils.js";
2+
import config from "../../config.js";
3+
4+
interface SelectorMapping {
5+
originalSelector: string;
6+
healedSelector: string;
7+
context: {
8+
before: string;
9+
after: string;
10+
};
11+
}
12+
13+
export async function getSelfHealSelectors(sessionId: string) {
14+
const credentials = `${config.browserstackUsername}:${config.browserstackAccessKey}`;
15+
const auth = Buffer.from(credentials).toString("base64");
16+
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`;
17+
18+
const response = await fetch(url, {
19+
headers: {
20+
"Content-Type": "application/json",
21+
Authorization: `Basic ${auth}`,
22+
},
23+
});
24+
25+
await assertOkResponse(response, "session logs");
26+
const logText = await response.text();
27+
return extractHealedSelectors(logText);
28+
}
29+
30+
function extractHealedSelectors(logText: string): SelectorMapping[] {
31+
// Split log text into lines for easier context handling
32+
const logLines = logText.split("\n");
33+
34+
// Pattern to match successful SELFHEAL entries only
35+
const selfhealPattern =
36+
/SELFHEAL\s*{\s*"status":"true",\s*"data":\s*{\s*"using":"css selector",\s*"value":"(.*?)"}/;
37+
38+
// Pattern to match preceding selector requests
39+
const requestPattern =
40+
/POST \/session\/[^/]+\/element.*?"using":"css selector","value":"(.*?)"/;
41+
42+
// Find all successful healed selectors with their line numbers and context
43+
const healedMappings: SelectorMapping[] = [];
44+
45+
for (let i = 0; i < logLines.length; i++) {
46+
const match = logLines[i].match(selfhealPattern);
47+
if (match) {
48+
const beforeLine = i > 0 ? logLines[i - 1] : "";
49+
const afterLine = i < logLines.length - 1 ? logLines[i + 1] : "";
50+
51+
// Look backwards to find the most recent original selector request
52+
let originalSelector = "UNKNOWN";
53+
for (let j = i - 1; j >= 0; j--) {
54+
const requestMatch = logLines[j].match(requestPattern);
55+
if (requestMatch) {
56+
originalSelector = requestMatch[1];
57+
break;
58+
}
59+
}
60+
61+
healedMappings.push({
62+
originalSelector,
63+
healedSelector: match[1],
64+
context: {
65+
before: beforeLine,
66+
after: afterLine,
67+
},
68+
});
69+
}
70+
}
71+
72+
return healedMappings;
73+
}

src/tools/selfheal.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { z } from "zod";
3+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4+
import { getSelfHealSelectors } from "./selfheal-utils/selfheal.js";
5+
import logger from "../logger.js";
6+
7+
// Tool function that fetches self-healing selectors
8+
export async function fetchSelfHealSelectorTool(args: {
9+
sessionId: string;
10+
}): Promise<CallToolResult> {
11+
try {
12+
const selectors = await getSelfHealSelectors(args.sessionId);
13+
return {
14+
content: [
15+
{
16+
type: "text",
17+
text:
18+
"Self-heal selectors fetched successfully" +
19+
JSON.stringify(selectors),
20+
},
21+
],
22+
};
23+
} catch (error) {
24+
logger.error("Error fetching self-heal selector suggestions", error);
25+
throw error;
26+
}
27+
}
28+
29+
// Registers the fetchSelfHealSelector tool with the MCP server
30+
export default function addSelfHealTools(server: McpServer) {
31+
server.tool(
32+
"fetchSelfHealedSelectors",
33+
"Retrieves AI-generated, self-healed selectors for a BrowserStack Automate session to resolve flaky tests caused by dynamic DOM changes.",
34+
{
35+
sessionId: z.string().describe("The session ID of the test run"),
36+
},
37+
async (args) => {
38+
try {
39+
return await fetchSelfHealSelectorTool(args);
40+
} catch (error) {
41+
const errorMessage =
42+
error instanceof Error ? error.message : "Unknown error";
43+
return {
44+
content: [
45+
{
46+
type: "text",
47+
text: `Error during fetching self-heal suggestions: ${errorMessage}`,
48+
},
49+
],
50+
};
51+
}
52+
},
53+
);
54+
}

0 commit comments

Comments
 (0)