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
580 changes: 555 additions & 25 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
"@electron-forge/plugin-fuses": "^7.10.2",
"@electron-forge/plugin-webpack": "^7.10.2",
"@electron/fuses": "^1.8.0",
"@types/diff": "^7.0.2",
"@types/pouchdb": "^6.4.2",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@types/react-virtualized-auto-sizer": "^1.0.4",
"@types/react-window": "^1.8.8",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vercel/webpack-asset-relocator-loader": "^1.7.3",
Expand All @@ -49,17 +52,24 @@
"typescript": "~4.5.4"
},
"dependencies": {
"@monaco-editor/react": "^4.7.0",
"@tailwindcss/postcss": "^4.1.15",
"copy-webpack-plugin": "^13.0.1",
"electron-squirrel-startup": "^1.0.1",
"lodash": "^4.17.21",
"monaco-editor": "^0.55.1",
"monaco-types": "^0.1.0",
"pouchdb": "^9.0.0",
"rc-field-form": "^2.7.0",
"react": "^19.2.0",
"react-diff-view": "^3.3.2",
"react-diff-viewer": "^3.1.1",
"react-dom": "^19.2.0",
"react-router-dom": "^7.9.4",
"react-simple-code-editor": "^0.14.1",
"react-toastify": "^11.0.5",
"react-virtualized-auto-sizer": "^1.0.26",
"react-window": "^2.2.3",
"sugar-high": "^0.9.4"
}
}
34 changes: 34 additions & 0 deletions src/channelHandlers/browserstack-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,37 @@ export const getAutomateParsedSeleniumLogs = async (session: AutomateSessionResp

return result;
}

