diff --git a/packages/selenium-ide/package.json b/packages/selenium-ide/package.json index c34e58b1f..55fbccc9a 100644 --- a/packages/selenium-ide/package.json +++ b/packages/selenium-ide/package.json @@ -1,6 +1,6 @@ { "name": "selenium-ide", - "version": "4.0.1-alpha.74", + "version": "4.0.1-alpha.75", "private": false, "description": "Selenium IDE electron app", "author": "Todd ", @@ -111,7 +111,7 @@ "@seleniumhq/code-export-python-pytest": "^4.0.0-alpha.4", "@seleniumhq/code-export-ruby-rspec": "^4.0.0-alpha.3", "@seleniumhq/get-driver": "^4.0.0-alpha.3", - "@seleniumhq/side-api": "^4.0.0-alpha.42", + "@seleniumhq/side-api": "^4.0.0-alpha.43", "@seleniumhq/side-commons": "^4.0.0-alpha.2", "@seleniumhq/side-model": "^4.0.0-alpha.5", "@seleniumhq/side-runtime": "^4.0.0-alpha.35", diff --git a/packages/selenium-ide/src/browser/index.css b/packages/selenium-ide/src/browser/index.css index 046fd6072..dc9988a78 100644 --- a/packages/selenium-ide/src/browser/index.css +++ b/packages/selenium-ide/src/browser/index.css @@ -110,6 +110,7 @@ body, display: block; text-overflow: ellipsis; overflow-x: hidden; + white-space: nowrap; } /** @@ -174,6 +175,16 @@ body, margin-bottom: auto; } +.m-0 { + margin: 0; +} +.mb-2 { + margin-bottom: 0.5rem; +} +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} .m-2 { margin: 0.5rem; } @@ -208,10 +219,24 @@ body, .pt-4 { padding-top: 1rem; } +.ps-3 { + padding-left: 0.5rem; +} +.ps-4 { + padding-left: 1rem; +} +.px-3 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} .px-4 { padding-left: 1rem; padding-right: 1rem; } +.py-2 { + padding-bottom: 0.25rem; + padding-top: 0.25rem; +} .py-3 { padding-bottom: 0.5rem; padding-top: 0.5rem; @@ -221,6 +246,13 @@ body, padding-top: 1rem; } +/** + * Width + */ +.mw-200 { + max-width: 200px; +} + /** * Text-Align */ diff --git a/packages/selenium-ide/src/browser/windows/Logger/main.tsx b/packages/selenium-ide/src/browser/windows/Logger/main.tsx index 792a7941a..7dbec4e81 100644 --- a/packages/selenium-ide/src/browser/windows/Logger/main.tsx +++ b/packages/selenium-ide/src/browser/windows/Logger/main.tsx @@ -42,7 +42,7 @@ const SIDELogger: React.FC = () => {
diff --git a/packages/selenium-ide/src/browser/windows/PlaybackWindow/controller.ts b/packages/selenium-ide/src/browser/windows/PlaybackWindow/controller.ts
index f95c827e2..981e92148 100644
--- a/packages/selenium-ide/src/browser/windows/PlaybackWindow/controller.ts
+++ b/packages/selenium-ide/src/browser/windows/PlaybackWindow/controller.ts
@@ -6,5 +6,6 @@ export const window: WindowConfig['window'] = () => ({
   resizable: false,
   roundedCorners: false,
   show: false,
+  skipTaskbar: true,
   title: 'Playback Window',
 })
diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/AppBar/index.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/AppBar/index.tsx
index ce1685458..9f15144f6 100644
--- a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/AppBar/index.tsx
+++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/AppBar/index.tsx
@@ -1,5 +1,3 @@
-import IconButton from '@mui/material/IconButton'
-import MenuIcon from '@mui/icons-material/Menu'
 import React from 'react'
 import TabPanel from '../Tab/Panel'
 import SuiteControls from '../../tabs/Suites/Controls'
@@ -9,29 +7,13 @@ import { SIDEMainProps } from '../types'
 import AppBarTabs from './AppBarTabs'
 import { Paper } from '@mui/material'
 
-const SIDEAppBar: React.FC = ({
-  openDrawer,
+const SIDEAppBar: React.FC> = ({
   session,
-  setOpenDrawer,
   setTab,
   tab,
 }) => {
   return (
-    
-       setOpenDrawer(true)}
-        sx={{
-          flex: 0,
-          paddingX: 2,
-          borderRadius: 0.5,
-          ...(openDrawer && { display: 'none' }),
-        }}
-        size="small"
-      >
-        
-      
+    
       
       
diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/Drawer/EditorToolbar.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/Drawer/EditorToolbar.tsx index 926024ae1..44a03fdb4 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/Drawer/EditorToolbar.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/Drawer/EditorToolbar.tsx @@ -14,32 +14,20 @@ export interface EditorToolbarIconsProps { disabled?: boolean onAdd?: () => void onEdit?: () => void + editText?: string onRemove?: () => void onView?: () => void } export const EditorToolbarIcons: FC = ({ disabled = false, + editText = 'Edit', onAdd, onEdit, onRemove, onView, }) => ( <> - {onAdd ? ( - - - - - - - - ) : null} {onRemove ? ( @@ -56,7 +44,7 @@ export const EditorToolbarIcons: FC = ({ ) : null} {onEdit ? ( - + = ({ ) : null} + {onAdd ? ( + + + + + + + + ) : null} {onView ? ( diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/Main/index.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/Main/index.tsx index 9f154b898..7e3a6e217 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/Main/index.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/Main/index.tsx @@ -6,36 +6,29 @@ import SuitesTab from '../../tabs/Suites/SuitesTab' import TestsTab from '../../tabs/Tests/TestsTab' import { SIDEMainProps } from '../types' -const SIDEMain: React.FC< - Pick< - SIDEMainProps, - 'openDrawer' | 'session' | 'setOpenDrawer' | 'setTab' | 'tab' - > -> = ({ openDrawer, session, setOpenDrawer, setTab, tab }) => ( +const SIDEMain: React.FC> = ({ + session, + setTab, + tab, +}) => ( <> diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/PlaybackTabBar/index.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/PlaybackTabBar/index.tsx new file mode 100644 index 000000000..e94e4b9a5 --- /dev/null +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/PlaybackTabBar/index.tsx @@ -0,0 +1,53 @@ +import Paper from '@mui/material/Paper' +import React from 'react' +import PlaybackTab, { TabShape } from './tab' + +const { + windows: { + onPlaybackWindowChanged, + onPlaybackWindowClosed, + onPlaybackWindowOpened, + }, +} = window.sideAPI +console.log(onPlaybackWindowChanged, onPlaybackWindowClosed, onPlaybackWindowOpened) + +const PlaybackTabBar: React.FC = () => { + const [tabs, setTabs] = React.useState([]) + React.useEffect(() => { + onPlaybackWindowChanged.addListener((id, partialTab) => { + setTabs((tabs) => { + const tab = tabs.find((tab) => tab.id === id) + if (!tab) return tabs + return tabs.map((tab) => { + if (tab.id !== id) return tab + return { + ...tab, + ...partialTab, + } + }) + }) + }); + onPlaybackWindowOpened.addListener((id, {title}) => { + setTabs((tabs) => [...tabs, { id, focused: false, title }]) + }); + onPlaybackWindowClosed.addListener((id) => { + setTabs((tabs) => tabs.filter((tab) => tab.id !== id)) + }); + }, []); + return ( + + {tabs.map((tab) => ( + + ))} + + ) +} + +export default PlaybackTabBar diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/PlaybackTabBar/tab.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/PlaybackTabBar/tab.tsx new file mode 100644 index 000000000..52e63c996 --- /dev/null +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/PlaybackTabBar/tab.tsx @@ -0,0 +1,52 @@ +import Clear from '@mui/icons-material/Clear' +import Box from '@mui/material/Box' +import IconButton from '@mui/material/IconButton' +import Paper from '@mui/material/Paper' +import Typography from '@mui/material/Typography' +import React from 'react' + +const { + windows: { closePlaybackWindow, focusPlaybackWindow }, +} = window.sideAPI + +export type TabShape = { + id: number + title: string + focused: boolean +} + +const PlaybackTab: React.FC = ({ focused, id, title }) => { + return ( + { + focusPlaybackWindow(id) + }} + square + sx={{ + marginBottom: focused ? 0 : 0.25, + }} + > + + + {title} + + + + { + e.preventDefault() + e.stopPropagation() + closePlaybackWindow(id) + }} + sx={{ borderRadius: 0.5 }} + > + + + + + ) +} + +export default PlaybackTab diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/URLBar/index.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/URLBar/index.tsx index 72bd53f5e..320be14ca 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/URLBar/index.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/URLBar/index.tsx @@ -1,6 +1,7 @@ -import FormControl from '@mui/material/FormControl' +import Box from '@mui/material/Box' import Paper from '@mui/material/Paper' import TextField from '@mui/material/TextField' +import Typography from '@mui/material/Typography' import React from 'react' import { SIDEMainProps } from '../types' @@ -10,21 +11,23 @@ const { const URLBar: React.FC> = ({ session }) => { return ( - - + + + URL + + { update({ url: e.target.value, }) }} + margin="none" size="small" value={session.project.url} /> - + ) } diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/types.ts b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/types.ts index 09988eee9..8d4a5a2f2 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/components/types.ts +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/components/types.ts @@ -4,8 +4,6 @@ import { TAB } from '../enums/tab' export interface SIDEMainProps { session: CoreSessionData - openDrawer: boolean - setOpenDrawer: React.Dispatch> setTab: React.Dispatch> tab: number } diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/main.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/main.tsx index 09f6010fc..b16671225 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/main.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/main.tsx @@ -1,39 +1,27 @@ import Box from '@mui/material/Box' import { loadingID } from '@seleniumhq/side-api/dist/constants/loadingID' -import subscribeToSession from 'browser/helpers/subscribeToSession' import React from 'react' import { DndProvider } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' import Main from './components/Main' -import URLBar from './components/URLBar' import { TAB, TESTS_TAB } from './enums/tab' +import { SIDEMainProps } from './components/types' -const ProjectEditor = () => { - const session = subscribeToSession() - const { - project: { id }, - } = session - +const ProjectEditor: React.FC> = ({ + session, +}) => { const [tab, setTab] = React.useState(TESTS_TAB) - const [openDrawer, setOpenDrawer] = React.useState(true) - - if (id == loadingID) { + if (session.project.id === loadingID) { return
} - return ( - - -
- +
) diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/renderer.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/renderer.tsx index d39104e17..e69de29bb 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/renderer.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/renderer.tsx @@ -1,4 +0,0 @@ -import renderWhenReady from 'browser/helpers/renderWhenReady' -import ProjectEditor from './main' - -renderWhenReady(ProjectEditor) diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/ProjectDrawer.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/ProjectDrawer.tsx deleted file mode 100644 index 81cd527bf..000000000 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/ProjectDrawer.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import List from '@mui/material/List' -import ListItemButton from '@mui/material/ListItemButton' -import ListItemText from '@mui/material/ListItemText' -import { ConfigSettingsGroup } from '@seleniumhq/side-api' -import React from 'react' -import Drawer from '../../components/Drawer/Wrapper' -import { SIDEMainProps } from '../../components/types' - -type ConfigGroupFactory = ( - group: ConfigSettingsGroup -) => React.FC<{ value: ConfigSettingsGroup }> - -const ConfigGroup: ConfigGroupFactory = - (group) => - ({ value }) => - ( - - window.sideAPI.state.set('editor.configSettingsGroup', group) - } - selected={value === group} - > - - {group.slice(0, 1).toUpperCase().concat(group.slice(1))} - - - ) - -const ProjectConfig = ConfigGroup('project') -const SystemConfig = ConfigGroup('system') - -const ProjectDrawer: React.FC< - Pick -> = ({ - openDrawer, - session: { - state: { - editor: { configSettingsGroup }, - }, - }, - setOpenDrawer, -}) => ( - - - - - - -) - -export default ProjectDrawer diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/ProjectTab.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/ProjectTab.tsx index df58cf2c3..55e884934 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/ProjectTab.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/ProjectTab.tsx @@ -5,7 +5,7 @@ import ProjectSettings from './ProjectSettings' import SystemSettings from './SystemSettings' import { SIDEMainProps } from '../../components/types' import AppBar from '../../components/AppBar' -import ProjectDrawer from './ProjectDrawer' +import SettingsTabs from './SettingTabs' export interface MiniProjectShape { id: string @@ -22,34 +22,19 @@ const SettingsWrapper: FC> = ({ ) const ProjectTab: React.FC< - Pick< - SIDEMainProps, - 'openDrawer' | 'session' | 'setOpenDrawer' | 'setTab' | 'tab' - > -> = ({ openDrawer, session, setOpenDrawer, setTab, tab }) => ( + Pick +> = ({ session, setTab, tab }) => ( - + - - - - - - - - - + + + + + + + ) diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/SettingTabs.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/SettingTabs.tsx new file mode 100644 index 000000000..176071c6c --- /dev/null +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Project/SettingTabs.tsx @@ -0,0 +1,32 @@ +import Tabs from '@mui/material/Tabs' +import Tab from '@mui/material/Tab' +import React from 'react' +import { SIDEMainProps } from '../../components/types' + +function a11yProps(name: string) { + return { + 'aria-controls': `tabpanel-${name}`, + id: `${name}`, + } +} + +const SettingsTabs: React.FC> = ({ + session, +}) => ( + { + window.sideAPI.state.set('editor.configSettingsGroup', group) + }} + textColor="inherit" + value={session.state.editor.configSettingsGroup} + variant='fullWidth' + > + + + +) + +export default SettingsTabs diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Editor/AvailableSuiteTestList.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Editor/AvailableSuiteTestList.tsx index 8f2c9c84f..3b92f1c7e 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Editor/AvailableSuiteTestList.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Editor/AvailableSuiteTestList.tsx @@ -15,8 +15,8 @@ const AvailableSuiteTestList: FC = ({ allTests, }) => ( - - Available tests + + Available tests = ({ useKeyboundNav(tests, selectedIndexes) return ( - - Tests in suite + + Tests in suite {preview.map(([id, origIndex], index) => { diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Editor/index.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Editor/index.tsx index bc72aa1c4..f14e43f01 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Editor/index.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Editor/index.tsx @@ -24,6 +24,7 @@ const SuiteCustomizer: React.FC> = ({ return ( <> window.sideAPI.state.toggleSuiteMode('viewer')} > Suite Editor diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteNewDialog.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteCreateDialog.tsx similarity index 81% rename from packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteNewDialog.tsx rename to packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteCreateDialog.tsx index 9b34f2ab2..42457335d 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteNewDialog.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteCreateDialog.tsx @@ -13,21 +13,26 @@ const { type CloseReason = 'Create' | 'Cancel' export interface SuiteNewDialogProps { - confirmNew: boolean - setConfirmNew: React.Dispatch> + open: boolean + setOpen: React.Dispatch> } const SuiteNewDialog: React.FC = ({ - confirmNew, - setConfirmNew, + open, + setOpen, }) => { const [suiteName, setSuiteName] = React.useState('') + const createSuite = async () => { + const newSuite = await window.sideAPI.suites.create(suiteName) + setSelected(newSuite.id) + } + const handleClose = async (value: CloseReason) => { if (value === 'Create') { createSuite() } - setConfirmNew(false) + setOpen(false) } const onKeyDown = (event: React.KeyboardEvent): void => { @@ -35,18 +40,12 @@ const SuiteNewDialog: React.FC = ({ if (event.key === 'Enter') { event.preventDefault() event.stopPropagation() - createSuite() - setConfirmNew(false) + handleClose('Create') } } - const createSuite = async () => { - const activeSuite = (await window.sideAPI.suites.create(suiteName)).id - setSelected(activeSuite) - } - return ( - + Please specify the new suite name > + suiteID: string + suiteName: string +} + +const SuiteDeleteDialog: React.FC = ({ + open, + setOpen, + suiteID, + suiteName, +}) => { + const handleClose = async (value: CloseReason) => { + if (value === 'Delete') { + await window.sideAPI.suites.delete(suiteID) + } + setOpen(false) + } + + React.useEffect(() => { + const onKeyDown = (event: KeyboardEvent): void => { + // 'keypress' event misbehaves on mobile so we track 'Enter' key via 'keydown' event + if (event.key === 'Enter') { + event.preventDefault() + event.stopPropagation() + handleClose('Delete') + } + } + window.addEventListener('keydown', onKeyDown) + return () => { + window.removeEventListener('keydown', onKeyDown) + } + }, []) + return ( + + + + Are you sure you want to delete suite {suiteName} + + + + + + + + ) +} + +export default SuiteDeleteDialog diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteRenameDialog.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteRenameDialog.tsx new file mode 100644 index 000000000..73c4049da --- /dev/null +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteRenameDialog.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogContentText from '@mui/material/DialogContentText' +import TextField from '@mui/material/TextField' + +type CloseReason = 'Rename' | 'Cancel' + +export interface SuiteRenameDialogProps { + open: boolean + setOpen: React.Dispatch> + suiteID: string + suiteName: string +} + +const SuiteRenameDialog: React.FC = ({ + open, + setOpen, + suiteID, + suiteName: _suiteName, +}) => { + const [suiteName, setSuiteName] = React.useState(_suiteName) + + const handleClose = async (value: CloseReason) => { + if (value === 'Rename') { + await window.sideAPI.suites.update(suiteID, {name: suiteName}) + } + setOpen(false) + } + + const onKeyDown = (event: React.KeyboardEvent): void => { + // 'keypress' event misbehaves on mobile so we track 'Enter' key via 'keydown' event + if (event.key === 'Enter') { + event.preventDefault() + event.stopPropagation() + handleClose('Rename') + } + } + + return ( + + + Please specify the updated suite name + setSuiteName(e.target.value)} + onKeyDown={onKeyDown} + value={suiteName} + variant="standard" + /> + + + + + + + ) +} + +export default SuiteRenameDialog diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteSelector.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteSelector.tsx index d165b0b3d..f0995b07a 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteSelector.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuiteSelector.tsx @@ -1,10 +1,13 @@ +import FormControl from '@mui/material/FormControl' +import InputLabel from '@mui/material/InputLabel' import MenuItem from '@mui/material/MenuItem' import Select from '@mui/material/Select' import React from 'react' import { SIDEMainProps } from '../../components/types' import EditorToolbar from '../../components/Drawer/EditorToolbar' -import SuiteNewDialog from './SuiteNewDialog' -import { FormControl, InputLabel } from '@mui/material' +import SuiteCreateDialog from './SuiteCreateDialog' +import SuiteRenameDialog from './SuiteRenameDialog' +import SuiteDeleteDialog from './SuiteDeleteDialog' const SuiteSelector: React.FC> = ({ session, @@ -13,22 +16,22 @@ const SuiteSelector: React.FC> = ({ project: { suites }, state: { activeSuiteID }, } = session - const [disabled, setDisabled] = React.useState(false) - const [confirmNew, setConfirmNew] = React.useState(false) + const [disabled /*, setDisabled*/] = React.useState(false) + const [confirmDelete, setConfirmDelete] = React.useState(false) + const [confirmRename, setConfirmRename] = React.useState(false) + const [confirmCreate, setConfirmCreate] = React.useState(false) + const activeSuiteName = suites.find((t) => t.id === activeSuiteID)?.name ?? '' return ( <> { - setDisabled(true) - const suite = await window.sideAPI.suites.create() - await window.sideAPI.state.setActiveSuite(suite.id) - setDisabled(false) - }} + onAdd={() => setConfirmCreate(true)} + onRemove={activeSuiteID ? async () => setConfirmDelete(true) : undefined} + onEdit={activeSuiteID ? async () => setConfirmRename(true) : undefined} > - Suite + Selected Suite - + {confirmDelete && ( + + )} + {confirmRename && ( + + )} + {confirmCreate && ( + + )} ) } diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuitesDrawer.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuitesDrawer.tsx deleted file mode 100644 index fed75415c..000000000 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuitesDrawer.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import List from '@mui/material/List' -import React from 'react' -import Drawer from '../../components/Drawer/Wrapper' -import EditorToolbar from '../../components/Drawer/EditorToolbar' -import RenamableListItem from '../../components/Drawer/RenamableListItem' -import SuiteNewDialog from './SuiteNewDialog' -import { SIDEMainProps } from '../../components/types' - -const { - state: { setActiveSuite: setSelected }, - suites: { update }, -} = window.sideAPI - -const rename = (id: string, name: string) => update(id, { name }) - -const SuiteList: React.FC< - Pick -> = ({ openDrawer, session, setOpenDrawer }) => { - const { - project: { suites }, - state: { activeSuiteID }, - } = session - const [confirmNew, setConfirmNew] = React.useState(false) - - return ( - - { - console.log('setIsOpen(true)') - setConfirmNew(true) - }} - onRemove={ - suites.length > 1 - ? () => { - const doDelete = window.confirm('Delete this suite?') - if (doDelete) { - window.sideAPI.suites.delete(activeSuiteID) - } - } - : undefined - } - /> - - {suites - .slice() - .sort((a, b) => a.name.localeCompare(b.name)) - .map(({ id, name }) => ( - { - window.sideAPI.menus.open('suiteManager', [id]) - }} - rename={rename} - selected={id === activeSuiteID} - setSelected={setSelected} - /> - ))} - - - - ) -} - -export default SuiteList diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuitesTab.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuitesTab.tsx index 78b929589..8ab622ac8 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuitesTab.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/SuitesTab.tsx @@ -7,11 +7,8 @@ import AppBar from '../../components/AppBar' import SuiteSelector from './SuiteSelector' const SuitesTab: React.FC< - Pick< - SIDEMainProps, - 'openDrawer' | 'session' | 'setOpenDrawer' | 'setTab' | 'tab' - > -> = ({ openDrawer, session, setOpenDrawer, setTab, tab }) => { + Pick +> = ({ session, setTab, tab }) => { const Component = session.state.editor.suiteMode === 'editor' ? SuiteEditor : SuiteViewer @@ -19,9 +16,7 @@ const SuitesTab: React.FC< diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Viewer/index.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Viewer/index.tsx index 420629643..b1c166c5c 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Viewer/index.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Suites/Viewer/index.tsx @@ -18,11 +18,12 @@ const SuiteViewer: React.FC> = ({ session }) => { return ( <> window.sideAPI.state.toggleSuiteMode('editor')} > Suite Player - + {activeSuite.tests.map((testID) => { const test = tests.find(hasID(testID)) as TestShape const lastCommand = testResults[test.id]?.lastCommand diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestNewDialog.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestCreateDialog.tsx similarity index 78% rename from packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestNewDialog.tsx rename to packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestCreateDialog.tsx index 466d01385..3270eaa6f 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestNewDialog.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestCreateDialog.tsx @@ -13,21 +13,26 @@ const { type CloseReason = 'Create' | 'Cancel' export interface TestNewDialogProps { - confirmNew: boolean - setConfirmNew: React.Dispatch> + open: boolean + setOpen: React.Dispatch> } const TestNewDialog: React.FC = ({ - confirmNew, - setConfirmNew, + open, + setOpen, }) => { const [testName, setTestName] = React.useState('') + const createTest = async () => { + const newTest = await window.sideAPI.tests.create(testName) + setSelected(newTest.id) + } + const handleClose = async (value: CloseReason) => { if (value === 'Create') { createTest() } - setConfirmNew(false) + setOpen(false) } const onKeyDown = (event: React.KeyboardEvent): void => { @@ -35,20 +40,12 @@ const TestNewDialog: React.FC = ({ if (event.key === 'Enter') { event.preventDefault() event.stopPropagation() - createTest() - setConfirmNew(false) + handleClose('Create') } } - const createTest = async () => { - console.log('Create ' + testName) - const activeTest = (await window.sideAPI.tests.create(testName)).id - console.log('activeTest is ' + activeTest) - setSelected(activeTest) - } - return ( - + Please specify the new test name > + testID: string + testName: string +} + +const TestDeleteDialog: React.FC = ({ + open, + setOpen, + testID, + testName, +}) => { + const handleClose = async (value: CloseReason) => { + if (value === 'Delete') { + await window.sideAPI.tests.delete(testID) + } + setOpen(false) + } + + React.useEffect(() => { + const onKeyDown = (event: KeyboardEvent): void => { + // 'keypress' event misbehaves on mobile so we track 'Enter' key via 'keydown' event + if (event.key === 'Enter') { + event.preventDefault() + event.stopPropagation() + handleClose('Delete') + } + } + window.addEventListener('keydown', onKeyDown) + return () => { + window.removeEventListener('keydown', onKeyDown) + } + }, []) + return ( + + + + Are you sure you want to delete test {testName} + + + + + + + + ) +} + +export default TestDeleteDialog diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestRenameDialog.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestRenameDialog.tsx new file mode 100644 index 000000000..a02e0e699 --- /dev/null +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestRenameDialog.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogContentText from '@mui/material/DialogContentText' +import TextField from '@mui/material/TextField' + +type CloseReason = 'Rename' | 'Cancel' + +export interface TestRenameDialogProps { + open: boolean + setOpen: React.Dispatch> + testID: string + testName: string +} + +const TestRenameDialog: React.FC = ({ + open, + setOpen, + testID, + testName: _testName, +}) => { + const [testName, setTestName] = React.useState(_testName) + + const handleClose = async (value: CloseReason) => { + if (value === 'Rename') { + await window.sideAPI.tests.rename(testID, testName) + } + setOpen(false) + } + + const onKeyDown = (event: React.KeyboardEvent): void => { + // 'keypress' event misbehaves on mobile so we track 'Enter' key via 'keydown' event + if (event.key === 'Enter') { + event.preventDefault() + event.stopPropagation() + handleClose('Rename') + } + } + + return ( + + + Please specify the updated test name + setTestName(e.target.value)} + onKeyDown={onKeyDown} + value={testName} + variant="standard" + /> + + + + + + + ) +} + +export default TestRenameDialog diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestSelector.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestSelector.tsx index a22504f2e..6738763e1 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestSelector.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestSelector.tsx @@ -1,10 +1,13 @@ +import FormControl from '@mui/material/FormControl' +import InputLabel from '@mui/material/InputLabel' import MenuItem from '@mui/material/MenuItem' import Select from '@mui/material/Select' import React from 'react' import { SIDEMainProps } from '../../components/types' import EditorToolbar from '../../components/Drawer/EditorToolbar' -import TestNewDialog from './TestNewDialog' -import { FormControl, InputLabel } from '@mui/material' +import TestCreateDialog from './TestCreateDialog' +import TestRenameDialog from './TestRenameDialog' +import TestDeleteDialog from './TestDeleteDialog' const TestSelector: React.FC> = ({ session, @@ -13,22 +16,22 @@ const TestSelector: React.FC> = ({ project: { tests }, state: { activeTestID }, } = session - const [disabled, setDisabled] = React.useState(false) - const [confirmNew, setConfirmNew] = React.useState(false) + const [disabled /*, setDisabled*/] = React.useState(false) + const [confirmDelete, setConfirmDelete] = React.useState(false) + const [confirmRename, setConfirmRename] = React.useState(false) + const [confirmCreate, setConfirmCreate] = React.useState(false) + const activeTestName = tests.find((t) => t.id === activeTestID)?.name ?? '' return ( <> { - setDisabled(true) - const test = await window.sideAPI.tests.create() - await window.sideAPI.state.setActiveTest(test.id) - setDisabled(false) - }} + onAdd={() => setConfirmCreate(true)} + onRemove={activeTestID ? async () => setConfirmDelete(true) : undefined} + onEdit={activeTestID ? async () => setConfirmRename(true) : undefined} > - Test + Selected Test - + {confirmDelete && ( + + )} + {confirmRename && ( + + )} + {confirmCreate && } ) } diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestsDrawer.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestsDrawer.tsx deleted file mode 100644 index 4ad170bd9..000000000 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestsDrawer.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import List from '@mui/material/List' -import Stack from '@mui/material/Stack' -import React from 'react' -import TestNewDialog from './TestNewDialog' -import Drawer from '../../components/Drawer/Wrapper' -import EditorToolbar from '../../components/Drawer/EditorToolbar' -import RenamableListItem from '../../components/Drawer/RenamableListItem' -import { SIDEMainProps } from '../../components/types' - -const { - state: { setActiveTest: setSelected }, - tests: { rename }, -} = window.sideAPI - -const TestList: React.FC< - Pick -> = ({ openDrawer, session, setOpenDrawer }) => { - const { - project: { tests }, - state: { - activeTestID, - playback: { commands, testResults }, - }, - } = session - const [confirmNew, setConfirmNew] = React.useState(false) - - return ( - - - { - setConfirmNew(true) - }} - onRemove={ - tests.length > 1 - ? () => { - const doDelete = window.confirm('Delete this test?') - if (doDelete) { - window.sideAPI.tests.delete(activeTestID) - } - } - : undefined - } - sx={{ - zIndex: 100, - }} - /> - - - {tests - .slice() - .sort((a, b) => a.name.localeCompare(b.name)) - .map(({ id, name }) => { - const lastCommand = testResults[id]?.lastCommand - return ( - { - window.sideAPI.menus.open('testManager', [id]) - }} - rename={rename} - selected={id === activeTestID} - setSelected={setSelected} - state={commands[lastCommand]?.state} - /> - ) - })} - - - - ) -} - -export default TestList diff --git a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestsTab.tsx b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestsTab.tsx index 25b8d8cce..b773e5131 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestsTab.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectEditor/tabs/Tests/TestsTab.tsx @@ -25,12 +25,11 @@ const NoTestFound = () => ( ) -const TestsTab: React.FC< - Pick< - SIDEMainProps, - 'openDrawer' | 'session' | 'setOpenDrawer' | 'setTab' | 'tab' - > -> = ({ openDrawer, session, setOpenDrawer, setTab, tab }) => { +const TestsTab: React.FC> = ({ + session, + setTab, + tab, +}) => { const { state: { activeTestID, @@ -70,9 +69,7 @@ const TestsTab: React.FC< diff --git a/packages/selenium-ide/src/browser/windows/ProjectMainWindow/renderer.tsx b/packages/selenium-ide/src/browser/windows/ProjectMainWindow/renderer.tsx index 0501f4e2a..c02aa1acf 100644 --- a/packages/selenium-ide/src/browser/windows/ProjectMainWindow/renderer.tsx +++ b/packages/selenium-ide/src/browser/windows/ProjectMainWindow/renderer.tsx @@ -1,15 +1,18 @@ +import AppWrapper from 'browser/components/AppWrapper' import renderWhenReady from 'browser/helpers/renderWhenReady' +import subscribeToSession from 'browser/helpers/subscribeToSession' +import React from 'react' import { ImperativePanelGroupHandle, Panel, PanelGroup, PanelResizeHandle, } from 'react-resizable-panels' -import React from 'react' import ProjectEditor from '../ProjectEditor/main' import ProjectPlaybackWindow from '../PlaybackWindow/main' import SIDELogger from '../Logger/main' -import AppWrapper from 'browser/components/AppWrapper' +import URLBar from '../ProjectEditor/components/URLBar' +import PlaybackTabBar from '../ProjectEditor/components/PlaybackTabBar' const usePanelGroup = (id: string) => { const [ready, setReady] = React.useState(false) @@ -34,38 +37,45 @@ const usePanelGroup = (id: string) => { } } -const ProjectMainWindow = () => ( - - - - - - - - - - - - - - - - - - - -) +const ProjectMainWindow = () => { + const session = subscribeToSession() + return ( + + + + + + + + +
+ + +
+ + + + + + + +
+
+
+
+ ) +} renderWhenReady(ProjectMainWindow) diff --git a/packages/selenium-ide/src/main/session/controllers/Windows/index.ts b/packages/selenium-ide/src/main/session/controllers/Windows/index.ts index 6d0259223..13a2eeace 100644 --- a/packages/selenium-ide/src/main/session/controllers/Windows/index.ts +++ b/packages/selenium-ide/src/main/session/controllers/Windows/index.ts @@ -219,19 +219,32 @@ export default class WindowsController extends BaseController { return true } - async getPlaybackWindowByHandle(handle: string) { + getPlaybackWindowByHandle(handle: string) { const id = this.handlesToIDs[handle] - console.log('match?', handle, id) return this.playbackWindows.find((bw) => bw.id === id) } - async getPlaybackWindowHandleByID(id: number) { + getPlaybackWindowHandleByID(id: number) { const handle = Object.entries(this.handlesToIDs).find( ([_, value]) => value === id )?.[0] return handle } + async closePlaybackWindow(id: number) { + const window = BrowserWindow.fromId(id) + if (window) { + window.close() + } + } + + async focusPlaybackWindow(id: number) { + const window = await BrowserWindow.fromId(id) + if (window) { + window.focus() + } + } + async openPlaybackWindow(opts: BrowserWindowConstructorOptions = {}) { const playbackScreenPosition = await this.session.resizablePanels.getPanelScreenPosition( @@ -242,6 +255,9 @@ export default class WindowsController extends BaseController { ...playbackScreenPosition, parent: await this.session.windows.get(projectEditorWindowName), }) + this.session.api.windows.onPlaybackWindowOpened.dispatchEvent(window.id, { + title: window.getTitle(), + }) this.handlePlaybackWindow(window) window.showInactive() if (opts.title) { @@ -289,13 +305,38 @@ export default class WindowsController extends BaseController { this.playbackWindows.splice(windowIndex, 1) this.playbackWindows.push(window) } + this.session.api.windows.onPlaybackWindowChanged.dispatchEvent( + window.id, + { + focused: true, + } + ) + }) + window.on('blur', () => { + this.session.api.windows.onPlaybackWindowChanged.dispatchEvent( + window.id, + { + focused: false, + } + ) + }) + window.webContents.on('did-navigate', () => { + this.session.api.windows.onPlaybackWindowChanged.dispatchEvent( + window.id, + { + title: window.webContents.getURL(), + } + ) + }) + window.on('closed', () => { + this.session.api.windows.onPlaybackWindowClosed.dispatchEvent(window.id) + this.removePlaybackWIndow(window) }) - window.on('closed', () => this.removePlaybackWIndow(window)) } async removePlaybackWIndow(window: Electron.BrowserWindow) { this.playbackWindows.splice(this.playbackWindows.indexOf(window), 1) - if (this.playbackWindows.length === 1) { + if (this.playbackWindows.length === 0) { if (this.session.state.state.status === 'recording') { await this.session.api.recorder.stop() } diff --git a/packages/side-api/package.json b/packages/side-api/package.json index ef9ce05f6..4cd81213c 100644 --- a/packages/side-api/package.json +++ b/packages/side-api/package.json @@ -1,6 +1,6 @@ { "name": "@seleniumhq/side-api", - "version": "4.0.0-alpha.42", + "version": "4.0.0-alpha.43", "private": false, "description": "Selenium IDE API command shapes and such", "author": "Todd Tarsi ", diff --git a/packages/side-api/src/commands/windows/closePlaybackWindow.ts b/packages/side-api/src/commands/windows/closePlaybackWindow.ts new file mode 100644 index 000000000..c7c7c390b --- /dev/null +++ b/packages/side-api/src/commands/windows/closePlaybackWindow.ts @@ -0,0 +1,4 @@ +/** + * Close a playback window + */ +export type Shape = (id: number) => Promise diff --git a/packages/side-api/src/commands/windows/focusPlaybackWindow.ts b/packages/side-api/src/commands/windows/focusPlaybackWindow.ts new file mode 100644 index 000000000..3fa6b07a3 --- /dev/null +++ b/packages/side-api/src/commands/windows/focusPlaybackWindow.ts @@ -0,0 +1,4 @@ +/** + * Focus a playback window + */ +export type Shape = (id: number) => Promise diff --git a/packages/side-api/src/commands/windows/index.ts b/packages/side-api/src/commands/windows/index.ts index 12b572cfb..889af835b 100644 --- a/packages/side-api/src/commands/windows/index.ts +++ b/packages/side-api/src/commands/windows/index.ts @@ -1,19 +1,39 @@ import type { Shape as Close } from './close' +import type { Shape as ClosePlaybackWindow } from './closePlaybackWindow' +import type { Shape as FocusPlaybackWindow } from './focusPlaybackWindow' +import type { Shape as OnPlaybackWindowChanged } from './onPlaybackWindowChanged' +import type { Shape as OnPlaybackWindowClosed } from './onPlaybackWindowClosed' +import type { Shape as OnPlaybackWindowOpened } from './onPlaybackWindowOpened' import type { Shape as OpenCustom } from './openCustom' import type { Shape as Open } from './open' import * as close from './close' +import * as closePlaybackWindow from './closePlaybackWindow' +import * as focusPlaybackWindow from './focusPlaybackWindow' +import * as onPlaybackWindowChanged from './onPlaybackWindowChanged' +import * as onPlaybackWindowClosed from './onPlaybackWindowClosed' +import * as onPlaybackWindowOpened from './onPlaybackWindowOpened' import * as open from './open' import * as openCustom from './openCustom' export const commands = { close, + closePlaybackWindow, + focusPlaybackWindow, + onPlaybackWindowChanged, + onPlaybackWindowClosed, + onPlaybackWindowOpened, open, openCustom, } export type Shape = { close: Close + closePlaybackWindow: ClosePlaybackWindow + focusPlaybackWindow: FocusPlaybackWindow + onPlaybackWindowChanged: OnPlaybackWindowChanged + onPlaybackWindowClosed: OnPlaybackWindowClosed + onPlaybackWindowOpened: OnPlaybackWindowOpened open: Open openCustom: OpenCustom } diff --git a/packages/side-api/src/commands/windows/onPlaybackWindowChanged.ts b/packages/side-api/src/commands/windows/onPlaybackWindowChanged.ts new file mode 100644 index 000000000..23f1629d0 --- /dev/null +++ b/packages/side-api/src/commands/windows/onPlaybackWindowChanged.ts @@ -0,0 +1,14 @@ +import { BaseListener } from '../../types/base' + +/** + * Runs when a playback window is updated + */ +export type OnPlaybackWindowChanged = [ + id: number, + update: Partial<{ + focused: boolean + state: 'idle' | 'playing' | 'recording' | 'paused' + title: string + }> +] +export type Shape = BaseListener diff --git a/packages/side-api/src/commands/windows/onPlaybackWindowClosed.ts b/packages/side-api/src/commands/windows/onPlaybackWindowClosed.ts new file mode 100644 index 000000000..e37fe0724 --- /dev/null +++ b/packages/side-api/src/commands/windows/onPlaybackWindowClosed.ts @@ -0,0 +1,7 @@ +import { BaseListener } from '../../types/base' + +/** + * Runs when a playback window is closed + */ +export type OnPlaybackWindowClosed = [id: number] +export type Shape = BaseListener diff --git a/packages/side-api/src/commands/windows/onPlaybackWindowOpened.ts b/packages/side-api/src/commands/windows/onPlaybackWindowOpened.ts new file mode 100644 index 000000000..f92b0215c --- /dev/null +++ b/packages/side-api/src/commands/windows/onPlaybackWindowOpened.ts @@ -0,0 +1,12 @@ +import { BaseListener } from '../../types/base' + +/** + * Runs when a playback window is opened + */ +export type OnPlaybackWindowOpened = [ + id: number, + update: { + title: string + } +] +export type Shape = BaseListener diff --git a/packages/side-example-suite/package.json b/packages/side-example-suite/package.json index c95f08434..667267d59 100644 --- a/packages/side-example-suite/package.json +++ b/packages/side-example-suite/package.json @@ -1,6 +1,6 @@ { "name": "@seleniumhq/side-example-suite", - "version": "4.0.0-alpha.7", + "version": "4.0.0-alpha.8", "private": true, "description": "Selenium IDE example suite, with tests, plugin, and export format", "author": "Todd ", @@ -21,7 +21,7 @@ "@seleniumhq/code-export-python-pytest": "4.0.0-alpha.3" }, "devDependencies": { - "@seleniumhq/side-api": "^4.0.0-alpha.42" + "@seleniumhq/side-api": "^4.0.0-alpha.43" }, "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7131582fc..47eca923e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -270,7 +270,7 @@ importers: specifier: ^4.0.0-alpha.3 version: link:../get-driver '@seleniumhq/side-api': - specifier: ^4.0.0-alpha.42 + specifier: ^4.0.0-alpha.43 version: link:../side-api '@seleniumhq/side-commons': specifier: ^4.0.0-alpha.2 @@ -489,7 +489,7 @@ importers: version: link:../side-code-export devDependencies: '@seleniumhq/side-api': - specifier: ^4.0.0-alpha.42 + specifier: ^4.0.0-alpha.43 version: link:../side-api packages/side-migrate: