From 8b1850edbd0804dac844966ed66d4508e4433a02 Mon Sep 17 00:00:00 2001 From: Leland Takamine Date: Wed, 15 Jan 2025 15:50:07 -0800 Subject: [PATCH 1/7] Select web when platform is specified --- .../src/main/java/maestro/cli/session/MaestroSessionManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt b/maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt index 0a68cf3948..932305fa1f 100644 --- a/maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt +++ b/maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt @@ -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 ) From b712471a6f481403ea273b3c9d380a8c2d7bee12 Mon Sep 17 00:00:00 2001 From: Leland Takamine Date: Wed, 15 Jan 2025 15:53:21 -0800 Subject: [PATCH 2/7] Do not log errors on device screen endpoint since this floods the terminal with unnecessary noise when killing studio --- .../server/src/main/java/maestro/studio/DeviceService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt b/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt index 298958d9e4..9b5d4d39ac 100644 --- a/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt +++ b/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt @@ -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 } } } From a99048b7ff88fd2c20f4a01bb1b53dc71ec7cb5d Mon Sep 17 00:00:00 2001 From: Leland Takamine Date: Wed, 15 Jan 2025 16:10:45 -0800 Subject: [PATCH 3/7] Add url input in studio for web --- .../main/java/maestro/drivers/WebDriver.kt | 7 ++- .../main/java/maestro/studio/DeviceService.kt | 3 +- .../src/main/java/maestro/studio/Models.kt | 3 + .../BrowserActionBar.tsx | 60 +++++++++++++++++++ .../InteractableDevice.tsx | 2 +- .../interact/InteractPageLayout.tsx | 21 ++++++- maestro-studio/web/src/helpers/models.ts | 2 + .../storybook/BrowserActionBar.stories.tsx | 41 +++++++++++++ 8 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx create mode 100644 maestro-studio/web/src/storybook/BrowserActionBar.stories.tsx diff --git a/maestro-client/src/main/java/maestro/drivers/WebDriver.kt b/maestro-client/src/main/java/maestro/drivers/WebDriver.kt index cecc2be782..8ef7f2c843 100644 --- a/maestro-client/src/main/java/maestro/drivers/WebDriver.kt +++ b/maestro-client/src/main/java/maestro/drivers/WebDriver.kt @@ -221,8 +221,11 @@ class WebDriver( return TreeNode(attributes = attributes, children = children.map { parse(it) }) } - - return parse(contentDesc as Map) + val root = parse(contentDesc as Map) + seleniumDriver?.currentUrl?.let { url -> + root.attributes["url"] = url + } + return root } private fun detectWindowChange() { diff --git a/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt b/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt index 9b5d4d39ac..00686ee1c5 100644 --- a/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt +++ b/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt @@ -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) diff --git a/maestro-studio/server/src/main/java/maestro/studio/Models.kt b/maestro-studio/server/src/main/java/maestro/studio/Models.kt index 9f744c62ff..f2d61f6e83 100644 --- a/maestro-studio/server/src/main/java/maestro/studio/Models.kt +++ b/maestro-studio/server/src/main/java/maestro/studio/Models.kt @@ -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, + val url: String?, ) data class UIElementBounds( diff --git a/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx b/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx new file mode 100644 index 0000000000..eaff563768 --- /dev/null +++ b/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx @@ -0,0 +1,60 @@ +import React, {useLayoutEffect} from "react"; +import {twMerge} from "tailwind-merge"; +import clsx from "clsx"; +import {Input} from "../design-system/input"; + +const GlobeIcon = ({ className }: { className?: string }) => ( + + + +); + +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 ( +
+
+ +
+ setEditedUrl(e.target.value)} + onFocus={() => setIsEditing(true)} + onBlur={() => setIsEditing(false)} + onKeyDown={(e) => { + // on enter update url + if (e.key === 'Enter' && isEditing && editedUrl) { + onUrlUpdated(editedUrl); + e.currentTarget.blur(); + setIsEditing(false); + } + }} + /> +
+ ) +} + +export default BrowserActionBar \ No newline at end of file diff --git a/maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx b/maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx index 264cdd30bd..c966f6bb76 100644 --- a/maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx +++ b/maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx @@ -46,7 +46,7 @@ export default function InteractableDevice({ return ( { const { @@ -24,6 +25,7 @@ const InteractPageLayout = () => { const { runCommandYaml } = useRepl(); const [showElementsPanel, setShowElementsPanel] = useState(false); + const [isUrlLoading, setIsUrlLoading] = useState(false); const onEdit = (example: CommandExample) => { if (example.status === "unavailable") return; @@ -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 (
@@ -76,6 +88,13 @@ const InteractPageLayout = () => { Search Elements with Text or Id )} + {deviceScreen?.platform === 'WEB' && ( + + )} diff --git a/maestro-studio/web/src/helpers/models.ts b/maestro-studio/web/src/helpers/models.ts index 7b84169560..3e6d2b36ec 100644 --- a/maestro-studio/web/src/helpers/models.ts +++ b/maestro-studio/web/src/helpers/models.ts @@ -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 = diff --git a/maestro-studio/web/src/storybook/BrowserActionBar.stories.tsx b/maestro-studio/web/src/storybook/BrowserActionBar.stories.tsx new file mode 100644 index 0000000000..d1441b9d2f --- /dev/null +++ b/maestro-studio/web/src/storybook/BrowserActionBar.stories.tsx @@ -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("https://google.com"); + const [isLoading, setIsLoading] = useState(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 ( +
+ +
+ ); +}; From 0d3ee10c3f600681bfbcbf4099cf2572df197749 Mon Sep 17 00:00:00 2001 From: Leland Takamine Date: Wed, 15 Jan 2025 18:31:08 -0800 Subject: [PATCH 4/7] Handle openLink without scheme --- maestro-client/src/main/java/maestro/drivers/WebDriver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro-client/src/main/java/maestro/drivers/WebDriver.kt b/maestro-client/src/main/java/maestro/drivers/WebDriver.kt index 8ef7f2c843..c5f37d77bd 100644 --- a/maestro-client/src/main/java/maestro/drivers/WebDriver.kt +++ b/maestro-client/src/main/java/maestro/drivers/WebDriver.kt @@ -370,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() { From e0e5271c78079a34d0e0f145b70e6c1dd512dcfc Mon Sep 17 00:00:00 2001 From: Leland Takamine Date: Wed, 15 Jan 2025 18:44:05 -0800 Subject: [PATCH 5/7] Remove comment --- .../components/device-and-device-elements/BrowserActionBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx b/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx index eaff563768..38a28ffcc9 100644 --- a/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx +++ b/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx @@ -45,7 +45,6 @@ const BrowserActionBar = ({currentUrl, onUrlUpdated, isLoading}: { onFocus={() => setIsEditing(true)} onBlur={() => setIsEditing(false)} onKeyDown={(e) => { - // on enter update url if (e.key === 'Enter' && isEditing && editedUrl) { onUrlUpdated(editedUrl); e.currentTarget.blur(); From a275abbb9ea883aed5ba5598d987a64a081d20a3 Mon Sep 17 00:00:00 2001 From: Leland Takamine Date: Wed, 15 Jan 2025 18:44:30 -0800 Subject: [PATCH 6/7] Revert unnecessary change --- .../device-and-device-elements/InteractableDevice.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx b/maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx index c966f6bb76..264cdd30bd 100644 --- a/maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx +++ b/maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx @@ -46,7 +46,7 @@ export default function InteractableDevice({ return ( Date: Wed, 15 Jan 2025 18:49:40 -0800 Subject: [PATCH 7/7] Remove unused import --- .../components/device-and-device-elements/BrowserActionBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx b/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx index 38a28ffcc9..753af2cab8 100644 --- a/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx +++ b/maestro-studio/web/src/components/device-and-device-elements/BrowserActionBar.tsx @@ -1,6 +1,5 @@ import React, {useLayoutEffect} from "react"; import {twMerge} from "tailwind-merge"; -import clsx from "clsx"; import {Input} from "../design-system/input"; const GlobeIcon = ({ className }: { className?: string }) => (