@browserstack/mcp-server 1.1.9 → 1.2.1
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 +4 -1
- package/dist/config.d.ts +13 -0
- package/dist/config.js +4 -6
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -31
- package/dist/lib/api.d.ts +2 -0
- package/dist/lib/api.js +10 -5
- package/dist/lib/apiClient.d.ts +31 -0
- package/dist/lib/apiClient.js +108 -0
- package/dist/lib/constants.d.ts +17 -0
- package/dist/lib/device-cache.d.ts +9 -0
- package/dist/lib/device-cache.js +3 -3
- package/dist/lib/error.d.ts +7 -0
- package/dist/lib/fuzzy.d.ts +1 -0
- package/dist/lib/get-auth.d.ts +2 -0
- package/dist/lib/get-auth.js +8 -0
- package/dist/lib/inmemory-store.d.ts +1 -0
- package/dist/lib/instrumentation.d.ts +4 -0
- package/dist/lib/instrumentation.js +8 -7
- package/dist/lib/local.d.ts +3 -0
- package/dist/lib/local.js +3 -3
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/utils.d.ts +4 -0
- package/dist/lib/version-resolver.d.ts +6 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +20 -4
- package/dist/oninitialized.d.ts +2 -0
- package/dist/oninitialized.js +2 -7
- package/dist/server-factory.d.ts +25 -0
- package/dist/server-factory.js +70 -0
- package/dist/tools/accessibility.d.ts +3 -0
- package/dist/tools/accessibility.js +21 -12
- package/dist/tools/accessiblity-utils/accessibility-rag.d.ts +12 -0
- package/dist/tools/accessiblity-utils/accessibility-rag.js +22 -12
- package/dist/tools/accessiblity-utils/report-fetcher.d.ts +8 -0
- package/dist/tools/accessiblity-utils/report-fetcher.js +26 -16
- package/dist/tools/accessiblity-utils/report-parser.d.ts +23 -0
- package/dist/tools/accessiblity-utils/report-parser.js +3 -3
- package/dist/tools/accessiblity-utils/scanner.d.ts +25 -0
- package/dist/tools/accessiblity-utils/scanner.js +43 -24
- package/dist/tools/appautomate-utils/appautomate.d.ts +42 -0
- package/dist/tools/appautomate-utils/appautomate.js +63 -38
- package/dist/tools/appautomate-utils/types.d.ts +5 -0
- package/dist/tools/appautomate.d.ts +3 -0
- package/dist/tools/appautomate.js +24 -21
- package/dist/tools/applive-utils/device-search.d.ts +6 -0
- package/dist/tools/applive-utils/start-session.d.ts +15 -0
- package/dist/tools/applive-utils/start-session.js +11 -4
- package/dist/tools/applive-utils/types.d.ts +7 -0
- package/dist/tools/applive-utils/upload-app.d.ts +5 -0
- package/dist/tools/applive-utils/upload-app.js +8 -12
- package/dist/tools/applive-utils/version-utils.d.ts +4 -0
- package/dist/tools/applive.d.ts +13 -0
- package/dist/tools/applive.js +9 -7
- package/dist/tools/automate-utils/fetch-screenshots.d.ts +6 -0
- package/dist/tools/automate-utils/fetch-screenshots.js +16 -12
- package/dist/tools/automate.d.ts +9 -0
- package/dist/tools/automate.js +9 -7
- package/dist/tools/bstack-sdk.d.ts +17 -0
- package/dist/tools/bstack-sdk.js +15 -8
- package/dist/tools/failurelogs-utils/app-automate.d.ts +7 -0
- package/dist/tools/failurelogs-utils/app-automate.js +29 -11
- package/dist/tools/failurelogs-utils/automate.d.ts +6 -0
- package/dist/tools/failurelogs-utils/automate.js +27 -12
- package/dist/tools/failurelogs-utils/utils.d.ts +30 -0
- package/dist/tools/getFailureLogs.d.ts +14 -0
- package/dist/tools/getFailureLogs.js +15 -16
- package/dist/tools/live-utils/desktop-filter.d.ts +2 -0
- package/dist/tools/live-utils/mobile-filter.d.ts +2 -0
- package/dist/tools/live-utils/start-session.d.ts +6 -0
- package/dist/tools/live-utils/start-session.js +18 -5
- package/dist/tools/live-utils/types.d.ts +33 -0
- package/dist/tools/live.d.ts +3 -0
- package/dist/tools/live.js +28 -16
- package/dist/tools/observability.d.ts +5 -0
- package/dist/tools/observability.js +14 -11
- package/dist/tools/sdk-utils/commands.d.ts +3 -0
- package/dist/tools/sdk-utils/commands.js +5 -4
- package/dist/tools/sdk-utils/constants.d.ts +2 -0
- package/dist/tools/sdk-utils/constants.js +82 -21
- package/dist/tools/sdk-utils/instructions.d.ts +6 -0
- package/dist/tools/sdk-utils/instructions.js +8 -6
- package/dist/tools/sdk-utils/percy/constants.d.ts +3 -0
- package/dist/tools/sdk-utils/percy/constants.js +1 -0
- package/dist/tools/sdk-utils/percy/instructions.d.ts +10 -0
- package/dist/tools/sdk-utils/percy/types.d.ts +5 -0
- package/dist/tools/sdk-utils/types.d.ts +39 -0
- package/dist/tools/sdk-utils/types.js +1 -0
- package/dist/tools/selfheal-utils/selfheal.d.ts +11 -0
- package/dist/tools/selfheal-utils/selfheal.js +10 -6
- package/dist/tools/selfheal.d.ts +7 -0
- package/dist/tools/selfheal.js +9 -7
- package/dist/tools/testmanagement-utils/TCG-utils/api.d.ts +34 -0
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +57 -44
- package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +5 -0
- package/dist/tools/testmanagement-utils/TCG-utils/helpers.d.ts +13 -0
- package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +2 -1
- package/dist/tools/testmanagement-utils/TCG-utils/types.d.ts +26 -0
- package/dist/tools/testmanagement-utils/add-test-result.d.ts +42 -0
- package/dist/tools/testmanagement-utils/add-test-result.js +23 -10
- package/dist/tools/testmanagement-utils/create-lca-steps.d.ts +100 -0
- package/dist/tools/testmanagement-utils/create-lca-steps.js +64 -55
- package/dist/tools/testmanagement-utils/create-project-folder.d.ts +31 -0
- package/dist/tools/testmanagement-utils/create-project-folder.js +31 -21
- package/dist/tools/testmanagement-utils/create-testcase.d.ts +122 -0
- package/dist/tools/testmanagement-utils/create-testcase.js +13 -10
- package/dist/tools/testmanagement-utils/create-testrun.d.ts +82 -0
- package/dist/tools/testmanagement-utils/create-testrun.js +11 -8
- package/dist/tools/testmanagement-utils/list-testcases.d.ts +30 -0
- package/dist/tools/testmanagement-utils/list-testcases.js +9 -7
- package/dist/tools/testmanagement-utils/list-testruns.d.ts +22 -0
- package/dist/tools/testmanagement-utils/list-testruns.js +9 -7
- package/dist/tools/testmanagement-utils/poll-lca-status.d.ts +11 -0
- package/dist/tools/testmanagement-utils/poll-lca-status.js +12 -8
- package/dist/tools/testmanagement-utils/testcase-from-file.d.ts +4 -0
- package/dist/tools/testmanagement-utils/testcase-from-file.js +6 -6
- package/dist/tools/testmanagement-utils/update-testrun.d.ts +40 -0
- package/dist/tools/testmanagement-utils/update-testrun.js +11 -7
- package/dist/tools/testmanagement-utils/upload-file.d.ts +20 -0
- package/dist/tools/testmanagement-utils/upload-file.js +8 -6
- package/dist/tools/testmanagement.d.ts +60 -0
- package/dist/tools/testmanagement.js +53 -53
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import logger from "../logger.js";
|
|
3
|
-
import
|
|
3
|
+
import { getBrowserStackAuth } from "../lib/get-auth.js";
|
|
4
4
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
5
5
|
import { maybeCompressBase64 } from "../lib/utils.js";
|
|
6
6
|
import { remote } from "webdriverio";
|
|
@@ -19,7 +19,7 @@ async function takeAppScreenshot(args) {
|
|
|
19
19
|
let driver;
|
|
20
20
|
try {
|
|
21
21
|
validateArgs(args);
|
|
22
|
-
const { desiredPlatform, desiredPhone, appPath } = args;
|
|
22
|
+
const { desiredPlatform, desiredPhone, appPath, config } = args;
|
|
23
23
|
let { desiredPlatformVersion } = args;
|
|
24
24
|
const platforms = (await getDevicesAndBrowsers(BrowserStackProducts.APP_AUTOMATE)).mobile;
|
|
25
25
|
const platformData = platforms.find((p) => p.os === desiredPlatform.toLowerCase());
|
|
@@ -33,7 +33,9 @@ async function takeAppScreenshot(args) {
|
|
|
33
33
|
if (!selectedDevice) {
|
|
34
34
|
throw new Error(`Device "${desiredPhone}" with version ${desiredPlatformVersion} not found.`);
|
|
35
35
|
}
|
|
36
|
-
const
|
|
36
|
+
const authString = getBrowserStackAuth(config);
|
|
37
|
+
const [username, password] = authString.split(":");
|
|
38
|
+
const app_url = await uploadApp(appPath, username, password);
|
|
37
39
|
logger.info(`App uploaded. URL: ${app_url}`);
|
|
38
40
|
const capabilities = {
|
|
39
41
|
platformName: desiredPlatform,
|
|
@@ -42,8 +44,8 @@ async function takeAppScreenshot(args) {
|
|
|
42
44
|
"appium:app": app_url,
|
|
43
45
|
"appium:autoGrantPermissions": true,
|
|
44
46
|
"bstack:options": {
|
|
45
|
-
userName:
|
|
46
|
-
accessKey:
|
|
47
|
+
userName: username,
|
|
48
|
+
accessKey: password,
|
|
47
49
|
appiumVersion: "2.0.1",
|
|
48
50
|
},
|
|
49
51
|
};
|
|
@@ -86,12 +88,12 @@ async function takeAppScreenshot(args) {
|
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
//Runs AppAutomate tests on BrowserStack by uploading app and test suite, then triggering a test run.
|
|
89
|
-
async function runAppTestsOnBrowserStack(args) {
|
|
91
|
+
async function runAppTestsOnBrowserStack(args, config) {
|
|
90
92
|
switch (args.detectedAutomationFramework) {
|
|
91
93
|
case AppTestPlatform.ESPRESSO: {
|
|
92
94
|
try {
|
|
93
|
-
const app_url = await uploadEspressoApp(args.appPath);
|
|
94
|
-
const test_suite_url = await uploadEspressoTestSuite(args.testSuitePath);
|
|
95
|
+
const app_url = await uploadEspressoApp(args.appPath, config);
|
|
96
|
+
const test_suite_url = await uploadEspressoTestSuite(args.testSuitePath, config);
|
|
95
97
|
const build_id = await triggerEspressoBuild(app_url, test_suite_url, args.devices, args.project);
|
|
96
98
|
return {
|
|
97
99
|
content: [
|
|
@@ -109,9 +111,9 @@ async function runAppTestsOnBrowserStack(args) {
|
|
|
109
111
|
}
|
|
110
112
|
case AppTestPlatform.XCUITEST: {
|
|
111
113
|
try {
|
|
112
|
-
const app_url = await uploadXcuiApp(args.appPath);
|
|
113
|
-
const test_suite_url = await uploadXcuiTestSuite(args.testSuitePath);
|
|
114
|
-
const build_id = await triggerXcuiBuild(app_url, test_suite_url, args.devices, args.project);
|
|
114
|
+
const app_url = await uploadXcuiApp(args.appPath, config);
|
|
115
|
+
const test_suite_url = await uploadXcuiTestSuite(args.testSuitePath, config);
|
|
116
|
+
const build_id = await triggerXcuiBuild(app_url, test_suite_url, args.devices, args.project, config);
|
|
115
117
|
return {
|
|
116
118
|
content: [
|
|
117
119
|
{
|
|
@@ -130,9 +132,9 @@ async function runAppTestsOnBrowserStack(args) {
|
|
|
130
132
|
throw new Error(`Unsupported automation framework: ${args.detectedAutomationFramework}. If you need support for this framework, please open an issue at Github`);
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
server.tool("takeAppScreenshot", "Use this tool to take a screenshot of an app running on a BrowserStack device. This is useful for visual testing and debugging.", {
|
|
135
|
+
export default function addAppAutomationTools(server, config) {
|
|
136
|
+
const tools = {};
|
|
137
|
+
tools.takeAppScreenshot = server.tool("takeAppScreenshot", "Use this tool to take a screenshot of an app running on a BrowserStack device. This is useful for visual testing and debugging.", {
|
|
136
138
|
desiredPhone: z
|
|
137
139
|
.string()
|
|
138
140
|
.describe("The full name of the device to run the app on. Example: 'iPhone 12 Pro' or 'Samsung Galaxy S20'. Always ask the user for the device they want to use."),
|
|
@@ -147,11 +149,11 @@ export default function addAppAutomationTools(server) {
|
|
|
147
149
|
.describe("The path to the .apk or .ipa file. Required for app installation."),
|
|
148
150
|
}, async (args) => {
|
|
149
151
|
try {
|
|
150
|
-
trackMCP("takeAppScreenshot", server.server.getClientVersion());
|
|
151
|
-
return await takeAppScreenshot(args);
|
|
152
|
+
trackMCP("takeAppScreenshot", server.server.getClientVersion(), undefined, config);
|
|
153
|
+
return await takeAppScreenshot({ ...args, config });
|
|
152
154
|
}
|
|
153
155
|
catch (error) {
|
|
154
|
-
trackMCP("takeAppScreenshot", server.server.getClientVersion(), error);
|
|
156
|
+
trackMCP("takeAppScreenshot", server.server.getClientVersion(), error, config);
|
|
155
157
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
156
158
|
return {
|
|
157
159
|
content: [
|
|
@@ -163,7 +165,7 @@ export default function addAppAutomationTools(server) {
|
|
|
163
165
|
};
|
|
164
166
|
}
|
|
165
167
|
});
|
|
166
|
-
server.tool("runAppTestsOnBrowserStack", "Run AppAutomate tests on BrowserStack by uploading app and test suite. If running from Android Studio or Xcode, the tool will help export app and test files automatically. For other environments, you'll need to provide the paths to your pre-built app and test files.", {
|
|
168
|
+
tools.runAppTestsOnBrowserStack = server.tool("runAppTestsOnBrowserStack", "Run AppAutomate tests on BrowserStack by uploading app and test suite. If running from Android Studio or Xcode, the tool will help export app and test files automatically. For other environments, you'll need to provide the paths to your pre-built app and test files.", {
|
|
167
169
|
appPath: z
|
|
168
170
|
.string()
|
|
169
171
|
.describe("Path to your application file:\n" +
|
|
@@ -197,11 +199,11 @@ export default function addAppAutomationTools(server) {
|
|
|
197
199
|
.describe("The automation framework used in the project, such as 'espresso' (Android) or 'xcuitest' (iOS)."),
|
|
198
200
|
}, async (args) => {
|
|
199
201
|
try {
|
|
200
|
-
trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion());
|
|
201
|
-
return await runAppTestsOnBrowserStack(args);
|
|
202
|
+
trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
|
|
203
|
+
return await runAppTestsOnBrowserStack(args, config);
|
|
202
204
|
}
|
|
203
205
|
catch (error) {
|
|
204
|
-
trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion(), error);
|
|
206
|
+
trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion(), error, config);
|
|
205
207
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
206
208
|
return {
|
|
207
209
|
content: [
|
|
@@ -214,4 +216,5 @@ export default function addAppAutomationTools(server) {
|
|
|
214
216
|
};
|
|
215
217
|
}
|
|
216
218
|
});
|
|
219
|
+
return tools;
|
|
217
220
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
2
|
+
interface StartSessionArgs {
|
|
3
|
+
appPath: string;
|
|
4
|
+
desiredPlatform: "android" | "ios";
|
|
5
|
+
desiredPhone: string;
|
|
6
|
+
desiredPlatformVersion: string;
|
|
7
|
+
}
|
|
8
|
+
interface StartSessionOptions {
|
|
9
|
+
config: BrowserStackConfig;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Start an App Live session: filter, select, upload, and open.
|
|
13
|
+
*/
|
|
14
|
+
export declare function startSession(args: StartSessionArgs, options: StartSessionOptions): Promise<string>;
|
|
15
|
+
export {};
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import logger from "../../logger.js";
|
|
2
|
-
import childProcess from "child_process";
|
|
3
2
|
import { getDevicesAndBrowsers, BrowserStackProducts, } from "../../lib/device-cache.js";
|
|
4
3
|
import { sanitizeUrlParam } from "../../lib/utils.js";
|
|
5
4
|
import { uploadApp } from "./upload-app.js";
|
|
5
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
6
6
|
import { findDeviceByName } from "./device-search.js";
|
|
7
7
|
import { pickVersion } from "./version-utils.js";
|
|
8
|
+
import childProcess from "child_process";
|
|
9
|
+
import envConfig from "../../config.js";
|
|
8
10
|
/**
|
|
9
11
|
* Start an App Live session: filter, select, upload, and open.
|
|
10
12
|
*/
|
|
11
|
-
export async function startSession(args) {
|
|
13
|
+
export async function startSession(args, options) {
|
|
12
14
|
const { appPath, desiredPlatform, desiredPhone, desiredPlatformVersion } = args;
|
|
15
|
+
const { config } = options;
|
|
13
16
|
// 1) Fetch devices for APP_LIVE
|
|
14
17
|
const data = await getDevicesAndBrowsers(BrowserStackProducts.APP_LIVE);
|
|
15
18
|
const all = data.mobile.flatMap((grp) => grp.devices.map((dev) => ({ ...dev, os: grp.os })));
|
|
@@ -36,7 +39,9 @@ export async function startSession(args) {
|
|
|
36
39
|
note = `\n Note: The requested version "${desiredPlatformVersion}" is not available. Using "${version}" instead.`;
|
|
37
40
|
}
|
|
38
41
|
// 6) Upload app
|
|
39
|
-
const
|
|
42
|
+
const authString = getBrowserStackAuth(config);
|
|
43
|
+
const [username, password] = authString.split(":");
|
|
44
|
+
const { app_url } = await uploadApp(appPath, username, password);
|
|
40
45
|
logger.info(`App uploaded: ${app_url}`);
|
|
41
46
|
if (!app_url) {
|
|
42
47
|
throw new Error("Failed to upload app. Please try again.");
|
|
@@ -52,7 +57,9 @@ export async function startSession(args) {
|
|
|
52
57
|
start: "true",
|
|
53
58
|
});
|
|
54
59
|
const launchUrl = `https://app-live.browserstack.com/dashboard#${params.toString()}&device=${deviceParam}`;
|
|
55
|
-
|
|
60
|
+
if (!envConfig.REMOTE_MCP) {
|
|
61
|
+
openBrowser(launchUrl);
|
|
62
|
+
}
|
|
56
63
|
return launchUrl + note;
|
|
57
64
|
}
|
|
58
65
|
/**
|
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
2
2
|
import FormData from "form-data";
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
|
|
5
|
-
export async function uploadApp(filePath) {
|
|
4
|
+
export async function uploadApp(filePath, username, password) {
|
|
6
5
|
if (!fs.existsSync(filePath)) {
|
|
7
6
|
throw new Error(`File not found at path: ${filePath}`);
|
|
8
7
|
}
|
|
9
8
|
const formData = new FormData();
|
|
10
9
|
formData.append("file", fs.createReadStream(filePath));
|
|
11
10
|
try {
|
|
12
|
-
const response = await
|
|
11
|
+
const response = await apiClient.post({
|
|
12
|
+
url: "https://api-cloud.browserstack.com/app-live/upload",
|
|
13
13
|
headers: {
|
|
14
14
|
...formData.getHeaders(),
|
|
15
|
+
Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
|
|
15
16
|
},
|
|
16
|
-
|
|
17
|
-
username: config.browserstackUsername,
|
|
18
|
-
password: config.browserstackAccessKey,
|
|
19
|
-
},
|
|
17
|
+
body: formData,
|
|
20
18
|
});
|
|
21
19
|
return response.data;
|
|
22
20
|
}
|
|
23
21
|
catch (error) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
throw error;
|
|
22
|
+
const msg = error?.response?.data?.message || error?.message || String(error);
|
|
23
|
+
throw new Error(`Failed to upload app: ${msg}`);
|
|
28
24
|
}
|
|
29
25
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { BrowserStackConfig } from "../lib/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Launches an App Live Session on BrowserStack.
|
|
6
|
+
*/
|
|
7
|
+
export declare function startAppLiveSession(args: {
|
|
8
|
+
desiredPlatform: string;
|
|
9
|
+
desiredPlatformVersion: string;
|
|
10
|
+
appPath: string;
|
|
11
|
+
desiredPhone: string;
|
|
12
|
+
}, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
13
|
+
export default function addAppLiveTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
package/dist/tools/applive.js
CHANGED
|
@@ -6,7 +6,7 @@ import { trackMCP } from "../lib/instrumentation.js";
|
|
|
6
6
|
/**
|
|
7
7
|
* Launches an App Live Session on BrowserStack.
|
|
8
8
|
*/
|
|
9
|
-
export async function startAppLiveSession(args) {
|
|
9
|
+
export async function startAppLiveSession(args, config) {
|
|
10
10
|
if (!args.desiredPlatform) {
|
|
11
11
|
throw new Error("You must provide a desiredPlatform.");
|
|
12
12
|
}
|
|
@@ -38,7 +38,7 @@ export async function startAppLiveSession(args) {
|
|
|
38
38
|
desiredPlatform: args.desiredPlatform,
|
|
39
39
|
desiredPhone: args.desiredPhone,
|
|
40
40
|
desiredPlatformVersion: args.desiredPlatformVersion,
|
|
41
|
-
});
|
|
41
|
+
}, { config });
|
|
42
42
|
return {
|
|
43
43
|
content: [
|
|
44
44
|
{
|
|
@@ -48,8 +48,9 @@ export async function startAppLiveSession(args) {
|
|
|
48
48
|
],
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
-
export default function addAppLiveTools(server) {
|
|
52
|
-
|
|
51
|
+
export default function addAppLiveTools(server, config) {
|
|
52
|
+
const tools = {};
|
|
53
|
+
tools.runAppLiveSession = server.tool("runAppLiveSession", "Use this tool when user wants to manually check their app on a particular mobile device using BrowserStack's cloud infrastructure. Can be used to debug crashes, slow performance, etc.", {
|
|
53
54
|
desiredPhone: z
|
|
54
55
|
.string()
|
|
55
56
|
.describe("The full name of the device to run the app on. Example: 'iPhone 12 Pro' or 'Samsung Galaxy S20' or 'Google Pixel 6'. Always ask the user for the device they want to use, do not assume it. "),
|
|
@@ -64,12 +65,12 @@ export default function addAppLiveTools(server) {
|
|
|
64
65
|
.describe("The path to the .ipa or .apk file to install on the device. Always ask the user for the app path, do not assume it."),
|
|
65
66
|
}, async (args) => {
|
|
66
67
|
try {
|
|
67
|
-
trackMCP("runAppLiveSession", server.server.getClientVersion());
|
|
68
|
-
return await startAppLiveSession(args);
|
|
68
|
+
trackMCP("runAppLiveSession", server.server.getClientVersion(), undefined, config);
|
|
69
|
+
return await startAppLiveSession(args, config);
|
|
69
70
|
}
|
|
70
71
|
catch (error) {
|
|
71
72
|
logger.error("App live session failed: %s", error);
|
|
72
|
-
trackMCP("runAppLiveSession", server.server.getClientVersion(), error);
|
|
73
|
+
trackMCP("runAppLiveSession", server.server.getClientVersion(), error, config);
|
|
73
74
|
return {
|
|
74
75
|
content: [
|
|
75
76
|
{
|
|
@@ -82,4 +83,5 @@ export default function addAppLiveTools(server) {
|
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
85
|
});
|
|
86
|
+
return tools;
|
|
85
87
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { SessionType } from "../../lib/constants.js";
|
|
2
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
3
|
+
export declare function fetchAutomationScreenshots(sessionId: string, sessionType: SessionType | undefined, config: BrowserStackConfig): Promise<{
|
|
4
|
+
url: string;
|
|
5
|
+
base64: string;
|
|
6
|
+
}[]>;
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import config from "../../config.js";
|
|
2
1
|
import { assertOkResponse, maybeCompressBase64 } from "../../lib/utils.js";
|
|
3
2
|
import { SessionType } from "../../lib/constants.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
3
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
4
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
5
|
+
async function extractScreenshotUrls(sessionId, sessionType, config) {
|
|
6
|
+
const authString = getBrowserStackAuth(config);
|
|
7
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
8
8
|
const baseUrl = `https://api.browserstack.com/${sessionType === SessionType.Automate ? "automate" : "app-automate"}`;
|
|
9
9
|
const url = `${baseUrl}/sessions/${sessionId}/logs`;
|
|
10
|
-
const response = await
|
|
10
|
+
const response = await apiClient.get({
|
|
11
|
+
url,
|
|
11
12
|
headers: {
|
|
12
13
|
"Content-Type": "application/json",
|
|
13
14
|
Authorization: `Basic ${auth}`,
|
|
14
15
|
},
|
|
16
|
+
raise_error: false,
|
|
15
17
|
});
|
|
16
18
|
await assertOkResponse(response, "Session");
|
|
17
|
-
const text =
|
|
19
|
+
const text = typeof response.data === "string"
|
|
20
|
+
? response.data
|
|
21
|
+
: JSON.stringify(response.data);
|
|
18
22
|
const urls = [];
|
|
19
23
|
const SCREENSHOT_PATTERN = /REQUEST.*GET.*\/screenshot/;
|
|
20
24
|
const RESPONSE_VALUE_PATTERN = /"value"\s*:\s*"([^"]+)"/;
|
|
@@ -35,9 +39,9 @@ async function extractScreenshotUrls(sessionId, sessionType) {
|
|
|
35
39
|
//Converts screenshot URLs to base64 encoded images
|
|
36
40
|
async function convertUrlsToBase64(urls) {
|
|
37
41
|
const screenshots = await Promise.all(urls.map(async (url) => {
|
|
38
|
-
const response = await
|
|
39
|
-
|
|
40
|
-
const base64 = Buffer.from(
|
|
42
|
+
const response = await apiClient.get({ url });
|
|
43
|
+
// Axios returns response.data as a Buffer for binary data
|
|
44
|
+
const base64 = Buffer.from(response.data).toString("base64");
|
|
41
45
|
// Compress the base64 image if needed
|
|
42
46
|
const compressedBase64 = await maybeCompressBase64(base64);
|
|
43
47
|
return {
|
|
@@ -48,8 +52,8 @@ async function convertUrlsToBase64(urls) {
|
|
|
48
52
|
return screenshots;
|
|
49
53
|
}
|
|
50
54
|
//Fetches and converts screenshot URLs to base64 encoded images
|
|
51
|
-
export async function fetchAutomationScreenshots(sessionId, sessionType = SessionType.Automate) {
|
|
52
|
-
const urls = await extractScreenshotUrls(sessionId, sessionType);
|
|
55
|
+
export async function fetchAutomationScreenshots(sessionId, sessionType = SessionType.Automate, config) {
|
|
56
|
+
const urls = await extractScreenshotUrls(sessionId, sessionType, config);
|
|
53
57
|
if (urls.length === 0) {
|
|
54
58
|
return [];
|
|
55
59
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { SessionType } from "../lib/constants.js";
|
|
4
|
+
import { BrowserStackConfig } from "../lib/types.js";
|
|
5
|
+
export declare function fetchAutomationScreenshotsTool(args: {
|
|
6
|
+
sessionId: string;
|
|
7
|
+
sessionType: SessionType;
|
|
8
|
+
}, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
9
|
+
export default function addAutomationTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
package/dist/tools/automate.js
CHANGED
|
@@ -4,9 +4,9 @@ import { SessionType } from "../lib/constants.js";
|
|
|
4
4
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
5
5
|
import logger from "../logger.js";
|
|
6
6
|
// Tool function that fetches and processes screenshots from BrowserStack Automate session
|
|
7
|
-
export async function fetchAutomationScreenshotsTool(args) {
|
|
7
|
+
export async function fetchAutomationScreenshotsTool(args, config) {
|
|
8
8
|
try {
|
|
9
|
-
const screenshots = await fetchAutomationScreenshots(args.sessionId, args.sessionType);
|
|
9
|
+
const screenshots = await fetchAutomationScreenshots(args.sessionId, args.sessionType, config);
|
|
10
10
|
if (screenshots.length === 0) {
|
|
11
11
|
return {
|
|
12
12
|
content: [
|
|
@@ -41,8 +41,9 @@ export async function fetchAutomationScreenshotsTool(args) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
//Registers the fetchAutomationScreenshots tool with the MCP server
|
|
44
|
-
export default function addAutomationTools(server) {
|
|
45
|
-
|
|
44
|
+
export default function addAutomationTools(server, config) {
|
|
45
|
+
const tools = {};
|
|
46
|
+
tools.fetchAutomationScreenshots = server.tool("fetchAutomationScreenshots", "Fetch and process screenshots from a BrowserStack Automate session", {
|
|
46
47
|
sessionId: z
|
|
47
48
|
.string()
|
|
48
49
|
.describe("The BrowserStack session ID to fetch screenshots from"),
|
|
@@ -51,11 +52,11 @@ export default function addAutomationTools(server) {
|
|
|
51
52
|
.describe("Type of BrowserStack session"),
|
|
52
53
|
}, async (args) => {
|
|
53
54
|
try {
|
|
54
|
-
trackMCP("fetchAutomationScreenshots", server.server.getClientVersion());
|
|
55
|
-
return await fetchAutomationScreenshotsTool(args);
|
|
55
|
+
trackMCP("fetchAutomationScreenshots", server.server.getClientVersion(), undefined, config);
|
|
56
|
+
return await fetchAutomationScreenshotsTool(args, config);
|
|
56
57
|
}
|
|
57
58
|
catch (error) {
|
|
58
|
-
trackMCP("fetchAutomationScreenshots", server.server.getClientVersion(), error);
|
|
59
|
+
trackMCP("fetchAutomationScreenshots", server.server.getClientVersion(), error, config);
|
|
59
60
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
60
61
|
return {
|
|
61
62
|
content: [
|
|
@@ -67,4 +68,5 @@ export default function addAutomationTools(server) {
|
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
});
|
|
71
|
+
return tools;
|
|
70
72
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { SDKSupportedBrowserAutomationFramework, SDKSupportedLanguage, SDKSupportedTestingFramework } from "./sdk-utils/types.js";
|
|
4
|
+
import { BrowserStackConfig } from "../lib/types.js";
|
|
5
|
+
/**
|
|
6
|
+
* BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
|
|
7
|
+
* This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
|
|
8
|
+
*/
|
|
9
|
+
export declare function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, enablePercy, config, }: {
|
|
10
|
+
detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFramework;
|
|
11
|
+
detectedTestingFramework: SDKSupportedTestingFramework;
|
|
12
|
+
detectedLanguage: SDKSupportedLanguage;
|
|
13
|
+
desiredPlatforms: string[];
|
|
14
|
+
enablePercy: boolean;
|
|
15
|
+
config: BrowserStackConfig;
|
|
16
|
+
}): Promise<CallToolResult>;
|
|
17
|
+
export default function addSDKTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
package/dist/tools/bstack-sdk.js
CHANGED
|
@@ -4,15 +4,19 @@ import { getSDKPrefixCommand } from "./sdk-utils/commands.js";
|
|
|
4
4
|
import { SDKSupportedLanguageEnum, SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, } from "./sdk-utils/types.js";
|
|
5
5
|
import { generateBrowserStackYMLInstructions, getInstructionsForProjectConfiguration, formatInstructionsWithNumbers, } from "./sdk-utils/instructions.js";
|
|
6
6
|
import { formatPercyInstructions, getPercyInstructions, } from "./sdk-utils/percy/instructions.js";
|
|
7
|
+
import { getBrowserStackAuth } from "../lib/get-auth.js";
|
|
7
8
|
/**
|
|
8
9
|
* BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
|
|
9
10
|
* This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
|
|
10
11
|
*/
|
|
11
|
-
export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, enablePercy, }) {
|
|
12
|
+
export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, enablePercy, config, }) {
|
|
13
|
+
// Get credentials from config
|
|
14
|
+
const authString = getBrowserStackAuth(config);
|
|
15
|
+
const [username, accessKey] = authString.split(":");
|
|
12
16
|
// Handle frameworks with unique setup instructions that don't use browserstack.yml
|
|
13
17
|
if (detectedBrowserAutomationFramework === "cypress" ||
|
|
14
18
|
detectedTestingFramework === "webdriverio") {
|
|
15
|
-
let combinedInstructions = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage);
|
|
19
|
+
let combinedInstructions = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, username, accessKey);
|
|
16
20
|
if (enablePercy) {
|
|
17
21
|
const percyInstructions = getPercyInstructions(detectedLanguage, detectedBrowserAutomationFramework, detectedTestingFramework);
|
|
18
22
|
if (percyInstructions) {
|
|
@@ -27,9 +31,9 @@ export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramewo
|
|
|
27
31
|
return formatFinalInstructions(combinedInstructions);
|
|
28
32
|
}
|
|
29
33
|
// Handle default flow using browserstack.yml
|
|
30
|
-
const sdkSetupCommand = getSDKPrefixCommand(detectedLanguage, detectedTestingFramework);
|
|
34
|
+
const sdkSetupCommand = getSDKPrefixCommand(detectedLanguage, detectedTestingFramework, username, accessKey);
|
|
31
35
|
const ymlInstructions = generateBrowserStackYMLInstructions(desiredPlatforms, enablePercy);
|
|
32
|
-
const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage);
|
|
36
|
+
const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, username, accessKey);
|
|
33
37
|
let combinedInstructions = "";
|
|
34
38
|
// Step 1: Add SDK setup command
|
|
35
39
|
if (sdkSetupCommand) {
|
|
@@ -74,8 +78,9 @@ function formatFinalInstructions(combinedInstructions) {
|
|
|
74
78
|
],
|
|
75
79
|
};
|
|
76
80
|
}
|
|
77
|
-
export default function addSDKTools(server) {
|
|
78
|
-
|
|
81
|
+
export default function addSDKTools(server, config) {
|
|
82
|
+
const tools = {};
|
|
83
|
+
tools.setupBrowserStackAutomateTests = server.tool("setupBrowserStackAutomateTests", "Set up and run automated web-based tests on BrowserStack using the BrowserStack SDK. Use for functional or integration tests on BrowserStack, with optional Percy visual testing for supported frameworks. Example prompts: run this test on browserstack; run this test on browserstack with Percy; set up this project for browserstack with Percy. Integrate BrowserStack SDK into your project", {
|
|
79
84
|
detectedBrowserAutomationFramework: z
|
|
80
85
|
.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum)
|
|
81
86
|
.describe("The automation framework configured in the project. Example: 'playwright', 'selenium'"),
|
|
@@ -95,17 +100,18 @@ export default function addSDKTools(server) {
|
|
|
95
100
|
.describe("Set to true if the user wants to enable Percy for visual testing. Defaults to false."),
|
|
96
101
|
}, async (args) => {
|
|
97
102
|
try {
|
|
98
|
-
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion());
|
|
103
|
+
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
|
|
99
104
|
return await bootstrapProjectWithSDK({
|
|
100
105
|
detectedBrowserAutomationFramework: args.detectedBrowserAutomationFramework,
|
|
101
106
|
detectedTestingFramework: args.detectedTestingFramework,
|
|
102
107
|
detectedLanguage: args.detectedLanguage,
|
|
103
108
|
desiredPlatforms: args.desiredPlatforms,
|
|
104
109
|
enablePercy: args.enablePercy,
|
|
110
|
+
config,
|
|
105
111
|
});
|
|
106
112
|
}
|
|
107
113
|
catch (error) {
|
|
108
|
-
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error);
|
|
114
|
+
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error, config);
|
|
109
115
|
return {
|
|
110
116
|
content: [
|
|
111
117
|
{
|
|
@@ -118,4 +124,5 @@ export default function addSDKTools(server) {
|
|
|
118
124
|
};
|
|
119
125
|
}
|
|
120
126
|
});
|
|
127
|
+
return tools;
|
|
121
128
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
2
|
+
export declare function retrieveDeviceLogs(sessionId: string, buildId: string, config: BrowserStackConfig): Promise<string>;
|
|
3
|
+
export declare function retrieveAppiumLogs(sessionId: string, buildId: string, config: BrowserStackConfig): Promise<string>;
|
|
4
|
+
export declare function retrieveCrashLogs(sessionId: string, buildId: string, config: BrowserStackConfig): Promise<string>;
|
|
5
|
+
export declare function filterDeviceFailures(logText: string): string[];
|
|
6
|
+
export declare function filterAppiumFailures(logText: string): string[];
|
|
7
|
+
export declare function filterCrashFailures(logText: string): string[];
|
|
@@ -1,55 +1,73 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
2
2
|
import { filterLinesByKeywords, validateLogResponse } from "./utils.js";
|
|
3
|
-
|
|
3
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
4
4
|
// DEVICE LOGS
|
|
5
|
-
export async function retrieveDeviceLogs(sessionId, buildId) {
|
|
5
|
+
export async function retrieveDeviceLogs(sessionId, buildId, config) {
|
|
6
6
|
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/deviceLogs`;
|
|
7
|
-
const
|
|
7
|
+
const authString = getBrowserStackAuth(config);
|
|
8
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
9
|
+
const response = await apiClient.get({
|
|
10
|
+
url,
|
|
8
11
|
headers: {
|
|
9
12
|
"Content-Type": "application/json",
|
|
10
13
|
Authorization: `Basic ${auth}`,
|
|
11
14
|
},
|
|
15
|
+
raise_error: false,
|
|
12
16
|
});
|
|
13
17
|
const validationError = validateLogResponse(response, "device logs");
|
|
14
18
|
if (validationError)
|
|
15
19
|
return validationError.message;
|
|
16
|
-
const logText =
|
|
20
|
+
const logText = typeof response.data === "string"
|
|
21
|
+
? response.data
|
|
22
|
+
: JSON.stringify(response.data);
|
|
17
23
|
const logs = filterDeviceFailures(logText);
|
|
18
24
|
return logs.length > 0
|
|
19
25
|
? `Device Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
20
26
|
: "No device failures found";
|
|
21
27
|
}
|
|
22
28
|
// APPIUM LOGS
|
|
23
|
-
export async function retrieveAppiumLogs(sessionId, buildId) {
|
|
29
|
+
export async function retrieveAppiumLogs(sessionId, buildId, config) {
|
|
24
30
|
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/appiumlogs`;
|
|
25
|
-
const
|
|
31
|
+
const authString = getBrowserStackAuth(config);
|
|
32
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
33
|
+
const response = await apiClient.get({
|
|
34
|
+
url,
|
|
26
35
|
headers: {
|
|
27
36
|
"Content-Type": "application/json",
|
|
28
37
|
Authorization: `Basic ${auth}`,
|
|
29
38
|
},
|
|
39
|
+
raise_error: false,
|
|
30
40
|
});
|
|
31
41
|
const validationError = validateLogResponse(response, "Appium logs");
|
|
32
42
|
if (validationError)
|
|
33
43
|
return validationError.message;
|
|
34
|
-
const logText =
|
|
44
|
+
const logText = typeof response.data === "string"
|
|
45
|
+
? response.data
|
|
46
|
+
: JSON.stringify(response.data);
|
|
35
47
|
const logs = filterAppiumFailures(logText);
|
|
36
48
|
return logs.length > 0
|
|
37
49
|
? `Appium Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
38
50
|
: "No Appium failures found";
|
|
39
51
|
}
|
|
40
52
|
// CRASH LOGS
|
|
41
|
-
export async function retrieveCrashLogs(sessionId, buildId) {
|
|
53
|
+
export async function retrieveCrashLogs(sessionId, buildId, config) {
|
|
42
54
|
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/crashlogs`;
|
|
43
|
-
const
|
|
55
|
+
const authString = getBrowserStackAuth(config);
|
|
56
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
57
|
+
const response = await apiClient.get({
|
|
58
|
+
url,
|
|
44
59
|
headers: {
|
|
45
60
|
"Content-Type": "application/json",
|
|
46
61
|
Authorization: `Basic ${auth}`,
|
|
47
62
|
},
|
|
63
|
+
raise_error: false,
|
|
48
64
|
});
|
|
49
65
|
const validationError = validateLogResponse(response, "crash logs");
|
|
50
66
|
if (validationError)
|
|
51
67
|
return validationError.message;
|
|
52
|
-
const logText =
|
|
68
|
+
const logText = typeof response.data === "string"
|
|
69
|
+
? response.data
|
|
70
|
+
: JSON.stringify(response.data);
|
|
53
71
|
const logs = filterCrashFailures(logText);
|
|
54
72
|
return logs.length > 0
|
|
55
73
|
? `Crash Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
2
|
+
export declare function retrieveNetworkFailures(sessionId: string, config: BrowserStackConfig): Promise<string>;
|
|
3
|
+
export declare function retrieveSessionFailures(sessionId: string, config: BrowserStackConfig): Promise<string>;
|
|
4
|
+
export declare function retrieveConsoleFailures(sessionId: string, config: BrowserStackConfig): Promise<string>;
|
|
5
|
+
export declare function filterSessionFailures(logText: string): string[];
|
|
6
|
+
export declare function filterConsoleFailures(logText: string): string[];
|