Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/tools/appautomate-utils/appium-sdk/config-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Configuration utilities for BrowserStack App SDK
import {
APP_DEVICE_CONFIGS,
AppSDKSupportedTestingFrameworkEnum,
DEFAULT_APP_PATH,
createStep,
} from "./index.js";

export function generateAppBrowserStackYMLInstructions(
platforms: string[],
username: string,
accessKey: string,
appPath: string = DEFAULT_APP_PATH,
testingFramework: string,
): string {
if (
testingFramework === AppSDKSupportedTestingFrameworkEnum.nightwatch ||
testingFramework === AppSDKSupportedTestingFrameworkEnum.webdriverio ||
testingFramework === AppSDKSupportedTestingFrameworkEnum.cucumberRuby
) {
return "";
}

// Generate platform and device configurations
const platformConfigs = platforms
.map((platform) => {
const devices =
APP_DEVICE_CONFIGS[platform as keyof typeof APP_DEVICE_CONFIGS];
if (!devices) return "";

return devices
.map(
(device) => ` - platformName: ${platform}
deviceName: ${device.deviceName}
platformVersion: "${device.platformVersion}"`,
)
.join("\n");
})
.filter(Boolean)
.join("\n");

// Construct YAML content
const configContent = `\`\`\`yaml
userName: ${username}
accessKey: ${accessKey}
app: ${appPath}
platforms:
${platformConfigs}
parallelsPerPlatform: 1
browserstackLocal: true
buildName: bstack-demo
projectName: BrowserStack Sample
debug: true
networkLogs: true
percy: false
percyCaptureMode: auto
accessibility: false
\`\`\`

**Important notes:**
- Replace \`app: ${appPath}\` with the path to your actual app file (e.g., \`./SampleApp.apk\` for Android or \`./SampleApp.ipa\` for iOS)
- You can upload your app using BrowserStack's App Upload API or manually through the dashboard
- Set \`browserstackLocal: true\` if you need to test with local/staging servers
- Adjust \`parallelsPerPlatform\` based on your subscription limits`;

// Return formatted step for instructions
return createStep(
"Update browserstack.yml file with App Automate configuration:",
`Create or update the browserstack.yml file in your project root with the following content:

${configContent}`,
);
}
68 changes: 68 additions & 0 deletions src/tools/appautomate-utils/appium-sdk/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { z } from "zod";
import {
AppSDKSupportedFrameworkEnum,
AppSDKSupportedTestingFrameworkEnum,
AppSDKSupportedLanguageEnum,
AppSDKSupportedPlatformEnum,
} from "./index.js";

// App Automate specific device configurations
export const APP_DEVICE_CONFIGS = {
android: [
{ deviceName: "Samsung Galaxy S22 Ultra", platformVersion: "12.0" },
{ deviceName: "Google Pixel 7 Pro", platformVersion: "13.0" },
{ deviceName: "OnePlus 9", platformVersion: "11.0" },
],
ios: [
{ deviceName: "iPhone 14", platformVersion: "16" },
{ deviceName: "iPhone 13", platformVersion: "15" },
{ deviceName: "iPad Air 4", platformVersion: "14" },
],
};

// Step delimiter for parsing instructions
export const STEP_DELIMITER = "---STEP---";

// Default app path for examples
export const DEFAULT_APP_PATH = "bs://sample.app";

// Tool description and schema for setupBrowserStackAppAutomateTests
export const SETUP_APP_AUTOMATE_DESCRIPTION =
"Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.";

export const SETUP_APP_AUTOMATE_SCHEMA = {
detectedFramework: z
.nativeEnum(AppSDKSupportedFrameworkEnum)
.describe(
"The mobile automation framework configured in the project. Example: 'appium'",
),

detectedTestingFramework: z
.nativeEnum(AppSDKSupportedTestingFrameworkEnum)
.describe(
"The testing framework used in the project. Be precise with framework selection Example: 'testng', 'behave', 'pytest', 'robot'",
),

detectedLanguage: z
.nativeEnum(AppSDKSupportedLanguageEnum)
.describe(
"The programming language used in the project. Supports Java and C#. Example: 'java', 'csharp'",
),

desiredPlatforms: z
.array(z.nativeEnum(AppSDKSupportedPlatformEnum))
.describe(
"The mobile platforms the user wants to test on. Always ask this to the user, do not try to infer this. Example: ['android', 'ios']",
),

appPath: z
.string()
.describe(
"Path to the mobile app file (.apk for Android, .ipa for iOS). Can be a local file path or a BrowserStack app URL (bs://)",
),
project: z
.string()
.optional()
.default("BStack-AppAutomate-Suite")
.describe("Project name for organizing test runs on BrowserStack."),
};
87 changes: 87 additions & 0 deletions src/tools/appautomate-utils/appium-sdk/formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { STEP_DELIMITER } from "./constants.js";

export function formatFinalAppInstructions(
formattedInstructions: string,
): CallToolResult {
const fullInstructions = `
⚠️ IMPORTANT: DO NOT SKIP ANY STEP
All the setup steps described in this file MUST be executed regardless of any existing configuration or setup.
This ensures proper BrowserStack App Automate SDK setup.
Each step is compulsory and sequence needs to be maintained.

${formattedInstructions}`;

return {
content: [
{
type: "text",
text: fullInstructions,
isError: false,
},
],
};
}

