@browserstack/mcp-server 1.2.3 → 1.2.5

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 (164) hide show
  1. package/README.md +96 -6
  2. package/dist/lib/apiClient.d.ts +7 -5
  3. package/dist/lib/apiClient.js +76 -15
  4. package/dist/lib/device-cache.d.ts +3 -1
  5. package/dist/lib/device-cache.js +24 -17
  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 +6 -3
  9. package/dist/lib/utils.d.ts +78 -0
  10. package/dist/lib/utils.js +47 -0
  11. package/dist/lib/version-resolver.js +26 -14
  12. package/dist/server-factory.js +6 -0
  13. package/dist/tools/add-percy-snapshots.d.ts +5 -0
  14. package/dist/tools/add-percy-snapshots.js +17 -0
  15. package/dist/tools/appautomate-utils/appium-sdk/config-generator.d.ts +7 -0
  16. package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +70 -0
  17. package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +23 -0
  18. package/dist/tools/appautomate-utils/appium-sdk/constants.js +64 -0
  19. package/dist/tools/appautomate-utils/appium-sdk/formatter.d.ts +8 -0
  20. package/dist/tools/appautomate-utils/appium-sdk/formatter.js +59 -0
  21. package/dist/tools/appautomate-utils/appium-sdk/handler.d.ts +3 -0
  22. package/dist/tools/appautomate-utils/appium-sdk/handler.js +66 -0
  23. package/dist/tools/appautomate-utils/appium-sdk/index.d.ts +7 -0
  24. package/dist/tools/appautomate-utils/appium-sdk/index.js +8 -0
  25. package/dist/tools/appautomate-utils/appium-sdk/instructions.d.ts +3 -0
  26. package/dist/tools/appautomate-utils/appium-sdk/instructions.js +47 -0
  27. package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.d.ts +2 -0
  28. package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.js +78 -0
  29. package/dist/tools/appautomate-utils/appium-sdk/languages/java.d.ts +10 -0
  30. package/dist/tools/appautomate-utils/appium-sdk/languages/java.js +121 -0
  31. package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.d.ts +3 -0
  32. package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.js +194 -0
  33. package/dist/tools/appautomate-utils/appium-sdk/languages/python.d.ts +3 -0
  34. package/dist/tools/appautomate-utils/appium-sdk/languages/python.js +76 -0
  35. package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.d.ts +2 -0
  36. package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.js +85 -0
  37. package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +58 -0
  38. package/dist/tools/appautomate-utils/appium-sdk/types.js +63 -0
  39. package/dist/tools/appautomate-utils/appium-sdk/utils.d.ts +17 -0
  40. package/dist/tools/appautomate-utils/appium-sdk/utils.js +64 -0
  41. package/dist/tools/appautomate-utils/{appautomate.d.ts → native-execution/appautomate.d.ts} +1 -1
  42. package/dist/tools/appautomate-utils/{appautomate.js → native-execution/appautomate.js} +2 -2
  43. package/dist/tools/appautomate-utils/native-execution/constants.d.ts +11 -0
  44. package/dist/tools/appautomate-utils/native-execution/constants.js +58 -0
  45. package/dist/tools/appautomate-utils/native-execution/types.d.ts +19 -0
  46. package/dist/tools/appautomate-utils/{types.js → native-execution/types.js} +5 -1
  47. package/dist/tools/appautomate.js +40 -42
  48. package/dist/tools/bstack-sdk.d.ts +2 -15
  49. package/dist/tools/bstack-sdk.js +10 -119
  50. package/dist/tools/build-insights.d.ts +7 -0
  51. package/dist/tools/build-insights.js +67 -0
  52. package/dist/tools/list-test-files.d.ts +2 -0
  53. package/dist/tools/list-test-files.js +36 -0
  54. package/dist/tools/percy-sdk.d.ts +4 -0
  55. package/dist/tools/percy-sdk.js +98 -0
  56. package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
  57. package/dist/tools/percy-snapshot-utils/constants.js +500 -0
  58. package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
  59. package/dist/tools/percy-snapshot-utils/detect-test-files.js +175 -0
  60. package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
  61. package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
  62. package/dist/tools/percy-snapshot-utils/utils.js +30 -0
  63. package/dist/tools/rca-agent-utils/constants.d.ts +13 -0
  64. package/dist/tools/rca-agent-utils/constants.js +24 -0
  65. package/dist/tools/rca-agent-utils/format-rca.d.ts +1 -0
  66. package/dist/tools/rca-agent-utils/format-rca.js +37 -0
  67. package/dist/tools/rca-agent-utils/get-build-id.d.ts +1 -0
  68. package/dist/tools/rca-agent-utils/get-build-id.js +18 -0
  69. package/dist/tools/rca-agent-utils/get-failed-test-id.d.ts +2 -0
  70. package/dist/tools/rca-agent-utils/get-failed-test-id.js +69 -0
  71. package/dist/tools/rca-agent-utils/rca-data.d.ts +9 -0
  72. package/dist/tools/rca-agent-utils/rca-data.js +196 -0
  73. package/dist/tools/rca-agent-utils/types.d.ts +48 -0
  74. package/dist/tools/rca-agent-utils/types.js +20 -0
  75. package/dist/tools/rca-agent.d.ts +14 -0
  76. package/dist/tools/rca-agent.js +119 -0
  77. package/dist/tools/review-agent-utils/build-counts.d.ts +7 -0
  78. package/dist/tools/review-agent-utils/build-counts.js +44 -0
  79. package/dist/tools/review-agent-utils/percy-approve-reject.d.ts +6 -0
  80. package/dist/tools/review-agent-utils/percy-approve-reject.js +39 -0
  81. package/dist/tools/review-agent-utils/percy-diffs.d.ts +9 -0
  82. package/dist/tools/review-agent-utils/percy-diffs.js +35 -0
  83. package/dist/tools/review-agent-utils/percy-snapshots.d.ts +11 -0
  84. package/dist/tools/review-agent-utils/percy-snapshots.js +58 -0
  85. package/dist/tools/review-agent.d.ts +5 -0
  86. package/dist/tools/review-agent.js +56 -0
  87. package/dist/tools/run-percy-scan.d.ts +8 -0
  88. package/dist/tools/run-percy-scan.js +37 -0
  89. package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
  90. package/dist/tools/sdk-utils/bstack/commands.js +88 -0
  91. package/dist/tools/sdk-utils/bstack/configUtils.d.ts +7 -0
  92. package/dist/tools/sdk-utils/bstack/configUtils.js +113 -0
  93. package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
  94. package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +117 -78
  95. package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
  96. package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
  97. package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
  98. package/dist/tools/sdk-utils/bstack/index.js +5 -0
  99. package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
  100. package/dist/tools/sdk-utils/bstack/sdkHandler.js +82 -0
  101. package/dist/tools/sdk-utils/common/constants.d.ts +11 -0
  102. package/dist/tools/sdk-utils/common/constants.js +87 -0
  103. package/dist/tools/sdk-utils/common/device-validator.d.ts +25 -0
  104. package/dist/tools/sdk-utils/common/device-validator.js +368 -0
  105. package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
  106. package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
  107. package/dist/tools/sdk-utils/common/index.d.ts +3 -0
  108. package/dist/tools/sdk-utils/common/index.js +4 -0
  109. package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
  110. package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
  111. package/dist/tools/sdk-utils/common/schema.d.ts +93 -0
  112. package/dist/tools/sdk-utils/common/schema.js +105 -0
  113. package/dist/tools/sdk-utils/common/types.d.ts +66 -0
  114. package/dist/tools/sdk-utils/{types.js → common/types.js} +15 -2
  115. package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
  116. package/dist/tools/sdk-utils/common/utils.js +91 -0
  117. package/dist/tools/sdk-utils/handler.d.ts +5 -0
  118. package/dist/tools/sdk-utils/handler.js +147 -0
  119. package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
  120. package/dist/tools/sdk-utils/percy-automate/constants.js +338 -0
  121. package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
  122. package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
  123. package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
  124. package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
  125. package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
  126. package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
  127. package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
  128. package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
  129. package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
  130. package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
  131. package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
  132. package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
  133. package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
  134. package/dist/tools/sdk-utils/percy-bstack/handler.js +103 -0
  135. package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
  136. package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
  137. package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
  138. package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
  139. package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
  140. package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
  141. package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
  142. package/dist/tools/sdk-utils/percy-web/constants.js +883 -0
  143. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
  144. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +32 -0
  145. package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
  146. package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
  147. package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
  148. package/dist/tools/sdk-utils/percy-web/handler.js +29 -0
  149. package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
  150. package/dist/tools/sdk-utils/percy-web/index.js +4 -0
  151. package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
  152. package/dist/tools/sdk-utils/percy-web/types.js +1 -0
  153. package/dist/tools/testmanagement-utils/create-testrun.d.ts +4 -4
  154. package/dist/tools/testmanagement-utils/update-testrun.d.ts +4 -4
  155. package/package.json +3 -2
  156. package/dist/tools/appautomate-utils/types.d.ts +0 -5
  157. package/dist/tools/sdk-utils/commands.js +0 -65
  158. package/dist/tools/sdk-utils/instructions.d.ts +0 -6
  159. package/dist/tools/sdk-utils/instructions.js +0 -99
  160. package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
  161. package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
  162. package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
  163. package/dist/tools/sdk-utils/types.d.ts +0 -40
  164. /package/dist/tools/{sdk-utils/percy → percy-snapshot-utils}/types.js +0 -0
