Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ typings/
out/

database

# VS Code settings
.vscode/*
27 changes: 27 additions & 0 deletions src/channelHandlers/browserstack-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { parseAutomateTextLogs } from "../utils/text-logs-parser"
import CONFIG from "../constants/config"
import { parseAutomateSessionLogs } from "../utils/latency-finder/session-logs-parser"
import { parseAutomateSeleniumLogs } from "../utils/latency-finder/selenium-logs-parser"
import { convertUTCToEpoch } from "../utils/latency-finder/helper"

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

Expand Down Expand Up @@ -201,3 +204,27 @@ function replaceElementIdDeep(obj: any, newId: string): any {
return obj;
}

export const getAutomateParsedSessionLogs = async (session: AutomateSessionResponse)=> {
const logs = await download(session.automation_session.logs);
const result = parseAutomateSessionLogs(logs);
return result;
}
export const getAutomateParsedSeleniumLogs = async (session: AutomateSessionResponse)=> {
const seleniumLogsUrl = `https://api.browserstack.com/automate/sessions/${session.automation_session.hashed_id}/seleniumlogs`
const logs = await download(seleniumLogsUrl);

// Convert created_at to epoch (UTC)
const sessionCreatedAtUTC = convertUTCToEpoch(
session.automation_session.created_at
);
// Extract just the date part from created_at
const date = session.automation_session.created_at.split("T")[0]; // date = "2025-11-13"

const result = parseAutomateSeleniumLogs(
logs,
date,
sessionCreatedAtUTC
);

return result;
}
4 changes: 3 additions & 1 deletion src/constants/ipc-channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const CHANNELS = {
BROWSERSTACK_START_SESSION:'BROWSERSTACK_START_SESSION',
BROWSERSTACK_STOP_SESSION:'BROWSERSTACK_STOP_SESSION',
BROWSERSTACK_EXECUTE_SESSION_COMMAND:'BROWSERSTACK_EXECUTE_SESSION_COMMAND',
ELECTRON_OPEN_URL:'ELECTRON_OPEN_URL'
ELECTRON_OPEN_URL:'ELECTRON_OPEN_URL',
GET_BROWSERSTACK_AUTOMATE_PARSED_SESSION_LOGS:'GET_BROWSERSTACK_AUTOMATE_PARSED_SESSION_LOGS',
GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS:'GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS'
}

export default CHANNELS
135 changes: 135 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ declare global {
startSession: (options: StartSessionOptions) => Promise<StartSessionResponse>
stopSession: (options: StopSessionOptions) => Promise<StopSessionResponse>
executeCommand: (options: ExecuteCommandOptions) => any
getAutomateParsedSessionLogs: (session: AutomateSessionResponse) => Promise<ScanResult>
getAutomateParsedSeleniumLogs: (session: AutomateSessionResponse) => Promise<SeleniumScanResult>
}

type ElectronAPI = {
Expand Down Expand Up @@ -112,4 +114,137 @@ declare global {
hubUrl?: string
}


// latency-finder

interface LogParams {
[key: string]: any;
}

interface LogRequest {
created_at: number;
line_number: number;
out_time: number;
http_type: string;
action: string;
params: LogParams;
}

interface LogResponse {
created_at: number;
line_number: number;
in_time: number;
params: LogParams;
}

interface LogDebug {
created_at: number;
line_number: number;
url: string;
}

interface Exchange {
id: number;
request?: LogRequest;
response?: LogResponse;
debug?: LogDebug;
}

interface Summary {
total_requests: number;
session_started_at: number | null;
session_completed_at: number | null;
session_duration: number | null;
setup_time: number;
execution_time: number;
in_time: number;
out_time: number;
passed_requests: number;
failed_requests: number;
unknown_requests: number;
log_length: number;
setup_time_perc: number;
in_time_perc: number;
out_time_perc: number;
average_cycle_time: number;
average_serve_time: number;
average_wait_time: number;
passed_perc: number;
failed_perc: number;
unknown_perc: number;
}

interface ScanResult {
summary: Summary;
exchanges: Exchange[];
}


type Phase = "Setup" | "Session" | "Tear Down" | null;

interface SeleniumLogLineType {
name: string;
identifier: string;
phase?: Phase;
}

interface LogHeaders {
[key: string]: string;
}

interface LogRequest {
created_at: number;
line_number: number;
out_time: number;
params?: any;
headers?: LogHeaders;
}

interface LogResponse {
created_at?: number;
line_number?: number;
in_time?: number;
params?: any;
headers?: LogHeaders;
}

interface SeleniumExchange {
id: number;
request: LogRequest;
response: LogResponse;
}

interface SeleniumSummary {
total_requests: number;
dialect: string;
setup_polls: number;
tear_down_polls: number;
session_started_at: number;
session_completed_at: number;
driver_started_at: number;
driver_init_time: number;
session_duration: number | null;
setup_time: number;
execution_time: number;
in_time: number;
out_time: number;
passed_requests: number;
failed_requests: number;
unknown_requests: number;
log_length: number;
setup_time_perc: number;
in_time_perc: number;
out_time_perc: number;
average_cycle_time: number;
average_serve_time: number;
average_wait_time: number;
passed_perc: number;
failed_perc: number;
unknown_perc: number;
}

interface SeleniumScanResult {
summary: SeleniumSummary;
exchanges: SeleniumExchange[];
}
}
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import StorageKeys from './constants/storage-keys';
import CONFIG from './constants/config';

import { mkdirSync } from 'fs'
import { executeCommand, getAutomateSessionDetails, getParsedAutomateTextLogs, startBrowserStackSession, stopBrowserStackSession } from './channelHandlers/browserstack-api';
import { executeCommand, getAutomateSessionDetails, getParsedAutomateTextLogs, startBrowserStackSession, stopBrowserStackSession, getAutomateParsedSeleniumLogs, getAutomateParsedSessionLogs, } from './channelHandlers/browserstack-api';
import { openExternalUrl } from './channelHandlers/electron-api';

import { get } from 'http';
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you're running in development or production).
Expand Down Expand Up @@ -93,6 +95,8 @@ app.whenReady().then(() => {
ipcMain.handle(CHANNELS.GET_DEMO_CREDENTIALS, getBrowserStackDemoCredentials);
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_SESSION, (_, id) => getAutomateSessionDetails(id))
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_TEXT_LOGS, (_, session) => getParsedAutomateTextLogs(session))
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_SESSION_LOGS, (_, session) => getAutomateParsedSessionLogs(session))
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS, (_, session) => getAutomateParsedSeleniumLogs(session))
ipcMain.handle(CHANNELS.BROWSERSTACK_START_SESSION, (_, options) => startBrowserStackSession(options))
ipcMain.handle(CHANNELS.BROWSERSTACK_STOP_SESSION, (_, options) => stopBrowserStackSession(options))
ipcMain.handle(CHANNELS.BROWSERSTACK_EXECUTE_SESSION_COMMAND, (_, options) => executeCommand(options))
Expand Down
4 changes: 3 additions & 1 deletion src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ const browserstackAPI: BrowserStackAPI = {
getAutomateParsedTextLogs: (session) => ipcRenderer.invoke(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_TEXT_LOGS, session),
startSession: (options) => ipcRenderer.invoke(CHANNELS.BROWSERSTACK_START_SESSION, options),
stopSession: (options) => ipcRenderer.invoke(CHANNELS.BROWSERSTACK_STOP_SESSION, options),
executeCommand: (options) => ipcRenderer.invoke(CHANNELS.BROWSERSTACK_EXECUTE_SESSION_COMMAND, options)
executeCommand: (options) => ipcRenderer.invoke(CHANNELS.BROWSERSTACK_EXECUTE_SESSION_COMMAND, options),
getAutomateParsedSessionLogs: (session)=>ipcRenderer.invoke(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_SESSION_LOGS,session),
getAutomateParsedSeleniumLogs: (session)=>ipcRenderer.invoke(CHANNELS.GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS,session),
}

const electronAPI: ElectronAPI = {
Expand Down
98 changes: 52 additions & 46 deletions src/renderer/components/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,57 @@
import { NavLink, useLocation } from "react-router-dom"
import Products from "../products"
import { NavLink, useLocation } from "react-router-dom";
import Products from "../products";

const TopMenu = [
{
title: "Configurations",
path: '/'
},
]
{
title: "Configurations",
path: "/",
},
];
const ProductsMenu = [
{
title: "Automate",
path: '/automate'
},
{
title: "App Automate",
path: '/app-automate'
},
{
title: "Percy",
path: '/percy'
},
{
title: "Accessibility",
path: '/accessibility'
}
]
{
title: "Automate",
path: "/automate",
},
{
title: "App Automate",
path: "/app-automate",
},
{
title: "Percy",
path: "/percy",
},
{
title: "Accessibility",
path: "/accessibility",
},
];

export default function Sidebar() {
const location = useLocation()
console.log(location)
return (
<div>
<ul className="menu bg-base-200 w-full bg-transparent p-4 gap-4">
{TopMenu.map((item) => {
const isActive = location.pathname == item.path
return (
<li className={`${isActive?'menu-active':''}`} key={item.path}><NavLink to={item.path} >{item.title}</NavLink></li>
)
})}
<label className="font-bold" htmlFor="">Products</label>
{Products.map((item) => {
const isActive = location.pathname.includes(item.path)
return (
<li className={`${isActive?'menu-active':''}`} key={item.path}><NavLink to={item.path} >{item.name}</NavLink></li>
)
})}
</ul>
</div>
)
}
const location = useLocation();
console.log(location);
return (
<div className="bg-base-100 h-full flex flex-col">
<ul className="menu bg-base-200 w-full bg-transparent p-4 gap-4">
{TopMenu.map((item) => {
const isActive = location.pathname == item.path;
return (
<li className={`${isActive ? "menu-active" : ""}`} key={item.path}>
<NavLink to={item.path}>{item.title}</NavLink>
</li>
);
})}
<label className="font-bold" htmlFor="">
Products
</label>
{Products.map((item) => {
const isActive = location.pathname.includes(item.path);
return (
<li className={`${isActive ? "menu-active" : ""}`} key={item.path}>
<NavLink to={item.path}>{item.name}</NavLink>
</li>
);
})}
</ul>
</div>
);
}
44 changes: 23 additions & 21 deletions src/renderer/products.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import AutomatePage from "./routes/automate";
import ReplayTool from "./routes/automate/tools/replay-tool";
import LatencyFinder from "./routes/automate/tools/latency-finder";

const Products = [
{
name:"Automate",
path:'/automate',
page: AutomatePage,
tools:[
{
title:"Replay Toolkit",
description:"Replays the sessions on BrowserStack by parsing Raw Logs",
path:'/automate/replay-toolkit',
component: ReplayTool
},
{
title:"Latency Analyser",
description:"Analyses time spend on different actions. Helpful to identify inside/outside time for a customer session.",
path:'/automate/latency-analyser',
component: null
}
]
}
]
{
name: "Automate",
path: "/automate",
page: AutomatePage,
tools: [
{
title: "Replay Toolkit",
description: "Replays the sessions on BrowserStack by parsing Raw Logs",
path: "/automate/replay-toolkit",
component: ReplayTool,
},
{
title: "Latency Analyser",
description:
"Analyses time spend on different actions. Helpful to identify inside/outside time for a customer session.",
path: "/automate/latency-analyser",
component: LatencyFinder,
},
],
},
];

export default Products;
export default Products;
Loading