Skip to content

Commit a03e7cd

Browse files
Merge pull request #4 from BrowserStackCE/merge-latency-finder
Merge latency finder
2 parents 168810a + 19292b1 commit a03e7cd

File tree

12 files changed

+1405
-70
lines changed

12 files changed

+1405
-70
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,6 @@ typings/
9292
out/
9393

9494
database
95+
96+
# VS Code settings
97+
.vscode/*

src/channelHandlers/browserstack-api.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { parseAutomateTextLogs } from "../utils/text-logs-parser"
22
import CONFIG from "../constants/config"
3+
import { parseAutomateSessionLogs } from "../utils/latency-finder/session-logs-parser"
4+
import { parseAutomateSeleniumLogs } from "../utils/latency-finder/selenium-logs-parser"
5+
import { convertUTCToEpoch } from "../utils/latency-finder/helper"
36

47
const BASE_URL = 'https://api.browserstack.com'
58

@@ -201,3 +204,27 @@ function replaceElementIdDeep(obj: any, newId: string): any {
201204
return obj;
202205
}
203206

207+
export const getAutomateParsedSessionLogs = async (session: AutomateSessionResponse)=> {
208+
const logs = await download(session.automation_session.logs);
209+
const result = parseAutomateSessionLogs(logs);
210+
return result;
211+
}
212+
export const getAutomateParsedSeleniumLogs = async (session: AutomateSessionResponse)=> {
213+
const seleniumLogsUrl = `https://api.browserstack.com/automate/sessions/${session.automation_session.hashed_id}/seleniumlogs`
214+
const logs = await download(seleniumLogsUrl);
215+
216+
// Convert created_at to epoch (UTC)
217+
const sessionCreatedAtUTC = convertUTCToEpoch(
218+
session.automation_session.created_at
219+
);
220+
// Extract just the date part from created_at
221+
const date = session.automation_session.created_at.split("T")[0]; // date = "2025-11-13"
222+
223+
const result = parseAutomateSeleniumLogs(
224+
logs,
225+
date,
226+
sessionCreatedAtUTC
227+
);
228+
229+
return result;
230+
}

src/constants/ipc-channels.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ const CHANNELS = {
99
BROWSERSTACK_START_SESSION:'BROWSERSTACK_START_SESSION',
1010
BROWSERSTACK_STOP_SESSION:'BROWSERSTACK_STOP_SESSION',
1111
BROWSERSTACK_EXECUTE_SESSION_COMMAND:'BROWSERSTACK_EXECUTE_SESSION_COMMAND',
12-
ELECTRON_OPEN_URL:'ELECTRON_OPEN_URL'
12+
ELECTRON_OPEN_URL:'ELECTRON_OPEN_URL',
13+
GET_BROWSERSTACK_AUTOMATE_PARSED_SESSION_LOGS:'GET_BROWSERSTACK_AUTOMATE_PARSED_SESSION_LOGS',
14+
GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS:'GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS'
1315
}
1416

1517
export default CHANNELS

src/global.d.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ declare global {
1313
startSession: (options: StartSessionOptions) => Promise<StartSessionResponse>
1414
stopSession: (options: StopSessionOptions) => Promise<StopSessionResponse>
1515
executeCommand: (options: ExecuteCommandOptions) => any
16+
getAutomateParsedSessionLogs: (session: AutomateSessionResponse) => Promise<ScanResult>
17+
getAutomateParsedSeleniumLogs: (session: AutomateSessionResponse) => Promise<SeleniumScanResult>
1618
}
1719

1820
type ElectronAPI = {
@@ -112,4 +114,137 @@ declare global {
112114
hubUrl?: string
113115
}
114116

117+
118+
// latency-finder
119+
120+
interface LogParams {
121+
[key: string]: any;
122+
}
123+
124+
interface LogRequest {
125+
created_at: number;
126+
line_number: number;
127+
out_time: number;
128+
http_type: string;
129+
action: string;
130+
params: LogParams;
131+
}
132+
133+
interface LogResponse {
134+
created_at: number;
135+
line_number: number;
136+
in_time: number;
137+
params: LogParams;
138+
}
139+
140+
interface LogDebug {
141+
created_at: number;
142+
line_number: number;
143+
url: string;
144+
}
145+
146+
interface Exchange {
147+
id: number;
148+
request?: LogRequest;
149+
response?: LogResponse;
150+
debug?: LogDebug;
151+
}
152+
153+
interface Summary {
154+
total_requests: number;
155+
session_started_at: number | null;
156+
session_completed_at: number | null;
157+
session_duration: number | null;
158+
setup_time: number;
159+
execution_time: number;
160+
in_time: number;
161+
out_time: number;
162+
passed_requests: number;
163+
failed_requests: number;
164+
unknown_requests: number;
165+
log_length: number;
166+
setup_time_perc: number;
167+
in_time_perc: number;
168+
out_time_perc: number;
169+
average_cycle_time: number;
170+
average_serve_time: number;
171+
average_wait_time: number;
172+
passed_perc: number;
173+
failed_perc: number;
174+
unknown_perc: number;
175+
}
176+
177+
interface ScanResult {
178+
summary: Summary;
179+
exchanges: Exchange[];
180+
}
181+
182+
183+
type Phase = "Setup" | "Session" | "Tear Down" | null;
184+
185+
interface SeleniumLogLineType {
186+
name: string;
187+
identifier: string;
188+
phase?: Phase;
189+
}
190+
191+
interface LogHeaders {
192+
[key: string]: string;
193+
}
194+
195+
interface LogRequest {
196+
created_at: number;
197+
line_number: number;
198+
out_time: number;
199+
params?: any;
200+
headers?: LogHeaders;
201+
}
202+
203+
interface LogResponse {
204+
created_at?: number;
205+
line_number?: number;
206+
in_time?: number;
207+
params?: any;
208+
headers?: LogHeaders;
209+
}
210+
211+
interface SeleniumExchange {
212+
id: number;
213+
request: LogRequest;
214+
response: LogResponse;
215+
}
216+
217+
interface SeleniumSummary {
218+
total_requests: number;
219+
dialect: string;
220+
setup_polls: number;
221+
tear_down_polls: number;
222+
session_started_at: number;
223+
session_completed_at: number;
224+
driver_started_at: number;
225+
driver_init_time: number;
226+
session_duration: number | null;
227+
setup_time: number;
228+
execution_time: number;
229+
in_time: number;
230+
out_time: number;
231+
passed_requests: number;
232+
failed_requests: number;
233+
unknown_requests: number;
234+
log_length: number;
235+
setup_time_perc: number;
236+
in_time_perc: number;
237+
out_time_perc: number;
238+
average_cycle_time: number;
239+
average_serve_time: number;
240+
average_wait_time: number;
241+
passed_perc: number;
242+
failed_perc: number;
243+
unknown_perc: number;
244+
}
245+
246+
interface SeleniumScanResult {
247+
summary: SeleniumSummary;
248+
exchanges: SeleniumExchange[];
249+
}
115250
}

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import StorageKeys from './constants/storage-keys';
88
import CONFIG from './constants/config';
99

1010
import { mkdirSync } from 'fs'
11-
import { executeCommand, getAutomateSessionDetails, getParsedAutomateTextLogs, startBrowserStackSession, stopBrowserStackSession } from './channelHandlers/browserstack-api';
11+
import { executeCommand, getAutomateSessionDetails, getParsedAutomateTextLogs, startBrowserStackSession, stopBrowserStackSession, getAutomateParsedSeleniumLogs, getAutomateParsedSessionLogs, } from './channelHandlers/browserstack-api';
1212
import { openExternalUrl } from './channelHandlers/electron-api';
13+
14+
import { get } from 'http';
1315
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
1416
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
1517
// whether you're running in development or production).
@@ -93,6 +95,8 @@ app.whenReady().then(() => {
9395
ipcMain.handle(CHANNELS.GET_DEMO_CREDENTIALS, getBrowserStackDemoCredentials);
9496
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_SESSION, (_, id) => getAutomateSessionDetails(id))
9597
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_TEXT_LOGS, (_, session) => getParsedAutomateTextLogs(session))
98+
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_SESSION_LOGS, (_, session) => getAutomateParsedSessionLogs(session))
99+
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS, (_, session) => getAutomateParsedSeleniumLogs(session))
96100
ipcMain.handle(CHANNELS.BROWSERSTACK_START_SESSION, (_, options) => startBrowserStackSession(options))
97101
ipcMain.handle(CHANNELS.BROWSERSTACK_STOP_SESSION, (_, options) => stopBrowserStackSession(options))
98102
ipcMain.handle(CHANNELS.BROWSERSTACK_EXECUTE_SESSION_COMMAND, (_, options) => executeCommand(options))

src/preload.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ const browserstackAPI: BrowserStackAPI = {
1414
getAutomateParsedTextLogs: (session) => ipcRenderer.invoke(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_TEXT_LOGS, session),
1515
startSession: (options) => ipcRenderer.invoke(CHANNELS.BROWSERSTACK_START_SESSION, options),
1616
stopSession: (options) => ipcRenderer.invoke(CHANNELS.BROWSERSTACK_STOP_SESSION, options),
17-
executeCommand: (options) => ipcRenderer.invoke(CHANNELS.BROWSERSTACK_EXECUTE_SESSION_COMMAND, options)
17+
executeCommand: (options) => ipcRenderer.invoke(CHANNELS.BROWSERSTACK_EXECUTE_SESSION_COMMAND, options),
18+
getAutomateParsedSessionLogs: (session)=>ipcRenderer.invoke(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_SESSION_LOGS,session),
19+
getAutomateParsedSeleniumLogs: (session)=>ipcRenderer.invoke(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS,session),
1820
}
1921

2022
const electronAPI: ElectronAPI = {
Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,57 @@
1-
import { NavLink, useLocation } from "react-router-dom"
2-
import Products from "../products"
1+
import { NavLink, useLocation } from "react-router-dom";
2+
import Products from "../products";
33

44
const TopMenu = [
5-
{
6-
title: "Configurations",
7-
path: '/'
8-
},
9-
]
5+
{
6+
title: "Configurations",
7+
path: "/",
8+
},
9+
];
1010
const ProductsMenu = [
11-
{
12-
title: "Automate",
13-
path: '/automate'
14-
},
15-
{
16-
title: "App Automate",
17-
path: '/app-automate'
18-
},
19-
{
20-
title: "Percy",
21-
path: '/percy'
22-
},
23-
{
24-
title: "Accessibility",
25-
path: '/accessibility'
26-
}
27-
]
11+
{
12+
title: "Automate",
13+
path: "/automate",
14+
},
15+
{
16+
title: "App Automate",
17+
path: "/app-automate",
18+
},
19+
{
20+
title: "Percy",
21+
path: "/percy",
22+
},
23+
{
24+
title: "Accessibility",
25+
path: "/accessibility",
26+
},
27+
];
2828

2929
export default function Sidebar() {
30-
const location = useLocation()
31-
console.log(location)
32-
return (
33-
<div>
34-
<ul className="menu bg-base-200 w-full bg-transparent p-4 gap-4">
35-
{TopMenu.map((item) => {
36-
const isActive = location.pathname == item.path
37-
return (
38-
<li className={`${isActive?'menu-active':''}`} key={item.path}><NavLink to={item.path} >{item.title}</NavLink></li>
39-
)
40-
})}
41-
<label className="font-bold" htmlFor="">Products</label>
42-
{Products.map((item) => {
43-
const isActive = location.pathname.includes(item.path)
44-
return (
45-
<li className={`${isActive?'menu-active':''}`} key={item.path}><NavLink to={item.path} >{item.name}</NavLink></li>
46-
)
47-
})}
48-
</ul>
49-
</div>
50-
)
51-
}
30+
const location = useLocation();
31+
console.log(location);
32+
return (
33+
<div className="bg-base-100 h-full flex flex-col">
34+
<ul className="menu bg-base-200 w-full bg-transparent p-4 gap-4">
35+
{TopMenu.map((item) => {
36+
const isActive = location.pathname == item.path;
37+
return (
38+
<li className={`${isActive ? "menu-active" : ""}`} key={item.path}>
39+
<NavLink to={item.path}>{item.title}</NavLink>
40+
</li>
41+
);
42+
})}
43+
<label className="font-bold" htmlFor="">
44+
Products
45+
</label>
46+
{Products.map((item) => {
47+
const isActive = location.pathname.includes(item.path);
48+
return (
49+
<li className={`${isActive ? "menu-active" : ""}`} key={item.path}>
50+
<NavLink to={item.path}>{item.name}</NavLink>
51+
</li>
52+
);
53+
})}
54+
</ul>
55+
</div>
56+
);
57+
}

src/renderer/products.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
import AutomatePage from "./routes/automate";
22
import ReplayTool from "./routes/automate/tools/replay-tool";
3+
import LatencyFinder from "./routes/automate/tools/latency-finder";
34

45
const Products = [
5-
{
6-
name:"Automate",
7-
path:'/automate',
8-
page: AutomatePage,
9-
tools:[
10-
{
11-
title:"Replay Toolkit",
12-
description:"Replays the sessions on BrowserStack by parsing Raw Logs",
13-
path:'/automate/replay-toolkit',
14-
component: ReplayTool
15-
},
16-
{
17-
title:"Latency Analyser",
18-
description:"Analyses time spend on different actions. Helpful to identify inside/outside time for a customer session.",
19-
path:'/automate/latency-analyser',
20-
component: null
21-
}
22-
]
23-
}
24-
]
6+
{
7+
name: "Automate",
8+
path: "/automate",
9+
page: AutomatePage,
10+
tools: [
11+
{
12+
title: "Replay Toolkit",
13+
description: "Replays the sessions on BrowserStack by parsing Raw Logs",
14+
path: "/automate/replay-toolkit",
15+
component: ReplayTool,
16+
},
17+
{
18+
title: "Latency Analyser",
19+
description:
20+
"Analyses time spend on different actions. Helpful to identify inside/outside time for a customer session.",
21+
path: "/automate/latency-analyser",
22+
component: LatencyFinder,
23+
},
24+
],
25+
},
26+
];
2527

26-
export default Products;
28+
export default Products;

0 commit comments

Comments
 (0)