@@ -0,0 +1,19 @@
1
+ export declare enum AppTestPlatform {
2
+ ESPRESSO = "espresso",
3
+ XCUITEST = "xcuitest"
4
+ }
5
+ export interface Device {
6
+ device: string;
7
+ display_name: string;
8
+ os_version: string;
9
+ real_mobile: boolean;
10
+ }
11
+ export interface PlatformDevices {
12
+ os: string;
13
+ os_display_name: string;
14
+ devices: Device[];
15
+ }
16
+ export declare enum Platform {
17
+ ANDROID = "android",
18
+ IOS = "ios"
19
+ }
@@ -1,6 +1,10 @@
1
1
  export var AppTestPlatform;
2
2
  (function (AppTestPlatform) {
3
3
  AppTestPlatform["ESPRESSO"] = "espresso";
4
- AppTestPlatform["APPIUM"] = "appium";
5
4
  AppTestPlatform["XCUITEST"] = "xcuitest";
6
5
  })(AppTestPlatform || (AppTestPlatform = {}));
6
+ export var Platform;
7
+ (function (Platform) {
8
+ Platform["ANDROID"] = "android";
9
+ Platform["IOS"] = "ios";
10
+ })(Platform || (Platform = {}));
@@ -4,14 +4,14 @@ import { getBrowserStackAuth } from "../lib/get-auth.js";
4
4
  import { trackMCP } from "../lib/instrumentation.js";
