@browserstack/mcp-server 1.2.4 → 1.2.6

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 (63) hide show
  1. package/README.md +9 -5
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/lib/apiClient.d.ts +8 -5
  5. package/dist/lib/apiClient.js +77 -15
  6. package/dist/lib/device-cache.d.ts +3 -1
  7. package/dist/lib/device-cache.js +4 -0
  8. package/dist/lib/inmemory-store.d.ts +5 -1
  9. package/dist/lib/inmemory-store.js +10 -1
  10. package/dist/lib/instrumentation.js +6 -3
  11. package/dist/lib/utils.d.ts +75 -2
  12. package/dist/lib/utils.js +20 -0
  13. package/dist/lib/version-resolver.js +30 -14
  14. package/dist/tools/add-percy-snapshots.d.ts +0 -1
  15. package/dist/tools/add-percy-snapshots.js +11 -6
  16. package/dist/tools/appautomate-utils/appium-sdk/config-generator.d.ts +7 -1
  17. package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +46 -26
  18. package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +1 -1
  19. package/dist/tools/appautomate-utils/appium-sdk/constants.js +24 -3
  20. package/dist/tools/appautomate-utils/appium-sdk/handler.js +16 -2
  21. package/dist/tools/appautomate-utils/appium-sdk/languages/java.d.ts +2 -0
  22. package/dist/tools/appautomate-utils/appium-sdk/languages/java.js +63 -29
  23. package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +2 -1
  24. package/dist/tools/appautomate-utils/appium-sdk/types.js +10 -1
  25. package/dist/tools/appautomate-utils/native-execution/constants.d.ts +2 -1
  26. package/dist/tools/appautomate-utils/native-execution/constants.js +24 -2
  27. package/dist/tools/appautomate.js +15 -2
  28. package/dist/tools/automate-utils/fetch-screenshots.js +4 -1
  29. package/dist/tools/list-test-files.d.ts +1 -1
  30. package/dist/tools/list-test-files.js +43 -19
  31. package/dist/tools/percy-sdk.js +33 -6
  32. package/dist/tools/percy-snapshot-utils/constants.d.ts +0 -6
  33. package/dist/tools/percy-snapshot-utils/constants.js +0 -15
  34. package/dist/tools/rca-agent-utils/constants.d.ts +1 -1
  35. package/dist/tools/rca-agent-utils/constants.js +2 -2
  36. package/dist/tools/rca-agent-utils/rca-data.d.ts +1 -1
  37. package/dist/tools/rca-agent-utils/rca-data.js +2 -2
  38. package/dist/tools/rca-agent-utils/types.d.ts +3 -3
  39. package/dist/tools/rca-agent.d.ts +1 -1
  40. package/dist/tools/run-percy-scan.js +51 -10
  41. package/dist/tools/sdk-utils/bstack/configUtils.d.ts +8 -4
  42. package/dist/tools/sdk-utils/bstack/configUtils.js +74 -20
  43. package/dist/tools/sdk-utils/bstack/constants.d.ts +1 -1
  44. package/dist/tools/sdk-utils/bstack/constants.js +7 -9
  45. package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +1 -1
  46. package/dist/tools/sdk-utils/bstack/sdkHandler.js +19 -9
  47. package/dist/tools/sdk-utils/common/constants.d.ts +6 -5
  48. package/dist/tools/sdk-utils/common/constants.js +8 -7
  49. package/dist/tools/sdk-utils/common/device-validator.d.ts +25 -0
  50. package/dist/tools/sdk-utils/common/device-validator.js +375 -0
  51. package/dist/tools/sdk-utils/common/schema.d.ts +32 -8
  52. package/dist/tools/sdk-utils/common/schema.js +62 -3
  53. package/dist/tools/sdk-utils/common/utils.d.ts +1 -1
  54. package/dist/tools/sdk-utils/common/utils.js +14 -2
  55. package/dist/tools/sdk-utils/handler.d.ts +1 -0
  56. package/dist/tools/sdk-utils/handler.js +59 -14
  57. package/dist/tools/sdk-utils/percy-automate/constants.d.ts +4 -4
  58. package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -4
  59. package/dist/tools/sdk-utils/percy-bstack/handler.js +5 -1
  60. package/dist/tools/sdk-utils/percy-web/constants.d.ts +22 -20
  61. package/dist/tools/sdk-utils/percy-web/constants.js +39 -0
  62. package/dist/tools/sdk-utils/percy-web/handler.js +3 -1
  63. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { getBrowserStackAuth } from "../../../lib/get-auth.js";
