@browserstack/mcp-server 1.2.27-beta.1 → 1.2.27-beta.3

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.
@@ -1,3 +1,4 @@
1
1
  import { TestStatus, FailedTestInfo, TestDetails } from "./types.js";
2
2
  export declare function getTestIds(buildId: string, authString: string, status?: TestStatus, includeFailureDetail?: boolean): Promise<FailedTestInfo[]>;
3
- export declare function extractFailedTestIds(hierarchy: TestDetails[], status?: TestStatus, includeFailureDetail?: boolean): FailedTestInfo[];
3
+ export declare function extractTestIds(hierarchy: TestDetails[], status?: TestStatus, includeFailureDetail?: boolean): FailedTestInfo[];
4
+ export declare const extractFailedTestIds: typeof extractTestIds;
@@ -1,12 +1,15 @@
1
1
  import logger from "../../logger.js";
2
2
  import { getAutomationBaseUrl } from "./constants.js";
3
+ import { TestStatus, } from "./types.js";
3
4
  // Cap on the failure summary line — keep the response payload lean (we never
4
5
  // return full stack traces into the MCP client's context window).
5
6
  const ERROR_SUMMARY_MAX = 200;
6
7
  export async function getTestIds(buildId, authString, status, includeFailureDetail = false) {
8
+ // No `status` → no `test_statuses` filter → the endpoint returns ALL tests
9
+ // (the default). A `status` narrows both the query and the extraction.
7
10
  const baseUrl = `${getAutomationBaseUrl()}/ext/v1/builds/${buildId}/testRuns`;
8
11
  let url = status ? `${baseUrl}?test_statuses=${status}` : baseUrl;
9
- let allFailedTests = [];
12
+ let allTests = [];
10
13
  let requestNumber = 0;
11
14
  // Construct Basic auth header
12
15
  const encodedCredentials = Buffer.from(authString).toString("base64");
@@ -24,10 +27,10 @@ export async function getTestIds(buildId, authString, status, includeFailureDeta
24
27
  throw new Error(`Failed to fetch test runs: ${response.status} ${response.statusText}`);
25
28
  }
26
29
  const data = (await response.json());
27
- // Extract failed IDs from current page
30
+ // Extract test IDs from the current page (all tests unless narrowed).
28
31
  if (data.hierarchy && data.hierarchy.length > 0) {
29
- const currentFailedTests = extractFailedTestIds(data.hierarchy, status, includeFailureDetail);
30
- allFailedTests = allFailedTests.concat(currentFailedTests);
32
+ const currentTests = extractTestIds(data.hierarchy, status, includeFailureDetail);
33
+ allTests = allTests.concat(currentTests);
31
34
  }
32
35
  // Check for pagination termination conditions
33
36
  if (!data.pagination?.has_next ||
@@ -42,44 +45,50 @@ export async function getTestIds(buildId, authString, status, includeFailureDeta
42
45
  params.test_statuses = status;
43
46
  url = `${baseUrl}?${new URLSearchParams(params).toString()}`;
44
47
  }
45
- // Return unique failed test IDs
46
- return allFailedTests;
48
+ return allTests;
47
49
  }
48
50
  catch (error) {
49
- logger.error("Error fetching failed tests:", error);
51
+ logger.error("Error fetching test runs:", error);
50
52
  throw error;
51
53
  }
52
54
  }
