@browserstack/mcp-server 1.0.10 → 1.0.12
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/dist/config.js +4 -2
- package/dist/lib/device-cache.js +52 -0
- package/dist/lib/instrumentation.js +51 -0
- package/dist/tools/accessibility.js +31 -28
- package/dist/tools/applive-utils/start-session.js +2 -2
- package/dist/tools/applive.js +6 -2
- package/dist/tools/automate.js +24 -13
- package/dist/tools/bstack-sdk.js +8 -9
- package/dist/tools/live-utils/desktop-filter.js +98 -0
- package/dist/tools/live-utils/mobile-filter.js +65 -0
- package/dist/tools/live-utils/start-session.js +64 -28
- package/dist/tools/live-utils/types.js +8 -0
- package/dist/tools/live-utils/version-resolver.js +48 -0
- package/dist/tools/live.js +68 -59
- package/dist/tools/observability.js +9 -1
- package/dist/tools/testmanagement-utils/create-testrun.js +90 -0
- package/dist/tools/testmanagement-utils/list-testcases.js +90 -0
- package/dist/tools/testmanagement-utils/list-testruns.js +68 -0
- package/dist/tools/testmanagement-utils/update-testrun.js +74 -0
- package/dist/tools/testmanagement.js +108 -0
- package/package.json +1 -1
- package/dist/tools/applive-utils/device-cache.js +0 -33
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveVersion = resolveVersion;
|
|
4
|
+
/**
|
|
5
|
+
* If req === "latest" or "oldest", returns max/min numeric (or lex)
|
|
6
|
+
* Else if exact match, returns that
|
|
7
|
+
* Else picks the numerically closest (or first)
|
|
8
|
+
*/
|
|
9
|
+
function resolveVersion(requested, available) {
|
|
10
|
+
// strip duplicates & sort
|
|
11
|
+
const uniq = Array.from(new Set(available));
|
|
12
|
+
// pick min/max
|
|
13
|
+
if (requested === "latest" || requested === "oldest") {
|
|
14
|
+
// try numeric
|
|
15
|
+
const nums = uniq
|
|
16
|
+
.map((v) => ({ v, n: parseFloat(v) }))
|
|
17
|
+
.filter((x) => !isNaN(x.n))
|
|
18
|
+
.sort((a, b) => a.n - b.n);
|
|
19
|
+
if (nums.length) {
|
|
20
|
+
return requested === "latest" ? nums[nums.length - 1].v : nums[0].v;
|
|
21
|
+
}
|
|
22
|
+
// fallback lex
|
|
23
|
+
const lex = uniq.slice().sort();
|
|
24
|
+
return requested === "latest" ? lex[lex.length - 1] : lex[0];
|
|
25
|
+
}
|
|
26
|
+
// exact?
|
|
27
|
+
if (uniq.includes(requested)) {
|
|
28
|
+
return requested;
|
|
29
|
+
}
|
|
30
|
+
// try closest numeric
|
|
31
|
+
const reqNum = parseFloat(requested);
|
|
32
|
+
const nums = uniq
|
|
33
|
+
.map((v) => ({ v, n: parseFloat(v) }))
|
|
34
|
+
.filter((x) => !isNaN(x.n));
|
|
35
|
+
if (!isNaN(reqNum) && nums.length) {
|
|
36
|
+
let best = nums[0], bestDiff = Math.abs(nums[0].n - reqNum);
|
|
37
|
+
for (const x of nums) {
|
|
38
|
+
const d = Math.abs(x.n - reqNum);
|
|
39
|
+
if (d < bestDiff) {
|
|
40
|
+
best = x;
|
|
41
|
+
bestDiff = d;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return best.v;
|
|
45
|
+
}
|
|
46
|
+
// final fallback
|
|
47
|
+
return uniq[0];
|
|
48
|
+
}
|
package/dist/tools/live.js
CHANGED
|
@@ -3,90 +3,99 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.startBrowserLiveSession = startBrowserLiveSession;
|
|
7
6
|
exports.default = addBrowserLiveTools;
|
|
8
7
|
const zod_1 = require("zod");
|
|
9
|
-
const start_session_1 = require("./live-utils/start-session");
|
|
10
8
|
const logger_1 = __importDefault(require("../logger"));
|
|
11
|
-
const
|
|
9
|
+
const start_session_1 = require("./live-utils/start-session");
|
|
10
|
+
const types_1 = require("./live-utils/types");
|
|
11
|
+
const instrumentation_1 = require("../lib/instrumentation");
|
|
12
|
+
// Define the schema shape
|
|
13
|
+
const LiveArgsShape = {
|
|
14
|
+
platformType: zod_1.z
|
|
15
|
+
.nativeEnum(types_1.PlatformType)
|
|
16
|
+
.describe("Must be 'desktop' or 'mobile'"),
|
|
17
|
+
desiredURL: zod_1.z.string().url().describe("The URL to test"),
|
|
18
|
+
desiredOS: zod_1.z
|
|
19
|
+
.enum(["Windows", "OS X", "android", "ios", "winphone"])
|
|
20
|
+
.describe("Desktop OS ('Windows' or 'OS X') or mobile OS ('android','ios','winphone')"),
|
|
21
|
+
desiredOSVersion: zod_1.z
|
|
22
|
+
.string()
|
|
23
|
+
.describe("The OS version must be specified as a version number (e.g., '10', '14.0') or as a keyword such as 'latest' or 'oldest'. Normalize variations like 'newest' or 'most recent' to 'latest', and terms like 'earliest' or 'first' to 'oldest'. For macOS, version names (e.g., 'Sequoia') must be used instead of numeric versions."),
|
|
24
|
+
desiredBrowser: zod_1.z
|
|
25
|
+
.enum(["chrome", "firefox", "safari", "edge", "ie"])
|
|
26
|
+
.describe("Browser for desktop (Chrome, IE, Firefox, Safari, Edge)"),
|
|
27
|
+
desiredBrowserVersion: zod_1.z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Browser version for desktop (e.g. '133.2', 'latest'). If the user says 'latest', 'newest', or similar, normalize it to 'latest'. Likewise, convert terms like 'earliest' or 'oldest' to 'oldest'."),
|
|
31
|
+
desiredDevice: zod_1.z.string().optional().describe("Device name for mobile"),
|
|
32
|
+
};
|
|
33
|
+
const LiveArgsSchema = zod_1.z.object(LiveArgsShape);
|
|
12
34
|
/**
|
|
13
|
-
* Launches a
|
|
35
|
+
* Launches a desktop browser session
|
|
14
36
|
*/
|
|
15
|
-
async function
|
|
16
|
-
if (!args.desiredBrowser)
|
|
17
|
-
throw new Error("You must provide a desiredBrowser
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
throw new Error("You must provide a desiredOS.");
|
|
24
|
-
}
|
|
25
|
-
if (!args.desiredOSVersion) {
|
|
26
|
-
throw new Error("You must provide a desiredOSVersion.");
|
|
27
|
-
}
|
|
28
|
-
if (!args.desiredBrowserVersion) {
|
|
29
|
-
throw new Error("You must provide a desiredBrowserVersion.");
|
|
30
|
-
}
|
|
31
|
-
// Validate URL format
|
|
32
|
-
try {
|
|
33
|
-
new URL(args.desiredURL);
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
logger_1.default.error("Invalid URL format: %s", error);
|
|
37
|
-
throw new Error("The provided URL is invalid.");
|
|
38
|
-
}
|
|
39
|
-
const isLocal = (0, local_1.isLocalURL)(args.desiredURL);
|
|
40
|
-
if (isLocal) {
|
|
41
|
-
await (0, local_1.ensureLocalBinarySetup)();
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
await (0, local_1.killExistingBrowserStackLocalProcesses)();
|
|
45
|
-
}
|
|
46
|
-
const launchUrl = await (0, start_session_1.startBrowserSession)({
|
|
47
|
-
browser: args.desiredBrowser,
|
|
37
|
+
async function launchDesktopSession(args) {
|
|
38
|
+
if (!args.desiredBrowser)
|
|
39
|
+
throw new Error("You must provide a desiredBrowser");
|
|
40
|
+
if (!args.desiredBrowserVersion)
|
|
41
|
+
throw new Error("You must provide a desiredBrowserVersion");
|
|
42
|
+
return (0, start_session_1.startBrowserSession)({
|
|
43
|
+
platformType: types_1.PlatformType.DESKTOP,
|
|
44
|
+
url: args.desiredURL,
|
|
48
45
|
os: args.desiredOS,
|
|
49
46
|
osVersion: args.desiredOSVersion,
|
|
50
|
-
|
|
47
|
+
browser: args.desiredBrowser,
|
|
51
48
|
browserVersion: args.desiredBrowserVersion,
|
|
52
|
-
isLocal,
|
|
53
49
|
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Launches a mobile browser session
|
|
53
|
+
*/
|
|
54
|
+
async function launchMobileSession(args) {
|
|
55
|
+
if (!args.desiredDevice)
|
|
56
|
+
throw new Error("You must provide a desiredDevice");
|
|
57
|
+
return (0, start_session_1.startBrowserSession)({
|
|
58
|
+
platformType: types_1.PlatformType.MOBILE,
|
|
59
|
+
browser: args.desiredBrowser,
|
|
60
|
+
url: args.desiredURL,
|
|
61
|
+
os: args.desiredOS,
|
|
62
|
+
osVersion: args.desiredOSVersion,
|
|
63
|
+
device: args.desiredDevice,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Handles the core logic for running a browser session
|
|
68
|
+
*/
|
|
69
|
+
async function runBrowserSession(rawArgs) {
|
|
70
|
+
// Validate and narrow
|
|
71
|
+
const args = LiveArgsSchema.parse(rawArgs);
|
|
72
|
+
// Branch desktop vs mobile and delegate
|
|
73
|
+
const launchUrl = args.platformType === types_1.PlatformType.DESKTOP
|
|
74
|
+
? await launchDesktopSession(args)
|
|
75
|
+
: await launchMobileSession(args);
|
|
54
76
|
return {
|
|
55
77
|
content: [
|
|
56
78
|
{
|
|
57
79
|
type: "text",
|
|
58
|
-
text:
|
|
80
|
+
text: `✅ Session started. If it didn't open automatically, visit:\n${launchUrl}`,
|
|
59
81
|
},
|
|
60
82
|
],
|
|
61
83
|
};
|
|
62
84
|
}
|
|
63
85
|
function addBrowserLiveTools(server) {
|
|
64
|
-
server.tool("runBrowserLiveSession", "
|
|
65
|
-
desiredBrowser: zod_1.z
|
|
66
|
-
.enum(["Chrome", "IE", "Firefox", "Safari", "Edge"])
|
|
67
|
-
.describe("The browser to run the test on. Example: 'Chrome', 'IE', 'Safari', 'Edge'. Always ask the user for the browser they want to use, do not assume it."),
|
|
68
|
-
desiredOSVersion: zod_1.z
|
|
69
|
-
.string()
|
|
70
|
-
.describe("The OS version to run the browser on. Example: '10' for Windows, '12' for macOS, '14' for iOS"),
|
|
71
|
-
desiredOS: zod_1.z
|
|
72
|
-
.enum(["Windows", "OS X"])
|
|
73
|
-
.describe("The operating system to run the browser on. Example: 'Windows', 'OS X'"),
|
|
74
|
-
desiredURL: zod_1.z
|
|
75
|
-
.string()
|
|
76
|
-
.describe("The URL of the website to test. This can be a local URL (e.g., http://localhost:3000) or a public URL. Always ask the user for the URL, do not assume it."),
|
|
77
|
-
desiredBrowserVersion: zod_1.z
|
|
78
|
-
.string()
|
|
79
|
-
.describe("The version of the browser to use. Example: 133.0, 134.0, 87.0"),
|
|
80
|
-
}, async (args) => {
|
|
86
|
+
server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
|
|
81
87
|
try {
|
|
82
|
-
|
|
88
|
+
(0, instrumentation_1.trackMCP)("runBrowserLiveSession", server.server.getClientVersion());
|
|
89
|
+
return await runBrowserSession(args);
|
|
83
90
|
}
|
|
84
91
|
catch (error) {
|
|
92
|
+
logger_1.default.error("Live session failed: %s", error);
|
|
93
|
+
(0, instrumentation_1.trackMCP)("runBrowserLiveSession", server.server.getClientVersion(), error);
|
|
85
94
|
return {
|
|
86
95
|
content: [
|
|
87
96
|
{
|
|
88
97
|
type: "text",
|
|
89
|
-
text: `Failed to start a browser live session. Error: ${error}
|
|
98
|
+
text: `Failed to start a browser live session. Error: ${error}`,
|
|
90
99
|
isError: true,
|
|
91
100
|
},
|
|
92
101
|
],
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.getFailuresInLastRun = getFailuresInLastRun;
|
|
4
7
|
exports.default = addObservabilityTools;
|
|
5
8
|
const zod_1 = require("zod");
|
|
6
9
|
const api_1 = require("../lib/api");
|
|
10
|
+
const instrumentation_1 = require("../lib/instrumentation");
|
|
11
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
7
12
|
async function getFailuresInLastRun(buildName, projectName) {
|
|
8
13
|
const buildsData = await (0, api_1.getLatestO11YBuildInfo)(buildName, projectName);
|
|
9
14
|
const observabilityUrl = buildsData.observability_url;
|
|
@@ -40,9 +45,12 @@ function addObservabilityTools(server) {
|
|
|
40
45
|
.describe("Name of the project to get failures for. This is the 'projectName' key in the browserstack.yml file. If not sure, ask the user for the project name."),
|
|
41
46
|
}, async (args) => {
|
|
42
47
|
try {
|
|
43
|
-
|
|
48
|
+
(0, instrumentation_1.trackMCP)("getFailuresInLastRun", server.server.getClientVersion());
|
|
49
|
+
return await getFailuresInLastRun(args.buildName, args.projectName);
|
|
44
50
|
}
|
|
45
51
|
catch (error) {
|
|
52
|
+
logger_1.default.error("Failed to get failures in the last run: %s", error);
|
|
53
|
+
(0, instrumentation_1.trackMCP)("getFailuresInLastRun", server.server.getClientVersion(), error);
|
|
46
54
|
return {
|
|
47
55
|
content: [
|
|
48
56
|
{
|
|
@@ -0,0 +1,90 @@
|
|
|
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.CreateTestRunSchema = void 0;
|
|
7
|
+
exports.createTestRun = createTestRun;
|
|
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");
|
|
12
|
+
/**
|
|
13
|
+
* Schema for creating a test run.
|
|
14
|
+
*/
|
|
15
|
+
exports.CreateTestRunSchema = zod_1.z.object({
|
|
16
|
+
project_identifier: zod_1.z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("Identifier of the project in which to create the test run."),
|
|
19
|
+
test_run: zod_1.z.object({
|
|
20
|
+
name: zod_1.z.string().describe("Name of the test run"),
|
|
21
|
+
description: zod_1.z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Brief information about the test run"),
|
|
25
|
+
run_state: zod_1.z
|
|
26
|
+
.enum([
|
|
27
|
+
"new_run",
|
|
28
|
+
"in_progress",
|
|
29
|
+
"under_review",
|
|
30
|
+
"rejected",
|
|
31
|
+
"done",
|
|
32
|
+
"closed",
|
|
33
|
+
])
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("State of the test run. One of new_run, in_progress, under_review, rejected, done, closed"),
|
|
36
|
+
issues: zod_1.z.array(zod_1.z.string()).optional().describe("Linked issue IDs"),
|
|
37
|
+
issue_tracker: zod_1.z
|
|
38
|
+
.object({ name: zod_1.z.string(), host: zod_1.z.string().url() })
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Issue tracker configuration"),
|
|
41
|
+
test_cases: zod_1.z
|
|
42
|
+
.array(zod_1.z.string())
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("List of test case IDs"),
|
|
45
|
+
folder_ids: zod_1.z
|
|
46
|
+
.array(zod_1.z.number())
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Folder IDs to include"),
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Creates a test run via BrowserStack Test Management API.
|
|
53
|
+
*/
|
|
54
|
+
async function createTestRun(rawArgs) {
|
|
55
|
+
try {
|
|
56
|
+
const inputArgs = {
|
|
57
|
+
...rawArgs,
|
|
58
|
+
test_run: {
|
|
59
|
+
...rawArgs.test_run,
|
|
60
|
+
include_all: false,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const args = exports.CreateTestRunSchema.parse(inputArgs);
|
|
64
|
+
const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs`;
|
|
65
|
+
const response = await axios_1.default.post(url, { test_run: args.test_run }, {
|
|
66
|
+
auth: {
|
|
67
|
+
username: config_1.default.browserstackUsername,
|
|
68
|
+
password: config_1.default.browserstackAccessKey,
|
|
69
|
+
},
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
});
|
|
72
|
+
const data = response.data;
|
|
73
|
+
if (!data.success) {
|
|
74
|
+
throw new Error(`API returned unsuccessful response: ${JSON.stringify(data)}`);
|
|
75
|
+
}
|
|
76
|
+
// Assume data.test_run contains created run info
|
|
77
|
+
const created = data.test_run || data;
|
|
78
|
+
const runId = created.identifier || created.id || created.name;
|
|
79
|
+
const summary = `Successfully created test run ${runId}`;
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{ type: "text", text: summary },
|
|
83
|
+
{ type: "text", text: JSON.stringify(created, null, 2) },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return (0, error_1.formatAxiosError)(err, "Failed to create test run");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
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.ListTestCasesSchema = void 0;
|
|
7
|
+
exports.listTestCases = listTestCases;
|
|
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");
|
|
12
|
+
/**
|
|
13
|
+
* Schema for listing test cases with optional filters.
|
|
14
|
+
*/
|
|
15
|
+
exports.ListTestCasesSchema = zod_1.z.object({
|
|
16
|
+
project_identifier: zod_1.z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("Identifier of the project to fetch test cases from. Example: PR-12345"),
|
|
19
|
+
folder_id: zod_1.z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("If provided, only return cases in this folder."),
|
|
23
|
+
case_type: zod_1.z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Comma-separated list of case types (e.g. functional,regression)."),
|
|
27
|
+
priority: zod_1.z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Comma-separated list of priorities (e.g. critical,medium,low)."),
|
|
31
|
+
p: zod_1.z.number().optional().describe("Page number."),
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Calls BrowserStack Test Management to list test cases with filters.
|
|
35
|
+
*/
|
|
36
|
+
async function listTestCases(args) {
|
|
37
|
+
try {
|
|
38
|
+
// Build query string
|
|
39
|
+
const params = new URLSearchParams();
|
|
40
|
+
if (args.folder_id)
|
|
41
|
+
params.append("folder_id", args.folder_id);
|
|
42
|
+
if (args.case_type)
|
|
43
|
+
params.append("case_type", args.case_type);
|
|
44
|
+
if (args.priority)
|
|
45
|
+
params.append("priority", args.priority);
|
|
46
|
+
if (args.p !== undefined)
|
|
47
|
+
params.append("p", args.p.toString());
|
|
48
|
+
const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-cases?${params.toString()}`;
|
|
49
|
+
const resp = await axios_1.default.get(url, {
|
|
50
|
+
auth: {
|
|
51
|
+
username: config_1.default.browserstackUsername,
|
|
52
|
+
password: config_1.default.browserstackAccessKey,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const resp_data = resp.data;
|
|
56
|
+
if (!resp_data.success) {
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `Failed to list test cases: ${JSON.stringify(resp_data)}`,
|
|
62
|
+
isError: true,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const { test_cases, info } = resp_data;
|
|
69
|
+
const count = info?.count ?? test_cases.length;
|
|
70
|
+
// Summary for more focused output
|
|
71
|
+
const summary = test_cases
|
|
72
|
+
.map((tc) => `• ${tc.identifier}: ${tc.title} [${tc.case_type} | ${tc.priority}]`)
|
|
73
|
+
.join("\n");
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `Found ${count} test case(s):\n\n${summary}`,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: JSON.stringify(test_cases, null, 2),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return (0, error_1.formatAxiosError)(err, "Failed to list test cases");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
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.ListTestRunsSchema = void 0;
|
|
7
|
+
exports.listTestRuns = listTestRuns;
|
|
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");
|
|
12
|
+
/**
|
|
13
|
+
* Schema for listing test runs with optional filters.
|
|
14
|
+
*/
|
|
15
|
+
exports.ListTestRunsSchema = zod_1.z.object({
|
|
16
|
+
project_identifier: zod_1.z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("Identifier of the project to fetch test runs from (e.g., PR-12345)"),
|
|
19
|
+
run_state: zod_1.z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Return all test runs with this state (comma-separated)"),
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Fetches and formats the list of test runs for a project.
|
|
26
|
+
*/
|
|
27
|
+
async function listTestRuns(args) {
|
|
28
|
+
try {
|
|
29
|
+
const params = new URLSearchParams();
|
|
30
|
+
if (args.run_state) {
|
|
31
|
+
params.set("run_state", args.run_state);
|
|
32
|
+
}
|
|
33
|
+
const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs?` + params.toString();
|
|
34
|
+
const resp = await axios_1.default.get(url, {
|
|
35
|
+
auth: {
|
|
36
|
+
username: config_1.default.browserstackUsername,
|
|
37
|
+
password: config_1.default.browserstackAccessKey,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const data = resp.data;
|
|
41
|
+
if (!data.success) {
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: `Failed to list test runs: ${JSON.stringify(data)}`,
|
|
47
|
+
isError: true,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const runs = data.test_runs;
|
|
54
|
+
const count = runs.length;
|
|
55
|
+
const summary = runs
|
|
56
|
+
.map((tr) => `• ${tr.identifier}: ${tr.name} [${tr.run_state}]`)
|
|
57
|
+
.join("\n");
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{ type: "text", text: `Found ${count} test run(s):\n\n${summary}` },
|
|
61
|
+
{ type: "text", text: JSON.stringify(runs, null, 2) },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return (0, error_1.formatAxiosError)(err, "Failed to list test runs");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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.UpdateTestRunSchema = void 0;
|
|
7
|
+
exports.updateTestRun = updateTestRun;
|
|
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");
|
|
12
|
+
/**
|
|
13
|
+
* Schema for updating a test run with partial fields.
|
|
14
|
+
*/
|
|
15
|
+
exports.UpdateTestRunSchema = zod_1.z.object({
|
|
16
|
+
project_identifier: zod_1.z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("Project identifier (e.g., PR-12345)"),
|
|
19
|
+
test_run_id: zod_1.z.string().describe("Test run identifier (e.g., TR-678)"),
|
|
20
|
+
test_run: zod_1.z.object({
|
|
21
|
+
name: zod_1.z.string().optional().describe("New name of the test run"),
|
|
22
|
+
run_state: zod_1.z
|
|
23
|
+
.enum([
|
|
24
|
+
"new_run",
|
|
25
|
+
"in_progress",
|
|
26
|
+
"under_review",
|
|
27
|
+
"rejected",
|
|
28
|
+
"done",
|
|
29
|
+
"closed",
|
|
30
|
+
])
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Updated state of the test run"),
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Partially updates an existing test run.
|
|
37
|
+
*/
|
|
38
|
+
async function updateTestRun(args) {
|
|
39
|
+
try {
|
|
40
|
+
const body = { test_run: args.test_run };
|
|
41
|
+
const url = `https://test-management.browserstack.com/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/update`;
|
|
42
|
+
const resp = await axios_1.default.patch(url, body, {
|
|
43
|
+
auth: {
|
|
44
|
+
username: config_1.default.browserstackUsername,
|
|
45
|
+
password: config_1.default.browserstackAccessKey,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const data = resp.data;
|
|
49
|
+
if (!data.success) {
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: `Failed to update test run: ${JSON.stringify(data)}`,
|
|
55
|
+
isError: true,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
isError: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: `Successfully updated test run ${args.test_run_id}`,
|
|
66
|
+
},
|
|
67
|
+
{ type: "text", text: JSON.stringify(data.testrun || data, null, 2) },
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
return (0, error_1.formatAxiosError)(err, "Failed to update test run");
|
|
73
|
+
}
|
|
74
|
+
}
|