Skip to content

Commit 7d80f7a

Browse files
committed
Add AppAutomate Dir
1 parent c333f5d commit 7d80f7a

File tree

4 files changed

+198
-2
lines changed

4 files changed

+198
-2
lines changed

src/global.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ declare global {
4545
title: string
4646
description: string,
4747
path: string
48-
component: React.ReactNode | null
48+
component: ()=>React.ReactElement | null
4949
}[]
5050
}
5151

src/renderer/products.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import AutomatePage from "./routes/automate";
22
import ReplayTool from "./routes/automate/tools/replay-tool";
33
import LatencyFinder from "./routes/automate/tools/latency-finder";
44
import SessionComparison from "./routes/automate/tools/session-comparison";
5+
import AppAutomatePage from "./routes/app-automate";
56

67
const Products = [
78
{
@@ -33,7 +34,7 @@ const Products = [
3334
{
3435
name: "App Automate",
3536
path: "/app-automate",
36-
page: AutomatePage,
37+
page: AppAutomatePage,
3738
tools: [
3839
{
3940
title: "Replay Toolkit",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { NavLink } from "react-router-dom"
2+
3+
export default function AppAutomatePage(props: ProductPageProps) {
4+
const { tools } = props
5+
6+
return (
7+
<div className="p-5">
8+
<div className="grid grid-col-3 lg:grid-cols-4 gap-4">
9+
{tools.map((tool) => {
10+
const isComingSoon = tool.component === null
11+
12+
const Card = (
13+
<div className="card bg-base-100 w-full h-full shadow-sm border">
14+
<div className="card-body">
15+
<h2 className="card-title flex items-center gap-2">
16+
{tool.title}
17+
</h2>
18+
<p>{tool.description}</p>
19+
<div>
20+
{isComingSoon && (
21+
<span className="badge badge-warning badge-sm">
22+
Coming soon
23+
</span>
24+
)}
25+
</div>
26+
</div>
27+
</div>
28+
)
29+
30+
return isComingSoon ? (
31+
<div key={tool.path} className="cursor-not-allowed opacity-60">
32+
{Card}
33+
</div>
34+
) : (
35+
<NavLink key={tool.path} to={tool.path}>
36+
{Card}
37+
</NavLink>
38+
)
39+
})}
40+
</div>
41+
</div>
42+
)
43+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import Form from "rc-field-form";
2+
import { usePromise } from "../../../hooks/use-promise";
3+
import { toast } from "react-toastify";
4+
import Editor from 'react-simple-code-editor';
5+
import { highlight } from 'sugar-high'
6+
import { useState } from "react";
7+
import SessionPlayer from "../../../components/replay-tool/session-player";
8+
import RequestCard from "../../../components/replay-tool/request-card";
9+
const { Field } = Form
10+
11+
function Info({ label, value }: { label: string; value: string }) {
12+
return (
13+
<div className="flex flex-col bg-base-200 rounded-lg p-3">
14+
<span className="text-xs text-base-content/70 uppercase tracking-wide">{label}</span>
15+
<span title={value} className="font-medium text-base-content truncate text-ellipsis">{value}</span>
16+
</div>
17+
);
18+
}
19+
20+
function RemoveUnwantedCaps(capabilities: any) {
21+
console.log(capabilities)
22+
const keysToRemove = [
23+
"W3C_capabilities",
24+
"new_bucketing",
25+
"detected_language",
26+
"bstack:options.testhubBuildUuid",
27+
"bstack:options.buildProductMap",
28+
"bstack:options.accessibilityOptions",
29+
"bstack:options.accessibility",
30+
"bstack:options.browserstackSDK",
31+
"bstack:options.hostName"
32+
]
33+
34+
const cleanedCaps = typeof capabilities == 'string' ? JSON.parse(capabilities) : { ...capabilities };
35+
36+
for (const key of keysToRemove) {
37+
// Handle dot notation like "bstack:options.testhubBuildUuid"
38+
if (key.includes(".")) {
39+
const parts = key.split(".");
40+
const parentKey = parts[0];
41+
const childKey = parts[1];
42+
43+
if (
44+
cleanedCaps[parentKey] &&
45+
typeof cleanedCaps[parentKey] === "object"
46+
) {
47+
delete cleanedCaps[parentKey][childKey];
48+
}
49+
} else {
50+
// Remove top-level key
51+
delete cleanedCaps[key];
52+
}
53+
}
54+
55+
return JSON.stringify(cleanedCaps, undefined, 2);
56+
}
57+
58+
export default function ReplayTool() {
59+
const [fetchSessionDetails, fetchingSession, session] = usePromise(window.browserstackAPI.getAutomateSessionDetails);
60+
const [parseTextLogs, parsingTextLogs, textLogsResult] = usePromise(window.browserstackAPI.getAutomateParsedTextLogs)
61+
const [capabilities, SetCapabilities] = useState<string>('')
62+
const [isExecuting, SetIsExecuting] = useState(false)
63+
const [hubURL, SetHubURL] = useState<string>(null)
64+
const OpenSession = (input: any) => {
65+
toast.promise(fetchSessionDetails(input.sessionId).then((res) => parseTextLogs(res)), {
66+
pending: "Opening Session...",
67+
success: "Session Loaded",
68+
error: "Failed to Load session probably invalid session ID. Please check console for errors"
69+
}).then((res) => {
70+
SetCapabilities(RemoveUnwantedCaps(res.capabilities[0]))
71+
}).catch((err) => {
72+
console.error(err)
73+
})
74+
75+
}
76+
77+
78+
return (
79+
<div className="p-5 space-y-4">
80+
<h1 className="text-2xl font-bold mb-4" >Replay Toolkit</h1>
81+
<Form className="flex gap-4" onFinish={OpenSession} >
82+
<Field name="sessionId" >
83+
<input className="input placeholder-gray-300" placeholder="Session ID" />
84+
</Field>
85+
<button disabled={fetchingSession || isExecuting} type="submit" className="btn btn-neutral" >Open</button>
86+
</Form>
87+
{textLogsResult && session && !isExecuting && <>
88+
<div className="grid lg:grid-cols-2">
89+
<div className="card bg-base-100 p-6">
90+
<h2 className="card-title text-lg font-semibold mb-4">
91+
{session.automation_session.name || "Unnamed Session"}
92+
</h2>
93+
94+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm">
95+
<Info label="Project" value={session.automation_session.project_name} />
96+
<Info label="Build" value={session.automation_session.build_name} />
97+
<Info label="Status" value={session.automation_session.status} />
98+
<Info label="OS" value={`${session.automation_session.os} ${session.automation_session.os_version}`} />
99+
<Info label="Browser" value={`${session.automation_session.browser} ${session.automation_session.browser_version}`} />
100+
<Info label="Device" value={session.automation_session.device || "N/A"} />
101+
<Info label="Duration" value={`${session.automation_session.duration}s`} />
102+
<Info label="Created At" value={new Date(session.automation_session.created_at).toLocaleString()} />
103+
</div>
104+
</div>
105+
{textLogsResult && <div className="card bg-base-100 p-6">
106+
<h2 className="card-title text-lg font-semibold mb-4">
107+
Capabilities
108+
</h2>
109+
<div className="w-full h-full bg-gray-50 border">
110+
<Editor
111+
highlight={(code) => highlight(code)}
112+
onValueChange={(caps) => SetCapabilities(caps)}
113+
value={capabilities}
114+
disabled={isExecuting}
115+
padding={10}
116+
style={{
117+
fontFamily: '"Fira code", "Fira Mono", monospace',
118+
fontSize: 12,
119+
}}
120+
/>
121+
</div>
122+
</div>}
123+
124+
</div>
125+
{textLogsResult && <details className="collapse collapse-arrow bg-base-100 border border-base-300" name="my-accordion-det-1" open={false}>
126+
<summary className="collapse-title font-semibold">Commands</summary>
127+
<div className="collapse-content">
128+
{textLogsResult.requests.map((request) => (
129+
<RequestCard request={request} />
130+
))}
131+
</div>
132+
</details>}
133+
134+
{textLogsResult && <div className="flex flex-col w-full gap-4">
135+
{textLogsResult && !isExecuting && <div className="flex flex-col gap-2">
136+
<label>Hub URL (Optional)</label>
137+
<select value={hubURL} onChange={(e) => SetHubURL(e.target.value)} className="select placeholder-gray-300 w-full" defaultValue="Default">
138+
<option disabled={true}>Default</option>
139+
</select>
140+
</div>}
141+
</div>}
142+
</>}
143+
{textLogsResult && <SessionPlayer
144+
loading={parsingTextLogs || fetchingSession}
145+
parsedTextLogs={textLogsResult}
146+
sessionDetails={session}
147+
overridenCaps={capabilities}
148+
onExecutionStateChange={SetIsExecuting}
149+
/>}
150+
</div>
151+
)
152+
}

0 commit comments

Comments
 (0)