Skip to content

Commit b376a26

Browse files
authored
fix : added proper error handling to failure logs (#42)
* Added proper error handling to failure logs * Moved all the utils related to getFailure tool moved to failure-utils * Refactor failure log retrieval functions to return formatted strings instead of objects * Updated TC as per new refactoring * Removing unused imports
1 parent 8a960b7 commit b376a26

File tree

6 files changed

+168
-160
lines changed

6 files changed

+168
-160
lines changed

src/lib/utils.ts

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,6 @@ export function sanitizeUrlParam(param: string): string {
55
return param.replace(/[;&|`$(){}[\]<>]/g, "");
66
}
77

8-
export interface HarFile {
9-
log: {
10-
entries: HarEntry[];
11-
};
12-
}
13-
14-
export interface HarEntry {
15-
startedDateTime: string;
16-
request: {
17-
method: string;
18-
url: string;
19-
queryString?: { name: string; value: string }[];
20-
};
21-
response: {
22-
status: number;
23-
statusText?: string;
24-
_error?: string;
25-
};
26-
serverIPAddress?: string;
27-
time?: number;
28-
}
29-
308
const ONE_MB = 1048576;
319

3210
//Compresses a base64 image intelligently to keep it under 1 MB if needed.
@@ -56,15 +34,3 @@ export async function assertOkResponse(response: Response, action: string) {
5634
);
5735
}
5836
}
59-
60-
export function filterLinesByKeywords(
61-
logText: string,
62-
keywords: string[],
63-
): string[] {
64-
return logText
65-
.split(/\r?\n/)
66-
.map((line) => line.trim())
67-
.filter((line) =>
68-
keywords.some((keyword) => line.toLowerCase().includes(keyword)),
69-
);
70-
}

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

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import config from "../../config.js";
2-
import { assertOkResponse, filterLinesByKeywords } from "../../lib/utils.js";
2+
import {
3+
filterLinesByKeywords,
4+
validateLogResponse,
5+
} from "./utils.js";
36

47
const auth = Buffer.from(
58
`${config.browserstackUsername}:${config.browserstackAccessKey}`,
@@ -9,7 +12,7 @@ const auth = Buffer.from(
912
export async function retrieveDeviceLogs(
1013
sessionId: string,
1114
buildId: string,
12-
): Promise<string[]> {
15+
): Promise<string> {
1316
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/deviceLogs`;
1417

1518
const response = await fetch(url, {
@@ -19,17 +22,21 @@ export async function retrieveDeviceLogs(
1922
},
2023
});
2124

22-
await assertOkResponse(response, "device logs");
25+
const validationError = validateLogResponse(response, "device logs");
26+
if (validationError) return validationError.message!;
2327

2428
const logText = await response.text();
25-
return filterDeviceFailures(logText);
29+
const logs = filterDeviceFailures(logText);
30+
return logs.length > 0
31+
? `Device Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
32+
: "No device failures found";
2633
}
2734

2835
// APPIUM LOGS
2936
export async function retrieveAppiumLogs(
3037
sessionId: string,
3138
buildId: string,
32-
): Promise<string[]> {
39+
): Promise<string> {
3340
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/appiumlogs`;
3441

3542
const response = await fetch(url, {
@@ -39,17 +46,21 @@ export async function retrieveAppiumLogs(
3946
},
4047
});
4148

42-
await assertOkResponse(response, "Appium logs");
49+
const validationError = validateLogResponse(response, "Appium logs");
50+
if (validationError) return validationError.message!;
4351

4452
const logText = await response.text();
45-
return filterAppiumFailures(logText);
53+
const logs = filterAppiumFailures(logText);
54+
return logs.length > 0
55+
? `Appium Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
56+
: "No Appium failures found";
4657
}
4758

4859
// CRASH LOGS
4960
export async function retrieveCrashLogs(
5061
sessionId: string,
5162
buildId: string,
52-
): Promise<string[]> {
63+
): Promise<string> {
5364
const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/crashlogs`;
5465

5566
const response = await fetch(url, {
@@ -59,14 +70,17 @@ export async function retrieveCrashLogs(
5970
},
6071
});
6172

62-
await assertOkResponse(response, "crash logs");
73+
const validationError = validateLogResponse(response, "crash logs");
74+
if (validationError) return validationError.message!;
6375

6476
const logText = await response.text();
65-
return filterCrashFailures(logText);
77+
const logs = filterCrashFailures(logText);
78+
return logs.length > 0
79+
? `Crash Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
80+
: "No crash failures found";
6681
}
6782

6883
// FILTER HELPERS
69-
7084
export function filterDeviceFailures(logText: string): string[] {
7185
const keywords = [
7286
"error",

src/tools/failurelogs-utils/automate.ts

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
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+
validateLogResponse,
7+
} from "./utils.js";
48

59
const auth = Buffer.from(
610
`${config.browserstackUsername}:${config.browserstackAccessKey}`,
711
).toString("base64");
812

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

1319
const response = await fetch(url, {
@@ -18,43 +24,40 @@ export async function retrieveNetworkFailures(sessionId: string): Promise<any> {
1824
},
1925
});
2026

21-
await assertOkResponse(response, "network logs");
27+
const validationError = validateLogResponse(response, "network logs");
28+
if (validationError) return validationError.message!;
2229

2330
const networklogs: HarFile = await response.json();
24-
25-
// Filter for failure logs
2631
const failureEntries: HarEntry[] = networklogs.log.entries.filter(
27-
(entry: HarEntry) => {
28-
return (
29-
entry.response.status === 0 ||
30-
entry.response.status >= 400 ||
31-
entry.response._error !== undefined
32-
);
33-
},
32+
(entry: HarEntry) =>
33+
entry.response.status === 0 ||
34+
entry.response.status >= 400 ||
35+
entry.response._error !== undefined
3436
);
35-
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-
}));
37+
38+
return failureEntries.length > 0
39+
? `Network Failures (${failureEntries.length} found):\n${JSON.stringify(failureEntries.map((entry: any) => ({
40+
startedDateTime: entry.startedDateTime,
41+
request: {
42+
method: entry.request?.method,
43+
url: entry.request?.url,
44+
queryString: entry.request?.queryString,
45+
},
46+
response: {
47+
status: entry.response?.status,
48+
statusText: entry.response?.statusText,
49+
_error: entry.response?._error,
50+
},
51+
serverIPAddress: entry.serverIPAddress,
52+
time: entry.time,
53+
})), null, 2)}`
54+
: "No network failures found";
5255
}
5356

5457
// SESSION LOGS
5558
export async function retrieveSessionFailures(
5659
sessionId: string,
57-
): Promise<string[]> {
60+
): Promise<string> {
5861
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`;
5962

6063
const response = await fetch(url, {
@@ -64,16 +67,20 @@ export async function retrieveSessionFailures(
6467
},
6568
});
6669

67-
await assertOkResponse(response, "session logs");
70+
const validationError = validateLogResponse(response, "session logs");
71+
if (validationError) return validationError.message!;
6872

6973
const logText = await response.text();
70-
return filterSessionFailures(logText);
74+
const logs = filterSessionFailures(logText);
75+
return logs.length > 0
76+
? `Session Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
77+
: "No session failures found";
7178
}
7279

7380
// CONSOLE LOGS
7481
export async function retrieveConsoleFailures(
7582
sessionId: string,
76-
): Promise<string[]> {
83+
): Promise<string> {
7784
const url = `https://api.browserstack.com/automate/sessions/${sessionId}/consolelogs`;
7885

7986
const response = await fetch(url, {
@@ -83,10 +90,14 @@ export async function retrieveConsoleFailures(
8390
},
8491
});
8592

86-
await assertOkResponse(response, "console logs");
93+
const validationError = validateLogResponse(response, "console logs");
94+
if (validationError) return validationError.message!;
8795

8896
const logText = await response.text();
89-
return filterConsoleFailures(logText);
97+
const logs = filterConsoleFailures(logText);
98+
return logs.length > 0
99+
? `Console Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
100+
: "No console failures found";
90101
}
91102

92103
// FILTER: session logs

src/tools/failurelogs-utils/utils.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export interface LogResponse {
2+
logs?: any[];
3+
message?: string;
4+
}
5+
6+
export interface HarFile {
7+
log: {
8+
entries: HarEntry[];
9+
};
10+
}
11+
12+
export interface HarEntry {
13+
startedDateTime: string;
14+
request: {
15+
method: string;
16+
url: string;
17+
queryString?: { name: string; value: string }[];
18+
};
19+
response: {
20+
status: number;
21+
statusText?: string;
22+
_error?: string;
23+
};
24+
serverIPAddress?: string;
25+
time?: number;
26+
}
27+
28+
export function validateLogResponse(
29+
response: Response,
30+
logType: string,
31+
): LogResponse | null {
32+
if (!response.ok) {
33+
if (response.status === 404) {
34+
return { message: `No ${logType} available for this session` };
35+
}
36+
if (response.status === 401 || response.status === 403) {
37+
return {
38+
message: `Unable to access ${logType} - please check your credentials`,
39+
};
40+
}
41+
return { message: `Unable to fetch ${logType} at this time` };
42+
}
43+
return null;
44+
}
45+
46+
export function filterLinesByKeywords(
47+
logText: string,
48+
keywords: string[],
49+
): string[] {
50+
return logText
51+
.split(/\r?\n/)
52+
.map((line) => line.trim())
53+
.filter((line) =>
54+
keywords.some((keyword) => line.toLowerCase().includes(keyword)),
55+
);
56+
}

0 commit comments

Comments
 (0)