@browserstack/mcp-server 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +96 -6
  2. package/dist/lib/apiClient.d.ts +7 -5
  3. package/dist/lib/apiClient.js +76 -15
  4. package/dist/lib/device-cache.d.ts +3 -1
  5. package/dist/lib/device-cache.js +24 -17
  6. package/dist/lib/inmemory-store.d.ts +1 -0
  7. package/dist/lib/inmemory-store.js +1 -0
  8. package/dist/lib/instrumentation.js +6 -3
  9. package/dist/lib/utils.d.ts +78 -0
  10. package/dist/lib/utils.js +47 -0
  11. package/dist/lib/version-resolver.js +26 -14
  12. package/dist/server-factory.js +6 -0
  13. package/dist/tools/add-percy-snapshots.d.ts +5 -0
  14. package/dist/tools/add-percy-snapshots.js +17 -0
  15. package/dist/tools/appautomate-utils/appium-sdk/config-generator.d.ts +7 -0
  16. package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +70 -0
  17. package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +23 -0
  18. package/dist/tools/appautomate-utils/appium-sdk/constants.js +64 -0
  19. package/dist/tools/appautomate-utils/appium-sdk/formatter.d.ts +8 -0
  20. package/dist/tools/appautomate-utils/appium-sdk/formatter.js +59 -0
  21. package/dist/tools/appautomate-utils/appium-sdk/handler.d.ts +3 -0
  22. package/dist/tools/appautomate-utils/appium-sdk/handler.js +66 -0
  23. package/dist/tools/appautomate-utils/appium-sdk/index.d.ts +7 -0
  24. package/dist/tools/appautomate-utils/appium-sdk/index.js +8 -0
  25. package/dist/tools/appautomate-utils/appium-sdk/instructions.d.ts +3 -0
  26. package/dist/tools/appautomate-utils/appium-sdk/instructions.js +47 -0
  27. package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.d.ts +2 -0
  28. package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.js +78 -0
  29. package/dist/tools/appautomate-utils/appium-sdk/languages/java.d.ts +10 -0
  30. package/dist/tools/appautomate-utils/appium-sdk/languages/java.js +121 -0
  31. package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.d.ts +3 -0
  32. package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.js +194 -0
  33. package/dist/tools/appautomate-utils/appium-sdk/languages/python.d.ts +3 -0
  34. package/dist/tools/appautomate-utils/appium-sdk/languages/python.js +76 -0
  35. package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.d.ts +2 -0
  36. package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.js +85 -0
  37. package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +58 -0
  38. package/dist/tools/appautomate-utils/appium-sdk/types.js +63 -0
  39. package/dist/tools/appautomate-utils/appium-sdk/utils.d.ts +17 -0
  40. package/dist/tools/appautomate-utils/appium-sdk/utils.js +64 -0
  41. package/dist/tools/appautomate-utils/{appautomate.d.ts → native-execution/appautomate.d.ts} +1 -1
  42. package/dist/tools/appautomate-utils/{appautomate.js → native-execution/appautomate.js} +2 -2
  43. package/dist/tools/appautomate-utils/native-execution/constants.d.ts +11 -0
  44. package/dist/tools/appautomate-utils/native-execution/constants.js +58 -0
  45. package/dist/tools/appautomate-utils/native-execution/types.d.ts +19 -0
  46. package/dist/tools/appautomate-utils/{types.js → native-execution/types.js} +5 -1
  47. package/dist/tools/appautomate.js +40 -42
  48. package/dist/tools/bstack-sdk.d.ts +2 -15
  49. package/dist/tools/bstack-sdk.js +10 -119
  50. package/dist/tools/build-insights.d.ts +7 -0
  51. package/dist/tools/build-insights.js +67 -0
  52. package/dist/tools/list-test-files.d.ts +2 -0
  53. package/dist/tools/list-test-files.js +36 -0
  54. package/dist/tools/percy-sdk.d.ts +4 -0
  55. package/dist/tools/percy-sdk.js +98 -0
  56. package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
  57. package/dist/tools/percy-snapshot-utils/constants.js +500 -0
  58. package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
  59. package/dist/tools/percy-snapshot-utils/detect-test-files.js +175 -0
  60. package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
  61. package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
  62. package/dist/tools/percy-snapshot-utils/utils.js +30 -0
  63. package/dist/tools/rca-agent-utils/constants.d.ts +13 -0
  64. package/dist/tools/rca-agent-utils/constants.js +24 -0
  65. package/dist/tools/rca-agent-utils/format-rca.d.ts +1 -0
  66. package/dist/tools/rca-agent-utils/format-rca.js +37 -0
  67. package/dist/tools/rca-agent-utils/get-build-id.d.ts +1 -0
  68. package/dist/tools/rca-agent-utils/get-build-id.js +18 -0
  69. package/dist/tools/rca-agent-utils/get-failed-test-id.d.ts +2 -0
  70. package/dist/tools/rca-agent-utils/get-failed-test-id.js +69 -0
  71. package/dist/tools/rca-agent-utils/rca-data.d.ts +9 -0
  72. package/dist/tools/rca-agent-utils/rca-data.js +196 -0
  73. package/dist/tools/rca-agent-utils/types.d.ts +48 -0
  74. package/dist/tools/rca-agent-utils/types.js +20 -0
  75. package/dist/tools/rca-agent.d.ts +14 -0
  76. package/dist/tools/rca-agent.js +119 -0
  77. package/dist/tools/review-agent-utils/build-counts.d.ts +7 -0
  78. package/dist/tools/review-agent-utils/build-counts.js +44 -0
  79. package/dist/tools/review-agent-utils/percy-approve-reject.d.ts +6 -0
  80. package/dist/tools/review-agent-utils/percy-approve-reject.js +39 -0
  81. package/dist/tools/review-agent-utils/percy-diffs.d.ts +9 -0
  82. package/dist/tools/review-agent-utils/percy-diffs.js +35 -0
  83. package/dist/tools/review-agent-utils/percy-snapshots.d.ts +11 -0
  84. package/dist/tools/review-agent-utils/percy-snapshots.js +58 -0
  85. package/dist/tools/review-agent.d.ts +5 -0
  86. package/dist/tools/review-agent.js +56 -0
  87. package/dist/tools/run-percy-scan.d.ts +8 -0
  88. package/dist/tools/run-percy-scan.js +37 -0
  89. package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
  90. package/dist/tools/sdk-utils/bstack/commands.js +88 -0
  91. package/dist/tools/sdk-utils/bstack/configUtils.d.ts +7 -0
  92. package/dist/tools/sdk-utils/bstack/configUtils.js +113 -0
  93. package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
  94. package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +117 -78
  95. package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
  96. package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
  97. package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
  98. package/dist/tools/sdk-utils/bstack/index.js +5 -0
  99. package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
  100. package/dist/tools/sdk-utils/bstack/sdkHandler.js +82 -0
  101. package/dist/tools/sdk-utils/common/constants.d.ts +11 -0
  102. package/dist/tools/sdk-utils/common/constants.js +87 -0
  103. package/dist/tools/sdk-utils/common/device-validator.d.ts +25 -0
  104. package/dist/tools/sdk-utils/common/device-validator.js +368 -0
  105. package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
  106. package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
  107. package/dist/tools/sdk-utils/common/index.d.ts +3 -0
  108. package/dist/tools/sdk-utils/common/index.js +4 -0
  109. package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
  110. package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
  111. package/dist/tools/sdk-utils/common/schema.d.ts +93 -0
  112. package/dist/tools/sdk-utils/common/schema.js +105 -0
  113. package/dist/tools/sdk-utils/common/types.d.ts +66 -0
  114. package/dist/tools/sdk-utils/{types.js → common/types.js} +15 -2
  115. package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
  116. package/dist/tools/sdk-utils/common/utils.js +91 -0
  117. package/dist/tools/sdk-utils/handler.d.ts +5 -0
  118. package/dist/tools/sdk-utils/handler.js +147 -0
  119. package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
  120. package/dist/tools/sdk-utils/percy-automate/constants.js +338 -0
  121. package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
  122. package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
  123. package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
  124. package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
  125. package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
  126. package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
  127. package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
  128. package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
  129. package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
  130. package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
  131. package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
  132. package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
  133. package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
  134. package/dist/tools/sdk-utils/percy-bstack/handler.js +103 -0
  135. package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
  136. package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
  137. package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
  138. package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
  139. package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
  140. package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
  141. package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
  142. package/dist/tools/sdk-utils/percy-web/constants.js +883 -0
  143. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
  144. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +32 -0
  145. package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
  146. package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
  147. package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
  148. package/dist/tools/sdk-utils/percy-web/handler.js +29 -0
  149. package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
  150. package/dist/tools/sdk-utils/percy-web/index.js +4 -0
  151. package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
  152. package/dist/tools/sdk-utils/percy-web/types.js +1 -0
  153. package/dist/tools/testmanagement-utils/create-testrun.d.ts +4 -4
  154. package/dist/tools/testmanagement-utils/update-testrun.d.ts +4 -4
  155. package/package.json +3 -2
  156. package/dist/tools/appautomate-utils/types.d.ts +0 -5
  157. package/dist/tools/sdk-utils/commands.js +0 -65
  158. package/dist/tools/sdk-utils/instructions.d.ts +0 -6
  159. package/dist/tools/sdk-utils/instructions.js +0 -99
  160. package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
  161. package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
  162. package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
  163. package/dist/tools/sdk-utils/types.d.ts +0 -40
  164. /package/dist/tools/{sdk-utils/percy → percy-snapshot-utils}/types.js +0 -0
