@browserstack/mcp-server 1.2.15-beta.1 → 1.2.16-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/percy-sdk.js +20 -11
- package/dist/tools/testmanagement-utils/get-testplan.d.ts +16 -0
- package/dist/tools/testmanagement-utils/get-testplan.js +99 -0
- package/dist/tools/testmanagement-utils/list-folders.d.ts +16 -0
- package/dist/tools/testmanagement-utils/list-folders.js +77 -0
- package/dist/tools/testmanagement-utils/list-testcases.js +1 -1
- package/dist/tools/testmanagement-utils/list-testplans.d.ts +15 -0
- package/dist/tools/testmanagement-utils/list-testplans.js +75 -0
- package/dist/tools/testmanagement-utils/update-testcase.d.ts +16 -0
- package/dist/tools/testmanagement-utils/update-testcase.js +133 -10
- package/dist/tools/testmanagement.d.ts +15 -0
- package/dist/tools/testmanagement.js +73 -2
- package/package.json +1 -1
package/dist/tools/percy-sdk.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { trackMCP } from "../index.js";
|
|
2
2
|
import { fetchPercyChanges } from "./review-agent.js";
|
|
3
3
|
import { addListTestFiles } from "./list-test-files.js";
|
|
4
|
-
import { runPercyScan } from "./run-percy-scan.js";
|
|
5
4
|
import { SetUpPercyParamsShape } from "./sdk-utils/common/schema.js";
|
|
6
5
|
import { updateTestsWithPercyCommands } from "./add-percy-snapshots.js";
|
|
7
6
|
import { approveOrDeclinePercyBuild } from "./review-agent-utils/percy-approve-reject.js";
|
|
@@ -9,7 +8,10 @@ import { setUpPercyHandler, simulatePercyChangeHandler, } from "./sdk-utils/hand
|
|
|
9
8
|
import { z } from "zod";
|
|
10
9
|
import { SETUP_PERCY_DESCRIPTION, LIST_TEST_FILES_DESCRIPTION, PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, SIMULATE_PERCY_CHANGE_DESCRIPTION, } from "./sdk-utils/common/constants.js";
|
|
11
10
|
import { UpdateTestFileWithInstructionsParams } from "./percy-snapshot-utils/constants.js";
|
|
12
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
// PMAA-100: kept commented so the registration block below is easy to restore once the proper fix lands.
|
|
13
|
+
// RunPercyScanParamsShape,
|
|
14
|
+
FetchPercyChangesParamsShape, ManagePercyBuildApprovalParamsShape, } from "./sdk-utils/common/schema.js";
|
|
13
15
|
import { handleMCPError } from "../lib/utils.js";
|
|
14
16
|
export function registerPercyTools(server, config) {
|
|
15
17
|
const tools = {};
|
|
@@ -66,15 +68,22 @@ export function registerPercyTools(server, config) {
|
|
|
66
68
|
return handleMCPError("listTestFiles", server, config, error);
|
|
67
69
|
}
|
|
68
70
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
// PMAA-100: runPercyScan temporarily disabled — fetched Percy token was being
|
|
72
|
+
// returned in plaintext within tool output (see HackerOne #3576387). Re-enable
|
|
73
|
+
// once the token is replaced with a placeholder in run-percy-scan.ts.
|
|
74
|
+
// tools.runPercyScan = server.tool(
|
|
75
|
+
// "runPercyScan",
|
|
76
|
+
// "Run a Percy visual test scan. Example prompts : Run this Percy build/scan. Never run percy scan/build without this tool",
|
|
77
|
+
// RunPercyScanParamsShape,
|
|
78
|
+
// async (args) => {
|
|
79
|
+
// try {
|
|
80
|
+
// trackMCP("runPercyScan", server.server.getClientVersion()!, config);
|
|
81
|
+
// return runPercyScan(args, config);
|
|
82
|
+
// } catch (error) {
|
|
83
|
+
// return handleMCPError("runPercyScan", server, config, error);
|
|
84
|
+
// }
|
|
85
|
+
// },
|
|
86
|
+
// );
|
|
78
87
|
tools.fetchPercyChanges = server.tool("fetchPercyChanges", "Retrieves and summarizes all visual changes detected by Percy AI between the latest and previous builds, helping quickly review what has changed in your project.", FetchPercyChangesParamsShape, async (args) => {
|
|
79
88
|
try {
|
|
80
89
|
trackMCP("fetchPercyChanges", server.server.getClientVersion(), config);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Schema for fetching a single test plan by identifier, including its linked test runs.
|
|
6
|
+
*/
|
|
7
|
+
export declare const GetTestPlanSchema: z.ZodObject<{
|
|
8
|
+
project_identifier: z.ZodString;
|
|
9
|
+
test_plan_identifier: z.ZodString;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type GetTestPlanArgs = z.infer<typeof GetTestPlanSchema>;
|
|
12
|
+
/**
|
|
13
|
+
* Fetches a test plan by identifier and its linked test runs, returning a unified view
|
|
14
|
+
* suitable for generating documentation (metadata + linked runs + status summary + case count).
|
|
15
|
+
*/
|
|
16
|
+
export declare function getTestPlan(args: GetTestPlanArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { formatAxiosError } from "../../lib/error.js";
|
|
4
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
|
+
/**
|
|
7
|
+
* Schema for fetching a single test plan by identifier, including its linked test runs.
|
|
8
|
+
*/
|
|
9
|
+
export const GetTestPlanSchema = z.object({
|
|
10
|
+
project_identifier: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Identifier of the project (starts with PR- followed by a number)."),
|
|
13
|
+
test_plan_identifier: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe("Identifier of the test plan (starts with TP- followed by a number)."),
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Fetches a test plan by identifier and its linked test runs, returning a unified view
|
|
19
|
+
* suitable for generating documentation (metadata + linked runs + status summary + case count).
|
|
20
|
+
*/
|
|
21
|
+
export async function getTestPlan(args, config) {
|
|
22
|
+
try {
|
|
23
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
24
|
+
const projectId = encodeURIComponent(args.project_identifier);
|
|
25
|
+
const planId = encodeURIComponent(args.test_plan_identifier);
|
|
26
|
+
const authString = getBrowserStackAuth(config);
|
|
27
|
+
const [username, password] = authString.split(":");
|
|
28
|
+
const authHeader = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
|
29
|
+
const planResp = await apiClient.get({
|
|
30
|
+
url: `${tmBaseUrl}/api/v2/projects/${projectId}/test-plans/${planId}`,
|
|
31
|
+
headers: { Authorization: authHeader },
|
|
32
|
+
});
|
|
33
|
+
if (!planResp.data?.success) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `Failed to fetch test plan: ${JSON.stringify(planResp.data)}`,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
isError: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const plan = planResp.data.test_plan;
|
|
45
|
+
const runsResp = await apiClient.get({
|
|
46
|
+
url: `${tmBaseUrl}/api/v2/projects/${projectId}/test-plans/${planId}/test-runs`,
|
|
47
|
+
headers: { Authorization: authHeader },
|
|
48
|
+
});
|
|
49
|
+
const runs = runsResp.data?.success
|
|
50
|
+
? (runsResp.data.test_runs ?? [])
|
|
51
|
+
: [];
|
|
52
|
+
const statusSummary = {};
|
|
53
|
+
let totalCases = 0;
|
|
54
|
+
for (const run of runs) {
|
|
55
|
+
statusSummary[run.run_state] = (statusSummary[run.run_state] ?? 0) + 1;
|
|
56
|
+
totalCases += run.test_cases_count ?? 0;
|
|
57
|
+
}
|
|
58
|
+
const header = [
|
|
59
|
+
`Test Plan ${plan.identifier}: ${plan.name}`,
|
|
60
|
+
`Status: ${plan.active_state}`,
|
|
61
|
+
plan.description ? `Description: ${plan.description}` : null,
|
|
62
|
+
plan.start_date || plan.end_date
|
|
63
|
+
? `Dates: ${plan.start_date ?? "—"} → ${plan.end_date ?? "—"}`
|
|
64
|
+
: null,
|
|
65
|
+
`Linked runs: ${runs.length} (plan counts — active ${plan.test_runs_count?.active ?? 0} / closed ${plan.test_runs_count?.closed ?? 0})`,
|
|
66
|
+
`Total test cases across runs: ${totalCases}`,
|
|
67
|
+
Object.keys(statusSummary).length > 0
|
|
68
|
+
? `Run-state breakdown: ${Object.entries(statusSummary)
|
|
69
|
+
.map(([s, n]) => `${s}=${n}`)
|
|
70
|
+
.join(", ")}`
|
|
71
|
+
: null,
|
|
72
|
+
]
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.join("\n");
|
|
75
|
+
const runsBlock = runs.length
|
|
76
|
+
? "\n\nLinked test runs:\n" +
|
|
77
|
+
runs
|
|
78
|
+
.map((r) => `• ${r.identifier}: ${r.name} [${r.run_state}] — ${r.test_cases_count} case(s)${r.assignee ? ` (assignee: ${r.assignee})` : ""}`)
|
|
79
|
+
.join("\n")
|
|
80
|
+
: "\n\nNo test runs linked to this plan.";
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{ type: "text", text: header + runsBlock },
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: JSON.stringify({
|
|
87
|
+
test_plan: plan,
|
|
88
|
+
linked_test_runs: runs,
|
|
89
|
+
status_summary: statusSummary,
|
|
90
|
+
total_test_cases: totalCases,
|
|
91
|
+
}, null, 2),
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
return formatAxiosError(err, "Failed to fetch test plan");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Schema for listing folders in a BrowserStack Test Management project.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ListFoldersSchema: z.ZodObject<{
|
|
8
|
+
project_identifier: z.ZodString;
|
|
9
|
+
parent_id: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
p: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export type ListFoldersArgs = z.infer<typeof ListFoldersSchema>;
|
|
13
|
+
/**
|
|
14
|
+
* Lists folders (or sub-folders) for a project in BrowserStack Test Management.
|
|
15
|
+
*/
|
|
16
|
+
export declare function listFolders(args: ListFoldersArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { formatAxiosError } from "../../lib/error.js";
|
|
4
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
|
+
/**
|
|
7
|
+
* Schema for listing folders in a BrowserStack Test Management project.
|
|
8
|
+
*/
|
|
9
|
+
export const ListFoldersSchema = z.object({
|
|
10
|
+
project_identifier: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Identifier of the project to fetch folders from (starts with PR- followed by a number)."),
|
|
13
|
+
parent_id: z
|
|
14
|
+
.number()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("If provided, list sub-folders under this parent folder id. If omitted, lists top-level folders."),
|
|
17
|
+
p: z.number().optional().describe("Page number."),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Lists folders (or sub-folders) for a project in BrowserStack Test Management.
|
|
21
|
+
*/
|
|
22
|
+
export async function listFolders(args, config) {
|
|
23
|
+
try {
|
|
24
|
+
const params = new URLSearchParams();
|
|
25
|
+
if (args.p !== undefined)
|
|
26
|
+
params.append("p", args.p.toString());
|
|
27
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
28
|
+
const projectId = encodeURIComponent(args.project_identifier);
|
|
29
|
+
// GET /api/v2/projects/{projectIdentifier}/folders
|
|
30
|
+
// or /api/v2/projects/{projectIdentifier}/folders/{parent_id}/sub-folders
|
|
31
|
+
const path = args.parent_id !== undefined
|
|
32
|
+
? `folders/${args.parent_id}/sub-folders`
|
|
33
|
+
: `folders`;
|
|
34
|
+
const url = `${tmBaseUrl}/api/v2/projects/${projectId}/${path}?${params.toString()}`;
|
|
35
|
+
const authString = getBrowserStackAuth(config);
|
|
36
|
+
const [username, password] = authString.split(":");
|
|
37
|
+
const resp = await apiClient.get({
|
|
38
|
+
url,
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const folders = resp.data?.folders ?? [];
|
|
44
|
+
const info = resp.data?.info ?? {};
|
|
45
|
+
const count = info.count ?? folders.length;
|
|
46
|
+
if (folders.length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: args.parent_id !== undefined
|
|
52
|
+
? `No sub-folders found under folder ${args.parent_id} in project ${args.project_identifier}.`
|
|
53
|
+
: `No folders found in project ${args.project_identifier}.`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const summary = folders
|
|
59
|
+
.map((f) => `• [id=${f.id}] ${f.name} — ${f.cases_count} case(s), ${f.sub_folders_count} sub-folder(s)${f.parent_id ? ` (parent=${f.parent_id})` : ""}`)
|
|
60
|
+
.join("\n");
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: `Found ${count} folder(s) in project ${args.project_identifier}:\n\n${summary}`,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: JSON.stringify(folders, null, 2),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
return formatAxiosError(err, "Failed to list folders");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -13,7 +13,7 @@ export const ListTestCasesSchema = z.object({
|
|
|
13
13
|
folder_id: z
|
|
14
14
|
.string()
|
|
15
15
|
.optional()
|
|
16
|
-
.describe("If provided, only return cases in this folder."),
|
|
16
|
+
.describe("Optional. If provided, only return test cases in this folder. If omitted, returns all test cases in the project. Folder ids can be discovered via listFolders."),
|
|
17
17
|
case_type: z
|
|
18
18
|
.string()
|
|
19
19
|
.optional()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Schema for listing test plans in a BrowserStack Test Management project.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ListTestPlansSchema: z.ZodObject<{
|
|
8
|
+
project_identifier: z.ZodString;
|
|
9
|
+
p: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type ListTestPlansArgs = z.infer<typeof ListTestPlansSchema>;
|
|
12
|
+
/**
|
|
13
|
+
* Lists test plans for a project in BrowserStack Test Management.
|
|
14
|
+
*/
|
|
15
|
+
export declare function listTestPlans(args: ListTestPlansArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { formatAxiosError } from "../../lib/error.js";
|
|
4
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
|
+
/**
|
|
7
|
+
* Schema for listing test plans in a BrowserStack Test Management project.
|
|
8
|
+
*/
|
|
9
|
+
export const ListTestPlansSchema = z.object({
|
|
10
|
+
project_identifier: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Identifier of the project to fetch test plans from (starts with PR- followed by a number)."),
|
|
13
|
+
p: z.number().optional().describe("Page number."),
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Lists test plans for a project in BrowserStack Test Management.
|
|
17
|
+
*/
|
|
18
|
+
export async function listTestPlans(args, config) {
|
|
19
|
+
try {
|
|
20
|
+
const params = new URLSearchParams();
|
|
21
|
+
if (args.p !== undefined)
|
|
22
|
+
params.append("p", args.p.toString());
|
|
23
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
24
|
+
const projectId = encodeURIComponent(args.project_identifier);
|
|
25
|
+
const url = `${tmBaseUrl}/api/v2/projects/${projectId}/test-plans?${params.toString()}`;
|
|
26
|
+
const authString = getBrowserStackAuth(config);
|
|
27
|
+
const [username, password] = authString.split(":");
|
|
28
|
+
const resp = await apiClient.get({
|
|
29
|
+
url,
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const data = resp.data;
|
|
35
|
+
if (!data.success) {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `Failed to list test plans: ${JSON.stringify(data)}`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
isError: true,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const plans = data.test_plans ?? [];
|
|
47
|
+
const info = data.info ?? {};
|
|
48
|
+
const count = info.count ?? plans.length;
|
|
49
|
+
if (plans.length === 0) {
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: `No test plans found in project ${args.project_identifier}.`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const summary = plans
|
|
60
|
+
.map((p) => `• ${p.identifier}: ${p.name} [${p.active_state}] — ${p.test_runs_count?.active ?? 0} active / ${p.test_runs_count?.closed ?? 0} closed run(s)`)
|
|
61
|
+
.join("\n");
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `Found ${count} test plan(s) in project ${args.project_identifier}:\n\n${summary}`,
|
|
67
|
+
},
|
|
68
|
+
{ type: "text", text: JSON.stringify(plans, null, 2) },
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return formatAxiosError(err, "Failed to list test plans");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -11,6 +11,14 @@ export interface TestCaseUpdateRequest {
|
|
|
11
11
|
step: string;
|
|
12
12
|
result: string;
|
|
13
13
|
}>;
|
|
14
|
+
owner?: string;
|
|
15
|
+
priority?: string;
|
|
16
|
+
case_type?: string;
|
|
17
|
+
automation_status?: string;
|
|
18
|
+
status?: string;
|
|
19
|
+
tags?: string[];
|
|
20
|
+
issues?: string[];
|
|
21
|
+
custom_fields?: Record<string, string | number | boolean>;
|
|
14
22
|
}
|
|
15
23
|
export declare const UpdateTestCaseSchema: z.ZodObject<{
|
|
16
24
|
project_identifier: z.ZodString;
|
|
@@ -22,6 +30,14 @@ export declare const UpdateTestCaseSchema: z.ZodObject<{
|
|
|
22
30
|
step: z.ZodString;
|
|
23
31
|
result: z.ZodString;
|
|
24
32
|
}, z.core.$strip>>>;
|
|
33
|
+
owner: z.ZodOptional<z.ZodString>;
|
|
34
|
+
priority: z.ZodOptional<z.ZodString>;
|
|
35
|
+
case_type: z.ZodOptional<z.ZodString>;
|
|
36
|
+
automation_status: z.ZodOptional<z.ZodString>;
|
|
37
|
+
status: z.ZodOptional<z.ZodString>;
|
|
38
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
39
|
+
issues: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
40
|
+
custom_fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
25
41
|
}, z.core.$strip>;
|
|
26
42
|
/**
|
|
27
43
|
* Updates an existing test case in BrowserStack Test Management.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { apiClient } from "../../lib/apiClient.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
|
-
import { projectIdentifierToId } from "./TCG-utils/api.js";
|
|
4
|
+
import { fetchFormFields, projectIdentifierToId } from "./TCG-utils/api.js";
|
|
5
5
|
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
6
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
7
7
|
import logger from "../../logger.js";
|
|
@@ -28,26 +28,148 @@ export const UpdateTestCaseSchema = z.object({
|
|
|
28
28
|
}))
|
|
29
29
|
.optional()
|
|
30
30
|
.describe("Updated list of test case steps with expected results."),
|
|
31
|
+
owner: z
|
|
32
|
+
.string()
|
|
33
|
+
.email()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Email of the test case owner."),
|
|
36
|
+
priority: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Updated priority. Accepts either display name (e.g. 'Medium', 'Critical', 'High', 'Low') or internal name (e.g. 'medium'). Valid values are per-project and discoverable via the form-fields endpoint."),
|
|
40
|
+
case_type: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Updated test case type. Accepts either display name (e.g. 'Functional', 'Regression', 'Smoke & Sanity') or internal name (e.g. 'functional', 'smoke_sanity'). Valid values are per-project."),
|
|
44
|
+
automation_status: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Updated automation status. Use internal name such as 'not_automated', 'automated', 'automation_not_required', 'cannot_be_automated', or 'obsolete'."),
|
|
48
|
+
status: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Updated review status of the test case (e.g. 'active', 'draft', 'in_review', 'outdated', 'rejected')."),
|
|
52
|
+
tags: z
|
|
53
|
+
.array(z.string())
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Replacement list of tags for the test case."),
|
|
56
|
+
issues: z
|
|
57
|
+
.array(z.string())
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Replacement list of linked Jira/Asana/Azure issue IDs for the test case."),
|
|
60
|
+
custom_fields: z
|
|
61
|
+
.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Map of custom field name/id to value. Valid field names and value types are per-project; discover them via the project's form fields."),
|
|
31
64
|
});
|
|
65
|
+
/**
|
|
66
|
+
* Build a normalizer for a default field's accepted value.
|
|
67
|
+
* The TM PATCH endpoint accepts different casings for different default
|
|
68
|
+
* fields (Title-Case display name for priority/case_type, snake_case
|
|
69
|
+
* internal_name for automation_status). We accept either from the caller
|
|
70
|
+
* and emit the form the API actually wants.
|
|
71
|
+
*
|
|
72
|
+
* Returns undefined when no matching option is found — callers should
|
|
73
|
+
* pass the raw value through so the backend can surface its own error.
|
|
74
|
+
*/
|
|
75
|
+
function normalizeDefaultFieldValue(fieldValues, input, emit) {
|
|
76
|
+
const normalized = input.toLowerCase().trim();
|
|
77
|
+
const match = fieldValues.find((v) => (v.internal_name ?? "").toLowerCase() === normalized ||
|
|
78
|
+
(v.name ?? "").toLowerCase() === normalized);
|
|
79
|
+
if (!match)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (emit === "name")
|
|
82
|
+
return match.name;
|
|
83
|
+
return match.internal_name ?? match.name;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Normalise default-field inputs (priority/case_type/automation_status) to
|
|
87
|
+
* what the TM PATCH endpoint accepts. Fetches the project's form-fields
|
|
88
|
+
* on demand; on failure, returns inputs unchanged and lets the backend
|
|
89
|
+
* surface validation errors.
|
|
90
|
+
*/
|
|
91
|
+
async function normalizeDefaultFields(params, config) {
|
|
92
|
+
const needsLookup = params.priority !== undefined ||
|
|
93
|
+
params.case_type !== undefined ||
|
|
94
|
+
params.automation_status !== undefined;
|
|
95
|
+
if (!needsLookup)
|
|
96
|
+
return {};
|
|
97
|
+
try {
|
|
98
|
+
const numericProjectId = await projectIdentifierToId(params.project_identifier, config);
|
|
99
|
+
const { default_fields } = await fetchFormFields(numericProjectId, config);
|
|
100
|
+
const out = {};
|
|
101
|
+
if (params.priority !== undefined) {
|
|
102
|
+
out.priority =
|
|
103
|
+
normalizeDefaultFieldValue(default_fields?.priority?.values ?? [], params.priority, "name") ?? params.priority;
|
|
104
|
+
}
|
|
105
|
+
if (params.case_type !== undefined) {
|
|
106
|
+
out.case_type =
|
|
107
|
+
normalizeDefaultFieldValue(default_fields?.case_type?.values ?? [], params.case_type, "name") ?? params.case_type;
|
|
108
|
+
}
|
|
109
|
+
if (params.automation_status !== undefined) {
|
|
110
|
+
// automation_status.values have null internal_name and the internal
|
|
111
|
+
// name is actually held in `value` (see API inspection). Accept
|
|
112
|
+
// either the display name or the internal snake_case form.
|
|
113
|
+
const values = default_fields?.automation_status?.values ?? [];
|
|
114
|
+
const input = params.automation_status.toLowerCase().trim();
|
|
115
|
+
const match = values.find((v) => (v.value ?? "").toLowerCase() === input ||
|
|
116
|
+
(v.name ?? "").toLowerCase() === input);
|
|
117
|
+
out.automation_status = match?.value ?? params.automation_status;
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
logger.warn("Failed to normalize default field values; passing through as given: %s", err instanceof Error ? err.message : String(err));
|
|
123
|
+
return {
|
|
124
|
+
priority: params.priority,
|
|
125
|
+
case_type: params.case_type,
|
|
126
|
+
automation_status: params.automation_status,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
32
130
|
/**
|
|
33
131
|
* Updates an existing test case in BrowserStack Test Management.
|
|
34
132
|
*/
|
|
35
133
|
export async function updateTestCase(params, config) {
|
|
36
134
|
const authString = getBrowserStackAuth(config);
|
|
37
135
|
const [username, password] = authString.split(":");
|
|
38
|
-
// Build the request body with only the fields to update
|
|
39
136
|
const testCaseBody = {};
|
|
40
|
-
if (params.name !== undefined)
|
|
137
|
+
if (params.name !== undefined)
|
|
41
138
|
testCaseBody.name = params.name;
|
|
42
|
-
|
|
43
|
-
if (params.description !== undefined) {
|
|
139
|
+
if (params.description !== undefined)
|
|
44
140
|
testCaseBody.description = params.description;
|
|
45
|
-
|
|
46
|
-
if (params.preconditions !== undefined) {
|
|
141
|
+
if (params.preconditions !== undefined)
|
|
47
142
|
testCaseBody.preconditions = params.preconditions;
|
|
48
|
-
|
|
49
|
-
if (params.test_case_steps !== undefined) {
|
|
143
|
+
if (params.test_case_steps !== undefined)
|
|
50
144
|
testCaseBody.steps = params.test_case_steps;
|
|
145
|
+
if (params.owner !== undefined)
|
|
146
|
+
testCaseBody.owner = params.owner;
|
|
147
|
+
if (params.status !== undefined)
|
|
148
|
+
testCaseBody.status = params.status;
|
|
149
|
+
if (params.tags !== undefined)
|
|
150
|
+
testCaseBody.tags = params.tags;
|
|
151
|
+
if (params.issues !== undefined)
|
|
152
|
+
testCaseBody.issues = params.issues;
|
|
153
|
+
if (params.custom_fields !== undefined)
|
|
154
|
+
testCaseBody.custom_fields = params.custom_fields;
|
|
155
|
+
// Default fields need value normalization (see notes above the helper).
|
|
156
|
+
const normalized = await normalizeDefaultFields(params, config);
|
|
157
|
+
if (normalized.priority !== undefined)
|
|
158
|
+
testCaseBody.priority = normalized.priority;
|
|
159
|
+
if (normalized.case_type !== undefined)
|
|
160
|
+
testCaseBody.case_type = normalized.case_type;
|
|
161
|
+
if (normalized.automation_status !== undefined)
|
|
162
|
+
testCaseBody.automation_status = normalized.automation_status;
|
|
163
|
+
if (Object.keys(testCaseBody).length === 0) {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: "No updatable fields provided. Pass at least one of: name, description, preconditions, test_case_steps, owner, priority, case_type, automation_status, status, tags, issues, custom_fields.",
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
51
173
|
}
|
|
52
174
|
const body = { test_case: testCaseBody };
|
|
53
175
|
try {
|
|
@@ -80,7 +202,7 @@ export async function updateTestCase(params, config) {
|
|
|
80
202
|
{
|
|
81
203
|
type: "text",
|
|
82
204
|
text: `Test case successfully updated:
|
|
83
|
-
|
|
205
|
+
|
|
84
206
|
**Test Case Details:**
|
|
85
207
|
- **ID**: ${tc.identifier}
|
|
86
208
|
- **Name**: ${tc.title}
|
|
@@ -88,6 +210,7 @@ export async function updateTestCase(params, config) {
|
|
|
88
210
|
- **Case Type**: ${tc.case_type}
|
|
89
211
|
- **Priority**: ${tc.priority}
|
|
90
212
|
- **Status**: ${tc.status}
|
|
213
|
+
- **Automation Status**: ${tc.automation_status ?? "N/A"}
|
|
91
214
|
|
|
92
215
|
**View on BrowserStack Dashboard:**
|
|
93
216
|
https://test-management.browserstack.com/projects/${projectId}/folders/${tc.folder_id}/test-cases/${tc.identifier}
|
|
@@ -5,6 +5,7 @@ import { CreateProjFoldSchema } from "./testmanagement-utils/create-project-fold
|
|
|
5
5
|
import { TestCaseCreateRequest } from "./testmanagement-utils/create-testcase.js";
|
|
6
6
|
import { TestCaseUpdateRequest } from "./testmanagement-utils/update-testcase.js";
|
|
7
7
|
import { ListTestCasesSchema } from "./testmanagement-utils/list-testcases.js";
|
|
8
|
+
import { ListFoldersSchema } from "./testmanagement-utils/list-folders.js";
|
|
8
9
|
import { CreateTestRunSchema } from "./testmanagement-utils/create-testrun.js";
|
|
9
10
|
import { ListTestRunsSchema } from "./testmanagement-utils/list-testruns.js";
|
|
10
11
|
import { UpdateTestRunSchema } from "./testmanagement-utils/update-testrun.js";
|
|
@@ -12,6 +13,8 @@ import { AddTestResultSchema } from "./testmanagement-utils/add-test-result.js";
|
|
|
12
13
|
import { UploadFileSchema } from "./testmanagement-utils/upload-file.js";
|
|
13
14
|
import { CreateTestCasesFromFileSchema } from "./testmanagement-utils/TCG-utils/types.js";
|
|
14
15
|
import { CreateLCAStepsSchema } from "./testmanagement-utils/create-lca-steps.js";
|
|
16
|
+
import { ListTestPlansSchema } from "./testmanagement-utils/list-testplans.js";
|
|
17
|
+
import { GetTestPlanSchema } from "./testmanagement-utils/get-testplan.js";
|
|
15
18
|
import { BrowserStackConfig } from "../lib/types.js";
|
|
16
19
|
/**
|
|
17
20
|
* Wrapper to call createProjectOrFolder util.
|
|
@@ -29,6 +32,10 @@ export declare function updateTestCaseTool(args: TestCaseUpdateRequest, config:
|
|
|
29
32
|
* Lists test cases in a project with optional filters (status, priority, custom fields, etc.)
|
|
30
33
|
*/
|
|
31
34
|
export declare function listTestCasesTool(args: z.infer<typeof ListTestCasesSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Lists folders in a project (or sub-folders under a parent folder).
|
|
37
|
+
*/
|
|
38
|
+
export declare function listFoldersTool(args: z.infer<typeof ListFoldersSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
32
39
|
/**
|
|
33
40
|
* Creates a test run in BrowserStack Test Management.
|
|
34
41
|
*/
|
|
@@ -59,6 +66,14 @@ export declare function createTestCasesFromFileTool(args: z.infer<typeof CreateT
|
|
|
59
66
|
* Creates LCA (Low Code Automation) steps for a test case in BrowserStack Test Management.
|
|
60
67
|
*/
|
|
61
68
|
export declare function createLCAStepsTool(args: z.infer<typeof CreateLCAStepsSchema>, context: any, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Lists test plans in a project.
|
|
71
|
+
*/
|
|
72
|
+
export declare function listTestPlansTool(args: z.infer<typeof ListTestPlansSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
73
|
+
/**
|
|
74
|
+
* Fetches a test plan by identifier, with its linked runs and a derived status summary.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getTestPlanTool(args: z.infer<typeof GetTestPlanSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
62
77
|
/**
|
|
63
78
|
* Registers both project/folder and test-case tools.
|
|
64
79
|
*/
|
|
@@ -4,6 +4,7 @@ import { createProjectOrFolder, CreateProjFoldSchema, } from "./testmanagement-u
|
|
|
4
4
|
import { createTestCase as createTestCaseAPI, sanitizeArgs, CreateTestCaseSchema, } from "./testmanagement-utils/create-testcase.js";
|
|
5
5
|
import { updateTestCase as updateTestCaseAPI, UpdateTestCaseSchema, } from "./testmanagement-utils/update-testcase.js";
|
|
6
6
|
import { listTestCases, ListTestCasesSchema, } from "./testmanagement-utils/list-testcases.js";
|
|
7
|
+
import { listFolders, ListFoldersSchema, } from "./testmanagement-utils/list-folders.js";
|
|
7
8
|
import { CreateTestRunSchema, createTestRun, } from "./testmanagement-utils/create-testrun.js";
|
|
8
9
|
import { ListTestRunsSchema, listTestRuns, } from "./testmanagement-utils/list-testruns.js";
|
|
9
10
|
import { UpdateTestRunSchema, updateTestRun, } from "./testmanagement-utils/update-testrun.js";
|
|
@@ -12,6 +13,8 @@ import { UploadFileSchema, uploadFile, } from "./testmanagement-utils/upload-fil
|
|
|
12
13
|
import { createTestCasesFromFile } from "./testmanagement-utils/testcase-from-file.js";
|
|
13
14
|
import { CreateTestCasesFromFileSchema } from "./testmanagement-utils/TCG-utils/types.js";
|
|
14
15
|
import { createLCASteps, CreateLCAStepsSchema, } from "./testmanagement-utils/create-lca-steps.js";
|
|
16
|
+
import { listTestPlans, ListTestPlansSchema, } from "./testmanagement-utils/list-testplans.js";
|
|
17
|
+
import { getTestPlan, GetTestPlanSchema, } from "./testmanagement-utils/get-testplan.js";
|
|
15
18
|
//TODO: Moving the traceMCP and catch block to the parent(server) function
|
|
16
19
|
/**
|
|
17
20
|
* Wrapper to call createProjectOrFolder util.
|
|
@@ -102,6 +105,27 @@ export async function listTestCasesTool(args, config, server) {
|
|
|
102
105
|
};
|
|
103
106
|
}
|
|
104
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Lists folders in a project (or sub-folders under a parent folder).
|
|
110
|
+
*/
|
|
111
|
+
export async function listFoldersTool(args, config, server) {
|
|
112
|
+
try {
|
|
113
|
+
trackMCP("listFolders", server.server.getClientVersion(), undefined, config);
|
|
114
|
+
return await listFolders(args, config);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
trackMCP("listFolders", server.server.getClientVersion(), err, config);
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: `Failed to list folders: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
isError: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
105
129
|
/**
|
|
106
130
|
* Creates a test run in BrowserStack Test Management.
|
|
107
131
|
*/
|
|
@@ -251,6 +275,50 @@ export async function createLCAStepsTool(args, context, config, server) {
|
|
|
251
275
|
};
|
|
252
276
|
}
|
|
253
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Lists test plans in a project.
|
|
280
|
+
*/
|
|
281
|
+
export async function listTestPlansTool(args, config, server) {
|
|
282
|
+
try {
|
|
283
|
+
trackMCP("listTestPlans", server.server.getClientVersion(), undefined, config);
|
|
284
|
+
return await listTestPlans(args, config);
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
logger.error("Failed to list test plans: %s", err);
|
|
288
|
+
trackMCP("listTestPlans", server.server.getClientVersion(), err, config);
|
|
289
|
+
return {
|
|
290
|
+
content: [
|
|
291
|
+
{
|
|
292
|
+
type: "text",
|
|
293
|
+
text: `Failed to list test plans: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
isError: true,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Fetches a test plan by identifier, with its linked runs and a derived status summary.
|
|
302
|
+
*/
|
|
303
|
+
export async function getTestPlanTool(args, config, server) {
|
|
304
|
+
try {
|
|
305
|
+
trackMCP("getTestPlan", server.server.getClientVersion(), undefined, config);
|
|
306
|
+
return await getTestPlan(args, config);
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
logger.error("Failed to fetch test plan: %s", err);
|
|
310
|
+
trackMCP("getTestPlan", server.server.getClientVersion(), err, config);
|
|
311
|
+
return {
|
|
312
|
+
content: [
|
|
313
|
+
{
|
|
314
|
+
type: "text",
|
|
315
|
+
text: `Failed to fetch test plan: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
isError: true,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
254
322
|
/**
|
|
255
323
|
* Registers both project/folder and test-case tools.
|
|
256
324
|
*/
|
|
@@ -258,8 +326,9 @@ export default function addTestManagementTools(server, config) {
|
|
|
258
326
|
const tools = {};
|
|
259
327
|
tools.createProjectOrFolder = server.tool("createProjectOrFolder", "Create a project and/or folder in BrowserStack Test Management.", CreateProjFoldSchema.shape, (args) => createProjectOrFolderTool(args, config, server));
|
|
260
328
|
tools.createTestCase = server.tool("createTestCase", "Use this tool to create a test case in BrowserStack Test Management.", CreateTestCaseSchema.shape, (args) => createTestCaseTool(args, config, server));
|
|
261
|
-
tools.updateTestCase = server.tool("updateTestCase", "
|
|
262
|
-
tools.listTestCases = server.tool("listTestCases", "List test cases in a project
|
|
329
|
+
tools.updateTestCase = server.tool("updateTestCase", "Update an existing test case in BrowserStack Test Management. Any subset of the following fields may be changed: name, description, preconditions, test_case_steps, owner, priority, case_type, automation_status, status, tags, issues, custom_fields. Only the supplied fields are modified.", UpdateTestCaseSchema.shape, (args) => updateTestCaseTool(args, config, server));
|
|
330
|
+
tools.listTestCases = server.tool("listTestCases", "List test cases in a project, optionally scoped to a specific folder. Omit folder_id to list all test cases in the project; provide folder_id (discoverable via listFolders) to list only that folder's cases. Supports filters: case_type, priority, pagination.", ListTestCasesSchema.shape, (args) => listTestCasesTool(args, config, server));
|
|
331
|
+
tools.listFolders = server.tool("listFolders", "List folders in a BrowserStack Test Management project, returning each folder's id and name (plus case counts and sub-folder counts). Pass parent_id to list sub-folders under a specific folder instead of top-level folders.", ListFoldersSchema.shape, (args) => listFoldersTool(args, config, server));
|
|
263
332
|
tools.createTestRun = server.tool("createTestRun", "Create a test run in BrowserStack Test Management.", CreateTestRunSchema.shape, (args) => createTestRunTool(args, config, server));
|
|
264
333
|
tools.listTestRuns = server.tool("listTestRuns", "List test runs in a project with optional filters (date ranges, assignee, state, etc.)", ListTestRunsSchema.shape, (args) => listTestRunsTool(args, config, server));
|
|
265
334
|
tools.updateTestRun = server.tool("updateTestRun", "Update a test run in BrowserStack Test Management.", UpdateTestRunSchema.shape, (args) => updateTestRunTool(args, config, server));
|
|
@@ -267,5 +336,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
267
336
|
tools.uploadProductRequirementFile = server.tool("uploadProductRequirementFile", "Upload files (e.g., PDRs, PDFs) to BrowserStack Test Management and retrieve a file mapping ID. This is utilized for generating test cases from files and is part of the Test Case Generator AI Agent in BrowserStack.", UploadFileSchema.shape, (args) => uploadProductRequirementFileTool(args, config, server));
|
|
268
337
|
tools.createTestCasesFromFile = server.tool("createTestCasesFromFile", "Generate test cases from a file in BrowserStack Test Management using the Test Case Generator AI Agent.", CreateTestCasesFromFileSchema.shape, (args, context) => createTestCasesFromFileTool(args, context, config, server));
|
|
269
338
|
tools.createLCASteps = server.tool("createLCASteps", "Generate Low Code Automation (LCA) steps for a test case in BrowserStack Test Management using the Low Code Automation Agent.", CreateLCAStepsSchema.shape, (args, context) => createLCAStepsTool(args, context, config, server));
|
|
339
|
+
tools.listTestPlans = server.tool("listTestPlans", "List test plans in a BrowserStack Test Management project. Returns each plan's identifier (TP-*), name, status, description, dates, and active/closed test-run counts. Supports pagination.", ListTestPlansSchema.shape, (args) => listTestPlansTool(args, config, server));
|
|
340
|
+
tools.getTestPlan = server.tool("getTestPlan", "Fetch a test plan by identifier (TP-*) from BrowserStack Test Management. Returns plan metadata, the full list of linked test runs, total test-case count across runs, and a status summary — suitable for generating test documentation or QA status reports.", GetTestPlanSchema.shape, (args) => getTestPlanTool(args, config, server));
|
|
270
341
|
return tools;
|
|
271
342
|
}
|