export const getSeleniumLogs = async (selenium_logs_url: string) => {
if (!selenium_logs_url) {
return 'No Selenium logs available for this session';
}
try {
const response = await fetch(selenium_logs_url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
// For other URLs, use the existing download function with auth
return await download(selenium_logs_url);
} catch (error) {
console.error('Failed to fetch Selenium logs:', error);
return 'Failed to load Selenium logs';
}
}

export const getHarLogs = async (harLogsUrl: string) => {
if (!harLogsUrl) {
return 'No network logs available for this session';
}
try {
const response = await fetch(harLogsUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
} catch (error) {
console.error('Failed to fetch HAR logs:', error);
return 'Failed to load network logs';
}
}
4 changes: 3 additions & 1 deletion src/constants/ipc-channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const CHANNELS = {
BROWSERSTACK_EXECUTE_SESSION_COMMAND:'BROWSERSTACK_EXECUTE_SESSION_COMMAND',
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'
GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS:'GET_BROWSERSTACK_AUTOMATE_PARSED_SELENIUM_LOGS',
GET_BROWSERSTACK_AUTOMATE_SELENIUM_LOGS: 'GET /automate/sessions/seleniumLogs',
GET_BROWSERSTACK_AUTOMATE_HAR_LOGS: 'GET /automate/sessions/harLogs'
}

export default CHANNELS
7 changes: 6 additions & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ declare global {
stopSession: (options: StopSessionOptions) => Promise<StopSessionResponse>
executeCommand: (options: ExecuteCommandOptions) => any
getAutomateParsedSessionLogs: (session: AutomateSessionResponse) => Promise<ScanResult>
getAutomateParsedSeleniumLogs: (session: AutomateSessionResponse) => Promise<SeleniumScanResult>
getAutomateParsedSeleniumLogs: (session: AutomateSessionResponse) => Promise<SeleniumScanResult>,
getAutomateParsedTextLogs: (session:AutomateSessionResponse) => Promise<ParsedTextLogsResult>
getSeleniumLogs: (selenium_logs_url: string) => Promise<string>
getHarLogs: (harLogsUrl: string) => Promise<string>
}

type ElectronAPI = {
openExternalUrl: (url: string) => Promise<void>

}

interface DBItem {
Expand All @@ -41,6 +45,7 @@ declare global {
title: string
description: string,
path: string
component: React.ReactNode | null
}[]
}

Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import StorageKeys from './constants/storage-keys';
import CONFIG from './constants/config';

import { mkdirSync } from 'fs'
import { executeCommand, getAutomateSessionDetails, getParsedAutomateTextLogs, startBrowserStackSession, stopBrowserStackSession, getAutomateParsedSeleniumLogs, getAutomateParsedSessionLogs, } from './channelHandlers/browserstack-api';
import { executeCommand, getAutomateSessionDetails, getParsedAutomateTextLogs, startBrowserStackSession, stopBrowserStackSession, getAutomateParsedSeleniumLogs, getAutomateParsedSessionLogs,getSeleniumLogs, getHarLogs } 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 @@ -101,6 +101,8 @@ app.whenReady().then(() => {
ipcMain.handle(CHANNELS.BROWSERSTACK_STOP_SESSION, (_, options) => stopBrowserStackSession(options))
ipcMain.handle(CHANNELS.BROWSERSTACK_EXECUTE_SESSION_COMMAND, (_, options) => executeCommand(options))
ipcMain.handle(CHANNELS.ELECTRON_OPEN_URL, (_, url) => openExternalUrl(url))
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_SELENIUM_LOGS,(_, selenium_logs_url) => getSeleniumLogs(selenium_logs_url));
ipcMain.handle(CHANNELS.GET_BROWSERSTACK_AUTOMATE_HAR_LOGS, (_, har_logs_url) => getHarLogs(har_logs_url));
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
2 changes: 2 additions & 0 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const browserstackAPI: BrowserStackAPI = {
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),
getSeleniumLogs: (selenium_logs_url) => ipcRenderer.invoke(CHANNELS.GET_BROWSERSTACK_AUTOMATE_SELENIUM_LOGS, selenium_logs_url),
getHarLogs: (har_logs_url) => ipcRenderer.invoke(CHANNELS.GET_BROWSERSTACK_AUTOMATE_HAR_LOGS, har_logs_url)
}

const electronAPI: ElectronAPI = {
Expand Down
18 changes: 0 additions & 18 deletions src/renderer/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,6 @@ const TopMenu = [
path: "/",
},
];
const ProductsMenu = [
{
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();
Expand Down
39 changes: 38 additions & 1 deletion src/renderer/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,41 @@
--sh-string: #00a99a;
--sh-keyword: #f47067;
--sh-comment: #a19595;
}
}

.diff-container .diff-viewer-code-column {
max-width: 600px;
overflow-wrap: anywhere;
}

.diff-container .rdv-code-wrapper {
max-width: 25vw;
white-space: pre-wrap !important;
word-break: break-word !important;
}

.diff-container .rdv-code {
white-space: pre-wrap !important;
word-break: break-word !important;
}

/* Force wrapping in ReactDiffViewer */
.react-diff-viewer,
.react-diff-viewer * {
white-space: pre-wrap !important;
word-break: break-word !important;
}

/* Limit each side to half of the container */
.react-diff-viewer .diff-viewer-container,
.react-diff-viewer .diff-viewer-split {
max-width: 48vw !important;
}

/* Override left + right code column width */
.react-diff-viewer .diff-code,
.react-diff-viewer .diff-line {
max-width: 48vw !important;
width: 48vw !important;
display: block !important;
}
78 changes: 78 additions & 0 deletions src/renderer/products.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AutomatePage from "./routes/automate";
import ReplayTool from "./routes/automate/tools/replay-tool";
import LatencyFinder from "./routes/automate/tools/latency-finder";
import SessionComparison from "./routes/automate/tools/session-comparison";

const Products = [
{
Expand All @@ -21,6 +22,83 @@ const Products = [
path: "/automate/latency-analyser",
component: LatencyFinder,
},
{
title: "Session Comparison",
description: "Compares logs across sessions and highlights differences",
path: '/automate/session-comparison',
component: SessionComparison
}
],
},
{
name: "App Automate",
path: "/app-automate",
page: AutomatePage,
tools: [
{
title: "Replay Toolkit",
description: "Replays the sessions on BrowserStack by parsing Raw Logs",
path: "/automate/replay-toolkit",
component: null,
},
{
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,
},
{
title: "Session Comparison",
description: "Compares logs across sessions and highlights differences",
path: '/automate/session-comparison',
component: null
}
],
},
{
name: "Percy",
path: "/percy",
page: AutomatePage,
tools: [
{
title: "Snapshot Replay",
description: "Replay snapshots",
path: "/percy/snapshot-replay",
component: null,
},
{
title: "CLI Logs Downloader",
description: "Download CLI logs using hash ID displayed in Customer's console",
path: "/percy/cli-log-downloader",
component: null,
},
],
},
{
name: "Test Report & Analytics",
path: "/tra",
page: AutomatePage,
tools: [
{
title: "SDK Logs Downloader",
description: "Download SDK logs from Backend",
path: "/tra/download-logs",
component: null,
},
],
},
{
name: "Web Accessibility",
path: "/web-accessibility",
page: AutomatePage,
tools: [
{
title: "Automate Session Finder",
description: "Find associated automate session for accessibility scanner run",
path: "/web-a11y/session-finder",
component: null,
},
],
},
];
Expand Down
34 changes: 27 additions & 7 deletions src/renderer/routes/automate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,42 @@ import { NavLink } from "react-router-dom"

export default function AutomatePage(props: ProductPageProps) {
const { tools } = props

return (
<div className="p-5">
<div className="grid grid-col-3 lg:grid-cols-4 gap-4">
{tools.map((tool) => {
return (
<NavLink key={tool.path} to={tool.path} >
<div className="card bg-base-100 w-full h-full shadow-sm border">
<div className="card-body">
<h2 className="card-title">{tool.title}</h2>
<p>{tool.description}</p>
const isComingSoon = tool.component === null

const Card = (
<div className="card bg-base-100 w-full h-full shadow-sm border">
<div className="card-body">
<h2 className="card-title flex items-center gap-2">
{tool.title}
</h2>
<p>{tool.description}</p>
<div>
{isComingSoon && (
<span className="badge badge-warning badge-sm">
Coming soon
</span>
)}
</div>
</div>
</div>
)

return isComingSoon ? (
<div key={tool.path} className="cursor-not-allowed opacity-60">
{Card}
</div>
) : (
<NavLink key={tool.path} to={tool.path}>
{Card}
</NavLink>
)
})}
</div>
</div>
)
}
}
1 change: 1 addition & 0 deletions src/renderer/routes/automate/tools/session-comparison.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './session-comparison/index';
Loading