Skip to content

Commit 50f5273

Browse files
committed
enhance device validation and configuration
1 parent 58a2b24 commit 50f5273

File tree

10 files changed

+551
-312
lines changed

10 files changed

+551
-312
lines changed

src/lib/device-cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const URLS: Record<BrowserStackProducts, string> = {
2525
[BrowserStackProducts.APP_AUTOMATE]:
2626
"https://www.browserstack.com/list-of-browsers-and-platforms/app_automate.json",
2727
[BrowserStackProducts.SELENIUM_AUTOMATE]:
28-
"https://www.browserstack.com/list-of-browsers-and-platforms/selenium_app_automate.json",
28+
"https://www.browserstack.com/list-of-browsers-and-platforms/automate.json",
2929
[BrowserStackProducts.PLAYWRIGHT_AUTOMATE]:
3030
"https://www.browserstack.com/list-of-browsers-and-platforms/playwright.json",
3131
};

src/lib/version-resolver.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,71 @@ export function resolveVersion(requested: string, available: string[]): string {
4747
// final fallback
4848
return uniq[0];
4949
}
50+
51+
export function resolveVersionWithFuzzy(
52+
requested: string,
53+
available: string[],
54+
): string {
55+
// strip duplicates & sort
56+
const uniq = Array.from(new Set(available));
57+
58+
// pick min/max
59+
if (requested === "latest" || requested === "oldest") {
60+
// try numeric
61+
const nums = uniq
62+
.map((v) => ({ v, n: parseFloat(v) }))
63+
.filter((x) => !isNaN(x.n))
64+
.sort((a, b) => a.n - b.n);
65+
if (nums.length) {
66+
return requested === "latest" ? nums[nums.length - 1].v : nums[0].v;
67+
}
68+
// fallback lex
69+
const lex = uniq.slice().sort();
70+
return requested === "latest" ? lex[lex.length - 1] : lex[0];
71+
}
72+
73+
// exact match?
74+
if (uniq.includes(requested)) {
75+
return requested;
76+
}
77+
78+
// Try major version matching (e.g., "14" matches "14.0", "14.1", etc.)
79+
const reqNum = parseFloat(requested);
80+
if (!isNaN(reqNum)) {
81+
const majorVersionMatches = uniq.filter((v) => {
82+
const vNum = parseFloat(v);
83+
return !isNaN(vNum) && Math.floor(vNum) === Math.floor(reqNum);
84+
});
85+
86+
if (majorVersionMatches.length > 0) {
87+
// If multiple matches, prefer the most common format or latest
88+
const exactMatch = majorVersionMatches.find(
89+
(v) => v === `${Math.floor(reqNum)}.0`,
90+
);
91+
if (exactMatch) {
92+
return exactMatch;
93+
}
94+
// Return the first match (usually the most common format)
95+
return majorVersionMatches[0];
96+
}
97+
}
98+
99+
// Fuzzy matching: find the closest version
100+
const reqNumForFuzzy = parseFloat(requested);
101+
if (!isNaN(reqNumForFuzzy)) {
102+
const numericVersions = uniq
103+
.map((v) => ({ v, n: parseFloat(v) }))
104+
.filter((x) => !isNaN(x.n))
105+
.sort(
106+
(a, b) =>
107+
Math.abs(a.n - reqNumForFuzzy) - Math.abs(b.n - reqNumForFuzzy),
108+
);
109+
110+
if (numericVersions.length > 0) {
111+
return numericVersions[0].v;
112+
}
113+
}
114+
115+
// Fallback: return the first available version
116+
return uniq[0];
117+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ export const SETUP_APP_AUTOMATE_SCHEMA = {
7575
]),
7676
)
7777
.max(3)
78-
.default([[AppSDKSupportedPlatformEnum.android, 'Samsung Galaxy S24', 'latest']])
78+
.default([
79+
[AppSDKSupportedPlatformEnum.android, "Samsung Galaxy S24", "latest"],
80+
])
7981
.describe(
8082
"Preferred input: 1-3 tuples describing target mobile devices. Example: [['android', 'Samsung Galaxy S24', '14'], ['ios', 'iPhone 15', '17']]",
8183
),
@@ -92,7 +94,6 @@ export const SETUP_APP_AUTOMATE_SCHEMA = {
9294
.describe("Project name for organizing test runs on BrowserStack."),
9395
};
9496

95-
// Legacy schema for backward compatibility
9697
export const SETUP_APP_AUTOMATE_SCHEMA_LEGACY = {
9798
detectedFramework: z
9899
.nativeEnum(AppSDKSupportedFrameworkEnum)

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
generateAppBrowserStackYMLInstructions,
1414
} from "./index.js";
1515

16-
import { validateDevices } from "../../sdk-utils/common/device-validator.js";
16+
import { validateAppAutomateDevices } from "../../sdk-utils/common/device-validator.js";
1717

