@browserstack/mcp-server 1.2.4 → 1.2.6-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) 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/live-utils/start-session.js +1 -1
  32. package/dist/tools/percy-sdk.js +33 -6
  33. package/dist/tools/percy-snapshot-utils/constants.d.ts +0 -6
  34. package/dist/tools/percy-snapshot-utils/constants.js +0 -15
  35. package/dist/tools/rca-agent-utils/constants.d.ts +1 -1
  36. package/dist/tools/rca-agent-utils/constants.js +2 -2
  37. package/dist/tools/rca-agent-utils/rca-data.d.ts +1 -1
  38. package/dist/tools/rca-agent-utils/rca-data.js +2 -2
  39. package/dist/tools/rca-agent-utils/types.d.ts +3 -3
  40. package/dist/tools/rca-agent.d.ts +1 -1
  41. package/dist/tools/run-percy-scan.js +51 -10
  42. package/dist/tools/sdk-utils/bstack/configUtils.d.ts +8 -4
  43. package/dist/tools/sdk-utils/bstack/configUtils.js +74 -20
  44. package/dist/tools/sdk-utils/bstack/constants.d.ts +1 -1
  45. package/dist/tools/sdk-utils/bstack/constants.js +7 -9
  46. package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +1 -1
  47. package/dist/tools/sdk-utils/bstack/sdkHandler.js +19 -9
  48. package/dist/tools/sdk-utils/common/constants.d.ts +6 -5
  49. package/dist/tools/sdk-utils/common/constants.js +8 -7
  50. package/dist/tools/sdk-utils/common/device-validator.d.ts +25 -0
  51. package/dist/tools/sdk-utils/common/device-validator.js +375 -0
  52. package/dist/tools/sdk-utils/common/schema.d.ts +32 -8
  53. package/dist/tools/sdk-utils/common/schema.js +62 -3
  54. package/dist/tools/sdk-utils/common/utils.d.ts +1 -1
  55. package/dist/tools/sdk-utils/common/utils.js +14 -2
  56. package/dist/tools/sdk-utils/handler.d.ts +1 -0
  57. package/dist/tools/sdk-utils/handler.js +59 -14
  58. package/dist/tools/sdk-utils/percy-automate/constants.d.ts +4 -4
  59. package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -4
  60. package/dist/tools/sdk-utils/percy-bstack/handler.js +5 -1
  61. package/dist/tools/sdk-utils/percy-web/constants.d.ts +22 -20
  62. package/dist/tools/sdk-utils/percy-web/constants.js +39 -0
  63. package/dist/tools/sdk-utils/percy-web/handler.js +3 -1
  64. package/package.json +2 -2
