@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.
- package/README.md +96 -6
- package/dist/lib/apiClient.d.ts +7 -5
- package/dist/lib/apiClient.js +76 -15
- package/dist/lib/device-cache.d.ts +3 -1
- package/dist/lib/device-cache.js +24 -17
- package/dist/lib/inmemory-store.d.ts +1 -0
- package/dist/lib/inmemory-store.js +1 -0
- package/dist/lib/instrumentation.js +6 -3
- package/dist/lib/utils.d.ts +78 -0
- package/dist/lib/utils.js +47 -0
- package/dist/lib/version-resolver.js +26 -14
- package/dist/server-factory.js +6 -0
- package/dist/tools/add-percy-snapshots.d.ts +5 -0
- package/dist/tools/add-percy-snapshots.js +17 -0
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.d.ts +7 -0
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +70 -0
- package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +23 -0
- package/dist/tools/appautomate-utils/appium-sdk/constants.js +64 -0
- package/dist/tools/appautomate-utils/appium-sdk/formatter.d.ts +8 -0
- package/dist/tools/appautomate-utils/appium-sdk/formatter.js +59 -0
- package/dist/tools/appautomate-utils/appium-sdk/handler.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/handler.js +66 -0
- package/dist/tools/appautomate-utils/appium-sdk/index.d.ts +7 -0
- package/dist/tools/appautomate-utils/appium-sdk/index.js +8 -0
- package/dist/tools/appautomate-utils/appium-sdk/instructions.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/instructions.js +47 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.d.ts +2 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.js +78 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/java.d.ts +10 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/java.js +121 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.js +194 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/python.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/python.js +76 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.d.ts +2 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.js +85 -0
- package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +58 -0
- package/dist/tools/appautomate-utils/appium-sdk/types.js +63 -0
- package/dist/tools/appautomate-utils/appium-sdk/utils.d.ts +17 -0
- package/dist/tools/appautomate-utils/appium-sdk/utils.js +64 -0
- package/dist/tools/appautomate-utils/{appautomate.d.ts → native-execution/appautomate.d.ts} +1 -1
- package/dist/tools/appautomate-utils/{appautomate.js → native-execution/appautomate.js} +2 -2
- package/dist/tools/appautomate-utils/native-execution/constants.d.ts +11 -0
- package/dist/tools/appautomate-utils/native-execution/constants.js +58 -0
- package/dist/tools/appautomate-utils/native-execution/types.d.ts +19 -0
- package/dist/tools/appautomate-utils/{types.js → native-execution/types.js} +5 -1
- package/dist/tools/appautomate.js +40 -42
- package/dist/tools/bstack-sdk.d.ts +2 -15
- package/dist/tools/bstack-sdk.js +10 -119
- package/dist/tools/build-insights.d.ts +7 -0
- package/dist/tools/build-insights.js +67 -0
- package/dist/tools/list-test-files.d.ts +2 -0
- package/dist/tools/list-test-files.js +36 -0
- package/dist/tools/percy-sdk.d.ts +4 -0
- package/dist/tools/percy-sdk.js +98 -0
- package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
- package/dist/tools/percy-snapshot-utils/constants.js +500 -0
- package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
- package/dist/tools/percy-snapshot-utils/detect-test-files.js +175 -0
- package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
- package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
- package/dist/tools/percy-snapshot-utils/utils.js +30 -0
- package/dist/tools/rca-agent-utils/constants.d.ts +13 -0
- package/dist/tools/rca-agent-utils/constants.js +24 -0
- package/dist/tools/rca-agent-utils/format-rca.d.ts +1 -0
- package/dist/tools/rca-agent-utils/format-rca.js +37 -0
- package/dist/tools/rca-agent-utils/get-build-id.d.ts +1 -0
- package/dist/tools/rca-agent-utils/get-build-id.js +18 -0
- package/dist/tools/rca-agent-utils/get-failed-test-id.d.ts +2 -0
- package/dist/tools/rca-agent-utils/get-failed-test-id.js +69 -0
- package/dist/tools/rca-agent-utils/rca-data.d.ts +9 -0
- package/dist/tools/rca-agent-utils/rca-data.js +196 -0
- package/dist/tools/rca-agent-utils/types.d.ts +48 -0
- package/dist/tools/rca-agent-utils/types.js +20 -0
- package/dist/tools/rca-agent.d.ts +14 -0
- package/dist/tools/rca-agent.js +119 -0
- package/dist/tools/review-agent-utils/build-counts.d.ts +7 -0
- package/dist/tools/review-agent-utils/build-counts.js +44 -0
- package/dist/tools/review-agent-utils/percy-approve-reject.d.ts +6 -0
- package/dist/tools/review-agent-utils/percy-approve-reject.js +39 -0
- package/dist/tools/review-agent-utils/percy-diffs.d.ts +9 -0
- package/dist/tools/review-agent-utils/percy-diffs.js +35 -0
- package/dist/tools/review-agent-utils/percy-snapshots.d.ts +11 -0
- package/dist/tools/review-agent-utils/percy-snapshots.js +58 -0
- package/dist/tools/review-agent.d.ts +5 -0
- package/dist/tools/review-agent.js +56 -0
- package/dist/tools/run-percy-scan.d.ts +8 -0
- package/dist/tools/run-percy-scan.js +37 -0
- package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
- package/dist/tools/sdk-utils/bstack/commands.js +88 -0
- package/dist/tools/sdk-utils/bstack/configUtils.d.ts +7 -0
- package/dist/tools/sdk-utils/bstack/configUtils.js +113 -0
- package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
- package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +117 -78
- package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
- package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
- package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/index.js +5 -0
- package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/sdkHandler.js +82 -0
- package/dist/tools/sdk-utils/common/constants.d.ts +11 -0
- package/dist/tools/sdk-utils/common/constants.js +87 -0
- package/dist/tools/sdk-utils/common/device-validator.d.ts +25 -0
- package/dist/tools/sdk-utils/common/device-validator.js +368 -0
- package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
- package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
- package/dist/tools/sdk-utils/common/index.d.ts +3 -0
- package/dist/tools/sdk-utils/common/index.js +4 -0
- package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
- package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
- package/dist/tools/sdk-utils/common/schema.d.ts +93 -0
- package/dist/tools/sdk-utils/common/schema.js +105 -0
- package/dist/tools/sdk-utils/common/types.d.ts +66 -0
- package/dist/tools/sdk-utils/{types.js → common/types.js} +15 -2
- package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
- package/dist/tools/sdk-utils/common/utils.js +91 -0
- package/dist/tools/sdk-utils/handler.d.ts +5 -0
- package/dist/tools/sdk-utils/handler.js +147 -0
- package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
- package/dist/tools/sdk-utils/percy-automate/constants.js +338 -0
- package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
- package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
- package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
- package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
- package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
- package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
- package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
- package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
- package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
- package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
- package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
- package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
- package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-bstack/handler.js +103 -0
- package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
- package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
- package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
- package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
- package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
- package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
- package/dist/tools/sdk-utils/percy-web/constants.js +883 -0
- package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +32 -0
- package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
- package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
- package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/handler.js +29 -0
- package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/index.js +4 -0
- package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
- package/dist/tools/sdk-utils/percy-web/types.js +1 -0
- package/dist/tools/testmanagement-utils/create-testrun.d.ts +4 -4
- package/dist/tools/testmanagement-utils/update-testrun.d.ts +4 -4
- package/package.json +3 -2
- package/dist/tools/appautomate-utils/types.d.ts +0 -5
- package/dist/tools/sdk-utils/commands.js +0 -65
- package/dist/tools/sdk-utils/instructions.d.ts +0 -6
- package/dist/tools/sdk-utils/instructions.js +0 -99
- package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
- package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
- package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
- package/dist/tools/sdk-utils/types.d.ts +0 -40
- /package/dist/tools/{sdk-utils/percy → percy-snapshot-utils}/types.js +0 -0
package/README.md
CHANGED
|
@@ -35,10 +35,21 @@ Manage, execute, debug tests, and even fix code using plain English prompts.
|
|
|
35
35
|
#### Reduced context switching:
|
|
36
36
|
Stay in flow—keep all project context in one place and trigger actions directly from your IDE or LLM.
|
|
37
37
|
|
|
38
|
-
## ⚡️ One Click MCP Setup
|
|
38
|
+
## ⚡️ One Click MCP Setup
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Click on the buttons below to install MCP in your respective IDE:
|
|
41
41
|
|
|
42
|
+
<a href="http://mcp.browserstack.com/one-click-setup?client=vscode"><img src="assets/one-click-vs-code.png" alt="Install in VS Code" width="160" height="80"></a> <a href="http://mcp.browserstack.com/one-click-setup?client=cursor"><img src="assets/one-click-cursor.png" alt="Install in Cursor" width="150" height="70"></a>
|
|
43
|
+
|
|
44
|
+
#### Note : Ensure you are using Node version >= `18.0`
|
|
45
|
+
- Check your node version using `node --version`. Recommended version: `v22.15.0` (LTS)
|
|
46
|
+
- To Upgrade Node :
|
|
47
|
+
- 1. On macOS `(Homebrew) - brew update && brew upgrade node or if using (nvm) - nvm install 22.15.0 && nvm use 22.15.0 && nvm alias default 22.15.0`
|
|
48
|
+
- 2. On Windows `(nvm-windows) : nvm install 22.15.0 && nvm use 22.15.0`
|
|
49
|
+
- 👉 <a href="https://nodejs.org/en/download" target="_blank">Or directly download the Node.js LTS Installer</a>
|
|
50
|
+
|
|
51
|
+
.
|
|
52
|
+
|
|
42
53
|
## 💡 Usage Examples
|
|
43
54
|
|
|
44
55
|
### 📱 Manual App Testing
|
|
@@ -142,10 +153,15 @@ Generate test cases from PRDs, convert manual tests to low-code automation, and
|
|
|
142
153
|
|
|
143
154
|
## 🛠️ Installation
|
|
144
155
|
|
|
156
|
+
### 📋 Prerequisites for MCP Setup
|
|
157
|
+
#### Note : Ensure you are using Node version >= `18.0`
|
|
158
|
+
- Check your node version using `node --version`. Recommended version: `v22.15.0` (LTS)
|
|
159
|
+
|
|
145
160
|
### **One Click MCP Setup**
|
|
146
161
|
|
|
147
|
-
|
|
162
|
+
Click on the buttons below to install MCP in your respective IDE:
|
|
148
163
|
|
|
164
|
+
<a href="http://mcp.browserstack.com/one-click-setup?client=vscode"><img src="assets/one-click-vs-code.png" alt="Install in VS Code" width="160" height="80"></a> <a href="http://mcp.browserstack.com/one-click-setup?client=cursor"><img src="assets/one-click-cursor.png" alt="Install in Cursor" width="150" height="70"></a>
|
|
149
165
|
|
|
150
166
|
### **Alternate ways to Setup MCP server**
|
|
151
167
|
|
|
@@ -158,7 +174,9 @@ Generate test cases from PRDs, convert manual tests to low-code automation, and
|
|
|
158
174
|
|
|
159
175
|
- Once you have an account (and purchased appropriate plan), note down your `username` and `access_key` from [Account Settings](https://www.browserstack.com/accounts/profile/details).
|
|
160
176
|
|
|
161
|
-
2. Ensure you are using Node version >= `18.0
|
|
177
|
+
2. #### Note : Ensure you are using Node version >= `18.0`
|
|
178
|
+
- Check your node version using `node --version`. Recommended version: `v22.15.0` (LTS)
|
|
179
|
+
|
|
162
180
|
|
|
163
181
|
3. **Install the MCP Server**
|
|
164
182
|
|
|
@@ -404,14 +422,14 @@ As of now we support 20 tools.
|
|
|
404
422
|
**Prompt example**
|
|
405
423
|
|
|
406
424
|
```text
|
|
407
|
-
Take a screenshot of my app on Google Pixel 6 with Android
|
|
425
|
+
Take a screenshot of my app on Google Pixel 6 with Android 12 while testing on App Automate. App file path: /Users/xyz/app-debug.apk
|
|
408
426
|
```
|
|
409
427
|
|
|
410
428
|
15. `runAppTestsOnBrowserStack` — Run automated mobile tests (Espresso/XCUITest, etc.) on real devices.
|
|
411
429
|
**Prompt example**
|
|
412
430
|
|
|
413
431
|
```text
|
|
414
|
-
Run Espresso tests from /tests/checkout.zip on Galaxy S21 and Pixel 6 with Android
|
|
432
|
+
Run Espresso tests from /tests/checkout.zip on Galaxy S21 and Pixel 6 with Android 12. App path is /apps/beta-release.apk under project 'Checkout Flow'
|
|
415
433
|
```
|
|
416
434
|
|
|
417
435
|
---
|
|
@@ -456,7 +474,79 @@ As of now we support 20 tools.
|
|
|
456
474
|
```text
|
|
457
475
|
Upload PRD from /Users/xyz/Desktop/login-flow.pdf and use BrowserStack AI to generate test cases
|
|
458
476
|
```
|
|
477
|
+
## 🚀 Remote MCP Server
|
|
478
|
+
|
|
479
|
+
Remote MCP comes with all the functionalities of an MCP server without the hassles of complex setup or local installation.
|
|
480
|
+
|
|
481
|
+
### Key benefits:
|
|
482
|
+
|
|
483
|
+
- ✅ Works seamlessly in enterprise networks without worrying about firewalls or binaries or where local installation is not allowed.
|
|
484
|
+
|
|
485
|
+
- ✅ Secure OAuth integration – no password sharing or manual credential handling.
|
|
486
|
+
|
|
487
|
+
### Limitations:
|
|
488
|
+
|
|
489
|
+
- ❌ No Local Testing support (cannot test apps behind VPNs, firewalls, or localhost). If you have to do Local Testing, you would have to use a BrowserStack Local MCP server.
|
|
490
|
+
- ❌ Latency can be slightly higher, but nothing considerable — you generally won’t notice it in normal use.
|
|
459
491
|
|
|
492
|
+
### Installation Steps:
|
|
493
|
+
|
|
494
|
+
- On VSCode (Copilot - Agent Mode): `.vscode/mcp.json`:
|
|
495
|
+
|
|
496
|
+
- Locate or Create the Configuration File:
|
|
497
|
+
- In the root directory of your project, look for a folder named .vscode. This folder is usually hidden so you will need to find it as mentioned in the expand.
|
|
498
|
+
- If this folder doesn't exist, create it.
|
|
499
|
+
- Inside the .vscode folder, create a new file named mcp.json
|
|
500
|
+
- To setup Remote BrowserStack MCP instead of local BrowserStack MCP you can add the following JSON content :
|
|
501
|
+
<div align="center">
|
|
502
|
+
<img src="assets/remotemcp_json_file.png" alt="Remote MCP JSON file" height="300" width="300">
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
### Alternative way to Setup Remote MCP
|
|
506
|
+
|
|
507
|
+
- Step 1.Click on the gear icon to Select Tools
|
|
508
|
+
|
|
509
|
+
<div align="center">
|
|
510
|
+
<img src="assets/select_tools.png" alt="Select Tools" height="300" width="300">
|
|
511
|
+
</div>
|
|
512
|
+
|
|
513
|
+
- Step 2. A tool menu would appear at the top-centre, scroll down on the menu at the top and then Click on Add MCP Server
|
|
514
|
+
|
|
515
|
+
<div align="center">
|
|
516
|
+
<img src="assets/add_mcp_server.png" alt="Add MCP Server" height="300" width="300">
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
- Step 3. Click on HTTP option
|
|
520
|
+
<div align="center">
|
|
521
|
+
<img src="assets/http_option.png" alt="HTTP Option" height="300" width="300">
|
|
522
|
+
</div>
|
|
523
|
+
|
|
524
|
+
- Step 4. Paste Remote MCP Server URL : https://mcp.browserstack.com/mcp
|
|
525
|
+
<div align="center">
|
|
526
|
+
<img src="assets/server_url.png" alt="Remote MCP Server URL" height="300" width="300">
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
- Step 5. Give server id as : browserstack
|
|
530
|
+
|
|
531
|
+
<div align="center">
|
|
532
|
+
<img src="assets/server_id.png" alt="Remote MCP Server ID" height="300" width="300">
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
- Step 6. In VSCode Click on start MCP Server and then click on "Allow"
|
|
536
|
+
|
|
537
|
+
<div align="center">
|
|
538
|
+
<img src="assets/authentication1.png" alt="authentication1" height="300" width="300">
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<div align="center">
|
|
542
|
+
<img src="assets/authentication2.png" alt="authentication2" height="300" width="300">
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
<div align="center">
|
|
546
|
+
<img src="assets/signin_success.png" alt="Sign_in_success" height="300" width="300">
|
|
547
|
+
</div>
|
|
548
|
+
|
|
549
|
+
|
|
460
550
|
|
|
461
551
|
## 🤝 Recommended MCP Clients
|
|
462
552
|
|
package/dist/lib/apiClient.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ type RequestOptions = {
|
|
|
4
4
|
headers?: Record<string, string>;
|
|
5
5
|
params?: Record<string, string | number>;
|
|
6
6
|
body?: any;
|
|
7
|
+
timeout?: number;
|
|
7
8
|
raise_error?: boolean;
|
|
8
9
|
};
|
|
9
10
|
declare class ApiResponse<T = any> {
|
|
@@ -20,12 +21,13 @@ declare class ApiResponse<T = any> {
|
|
|
20
21
|
declare class ApiClient {
|
|
21
22
|
private instance;
|
|
22
23
|
private get axiosAgent();
|
|
24
|
+
private validateUrl;
|
|
23
25
|
private requestWrapper;
|
|
24
|
-
get<T = any>({ url, headers, params, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
25
|
-
post<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
26
|
-
put<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
27
|
-
patch<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
28
|
-
delete<T = any>({ url, headers, params, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
26
|
+
get<T = any>({ url, headers, params, timeout, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
27
|
+
post<T = any>({ url, headers, body, timeout, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
28
|
+
put<T = any>({ url, headers, body, timeout, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
29
|
+
patch<T = any>({ url, headers, body, timeout, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
30
|
+
delete<T = any>({ url, headers, params, timeout, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
|
|
29
31
|
}
|
|
30
32
|
export declare const apiClient: ApiClient;
|
|
31
33
|
export type { ApiResponse, RequestOptions };
|
package/dist/lib/apiClient.js
CHANGED
|
@@ -4,6 +4,7 @@ const { HttpsProxyAgent } = httpsProxyAgentPkg;
|
|
|
4
4
|
import * as https from "https";
|
|
5
5
|
import * as fs from "fs";
|
|
6
6
|
import config from "../config.js";
|
|
7
|
+
import { isDataUrlPayloadTooLarge } from "../lib/utils.js";
|
|
7
8
|
class ApiResponse {
|
|
8
9
|
_response;
|
|
9
10
|
constructor(response) {
|
|
@@ -77,8 +78,38 @@ class ApiClient {
|
|
|
77
78
|
get axiosAgent() {
|
|
78
79
|
return getAxiosAgent();
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
validateUrl(url, options) {
|
|
81
82
|
try {
|
|
83
|
+
const parsedUrl = new URL(url);
|
|
84
|
+
// Default safe limits
|
|
85
|
+
const maxContentLength = options?.maxContentLength ?? 20 * 1024 * 1024; // 20MB
|
|
86
|
+
const maxBodyLength = options?.maxBodyLength ?? 20 * 1024 * 1024; // 20MB
|
|
87
|
+
const maxUrlLength = 8000; // cutoff for URLs
|
|
88
|
+
// Check overall URL length
|
|
89
|
+
if (url.length > maxUrlLength) {
|
|
90
|
+
throw new Error(`URL length exceeds maxUrlLength (${maxUrlLength} chars)`);
|
|
91
|
+
}
|
|
92
|
+
if (parsedUrl.protocol === "data:") {
|
|
93
|
+
// Either reject completely OR check payload size
|
|
94
|
+
if (isDataUrlPayloadTooLarge(url, maxContentLength)) {
|
|
95
|
+
throw new Error("data: URI payload too large or invalid");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
99
|
+
throw new Error(`Unsupported URL scheme: ${parsedUrl.protocol}`);
|
|
100
|
+
}
|
|
101
|
+
if (options?.data &&
|
|
102
|
+
Buffer.byteLength(JSON.stringify(options.data), "utf8") > maxBodyLength) {
|
|
103
|
+
throw new Error(`Request body exceeds maxBodyLength (${maxBodyLength} bytes)`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
throw new Error(`Invalid URL: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async requestWrapper(fn, url, config, raise_error = true) {
|
|
111
|
+
try {
|
|
112
|
+
this.validateUrl(url, config);
|
|
82
113
|
const res = await fn(this.axiosAgent);
|
|
83
114
|
return new ApiResponse(res);
|
|
84
115
|
}
|
|
@@ -89,20 +120,50 @@ class ApiClient {
|
|
|
89
120
|
throw error;
|
|
90
121
|
}
|
|
91
122
|
}
|
|
92
|
-
async get({ url, headers, params, raise_error = true, }) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return this.requestWrapper((
|
|
100
|
-
}
|
|
101
|
-
async
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
123
|
+
async get({ url, headers, params, timeout, raise_error = true, }) {
|
|
124
|
+
const config = {
|
|
125
|
+
headers,
|
|
126
|
+
params,
|
|
127
|
+
timeout,
|
|
128
|
+
httpsAgent: this.axiosAgent,
|
|
129
|
+
};
|
|
130
|
+
return this.requestWrapper(() => this.instance.get(url, config), url, config, raise_error);
|
|
131
|
+
}
|
|
132
|
+
async post({ url, headers, body, timeout, raise_error = true, }) {
|
|
133
|
+
const config = {
|
|
134
|
+
headers,
|
|
135
|
+
timeout,
|
|
136
|
+
httpsAgent: this.axiosAgent,
|
|
137
|
+
data: body,
|
|
138
|
+
};
|
|
139
|
+
return this.requestWrapper(() => this.instance.post(url, config.data, config), url, config, raise_error);
|
|
140
|
+
}
|
|
141
|
+
async put({ url, headers, body, timeout, raise_error = true, }) {
|
|
142
|
+
const config = {
|
|
143
|
+
headers,
|
|
144
|
+
timeout,
|
|
145
|
+
httpsAgent: this.axiosAgent,
|
|
146
|
+
data: body,
|
|
147
|
+
};
|
|
148
|
+
return this.requestWrapper(() => this.instance.put(url, config.data, config), url, config, raise_error);
|
|
149
|
+
}
|
|
150
|
+
async patch({ url, headers, body, timeout, raise_error = true, }) {
|
|
151
|
+
const config = {
|
|
152
|
+
headers,
|
|
153
|
+
timeout,
|
|
154
|
+
httpsAgent: this.axiosAgent,
|
|
155
|
+
data: body,
|
|
156
|
+
};
|
|
157
|
+
return this.requestWrapper(() => this.instance.patch(url, config.data, config), url, config, raise_error);
|
|
158
|
+
}
|
|
159
|
+
async delete({ url, headers, params, timeout, raise_error = true, }) {
|
|
160
|
+
const config = {
|
|
161
|
+
headers,
|
|
162
|
+
params,
|
|
163
|
+
timeout,
|
|
164
|
+
httpsAgent: this.axiosAgent,
|
|
165
|
+
};
|
|
166
|
+
return this.requestWrapper(() => this.instance.delete(url, config), url, config, raise_error);
|
|
106
167
|
}
|
|
107
168
|
}
|
|
108
169
|
export const apiClient = new ApiClient();
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export declare enum BrowserStackProducts {
|
|
2
2
|
LIVE = "live",
|
|
3
3
|
APP_LIVE = "app_live",
|
|
4
|
-
APP_AUTOMATE = "app_automate"
|
|
4
|
+
APP_AUTOMATE = "app_automate",
|
|
5
|
+
SELENIUM_AUTOMATE = "selenium_automate",
|
|
6
|
+
PLAYWRIGHT_AUTOMATE = "playwright_automate"
|
|
5
7
|
}
|
|
6
8
|
/**
|
|
7
9
|
* Fetches and caches BrowserStack datasets (live + app_live + app_automate) with a shared TTL.
|
package/dist/lib/device-cache.js
CHANGED
|
@@ -12,11 +12,15 @@ export var BrowserStackProducts;
|
|
|
12
12
|
BrowserStackProducts["LIVE"] = "live";
|
|
13
13
|
BrowserStackProducts["APP_LIVE"] = "app_live";
|
|
14
14
|
BrowserStackProducts["APP_AUTOMATE"] = "app_automate";
|
|
15
|
+
BrowserStackProducts["SELENIUM_AUTOMATE"] = "selenium_automate";
|
|
16
|
+
BrowserStackProducts["PLAYWRIGHT_AUTOMATE"] = "playwright_automate";
|
|
15
17
|
})(BrowserStackProducts || (BrowserStackProducts = {}));
|
|
16
18
|
const URLS = {
|
|
17
19
|
[BrowserStackProducts.LIVE]: "https://www.browserstack.com/list-of-browsers-and-platforms/live.json",
|
|
18
20
|
[BrowserStackProducts.APP_LIVE]: "https://www.browserstack.com/list-of-browsers-and-platforms/app_live.json",
|
|
19
21
|
[BrowserStackProducts.APP_AUTOMATE]: "https://www.browserstack.com/list-of-browsers-and-platforms/app_automate.json",
|
|
22
|
+
[BrowserStackProducts.SELENIUM_AUTOMATE]: "https://www.browserstack.com/list-of-browsers-and-platforms/automate.json",
|
|
23
|
+
[BrowserStackProducts.PLAYWRIGHT_AUTOMATE]: "https://www.browserstack.com/list-of-browsers-and-platforms/playwright.json",
|
|
20
24
|
};
|
|
21
25
|
/**
|
|
22
26
|
* Fetches and caches BrowserStack datasets (live + app_live + app_automate) with a shared TTL.
|
|
@@ -26,30 +30,33 @@ export async function getDevicesAndBrowsers(type) {
|
|
|
26
30
|
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
27
31
|
}
|
|
28
32
|
let cache = {};
|
|
33
|
+
// Load existing cache
|
|
29
34
|
if (fs.existsSync(CACHE_FILE)) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
35
|
+
try {
|
|
36
|
+
cache = JSON.parse(fs.readFileSync(CACHE_FILE, "utf8"));
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error("Error parsing cache file:", err);
|
|
40
|
+
cache = {};
|
|
41
|
+
}
|
|
42
|
+
// Check per-product TTL
|
|
43
|
+
const cachedEntry = cache[type];
|
|
44
|
+
if (cachedEntry?.timestamp && Date.now() - cachedEntry.timestamp < TTL_MS) {
|
|
45
|
+
return cachedEntry.data;
|
|
42
46
|
}
|
|
43
47
|
}
|
|
48
|
+
// Fetch fresh data from BrowserStack
|
|
44
49
|
const liveRes = await apiClient.get({ url: URLS[type], raise_error: false });
|
|
45
50
|
if (!liveRes.ok) {
|
|
46
|
-
throw new Error(`Failed to fetch configuration from BrowserStack
|
|
51
|
+
throw new Error(`Failed to fetch configuration from BrowserStack: ${type} = ${liveRes.statusText}`);
|
|
47
52
|
}
|
|
48
|
-
cache
|
|
49
|
-
|
|
53
|
+
// Save to cache with timestamp and data directly under product key
|
|
54
|
+
cache[type] = {
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
data: liveRes.data,
|
|
50
57
|
};
|
|
51
|
-
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
|
|
52
|
-
return
|
|
58
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf8");
|
|
59
|
+
return liveRes.data;
|
|
53
60
|
}
|
|
54
61
|
// Rate limiter for started event (3H)
|
|
55
62
|
export function shouldSendStartedEvent() {
|
|
@@ -3,7 +3,7 @@ import { getBrowserStackAuth } from "./get-auth.js";
|
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
const packageJson = require("../../package.json");
|
|
6
|
-
import
|
|
6
|
+
import { apiClient } from "./apiClient.js";
|
|
7
7
|
import globalConfig from "../config.js";
|
|
8
8
|
export function trackMCP(toolName, clientInfo, error, config) {
|
|
9
9
|
const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event";
|
|
@@ -38,13 +38,16 @@ export function trackMCP(toolName, clientInfo, error, config) {
|
|
|
38
38
|
const authString = getBrowserStackAuth(config);
|
|
39
39
|
authHeader = `Basic ${Buffer.from(authString).toString("base64")}`;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
.post(
|
|
41
|
+
apiClient
|
|
42
|
+
.post({
|
|
43
|
+
url: instrumentationEndpoint,
|
|
44
|
+
body: event,
|
|
43
45
|
headers: {
|
|
44
46
|
"Content-Type": "application/json",
|
|
45
47
|
...(authHeader ? { Authorization: authHeader } : {}),
|
|
46
48
|
},
|
|
47
49
|
timeout: 2000,
|
|
50
|
+
raise_error: false,
|
|
48
51
|
})
|
|
49
52
|
.catch(() => { });
|
|
50
53
|
}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -1,4 +1,82 @@
|
|
|
1
1
|
import type { ApiResponse } from "./apiClient.js";
|
|
2
|
+
import { BrowserStackConfig } from "./types.js";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
4
|
export declare function sanitizeUrlParam(param: string): string;
|
|
3
5
|
export declare function maybeCompressBase64(base64: string): Promise<string>;
|
|
4
6
|
export declare function assertOkResponse(response: Response | ApiResponse, action: string): Promise<void>;
|
|
7
|
+
export declare function fetchFromBrowserStackAPI(url: string, config: BrowserStackConfig): Promise<any>;
|
|
8
|
+
export declare function handleMCPError(toolName: string, server: McpServer, config: BrowserStackConfig, error: unknown): {
|
|
9
|
+
[x: string]: unknown;
|
|
10
|
+
content: ({
|
|
11
|
+
[x: string]: unknown;
|
|
12
|
+
type: "text";
|
|
13
|
+
text: string;
|
|
14
|
+
_meta?: {
|
|
15
|
+
[x: string]: unknown;
|
|
16
|
+
} | undefined;
|
|
17
|
+
} | {
|
|
18
|
+
[x: string]: unknown;
|
|
19
|
+
type: "image";
|
|
20
|
+
data: string;
|
|
21
|
+
mimeType: string;
|
|
22
|
+
_meta?: {
|
|
23
|
+
[x: string]: unknown;
|
|
24
|
+
} | undefined;
|
|
25
|
+
} | {
|
|
26
|
+
[x: string]: unknown;
|
|
27
|
+
type: "audio";
|
|
28
|
+
data: string;
|
|
29
|
+
mimeType: string;
|
|
30
|
+
_meta?: {
|
|
31
|
+
[x: string]: unknown;
|
|
32
|
+
} | undefined;
|
|
33
|
+
} | {
|
|
34
|
+
[x: string]: unknown;
|
|
35
|
+
type: "resource_link";
|
|
36
|
+
name: string;
|
|
37
|
+
uri: string;
|
|
38
|
+
_meta?: {
|
|
39
|
+
[x: string]: unknown;
|
|
40
|
+
} | undefined;
|
|
41
|
+
mimeType?: string | undefined;
|
|
42
|
+
title?: string | undefined;
|
|
43
|
+
description?: string | undefined;
|
|
44
|
+
icons?: {
|
|
45
|
+
[x: string]: unknown;
|
|
46
|
+
src: string;
|
|
47
|
+
mimeType?: string | undefined;
|
|
48
|
+
sizes?: string | undefined;
|
|
49
|
+
}[] | undefined;
|
|
50
|
+
} | {
|
|
51
|
+
[x: string]: unknown;
|
|
52
|
+
type: "resource";
|
|
53
|
+
resource: {
|
|
54
|
+
[x: string]: unknown;
|
|
55
|
+
text: string;
|
|
56
|
+
uri: string;
|
|
57
|
+
_meta?: {
|
|
58
|
+
[x: string]: unknown;
|
|
59
|
+
} | undefined;
|
|
60
|
+
mimeType?: string | undefined;
|
|
61
|
+
} | {
|
|
62
|
+
[x: string]: unknown;
|
|
63
|
+
uri: string;
|
|
64
|
+
blob: string;
|
|
65
|
+
_meta?: {
|
|
66
|
+
[x: string]: unknown;
|
|
67
|
+
} | undefined;
|
|
68
|
+
mimeType?: string | undefined;
|
|
69
|
+
};
|
|
70
|
+
_meta?: {
|
|
71
|
+
[x: string]: unknown;
|
|
72
|
+
} | undefined;
|
|
73
|
+
})[];
|
|
74
|
+
_meta?: {
|
|
75
|
+
[x: string]: unknown;
|
|
76
|
+
} | undefined;
|
|
77
|
+
structuredContent?: {
|
|
78
|
+
[x: string]: unknown;
|
|
79
|
+
} | undefined;
|
|
80
|
+
isError?: boolean | undefined;
|
|
81
|
+
};
|
|
82
|
+
export declare function isDataUrlPayloadTooLarge(dataUrl: string, maxBytes: number): boolean;
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import sharp from "sharp";
|
|
2
|
+
import { getBrowserStackAuth } from "./get-auth.js";
|
|
3
|
+
import { trackMCP } from "../index.js";
|
|
2
4
|
export function sanitizeUrlParam(param) {
|
|
3
5
|
// Remove any characters that could be used for command injection
|
|
4
6
|
return param.replace(/[;&|`$(){}[\]<>]/g, "");
|
|
@@ -24,3 +26,48 @@ export async function assertOkResponse(response, action) {
|
|
|
24
26
|
throw new Error(`Failed to fetch logs for ${action}: ${response.statusText}`);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
29
|
+
export async function fetchFromBrowserStackAPI(url, config) {
|
|
30
|
+
const authString = getBrowserStackAuth(config);
|
|
31
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
32
|
+
const res = await fetch(url, {
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Basic ${auth}`,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`Failed to fetch from ${url}: ${res.status} ${res.statusText}`);
|
|
39
|
+
}
|
|
40
|
+
return res.json();
|
|
41
|
+
}
|
|
42
|
+
function errorContent(message) {
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: message }],
|
|
45
|
+
isError: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function handleMCPError(toolName, server, config, error) {
|
|
49
|
+
trackMCP(toolName, server.server.getClientVersion(), error, config);
|
|
50
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
51
|
+
const readableToolName = toolName.replace(/([A-Z])/g, " $1").toLowerCase();
|
|
52
|
+
return errorContent(`Failed to ${readableToolName}: ${errorMessage}. Please open an issue on GitHub if the problem persists`);
|
|
53
|
+
}
|
|
54
|
+
export function isDataUrlPayloadTooLarge(dataUrl, maxBytes) {
|
|
55
|
+
const commaIndex = dataUrl.indexOf(",");
|
|
56
|
+
if (commaIndex === -1)
|
|
57
|
+
return true; // malformed
|
|
58
|
+
const meta = dataUrl.slice(0, commaIndex);
|
|
59
|
+
const payload = dataUrl.slice(commaIndex + 1);
|
|
60
|
+
const isBase64 = /;base64$/i.test(meta);
|
|
61
|
+
if (!isBase64) {
|
|
62
|
+
try {
|
|
63
|
+
const decoded = decodeURIComponent(payload);
|
|
64
|
+
return Buffer.byteLength(decoded, "utf8") > maxBytes;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const padding = payload.endsWith("==") ? 2 : payload.endsWith("=") ? 1 : 0;
|
|
71
|
+
const decodedBytes = Math.floor((payload.length * 3) / 4) - padding;
|
|
72
|
+
return decodedBytes > maxBytes;
|
|
73
|
+
}
|
|
@@ -20,26 +20,38 @@ export function resolveVersion(requested, available) {
|
|
|
20
20
|
const lex = uniq.slice().sort();
|
|
21
21
|
return requested === "latest" ? lex[lex.length - 1] : lex[0];
|
|
22
22
|
}
|
|
23
|
-
// exact?
|
|
23
|
+
// exact match?
|
|
24
24
|
if (uniq.includes(requested)) {
|
|
25
25
|
return requested;
|
|
26
26
|
}
|
|
27
|
-
//
|
|
27
|
+
// Try major version matching (e.g., "14" matches "14.0", "14.1", etc.)
|
|
28
28
|
const reqNum = parseFloat(requested);
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
if (!isNaN(reqNum)) {
|
|
30
|
+
const majorVersionMatches = uniq.filter((v) => {
|
|
31
|
+
const vNum = parseFloat(v);
|
|
32
|
+
return !isNaN(vNum) && Math.floor(vNum) === Math.floor(reqNum);
|
|
33
|
+
});
|
|
34
|
+
if (majorVersionMatches.length > 0) {
|
|
35
|
+
// If multiple matches, prefer the most common format or latest
|
|
36
|
+
const exactMatch = majorVersionMatches.find((v) => v === `${Math.floor(reqNum)}.0`);
|
|
37
|
+
if (exactMatch) {
|
|
38
|
+
return exactMatch;
|
|
39
39
|
}
|
|
40
|
+
// Return the first match (usually the most common format)
|
|
41
|
+
return majorVersionMatches[0];
|
|
40
42
|
}
|
|
41
|
-
return best.v;
|
|
42
43
|
}
|
|
43
|
-
//
|
|
44
|
+
// Fuzzy matching: find the closest version
|
|
45
|
+
const reqNumForFuzzy = parseFloat(requested);
|
|
46
|
+
if (!isNaN(reqNumForFuzzy)) {
|
|
47
|
+
const numericVersions = uniq
|
|
48
|
+
.map((v) => ({ v, n: parseFloat(v) }))
|
|
49
|
+
.filter((x) => !isNaN(x.n))
|
|
50
|
+
.sort((a, b) => Math.abs(a.n - reqNumForFuzzy) - Math.abs(b.n - reqNumForFuzzy));
|
|
51
|
+
if (numericVersions.length > 0) {
|
|
52
|
+
return numericVersions[0].v;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Fallback: return the first available version
|
|
44
56
|
return uniq[0];
|
|
45
57
|
}
|
package/dist/server-factory.js
CHANGED
|
@@ -4,6 +4,7 @@ const require = createRequire(import.meta.url);
|
|
|
4
4
|
const packageJson = require("../package.json");
|
|
5
5
|
import logger from "./logger.js";
|
|
6
6
|
import addSDKTools from "./tools/bstack-sdk.js";
|
|
7
|
+
import addPercyTools from "./tools/percy-sdk.js";
|
|
7
8
|
import addBrowserLiveTools from "./tools/live.js";
|
|
8
9
|
import addAccessibilityTools from "./tools/accessibility.js";
|
|
9
10
|
import addTestManagementTools from "./tools/testmanagement.js";
|
|
@@ -12,7 +13,9 @@ import addFailureLogsTools from "./tools/get-failure-logs.js";
|
|
|
12
13
|
import addAutomateTools from "./tools/automate.js";
|
|
13
14
|
import addSelfHealTools from "./tools/selfheal.js";
|
|
14
15
|
import addAppLiveTools from "./tools/applive.js";
|
|
16
|
+
import addBuildInsightsTools from "./tools/build-insights.js";
|
|
15
17
|
import { setupOnInitialized } from "./oninitialized.js";
|
|
18
|
+
import addRCATools from "./tools/rca-agent.js";
|
|
16
19
|
/**
|
|
17
20
|
* Wrapper class for BrowserStack MCP Server
|
|
18
21
|
* Stores a map of registered tools by name
|
|
@@ -38,6 +41,7 @@ export class BrowserStackMcpServer {
|
|
|
38
41
|
const toolAdders = [
|
|
39
42
|
addAccessibilityTools,
|
|
40
43
|
addSDKTools,
|
|
44
|
+
addPercyTools,
|
|
41
45
|
addAppLiveTools,
|
|
42
46
|
addBrowserLiveTools,
|
|
43
47
|
addTestManagementTools,
|
|
@@ -45,6 +49,8 @@ export class BrowserStackMcpServer {
|
|
|
45
49
|
addFailureLogsTools,
|
|
46
50
|
addAutomateTools,
|
|
47
51
|
addSelfHealTools,
|
|
52
|
+
addBuildInsightsTools,
|
|
53
|
+
addRCATools,
|
|
48
54
|
];
|
|
49
55
|
toolAdders.forEach((adder) => {
|
|
50
56
|
// Each adder now returns a Record<string, Tool>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { testFilePathsMap } from "../lib/inmemory-store.js";
|
|
2
|
+
import { updateFileAndStep } from "./percy-snapshot-utils/utils.js";
|
|
3
|
+
import { percyWebSetupInstructions } from "../tools/sdk-utils/percy-web/handler.js";
|
|
4
|
+
export async function updateTestsWithPercyCommands(args) {
|
|
5
|
+
const { uuid, index } = args;
|
|
6
|
+
const filePaths = testFilePathsMap.get(uuid);
|
|
7
|
+
if (!filePaths) {
|
|
8
|
+
throw new Error(`No test files found in memory for UUID: ${uuid}`);
|
|
9
|
+
}
|
|
10
|
+
if (index < 0 || index >= filePaths.length) {
|
|
11
|
+
throw new Error(`Invalid index: ${index}. There are ${filePaths.length} files for UUID: ${uuid}`);
|
|
12
|
+
}
|
|
13
|
+
const result = await updateFileAndStep(filePaths[index], index, filePaths.length, percyWebSetupInstructions);
|
|
14
|
+
return {
|
|
15
|
+
content: result,
|
|
16
|
+
};
|
|
17
|
+
}
|