Skip to content

Commit 58a2b24

Browse files
committed
refactor: update device validation and configuration generation for BrowserStack
1 parent 61847da commit 58a2b24

File tree

10 files changed

+240
-247
lines changed

10 files changed

+240
-247
lines changed

src/lib/device-cache.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export enum BrowserStackProducts {
1313
LIVE = "live",
1414
APP_LIVE = "app_live",
1515
APP_AUTOMATE = "app_automate",
16-
SELENIUM_APP_AUTOMATE = "selenium_app_automate",
17-
PLAYWRIGHT_APP_AUTOMATE = "playwright_app_automate",
16+
SELENIUM_AUTOMATE = "selenium_automate",
17+
PLAYWRIGHT_AUTOMATE = "playwright_automate",
1818
}
1919

2020
const URLS: Record<BrowserStackProducts, string> = {
@@ -24,9 +24,9 @@ const URLS: Record<BrowserStackProducts, string> = {
2424
"https://www.browserstack.com/list-of-browsers-and-platforms/app_live.json",
2525
[BrowserStackProducts.APP_AUTOMATE]:
2626
"https://www.browserstack.com/list-of-browsers-and-platforms/app_automate.json",
27-
[BrowserStackProducts.SELENIUM_APP_AUTOMATE]:
27+
[BrowserStackProducts.SELENIUM_AUTOMATE]:
2828
"https://www.browserstack.com/list-of-browsers-and-platforms/selenium_app_automate.json",
29-
[BrowserStackProducts.PLAYWRIGHT_APP_AUTOMATE]:
29+
[BrowserStackProducts.PLAYWRIGHT_AUTOMATE]:
3030
"https://www.browserstack.com/list-of-browsers-and-platforms/playwright.json",
3131
};
3232

Lines changed: 53 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// Configuration utilities for BrowserStack App SDK
21
import {
32
APP_DEVICE_CONFIGS,
43
AppSDKSupportedTestingFrameworkEnum,
@@ -8,39 +7,33 @@ import {
87
import { ValidatedEnvironment } from "../../sdk-utils/common/device-validator.js";
98

109
export function generateAppBrowserStackYMLInstructions(
11-
platforms: string[],
10+
config: {
11+
validatedEnvironments?: ValidatedEnvironment[];
12+
platforms?: string[];
13+
testingFramework?: string;
14+
projectName?: string;
15+
},
1216
username: string,
1317
accessKey: string,
1418
appPath: string = DEFAULT_APP_PATH,
15-
testingFramework: string,
1619
): string {
1720
if (
18-
testingFramework === AppSDKSupportedTestingFrameworkEnum.nightwatch ||
19-
testingFramework === AppSDKSupportedTestingFrameworkEnum.webdriverio ||
20-
testingFramework === AppSDKSupportedTestingFrameworkEnum.cucumberRuby
21+
config.testingFramework ===
22+
AppSDKSupportedTestingFrameworkEnum.nightwatch ||
23+
config.testingFramework ===
24+
AppSDKSupportedTestingFrameworkEnum.webdriverio ||
25+
config.testingFramework === AppSDKSupportedTestingFrameworkEnum.cucumberRuby
2126
) {
2227
return "";
2328
}
2429

25-
// Generate platform and device configurations
26-
const platformConfigs = platforms
27-
.map((platform) => {
28-
const devices =
29-
APP_DEVICE_CONFIGS[platform as keyof typeof APP_DEVICE_CONFIGS];
30-
if (!devices) return "";
30+
const platformConfigs = generatePlatformConfigs(config);
3131

32-
return devices
33-
.map(
34-
(device) => ` - platformName: ${platform}
35-
deviceName: ${device.deviceName}
36-
platformVersion: "${device.platformVersion}"`,
37-
)
38-
.join("\n");
39-
})
40-
.filter(Boolean)
41-
.join("\n");
32+
const projectName = config.projectName || "BrowserStack Sample";
33+
const buildName = config.projectName
34+
? `${config.projectName}-AppAutomate-Build`
35+
: "bstack-demo";
4236

43-
// Construct YAML content
4437
const configContent = `\`\`\`yaml
4538
userName: ${username}
4639
accessKey: ${accessKey}
@@ -49,8 +42,8 @@ platforms:
4942
${platformConfigs}
5043
parallelsPerPlatform: 1
5144
browserstackLocal: true
52-
buildName: bstack-demo
53-
projectName: BrowserStack Sample
45+
buildName: ${buildName}
46+
projectName: ${projectName}
5447
debug: true
5548
networkLogs: true
5649
percy: false
@@ -64,64 +57,46 @@ accessibility: false
6457
- Set \`browserstackLocal: true\` if you need to test with local/staging servers
6558
- Adjust \`parallelsPerPlatform\` based on your subscription limits`;
6659

67-
// Return formatted step for instructions
68-
return createStep(
69-
"Update browserstack.yml file with App Automate configuration:",
70-
`Create or update the browserstack.yml file in your project root with the following content:
60+
const stepTitle =
61+
"Update browserstack.yml file with App Automate configuration:";
7162

72-
${configContent}`,
73-
);
63+
const stepDescription = `Create or update the browserstack.yml file in your project root with the following content:
64+
${configContent}`;
65+
66+
return createStep(stepTitle, stepDescription);
7467
}
7568

76-
/**
77-
* Generate App Automate browserstack.yml from validated device configurations
78-
*/
79-
export function generateAppAutomateYML(
80-
validatedEnvironments: ValidatedEnvironment[],
81-
username: string,
82-
accessKey: string,
83-
appPath: string = DEFAULT_APP_PATH,
84-
projectName: string,
85-
): string {
86-
// Generate platform configurations from validated environments
87-
const platformConfigs = validatedEnvironments
88-
.filter((env) => env.platform === "android" || env.platform === "ios")
89-
.map((env) => {
90-
return ` - platformName: ${env.platform}
69+
function generatePlatformConfigs(config: {
70+
validatedEnvironments?: ValidatedEnvironment[];
71+
platforms?: string[];
72+
}): string {
73+
if (config.validatedEnvironments && config.validatedEnvironments.length > 0) {
74+
return config.validatedEnvironments
75+
.filter((env) => env.platform === "android" || env.platform === "ios")
76+
.map((env) => {
77+
return ` - platformName: ${env.platform}
9178
deviceName: "${env.deviceName}"
9279
platformVersion: "${env.osVersion}"`;
93-
})
94-
.join("\n");
80+
})
81+
.join("\n");
82+
} else if (config.platforms && config.platforms.length > 0) {
83+
return config.platforms
84+
.map((platform) => {
85+
const devices =
86+
APP_DEVICE_CONFIGS[platform as keyof typeof APP_DEVICE_CONFIGS];
87+
if (!devices) return "";
9588

96-
// Construct YAML content with validated data
97-
const configContent = `\`\`\`yaml
98-
userName: ${username}
99-
accessKey: ${accessKey}
100-
app: ${appPath}
101-
platforms:
102-
${platformConfigs}
103-
parallelsPerPlatform: 1
104-
browserstackLocal: true
105-
buildName: ${projectName}-AppAutomate-Build
106-
projectName: ${projectName}
107-
debug: true
108-
networkLogs: true
109-
percy: false
110-
percyCaptureMode: auto
111-
accessibility: false
112-
\`\`\`
113-
114-
**Important notes:**
115-
- Replace \`app: ${appPath}\` with the path to your actual app file (e.g., \`./SampleApp.apk\` for Android or \`./SampleApp.ipa\` for iOS)
116-
- You can upload your app using BrowserStack's App Upload API or manually through the dashboard
117-
- Set \`browserstackLocal: true\` if you need to test with local/staging servers
118-
- Adjust \`parallelsPerPlatform\` based on your subscription limits`;
119-
120-
// Return formatted step for instructions
121-
return createStep(
122-
"Update browserstack.yml file with validated App Automate configuration:",
123-
`Create or update the browserstack.yml file in your project root with your validated device configurations:
89+
return devices
90+
.map(
91+
(device) => ` - platformName: ${platform}
92+
deviceName: ${device.deviceName}
93+
platformVersion: "${device.platformVersion}"`,
94+
)
95+
.join("\n");
96+
})
97+
.filter(Boolean)
98+
.join("\n");
99+
}
124100

125-
${configContent}`,
126-
);
101+
return "";
127102
}

src/tools/appautomate-utils/appium-sdk/constants.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,69 @@ export const SETUP_APP_AUTOMATE_SCHEMA = {
4949
"The programming language used in the project. Supports Java and C#. Example: 'java', 'csharp'",
5050
),
5151

52+
devices: z
53+
.array(
54+
z.union([
55+
// Android: [android, deviceName, osVersion]
56+
z.tuple([
57+
z
58+
.literal(AppSDKSupportedPlatformEnum.android)
59+
.describe("Platform identifier: 'android'"),
60+
z
61+
.string()
62+
.describe(
63+
"Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'",
64+
),
65+
z.string().describe("Android version, e.g. '14', '16', 'latest'"),
66+
]),
67+
// iOS: [ios, deviceName, osVersion]
68+
z.tuple([
69+
z
70+
.literal(AppSDKSupportedPlatformEnum.ios)
71+
.describe("Platform identifier: 'ios'"),
72+
z.string().describe("Device name, e.g. 'iPhone 15', 'iPhone 14 Pro'"),
73+
z.string().describe("iOS version, e.g. '17', '16', 'latest'"),
74+
]),
75+
]),
76+
)
77+
.max(3)
78+
.default([[AppSDKSupportedPlatformEnum.android, 'Samsung Galaxy S24', 'latest']])
79+
.describe(
80+
"Preferred input: 1-3 tuples describing target mobile devices. Example: [['android', 'Samsung Galaxy S24', '14'], ['ios', 'iPhone 15', '17']]",
81+
),
82+
83+
appPath: z
84+
.string()
85+
.describe(
86+
"Path to the mobile app file (.apk for Android, .ipa for iOS). Can be a local file path or a BrowserStack app URL (bs://)",
87+
),
88+
project: z
89+
.string()
90+
.optional()
91+
.default("BStack-AppAutomate-Suite")
92+
.describe("Project name for organizing test runs on BrowserStack."),
93+
};
94+
95+
// Legacy schema for backward compatibility
96+
export const SETUP_APP_AUTOMATE_SCHEMA_LEGACY = {
97+
detectedFramework: z
98+
.nativeEnum(AppSDKSupportedFrameworkEnum)
99+
.describe(
100+
"The mobile automation framework configured in the project. Example: 'appium'",
101+
),
102+
103+
detectedTestingFramework: z
104+
.nativeEnum(AppSDKSupportedTestingFrameworkEnum)
105+
.describe(
106+
"The testing framework used in the project. Be precise with framework selection Example: 'testng', 'behave', 'pytest', 'robot'",
107+
),
108+
109+
detectedLanguage: z
110+
.nativeEnum(AppSDKSupportedLanguageEnum)
111+
.describe(
112+
"The programming language used in the project. Supports Java and C#. Example: 'java', 'csharp'",
113+
),
114+
52115
desiredPlatforms: z
53116
.array(z.nativeEnum(AppSDKSupportedPlatformEnum))
54117
.describe(

src/tools/appautomate-utils/appium-sdk/handler.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { BrowserStackConfig } from "../../../lib/types.js";
44
import { getBrowserStackAuth } from "../../../lib/get-auth.js";
5-
import { generateAppAutomateYML } from "./config-generator.js";
6-
75
import {
86
getAppUploadInstruction,
97
validateSupportforAppAutomate,
@@ -15,6 +13,8 @@ import {
1513
generateAppBrowserStackYMLInstructions,
1614
} from "./index.js";
1715

16+
import { validateDevices } from "../../sdk-utils/common/device-validator.js";
17+
1818
import {
1919
AppSDKSupportedLanguage,
2020
AppSDKSupportedTestingFramework,
@@ -38,16 +38,23 @@ export async function setupAppAutomateHandler(
3838
const testingFramework =
3939
input.detectedTestingFramework as AppSDKSupportedTestingFramework;
4040
const language = input.detectedLanguage as AppSDKSupportedLanguage;
41-
const platforms = (input.desiredPlatforms as string[]) ?? ["android"];
41+
const inputDevices = (input.devices as Array<Array<string>>) ?? [];
4242
const appPath = input.appPath as string;
4343
const framework = input.detectedFramework as SupportedFramework;
4444

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

48-
// For app automate, we don't have individual device validation like in automate
49-
// The platforms array already contains the desired platforms
50-
const validatedEnvironments: any[] = [];
48+
// Use default mobile devices when array is empty
49+
const devices = inputDevices.length === 0
50+
? [['android', 'Samsung Galaxy S24', 'latest']] // Default mobile device for App Automate
51+
: inputDevices;
52+
53+
// Validate devices against real BrowserStack device data
54+
const validatedEnvironments = await validateDevices(devices, framework);
55+
56+
// Extract platforms for backward compatibility (if needed)
57+
const platforms = validatedEnvironments.map((env) => env.platform);
5158

5259
// Step 1: Generate SDK setup command
5360
const sdkCommand = getAppSDKPrefixCommand(
@@ -63,26 +70,17 @@ export async function setupAppAutomateHandler(
6370
}
6471

6572
// Step 2: Generate browserstack.yml configuration
66-
let configInstructions;
67-
if (validatedEnvironments.length > 0) {
68-
// Use validated environments for YML generation
69-
configInstructions = generateAppAutomateYML(
73+
const configInstructions = generateAppBrowserStackYMLInstructions(
74+
{
7075
validatedEnvironments,
71-
username,
72-
accessKey,
73-
appPath,
74-
input.project as string,
75-
);
76-
} else {
77-
// Fallback to original method
78-
configInstructions = generateAppBrowserStackYMLInstructions(
7976
platforms,
80-
username,
81-
accessKey,
82-
appPath,
8377
testingFramework,
84-
);
85-
}
78+
projectName: input.project as string,
79+
},
80+
username,
81+
accessKey,
82+
appPath,
83+
);
8684

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

src/tools/appautomate.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { maybeCompressBase64 } from "../lib/utils.js";
99
import { remote } from "webdriverio";
1010
import { AppTestPlatform } from "./appautomate-utils/native-execution/types.js";
1111
import { setupAppAutomateHandler } from "./appautomate-utils/appium-sdk/handler.js";
12+
import { validateAppAutomateDevices } from "./sdk-utils/common/device-validator.js";
1213

1314
import {
1415
SETUP_APP_AUTOMATE_DESCRIPTION,
@@ -25,8 +26,6 @@ import {
2526
BrowserStackProducts,
2627
} from "../lib/device-cache.js";
2728

28-
import { validateAppAutomateDevices } from "./sdk-utils/common/device-validator.js";
29-
3029
import {
3130
findMatchingDevice,
3231
getDeviceVersions,

0 commit comments

Comments
 (0)