@@ -0,0 +1,375 @@
1
+ import { getDevicesAndBrowsers, BrowserStackProducts, } from "../../../lib/device-cache.js";
2
+ import { resolveVersion } from "../../../lib/version-resolver.js";
3
+ import { customFuzzySearch } from "../../../lib/fuzzy.js";
4
+ import { SDKSupportedBrowserAutomationFrameworkEnum } from "./types.js";
5
+ const DEFAULTS = {
6
+ windows: { browser: "chrome" },
7
+ macos: { browser: "safari" },
8
+ android: { device: "Samsung Galaxy S24", browser: "chrome" },
9
+ ios: { device: "iPhone 15", browser: "safari" },
10
+ };
11
+ // ============================================================================
12
+ // AUTOMATE SECTION (Desktop + Mobile for BrowserStack SDK)
13
+ // ============================================================================
14
+ // Helper functions to build device entries and eliminate duplication
15
+ function buildDesktopEntries(automateData) {
16
+ if (!automateData.desktop) {
17
+ return [];
18
+ }
19
+ return automateData.desktop.flatMap((platform) => platform.browsers.map((browser) => ({
20
+ os: platform.os,
21
+ os_version: platform.os_version,
22
+ browser: browser.browser,
23
+ browser_version: browser.browser_version,
24
+ })));
25
+ }
26
+ function buildMobileEntries(appAutomateData, platform) {
27
+ if (!appAutomateData.mobile) {
28
+ return [];
29
+ }
30
+ return appAutomateData.mobile
31
+ .filter((group) => group.os === platform)
32
+ .flatMap((group) => group.devices.map((device) => ({
33
+ os: group.os,
34
+ os_version: device.os_version,
35
+ display_name: device.display_name,
36
+ browsers: device.browsers || [
37
+ {
38
+ browser: device.browser || (platform === "android" ? "chrome" : "safari"),
39
+ },
40
+ ],
41
+ })));
42
+ }
43
+ // Performance optimization: Create indexed maps for faster lookups
44
+ function createDesktopIndex(entries) {
45
+ const byOS = new Map();
46
+ const byOSVersion = new Map();
47
+ const byBrowser = new Map();
48
+ const nested = new Map();
49
+ for (const entry of entries) {
50
+ // Index by OS
51
+ if (!byOS.has(entry.os)) {
52
+ byOS.set(entry.os, []);
53
+ }
54
+ byOS.get(entry.os).push(entry);
55
+ // Index by OS version
56
+ if (!byOSVersion.has(entry.os_version)) {
57
+ byOSVersion.set(entry.os_version, []);
58
+ }
59
+ byOSVersion.get(entry.os_version).push(entry);
60
+ // Index by browser
61
+ if (!byBrowser.has(entry.browser)) {
62
+ byBrowser.set(entry.browser, []);
63
+ }
64
+ byBrowser.get(entry.browser).push(entry);
65
+ // Build nested index: Map<os, Map<os_version, Map<browser, DesktopBrowserEntry[]>>>
66
+ if (!nested.has(entry.os)) {
67
+ nested.set(entry.os, new Map());
68
+ }
69
+ const osMap = nested.get(entry.os);
70
+ if (!osMap.has(entry.os_version)) {
71
+ osMap.set(entry.os_version, new Map());
72
+ }
73
+ const osVersionMap = osMap.get(entry.os_version);
74
+ if (!osVersionMap.has(entry.browser)) {
75
+ osVersionMap.set(entry.browser, []);
76
+ }
77
+ osVersionMap.get(entry.browser).push(entry);
78
+ }
79
+ return { byOS, byOSVersion, byBrowser, nested };
80
+ }
81
+ function createMobileIndex(entries) {
82
+ const byPlatform = new Map();
83
+ const byDeviceName = new Map();
84
+ const byOSVersion = new Map();
85
+ for (const entry of entries) {
86
+ // Index by platform
87
+ if (!byPlatform.has(entry.os)) {
88
+ byPlatform.set(entry.os, []);
89
+ }
90
+ byPlatform.get(entry.os).push(entry);
91
+ // Index by device name (case-insensitive)
92
+ const deviceKey = entry.display_name.toLowerCase();
93
+ if (!byDeviceName.has(deviceKey)) {
94
+ byDeviceName.set(deviceKey, []);
95
+ }
96
+ byDeviceName.get(deviceKey).push(entry);
97
+ // Index by OS version
98
+ if (!byOSVersion.has(entry.os_version)) {
99
+ byOSVersion.set(entry.os_version, []);
100
+ }
101
+ byOSVersion.get(entry.os_version).push(entry);
102
+ }
103
+ return { byPlatform, byDeviceName, byOSVersion };
104
+ }
105
+ export async function validateDevices(devices, framework) {
106
+ const validatedEnvironments = [];
107
+ if (!devices || devices.length === 0) {
108
+ // Use centralized default fallback
109
+ return [
110
+ {
111
+ platform: "windows",
112
+ osVersion: "11",
113
+ browser: DEFAULTS.windows.browser,
114
+ browserVersion: "latest",
115
+ },
116
+ ];
117
+ }
118
+ // Determine what data we need to fetch
119
+ const needsDesktop = devices.some((env) => ["windows", "macos"].includes((env[0] || "").toLowerCase()));
120
+ const needsMobile = devices.some((env) => ["android", "ios"].includes((env[0] || "").toLowerCase()));
121
+ // Fetch data using framework-specific endpoint for both desktop and mobile
122
+ let deviceData = null;
123
+ try {
124
+ if (needsDesktop || needsMobile) {
125
+ if (framework === SDKSupportedBrowserAutomationFrameworkEnum.playwright) {
126
+ deviceData = (await getDevicesAndBrowsers(BrowserStackProducts.PLAYWRIGHT_AUTOMATE));
127
+ }
128
+ else {
129
+ deviceData = (await getDevicesAndBrowsers(BrowserStackProducts.SELENIUM_AUTOMATE));
130
+ }
131
+ }
132
+ }
133
+ catch (error) {
134
+ throw new Error(`Failed to fetch device data: ${error instanceof Error ? error.message : String(error)}`);
135
+ }
136
+ // Preprocess data into indexed maps for better performance
137
+ let desktopIndex = null;
138
+ let androidIndex = null;
139
+ let iosIndex = null;
140
+ if (needsDesktop && deviceData) {
141
+ const desktopEntries = buildDesktopEntries(deviceData);
142
+ desktopIndex = createDesktopIndex(desktopEntries);
143
+ }
144
+ if (needsMobile && deviceData) {
145
+ const androidEntries = buildMobileEntries(deviceData, "android");
146
+ const iosEntries = buildMobileEntries(deviceData, "ios");
147
+ androidIndex = createMobileIndex(androidEntries);
148
+ iosIndex = createMobileIndex(iosEntries);
149
+ }
150
+ for (const env of devices) {
151
+ const discriminator = (env[0] || "").toLowerCase();
152
+ let validatedEnv;
153
+ if (discriminator === "windows") {
154
+ validatedEnv = validateDesktopEnvironment(env, desktopIndex, "windows", DEFAULTS.windows.browser);
155
+ }
156
+ else if (discriminator === "macos") {
157
+ validatedEnv = validateDesktopEnvironment(env, desktopIndex, "macos", DEFAULTS.macos.browser);
158
+ }
159
+ else if (discriminator === "android") {
160
+ validatedEnv = validateMobileEnvironment(env, androidIndex, "android", DEFAULTS.android.device, DEFAULTS.android.browser);
161
+ }
162
+ else if (discriminator === "ios") {
163
+ validatedEnv = validateMobileEnvironment(env, iosIndex, "ios", DEFAULTS.ios.device, DEFAULTS.ios.browser);
164
+ }
165
+ else {
166
+ throw new Error(`Unsupported platform: ${discriminator}`);
167
+ }
168
+ validatedEnvironments.push(validatedEnv);
169
+ }
170
+ if (framework === SDKSupportedBrowserAutomationFrameworkEnum.playwright) {
171
+ validatedEnvironments.forEach((env) => {
172
+ if (env.browser) {
173
+ env.browser = env.browser.toLowerCase();
174
+ }
175
+ });
176
+ }
177
+ return validatedEnvironments;
178
+ }
179
+ // Optimized desktop validation using nested indexed maps for O(1) lookups
180
+ function validateDesktopEnvironment(env, index, platform, defaultBrowser) {
181
+ const [, osVersion, browser, browserVersion] = env;
182
+ const osKey = platform === "windows" ? "Windows" : "OS X";
183
+ // Use nested index for O(1) lookup instead of filtering
184
+ const osMap = index.nested.get(osKey);
185
+ if (!osMap) {
186
+ throw new Error(`No ${platform} devices available`);
187
+ }
188
+ // Get available OS versions for this platform
189
+ const availableOSVersions = Array.from(osMap.keys());
190
+ const validatedOSVersion = resolveVersion(osVersion || "latest", availableOSVersions);
191
+ // Use nested index for O(1) lookup
192
+ const osVersionMap = osMap.get(validatedOSVersion);
193
+ if (!osVersionMap) {
194
+ throw new Error(`OS version "${validatedOSVersion}" not available for ${platform}`);
195
+ }
196
+ // Get available browsers for this OS version
197
+ const availableBrowsers = Array.from(osVersionMap.keys());
198
+ const validatedBrowser = validateBrowserExact(browser || defaultBrowser, availableBrowsers);
199
+ // Use nested index for O(1) lookup
200
+ const browserEntries = osVersionMap.get(validatedBrowser);
201
+ if (!browserEntries || browserEntries.length === 0) {
202
+ throw new Error(`Browser "${validatedBrowser}" not available for ${platform} ${validatedOSVersion}`);
203
+ }
204
+ const availableBrowserVersions = [
205
+ ...new Set(browserEntries.map((e) => e.browser_version)),
206
+ ];
207
+ const validatedBrowserVersion = resolveVersion(browserVersion || "latest", availableBrowserVersions);
208
+ return {
209
+ platform,
210
+ osVersion: validatedOSVersion,
211
+ browser: validatedBrowser,
212
+ browserVersion: validatedBrowserVersion,
213
+ };
214
+ }
215
+ // Optimized mobile validation using indexed maps
216
+ function validateMobileEnvironment(env, index, platform, defaultDevice, defaultBrowser) {
217
+ const [, deviceName, osVersion, browser] = env;
218
+ const platformEntries = index.byPlatform.get(platform) || [];
219
+ if (platformEntries.length === 0) {
220
+ throw new Error(`No ${platform} devices available`);
221
+ }
222
+ // Use fuzzy search only for device names (as suggested in feedback)
223
+ const deviceMatches = customFuzzySearch(platformEntries, ["display_name"], deviceName || defaultDevice, 5);
224
+ if (deviceMatches.length === 0) {
225
+ throw new Error(`No ${platform} devices matching "${deviceName}". Available devices: ${platformEntries
226
+ .map((d) => d.display_name || "unknown")
227
+ .slice(0, 5)
228
+ .join(", ")}`);
229
+ }
230
+ // Try to find exact match first
231
+ const exactMatch = deviceMatches.find((m) => m.display_name.toLowerCase() === (deviceName || "").toLowerCase());
232
+ // If no exact match, throw error instead of using fuzzy match
233
+ if (!exactMatch) {
234
+ const suggestions = deviceMatches.map((m) => m.display_name).join(", ");
235
+ throw new Error(`Device "${deviceName}" not found exactly for ${platform}. Available similar devices: ${suggestions}. Please use the exact device name.`);
236
+ }
237
+ // Use index for faster filtering
238
+ const deviceKey = exactMatch.display_name.toLowerCase();
239
+ const deviceFiltered = index.byDeviceName.get(deviceKey) || [];
240
+ const availableOSVersions = [
241
+ ...new Set(deviceFiltered.map((d) => d.os_version)),
242
+ ];
243
+ const validatedOSVersion = resolveVersion(osVersion || "latest", availableOSVersions);
244
+ // Use index for faster filtering
245
+ const osVersionEntries = index.byOSVersion.get(validatedOSVersion) || [];
246
+ const osFiltered = osVersionEntries.filter((d) => d.display_name.toLowerCase() === deviceKey);
247
+ // Validate browser if provided - use exact matching for browsers
248
+ let validatedBrowser = browser || defaultBrowser;
249
+ if (browser && osFiltered.length > 0) {
250
+ // Extract browsers more carefully - handle different possible structures
251
+ const availableBrowsers = [
252
+ ...new Set(osFiltered.flatMap((d) => {
253
+ if (d.browsers && Array.isArray(d.browsers)) {
254
+ // If browsers is an array of objects with browser property
255
+ return d.browsers
256
+ .map((b) => {
257
+ // Use display_name for user-friendly browser names, fallback to browser field
258
+ return b.display_name || b.browser;
259
+ })
260
+ .filter(Boolean);
261
+ }
262
+ // For mobile devices, provide default browsers if none found
263
+ return platform === "android" ? ["chrome"] : ["safari"];
264
+ })),
265
+ ].filter(Boolean);
266
+ if (availableBrowsers.length > 0) {
267
+ try {
268
+ validatedBrowser = validateBrowserExact(browser, availableBrowsers);
269
+ }
270
+ catch (error) {
271
+ // Add more context to browser validation errors
272
+ throw new Error(`Failed to validate browser "${browser}" for ${platform} device "${exactMatch.display_name}" on OS version "${validatedOSVersion}". ${error instanceof Error ? error.message : String(error)}`);
273
+ }
274
+ }
275
+ else {
276
+ // For mobile, if no specific browsers found, just use the requested browser
277
+ // as most mobile devices support standard browsers
278
+ validatedBrowser = browser || defaultBrowser;
279
+ }
280
+ }
281
+ return {
282
+ platform,
283
+ osVersion: validatedOSVersion,
284
+ deviceName: exactMatch.display_name,
285
+ browser: validatedBrowser,
286
+ };
287
+ }
288
+ // ============================================================================
289
+ // APP AUTOMATE SECTION (Mobile devices for App Automate)
290
+ // ============================================================================
291
+ export async function validateAppAutomateDevices(devices) {
292
+ const validatedDevices = [];
293
+ if (!devices || devices.length === 0) {
294
+ // Use centralized default fallback
295
+ return [
296
+ {
297
+ platform: "android",
298
+ osVersion: "latest",
299
+ deviceName: DEFAULTS.android.device,
300
+ },
301
+ ];
302
+ }
303
+ let appAutomateData;
304
+ try {
305
+ // Fetch app automate device data
306
+ appAutomateData = (await getDevicesAndBrowsers(BrowserStackProducts.APP_AUTOMATE));
307
+ }
308
+ catch (error) {
309
+ // Only wrap fetch-related errors
310
+ throw new Error(`Failed to fetch device data: ${error instanceof Error ? error.message : String(error)}`);
311
+ }
312
+ for (const device of devices) {
313
+ // Parse device array in format ["android", "Device Name", "OS Version"]
314
+ const [platform, deviceName, osVersion] = device;
315
+ // Find matching device in the data
316
+ let validatedDevice = null;
317
+ if (!appAutomateData.mobile) {
318
+ throw new Error("No mobile device data available");
319
+ }
320
+ // Filter by platform first
321
+ const platformGroup = appAutomateData.mobile.find((group) => group.os === platform.toLowerCase());
322
+ if (!platformGroup) {
323
+ throw new Error(`Platform "${platform}" not supported for App Automate`);
324
+ }
325
+ const platformDevices = platformGroup.devices;
326
+ // Find exact device name match (case-insensitive)
327
+ const exactMatch = platformDevices.find((d) => d.display_name.toLowerCase() === deviceName.toLowerCase());
328
+ if (exactMatch) {
329
+ // Check if the OS version is available for this device
330
+ const deviceVersions = platformDevices
331
+ .filter((d) => d.display_name === exactMatch.display_name)
332
+ .map((d) => d.os_version);
333
+ const validatedOSVersion = resolveVersion(osVersion || "latest", deviceVersions);
334
+ validatedDevice = {
335
+ platform: platformGroup.os,
336
+ osVersion: validatedOSVersion,
337
+ deviceName: exactMatch.display_name,
338
+ };
339
+ }
340
+ if (!validatedDevice) {
341
+ // If no exact match found, suggest similar devices from the SAME platform only
342
+ const platformDevicesForSearch = platformDevices.map((d) => ({
343
+ ...d,
344
+ platform: platformGroup.os,
345
+ }));
346
+ // Try fuzzy search with a more lenient threshold
347
+ const deviceMatches = customFuzzySearch(platformDevicesForSearch, ["display_name"], deviceName, 5, 0.8);
348
+ const suggestions = deviceMatches
349
+ .map((m) => `${m.display_name}`)
350
+ .join(", ");
351
+ // If no fuzzy matches, show some available devices as fallback
352
+ const fallbackDevices = platformDevicesForSearch
353
+ .slice(0, 5)
354
+ .map((d) => d.display_name)
355
+ .join(", ");
356
+ const errorMessage = suggestions
357
+ ? `Device "${deviceName}" not found for platform "${platform}".\nAvailable similar devices: ${suggestions}`
358
+ : `Device "${deviceName}" not found for platform "${platform}".\nAvailable devices: ${fallbackDevices}`;
359
+ throw new Error(errorMessage);
360
+ }
361
+ validatedDevices.push(validatedDevice);
362
+ }
363
+ return validatedDevices;
364
+ }
365
+ // ============================================================================
366
+ // SHARED UTILITY FUNCTIONS
367
+ // ============================================================================
368
+ // Exact browser validation (preferred for structured fields)
369
+ function validateBrowserExact(requestedBrowser, availableBrowsers) {
370
+ const exactMatch = availableBrowsers.find((b) => b.toLowerCase() === requestedBrowser.toLowerCase());
371
+ if (exactMatch) {
372
+ return exactMatch;
373
+ }
374
+ throw new Error(`Browser "${requestedBrowser}" not found. Available options: ${availableBrowsers.join(", ")}`);
375
+ }
@@ -1,20 +1,37 @@
1
1
  import { z } from "zod";
