@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.
Files changed (113) hide show
  1. package/README.md +227 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/lib/device-cache.d.ts +1 -0
  5. package/dist/lib/device-cache.js +28 -0
  6. package/dist/lib/inmemory-store.d.ts +1 -0
  7. package/dist/lib/inmemory-store.js +1 -0
  8. package/dist/lib/instrumentation.js +2 -0
  9. package/dist/oninitialized.js +4 -1
  10. package/dist/server-factory.js +3 -1
  11. package/dist/tools/accessibility.js +238 -78
  12. package/dist/tools/accessiblity-utils/auth-config.d.ts +39 -0
  13. package/dist/tools/accessiblity-utils/auth-config.js +125 -0
  14. package/dist/tools/accessiblity-utils/scanner.d.ts +1 -1
  15. package/dist/tools/accessiblity-utils/scanner.js +2 -1
  16. package/dist/tools/add-percy-snapshots.d.ts +5 -0
  17. package/dist/tools/add-percy-snapshots.js +17 -0
  18. package/dist/tools/appautomate-utils/appautomate.d.ts +2 -1
  19. package/dist/tools/appautomate-utils/appautomate.js +12 -9
  20. package/dist/tools/appautomate.js +56 -7
  21. package/dist/tools/applive-utils/start-session.d.ts +2 -1
  22. package/dist/tools/applive-utils/start-session.js +17 -6
  23. package/dist/tools/applive.d.ts +2 -1
  24. package/dist/tools/applive.js +21 -17
  25. package/dist/tools/bstack-sdk.d.ts +2 -15
  26. package/dist/tools/bstack-sdk.js +7 -124
  27. package/dist/tools/list-test-files.d.ts +2 -0
  28. package/dist/tools/list-test-files.js +33 -0
  29. package/dist/tools/percy-sdk.d.ts +4 -0
  30. package/dist/tools/percy-sdk.js +88 -0
  31. package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
  32. package/dist/tools/percy-snapshot-utils/constants.js +500 -0
  33. package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
  34. package/dist/tools/percy-snapshot-utils/detect-test-files.js +194 -0
  35. package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
  36. package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
  37. package/dist/tools/percy-snapshot-utils/utils.js +30 -0
  38. package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
  39. package/dist/tools/sdk-utils/bstack/commands.js +88 -0
  40. package/dist/tools/sdk-utils/bstack/configUtils.d.ts +4 -0
  41. package/dist/tools/sdk-utils/bstack/configUtils.js +66 -0
  42. package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
  43. package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +128 -76
  44. package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
  45. package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
  46. package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
  47. package/dist/tools/sdk-utils/bstack/index.js +5 -0
  48. package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
  49. package/dist/tools/sdk-utils/bstack/sdkHandler.js +74 -0
  50. package/dist/tools/sdk-utils/common/constants.d.ts +10 -0
  51. package/dist/tools/sdk-utils/common/constants.js +86 -0
  52. package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
  53. package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
  54. package/dist/tools/sdk-utils/common/index.d.ts +3 -0
  55. package/dist/tools/sdk-utils/common/index.js +4 -0
  56. package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
  57. package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
  58. package/dist/tools/sdk-utils/common/schema.d.ts +61 -0
  59. package/dist/tools/sdk-utils/common/schema.js +28 -0
  60. package/dist/tools/sdk-utils/common/types.d.ts +66 -0
  61. package/dist/tools/sdk-utils/common/types.js +50 -0
  62. package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
  63. package/dist/tools/sdk-utils/common/utils.js +84 -0
  64. package/dist/tools/sdk-utils/handler.d.ts +5 -0
  65. package/dist/tools/sdk-utils/handler.js +144 -0
  66. package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
  67. package/dist/tools/sdk-utils/percy-automate/constants.js +365 -0
  68. package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
  69. package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
  70. package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
  71. package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
  72. package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
  73. package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
  74. package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
  75. package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
  76. package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
  77. package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
  78. package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
  79. package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
  80. package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
  81. package/dist/tools/sdk-utils/percy-bstack/handler.js +99 -0
  82. package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
  83. package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
  84. package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
  85. package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
  86. package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
  87. package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
  88. package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
  89. package/dist/tools/sdk-utils/percy-web/constants.js +941 -0
  90. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
  91. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +28 -0
  92. package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
  93. package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
  94. package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
  95. package/dist/tools/sdk-utils/percy-web/handler.js +27 -0
  96. package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
  97. package/dist/tools/sdk-utils/percy-web/index.js +4 -0
  98. package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
  99. package/dist/tools/sdk-utils/percy-web/types.js +1 -0
  100. package/dist/tools/sdk-utils/types.d.ts +2 -1
  101. package/dist/tools/sdk-utils/types.js +1 -0
  102. package/dist/tools/testmanagement-utils/create-testcase.d.ts +4 -0
  103. package/dist/tools/testmanagement-utils/create-testcase.js +6 -0
  104. package/package.json +1 -1
  105. package/dist/tools/sdk-utils/commands.js +0 -65
  106. package/dist/tools/sdk-utils/instructions.d.ts +0 -6
  107. package/dist/tools/sdk-utils/instructions.js +0 -99
  108. package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
  109. package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
  110. package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
  111. /package/dist/tools/{getFailureLogs.d.ts → get-failure-logs.d.ts} +0 -0
  112. /package/dist/tools/{getFailureLogs.js → get-failure-logs.js} +0 -0
  113. /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
- const app_url = await uploadApp(appPath, username, password);
39
- logger.info(`App uploaded. URL: ${app_url}`);
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
- const app_url = await uploadEspressoApp(args.appPath, config);
96
- const test_suite_url = await uploadEspressoTestSuite(args.testSuitePath, config);
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
- const app_url = await uploadXcuiApp(args.appPath, config);
115
- const test_suite_url = await uploadXcuiTestSuite(args.testSuitePath, config);
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: string;
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
- const authString = getBrowserStackAuth(config);
43
- const [username, password] = authString.split(":");
44
- const { app_url } = await uploadApp(appPath, username, password);
45
- logger.info(`App uploaded: ${app_url}`);
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
  }
@@ -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: string;
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>;
@@ -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 a appPath.");
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 (args.desiredPlatform === "android" && !args.appPath.endsWith(".apk")) {
20
- throw new Error("You must provide a valid Android app path.");
21
- }
22
- if (args.desiredPlatform === "ios" && !args.appPath.endsWith(".ipa")) {
23
- throw new Error("You must provide a valid iOS app path.");
24
- }
25
- // check if the app path exists && is readable
26
- try {
27
- if (!fs.existsSync(args.appPath)) {
28
- throw new Error("The app path does not exist.");
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
- * 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>;
3
+ export declare function registerRunBrowserStackTestsTool(server: McpServer, config: BrowserStackConfig): Record<string, any>;
4
+ export default registerRunBrowserStackTestsTool;
@@ -1,128 +1,11 @@
1
- import { z } from "zod";
2
- import { trackMCP } from "../lib/instrumentation.js";
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";
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", "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", {
84
- detectedBrowserAutomationFramework: z
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,2 @@
1
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare function addListTestFiles(args: any): Promise<CallToolResult>;
@@ -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[];