Skip to content

Commit f8f61f7

Browse files
internal(browser): Add support for fill action to Browser Test Editor (#1157)
1 parent 53dec50 commit f8f61f7

File tree

9 files changed

+124
-4
lines changed

9 files changed

+124
-4
lines changed

src/codegen/browser/test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,11 +396,19 @@ function buildBrowserNodeGraphFromActions(
396396
locator: getLocator(action.locator),
397397
},
398398
}
399+
case 'locator.fill':
400+
return {
401+
type: 'type-text',
402+
nodeId: crypto.randomUUID(),
403+
value: action.value,
404+
inputs: {
405+
locator: getLocator(action.locator),
406+
},
407+
}
399408
case 'page.waitForNavigation':
400409
case 'page.close':
401410
case 'page.*':
402411
case 'locator.dblclick':
403-
case 'locator.fill':
404412
case 'locator.type':
405413
case 'locator.check':
406414
case 'locator.uncheck':
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Popover, TextArea } from '@radix-ui/themes'
2+
import { useState } from 'react'
3+
4+
import { FieldGroup } from '@/components/Form'
5+
6+
import { ValuePopoverBadge } from '../components'
7+
8+
interface FillValueFormProps {
9+
value: string
10+
onChange: (value: string) => void
11+
}
12+
13+
export function FillValueForm({ value, onChange }: FillValueFormProps) {
14+
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
15+
16+
return (
17+
<Popover.Root open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
18+
<Popover.Trigger>
19+
<ValuePopoverBadge displayValue={`"${value}"`} />
20+
</Popover.Trigger>
21+
<Popover.Content align="start" size="1" width="300px">
22+
<FieldGroup name="value" label="Value" labelSize="1" mb="0">
23+
<TextArea
24+
size="1"
25+
name="value"
26+
value={value}
27+
onChange={(e) => onChange(e.target.value)}
28+
/>
29+
</FieldGroup>
30+
</Popover.Content>
31+
</Popover.Root>
32+
)
33+
}

