@browserstack/mcp-server 1.2.7 → 1.2.8

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 (29) hide show
  1. package/dist/lib/tm-base-url.d.ts +2 -0
  2. package/dist/lib/tm-base-url.js +37 -0
  3. package/dist/lib/utils.d.ts +1 -1
  4. package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +27 -2
  5. package/dist/tools/appautomate-utils/appium-sdk/constants.js +11 -21
  6. package/dist/tools/appautomate-utils/appium-sdk/handler.js +5 -1
  7. package/dist/tools/appautomate-utils/native-execution/constants.d.ts +26 -2
  8. package/dist/tools/appautomate-utils/native-execution/constants.js +10 -21
  9. package/dist/tools/appautomate.js +2 -1
  10. package/dist/tools/sdk-utils/bstack/sdkHandler.js +22 -3
  11. package/dist/tools/sdk-utils/common/constants.d.ts +2 -2
  12. package/dist/tools/sdk-utils/common/constants.js +3 -3
  13. package/dist/tools/sdk-utils/common/schema.d.ts +52 -12
  14. package/dist/tools/sdk-utils/common/schema.js +23 -41
  15. package/dist/tools/testmanagement-utils/TCG-utils/api.js +21 -9
  16. package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +5 -5
  17. package/dist/tools/testmanagement-utils/TCG-utils/config.js +5 -5
  18. package/dist/tools/testmanagement-utils/add-test-result.js +3 -1
  19. package/dist/tools/testmanagement-utils/create-lca-steps.js +4 -2
  20. package/dist/tools/testmanagement-utils/create-project-folder.js +6 -3
  21. package/dist/tools/testmanagement-utils/create-testcase.js +4 -2
  22. package/dist/tools/testmanagement-utils/create-testrun.js +3 -1
  23. package/dist/tools/testmanagement-utils/list-testcases.js +3 -1
  24. package/dist/tools/testmanagement-utils/list-testruns.js +3 -1
  25. package/dist/tools/testmanagement-utils/poll-lca-status.js +3 -1
  26. package/dist/tools/testmanagement-utils/testcase-from-file.js +3 -1
  27. package/dist/tools/testmanagement-utils/update-testrun.js +3 -1
  28. package/dist/tools/testmanagement-utils/upload-file.js +3 -1
  29. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ import { BrowserStackConfig } from "./types.js";
2
+ export declare function getTMBaseURL(config: BrowserStackConfig): Promise<string>;
@@ -0,0 +1,37 @@
1
+ import { apiClient } from "./apiClient.js";
2
+ import logger from "../logger.js";
3
+ import { getBrowserStackAuth } from "./get-auth.js";
4
+ const TM_BASE_URLS = [
5
+ "https://test-management.browserstack.com",
6
+ "https://test-management-eu.browserstack.com",
7
+ "https://test-management-in.browserstack.com",
8
+ ];
9
+ let cachedBaseUrl = null;
10
+ export async function getTMBaseURL(config) {
11
+ if (cachedBaseUrl) {
12
+ logger.debug(`Using cached TM base URL: ${cachedBaseUrl}`);
13
+ return cachedBaseUrl;
14
+ }
15
+ logger.info("No cached TM base URL found, testing available URLs with authentication");
16
+ const authString = getBrowserStackAuth(config);
17
+ const [username, password] = authString.split(":");
18
+ const authHeader = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
19
+ for (const baseUrl of TM_BASE_URLS) {
20
+ try {
21
+ const res = await apiClient.get({
22
+ url: `${baseUrl}/api/v2/projects/`,
23
+ headers: { Authorization: authHeader },
24
+ raise_error: false,
25
+ });
26
+ if (res.ok) {
27
+ cachedBaseUrl = baseUrl;
28
+ logger.info(`Selected TM base URL: ${baseUrl}`);
29
+ return baseUrl;
30
+ }
31
+ }
32
+ catch (err) {
33
+ logger.debug(`Failed TM base URL: ${baseUrl} (${err})`);
34
+ }
35
+ }
36
+ throw new Error("Unable to connect to BrowserStack Test Management. Please check your credentials and network connection.Please open an issue on GitHub if the problem persists");
37
+ }
@@ -45,7 +45,7 @@ export declare function handleMCPError(toolName: string, server: McpServer, conf
45
45
  [x: string]: unknown;
46
46
  src: string;
47
47
  mimeType?: string | undefined;
48
- sizes?: string | undefined;
48
+ sizes?: string[] | undefined;
49
49
  }[] | undefined;
