Skip to content

Commit 48abda5

Browse files
committed
Added proper error handling to failure logs
1 parent 6dd14f2 commit 48abda5

File tree

5 files changed

+125
-90
lines changed

5 files changed

+125
-90
lines changed

src/lib/utils.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import sharp from "sharp";
22

3-
export function sanitizeUrlParam(param: string): string {
4-
// Remove any characters that could be used for command injection
5-
return param.replace(/[;&|`$(){}[\]<>]/g, "");
3+
export interface LogResponse {
4+
logs?: any[];
5+
message?: string;
66
}
77

88
export interface HarFile {
@@ -27,6 +27,29 @@ export interface HarEntry {
2727
time?: number;
2828
}
2929

30+
export function validateResponse(
31+
response: Response,
32+
logType: string,
33+
): LogResponse | null {
34+
if (!response.ok) {
35+
if (response.status === 404) {
36+
return { message: `No ${logType} available for this session` };
37+
}
38+
if (response.status === 401 || response.status === 403) {
39+
return {
40+
message: `Unable to access ${logType} - please check your credentials`,
41+
};
42+
}
43+
return { message: `Unable to fetch ${logType} at this time` };
44+
}
45+
return null;
46+
}
47+
48+
export function sanitizeUrlParam(param: string): string {
49+
// Remove any characters that could be used for command injection
50+
return param.replace(/[;&|`$(){}[\]<>]/g, "");
51+
}
52+
3053
const ONE_MB = 1048576;
3154

3255
//Compresses a base64 image intelligently to keep it under 1 MB if needed.

src/tools/failurelogs-utils/app-automate.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import config from "../../config.js";
2-
import { assertOkResponse, filterLinesByKeywords } from "../../lib/utils.js";
2+
import {
3+
filterLinesByKeywords,
4+
validateResponse,
5+
LogResponse,
6+
} from "../../lib/utils.js";
37

