@browserstack/mcp-server 1.2.24 → 1.2.25-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/testmanagement-utils/create-testcase.d.ts +3 -0
- package/dist/tools/testmanagement-utils/create-testcase.js +133 -12
- package/dist/tools/testmanagement-utils/list-templates.d.ts +20 -0
- package/dist/tools/testmanagement-utils/list-templates.js +78 -0
- package/dist/tools/testmanagement.d.ts +5 -0
- package/dist/tools/testmanagement.js +23 -0
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ export interface TestCaseCreateRequest {
|
|
|
25
25
|
automation_status?: string;
|
|
26
26
|
priority?: string;
|
|
27
27
|
template?: string;
|
|
28
|
+
template_id?: number;
|
|
28
29
|
}
|
|
29
30
|
export interface TestCaseResponse {
|
|
30
31
|
data: {
|
|
@@ -40,6 +41,7 @@ export interface TestCaseResponse {
|
|
|
40
41
|
}>;
|
|
41
42
|
tags: string[];
|
|
42
43
|
template: string;
|
|
44
|
+
template_id?: number;
|
|
43
45
|
description: string;
|
|
44
46
|
preconditions: string;
|
|
45
47
|
title: string;
|
|
@@ -76,6 +78,7 @@ export declare const CreateTestCaseSchema: z.ZodObject<{
|
|
|
76
78
|
automation_status: z.ZodOptional<z.ZodString>;
|
|
77
79
|
priority: z.ZodOptional<z.ZodString>;
|
|
78
80
|
template: z.ZodOptional<z.ZodString>;
|
|
81
|
+
template_id: z.ZodOptional<z.ZodNumber>;
|
|
79
82
|
}, z.core.$strip>;
|
|
80
83
|
export declare function sanitizeArgs(args: any): any;
|
|
81
84
|
export declare function createTestCase(params: TestCaseCreateRequest, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
@@ -69,7 +69,11 @@ export const CreateTestCaseSchema = z.object({
|
|
|
69
69
|
template: z
|
|
70
70
|
.string()
|
|
71
71
|
.optional()
|
|
72
|
-
.describe("
|
|
72
|
+
.describe("System template slug only: 'test_case_steps' or 'test_case_bdd'. For a custom template, use template_id instead."),
|
|
73
|
+
template_id: z
|
|
74
|
+
.number()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Numeric ID of a custom template (from listTestCaseTemplates); applies that template. Overrides 'template'."),
|
|
73
77
|
});
|
|
74
78
|
export function sanitizeArgs(args) {
|
|
75
79
|
const cleaned = { ...args };
|
|
@@ -83,6 +87,8 @@ export function sanitizeArgs(args) {
|
|
|
83
87
|
delete cleaned.automation_status;
|
|
84
88
|
if (cleaned.template === null)
|
|
85
89
|
delete cleaned.template;
|
|
90
|
+
if (cleaned.template_id === null)
|
|
91
|
+
delete cleaned.template_id;
|
|
86
92
|
if (cleaned.issue_tracker) {
|
|
87
93
|
if (cleaned.issue_tracker.name === undefined ||
|
|
88
94
|
cleaned.issue_tracker.host === undefined) {
|
|
@@ -107,24 +113,121 @@ async function normalizePriority(projectIdentifier, priority, config) {
|
|
|
107
113
|
return priority;
|
|
108
114
|
}
|
|
109
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Read a freshly-created test case back to learn which template was actually
|
|
118
|
+
* applied. The create response does not echo template_id, but the v1 search
|
|
119
|
+
* endpoint does. Returns undefined on any failure (caller then skips the
|
|
120
|
+
* verification warning rather than blocking the success path).
|
|
121
|
+
*/
|
|
122
|
+
async function fetchAppliedTemplateId(numericProjectId, identifier, config) {
|
|
123
|
+
try {
|
|
124
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
125
|
+
const resp = await apiClient.get({
|
|
126
|
+
url: `${tmBaseUrl}/api/v1/projects/${encodeURIComponent(numericProjectId)}/test-cases/search?q%5Bquery%5D=${encodeURIComponent(identifier)}`,
|
|
127
|
+
headers: {
|
|
128
|
+
"API-TOKEN": getBrowserStackAuth(config),
|
|
129
|
+
accept: "application/json, text/plain, */*",
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
const cases = resp.data?.test_cases ?? [];
|
|
133
|
+
const match = cases.find((c) => c.identifier === identifier);
|
|
134
|
+
return match?.template_id;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The v1 create endpoint (used when a template_id is requested) keys
|
|
142
|
+
* custom_fields by numeric field id with option *ids* — unlike the v2 endpoint,
|
|
143
|
+
* which keys by field name with option *values*. Translate the MCP's by-name
|
|
144
|
+
* shape into v1's by-id shape using the project's form fields. Best-effort:
|
|
145
|
+
* unknown fields/options pass through unchanged.
|
|
146
|
+
*/
|
|
147
|
+
async function toV1CustomFields(customFields, numericProjectId, config) {
|
|
148
|
+
let defs = [];
|
|
149
|
+
try {
|
|
150
|
+
const formFields = await fetchFormFields(numericProjectId, config);
|
|
151
|
+
defs = Array.isArray(formFields?.custom_fields)
|
|
152
|
+
? formFields.custom_fields
|
|
153
|
+
: [];
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return customFields;
|
|
157
|
+
}
|
|
158
|
+
const byName = new Map(defs.map((f) => [f.field_name, f]));
|
|
159
|
+
const out = {};
|
|
160
|
+
for (const [name, value] of Object.entries(customFields)) {
|
|
161
|
+
const def = byName.get(name);
|
|
162
|
+
if (!def) {
|
|
163
|
+
out[name] = value; // unknown field name — leave as-is
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const isOptionField = def.field_type === "field_dropdown" ||
|
|
167
|
+
def.field_type === "field_multi_dropdown";
|
|
168
|
+
if (isOptionField) {
|
|
169
|
+
const optionIdByValue = new Map();
|
|
170
|
+
for (const o of (def.option_values ?? [])) {
|
|
171
|
+
optionIdByValue.set(String(o.option_value), o.id);
|
|
172
|
+
}
|
|
173
|
+
const toOptionId = (v) => optionIdByValue.get(String(v)) ?? v;
|
|
174
|
+
out[String(def.id)] = Array.isArray(value)
|
|
175
|
+
? value.map(toOptionId)
|
|
176
|
+
: toOptionId(value);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
out[String(def.id)] = value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
110
184
|
export async function createTestCase(params, config) {
|
|
111
185
|
const testCaseParams = { ...params };
|
|
112
186
|
if (testCaseParams.priority !== undefined) {
|
|
113
187
|
testCaseParams.priority = await normalizePriority(params.project_identifier, testCaseParams.priority, config);
|
|
114
188
|
}
|
|
115
|
-
const body = { test_case: testCaseParams };
|
|
116
189
|
const authString = getBrowserStackAuth(config);
|
|
117
190
|
const [username, password] = authString.split(":");
|
|
118
191
|
try {
|
|
119
192
|
const tmBaseUrl = await getTMBaseURL(config);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
193
|
+
// The public v2 create endpoint silently drops template_id, so a specific
|
|
194
|
+
// (custom) template cannot be applied through it. The v1 create endpoint
|
|
195
|
+
// DOES honour template_id — but it needs the numeric project id, the folder
|
|
196
|
+
// in the body, API-TOKEN auth, and custom_fields keyed by id. Use v1 only
|
|
197
|
+
// when a template_id is requested; otherwise keep the proven v2 path so
|
|
198
|
+
// existing behaviour (incl. custom_fields by name) is unchanged.
|
|
199
|
+
let request;
|
|
200
|
+
if (testCaseParams.template_id !== undefined) {
|
|
201
|
+
const numericProjectId = await projectIdentifierToId(params.project_identifier, config);
|
|
202
|
+
const v1TestCase = { ...testCaseParams };
|
|
203
|
+
delete v1TestCase.project_identifier;
|
|
204
|
+
delete v1TestCase.folder_id;
|
|
205
|
+
delete v1TestCase.custom_fields;
|
|
206
|
+
v1TestCase.test_case_folder_id = Number(params.folder_id);
|
|
207
|
+
if (testCaseParams.custom_fields) {
|
|
208
|
+
v1TestCase.custom_fields = await toV1CustomFields(testCaseParams.custom_fields, numericProjectId, config);
|
|
209
|
+
}
|
|
210
|
+
request = {
|
|
211
|
+
url: `${tmBaseUrl}/api/v1/projects/${encodeURIComponent(numericProjectId)}/test-cases`,
|
|
212
|
+
headers: {
|
|
213
|
+
"Content-Type": "application/json",
|
|
214
|
+
"API-TOKEN": authString,
|
|
215
|
+
},
|
|
216
|
+
body: { folder_id: Number(params.folder_id), test_case: v1TestCase },
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
request = {
|
|
221
|
+
url: `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(params.project_identifier)}/folders/${encodeURIComponent(params.folder_id)}/test-cases`,
|
|
222
|
+
headers: {
|
|
223
|
+
"Content-Type": "application/json",
|
|
224
|
+
Authorization: "Basic " +
|
|
225
|
+
Buffer.from(`${username}:${password}`).toString("base64"),
|
|
226
|
+
},
|
|
227
|
+
body: { test_case: testCaseParams },
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const response = await apiClient.post(request);
|
|
128
231
|
const { data } = response.data;
|
|
129
232
|
if (!data.success) {
|
|
130
233
|
return {
|
|
@@ -140,15 +243,33 @@ export async function createTestCase(params, config) {
|
|
|
140
243
|
const tc = data.test_case;
|
|
141
244
|
const projectId = await projectIdentifierToId(params.project_identifier, config);
|
|
142
245
|
const content = [];
|
|
246
|
+
// A specific custom template is selected by numeric template_id. The create
|
|
247
|
+
// response does not echo template_id, so read the case back to learn which
|
|
248
|
+
// template was actually applied and warn on mismatch — the public create
|
|
249
|
+
// endpoint may silently ignore the requested template.
|
|
250
|
+
if (params.template_id !== undefined) {
|
|
251
|
+
const appliedId = tc.template_id !== undefined
|
|
252
|
+
? Number(tc.template_id)
|
|
253
|
+
: await fetchAppliedTemplateId(projectId, tc.identifier, config);
|
|
254
|
+
if (appliedId !== undefined && appliedId !== Number(params.template_id)) {
|
|
255
|
+
content.push({
|
|
256
|
+
type: "text",
|
|
257
|
+
text: `Warning: requested template_id ${params.template_id} was not applied — the test case uses template_id ${appliedId}. Confirm the id via listTestCaseTemplates and that the template is linked to this project.`,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
143
261
|
// The TM API silently ignores an unrecognized template slug and falls back
|
|
144
262
|
// to the default. Surface that instead of letting it pass as success.
|
|
145
|
-
|
|
263
|
+
// Note: the `template` slug only ever selects a SYSTEM template; a custom
|
|
264
|
+
// template must be selected with template_id.
|
|
265
|
+
if (params.template_id === undefined &&
|
|
266
|
+
params.template &&
|
|
146
267
|
tc.template &&
|
|
147
268
|
String(tc.template).toLowerCase() !==
|
|
148
269
|
String(params.template).toLowerCase()) {
|
|
149
270
|
content.push({
|
|
150
271
|
type: "text",
|
|
151
|
-
text: `Warning: requested template "${params.template}" was not applied — the test case was created with "${tc.template}".
|
|
272
|
+
text: `Warning: requested template "${params.template}" was not applied — the test case was created with "${tc.template}". The 'template' field accepts only the system slugs "test_case_steps" or "test_case_bdd"; for a custom template pass template_id (see listTestCaseTemplates).`,
|
|
152
273
|
});
|
|
153
274
|
}
|
|
154
275
|
content.push({
|
|
@@ -0,0 +1,20 @@
|
|
|
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-case templates in BrowserStack Test Management.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ListTemplatesSchema: z.ZodObject<{
|
|
8
|
+
name: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export type ListTemplatesArgs = z.infer<typeof ListTemplatesSchema>;
|
|
11
|
+
/**
|
|
12
|
+
* Lists test-case templates (group-wide) so callers can resolve a template
|
|
13
|
+
* name to the numeric template_id.
|
|
14
|
+
*
|
|
15
|
+
* Custom templates share a step_type (test_case_steps | test_case_bdd) with the
|
|
16
|
+
* system templates, so the slug cannot identify them — only the id can. The
|
|
17
|
+
* list is account-wide; a template must also be linked to the target project to
|
|
18
|
+
* be usable there.
|
|
19
|
+
*/
|
|
20
|
+
export declare function listTemplates(args: ListTemplatesArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
@@ -0,0 +1,78 @@
|
|
|
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-case templates in BrowserStack Test Management.
|
|
8
|
+
*/
|
|
9
|
+
export const ListTemplatesSchema = z.object({
|
|
10
|
+
name: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Case-insensitive substring filter on template name."),
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Lists test-case templates (group-wide) so callers can resolve a template
|
|
17
|
+
* name to the numeric template_id.
|
|
18
|
+
*
|
|
19
|
+
* Custom templates share a step_type (test_case_steps | test_case_bdd) with the
|
|
20
|
+
* system templates, so the slug cannot identify them — only the id can. The
|
|
21
|
+
* list is account-wide; a template must also be linked to the target project to
|
|
22
|
+
* be usable there.
|
|
23
|
+
*/
|
|
24
|
+
export async function listTemplates(args, config) {
|
|
25
|
+
try {
|
|
26
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
27
|
+
// Verified working with API-TOKEN auth (same surface as form-fields-v2).
|
|
28
|
+
const resp = await apiClient.get({
|
|
29
|
+
url: `${tmBaseUrl}/api/v1/admin-v2/settings/templates?entity_type=TestCase&paginated=false`,
|
|
30
|
+
headers: {
|
|
31
|
+
"API-TOKEN": getBrowserStackAuth(config),
|
|
32
|
+
accept: "application/json, text/plain, */*",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
let templates = resp.data?.templates ?? [];
|
|
36
|
+
if (args.name) {
|
|
37
|
+
const needle = args.name.toLowerCase();
|
|
38
|
+
templates = templates.filter((t) => (t.name ?? "").toLowerCase().includes(needle));
|
|
39
|
+
}
|
|
40
|
+
if (templates.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: args.name
|
|
46
|
+
? `No templates matching "${args.name}".`
|
|
47
|
+
: "No templates found.",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const summary = templates
|
|
53
|
+
.map((t) => `• [template_id=${t.id}] ${t.name} — step_type=${t.step_type}${t.is_system ? " (system)" : ""}${t.is_default ? " (default)" : ""}${t.enabled === false ? " (disabled)" : ""}`)
|
|
54
|
+
.join("\n");
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: `Found ${templates.length} template(s):\n\n${summary}`,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: JSON.stringify(templates.map((t) => ({
|
|
64
|
+
template_id: t.id,
|
|
65
|
+
name: t.name,
|
|
66
|
+
step_type: t.step_type,
|
|
67
|
+
is_system: t.is_system,
|
|
68
|
+
is_default: t.is_default,
|
|
69
|
+
enabled: t.enabled,
|
|
70
|
+
})), null, 2),
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return formatAxiosError(err, "Failed to list templates");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -6,6 +6,7 @@ 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
8
|
import { ListFoldersSchema } from "./testmanagement-utils/list-folders.js";
|
|
9
|
+
import { ListTemplatesSchema } from "./testmanagement-utils/list-templates.js";
|
|
9
10
|
import { CreateTestRunSchema } from "./testmanagement-utils/create-testrun.js";
|
|
10
11
|
import { ListTestRunsSchema } from "./testmanagement-utils/list-testruns.js";
|
|
11
12
|
import { UpdateTestRunSchema } from "./testmanagement-utils/update-testrun.js";
|
|
@@ -38,6 +39,10 @@ export declare function listTestCasesTool(args: z.infer<typeof ListTestCasesSche
|
|
|
38
39
|
* Lists folders in a project (or sub-folders under a parent folder).
|
|
39
40
|
*/
|
|
40
41
|
export declare function listFoldersTool(args: z.infer<typeof ListFoldersSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Lists test-case templates so callers can resolve a name to a template_id.
|
|
44
|
+
*/
|
|
45
|
+
export declare function listTemplatesTool(args: z.infer<typeof ListTemplatesSchema>, config: BrowserStackConfig, server: McpServer): Promise<CallToolResult>;
|
|
41
46
|
/**
|
|
42
47
|
* Creates a test run in BrowserStack Test Management.
|
|
43
48
|
*/
|
|
@@ -5,6 +5,7 @@ import { createTestCase as createTestCaseAPI, sanitizeArgs, CreateTestCaseSchema
|
|
|
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
7
|
import { listFolders, ListFoldersSchema, } from "./testmanagement-utils/list-folders.js";
|
|
8
|
+
import { listTemplates, ListTemplatesSchema, } from "./testmanagement-utils/list-templates.js";
|
|
8
9
|
import { CreateTestRunSchema, createTestRun, } from "./testmanagement-utils/create-testrun.js";
|
|
9
10
|
import { ListTestRunsSchema, listTestRuns, } from "./testmanagement-utils/list-testruns.js";
|
|
10
11
|
import { UpdateTestRunSchema, updateTestRun, } from "./testmanagement-utils/update-testrun.js";
|
|
@@ -128,6 +129,27 @@ export async function listFoldersTool(args, config, server) {
|
|
|
128
129
|
};
|
|
129
130
|
}
|
|
130
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Lists test-case templates so callers can resolve a name to a template_id.
|
|
134
|
+
*/
|
|
135
|
+
export async function listTemplatesTool(args, config, server) {
|
|
136
|
+
try {
|
|
137
|
+
trackMCP("listTestCaseTemplates", server.server.getClientVersion(), undefined, config);
|
|
138
|
+
return await listTemplates(args, config);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
trackMCP("listTestCaseTemplates", server.server.getClientVersion(), err, config);
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: `Failed to list templates: ${err instanceof Error ? err.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
isError: true,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
131
153
|
/**
|
|
132
154
|
* Creates a test run in BrowserStack Test Management.
|
|
133
155
|
*/
|
|
@@ -375,6 +397,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
375
397
|
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));
|
|
376
398
|
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));
|
|
377
399
|
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));
|
|
400
|
+
tools.listTestCaseTemplates = server.tool("listTestCaseTemplates", "List test-case templates with their numeric template_id. Use the id with createTestCase to apply a custom template (the 'template' slug only selects system templates).", ListTemplatesSchema.shape, (args) => listTemplatesTool(args, config, server));
|
|
378
401
|
tools.createTestRun = server.tool("createTestRun", "Create a test run in BrowserStack Test Management.", CreateTestRunSchema.shape, (args) => createTestRunTool(args, config, server));
|
|
379
402
|
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));
|
|
380
403
|
tools.updateTestRun = server.tool("updateTestRun", "Update a test run in BrowserStack Test Management.", UpdateTestRunSchema.shape, (args) => updateTestRunTool(args, config, server));
|