@browserstack/mcp-server 1.0.5 → 1.0.8
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 +9 -1
- package/package.json +1 -1
- package/dist/config.js +0 -18
- package/dist/index.js +0 -41
- package/dist/lib/api.js +0 -22
- package/dist/lib/local.js +0 -109
- package/dist/lib/utils.js +0 -7
- package/dist/logger.js +0 -40
- package/dist/tools/accessibility.js +0 -43
- package/dist/tools/accessiblity-utils/accessibility.js +0 -82
- package/dist/tools/applive-utils/constants.js +0 -82
- package/dist/tools/applive-utils/start-session.js +0 -55
- package/dist/tools/applive-utils/types.js +0 -2
- package/dist/tools/applive-utils/upload-app.js +0 -68
- package/dist/tools/applive.js +0 -93
- package/dist/tools/bstack-sdk.js +0 -64
- package/dist/tools/live-utils/start-session.js +0 -58
- package/dist/tools/live.js +0 -97
- package/dist/tools/observability.js +0 -58
- package/dist/tools/sdk-utils/constants.js +0 -63
- package/dist/tools/sdk-utils/instructions.js +0 -64
- package/dist/tools/sdk-utils/types.js +0 -2
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ Use the following prompts to run/debug/fix your **automated tests** on BrowserSt
|
|
|
88
88
|
|
|
89
89
|
1. **Create a BrowserStack Account**
|
|
90
90
|
|
|
91
|
-
- Sign up for [BrowserStack](https://www.browserstack.com/
|
|
91
|
+
- Sign up for [BrowserStack](https://www.browserstack.com/users/sign_up) if you don't have an account already.
|
|
92
92
|
|
|
93
93
|
- ℹ️ If you have an open-source project, we'll be able to provide you with a [free plan](https://www.browserstack.com/open-source).
|
|
94
94
|
<div align="center">
|
|
@@ -154,6 +154,14 @@ Use the following prompts to run/debug/fix your **automated tests** on BrowserSt
|
|
|
154
154
|
}
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
### Installing via Smithery
|
|
158
|
+
|
|
159
|
+
To install BrowserStack Test Platform Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@browserstack/mcp-server):
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npx -y @smithery/cli install @browserstack/mcp-server --client claude
|
|
163
|
+
```
|
|
164
|
+
|
|
157
165
|
## 🤝 Recommended MCP Clients
|
|
158
166
|
|
|
159
167
|
- We recommend using **Github Copilot or Cursor** for automated testing + debugging use cases.
|
package/package.json
CHANGED
package/dist/config.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Config = void 0;
|
|
4
|
-
if (!process.env.BROWSERSTACK_ACCESS_KEY ||
|
|
5
|
-
!process.env.BROWSERSTACK_USERNAME) {
|
|
6
|
-
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
|
-
}
|
|
8
|
-
class Config {
|
|
9
|
-
browserstackUsername;
|
|
10
|
-
browserstackAccessKey;
|
|
11
|
-
constructor(browserstackUsername, browserstackAccessKey) {
|
|
12
|
-
this.browserstackUsername = browserstackUsername;
|
|
13
|
-
this.browserstackAccessKey = browserstackAccessKey;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
exports.Config = Config;
|
|
17
|
-
const config = new Config(process.env.BROWSERSTACK_USERNAME, process.env.BROWSERSTACK_ACCESS_KEY);
|
|
18
|
-
exports.default = config;
|
package/dist/index.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
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
|
-
function registerTools(server) {
|
|
18
|
-
(0, bstack_sdk_1.default)(server);
|
|
19
|
-
(0, applive_1.default)(server);
|
|
20
|
-
(0, live_1.default)(server);
|
|
21
|
-
(0, observability_1.default)(server);
|
|
22
|
-
(0, accessibility_1.default)(server);
|
|
23
|
-
}
|
|
24
|
-
// Create an MCP server
|
|
25
|
-
const server = new mcp_js_1.McpServer({
|
|
26
|
-
name: "BrowserStack MCP Server",
|
|
27
|
-
version: package_json_1.default.version,
|
|
28
|
-
});
|
|
29
|
-
registerTools(server);
|
|
30
|
-
async function main() {
|
|
31
|
-
logger_1.default.info("Launching BrowserStack MCP server, version %s", package_json_1.default.version);
|
|
32
|
-
// Start receiving messages on stdin and sending messages on stdout
|
|
33
|
-
const transport = new stdio_js_1.StdioServerTransport();
|
|
34
|
-
await server.connect(transport);
|
|
35
|
-
logger_1.default.info("MCP server started successfully");
|
|
36
|
-
}
|
|
37
|
-
main().catch(console.error);
|
|
38
|
-
// Ensure logs are flushed before exit
|
|
39
|
-
process.on("exit", () => {
|
|
40
|
-
logger_1.default.flush();
|
|
41
|
-
});
|
package/dist/lib/api.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
const config_1 = __importDefault(require("../config"));
|
|
8
|
-
async function getLatestO11YBuildInfo(buildName, projectName) {
|
|
9
|
-
const buildsUrl = `https://api-observability.browserstack.com/ext/v1/builds/latest?build_name=${encodeURIComponent(buildName)}&project_name=${encodeURIComponent(projectName)}`;
|
|
10
|
-
const buildsResponse = await fetch(buildsUrl, {
|
|
11
|
-
headers: {
|
|
12
|
-
Authorization: `Basic ${Buffer.from(`${config_1.default.browserstackUsername}:${config_1.default.browserstackAccessKey}`).toString("base64")}`,
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
if (!buildsResponse.ok) {
|
|
16
|
-
if (buildsResponse.statusText === "Unauthorized") {
|
|
17
|
-
throw new Error(`Failed to fetch builds: ${buildsResponse.statusText}. Please check if the BrowserStack credentials are correctly configured when installing the MCP server.`);
|
|
18
|
-
}
|
|
19
|
-
throw new Error(`Failed to fetch builds: ${buildsResponse.statusText}`);
|
|
20
|
-
}
|
|
21
|
-
return buildsResponse.json();
|
|
22
|
-
}
|
package/dist/lib/local.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
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"));
|
|
13
|
-
async function isBrowserStackLocalRunning() {
|
|
14
|
-
// Check if BrowserStackLocal binary is already running
|
|
15
|
-
try {
|
|
16
|
-
if (process.platform === "win32") {
|
|
17
|
-
const result = (0, child_process_1.execSync)('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', {
|
|
18
|
-
encoding: "utf8",
|
|
19
|
-
});
|
|
20
|
-
if (result.includes("BrowserStackLocal.exe")) {
|
|
21
|
-
logger_1.default.info("BrowserStackLocal binary is already running");
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
const result = (0, child_process_1.execSync)("pgrep -f BrowserStackLocal", {
|
|
27
|
-
encoding: "utf8",
|
|
28
|
-
stdio: "pipe",
|
|
29
|
-
}).toString();
|
|
30
|
-
if (result) {
|
|
31
|
-
logger_1.default.info("BrowserStackLocal binary is already running");
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
logger_1.default.info("BrowserStackLocal binary is not running");
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
logger_1.default.info("Error checking BrowserStackLocal status, assuming not running ... " +
|
|
40
|
-
error);
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
async function killExistingBrowserStackLocalProcesses() {
|
|
45
|
-
const isRunning = await isBrowserStackLocalRunning();
|
|
46
|
-
if (!isRunning) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
// Check and kill any existing BrowserStackLocal processes before starting new one
|
|
50
|
-
try {
|
|
51
|
-
if (process.platform === "win32") {
|
|
52
|
-
// Check if process exists on Windows
|
|
53
|
-
const checkResult = (0, child_process_1.execSync)('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', { encoding: "utf8" });
|
|
54
|
-
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");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
// Check if process exists on Unix-like systems
|
|
61
|
-
const checkResult = (0, child_process_1.execSync)("pgrep -f BrowserStackLocal", {
|
|
62
|
-
encoding: "utf8",
|
|
63
|
-
stdio: "pipe",
|
|
64
|
-
}).toString();
|
|
65
|
-
if (checkResult) {
|
|
66
|
-
(0, child_process_1.execSync)("pkill -f BrowserStackLocal", { stdio: "ignore" });
|
|
67
|
-
logger_1.default.info("Successfully killed existing BrowserStackLocal processes");
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
logger_1.default.info(`Error checking/killing BrowserStackLocal processes: ${error}`);
|
|
73
|
-
// Continue execution as there may not be any processes running
|
|
74
|
-
}
|
|
75
|
-
}
|
|
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();
|
|
79
|
-
await killExistingBrowserStackLocalProcesses();
|
|
80
|
-
return await new Promise((resolve, reject) => {
|
|
81
|
-
localBinary.start({
|
|
82
|
-
key: config_1.default.browserstackAccessKey,
|
|
83
|
-
username: config_1.default.browserstackUsername,
|
|
84
|
-
}, (error) => {
|
|
85
|
-
if (error) {
|
|
86
|
-
logger_1.default.error(`Unable to start BrowserStack Local... please check your credentials and try again. Error: ${error}`);
|
|
87
|
-
reject(new Error(`Unable to configure local tunnel binary, please check your credentials and try again. Error: ${error}`));
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
logger_1.default.info("Successfully started BrowserStack Local");
|
|
91
|
-
resolve();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
function isLocalURL(url) {
|
|
97
|
-
try {
|
|
98
|
-
const urlObj = new URL(url);
|
|
99
|
-
const hostname = urlObj.hostname.toLowerCase();
|
|
100
|
-
return (hostname === "localhost" ||
|
|
101
|
-
hostname === "127.0.0.1" ||
|
|
102
|
-
hostname.endsWith(".local") ||
|
|
103
|
-
hostname.endsWith(".localhost"));
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
logger_1.default.error(`Error checking if URL is local: ${error}`);
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
}
|
package/dist/lib/utils.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sanitizeUrlParam = sanitizeUrlParam;
|
|
4
|
-
function sanitizeUrlParam(param) {
|
|
5
|
-
// Remove any characters that could be used for command injection
|
|
6
|
-
return param.replace(/[;&|`$(){}[\]<>]/g, "");
|
|
7
|
-
}
|
package/dist/logger.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
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"));
|
|
7
|
-
let logger;
|
|
8
|
-
if (process.env.NODE_ENV === "development") {
|
|
9
|
-
logger = (0, pino_1.default)({
|
|
10
|
-
level: "debug",
|
|
11
|
-
transport: {
|
|
12
|
-
targets: [
|
|
13
|
-
{
|
|
14
|
-
level: "debug",
|
|
15
|
-
target: "pino-pretty",
|
|
16
|
-
options: {
|
|
17
|
-
colorize: true,
|
|
18
|
-
levelFirst: true,
|
|
19
|
-
destination: process.platform === "win32"
|
|
20
|
-
? "C:\\Windows\\Temp\\browserstack-mcp-server.log"
|
|
21
|
-
: "/tmp/browserstack-mcp-server.log",
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
// NULL logger
|
|
30
|
-
logger = (0, pino_1.default)({
|
|
31
|
-
level: "info",
|
|
32
|
-
transport: {
|
|
33
|
-
target: "pino/file",
|
|
34
|
-
options: {
|
|
35
|
-
destination: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
exports.default = logger;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.default = addAccessibilityTools;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
const accessibility_1 = require("./accessiblity-utils/accessibility");
|
|
6
|
-
async function runAccessibilityScan(name, pageURL) {
|
|
7
|
-
try {
|
|
8
|
-
const response = await (0, accessibility_1.startAccessibilityScan)(name, [pageURL]);
|
|
9
|
-
const scanId = response.data?.id;
|
|
10
|
-
const scanRunId = response.data?.scanRunId;
|
|
11
|
-
if (!scanId || !scanRunId) {
|
|
12
|
-
throw new Error("Unable to start a accessibility scan, please try again later or open an issue on GitHub if the problem persists");
|
|
13
|
-
}
|
|
14
|
-
return {
|
|
15
|
-
content: [
|
|
16
|
-
{
|
|
17
|
-
type: "text",
|
|
18
|
-
text: `Successfully queued accessibility scan, you will get a report via email within 5 minutes.`,
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
return {
|
|
25
|
-
content: [
|
|
26
|
-
{
|
|
27
|
-
type: "text",
|
|
28
|
-
text: `Failed to start accessibility scan: ${error instanceof Error ? error.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
29
|
-
isError: true,
|
|
30
|
-
},
|
|
31
|
-
],
|
|
32
|
-
isError: true,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function addAccessibilityTools(server) {
|
|
37
|
-
server.tool("startAccessibilityScan", "Use this tool to start an accessibility scan for a list of URLs on BrowserStack.", {
|
|
38
|
-
name: zod_1.z.string().describe("Name of the accessibility scan"),
|
|
39
|
-
pageURL: zod_1.z.string().describe("The URL to scan for accessibility issues"),
|
|
40
|
-
}, async (args) => {
|
|
41
|
-
return runAccessibilityScan(args.name, args.pageURL);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
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.startAccessibilityScan = startAccessibilityScan;
|
|
7
|
-
exports.pollScanStatus = pollScanStatus;
|
|
8
|
-
exports.waitUntilScanComplete = waitUntilScanComplete;
|
|
9
|
-
const axios_1 = __importDefault(require("axios"));
|
|
10
|
-
const config_js_1 = __importDefault(require("../../config.js"));
|
|
11
|
-
const axios_2 = require("axios");
|
|
12
|
-
async function startAccessibilityScan(name, urlList) {
|
|
13
|
-
try {
|
|
14
|
-
const response = await axios_1.default.post("https://api-accessibility.browserstack.com/api/website-scanner/v1/scans", {
|
|
15
|
-
name,
|
|
16
|
-
urlList,
|
|
17
|
-
recurring: false,
|
|
18
|
-
}, {
|
|
19
|
-
auth: {
|
|
20
|
-
username: config_js_1.default.browserstackUsername,
|
|
21
|
-
password: config_js_1.default.browserstackAccessKey,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
if (!response.data.success) {
|
|
25
|
-
throw new Error(`Unable to create an accessibility scan: ${response.data.errors?.join(", ")}`);
|
|
26
|
-
}
|
|
27
|
-
return response.data;
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
if (error instanceof axios_2.AxiosError) {
|
|
31
|
-
if (error.response?.data?.error) {
|
|
32
|
-
throw new Error(`Failed to start accessibility scan: ${error.response?.data?.error}`);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
throw new Error(`Failed to start accessibility scan: ${error.response?.data?.message || error.message}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async function pollScanStatus(scanId, scanRunId) {
|
|
42
|
-
try {
|
|
43
|
-
const response = await axios_1.default.get(`https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/${scanRunId}/status`, {
|
|
44
|
-
auth: {
|
|
45
|
-
username: config_js_1.default.browserstackUsername,
|
|
46
|
-
password: config_js_1.default.browserstackAccessKey,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
if (!response.data.success) {
|
|
50
|
-
throw new Error(`Failed to get scan status: ${response.data.errors?.join(", ")}`);
|
|
51
|
-
}
|
|
52
|
-
return response.data.data?.status || "unknown";
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
if (error instanceof axios_2.AxiosError) {
|
|
56
|
-
throw new Error(`Failed to get scan status: ${error.response?.data?.message || error.message}`);
|
|
57
|
-
}
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async function waitUntilScanComplete(scanId, scanRunId) {
|
|
62
|
-
return new Promise((resolve, reject) => {
|
|
63
|
-
const interval = setInterval(async () => {
|
|
64
|
-
try {
|
|
65
|
-
const status = await pollScanStatus(scanId, scanRunId);
|
|
66
|
-
if (status === "completed") {
|
|
67
|
-
clearInterval(interval);
|
|
68
|
-
resolve();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
clearInterval(interval);
|
|
73
|
-
reject(error);
|
|
74
|
-
}
|
|
75
|
-
}, 5000); // Poll every 5 seconds
|
|
76
|
-
// Set a timeout of 5 minutes
|
|
77
|
-
setTimeout(() => {
|
|
78
|
-
clearInterval(interval);
|
|
79
|
-
reject(new Error("Scan timed out after 5 minutes"));
|
|
80
|
-
}, 5 * 60 * 1000);
|
|
81
|
-
});
|
|
82
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SUPPORTED_LIVE_CONFIGURATIONS = void 0;
|
|
4
|
-
exports.SUPPORTED_LIVE_CONFIGURATIONS = {
|
|
5
|
-
android: {
|
|
6
|
-
chrome: {
|
|
7
|
-
version: "100",
|
|
8
|
-
},
|
|
9
|
-
firefox: {
|
|
10
|
-
version: "100",
|
|
11
|
-
},
|
|
12
|
-
safari: {
|
|
13
|
-
version: "100",
|
|
14
|
-
},
|
|
15
|
-
"samsung browser": {
|
|
16
|
-
version: "100",
|
|
17
|
-
},
|
|
18
|
-
"internet-explorer": {
|
|
19
|
-
version: "100",
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
ios: {
|
|
23
|
-
chrome: {
|
|
24
|
-
version: "100",
|
|
25
|
-
},
|
|
26
|
-
firefox: {
|
|
27
|
-
version: "100",
|
|
28
|
-
},
|
|
29
|
-
edge: {
|
|
30
|
-
version: "100",
|
|
31
|
-
},
|
|
32
|
-
safari: {
|
|
33
|
-
version: "100",
|
|
34
|
-
},
|
|
35
|
-
"samsung browser": {
|
|
36
|
-
version: "100",
|
|
37
|
-
},
|
|
38
|
-
"internet-explorer": {
|
|
39
|
-
version: "100",
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
windows: {
|
|
43
|
-
chrome: {
|
|
44
|
-
version: "100",
|
|
45
|
-
},
|
|
46
|
-
firefox: {
|
|
47
|
-
version: "100",
|
|
48
|
-
},
|
|
49
|
-
edge: {
|
|
50
|
-
version: "100",
|
|
51
|
-
},
|
|
52
|
-
safari: {
|
|
53
|
-
version: "100",
|
|
54
|
-
},
|
|
55
|
-
"samsung browser": {
|
|
56
|
-
version: "100",
|
|
57
|
-
},
|
|
58
|
-
"internet-explorer": {
|
|
59
|
-
version: "100",
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
macos: {
|
|
63
|
-
chrome: {
|
|
64
|
-
version: "100",
|
|
65
|
-
},
|
|
66
|
-
firefox: {
|
|
67
|
-
version: "100",
|
|
68
|
-
},
|
|
69
|
-
edge: {
|
|
70
|
-
version: "100",
|
|
71
|
-
},
|
|
72
|
-
safari: {
|
|
73
|
-
version: "100",
|
|
74
|
-
},
|
|
75
|
-
"samsung browser": {
|
|
76
|
-
version: "100",
|
|
77
|
-
},
|
|
78
|
-
"internet-explorer": {
|
|
79
|
-
version: "100",
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
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.startSession = startSession;
|
|
7
|
-
const child_process_1 = __importDefault(require("child_process"));
|
|
8
|
-
const logger_1 = __importDefault(require("../../logger"));
|
|
9
|
-
const utils_1 = require("../../lib/utils");
|
|
10
|
-
async function startSession(args) {
|
|
11
|
-
// Sanitize all input parameters
|
|
12
|
-
const sanitizedArgs = {
|
|
13
|
-
appUrl: (0, utils_1.sanitizeUrlParam)(args.appUrl),
|
|
14
|
-
desiredPlatform: (0, utils_1.sanitizeUrlParam)(args.desiredPlatform),
|
|
15
|
-
desiredPhone: (0, utils_1.sanitizeUrlParam)(args.desiredPhone),
|
|
16
|
-
desiredPlatformVersion: (0, utils_1.sanitizeUrlParam)(args.desiredPlatformVersion),
|
|
17
|
-
};
|
|
18
|
-
// Get app hash ID and format phone name
|
|
19
|
-
const appHashedId = sanitizedArgs.appUrl.split("bs://").pop();
|
|
20
|
-
const desiredPhoneWithSpaces = sanitizedArgs.desiredPhone.replace(/\s+/g, "+");
|
|
21
|
-
// Construct URL with encoded parameters
|
|
22
|
-
const params = new URLSearchParams({
|
|
23
|
-
os: sanitizedArgs.desiredPlatform,
|
|
24
|
-
os_version: sanitizedArgs.desiredPlatformVersion,
|
|
25
|
-
app_hashed_id: appHashedId || "",
|
|
26
|
-
scale_to_fit: "true",
|
|
27
|
-
speed: "1",
|
|
28
|
-
start: "true",
|
|
29
|
-
});
|
|
30
|
-
const launchUrl = `https://app-live.browserstack.com/dashboard#${params.toString()}&device=${desiredPhoneWithSpaces}`;
|
|
31
|
-
try {
|
|
32
|
-
// Use platform-specific commands with proper escaping
|
|
33
|
-
const command = process.platform === "darwin"
|
|
34
|
-
? ["open", launchUrl]
|
|
35
|
-
: process.platform === "win32"
|
|
36
|
-
? ["cmd", "/c", "start", launchUrl]
|
|
37
|
-
: ["xdg-open", launchUrl];
|
|
38
|
-
// nosemgrep:javascript.lang.security.detect-child-process.detect-child-process
|
|
39
|
-
const child = child_process_1.default.spawn(command[0], command.slice(1), {
|
|
40
|
-
stdio: "ignore",
|
|
41
|
-
detached: true,
|
|
42
|
-
});
|
|
43
|
-
// Handle process errors
|
|
44
|
-
child.on("error", (error) => {
|
|
45
|
-
logger_1.default.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
|
|
46
|
-
});
|
|
47
|
-
// Unref the child process to allow the parent to exit
|
|
48
|
-
child.unref();
|
|
49
|
-
return launchUrl;
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
logger_1.default.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
|
|
53
|
-
return launchUrl;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.uploadApp = uploadApp;
|
|
40
|
-
const axios_1 = __importStar(require("axios"));
|
|
41
|
-
const form_data_1 = __importDefault(require("form-data"));
|
|
42
|
-
const fs_1 = __importDefault(require("fs"));
|
|
43
|
-
const config_1 = __importDefault(require("../../config"));
|
|
44
|
-
async function uploadApp(filePath) {
|
|
45
|
-
if (!fs_1.default.existsSync(filePath)) {
|
|
46
|
-
throw new Error(`File not found at path: ${filePath}`);
|
|
47
|
-
}
|
|
48
|
-
const formData = new form_data_1.default();
|
|
49
|
-
formData.append("file", fs_1.default.createReadStream(filePath));
|
|
50
|
-
try {
|
|
51
|
-
const response = await axios_1.default.post("https://api-cloud.browserstack.com/app-live/upload", formData, {
|
|
52
|
-
headers: {
|
|
53
|
-
...formData.getHeaders(),
|
|
54
|
-
},
|
|
55
|
-
auth: {
|
|
56
|
-
username: config_1.default.browserstackUsername,
|
|
57
|
-
password: config_1.default.browserstackAccessKey,
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
return response.data;
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
if (error instanceof axios_1.AxiosError) {
|
|
64
|
-
throw new Error(`Failed to upload app: ${error.response?.data?.message || error.message}`);
|
|
65
|
-
}
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
}
|
package/dist/tools/applive.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
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 upload_app_1 = require("./applive-utils/upload-app");
|
|
11
|
-
const start_session_1 = require("./applive-utils/start-session");
|
|
12
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
13
|
-
/**
|
|
14
|
-
* Launches an App Live Session on BrowserStack.
|
|
15
|
-
*/
|
|
16
|
-
async function startAppLiveSession(args) {
|
|
17
|
-
if (!args.desiredPlatform) {
|
|
18
|
-
throw new Error("You must provide a desiredPlatform.");
|
|
19
|
-
}
|
|
20
|
-
if (!args.appPath) {
|
|
21
|
-
throw new Error("You must provide a appPath.");
|
|
22
|
-
}
|
|
23
|
-
if (!args.desiredPhone) {
|
|
24
|
-
throw new Error("You must provide a desiredPhone.");
|
|
25
|
-
}
|
|
26
|
-
if (args.desiredPlatform === "android" && !args.appPath.endsWith(".apk")) {
|
|
27
|
-
throw new Error("You must provide a valid Android app path.");
|
|
28
|
-
}
|
|
29
|
-
if (args.desiredPlatform === "ios" && !args.appPath.endsWith(".ipa")) {
|
|
30
|
-
throw new Error("You must provide a valid iOS app path.");
|
|
31
|
-
}
|
|
32
|
-
// check if the app path exists && is readable
|
|
33
|
-
try {
|
|
34
|
-
if (!fs_1.default.existsSync(args.appPath)) {
|
|
35
|
-
throw new Error("The app path does not exist.");
|
|
36
|
-
}
|
|
37
|
-
fs_1.default.accessSync(args.appPath, fs_1.default.constants.R_OK);
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
logger_1.default.error("The app path does not exist or is not readable: %s", error);
|
|
41
|
-
throw new Error("The app path does not exist or is not readable.");
|
|
42
|
-
}
|
|
43
|
-
const { app_url } = await (0, upload_app_1.uploadApp)(args.appPath);
|
|
44
|
-
if (!app_url.match("bs://")) {
|
|
45
|
-
throw new Error("The app path is not a valid BrowserStack app URL.");
|
|
46
|
-
}
|
|
47
|
-
const launchUrl = await (0, start_session_1.startSession)({
|
|
48
|
-
appUrl: app_url,
|
|
49
|
-
desiredPlatform: args.desiredPlatform,
|
|
50
|
-
desiredPhone: args.desiredPhone,
|
|
51
|
-
desiredPlatformVersion: args.desiredPlatformVersion,
|
|
52
|
-
});
|
|
53
|
-
return {
|
|
54
|
-
content: [
|
|
55
|
-
{
|
|
56
|
-
type: "text",
|
|
57
|
-
text: `Successfully started a session. If a browser window did not open automatically, use the following URL to start the session: ${launchUrl}`,
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function addAppLiveTools(server) {
|
|
63
|
-
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.", {
|
|
64
|
-
desiredPhone: zod_1.z
|
|
65
|
-
.string()
|
|
66
|
-
.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. "),
|
|
67
|
-
desiredPlatformVersion: zod_1.z
|
|
68
|
-
.string()
|
|
69
|
-
.describe("The platform version to run the app on. Example: '12.0' for Android devices or '16.0' for iOS devices"),
|
|
70
|
-
desiredPlatform: zod_1.z
|
|
71
|
-
.enum(["android", "ios"])
|
|
72
|
-
.describe("Which platform to run on, examples: 'android', 'ios'. Set this based on the app path provided."),
|
|
73
|
-
appPath: zod_1.z
|
|
74
|
-
.string()
|
|
75
|
-
.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."),
|
|
76
|
-
}, async (args) => {
|
|
77
|
-
try {
|
|
78
|
-
return startAppLiveSession(args);
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
return {
|
|
82
|
-
content: [
|
|
83
|
-
{
|
|
84
|
-
type: "text",
|
|
85
|
-
text: `Failed to start an app live session. Error: ${error}. Please open an issue on GitHub if the problem persists`,
|
|
86
|
-
isError: true,
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
isError: true,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
}
|
package/dist/tools/bstack-sdk.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
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
|
-
/**
|
|
8
|
-
* BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
|
|
9
|
-
* This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
|
|
10
|
-
*/
|
|
11
|
-
async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, }) {
|
|
12
|
-
const instructions = (0, instructions_1.generateBrowserStackYMLInstructions)(desiredPlatforms);
|
|
13
|
-
const instructionsForProjectConfiguration = (0, instructions_1.getInstructionsForProjectConfiguration)(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage);
|
|
14
|
-
return {
|
|
15
|
-
content: [
|
|
16
|
-
{
|
|
17
|
-
type: "text",
|
|
18
|
-
text: `${instructions}\n\n After creating the browserstack.yml file above, do the following: ${instructionsForProjectConfiguration}`,
|
|
19
|
-
isError: false,
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function addSDKTools(server) {
|
|
25
|
-
server.tool("runTestsOnBrowserStack", "Use this tool to get instructions for running tests on BrowserStack.", {
|
|
26
|
-
detectedBrowserAutomationFramework: zod_1.z
|
|
27
|
-
.string()
|
|
28
|
-
.describe("The automation framework configured in the project. Example: 'playwright', 'selenium'"),
|
|
29
|
-
detectedTestingFramework: zod_1.z
|
|
30
|
-
.string()
|
|
31
|
-
.describe("The testing framework used in the project. Example: 'jest', 'pytest'"),
|
|
32
|
-
detectedLanguage: zod_1.z
|
|
33
|
-
.string()
|
|
34
|
-
.describe("The programming language used in the project. Example: 'nodejs', 'python'"),
|
|
35
|
-
desiredPlatforms: zod_1.z
|
|
36
|
-
.array(zod_1.z.enum(["windows", "macos", "android", "ios"]))
|
|
37
|
-
.describe("The platforms the user wants to test on. Always ask this to the user, do not try to infer this."),
|
|
38
|
-
}, async (args) => {
|
|
39
|
-
const detectedBrowserAutomationFramework = args.detectedBrowserAutomationFramework;
|
|
40
|
-
const detectedTestingFramework = args.detectedTestingFramework;
|
|
41
|
-
const detectedLanguage = args.detectedLanguage;
|
|
42
|
-
const desiredPlatforms = args.desiredPlatforms;
|
|
43
|
-
try {
|
|
44
|
-
return bootstrapProjectWithSDK({
|
|
45
|
-
detectedBrowserAutomationFramework,
|
|
46
|
-
detectedTestingFramework,
|
|
47
|
-
detectedLanguage,
|
|
48
|
-
desiredPlatforms,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
return {
|
|
53
|
-
content: [
|
|
54
|
-
{
|
|
55
|
-
type: "text",
|
|
56
|
-
text: `Failed to bootstrap project with BrowserStack SDK. Error: ${error}. Please open an issue on GitHub if the problem persists`,
|
|
57
|
-
isError: true,
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
isError: true,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
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.startBrowserSession = startBrowserSession;
|
|
7
|
-
const utils_1 = require("../../lib/utils");
|
|
8
|
-
const logger_1 = __importDefault(require("../../logger"));
|
|
9
|
-
const child_process_1 = __importDefault(require("child_process"));
|
|
10
|
-
async function startBrowserSession(args) {
|
|
11
|
-
// Sanitize all input parameters
|
|
12
|
-
const sanitizedArgs = {
|
|
13
|
-
browser: (0, utils_1.sanitizeUrlParam)(args.browser),
|
|
14
|
-
os: (0, utils_1.sanitizeUrlParam)(args.os),
|
|
15
|
-
osVersion: (0, utils_1.sanitizeUrlParam)(args.osVersion),
|
|
16
|
-
url: (0, utils_1.sanitizeUrlParam)(args.url),
|
|
17
|
-
browserVersion: (0, utils_1.sanitizeUrlParam)(args.browserVersion),
|
|
18
|
-
isLocal: args.isLocal,
|
|
19
|
-
};
|
|
20
|
-
// Construct URL with encoded parameters
|
|
21
|
-
const params = new URLSearchParams({
|
|
22
|
-
os: sanitizedArgs.os,
|
|
23
|
-
os_version: sanitizedArgs.osVersion,
|
|
24
|
-
browser: sanitizedArgs.browser,
|
|
25
|
-
browser_version: sanitizedArgs.browserVersion,
|
|
26
|
-
scale_to_fit: "true",
|
|
27
|
-
url: sanitizedArgs.url,
|
|
28
|
-
resolution: "responsive-mode",
|
|
29
|
-
speed: "1",
|
|
30
|
-
local: sanitizedArgs.isLocal ? "true" : "false",
|
|
31
|
-
start: "true",
|
|
32
|
-
});
|
|
33
|
-
const launchUrl = `https://live.browserstack.com/dashboard#${params.toString()}`;
|
|
34
|
-
try {
|
|
35
|
-
// Use platform-specific commands with proper escaping
|
|
36
|
-
const command = process.platform === "darwin"
|
|
37
|
-
? ["open", launchUrl]
|
|
38
|
-
: process.platform === "win32"
|
|
39
|
-
? ["cmd", "/c", "start", launchUrl]
|
|
40
|
-
: ["xdg-open", launchUrl];
|
|
41
|
-
// nosemgrep:javascript.lang.security.detect-child-process.detect-child-process
|
|
42
|
-
const child = child_process_1.default.spawn(command[0], command.slice(1), {
|
|
43
|
-
stdio: "ignore",
|
|
44
|
-
detached: true,
|
|
45
|
-
});
|
|
46
|
-
// Handle process errors
|
|
47
|
-
child.on("error", (error) => {
|
|
48
|
-
logger_1.default.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
|
|
49
|
-
});
|
|
50
|
-
// Unref the child process to allow the parent to exit
|
|
51
|
-
child.unref();
|
|
52
|
-
return launchUrl;
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
logger_1.default.error(`Failed to open browser automatically: ${error}. Please open this URL manually: ${launchUrl}`);
|
|
56
|
-
return launchUrl;
|
|
57
|
-
}
|
|
58
|
-
}
|
package/dist/tools/live.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
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.startBrowserLiveSession = startBrowserLiveSession;
|
|
7
|
-
exports.default = addBrowserLiveTools;
|
|
8
|
-
const zod_1 = require("zod");
|
|
9
|
-
const start_session_1 = require("./live-utils/start-session");
|
|
10
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
11
|
-
const local_1 = require("../lib/local");
|
|
12
|
-
/**
|
|
13
|
-
* Launches a Browser Live Session on BrowserStack.
|
|
14
|
-
*/
|
|
15
|
-
async function startBrowserLiveSession(args) {
|
|
16
|
-
if (!args.desiredBrowser) {
|
|
17
|
-
throw new Error("You must provide a desiredBrowser.");
|
|
18
|
-
}
|
|
19
|
-
if (!args.desiredURL) {
|
|
20
|
-
throw new Error("You must provide a desiredURL.");
|
|
21
|
-
}
|
|
22
|
-
if (!args.desiredOS) {
|
|
23
|
-
throw new Error("You must provide a desiredOS.");
|
|
24
|
-
}
|
|
25
|
-
if (!args.desiredOSVersion) {
|
|
26
|
-
throw new Error("You must provide a desiredOSVersion.");
|
|
27
|
-
}
|
|
28
|
-
if (!args.desiredBrowserVersion) {
|
|
29
|
-
throw new Error("You must provide a desiredBrowserVersion.");
|
|
30
|
-
}
|
|
31
|
-
// Validate URL format
|
|
32
|
-
try {
|
|
33
|
-
new URL(args.desiredURL);
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
logger_1.default.error("Invalid URL format: %s", error);
|
|
37
|
-
throw new Error("The provided URL is invalid.");
|
|
38
|
-
}
|
|
39
|
-
const isLocal = (0, local_1.isLocalURL)(args.desiredURL);
|
|
40
|
-
if (isLocal) {
|
|
41
|
-
await (0, local_1.ensureLocalBinarySetup)();
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
await (0, local_1.killExistingBrowserStackLocalProcesses)();
|
|
45
|
-
}
|
|
46
|
-
const launchUrl = await (0, start_session_1.startBrowserSession)({
|
|
47
|
-
browser: args.desiredBrowser,
|
|
48
|
-
os: args.desiredOS,
|
|
49
|
-
osVersion: args.desiredOSVersion,
|
|
50
|
-
url: args.desiredURL,
|
|
51
|
-
browserVersion: args.desiredBrowserVersion,
|
|
52
|
-
isLocal,
|
|
53
|
-
});
|
|
54
|
-
return {
|
|
55
|
-
content: [
|
|
56
|
-
{
|
|
57
|
-
type: "text",
|
|
58
|
-
text: `Successfully started a browser session. If a browser window did not open automatically, use the following URL to start the session: ${launchUrl}`,
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function addBrowserLiveTools(server) {
|
|
64
|
-
server.tool("runBrowserLiveSession", "Use this tool when user wants to manually check their website on a particular browser and OS combination using BrowserStack's cloud infrastructure. Can be used to debug layout issues, compatibility problems, etc.", {
|
|
65
|
-
desiredBrowser: zod_1.z
|
|
66
|
-
.enum(["Chrome", "IE", "Firefox", "Safari", "Edge"])
|
|
67
|
-
.describe("The browser to run the test on. Example: 'Chrome', 'IE', 'Safari', 'Edge'. Always ask the user for the browser they want to use, do not assume it."),
|
|
68
|
-
desiredOSVersion: zod_1.z
|
|
69
|
-
.string()
|
|
70
|
-
.describe("The OS version to run the browser on. Example: '10' for Windows, '12' for macOS, '14' for iOS"),
|
|
71
|
-
desiredOS: zod_1.z
|
|
72
|
-
.enum(["Windows", "OS X"])
|
|
73
|
-
.describe("The operating system to run the browser on. Example: 'Windows', 'OS X'"),
|
|
74
|
-
desiredURL: zod_1.z
|
|
75
|
-
.string()
|
|
76
|
-
.describe("The URL of the website to test. This can be a local URL (e.g., http://localhost:3000) or a public URL. Always ask the user for the URL, do not assume it."),
|
|
77
|
-
desiredBrowserVersion: zod_1.z
|
|
78
|
-
.string()
|
|
79
|
-
.describe("The version of the browser to use. Example: 133.0, 134.0, 87.0"),
|
|
80
|
-
}, async (args) => {
|
|
81
|
-
try {
|
|
82
|
-
return startBrowserLiveSession(args);
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
return {
|
|
86
|
-
content: [
|
|
87
|
-
{
|
|
88
|
-
type: "text",
|
|
89
|
-
text: `Failed to start a browser live session. Error: ${error}. Please open an issue on GitHub if the problem persists`,
|
|
90
|
-
isError: true,
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
isError: true,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFailuresInLastRun = getFailuresInLastRun;
|
|
4
|
-
exports.default = addObservabilityTools;
|
|
5
|
-
const zod_1 = require("zod");
|
|
6
|
-
const api_1 = require("../lib/api");
|
|
7
|
-
async function getFailuresInLastRun(buildName, projectName) {
|
|
8
|
-
const buildsData = await (0, api_1.getLatestO11YBuildInfo)(buildName, projectName);
|
|
9
|
-
const observabilityUrl = buildsData.observability_url;
|
|
10
|
-
if (!observabilityUrl) {
|
|
11
|
-
throw new Error("No observability URL found in build data, this is likely because the build is not yet available on BrowserStack Observability.");
|
|
12
|
-
}
|
|
13
|
-
let overview = "No overview available";
|
|
14
|
-
if (buildsData.unique_errors?.overview?.insight) {
|
|
15
|
-
overview = buildsData.unique_errors.overview.insight;
|
|
16
|
-
}
|
|
17
|
-
let details = "No error details available";
|
|
18
|
-
if (buildsData.unique_errors?.top_unique_errors?.length > 0) {
|
|
19
|
-
details = buildsData.unique_errors.top_unique_errors
|
|
20
|
-
.map((error) => error.error)
|
|
21
|
-
.filter(Boolean)
|
|
22
|
-
.join("\n");
|
|
23
|
-
}
|
|
24
|
-
return {
|
|
25
|
-
content: [
|
|
26
|
-
{
|
|
27
|
-
type: "text",
|
|
28
|
-
text: `Observability URL: ${observabilityUrl}\nOverview: ${overview}\nError Details: ${details}`,
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
function addObservabilityTools(server) {
|
|
34
|
-
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.", {
|
|
35
|
-
buildName: zod_1.z
|
|
36
|
-
.string()
|
|
37
|
-
.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."),
|
|
38
|
-
projectName: zod_1.z
|
|
39
|
-
.string()
|
|
40
|
-
.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."),
|
|
41
|
-
}, async (args) => {
|
|
42
|
-
try {
|
|
43
|
-
return getFailuresInLastRun(args.buildName, args.projectName);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
return {
|
|
47
|
-
content: [
|
|
48
|
-
{
|
|
49
|
-
type: "text",
|
|
50
|
-
text: `Failed to get failures in the last run. Error: ${error}. Please open an issue on GitHub if this is an issue with BrowserStack`,
|
|
51
|
-
isError: true,
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
isError: true,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
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.SUPPORTED_CONFIGURATIONS = void 0;
|
|
7
|
-
const config_1 = __importDefault(require("../../config"));
|
|
8
|
-
const nodejsInstructions = `
|
|
9
|
-
- Ensure that \`browserstack-node-sdk\` is present in package.json, use the latest version.
|
|
10
|
-
- Add new scripts to package.json for running tests on BrowserStack (use \`npx\` to trigger the sdk):
|
|
11
|
-
\`\`\`json
|
|
12
|
-
"scripts": {
|
|
13
|
-
"test:browserstack": "npx browserstack-node-sdk <framework-specific-test-execution-command>"
|
|
14
|
-
}
|
|
15
|
-
\`\`\`
|
|
16
|
-
- Add to dependencies:
|
|
17
|
-
\`\`\`json
|
|
18
|
-
"browserstack-node-sdk": "latest"
|
|
19
|
-
\`\`\`
|
|
20
|
-
`;
|
|
21
|
-
const pythonInstructions = `
|
|
22
|
-
Run the following command to install the browserstack-sdk:
|
|
23
|
-
\`\`\`bash
|
|
24
|
-
python3 -m pip install browserstack-sdk
|
|
25
|
-
\`\`\`
|
|
26
|
-
|
|
27
|
-
Run the following command to setup the browserstack-sdk:
|
|
28
|
-
\`\`\`bash
|
|
29
|
-
browserstack-sdk setup --username "${config_1.default.browserstackUsername}" --key "${config_1.default.browserstackAccessKey}"
|
|
30
|
-
\`\`\`
|
|
31
|
-
|
|
32
|
-
In order to run tests on BrowserStack, run the following command:
|
|
33
|
-
\`\`\`bash
|
|
34
|
-
browserstack-sdk python <path-to-test-file>
|
|
35
|
-
\`\`\`
|
|
36
|
-
`;
|
|
37
|
-
exports.SUPPORTED_CONFIGURATIONS = {
|
|
38
|
-
nodejs: {
|
|
39
|
-
playwright: {
|
|
40
|
-
jest: { instructions: nodejsInstructions },
|
|
41
|
-
codeceptjs: { instructions: nodejsInstructions },
|
|
42
|
-
playwright: { instructions: nodejsInstructions },
|
|
43
|
-
},
|
|
44
|
-
selenium: {
|
|
45
|
-
jest: { instructions: nodejsInstructions },
|
|
46
|
-
webdriverio: { instructions: nodejsInstructions },
|
|
47
|
-
mocha: { instructions: nodejsInstructions },
|
|
48
|
-
cucumber: { instructions: nodejsInstructions },
|
|
49
|
-
nightwatch: { instructions: nodejsInstructions },
|
|
50
|
-
codeceptjs: { instructions: nodejsInstructions },
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
python: {
|
|
54
|
-
playwright: {
|
|
55
|
-
pytest: { instructions: pythonInstructions },
|
|
56
|
-
},
|
|
57
|
-
selenium: {
|
|
58
|
-
pytest: { instructions: pythonInstructions },
|
|
59
|
-
robot: { instructions: pythonInstructions },
|
|
60
|
-
behave: { instructions: pythonInstructions },
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getInstructionsForProjectConfiguration = void 0;
|
|
4
|
-
exports.generateBrowserStackYMLInstructions = generateBrowserStackYMLInstructions;
|
|
5
|
-
const constants_1 = require("./constants");
|
|
6
|
-
const errorMessageSuffix = "Please open an issue at our Github repo: https://github.com/browserstack/browserstack-mcp-server/issues to request support for your project configuration";
|
|
7
|
-
const getInstructionsForProjectConfiguration = (detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage) => {
|
|
8
|
-
const configuration = constants_1.SUPPORTED_CONFIGURATIONS[detectedLanguage];
|
|
9
|
-
if (!configuration) {
|
|
10
|
-
throw new Error(`BrowserStack MCP Server currently does not support ${detectedLanguage}, ${errorMessageSuffix}`);
|
|
11
|
-
}
|
|
12
|
-
if (!configuration[detectedBrowserAutomationFramework]) {
|
|
13
|
-
throw new Error(`BrowserStack MCP Server currently does not support ${detectedBrowserAutomationFramework} for ${detectedLanguage}, ${errorMessageSuffix}`);
|
|
14
|
-
}
|
|
15
|
-
if (!configuration[detectedBrowserAutomationFramework][detectedTestingFramework]) {
|
|
16
|
-
throw new Error(`BrowserStack MCP Server currently does not support ${detectedTestingFramework} for ${detectedBrowserAutomationFramework} on ${detectedLanguage}, ${errorMessageSuffix}`);
|
|
17
|
-
}
|
|
18
|
-
return configuration[detectedBrowserAutomationFramework][detectedTestingFramework].instructions;
|
|
19
|
-
};
|
|
20
|
-
exports.getInstructionsForProjectConfiguration = getInstructionsForProjectConfiguration;
|
|
21
|
-
function generateBrowserStackYMLInstructions(desiredPlatforms) {
|
|
22
|
-
return `
|
|
23
|
-
Create a browserstack.yml file in the project root. The file should be in the following format:
|
|
24
|
-
|
|
25
|
-
\`\`\`yaml
|
|
26
|
-
# ======================
|
|
27
|
-
# BrowserStack Reporting
|
|
28
|
-
# ======================
|
|
29
|
-
projectName: BrowserStack MCP Runs
|
|
30
|
-
build: mcp-run
|
|
31
|
-
|
|
32
|
-
# =======================================
|
|
33
|
-
# Platforms (Browsers / Devices to test)
|
|
34
|
-
# =======================================
|
|
35
|
-
# Platforms object contains all the browser / device combinations you want to test on.
|
|
36
|
-
# Generate this on the basis of the following platforms requested by the user:
|
|
37
|
-
# Requested platforms: ${desiredPlatforms}
|
|
38
|
-
platforms:
|
|
39
|
-
- os: Windows
|
|
40
|
-
osVersion: 11
|
|
41
|
-
browserName: chrome
|
|
42
|
-
browserVersion: latest
|
|
43
|
-
|
|
44
|
-
# =======================
|
|
45
|
-
# Parallels per Platform
|
|
46
|
-
# =======================
|
|
47
|
-
# The number of parallel threads to be used for each platform set.
|
|
48
|
-
# BrowserStack's SDK runner will select the best strategy based on the configured value
|
|
49
|
-
#
|
|
50
|
-
# Example 1 - If you have configured 3 platforms and set \`parallelsPerPlatform\` as 2, a total of 6 (2 * 3) parallel threads will be used on BrowserStack
|
|
51
|
-
#
|
|
52
|
-
# Example 2 - If you have configured 1 platform and set \`parallelsPerPlatform\` as 5, a total of 5 (1 * 5) parallel threads will be used on BrowserStack
|
|
53
|
-
parallelsPerPlatform: 1
|
|
54
|
-
|
|
55
|
-
browserstackLocal: true
|
|
56
|
-
|
|
57
|
-
# ===================
|
|
58
|
-
# Debugging features
|
|
59
|
-
# ===================
|
|
60
|
-
debug: true
|
|
61
|
-
testObservability: true
|
|
62
|
-
\`\`\`
|
|
63
|
-
\n`;
|
|
64
|
-
}
|