48
const auth = Buffer.from(
59
`${config.browserstackUsername}:${config.browserstackAccessKey}`,
@@ -9,7 +13,7 @@ const auth = Buffer.from(
913
export async function retrieveDeviceLogs(
1014
sessionId: string,
1115
buildId: string,
12-
): Promise<string[]> {
16+
): Promise<LogResponse> {
1317
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/deviceLogs`;
1418

1519
const response = await fetch(url, {
@@ -19,17 +23,18 @@ export async function retrieveDeviceLogs(
1923
},
2024
});
2125

22-
await assertOkResponse(response, "device logs");
26+
const validationResult = validateResponse(response, "device logs");
27+
if (validationResult) return validationResult;
2328

2429
const logText = await response.text();
25-
return filterDeviceFailures(logText);
30+
return { logs: filterDeviceFailures(logText) };
2631
}
2732

2833
// APPIUM LOGS
2934
export async function retrieveAppiumLogs(
3035
sessionId: string,
3136
buildId: string,
32-
): Promise<string[]> {
37+
): Promise<LogResponse> {
3338
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/appiumlogs`;
3439

3540
const response = await fetch(url, {
@@ -39,17 +44,18 @@ export async function retrieveAppiumLogs(
3944
},
4045
});
4146

42-
await assertOkResponse(response, "Appium logs");
47+
const validationResult = validateResponse(response, "Appium logs");
48+
if (validationResult) return validationResult;
4349

4450
const logText = await response.text();
45-
return filterAppiumFailures(logText);
51+
return { logs: filterAppiumFailures(logText) };
4652
}
4753

4854
// CRASH LOGS
4955
export async function retrieveCrashLogs(
5056
sessionId: string,
5157
buildId: string,
52-
): Promise<string[]> {
58+
): Promise<LogResponse> {
5359
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/crashlogs`;
5460

5561
const response = await fetch(url, {
@@ -59,14 +65,14 @@ export async function retrieveCrashLogs(
5965
},
6066
});
6167

62-
await assertOkResponse(response, "crash logs");
68+
const validationResult = validateResponse(response, "crash logs");
69+
if (validationResult) return validationResult;
6370

6471
const logText = await response.text();
65-
return filterCrashFailures(logText);
72+
return { logs: filterCrashFailures(logText) };
6673
}
6774

6875
// FILTER HELPERS
69-
7076
export function filterDeviceFailures(logText: string): string[] {
7177
const keywords = [
7278
"error",

src/tools/failurelogs-utils/automate.ts

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import config from "../../config.js";
2-
import { HarEntry, HarFile } from "../../lib/utils.js";
3-
import { assertOkResponse, filterLinesByKeywords } from "../../lib/utils.js";
2+
import {
3+
HarEntry,
4+
HarFile,
5+
filterLinesByKeywords,
6+
validateResponse,
7+
LogResponse,
8+
} from "../../lib/utils.js";
49

510
const auth = Buffer.from(
611
`${config.browserstackUsername}:${config.browserstackAccessKey}`,
712
).toString("base64");
813

914
// NETWORK LOGS
10-
export async function retrieveNetworkFailures(sessionId: string): Promise<any> {
15+
export async function retrieveNetworkFailures(
16+
sessionId: string,
17+
): Promise<LogResponse> {
1118
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/networklogs`;
1219

1320
const response = await fetch(url, {
@@ -18,7 +25,8 @@ export async function retrieveNetworkFailures(sessionId: string): Promise<any> {
1825
},
1926
});
2027

21-
await assertOkResponse(response, "network logs");
28+
const validationResult = validateResponse(response, "network logs");
29+
if (validationResult) return validationResult;
2230

2331
const networklogs: HarFile = await response.json();
2432

@@ -33,28 +41,29 @@ export async function retrieveNetworkFailures(sessionId: string): Promise<any> {
3341
},
3442
);
3543

36-
// Return only the failure entries with some context
37-
return failureEntries.map((entry: any) => ({
38-
startedDateTime: entry.startedDateTime,
39-
request: {
40-
method: entry.request?.method,
41-
url: entry.request?.url,
42-
queryString: entry.request?.queryString,
43-
},
44-
response: {
45-
status: entry.response?.status,
46-
statusText: entry.response?.statusText,
47-
_error: entry.response?._error,
48-
},
49-
serverIPAddress: entry.serverIPAddress,
50-
time: entry.time,
51-
}));
44+
return {
45+
logs: failureEntries.map((entry: any) => ({
46+
startedDateTime: entry.startedDateTime,
47+
request: {
48+
method: entry.request?.method,
49+
url: entry.request?.url,
50+
queryString: entry.request?.queryString,
51+
},
52+
response: {
53+
status: entry.response?.status,
54+
statusText: entry.response?.statusText,
55+
_error: entry.response?._error,
56+
},
57+
serverIPAddress: entry.serverIPAddress,
58+
time: entry.time,
59+
})),
60+
};
5261
}
5362

5463
// SESSION LOGS
5564
export async function retrieveSessionFailures(
5665
sessionId: string,
57-
): Promise<string[]> {
66+
): Promise<LogResponse> {
5867
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`;
5968

6069
const response = await fetch(url, {
@@ -64,16 +73,17 @@ export async function retrieveSessionFailures(
6473
},
6574
});
6675

67-
await assertOkResponse(response, "session logs");
76+
const validationResult = validateResponse(response, "session logs");
77+
if (validationResult) return validationResult;
6878

6979
const logText = await response.text();
70-
return filterSessionFailures(logText);
80+
return { logs: filterSessionFailures(logText) };
7181
}
7282

7383
// CONSOLE LOGS
7484
export async function retrieveConsoleFailures(
7585
sessionId: string,
76-
): Promise<string[]> {
86+
): Promise<LogResponse> {
7787
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/consolelogs`;
7888

7989
const response = await fetch(url, {
@@ -83,10 +93,11 @@ export async function retrieveConsoleFailures(
8393
},
8494
});
8595

86-
await assertOkResponse(response, "console logs");
96+
const validationResult = validateResponse(response, "console logs");
97+
if (validationResult) return validationResult;
8798

8899
const logText = await response.text();
89-
return filterConsoleFailures(logText);
100+
return { logs: filterConsoleFailures(logText) };
90101
}
91102

92103
// FILTER: session logs

src/tools/getFailureLogs.ts

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import {
1515
retrieveCrashLogs,
1616
} from "./failurelogs-utils/app-automate.js";
1717
import { trackMCP } from "../lib/instrumentation.js";
18-
import { AppAutomateLogType, AutomateLogType, SessionType } from "../lib/constants.js";
18+
import {
19+
AppAutomateLogType,
20+
AutomateLogType,
21+
SessionType,
22+
} from "../lib/constants.js";
1923

2024
type LogType = AutomateLogType | AppAutomateLogType;
2125
type SessionTypeValues = SessionType;
@@ -86,79 +90,85 @@ export async function getFailureLogs(args: {
8690
],
8791
};
8892
}
89-
93+
let response;
9094
// eslint-disable-next-line no-useless-catch
9195
try {
9296
for (const logType of validLogTypes) {
9397
switch (logType) {
9498
case AutomateLogType.NetworkLogs: {
95-
const logs = await retrieveNetworkFailures(args.sessionId);
99+
response = await retrieveNetworkFailures(args.sessionId);
96100
results.push({
97101
type: "text",
98102
text:
99-
logs.length > 0
100-
? `Network Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
101-
: "No network failures found",
103+
response.message ||
104+
(response.logs && response.logs.length > 0
105+
? `Network Failures (${response.logs.length} found):\n${JSON.stringify(response.logs, null, 2)}`
106+
: "No network failures found"),
102107
});
103108
break;
104109
}
105110

106111
case AutomateLogType.SessionLogs: {
107-
const logs = await retrieveSessionFailures(args.sessionId);
112+
response = await retrieveSessionFailures(args.sessionId);
108113
results.push({
109114
type: "text",
110115
text:
111-
logs.length > 0
112-
? `Session Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
113-
: "No session failures found",
116+
response.message ||
117+
(response.logs && response.logs.length > 0
118+
? `Session Failures (${response.logs.length} found):\n${JSON.stringify(response.logs, null, 2)}`
119+
: "No session failures found"),
114120
});
115121
break;
116122
}
117123

118124
case AutomateLogType.ConsoleLogs: {
119-
const logs = await retrieveConsoleFailures(args.sessionId);
125+
response = await retrieveConsoleFailures(args.sessionId);
120126
results.push({
121127
type: "text",
122128
text:
123-
logs.length > 0
124-
? `Console Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
125-
: "No console failures found",
129+
response.message ||
130+
(response.logs && response.logs.length > 0
131+
? `Console Failures (${response.logs.length} found):\n${JSON.stringify(response.logs, null, 2)}`
132+
: "No console failures found"),
126133
});
127134
break;
128135
}
129136

130137
case AppAutomateLogType.DeviceLogs: {
131-
const logs = await retrieveDeviceLogs(args.sessionId, args.buildId!);
138+
response = await retrieveDeviceLogs(args.sessionId, args.buildId!);
132139
results.push({
133140
type: "text",
134141
text:
135-
logs.length > 0
136-
? `Device Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
137-
: "No device failures found",
142+
response.message ||
143+
(response.logs && response.logs.length > 0
144+
? `Device Failures (${response.logs.length} found):\n${JSON.stringify(response.logs, null, 2)}`
145+
: "No device failures found"),
138146
});
139147
break;
140148
}
141149

142150
case AppAutomateLogType.AppiumLogs: {
143-
const logs = await retrieveAppiumLogs(args.sessionId, args.buildId!);
151+
response = await retrieveAppiumLogs(args.sessionId, args.buildId!);
144152
results.push({
145153
type: "text",
146154
text:
147-
logs.length > 0
148-
? `Appium Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
149-
: "No Appium failures found",
155+
response.message ||
156+
(response.logs && response.logs.length > 0
157+
? `Appium Failures (${response.logs.length} found):\n${JSON.stringify(response.logs, null, 2)}`
158+
: "No Appium failures found"),
150159
});
151160
break;
152161
}
153162

154163
case AppAutomateLogType.CrashLogs: {
155-
const logs = await retrieveCrashLogs(args.sessionId, args.buildId!);
164+
response = await retrieveCrashLogs(args.sessionId, args.buildId!);
156165
results.push({
157166
type: "text",
158167
text:
159-
logs.length > 0
160-
? `Crash Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
161-
: "No crash failures found",
168+
response.message ||
169+
(response.logs && response.logs.length > 0
170+
? `Crash Failures (${response.logs.length} found):\n${JSON.stringify(response.logs, null, 2)}`
171+
: "No crash failures found"),
162172
});
163173
break;
164174
}

0 commit comments

Comments
 (0)