@@ -0,0 +1,368 @@
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
+ return validatedEnvironments;
171
+ }
172
+ // Optimized desktop validation using nested indexed maps for O(1) lookups
173
+ function validateDesktopEnvironment(env, index, platform, defaultBrowser) {
174
+ const [, osVersion, browser, browserVersion] = env;
175
+ const osKey = platform === "windows" ? "Windows" : "OS X";
176
+ // Use nested index for O(1) lookup instead of filtering
177
+ const osMap = index.nested.get(osKey);
178
+ if (!osMap) {
179
+ throw new Error(`No ${platform} devices available`);
180
+ }
181
+ // Get available OS versions for this platform
182
+ const availableOSVersions = Array.from(osMap.keys());
183
+ const validatedOSVersion = resolveVersion(osVersion || "latest", availableOSVersions);
184
+ // Use nested index for O(1) lookup
185
+ const osVersionMap = osMap.get(validatedOSVersion);
186
+ if (!osVersionMap) {
187
+ throw new Error(`OS version "${validatedOSVersion}" not available for ${platform}`);
188
+ }
189
+ // Get available browsers for this OS version
190
+ const availableBrowsers = Array.from(osVersionMap.keys());
191
+ const validatedBrowser = validateBrowserExact(browser || defaultBrowser, availableBrowsers);
192
+ // Use nested index for O(1) lookup
193
+ const browserEntries = osVersionMap.get(validatedBrowser);
194
+ if (!browserEntries || browserEntries.length === 0) {
195
+ throw new Error(`Browser "${validatedBrowser}" not available for ${platform} ${validatedOSVersion}`);
196
+ }
197
+ const availableBrowserVersions = [
198
+ ...new Set(browserEntries.map((e) => e.browser_version)),
199
+ ];
200
+ const validatedBrowserVersion = resolveVersion(browserVersion || "latest", availableBrowserVersions);
201
+ return {
202
+ platform,
203
+ osVersion: validatedOSVersion,
204
+ browser: validatedBrowser,
205
+ browserVersion: validatedBrowserVersion,
206
+ };
207
+ }
208
+ // Optimized mobile validation using indexed maps
209
+ function validateMobileEnvironment(env, index, platform, defaultDevice, defaultBrowser) {
210
+ const [, deviceName, osVersion, browser] = env;
211
+ const platformEntries = index.byPlatform.get(platform) || [];
212
+ if (platformEntries.length === 0) {
213
+ throw new Error(`No ${platform} devices available`);
214
+ }
215
+ // Use fuzzy search only for device names (as suggested in feedback)
216
+ const deviceMatches = customFuzzySearch(platformEntries, ["display_name"], deviceName || defaultDevice, 5);
217
+ if (deviceMatches.length === 0) {
218
+ throw new Error(`No ${platform} devices matching "${deviceName}". Available devices: ${platformEntries
219
+ .map((d) => d.display_name || "unknown")
220
+ .slice(0, 5)
221
+ .join(", ")}`);
222
+ }
223
+ // Try to find exact match first
224
+ const exactMatch = deviceMatches.find((m) => m.display_name.toLowerCase() === (deviceName || "").toLowerCase());
225
+ // If no exact match, throw error instead of using fuzzy match
226
+ if (!exactMatch) {
227
+ const suggestions = deviceMatches.map((m) => m.display_name).join(", ");
228
+ throw new Error(`Device "${deviceName}" not found exactly for ${platform}. Available similar devices: ${suggestions}. Please use the exact device name.`);
229
+ }
230
+ // Use index for faster filtering
231
+ const deviceKey = exactMatch.display_name.toLowerCase();
232
+ const deviceFiltered = index.byDeviceName.get(deviceKey) || [];
233
+ const availableOSVersions = [
234
+ ...new Set(deviceFiltered.map((d) => d.os_version)),
235
+ ];
236
+ const validatedOSVersion = resolveVersion(osVersion || "latest", availableOSVersions);
237
+ // Use index for faster filtering
238
+ const osVersionEntries = index.byOSVersion.get(validatedOSVersion) || [];
239
+ const osFiltered = osVersionEntries.filter((d) => d.display_name.toLowerCase() === deviceKey);
240
+ // Validate browser if provided - use exact matching for browsers
241
+ let validatedBrowser = browser || defaultBrowser;
242
+ if (browser && osFiltered.length > 0) {
243
+ // Extract browsers more carefully - handle different possible structures
244
+ const availableBrowsers = [
245
+ ...new Set(osFiltered.flatMap((d) => {
246
+ if (d.browsers && Array.isArray(d.browsers)) {
247
+ // If browsers is an array of objects with browser property
248
+ return d.browsers
249
+ .map((b) => {
250
+ // Use display_name for user-friendly browser names, fallback to browser field
251
+ return b.display_name || b.browser;
252
+ })
253
+ .filter(Boolean);
254
+ }
255
+ // For mobile devices, provide default browsers if none found
256
+ return platform === "android" ? ["chrome"] : ["safari"];
257
+ })),
258
+ ].filter(Boolean);
259
+ if (availableBrowsers.length > 0) {
260
+ try {
261
+ validatedBrowser = validateBrowserExact(browser, availableBrowsers);
262
+ }
263
+ catch (error) {
264
+ // Add more context to browser validation errors
265
+ 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)}`);
266
+ }
267
+ }
268
+ else {
269
+ // For mobile, if no specific browsers found, just use the requested browser
270
+ // as most mobile devices support standard browsers
271
+ validatedBrowser = browser || defaultBrowser;
272
+ }
273
+ }
274
+ return {
275
+ platform,
276
+ osVersion: validatedOSVersion,
277
+ deviceName: exactMatch.display_name,
278
+ browser: validatedBrowser,
279
+ };
280
+ }
281
+ // ============================================================================
282
+ // APP AUTOMATE SECTION (Mobile devices for App Automate)
283
+ // ============================================================================
284
+ export async function validateAppAutomateDevices(devices) {
285
+ const validatedDevices = [];
286
+ if (!devices || devices.length === 0) {
287
+ // Use centralized default fallback
288
+ return [
289
+ {
290
+ platform: "android",
291
+ osVersion: "latest",
292
+ deviceName: DEFAULTS.android.device,
293
+ },
294
+ ];
295
+ }
296
+ let appAutomateData;
297
+ try {
298
+ // Fetch app automate device data
299
+ appAutomateData = (await getDevicesAndBrowsers(BrowserStackProducts.APP_AUTOMATE));
300
+ }
301
+ catch (error) {
302
+ // Only wrap fetch-related errors
303
+ throw new Error(`Failed to fetch device data: ${error instanceof Error ? error.message : String(error)}`);
304
+ }
305
+ for (const device of devices) {
306
+ // Parse device array in format ["android", "Device Name", "OS Version"]
307
+ const [platform, deviceName, osVersion] = device;
308
+ // Find matching device in the data
309
+ let validatedDevice = null;
310
+ if (!appAutomateData.mobile) {
311
+ throw new Error("No mobile device data available");
312
+ }
313
+ // Filter by platform first
314
+ const platformGroup = appAutomateData.mobile.find((group) => group.os === platform.toLowerCase());
315
+ if (!platformGroup) {
316
+ throw new Error(`Platform "${platform}" not supported for App Automate`);
317
+ }
318
+ const platformDevices = platformGroup.devices;
319
+ // Find exact device name match (case-insensitive)
320
+ const exactMatch = platformDevices.find((d) => d.display_name.toLowerCase() === deviceName.toLowerCase());
321
+ if (exactMatch) {
322
+ // Check if the OS version is available for this device
323
+ const deviceVersions = platformDevices
324
+ .filter((d) => d.display_name === exactMatch.display_name)
325
+ .map((d) => d.os_version);
326
+ const validatedOSVersion = resolveVersion(osVersion || "latest", deviceVersions);
327
+ validatedDevice = {
328
+ platform: platformGroup.os,
329
+ osVersion: validatedOSVersion,
330
+ deviceName: exactMatch.display_name,
331
+ };
332
+ }
333
+ if (!validatedDevice) {
334
+ // If no exact match found, suggest similar devices from the SAME platform only
335
+ const platformDevicesForSearch = platformDevices.map((d) => ({
336
+ ...d,
337
+ platform: platformGroup.os,
338
+ }));
339
+ // Try fuzzy search with a more lenient threshold
340
+ const deviceMatches = customFuzzySearch(platformDevicesForSearch, ["display_name"], deviceName, 5, 0.8);
341
+ const suggestions = deviceMatches
342
+ .map((m) => `${m.display_name}`)
343
+ .join(", ");
344
+ // If no fuzzy matches, show some available devices as fallback
345
+ const fallbackDevices = platformDevicesForSearch
346
+ .slice(0, 5)
347
+ .map((d) => d.display_name)
348
+ .join(", ");
349
+ const errorMessage = suggestions
350
+ ? `Device "${deviceName}" not found for platform "${platform}".\nAvailable similar devices: ${suggestions}`
351
+ : `Device "${deviceName}" not found for platform "${platform}".\nAvailable devices: ${fallbackDevices}`;
352
+ throw new Error(errorMessage);
353
+ }
354
+ validatedDevices.push(validatedDevice);
355
+ }
356
+ return validatedDevices;
357
+ }
358
+ // ============================================================================
359
+ // SHARED UTILITY FUNCTIONS
360
+ // ============================================================================
361
+ // Exact browser validation (preferred for structured fields)
362
+ function validateBrowserExact(requestedBrowser, availableBrowsers) {
363
+ const exactMatch = availableBrowsers.find((b) => b.toLowerCase() === requestedBrowser.toLowerCase());
364
+ if (exactMatch) {
365
+ return exactMatch;
366
+ }
367
+ throw new Error(`Browser "${requestedBrowser}" not found. Available options: ${availableBrowsers.join(", ")}`);
368
+ }
@@ -0,0 +1,5 @@
1
+ export declare function formatInstructionsWithNumbers(instructionText: string, separator?: string): {
2
+ formattedSteps: string;
3
+ stepCount: number;
4
+ };
5
+ export declare function generateVerificationMessage(stepCount: number): string;
@@ -0,0 +1,27 @@
1
+ export function formatInstructionsWithNumbers(instructionText, separator = "---STEP---") {
2
+ // Split the instructions by the separator
3
+ const steps = instructionText
4
+ .split(separator)
5
+ .map((step) => step.trim())
6
+ .filter((step) => step.length > 0);
7
+ // If no separators found, treat the entire text as one step
8
+ if (steps.length === 1 && !instructionText.includes(separator)) {
9
+ return {
10
+ formattedSteps: `**Step 1:**\n${instructionText.trim()}`,
11
+ stepCount: 1,
12
+ };
13
+ }
14
+ // Format each step with numbering
15
+ const formattedSteps = steps
16
+ .map((step, index) => {
17
+ return `**Step ${index + 1}:**\n${step.trim()}`;
18
+ })
19
+ .join("\n\n");
20
+ return {
21
+ formattedSteps,
22
+ stepCount: steps.length,
23
+ };
24
+ }
25
+ export function generateVerificationMessage(stepCount) {
26
+ return `**✅ Verification:**\nPlease verify that you have completed all ${stepCount} steps above to ensure proper setup. If you encounter any issues, double-check each step and ensure all commands executed successfully.`;
27
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./constants.js";
3
+ export * from "./formatUtils.js";
@@ -0,0 +1,4 @@
1
+ // Common utilities and types for SDK tools
2
+ export * from "./types.js";
3
+ export * from "./constants.js";
4
+ export * from "./formatUtils.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Core instruction configuration utilities for runTestsOnBrowserStack tool.
3
+ */
4
+ import { SDKSupportedLanguage, SDKSupportedBrowserAutomationFramework, SDKSupportedTestingFramework } from "./types.js";
5
+ export declare const getInstructionsForProjectConfiguration: (detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFramework, detectedTestingFramework: SDKSupportedTestingFramework, detectedLanguage: SDKSupportedLanguage, username: string, accessKey: string) => {
6
+ setup: string;
7
+ run: string;
8
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Core instruction configuration utilities for runTestsOnBrowserStack tool.
3
+ */
4
+ import { SUPPORTED_CONFIGURATIONS } from "../bstack/frameworks.js";
5
+ const errorMessageSuffix = "Please open an issue at our Github repo: https://github.com/browserstack/browserstack-mcp-server/issues to request support for your project configuration";
6
+ export const getInstructionsForProjectConfiguration = (detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, username, accessKey) => {
7
+ const configuration = SUPPORTED_CONFIGURATIONS[detectedLanguage];
8
+ if (!configuration) {
9
+ throw new Error(`BrowserStack MCP Server currently does not support ${detectedLanguage}, ${errorMessageSuffix}`);
10
+ }
11
+ if (!configuration[detectedBrowserAutomationFramework]) {
12
+ throw new Error(`BrowserStack MCP Server currently does not support ${detectedBrowserAutomationFramework} for ${detectedLanguage}, ${errorMessageSuffix}`);
13
+ }
14
+ if (!configuration[detectedBrowserAutomationFramework][detectedTestingFramework]) {
15
+ throw new Error(`BrowserStack MCP Server currently does not support ${detectedTestingFramework} for ${detectedBrowserAutomationFramework} on ${detectedLanguage}, ${errorMessageSuffix}`);
16
+ }
17
+ const instructionFunction = configuration[detectedBrowserAutomationFramework][detectedTestingFramework]
18
+ .instructions;
19
+ return instructionFunction(username, accessKey);
20
+ };
@@ -0,0 +1,93 @@
1
+ import { z } from "zod";
2
+ import { PercyIntegrationTypeEnum } from "./types.js";
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
+ };
16
+ export declare const SetUpPercyParamsShape: {
17
+ projectName: z.ZodString;
18
+ detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
19
+ detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
20
+ detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
21
+ integrationType: z.ZodNativeEnum<typeof PercyIntegrationTypeEnum>;
22
+ folderPaths: z.ZodArray<z.ZodString, "many">;
23
+ };
24
+ export declare const RunTestsOnBrowserStackParamsShape: {
25
+ projectName: z.ZodString;
26
+ detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
27
+ detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
28
+ detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
29
+ devices: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodTuple<[z.ZodNativeEnum<{
30
+ readonly WINDOWS: "windows";
31
+ }>, 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<{
32
+ readonly MACOS: "macos";
33
+ }>, z.ZodString, z.ZodString, z.ZodString], null>]>, "many">>;
34
+ };
35
+ export declare const SetUpPercySchema: z.ZodObject<{
36
+ projectName: z.ZodString;
37
+ detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
38
+ detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
39
+ detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
40
+ integrationType: z.ZodNativeEnum<typeof PercyIntegrationTypeEnum>;
41
+ folderPaths: z.ZodArray<z.ZodString, "many">;
42
+ }, "strip", z.ZodTypeAny, {
43
+ projectName: string;
44
+ detectedLanguage: SDKSupportedLanguageEnum;
45
+ detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
46
+ detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
47
+ integrationType: PercyIntegrationTypeEnum;
48
+ folderPaths: string[];
49
+ }, {
50
+ projectName: string;
51
+ detectedLanguage: SDKSupportedLanguageEnum;
52
+ detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
53
+ detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
54
+ integrationType: PercyIntegrationTypeEnum;
55
+ folderPaths: string[];
56
+ }>;
57
+ export declare const RunTestsOnBrowserStackSchema: z.ZodObject<{
58
+ projectName: z.ZodString;
59
+ detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
60
+ detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
61
+ detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
62
+ devices: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodTuple<[z.ZodNativeEnum<{
63
+ readonly WINDOWS: "windows";
64
+ }>, 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<{
65
+ readonly MACOS: "macos";
66
+ }>, z.ZodString, z.ZodString, z.ZodString], null>]>, "many">>;
67
+ }, "strip", z.ZodTypeAny, {
68
+ projectName: string;
69
+ detectedLanguage: SDKSupportedLanguageEnum;
70
+ detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
71
+ detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
72
+ devices: (["windows", string, string, string] | ["android", string, string, string] | ["ios", string, string, string] | ["macos", string, string, string])[];
73
+ }, {
74
+ projectName: string;
75
+ detectedLanguage: SDKSupportedLanguageEnum;
76
+ detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
77
+ detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
78
+ devices?: (["windows", string, string, string] | ["android", string, string, string] | ["ios", string, string, string] | ["macos", string, string, string])[] | undefined;
79
+ }>;
80
+ export type SetUpPercyInput = z.infer<typeof SetUpPercySchema>;
81
+ export type RunTestsOnBrowserStackInput = z.infer<typeof RunTestsOnBrowserStackSchema>;
82
+ export declare const RunPercyScanParamsShape: {
83
+ projectName: z.ZodString;
84
+ percyRunCommand: z.ZodOptional<z.ZodString>;
85
+ integrationType: z.ZodNativeEnum<typeof PercyIntegrationTypeEnum>;
86
+ };
87
+ export declare const FetchPercyChangesParamsShape: {
88
+ project_name: z.ZodString;
89
+ };
90
+ export declare const ManagePercyBuildApprovalParamsShape: {
91
+ buildId: z.ZodString;
92
+ action: z.ZodEnum<["approve", "unapprove", "reject"]>;
93
+ };
@@ -0,0 +1,105 @@
1
+ import { z } from "zod";
2
+ import { PercyIntegrationTypeEnum } from "./types.js";
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
+ };
17
+ export const SetUpPercyParamsShape = {
18
+ projectName: z.string().describe("A unique name for your Percy project."),
19
+ detectedLanguage: z.nativeEnum(SDKSupportedLanguageEnum),
20
+ detectedBrowserAutomationFramework: z.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum),
21
+ detectedTestingFramework: z.nativeEnum(SDKSupportedTestingFrameworkEnum),
22
+ integrationType: z
23
+ .nativeEnum(PercyIntegrationTypeEnum)
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."),
25
+ folderPaths: z
26
+ .array(z.string())
27
+ .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."),
28
+ };
29
+ export const RunTestsOnBrowserStackParamsShape = {
30
+ projectName: z
31
+ .string()
32
+ .describe("A single name for your project to organize all your tests."),
33
+ detectedLanguage: z.nativeEnum(SDKSupportedLanguageEnum),
34
+ detectedBrowserAutomationFramework: z.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum),
35
+ detectedTestingFramework: z.nativeEnum(SDKSupportedTestingFrameworkEnum),
36
+ devices: z
37
+ .array(z.union([
38
+ // Windows: [windows, osVersion, browser, browserVersion]
39
+ z.tuple([
40
+ z
41
+ .nativeEnum(WindowsPlatformEnum)
42
+ .describe("Platform identifier: 'windows'"),
43
+ z.string().describe("Windows version, e.g. '10', '11'"),
44
+ z.string().describe("Browser name, e.g. 'chrome', 'firefox', 'edge'"),
45
+ z
46
+ .string()
47
+ .describe("Browser version, e.g. '132', 'latest', 'oldest'"),
48
+ ]),
49
+ // Android: [android, name, model, osVersion, browser]
50
+ z.tuple([
51
+ z
52
+ .literal(PlatformEnum.ANDROID)
53
+ .describe("Platform identifier: 'android'"),
54
+ z
55
+ .string()
56
+ .describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'"),
57
+ z.string().describe("Android version, e.g. '14', '16', 'latest'"),
58
+ z.string().describe("Browser name, e.g. 'chrome', 'samsung browser'"),
59
+ ]),
60
+ // iOS: [ios, name, model, osVersion, browser]
61
+ z.tuple([
62
+ z.literal(PlatformEnum.IOS).describe("Platform identifier: 'ios'"),
63
+ z.string().describe("Device name, e.g. 'iPhone 12 Pro'"),
64
+ z.string().describe("iOS version, e.g. '17', 'latest'"),
65
+ z.string().describe("Browser name, typically 'safari'"),
66
+ ]),
67
+ // macOS: [mac|macos, name, model, browser, browserVersion]
68
+ z.tuple([
69
+ z
70
+ .nativeEnum(MacOSPlatformEnum)
71
+ .describe("Platform identifier: 'mac' or 'macos'"),
72
+ z.string().describe("macOS version name, e.g. 'Sequoia', 'Ventura'"),
73
+ z.string().describe("Browser name, e.g. 'safari', 'chrome'"),
74
+ z.string().describe("Browser version, e.g. 'latest'"),
75
+ ]),
76
+ ]))
77
+ .max(3)
78
+ .default([])
79
+ .describe("Preferred tuples of target devices.Add device only when user asks explicitly for it. Defaults to [] . Example: [['windows', '11', 'chrome', 'latest']]"),
80
+ };
81
+ export const SetUpPercySchema = z.object(SetUpPercyParamsShape);
82
+ export const RunTestsOnBrowserStackSchema = z.object(RunTestsOnBrowserStackParamsShape);
83
+ export const RunPercyScanParamsShape = {
84
+ projectName: z.string().describe("The name of the project to run Percy on."),
85
+ percyRunCommand: z
86
+ .string()
87
+ .optional()
88
+ .describe("The test command to run with Percy. Optional — the LLM should try to infer it first from project context."),
89
+ integrationType: z
90
+ .nativeEnum(PercyIntegrationTypeEnum)
91
+ .describe("Specifies whether to integrate with Percy Web or Percy Automate. If not explicitly provided, prompt the user to select the desired integration type."),
92
+ };
93
+ export const FetchPercyChangesParamsShape = {
94
+ project_name: z
95
+ .string()
96
+ .describe("The name of the BrowserStack project. If not found, ask user directly."),
97
+ };
98
+ export const ManagePercyBuildApprovalParamsShape = {
99
+ buildId: z
100
+ .string()
101
+ .describe("The ID of the Percy build to approve or reject."),
102
+ action: z
103
+ .enum(["approve", "unapprove", "reject"])
104
+ .describe("The action to perform on the Percy build."),
105
+ };