Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
38 changes: 13 additions & 25 deletions src/tools/appautomate-utils/appium-sdk/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
AppSDKSupportedFrameworkEnum,
AppSDKSupportedTestingFrameworkEnum,
AppSDKSupportedLanguageEnum,
AppSDKSupportedPlatformEnum,
} from "./index.js";

// App Automate specific device configurations
Expand All @@ -26,6 +25,18 @@ export const STEP_DELIMITER = "---STEP---";
// Default app path for examples
export const DEFAULT_APP_PATH = "bs://sample.app";

export const MobileDeviceSchema = z.object({
platform: z
.enum(["android", "ios"])
.describe("Platform name: 'android' or 'ios'"),
deviceName: z
.string()
.describe(
"Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8', 'iPhone 15', 'iPhone 14 Pro'",
),
osVersion: z.string().describe("OS version, e.g. '14', '16', '17', 'latest'"),
});

// 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.";
Expand All @@ -50,30 +61,7 @@ export const SETUP_APP_AUTOMATE_SCHEMA = {
),

devices: z
.array(
z.union([
// Android: [android, deviceName, osVersion]
z.tuple([
z
.literal(AppSDKSupportedPlatformEnum.android)
.describe("Platform identifier: 'android'"),
z
.string()
.describe(
"Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'",
),
z.string().describe("Android version, e.g. '14', '16', 'latest'"),
]),
// iOS: [ios, deviceName, osVersion]
z.tuple([
z
.literal(AppSDKSupportedPlatformEnum.ios)
.describe("Platform identifier: 'ios'"),
z.string().describe("Device name, e.g. 'iPhone 15', 'iPhone 14 Pro'"),
z.string().describe("iOS version, e.g. '17', '16', 'latest'"),
]),
]),
)
.array(MobileDeviceSchema)
.max(3)
.default([])
.describe(
Expand Down
7 changes: 6 additions & 1 deletion src/tools/appautomate-utils/appium-sdk/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ export async function setupAppAutomateHandler(
const testingFramework =
input.detectedTestingFramework as AppSDKSupportedTestingFramework;
const language = input.detectedLanguage as AppSDKSupportedLanguage;
const inputDevices = (input.devices as Array<Array<string>>) ?? [];
const inputDevices: Array<Array<string>> =
input.devices?.map((device) => [
device.platform,
device.deviceName,
device.osVersion,
]) ?? [];
const appPath = input.appPath as string;
const framework = input.detectedFramework as SupportedFramework;

Expand Down
38 changes: 13 additions & 25 deletions src/tools/appautomate-utils/native-execution/constants.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { z } from "zod";
import { AppTestPlatform } from "./types.js";
import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";

export const RUN_APP_AUTOMATE_DESCRIPTION = `Execute pre-built native mobile test suites (Espresso for Android, XCUITest for iOS) by direct upload to BrowserStack. ONLY for compiled .apk/.ipa test files. This is NOT for SDK integration or Appium tests. For Appium-based testing with SDK setup, use 'setupBrowserStackAppAutomateTests' instead.`;

export const MobileDeviceSchema = z.object({
platform: z
.enum(["android", "ios"])
.describe("Platform name: 'android' or 'ios'"),
deviceName: z
.string()
.describe(
"Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8', 'iPhone 15', 'iPhone 14 Pro'",
),
osVersion: z.string().describe("OS version, e.g. '14', '16', '17', 'latest'"),
});

export const RUN_APP_AUTOMATE_SCHEMA = {
appPath: z
.string()
Expand All @@ -30,30 +41,7 @@ export const RUN_APP_AUTOMATE_SCHEMA = {
"If in other directory, provide existing test file path",
),
devices: z
.array(
z.union([
// Android: [android, deviceName, osVersion]
z.tuple([
z
.literal(AppSDKSupportedPlatformEnum.android)
.describe("Platform identifier: 'android'"),
z
.string()
.describe(
"Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'",
),
z.string().describe("Android version, e.g. '14', '16', 'latest'"),
]),
// iOS: [ios, deviceName, osVersion]
z.tuple([
z
.literal(AppSDKSupportedPlatformEnum.ios)
.describe("Platform identifier: 'ios'"),
z.string().describe("Device name, e.g. 'iPhone 15', 'iPhone 14 Pro'"),
z.string().describe("iOS version, e.g. '17', '16', 'latest'"),
]),
]),
)
.array(MobileDeviceSchema)
.max(3)
.default([])
.describe(
Expand Down
8 changes: 7 additions & 1 deletion src/tools/appautomate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,13 @@ export default function addAppAutomationTools(
undefined,
config,
);
return await runAppTestsOnBrowserStack(args, config);
const devicesAsArrays: Array<Array<string>> = args.devices.map(
(device) => [device.platform, device.deviceName, device.osVersion],
);
return await runAppTestsOnBrowserStack(
{ ...args, devices: devicesAsArrays },
config,
);
} catch (error) {
trackMCP(
"runAppTestsOnBrowserStack",
Expand Down
27 changes: 22 additions & 5 deletions src/tools/sdk-utils/bstack/sdkHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,30 @@ export async function runBstackSDKOnly(
const authString = getBrowserStackAuth(config);
const [username, accessKey] = authString.split(":");

// Validate devices against real BrowserStack device data
const tupleTargets = (input as any).devices as
| Array<Array<string>>
| undefined;
const tupleTargets: Array<Array<string>> =
input.devices?.map((device) => {
const platform = device.platform.toLowerCase();
if (platform === "windows" || platform === "macos") {
// Desktop: ["platform", "osVersion", "browser", "browserVersion"]
return [
platform,
device.osVersion || "latest",
device.browser || "",
device.browserVersion || "latest",
];
} else {
// Mobile: ["platform", "deviceName", "osVersion", "browser"]
return [
platform,
device.deviceName || "",
device.osVersion || "latest",
device.browser || "",
];
}
}) || [];

const validatedEnvironments = await validateDevices(
tupleTargets || [],
tupleTargets,
input.detectedBrowserAutomationFramework,
);

Expand Down
74 changes: 29 additions & 45 deletions src/tools/sdk-utils/common/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,33 @@ export const SetUpPercyParamsShape = {
),
};

// Device schema for BrowserStack Automate (supports desktop and mobile)
const DeviceSchema = z
.object({
platform: z
.enum(["windows", "macos", "android", "ios"])
.describe("Platform name, e.g. 'windows', 'macos', 'android', 'ios'"),
deviceName: z
.string()
.optional()
.describe(
"Device name for mobile platforms, e.g. 'iPhone 15', 'Samsung Galaxy S24'",
),
osVersion: z
.string()
.describe("OS version, e.g. '11', 'Sequoia', '14', '17', 'latest'"),
browser: z
.string()
.optional()
.describe("Browser name, e.g. 'chrome', 'safari', 'edge', 'firefox'"),
browserVersion: z
.string()
.optional()
.describe(
"Browser version for desktop platforms only (windows, macos), e.g. '132', 'latest', 'oldest'. Not used for mobile devices (android, ios).",
),
});

export const RunTestsOnBrowserStackParamsShape = {
projectName: z
.string()
Expand All @@ -58,54 +85,11 @@ export const RunTestsOnBrowserStackParamsShape = {
),
detectedTestingFramework: z.nativeEnum(SDKSupportedTestingFrameworkEnum),
devices: z
.array(
z.union([
// Windows: [windows, osVersion, browser, browserVersion]
z.tuple([
z
.nativeEnum(WindowsPlatformEnum)
.describe("Platform identifier: 'windows'"),
z.string().describe("Windows version, e.g. '10', '11'"),
z.string().describe("Browser name, e.g. 'chrome', 'firefox', 'edge'"),
z
.string()
.describe("Browser version, e.g. '132', 'latest', 'oldest'"),
]),
// Android: [android, name, model, osVersion, browser]
z.tuple([
z
.literal(PlatformEnum.ANDROID)
.describe("Platform identifier: 'android'"),
z
.string()
.describe(
"Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'",
),
z.string().describe("Android version, e.g. '14', '16', 'latest'"),
z.string().describe("Browser name, e.g. 'chrome', 'samsung browser'"),
]),
// iOS: [ios, name, model, osVersion, browser]
z.tuple([
z.literal(PlatformEnum.IOS).describe("Platform identifier: 'ios'"),
z.string().describe("Device name, e.g. 'iPhone 12 Pro'"),
z.string().describe("iOS version, e.g. '17', 'latest'"),
z.string().describe("Browser name, typically 'safari'"),
]),
// macOS: [mac|macos, name, model, browser, browserVersion]
z.tuple([
z
.nativeEnum(MacOSPlatformEnum)
.describe("Platform identifier: 'mac' or 'macos'"),
z.string().describe("macOS version name, e.g. 'Sequoia', 'Ventura'"),
z.string().describe("Browser name, e.g. 'safari', 'chrome'"),
z.string().describe("Browser version, e.g. 'latest'"),
]),
]),
)
.array(DeviceSchema)
.max(3)
.default([])
.describe(
"Preferred tuples of target devices.Add device only when user asks explicitly for it. Defaults to [] . Example: [['windows', '11', 'chrome', 'latest']]",
"Device objects array. Use the object format directly - no transformation needed. Add only when user explicitly requests devices. Examples: [{ platform: 'windows', osVersion: '11', browser: 'chrome', browserVersion: 'latest' }] or [{ platform: 'android', deviceName: 'Samsung Galaxy S24', osVersion: '14', browser: 'chrome' }].",
),
};

Expand Down