2
2
  import { PercyIntegrationTypeEnum } from "./types.js";
3
3
  import { SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, SDKSupportedLanguageEnum } from "./types.js";
4
+ export declare const PlatformEnum: {
5
+ readonly WINDOWS: "windows";
6
+ readonly MACOS: "macos";
7
+ readonly ANDROID: "android";
8
+ readonly IOS: "ios";
9
+ };
10
+ export declare const WindowsPlatformEnum: {
11
+ readonly WINDOWS: "windows";
12
+ };
13
+ export declare const MacOSPlatformEnum: {
14
+ readonly MACOS: "macos";
15
+ };
4
16
  export declare const SetUpPercyParamsShape: {
5
17
  projectName: z.ZodString;
6
18
  detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
7
19
  detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
8
20
  detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
9
21
  integrationType: z.ZodNativeEnum<typeof PercyIntegrationTypeEnum>;
10
- folderPaths: z.ZodArray<z.ZodString, "many">;
22
+ folderPaths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
23
+ filePaths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
11
24
  };
12
25
  export declare const RunTestsOnBrowserStackParamsShape: {
13
26
  projectName: z.ZodString;
14
27
  detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
15
28
  detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
16
29
  detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
17
- desiredPlatforms: z.ZodArray<z.ZodEnum<["windows", "macos", "android", "ios"]>, "many">;
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">>;
18
35
  };