3
+ import { validateAppAutomateDevices } from "../../sdk-utils/common/device-validator.js";
3
4
  import { getAppUploadInstruction, validateSupportforAppAutomate, } from "./utils.js";
4
5
  import { getAppSDKPrefixCommand, generateAppBrowserStackYMLInstructions, } from "./index.js";
5
6
  import { formatAppInstructionsWithNumbers, getAppInstructionsForProjectConfiguration, SETUP_APP_AUTOMATE_SCHEMA, } from "./index.js";
@@ -11,18 +12,31 @@ export async function setupAppAutomateHandler(rawInput, config) {
11
12
  // Use variables for all major input properties
12
13
  const testingFramework = input.detectedTestingFramework;
13
14
  const language = input.detectedLanguage;
14
- const platforms = input.desiredPlatforms ?? ["android"];
15
+ const inputDevices = input.devices ?? [];
15
16
  const appPath = input.appPath;
16
17
  const framework = input.detectedFramework;
17
18
  //Validating if supported framework or not
18
19
  validateSupportforAppAutomate(framework, language, testingFramework);
20
+ // Use default mobile devices when array is empty
21
+ const devices = inputDevices.length === 0
22
+ ? [["android", "Samsung Galaxy S24", "latest"]]
23
+ : inputDevices;
24
+ // Validate devices against real BrowserStack device data
25
+ const validatedEnvironments = await validateAppAutomateDevices(devices);
26
+ // Extract platforms for backward compatibility (if needed)
27
+ const platforms = validatedEnvironments.map((env) => env.platform);
19
28
  // Step 1: Generate SDK setup command
20
29
  const sdkCommand = getAppSDKPrefixCommand(language, testingFramework, username, accessKey, appPath);
21
30
  if (sdkCommand) {
22
31
  instructions.push({ content: sdkCommand, type: "setup" });
23
32
  }
24
33
  // Step 2: Generate browserstack.yml configuration
25
- const configInstructions = generateAppBrowserStackYMLInstructions(platforms, username, accessKey, appPath, testingFramework);
34
+ const configInstructions = generateAppBrowserStackYMLInstructions({
35
+ validatedEnvironments,
36
+ platforms,
37
+ testingFramework,
38
+ projectName: input.project,
39
+ }, username, accessKey, appPath);
26
40
  if (configInstructions) {
27
41
  instructions.push({ content: configInstructions, type: "config" });
28
42
  }
@@ -1,8 +1,10 @@
1
1
  export declare const MAVEN_ARCHETYPE_GROUP_ID = "com.browserstack";
2
2
  export declare const MAVEN_ARCHETYPE_ARTIFACT_ID = "junit-archetype-integrate";
3
3
  export declare const MAVEN_ARCHETYPE_VERSION = "1.0";
4
+ export declare const JAVA_APP_FRAMEWORK_VERSION_MAP: Record<string, string>;
4
5
  export declare const JAVA_APP_FRAMEWORK_MAP: Record<string, string>;
5
6
  export declare const GRADLE_APP_SETUP_INSTRUCTIONS = "\n**For Gradle setup:**\n1. Add browserstack-java-sdk to dependencies:\n compileOnly 'com.browserstack:browserstack-java-sdk:latest.release'\n\n2. Add browserstackSDK path variable:\n def browserstackSDKArtifact = configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.find { it.name == 'browserstack-java-sdk' }\n\n3. Add javaagent to gradle tasks:\n jvmArgs \"-javaagent:${browserstackSDKArtifact.file}\"\n";
6
7
  export declare function getJavaAppInstructions(): string;
7
8
  export declare function getJavaAppFrameworkForMaven(framework: string): string;
9
+ export declare function getJavaAppFrameworkVersion(framework: string): string;
8
10
  export declare function getJavaSDKCommand(framework: string, username: string, accessKey: string, appPath?: string): string;
@@ -4,15 +4,31 @@ import { createStep, combineInstructions, createEnvStep, PLATFORM_UTILS, } from
4
4
  export const MAVEN_ARCHETYPE_GROUP_ID = "com.browserstack";
5
5
  export const MAVEN_ARCHETYPE_ARTIFACT_ID = "junit-archetype-integrate";
6
6
  export const MAVEN_ARCHETYPE_VERSION = "1.0";
7
+ // Version mapping for different frameworks
8
+ export const JAVA_APP_FRAMEWORK_VERSION_MAP = {
9
+ testng: "1.4",
10
+ selenide: "1.4",
11
+ junit5: "1.0",
12
+ junit4: "1.0",
13
+ jbehave: "1.0",
14
+ cucumberTestng: "1.0",
15
+ cucumberJunit4: "1.0",
16
+ cucumberJunit5: "1.0",
17
+ cucumber: "1.0",
18
+ serenity: "1.0",
19
+ };
7
20
  // Framework mapping for Java Maven archetype generation for App Automate
8
21
  export const JAVA_APP_FRAMEWORK_MAP = {
9
- testng: "browserstack-sdk-archetype-integrate",
22
+ testng: "testng-archetype-integrate",
10
23
  junit5: "browserstack-sdk-archetype-integrate",
11
24
  selenide: "selenide-archetype-integrate",
12
25
  jbehave: "browserstack-sdk-archetype-integrate",
26
+ junit4: "browserstack-sdk-archetype-integrate",
13
27
  cucumberTestng: "browserstack-sdk-archetype-integrate",
14
28
  cucumberJunit4: "browserstack-sdk-archetype-integrate",
15
29
  cucumberJunit5: "browserstack-sdk-archetype-integrate",
30
+ cucumber: "browserstack-sdk-archetype-integrate",
31
+ serenity: "browserstack-sdk-archetype-integrate",
16
32
  };
17
33
  // Common Gradle setup instructions for App Automate (platform-independent)
18
34
  export const GRADLE_APP_SETUP_INSTRUCTIONS = `
@@ -35,53 +51,71 @@ mvn test
35
51
  export function getJavaAppFrameworkForMaven(framework) {
36
52
  return JAVA_APP_FRAMEWORK_MAP[framework] || framework;
37
53
  }
38
- function getMavenCommandForWindows(framework, mavenFramework, username, accessKey) {
39
- return (`mvn archetype:generate -B ` +
54
+ export function getJavaAppFrameworkVersion(framework) {
55
+ return JAVA_APP_FRAMEWORK_VERSION_MAP[framework] || MAVEN_ARCHETYPE_VERSION;
56
+ }
57
+ function getMavenCommandForWindows(framework, mavenFramework, version, username, accessKey, appPath) {
58
+ let command = `mvn archetype:generate -B ` +
40
59
  `-DarchetypeGroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
41
60
  `-DarchetypeArtifactId="${mavenFramework}" ` +
42
- `-DarchetypeVersion="${MAVEN_ARCHETYPE_VERSION}" ` +
61
+ `-DarchetypeVersion="${version}" ` +
43
62
  `-DgroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
44
- `-DartifactId="${MAVEN_ARCHETYPE_ARTIFACT_ID}" ` +
45
- `-Dversion="${MAVEN_ARCHETYPE_VERSION}" ` +
63
+ `-DartifactId="${mavenFramework}" ` +
64
+ `-Dversion="${version}" ` +
46
65
  `-DBROWSERSTACK_USERNAME="${username}" ` +
47
- `-DBROWSERSTACK_ACCESS_KEY="${accessKey}" ` +
48
- `-DBROWSERSTACK_FRAMEWORK="${framework}"`);
66
+ `-DBROWSERSTACK_ACCESS_KEY="${accessKey}"`;
67
+ // Add framework parameter for browserstack-sdk-archetype-integrate
68
+ if (mavenFramework === "browserstack-sdk-archetype-integrate") {
69
+ command += ` -DBROWSERSTACK_FRAMEWORK="${framework}"`;
70
+ }
71
+ // Add app path if provided
72
+ if (appPath) {
73
+ command += ` -DBROWSERSTACK_APP="${appPath}"`;
74
+ }
75
+ return command;
49
76
  }
50
- function getMavenCommandForUnix(framework, mavenFramework, username, accessKey) {
51
- return (`mvn archetype:generate -B ` +
77
+ function getMavenCommandForUnix(framework, mavenFramework, version, username, accessKey, appPath) {
78
+ let command = `mvn archetype:generate -B ` +
52
79
  `-DarchetypeGroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
53
80
  `-DarchetypeArtifactId="${mavenFramework}" ` +
54
- `-DarchetypeVersion="${MAVEN_ARCHETYPE_VERSION}" ` +
81
+ `-DarchetypeVersion="${version}" ` +
55
82
  `-DgroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
56
- `-DartifactId="${MAVEN_ARCHETYPE_ARTIFACT_ID}" ` +
57
- `-Dversion="${MAVEN_ARCHETYPE_VERSION}" ` +
83
+ `-DartifactId="${mavenFramework}" ` +
84
+ `-Dversion="${version}" ` +
58
85
  `-DBROWSERSTACK_USERNAME="${username}" ` +
59
- `-DBROWSERSTACK_ACCESS_KEY="${accessKey}" ` +
60
- `-DBROWSERSTACK_FRAMEWORK="${framework}"`);
86
+ `-DBROWSERSTACK_ACCESS_KEY="${accessKey}"`;
87
+ // Add framework parameter for browserstack-sdk-archetype-integrate
88
+ if (mavenFramework === "browserstack-sdk-archetype-integrate") {
89
+ command += ` -DBROWSERSTACK_FRAMEWORK="${framework}"`;
90
+ }
91
+ // Add app path if provided
92
+ if (appPath) {
93
+ command += ` -DBROWSERSTACK_APP="${appPath}"`;
94
+ }
95
+ return command;
61
96
  }
62
97
  export function getJavaSDKCommand(framework, username, accessKey, appPath) {
63
98
  const { isWindows = false, getPlatformLabel } = PLATFORM_UTILS || {};
64
99
  const mavenFramework = getJavaAppFrameworkForMaven(framework);
100
+ const version = getJavaAppFrameworkVersion(framework);
65
101
  let mavenCommand;
66
102
  if (isWindows) {
67
- mavenCommand = getMavenCommandForWindows(framework, mavenFramework, username, accessKey);
68
- if (appPath) {
69
- mavenCommand += ` -DBROWSERSTACK_APP="${appPath}"`;
70
- }
103
+ mavenCommand = getMavenCommandForWindows(framework, mavenFramework, version, username, accessKey, appPath);
71
104
  }
72
105
  else {
73
- mavenCommand = getMavenCommandForUnix(framework, mavenFramework, username, accessKey);
74
- if (appPath) {
75
- mavenCommand += ` -DBROWSERSTACK_APP="${appPath}"`;
76
- }
106
+ mavenCommand = getMavenCommandForUnix(framework, mavenFramework, version, username, accessKey, appPath);
77
107
  }
78
108
  const envStep = createEnvStep(username, accessKey, isWindows, getPlatformLabel());
79
109
  const mavenStep = createStep("Install BrowserStack SDK using Maven Archetype for App Automate", `Maven command for ${framework} (${getPlatformLabel()}):
80
- \`\`\`bash
81
- ${mavenCommand}
82
- \`\`\`
110
+ \`\`\`bash
111
+ ${mavenCommand}
112
+ \`\`\`
83
113
 
84
- Alternative setup for Gradle users:
85
- ${GRADLE_APP_SETUP_INSTRUCTIONS}`);
86
- return combineInstructions(envStep, mavenStep);
114
+ Alternative setup for Gradle users:
115
+ ${GRADLE_APP_SETUP_INSTRUCTIONS}`);
116
+ const argsLineStep = createStep("Verifying dependency and argsLine", `Verify browserstack-java-sdk with LATEST is added as dependency and add this line in pom.xml if not added:
117
+ \`\`\`xml
118
+ <argLine>-javaagent:"\${com.browserstack:browserstack-java-sdk:jar}"</argLine>
119
+ \`\`\``);
120
+ return combineInstructions(envStep, mavenStep, argsLineStep);
87
121
  }
