@browserstack/mcp-server 1.2.17 → 1.2.18-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/selfheal-utils/fetch-test-code.d.ts +60 -0
- package/dist/tools/selfheal-utils/fetch-test-code.js +258 -0
- package/dist/tools/selfheal-utils/selfheal.d.ts +45 -1
- package/dist/tools/selfheal-utils/selfheal.js +121 -25
- package/dist/tools/selfheal.d.ts +27 -2
- package/dist/tools/selfheal.js +471 -12
- package/dist/tools/testmanagement-utils/TCG-utils/api.d.ts +9 -0
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +14 -0
- package/dist/tools/testmanagement-utils/create-testcase.d.ts +2 -0
- package/dist/tools/testmanagement-utils/create-testcase.js +26 -2
- package/dist/tools/testmanagement-utils/get-sub-testplan.d.ts +20 -0
- package/dist/tools/testmanagement-utils/get-sub-testplan.js +149 -0
- package/dist/tools/testmanagement-utils/list-sub-testplans.d.ts +17 -0
- package/dist/tools/testmanagement-utils/list-sub-testplans.js +83 -0
- package/dist/tools/testmanagement-utils/update-testcase.js +1 -21
- package/dist/tools/testmanagement.d.ts +10 -0
- package/dist/tools/testmanagement.js +48 -0
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
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 sub-test-plan by identifier under a parent test
|
|
8
|
+
* plan, including its linked test runs.
|
|
9
|
+
*/
|
|
10
|
+
export const GetSubTestPlanSchema = z.object({
|
|
11
|
+
project_identifier: z.string().describe("Project identifier (PR-*)."),
|
|
12
|
+
parent_test_plan_identifier: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe("Parent test plan identifier (TP-*)."),
|
|
15
|
+
sub_test_plan_identifier: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe("Sub-test-plan identifier (STP-*)."),
|
|
18
|
+
});
|
|
19
|
+
// Validates host parity with the TM base URL to prevent SSRF if `links.test_runs`
|
|
20
|
+
// ever resolves to an absolute URL pointing elsewhere.
|
|
21
|
+
//
|
|
22
|
+
// The constructed fallback uses the flat shape `/test-plans/{STP}/test-runs`
|
|
23
|
+
// (not nested under the parent) because that is the form the v2 API itself
|
|
24
|
+
// returns in `sub_test_plan.links.test_runs`. The parent identifier deliberately
|
|
25
|
+
// does NOT appear in this URL.
|
|
26
|
+
function resolveEnrichmentUrl(linksTestRuns, tmBaseUrl, projectId, subPlanId) {
|
|
27
|
+
if (typeof linksTestRuns === "string" && linksTestRuns.length > 0) {
|
|
28
|
+
if (linksTestRuns.startsWith("/")) {
|
|
29
|
+
// Relative path — safe to join under tmBaseUrl.
|
|
30
|
+
return `${tmBaseUrl}${linksTestRuns}`;
|
|
31
|
+
}
|
|
32
|
+
if (linksTestRuns.startsWith("http://") ||
|
|
33
|
+
linksTestRuns.startsWith("https://")) {
|
|
34
|
+
try {
|
|
35
|
+
const linkUrl = new URL(linksTestRuns);
|
|
36
|
+
const baseUrl = new URL(tmBaseUrl);
|
|
37
|
+
if (linkUrl.host === baseUrl.host) {
|
|
38
|
+
return linksTestRuns;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Fall through to constructed fallback.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return `${tmBaseUrl}/api/v2/projects/${projectId}/test-plans/${subPlanId}/test-runs`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fetches a sub-test-plan by identifier and best-effort-enriches it with its
|
|
50
|
+
* linked test runs. The enrichment call is fail-soft: any failure (thrown
|
|
51
|
+
* AxiosError or non-success response) is swallowed into an empty runs list
|
|
52
|
+
* without flipping `isError` on the primary response.
|
|
53
|
+
*/
|
|
54
|
+
export async function getSubTestPlan(args, config) {
|
|
55
|
+
try {
|
|
56
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
57
|
+
const projectId = encodeURIComponent(args.project_identifier);
|
|
58
|
+
const parentPlanId = encodeURIComponent(args.parent_test_plan_identifier);
|
|
59
|
+
const subPlanId = encodeURIComponent(args.sub_test_plan_identifier);
|
|
60
|
+
const authString = getBrowserStackAuth(config);
|
|
61
|
+
const [username, password] = authString.split(":");
|
|
62
|
+
const authHeader = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
|
63
|
+
const planResp = await apiClient.get({
|
|
64
|
+
url: `${tmBaseUrl}/api/v2/projects/${projectId}/test-plans/${parentPlanId}/sub-test-plans/${subPlanId}`,
|
|
65
|
+
headers: { Authorization: authHeader },
|
|
66
|
+
});
|
|
67
|
+
if (!planResp.data?.success) {
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Failed to fetch sub-test-plan: ${JSON.stringify(planResp.data)}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const plan = planResp.data.sub_test_plan;
|
|
79
|
+
const enrichmentUrl = resolveEnrichmentUrl(plan.links?.test_runs, tmBaseUrl, projectId, subPlanId);
|
|
80
|
+
let runs = [];
|
|
81
|
+
try {
|
|
82
|
+
const runsResp = await apiClient.get({
|
|
83
|
+
url: enrichmentUrl,
|
|
84
|
+
headers: { Authorization: authHeader },
|
|
85
|
+
});
|
|
86
|
+
if (runsResp.data?.success) {
|
|
87
|
+
runs = runsResp.data.test_runs ?? [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Enrichment is best-effort — leave runs empty.
|
|
92
|
+
}
|
|
93
|
+
const statusSummary = {};
|
|
94
|
+
let totalCases = 0;
|
|
95
|
+
for (const run of runs) {
|
|
96
|
+
statusSummary[run.run_state] = (statusSummary[run.run_state] ?? 0) + 1;
|
|
97
|
+
totalCases += run.test_cases_count ?? 0;
|
|
98
|
+
}
|
|
99
|
+
const tagsLine = Array.isArray(plan.tags) && plan.tags.length > 0
|
|
100
|
+
? `Tags: ${plan.tags.join(", ")}`
|
|
101
|
+
: null;
|
|
102
|
+
const issuesLine = Array.isArray(plan.issues) && plan.issues.length > 0
|
|
103
|
+
? `Issues: ${plan.issues.join(", ")}`
|
|
104
|
+
: null;
|
|
105
|
+
const header = [
|
|
106
|
+
`Sub-Test-Plan ${plan.identifier}: ${plan.name}`,
|
|
107
|
+
`Parent plan: ${plan.parent_plan_id}`,
|
|
108
|
+
`Status: ${plan.active_state}`,
|
|
109
|
+
plan.description ? `Description: ${plan.description}` : null,
|
|
110
|
+
tagsLine,
|
|
111
|
+
issuesLine,
|
|
112
|
+
plan.start_date || plan.end_date
|
|
113
|
+
? `Dates: ${plan.start_date ?? "—"} → ${plan.end_date ?? "—"}`
|
|
114
|
+
: null,
|
|
115
|
+
`Linked runs: ${runs.length} (plan counts — active ${plan.test_runs_count?.active ?? 0} / closed ${plan.test_runs_count?.closed ?? 0})`,
|
|
116
|
+
`Total test cases across runs: ${totalCases}`,
|
|
117
|
+
Object.keys(statusSummary).length > 0
|
|
118
|
+
? `Run-state breakdown: ${Object.entries(statusSummary)
|
|
119
|
+
.map(([s, n]) => `${s}=${n}`)
|
|
120
|
+
.join(", ")}`
|
|
121
|
+
: null,
|
|
122
|
+
]
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.join("\n");
|
|
125
|
+
const runsBlock = runs.length
|
|
126
|
+
? "\n\nLinked test runs:\n" +
|
|
127
|
+
runs
|
|
128
|
+
.map((r) => `• ${r.identifier}: ${r.name} [${r.run_state}] — ${r.test_cases_count} case(s)${r.assignee ? ` (assignee: ${r.assignee})` : ""}`)
|
|
129
|
+
.join("\n")
|
|
130
|
+
: "\n\nNo test runs linked to this sub-plan.";
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{ type: "text", text: header + runsBlock },
|
|
134
|
+
{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: JSON.stringify({
|
|
137
|
+
sub_test_plan: plan,
|
|
138
|
+
linked_test_runs: runs,
|
|
139
|
+
status_summary: statusSummary,
|
|
140
|
+
total_test_cases: totalCases,
|
|
141
|
+
}, null, 2),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return formatAxiosError(err, "Failed to fetch sub-test-plan");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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 sub-test-plans under a parent test plan in a BrowserStack
|
|
6
|
+
* Test Management project.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ListSubTestPlansSchema: z.ZodObject<{
|
|
9
|
+
project_identifier: z.ZodString;
|
|
10
|
+
parent_test_plan_identifier: z.ZodString;
|
|
11
|
+
p: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type ListSubTestPlansArgs = z.infer<typeof ListSubTestPlansSchema>;
|
|
14
|
+
/**
|
|
15
|
+
* Lists sub-test-plans under a parent test plan in BrowserStack Test Management.
|
|
16
|
+
*/
|
|
17
|
+
export declare function listSubTestPlans(args: ListSubTestPlansArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
@@ -0,0 +1,83 @@
|
|
|
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 sub-test-plans under a parent test plan in a BrowserStack
|
|
8
|
+
* Test Management project.
|
|
9
|
+
*/
|
|
10
|
+
export const ListSubTestPlansSchema = z.object({
|
|
11
|
+
project_identifier: z.string().describe("Project identifier (PR-*)."),
|
|
12
|
+
parent_test_plan_identifier: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe("Parent test plan identifier (TP-*)."),
|
|
15
|
+
p: z.number().optional().describe("Page number."),
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Lists sub-test-plans under a parent test plan in BrowserStack Test Management.
|
|
19
|
+
*/
|
|
20
|
+
export async function listSubTestPlans(args, config) {
|
|
21
|
+
try {
|
|
22
|
+
const params = new URLSearchParams();
|
|
23
|
+
if (args.p !== undefined)
|
|
24
|
+
params.append("p", args.p.toString());
|
|
25
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
26
|
+
const projectId = encodeURIComponent(args.project_identifier);
|
|
27
|
+
const parentPlanId = encodeURIComponent(args.parent_test_plan_identifier);
|
|
28
|
+
const url = `${tmBaseUrl}/api/v2/projects/${projectId}/test-plans/${parentPlanId}/sub-test-plans?${params.toString()}`;
|
|
29
|
+
const authString = getBrowserStackAuth(config);
|
|
30
|
+
const [username, password] = authString.split(":");
|
|
31
|
+
const resp = await apiClient.get({
|
|
32
|
+
url,
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const data = resp.data;
|
|
38
|
+
if (!data.success) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: `Failed to list sub-test-plans: ${JSON.stringify(data)}`,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const plans = data.sub_test_plans ?? [];
|
|
50
|
+
const info = data.info ?? {};
|
|
51
|
+
const count = info.count ?? plans.length;
|
|
52
|
+
if (plans.length === 0) {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: `No sub-test-plans found under ${args.parent_test_plan_identifier} in project ${args.project_identifier}.`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const summary = plans
|
|
63
|
+
.map((p) => {
|
|
64
|
+
const tagsSegment = Array.isArray(p.tags) && p.tags.length > 0
|
|
65
|
+
? ` — tags: [${p.tags.join(", ")}]`
|
|
66
|
+
: "";
|
|
67
|
+
return `• ${p.identifier}: ${p.name} [${p.active_state}] — ${p.test_runs_count?.active ?? 0} active / ${p.test_runs_count?.closed ?? 0} closed run(s)${tagsSegment}`;
|
|
68
|
+
})
|
|
69
|
+
.join("\n");
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `Found ${count} sub-test-plan(s) under ${args.parent_test_plan_identifier} in project ${args.project_identifier}:\n\n${summary}`,
|
|
75
|
+
},
|
|
76
|
+
{ type: "text", text: JSON.stringify(plans, null, 2) },
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return formatAxiosError(err, "Failed to list sub-test-plans");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -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 { fetchFormFields, projectIdentifierToId } from "./TCG-utils/api.js";
|
|
4
|
+
import { fetchFormFields, normalizeDefaultFieldValue, 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";
|
|
@@ -62,26 +62,6 @@ export const UpdateTestCaseSchema = z.object({
|
|
|
62
62
|
.optional()
|
|
63
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."),
|
|
64
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
65
|
/**
|
|
86
66
|
* Normalise default-field inputs (priority/case_type/automation_status) to
|
|
87
67
|
* what the TM PATCH endpoint accepts. Fetches the project's form-fields
|
|
@@ -15,6 +15,8 @@ import { CreateTestCasesFromFileSchema } from "./testmanagement-utils/TCG-utils/
|
|
|
15
15
|
import { CreateLCAStepsSchema } from "./testmanagement-utils/create-lca-steps.js";
|
|
16
16
|
import { ListTestPlansSchema } from "./testmanagement-utils/list-testplans.js";
|
|
17
17
|
import { GetTestPlanSchema } from "./testmanagement-utils/get-testplan.js";
|
|
18
|
+
import { ListSubTestPlansSchema } from "./testmanagement-utils/list-sub-testplans.js";
|
|
19
|
+
import { GetSubTestPlanSchema } from "./testmanagement-utils/get-sub-testplan.js";
|
|
18
20
|
import { BrowserStackConfig } from "../lib/types.js";
|
|
19
21
|
/**
|
|
20
22
|
* Wrapper to call createProjectOrFolder util.
|
|
@@ -74,6 +76,14 @@ export declare function listTestPlansTool(args: z.infer<typeof ListTestPlansSche
|
|
|
74
76
|
* Fetches a test plan by identifier, with its linked runs and a derived status summary.
|
|
75
77
|
*/
|
|
76
78
|
export declare function getTestPlanTool(args: z.infer<typeof GetTestPlanSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
79
|
+
/**
|
|
80
|
+
* Lists sub-test-plans under a parent test plan.
|
|
81
|
+
*/
|
|
82
|
+
export declare function listSubTestPlansTool(args: z.infer<typeof ListSubTestPlansSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
83
|
+
/**
|
|
84
|
+
* Fetches a sub-test-plan by identifier, with its linked runs and a derived status summary.
|
|
85
|
+
*/
|
|
86
|
+
export declare function getSubTestPlanTool(args: z.infer<typeof GetSubTestPlanSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
77
87
|
/**
|
|
78
88
|
* Registers both project/folder and test-case tools.
|
|
79
89
|
*/
|
|
@@ -15,6 +15,8 @@ import { CreateTestCasesFromFileSchema } from "./testmanagement-utils/TCG-utils/
|
|
|
15
15
|
import { createLCASteps, CreateLCAStepsSchema, } from "./testmanagement-utils/create-lca-steps.js";
|
|
16
16
|
import { listTestPlans, ListTestPlansSchema, } from "./testmanagement-utils/list-testplans.js";
|
|
17
17
|
import { getTestPlan, GetTestPlanSchema, } from "./testmanagement-utils/get-testplan.js";
|
|
18
|
+
import { listSubTestPlans, ListSubTestPlansSchema, } from "./testmanagement-utils/list-sub-testplans.js";
|
|
19
|
+
import { getSubTestPlan, GetSubTestPlanSchema, } from "./testmanagement-utils/get-sub-testplan.js";
|
|
18
20
|
//TODO: Moving the traceMCP and catch block to the parent(server) function
|
|
19
21
|
/**
|
|
20
22
|
* Wrapper to call createProjectOrFolder util.
|
|
@@ -319,6 +321,50 @@ export async function getTestPlanTool(args, config, server) {
|
|
|
319
321
|
};
|
|
320
322
|
}
|
|
321
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Lists sub-test-plans under a parent test plan.
|
|
326
|
+
*/
|
|
327
|
+
export async function listSubTestPlansTool(args, config, server) {
|
|
328
|
+
try {
|
|
329
|
+
trackMCP("listSubTestPlans", server.server.getClientVersion(), undefined, config);
|
|
330
|
+
return await listSubTestPlans(args, config);
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
logger.error("Failed to list sub-test-plans: %s", err);
|
|
334
|
+
trackMCP("listSubTestPlans", server.server.getClientVersion(), err, config);
|
|
335
|
+
return {
|
|
336
|
+
content: [
|
|
337
|
+
{
|
|
338
|
+
type: "text",
|
|
339
|
+
text: `Failed to list sub-test-plans: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
isError: true,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Fetches a sub-test-plan by identifier, with its linked runs and a derived status summary.
|
|
348
|
+
*/
|
|
349
|
+
export async function getSubTestPlanTool(args, config, server) {
|
|
350
|
+
try {
|
|
351
|
+
trackMCP("getSubTestPlan", server.server.getClientVersion(), undefined, config);
|
|
352
|
+
return await getSubTestPlan(args, config);
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
logger.error("Failed to fetch sub-test-plan: %s", err);
|
|
356
|
+
trackMCP("getSubTestPlan", server.server.getClientVersion(), err, config);
|
|
357
|
+
return {
|
|
358
|
+
content: [
|
|
359
|
+
{
|
|
360
|
+
type: "text",
|
|
361
|
+
text: `Failed to fetch sub-test-plan: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
isError: true,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
322
368
|
/**
|
|
323
369
|
* Registers both project/folder and test-case tools.
|
|
324
370
|
*/
|
|
@@ -338,5 +384,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
338
384
|
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
385
|
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
386
|
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));
|
|
387
|
+
tools.listSubTestPlans = server.tool("listSubTestPlans", "List sub-test-plans under a parent test plan (TP-*) in a Test Management project. Supports pagination.", ListSubTestPlansSchema.shape, (args) => listSubTestPlansTool(args, config, server));
|
|
388
|
+
tools.getSubTestPlan = server.tool("getSubTestPlan", "Fetch a sub-test-plan (STP-*) under a parent plan (TP-*). Returns metadata and linked test runs.", GetSubTestPlanSchema.shape, (args) => getSubTestPlanTool(args, config, server));
|
|
341
389
|
return tools;
|
|
342
390
|
}
|