Skip to content

Commit

Permalink
Web fixes (#2250)
Browse files Browse the repository at this point in the history
* Select web when platform is specified

* Do not log errors on device screen endpoint since this floods the terminal with unnecessary noise when killing studio

* Add url input in studio for web

* Handle openLink without scheme

* Remove comment

* Revert unnecessary change

* Remove unused import
  • Loading branch information
Leland-Takamine authored Jan 16, 2025
1 parent 10d2aed commit 14f69eb
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ object MaestroSessionManager {
deviceId: String?,
platform: Platform? = null,
): SelectedDevice {
if (deviceId == "chromium") {
if (deviceId == "chromium" || platform == Platform.WEB) {
return SelectedDevice(
platform = Platform.WEB
)
Expand Down
9 changes: 6 additions & 3 deletions maestro-client/src/main/java/maestro/drivers/WebDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,11 @@ class WebDriver(
return TreeNode(attributes = attributes, children = children.map { parse(it) })
}


return parse(contentDesc as Map<String, Any>)
val root = parse(contentDesc as Map<String, Any>)
seleniumDriver?.currentUrl?.let { url ->
root.attributes["url"] = url
}
return root
}

private fun detectWindowChange() {
Expand Down Expand Up @@ -367,7 +370,7 @@ class WebDriver(
override fun openLink(link: String, appId: String?, autoVerify: Boolean, browser: Boolean) {
val driver = ensureOpen()

driver.get(link)
driver.get(if (link.startsWith("http")) link else "https://$link")
}

override fun hideKeyboard() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ object DeviceService {
val deviceScreen = getDeviceScreen(maestro)
writeStringUtf8("data: $deviceScreen\n\n")
flush()
} catch (e: Exception) {
} catch (_: Exception) {
// Ignoring the exception to prevent SSE stream from dying
e.printStackTrace()
// Don't log since this floods the terminal after killing studio
}
}
}
Expand Down Expand Up @@ -187,8 +187,9 @@ object DeviceService {
val deviceWidth = deviceInfo.widthGrid
val deviceHeight = deviceInfo.heightGrid

val url = tree.attributes["url"]
val elements = treeToElements(tree)
val deviceScreen = DeviceScreen("/screenshot/${screenshotFile.name}", deviceWidth, deviceHeight, elements)
val deviceScreen = DeviceScreen(deviceInfo.platform, "/screenshot/${screenshotFile.name}", deviceWidth, deviceHeight, elements, url)
return jacksonObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.writeValueAsString(deviceScreen)
Expand Down
3 changes: 3 additions & 0 deletions maestro-studio/server/src/main/java/maestro/studio/Models.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package maestro.studio

import com.fasterxml.jackson.annotation.JsonProperty
import maestro.Platform
import java.util.UUID

data class DeviceScreen(
val platform: Platform,
val screenshot: String,
val width: Int,
val height: Int,
val elements: List<UIElement>,
val url: String?,
)

data class UIElementBounds(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, {useLayoutEffect} from "react";
import {twMerge} from "tailwind-merge";
import {Input} from "../design-system/input";

const GlobeIcon = ({ className }: { className?: string }) => (
<svg
className={className}
width="24"
height="24"
viewBox="0 0 256 256"
>
<path
fill="currentColor"
d="M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm88,104a87.61,87.61,0,0,1-3.33,24H174.16a157.44,157.44,0,0,0,0-48h38.51A87.61,87.61,0,0,1,216,128ZM102,168H154a115.11,115.11,0,0,1-26,45A115.27,115.27,0,0,1,102,168Zm-3.9-16a140.84,140.84,0,0,1,0-48h59.88a140.84,140.84,0,0,1,0,48ZM40,128a87.61,87.61,0,0,1,3.33-24H81.84a157.44,157.44,0,0,0,0,48H43.33A87.61,87.61,0,0,1,40,128ZM154,88H102a115.11,115.11,0,0,1,26-45A115.27,115.27,0,0,1,154,88Zm52.33,0H170.71a135.28,135.28,0,0,0-22.3-45.6A88.29,88.29,0,0,1,206.37,88ZM107.59,42.4A135.28,135.28,0,0,0,85.29,88H49.63A88.29,88.29,0,0,1,107.59,42.4ZM49.63,168H85.29a135.28,135.28,0,0,0,22.3,45.6A88.29,88.29,0,0,1,49.63,168Zm98.78,45.6a135.28,135.28,0,0,0,22.3-45.6h35.66A88.29,88.29,0,0,1,148.41,213.6Z"></path>
</svg>
);

const BrowserActionBar = ({currentUrl, onUrlUpdated, isLoading}: {
currentUrl?: string,
onUrlUpdated: (url: string) => void,
isLoading: boolean
}) => {
const [isEditing, setIsEditing] = React.useState(false)
const [editedUrl, setEditedUrl] = React.useState(currentUrl)
useLayoutEffect(() => {
if (!isEditing && !isLoading) {
setEditedUrl(currentUrl)
}
}, [isLoading, isEditing, currentUrl]);
return (
<div className="w-full relative">
<div className="inset-y-0 absolute flex items-center px-1.5">
<GlobeIcon className="text-gray-300" />
</div>
<Input
className={twMerge(
"w-full pl-8 pr-1 py-0.5 rounded-full border-2 bg-slate-50",
isLoading && "bg-gray-100",
)}
size="sm"
disabled={isLoading}
value={(isEditing || isLoading) ? editedUrl : currentUrl}
onChange={(e) => setEditedUrl(e.target.value)}
onFocus={() => setIsEditing(true)}
onBlur={() => setIsEditing(false)}
onKeyDown={(e) => {
if (e.key === 'Enter' && isEditing && editedUrl) {
onUrlUpdated(editedUrl);
e.currentTarget.blur();
setIsEditing(false);
}
}}
/>
</div>
)
}

export default BrowserActionBar
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import React, { useState } from "react";
import clsx from "clsx";

import InteractableDevice from "../device-and-device-elements/InteractableDevice";
Expand All @@ -12,6 +12,7 @@ import { useDeviceContext } from "../../context/DeviceContext";
import { Spinner } from "../design-system/spinner";
import { useRepl } from '../../context/ReplContext';
import { DeviceScreen } from "../../helpers/models";
import BrowserActionBar from "../device-and-device-elements/BrowserActionBar";

const InteractPageLayout = () => {
const {
Expand All @@ -24,6 +25,7 @@ const InteractPageLayout = () => {
const { runCommandYaml } = useRepl();

const [showElementsPanel, setShowElementsPanel] = useState<boolean>(false);
const [isUrlLoading, setIsUrlLoading] = useState<boolean>(false);

const onEdit = (example: CommandExample) => {
if (example.status === "unavailable") return;
Expand All @@ -44,6 +46,16 @@ const InteractPageLayout = () => {
await runCommandYaml(example.content);
};

const onUrlUpdated = (url: string) => {
setIsUrlLoading(true);
runCommandYaml(`openLink: ${url}`).finally(() => {
// Wait some time to update the url from the device screen
setTimeout(() => {
setIsUrlLoading(false);
}, 1000);
});
}

if (isLoading)
return (
<div className="flex items-center justify-center h-full">
Expand Down Expand Up @@ -76,6 +88,13 @@ const InteractPageLayout = () => {
Search Elements with Text or Id
</Button>
)}
{deviceScreen?.platform === 'WEB' && (
<BrowserActionBar
currentUrl={deviceScreen.url}
onUrlUpdated={onUrlUpdated}
isLoading={isUrlLoading}
/>
)}
<DeviceWrapperAspectRatio>
<InteractableDevice />
</DeviceWrapperAspectRatio>
Expand Down
2 changes: 2 additions & 0 deletions maestro-studio/web/src/helpers/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ export type UIElement = {
};

export type DeviceScreen = {
platform: string;
screenshot: string;
width: number;
height: number;
elements: UIElement[];
url?: string;
};

export type ReplCommandStatus =
Expand Down
41 changes: 41 additions & 0 deletions maestro-studio/web/src/storybook/BrowserActionBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import BrowserActionBar from "../components/device-and-device-elements/BrowserActionBar";
import {useEffect, useState} from "react";

export default {
title: "BrowserActionBar",
};

export const Main = () => {
const [currentUrl, setCurrentUrl] = useState<string>("https://google.com");
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
const interval = setInterval(() => {
setCurrentUrl(old => {
// if no scheme, add https
if (!old.startsWith("http")) {
old = "https://" + old;
}
const url = new URL(old);
url.searchParams.set("ts", Date.now().toString());
return url.toString();
});
}, 1000);
return () => clearInterval(interval);
}, []);
const onUrlUpdated = (url: string) => {
setIsLoading(true);
setTimeout(() => {
setCurrentUrl(url);
setIsLoading(false);
}, 1000);
}
return (
<div className="flex flex-col w-full">
<BrowserActionBar
currentUrl={currentUrl}
onUrlUpdated={onUrlUpdated}
isLoading={isLoading}
/>
</div>
);
};

0 comments on commit 14f69eb

Please sign in to comment.