5
5
  import { maybeCompressBase64 } from "../lib/utils.js";
6
6
  import { remote } from "webdriverio";
7
- import { AppTestPlatform } from "./appautomate-utils/types.js";
7
+ import { AppTestPlatform } from "./appautomate-utils/native-execution/types.js";
8
+ import { setupAppAutomateHandler } from "./appautomate-utils/appium-sdk/handler.js";
9
+ import { validateAppAutomateDevices } from "./sdk-utils/common/device-validator.js";
10
+ import { SETUP_APP_AUTOMATE_DESCRIPTION, SETUP_APP_AUTOMATE_SCHEMA, } from "./appautomate-utils/appium-sdk/constants.js";
11
+ import { Platform, } from "./appautomate-utils/native-execution/types.js";
8
12
  import { getDevicesAndBrowsers, BrowserStackProducts, } from "../lib/device-cache.js";
9
- import { findMatchingDevice, getDeviceVersions, resolveVersion, validateArgs, uploadApp, uploadEspressoApp, uploadEspressoTestSuite, triggerEspressoBuild, uploadXcuiApp, uploadXcuiTestSuite, triggerXcuiBuild, } from "./appautomate-utils/appautomate.js";
10
- var Platform;
11
- (function (Platform) {
12
- Platform["ANDROID"] = "android";
13
- Platform["IOS"] = "ios";
14
- })(Platform || (Platform = {}));
13
+ import { findMatchingDevice, getDeviceVersions, resolveVersion, validateArgs, uploadApp, uploadEspressoApp, uploadEspressoTestSuite, triggerEspressoBuild, uploadXcuiApp, uploadXcuiTestSuite, triggerXcuiBuild, } from "./appautomate-utils/native-execution/appautomate.js";
14
+ import { RUN_APP_AUTOMATE_DESCRIPTION, RUN_APP_AUTOMATE_SCHEMA, } from "./appautomate-utils/native-execution/constants.js";
15
15
  /**
16
16
  * Launches an app on a selected BrowserStack device and takes a screenshot.
17
17
  */