src/views/BrowserTestEditor/ActionForms/forms/LocatorForm.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ const LOCATOR_TYPES: Record<ActionLocator['type'], string> = {
4545
interface LocatorFormProps {
4646
state: LocatorOptions
4747
onChange: (value: LocatorOptions) => void
48+
suggestedRoles?: string[]
4849
}
4950

5051
export function LocatorForm({
5152
state: { current, values },
5253
onChange,
54+
suggestedRoles,
5355
}: LocatorFormProps) {
5456
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
5557
const [touchedTypes, setTouchedTypes] = useState(
@@ -142,6 +144,7 @@ export function LocatorForm({
142144
errors={validation.fieldErrors}
143145
onChange={handleLocatorChange}
144146
onBlur={handleFieldBlur}
147+
suggestedRoles={suggestedRoles}
145148
/>
146149
</Grid>
147150
</Popover.Content>
@@ -154,13 +157,15 @@ interface LocatorFieldsFormProps {
154157
errors?: Record<string, string>
155158
onChange: (locator: ActionLocator) => void
156159
onBlur?: () => void
160+
suggestedRoles?: string[]
157161
}
158162

159163
function LocatorFieldsForm({
160164
locator,
161165
errors,
162166
onChange,
163167
onBlur,
168+
suggestedRoles,
164169
}: LocatorFieldsFormProps) {
165170
switch (locator.type) {
166171
case 'role':
@@ -170,6 +175,7 @@ function LocatorFieldsForm({
170175
errors={errors}
171176
onChange={onChange}
172177
onBlur={onBlur}
178+
suggestedRoles={suggestedRoles}
173179
/>
174180
)
175181
case 'css':

src/views/BrowserTestEditor/ActionForms/forms/locators/GetByRoleForm.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { ActionLocator } from '@/main/runner/schema'
66
import { ComboBox, TextFieldWithExactToggle } from '../../components'
77
import { toFieldErrors } from '../utils'
88

9-
const ROLE_OPTIONS = [
9+
const DEFAULT_ROLES = [
1010
'button',
1111
'link',
1212
'checkbox',
@@ -17,7 +17,11 @@ const ROLE_OPTIONS = [
1717
'combobox',
1818
'listbox',
1919
'option',
20-
].map((role) => ({ value: role, label: role }))
20+
]
21+
22+
function toRoleOptions(roles: string[]) {
23+
return roles.map((role) => ({ value: role, label: role }))
24+
}
2125

2226
type RoleLocator = Extract<ActionLocator, { type: 'role' }>
2327

@@ -26,13 +30,15 @@ interface GetByRoleFormProps {
2630
errors?: Record<string, string>
2731
onChange: (locator: ActionLocator) => void
2832
onBlur?: () => void
33+
suggestedRoles?: string[]
2934
}
3035

3136
export function GetByRoleForm({
3237
locator,
3338
errors,
3439
onChange,
3540
onBlur,
41+
suggestedRoles,
3642
}: GetByRoleFormProps) {
3743
return (
3844
<Flex direction="column" gap="2" align="stretch">
@@ -46,7 +52,7 @@ export function GetByRoleForm({
4652
<ComboBox
4753
id="role"
4854
value={locator.role}
49-
options={ROLE_OPTIONS}
55+
options={toRoleOptions(suggestedRoles ?? DEFAULT_ROLES)}
5056
onChange={(value) => {
5157
onChange({ ...locator, role: value.trim() })
5258
onBlur?.()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Grid } from '@radix-ui/themes'
2+
3+
import { LocatorFillAction } from '@/main/runner/schema'
4+
5+
import { FillValueForm } from '../../ActionForms/forms/FillValueForm'
6+
import { LocatorForm } from '../../ActionForms/forms/LocatorForm'
7+
import { WithEditorMetadata } from '../../types'
8+
9+
const FILL_ROLES = ['textbox', 'searchbox', 'combobox']
10+
11+
interface FillActionBodyProps {
12+
action: WithEditorMetadata<LocatorFillAction>
13+
onChange: (action: WithEditorMetadata<LocatorFillAction>) => void
14+
}
15+
16+
export function FillActionBody({ action, onChange }: FillActionBodyProps) {
17+
const handleChangeLocator = (
18+
locator: WithEditorMetadata<LocatorFillAction>['locator']
19+
) => {
20+
onChange({ ...action, locator })
21+
}
22+
23+
const handleChangeValue = (value: string) => {
24+
onChange({ ...action, value })
25+
}
26+
27+
return (
28+
<Grid
29+
columns="max-content minmax(0, max-content) minmax(0, max-content) 1fr"
30+
gap="2"
31+
align="center"
32+
width="100%"
33+
>
34+
Fill
35+
<LocatorForm
36+
state={action.locator}
37+
onChange={handleChangeLocator}
38+
suggestedRoles={FILL_ROLES}
39+
/>
40+
with
41+
<FillValueForm value={action.value} onChange={handleChangeValue} />
42+
</Grid>
43+
)
44+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './FillActionBody'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './ClickAction'
2+
export * from './FillAction'
23
export * from './GoToAction'
34
export * from './PageReloadAction'
45
export * from './WaitForAction'

src/views/BrowserTestEditor/EditableBrowserActionList.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ function NewActionMenu({ onAddAction }: NewActionMenuProps) {
8383
>
8484
Click element
8585
</DropdownMenu.Item>
86+
<DropdownMenu.Item
87+
onClick={() => {
88+
onAddAction('locator.fill')
89+
}}
90+
>
91+
Fill input
92+
</DropdownMenu.Item>
8693
<DropdownMenu.Item
8794
onClick={() => {
8895
onAddAction('page.goto')

src/views/BrowserTestEditor/actionEditorRegistry.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import {
44
GlobeIcon,
55
MousePointerClickIcon,
66
RefreshCwIcon,
7+
TextCursorInputIcon,
78
TimerIcon,
89
} from 'lucide-react'
910
import { ReactElement, ReactNode } from 'react'
1011

1112
import {
1213
ClickActionBody,
14+
FillActionBody,
1315
GoToActionBody,
1416
PageReloadActionBody,
1517
WaitForActionBody,
@@ -67,6 +69,18 @@ const actionEditors: ActionEditorRegistry = {
6769
locator: createDefaultLocatorOptions(),
6870
}),
6971
},
72+
'locator.fill': {
73+
icon: <TextCursorInputIcon aria-hidden="true" />,
74+
render: ({ action, onChange }) => (
75+
<FillActionBody action={action} onChange={onChange} />
76+
),
77+
create: () => ({
78+
id: crypto.randomUUID(),
79+
method: 'locator.fill',
80+
value: '',
81+
locator: createDefaultLocatorOptions(),
82+
}),
83+
},
7084
'page.goto': {
7185
icon: <GlobeIcon aria-hidden="true" />,
7286
render: ({ action, onChange }) => (

0 commit comments

Comments
 (0)