@browserstack/mcp-server 1.0.13 → 1.0.15

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.
Files changed (55) hide show
  1. package/README.md +22 -0
  2. package/dist/config.js +2 -6
  3. package/dist/index.js +29 -32
  4. package/dist/lib/api.js +3 -57
  5. package/dist/lib/constants.js +14 -0
  6. package/dist/lib/device-cache.js +32 -33
  7. package/dist/lib/error.js +3 -6
  8. package/dist/lib/fuzzy.js +1 -4
  9. package/dist/lib/inmemory-store.js +1 -0
  10. package/dist/lib/instrumentation.js +12 -18
  11. package/dist/lib/local.js +27 -35
  12. package/dist/lib/utils.js +29 -4
  13. package/dist/logger.js +4 -9
  14. package/dist/tools/accessibility.js +51 -21
  15. package/dist/tools/accessiblity-utils/report-fetcher.js +28 -0
  16. package/dist/tools/accessiblity-utils/report-parser.js +51 -0
  17. package/dist/tools/accessiblity-utils/scanner.js +80 -0
  18. package/dist/tools/appautomate-utils/appautomate.js +95 -0
  19. package/dist/tools/appautomate.js +116 -0
  20. package/dist/tools/applive-utils/fuzzy-search.js +3 -6
  21. package/dist/tools/applive-utils/start-session.js +14 -20
  22. package/dist/tools/applive-utils/upload-app.js +12 -51
  23. package/dist/tools/applive.js +18 -25
  24. package/dist/tools/automate-utils/fetch-screenshots.js +59 -0
  25. package/dist/tools/automate.js +44 -37
  26. package/dist/tools/bstack-sdk.js +14 -18
  27. package/dist/tools/failurelogs-utils/app-automate.js +88 -0
  28. package/dist/tools/failurelogs-utils/automate.js +97 -0
  29. package/dist/tools/getFailureLogs.js +173 -0
  30. package/dist/tools/live-utils/desktop-filter.js +8 -11
  31. package/dist/tools/live-utils/mobile-filter.js +7 -10
  32. package/dist/tools/live-utils/start-session.js +17 -23
  33. package/dist/tools/live-utils/types.js +2 -5
  34. package/dist/tools/live-utils/version-resolver.js +1 -4
  35. package/dist/tools/live.js +23 -29
  36. package/dist/tools/observability.js +12 -19
  37. package/dist/tools/sdk-utils/constants.js +3 -9
  38. package/dist/tools/sdk-utils/instructions.js +4 -9
  39. package/dist/tools/sdk-utils/types.js +1 -2
  40. package/dist/tools/testmanagement-utils/TCG-utils/api.js +259 -0
  41. package/dist/tools/testmanagement-utils/TCG-utils/config.js +5 -0
  42. package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +53 -0
  43. package/dist/tools/testmanagement-utils/TCG-utils/types.js +8 -0
  44. package/dist/tools/testmanagement-utils/add-test-result.js +18 -25
  45. package/dist/tools/testmanagement-utils/create-project-folder.js +21 -28
  46. package/dist/tools/testmanagement-utils/create-testcase.js +30 -38
  47. package/dist/tools/testmanagement-utils/create-testrun.js +23 -30
  48. package/dist/tools/testmanagement-utils/list-testcases.js +16 -23
  49. package/dist/tools/testmanagement-utils/list-testruns.js +13 -20
  50. package/dist/tools/testmanagement-utils/testcase-from-file.js +42 -0
  51. package/dist/tools/testmanagement-utils/update-testrun.js +16 -23
  52. package/dist/tools/testmanagement-utils/upload-file.js +101 -0
  53. package/dist/tools/testmanagement.js +98 -61
  54. package/package.json +13 -7
  55. package/dist/tools/accessiblity-utils/accessibility.js +0 -82