19
36
  export declare const SetUpPercySchema: z.ZodObject<{
20
37
  projectName: z.ZodString;
@@ -22,40 +39,47 @@ export declare const SetUpPercySchema: z.ZodObject<{
22
39
  detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
23
40
  detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
24
41
  integrationType: z.ZodNativeEnum<typeof PercyIntegrationTypeEnum>;
25
- folderPaths: z.ZodArray<z.ZodString, "many">;
42
+ folderPaths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
43
+ filePaths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
26
44
  }, "strip", z.ZodTypeAny, {
27
45
  projectName: string;
28
46
  detectedLanguage: SDKSupportedLanguageEnum;
29
47
  detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
30
48
  detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
31
49
  integrationType: PercyIntegrationTypeEnum;
32
- folderPaths: string[];
50
+ folderPaths?: string[] | undefined;
51
+ filePaths?: string[] | undefined;
33
52
  }, {
34
53
  projectName: string;
35
54
  detectedLanguage: SDKSupportedLanguageEnum;
36
55
  detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
37
56
  detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
38
57
  integrationType: PercyIntegrationTypeEnum;
39
- folderPaths: string[];
58
+ folderPaths?: string[] | undefined;
59
+ filePaths?: string[] | undefined;
40
60
  }>;
41
61
  export declare const RunTestsOnBrowserStackSchema: z.ZodObject<{
42
62
  projectName: z.ZodString;
43
63
  detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
44
64
  detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
45
65
  detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
46
- desiredPlatforms: z.ZodArray<z.ZodEnum<["windows", "macos", "android", "ios"]>, "many">;
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">>;
47
71
  }, "strip", z.ZodTypeAny, {
48
72
  projectName: string;
49
73
  detectedLanguage: SDKSupportedLanguageEnum;
50
74
  detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
51
75
  detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
52
- desiredPlatforms: ("android" | "windows" | "macos" | "ios")[];
76
+ devices: (["windows", string, string, string] | ["android", string, string, string] | ["ios", string, string, string] | ["macos", string, string, string])[];
53
77
  }, {
54
78
  projectName: string;
55
79
  detectedLanguage: SDKSupportedLanguageEnum;
56
80
  detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
57
81
  detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
58
- desiredPlatforms: ("android" | "windows" | "macos" | "ios")[];
82
+ devices?: (["windows", string, string, string] | ["android", string, string, string] | ["ios", string, string, string] | ["macos", string, string, string])[] | undefined;
59
83
  }>;
60
84
  export type SetUpPercyInput = z.infer<typeof SetUpPercySchema>;
61
85
  export type RunTestsOnBrowserStackInput = z.infer<typeof RunTestsOnBrowserStackSchema>;
@@ -1,6 +1,19 @@
1
1
  import { z } from "zod";
2
2
  import { PercyIntegrationTypeEnum } from "./types.js";
3
3
  import { SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, SDKSupportedLanguageEnum, } from "./types.js";
4
+ // Platform enums for better validation
5
+ export const PlatformEnum = {
6
+ WINDOWS: "windows",
7
+ MACOS: "macos",
8
+ ANDROID: "android",
9
+ IOS: "ios",
10
+ };
11
+ export const WindowsPlatformEnum = {
12
+ WINDOWS: "windows",
13
+ };
14
+ export const MacOSPlatformEnum = {
15
+ MACOS: "macos",
16
+ };
4
17
  export const SetUpPercyParamsShape = {
5
18
  projectName: z.string().describe("A unique name for your Percy project."),
6
19
  detectedLanguage: z.nativeEnum(SDKSupportedLanguageEnum),
@@ -11,7 +24,12 @@ export const SetUpPercyParamsShape = {
11
24
  .describe("Specify the Percy integration type: web (Percy Web) or automate (Percy Automate). If not provided, always prompt the user with: 'Please specify the Percy integration type.' Do not proceed without an explicit selection. Never use a default."),
12
25
  folderPaths: z
13
26
  .array(z.string())
27
+ .optional()
14
28
  .describe("An array of absolute folder paths containing UI test files. If not provided, analyze codebase for UI test folders by scanning for test patterns which contain UI test cases as per framework. Return empty array if none found."),
29
+ filePaths: z
30
+ .array(z.string())
31
+ .optional()
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."),
15
33
  };
16
34
  export const RunTestsOnBrowserStackParamsShape = {
17
35
  projectName: z
@@ -20,9 +38,50 @@ export const RunTestsOnBrowserStackParamsShape = {
20
38
  detectedLanguage: z.nativeEnum(SDKSupportedLanguageEnum),
21
39
  detectedBrowserAutomationFramework: z.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum),
22
40
  detectedTestingFramework: z.nativeEnum(SDKSupportedTestingFrameworkEnum),
23
- desiredPlatforms: z
24
- .array(z.enum(["windows", "macos", "android", "ios"]))
25
- .describe("An array of platforms to run tests on."),
41
+ 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
+ ]))
82
+ .max(3)
83
+ .default([])
84
+ .describe("Preferred tuples of target devices.Add device only when user asks explicitly for it. Defaults to [] . Example: [['windows', '11', 'chrome', 'latest']]"),
26
85
  };