53
- export function extractFailedTestIds(hierarchy, status, includeFailureDetail = false) {
54
- let failedTests = [];
55
+ export function extractTestIds(hierarchy, status, includeFailureDetail = false) {
56
+ let tests = [];
55
57
  for (const node of hierarchy) {
56
- // Match on status alone the observability_url `details=<id>` check below
57
- // already filters to real test nodes (suite/hook nodes carry no status and
58
- // no such URL). Do NOT also require run_count: JUnit-uploaded builds report
59
- // run_count=0 even for genuinely failed tests, which would drop them all.
60
- if (node.details?.status === status) {
61
- if (node.details?.observability_url) {
62
- const idMatch = node.details.observability_url.match(/details=(\d+)/);
63
- if (idMatch) {
64
- const entry = {
65
- test_id: idMatch[1],
66
- test_name: node.display_name || `Test ${idMatch[1]}`,
67
- };
68
- if (includeFailureDetail) {
69
- const signature = buildFailureSignature(node.details);
70
- if (signature)
71
- entry.failure = signature;
72
- }
73
- failedTests.push(entry);
58
+ // Include every REAL test node. The observability_url `details=<id>` check
59
+ // already filters out suite/hook nodes (they carry no such URL). We do NOT
60
+ // require run_count: JUnit-uploaded builds report run_count=0 even for
61
+ // genuine tests, which would drop them all.
62
+ // Status filtering is optional: when `status` is omitted we return ALL
63
+ // tests (the default); when provided we narrow to that status.
64
+ const nodeStatus = node.details?.status;
65
+ const statusMatches = status === undefined || nodeStatus === status;
66
+ if (statusMatches && node.details?.observability_url) {
67
+ const idMatch = node.details.observability_url.match(/details=(\d+)/);
68
+ if (idMatch) {
69
+ const entry = {
70
+ test_id: idMatch[1],
71
+ test_name: node.display_name || `Test ${idMatch[1]}`,
72
+ status: nodeStatus,
73
+ };
74
+ // Failure signatures only exist for failed tests; include when asked.
75
+ if (includeFailureDetail && nodeStatus === TestStatus.FAILED) {
76
+ const signature = buildFailureSignature(node.details);
77
+ if (signature)
78
+ entry.failure = signature;
74
79
  }
80
+ tests.push(entry);
75
81
  }
76
82
  }
77
83
  if (node.children && node.children.length > 0) {
78
- failedTests = failedTests.concat(extractFailedTestIds(node.children, status, includeFailureDetail));
84
+ tests = tests.concat(extractTestIds(node.children, status, includeFailureDetail));
79
85
  }
80
86
  }
81
- return failedTests;
87
+ return tests;
82
88
  }
89
+ // Back-compat alias — prefer extractTestIds. Kept so existing imports/tests
90
+ // referencing the old name continue to resolve.
91
+ export const extractFailedTestIds = extractTestIds;
83
92
  // Build a trimmed failure signature from a test node's `details`. Returns
84
93
  // undefined when no signal is available so the field is simply omitted.
85
94
  function buildFailureSignature(details) {
@@ -28,6 +28,7 @@ export interface TestFailureSignature {
28
28
  export interface FailedTestInfo {
29
29
  test_id: number;
30
30
  test_name: string;
31
+ status?: TestStatus;
31
32
  failure?: TestFailureSignature;
32
33
  }
33
34
  export declare enum RCAState {
@@ -157,7 +157,7 @@ export default function addRCATools(server, config) {
157
157
  return handleMCPError("listBuildId", server, config, error);
158
158
  }
159
159
  });
160
- tools.listTestIds = server.tool("listTestIds", "List test IDs from a BrowserStack Automate build, optionally filtered by status", LIST_TEST_IDS_PARAMS, async (args) => {
160
+ tools.listTestIds = server.tool("listTestIds", "List all tests of a BrowserStack build (each with its status); optional status filter.", LIST_TEST_IDS_PARAMS, async (args) => {
161
161
  try {
162
162
  trackMCP("listTestIds", server.server.getClientVersion(), undefined, config);
163
163
  return await listTestIdsTool(args, config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserstack/mcp-server",
3
- "version": "1.2.27-beta.1",
3
+ "version": "1.2.27-beta.3",
4
4
  "description": "BrowserStack's Official MCP Server",
5
5
  "mcpName": "io.github.browserstack/mcp-server",
6
6
  "main": "dist/index.js",