export function createStep(title: string, content: string): string {
return `${STEP_DELIMITER}
${title}

${content}`;
}

export function combineInstructions(...instructionParts: string[]): string {
return instructionParts.filter(Boolean).join("\n\n");
}

export function formatEnvCommands(
username: string,
accessKey: string,
isWindows: boolean,
): string {
if (isWindows) {
return `\`\`\`cmd
setx BROWSERSTACK_USERNAME "${username}"
setx BROWSERSTACK_ACCESS_KEY "${accessKey}"
\`\`\``;
}
return `\`\`\`bash
export BROWSERSTACK_USERNAME=${username}
export BROWSERSTACK_ACCESS_KEY=${accessKey}
\`\`\``;
}

export function createEnvStep(
username: string,
accessKey: string,
isWindows: boolean,
platformLabel: string,
title: string = "Set BrowserStack credentials as environment variables:",
): string {
return createStep(
title,
`**${platformLabel}:**
${formatEnvCommands(username, accessKey, isWindows)}`,
);
}

export function formatMultiLineCommand(
command: string,
isWindows: boolean = process.platform === "win32",
): string {
if (isWindows) {
// For Windows, keep commands on single line
return command.replace(/\s*\\\s*\n\s*/g, " ");
}
return command;
}

export function formatAppInstructionsWithNumbers(instructions: string): string {
const steps = instructions
.split(STEP_DELIMITER)
.filter((step) => step.trim());

return steps
.map((step, index) => `**Step ${index + 1}:**\n${step.trim()}`)
.join("\n\n");
}
109 changes: 109 additions & 0 deletions src/tools/appautomate-utils/appium-sdk/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { z } from "zod";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { BrowserStackConfig } from "../../../lib/types.js";
import { getBrowserStackAuth } from "../../../lib/get-auth.js";
import {
getAppUploadInstruction,
validateSupportforAppAutomate,
SupportedFramework,
} from "./utils.js";

import {
getAppSDKPrefixCommand,
generateAppBrowserStackYMLInstructions,
} from "./index.js";

import {
AppSDKSupportedLanguage,
AppSDKSupportedTestingFramework,
AppSDKInstruction,
formatAppInstructionsWithNumbers,
getAppInstructionsForProjectConfiguration,
SETUP_APP_AUTOMATE_SCHEMA,
} from "./index.js";

export async function setupAppAutomateHandler(
rawInput: unknown,
config: BrowserStackConfig,
): Promise<CallToolResult> {
const input = z.object(SETUP_APP_AUTOMATE_SCHEMA).parse(rawInput);
const auth = getBrowserStackAuth(config);
const [username, accessKey] = auth.split(":");

const instructions: AppSDKInstruction[] = [];

// Use variables for all major input properties
const testingFramework =
input.detectedTestingFramework as AppSDKSupportedTestingFramework;
const language = input.detectedLanguage as AppSDKSupportedLanguage;
const platforms = (input.desiredPlatforms as string[]) ?? ["android"];
const appPath = input.appPath as string;
const framework = input.detectedFramework as SupportedFramework;

//Validating if supported framework or not
validateSupportforAppAutomate(framework, language, testingFramework);

// Step 1: Generate SDK setup command
const sdkCommand = getAppSDKPrefixCommand(
language,
testingFramework,
username,
accessKey,
appPath,
);

if (sdkCommand) {
instructions.push({ content: sdkCommand, type: "setup" });
}

// Step 2: Generate browserstack.yml configuration
const configInstructions = generateAppBrowserStackYMLInstructions(
platforms,
username,
accessKey,
appPath,
testingFramework,
);

if (configInstructions) {
instructions.push({ content: configInstructions, type: "config" });
}

// Step 3: Generate app upload instruction
const appUploadInstruction = await getAppUploadInstruction(
appPath,
username,
accessKey,
testingFramework,
);

if (appUploadInstruction) {
instructions.push({ content: appUploadInstruction, type: "setup" });
}

// Step 4: Generate project configuration and run instructions
const projectInstructions = getAppInstructionsForProjectConfiguration(
framework,
testingFramework,
language,
);

if (projectInstructions) {
instructions.push({ content: projectInstructions, type: "run" });
}

const combinedInstructions = instructions
.map((instruction) => instruction.content)
.join("\n\n");

return {
content: [
{
type: "text",
text: formatAppInstructionsWithNumbers(combinedInstructions),
isError: false,
},
],
isError: false,
};
}
12 changes: 12 additions & 0 deletions src/tools/appautomate-utils/appium-sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Barrel exports for App BrowserStack module
export {
getAppSDKPrefixCommand,
getAppInstructionsForProjectConfiguration,
} from "./instructions.js";
export { generateAppBrowserStackYMLInstructions } from "./config-generator.js";

export * from "./types.js";
export * from "./constants.js";
export * from "./utils.js";
export * from "./instructions.js";
export * from "./formatter.js";
Loading