27
86
  export const SetUpPercySchema = z.object(SetUpPercyParamsShape);
28
87
  export const RunTestsOnBrowserStackSchema = z.object(RunTestsOnBrowserStackParamsShape);
@@ -18,8 +18,8 @@ export declare function getPercyAutomateNotImplementedMessage(type: PercyAutomat
18
18
  export declare function getBootstrapFailedMessage(error: unknown, context: {
19
19
  config: unknown;
20
20
  percyMode?: string;
21
- sdkVersion?: string;
22
21
  }): string;
23
22
  export declare function percyUnsupportedResult(integrationType: PercyIntegrationTypeEnum, supportCheck?: {
24
23
  errorMessage?: string;
25
24
  }): CallToolResult;
25
+ export declare function validatePercyPathandFolders(input: any): void;
@@ -4,6 +4,7 @@ import { isPercyWebFrameworkSupported } from "../percy-web/frameworks.js";
4
4
  import { formatInstructionsWithNumbers, generateVerificationMessage, } from "./formatUtils.js";
5
5
  import { PercyAutomateNotImplementedType, } from "./types.js";
6
6
  import { IMPORTANT_SETUP_WARNING } from "./index.js";
7
+ import { PackageJsonVersion } from "../../../index.js";
7
8
  export function checkPercyIntegrationSupport(input) {
8
9
  if (input.integrationType === PercyIntegrationTypeEnum.AUTOMATE) {
9
10
  const isSupported = isPercyAutomateFrameworkSupported(input.detectedLanguage, input.detectedBrowserAutomationFramework || "", input.detectedTestingFramework || "");
@@ -69,10 +70,11 @@ export function getPercyAutomateNotImplementedMessage(type, input, supported) {
69
70
  }
70
71
  }
71
72
  export function getBootstrapFailedMessage(error, context) {
73
+ const error_message = error instanceof Error ? error.message : "unknown error";
72
74
  return `Failed to bootstrap project with BrowserStack SDK.
73
- Error: ${error}
75
+ Error: ${error_message}
74
76
  Percy Mode: ${context.percyMode ?? "automate"}
75
- SDK Version: ${context.sdkVersion ?? "N/A"}
77
+ MCP Version: ${PackageJsonVersion}
76
78
  Please open an issue on GitHub if the problem persists.`;
77
79
  }
78
80
  export function percyUnsupportedResult(integrationType, supportCheck) {
@@ -88,3 +90,13 @@ export function percyUnsupportedResult(integrationType, supportCheck) {
88
90
  shouldSkipFormatting: true,
89
91
  };
90
92
  }
93
+ export function validatePercyPathandFolders(input) {
94
+ const hasFolderPaths = input.folderPaths && input.folderPaths.length > 0;
95
+ const hasFilePaths = input.filePaths && input.filePaths.length > 0;
96
+ if (!hasFolderPaths && !hasFilePaths) {
97
+ throw new Error("Please provide either:\n" +
98
+ "• folderPaths: Array of directory paths containing test files\n" +
99
+ "• filePaths: Array of specific test file paths\n\n" +
100
+ "Example: { filePaths: ['/path/to/test.spec.js'] }");
101
+ }
102
+ }
@@ -2,3 +2,4 @@ import { BrowserStackConfig } from "../../lib/types.js";
2
2
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  export declare function runTestsOnBrowserStackHandler(rawInput: unknown, config: BrowserStackConfig): Promise<CallToolResult>;
4
4
  export declare function setUpPercyHandler(rawInput: unknown, config: BrowserStackConfig): Promise<CallToolResult>;
5
+ export declare function simulatePercyChangeHandler(rawInput: unknown, config: BrowserStackConfig): Promise<CallToolResult>;