@browserstack/mcp-server 1.0.4 â 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -14
- package/dist/index.js +4 -0
- package/dist/lib/api.js +48 -0
- package/dist/lib/error.js +26 -0
- package/dist/tools/automate.js +52 -0
- package/dist/tools/sdk-utils/constants.js +1 -0
- package/dist/tools/testmanagement-utils/create-project-folder.js +107 -0
- package/dist/tools/testmanagement-utils/create-testcase.js +115 -0
- package/dist/tools/testmanagement.js +56 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<img src="assets/browserstack-logo.png" alt="BrowserStack Logo" height="100"> <img src="assets/mcp-logo.png" alt="MCP Server Logo" width="100">
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
|
+
|
|
7
8
|
<div align="center">
|
|
8
9
|
<a href="https://www.npmjs.com/package/@browserstack/mcp-server">
|
|
9
10
|
<img alt="NPM Version" src="https://img.shields.io/npm/v/%40browserstack%2Fmcp-server">
|
|
@@ -11,10 +12,20 @@
|
|
|
11
12
|
|
|
12
13
|
</div>
|
|
13
14
|
|
|
15
|
+
<p align="center">One Platform For All Your Testing Needs</p>
|
|
14
16
|
|
|
15
|
-
<
|
|
17
|
+
<div align="center">
|
|
18
|
+
<a href="https://glama.ai/mcp/servers/@browserstack/mcp-server">
|
|
19
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@browserstack/mcp-server/badge" alt="BrowserStack server MCP server" />
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
<div>
|
|
24
|
+
<a href="https://www.youtube.com/watch?v=sLA7K9v7qZc">
|
|
25
|
+
<img src="assets/thumbnail.webp">
|
|
26
|
+
</a>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
18
29
|
Enable every developer and tester in your team, whether they are testing manually, starting their automation journey, or scaling test automation.
|
|
19
30
|
BrowserStack MCP Server allows you to use our cutting-edge [Test Platform](https://www.browserstack.com/test-platform) directly from your favourite AI tools.
|
|
20
31
|
|
|
@@ -38,20 +49,17 @@ Use the following prompts to use your **mobile apps** on BrowserStack's extensiv
|
|
|
38
49
|
"My app crashed on Android 14 device, can you help me debug?"
|
|
39
50
|
```
|
|
40
51
|
|
|
41
|
-
Video Walkthrough:
|
|
42
|
-
|
|
43
|
-
[](https://www.youtube.com/watch?v=vy1sx0J7sTk)
|
|
44
52
|
|
|
45
53
|
- Unlike emulators, test your app's real-world performance on actual devices. With advanced [App-Profiling features](https://www.browserstack.com/docs/app-live/app-performance-testing), you can debug crashes and performance issues in real-time.
|
|
46
54
|
- Access all major devices and OS versions from our [device grid](https://www.browserstack.com/list-of-browsers-and-platforms/app_live), We have strict SLAs to provision our global datacenters with newly released devices on [launch day](https://www.browserstack.com/blog/browserstack-launches-iphone-15-on-day-0-behind-the-scenes/).
|
|
47
55
|
|
|
48
56
|
### đ Manual Web Testing
|
|
49
57
|
|
|
50
|
-
Similar to the app testing, you can use the following prompts to test your **websites** on BrowserStack's extensive cloud of real browsers and devices. Don't have
|
|
58
|
+
Similar to the app testing, you can use the following prompts to test your **websites** on BrowserStack's extensive cloud of real browsers and devices. Don't have Edge browser installed on your machine ? We've got you covered!
|
|
51
59
|
|
|
52
60
|
```bash
|
|
53
61
|
# Test your local websites
|
|
54
|
-
"open my website hosted on localhost:3001 on
|
|
62
|
+
"open my website hosted on localhost:3001 on Edge"
|
|
55
63
|
```
|
|
56
64
|
|
|
57
65
|
- Test websites across different browsers and devices. We support [every major browser](https://www.browserstack.com/list-of-browsers-and-platforms/live) across every major OS.
|
|
@@ -81,10 +89,19 @@ Use the following prompts to run/debug/fix your **automated tests** on BrowserSt
|
|
|
81
89
|
1. **Create a BrowserStack Account**
|
|
82
90
|
|
|
83
91
|
- Sign up for [BrowserStack](https://www.browserstack.com/signup) if you don't have an account already.
|
|
84
|
-
- Note down your `username` and `access_key` from [Account Settings](https://www.browserstack.com/accounts/profile/details)
|
|
85
92
|
|
|
86
|
-
|
|
93
|
+
- âšī¸ If you have an open-source project, we'll be able to provide you with a [free plan](https://www.browserstack.com/open-source).
|
|
94
|
+
<div align="center">
|
|
95
|
+
<img src="assets/open-source-plan.png" alt="Open Source Plan">
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
- 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).
|
|
99
|
+
|
|
100
|
+
2. Ensure you are using Node version >= `18.0`. Check your node version using `node --version`. Recommended version: `v22.15.0` (LTS)
|
|
101
|
+
3. **Install the MCP Server**
|
|
102
|
+
|
|
87
103
|
- VSCode (Copilot - Agent Mode): `.vscode/mcp.json`:
|
|
104
|
+
|
|
88
105
|
```json
|
|
89
106
|
{
|
|
90
107
|
"servers": {
|
|
@@ -99,10 +116,12 @@ Use the following prompts to run/debug/fix your **automated tests** on BrowserSt
|
|
|
99
116
|
}
|
|
100
117
|
}
|
|
101
118
|
```
|
|
102
|
-
* In VSCode, make sure to click on `Start` button in the MCP Server to start the server.
|
|
103
|
-

|
|
104
119
|
|
|
105
|
-
-
|
|
120
|
+
- In VSCode, make sure to click on `Start` button in the MCP Server to start the server.
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
* For Cursor: `.cursor/mcp.json`:
|
|
124
|
+
|
|
106
125
|
```json
|
|
107
126
|
{
|
|
108
127
|
"mcpServers": {
|
|
@@ -117,7 +136,9 @@ Use the following prompts to run/debug/fix your **automated tests** on BrowserSt
|
|
|
117
136
|
}
|
|
118
137
|
}
|
|
119
138
|
```
|
|
139
|
+
|
|
120
140
|
- Claude Desktop: `~/claude_desktop_config.json`:
|
|
141
|
+
|
|
121
142
|
```json
|
|
122
143
|
{
|
|
123
144
|
"mcpServers": {
|
|
@@ -133,7 +154,6 @@ Use the following prompts to run/debug/fix your **automated tests** on BrowserSt
|
|
|
133
154
|
}
|
|
134
155
|
```
|
|
135
156
|
|
|
136
|
-
|
|
137
157
|
## đ¤ Recommended MCP Clients
|
|
138
158
|
|
|
139
159
|
- We recommend using **Github Copilot or Cursor** for automated testing + debugging use cases.
|
|
@@ -147,6 +167,7 @@ Use the following prompts to run/debug/fix your **automated tests** on BrowserSt
|
|
|
147
167
|
## đ Contributing
|
|
148
168
|
|
|
149
169
|
We welcome contributions! Please open an issue to discuss any changes you'd like to make.
|
|
170
|
+
đ [**Click here to view our Contributing Guidelines**](https://github.com/browserstack/mcp-server/blob/main/CONTRIBUTING.md)
|
|
150
171
|
|
|
151
172
|
## đ Support
|
|
152
173
|
|
|
@@ -160,7 +181,6 @@ For support, please:
|
|
|
160
181
|
|
|
161
182
|
Stay tuned for exciting updates! Have any suggestions? Please open an issue to discuss.
|
|
162
183
|
|
|
163
|
-
|
|
164
184
|
## đ Resources
|
|
165
185
|
|
|
166
186
|
- [BrowserStack Test Platform](https://www.browserstack.com/test-platform)
|
package/dist/index.js
CHANGED
|
@@ -14,12 +14,16 @@ const applive_1 = __importDefault(require("./tools/applive"));
|
|
|
14
14
|
const observability_1 = __importDefault(require("./tools/observability"));
|
|
15
15
|
const live_1 = __importDefault(require("./tools/live"));
|
|
16
16
|
const accessibility_1 = __importDefault(require("./tools/accessibility"));
|
|
17
|
+
const automate_1 = __importDefault(require("./tools/automate"));
|
|
18
|
+
const testmanagement_1 = __importDefault(require("./tools/testmanagement"));
|
|
17
19
|
function registerTools(server) {
|
|
18
20
|
(0, bstack_sdk_1.default)(server);
|
|
19
21
|
(0, applive_1.default)(server);
|
|
20
22
|
(0, live_1.default)(server);
|
|
21
23
|
(0, observability_1.default)(server);
|
|
22
24
|
(0, accessibility_1.default)(server);
|
|
25
|
+
(0, automate_1.default)(server);
|
|
26
|
+
(0, testmanagement_1.default)(server);
|
|
23
27
|
}
|
|
24
28
|
// Create an MCP server
|
|
25
29
|
const server = new mcp_js_1.McpServer({
|
package/dist/lib/api.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getLatestO11YBuildInfo = getLatestO11YBuildInfo;
|
|
7
|
+
exports.retrieveNetworkFailures = retrieveNetworkFailures;
|
|
7
8
|
const config_1 = __importDefault(require("../config"));
|
|
8
9
|
async function getLatestO11YBuildInfo(buildName, projectName) {
|
|
9
10
|
const buildsUrl = `https://api-observability.browserstack.com/ext/v1/builds/latest?build_name=${encodeURIComponent(buildName)}&project_name=${encodeURIComponent(projectName)}`;
|
|
@@ -20,3 +21,50 @@ async function getLatestO11YBuildInfo(buildName, projectName) {
|
|
|
20
21
|
}
|
|
21
22
|
return buildsResponse.json();
|
|
22
23
|
}
|
|
24
|
+
// Fetches network logs for a given session ID and returns only failure logs
|
|
25
|
+
async function retrieveNetworkFailures(sessionId) {
|
|
26
|
+
if (!sessionId) {
|
|
27
|
+
throw new Error("Session ID is required");
|
|
28
|
+
}
|
|
29
|
+
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/networklogs`;
|
|
30
|
+
const auth = Buffer.from(`${config_1.default.browserstackUsername}:${config_1.default.browserstackAccessKey}`).toString("base64");
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
Authorization: `Basic ${auth}`,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
if (response.status === 404) {
|
|
40
|
+
throw new Error("Invalid session ID");
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Failed to fetch network logs: ${response.statusText}`);
|
|
43
|
+
}
|
|
44
|
+
const networklogs = await response.json();
|
|
45
|
+
// Filter for failure logs
|
|
46
|
+
const failureEntries = networklogs.log.entries.filter((entry) => {
|
|
47
|
+
return (entry.response.status === 0 ||
|
|
48
|
+
entry.response.status >= 400 ||
|
|
49
|
+
entry.response._error !== undefined);
|
|
50
|
+
});
|
|
51
|
+
// Return only the failure entries with some context
|
|
52
|
+
return {
|
|
53
|
+
failures: failureEntries.map((entry) => ({
|
|
54
|
+
startedDateTime: entry.startedDateTime,
|
|
55
|
+
request: {
|
|
56
|
+
method: entry.request?.method,
|
|
57
|
+
url: entry.request?.url,
|
|
58
|
+
queryString: entry.request?.queryString,
|
|
59
|
+
},
|
|
60
|
+
response: {
|
|
61
|
+
status: entry.response?.status,
|
|
62
|
+
statusText: entry.response?.statusText,
|
|
63
|
+
_error: entry.response?._error,
|
|
64
|
+
},
|
|
65
|
+
serverIPAddress: entry.serverIPAddress,
|
|
66
|
+
time: entry.time,
|
|
67
|
+
})),
|
|
68
|
+
totalFailures: failureEntries.length,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatAxiosError = formatAxiosError;
|
|
4
|
+
const axios_1 = require("axios");
|
|
5
|
+
/**
|
|
6
|
+
* Formats an AxiosError into a CallToolResult with an appropriate message.
|
|
7
|
+
* @param err - The error object to format
|
|
8
|
+
* @param defaultText - The fallback error message
|
|
9
|
+
*/
|
|
10
|
+
function formatAxiosError(err, defaultText) {
|
|
11
|
+
let text = defaultText;
|
|
12
|
+
if (err instanceof axios_1.AxiosError && err.response?.data) {
|
|
13
|
+
const message = err.response.data.message ||
|
|
14
|
+
err.response.data.error ||
|
|
15
|
+
err.message ||
|
|
16
|
+
defaultText;
|
|
17
|
+
text = message;
|
|
18
|
+
}
|
|
19
|
+
else if (err instanceof Error) {
|
|
20
|
+
text = err.message;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text }],
|
|
24
|
+
isError: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getNetworkFailures = getNetworkFailures;
|
|
7
|
+
exports.default = addAutomateTools;
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
10
|
+
const api_1 = require("../lib/api");
|
|
11
|
+
/**
|
|
12
|
+
* Fetches failed network requests from a BrowserStack Automate session.
|
|
13
|
+
* Returns network requests that resulted in errors or failed to complete.
|
|
14
|
+
*/
|
|
15
|
+
async function getNetworkFailures(args) {
|
|
16
|
+
try {
|
|
17
|
+
const failureLogs = await (0, api_1.retrieveNetworkFailures)(args.sessionId);
|
|
18
|
+
logger_1.default.info("Successfully fetched failure network logs for session: %s", args.sessionId);
|
|
19
|
+
// Check if there are any failures
|
|
20
|
+
const hasFailures = failureLogs.totalFailures > 0;
|
|
21
|
+
const text = hasFailures
|
|
22
|
+
? `${failureLogs.totalFailures} network failure(s) found for session :\n\n${JSON.stringify(failureLogs.failures, null, 2)}`
|
|
23
|
+
: `No network failures found for session`;
|
|
24
|
+
return {
|
|
25
|
+
content: [
|
|
26
|
+
{
|
|
27
|
+
type: "text",
|
|
28
|
+
text,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
|
|
35
|
+
logger_1.default.error("Failed to fetch network logs: %s", errorMessage);
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `Failed to fetch network logs: ${errorMessage}`,
|
|
41
|
+
isError: true,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function addAutomateTools(server) {
|
|
49
|
+
server.tool("getNetworkFailures", "Use this tool to fetch failed network requests from a BrowserStack Automate session.", {
|
|
50
|
+
sessionId: zod_1.z.string().describe("The Automate session ID."),
|
|
51
|
+
}, getNetworkFailures);
|
|
52
|
+
}
|
|
@@ -17,6 +17,7 @@ const nodejsInstructions = `
|
|
|
17
17
|
\`\`\`json
|
|
18
18
|
"browserstack-node-sdk": "latest"
|
|
19
19
|
\`\`\`
|
|
20
|
+
- Inform user to export BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY as environment variables.
|
|
20
21
|
`;
|
|
21
22
|
const pythonInstructions = `
|
|
22
23
|
Run the following command to install the browserstack-sdk:
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CreateProjFoldSchema = void 0;
|
|
7
|
+
exports.createProjectOrFolder = createProjectOrFolder;
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const config_1 = __importDefault(require("../../config"));
|
|
10
|
+
const zod_1 = require("zod");
|
|
11
|
+
const error_1 = require("../../lib/error"); // or correct path
|
|
12
|
+
// Schema for combined project/folder creation
|
|
13
|
+
exports.CreateProjFoldSchema = zod_1.z.object({
|
|
14
|
+
project_name: zod_1.z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Name of the project to create."),
|
|
18
|
+
project_description: zod_1.z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Description for the new project."),
|
|
22
|
+
project_identifier: zod_1.z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Existing project identifier to use for folder creation."),
|
|
26
|
+
folder_name: zod_1.z.string().optional().describe("Name of the folder to create."),
|
|
27
|
+
folder_description: zod_1.z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Description for the new folder."),
|
|
31
|
+
parent_id: zod_1.z
|
|
32
|
+
.number()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Parent folder ID; if omitted, folder is created at root."),
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Creates a project and/or folder in BrowserStack Test Management.
|
|
38
|
+
*/
|
|
39
|
+
async function createProjectOrFolder(args) {
|
|
40
|
+
const { project_name, project_description, project_identifier, folder_name, folder_description, parent_id, } = exports.CreateProjFoldSchema.parse(args);
|
|
41
|
+
if (!project_name && !project_identifier && !folder_name) {
|
|
42
|
+
throw new Error("Provide project_name (to create project), or project_identifier and folder_name (to create folder).");
|
|
43
|
+
}
|
|
44
|
+
let projId = project_identifier;
|
|
45
|
+
// Step 1: Create project if project_name provided
|
|
46
|
+
if (project_name) {
|
|
47
|
+
try {
|
|
48
|
+
const res = await axios_1.default.post("https://test-management.browserstack.com/api/v2/projects", { project: { name: project_name, description: project_description } }, {
|
|
49
|
+
auth: {
|
|
50
|
+
username: config_1.default.browserstackUsername,
|
|
51
|
+
password: config_1.default.browserstackAccessKey,
|
|
52
|
+
},
|
|
53
|
+
headers: { "Content-Type": "application/json" },
|
|
54
|
+
});
|
|
55
|
+
if (!res.data.success) {
|
|
56
|
+
throw new Error(`Failed to create project: ${JSON.stringify(res.data)}`);
|
|
57
|
+
}
|
|
58
|
+
// Project created successfully
|
|
59
|
+
projId = res.data.project.identifier;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
return (0, error_1.formatAxiosError)(err, "Failed to create project..");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Step 2: Create folder if folder_name provided
|
|
66
|
+
if (folder_name) {
|
|
67
|
+
if (!projId)
|
|
68
|
+
throw new Error("Cannot create folder without project_identifier.");
|
|
69
|
+
try {
|
|
70
|
+
const res = await axios_1.default.post(`https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(projId)}/folders`, {
|
|
71
|
+
folder: {
|
|
72
|
+
name: folder_name,
|
|
73
|
+
description: folder_description,
|
|
74
|
+
parent_id,
|
|
75
|
+
},
|
|
76
|
+
}, {
|
|
77
|
+
auth: {
|
|
78
|
+
username: config_1.default.browserstackUsername,
|
|
79
|
+
password: config_1.default.browserstackAccessKey,
|
|
80
|
+
},
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
});
|
|
83
|
+
if (!res.data.success) {
|
|
84
|
+
throw new Error(`Failed to create folder: ${JSON.stringify(res.data)}`);
|
|
85
|
+
}
|
|
86
|
+
// Folder created successfully
|
|
87
|
+
const folder = res.data.folder;
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
text: `Folder created: ID=${folder.id}, name=${folder.name} in project with identifier ${projId}`,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return (0, error_1.formatAxiosError)(err, "Failed to create folder.");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Only project was created
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{ type: "text", text: `Project created with identifier=${projId}` },
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CreateTestCaseSchema = void 0;
|
|
7
|
+
exports.sanitizeArgs = sanitizeArgs;
|
|
8
|
+
exports.createTestCase = createTestCase;
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const config_1 = __importDefault(require("../../config"));
|
|
11
|
+
const zod_1 = require("zod");
|
|
12
|
+
const error_1 = require("../../lib/error"); // or correct
|
|
13
|
+
exports.CreateTestCaseSchema = zod_1.z.object({
|
|
14
|
+
project_identifier: zod_1.z
|
|
15
|
+
.string()
|
|
16
|
+
.describe("The ID of the BrowserStack project where the test case should be created. If no project identifier is provided, ask the user if they would like to create a new project using the createProjectOrFolder tool."),
|
|
17
|
+
folder_id: zod_1.z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("The ID of the folder within the project where the test case should be created. If not provided, ask the user if they would like to create a new folder using the createProjectOrFolder tool."),
|
|
20
|
+
name: zod_1.z.string().describe("Name of the test case."),
|
|
21
|
+
description: zod_1.z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Brief description of the test case."),
|
|
25
|
+
owner: zod_1.z
|
|
26
|
+
.string()
|
|
27
|
+
.email()
|
|
28
|
+
.describe("Email of the test case owner.")
|
|
29
|
+
.optional(),
|
|
30
|
+
preconditions: zod_1.z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Any preconditions (HTML allowed)."),
|
|
34
|
+
test_case_steps: zod_1.z
|
|
35
|
+
.array(zod_1.z.object({
|
|
36
|
+
step: zod_1.z.string().describe("Action to perform in this step."),
|
|
37
|
+
result: zod_1.z.string().describe("Expected result of this step."),
|
|
38
|
+
}))
|
|
39
|
+
.describe("List of steps and expected results."),
|
|
40
|
+
issues: zod_1.z
|
|
41
|
+
.array(zod_1.z.string())
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("List of the linked Jira, Asana or Azure issues ID's. This should be strictly in array format not the string of json."),
|
|
44
|
+
issue_tracker: zod_1.z
|
|
45
|
+
.object({
|
|
46
|
+
name: zod_1.z
|
|
47
|
+
.string()
|
|
48
|
+
.describe("Issue tracker name, For example, use jira for Jira, azure for Azure DevOps, or asana for Asana."),
|
|
49
|
+
host: zod_1.z.string().url().describe("Base URL of the issue tracker."),
|
|
50
|
+
})
|
|
51
|
+
.optional(),
|
|
52
|
+
tags: zod_1.z
|
|
53
|
+
.array(zod_1.z.string())
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Tags to attach to the test case. This should be strictly in array format not the string of json"),
|
|
56
|
+
custom_fields: zod_1.z
|
|
57
|
+
.record(zod_1.z.string(), zod_1.z.string())
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Map of custom field names to values."),
|
|
60
|
+
});
|
|
61
|
+
function sanitizeArgs(args) {
|
|
62
|
+
const cleaned = { ...args };
|
|
63
|
+
if (cleaned.description === null)
|
|
64
|
+
delete cleaned.description;
|
|
65
|
+
if (cleaned.owner === null)
|
|
66
|
+
delete cleaned.owner;
|
|
67
|
+
if (cleaned.preconditions === null)
|
|
68
|
+
delete cleaned.preconditions;
|
|
69
|
+
if (cleaned.issue_tracker) {
|
|
70
|
+
if (cleaned.issue_tracker.name === undefined ||
|
|
71
|
+
cleaned.issue_tracker.host === undefined) {
|
|
72
|
+
delete cleaned.issue_tracker;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return cleaned;
|
|
76
|
+
}
|
|
77
|
+
async function createTestCase(params) {
|
|
78
|
+
const body = { test_case: params };
|
|
79
|
+
try {
|
|
80
|
+
const response = await axios_1.default.post(`https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(params.project_identifier)}/folders/${encodeURIComponent(params.folder_id)}/test-cases`, body, {
|
|
81
|
+
auth: {
|
|
82
|
+
username: config_1.default.browserstackUsername,
|
|
83
|
+
password: config_1.default.browserstackAccessKey,
|
|
84
|
+
},
|
|
85
|
+
headers: { "Content-Type": "application/json" },
|
|
86
|
+
});
|
|
87
|
+
const { data } = response.data;
|
|
88
|
+
if (!data.success) {
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `Failed to create test case: ${JSON.stringify(response.data)}`,
|
|
94
|
+
isError: true,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const tc = data.test_case;
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: `Successfully created test case ${tc.identifier}: ${tc.title}`,
|
|
106
|
+
},
|
|
107
|
+
{ type: "text", text: JSON.stringify(tc, null, 2) },
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
// Delegate to our centralized Axios error formatter
|
|
113
|
+
return (0, error_1.formatAxiosError)(err, "Failed to create test case");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProjectOrFolderTool = createProjectOrFolderTool;
|
|
4
|
+
exports.createTestCaseTool = createTestCaseTool;
|
|
5
|
+
exports.default = addTestManagementTools;
|
|
6
|
+
const create_project_folder_1 = require("./testmanagement-utils/create-project-folder");
|
|
7
|
+
const create_testcase_1 = require("./testmanagement-utils/create-testcase");
|
|
8
|
+
/**
|
|
9
|
+
* Wrapper to call createProjectOrFolder util.
|
|
10
|
+
*/
|
|
11
|
+
async function createProjectOrFolderTool(args) {
|
|
12
|
+
try {
|
|
13
|
+
return await (0, create_project_folder_1.createProjectOrFolder)(args);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: `Failed to create project/folder: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
21
|
+
isError: true,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
isError: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a test case in BrowserStack Test Management.
|
|
30
|
+
*/
|
|
31
|
+
async function createTestCaseTool(args) {
|
|
32
|
+
// Sanitize input arguments
|
|
33
|
+
const cleanedArgs = (0, create_testcase_1.sanitizeArgs)(args);
|
|
34
|
+
try {
|
|
35
|
+
return await (0, create_testcase_1.createTestCase)(cleanedArgs);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `Failed to create test case: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
43
|
+
isError: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Registers both project/folder and test-case tools.
|
|
52
|
+
*/
|
|
53
|
+
function addTestManagementTools(server) {
|
|
54
|
+
server.tool("createProjectOrFolder", "Create a project and/or folder in BrowserStack Test Management.", create_project_folder_1.CreateProjFoldSchema.shape, createProjectOrFolderTool);
|
|
55
|
+
server.tool("createTestCase", "Use this tool to create a test case in BrowserStack Test Management.", create_testcase_1.CreateTestCaseSchema.shape, createTestCaseTool);
|
|
56
|
+
}
|