1818
import {
1919
AppSDKSupportedLanguage,
@@ -46,12 +46,13 @@ export async function setupAppAutomateHandler(
4646
validateSupportforAppAutomate(framework, language, testingFramework);
4747

4848
// 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;
49+
const devices =
50+
inputDevices.length === 0
51+
? [["android", "Samsung Galaxy S24", "latest"]]
52+
: inputDevices;
5253

5354
// Validate devices against real BrowserStack device data
54-
const validatedEnvironments = await validateDevices(devices, framework);
55+
const validatedEnvironments = await validateAppAutomateDevices(devices);
5556

5657
// Extract platforms for backward compatibility (if needed)
5758
const platforms = validatedEnvironments.map((env) => env.platform);

src/tools/appautomate-utils/native-execution/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ export const RUN_APP_AUTOMATE_SCHEMA = {
2929
"If in other directory, provide existing test file path",
3030
),
3131
devices: z
32-
.array(z.string())
32+
.array(z.array(z.string()))
3333
.describe(
34-
"List of devices to run the test on, e.g., ['Samsung Galaxy S20-10.0', 'iPhone 12 Pro-16.0'].",
34+
"List of devices to run the test on, e.g., [['android', 'Samsung Galaxy S20', '10.0'], ['ios', 'iPhone 12 Pro', '16.0']].",
3535
),
3636
project: z
3737
.string()

src/tools/appautomate.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ async function runAppTestsOnBrowserStack(
176176
testSuitePath?: string;
177177
browserstackAppUrl?: string;
178178
browserstackTestSuiteUrl?: string;
179-
devices: string[];
179+
devices: Array<Array<string>>;
180180
project: string;
181181
detectedAutomationFramework: string;
182182
},
@@ -223,10 +223,16 @@ async function runAppTestsOnBrowserStack(
223223
logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
224224
}
225225

226+
// Convert array format to string format for Espresso
227+
const deviceStrings = args.devices.map((device) => {
228+
const [, deviceName, osVersion] = device;
229+
return `${deviceName}-${osVersion}`;
230+
});
231+
226232
const build_id = await triggerEspressoBuild(
227233
app_url,
228234
test_suite_url,
229-
args.devices,
235+
deviceStrings,
230236
args.project,
231237
);
232238

@@ -268,10 +274,16 @@ async function runAppTestsOnBrowserStack(
268274
logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
269275
}
270276

277+
// Convert array format to string format for XCUITest
278+
const deviceStrings = args.devices.map((device) => {
279+
const [, deviceName, osVersion] = device;
280+
return `${deviceName}-${osVersion}`;
281+
});
282+
271283
const build_id = await triggerXcuiBuild(
272284
app_url,
273285
test_suite_url,
274-
args.devices,
286+
deviceStrings,
275287
args.project,
276288
config,
277289
);

src/tools/sdk-utils/bstack/configUtils.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
import { ValidatedEnvironment } from "../common/device-validator.js";
22

3-
export function generateBrowserStackYMLInstructions(
4-
config: {
5-
validatedEnvironments?: ValidatedEnvironment[];
6-
platforms?: string[];
7-
enablePercy?: boolean;
8-
projectName: string;
9-
}
10-
): string {
3+
export function generateBrowserStackYMLInstructions(config: {
4+
validatedEnvironments?: ValidatedEnvironment[];
5+
platforms?: string[];
6+
enablePercy?: boolean;
7+
projectName: string;
8+
}): string {
119
const enablePercy = config.enablePercy || false;
1210
const projectName = config.projectName;
1311

1412
// Generate platform configurations using the utility function
1513
const platformConfigs = generatePlatformConfigs(config);
1614

1715
// Determine build name and step title
18-
const buildName = config.validatedEnvironments && config.validatedEnvironments.length > 0
19-
? `${projectName}-Build`
20-
: "Sample-Build";
16+
const buildName =
17+
config.validatedEnvironments && config.validatedEnvironments.length > 0
18+
? `${projectName}-Build`
19+
: "Sample-Build";
2120

22-
const stepTitle = config.validatedEnvironments && config.validatedEnvironments.length > 0
23-
? "Create a browserstack.yml file in the project root with your validated device configurations:"
24-
: "Create a browserstack.yml file in the project root. The file should be in the following format:";
21+
const stepTitle =
22+
config.validatedEnvironments && config.validatedEnvironments.length > 0
23+
? "Create a browserstack.yml file in the project root with your validated device configurations:"
24+
: "Create a browserstack.yml file in the project root. The file should be in the following format:";
2525

2626
let ymlContent = `
2727
# ======================
@@ -139,6 +139,6 @@ function generatePlatformConfigs(config: {
139139
browserName: chrome
140140
browserVersion: latest`;
141141
}
142-
142+
143143
return "";
144144
}

0 commit comments

Comments
 (0)