Skip to content

Commit 672d307

Browse files
authored
Merge branch 'browserstack:main' into main
2 parents 9f7d1a6 + 32eead6 commit 672d307

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+5412
-682
lines changed

src/lib/device-cache.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,37 +34,41 @@ export async function getDevicesAndBrowsers(
3434
fs.mkdirSync(CACHE_DIR, { recursive: true });
3535
}
3636

37-
let cache: any = {};
37+
let cache: Record<string, any> = {};
3838

39+
// Load existing cache
3940
if (fs.existsSync(CACHE_FILE)) {
40-
const stats = fs.statSync(CACHE_FILE);
41-
if (Date.now() - stats.mtimeMs < TTL_MS) {
42-
try {
43-
cache = JSON.parse(fs.readFileSync(CACHE_FILE, "utf8"));
44-
if (cache[type]) {
45-
return cache[type];
46-
}
47-
} catch (error) {
48-
console.error("Error parsing cache file:", error);
49-
// Continue with fetching fresh data
50-
}
41+
try {
42+
cache = JSON.parse(fs.readFileSync(CACHE_FILE, "utf8"));
43+
} catch (err) {
44+
console.error("Error parsing cache file:", err);
45+
cache = {};
46+
}
47+
48+
// Check per-product TTL
49+
const cachedEntry = cache[type];
50+
if (cachedEntry?.timestamp && Date.now() - cachedEntry.timestamp < TTL_MS) {
51+
return cachedEntry.data;
5152
}
5253
}
5354

55+
// Fetch fresh data from BrowserStack
5456
const liveRes = await apiClient.get({ url: URLS[type], raise_error: false });
55-
5657
if (!liveRes.ok) {
5758
throw new Error(
58-
`Failed to fetch configuration from BrowserStack : ${type}=${liveRes.statusText}`,
59+
`Failed to fetch configuration from BrowserStack: ${type} = ${liveRes.statusText}`,
5960
);
6061
}
6162

62-
cache = {
63-
[type]: liveRes.data,
63+
// Save to cache with timestamp and data directly under product key
64+
cache[type] = {
65+
timestamp: Date.now(),
66+
data: liveRes.data,
6467
};
65-
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
6668

67-
return cache[type];
69+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf8");
70+
71+
return liveRes.data;
6872
}
6973

7074
// Rate limiter for started event (3H)

src/lib/inmemory-store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const signedUrlMap = new Map<string, object>();
2+
export const testFilePathsMap = new Map<string, string[]>();

src/lib/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import sharp from "sharp";
22
import type { ApiResponse } from "./apiClient.js";
3+
import { BrowserStackConfig } from "./types.js";
4+
import { getBrowserStackAuth } from "./get-auth.js";
35

46
export function sanitizeUrlParam(param: string): string {
57
// Remove any characters that could be used for command injection
@@ -38,3 +40,25 @@ export async function assertOkResponse(
3840
);
3941
}
4042
}
43+
44+
export async function fetchFromBrowserStackAPI(
45+
url: string,
46+
config: BrowserStackConfig,
47+
): Promise<any> {
48+
const authString = getBrowserStackAuth(config);
49+
const auth = Buffer.from(authString).toString("base64");
50+
51+
const res = await fetch(url, {
52+
headers: {
53+
Authorization: `Basic ${auth}`,
54+
},
55+
});
56+
57+
if (!res.ok) {
58+
throw new Error(
59+
`Failed to fetch from ${url}: ${res.status} ${res.statusText}`,
60+
);
61+
}
62+
63+
return res.json();
64+
}

src/server-factory.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const require = createRequire(import.meta.url);
77
const packageJson = require("../package.json");
88
import logger from "./logger.js";
99
import addSDKTools from "./tools/bstack-sdk.js";
10+
import addPercyTools from "./tools/percy-sdk.js";
1011
import addBrowserLiveTools from "./tools/live.js";
1112
import addAccessibilityTools from "./tools/accessibility.js";
1213
import addTestManagementTools from "./tools/testmanagement.js";
@@ -15,8 +16,10 @@ import addFailureLogsTools from "./tools/get-failure-logs.js";
1516
import addAutomateTools from "./tools/automate.js";
1617
import addSelfHealTools from "./tools/selfheal.js";
1718
import addAppLiveTools from "./tools/applive.js";
19+
import addBuildInsightsTools from "./tools/build-insights.js";
1820
import { setupOnInitialized } from "./oninitialized.js";
1921
import { BrowserStackConfig } from "./lib/types.js";
22+
import addRCATools from "./tools/rca-agent.js";
2023

2124
/**
2225
* Wrapper class for BrowserStack MCP Server
@@ -48,13 +51,16 @@ export class BrowserStackMcpServer {
4851
const toolAdders = [
4952
addAccessibilityTools,
5053
addSDKTools,
54+
addPercyTools,
5155
addAppLiveTools,
5256
addBrowserLiveTools,
5357
addTestManagementTools,
5458
addAppAutomationTools,
5559
addFailureLogsTools,
5660
addAutomateTools,
5761
addSelfHealTools,
62+
addBuildInsightsTools,
63+
addRCATools,
5864
];
5965

6066
toolAdders.forEach((adder) => {

src/tools/add-percy-snapshots.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { testFilePathsMap } from "../lib/inmemory-store.js";
2+
import { updateFileAndStep } from "./percy-snapshot-utils/utils.js";
3+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4+
import { percyWebSetupInstructions } from "../tools/sdk-utils/percy-web/handler.js";
5+
6+
export async function updateTestsWithPercyCommands(args: {
7+
uuid: string;
8+
index: number;
9+
}): Promise<CallToolResult> {
10+
const { uuid, index } = args;
11+
const filePaths = testFilePathsMap.get(uuid);
12+
13+
if (!filePaths) {
14+
throw new Error(`No test files found in memory for UUID: ${uuid}`);
15+
}
16+
17+
if (index < 0 || index >= filePaths.length) {
18+
throw new Error(
19+
`Invalid index: ${index}. There are ${filePaths.length} files for UUID: ${uuid}`,
20+
);
21+
}
22+
const result = await updateFileAndStep(
23+
filePaths[index],
24+
index,
25+
filePaths.length,
26+
percyWebSetupInstructions,
27+
);
28+
29+
return {
30+
content: result,
31+
};
32+
}

0 commit comments

Comments
 (0)