@browserstack/mcp-server 1.2.1 → 1.2.2-beta.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 +227 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/device-cache.d.ts +1 -0
- package/dist/lib/device-cache.js +28 -0
- package/dist/lib/inmemory-store.d.ts +1 -0
- package/dist/lib/inmemory-store.js +1 -0
- package/dist/lib/instrumentation.js +2 -0
- package/dist/oninitialized.js +4 -1
- package/dist/server-factory.js +3 -1
- package/dist/tools/accessibility.js +238 -78
- package/dist/tools/accessiblity-utils/auth-config.d.ts +39 -0
- package/dist/tools/accessiblity-utils/auth-config.js +125 -0
- package/dist/tools/accessiblity-utils/scanner.d.ts +1 -1
- package/dist/tools/accessiblity-utils/scanner.js +2 -1
- package/dist/tools/add-percy-snapshots.d.ts +5 -0
- package/dist/tools/add-percy-snapshots.js +17 -0
- package/dist/tools/appautomate-utils/appautomate.d.ts +2 -1
- package/dist/tools/appautomate-utils/appautomate.js +12 -9
- package/dist/tools/appautomate.js +56 -7
- package/dist/tools/applive-utils/start-session.d.ts +2 -1
- package/dist/tools/applive-utils/start-session.js +17 -6
- package/dist/tools/applive.d.ts +2 -1
- package/dist/tools/applive.js +21 -17
- package/dist/tools/bstack-sdk.d.ts +2 -15
- package/dist/tools/bstack-sdk.js +7 -124
- package/dist/tools/list-test-files.d.ts +2 -0
- package/dist/tools/list-test-files.js +33 -0
- package/dist/tools/percy-sdk.d.ts +4 -0
- package/dist/tools/percy-sdk.js +88 -0
- package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
- package/dist/tools/percy-snapshot-utils/constants.js +500 -0
- package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
- package/dist/tools/percy-snapshot-utils/detect-test-files.js +194 -0
- package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
- package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
- package/dist/tools/percy-snapshot-utils/utils.js +30 -0
- package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
- package/dist/tools/sdk-utils/bstack/commands.js +88 -0
- package/dist/tools/sdk-utils/bstack/configUtils.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/configUtils.js +66 -0
- package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
- package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +128 -76
- package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
- package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
- package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/index.js +5 -0
- package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/sdkHandler.js +74 -0
- package/dist/tools/sdk-utils/common/constants.d.ts +10 -0
- package/dist/tools/sdk-utils/common/constants.js +86 -0
- package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
- package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
- package/dist/tools/sdk-utils/common/index.d.ts +3 -0
- package/dist/tools/sdk-utils/common/index.js +4 -0
- package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
- package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
- package/dist/tools/sdk-utils/common/schema.d.ts +61 -0
- package/dist/tools/sdk-utils/common/schema.js +28 -0
- package/dist/tools/sdk-utils/common/types.d.ts +66 -0
- package/dist/tools/sdk-utils/common/types.js +50 -0
- package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
- package/dist/tools/sdk-utils/common/utils.js +84 -0
- package/dist/tools/sdk-utils/handler.d.ts +5 -0
- package/dist/tools/sdk-utils/handler.js +144 -0
- package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
- package/dist/tools/sdk-utils/percy-automate/constants.js +365 -0
- package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
- package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
- package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
- package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
- package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
- package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
- package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
- package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
- package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
- package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
- package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
- package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
- package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-bstack/handler.js +99 -0
- package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
- package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
- package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
- package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
- package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
- package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
- package/dist/tools/sdk-utils/percy-web/constants.js +941 -0
- package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +28 -0
- package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
- package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
- package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/handler.js +27 -0
- package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/index.js +4 -0
- package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
- package/dist/tools/sdk-utils/percy-web/types.js +1 -0
- package/dist/tools/sdk-utils/types.d.ts +2 -1
- package/dist/tools/sdk-utils/types.js +1 -0
- package/dist/tools/testmanagement-utils/create-testcase.d.ts +4 -0
- package/dist/tools/testmanagement-utils/create-testcase.js +6 -0
- package/package.json +1 -1
- package/dist/tools/sdk-utils/commands.js +0 -65
- package/dist/tools/sdk-utils/instructions.d.ts +0 -6
- package/dist/tools/sdk-utils/instructions.js +0 -99
- package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
- package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
- package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
- /package/dist/tools/{getFailureLogs.d.ts → get-failure-logs.d.ts} +0 -0
- /package/dist/tools/{getFailureLogs.js → get-failure-logs.js} +0 -0
- /package/dist/tools/{sdk-utils/percy → percy-snapshot-utils}/types.js +0 -0
|
@@ -19,7 +19,7 @@ async function takeAppScreenshot(args) {
|
|
|
19
19
|
let driver;
|
|
20
20
|
try {
|
|
21
21
|
validateArgs(args);
|
|
22
|
-
const { desiredPlatform, desiredPhone, appPath, config } = args;
|
|
22
|
+
const { desiredPlatform, desiredPhone, appPath, browserstackAppUrl, 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());
|
|
@@ -35,8 +35,18 @@ async function takeAppScreenshot(args) {
|
|
|
35
35
|
}
|
|
36
36
|
const authString = getBrowserStackAuth(config);
|
|
37
37
|
const [username, password] = authString.split(":");
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
let app_url;
|
|
39
|
+
if (browserstackAppUrl) {
|
|
40
|
+
app_url = browserstackAppUrl;
|
|
41
|
+
logger.info(`Using provided BrowserStack app URL: ${app_url}`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (!appPath) {
|
|
45
|
+
throw new Error("appPath is required when browserstackAppUrl is not provided");
|
|
46
|
+
}
|
|
47
|
+
app_url = await uploadApp(appPath, username, password);
|
|
48
|
+
logger.info(`App uploaded. URL: ${app_url}`);
|
|
49
|
+
}
|
|
40
50
|
const capabilities = {
|
|
41
51
|
platformName: desiredPlatform,
|
|
42
52
|
"appium:platformVersion": selectedDevice.os_version,
|
|
@@ -89,11 +99,34 @@ async function takeAppScreenshot(args) {
|
|
|
89
99
|
}
|
|
90
100
|
//Runs AppAutomate tests on BrowserStack by uploading app and test suite, then triggering a test run.
|
|
91
101
|
async function runAppTestsOnBrowserStack(args, config) {
|
|
102
|
+
// Validate that either paths or URLs are provided for both app and test suite
|
|
103
|
+
if (!args.browserstackAppUrl && !args.appPath) {
|
|
104
|
+
throw new Error("appPath is required when browserstackAppUrl is not provided");
|
|
105
|
+
}
|
|
106
|
+
if (!args.browserstackTestSuiteUrl && !args.testSuitePath) {
|
|
107
|
+
throw new Error("testSuitePath is required when browserstackTestSuiteUrl is not provided");
|
|
108
|
+
}
|
|
92
109
|
switch (args.detectedAutomationFramework) {
|
|
93
110
|
case AppTestPlatform.ESPRESSO: {
|
|
94
111
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
let app_url;
|
|
113
|
+
if (args.browserstackAppUrl) {
|
|
114
|
+
app_url = args.browserstackAppUrl;
|
|
115
|
+
logger.info(`Using provided BrowserStack app URL: ${app_url}`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
app_url = await uploadEspressoApp(args.appPath, config);
|
|
119
|
+
logger.info(`App uploaded. URL: ${app_url}`);
|
|
120
|
+
}
|
|
121
|
+
let test_suite_url;
|
|
122
|
+
if (args.browserstackTestSuiteUrl) {
|
|
123
|
+
test_suite_url = args.browserstackTestSuiteUrl;
|
|
124
|
+
logger.info(`Using provided BrowserStack test suite URL: ${test_suite_url}`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
test_suite_url = await uploadEspressoTestSuite(args.testSuitePath, config);
|
|
128
|
+
logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
|
|
129
|
+
}
|
|
97
130
|
const build_id = await triggerEspressoBuild(app_url, test_suite_url, args.devices, args.project);
|
|
98
131
|
return {
|
|
99
132
|
content: [
|
|
@@ -111,8 +144,24 @@ async function runAppTestsOnBrowserStack(args, config) {
|
|
|
111
144
|
}
|
|
112
145
|
case AppTestPlatform.XCUITEST: {
|
|
113
146
|
try {
|
|
114
|
-
|
|
115
|
-
|
|
147
|
+
let app_url;
|
|
148
|
+
if (args.browserstackAppUrl) {
|
|
149
|
+
app_url = args.browserstackAppUrl;
|
|
150
|
+
logger.info(`Using provided BrowserStack app URL: ${app_url}`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
app_url = await uploadXcuiApp(args.appPath, config);
|
|
154
|
+
logger.info(`App uploaded. URL: ${app_url}`);
|
|
155
|
+
}
|
|
156
|
+
let test_suite_url;
|
|
157
|
+
if (args.browserstackTestSuiteUrl) {
|
|
158
|
+
test_suite_url = args.browserstackTestSuiteUrl;
|
|
159
|
+
logger.info(`Using provided BrowserStack test suite URL: ${test_suite_url}`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
test_suite_url = await uploadXcuiTestSuite(args.testSuitePath, config);
|
|
163
|
+
logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
|
|
164
|
+
}
|
|
116
165
|
const build_id = await triggerXcuiBuild(app_url, test_suite_url, args.devices, args.project, config);
|
|
117
166
|
return {
|
|
118
167
|
content: [
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { BrowserStackConfig } from "../../lib/types.js";
|
|
2
2
|
interface StartSessionArgs {
|
|
3
|
-
appPath
|
|
3
|
+
appPath?: string;
|
|
4
4
|
desiredPlatform: "android" | "ios";
|
|
5
5
|
desiredPhone: string;
|
|
6
6
|
desiredPlatformVersion: string;
|
|
7
|
+
browserstackAppUrl?: string;
|
|
7
8
|
}
|
|
8
9
|
interface StartSessionOptions {
|
|
9
10
|
config: BrowserStackConfig;
|
|
@@ -11,7 +11,7 @@ import envConfig from "../../config.js";
|
|
|
11
11
|
* Start an App Live session: filter, select, upload, and open.
|
|
12
12
|
*/
|
|
13
13
|
export async function startSession(args, options) {
|
|
14
|
-
const { appPath, desiredPlatform, desiredPhone, desiredPlatformVersion } = args;
|
|
14
|
+
const { appPath, desiredPlatform, desiredPhone, desiredPlatformVersion, browserstackAppUrl, } = args;
|
|
15
15
|
const { config } = options;
|
|
16
16
|
// 1) Fetch devices for APP_LIVE
|
|
17
17
|
const data = await getDevicesAndBrowsers(BrowserStackProducts.APP_LIVE);
|
|
@@ -38,11 +38,22 @@ export async function startSession(args, options) {
|
|
|
38
38
|
desiredPlatformVersion !== "oldest") {
|
|
39
39
|
note = `\n Note: The requested version "${desiredPlatformVersion}" is not available. Using "${version}" instead.`;
|
|
40
40
|
}
|
|
41
|
-
// 6) Upload app
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
// 6) Upload app or use provided URL
|
|
42
|
+
let app_url;
|
|
43
|
+
if (browserstackAppUrl) {
|
|
44
|
+
app_url = browserstackAppUrl;
|
|
45
|
+
logger.info(`Using provided BrowserStack app URL: ${app_url}`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (!appPath) {
|
|
49
|
+
throw new Error("appPath is required when browserstackAppUrl is not provided");
|
|
50
|
+
}
|
|
51
|
+
const authString = getBrowserStackAuth(config);
|
|
52
|
+
const [username, password] = authString.split(":");
|
|
53
|
+
const result = await uploadApp(appPath, username, password);
|
|
54
|
+
app_url = result.app_url;
|
|
55
|
+
logger.info(`App uploaded: ${app_url}`);
|
|
56
|
+
}
|
|
46
57
|
if (!app_url) {
|
|
47
58
|
throw new Error("Failed to upload app. Please try again.");
|
|
48
59
|
}
|
package/dist/tools/applive.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { BrowserStackConfig } from "../lib/types.js";
|
|
|
7
7
|
export declare function startAppLiveSession(args: {
|
|
8
8
|
desiredPlatform: string;
|
|
9
9
|
desiredPlatformVersion: string;
|
|
10
|
-
appPath
|
|
10
|
+
appPath?: string;
|
|
11
11
|
desiredPhone: string;
|
|
12
|
+
browserstackAppUrl?: string;
|
|
12
13
|
}, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
13
14
|
export default function addAppLiveTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
package/dist/tools/applive.js
CHANGED
|
@@ -10,34 +10,38 @@ export async function startAppLiveSession(args, config) {
|
|
|
10
10
|
if (!args.desiredPlatform) {
|
|
11
11
|
throw new Error("You must provide a desiredPlatform.");
|
|
12
12
|
}
|
|
13
|
-
if (!args.appPath) {
|
|
14
|
-
throw new Error("You must provide
|
|
13
|
+
if (!args.appPath && !args.browserstackAppUrl) {
|
|
14
|
+
throw new Error("You must provide either appPath or browserstackAppUrl.");
|
|
15
15
|
}
|
|
16
16
|
if (!args.desiredPhone) {
|
|
17
17
|
throw new Error("You must provide a desiredPhone.");
|
|
18
18
|
}
|
|
19
|
-
if
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if
|
|
28
|
-
|
|
19
|
+
// Only validate app path if it's provided (not using browserstackAppUrl)
|
|
20
|
+
if (args.appPath) {
|
|
21
|
+
if (args.desiredPlatform === "android" && !args.appPath.endsWith(".apk")) {
|
|
22
|
+
throw new Error("You must provide a valid Android app path.");
|
|
23
|
+
}
|
|
24
|
+
if (args.desiredPlatform === "ios" && !args.appPath.endsWith(".ipa")) {
|
|
25
|
+
throw new Error("You must provide a valid iOS app path.");
|
|
26
|
+
}
|
|
27
|
+
// check if the app path exists && is readable
|
|
28
|
+
try {
|
|
29
|
+
if (!fs.existsSync(args.appPath)) {
|
|
30
|
+
throw new Error("The app path does not exist.");
|
|
31
|
+
}
|
|
32
|
+
fs.accessSync(args.appPath, fs.constants.R_OK);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
logger.error("The app path does not exist or is not readable: %s", error);
|
|
36
|
+
throw new Error("The app path does not exist or is not readable.");
|
|
29
37
|
}
|
|
30
|
-
fs.accessSync(args.appPath, fs.constants.R_OK);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
logger.error("The app path does not exist or is not readable: %s", error);
|
|
34
|
-
throw new Error("The app path does not exist or is not readable.");
|
|
35
38
|
}
|
|
36
39
|
const launchUrl = await startSession({
|
|
37
40
|
appPath: args.appPath,
|
|
38
41
|
desiredPlatform: args.desiredPlatform,
|
|
39
42
|
desiredPhone: args.desiredPhone,
|
|
40
43
|
desiredPlatformVersion: args.desiredPlatformVersion,
|
|
44
|
+
browserstackAppUrl: args.browserstackAppUrl,
|
|
41
45
|
}, { config });
|
|
42
46
|
return {
|
|
43
47
|
content: [
|
|
@@ -1,17 +1,4 @@
|
|
|
1
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
2
|
import { BrowserStackConfig } from "../lib/types.js";
|
|
5
|
-
|
|
6
|
-
|
|
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>;
|
|
3
|
+
export declare function registerRunBrowserStackTestsTool(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
|
4
|
+
export default registerRunBrowserStackTestsTool;
|
package/dist/tools/bstack-sdk.js
CHANGED
|
@@ -1,128 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
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";
|
|
8
|
-
/**
|
|
9
|
-
* BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
|
|
10
|
-
* This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
|
|
11
|
-
*/
|
|
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(":");
|
|
16
|
-
// Handle frameworks with unique setup instructions that don't use browserstack.yml
|
|
17
|
-
if (detectedBrowserAutomationFramework === "cypress" ||
|
|
18
|
-
detectedTestingFramework === "webdriverio") {
|
|
19
|
-
let combinedInstructions = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, username, accessKey);
|
|
20
|
-
if (enablePercy) {
|
|
21
|
-
const percyInstructions = getPercyInstructions(detectedLanguage, detectedBrowserAutomationFramework, detectedTestingFramework);
|
|
22
|
-
if (percyInstructions) {
|
|
23
|
-
combinedInstructions +=
|
|
24
|
-
"\n\n" + formatPercyInstructions(percyInstructions);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
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.`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// Apply consistent formatting for all configurations
|
|
31
|
-
return formatFinalInstructions(combinedInstructions);
|
|
32
|
-
}
|
|
33
|
-
// Handle default flow using browserstack.yml
|
|
34
|
-
const sdkSetupCommand = getSDKPrefixCommand(detectedLanguage, detectedTestingFramework, username, accessKey);
|
|
35
|
-
const ymlInstructions = generateBrowserStackYMLInstructions(desiredPlatforms, enablePercy);
|
|
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
|
|
51
|
-
if (enablePercy) {
|
|
52
|
-
const percyInstructions = getPercyInstructions(detectedLanguage, detectedBrowserAutomationFramework, detectedTestingFramework);
|
|
53
|
-
if (percyInstructions) {
|
|
54
|
-
combinedInstructions +=
|
|
55
|
-
"\n\n" + formatPercyInstructions(percyInstructions);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
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.`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
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)}`;
|
|
71
|
-
return {
|
|
72
|
-
content: [
|
|
73
|
-
{
|
|
74
|
-
type: "text",
|
|
75
|
-
text: fullInstructions,
|
|
76
|
-
isError: false,
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
export default function addSDKTools(server, config) {
|
|
1
|
+
import { RunTestsOnBrowserStackParamsShape } from "./sdk-utils/common/schema.js";
|
|
2
|
+
import { runTestsOnBrowserStackHandler } from "./sdk-utils/handler.js";
|
|
3
|
+
import { RUN_ON_BROWSERSTACK_DESCRIPTION } from "./sdk-utils/common/constants.js";
|
|
4
|
+
export function registerRunBrowserStackTestsTool(server, config) {
|
|
82
5
|
const tools = {};
|
|
83
|
-
tools.setupBrowserStackAutomateTests = server.tool("setupBrowserStackAutomateTests",
|
|
84
|
-
|
|
85
|
-
.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum)
|
|
86
|
-
.describe("The automation framework configured in the project. Example: 'playwright', 'selenium'"),
|
|
87
|
-
detectedTestingFramework: z
|
|
88
|
-
.nativeEnum(SDKSupportedTestingFrameworkEnum)
|
|
89
|
-
.describe("The testing framework used in the project. Be precise with framework selection Example: 'webdriverio', 'jest', 'pytest', 'junit4', 'junit5', 'mocha'"),
|
|
90
|
-
detectedLanguage: z
|
|
91
|
-
.nativeEnum(SDKSupportedLanguageEnum)
|
|
92
|
-
.describe("The programming language used in the project. Example: 'nodejs', 'python', 'java', 'csharp'"),
|
|
93
|
-
desiredPlatforms: z
|
|
94
|
-
.array(z.enum(["windows", "macos", "android", "ios"]))
|
|
95
|
-
.describe("The platforms the user wants to test on. Always ask this to the user, do not try to infer this."),
|
|
96
|
-
enablePercy: z
|
|
97
|
-
.boolean()
|
|
98
|
-
.optional()
|
|
99
|
-
.default(false)
|
|
100
|
-
.describe("Set to true if the user wants to enable Percy for visual testing. Defaults to false."),
|
|
101
|
-
}, async (args) => {
|
|
102
|
-
try {
|
|
103
|
-
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
|
|
104
|
-
return await bootstrapProjectWithSDK({
|
|
105
|
-
detectedBrowserAutomationFramework: args.detectedBrowserAutomationFramework,
|
|
106
|
-
detectedTestingFramework: args.detectedTestingFramework,
|
|
107
|
-
detectedLanguage: args.detectedLanguage,
|
|
108
|
-
desiredPlatforms: args.desiredPlatforms,
|
|
109
|
-
enablePercy: args.enablePercy,
|
|
110
|
-
config,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error, config);
|
|
115
|
-
return {
|
|
116
|
-
content: [
|
|
117
|
-
{
|
|
118
|
-
type: "text",
|
|
119
|
-
text: `Failed to bootstrap project with BrowserStack SDK. Error: ${error}. Please open an issue on GitHub if the problem persists`,
|
|
120
|
-
isError: true,
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
isError: true,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
6
|
+
tools.setupBrowserStackAutomateTests = server.tool("setupBrowserStackAutomateTests", RUN_ON_BROWSERSTACK_DESCRIPTION, RunTestsOnBrowserStackParamsShape, async (args) => {
|
|
7
|
+
return runTestsOnBrowserStackHandler(args, config);
|
|
126
8
|
});
|
|
127
9
|
return tools;
|
|
128
10
|
}
|
|
11
|
+
export default registerRunBrowserStackTestsTool;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { listTestFiles } from "./percy-snapshot-utils/detect-test-files.js";
|
|
2
|
+
import { testFilePathsMap } from "../lib/inmemory-store.js";
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
export async function addListTestFiles(args) {
|
|
5
|
+
const { dirs, language, framework } = args;
|
|
6
|
+
let testFiles = [];
|
|
7
|
+
for (const dir of dirs) {
|
|
8
|
+
const files = await listTestFiles({
|
|
9
|
+
language,
|
|
10
|
+
framework,
|
|
11
|
+
baseDir: dir,
|
|
12
|
+
});
|
|
13
|
+
testFiles = testFiles.concat(files);
|
|
14
|
+
}
|
|
15
|
+
if (testFiles.length === 0) {
|
|
16
|
+
throw new Error("No test files found");
|
|
17
|
+
}
|
|
18
|
+
// Generate a UUID and store the test files in memory
|
|
19
|
+
const uuid = crypto.randomUUID();
|
|
20
|
+
testFilePathsMap.set(uuid, testFiles);
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: `The Test files are stored in memory with id ${uuid} and the total number of tests files found is ${testFiles.length}. You can use this UUID to retrieve the tests file paths later.`,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: `You can now use the tool addPercySnapshotCommands to update the test file with Percy commands for visual testing with the UUID ${uuid}`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { BrowserStackConfig } from "../lib/types.js";
|
|
3
|
+
export declare function registerPercyTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
|
4
|
+
export default registerPercyTools;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { SetUpPercyParamsShape } from "./sdk-utils/common/schema.js";
|
|
2
|
+
import { updateTestsWithPercyCommands } from "./add-percy-snapshots.js";
|
|
3
|
+
import { addListTestFiles } from "./list-test-files.js";
|
|
4
|
+
import { trackMCP } from "../index.js";
|
|
5
|
+
import { setUpPercyHandler, setUpSimulatePercyChangeHandler, } from "./sdk-utils/handler.js";
|
|
6
|
+
import { SETUP_PERCY_DESCRIPTION, SIMULATE_PERCY_CHANGE_DESCRIPTION, LIST_TEST_FILES_DESCRIPTION, PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, } from "./sdk-utils/common/constants.js";
|
|
7
|
+
import { ListTestFilesParamsShape, UpdateTestFileWithInstructionsParams, } from "./percy-snapshot-utils/constants.js";
|
|
8
|
+
export function registerPercyTools(server, config) {
|
|
9
|
+
const tools = {};
|
|
10
|
+
// Register setupPercyVisualTesting
|
|
11
|
+
tools.setupPercyVisualTesting = server.tool("setupPercyVisualTesting", SETUP_PERCY_DESCRIPTION, SetUpPercyParamsShape, async (args) => {
|
|
12
|
+
try {
|
|
13
|
+
trackMCP("setupPercyVisualTesting", server.server.getClientVersion(), config);
|
|
14
|
+
return setUpPercyHandler(args, config);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
trackMCP("setupPercyVisualTesting", server.server.getClientVersion(), error, config);
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
type: "text",
|
|
22
|
+
text: error instanceof Error ? error.message : String(error),
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// Register simulatePercyChange
|
|
30
|
+
tools.simulatePercyChange = server.tool("simulatePercyChange", SIMULATE_PERCY_CHANGE_DESCRIPTION, SetUpPercyParamsShape, async (args) => {
|
|
31
|
+
try {
|
|
32
|
+
trackMCP("simulatePercyChange", server.server.getClientVersion(), config);
|
|
33
|
+
return setUpSimulatePercyChangeHandler(args, config);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
trackMCP("simulatePercyChange", server.server.getClientVersion(), error, config);
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: error instanceof Error ? error.message : String(error),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// Register addPercySnapshotCommands
|
|
49
|
+
tools.addPercySnapshotCommands = server.tool("addPercySnapshotCommands", PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, UpdateTestFileWithInstructionsParams, async (args) => {
|
|
50
|
+
try {
|
|
51
|
+
trackMCP("addPercySnapshotCommands", server.server.getClientVersion(), config);
|
|
52
|
+
return await updateTestsWithPercyCommands(args);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
trackMCP("addPercySnapshotCommands", server.server.getClientVersion(), error, config);
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: error instanceof Error ? error.message : String(error),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// Register listTestFiles
|
|
68
|
+
tools.listTestFiles = server.tool("listTestFiles", LIST_TEST_FILES_DESCRIPTION, ListTestFilesParamsShape, async (args) => {
|
|
69
|
+
try {
|
|
70
|
+
trackMCP("listTestFiles", server.server.getClientVersion(), config);
|
|
71
|
+
return addListTestFiles(args);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
trackMCP("listTestFiles", server.server.getClientVersion(), error, config);
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: "text",
|
|
79
|
+
text: error instanceof Error ? error.message : String(error),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
isError: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return tools;
|
|
87
|
+
}
|
|
88
|
+
export default registerPercyTools;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SDKSupportedLanguage } from "../sdk-utils/common/types.js";
|
|
3
|
+
import { DetectionConfig } from "./types.js";
|
|
4
|
+
export declare const UpdateTestFileWithInstructionsParams: {
|
|
5
|
+
uuid: z.ZodString;
|
|
6
|
+
index: z.ZodNumber;
|
|
7
|
+
};
|
|
8
|
+
export declare const ListTestFilesParamsShape: {
|
|
9
|
+
dirs: z.ZodArray<z.ZodString, "many">;
|
|
10
|
+
language: z.ZodEnum<[string, ...string[]]>;
|
|
11
|
+
framework: z.ZodEnum<[string, ...string[]]>;
|
|
12
|
+
};
|
|
13
|
+
export declare const TEST_FILE_DETECTION: Record<SDKSupportedLanguage, DetectionConfig>;
|
|
14
|
+
export declare const EXCLUDED_DIRS: Set<string>;
|
|
15
|
+
export declare const backendIndicators: RegExp[];
|
|
16
|
+
export declare const strongUIIndicators: RegExp[];
|