From 33c66ecb8d1bd05c7a75851d353063cf18f4fddd Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Mon, 19 May 2025 12:33:54 +0530 Subject: [PATCH 1/2] Disabling O11Y get run failures tool due to confilct --- src/index.ts | 2 - src/tools/observability.ts | 84 ------------------------ tests/tools/observability.test.ts | 104 ------------------------------ 3 files changed, 190 deletions(-) delete mode 100644 src/tools/observability.ts delete mode 100644 tests/tools/observability.test.ts diff --git a/src/index.ts b/src/index.ts index 4ead9c1..c1655ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,6 @@ import "dotenv/config"; import logger from "./logger.js"; import addSDKTools from "./tools/bstack-sdk.js"; import addAppLiveTools from "./tools/applive.js"; -import addObservabilityTools from "./tools/observability.js"; import addBrowserLiveTools from "./tools/live.js"; import addAccessibilityTools from "./tools/accessibility.js"; import addTestManagementTools from "./tools/testmanagement.js"; @@ -20,7 +19,6 @@ function registerTools(server: McpServer) { addSDKTools(server); addAppLiveTools(server); addBrowserLiveTools(server); - addObservabilityTools(server); addAccessibilityTools(server); addTestManagementTools(server); addAppAutomationTools(server); diff --git a/src/tools/observability.ts b/src/tools/observability.ts deleted file mode 100644 index b5ad127..0000000 --- a/src/tools/observability.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { z } from "zod"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { getLatestO11YBuildInfo } from "../lib/api.js"; -import { trackMCP } from "../lib/instrumentation.js"; -import logger from "../logger.js"; - -export async function getFailuresInLastRun( - buildName: string, - projectName: string, -): Promise { - const buildsData = await getLatestO11YBuildInfo(buildName, projectName); - - const observabilityUrl = buildsData.observability_url; - if (!observabilityUrl) { - throw new Error( - "No observability URL found in build data, this is likely because the build is not yet available on BrowserStack Observability.", - ); - } - - let overview = "No overview available"; - if (buildsData.unique_errors?.overview?.insight) { - overview = buildsData.unique_errors.overview.insight; - } - - let details = "No error details available"; - if (buildsData.unique_errors?.top_unique_errors?.length > 0) { - details = buildsData.unique_errors.top_unique_errors - .map((error: any) => error.error) - .filter(Boolean) - .join("\n"); - } - - return { - content: [ - { - type: "text", - text: `Observability URL: ${observabilityUrl}\nOverview: ${overview}\nError Details: ${details}`, - }, - ], - }; -} - -export default function addObservabilityTools(server: McpServer) { - server.tool( - "getFailuresInLastRun", - "Use this tool to debug failures in the last run of the test suite on BrowserStack. Use only when browserstack.yml file is present in the project root.", - { - buildName: z - .string() - .describe( - "Name of the build to get failures for. This is the 'build' key in the browserstack.yml file. If not sure, ask the user for the build name.", - ), - projectName: z - .string() - .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.", - ), - }, - async (args) => { - try { - trackMCP("getFailuresInLastRun", server.server.getClientVersion()!); - return await getFailuresInLastRun(args.buildName, args.projectName); - } catch (error) { - logger.error("Failed to get failures in the last run: %s", error); - trackMCP( - "getFailuresInLastRun", - server.server.getClientVersion()!, - error, - ); - return { - content: [ - { - type: "text", - text: `Failed to get failures in the last run. Error: ${error}. Please open an issue on GitHub if this is an issue with BrowserStack`, - isError: true, - }, - ], - isError: true, - }; - } - }, - ); -} diff --git a/tests/tools/observability.test.ts b/tests/tools/observability.test.ts deleted file mode 100644 index 78e3cac..0000000 --- a/tests/tools/observability.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { getFailuresInLastRun } from '../../src/tools/observability'; -import { getLatestO11YBuildInfo } from '../../src/lib/api'; -import { beforeEach, it, expect, describe, vi, Mock } from 'vitest' - -// Mock the API module -vi.mock('../../src/lib/api', () => ({ - getLatestO11YBuildInfo: vi.fn(), -})); - -vi.mock('../../src/lib/instrumentation', () => ({ - trackMCP: vi.fn() -})); - -describe('getFailuresInLastRun', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - const validBuildData = { - observability_url: 'https://observability.browserstack.com/123', - unique_errors: { - overview: { - insight: 'Test insight message' - }, - top_unique_errors: [ - { error: 'Error 1' }, - { error: 'Error 2' } - ] - } - }; - - it('should successfully retrieve failures for a valid build', async () => { - (getLatestO11YBuildInfo as Mock).mockResolvedValue(validBuildData); - - const result = await getFailuresInLastRun('test-build', 'test-project'); - - expect(getLatestO11YBuildInfo).toHaveBeenCalledWith('test-build', 'test-project'); - expect(result.content[0].text).toContain('https://observability.browserstack.com/123'); - expect(result.content[0].text).toContain('Test insight message'); - expect(result.content[0].text).toContain('Error 1'); - expect(result.content[0].text).toContain('Error 2'); - }); - - it('should handle missing observability URL', async () => { - (getLatestO11YBuildInfo as Mock).mockResolvedValue({ - ...validBuildData, - observability_url: null - }); - - await expect(getFailuresInLastRun('test-build', 'test-project')) - .rejects.toThrow('No observability URL found in build data'); - }); - - it('should handle missing overview insight', async () => { - (getLatestO11YBuildInfo as Mock).mockResolvedValue({ - ...validBuildData, - unique_errors: { - ...validBuildData.unique_errors, - overview: {} - } - }); - - const result = await getFailuresInLastRun('test-build', 'test-project'); - expect(result.content[0].text).toContain('No overview available'); - }); - - it('should handle missing error details', async () => { - (getLatestO11YBuildInfo as Mock).mockResolvedValue({ - ...validBuildData, - unique_errors: { - ...validBuildData.unique_errors, - top_unique_errors: [] - } - }); - - const result = await getFailuresInLastRun('test-build', 'test-project'); - expect(result.content[0].text).toContain('No error details available'); - }); - - it('should handle API errors', async () => { - (getLatestO11YBuildInfo as Mock).mockRejectedValue(new Error('API Error')); - - await expect(getFailuresInLastRun('test-build', 'test-project')) - .rejects.toThrow('API Error'); - }); - - it('should handle empty build data', async () => { - (getLatestO11YBuildInfo as Mock).mockResolvedValue({}); - - await expect(getFailuresInLastRun('test-build', 'test-project')) - .rejects.toThrow('No observability URL found in build data'); - }); - - it('should handle partial build data', async () => { - (getLatestO11YBuildInfo as Mock).mockResolvedValue({ - observability_url: 'https://observability.browserstack.com/123', - unique_errors: {} - }); - - const result = await getFailuresInLastRun('test-build', 'test-project'); - expect(result.content[0].text).toContain('No overview available'); - expect(result.content[0].text).toContain('No error details available'); - }); -}); \ No newline at end of file From bb7e639ad5019102151708a283c61ef790324036 Mon Sep 17 00:00:00 2001 From: tech-sushant Date: Mon, 19 May 2025 13:52:44 +0530 Subject: [PATCH 2/2] Restoring files --- src/tools/observability.ts | 84 ++++++++++++++++++++++++ tests/tools/observability.test.ts | 104 ++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 src/tools/observability.ts create mode 100644 tests/tools/observability.test.ts diff --git a/src/tools/observability.ts b/src/tools/observability.ts new file mode 100644 index 0000000..b5ad127 --- /dev/null +++ b/src/tools/observability.ts @@ -0,0 +1,84 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { getLatestO11YBuildInfo } from "../lib/api.js"; +import { trackMCP } from "../lib/instrumentation.js"; +import logger from "../logger.js"; + +export async function getFailuresInLastRun( + buildName: string, + projectName: string, +): Promise { + const buildsData = await getLatestO11YBuildInfo(buildName, projectName); + + const observabilityUrl = buildsData.observability_url; + if (!observabilityUrl) { + throw new Error( + "No observability URL found in build data, this is likely because the build is not yet available on BrowserStack Observability.", + ); + } + + let overview = "No overview available"; + if (buildsData.unique_errors?.overview?.insight) { + overview = buildsData.unique_errors.overview.insight; + } + + let details = "No error details available"; + if (buildsData.unique_errors?.top_unique_errors?.length > 0) { + details = buildsData.unique_errors.top_unique_errors + .map((error: any) => error.error) + .filter(Boolean) + .join("\n"); + } + + return { + content: [ + { + type: "text", + text: `Observability URL: ${observabilityUrl}\nOverview: ${overview}\nError Details: ${details}`, + }, + ], + }; +} + +export default function addObservabilityTools(server: McpServer) { + server.tool( + "getFailuresInLastRun", + "Use this tool to debug failures in the last run of the test suite on BrowserStack. Use only when browserstack.yml file is present in the project root.", + { + buildName: z + .string() + .describe( + "Name of the build to get failures for. This is the 'build' key in the browserstack.yml file. If not sure, ask the user for the build name.", + ), + projectName: z + .string() + .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.", + ), + }, + async (args) => { + try { + trackMCP("getFailuresInLastRun", server.server.getClientVersion()!); + return await getFailuresInLastRun(args.buildName, args.projectName); + } catch (error) { + logger.error("Failed to get failures in the last run: %s", error); + trackMCP( + "getFailuresInLastRun", + server.server.getClientVersion()!, + error, + ); + return { + content: [ + { + type: "text", + text: `Failed to get failures in the last run. Error: ${error}. Please open an issue on GitHub if this is an issue with BrowserStack`, + isError: true, + }, + ], + isError: true, + }; + } + }, + ); +} diff --git a/tests/tools/observability.test.ts b/tests/tools/observability.test.ts new file mode 100644 index 0000000..78e3cac --- /dev/null +++ b/tests/tools/observability.test.ts @@ -0,0 +1,104 @@ +import { getFailuresInLastRun } from '../../src/tools/observability'; +import { getLatestO11YBuildInfo } from '../../src/lib/api'; +import { beforeEach, it, expect, describe, vi, Mock } from 'vitest' + +// Mock the API module +vi.mock('../../src/lib/api', () => ({ + getLatestO11YBuildInfo: vi.fn(), +})); + +vi.mock('../../src/lib/instrumentation', () => ({ + trackMCP: vi.fn() +})); + +describe('getFailuresInLastRun', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const validBuildData = { + observability_url: 'https://observability.browserstack.com/123', + unique_errors: { + overview: { + insight: 'Test insight message' + }, + top_unique_errors: [ + { error: 'Error 1' }, + { error: 'Error 2' } + ] + } + }; + + it('should successfully retrieve failures for a valid build', async () => { + (getLatestO11YBuildInfo as Mock).mockResolvedValue(validBuildData); + + const result = await getFailuresInLastRun('test-build', 'test-project'); + + expect(getLatestO11YBuildInfo).toHaveBeenCalledWith('test-build', 'test-project'); + expect(result.content[0].text).toContain('https://observability.browserstack.com/123'); + expect(result.content[0].text).toContain('Test insight message'); + expect(result.content[0].text).toContain('Error 1'); + expect(result.content[0].text).toContain('Error 2'); + }); + + it('should handle missing observability URL', async () => { + (getLatestO11YBuildInfo as Mock).mockResolvedValue({ + ...validBuildData, + observability_url: null + }); + + await expect(getFailuresInLastRun('test-build', 'test-project')) + .rejects.toThrow('No observability URL found in build data'); + }); + + it('should handle missing overview insight', async () => { + (getLatestO11YBuildInfo as Mock).mockResolvedValue({ + ...validBuildData, + unique_errors: { + ...validBuildData.unique_errors, + overview: {} + } + }); + + const result = await getFailuresInLastRun('test-build', 'test-project'); + expect(result.content[0].text).toContain('No overview available'); + }); + + it('should handle missing error details', async () => { + (getLatestO11YBuildInfo as Mock).mockResolvedValue({ + ...validBuildData, + unique_errors: { + ...validBuildData.unique_errors, + top_unique_errors: [] + } + }); + + const result = await getFailuresInLastRun('test-build', 'test-project'); + expect(result.content[0].text).toContain('No error details available'); + }); + + it('should handle API errors', async () => { + (getLatestO11YBuildInfo as Mock).mockRejectedValue(new Error('API Error')); + + await expect(getFailuresInLastRun('test-build', 'test-project')) + .rejects.toThrow('API Error'); + }); + + it('should handle empty build data', async () => { + (getLatestO11YBuildInfo as Mock).mockResolvedValue({}); + + await expect(getFailuresInLastRun('test-build', 'test-project')) + .rejects.toThrow('No observability URL found in build data'); + }); + + it('should handle partial build data', async () => { + (getLatestO11YBuildInfo as Mock).mockResolvedValue({ + observability_url: 'https://observability.browserstack.com/123', + unique_errors: {} + }); + + const result = await getFailuresInLastRun('test-build', 'test-project'); + expect(result.content[0].text).toContain('No overview available'); + expect(result.content[0].text).toContain('No error details available'); + }); +}); \ No newline at end of file