@browserstack/mcp-server 1.1.8 → 1.2.0
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 +71 -35
- package/dist/config.d.ts +13 -0
- package/dist/config.js +10 -6
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -30
- 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 +12 -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 +3 -0
- package/dist/server-factory.js +37 -0
- package/dist/tools/accessibility.d.ts +3 -0
- package/dist/tools/accessibility.js +17 -10
- 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 +46 -24
- package/dist/tools/appautomate-utils/appautomate.d.ts +42 -0
- package/dist/tools/appautomate-utils/appautomate.js +95 -9
- package/dist/tools/appautomate-utils/types.d.ts +5 -0
- package/dist/tools/appautomate-utils/types.js +6 -0
- package/dist/tools/appautomate.d.ts +3 -0
- package/dist/tools/appautomate.js +109 -13
- 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 +6 -6
- 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 +6 -6
- package/dist/tools/bstack-sdk.d.ts +17 -0
- package/dist/tools/bstack-sdk.js +47 -20
- 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 +11 -11
- 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 +11 -11
- 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 +20 -5
- package/dist/tools/sdk-utils/constants.d.ts +2 -0
- package/dist/tools/sdk-utils/constants.js +284 -160
- package/dist/tools/sdk-utils/instructions.d.ts +6 -0
- package/dist/tools/sdk-utils/instructions.js +28 -6
- package/dist/tools/sdk-utils/percy/constants.d.ts +3 -0
- package/dist/tools/sdk-utils/percy/constants.js +36 -25
- 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 +3 -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 +6 -6
- 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 +51 -53
- package/package.json +1 -1
package/dist/tools/bstack-sdk.js
CHANGED
|
@@ -1,47 +1,73 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { SDKSupportedLanguageEnum, SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, } from "./sdk-utils/types.js";
|
|
3
|
-
import { generateBrowserStackYMLInstructions, getInstructionsForProjectConfiguration, } from "./sdk-utils/instructions.js";
|
|
4
2
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
5
|
-
import { formatPercyInstructions, getPercyInstructions, } from "./sdk-utils/percy/instructions.js";
|
|
6
3
|
import { getSDKPrefixCommand } from "./sdk-utils/commands.js";
|
|
4
|
+
import { SDKSupportedLanguageEnum, SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, } from "./sdk-utils/types.js";
|
|
5
|
+
import { generateBrowserStackYMLInstructions, getInstructionsForProjectConfiguration, formatInstructionsWithNumbers, } from "./sdk-utils/instructions.js";
|
|
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
|
|
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) {
|
|
19
|
-
|
|
23
|
+
combinedInstructions +=
|
|
24
|
+
"\n\n" + formatPercyInstructions(percyInstructions);
|
|
20
25
|
}
|
|
21
26
|
else {
|
|
22
27
|
throw new Error(`Percy is currently not supported through MCP for ${detectedLanguage} with ${detectedTestingFramework}. If you want to run the test cases without Percy, disable Percy and run it again.`);
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
30
|
+
// Apply consistent formatting for all configurations
|
|
31
|
+
return formatFinalInstructions(combinedInstructions);
|
|
28
32
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
fullInstructions += getSDKPrefixCommand(detectedLanguage, detectedTestingFramework);
|
|
33
|
+
// Handle default flow using browserstack.yml
|
|
34
|
+
const sdkSetupCommand = getSDKPrefixCommand(detectedLanguage, detectedTestingFramework, username, accessKey);
|
|
32
35
|
const ymlInstructions = generateBrowserStackYMLInstructions(desiredPlatforms, enablePercy);
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, username, accessKey);
|
|
37
|
+
let combinedInstructions = "";
|
|
38
|
+
// Step 1: Add SDK setup command
|
|
39
|
+
if (sdkSetupCommand) {
|
|
40
|
+
combinedInstructions += sdkSetupCommand;
|
|
41
|
+
}
|
|
42
|
+
// Step 2: Add browserstack.yml setup
|
|
43
|
+
if (ymlInstructions) {
|
|
44
|
+
combinedInstructions += "\n\n---STEP---\n" + ymlInstructions;
|
|
45
|
+
}
|
|
46
|
+
// Step 3: Add language/framework-specific setup
|
|
47
|
+
if (instructionsForProjectConfiguration) {
|
|
48
|
+
combinedInstructions += "\n\n" + instructionsForProjectConfiguration;
|
|
49
|
+
}
|
|
50
|
+
// Step 4: Add Percy setup if applicable
|
|
35
51
|
if (enablePercy) {
|
|
36
52
|
const percyInstructions = getPercyInstructions(detectedLanguage, detectedBrowserAutomationFramework, detectedTestingFramework);
|
|
37
53
|
if (percyInstructions) {
|
|
38
|
-
|
|
54
|
+
combinedInstructions +=
|
|
55
|
+
"\n\n" + formatPercyInstructions(percyInstructions);
|
|
39
56
|
}
|
|
40
57
|
else {
|
|
41
58
|
throw new Error(`Percy is currently not supported through MCP for ${detectedLanguage} with ${detectedTestingFramework}. If you want to run the test cases without Percy, disable Percy and run it again.`);
|
|
42
59
|
}
|
|
43
60
|
}
|
|
44
|
-
|
|
61
|
+
// Apply consistent formatting for all configurations
|
|
62
|
+
return formatFinalInstructions(combinedInstructions);
|
|
63
|
+
}
|
|
64
|
+
// Helper function to apply consistent formatting to all instruction types
|
|
65
|
+
function formatFinalInstructions(combinedInstructions) {
|
|
66
|
+
const fullInstructions = `⚠️ IMPORTANT: DO NOT SKIP ANY STEP
|
|
67
|
+
All the setup steps described in this file MUST be executed regardless of any existing configuration or setup.
|
|
68
|
+
This ensures proper BrowserStack SDK setup.
|
|
69
|
+
|
|
70
|
+
${formatInstructionsWithNumbers(combinedInstructions)}`;
|
|
45
71
|
return {
|
|
46
72
|
content: [
|
|
47
73
|
{
|
|
@@ -52,8 +78,8 @@ export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramewo
|
|
|
52
78
|
],
|
|
53
79
|
};
|
|
54
80
|
}
|
|
55
|
-
export default function addSDKTools(server) {
|
|
56
|
-
server.tool("runTestsOnBrowserStack", "Use this tool to get instructions for running tests on BrowserStack and
|
|
81
|
+
export default function addSDKTools(server, config) {
|
|
82
|
+
server.tool("runTestsOnBrowserStack", "Use this tool to get instructions for running tests on BrowserStack and BrowserStack Percy. It sets up the BrowserStack SDK and runs your test cases on BrowserStack.", {
|
|
57
83
|
detectedBrowserAutomationFramework: z
|
|
58
84
|
.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum)
|
|
59
85
|
.describe("The automation framework configured in the project. Example: 'playwright', 'selenium'"),
|
|
@@ -73,17 +99,18 @@ export default function addSDKTools(server) {
|
|
|
73
99
|
.describe("Set to true if the user wants to enable Percy for visual testing. Defaults to false."),
|
|
74
100
|
}, async (args) => {
|
|
75
101
|
try {
|
|
76
|
-
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion());
|
|
102
|
+
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
|
|
77
103
|
return await bootstrapProjectWithSDK({
|
|
78
104
|
detectedBrowserAutomationFramework: args.detectedBrowserAutomationFramework,
|
|
79
105
|
detectedTestingFramework: args.detectedTestingFramework,
|
|
80
106
|
detectedLanguage: args.detectedLanguage,
|
|
81
107
|
desiredPlatforms: args.desiredPlatforms,
|
|
82
108
|
enablePercy: args.enablePercy,
|
|
109
|
+
config,
|
|
83
110
|
});
|
|
84
111
|
}
|
|
85
112
|
catch (error) {
|
|
86
|
-
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error);
|
|
113
|
+
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error, config);
|
|
87
114
|
return {
|
|
88
115
|
content: [
|
|
89
116
|
{
|
|
@@ -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[];
|
|
@@ -1,20 +1,23 @@
|
|
|
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
|
// NETWORK LOGS
|
|
5
|
-
export async function retrieveNetworkFailures(sessionId) {
|
|
5
|
+
export async function retrieveNetworkFailures(sessionId, config) {
|
|
6
6
|
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/networklogs`;
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const authString = getBrowserStackAuth(config);
|
|
8
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
9
|
+
const response = await apiClient.get({
|
|
10
|
+
url,
|
|
9
11
|
headers: {
|
|
10
12
|
"Content-Type": "application/json",
|
|
11
13
|
Authorization: `Basic ${auth}`,
|
|
12
14
|
},
|
|
15
|
+
raise_error: false,
|
|
13
16
|
});
|
|
14
17
|
const validationError = validateLogResponse(response, "network logs");
|
|
15
18
|
if (validationError)
|
|
16
19
|
return validationError.message;
|
|
17
|
-
const networklogs =
|
|
20
|
+
const networklogs = response.data;
|
|
18
21
|
const failureEntries = networklogs.log.entries.filter((entry) => entry.response.status === 0 ||
|
|
19
22
|
entry.response.status >= 400 ||
|
|
20
23
|
entry.response._error !== undefined);
|
|
@@ -37,36 +40,48 @@ export async function retrieveNetworkFailures(sessionId) {
|
|
|
37
40
|
: "No network failures found";
|
|
38
41
|
}
|
|
39
42
|
// SESSION LOGS
|
|
40
|
-
export async function retrieveSessionFailures(sessionId) {
|
|
43
|
+
export async function retrieveSessionFailures(sessionId, config) {
|
|
41
44
|
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`;
|
|
42
|
-
const
|
|
45
|
+
const authString = getBrowserStackAuth(config);
|
|
46
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
47
|
+
const response = await apiClient.get({
|
|
48
|
+
url,
|
|
43
49
|
headers: {
|
|
44
50
|
"Content-Type": "application/json",
|
|
45
51
|
Authorization: `Basic ${auth}`,
|
|
46
52
|
},
|
|
53
|
+
raise_error: false,
|
|
47
54
|
});
|
|
48
55
|
const validationError = validateLogResponse(response, "session logs");
|
|
49
56
|
if (validationError)
|
|
50
57
|
return validationError.message;
|
|
51
|
-
const logText =
|
|
58
|
+
const logText = typeof response.data === "string"
|
|
59
|
+
? response.data
|
|
60
|
+
: JSON.stringify(response.data);
|
|
52
61
|
const logs = filterSessionFailures(logText);
|
|
53
62
|
return logs.length > 0
|
|
54
63
|
? `Session Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
55
64
|
: "No session failures found";
|
|
56
65
|
}
|
|
57
66
|
// CONSOLE LOGS
|
|
58
|
-
export async function retrieveConsoleFailures(sessionId) {
|
|
67
|
+
export async function retrieveConsoleFailures(sessionId, config) {
|
|
59
68
|
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/consolelogs`;
|
|
60
|
-
const
|
|
69
|
+
const authString = getBrowserStackAuth(config);
|
|
70
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
71
|
+
const response = await apiClient.get({
|
|
72
|
+
url,
|
|
61
73
|
headers: {
|
|
62
74
|
"Content-Type": "application/json",
|
|
63
75
|
Authorization: `Basic ${auth}`,
|
|
64
76
|
},
|
|
77
|
+
raise_error: false,
|
|
65
78
|
});
|
|
66
79
|
const validationError = validateLogResponse(response, "console logs");
|
|
67
80
|
if (validationError)
|
|
68
81
|
return validationError.message;
|
|
69
|
-
const logText =
|
|
82
|
+
const logText = typeof response.data === "string"
|
|
83
|
+
? response.data
|
|
84
|
+
: JSON.stringify(response.data);
|
|
70
85
|
const logs = filterConsoleFailures(logText);
|
|
71
86
|
return logs.length > 0
|
|
72
87
|
? `Console Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ApiResponse } from "../../lib/apiClient.js";
|
|
2
|
+
export interface LogResponse {
|
|
3
|
+
logs?: any[];
|
|
4
|
+
message?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HarFile {
|
|
7
|
+
log: {
|
|
8
|
+
entries: HarEntry[];
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface HarEntry {
|
|
12
|
+
startedDateTime: string;
|
|
13
|
+
request: {
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
queryString?: {
|
|
17
|
+
name: string;
|
|
18
|
+
value: string;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
response: {
|
|
22
|
+
status: number;
|
|
23
|
+
statusText?: string;
|
|
24
|
+
_error?: string;
|
|
25
|
+
};
|
|
26
|
+
serverIPAddress?: string;
|
|
27
|
+
time?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function validateLogResponse(response: Response | ApiResponse, logType: string): LogResponse | null;
|
|
30
|
+
export declare function filterLinesByKeywords(logText: string, keywords: string[]): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
import { AppAutomateLogType, AutomateLogType, SessionType } from "../lib/constants.js";
|
|
5
|
+
type LogType = AutomateLogType | AppAutomateLogType;
|
|
6
|
+
type SessionTypeValues = SessionType;
|
|
7
|
+
export declare function getFailureLogs(args: {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
buildId?: string;
|
|
10
|
+
logTypes: LogType[];
|
|
11
|
+
sessionType: SessionTypeValues;
|
|
12
|
+
}, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
13
|
+
export default function registerGetFailureLogs(server: McpServer, config: BrowserStackConfig): void;
|
|
14
|
+
export {};
|
|
@@ -5,7 +5,7 @@ import { retrieveNetworkFailures, retrieveSessionFailures, retrieveConsoleFailur
|
|
|
5
5
|
import { retrieveDeviceLogs, retrieveAppiumLogs, retrieveCrashLogs, } from "./failurelogs-utils/app-automate.js";
|
|
6
6
|
import { AppAutomateLogType, AutomateLogType, SessionType, } from "../lib/constants.js";
|
|
7
7
|
// Main log fetcher function
|
|
8
|
-
export async function getFailureLogs(args) {
|
|
8
|
+
export async function getFailureLogs(args, config) {
|
|
9
9
|
const results = [];
|
|
10
10
|
const errors = [];
|
|
11
11
|
let validLogTypes = [];
|
|
@@ -53,32 +53,32 @@ export async function getFailureLogs(args) {
|
|
|
53
53
|
for (const logType of validLogTypes) {
|
|
54
54
|
switch (logType) {
|
|
55
55
|
case AutomateLogType.NetworkLogs: {
|
|
56
|
-
response = await retrieveNetworkFailures(args.sessionId);
|
|
56
|
+
response = await retrieveNetworkFailures(args.sessionId, config);
|
|
57
57
|
results.push({ type: "text", text: response });
|
|
58
58
|
break;
|
|
59
59
|
}
|
|
60
60
|
case AutomateLogType.SessionLogs: {
|
|
61
|
-
response = await retrieveSessionFailures(args.sessionId);
|
|
61
|
+
response = await retrieveSessionFailures(args.sessionId, config);
|
|
62
62
|
results.push({ type: "text", text: response });
|
|
63
63
|
break;
|
|
64
64
|
}
|
|
65
65
|
case AutomateLogType.ConsoleLogs: {
|
|
66
|
-
response = await retrieveConsoleFailures(args.sessionId);
|
|
66
|
+
response = await retrieveConsoleFailures(args.sessionId, config);
|
|
67
67
|
results.push({ type: "text", text: response });
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
70
|
case AppAutomateLogType.DeviceLogs: {
|
|
71
|
-
response = await retrieveDeviceLogs(args.sessionId, args.buildId);
|
|
71
|
+
response = await retrieveDeviceLogs(args.sessionId, args.buildId, config);
|
|
72
72
|
results.push({ type: "text", text: response });
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
case AppAutomateLogType.AppiumLogs: {
|
|
76
|
-
response = await retrieveAppiumLogs(args.sessionId, args.buildId);
|
|
76
|
+
response = await retrieveAppiumLogs(args.sessionId, args.buildId, config);
|
|
77
77
|
results.push({ type: "text", text: response });
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
80
80
|
case AppAutomateLogType.CrashLogs: {
|
|
81
|
-
response = await retrieveCrashLogs(args.sessionId, args.buildId);
|
|
81
|
+
response = await retrieveCrashLogs(args.sessionId, args.buildId, config);
|
|
82
82
|
results.push({ type: "text", text: response });
|
|
83
83
|
break;
|
|
84
84
|
}
|
|
@@ -98,7 +98,7 @@ export async function getFailureLogs(args) {
|
|
|
98
98
|
return { content: results };
|
|
99
99
|
}
|
|
100
100
|
// Register tool with the MCP server
|
|
101
|
-
export default function registerGetFailureLogs(server) {
|
|
101
|
+
export default function registerGetFailureLogs(server, config) {
|
|
102
102
|
server.tool("getFailureLogs", "Fetch various types of logs from a BrowserStack session. Supports both automate and app-automate sessions.", {
|
|
103
103
|
sessionType: z
|
|
104
104
|
.enum([SessionType.Automate, SessionType.AppAutomate])
|
|
@@ -122,12 +122,12 @@ export default function registerGetFailureLogs(server) {
|
|
|
122
122
|
.describe("The types of logs to fetch."),
|
|
123
123
|
}, async (args) => {
|
|
124
124
|
try {
|
|
125
|
-
trackMCP("getFailureLogs", server.server.getClientVersion());
|
|
126
|
-
return await getFailureLogs(args);
|
|
125
|
+
trackMCP("getFailureLogs", server.server.getClientVersion(), undefined, config);
|
|
126
|
+
return await getFailureLogs(args, config);
|
|
127
127
|
}
|
|
128
128
|
catch (error) {
|
|
129
129
|
const message = error instanceof Error ? error.message : String(error);
|
|
130
|
-
trackMCP("getFailureLogs", server.server.getClientVersion(), error);
|
|
130
|
+
trackMCP("getFailureLogs", server.server.getClientVersion(), error, config);
|
|
131
131
|
logger.error("Failed to fetch logs: %s", message);
|
|
132
132
|
return {
|
|
133
133
|
content: [
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DesktopSearchArgs, MobileSearchArgs } from "./types.js";
|
|
2
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Entrypoint: detects platformType & delegates.
|
|
5
|
+
*/
|
|
6
|
+
export declare function startBrowserSession(args: DesktopSearchArgs | MobileSearchArgs, config: BrowserStackConfig): Promise<string>;
|
|
@@ -4,13 +4,18 @@ import { filterDesktop } from "./desktop-filter.js";
|
|
|
4
4
|
import { filterMobile } from "./mobile-filter.js";
|
|
5
5
|
import { PlatformType, } from "./types.js";
|
|
6
6
|
import { isLocalURL, ensureLocalBinarySetup, killExistingBrowserStackLocalProcesses, } from "../../lib/local.js";
|
|
7
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
8
|
+
import envConfig from "../../config.js";
|
|
7
9
|
/**
|
|
8
10
|
* Prepares local tunnel setup based on URL type
|
|
9
11
|
*/
|
|
10
|
-
async function prepareLocalTunnel(url) {
|
|
12
|
+
async function prepareLocalTunnel(url, username, password) {
|
|
11
13
|
const isLocal = isLocalURL(url);
|
|
14
|
+
if (isLocal && envConfig.REMOTE_MCP) {
|
|
15
|
+
throw new Error("Local URLs are not supported in this remote mcp. Please use a public URL.");
|
|
16
|
+
}
|
|
12
17
|
if (isLocal) {
|
|
13
|
-
await ensureLocalBinarySetup();
|
|
18
|
+
await ensureLocalBinarySetup(username, password);
|
|
14
19
|
}
|
|
15
20
|
else {
|
|
16
21
|
await killExistingBrowserStackLocalProcesses();
|
|
@@ -20,15 +25,23 @@ async function prepareLocalTunnel(url) {
|
|
|
20
25
|
/**
|
|
21
26
|
* Entrypoint: detects platformType & delegates.
|
|
22
27
|
*/
|
|
23
|
-
export async function startBrowserSession(args) {
|
|
28
|
+
export async function startBrowserSession(args, config) {
|
|
24
29
|
const entry = args.platformType === PlatformType.DESKTOP
|
|
25
30
|
? await filterDesktop(args)
|
|
26
31
|
: await filterMobile(args);
|
|
27
|
-
|
|
32
|
+
// Get credentials from config
|
|
33
|
+
const authString = getBrowserStackAuth(config);
|
|
34
|
+
const [username, password] = authString.split(":");
|
|
35
|
+
if (!username || !password) {
|
|
36
|
+
throw new Error("BrowserStack credentials are not set. Please configure them in the server settings.");
|
|
37
|
+
}
|
|
38
|
+
const isLocal = await prepareLocalTunnel(args.url, username, password);
|
|
28
39
|
const url = args.platformType === PlatformType.DESKTOP
|
|
29
40
|
? buildDesktopUrl(args, entry, isLocal)
|
|
30
41
|
: buildMobileUrl(args, entry, isLocal);
|
|
31
|
-
|
|
42
|
+
if (!envConfig.REMOTE_MCP) {
|
|
43
|
+
openBrowser(url);
|
|
44
|
+
}
|
|
32
45
|
return entry.notes ? `${url}, ${entry.notes}` : url;
|
|
33
46
|
}
|
|
34
47
|
function buildDesktopUrl(args, e, isLocal) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface DesktopSearchArgs {
|
|
2
|
+
platformType: "desktop";
|
|
3
|
+
url: string;
|
|
4
|
+
os: string;
|
|
5
|
+
osVersion: string;
|
|
6
|
+
browser: string;
|
|
7
|
+
browserVersion: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DesktopEntry {
|
|
10
|
+
os: string;
|
|
11
|
+
os_version: string;
|
|
12
|
+
browser: string;
|
|
13
|
+
browser_version: string;
|
|
14
|
+
notes?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MobileSearchArgs {
|
|
17
|
+
platformType: "mobile";
|
|
18
|
+
url: string;
|
|
19
|
+
os: string;
|
|
20
|
+
osVersion: string;
|
|
21
|
+
device: string;
|
|
22
|
+
browser: string;
|
|
23
|
+
}
|
|
24
|
+
export interface MobileEntry {
|
|
25
|
+
os: string;
|
|
26
|
+
os_version: string;
|
|
27
|
+
display_name: string;
|
|
28
|
+
notes?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare enum PlatformType {
|
|
31
|
+
DESKTOP = "desktop",
|
|
32
|
+
MOBILE = "mobile"
|
|
33
|
+
}
|
package/dist/tools/live.js
CHANGED
|
@@ -28,7 +28,7 @@ const LiveArgsSchema = z.object(LiveArgsShape);
|
|
|
28
28
|
/**
|
|
29
29
|
* Launches a desktop browser session
|
|
30
30
|
*/
|
|
31
|
-
async function launchDesktopSession(args) {
|
|
31
|
+
async function launchDesktopSession(args, config) {
|
|
32
32
|
if (!args.desiredBrowser)
|
|
33
33
|
throw new Error("You must provide a desiredBrowser");
|
|
34
34
|
if (!args.desiredBrowserVersion)
|
|
@@ -40,12 +40,12 @@ async function launchDesktopSession(args) {
|
|
|
40
40
|
osVersion: args.desiredOSVersion,
|
|
41
41
|
browser: args.desiredBrowser,
|
|
42
42
|
browserVersion: args.desiredBrowserVersion,
|
|
43
|
-
});
|
|
43
|
+
}, config);
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Launches a mobile browser session
|
|
47
47
|
*/
|
|
48
|
-
async function launchMobileSession(args) {
|
|
48
|
+
async function launchMobileSession(args, config) {
|
|
49
49
|
if (!args.desiredDevice)
|
|
50
50
|
throw new Error("You must provide a desiredDevice");
|
|
51
51
|
return startBrowserSession({
|
|
@@ -55,18 +55,18 @@ async function launchMobileSession(args) {
|
|
|
55
55
|
os: args.desiredOS,
|
|
56
56
|
osVersion: args.desiredOSVersion,
|
|
57
57
|
device: args.desiredDevice,
|
|
58
|
-
});
|
|
58
|
+
}, config);
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Handles the core logic for running a browser session
|
|
62
62
|
*/
|
|
63
|
-
async function runBrowserSession(rawArgs) {
|
|
63
|
+
async function runBrowserSession(rawArgs, config) {
|
|
64
64
|
// Validate and narrow
|
|
65
65
|
const args = LiveArgsSchema.parse(rawArgs);
|
|
66
66
|
// Branch desktop vs mobile and delegate
|
|
67
67
|
const launchUrl = args.platformType === PlatformType.DESKTOP
|
|
68
|
-
? await launchDesktopSession(args)
|
|
69
|
-
: await launchMobileSession(args);
|
|
68
|
+
? await launchDesktopSession(args, config)
|
|
69
|
+
: await launchMobileSession(args, config);
|
|
70
70
|
return {
|
|
71
71
|
content: [
|
|
72
72
|
{
|
|
@@ -76,15 +76,15 @@ async function runBrowserSession(rawArgs) {
|
|
|
76
76
|
],
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
export default function addBrowserLiveTools(server) {
|
|
79
|
+
export default function addBrowserLiveTools(server, config) {
|
|
80
80
|
server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
|
|
81
81
|
try {
|
|
82
|
-
trackMCP("runBrowserLiveSession", server.server.getClientVersion());
|
|
83
|
-
return await runBrowserSession(args);
|
|
82
|
+
trackMCP("runBrowserLiveSession", server.server.getClientVersion(), undefined, config);
|
|
83
|
+
return await runBrowserSession(args, config);
|
|
84
84
|
}
|
|
85
85
|
catch (error) {
|
|
86
86
|
logger.error("Live session failed: %s", error);
|
|
87
|
-
trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error);
|
|
87
|
+
trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error, config);
|
|
88
88
|
return {
|
|
89
89
|
content: [
|
|
90
90
|
{
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
export declare function getFailuresInLastRun(buildName: string, projectName: string, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
5
|
+
export default function addObservabilityTools(server: McpServer, config: BrowserStackConfig): void;
|