package/README.md CHANGED
@@ -84,6 +84,28 @@ Use the following prompts to run/debug/fix your **automated tests** on BrowserSt
84
84
  - Run tests written in Jest, Playwright, Selenium, and more on BrowserStack's [Test Platform](https://www.browserstack.com/test-platform)
85
85
  - **Accessibility Testing**: Ensure WCAG and ADA compliance with our [Accessibility Testing](https://www.browserstack.com/accessibility-testing) tool
86
86
 
87
+
88
+ ### 📋 Test Management
89
+
90
+ Use the following prompts to utilise capabilities of BrowserStack's [Test Management](https://www.browserstack.com/test-management) with MCP server.
91
+
92
+ ```bash
93
+ # Create project & folder structure
94
+ "create new Test management project named My Demo Project with two sub folders - Login & Checkout"
95
+
96
+ # Add test cases
97
+ "add invalid login test case in Test Management project named My Demo Project"
98
+
99
+ # List added test cases
100
+ "list high priority Login test cases from Test Management project - My Demo Project"
101
+
102
+ # Create test run
103
+ "create a test run for Login tests from Test Management project - My Demo Project"
104
+
105
+ # Update test results
106
+ "update test results as passed for Login tests test run from My Demo Project"
107
+ ```
108
+
87
109
  ## 🛠️ Installation
88
110
 
89
111
  1. **Create a BrowserStack Account**
package/dist/config.js CHANGED
@@ -1,11 +1,8 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Config = void 0;
4
1
  if (!process.env.BROWSERSTACK_ACCESS_KEY ||
5
2
  !process.env.BROWSERSTACK_USERNAME) {
6
3
  throw new Error("Unable to start MCP server. Please set the BROWSERSTACK_ACCESS_KEY and BROWSERSTACK_USERNAME environment variables. Go to https://www.browserstack.com/accounts/profile/details to access them");
7
4
  }
8
- class Config {
5
+ export class Config {
9
6
  browserstackUsername;
10
7
  browserstackAccessKey;
11
8
  DEV_MODE;
@@ -15,6 +12,5 @@ class Config {
15
12
  this.DEV_MODE = DEV_MODE;
16
13
  }
17
14
  }
18
- exports.Config = Config;
19
15
  const config = new Config(process.env.BROWSERSTACK_USERNAME, process.env.BROWSERSTACK_ACCESS_KEY, process.env.DEV_MODE === "true");
20
- exports.default = config;
16
+ export default config;
package/dist/index.js CHANGED
@@ -1,47 +1,44 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
- const package_json_1 = __importDefault(require("../package.json"));
10
- require("dotenv/config");
11
- const logger_1 = __importDefault(require("./logger"));
12
- const bstack_sdk_1 = __importDefault(require("./tools/bstack-sdk"));
13
- const applive_1 = __importDefault(require("./tools/applive"));
14
- const observability_1 = __importDefault(require("./tools/observability"));
15
- const live_1 = __importDefault(require("./tools/live"));
16
- const accessibility_1 = __importDefault(require("./tools/accessibility"));
17
- const automate_1 = __importDefault(require("./tools/automate"));
18
- const testmanagement_1 = __importDefault(require("./tools/testmanagement"));
19
- const instrumentation_1 = require("./lib/instrumentation");
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import packageJson from "../package.json" with { type: "json" };
5
+ import "dotenv/config";
6
+ import logger from "./logger.js";
7
+ import addSDKTools from "./tools/bstack-sdk.js";
8
+ import addAppLiveTools from "./tools/applive.js";
9
+ import addBrowserLiveTools from "./tools/live.js";
10
+ import addAccessibilityTools from "./tools/accessibility.js";
11
+ import addTestManagementTools from "./tools/testmanagement.js";
12
+ import addAppAutomationTools from "./tools/appautomate.js";
13
+ import addFailureLogsTools from "./tools/getFailureLogs.js";
14
+ import addAutomateTools from "./tools/automate.js";
15
+ import { trackMCP } from "./lib/instrumentation.js";
20
16
  function registerTools(server) {
21
- (0, bstack_sdk_1.default)(server);
22
- (0, applive_1.default)(server);
23
- (0, live_1.default)(server);
24
- (0, observability_1.default)(server);
25
- (0, accessibility_1.default)(server);
26
- (0, automate_1.default)(server);
27
- (0, testmanagement_1.default)(server);
17
+ addSDKTools(server);
18
+ addAppLiveTools(server);
19
+ addBrowserLiveTools(server);
20
+ addAccessibilityTools(server);
21
+ addTestManagementTools(server);
22
+ addAppAutomationTools(server);
23
+ addFailureLogsTools(server);
24
+ addAutomateTools(server);
28
25
  }
29
26
  // Create an MCP server
30
- const server = new mcp_js_1.McpServer({
27
+ const server = new McpServer({
31
28
  name: "BrowserStack MCP Server",
32
- version: package_json_1.default.version,
29
+ version: packageJson.version,
33
30
  });
34
31
  registerTools(server);
35
32
  async function main() {
36
- logger_1.default.info("Launching BrowserStack MCP server, version %s", package_json_1.default.version);
33
+ logger.info("Launching BrowserStack MCP server, version %s", packageJson.version);
37
34
  // Start receiving messages on stdin and sending messages on stdout
38
- const transport = new stdio_js_1.StdioServerTransport();
35
+ const transport = new StdioServerTransport();
39
36
  await server.connect(transport);
40
- logger_1.default.info("MCP server started successfully");
41
- (0, instrumentation_1.trackMCP)("started", server.server.getClientVersion());
37
+ logger.info("MCP server started successfully");
38
+ trackMCP("started", server.server.getClientVersion());
42
39
  }
43
40
  main().catch(console.error);
44
41
  // Ensure logs are flushed before exit
45
42
  process.on("exit", () => {
46
- logger_1.default.flush();
43
+ logger.flush();
47
44
  });
package/dist/lib/api.js CHANGED
@@ -1,16 +1,9 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getLatestO11YBuildInfo = getLatestO11YBuildInfo;
7
- exports.retrieveNetworkFailures = retrieveNetworkFailures;
8
- const config_1 = __importDefault(require("../config"));
9
- async function getLatestO11YBuildInfo(buildName, projectName) {
1
+ import config from "../config.js";
2
+ export async function getLatestO11YBuildInfo(buildName, projectName) {
10
3
  const buildsUrl = `https://api-observability.browserstack.com/ext/v1/builds/latest?build_name=${encodeURIComponent(buildName)}&project_name=${encodeURIComponent(projectName)}`;
11
4
  const buildsResponse = await fetch(buildsUrl, {
12
5
  headers: {
13
- Authorization: `Basic ${Buffer.from(`${config_1.default.browserstackUsername}:${config_1.default.browserstackAccessKey}`).toString("base64")}`,
6
+ Authorization: `Basic ${Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64")}`,
14
7
  },
15
8
  });
16
9
  if (!buildsResponse.ok) {
@@ -21,50 +14,3 @@ async function getLatestO11YBuildInfo(buildName, projectName) {
21
14
  }
22
15
  return buildsResponse.json();
23
16
  }
24
- // Fetches network logs for a given session ID and returns only failure logs
25
- async function retrieveNetworkFailures(sessionId) {
26
- if (!sessionId) {
27
- throw new Error("Session ID is required");
28
- }
29
- const url = `https://api.browserstack.com/automate/sessions/${sessionId}/networklogs`;
30
- const auth = Buffer.from(`${config_1.default.browserstackUsername}:${config_1.default.browserstackAccessKey}`).toString("base64");
31
- const response = await fetch(url, {
32
- method: "GET",
33
- headers: {
34
- "Content-Type": "application/json",
35
- Authorization: `Basic ${auth}`,
36
- },
37
- });
38
- if (!response.ok) {
39
- if (response.status === 404) {
40
- throw new Error("Invalid session ID");
41
- }
42
- throw new Error(`Failed to fetch network logs: ${response.statusText}`);
43
- }
44
- const networklogs = await response.json();
45
- // Filter for failure logs
46
- const failureEntries = networklogs.log.entries.filter((entry) => {
47
- return (entry.response.status === 0 ||
48
- entry.response.status >= 400 ||
49
- entry.response._error !== undefined);
50
- });
51
- // Return only the failure entries with some context
52
- return {
53
- failures: failureEntries.map((entry) => ({
54
- startedDateTime: entry.startedDateTime,
55
- request: {
56
- method: entry.request?.method,
57
- url: entry.request?.url,
58
- queryString: entry.request?.queryString,
59
- },
60
- response: {
61
- status: entry.response?.status,
62
- statusText: entry.response?.statusText,
63
- _error: entry.response?._error,
64
- },
65
- serverIPAddress: entry.serverIPAddress,
66
- time: entry.time,
67
- })),
68
- totalFailures: failureEntries.length,
69
- };
70
- }
@@ -0,0 +1,14 @@
1
+ export const SessionType = {
2
+ Automate: "automate",
3
+ AppAutomate: "app-automate",
4
+ };
5
+ export const AutomateLogType = {
6
+ NetworkLogs: "networkLogs",
7
+ SessionLogs: "sessionLogs",
8
+ ConsoleLogs: "consoleLogs",
9
+ };
10
+ export const AppAutomateLogType = {
11
+ DeviceLogs: "deviceLogs",
12
+ AppiumLogs: "appiumLogs",
13
+ CrashLogs: "crashLogs",
14
+ };
@@ -1,33 +1,36 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getDevicesAndBrowsers = getDevicesAndBrowsers;
7
- const fs_1 = __importDefault(require("fs"));
8
- const os_1 = __importDefault(require("os"));
9
- const path_1 = __importDefault(require("path"));
10
- const CACHE_DIR = path_1.default.join(os_1.default.homedir(), ".browserstack", "combined_cache");
11
- const CACHE_FILE = path_1.default.join(CACHE_DIR, "data.json");
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ const CACHE_DIR = path.join(os.homedir(), ".browserstack", "combined_cache");
5
+ const CACHE_FILE = path.join(CACHE_DIR, "data.json");
12
6
  const TTL_MS = 24 * 60 * 60 * 1000; // 1 day
7
+ export var BrowserStackProducts;
8
+ (function (BrowserStackProducts) {
9
+ BrowserStackProducts["LIVE"] = "live";
10
+ BrowserStackProducts["APP_LIVE"] = "app_live";
11
+ BrowserStackProducts["APP_AUTOMATE"] = "app_automate";
12
+ })(BrowserStackProducts || (BrowserStackProducts = {}));
13
13
  const URLS = {
14
- live: "https://www.browserstack.com/list-of-browsers-and-platforms/live.json",
15
- app_live: "https://www.browserstack.com/list-of-browsers-and-platforms/app_live.json",
14
+ [BrowserStackProducts.LIVE]: "https://www.browserstack.com/list-of-browsers-and-platforms/live.json",
15
+ [BrowserStackProducts.APP_LIVE]: "https://www.browserstack.com/list-of-browsers-and-platforms/app_live.json",
16
+ [BrowserStackProducts.APP_AUTOMATE]: "https://www.browserstack.com/list-of-browsers-and-platforms/app_automate.json",
16
17
  };
17
18
  /**
18
- * Fetches and caches both BrowserStack datasets (live + app_live) with a shared TTL.
19
+ * Fetches and caches BrowserStack datasets (live + app_live + app_automate) with a shared TTL.
19
20
  */
20
- async function getDevicesAndBrowsers(type) {
21
- if (!fs_1.default.existsSync(CACHE_DIR)) {
22
- fs_1.default.mkdirSync(CACHE_DIR, { recursive: true });
21
+ export async function getDevicesAndBrowsers(type) {
22
+ if (!fs.existsSync(CACHE_DIR)) {
23
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
23
24
  }
24
25
  let cache = {};
25
- if (fs_1.default.existsSync(CACHE_FILE)) {
26
- const stats = fs_1.default.statSync(CACHE_FILE);
26
+ if (fs.existsSync(CACHE_FILE)) {
27
+ const stats = fs.statSync(CACHE_FILE);
27
28
  if (Date.now() - stats.mtimeMs < TTL_MS) {
28
29
  try {
29
- cache = JSON.parse(fs_1.default.readFileSync(CACHE_FILE, "utf8"));
30
- return cache[type];
30
+ cache = JSON.parse(fs.readFileSync(CACHE_FILE, "utf8"));
31
+ if (cache[type]) {
32
+ return cache[type];
33
+ }
31
34
  }
32
35
  catch (error) {
33
36
  console.error("Error parsing cache file:", error);
@@ -35,18 +38,14 @@ async function getDevicesAndBrowsers(type) {
35
38
  }
36
39
  }
37
40
  }
38
- const [liveRes, appLiveRes] = await Promise.all([
39
- fetch(URLS.live),
40
- fetch(URLS.app_live),
41
- ]);
42
- if (!liveRes.ok || !appLiveRes.ok) {
43
- throw new Error(`Failed to fetch configuration from BrowserStack : live=${liveRes.statusText}, app_live=${appLiveRes.statusText}`);
41
+ const liveRes = await fetch(URLS[type]);
42
+ if (!liveRes.ok) {
43
+ throw new Error(`Failed to fetch configuration from BrowserStack : ${type}=${liveRes.statusText}`);
44
44
  }
45
- const [liveData, appLiveData] = await Promise.all([
46
- liveRes.json(),
47
- appLiveRes.json(),
48
- ]);
49
- cache = { live: liveData, app_live: appLiveData };
50
- fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
45
+ const data = await liveRes.json();
46
+ cache = {
47
+ [type]: data,
48
+ };
49
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
51
50
  return cache[type];
52
51
  }
package/dist/lib/error.js CHANGED
@@ -1,15 +1,12 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatAxiosError = formatAxiosError;
4
- const axios_1 = require("axios");
1
+ import { AxiosError } from "axios";
5
2
  /**
6
3
  * Formats an AxiosError into a CallToolResult with an appropriate message.
7
4
  * @param err - The error object to format
8
5
  * @param defaultText - The fallback error message
9
6
  */
10
- function formatAxiosError(err, defaultText) {
7
+ export function formatAxiosError(err, defaultText) {
11
8
  let text = defaultText;
12
- if (err instanceof axios_1.AxiosError && err.response?.data) {
9
+ if (err instanceof AxiosError && err.response?.data) {
13
10
  const message = err.response.data.message ||
14
11
  err.response.data.error ||
15
12
  err.message ||
package/dist/lib/fuzzy.js CHANGED
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.customFuzzySearch = customFuzzySearch;
4
1
  // 1. Compute Levenshtein distance between two strings
5
2
  function levenshtein(a, b) {
6
3
  const dp = Array(a.length + 1)
@@ -39,7 +36,7 @@ function scoreItem(item, keys, queryTokens) {
39
36
  return best;
40
37
  }
41
38
  // 3. The search entrypoint
42
- function customFuzzySearch(list, keys, query, limit = 5, maxDistance = 0.6) {
39
+ export function customFuzzySearch(list, keys, query, limit = 5, maxDistance = 0.6) {
43
40
  const q = query.toLowerCase().trim();
44
41
  const queryTokens = q.split(/\s+/);
45
42
  return list
@@ -0,0 +1 @@
1
+ export const signedUrlMap = new Map();
@@ -1,16 +1,10 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.trackMCP = trackMCP;
7
- const logger_1 = __importDefault(require("../logger"));
8
- const config_1 = __importDefault(require("../config"));
9
- const package_json_1 = __importDefault(require("../../package.json"));
10
- const axios_1 = __importDefault(require("axios"));
11
- function trackMCP(toolName, clientInfo, error) {
12
- if (config_1.default.DEV_MODE) {
13
- logger_1.default.info("Tracking MCP is disabled in dev mode");
1
+ import logger from "../logger.js";
2
+ import config from "../config.js";
3
+ import packageJson from "../../package.json" with { type: "json" };
4
+ import axios from "axios";
5
+ export function trackMCP(toolName, clientInfo, error) {
6
+ if (config.DEV_MODE) {
7
+ logger.info("Tracking MCP is disabled in dev mode");
14
8
  return;
15
9
  }
16
10
  const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event";
@@ -18,15 +12,15 @@ function trackMCP(toolName, clientInfo, error) {
18
12
  const mcpClient = clientInfo?.name || "unknown";
19
13
  // Log client information
20
14
  if (clientInfo?.name) {
21
- logger_1.default.info(`Client connected: ${clientInfo.name} (version: ${clientInfo.version})`);
15
+ logger.info(`Client connected: ${clientInfo.name} (version: ${clientInfo.version})`);
22
16
  }
23
17
  else {
24
- logger_1.default.info("Client connected: unknown client");
18
+ logger.info("Client connected: unknown client");
25
19
  }
26
20
  const event = {
27
21
  event_type: "MCPInstrumentation",
28
22
  event_properties: {
29
- mcp_version: package_json_1.default.version,
23
+ mcp_version: packageJson.version,
30
24
  tool_name: toolName,
31
25
  mcp_client: mcpClient,
32
26
  success: isSuccess,
@@ -39,11 +33,11 @@ function trackMCP(toolName, clientInfo, error) {
39
33
  event.event_properties.error_type =
40
34
  error instanceof Error ? error.constructor.name : "Unknown";
41
35
  }
42
- axios_1.default
36
+ axios
43
37
  .post(instrumentationEndpoint, event, {
44
38
  headers: {
45
39
  "Content-Type": "application/json",
46
- Authorization: `Basic ${Buffer.from(`${config_1.default.browserstackUsername}:${config_1.default.browserstackAccessKey}`).toString("base64")}`,
40
+ Authorization: `Basic ${Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64")}`,
47
41
  },
48
42
  timeout: 2000,
49
43
  })
package/dist/lib/local.js CHANGED
@@ -1,47 +1,39 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.killExistingBrowserStackLocalProcesses = killExistingBrowserStackLocalProcesses;
7
- exports.ensureLocalBinarySetup = ensureLocalBinarySetup;
8
- exports.isLocalURL = isLocalURL;
9
- const logger_1 = __importDefault(require("../logger"));
10
- const child_process_1 = require("child_process");
11
- const browserstack_local_1 = require("browserstack-local");
12
- const config_1 = __importDefault(require("../config"));
1
+ import logger from "../logger.js";
2
+ import { execSync } from "child_process";
3
+ import { Local } from "browserstack-local";
4
+ import config from "../config.js";
13
5
  async function isBrowserStackLocalRunning() {
14
6
  // Check if BrowserStackLocal binary is already running
15
7
  try {
16
8
  if (process.platform === "win32") {
17
- const result = (0, child_process_1.execSync)('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', {
9
+ const result = execSync('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', {
18
10
  encoding: "utf8",
19
11
  });
20
12
  if (result.includes("BrowserStackLocal.exe")) {
21
- logger_1.default.info("BrowserStackLocal binary is already running");
13
+ logger.info("BrowserStackLocal binary is already running");
22
14
  return true;
23
15
  }
24
16
  }
25
17
  else {
26
- const result = (0, child_process_1.execSync)("pgrep -f BrowserStackLocal", {
18
+ const result = execSync("pgrep -f BrowserStackLocal", {
27
19
  encoding: "utf8",
28
20
  stdio: "pipe",
29
21
  }).toString();
30
22
  if (result) {
31
- logger_1.default.info("BrowserStackLocal binary is already running");
23
+ logger.info("BrowserStackLocal binary is already running");
32
24
  return true;
33
25
  }
34
26
  }
35
- logger_1.default.info("BrowserStackLocal binary is not running");
27
+ logger.info("BrowserStackLocal binary is not running");
36
28
  return false;
37
29
  }
38
30
  catch (error) {
39
- logger_1.default.info("Error checking BrowserStackLocal status, assuming not running ... " +
31
+ logger.info("Error checking BrowserStackLocal status, assuming not running ... " +
40
32
  error);
41
33
  return false;
42
34
  }
43
35
  }
44
- async function killExistingBrowserStackLocalProcesses() {
36
+ export async function killExistingBrowserStackLocalProcesses() {
45
37
  const isRunning = await isBrowserStackLocalRunning();
46
38
  if (!isRunning) {
47
39
  return;
@@ -50,50 +42,50 @@ async function killExistingBrowserStackLocalProcesses() {
50
42
  try {
51
43
  if (process.platform === "win32") {
52
44
  // Check if process exists on Windows
53
- const checkResult = (0, child_process_1.execSync)('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', { encoding: "utf8" });
45
+ const checkResult = execSync('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', { encoding: "utf8" });
54
46
  if (checkResult.includes("BrowserStackLocal.exe")) {
55
- (0, child_process_1.execSync)("taskkill /F /IM BrowserStackLocal.exe", { stdio: "ignore" });
56
- logger_1.default.info("Successfully killed existing BrowserStackLocal processes");
47
+ execSync("taskkill /F /IM BrowserStackLocal.exe", { stdio: "ignore" });
48
+ logger.info("Successfully killed existing BrowserStackLocal processes");
57
49
  }
58
50
  }
59
51
  else {
60
52
  // Check if process exists on Unix-like systems
61
- const checkResult = (0, child_process_1.execSync)("pgrep -f BrowserStackLocal", {
53
+ const checkResult = execSync("pgrep -f BrowserStackLocal", {
62
54
  encoding: "utf8",
63
55
  stdio: "pipe",
64
56
  }).toString();
65
57
  if (checkResult) {
66
- (0, child_process_1.execSync)("pkill -f BrowserStackLocal", { stdio: "ignore" });
67
- logger_1.default.info("Successfully killed existing BrowserStackLocal processes");
58
+ execSync("pkill -f BrowserStackLocal", { stdio: "ignore" });
59
+ logger.info("Successfully killed existing BrowserStackLocal processes");
68
60
  }
69
61
  }
70
62
  }
71
63
  catch (error) {
72
- logger_1.default.info(`Error checking/killing BrowserStackLocal processes: ${error}`);
64
+ logger.info(`Error checking/killing BrowserStackLocal processes: ${error}`);
73
65
  // Continue execution as there may not be any processes running
74
66
  }
75
67
  }
76
- async function ensureLocalBinarySetup() {
77
- logger_1.default.info("Ensuring local binary setup as it is required for private URLs...");
78
- const localBinary = new browserstack_local_1.Local();
68
+ export async function ensureLocalBinarySetup() {
69
+ logger.info("Ensuring local binary setup as it is required for private URLs...");
70
+ const localBinary = new Local();
79
71
  await killExistingBrowserStackLocalProcesses();
80
72
  return await new Promise((resolve, reject) => {
81
73
  localBinary.start({
82
- key: config_1.default.browserstackAccessKey,
83
- username: config_1.default.browserstackUsername,
74
+ key: config.browserstackAccessKey,
75
+ username: config.browserstackUsername,
84
76
  }, (error) => {
85
77
  if (error) {
86
- logger_1.default.error(`Unable to start BrowserStack Local... please check your credentials and try again. Error: ${error}`);
78
+ logger.error(`Unable to start BrowserStack Local... please check your credentials and try again. Error: ${error}`);
87
79
  reject(new Error(`Unable to configure local tunnel binary, please check your credentials and try again. Error: ${error}`));
88
80
  }
89
81
  else {
90
- logger_1.default.info("Successfully started BrowserStack Local");
82
+ logger.info("Successfully started BrowserStack Local");
91
83
  resolve();
92
84
  }
93
85
  });
94
86
  });
95
87
  }
96
- function isLocalURL(url) {
88
+ export function isLocalURL(url) {
97
89
  try {
98
90
  const urlObj = new URL(url);
99
91
  const hostname = urlObj.hostname.toLowerCase();
@@ -103,7 +95,7 @@ function isLocalURL(url) {
103
95
  hostname.endsWith(".localhost"));
104
96
  }
105
97
  catch (error) {
106
- logger_1.default.error(`Error checking if URL is local: ${error}`);
98
+ logger.error(`Error checking if URL is local: ${error}`);
107
99
  return false;
108
100
  }
109
101
  }
package/dist/lib/utils.js CHANGED
@@ -1,7 +1,32 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sanitizeUrlParam = sanitizeUrlParam;
4
- function sanitizeUrlParam(param) {
1
+ import sharp from "sharp";
2
+ export function sanitizeUrlParam(param) {
5
3
  // Remove any characters that could be used for command injection
6
4
  return param.replace(/[;&|`$(){}[\]<>]/g, "");
7
5
  }
6
+ const ONE_MB = 1048576;
7
+ //Compresses a base64 image intelligently to keep it under 1 MB if needed.
8
+ export async function maybeCompressBase64(base64) {
9
+ const buffer = Buffer.from(base64, "base64");
10
+ if (buffer.length <= ONE_MB) {
11
+ return base64;
12
+ }
13
+ const sizeRatio = 1048576 / buffer.length;
14
+ const estimatedQuality = Math.floor(sizeRatio * 100);
15
+ const quality = Math.min(95, Math.max(30, estimatedQuality));
16
+ const compressedBuffer = await sharp(buffer).png({ quality }).toBuffer();
17
+ return compressedBuffer.toString("base64");
18
+ }
19
+ export async function assertOkResponse(response, action) {
20
+ if (!response.ok) {
21
+ if (response.status === 404) {
22
+ throw new Error(`Invalid session ID for ${action}`);
23
+ }
24
+ throw new Error(`Failed to fetch logs for ${action}: ${response.statusText}`);
25
+ }
26
+ }
27
+ export function filterLinesByKeywords(logText, keywords) {
28
+ return logText
29
+ .split(/\r?\n/)
30
+ .map((line) => line.trim())
31
+ .filter((line) => keywords.some((keyword) => line.toLowerCase().includes(keyword)));
32
+ }
package/dist/logger.js CHANGED
@@ -1,12 +1,7 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const pino_1 = __importDefault(require("pino"));
1
+ import { pino } from "pino";
7
2
  let logger;
8
3
  if (process.env.NODE_ENV === "development") {
9
- logger = (0, pino_1.default)({
4
+ logger = pino({
10
5
  level: "debug",
11
6
  transport: {
12
7
  targets: [
@@ -27,7 +22,7 @@ if (process.env.NODE_ENV === "development") {
27
22
  }
28
23
  else {
29
24
  // NULL logger
30
- logger = (0, pino_1.default)({
25
+ logger = pino({
31
26
  level: "info",
32
27
  transport: {
33
28
  target: "pino/file",
@@ -37,4 +32,4 @@ else {
37
32
  },
38
33
  });
39
34
  }
40
- exports.default = logger;
35
+ export default logger;