Skip to content

Commit 161ef45

Browse files
Fix : Version Resolution and Device Search in App Live (#51)
* feat: enhance accessibility scan report with issue count and pagination details * Fix : version resolution and device search functionalities in app live
1 parent 47ccfc8 commit 161ef45

File tree

8 files changed

+98
-193
lines changed

8 files changed

+98
-193
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { customFuzzySearch } from "../../lib/fuzzy.js";
2+
import { DeviceEntry } from "./types.js";
3+
4+
/**
5+
* Find matching devices by name with exact match preference.
6+
* Throws if none or multiple exact matches.
7+
*/
8+
export function findDeviceByName(
9+
devices: DeviceEntry[],
10+
desiredPhone: string,
11+
): DeviceEntry[] {
12+
const matches = customFuzzySearch(devices, ["display_name"], desiredPhone, 5);
13+
if (matches.length === 0) {
14+
const options = [...new Set(devices.map((d) => d.display_name))].join(", ");
15+
throw new Error(
16+
`No devices matching "${desiredPhone}". Available devices: ${options}`,
17+
);
18+
}
19+
// Exact-case-insensitive filter
20+
const exact = matches.filter(
21+
(m) => m.display_name.toLowerCase() === desiredPhone.toLowerCase(),
22+
);
23+
if (exact) return exact;
24+
// If no exact but multiple fuzzy, ask user
25+
if (matches.length > 1) {
26+
const names = matches.map((d) => d.display_name).join(", ");
27+
throw new Error(
28+
`Alternative Device/Device's found : ${names}. Please Select one.`,
29+
);
30+
}
31+
return matches;
32+
}

src/tools/applive-utils/fuzzy-search.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/tools/applive-utils/start-session.ts

Lines changed: 42 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
1-
import childProcess from "child_process";
21
import logger from "../../logger.js";
2+
import childProcess from "child_process";
33
import {
4-
BrowserStackProducts,
54
getDevicesAndBrowsers,
5+
BrowserStackProducts,
66
} from "../../lib/device-cache.js";
7-
import { fuzzySearchDevices } from "./fuzzy-search.js";
87
import { sanitizeUrlParam } from "../../lib/utils.js";
98
import { uploadApp } from "./upload-app.js";
10-
11-
export interface DeviceEntry {
12-
device: string;
13-
display_name: string;
14-
os: string;
15-
os_version: string;
16-
real_mobile: boolean;
17-
}
9+
import { findDeviceByName } from "./device-search.js";
10+
import { pickVersion } from "./version-utils.js";
11+
import { DeviceEntry } from "./types.js";
1812

1913
interface StartSessionArgs {
2014
appPath: string;
@@ -24,192 +18,68 @@ interface StartSessionArgs {
2418
}
2519

2620
/**
27-
* Starts an App Live session after filtering, fuzzy matching, and launching.
28-
* @param args - The arguments for starting the session.
29-
* @returns The launch URL for the session.
30-
* @throws Will throw an error if no devices are found or if the app URL is invalid.
21+
* Start an App Live session: filter, select, upload, and open.
3122
*/
3223
export async function startSession(args: StartSessionArgs): Promise<string> {
33-
const { appPath, desiredPlatform, desiredPhone } = args;
34-
let { desiredPlatformVersion } = args;
24+
const { appPath, desiredPlatform, desiredPhone, desiredPlatformVersion } =
25+
args;
3526

27+
// 1) Fetch devices for APP_LIVE
3628
const data = await getDevicesAndBrowsers(BrowserStackProducts.APP_LIVE);
37-
38-
const allDevices: DeviceEntry[] = data.mobile.flatMap((group: any) =>
39-
group.devices.map((dev: any) => ({ ...dev, os: group.os })),
40-
);
41-
42-
desiredPlatformVersion = resolvePlatformVersion(
43-
allDevices,
44-
desiredPlatform,
45-
desiredPlatformVersion,
46-
);
47-
48-
const filteredDevices = filterDevicesByPlatformAndVersion(
49-
allDevices,
50-
desiredPlatform,
51-
desiredPlatformVersion,
52-
);
53-
54-
const matches = await fuzzySearchDevices(filteredDevices, desiredPhone);
55-
56-
const selectedDevice = validateAndSelectDevice(
57-
matches,
58-
desiredPhone,
59-
desiredPlatform,
60-
desiredPlatformVersion,
29+
const all: DeviceEntry[] = data.mobile.flatMap((grp: any) =>
30+
grp.devices.map((dev: any) => ({ ...dev, os: grp.os })),
6131
);
6232

63-
const { app_url } = await uploadApp(appPath);
64-
65-
validateAppUrl(app_url);
66-
67-
const launchUrl = constructLaunchUrl(
68-
app_url,
69-
selectedDevice,
70-
desiredPlatform,
71-
desiredPlatformVersion,
72-
);
73-
74-
openBrowser(launchUrl);
75-
76-
return launchUrl;
77-
}
78-
79-
/**
80-
* Resolves the platform version based on the desired platform and version.
81-
* @param allDevices - The list of all devices.
82-
* @param desiredPlatform - The desired platform (android or ios).
83-
* @param desiredPlatformVersion - The desired platform version.
84-
* @returns The resolved platform version.
85-
* @throws Will throw an error if the platform version is not valid.
86-
*/
87-
function resolvePlatformVersion(
88-
allDevices: DeviceEntry[],
89-
desiredPlatform: string,
90-
desiredPlatformVersion: string,
91-
): string {
92-
if (
93-
desiredPlatformVersion === "latest" ||
94-
desiredPlatformVersion === "oldest"
95-
) {
96-
const filtered = allDevices.filter((d) => d.os === desiredPlatform);
97-
filtered.sort((a, b) => {
98-
const versionA = parseFloat(a.os_version);
99-
const versionB = parseFloat(b.os_version);
100-
return desiredPlatformVersion === "latest"
101-
? versionB - versionA
102-
: versionA - versionB;
103-
});
104-
105-
return filtered[0].os_version;
33+
// 2) Filter by OS
34+
const osMatches = all.filter((d) => d.os === desiredPlatform);
35+
if (!osMatches.length) {
36+
throw new Error(`No devices for OS "${desiredPlatform}"`);
10637
}
107-
return desiredPlatformVersion;
108-
}
10938

110-
/**
111-
* Filters devices based on the desired platform and version.
112-
* @param allDevices - The list of all devices.
113-
* @param desiredPlatform - The desired platform (android or ios).
114-
* @param desiredPlatformVersion - The desired platform version.
115-
* @returns The filtered list of devices.
116-
* @throws Will throw an error if the platform version is not valid.
117-
*/
118-
function filterDevicesByPlatformAndVersion(
119-
allDevices: DeviceEntry[],
120-
desiredPlatform: string,
121-
desiredPlatformVersion: string,
122-
): DeviceEntry[] {
123-
return allDevices.filter((d) => {
124-
if (d.os !== desiredPlatform) return false;
39+
// 3) Select by name
40+
const nameMatches = findDeviceByName(osMatches, desiredPhone);
12541

126-
try {
127-
const versionA = parseFloat(d.os_version);
128-
const versionB = parseFloat(desiredPlatformVersion);
129-
return versionA === versionB;
130-
} catch {
131-
return d.os_version === desiredPlatformVersion;
132-
}
133-
});
134-
}
42+
// 4) Resolve version
43+
const versions = [...new Set(nameMatches.map((d) => d.os_version))];
44+
const version = pickVersion(versions, desiredPlatformVersion);
13545

136-
/**
137-
* Validates the selected device and handles multiple matches.
138-
* @param matches - The list of device matches.
139-
* @param desiredPhone - The desired phone name.
140-
* @param desiredPlatform - The desired platform (android or ios).
141-
* @param desiredPlatformVersion - The desired platform version.
142-
* @returns The selected device entry.
143-
*/
144-
function validateAndSelectDevice(
145-
matches: DeviceEntry[],
146-
desiredPhone: string,
147-
desiredPlatform: string,
148-
desiredPlatformVersion: string,
149-
): DeviceEntry {
150-
if (matches.length === 0) {
46+
// 5) Final candidates for version
47+
const final = nameMatches.filter((d) => d.os_version === version);
48+
if (!final.length) {
15149
throw new Error(
152-
`No devices found matching "${desiredPhone}" for ${desiredPlatform} ${desiredPlatformVersion}`,
50+
`No devices for version "${version}" on ${desiredPlatform}`,
15351
);
15452
}
155-
156-
const exactMatch = matches.find(
157-
(d) => d.display_name.toLowerCase() === desiredPhone.toLowerCase(),
158-
);
159-
160-
if (exactMatch) {
161-
return exactMatch;
162-
} else if (matches.length >= 1) {
163-
const names = matches.map((d) => d.display_name).join(", ");
164-
const error_message =
165-
matches.length === 1
166-
? `Alternative device found: ${names}. Would you like to use it?`
167-
: `Multiple devices found: ${names}. Please select one.`;
168-
throw new Error(`${error_message}`);
53+
const selected = final[0];
54+
let note = "";
55+
if (
56+
version != desiredPlatformVersion &&
57+
desiredPlatformVersion !== "latest" &&
58+
desiredPlatformVersion !== "oldest"
59+
) {
60+
note = `\n Note: The requested version "${desiredPlatformVersion}" is not available. Using "${version}" instead.`;
16961
}
17062

171-
return matches[0];
172-
}
173-
174-
/**
175-
* Validates the app URL.
176-
* @param appUrl - The app URL to validate.
177-
* @throws Will throw an error if the app URL is not valid.
178-
*/
179-
function validateAppUrl(appUrl: string): void {
180-
if (!appUrl.match("bs://")) {
181-
throw new Error("The app path is not a valid BrowserStack app URL.");
182-
}
183-
}
63+
// 6) Upload app
64+
const { app_url } = await uploadApp(appPath);
65+
logger.info(`App uploaded: ${app_url}`);
18466

185-
/**
186-
* Constructs the launch URL for the App Live session.
187-
* @param appUrl - The app URL.
188-
* @param device - The selected device entry.
189-
* @param desiredPlatform - The desired platform (android or ios).
190-
* @param desiredPlatformVersion - The desired platform version.
191-
* @returns The constructed launch URL.
192-
*/
193-
function constructLaunchUrl(
194-
appUrl: string,
195-
device: DeviceEntry,
196-
desiredPlatform: string,
197-
desiredPlatformVersion: string,
198-
): string {
67+
// 7) Build URL & open
19968
const deviceParam = sanitizeUrlParam(
200-
device.display_name.replace(/\s+/g, "+"),
69+
selected.display_name.replace(/\s+/g, "+"),
20170
);
202-
20371
const params = new URLSearchParams({
20472
os: desiredPlatform,
205-
os_version: desiredPlatformVersion,
206-
app_hashed_id: appUrl.split("bs://").pop() || "",
73+
os_version: version,
74+
app_hashed_id: app_url.split("bs://").pop() || "",
20775
scale_to_fit: "true",
20876
speed: "1",
20977
start: "true",
21078
});
79+
const launchUrl = `https://app-live.browserstack.com/dashboard#${params.toString()}&device=${deviceParam}`;
21180

212-
return `https://app-live.browserstack.com/dashboard#${params.toString()}&device=${deviceParam}`;
81+
openBrowser(launchUrl);
82+
return launchUrl + note;
21383
}
21484

21585
/**

src/tools/applive-utils/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface DeviceEntry {
2+
display_name: string;
3+
device: string;
4+
os: string;
5+
os_version: string;
6+
real_mobile: boolean;
7+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { resolveVersion } from "../../lib/version-resolver.js";
2+
3+
/**
4+
* Resolve desired version against available list
5+
*/
6+
export function pickVersion(available: string[], requested: string): string {
7+
try {
8+
return resolveVersion(requested, available);
9+
} catch {
10+
const opts = available.join(", ");
11+
throw new Error(
12+
`Version "${requested}" not found. Available versions: ${opts}`,
13+
);
14+
}
15+
}

src/tools/live-utils/desktop-filter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
getDevicesAndBrowsers,
33
BrowserStackProducts,
44
} from "../../lib/device-cache.js";
5-
import { resolveVersion } from "./version-resolver.js";
5+
import { resolveVersion } from "../../lib/version-resolver.js";
66
import { customFuzzySearch } from "../../lib/fuzzy.js";
77
import { DesktopSearchArgs, DesktopEntry } from "./types.js";
88

src/tools/live-utils/mobile-filter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
getDevicesAndBrowsers,
33
BrowserStackProducts,
44
} from "../../lib/device-cache.js";
5-
import { resolveVersion } from "./version-resolver.js";
5+
import { resolveVersion } from "../../lib/version-resolver.js";
66
import { customFuzzySearch } from "../../lib/fuzzy.js";
77
import { MobileSearchArgs, MobileEntry } from "./types.js";
88

0 commit comments

Comments
 (0)