@browserstack/mcp-server 1.1.8 → 1.2.0
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/README.md +71 -35
- package/dist/config.d.ts +13 -0
- package/dist/config.js +10 -6
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -30
- package/dist/lib/api.d.ts +2 -0
- package/dist/lib/api.js +10 -5
- package/dist/lib/apiClient.d.ts +31 -0
- package/dist/lib/apiClient.js +108 -0
- package/dist/lib/constants.d.ts +17 -0
- package/dist/lib/device-cache.d.ts +9 -0
- package/dist/lib/device-cache.js +3 -3
- package/dist/lib/error.d.ts +7 -0
- package/dist/lib/fuzzy.d.ts +1 -0
- package/dist/lib/get-auth.d.ts +2 -0
- package/dist/lib/get-auth.js +8 -0
- package/dist/lib/inmemory-store.d.ts +1 -0
- package/dist/lib/instrumentation.d.ts +4 -0
- package/dist/lib/instrumentation.js +8 -7
- package/dist/lib/local.d.ts +3 -0
- package/dist/lib/local.js +12 -3
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/utils.d.ts +4 -0
- package/dist/lib/version-resolver.d.ts +6 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +20 -4
- package/dist/oninitialized.d.ts +2 -0
- package/dist/oninitialized.js +2 -7
- package/dist/server-factory.d.ts +3 -0
- package/dist/server-factory.js +37 -0
- package/dist/tools/accessibility.d.ts +3 -0
- package/dist/tools/accessibility.js +17 -10
- package/dist/tools/accessiblity-utils/accessibility-rag.d.ts +12 -0
- package/dist/tools/accessiblity-utils/accessibility-rag.js +22 -12
- package/dist/tools/accessiblity-utils/report-fetcher.d.ts +8 -0
- package/dist/tools/accessiblity-utils/report-fetcher.js +26 -16
- package/dist/tools/accessiblity-utils/report-parser.d.ts +23 -0
- package/dist/tools/accessiblity-utils/report-parser.js +3 -3
- package/dist/tools/accessiblity-utils/scanner.d.ts +25 -0
- package/dist/tools/accessiblity-utils/scanner.js +46 -24
- package/dist/tools/appautomate-utils/appautomate.d.ts +42 -0
- package/dist/tools/appautomate-utils/appautomate.js +95 -9
- package/dist/tools/appautomate-utils/types.d.ts +5 -0
- package/dist/tools/appautomate-utils/types.js +6 -0
- package/dist/tools/appautomate.d.ts +3 -0
- package/dist/tools/appautomate.js +109 -13
- package/dist/tools/applive-utils/device-search.d.ts +6 -0
- package/dist/tools/applive-utils/start-session.d.ts +15 -0
- package/dist/tools/applive-utils/start-session.js +11 -4
- package/dist/tools/applive-utils/types.d.ts +7 -0
- package/dist/tools/applive-utils/upload-app.d.ts +5 -0
- package/dist/tools/applive-utils/upload-app.js +8 -12
- package/dist/tools/applive-utils/version-utils.d.ts +4 -0
- package/dist/tools/applive.d.ts +13 -0
- package/dist/tools/applive.js +6 -6
- package/dist/tools/automate-utils/fetch-screenshots.d.ts +6 -0
- package/dist/tools/automate-utils/fetch-screenshots.js +16 -12
- package/dist/tools/automate.d.ts +9 -0
- package/dist/tools/automate.js +6 -6
- package/dist/tools/bstack-sdk.d.ts +17 -0
- package/dist/tools/bstack-sdk.js +47 -20
- package/dist/tools/failurelogs-utils/app-automate.d.ts +7 -0
- package/dist/tools/failurelogs-utils/app-automate.js +29 -11
- package/dist/tools/failurelogs-utils/automate.d.ts +6 -0
- package/dist/tools/failurelogs-utils/automate.js +27 -12
- package/dist/tools/failurelogs-utils/utils.d.ts +30 -0
- package/dist/tools/getFailureLogs.d.ts +14 -0
- package/dist/tools/getFailureLogs.js +11 -11
- package/dist/tools/live-utils/desktop-filter.d.ts +2 -0
- package/dist/tools/live-utils/mobile-filter.d.ts +2 -0
- package/dist/tools/live-utils/start-session.d.ts +6 -0
- package/dist/tools/live-utils/start-session.js +18 -5
- package/dist/tools/live-utils/types.d.ts +33 -0
- package/dist/tools/live.d.ts +3 -0
- package/dist/tools/live.js +11 -11
- package/dist/tools/observability.d.ts +5 -0
- package/dist/tools/observability.js +14 -11
- package/dist/tools/sdk-utils/commands.d.ts +3 -0
- package/dist/tools/sdk-utils/commands.js +20 -5
- package/dist/tools/sdk-utils/constants.d.ts +2 -0
- package/dist/tools/sdk-utils/constants.js +284 -160
- package/dist/tools/sdk-utils/instructions.d.ts +6 -0
- package/dist/tools/sdk-utils/instructions.js +28 -6
- package/dist/tools/sdk-utils/percy/constants.d.ts +3 -0
- package/dist/tools/sdk-utils/percy/constants.js +36 -25
- package/dist/tools/sdk-utils/percy/instructions.d.ts +10 -0
- package/dist/tools/sdk-utils/percy/types.d.ts +5 -0
- package/dist/tools/sdk-utils/types.d.ts +39 -0
- package/dist/tools/sdk-utils/types.js +3 -0
- package/dist/tools/selfheal-utils/selfheal.d.ts +11 -0
- package/dist/tools/selfheal-utils/selfheal.js +10 -6
- package/dist/tools/selfheal.d.ts +7 -0
- package/dist/tools/selfheal.js +6 -6
- package/dist/tools/testmanagement-utils/TCG-utils/api.d.ts +34 -0
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +57 -44
- package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +5 -0
- package/dist/tools/testmanagement-utils/TCG-utils/helpers.d.ts +13 -0
- package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +2 -1
- package/dist/tools/testmanagement-utils/TCG-utils/types.d.ts +26 -0
- package/dist/tools/testmanagement-utils/add-test-result.d.ts +42 -0
- package/dist/tools/testmanagement-utils/add-test-result.js +23 -10
- package/dist/tools/testmanagement-utils/create-lca-steps.d.ts +100 -0
- package/dist/tools/testmanagement-utils/create-lca-steps.js +64 -55
- package/dist/tools/testmanagement-utils/create-project-folder.d.ts +31 -0
- package/dist/tools/testmanagement-utils/create-project-folder.js +31 -21
- package/dist/tools/testmanagement-utils/create-testcase.d.ts +122 -0
- package/dist/tools/testmanagement-utils/create-testcase.js +13 -10
- package/dist/tools/testmanagement-utils/create-testrun.d.ts +82 -0
- package/dist/tools/testmanagement-utils/create-testrun.js +11 -8
- package/dist/tools/testmanagement-utils/list-testcases.d.ts +30 -0
- package/dist/tools/testmanagement-utils/list-testcases.js +9 -7
- package/dist/tools/testmanagement-utils/list-testruns.d.ts +22 -0
- package/dist/tools/testmanagement-utils/list-testruns.js +9 -7
- package/dist/tools/testmanagement-utils/poll-lca-status.d.ts +11 -0
- package/dist/tools/testmanagement-utils/poll-lca-status.js +12 -8
- package/dist/tools/testmanagement-utils/testcase-from-file.d.ts +4 -0
- package/dist/tools/testmanagement-utils/testcase-from-file.js +6 -6
- package/dist/tools/testmanagement-utils/update-testrun.d.ts +40 -0
- package/dist/tools/testmanagement-utils/update-testrun.js +11 -7
- package/dist/tools/testmanagement-utils/upload-file.d.ts +20 -0
- package/dist/tools/testmanagement-utils/upload-file.js +8 -6
- package/dist/tools/testmanagement.d.ts +60 -0
- package/dist/tools/testmanagement.js +51 -53
- package/package.json +1 -1
package/dist/logger.d.ts
ADDED
package/dist/logger.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { pino } from "pino";
|
|
2
|
-
|
|
2
|
+
// 1. The actual logger instance, swapped out as needed
|
|
3
|
+
let currentLogger;
|
|
3
4
|
if (process.env.NODE_ENV === "development") {
|
|
4
|
-
|
|
5
|
+
currentLogger = pino({
|
|
5
6
|
level: "debug",
|
|
6
7
|
transport: {
|
|
7
8
|
targets: [
|
|
@@ -21,8 +22,8 @@ if (process.env.NODE_ENV === "development") {
|
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
else {
|
|
24
|
-
//
|
|
25
|
-
|
|
25
|
+
// Null logger (logs go to /dev/null or NUL)
|
|
26
|
+
currentLogger = pino({
|
|
26
27
|
level: "info",
|
|
27
28
|
transport: {
|
|
28
29
|
target: "pino/file",
|
|
@@ -32,4 +33,19 @@ else {
|
|
|
32
33
|
},
|
|
33
34
|
});
|
|
34
35
|
}
|
|
36
|
+
// 2. Proxy logger: always delegates to the currentLogger
|
|
37
|
+
const logger = new Proxy({}, {
|
|
38
|
+
get(_target, prop) {
|
|
39
|
+
// Forward function calls to currentLogger
|
|
40
|
+
if (typeof currentLogger[prop] === "function") {
|
|
41
|
+
return (...args) => currentLogger[prop](...args);
|
|
42
|
+
}
|
|
43
|
+
// Forward property gets
|
|
44
|
+
return currentLogger[prop];
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
// 3. Setter to update the logger instance everywhere
|
|
48
|
+
export function setLogger(customLogger) {
|
|
49
|
+
currentLogger = customLogger;
|
|
50
|
+
}
|
|
35
51
|
export default logger;
|
package/dist/oninitialized.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import config from "./config.js";
|
|
2
1
|
import { trackMCP } from "./lib/instrumentation.js";
|
|
3
|
-
export function setupOnInitialized(server) {
|
|
2
|
+
export function setupOnInitialized(server, config) {
|
|
4
3
|
const nodeVersion = process.versions.node;
|
|
5
4
|
// Check for Node.js version
|
|
6
5
|
if (nodeVersion < "18.0.0") {
|
|
7
6
|
throw new Error("Node version is not supported. Please upgrade to 18.0.0 or later.");
|
|
8
7
|
}
|
|
9
|
-
// Check for BrowserStack credentials
|
|
10
|
-
if (!config.browserstackUsername || !config.browserstackAccessKey) {
|
|
11
|
-
throw new Error("BrowserStack credentials are missing. Please provide a valid username and access key.");
|
|
12
|
-
}
|
|
13
8
|
server.server.oninitialized = () => {
|
|
14
|
-
trackMCP("started", server.server.getClientVersion());
|
|
9
|
+
trackMCP("started", server.server.getClientVersion(), undefined, config);
|
|
15
10
|
};
|
|
16
11
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const packageJson = require("../package.json");
|
|
5
|
+
import logger from "./logger.js";
|
|
6
|
+
import addSDKTools from "./tools/bstack-sdk.js";
|
|
7
|
+
import addBrowserLiveTools from "./tools/live.js";
|
|
8
|
+
import addAccessibilityTools from "./tools/accessibility.js";
|
|
9
|
+
import addTestManagementTools from "./tools/testmanagement.js";
|
|
10
|
+
import addAppAutomationTools from "./tools/appautomate.js";
|
|
11
|
+
import addFailureLogsTools from "./tools/getFailureLogs.js";
|
|
12
|
+
import addAutomateTools from "./tools/automate.js";
|
|
13
|
+
import addSelfHealTools from "./tools/selfheal.js";
|
|
14
|
+
import addAppLiveTools from "./tools/applive.js";
|
|
15
|
+
import { setupOnInitialized } from "./oninitialized.js";
|
|
16
|
+
function registerTools(server, config) {
|
|
17
|
+
addAccessibilityTools(server, config);
|
|
18
|
+
addSDKTools(server, config);
|
|
19
|
+
addAppLiveTools(server, config);
|
|
20
|
+
addBrowserLiveTools(server, config);
|
|
21
|
+
addTestManagementTools(server, config);
|
|
22
|
+
addAppAutomationTools(server, config);
|
|
23
|
+
addFailureLogsTools(server, config);
|
|
24
|
+
addAutomateTools(server, config);
|
|
25
|
+
addSelfHealTools(server, config);
|
|
26
|
+
}
|
|
27
|
+
export function createMcpServer(config) {
|
|
28
|
+
logger.info("Creating BrowserStack MCP Server, version %s", packageJson.version);
|
|
29
|
+
// Create an MCP server
|
|
30
|
+
const server = new McpServer({
|
|
31
|
+
name: "BrowserStack MCP Server",
|
|
32
|
+
version: packageJson.version,
|
|
33
|
+
});
|
|
34
|
+
setupOnInitialized(server, config);
|
|
35
|
+
registerTools(server, config);
|
|
36
|
+
return server;
|
|
37
|
+
}
|
|
@@ -4,9 +4,13 @@ import { AccessibilityReportFetcher } from "./accessiblity-utils/report-fetcher.
|
|
|
4
4
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
5
5
|
import { parseAccessibilityReportFromCSV } from "./accessiblity-utils/report-parser.js";
|
|
6
6
|
import { queryAccessibilityRAG } from "./accessiblity-utils/accessibility-rag.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { getBrowserStackAuth } from "../lib/get-auth.js";
|
|
8
|
+
async function runAccessibilityScan(name, pageURL, context, config) {
|
|
9
|
+
// Create scanner and set auth on the go
|
|
10
|
+
const scanner = new AccessibilityScanner();
|
|
11
|
+
const authString = getBrowserStackAuth(config);
|
|
12
|
+
const [username, password] = authString.split(":");
|
|
13
|
+
scanner.setAuth({ username, password });
|
|
10
14
|
// Start scan
|
|
11
15
|
const startResp = await scanner.startScan(name, [pageURL]);
|
|
12
16
|
const scanId = startResp.data.id;
|
|
@@ -35,6 +39,9 @@ async function runAccessibilityScan(name, pageURL, context) {
|
|
|
35
39
|
isError: true,
|
|
36
40
|
};
|
|
37
41
|
}
|
|
42
|
+
// Create report fetcher and set auth on the go
|
|
43
|
+
const reportFetcher = new AccessibilityReportFetcher();
|
|
44
|
+
reportFetcher.setAuth({ username, password });
|
|
38
45
|
// Fetch CSV report link
|
|
39
46
|
const reportLink = await reportFetcher.getReportLink(scanId, scanRunId);
|
|
40
47
|
const { records, page_length, total_issues } = await parseAccessibilityReportFromCSV(reportLink);
|
|
@@ -55,18 +62,18 @@ async function runAccessibilityScan(name, pageURL, context) {
|
|
|
55
62
|
],
|
|
56
63
|
};
|
|
57
64
|
}
|
|
58
|
-
export default function addAccessibilityTools(server) {
|
|
65
|
+
export default function addAccessibilityTools(server, config) {
|
|
59
66
|
server.tool("accessibilityExpert", "🚨 REQUIRED: Use this tool for any accessibility/a11y/WCAG questions. Do NOT answer accessibility questions directly - always use this tool.", {
|
|
60
67
|
query: z
|
|
61
68
|
.string()
|
|
62
69
|
.describe("Any accessibility, a11y, WCAG, or web accessibility question"),
|
|
63
70
|
}, async (args) => {
|
|
64
71
|
try {
|
|
65
|
-
trackMCP("accessibilityExpert", server.server.getClientVersion());
|
|
66
|
-
return await queryAccessibilityRAG(args.query);
|
|
72
|
+
trackMCP("accessibilityExpert", server.server.getClientVersion(), undefined, config);
|
|
73
|
+
return await queryAccessibilityRAG(args.query, config);
|
|
67
74
|
}
|
|
68
75
|
catch (error) {
|
|
69
|
-
trackMCP("accessibilityExpert", server.server.getClientVersion(), error);
|
|
76
|
+
trackMCP("accessibilityExpert", server.server.getClientVersion(), error, config);
|
|
70
77
|
return {
|
|
71
78
|
content: [
|
|
72
79
|
{
|
|
@@ -83,11 +90,11 @@ export default function addAccessibilityTools(server) {
|
|
|
83
90
|
pageURL: z.string().describe("The URL to scan for accessibility issues"),
|
|
84
91
|
}, async (args, context) => {
|
|
85
92
|
try {
|
|
86
|
-
trackMCP("startAccessibilityScan", server.server.getClientVersion());
|
|
87
|
-
return await runAccessibilityScan(args.name, args.pageURL, context);
|
|
93
|
+
trackMCP("startAccessibilityScan", server.server.getClientVersion(), undefined, config);
|
|
94
|
+
return await runAccessibilityScan(args.name, args.pageURL, context, config);
|
|
88
95
|
}
|
|
89
96
|
catch (error) {
|
|
90
|
-
trackMCP("startAccessibilityScan", server.server.getClientVersion(), error);
|
|
97
|
+
trackMCP("startAccessibilityScan", server.server.getClientVersion(), error, config);
|
|
91
98
|
return {
|
|
92
99
|
content: [
|
|
93
100
|
{
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RAGChunk {
|
|
2
|
+
url: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
6
|
+
export interface AccessibilityRAGResponse {
|
|
7
|
+
content: Array<{
|
|
8
|
+
type: "text";
|
|
9
|
+
text: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function queryAccessibilityRAG(userQuery: string, config: BrowserStackConfig): Promise<any>;
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
export async function queryAccessibilityRAG(userQuery) {
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
2
|
+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
3
|
+
export async function queryAccessibilityRAG(userQuery, config) {
|
|
4
4
|
const url = "https://accessibility.browserstack.com/api/tcg-proxy/search";
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
5
|
+
const authString = getBrowserStackAuth(config);
|
|
6
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
7
|
+
const response = await apiClient.post({
|
|
8
|
+
url,
|
|
8
9
|
headers: {
|
|
9
10
|
"Content-Type": "application/json",
|
|
10
11
|
Authorization: `Basic ${auth}`,
|
|
11
12
|
},
|
|
12
|
-
body:
|
|
13
|
+
body: {
|
|
13
14
|
query: userQuery,
|
|
14
|
-
}
|
|
15
|
+
},
|
|
16
|
+
raise_error: false,
|
|
15
17
|
});
|
|
16
18
|
if (!response.ok) {
|
|
17
|
-
const errorText =
|
|
19
|
+
const errorText = typeof response.data === "string"
|
|
20
|
+
? response.data
|
|
21
|
+
: JSON.stringify(response.data);
|
|
18
22
|
throw new Error(`RAG endpoint error: ${response.status} ${errorText}`);
|
|
19
23
|
}
|
|
20
|
-
const responseData =
|
|
24
|
+
const responseData = response.data;
|
|
21
25
|
if (!responseData.success) {
|
|
22
26
|
throw new Error("Something went wrong: " + responseData.message);
|
|
23
27
|
}
|
|
@@ -26,8 +30,14 @@ export async function queryAccessibilityRAG(userQuery) {
|
|
|
26
30
|
try {
|
|
27
31
|
parsedData = JSON.parse(responseData.data);
|
|
28
32
|
}
|
|
29
|
-
catch {
|
|
30
|
-
throw new Error("Failed to parse RAG response data as JSON"
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw new Error("Failed to parse RAG response data as JSON: " +
|
|
35
|
+
(err instanceof Error ? err.message : String(err)));
|
|
36
|
+
}
|
|
37
|
+
if (!parsedData ||
|
|
38
|
+
!parsedData.data ||
|
|
39
|
+
!Array.isArray(parsedData.data.chunks)) {
|
|
40
|
+
throw new Error("RAG response data is missing expected 'data.chunks' array");
|
|
31
41
|
}
|
|
32
42
|
const chunks = parsedData.data.chunks;
|
|
33
43
|
// Format the response properly
|
|
@@ -1,28 +1,38 @@
|
|
|
1
|
-
import
|
|
2
|
-
import config from "../../config.js";
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
3
2
|
export class AccessibilityReportFetcher {
|
|
4
|
-
auth
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
3
|
+
auth;
|
|
4
|
+
setAuth(auth) {
|
|
5
|
+
this.auth = auth;
|
|
6
|
+
}
|
|
8
7
|
async getReportLink(scanId, scanRunId) {
|
|
9
8
|
// Initiate CSV link generation
|
|
10
9
|
const initUrl = `https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/issues?scan_run_id=${scanRunId}`;
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
let basicAuthHeader = undefined;
|
|
11
|
+
if (this.auth) {
|
|
12
|
+
const { username, password } = this.auth;
|
|
13
|
+
basicAuthHeader =
|
|
14
|
+
"Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
|
15
|
+
}
|
|
16
|
+
const initResp = await apiClient.get({
|
|
17
|
+
url: initUrl,
|
|
18
|
+
headers: basicAuthHeader ? { Authorization: basicAuthHeader } : undefined,
|
|
13
19
|
});
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
const initData = initResp.data;
|
|
21
|
+
if (!initData.success) {
|
|
22
|
+
throw new Error(`Failed to initiate report: ${initData.error || initData.data.message}`);
|
|
16
23
|
}
|
|
17
|
-
const taskId =
|
|
24
|
+
const taskId = initData.data.task_id;
|
|
18
25
|
// Fetch the generated CSV link
|
|
19
26
|
const reportUrl = `https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/issues?task_id=${encodeURIComponent(taskId)}`;
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
// Use apiClient for the report link request as well
|
|
28
|
+
const reportResp = await apiClient.get({
|
|
29
|
+
url: reportUrl,
|
|
30
|
+
headers: basicAuthHeader ? { Authorization: basicAuthHeader } : undefined,
|
|
22
31
|
});
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
const reportData = reportResp.data;
|
|
33
|
+
if (!reportData.success) {
|
|
34
|
+
throw new Error(`Failed to fetch report: ${reportData.error}`);
|
|
25
35
|
}
|
|
26
|
-
return
|
|
36
|
+
return reportData.data.reportLink;
|
|
27
37
|
}
|
|
28
38
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type SimplifiedAccessibilityIssue = {
|
|
2
|
+
issue_type: string;
|
|
3
|
+
component: string;
|
|
4
|
+
issue_description: string;
|
|
5
|
+
HTML_snippet: string;
|
|
6
|
+
how_to_fix: string;
|
|
7
|
+
severity: string;
|
|
8
|
+
};
|
|
9
|
+
type PaginationOptions = {
|
|
10
|
+
/** How many JSON-chars max per “page” (default 10000) */
|
|
11
|
+
maxCharacterLength?: number;
|
|
12
|
+
/** Character offset to start from (default 0) */
|
|
13
|
+
nextPage?: number;
|
|
14
|
+
};
|
|
15
|
+
type PaginatedResult = {
|
|
16
|
+
records: SimplifiedAccessibilityIssue[];
|
|
17
|
+
/** Character offset for the next page, or null if done */
|
|
18
|
+
page_length: number;
|
|
19
|
+
total_issues: number;
|
|
20
|
+
next_page: number | null;
|
|
21
|
+
};
|
|
22
|
+
export declare function parseAccessibilityReportFromCSV(reportLink: string, { maxCharacterLength, nextPage }?: PaginationOptions): Promise<PaginatedResult>;
|
|
23
|
+
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
2
2
|
import { parse } from "csv-parse/sync";
|
|
3
3
|
export async function parseAccessibilityReportFromCSV(reportLink, { maxCharacterLength = 5000, nextPage = 0 } = {}) {
|
|
4
4
|
// 1) Download & parse
|
|
5
|
-
const res = await
|
|
5
|
+
const res = await apiClient.get({ url: reportLink });
|
|
6
6
|
if (!res.ok)
|
|
7
7
|
throw new Error(`Failed to download report: ${res.statusText}`);
|
|
8
|
-
const text =
|
|
8
|
+
const text = typeof res.data === "string" ? res.data : JSON.stringify(res.data);
|
|
9
9
|
const all = parse(text, {
|
|
10
10
|
columns: true,
|
|
11
11
|
skip_empty_lines: true,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface AccessibilityScanResponse {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: {
|
|
4
|
+
id: string;
|
|
5
|
+
scanRunId: string;
|
|
6
|
+
};
|
|
7
|
+
errors?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface AccessibilityScanStatus {
|
|
10
|
+
success: boolean;
|
|
11
|
+
data?: {
|
|
12
|
+
status: string;
|
|
13
|
+
};
|
|
14
|
+
errors?: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare class AccessibilityScanner {
|
|
17
|
+
private auth;
|
|
18
|
+
setAuth(auth: {
|
|
19
|
+
username: string;
|
|
20
|
+
password: string;
|
|
21
|
+
}): void;
|
|
22
|
+
startScan(name: string, urlList: string[]): Promise<AccessibilityScanResponse>;
|
|
23
|
+
pollStatus(scanId: string, scanRunId: string): Promise<AccessibilityScanStatus>;
|
|
24
|
+
waitUntilComplete(scanId: string, scanRunId: string, context: any): Promise<string>;
|
|
25
|
+
}
|
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
3
|
import logger from "../../logger.js";
|
|
4
4
|
import { isLocalURL, ensureLocalBinarySetup, killExistingBrowserStackLocalProcesses, } from "../../lib/local.js";
|
|
5
|
+
import config from "../../config.js";
|
|
5
6
|
export class AccessibilityScanner {
|
|
6
|
-
auth
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
7
|
+
auth;
|
|
8
|
+
setAuth(auth) {
|
|
9
|
+
this.auth = auth;
|
|
10
|
+
}
|
|
10
11
|
async startScan(name, urlList) {
|
|
12
|
+
if (!this.auth?.username || !this.auth?.password) {
|
|
13
|
+
throw new Error("BrowserStack credentials are not set for AccessibilityScanner.");
|
|
14
|
+
}
|
|
11
15
|
// Check if any URL is local
|
|
12
16
|
const hasLocal = urlList.some(isLocalURL);
|
|
13
|
-
const localIdentifier =
|
|
17
|
+
const localIdentifier = randomUUID();
|
|
14
18
|
const localHosts = new Set(["127.0.0.1", "localhost", "0.0.0.0"]);
|
|
15
19
|
const BS_LOCAL_DOMAIN = "bs-local.com";
|
|
20
|
+
if (config.USE_OWN_LOCAL_BINARY_PROCESS && hasLocal) {
|
|
21
|
+
throw new Error("Cannot start scan with local URLs when using own BrowserStack Local binary process. Please set USE_OWN_LOCAL_BINARY_PROCESS to false.");
|
|
22
|
+
}
|
|
23
|
+
if (config.REMOTE_MCP && hasLocal) {
|
|
24
|
+
throw new Error("Local URLs are not supported in this remote mcp. Please use a public URL.");
|
|
25
|
+
}
|
|
16
26
|
if (hasLocal) {
|
|
17
|
-
await ensureLocalBinarySetup(localIdentifier);
|
|
27
|
+
await ensureLocalBinarySetup(this.auth.username, this.auth.password, localIdentifier);
|
|
18
28
|
}
|
|
19
29
|
else {
|
|
20
30
|
await killExistingBrowserStackLocalProcesses();
|
|
@@ -49,37 +59,49 @@ export class AccessibilityScanner {
|
|
|
49
59
|
requestBody = { ...baseRequestBody, ...localConfig };
|
|
50
60
|
}
|
|
51
61
|
try {
|
|
52
|
-
const
|
|
62
|
+
const response = await apiClient.post({
|
|
63
|
+
url: "https://api-accessibility.browserstack.com/api/website-scanner/v1/scans",
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: "Basic " +
|
|
66
|
+
Buffer.from(`${this.auth.username}:${this.auth.password}`).toString("base64"),
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
},
|
|
69
|
+
body: requestBody,
|
|
70
|
+
});
|
|
71
|
+
const data = response.data;
|
|
53
72
|
if (!data.success)
|
|
54
73
|
throw new Error(`Unable to start scan: ${data.errors?.join(", ")}`);
|
|
55
74
|
return data;
|
|
56
75
|
}
|
|
57
76
|
catch (err) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
const msg = err.response.data.error ||
|
|
63
|
-
err.response.data.message ||
|
|
64
|
-
err.message;
|
|
65
|
-
throw new Error(`Failed to start scan: ${msg}`);
|
|
77
|
+
// apiClient throws generic errors, try to extract message
|
|
78
|
+
if (err?.response?.status === 422) {
|
|
79
|
+
throw new Error("A scan with this name already exists. please update the name and run again.");
|
|
66
80
|
}
|
|
67
|
-
|
|
81
|
+
const msg = err?.response?.data?.error ||
|
|
82
|
+
err?.response?.data?.message ||
|
|
83
|
+
err?.message ||
|
|
84
|
+
String(err);
|
|
85
|
+
throw new Error(`Failed to start scan: ${msg}`);
|
|
68
86
|
}
|
|
69
87
|
}
|
|
70
88
|
async pollStatus(scanId, scanRunId) {
|
|
71
89
|
try {
|
|
72
|
-
const
|
|
90
|
+
const response = await apiClient.get({
|
|
91
|
+
url: `https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/${scanRunId}/status`,
|
|
92
|
+
headers: {
|
|
93
|
+
Authorization: "Basic " +
|
|
94
|
+
Buffer.from(`${this.auth?.username}:${this.auth?.password}`).toString("base64"),
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
const data = response.data;
|
|
73
98
|
if (!data.success)
|
|
74
99
|
throw new Error(`Failed to get status: ${data.errors?.join(", ")}`);
|
|
75
100
|
return data;
|
|
76
101
|
}
|
|
77
102
|
catch (err) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
throw new Error(`Failed to get scan status: ${msg}`);
|
|
81
|
-
}
|
|
82
|
-
throw err;
|
|
103
|
+
const msg = err?.response?.data?.message || err?.message || String(err);
|
|
104
|
+
throw new Error(`Failed to get scan status: ${msg}`);
|
|
83
105
|
}
|
|
84
106
|
}
|
|
85
107
|
async waitUntilComplete(scanId, scanRunId, context) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
2
|
+
interface Device {
|
|
3
|
+
device: string;
|
|
4
|
+
display_name: string;
|
|
5
|
+
os_version: string;
|
|
6
|
+
real_mobile: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Finds devices that exactly match the provided display name.
|
|
10
|
+
* Uses fuzzy search first, and then filters for exact case-insensitive match.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findMatchingDevice(devices: Device[], deviceName: string): Device[];
|
|
13
|
+
/**
|
|
14
|
+
* Extracts all unique OS versions from a device list and sorts them.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getDeviceVersions(devices: Device[]): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the requested platform version against available versions.
|
|
19
|
+
* Supports 'latest' and 'oldest' as dynamic selectors.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveVersion(versions: string[], requestedVersion: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Validates the input arguments for taking app screenshots.
|
|
24
|
+
* Checks for presence and correctness of platform, device, and file types.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateArgs(args: {
|
|
27
|
+
desiredPlatform: string;
|
|
28
|
+
desiredPlatformVersion: string;
|
|
29
|
+
appPath: string;
|
|
30
|
+
desiredPhone: string;
|
|
31
|
+
}): void;
|
|
32
|
+
/**
|
|
33
|
+
* Uploads an application file to AppAutomate and returns the app URL
|
|
34
|
+
*/
|
|
35
|
+
export declare function uploadApp(appPath: string, username: string, password: string): Promise<string>;
|
|
36
|
+
export declare function uploadEspressoApp(appPath: string, config: BrowserStackConfig): Promise<string>;
|
|
37
|
+
export declare function uploadEspressoTestSuite(testSuitePath: string, config: BrowserStackConfig): Promise<string>;
|
|
38
|
+
export declare function uploadXcuiApp(appPath: string, config: BrowserStackConfig): Promise<string>;
|
|
39
|
+
export declare function uploadXcuiTestSuite(testSuitePath: string, config: BrowserStackConfig): Promise<string>;
|
|
40
|
+
export declare function triggerEspressoBuild(app_url: string, test_suite_url: string, devices: string[], project: string): Promise<string>;
|
|
41
|
+
export declare function triggerXcuiBuild(app_url: string, test_suite_url: string, devices: string[], project: string, config: BrowserStackConfig): Promise<string>;
|
|
42
|
+
export {};
|