@browserstack/mcp-server 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/config.js +2 -6
- package/dist/index.js +29 -32
- package/dist/lib/api.js +3 -57
- package/dist/lib/constants.js +14 -0
- package/dist/lib/device-cache.js +32 -33
- package/dist/lib/error.js +3 -6
- package/dist/lib/fuzzy.js +1 -4
- package/dist/lib/inmemory-store.js +1 -0
- package/dist/lib/instrumentation.js +12 -18
- package/dist/lib/local.js +27 -35
- package/dist/lib/utils.js +29 -4
- package/dist/logger.js +4 -9
- package/dist/tools/accessibility.js +51 -21
- package/dist/tools/accessiblity-utils/report-fetcher.js +28 -0
- package/dist/tools/accessiblity-utils/report-parser.js +51 -0
- package/dist/tools/accessiblity-utils/scanner.js +80 -0
- package/dist/tools/appautomate-utils/appautomate.js +95 -0
- package/dist/tools/appautomate.js +116 -0
- package/dist/tools/applive-utils/fuzzy-search.js +3 -6
- package/dist/tools/applive-utils/start-session.js +14 -20
- package/dist/tools/applive-utils/upload-app.js +12 -51
- package/dist/tools/applive.js +18 -25
- package/dist/tools/automate-utils/fetch-screenshots.js +59 -0
- package/dist/tools/automate.js +44 -37
- package/dist/tools/bstack-sdk.js +14 -18
- package/dist/tools/failurelogs-utils/app-automate.js +88 -0
- package/dist/tools/failurelogs-utils/automate.js +97 -0
- package/dist/tools/getFailureLogs.js +173 -0
- package/dist/tools/live-utils/desktop-filter.js +8 -11
- package/dist/tools/live-utils/mobile-filter.js +7 -10
- package/dist/tools/live-utils/start-session.js +17 -23
- package/dist/tools/live-utils/types.js +2 -5
- package/dist/tools/live-utils/version-resolver.js +1 -4
- package/dist/tools/live.js +23 -29
- package/dist/tools/observability.js +12 -19
- package/dist/tools/sdk-utils/constants.js +3 -9
- package/dist/tools/sdk-utils/instructions.js +4 -9
- package/dist/tools/sdk-utils/types.js +1 -2
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +259 -0
- package/dist/tools/testmanagement-utils/TCG-utils/config.js +5 -0
- package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +53 -0
- package/dist/tools/testmanagement-utils/TCG-utils/types.js +8 -0
- package/dist/tools/testmanagement-utils/add-test-result.js +18 -25
- package/dist/tools/testmanagement-utils/create-project-folder.js +21 -28
- package/dist/tools/testmanagement-utils/create-testcase.js +30 -38
- package/dist/tools/testmanagement-utils/create-testrun.js +23 -30
- package/dist/tools/testmanagement-utils/list-testcases.js +16 -23
- package/dist/tools/testmanagement-utils/list-testruns.js +13 -20
- package/dist/tools/testmanagement-utils/testcase-from-file.js +42 -0
- package/dist/tools/testmanagement-utils/update-testrun.js +16 -23
- package/dist/tools/testmanagement-utils/upload-file.js +101 -0
- package/dist/tools/testmanagement.js +98 -61
- package/package.json +13 -7
- package/dist/tools/accessiblity-utils/accessibility.js +0 -82
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import logger from "../logger.js";
|
|
3
|
+
import { retrieveNetworkFailures, retrieveSessionFailures, retrieveConsoleFailures, } from "./failurelogs-utils/automate.js";
|
|
4
|
+
import { retrieveDeviceLogs, retrieveAppiumLogs, retrieveCrashLogs, } from "./failurelogs-utils/app-automate.js";
|
|
5
|
+
import { trackMCP } from "../lib/instrumentation.js";
|
|
6
|
+
import { AppAutomateLogType, AutomateLogType, SessionType, } from "../lib/constants.js";
|
|
7
|
+
// Main log fetcher function
|
|
8
|
+
export async function getFailureLogs(args) {
|
|
9
|
+
const results = [];
|
|
10
|
+
const errors = [];
|
|
11
|
+
let validLogTypes = [];
|
|
12
|
+
if (!args.sessionId) {
|
|
13
|
+
throw new Error("Session ID is required");
|
|
14
|
+
}
|
|
15
|
+
if (args.sessionType === SessionType.AppAutomate && !args.buildId) {
|
|
16
|
+
throw new Error("Build ID is required for app-automate sessions");
|
|
17
|
+
}
|
|
18
|
+
// Validate log types and collect errors
|
|
19
|
+
validLogTypes = args.logTypes.filter((logType) => {
|
|
20
|
+
const isAutomate = Object.values(AutomateLogType).includes(logType);
|
|
21
|
+
const isAppAutomate = Object.values(AppAutomateLogType).includes(logType);
|
|
22
|
+
if (!isAutomate && !isAppAutomate) {
|
|
23
|
+
errors.push(`Invalid log type '${logType}'. Valid log types are: ${[
|
|
24
|
+
...Object.values(AutomateLogType),
|
|
25
|
+
...Object.values(AppAutomateLogType),
|
|
26
|
+
].join(", ")}`);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (args.sessionType === SessionType.Automate && !isAutomate) {
|
|
30
|
+
errors.push(`Log type '${logType}' is only available for app-automate sessions.`);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (args.sessionType === SessionType.AppAutomate && !isAppAutomate) {
|
|
34
|
+
errors.push(`Log type '${logType}' is only available for automate sessions.`);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
});
|
|
39
|
+
if (validLogTypes.length === 0) {
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: `No valid log types found for ${args.sessionType} session.\nErrors encountered:\n${errors.join("\n")}`,
|
|
45
|
+
isError: true,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// eslint-disable-next-line no-useless-catch
|
|
51
|
+
try {
|
|
52
|
+
for (const logType of validLogTypes) {
|
|
53
|
+
switch (logType) {
|
|
54
|
+
case AutomateLogType.NetworkLogs: {
|
|
55
|
+
const logs = await retrieveNetworkFailures(args.sessionId);
|
|
56
|
+
results.push({
|
|
57
|
+
type: "text",
|
|
58
|
+
text: logs.length > 0
|
|
59
|
+
? `Network Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
60
|
+
: "No network failures found",
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case AutomateLogType.SessionLogs: {
|
|
65
|
+
const logs = await retrieveSessionFailures(args.sessionId);
|
|
66
|
+
results.push({
|
|
67
|
+
type: "text",
|
|
68
|
+
text: logs.length > 0
|
|
69
|
+
? `Session Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
70
|
+
: "No session failures found",
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case AutomateLogType.ConsoleLogs: {
|
|
75
|
+
const logs = await retrieveConsoleFailures(args.sessionId);
|
|
76
|
+
results.push({
|
|
77
|
+
type: "text",
|
|
78
|
+
text: logs.length > 0
|
|
79
|
+
? `Console Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
80
|
+
: "No console failures found",
|
|
81
|
+
});
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case AppAutomateLogType.DeviceLogs: {
|
|
85
|
+
const logs = await retrieveDeviceLogs(args.sessionId, args.buildId);
|
|
86
|
+
results.push({
|
|
87
|
+
type: "text",
|
|
88
|
+
text: logs.length > 0
|
|
89
|
+
? `Device Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
90
|
+
: "No device failures found",
|
|
91
|
+
});
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case AppAutomateLogType.AppiumLogs: {
|
|
95
|
+
const logs = await retrieveAppiumLogs(args.sessionId, args.buildId);
|
|
96
|
+
results.push({
|
|
97
|
+
type: "text",
|
|
98
|
+
text: logs.length > 0
|
|
99
|
+
? `Appium Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
100
|
+
: "No Appium failures found",
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case AppAutomateLogType.CrashLogs: {
|
|
105
|
+
const logs = await retrieveCrashLogs(args.sessionId, args.buildId);
|
|
106
|
+
results.push({
|
|
107
|
+
type: "text",
|
|
108
|
+
text: logs.length > 0
|
|
109
|
+
? `Crash Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
110
|
+
: "No crash failures found",
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
results.push({
|
|
122
|
+
type: "text",
|
|
123
|
+
text: `Errors encountered:\n${errors.join("\n")}`,
|
|
124
|
+
isError: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return { content: results };
|
|
128
|
+
}
|
|
129
|
+
// Register tool with the MCP server
|
|
130
|
+
export default function registerGetFailureLogs(server) {
|
|
131
|
+
server.tool("getFailureLogs", "Fetch various types of logs from a BrowserStack session. Supports both automate and app-automate sessions.", {
|
|
132
|
+
sessionType: z
|
|
133
|
+
.enum([SessionType.Automate, SessionType.AppAutomate])
|
|
134
|
+
.describe("Type of BrowserStack session. Must be explicitly provided by the user."),
|
|
135
|
+
sessionId: z
|
|
136
|
+
.string()
|
|
137
|
+
.describe("The BrowserStack session ID. Must be explicitly provided by the user."),
|
|
138
|
+
buildId: z
|
|
139
|
+
.string()
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Required only when sessionType is 'app-automate'. If sessionType is 'app-automate', always ask the user to provide the build ID before proceeding."),
|
|
142
|
+
logTypes: z
|
|
143
|
+
.array(z.enum([
|
|
144
|
+
AutomateLogType.NetworkLogs,
|
|
145
|
+
AutomateLogType.SessionLogs,
|
|
146
|
+
AutomateLogType.ConsoleLogs,
|
|
147
|
+
AppAutomateLogType.DeviceLogs,
|
|
148
|
+
AppAutomateLogType.AppiumLogs,
|
|
149
|
+
AppAutomateLogType.CrashLogs,
|
|
150
|
+
]))
|
|
151
|
+
.describe("The types of logs to fetch."),
|
|
152
|
+
}, async (args) => {
|
|
153
|
+
try {
|
|
154
|
+
trackMCP("getFailureLogs", server.server.getClientVersion());
|
|
155
|
+
return await getFailureLogs(args);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
159
|
+
trackMCP("getFailureLogs", server.server.getClientVersion(), error);
|
|
160
|
+
logger.error("Failed to fetch logs: %s", message);
|
|
161
|
+
return {
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: "text",
|
|
165
|
+
text: `Failed to fetch logs: ${message}`,
|
|
166
|
+
isError: true,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
isError: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const fuzzy_1 = require("../../lib/fuzzy");
|
|
7
|
-
async function filterDesktop(args) {
|
|
8
|
-
const data = await (0, device_cache_1.getDevicesAndBrowsers)("live");
|
|
1
|
+
import { getDevicesAndBrowsers, BrowserStackProducts, } from "../../lib/device-cache.js";
|
|
2
|
+
import { resolveVersion } from "./version-resolver.js";
|
|
3
|
+
import { customFuzzySearch } from "../../lib/fuzzy.js";
|
|
4
|
+
export async function filterDesktop(args) {
|
|
5
|
+
const data = await getDevicesAndBrowsers(BrowserStackProducts.LIVE);
|
|
9
6
|
const allEntries = getAllDesktopEntries(data);
|
|
10
7
|
// Filter OS
|
|
11
8
|
const osList = filterByOS(allEntries, args.os);
|
|
@@ -18,7 +15,7 @@ async function filterDesktop(args) {
|
|
|
18
15
|
const entriesForOS = filterByOSVersion(browserList, chosenOS);
|
|
19
16
|
// Resolve browser version
|
|
20
17
|
const browserVersions = entriesForOS.map((e) => e.browser_version);
|
|
21
|
-
const chosenBrowserVersion =
|
|
18
|
+
const chosenBrowserVersion = resolveVersion(args.browserVersion, browserVersions);
|
|
22
19
|
// Find final entry
|
|
23
20
|
const finalEntry = entriesForOS.find((e) => e.browser_version === chosenBrowserVersion);
|
|
24
21
|
if (!finalEntry) {
|
|
@@ -57,7 +54,7 @@ function resolveOSVersion(os, requestedVersion, availableVersions) {
|
|
|
57
54
|
}
|
|
58
55
|
else {
|
|
59
56
|
// For Windows, use semantic versioning
|
|
60
|
-
return
|
|
57
|
+
return resolveVersion(requestedVersion, availableVersions);
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
function resolveMacOSVersion(requested, available) {
|
|
@@ -69,7 +66,7 @@ function resolveMacOSVersion(requested, available) {
|
|
|
69
66
|
}
|
|
70
67
|
else {
|
|
71
68
|
// Try fuzzy matching
|
|
72
|
-
const fuzzy =
|
|
69
|
+
const fuzzy = customFuzzySearch(available.map((v) => ({ os_version: v })), ["os_version"], requested, 1);
|
|
73
70
|
const matched = fuzzy.length ? fuzzy[0].os_version : requested;
|
|
74
71
|
// Fallback if not valid
|
|
75
72
|
return available.includes(matched) ? matched : available[0];
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const device_cache_1 = require("../../lib/device-cache");
|
|
5
|
-
const version_resolver_1 = require("./version-resolver");
|
|
6
|
-
const fuzzy_1 = require("../../lib/fuzzy");
|
|
1
|
+
import { getDevicesAndBrowsers, BrowserStackProducts, } from "../../lib/device-cache.js";
|
|
2
|
+
import { resolveVersion } from "./version-resolver.js";
|
|
3
|
+
import { customFuzzySearch } from "../../lib/fuzzy.js";
|
|
7
4
|
// Extract all mobile entries from the data
|
|
8
5
|
function getAllMobileEntries(data) {
|
|
9
6
|
return data.mobile.flatMap((grp) => grp.devices.map((d) => ({
|
|
@@ -22,7 +19,7 @@ function filterByOS(entries, os) {
|
|
|
22
19
|
}
|
|
23
20
|
// Find matching device with exact match validation
|
|
24
21
|
function findMatchingDevice(entries, deviceName, os) {
|
|
25
|
-
const matches =
|
|
22
|
+
const matches = customFuzzySearch(entries, ["display_name"], deviceName, 5);
|
|
26
23
|
if (!matches.length)
|
|
27
24
|
throw new Error(`No devices matching "${deviceName}" on ${os}.`);
|
|
28
25
|
const exact = matches.find((m) => m.display_name.toLowerCase() === deviceName.toLowerCase());
|
|
@@ -38,7 +35,7 @@ function findMatchingDevice(entries, deviceName, os) {
|
|
|
38
35
|
// Find the appropriate OS version
|
|
39
36
|
function findOSVersion(entries, requestedVersion) {
|
|
40
37
|
const versions = entries.map((d) => d.os_version);
|
|
41
|
-
const chosenVersion =
|
|
38
|
+
const chosenVersion = resolveVersion(requestedVersion, versions);
|
|
42
39
|
const result = entries.filter((d) => d.os_version === chosenVersion);
|
|
43
40
|
if (!result.length)
|
|
44
41
|
throw new Error(`No entry for OS version "${requestedVersion}".`);
|
|
@@ -53,8 +50,8 @@ function createVersionNote(requestedVersion, actualVersion) {
|
|
|
53
50
|
}
|
|
54
51
|
return "";
|
|
55
52
|
}
|
|
56
|
-
async function filterMobile(args) {
|
|
57
|
-
const data = await (
|
|
53
|
+
export async function filterMobile(args) {
|
|
54
|
+
const data = await getDevicesAndBrowsers(BrowserStackProducts.LIVE);
|
|
58
55
|
const allEntries = getAllMobileEntries(data);
|
|
59
56
|
const osCandidates = filterByOS(allEntries, args.os);
|
|
60
57
|
const deviceCandidates = findMatchingDevice(osCandidates, args.device, args.os);
|
|
@@ -1,37 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const logger_1 = __importDefault(require("../../logger"));
|
|
8
|
-
const child_process_1 = __importDefault(require("child_process"));
|
|
9
|
-
const desktop_filter_1 = require("./desktop-filter");
|
|
10
|
-
const mobile_filter_1 = require("./mobile-filter");
|
|
11
|
-
const types_1 = require("./types");
|
|
12
|
-
const local_1 = require("../../lib/local");
|
|
1
|
+
import logger from "../../logger.js";
|
|
2
|
+
import childProcess from "child_process";
|
|
3
|
+
import { filterDesktop } from "./desktop-filter.js";
|
|
4
|
+
import { filterMobile } from "./mobile-filter.js";
|
|
5
|
+
import { PlatformType, } from "./types.js";
|
|
6
|
+
import { isLocalURL, ensureLocalBinarySetup, killExistingBrowserStackLocalProcesses, } from "../../lib/local.js";
|
|
13
7
|
/**
|
|
14
8
|
* Prepares local tunnel setup based on URL type
|
|
15
9
|
*/
|
|
16
10
|
async function prepareLocalTunnel(url) {
|
|
17
|
-
const isLocal =
|
|
11
|
+
const isLocal = isLocalURL(url);
|
|
18
12
|
if (isLocal) {
|
|
19
|
-
await
|
|
13
|
+
await ensureLocalBinarySetup();
|
|
20
14
|
}
|
|
21
15
|
else {
|
|
22
|
-
await
|
|
16
|
+
await killExistingBrowserStackLocalProcesses();
|
|
23
17
|
}
|
|
24
18
|
return isLocal;
|
|
25
19
|
}
|
|
26
20
|
/**
|
|
27
21
|
* Entrypoint: detects platformType & delegates.
|
|
28
22
|
*/
|
|
29
|
-
async function startBrowserSession(args) {
|
|
30
|
-
const entry = args.platformType ===
|
|
31
|
-
? await
|
|
32
|
-
: await
|
|
23
|
+
export async function startBrowserSession(args) {
|
|
24
|
+
const entry = args.platformType === PlatformType.DESKTOP
|
|
25
|
+
? await filterDesktop(args)
|
|
26
|
+
: await filterMobile(args);
|
|
33
27
|
const isLocal = await prepareLocalTunnel(args.url);
|
|
34
|
-
const url = args.platformType ===
|
|
28
|
+
const url = args.platformType === PlatformType.DESKTOP
|
|
35
29
|
? buildDesktopUrl(args, entry, isLocal)
|
|
36
30
|
: buildMobileUrl(args, entry, isLocal);
|
|
37
31
|
openBrowser(url);
|
|
@@ -81,14 +75,14 @@ function openBrowser(launchUrl) {
|
|
|
81
75
|
? ["cmd", "/c", "start", launchUrl]
|
|
82
76
|
: ["xdg-open", launchUrl];
|
|
83
77
|
// nosemgrep:javascript.lang.security.detect-child-process.detect-child-process
|
|
84
|
-
const child =
|
|
78
|
+
const child = childProcess.spawn(command[0], command.slice(1), {
|
|
85
79
|
stdio: "ignore",
|
|
86
80
|
detached: true,
|
|
87
81
|
});
|
|
88
|
-
child.on("error", (err) =>
|
|
82
|
+
child.on("error", (err) => logger.error(`Failed to open browser: ${err}. URL: ${launchUrl}`));
|
|
89
83
|
child.unref();
|
|
90
84
|
}
|
|
91
85
|
catch (err) {
|
|
92
|
-
|
|
86
|
+
logger.error(`Failed to launch browser: ${err}. URL: ${launchUrl}`);
|
|
93
87
|
}
|
|
94
88
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PlatformType = void 0;
|
|
4
|
-
var PlatformType;
|
|
1
|
+
export var PlatformType;
|
|
5
2
|
(function (PlatformType) {
|
|
6
3
|
PlatformType["DESKTOP"] = "desktop";
|
|
7
4
|
PlatformType["MOBILE"] = "mobile";
|
|
8
|
-
})(PlatformType || (
|
|
5
|
+
})(PlatformType || (PlatformType = {}));
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveVersion = resolveVersion;
|
|
4
1
|
/**
|
|
5
2
|
* If req === "latest" or "oldest", returns max/min numeric (or lex)
|
|
6
3
|
* Else if exact match, returns that
|
|
7
4
|
* Else picks the numerically closest (or first)
|
|
8
5
|
*/
|
|
9
|
-
function resolveVersion(requested, available) {
|
|
6
|
+
export function resolveVersion(requested, available) {
|
|
10
7
|
// strip duplicates & sort
|
|
11
8
|
const uniq = Array.from(new Set(available));
|
|
12
9
|
// pick min/max
|
package/dist/tools/live.js
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
exports.default = addBrowserLiveTools;
|
|
7
|
-
const zod_1 = require("zod");
|
|
8
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
9
|
-
const start_session_1 = require("./live-utils/start-session");
|
|
10
|
-
const types_1 = require("./live-utils/types");
|
|
11
|
-
const instrumentation_1 = require("../lib/instrumentation");
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import logger from "../logger.js";
|
|
3
|
+
import { startBrowserSession } from "./live-utils/start-session.js";
|
|
4
|
+
import { PlatformType } from "./live-utils/types.js";
|
|
5
|
+
import { trackMCP } from "../lib/instrumentation.js";
|
|
12
6
|
// Define the schema shape
|
|
13
7
|
const LiveArgsShape = {
|
|
14
|
-
platformType:
|
|
15
|
-
.nativeEnum(
|
|
8
|
+
platformType: z
|
|
9
|
+
.nativeEnum(PlatformType)
|
|
16
10
|
.describe("Must be 'desktop' or 'mobile'"),
|
|
17
|
-
desiredURL:
|
|
18
|
-
desiredOS:
|
|
11
|
+
desiredURL: z.string().url().describe("The URL to test"),
|
|
12
|
+
desiredOS: z
|
|
19
13
|
.enum(["Windows", "OS X", "android", "ios", "winphone"])
|
|
20
14
|
.describe("Desktop OS ('Windows' or 'OS X') or mobile OS ('android','ios','winphone')"),
|
|
21
|
-
desiredOSVersion:
|
|
15
|
+
desiredOSVersion: z
|
|
22
16
|
.string()
|
|
23
17
|
.describe("The OS version must be specified as a version number (e.g., '10', '14.0') or as a keyword such as 'latest' or 'oldest'. Normalize variations like 'newest' or 'most recent' to 'latest', and terms like 'earliest' or 'first' to 'oldest'. For macOS, version names (e.g., 'Sequoia') must be used instead of numeric versions."),
|
|
24
|
-
desiredBrowser:
|
|
18
|
+
desiredBrowser: z
|
|
25
19
|
.enum(["chrome", "firefox", "safari", "edge", "ie"])
|
|
26
20
|
.describe("Browser for desktop (Chrome, IE, Firefox, Safari, Edge)"),
|
|
27
|
-
desiredBrowserVersion:
|
|
21
|
+
desiredBrowserVersion: z
|
|
28
22
|
.string()
|
|
29
23
|
.optional()
|
|
30
24
|
.describe("Browser version for desktop (e.g. '133.2', 'latest'). If the user says 'latest', 'newest', or similar, normalize it to 'latest'. Likewise, convert terms like 'earliest' or 'oldest' to 'oldest'."),
|
|
31
|
-
desiredDevice:
|
|
25
|
+
desiredDevice: z.string().optional().describe("Device name for mobile"),
|
|
32
26
|
};
|
|
33
|
-
const LiveArgsSchema =
|
|
27
|
+
const LiveArgsSchema = z.object(LiveArgsShape);
|
|
34
28
|
/**
|
|
35
29
|
* Launches a desktop browser session
|
|
36
30
|
*/
|
|
@@ -39,8 +33,8 @@ async function launchDesktopSession(args) {
|
|
|
39
33
|
throw new Error("You must provide a desiredBrowser");
|
|
40
34
|
if (!args.desiredBrowserVersion)
|
|
41
35
|
throw new Error("You must provide a desiredBrowserVersion");
|
|
42
|
-
return
|
|
43
|
-
platformType:
|
|
36
|
+
return startBrowserSession({
|
|
37
|
+
platformType: PlatformType.DESKTOP,
|
|
44
38
|
url: args.desiredURL,
|
|
45
39
|
os: args.desiredOS,
|
|
46
40
|
osVersion: args.desiredOSVersion,
|
|
@@ -54,8 +48,8 @@ async function launchDesktopSession(args) {
|
|
|
54
48
|
async function launchMobileSession(args) {
|
|
55
49
|
if (!args.desiredDevice)
|
|
56
50
|
throw new Error("You must provide a desiredDevice");
|
|
57
|
-
return
|
|
58
|
-
platformType:
|
|
51
|
+
return startBrowserSession({
|
|
52
|
+
platformType: PlatformType.MOBILE,
|
|
59
53
|
browser: args.desiredBrowser,
|
|
60
54
|
url: args.desiredURL,
|
|
61
55
|
os: args.desiredOS,
|
|
@@ -70,7 +64,7 @@ async function runBrowserSession(rawArgs) {
|
|
|
70
64
|
// Validate and narrow
|
|
71
65
|
const args = LiveArgsSchema.parse(rawArgs);
|
|
72
66
|
// Branch desktop vs mobile and delegate
|
|
73
|
-
const launchUrl = args.platformType ===
|
|
67
|
+
const launchUrl = args.platformType === PlatformType.DESKTOP
|
|
74
68
|
? await launchDesktopSession(args)
|
|
75
69
|
: await launchMobileSession(args);
|
|
76
70
|
return {
|
|
@@ -82,15 +76,15 @@ async function runBrowserSession(rawArgs) {
|
|
|
82
76
|
],
|
|
83
77
|
};
|
|
84
78
|
}
|
|
85
|
-
function addBrowserLiveTools(server) {
|
|
79
|
+
export default function addBrowserLiveTools(server) {
|
|
86
80
|
server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
|
|
87
81
|
try {
|
|
88
|
-
|
|
82
|
+
trackMCP("runBrowserLiveSession", server.server.getClientVersion());
|
|
89
83
|
return await runBrowserSession(args);
|
|
90
84
|
}
|
|
91
85
|
catch (error) {
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
logger.error("Live session failed: %s", error);
|
|
87
|
+
trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error);
|
|
94
88
|
return {
|
|
95
89
|
content: [
|
|
96
90
|
{
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
exports.default = addObservabilityTools;
|
|
8
|
-
const zod_1 = require("zod");
|
|
9
|
-
const api_1 = require("../lib/api");
|
|
10
|
-
const instrumentation_1 = require("../lib/instrumentation");
|
|
11
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
12
|
-
async function getFailuresInLastRun(buildName, projectName) {
|
|
13
|
-
const buildsData = await (0, api_1.getLatestO11YBuildInfo)(buildName, projectName);
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getLatestO11YBuildInfo } from "../lib/api.js";
|
|
3
|
+
import { trackMCP } from "../lib/instrumentation.js";
|
|
4
|
+
import logger from "../logger.js";
|
|
5
|
+
export async function getFailuresInLastRun(buildName, projectName) {
|
|
6
|
+
const buildsData = await getLatestO11YBuildInfo(buildName, projectName);
|
|
14
7
|
const observabilityUrl = buildsData.observability_url;
|
|
15
8
|
if (!observabilityUrl) {
|
|
16
9
|
throw new Error("No observability URL found in build data, this is likely because the build is not yet available on BrowserStack Observability.");
|
|
@@ -35,22 +28,22 @@ async function getFailuresInLastRun(buildName, projectName) {
|
|
|
35
28
|
],
|
|
36
29
|
};
|
|
37
30
|
}
|
|
38
|
-
function addObservabilityTools(server) {
|
|
31
|
+
export default function addObservabilityTools(server) {
|
|
39
32
|
server.tool("getFailuresInLastRun", "Use this tool to debug failures in the last run of the test suite on BrowserStack. Use only when browserstack.yml file is present in the project root.", {
|
|
40
|
-
buildName:
|
|
33
|
+
buildName: z
|
|
41
34
|
.string()
|
|
42
35
|
.describe("Name of the build to get failures for. This is the 'build' key in the browserstack.yml file. If not sure, ask the user for the build name."),
|
|
43
|
-
projectName:
|
|
36
|
+
projectName: z
|
|
44
37
|
.string()
|
|
45
38
|
.describe("Name of the project to get failures for. This is the 'projectName' key in the browserstack.yml file. If not sure, ask the user for the project name."),
|
|
46
39
|
}, async (args) => {
|
|
47
40
|
try {
|
|
48
|
-
|
|
41
|
+
trackMCP("getFailuresInLastRun", server.server.getClientVersion());
|
|
49
42
|
return await getFailuresInLastRun(args.buildName, args.projectName);
|
|
50
43
|
}
|
|
51
44
|
catch (error) {
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
logger.error("Failed to get failures in the last run: %s", error);
|
|
46
|
+
trackMCP("getFailuresInLastRun", server.server.getClientVersion(), error);
|
|
54
47
|
return {
|
|
55
48
|
content: [
|
|
56
49
|
{
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SUPPORTED_CONFIGURATIONS = void 0;
|
|
7
|
-
const config_1 = __importDefault(require("../../config"));
|
|
1
|
+
import config from "../../config.js";
|
|
8
2
|
const nodejsInstructions = `
|
|
9
3
|
- Ensure that \`browserstack-node-sdk\` is present in package.json, use the latest version.
|
|
10
4
|
- Add new scripts to package.json for running tests on BrowserStack (use \`npx\` to trigger the sdk):
|
|
@@ -27,7 +21,7 @@ python3 -m pip install browserstack-sdk
|
|
|
27
21
|
|
|
28
22
|
Run the following command to setup the browserstack-sdk:
|
|
29
23
|
\`\`\`bash
|
|
30
|
-
browserstack-sdk setup --username "${
|
|
24
|
+
browserstack-sdk setup --username "${config.browserstackUsername}" --key "${config.browserstackAccessKey}"
|
|
31
25
|
\`\`\`
|
|
32
26
|
|
|
33
27
|
In order to run tests on BrowserStack, run the following command:
|
|
@@ -35,7 +29,7 @@ In order to run tests on BrowserStack, run the following command:
|
|
|
35
29
|
browserstack-sdk python <path-to-test-file>
|
|
36
30
|
\`\`\`
|
|
37
31
|
`;
|
|
38
|
-
|
|
32
|
+
export const SUPPORTED_CONFIGURATIONS = {
|
|
39
33
|
nodejs: {
|
|
40
34
|
playwright: {
|
|
41
35
|
jest: { instructions: nodejsInstructions },
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getInstructionsForProjectConfiguration = void 0;
|
|
4
|
-
exports.generateBrowserStackYMLInstructions = generateBrowserStackYMLInstructions;
|
|
5
|
-
const constants_1 = require("./constants");
|
|
1
|
+
import { SUPPORTED_CONFIGURATIONS } from "./constants.js";
|
|
6
2
|
const errorMessageSuffix = "Please open an issue at our Github repo: https://github.com/browserstack/browserstack-mcp-server/issues to request support for your project configuration";
|
|
7
|
-
const getInstructionsForProjectConfiguration = (detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage) => {
|
|
8
|
-
const configuration =
|
|
3
|
+
export const getInstructionsForProjectConfiguration = (detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage) => {
|
|
4
|
+
const configuration = SUPPORTED_CONFIGURATIONS[detectedLanguage];
|
|
9
5
|
if (!configuration) {
|
|
10
6
|
throw new Error(`BrowserStack MCP Server currently does not support ${detectedLanguage}, ${errorMessageSuffix}`);
|
|
11
7
|
}
|
|
@@ -17,8 +13,7 @@ const getInstructionsForProjectConfiguration = (detectedBrowserAutomationFramewo
|
|
|
17
13
|
}
|
|
18
14
|
return configuration[detectedBrowserAutomationFramework][detectedTestingFramework].instructions;
|
|
19
15
|
};
|
|
20
|
-
|
|
21
|
-
function generateBrowserStackYMLInstructions(desiredPlatforms) {
|
|
16
|
+
export function generateBrowserStackYMLInstructions(desiredPlatforms) {
|
|
22
17
|
return `
|
|
23
18
|
Create a browserstack.yml file in the project root. The file should be in the following format:
|
|
24
19
|
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|