@@ -16,6 +16,7 @@ export declare enum AppSDKSupportedTestingFrameworkEnum {
16
16
  junit4 = "junit4",
17
17
  selenide = "selenide",
18
18
  jbehave = "jbehave",
19
+ serenity = "serenity",
19
20
  cucumberTestng = "cucumberTestng",
20
21
  cucumberJunit4 = "cucumberJunit4",
21
22
  cucumberJunit5 = "cucumberJunit5",
@@ -49,7 +50,7 @@ export interface AppSDKInstruction {
49
50
  export declare const SUPPORTED_CONFIGURATIONS: {
50
51
  appium: {
51
52
  ruby: string[];
52
- java: never[];
53
+ java: string[];
53
54
  csharp: never[];
54
55
  python: string[];
55
56
  nodejs: string[];
@@ -18,6 +18,7 @@ export var AppSDKSupportedTestingFrameworkEnum;
18
18
  AppSDKSupportedTestingFrameworkEnum["junit4"] = "junit4";
19
19
  AppSDKSupportedTestingFrameworkEnum["selenide"] = "selenide";
20
20
  AppSDKSupportedTestingFrameworkEnum["jbehave"] = "jbehave";
21
+ AppSDKSupportedTestingFrameworkEnum["serenity"] = "serenity";
21
22
  AppSDKSupportedTestingFrameworkEnum["cucumberTestng"] = "cucumberTestng";
22
23
  AppSDKSupportedTestingFrameworkEnum["cucumberJunit4"] = "cucumberJunit4";
23
24
  AppSDKSupportedTestingFrameworkEnum["cucumberJunit5"] = "cucumberJunit5";
@@ -46,7 +47,15 @@ export var AppSDKSupportedPlatformEnum;
46
47
  export const SUPPORTED_CONFIGURATIONS = {
47
48
  appium: {
48
49
  ruby: ["cucumberRuby"],
49
- java: [],
50
+ java: [
51
+ "testng",
52
+ "cucumber",
53
+ "junit4",
54
+ "junit5",
55
+ "jbehave",
56
+ "selenide",
57
+ "serenity",
58
+ ],
50
59
  csharp: [],
51
60
  python: ["pytest", "robot", "behave", "lettuce"],
52
61
  nodejs: ["jest", "mocha", "cucumberJs", "webdriverio", "nightwatch"],
@@ -1,10 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { AppTestPlatform } from "./types.js";
3
+ import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";
3
4
  export declare const RUN_APP_AUTOMATE_DESCRIPTION = "Execute pre-built native mobile test suites (Espresso for Android, XCUITest for iOS) by direct upload to BrowserStack. ONLY for compiled .apk/.ipa test files. This is NOT for SDK integration or Appium tests. For Appium-based testing with SDK setup, use 'setupBrowserStackAppAutomateTests' instead.";
4
5
  export declare const RUN_APP_AUTOMATE_SCHEMA: {
5
6
  appPath: z.ZodString;
6
7
  testSuitePath: z.ZodString;
7
- devices: z.ZodArray<z.ZodString, "many">;
8
+ devices: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodTuple<[z.ZodLiteral<AppSDKSupportedPlatformEnum.android>, z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodLiteral<AppSDKSupportedPlatformEnum.ios>, z.ZodString, z.ZodString], null>]>, "many">>;
8
9
  project: z.ZodDefault<z.ZodOptional<z.ZodString>>;
9
10
  detectedAutomationFramework: z.ZodNativeEnum<typeof AppTestPlatform>;
10
11
  };
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { AppTestPlatform } from "./types.js";
3
+ import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";
3
4
  export const RUN_APP_AUTOMATE_DESCRIPTION = `Execute pre-built native mobile test suites (Espresso for Android, XCUITest for iOS) by direct upload to BrowserStack. ONLY for compiled .apk/.ipa test files. This is NOT for SDK integration or Appium tests. For Appium-based testing with SDK setup, use 'setupBrowserStackAppAutomateTests' instead.`;
4
5
  export const RUN_APP_AUTOMATE_SCHEMA = {
5
6
  appPath: z
@@ -23,8 +24,29 @@ export const RUN_APP_AUTOMATE_SCHEMA = {
23
24
  " zip -r Tests.zip *.xctestrun *-Runner.app\n\n" +
24
25
  "If in other directory, provide existing test file path"),
25
26
  devices: z
26
- .array(z.string())
27
- .describe("List of devices to run the test on, e.g., ['Samsung Galaxy S20-10.0', 'iPhone 12 Pro-16.0']."),
27
+ .array(z.union([
28
+ // Android: [android, deviceName, osVersion]
29
+ z.tuple([
30
+ z
31
+ .literal(AppSDKSupportedPlatformEnum.android)
32
+ .describe("Platform identifier: 'android'"),
33
+ z
34
+ .string()
35
+ .describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'"),
36
+ z.string().describe("Android version, e.g. '14', '16', 'latest'"),
37
+ ]),
38
+ // iOS: [ios, deviceName, osVersion]
39
+ z.tuple([
40
+ z
41
+ .literal(AppSDKSupportedPlatformEnum.ios)
42
+ .describe("Platform identifier: 'ios'"),
43
+ z.string().describe("Device name, e.g. 'iPhone 15', 'iPhone 14 Pro'"),
44
+ z.string().describe("iOS version, e.g. '17', '16', 'latest'"),
45
+ ]),
46
+ ]))
47
+ .max(3)
48
+ .default([])
49
+ .describe("Tuples describing target mobile devices. Add device only when user asks explicitly for it. Defaults to [] . Example: [['android', 'Samsung Galaxy S24', '14'], ['ios', 'iPhone 15', '17']]"),
28
50
  project: z
29
51
  .string()
30
52
  .optional()
@@ -6,6 +6,7 @@ import { maybeCompressBase64 } from "../lib/utils.js";
6
6
  import { remote } from "webdriverio";
7
7
  import { AppTestPlatform } from "./appautomate-utils/native-execution/types.js";
8
8
  import { setupAppAutomateHandler } from "./appautomate-utils/appium-sdk/handler.js";
9
+ import { validateAppAutomateDevices } from "./sdk-utils/common/device-validator.js";
9
10
  import { SETUP_APP_AUTOMATE_DESCRIPTION, SETUP_APP_AUTOMATE_SCHEMA, } from "./appautomate-utils/appium-sdk/constants.js";
10
11
  import { Platform, } from "./appautomate-utils/native-execution/types.js";
11
12
  import { getDevicesAndBrowsers, BrowserStackProducts, } from "../lib/device-cache.js";
@@ -105,6 +106,8 @@ async function runAppTestsOnBrowserStack(args, config) {
105
106
  if (!args.browserstackTestSuiteUrl && !args.testSuitePath) {
106
107
  throw new Error("testSuitePath is required when browserstackTestSuiteUrl is not provided");
107
108
  }
109
+ // Validate devices against real BrowserStack device data
110
+ await validateAppAutomateDevices(args.devices);
108
111
  switch (args.detectedAutomationFramework) {
109
112
  case AppTestPlatform.ESPRESSO: {
110
113
  try {
@@ -126,7 +129,12 @@ async function runAppTestsOnBrowserStack(args, config) {
126
129
  test_suite_url = await uploadEspressoTestSuite(args.testSuitePath, config);
127
130
  logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
128
131
  }
129
- 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);
130
138
  return {
131
139
  content: [
132
140
  {
@@ -161,7 +169,12 @@ async function runAppTestsOnBrowserStack(args, config) {
161
169
  test_suite_url = await uploadXcuiTestSuite(args.testSuitePath, config);
162
170
  logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
163
171
  }
164
- 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);
165
178
  return {
166
179
  content: [
167
180
  {
@@ -39,7 +39,10 @@ async function extractScreenshotUrls(sessionId, sessionType, config) {
39
39
  //Converts screenshot URLs to base64 encoded images
40
40
  async function convertUrlsToBase64(urls) {
41
41
  const screenshots = await Promise.all(urls.map(async (url) => {
42
- const response = await apiClient.get({ url });
42
+ const response = await apiClient.get({
43
+ url,
44
+ responseType: "arraybuffer",
45
+ });
43
46
  // Axios returns response.data as a Buffer for binary data
44
47
  const base64 = Buffer.from(response.data).toString("base64");
45
48
  // Compress the base64 image if needed
@@ -1,2 +1,2 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
- export declare function addListTestFiles(args: any): Promise<CallToolResult>;
2
+ export declare function addListTestFiles(): Promise<CallToolResult>;
@@ -1,35 +1,59 @@
1
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.");
2
+ import { storedPercyResults } from "../lib/inmemory-store.js";
3
+ import { updateFileAndStep } from "./percy-snapshot-utils/utils.js";
4
+ import { percyWebSetupInstructions } from "./sdk-utils/percy-web/handler.js";
5
+ export async function addListTestFiles() {
6
+ const storedResults = storedPercyResults.get();
7
+ if (!storedResults) {
8
+ throw new Error("No Framework details found. Please call expandPercyVisualTesting first to fetch the framework details.");
9
9
  }
10
- for (const dir of dirs) {
11
- const files = await listTestFiles({
12
- language,
13
- framework,
14
- baseDir: dir,
15
- });
10
+ const language = storedResults.detectedLanguage;
11
+ const framework = storedResults.detectedTestingFramework;
12
+ // Use stored paths from setUpPercy
13
+ const dirs = storedResults.folderPaths;
14
+ const files = storedResults.filePaths;
15
+ let testFiles = [];
16
+ if (files && files.length > 0) {
16
17
  testFiles = testFiles.concat(files);
17
18
  }
19
+ if (dirs && dirs.length > 0) {
20
+ for (const dir of dirs) {
21
+ const discoveredFiles = await listTestFiles({
22
+ language,
23
+ framework,
24
+ baseDir: dir,
25
+ });
26
+ testFiles = testFiles.concat(discoveredFiles);
27
+ }
28
+ }
29
+ // Validate that we have at least one test file
18
30
  if (testFiles.length === 0) {
19
- throw new Error("No test files found");
31
+ throw new Error("No test files found. Please provide either specific file paths (files) or directory paths (dirs) containing test files.");
32
+ }
33
+ if (testFiles.length === 1) {
34
+ const result = await updateFileAndStep(testFiles[0], 0, 1, percyWebSetupInstructions);
35
+ return {
36
+ content: result,
37
+ };
20
38
  }
21
- // Generate a UUID and store the test files in memory
22
- const uuid = crypto.randomUUID();
23
- testFilePathsMap.set(uuid, testFiles);
39
+ // For multiple files, store directly in testFiles
40
+ const fileStatusMap = {};
41
+ testFiles.forEach((file) => {
42
+ fileStatusMap[file] = false; // false = not updated, true = updated
43
+ });
44
+ // Update storedPercyResults with test files
45
+ const updatedStored = { ...storedResults };
46
+ updatedStored.testFiles = fileStatusMap;
47
+ storedPercyResults.set(updatedStored);
24
48
  return {
25
49
  content: [
26
50
  {
27
51
  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.`,
52
+ text: `The Test files are stored in memory and the total number of tests files found is ${testFiles.length}.`,
29
53
  },
30
54
  {
31
55
  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}`,
56
+ text: `You can now use the tool addPercySnapshotCommands to update the test file with Percy commands for visual testing.`,
33
57
  },
34
58
  ],
35
59
  };
@@ -5,14 +5,41 @@ import { runPercyScan } from "./run-percy-scan.js";
5
5
  import { SetUpPercyParamsShape } from "./sdk-utils/common/schema.js";
6
6
  import { updateTestsWithPercyCommands } from "./add-percy-snapshots.js";
7
7
  import { approveOrDeclinePercyBuild } from "./review-agent-utils/percy-approve-reject.js";
8
- import { setUpPercyHandler } from "./sdk-utils/handler.js";
9
- import { SETUP_PERCY_DESCRIPTION, LIST_TEST_FILES_DESCRIPTION, PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, } from "./sdk-utils/common/constants.js";
10
- import { ListTestFilesParamsShape, UpdateTestFileWithInstructionsParams, } from "./percy-snapshot-utils/constants.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 { UpdateTestFileWithInstructionsParams } from "./percy-snapshot-utils/constants.js";
11
12
  import { RunPercyScanParamsShape, FetchPercyChangesParamsShape, ManagePercyBuildApprovalParamsShape, } from "./sdk-utils/common/schema.js";
12
13
  import { handleMCPError } from "../lib/utils.js";
13
14
  export function registerPercyTools(server, config) {
14
15
  const tools = {};
15
- tools.setupPercyVisualTesting = server.tool("setupPercyVisualTesting", SETUP_PERCY_DESCRIPTION, SetUpPercyParamsShape, async (args) => {
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) => {
16
43
  try {
17
44
  trackMCP("setupPercyVisualTesting", server.server.getClientVersion(), config);
18
45
  return setUpPercyHandler(args, config);
@@ -30,10 +57,10 @@ export function registerPercyTools(server, config) {
30
57
  return handleMCPError("addPercySnapshotCommands", server, config, error);
31
58
  }
32
59
  });
33
- tools.listTestFiles = server.tool("listTestFiles", LIST_TEST_FILES_DESCRIPTION, ListTestFilesParamsShape, async (args) => {
60
+ tools.listTestFiles = server.tool("listTestFiles", LIST_TEST_FILES_DESCRIPTION, {}, async () => {
34
61
  try {
35
62
  trackMCP("listTestFiles", server.server.getClientVersion(), config);
36
- return addListTestFiles(args);
63
+ return addListTestFiles();
37
64
  }
38
65
  catch (error) {
39
66
  return handleMCPError("listTestFiles", server, config, error);
@@ -2,14 +2,8 @@ import { z } from "zod";
2
2
  import { SDKSupportedLanguage } from "../sdk-utils/common/types.js";
3
3
  import { DetectionConfig } from "./types.js";
4
4
  export declare const UpdateTestFileWithInstructionsParams: {
5
- uuid: z.ZodString;
6
5
  index: z.ZodNumber;
7
6
  };
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
7
  export declare const TEST_FILE_DETECTION: Record<SDKSupportedLanguage, DetectionConfig>;
14
8
  export declare const EXCLUDED_DIRS: Set<string>;
15
9
  export declare const backendIndicators: RegExp[];
@@ -1,22 +1,7 @@
1
1
  import { z } from "zod";
2
- import { SDKSupportedLanguages, SDKSupportedTestingFrameworks, } from "../sdk-utils/common/types.js";
3
2
  export const UpdateTestFileWithInstructionsParams = {
4
- uuid: z
5
- .string()
6
- .describe("UUID referencing the in-memory array of test file paths"),
7
3
  index: z.number().describe("Index of the test file to update"),
8
4
  };
9
- export const ListTestFilesParamsShape = {
10
- dirs: z
11
- .array(z.string())
12
- .describe("Array of directory paths to search for test files"),
13
- language: z
14
- .enum(SDKSupportedLanguages)
15
- .describe("Programming language"),
16
- framework: z
17
- .enum(SDKSupportedTestingFrameworks)
18
- .describe("Testing framework (optional)"),
19
- };
20
5
  export const TEST_FILE_DETECTION = {
21
6
  java: {
22
7
  extensions: [".java"],
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { TestStatus } from "./types.js";
3
3
  export declare const FETCH_RCA_PARAMS: {
4
- testId: z.ZodArray<z.ZodString, "many">;
4
+ testId: z.ZodArray<z.ZodNumber, "many">;
5
5
  };
6
6
  export declare const GET_BUILD_ID_PARAMS: {
7
7
  browserStackProjectName: z.ZodString;
@@ -2,9 +2,9 @@ import { z } from "zod";
2
2
  import { TestStatus } from "./types.js";
3
3
  export const FETCH_RCA_PARAMS = {
4
4
  testId: z
5
- .array(z.string())
5
+ .array(z.number().int())
6
6
  .max(3)
7
- .describe("Array of test IDs to fetch RCA data for (maximum 3 IDs). If not provided, use the listTestIds tool get all failed testcases. If more than 3 IDs are provided, only the first 3 will be processed."),
7
+ .describe("Array of integer test IDs to fetch RCA data for (maximum 3 IDs). These must be numeric test IDs, not session IDs or strings. If not provided, use the listTestIds tool to get all failed testcases. If more than 3 IDs are provided, only the first 3 will be processed."),
8
8
  };
9
9
  export const GET_BUILD_ID_PARAMS = {
10
10
  browserStackProjectName: z
@@ -5,5 +5,5 @@ interface ScanProgressContext {
5
5
  progressToken?: string | number;
6
6
  };
7
7
  }
8
- export declare function getRCAData(testIds: string[], authString: string, context?: ScanProgressContext): Promise<RCAResponse>;
8
+ export declare function getRCAData(testIds: number[], authString: string, context?: ScanProgressContext): Promise<RCAResponse>;
9
9
  export {};
@@ -66,7 +66,7 @@ async function updateProgress(context, testCases, message) {
66
66
  : calculateProgress(resolvedCount, testCases.length));
67
67
  }
68
68
  async function fetchInitialRCA(testId, headers, baseUrl) {
69
- const url = baseUrl.replace("{testId}", testId);
69
+ const url = baseUrl.replace("{testId}", testId.toString());
70
70
  try {
71
71
  const response = await fetch(url, { headers });
72
72
  if (!response.ok) {
@@ -125,7 +125,7 @@ async function pollRCAResults(testCases, headers, baseUrl, context, pollInterval
125
125
  }
126
126
  await Promise.allSettled(inProgressCases.map(async (tc) => {
127
127
  try {
128
- const pollUrl = baseUrl.replace("{testId}", tc.id);
128
+ const pollUrl = baseUrl.replace("{testId}", tc.id.toString());
129
129
  const response = await fetch(pollUrl, { headers });
130
130
  if (!response.ok) {
131
131
  const errorText = await response.text();
@@ -18,7 +18,7 @@ export interface TestRun {
18
18
  };
19
19
  }
20
20
  export interface FailedTestInfo {
21
- test_id: string;
21
+ test_id: number;
22
22
  test_name: string;
23
23
  }
24
24
  export declare enum RCAState {
@@ -34,8 +34,8 @@ export declare enum RCAState {
34
34
  TIMEOUT = "TIMEOUT"
35
35
  }
36
36
  export interface RCATestCase {
37
- id: string;
38
- testRunId: string;
37
+ id: number;
38
+ testRunId: number;
39
39
  state: RCAState;
40
40
  rcaData?: any;
41
41
  }
@@ -5,7 +5,7 @@ import { TestStatus } from "./rca-agent-utils/types.js";
5
5
  import { BuildIdArgs } from "./rca-agent-utils/types.js";
6
6
  export declare function getBuildIdTool(args: BuildIdArgs, config: BrowserStackConfig): Promise<CallToolResult>;
7
7
  export declare function fetchRCADataTool(args: {
8
- testId: string[];
8
+ testId: number[];
9
9
  }, config: BrowserStackConfig): Promise<CallToolResult>;
10
10
  export declare function listTestIdsTool(args: {
11
11
  buildId: string;