@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
@@ -1,19 +1,12 @@
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.startAppLiveSession = startAppLiveSession;
7
- exports.default = addAppLiveTools;
8
- const zod_1 = require("zod");
9
- const fs_1 = __importDefault(require("fs"));
10
- const start_session_1 = require("./applive-utils/start-session");
11
- const logger_1 = __importDefault(require("../logger"));
12
- const instrumentation_1 = require("../lib/instrumentation");
1
+ import { z } from "zod";
2
+ import fs from "fs";
3
+ import { startSession } from "./applive-utils/start-session.js";
4
+ import logger from "../logger.js";
5
+ import { trackMCP } from "../lib/instrumentation.js";
13
6
  /**
14
7
  * Launches an App Live Session on BrowserStack.
15
8
  */
16
- async function startAppLiveSession(args) {
9
+ export async function startAppLiveSession(args) {
17
10
  if (!args.desiredPlatform) {
18
11
  throw new Error("You must provide a desiredPlatform.");
19
12
  }
@@ -31,16 +24,16 @@ async function startAppLiveSession(args) {
31
24
  }
32
25
  // check if the app path exists && is readable
33
26
  try {
34
- if (!fs_1.default.existsSync(args.appPath)) {
27
+ if (!fs.existsSync(args.appPath)) {
35
28
  throw new Error("The app path does not exist.");
36
29
  }
37
- fs_1.default.accessSync(args.appPath, fs_1.default.constants.R_OK);
30
+ fs.accessSync(args.appPath, fs.constants.R_OK);
38
31
  }
39
32
  catch (error) {
40
- logger_1.default.error("The app path does not exist or is not readable: %s", error);
33
+ logger.error("The app path does not exist or is not readable: %s", error);
41
34
  throw new Error("The app path does not exist or is not readable.");
42
35
  }
43
- const launchUrl = await (0, start_session_1.startSession)({
36
+ const launchUrl = await startSession({
44
37
  appPath: args.appPath,
45
38
  desiredPlatform: args.desiredPlatform,
46
39
  desiredPhone: args.desiredPhone,
@@ -55,28 +48,28 @@ async function startAppLiveSession(args) {
55
48
  ],
56
49
  };
57
50
  }
58
- function addAppLiveTools(server) {
51
+ export default function addAppLiveTools(server) {
59
52
  server.tool("runAppLiveSession", "Use this tool when user wants to manually check their app on a particular mobile device using BrowserStack's cloud infrastructure. Can be used to debug crashes, slow performance, etc.", {
60
- desiredPhone: zod_1.z
53
+ desiredPhone: z
61
54
  .string()
62
55
  .describe("The full name of the device to run the app on. Example: 'iPhone 12 Pro' or 'Samsung Galaxy S20' or 'Google Pixel 6'. Always ask the user for the device they want to use, do not assume it. "),
63
- desiredPlatformVersion: zod_1.z
56
+ desiredPlatformVersion: z
64
57
  .string()
65
58
  .describe("Specifies the platform version to run the app on. For example, use '12.0' for Android or '16.0' for iOS. If the user says 'latest', 'newest', or similar, normalize it to 'latest'. Likewise, convert terms like 'earliest' or 'oldest' to 'oldest'."),
66
- desiredPlatform: zod_1.z
59
+ desiredPlatform: z
67
60
  .enum(["android", "ios"])
68
61
  .describe("Which platform to run on, examples: 'android', 'ios'. Set this based on the app path provided."),
69
- appPath: zod_1.z
62
+ appPath: z
70
63
  .string()
71
64
  .describe("The path to the .ipa or .apk file to install on the device. Always ask the user for the app path, do not assume it."),
72
65
  }, async (args) => {
73
66
  try {
74
- (0, instrumentation_1.trackMCP)("runAppLiveSession", server.server.getClientVersion());
67
+ trackMCP("runAppLiveSession", server.server.getClientVersion());
75
68
  return await startAppLiveSession(args);
76
69
  }
77
70
  catch (error) {
78
- logger_1.default.error("App live session failed: %s", error);
79
- (0, instrumentation_1.trackMCP)("runAppLiveSession", server.server.getClientVersion(), error);
71
+ logger.error("App live session failed: %s", error);
72
+ trackMCP("runAppLiveSession", server.server.getClientVersion(), error);
80
73
  return {
81
74
  content: [
82
75
  {
@@ -0,0 +1,59 @@
1
+ import config from "../../config.js";
2
+ import { assertOkResponse, maybeCompressBase64 } from "../../lib/utils.js";
3
+ import { SessionType } from "../../lib/constants.js";
4
+ //Extracts screenshot URLs from BrowserStack session logs
5
+ async function extractScreenshotUrls(sessionId, sessionType) {
6
+ const credentials = `${config.browserstackUsername}:${config.browserstackAccessKey}`;
7
+ const auth = Buffer.from(credentials).toString("base64");
8
+ const baseUrl = `https://api.browserstack.com/${sessionType === SessionType.Automate ? "automate" : "app-automate"}`;
9
+ const url = `${baseUrl}/sessions/${sessionId}/logs`;
10
+ const response = await fetch(url, {
11
+ headers: {
12
+ "Content-Type": "application/json",
13
+ Authorization: `Basic ${auth}`,
14
+ },
15
+ });
16
+ await assertOkResponse(response, "Session");
17
+ const text = await response.text();
18
+ const urls = [];
19
+ const SCREENSHOT_PATTERN = /REQUEST.*GET.*\/screenshot/;
20
+ const RESPONSE_VALUE_PATTERN = /"value"\s*:\s*"([^"]+)"/;
21
+ // Split logs into lines and process them
22
+ const lines = text.split("\n");
23
+ for (let i = 0; i < lines.length - 1; i++) {
24
+ const currentLine = lines[i];
25
+ const nextLine = lines[i + 1];
26
+ if (SCREENSHOT_PATTERN.test(currentLine)) {
27
+ const match = nextLine.match(RESPONSE_VALUE_PATTERN);
28
+ if (match && match[1]) {
29
+ urls.push(match[1]);
30
+ }
31
+ }
32
+ }
33
+ return urls;
34
+ }
35
+ //Converts screenshot URLs to base64 encoded images
36
+ async function convertUrlsToBase64(urls) {
37
+ const screenshots = await Promise.all(urls.map(async (url) => {
38
+ const response = await fetch(url);
39
+ const arrayBuffer = await response.arrayBuffer();
40
+ const base64 = Buffer.from(arrayBuffer).toString("base64");
41
+ // Compress the base64 image if needed
42
+ const compressedBase64 = await maybeCompressBase64(base64);
43
+ return {
44
+ url,
45
+ base64: compressedBase64,
46
+ };
47
+ }));
48
+ return screenshots;
49
+ }
50
+ //Fetches and converts screenshot URLs to base64 encoded images
51
+ export async function fetchAutomationScreenshots(sessionId, sessionType = SessionType.Automate) {
52
+ const urls = await extractScreenshotUrls(sessionId, sessionType);
53
+ if (urls.length === 0) {
54
+ return [];
55
+ }
56
+ // Take only the last 5 URLs
57
+ const lastFiveUrls = urls.slice(-5);
58
+ return await convertUrlsToBase64(lastFiveUrls);
59
+ }
@@ -1,62 +1,69 @@
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.getNetworkFailures = getNetworkFailures;
7
- exports.default = addAutomateTools;
8
- const zod_1 = require("zod");
9
- const logger_1 = __importDefault(require("../logger"));
10
- const api_1 = require("../lib/api");
11
- const instrumentation_1 = require("../lib/instrumentation");
12
- /**
13
- * Fetches failed network requests from a BrowserStack Automate session.
14
- * Returns network requests that resulted in errors or failed to complete.
15
- */
16
- async function getNetworkFailures(args) {
1
+ import { z } from "zod";
2
+ import { fetchAutomationScreenshots } from "./automate-utils/fetch-screenshots.js";
3
+ import { SessionType } from "../lib/constants.js";
4
+ import { trackMCP } from "../lib/instrumentation.js";
5
+ import logger from "../logger.js";
6
+ // Tool function that fetches and processes screenshots from BrowserStack Automate session
7
+ export async function fetchAutomationScreenshotsTool(args) {
17
8
  try {
18
- const failureLogs = await (0, api_1.retrieveNetworkFailures)(args.sessionId);
19
- logger_1.default.info("Successfully fetched failure network logs for session: %s", args.sessionId);
20
- // Check if there are any failures
21
- const hasFailures = failureLogs.totalFailures > 0;
22
- const text = hasFailures
23
- ? `${failureLogs.totalFailures} network failure(s) found for session :\n\n${JSON.stringify(failureLogs.failures, null, 2)}`
24
- : `No network failures found for session`;
9
+ const screenshots = await fetchAutomationScreenshots(args.sessionId, args.sessionType);
10
+ if (screenshots.length === 0) {
11
+ return {
12
+ content: [
13
+ {
14
+ type: "text",
15
+ text: "No screenshots found in the session or some unexpected error occurred",
16
+ },
17
+ ],
18
+ isError: true,
19
+ };
20
+ }
21
+ const results = screenshots.map((screenshot, index) => ({
22
+ type: "image",
23
+ text: `Screenshot ${index + 1}`,
24
+ data: screenshot.base64,
25
+ mimeType: "image/png",
26
+ metadata: { url: screenshot.url },
27
+ }));
25
28
  return {
26
29
  content: [
27
30
  {
28
31
  type: "text",
29
- text,
32
+ text: `Retrieved ${screenshots.length} screenshot(s) from the end of the session.`,
30
33
  },
34
+ ...results,
31
35
  ],
32
36
  };
33
37
  }
34
38
  catch (error) {
35
- logger_1.default.error("Failed to fetch network logs: %s", error);
36
- throw new Error(error instanceof Error ? error.message : String(error));
39
+ logger.error("Error during fetching screenshots", error);
40
+ throw error;
37
41
  }
38
42
  }
39
- function addAutomateTools(server) {
40
- server.tool("getNetworkFailures", "Use this tool to fetch failed network requests from a BrowserStack Automate session.", {
41
- sessionId: zod_1.z.string().describe("The Automate session ID."),
43
+ //Registers the fetchAutomationScreenshots tool with the MCP server
44
+ export default function addAutomationTools(server) {
45
+ server.tool("fetchAutomationScreenshots", "Fetch and process screenshots from a BrowserStack Automate session", {
46
+ sessionId: z
47
+ .string()
48
+ .describe("The BrowserStack session ID to fetch screenshots from"),
49
+ sessionType: z
50
+ .enum([SessionType.Automate, SessionType.AppAutomate])
51
+ .describe("Type of BrowserStack session"),
42
52
  }, async (args) => {
43
53
  try {
44
- (0, instrumentation_1.trackMCP)("getNetworkFailures", server.server.getClientVersion());
45
- return await getNetworkFailures(args);
54
+ trackMCP("fetchAutomationScreenshots", server.server.getClientVersion());
55
+ return await fetchAutomationScreenshotsTool(args);
46
56
  }
47
57
  catch (error) {
48
- const errorMessage = error instanceof Error ? error.message : String(error);
49
- logger_1.default.error("Failed to fetch network logs: %s", errorMessage);
50
- (0, instrumentation_1.trackMCP)("getNetworkFailures", server.server.getClientVersion(), error);
58
+ trackMCP("fetchAutomationScreenshots", server.server.getClientVersion(), error);
59
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
51
60
  return {
52
61
  content: [
53
62
  {
54
63
  type: "text",
55
- text: `Failed to fetch network logs: ${errorMessage}`,
56
- isError: true,
64
+ text: `Error during fetching automate screenshots: ${errorMessage}`,
57
65
  },
58
66
  ],
59
- isError: true,
60
67
  };
61
68
  }
62
69
  });
@@ -1,17 +1,13 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.bootstrapProjectWithSDK = bootstrapProjectWithSDK;
4
- exports.default = addSDKTools;
5
- const zod_1 = require("zod");
6
- const instructions_1 = require("./sdk-utils/instructions");
7
- const instrumentation_1 = require("../lib/instrumentation");
1
+ import { z } from "zod";
2
+ import { generateBrowserStackYMLInstructions, getInstructionsForProjectConfiguration, } from "./sdk-utils/instructions.js";
3
+ import { trackMCP } from "../lib/instrumentation.js";
8
4
  /**
9
5
  * BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
10
6
  * This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
11
7
  */
12
- async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, }) {
13
- const instructions = (0, instructions_1.generateBrowserStackYMLInstructions)(desiredPlatforms);
14
- const instructionsForProjectConfiguration = (0, instructions_1.getInstructionsForProjectConfiguration)(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage);
8
+ export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, }) {
9
+ const instructions = generateBrowserStackYMLInstructions(desiredPlatforms);
10
+ const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage);
15
11
  return {
16
12
  content: [
17
13
  {
@@ -22,23 +18,23 @@ async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, det
22
18
  ],
23
19
  };
24
20
  }
25
- function addSDKTools(server) {
21
+ export default function addSDKTools(server) {
26
22
  server.tool("runTestsOnBrowserStack", "Use this tool to get instructions for running tests on BrowserStack.", {
27
- detectedBrowserAutomationFramework: zod_1.z
23
+ detectedBrowserAutomationFramework: z
28
24
  .string()
29
25
  .describe("The automation framework configured in the project. Example: 'playwright', 'selenium'"),
30
- detectedTestingFramework: zod_1.z
26
+ detectedTestingFramework: z
31
27
  .string()
32
28
  .describe("The testing framework used in the project. Example: 'jest', 'pytest'"),
33
- detectedLanguage: zod_1.z
29
+ detectedLanguage: z
34
30
  .string()
35
31
  .describe("The programming language used in the project. Example: 'nodejs', 'python'"),
36
- desiredPlatforms: zod_1.z
37
- .array(zod_1.z.enum(["windows", "macos", "android", "ios"]))
32
+ desiredPlatforms: z
33
+ .array(z.enum(["windows", "macos", "android", "ios"]))
38
34
  .describe("The platforms the user wants to test on. Always ask this to the user, do not try to infer this."),
39
35
  }, async (args) => {
40
36
  try {
41
- (0, instrumentation_1.trackMCP)("runTestsOnBrowserStack", server.server.getClientVersion());
37
+ trackMCP("runTestsOnBrowserStack", server.server.getClientVersion());
42
38
  return await bootstrapProjectWithSDK({
43
39
  detectedBrowserAutomationFramework: args.detectedBrowserAutomationFramework,
44
40
  detectedTestingFramework: args.detectedTestingFramework,
@@ -47,7 +43,7 @@ function addSDKTools(server) {
47
43
  });
48
44
  }
49
45
  catch (error) {
50
- (0, instrumentation_1.trackMCP)("runTestsOnBrowserStack", server.server.getClientVersion(), error);
46
+ trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error);
51
47
  return {
52
48
  content: [
53
49
  {
@@ -0,0 +1,88 @@
1
+ import config from "../../config.js";
2
+ import { assertOkResponse, filterLinesByKeywords } from "../../lib/utils.js";
3
+ const auth = Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64");
4
+ // DEVICE LOGS
5
+ export async function retrieveDeviceLogs(sessionId, buildId) {
6
+ const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/deviceLogs`;
7
+ const response = await fetch(url, {
8
+ headers: {
9
+ "Content-Type": "application/json",
10
+ Authorization: `Basic ${auth}`,
11
+ },
12
+ });
13
+ await assertOkResponse(response, "device logs");
14
+ const logText = await response.text();
15
+ return filterDeviceFailures(logText);
16
+ }
17
+ // APPIUM LOGS
18
+ export async function retrieveAppiumLogs(sessionId, buildId) {
19
+ const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/appiumlogs`;
20
+ const response = await fetch(url, {
21
+ headers: {
22
+ "Content-Type": "application/json",
23
+ Authorization: `Basic ${auth}`,
24
+ },
25
+ });
26
+ await assertOkResponse(response, "Appium logs");
27
+ const logText = await response.text();
28
+ return filterAppiumFailures(logText);
29
+ }
30
+ // CRASH LOGS
31
+ export async function retrieveCrashLogs(sessionId, buildId) {
32
+ const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/crashlogs`;
33
+ const response = await fetch(url, {
34
+ headers: {
35
+ "Content-Type": "application/json",
36
+ Authorization: `Basic ${auth}`,
37
+ },
38
+ });
39
+ await assertOkResponse(response, "crash logs");
40
+ const logText = await response.text();
41
+ return filterCrashFailures(logText);
42
+ }
43
+ // FILTER HELPERS
44
+ export function filterDeviceFailures(logText) {
45
+ const keywords = [
46
+ "error",
47
+ "exception",
48
+ "fatal",
49
+ "anr",
50
+ "not responding",
51
+ "process crashed",
52
+ "crash",
53
+ "force close",
54
+ "signal",
55
+ "java.lang.",
56
+ "unable to",
57
+ ];
58
+ return filterLinesByKeywords(logText, keywords);
59
+ }
60
+ export function filterAppiumFailures(logText) {
61
+ const keywords = [
62
+ "error",
63
+ "fail",
64
+ "exception",
65
+ "not found",
66
+ "no such element",
67
+ "unable to",
68
+ "stacktrace",
69
+ "appium exited",
70
+ "command failed",
71
+ "invalid selector",
72
+ ];
73
+ return filterLinesByKeywords(logText, keywords);
74
+ }
75
+ export function filterCrashFailures(logText) {
76
+ const keywords = [
77
+ "fatal exception",
78
+ "crash",
79
+ "signal",
80
+ "java.lang.",
81
+ "caused by:",
82
+ "native crash",
83
+ "anr",
84
+ "abort message",
85
+ "application has stopped unexpectedly",
86
+ ];
87
+ return filterLinesByKeywords(logText, keywords);
88
+ }
@@ -0,0 +1,97 @@
1
+ import config from "../../config.js";
2
+ import { assertOkResponse, filterLinesByKeywords } from "../../lib/utils.js";
3
+ const auth = Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64");
4
+ // NETWORK LOGS
5
+ export async function retrieveNetworkFailures(sessionId) {
6
+ const url = `https://api.browserstack.com/automate/sessions/${sessionId}/networklogs`;
7
+ const response = await fetch(url, {
8
+ method: "GET",
9
+ headers: {
10
+ "Content-Type": "application/json",
11
+ Authorization: `Basic ${auth}`,
12
+ },
13
+ });
14
+ await assertOkResponse(response, "network logs");
15
+ const networklogs = await response.json();
16
+ // Filter for failure logs
17
+ const failureEntries = networklogs.log.entries.filter((entry) => {
18
+ return (entry.response.status === 0 ||
19
+ entry.response.status >= 400 ||
20
+ entry.response._error !== undefined);
21
+ });
22
+ // Return only the failure entries with some context
23
+ return failureEntries.map((entry) => ({
24
+ startedDateTime: entry.startedDateTime,
25
+ request: {
26
+ method: entry.request?.method,
27
+ url: entry.request?.url,
28
+ queryString: entry.request?.queryString,
29
+ },
30
+ response: {
31
+ status: entry.response?.status,
32
+ statusText: entry.response?.statusText,
33
+ _error: entry.response?._error,
34
+ },
35
+ serverIPAddress: entry.serverIPAddress,
36
+ time: entry.time,
37
+ }));
38
+ }
39
+ // SESSION LOGS
40
+ export async function retrieveSessionFailures(sessionId) {
41
+ const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`;
42
+ const response = await fetch(url, {
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ Authorization: `Basic ${auth}`,
46
+ },
47
+ });
48
+ await assertOkResponse(response, "session logs");
49
+ const logText = await response.text();
50
+ return filterSessionFailures(logText);
51
+ }
52
+ // CONSOLE LOGS
53
+ export async function retrieveConsoleFailures(sessionId) {
54
+ const url = `https://api.browserstack.com/automate/sessions/${sessionId}/consolelogs`;
55
+ const response = await fetch(url, {
56
+ headers: {
57
+ "Content-Type": "application/json",
58
+ Authorization: `Basic ${auth}`,
59
+ },
60
+ });
61
+ await assertOkResponse(response, "console logs");
62
+ const logText = await response.text();
63
+ return filterConsoleFailures(logText);
64
+ }
65
+ // FILTER: session logs
66
+ export function filterSessionFailures(logText) {
67
+ const keywords = [
68
+ "error",
69
+ "fail",
70
+ "exception",
71
+ "fatal",
72
+ "unable to",
73
+ "not found",
74
+ '"success":false',
75
+ '"success": false',
76
+ '"msg":',
77
+ "console.error",
78
+ "stderr",
79
+ ];
80
+ return filterLinesByKeywords(logText, keywords);
81
+ }
82
+ // FILTER: console logs
83
+ export function filterConsoleFailures(logText) {
84
+ const keywords = [
85
+ "failed to load resource",
86
+ "uncaught",
87
+ "typeerror",
88
+ "referenceerror",
89
+ "scanner is not ready",
90
+ "status of 4",
91
+ "status of 5",
92
+ "not found",
93
+ "undefined",
94
+ "error:",
95
+ ];
96
+ return filterLinesByKeywords(logText, keywords);
97
+ }