@@ -106,6 +106,8 @@ async function runAppTestsOnBrowserStack(args, config) {
106
106
  if (!args.browserstackTestSuiteUrl && !args.testSuitePath) {
107
107
  throw new Error("testSuitePath is required when browserstackTestSuiteUrl is not provided");
108
108
  }
109
+ // Validate devices against real BrowserStack device data
110
+ await validateAppAutomateDevices(args.devices);
109
111
  switch (args.detectedAutomationFramework) {
110
112
  case AppTestPlatform.ESPRESSO: {
111
113
  try {
@@ -127,7 +129,12 @@ async function runAppTestsOnBrowserStack(args, config) {
127
129
  test_suite_url = await uploadEspressoTestSuite(args.testSuitePath, config);
128
130
  logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
129
131
  }
130
- const build_id = await triggerEspressoBuild(app_url, test_suite_url, args.devices, args.project);
132
+ // Convert array format to string format for Espresso
133
+ const deviceStrings = args.devices.map((device) => {
134
+ const [, deviceName, osVersion] = device;
135
+ return `${deviceName}-${osVersion}`;
136
+ });
137
+ const build_id = await triggerEspressoBuild(app_url, test_suite_url, deviceStrings, args.project);
131
138
  return {
132
139
  content: [
133
140
  {
@@ -162,7 +169,12 @@ async function runAppTestsOnBrowserStack(args, config) {
162
169
  test_suite_url = await uploadXcuiTestSuite(args.testSuitePath, config);
163
170
  logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
164
171
  }
165
- const build_id = await triggerXcuiBuild(app_url, test_suite_url, args.devices, args.project, config);
172
+ // Convert array format to string format for XCUITest
173
+ const deviceStrings = args.devices.map((device) => {
174
+ const [, deviceName, osVersion] = device;
175
+ return `${deviceName}-${osVersion}`;
176
+ });
177
+ const build_id = await triggerXcuiBuild(app_url, test_suite_url, deviceStrings, args.project, config);
166
178
  return {
167
179
  content: [
168
180
  {
@@ -214,39 +226,7 @@ export default function addAppAutomationTools(server, config) {
214
226
  };
215
227
  }
216
228
  });
217
- tools.runAppTestsOnBrowserStack = server.tool("runAppTestsOnBrowserStack", "Run AppAutomate tests on BrowserStack by uploading app and test suite. If running from Android Studio or Xcode, the tool will help export app and test files automatically. For other environments, you'll need to provide the paths to your pre-built app and test files.", {
218
- appPath: z
219
- .string()
220
- .describe("Path to your application file:\n" +
221
- "If in development IDE directory:\n" +
222
- "• For Android: 'gradle assembleDebug'\n" +
223
- "• For iOS:\n" +
224
- " xcodebuild clean -scheme YOUR_SCHEME && \\\n" +
225
- " xcodebuild archive -scheme YOUR_SCHEME -configuration Release -archivePath build/app.xcarchive && \\\n" +
226
- " xcodebuild -exportArchive -archivePath build/app.xcarchive -exportPath build/ipa -exportOptionsPlist exportOptions.plist\n\n" +
227
- "If in other directory, provide existing app path"),
228
- testSuitePath: z
229
- .string()
230
- .describe("Path to your test suite file:\n" +
231
- "If in development IDE directory:\n" +
232
- "• For Android: 'gradle assembleAndroidTest'\n" +
233
- "• For iOS:\n" +
234
- " xcodebuild test-without-building -scheme YOUR_SCHEME -destination 'generic/platform=iOS' && \\\n" +
235
- " cd ~/Library/Developer/Xcode/DerivedData/*/Build/Products/Debug-iphonesimulator/ && \\\n" +
236
- " zip -r Tests.zip *.xctestrun *-Runner.app\n\n" +
237
- "If in other directory, provide existing test file path"),
238
- devices: z
239
- .array(z.string())
240
- .describe("List of devices to run the test on, e.g., ['Samsung Galaxy S20-10.0', 'iPhone 12 Pro-16.0']."),
241
- project: z
242
- .string()
243
- .optional()
244
- .default("BStack-AppAutomate-Suite")
245
- .describe("Project name for organizing test runs on BrowserStack."),
246
- detectedAutomationFramework: z
247
- .string()
248
- .describe("The automation framework used in the project, such as 'espresso' (Android) or 'xcuitest' (iOS)."),
249
- }, async (args) => {
229
+ tools.runAppTestsOnBrowserStack = server.tool("runAppTestsOnBrowserStack", RUN_APP_AUTOMATE_DESCRIPTION, RUN_APP_AUTOMATE_SCHEMA, async (args) => {
250
230
  try {
251
231
  trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
252
232
  return await runAppTestsOnBrowserStack(args, config);
@@ -265,5 +245,23 @@ export default function addAppAutomationTools(server, config) {
265
245
  };
266
246
  }
267
247
  });
248
+ tools.setupBrowserStackAppAutomateTests = server.tool("setupBrowserStackAppAutomateTests", SETUP_APP_AUTOMATE_DESCRIPTION, SETUP_APP_AUTOMATE_SCHEMA, async (args) => {
249
+ try {
250
+ return await setupAppAutomateHandler(args, config);
251
+ }
252
+ catch (error) {
253
+ const error_message = error instanceof Error ? error.message : "Unknown error";
254
+ return {
255
+ content: [
256
+ {
257
+ type: "text",
258
+ text: `Failed to bootstrap project with BrowserStack App Automate SDK. Error: ${error_message}. Please open an issue on GitHub if the problem persists`,
259
+ isError: true,
260
+ },
261
+ ],
262
+ isError: true,
263
+ };
264
+ }
265
+ });
268
266
  return tools;
269
267
  }
@@ -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,19 @@
1
- import { z } from "zod";
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
+ import { handleMCPError } from "../lib/utils.js";
2
5
  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) {
6
+ export function registerRunBrowserStackTestsTool(server, config) {
82
7
  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) => {
8
+ tools.setupBrowserStackAutomateTests = server.tool("setupBrowserStackAutomateTests", RUN_ON_BROWSERSTACK_DESCRIPTION, RunTestsOnBrowserStackParamsShape, async (args) => {
102
9
  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
- });
10
+ trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), config);
11
+ return await runTestsOnBrowserStackHandler(args, config);
112
12
  }
113
13
  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
- };
14
+ return handleMCPError("runTestsOnBrowserStack", server, config, error);
125
15
  }
126
16
  });
127
17
  return tools;
128
18
  }
19
+ export default registerRunBrowserStackTestsTool;
@@ -0,0 +1,7 @@
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 fetchBuildInsightsTool(args: {
5
+ buildId: string;
6
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
7
+ export default function addBuildInsightsTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+ import logger from "../logger.js";
3
+ import { fetchFromBrowserStackAPI, handleMCPError } from "../lib/utils.js";
4
+ import { trackMCP } from "../lib/instrumentation.js";
5
+ // Tool function that fetches build insights from two APIs
6
+ export async function fetchBuildInsightsTool(args, config) {
7
+ try {
8
+ const buildUrl = `https://api-automation.browserstack.com/ext/v1/builds/${args.buildId}`;
9
+ const qualityGateUrl = `https://api-automation.browserstack.com/ext/v1/quality-gates/${args.buildId}`;
10
+ const [buildData, qualityData] = await Promise.all([
11
+ fetchFromBrowserStackAPI(buildUrl, config),
12
+ fetchFromBrowserStackAPI(qualityGateUrl, config),
13
+ ]);
14
+ // Select useful fields for users
15
+ const insights = {
16
+ name: buildData.name,
17
+ status: buildData.status,
18
+ duration: buildData.duration,
19
+ user: buildData.user,
20
+ tags: buildData.tags,
21
+ alerts: buildData.alerts,
22
+ status_stats: buildData.status_stats,
23
+ failure_categories: buildData.failure_categories,
24
+ smart_tags: buildData.smart_tags,
25
+ unique_errors: buildData.unique_errors?.overview,
26
+ observability_url: buildData?.observability_url,
27
+ ci_build_url: buildData.ci_info?.build_url,
28
+ quality_gate_result: qualityData.quality_gate_result,
29
+ };
30
+ const qualityProfiles = qualityData.quality_profiles?.map((profile) => ({
31
+ name: profile.name,
32
+ result: profile.result,
33
+ }));
34
+ const qualityProfilesText = qualityProfiles && qualityProfiles.length > 0
35
+ ? `Quality Gate Profiles (respond only if explicitly requested): ${JSON.stringify(qualityProfiles, null, 2)}`
36
+ : "No Quality Gate Profiles available.";
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: "Build insights:\n" + JSON.stringify(insights, null, 2),
42
+ },
43
+ { type: "text", text: qualityProfilesText },
44
+ ],
45
+ };
46
+ }
47
+ catch (error) {
48
+ logger.error("Error fetching build insights", error);
49
+ throw error;
50
+ }
51
+ }
52
+ // Registers the fetchBuildInsights tool with the MCP server
53
+ export default function addBuildInsightsTools(server, config) {
54
+ const tools = {};
55
+ tools.fetchBuildInsights = server.tool("fetchBuildInsights", "Fetches insights about a BrowserStack build by combining build details and quality gate results.", {
56
+ buildId: z.string().describe("The build UUID of the BrowserStack build"),
57
+ }, async (args) => {
58
+ try {
59
+ trackMCP("fetchBuildInsights", server.server.getClientVersion(), config);
60
+ return await fetchBuildInsightsTool(args, config);
61
+ }
62
+ catch (error) {
63
+ return handleMCPError("fetchBuildInsights", server, config, error);
64
+ }
65
+ });
66
+ return tools;
67
+ }
@@ -0,0 +1,2 @@
1
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare function addListTestFiles(args: any): Promise<CallToolResult>;
@@ -0,0 +1,36 @@
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
+ if (!dirs || dirs.length === 0) {
8
+ throw new Error("No directories provided to add the test files. Please provide test directories to add percy snapshot commands.");
9
+ }
10
+ for (const dir of dirs) {
11
+ const files = await listTestFiles({
12
+ language,
13
+ framework,
14
+ baseDir: dir,
15
+ });
16
+ testFiles = testFiles.concat(files);
17
+ }
18
+ if (testFiles.length === 0) {
19
+ throw new Error("No test files found");
20
+ }
21
+ // Generate a UUID and store the test files in memory
22
+ const uuid = crypto.randomUUID();
23
+ testFilePathsMap.set(uuid, testFiles);
24
+ return {
25
+ content: [
26
+ {
27
+ type: "text",
28
+ 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.`,
29
+ },
30
+ {
31
+ type: "text",
32
+ text: `You can now use the tool addPercySnapshotCommands to update the test file with Percy commands for visual testing with the UUID ${uuid}`,
33
+ },
34
+ ],
35
+ };
36
+ }
@@ -0,0 +1,4 @@
1
+ import { BrowserStackConfig } from "../lib/types.js";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare function registerPercyTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
4
+ export default registerPercyTools;
@@ -0,0 +1,98 @@
1
+ import { trackMCP } from "../index.js";
2
+ import { fetchPercyChanges } from "./review-agent.js";
3
+ import { addListTestFiles } from "./list-test-files.js";
4
+ import { runPercyScan } from "./run-percy-scan.js";
5
+ import { SetUpPercyParamsShape } from "./sdk-utils/common/schema.js";
6
+ import { updateTestsWithPercyCommands } from "./add-percy-snapshots.js";
7
+ import { approveOrDeclinePercyBuild } from "./review-agent-utils/percy-approve-reject.js";
8
+ import { setUpPercyHandler, simulatePercyChangeHandler, } from "./sdk-utils/handler.js";
9
+ import { z } from "zod";
10
+ import { SETUP_PERCY_DESCRIPTION, LIST_TEST_FILES_DESCRIPTION, PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, SIMULATE_PERCY_CHANGE_DESCRIPTION, } from "./sdk-utils/common/constants.js";
11
+ import { ListTestFilesParamsShape, UpdateTestFileWithInstructionsParams, } from "./percy-snapshot-utils/constants.js";
12
+ import { RunPercyScanParamsShape, FetchPercyChangesParamsShape, ManagePercyBuildApprovalParamsShape, } from "./sdk-utils/common/schema.js";
13
+ import { handleMCPError } from "../lib/utils.js";
14
+ export function registerPercyTools(server, config) {
15
+ const tools = {};
16
+ server.prompt("integrate-percy", {
17
+ project_name: z
18
+ .string()
19
+ .describe("The name of the project to integrate with Percy"),
20
+ }, async ({ project_name }) => {
21
+ return {
22
+ messages: [
23
+ {
24
+ role: "assistant",
25
+ content: {
26
+ type: "text",
27
+ text: `Integrate percy in this project ${project_name} using tool percyVisualTestIntegrationAgent.`,
28
+ },
29
+ },
30
+ ],
31
+ };
32
+ });
33
+ tools.percyVisualTestIntegrationAgent = server.tool("percyVisualTestIntegrationAgent", SIMULATE_PERCY_CHANGE_DESCRIPTION, SetUpPercyParamsShape, async (args) => {
34
+ try {
35
+ trackMCP("VisualTestIntegrationAgent", server.server.getClientVersion(), config);
36
+ return simulatePercyChangeHandler(args, config);
37
+ }
38
+ catch (error) {
39
+ return handleMCPError("VisualTestIntegrationAgent", server, config, error);
40
+ }
41
+ });
42
+ tools.setupPercyVisualTesting = server.tool("expandPercyVisualTesting", SETUP_PERCY_DESCRIPTION, SetUpPercyParamsShape, async (args) => {
43
+ try {
44
+ trackMCP("setupPercyVisualTesting", server.server.getClientVersion(), config);
45
+ return setUpPercyHandler(args, config);
46
+ }
47
+ catch (error) {
48
+ return handleMCPError("setupPercyVisualTesting", server, config, error);
49
+ }
50
+ });
51
+ tools.addPercySnapshotCommands = server.tool("addPercySnapshotCommands", PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, UpdateTestFileWithInstructionsParams, async (args) => {
52
+ try {
53
+ trackMCP("addPercySnapshotCommands", server.server.getClientVersion(), config);
54
+ return await updateTestsWithPercyCommands(args);
55
+ }
56
+ catch (error) {
57
+ return handleMCPError("addPercySnapshotCommands", server, config, error);
58
+ }
59
+ });
60
+ tools.listTestFiles = server.tool("listTestFiles", LIST_TEST_FILES_DESCRIPTION, ListTestFilesParamsShape, async (args) => {
61
+ try {
62
+ trackMCP("listTestFiles", server.server.getClientVersion(), config);
63
+ return addListTestFiles(args);
64
+ }
65
+ catch (error) {
66
+ return handleMCPError("listTestFiles", server, config, error);
67
+ }
68
+ });
69
+ tools.runPercyScan = server.tool("runPercyScan", "Run a Percy visual test scan. Example prompts : Run this Percy build/scan. Never run percy scan/build without this tool", RunPercyScanParamsShape, async (args) => {
70
+ try {
71
+ trackMCP("runPercyScan", server.server.getClientVersion(), config);
72
+ return runPercyScan(args, config);
73
+ }
74
+ catch (error) {
75
+ return handleMCPError("runPercyScan", server, config, error);
76
+ }
77
+ });
78
+ tools.fetchPercyChanges = server.tool("fetchPercyChanges", "Retrieves and summarizes all visual changes detected by Percy AI between the latest and previous builds, helping quickly review what has changed in your project.", FetchPercyChangesParamsShape, async (args) => {
79
+ try {
80
+ trackMCP("fetchPercyChanges", server.server.getClientVersion(), config);
81
+ return await fetchPercyChanges(args, config);
82
+ }
83
+ catch (error) {
84
+ return handleMCPError("fetchPercyChanges", server, config, error);
85
+ }
86
+ });
87
+ tools.managePercyBuildApproval = server.tool("managePercyBuildApproval", "Approve or reject a Percy build", ManagePercyBuildApprovalParamsShape, async (args) => {
88
+ try {
89
+ trackMCP("managePercyBuildApproval", server.server.getClientVersion(), config);
90
+ return await approveOrDeclinePercyBuild(args, config);
91
+ }
92
+ catch (error) {
93
+ return handleMCPError("managePercyBuildApproval", server, config, error);
94
+ }
95
+ });
96
+ return tools;
97
+ }
98
+ 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[];