50
50
  } | {
51
51
  [x: string]: unknown;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { AppSDKSupportedFrameworkEnum, AppSDKSupportedTestingFrameworkEnum, AppSDKSupportedLanguageEnum, AppSDKSupportedPlatformEnum } from "./index.js";
2
+ import { AppSDKSupportedFrameworkEnum, AppSDKSupportedTestingFrameworkEnum, AppSDKSupportedLanguageEnum } from "./index.js";
3
3
  export declare const APP_DEVICE_CONFIGS: {
4
4
  android: {
5
5
  deviceName: string;
@@ -12,12 +12,37 @@ export declare const APP_DEVICE_CONFIGS: {
12
12
  };
13
13
  export declare const STEP_DELIMITER = "---STEP---";
14
14
  export declare const DEFAULT_APP_PATH = "bs://sample.app";
15
+ export declare const MobileDeviceSchema: z.ZodObject<{
16
+ platform: z.ZodEnum<["android", "ios"]>;
17
+ deviceName: z.ZodString;
18
+ osVersion: z.ZodString;
19
+ }, "strip", z.ZodTypeAny, {
20
+ platform: "android" | "ios";
21
+ deviceName: string;
22
+ osVersion: string;
23
+ }, {
24
+ platform: "android" | "ios";
25
+ deviceName: string;
26
+ osVersion: string;
27
+ }>;
15
28
  export declare const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.";
16
29
  export declare const SETUP_APP_AUTOMATE_SCHEMA: {
17
30
  detectedFramework: z.ZodNativeEnum<typeof AppSDKSupportedFrameworkEnum>;
18
31
  detectedTestingFramework: z.ZodNativeEnum<typeof AppSDKSupportedTestingFrameworkEnum>;
19
32
  detectedLanguage: z.ZodNativeEnum<typeof AppSDKSupportedLanguageEnum>;
20
- 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">>;
33
+ devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
34
+ platform: z.ZodEnum<["android", "ios"]>;
35
+ deviceName: z.ZodString;
36
+ osVersion: z.ZodString;
37
+ }, "strip", z.ZodTypeAny, {
38
+ platform: "android" | "ios";
39
+ deviceName: string;
40
+ osVersion: string;
41
+ }, {
42
+ platform: "android" | "ios";
43
+ deviceName: string;
44
+ osVersion: string;
45
+ }>, "many">>;
21
46
  appPath: z.ZodString;
22
47
  project: z.ZodDefault<z.ZodOptional<z.ZodString>>;
23
48
  };
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { AppSDKSupportedFrameworkEnum, AppSDKSupportedTestingFrameworkEnum, AppSDKSupportedLanguageEnum, AppSDKSupportedPlatformEnum, } from "./index.js";
2
+ import { AppSDKSupportedFrameworkEnum, AppSDKSupportedTestingFrameworkEnum, AppSDKSupportedLanguageEnum, } from "./index.js";
3
3
  // App Automate specific device configurations
4
4
  export const APP_DEVICE_CONFIGS = {
5
5
  android: [
@@ -17,6 +17,15 @@ export const APP_DEVICE_CONFIGS = {
17
17
  export const STEP_DELIMITER = "---STEP---";
18
18
  // Default app path for examples
19
19
  export const DEFAULT_APP_PATH = "bs://sample.app";
20
+ export const MobileDeviceSchema = z.object({
21
+ platform: z
22
+ .enum(["android", "ios"])
23
+ .describe("Platform name: 'android' or 'ios'"),
24
+ deviceName: z
25
+ .string()
26
+ .describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8', 'iPhone 15', 'iPhone 14 Pro'"),
27
+ osVersion: z.string().describe("OS version, e.g. '14', '16', '17', 'latest'"),
28
+ });
20
29
  // Tool description and schema for setupBrowserStackAppAutomateTests
21
30
  export const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.";
22
31
  export const SETUP_APP_AUTOMATE_SCHEMA = {
@@ -30,26 +39,7 @@ export const SETUP_APP_AUTOMATE_SCHEMA = {
30
39
  .nativeEnum(AppSDKSupportedLanguageEnum)
31
40
  .describe("The programming language used in the project. Supports Java and C#. Example: 'java', 'csharp'"),
32
41
  devices: z
33
- .array(z.union([
34
- // Android: [android, deviceName, osVersion]
35
- z.tuple([
36
- z
37
- .literal(AppSDKSupportedPlatformEnum.android)
38
- .describe("Platform identifier: 'android'"),
39
- z
40
- .string()
41
- .describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'"),
42
- z.string().describe("Android version, e.g. '14', '16', 'latest'"),
43
- ]),
44
- // iOS: [ios, deviceName, osVersion]
45
- z.tuple([
46
- z
47
- .literal(AppSDKSupportedPlatformEnum.ios)
48
- .describe("Platform identifier: 'ios'"),
49
- z.string().describe("Device name, e.g. 'iPhone 15', 'iPhone 14 Pro'"),
50
- z.string().describe("iOS version, e.g. '17', '16', 'latest'"),
51
- ]),
52
- ]))
42
+ .array(MobileDeviceSchema)
53
43
  .max(3)
54
44
  .default([])
55
45
  .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']]"),
@@ -12,7 +12,11 @@ export async function setupAppAutomateHandler(rawInput, config) {
12
12
  // Use variables for all major input properties
13
13
  const testingFramework = input.detectedTestingFramework;
14
14
  const language = input.detectedLanguage;
15
- const inputDevices = input.devices ?? [];
15
+ const inputDevices = input.devices?.map((device) => [
16
+ device.platform,
17
+ device.deviceName,
18
+ device.osVersion,
19
+ ]) ?? [];
16
20
  const appPath = input.appPath;
17
21
  const framework = input.detectedFramework;
18
22
  //Validating if supported framework or not
@@ -1,11 +1,35 @@
1
1
  import { z } from "zod";
2
2
  import { AppTestPlatform } from "./types.js";
3
- import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";
4
3
  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
+ export declare const MobileDeviceSchema: z.ZodObject<{
5
+ platform: z.ZodEnum<["android", "ios"]>;
6
+ deviceName: z.ZodString;
7
+ osVersion: z.ZodString;
8
+ }, "strip", z.ZodTypeAny, {
9
+ platform: "android" | "ios";
10
+ deviceName: string;
11
+ osVersion: string;
12
+ }, {
13
+ platform: "android" | "ios";
14
+ deviceName: string;
15
+ osVersion: string;
16
+ }>;
5
17
  export declare const RUN_APP_AUTOMATE_SCHEMA: {
6
18
  appPath: z.ZodString;
7
19
  testSuitePath: z.ZodString;
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">>;
20
+ devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
21
+ platform: z.ZodEnum<["android", "ios"]>;
22
+ deviceName: z.ZodString;
23
+ osVersion: z.ZodString;
24
+ }, "strip", z.ZodTypeAny, {
25
+ platform: "android" | "ios";
26
+ deviceName: string;
27
+ osVersion: string;
28
+ }, {
29
+ platform: "android" | "ios";
30
+ deviceName: string;
31
+ osVersion: string;
32
+ }>, "many">>;
9
33
  project: z.ZodDefault<z.ZodOptional<z.ZodString>>;
10
34
  detectedAutomationFramework: z.ZodNativeEnum<typeof AppTestPlatform>;
11
35
  };
@@ -1,7 +1,15 @@
1
1
  import { z } from "zod";
2
2
  import { AppTestPlatform } from "./types.js";
3
- import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";
4
3
  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
+ export const MobileDeviceSchema = z.object({
5
+ platform: z
6
+ .enum(["android", "ios"])
7
+ .describe("Platform name: 'android' or 'ios'"),
8
+ deviceName: z
9
+ .string()
10
+ .describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8', 'iPhone 15', 'iPhone 14 Pro'"),
11
+ osVersion: z.string().describe("OS version, e.g. '14', '16', '17', 'latest'"),
12
+ });
5
13
  export const RUN_APP_AUTOMATE_SCHEMA = {
6
14
  appPath: z
7
15
  .string()
@@ -24,26 +32,7 @@ export const RUN_APP_AUTOMATE_SCHEMA = {
24
32
  " zip -r Tests.zip *.xctestrun *-Runner.app\n\n" +
25
33
  "If in other directory, provide existing test file path"),
26
34
  devices: z
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
- ]))
35
+ .array(MobileDeviceSchema)
47
36
  .max(3)
48
37
  .default([])
49
38
  .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']]"),
@@ -229,7 +229,8 @@ export default function addAppAutomationTools(server, config) {
229
229
  tools.runAppTestsOnBrowserStack = server.tool("runAppTestsOnBrowserStack", RUN_APP_AUTOMATE_DESCRIPTION, RUN_APP_AUTOMATE_SCHEMA, async (args) => {
230
230
  try {
231
231
  trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
232
- return await runAppTestsOnBrowserStack(args, config);
232
+ const devicesAsArrays = args.devices.map((device) => [device.platform, device.deviceName, device.osVersion]);
233
+ return await runAppTestsOnBrowserStack({ ...args, devices: devicesAsArrays }, config);
233
234
  }
234
235
  catch (error) {
235
236
  trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion(), error, config);
@@ -7,9 +7,28 @@ export async function runBstackSDKOnly(input, config, isPercyAutomate = false) {
7
7
  const steps = [];
8
8
  const authString = getBrowserStackAuth(config);
9
9
  const [username, accessKey] = authString.split(":");
10
- // Validate devices against real BrowserStack device data
11
- const tupleTargets = input.devices;
12
- const validatedEnvironments = await validateDevices(tupleTargets || [], input.detectedBrowserAutomationFramework);
10
+ const tupleTargets = input.devices?.map((device) => {
11
+ const platform = device.platform.toLowerCase();
12
+ if (platform === "windows" || platform === "macos") {
13
+ // Desktop: ["platform", "osVersion", "browser", "browserVersion"]
14
+ return [
15
+ platform,
16
+ device.osVersion || "latest",
17
+ device.browser || "",
18
+ device.browserVersion || "latest",
19
+ ];
20
+ }
21
+ else {
22
+ // Mobile: ["platform", "deviceName", "osVersion", "browser"]
23
+ return [
24
+ platform,
25
+ device.deviceName || "",
26
+ device.osVersion || "latest",
27
+ device.browser || "",
28
+ ];
29
+ }
30
+ }) || [];
31
+ const validatedEnvironments = await validateDevices(tupleTargets, input.detectedBrowserAutomationFramework);
13
32
  // Handle frameworks with unique setup instructions that don't use browserstack.yml
14
33
  if (input.detectedBrowserAutomationFramework === "cypress" ||
15
34
  input.detectedTestingFramework === "webdriverio") {
@@ -6,6 +6,6 @@ export declare const RUN_ON_BROWSERSTACK_DESCRIPTION = "Set up and run automated
6
6
  export declare const SIMULATE_PERCY_CHANGE_DESCRIPTION = "Integrate Percy visual testing into new projects and demonstrate visual change detection through a step-by-step simulation. This tool handles initial Percy integration, setup, and creates a working demonstration for new users. Primary tool for prompts like: \"Integrate percy for this project\", \"Integrate percy in this project {project_name}\"";
7
7
  export declare const PERCY_REPLACE_REGEX: RegExp;
8
8
  export declare const PERCY_SNAPSHOT_INSTRUCTION = "\nInvoke listTestFiles() with the provided directories from user to gather all test files in memory ---STEP---\nProcess files in STRICT sequential order using tool addPercySnapshotCommands() with below instructions:\n- Start with index 0\n- Then index 1 \n- Then index 2\n- Continue sequentially until the last index\n- DO NOT skip any index numbers\n- DO NOT jump to random indices like 10, 20, 30\n- DO NOT worry about time consuming and any constraints.\n- DO NOT STOP until you add commands in all the files or you reach end of the files.\n";
9
- export declare const PERCY_SIMULATE_INSTRUCTION = "\nMANDATORY: Aggressive, Accurate Percy Snapshots Integration\n\nPrerequisite: Before making any code changes in the user's repository, create and switch to a new branch named browserstack-simulation. Stage all changes but do not commit them. If the repository has not been initialized yet, run git init first.\n\nYou MUST follow these steps with NO EXCEPTIONS:\n1. Identify 4-5 key test files in the project that are critical for visual testing.\n2. For EACH identified test file, analyze the code to find EVERY location where a Percy snapshot should be called (e.g., after each navigation, UI change, or significant state in tests).\n3. Insert snapshot calls at ALL such points, not just the first or most obvious one.\n\nIteratively update every selected test file with percy snapshot calls at all identified points, ensuring comprehensive coverage of visual changes.";
10
- export declare const PERCY_SIMULATION_DRIVER_INSTRUCTION = "\nMANDATORY: Step-by-Step Percy Visual Change Simulation\nYou MUST follow these steps with NO EXCEPTIONS:\n\nSTEP 1: At this case we have added Percy snapshot commands to the test files at the appropriate locations (e.g., after navigation, UI change, or significant state). \n- Use ONLY existing snapshot names.\n- Do NOT add any popup injection or visual changes yet.\n- Do NOT run any Percy builds at this stage.\n\nSTEP 2: Run ONE comprehensive baseline Percy build that executes ALL tests containing Percy snapshots in a SINGLE build. This creates one baseline build with all snapshots for comparison. Use a command like: npx percy exec --label=mcp -- python -m pytest tests/ -k 'test_name1 or test_name2 or test_name3' -v to run multiple specific tests in one build.\"\n\nSTEP 3: Modify your test to inject a visible UI change (such as a popup) IMMEDIATELY BEFORE an EXISTING snapshot command (e.g., before percy_snapshot(self.driver, \"screenshot name\")).\n- Do NOT add a new snapshot name for the popup.\n- The popup must appear in an existing snapshot, not a new one.\n- Add this popup code in some test files before the percy_snapshot command you've added, to display the visual changes.\n\n```Javascript\npopup_script = `\nvar popup = document.createElement('div');\npopup.id = 'percy-test-popup';\npopup.style.cssText = popup.style.cssText = `\n /* TODO: Add styles to make the popup large, centered, and visually noticeable.\n Suggested properties: position: fixed; top/left; transform; background; color; font-size; padding; z-index; animation, etc. */\n`;\npopup.innerHTML = 'PERCY TEST<br>VISUAL CHANGE<br>DETECTED!';\ndocument.body.appendChild(popup);\n`;\n\n# Insert this just before the EXISTING snapshot command:\ndriver.execute_script(popup_script)\npercy_snapshot(self.driver, \"Before Adding to Cart\") # (Do NOT change the snapshot name, keep existing one)\n```\n\nSTEP 4: Run a second Percy build with same label and same test command as the baseline.\n- The snapshot names must remain the same as in the baseline.\n- The visual change should now appear in the same snapshot as before.\n- Use the same build command you ran for the baseline.\n\nSTEP 5: Compare the two Percy builds to see the detected visual difference.\n\nSTEP 6: Now ask user if they want to expand percy for other testcases? If yes, call the \"expandPercyVisualTesting\" tool to enable complete coverage for the entire project.\n\nCONSTRAINTS:\n- Do NOT run any builds until explicitly instructed in the steps.\n- Do NOT add new snapshot names\u2014only use existing ones.\n- Do NOT add popup injection until the baseline is established.\n- Visual changes must appear in EXISTING snapshots, not new ones.\n\nVALIDATION CHECKPOINTS (before proceeding to the next step):\n- Are you adding only snapshot commands (not running builds)?\n- Are you reusing existing snapshot names (not creating new ones)?\n- Have you established the baseline first (before adding visual changes)\n\nCRITICAL: \nDo NOT run tests separately or create multiple builds during baseline establishment. The goal is to have exactly TWO builds total: (1) baseline build with all original snapshots, (2) modified build with the same tests but visual changes injected.\n";
9
+ export declare const PERCY_SIMULATE_INSTRUCTION = "\nMANDATORY: Aggressive, Accurate Percy Snapshots Integration\n\nPrerequisite: Before making any code changes in the user's repository, create and switch to a new branch named percy-integration. Stage all changes but do not commit them. If the repository has not been initialized yet, run git init first.\n\nYou MUST follow these steps with NO EXCEPTIONS:\n1. Identify 4-5 key test files in the project that are critical for visual testing.\n2. For EACH identified test file, analyze the code to find EVERY location where a Percy snapshot should be called (e.g., after each navigation, UI change, or significant state in tests).\n3. Insert snapshot calls at ALL such points, not just the first or most obvious one.\n\nIteratively update every selected test file with percy snapshot calls at all identified points, ensuring comprehensive coverage of visual changes.";
10
+ export declare const PERCY_SIMULATION_DRIVER_INSTRUCTION = "\nMANDATORY: Step-by-Step Percy Visual Change Simulation\nYou MUST follow these steps with NO EXCEPTIONS:\n\nSTEP 1: At this case we have added Percy snapshot commands to the test files at the appropriate locations (e.g., after navigation, UI change, or significant state). \n- Use ONLY existing snapshot names.\n- Do NOT add any popup injection or visual changes yet.\n- Do NOT run any Percy builds at this stage.\n\nSTEP 2: Run ONE comprehensive baseline Percy build that executes ALL tests containing Percy snapshots in a SINGLE build. This creates one baseline build with all snapshots for comparison. Use a command like: npx percy exec --labels=mcp -- python -m pytest tests/ -k 'test_name1 or test_name2 or test_name3' -v to run multiple specific tests in one build.\"\n\nSTEP 3: Modify your test to inject a visible UI change (such as a popup) IMMEDIATELY BEFORE an EXISTING snapshot command (e.g., before percy_snapshot(self.driver, \"screenshot name\")).\n- Do NOT add a new snapshot name for the popup.\n- The popup must appear in an existing snapshot, not a new one.\n- Add this popup code in some test files before the percy_snapshot command you've added, to display the visual changes.\n\n```Javascript\npopup_script = `\nvar popup = document.createElement('div');\npopup.id = 'percy-test-popup';\npopup.style.cssText = popup.style.cssText = `\n /* TODO: Add styles to make the popup large, centered, and visually noticeable.\n Suggested properties: position: fixed; top/left; transform; background; color; font-size; padding; z-index; animation, etc. */\n`;\npopup.innerHTML = 'PERCY TEST<br>VISUAL CHANGE<br>DETECTED!';\ndocument.body.appendChild(popup);\n`;\n\n# Insert this just before the EXISTING snapshot command:\ndriver.execute_script(popup_script)\npercy_snapshot(self.driver, \"Before Adding to Cart\") # (Do NOT change the snapshot name, keep existing one)\n```\n\nSTEP 4: Run a second Percy build with same label and same test command as the baseline.\n- The snapshot names must remain the same as in the baseline.\n- The visual change should now appear in the same snapshot as before.\n- Use the same build command you ran for the baseline.\n\nSTEP 5: Compare the two Percy builds to see the detected visual difference.\n\nSTEP 6: Now ask user if they want to expand percy for other testcases? If yes, call the \"expandPercyVisualTesting\" tool to expand the coverage for the project.\n\nCONSTRAINTS:\n- Do NOT run any builds until explicitly instructed in the steps.\n- Do NOT add new snapshot names\u2014only use existing ones.\n- Do NOT add popup injection until the baseline is established.\n- Visual changes must appear in EXISTING snapshots, not new ones.\n\nVALIDATION CHECKPOINTS (before proceeding to the next step):\n- Are you adding only snapshot commands (not running builds)?\n- Are you reusing existing snapshot names (not creating new ones)?\n- Have you established the baseline first (before adding visual changes)\n\nCRITICAL: \nDo NOT run tests separately or create multiple builds during baseline establishment. The goal is to have exactly TWO builds total: (1) baseline build with all original snapshots, (2) modified build with the same tests but visual changes injected.\n";
11
11
  export declare const PERCY_VERIFICATION_REGEX: RegExp;
@@ -20,7 +20,7 @@ Process files in STRICT sequential order using tool addPercySnapshotCommands() w
20
20
  export const PERCY_SIMULATE_INSTRUCTION = `
21
21
  MANDATORY: Aggressive, Accurate Percy Snapshots Integration
22
22
 
23
- Prerequisite: Before making any code changes in the user's repository, create and switch to a new branch named browserstack-simulation. Stage all changes but do not commit them. If the repository has not been initialized yet, run git init first.
23
+ Prerequisite: Before making any code changes in the user's repository, create and switch to a new branch named percy-integration. Stage all changes but do not commit them. If the repository has not been initialized yet, run git init first.
24
24
 
25
25
  You MUST follow these steps with NO EXCEPTIONS:
26
26
  1. Identify 4-5 key test files in the project that are critical for visual testing.
@@ -37,7 +37,7 @@ STEP 1: At this case we have added Percy snapshot commands to the test files at
37
37
  - Do NOT add any popup injection or visual changes yet.
38
38
  - Do NOT run any Percy builds at this stage.
39
39
 
40
- STEP 2: Run ONE comprehensive baseline Percy build that executes ALL tests containing Percy snapshots in a SINGLE build. This creates one baseline build with all snapshots for comparison. Use a command like: npx percy exec --label=mcp -- python -m pytest tests/ -k 'test_name1 or test_name2 or test_name3' -v to run multiple specific tests in one build."
40
+ STEP 2: Run ONE comprehensive baseline Percy build that executes ALL tests containing Percy snapshots in a SINGLE build. This creates one baseline build with all snapshots for comparison. Use a command like: npx percy exec --labels=mcp -- python -m pytest tests/ -k 'test_name1 or test_name2 or test_name3' -v to run multiple specific tests in one build."
41
41
 
42
42
  STEP 3: Modify your test to inject a visible UI change (such as a popup) IMMEDIATELY BEFORE an EXISTING snapshot command (e.g., before percy_snapshot(self.driver, "screenshot name")).
43
43
  - Do NOT add a new snapshot name for the popup.
@@ -68,7 +68,7 @@ STEP 4: Run a second Percy build with same label and same test command as the ba
68
68
 
69
69
  STEP 5: Compare the two Percy builds to see the detected visual difference.
70
70
 
71
- STEP 6: Now ask user if they want to expand percy for other testcases? If yes, call the "expandPercyVisualTesting" tool to enable complete coverage for the entire project.
71
+ STEP 6: Now ask user if they want to expand percy for other testcases? If yes, call the "expandPercyVisualTesting" tool to expand the coverage for the project.
72
72
 
73
73
  CONSTRAINTS:
74
74
  - Do NOT run any builds until explicitly instructed in the steps.
@@ -27,11 +27,25 @@ export declare const RunTestsOnBrowserStackParamsShape: {
27
27
  detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
28
28
  detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
29
29
  detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
30
- devices: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodTuple<[z.ZodNativeEnum<{
31
- readonly WINDOWS: "windows";
32
- }>, z.ZodString, z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodLiteral<"android">, z.ZodString, z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodLiteral<"ios">, z.ZodString, z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodNativeEnum<{
33
- readonly MACOS: "macos";
34
- }>, z.ZodString, z.ZodString, z.ZodString], null>]>, "many">>;
30
+ devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
31
+ platform: z.ZodEnum<["windows", "macos", "android", "ios"]>;
32
+ deviceName: z.ZodOptional<z.ZodString>;
33
+ osVersion: z.ZodString;
34
+ browser: z.ZodOptional<z.ZodString>;
35
+ browserVersion: z.ZodOptional<z.ZodString>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ platform: "android" | "windows" | "macos" | "ios";
38
+ osVersion: string;
39
+ deviceName?: string | undefined;
40
+ browser?: string | undefined;
41
+ browserVersion?: string | undefined;
42
+ }, {
43
+ platform: "android" | "windows" | "macos" | "ios";
44
+ osVersion: string;
45
+ deviceName?: string | undefined;
46
+ browser?: string | undefined;
47
+ browserVersion?: string | undefined;
48
+ }>, "many">>;
35
49
  };
36
50
  export declare const SetUpPercySchema: z.ZodObject<{
37
51
  projectName: z.ZodString;
@@ -63,23 +77,49 @@ export declare const RunTestsOnBrowserStackSchema: z.ZodObject<{
63
77
  detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
64
78
  detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
65
79
  detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
66
- devices: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodTuple<[z.ZodNativeEnum<{
67
- readonly WINDOWS: "windows";
68
- }>, z.ZodString, z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodLiteral<"android">, z.ZodString, z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodLiteral<"ios">, z.ZodString, z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodNativeEnum<{
69
- readonly MACOS: "macos";
70
- }>, z.ZodString, z.ZodString, z.ZodString], null>]>, "many">>;
80
+ devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
81
+ platform: z.ZodEnum<["windows", "macos", "android", "ios"]>;
82
+ deviceName: z.ZodOptional<z.ZodString>;
83
+ osVersion: z.ZodString;
84
+ browser: z.ZodOptional<z.ZodString>;
85
+ browserVersion: z.ZodOptional<z.ZodString>;
86
+ }, "strip", z.ZodTypeAny, {
87
+ platform: "android" | "windows" | "macos" | "ios";
88
+ osVersion: string;
89
+ deviceName?: string | undefined;
90
+ browser?: string | undefined;
91
+ browserVersion?: string | undefined;
92
+ }, {
93
+ platform: "android" | "windows" | "macos" | "ios";
94
+ osVersion: string;
95
+ deviceName?: string | undefined;
96
+ browser?: string | undefined;
97
+ browserVersion?: string | undefined;
98
+ }>, "many">>;
71
99
  }, "strip", z.ZodTypeAny, {
72
100
  projectName: string;
73
101
  detectedLanguage: SDKSupportedLanguageEnum;
74
102
  detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
75
103
  detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
76
- devices: (["windows", string, string, string] | ["android", string, string, string] | ["ios", string, string, string] | ["macos", string, string, string])[];
104
+ devices: {
105
+ platform: "android" | "windows" | "macos" | "ios";
106
+ osVersion: string;
107
+ deviceName?: string | undefined;
108
+ browser?: string | undefined;
109
+ browserVersion?: string | undefined;
110
+ }[];
77
111
  }, {
78
112
  projectName: string;
79
113
  detectedLanguage: SDKSupportedLanguageEnum;
80
114
  detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
81
115
  detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
82
- devices?: (["windows", string, string, string] | ["android", string, string, string] | ["ios", string, string, string] | ["macos", string, string, string])[] | undefined;
116
+ devices?: {
117
+ platform: "android" | "windows" | "macos" | "ios";
118
+ osVersion: string;
119
+ deviceName?: string | undefined;
120
+ browser?: string | undefined;
121
+ browserVersion?: string | undefined;
122
+ }[] | undefined;
83
123
  }>;
84
124
  export type SetUpPercyInput = z.infer<typeof SetUpPercySchema>;
85
125
  export type RunTestsOnBrowserStackInput = z.infer<typeof RunTestsOnBrowserStackSchema>;
@@ -31,6 +31,27 @@ export const SetUpPercyParamsShape = {
31
31
  .optional()
32
32
  .describe("An array of absolute file paths to specific UI test files. Use this when you want to target specific test files rather than entire folders. If not provided, will use folderPaths instead."),
33
33
  };
34
+ // Device schema for BrowserStack Automate (supports desktop and mobile)
35
+ const DeviceSchema = z.object({
36
+ platform: z
37
+ .enum(["windows", "macos", "android", "ios"])
38
+ .describe("Platform name, e.g. 'windows', 'macos', 'android', 'ios'"),
39
+ deviceName: z
40
+ .string()
41
+ .optional()
42
+ .describe("Device name for mobile platforms, e.g. 'iPhone 15', 'Samsung Galaxy S24'"),
43
+ osVersion: z
44
+ .string()
45
+ .describe("OS version, e.g. '11', 'Sequoia', '14', '17', 'latest'"),
46
+ browser: z
47
+ .string()
48
+ .optional()
49
+ .describe("Browser name, e.g. 'chrome', 'safari', 'edge', 'firefox'"),
50
+ browserVersion: z
51
+ .string()
52
+ .optional()
53
+ .describe("Browser version for desktop platforms only (windows, macos), e.g. '132', 'latest', 'oldest'. Not used for mobile devices (android, ios)."),
54
+ });
34
55
  export const RunTestsOnBrowserStackParamsShape = {
35
56
  projectName: z
36
57
  .string()
@@ -39,49 +60,10 @@ export const RunTestsOnBrowserStackParamsShape = {
39
60
  detectedBrowserAutomationFramework: z.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum),
40
61
  detectedTestingFramework: z.nativeEnum(SDKSupportedTestingFrameworkEnum),
41
62
  devices: z
42
- .array(z.union([
43
- // Windows: [windows, osVersion, browser, browserVersion]
44
- z.tuple([
45
- z
46
- .nativeEnum(WindowsPlatformEnum)
47
- .describe("Platform identifier: 'windows'"),
48
- z.string().describe("Windows version, e.g. '10', '11'"),
49
- z.string().describe("Browser name, e.g. 'chrome', 'firefox', 'edge'"),
50
- z
51
- .string()
52
- .describe("Browser version, e.g. '132', 'latest', 'oldest'"),
53
- ]),
54
- // Android: [android, name, model, osVersion, browser]
55
- z.tuple([
56
- z
57
- .literal(PlatformEnum.ANDROID)
58
- .describe("Platform identifier: 'android'"),
59
- z
60
- .string()
61
- .describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'"),
62
- z.string().describe("Android version, e.g. '14', '16', 'latest'"),
63
- z.string().describe("Browser name, e.g. 'chrome', 'samsung browser'"),
64
- ]),
65
- // iOS: [ios, name, model, osVersion, browser]
66
- z.tuple([
67
- z.literal(PlatformEnum.IOS).describe("Platform identifier: 'ios'"),
68
- z.string().describe("Device name, e.g. 'iPhone 12 Pro'"),
69
- z.string().describe("iOS version, e.g. '17', 'latest'"),
70
- z.string().describe("Browser name, typically 'safari'"),
71
- ]),
72
- // macOS: [mac|macos, name, model, browser, browserVersion]
73
- z.tuple([
74
- z
75
- .nativeEnum(MacOSPlatformEnum)
76
- .describe("Platform identifier: 'mac' or 'macos'"),
77
- z.string().describe("macOS version name, e.g. 'Sequoia', 'Ventura'"),
78
- z.string().describe("Browser name, e.g. 'safari', 'chrome'"),
79
- z.string().describe("Browser version, e.g. 'latest'"),
80
- ]),
81
- ]))
63
+ .array(DeviceSchema)
82
64
  .max(3)
83
65
  .default([])
84
- .describe("Preferred tuples of target devices.Add device only when user asks explicitly for it. Defaults to [] . Example: [['windows', '11', 'chrome', 'latest']]"),
66
+ .describe("Device objects array. Use the object format directly - no transformation needed. Add only when user explicitly requests devices. Examples: [{ platform: 'windows', osVersion: '11', browser: 'chrome', browserVersion: 'latest' }] or [{ platform: 'android', deviceName: 'Samsung Galaxy S24', osVersion: '14', browser: 'chrome' }]."),
85
67
  };
86
68
  export const SetUpPercySchema = z.object(SetUpPercyParamsShape);
87
69
  export const RunTestsOnBrowserStackSchema = z.object(RunTestsOnBrowserStackParamsShape);
@@ -2,12 +2,14 @@ import { apiClient } from "../../../lib/apiClient.js";
2
2
  import { TCG_TRIGGER_URL, TCG_POLL_URL, FETCH_DETAILS_URL, FORM_FIELDS_URL, BULK_CREATE_URL, } from "./config.js";
3
3
  import { createTestCasePayload } from "./helpers.js";
4
4
  import { getBrowserStackAuth } from "../../../lib/get-auth.js";
5
+ import { getTMBaseURL } from "../../../lib/tm-base-url.js";
5
6
  /**
6
7
  * Fetch default and custom form fields for a project.
7
8
  */
8
9
  export async function fetchFormFields(projectId, config) {
10
+ const tmBaseUrl = await getTMBaseURL(config);
9
11
  const res = await apiClient.get({
10
- url: FORM_FIELDS_URL(projectId),
12
+ url: FORM_FIELDS_URL(tmBaseUrl, projectId),
11
13
  headers: {
12
14
  "API-TOKEN": getBrowserStackAuth(config),
13
15
  },
@@ -18,8 +20,9 @@ export async function fetchFormFields(projectId, config) {
18
20
  * Trigger AI-based test case generation for a document.
19
21
  */
20
22
  export async function triggerTestCaseGeneration(document, documentId, folderId, projectId, source, config) {
23
+ const tmBaseUrl = await getTMBaseURL(config);
21
24
  const res = await apiClient.post({
22
- url: TCG_TRIGGER_URL,
25
+ url: TCG_TRIGGER_URL(tmBaseUrl),
23
26
  headers: {
24
27
  "API-TOKEN": getBrowserStackAuth(config),
25
28
  "Content-Type": "application/json",
@@ -31,7 +34,7 @@ export async function triggerTestCaseGeneration(document, documentId, folderId,
31
34
  folderId,
32
35
  projectId,
33
36
  source,
34
- webhookUrl: `https://test-management.browserstack.com/api/v1/projects/${projectId}/folder/${folderId}/webhooks/tcg`,
37
+ webhookUrl: `${tmBaseUrl}/api/v1/projects/${projectId}/folder/${folderId}/webhooks/tcg`,
35
38
  },
36
39
  });
37
40
  if (res.status !== 200) {
@@ -46,8 +49,9 @@ export async function fetchTestCaseDetails(documentId, folderId, projectId, test
46
49
  if (testCaseIds.length === 0) {
47
50
  throw new Error("No testCaseIds provided to fetchTestCaseDetails");
48
51
  }
52
+ const tmBaseUrl = await getTMBaseURL(config);
49
53
  const res = await apiClient.post({
50
- url: FETCH_DETAILS_URL,
54
+ url: FETCH_DETAILS_URL(tmBaseUrl),
51
55
  headers: {
52
56
  "API-TOKEN": getBrowserStackAuth(config),
53
57
  "request-source": source,
@@ -71,11 +75,13 @@ export async function fetchTestCaseDetails(documentId, folderId, projectId, test
71
75
  export async function pollTestCaseDetails(traceRequestId, config) {
72
76
  const detailMap = {};
73
77
  let done = false;
78
+ const tmBaseUrl = await getTMBaseURL(config);
79
+ const TCG_POLL_URL_VALUE = TCG_POLL_URL(tmBaseUrl);
74
80
  while (!done) {
75
81
  // add a bit of jitter to avoid synchronized polling storms
76
82
  await new Promise((r) => setTimeout(r, 10000 + Math.random() * 5000));
77
83
  const poll = await apiClient.post({
78
- url: `${TCG_POLL_URL}?x-bstack-traceRequestId=${encodeURIComponent(traceRequestId)}`,
84
+ url: `${TCG_POLL_URL_VALUE}?x-bstack-traceRequestId=${encodeURIComponent(traceRequestId)}`,
79
85
  headers: {
80
86
  "API-TOKEN": getBrowserStackAuth(config),
81
87
  },
@@ -108,12 +114,14 @@ export async function pollScenariosTestDetails(args, traceId, context, documentI
108
114
  const scenariosMap = {};
109
115
  const detailPromises = [];
110
116
  let iteratorCount = 0;
117
+ const tmBaseUrl = await getTMBaseURL(config);
118
+ const TCG_POLL_URL_VALUE = TCG_POLL_URL(tmBaseUrl);
111
119
  // Promisify interval-style polling using a wrapper
112
120
  await new Promise((resolve, reject) => {
113
121
  const intervalId = setInterval(async () => {
114
122
  try {
115
123
  const poll = await apiClient.post({
116
- url: `${TCG_POLL_URL}?x-bstack-traceRequestId=${encodeURIComponent(traceId)}`,
124
+ url: `${TCG_POLL_URL_VALUE}?x-bstack-traceRequestId=${encodeURIComponent(traceId)}`,
117
125
  headers: {
118
126
  "API-TOKEN": getBrowserStackAuth(config),
119
127
  },
@@ -203,6 +211,8 @@ export async function bulkCreateTestCases(scenariosMap, projectId, folderId, fie
203
211
  const total = Object.keys(scenariosMap).length;
204
212
  let doneCount = 0;
205
213
  let testCaseCount = 0;
214
+ const tmBaseUrl = await getTMBaseURL(config);
215
+ const BULK_CREATE_URL_VALUE = BULK_CREATE_URL(tmBaseUrl, projectId, folderId);
206
216
  for (const { id, testcases } of Object.values(scenariosMap)) {
207
217
  const testCaseLength = testcases.length;
208
218
  testCaseCount += testCaseLength;
@@ -213,7 +223,7 @@ export async function bulkCreateTestCases(scenariosMap, projectId, folderId, fie
213
223
  };
214
224
  try {
215
225
  const resp = await apiClient.post({
216
- url: BULK_CREATE_URL(projectId, folderId),
226
+ url: BULK_CREATE_URL_VALUE,
217
227
  headers: {
218
228
  "API-TOKEN": getBrowserStackAuth(config),
219
229
  "Content-Type": "application/json",
@@ -251,7 +261,8 @@ export async function bulkCreateTestCases(scenariosMap, projectId, folderId, fie
251
261
  return resultString;
252
262
  }
253
263
  export async function projectIdentifierToId(projectId, config) {
254
- const url = `https://test-management.browserstack.com/api/v1/projects/?q=${projectId}`;
264
+ const tmBaseUrl = await getTMBaseURL(config);
265
+ const url = `${tmBaseUrl}/api/v1/projects/?q=${projectId}`;
255
266
  const response = await apiClient.get({
256
267
  url,
257
268
  headers: {
@@ -270,7 +281,8 @@ export async function projectIdentifierToId(projectId, config) {
270
281
  throw new Error(`Project with identifier ${projectId} not found.`);
271
282
  }
272
283
  export async function testCaseIdentifierToDetails(projectId, testCaseIdentifier, config) {
273
- const url = `https://test-management.browserstack.com/api/v1/projects/${projectId}/test-cases/search?q[query]=${testCaseIdentifier}`;
284
+ const tmBaseUrl = await getTMBaseURL(config);
285
+ const url = `${tmBaseUrl}/api/v1/projects/${projectId}/test-cases/search?q[query]=${testCaseIdentifier}`;
274
286
  const response = await apiClient.get({
275
287
  url,
276
288
  headers: {
@@ -1,5 +1,5 @@
1
- export declare const TCG_TRIGGER_URL = "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/suggest-test-cases";
2
- export declare const TCG_POLL_URL = "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/test-cases-polling";
3
- export declare const FETCH_DETAILS_URL = "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/fetch-test-case-details";
4
- export declare const FORM_FIELDS_URL: (projectId: string) => string;
5
- export declare const BULK_CREATE_URL: (projectId: string, folderId: string) => string;
1
+ export declare const TCG_TRIGGER_URL: (baseUrl: string) => string;
2
+ export declare const TCG_POLL_URL: (baseUrl: string) => string;
3
+ export declare const FETCH_DETAILS_URL: (baseUrl: string) => string;
4
+ export declare const FORM_FIELDS_URL: (baseUrl: string, projectId: string) => string;
5
+ export declare const BULK_CREATE_URL: (baseUrl: string, projectId: string, folderId: string) => string;
@@ -1,5 +1,5 @@
1
- export const TCG_TRIGGER_URL = "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/suggest-test-cases";
2
- export const TCG_POLL_URL = "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/test-cases-polling";
3
- export const FETCH_DETAILS_URL = "https://test-management.browserstack.com/api/v1/integration/tcg/test-generation/fetch-test-case-details";
4
- export const FORM_FIELDS_URL = (projectId) => `https://test-management.browserstack.com/api/v1/projects/${projectId}/form-fields-v2`;
5
- export const BULK_CREATE_URL = (projectId, folderId) => `https://test-management.browserstack.com/api/v1/projects/${projectId}/folder/${folderId}/bulk-test-cases`;
1
+ export const TCG_TRIGGER_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/suggest-test-cases`;
2
+ export const TCG_POLL_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/test-cases-polling`;
3
+ export const FETCH_DETAILS_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/fetch-test-case-details`;
4
+ export const FORM_FIELDS_URL = (baseUrl, projectId) => `${baseUrl}/api/v1/projects/${projectId}/form-fields-v2`;
5
+ export const BULK_CREATE_URL = (baseUrl, projectId, folderId) => `${baseUrl}/api/v1/projects/${projectId}/folder/${folderId}/bulk-test-cases`;
@@ -1,6 +1,7 @@
1
1
  import { apiClient } from "../../lib/apiClient.js";
2
2
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
3
3
  import { z } from "zod";
4
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
4
5
  /**
5
6
  * Schema for adding a test result to a test run.
6
7
  */
@@ -28,7 +29,8 @@ export const AddTestResultSchema = z.object({
28
29
  export async function addTestResult(rawArgs, config) {
29
30
  try {
30
31
  const args = AddTestResultSchema.parse(rawArgs);
31
- const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/results`;
32
+ const tmBaseUrl = await getTMBaseURL(config);
33
+ const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/results`;
32
34
  const body = {
33
35
  test_result: args.test_result,
34
36
  test_case_id: args.test_case_id,
@@ -3,6 +3,7 @@ import { apiClient } from "../../lib/apiClient.js";
3
3
  import { projectIdentifierToId, testCaseIdentifierToDetails, } from "./TCG-utils/api.js";
4
4
  import { pollLCAStatus } from "./poll-lca-status.js";
5
5
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
6
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
6
7
  /**
7
8
  * Schema for creating LCA steps for a test case
8
9
  */
@@ -55,7 +56,8 @@ export async function createLCASteps(args, context, config) {
55
56
  const projectId = await projectIdentifierToId(args.project_identifier, config);
56
57
  // Get the test case ID and folder ID from identifier
57
58
  const { testCaseId, folderId } = await testCaseIdentifierToDetails(projectId, args.test_case_identifier, config);
58
- const url = `https://test-management.browserstack.com/api/v1/projects/${projectId}/test-cases/${testCaseId}/lcnc`;
59
+ const tmBaseUrl = await getTMBaseURL(config);
60
+ const url = `${tmBaseUrl}/api/v1/projects/${projectId}/test-cases/${testCaseId}/lcnc`;
59
61
  const payload = {
60
62
  base_url: args.base_url,
61
63
  credentials: args.credentials,
@@ -63,7 +65,7 @@ export async function createLCASteps(args, context, config) {
63
65
  test_name: args.test_name,
64
66
  test_case_details: args.test_case_details,
65
67
  version: "v2",
66
- webhook_path: `https://test-management.browserstack.com/api/v1/projects/${projectId}/test-cases/${testCaseId}/webhooks/lcnc`,
68
+ webhook_path: `${tmBaseUrl}/api/v1/projects/${projectId}/test-cases/${testCaseId}/webhooks/lcnc`,
67
69
  };
68
70
  await apiClient.post({
69
71
  url,
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { formatAxiosError } from "../../lib/error.js";
4
4
  import { projectIdentifierToId } from "../testmanagement-utils/TCG-utils/api.js";
5
5
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
6
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
6
7
  // Schema for combined project/folder creation
7
8
  export const CreateProjFoldSchema = z.object({
8
9
  project_name: z
@@ -43,8 +44,9 @@ export async function createProjectOrFolder(args, config) {
43
44
  try {
44
45
  const authString = getBrowserStackAuth(config);
45
46
  const [username, password] = authString.split(":");
47
+ const tmBaseUrl = await getTMBaseURL(config);
46
48
  const res = await apiClient.post({
47
- url: "https://test-management.browserstack.com/api/v2/projects",
49
+ url: `${tmBaseUrl}/api/v2/projects`,
48
50
  headers: {
49
51
  "Content-Type": "application/json",
50
52
  Authorization: "Basic " +
@@ -69,8 +71,9 @@ export async function createProjectOrFolder(args, config) {
69
71
  if (!projId)
70
72
  throw new Error("Cannot create folder without project_identifier.");
71
73
  try {
74
+ const tmBaseUrl = await getTMBaseURL(config);
72
75
  const res = await apiClient.post({
73
- url: `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(projId)}/folders`,
76
+ url: `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(projId)}/folders`,
74
77
  headers: {
75
78
  "Content-Type": "application/json",
76
79
  Authorization: "Basic " +
@@ -98,7 +101,7 @@ export async function createProjectOrFolder(args, config) {
98
101
  - ID: ${folder.id}
99
102
  - Name: ${folder.name}
100
103
  - Project Identifier: ${projId}
101
- Access it here: https://test-management.browserstack.com/projects/${projectId}/folder/${folder.id}/`,
104
+ Access it here: ${tmBaseUrl}/projects/${projectId}/folder/${folder.id}/`,
102
105
  },
103
106
  ],
104
107
  };
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
2
2
  import { z } from "zod";
3
3
  import { formatAxiosError } from "../../lib/error.js";
4
4
  import { projectIdentifierToId } from "./TCG-utils/api.js";
5
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
5
6
  export const CreateTestCaseSchema = z.object({
6
7
  project_identifier: z
7
8
  .string()
@@ -78,8 +79,9 @@ export async function createTestCase(params, config) {
78
79
  const authString = getBrowserStackAuth(config);
79
80
  const [username, password] = authString.split(":");
80
81
  try {
82
+ const tmBaseUrl = await getTMBaseURL(config);
81
83
  const response = await apiClient.post({
82
- url: `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(params.project_identifier)}/folders/${encodeURIComponent(params.folder_id)}/test-cases`,
84
+ url: `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(params.project_identifier)}/folders/${encodeURIComponent(params.folder_id)}/test-cases`,
83
85
  headers: {
84
86
  "Content-Type": "application/json",
85
87
  Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
@@ -109,7 +111,7 @@ export async function createTestCase(params, config) {
109
111
  - Identifier: ${tc.identifier}
110
112
  - Title: ${tc.title}
111
113
 
112
- You can view it here: https://test-management.browserstack.com/projects/${projectId}/folder/search?q=${tc.identifier}`,
114
+ You can view it here: ${tmBaseUrl}/projects/${projectId}/folder/search?q=${tc.identifier}`,
113
115
  },
114
116
  {
115
117
  type: "text",
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
2
2
  import { z } from "zod";
3
3
  import { formatAxiosError } from "../../lib/error.js";
4
4
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
5
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
5
6
  /**
6
7
  * Schema for creating a test run.
7
8
  */
@@ -54,7 +55,8 @@ export async function createTestRun(rawArgs, config) {
54
55
  },
55
56
  };
56
57
  const args = CreateTestRunSchema.parse(inputArgs);
57
- const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs`;
58
+ const tmBaseUrl = await getTMBaseURL(config);
59
+ const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs`;
58
60
  const authString = getBrowserStackAuth(config);
59
61
  const [username, password] = authString.split(":");
60
62
  const response = await apiClient.post({
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
2
2
  import { z } from "zod";
3
3
  import { formatAxiosError } from "../../lib/error.js";
4
4
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
5
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
5
6
  /**
6
7
  * Schema for listing test cases with optional filters.
7
8
  */
@@ -38,7 +39,8 @@ export async function listTestCases(args, config) {
38
39
  params.append("priority", args.priority);
39
40
  if (args.p !== undefined)
40
41
  params.append("p", args.p.toString());
41
- const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-cases?${params.toString()}`;
42
+ const tmBaseUrl = await getTMBaseURL(config);
43
+ const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-cases?${params.toString()}`;
42
44
  const authString = getBrowserStackAuth(config);
43
45
  const [username, password] = authString.split(":");
44
46
  const resp = await apiClient.get({
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
2
2
  import { z } from "zod";
3
3
  import { formatAxiosError } from "../../lib/error.js";
4
4
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
5
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
5
6
  /**
6
7
  * Schema for listing test runs with optional filters.
7
8
  */
@@ -23,7 +24,8 @@ export async function listTestRuns(args, config) {
23
24
  if (args.run_state) {
24
25
  params.set("run_state", args.run_state);
25
26
  }
26
- const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs?` + params.toString();
27
+ const tmBaseUrl = await getTMBaseURL(config);
28
+ const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs?` + params.toString();
27
29
  const authString = getBrowserStackAuth(config);
28
30
  const [username, password] = authString.split(":");
29
31
  const resp = await apiClient.get({
@@ -1,5 +1,6 @@
1
1
  import { apiClient } from "../../lib/apiClient.js";
2
2
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
3
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
3
4
  /**
4
5
  * Poll test case details to check LCA build status
5
6
  */
@@ -7,7 +8,8 @@ export async function pollLCAStatus(projectId, folderId, testCaseId, context, ma
7
8
  initialWaitMs = 2 * 60 * 1000, // 2 minutes initial wait
8
9
  pollIntervalMs = 10 * 1000, // 10 seconds interval
9
10
  config) {
10
- const url = `https://test-management.browserstack.com/api/v1/projects/${projectId}/folder/${folderId}/test-cases/${testCaseId}`;
11
+ const tmBaseUrl = await getTMBaseURL(config);
12
+ const url = `${tmBaseUrl}/api/v1/projects/${projectId}/folder/${folderId}/test-cases/${testCaseId}`;
11
13
  const startTime = Date.now();
12
14
  // Send initial notification that polling is starting
13
15
  const notificationInterval = Math.min(initialWaitMs, pollIntervalMs);
@@ -3,6 +3,7 @@ import { buildDefaultFieldMaps, findBooleanFieldId, } from "./TCG-utils/helpers.
3
3
  import { signedUrlMap } from "../../lib/inmemory-store.js";
4
4
  import logger from "../../logger.js";
5
5
  import { projectIdentifierToId } from "./TCG-utils/api.js";
6
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
6
7
  export async function createTestCasesFromFile(args, context, config) {
7
8
  logger.info(`createTestCasesFromFile called with projectId: ${args.projectReferenceId}, folderId: ${args.folderId}`);
8
9
  if (args.projectReferenceId.startsWith("PR-")) {
@@ -31,7 +32,8 @@ export async function createTestCasesFromFile(args, context, config) {
31
32
  const scenariosMap = await pollScenariosTestDetails(args, traceId, context, documentId, source, config);
32
33
  const resultString = await bulkCreateTestCases(scenariosMap, args.projectReferenceId, args.folderId, fieldMaps, booleanFieldId, traceId, context, documentId, config);
33
34
  signedUrlMap.delete(args.documentId);
34
- const dashboardURL = `https://test-management.browserstack.com/projects/${args.projectReferenceId}/folder/${args.folderId}/test-cases`;
35
+ const tmBaseUrl = await getTMBaseURL(config);
36
+ const dashboardURL = `${tmBaseUrl}/projects/${args.projectReferenceId}/folder/${args.folderId}/test-cases`;
35
37
  return {
36
38
  content: [
37
39
  {
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
2
2
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
3
3
  import { z } from "zod";
4
4
  import { formatAxiosError } from "../../lib/error.js";
5
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
5
6
  /**
6
7
  * Schema for updating a test run with partial fields.
7
8
  */
@@ -31,7 +32,8 @@ export const UpdateTestRunSchema = z.object({
31
32
  export async function updateTestRun(args, config) {
32
33
  try {
33
34
  const body = { test_run: args.test_run };
34
- const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/update`;
35
+ const tmBaseUrl = await getTMBaseURL(config);
36
+ const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/update`;
35
37
  const authString = getBrowserStackAuth(config);
36
38
  const [username, password] = authString.split(":");
37
39
  const resp = await apiClient.patch({
@@ -7,6 +7,7 @@ import { v4 as uuidv4 } from "uuid";
7
7
  import { getBrowserStackAuth } from "../../lib/get-auth.js";
8
8
  import { signedUrlMap } from "../../lib/inmemory-store.js";
9
9
  import { projectIdentifierToId } from "./TCG-utils/api.js";
10
+ import { getTMBaseURL } from "../../lib/tm-base-url.js";
10
11
  /**
11
12
  * Schema for the upload file tool
12
13
  */
@@ -41,7 +42,8 @@ export async function uploadFile(args, config) {
41
42
  const projectIdResponse = await projectIdentifierToId(project_identifier, config);
42
43
  const formData = new FormData();
43
44
  formData.append("attachments[]", fs.createReadStream(file_path));
44
- const uploadUrl = `https://test-management.browserstack.com/api/v1/projects/${projectIdResponse}/generic/attachments/ai_uploads`;
45
+ const tmBaseUrl = await getTMBaseURL(config);
46
+ const uploadUrl = `${tmBaseUrl}/api/v1/projects/${projectIdResponse}/generic/attachments/ai_uploads`;
45
47
  const response = await apiClient.post({
46
48
  url: uploadUrl,
47
49
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserstack/mcp-server",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "BrowserStack's Official MCP Server",
5
5
  "mcpName": "io.github.browserstack/mcp-server",
6
6
  "main": "dist/index.js",