diff --git a/src/codegen/browser/code/options.ts b/src/codegen/browser/code/options.ts index a3eff9d4e..263a64960 100644 --- a/src/codegen/browser/code/options.ts +++ b/src/codegen/browser/code/options.ts @@ -37,6 +37,7 @@ function isBrowserScenario(scenario: ir.Scenario) { case 'ReloadExpression': case 'NewRoleLocatorExpression': case 'RoleLocatorOptionsExpression': + case 'TextLocatorOptionsExpression': case 'NewLabelLocatorExpression': case 'NewCssLocatorExpression': case 'NewAltTextLocatorExpression': diff --git a/src/codegen/browser/code/scenario.ts b/src/codegen/browser/code/scenario.ts index 02df1e79b..55e1fbf76 100644 --- a/src/codegen/browser/code/scenario.ts +++ b/src/codegen/browser/code/scenario.ts @@ -85,6 +85,17 @@ function emitRoleLocatorOptionsExpression( }) } +function emitTextLocatorOptionsExpression( + _context: ScenarioContext, + expression: ir.TextLocatorOptionsExpression +): ts.Expression { + const exact = expression.exact + + return ObjectBuilder.from({ + ...(exact && { exact }), + }) +} + function emitNewLabelLocatorExpression( context: ScenarioContext, expression: ir.NewLabelLocatorExpression @@ -92,9 +103,14 @@ function emitNewLabelLocatorExpression( const page = emitExpression(context, expression.page) const text = emitExpression(context, expression.text) + const options = + expression.options !== null + ? [emitExpression(context, expression.options)] + : [] + return new ExpressionBuilder(page) .member('getByLabel') - .call([text, ObjectBuilder.from({ exact: true })]) + .call([text, ...options]) .done() } @@ -105,9 +121,14 @@ function emitNewAltTextLocatorExpression( const page = emitExpression(context, expression.page) const text = emitExpression(context, expression.text) + const options = + expression.options !== null + ? [emitExpression(context, expression.options)] + : [] + return new ExpressionBuilder(page) .member('getByAltText') - .call([text, ObjectBuilder.from({ exact: true })]) + .call([text, ...options]) .done() } @@ -118,9 +139,14 @@ function emitNewPlaceholderLocatorExpression( const page = emitExpression(context, expression.page) const text = emitExpression(context, expression.text) + const options = + expression.options !== null + ? [emitExpression(context, expression.options)] + : [] + return new ExpressionBuilder(page) .member('getByPlaceholder') - .call([text, ObjectBuilder.from({ exact: true })]) + .call([text, ...options]) .done() } @@ -131,9 +157,14 @@ function emitNewTitleLocatorExpression( const page = emitExpression(context, expression.page) const text = emitExpression(context, expression.text) + const options = + expression.options !== null + ? [emitExpression(context, expression.options)] + : [] + return new ExpressionBuilder(page) .member('getByTitle') - .call([text, ObjectBuilder.from({ exact: true })]) + .call([text, ...options]) .done() } @@ -457,6 +488,9 @@ function emitExpression( case 'RoleLocatorOptionsExpression': return emitRoleLocatorOptionsExpression(context, expression) + case 'TextLocatorOptionsExpression': + return emitTextLocatorOptionsExpression(context, expression) + case 'NewLabelLocatorExpression': return emitNewLabelLocatorExpression(context, expression) diff --git a/src/codegen/browser/codegen.test.ts b/src/codegen/browser/codegen.test.ts index 8f429d480..8b10c098c 100644 --- a/src/codegen/browser/codegen.test.ts +++ b/src/codegen/browser/codegen.test.ts @@ -1044,7 +1044,7 @@ it('should emit a getByAltText locator', async ({ expect }) => { nodeId: 'locator', selector: { type: 'alt', - text: 'Grot is happy', + text: { value: 'Grot is happy', exact: true }, }, inputs: { page: { nodeId: 'page' } }, }, @@ -1085,7 +1085,7 @@ it('should emit a getByLabel locator', async ({ expect }) => { nodeId: 'locator', selector: { type: 'label', - text: 'Username', + text: { value: 'Username', exact: true }, }, inputs: { page: { nodeId: 'page' } }, }, @@ -1120,7 +1120,7 @@ it('should emit a getByPlaceholder locator', async ({ expect }) => { nodeId: 'locator', selector: { type: 'placeholder', - text: 'Enter your email', + text: { value: 'Enter your email', exact: true }, }, inputs: { page: { nodeId: 'page' } }, }, @@ -1155,7 +1155,7 @@ it('should emit a getByTitle locator', async ({ expect }) => { nodeId: 'locator', selector: { type: 'title', - text: 'Submit your form', + text: { value: 'Submit your form', exact: true }, }, inputs: { page: { nodeId: 'page' } }, }, @@ -1196,7 +1196,7 @@ it('should emit a waitFor statement', async ({ expect }) => { nodeId: 'locator', selector: { type: 'title', - text: 'Submit your form', + text: { value: 'Submit your form', exact: true }, }, inputs: { page: { nodeId: 'page' } }, }, diff --git a/src/codegen/browser/intermediate/ast.ts b/src/codegen/browser/intermediate/ast.ts index 1b6e9d644..4dd4f9636 100644 --- a/src/codegen/browser/intermediate/ast.ts +++ b/src/codegen/browser/intermediate/ast.ts @@ -37,24 +37,33 @@ export interface NewAltTextLocatorExpression { type: 'NewAltTextLocatorExpression' text: Expression page: Expression + options: Expression | null } export interface NewLabelLocatorExpression { type: 'NewLabelLocatorExpression' text: Expression page: Expression + options: Expression | null } export interface NewPlaceholderLocatorExpression { type: 'NewPlaceholderLocatorExpression' text: Expression page: Expression + options: Expression | null } export interface NewTitleLocatorExpression { type: 'NewTitleLocatorExpression' text: Expression page: Expression + options: Expression | null +} + +export interface TextLocatorOptionsExpression { + type: 'TextLocatorOptionsExpression' + exact?: boolean } export interface RoleLocatorOptionsExpression { @@ -196,6 +205,7 @@ export type Expression = | ClosePageExpression | NewRoleLocatorExpression | RoleLocatorOptionsExpression + | TextLocatorOptionsExpression | NewLabelLocatorExpression | NewPlaceholderLocatorExpression | NewTitleLocatorExpression diff --git a/src/codegen/browser/intermediate/index.ts b/src/codegen/browser/intermediate/index.ts index 0bc5394e1..5a688b465 100644 --- a/src/codegen/browser/intermediate/index.ts +++ b/src/codegen/browser/intermediate/index.ts @@ -91,9 +91,15 @@ function emitLocatorNode(context: IntermediateContext, node: m.LocatorNode) { type: 'NewLabelLocatorExpression', text: { type: 'StringLiteral', - value: node.selector.text, + value: node.selector.text.value, }, page, + options: node.selector.text.exact + ? { + type: 'TextLocatorOptionsExpression', + exact: node.selector.text.exact, + } + : null, }) break @@ -102,9 +108,15 @@ function emitLocatorNode(context: IntermediateContext, node: m.LocatorNode) { type: 'NewPlaceholderLocatorExpression', text: { type: 'StringLiteral', - value: node.selector.text, + value: node.selector.text.value, }, page, + options: node.selector.text.exact + ? { + type: 'TextLocatorOptionsExpression', + exact: node.selector.text.exact, + } + : null, }) break @@ -113,9 +125,15 @@ function emitLocatorNode(context: IntermediateContext, node: m.LocatorNode) { type: 'NewTitleLocatorExpression', text: { type: 'StringLiteral', - value: node.selector.text, + value: node.selector.text.value, }, page, + options: node.selector.text.exact + ? { + type: 'TextLocatorOptionsExpression', + exact: node.selector.text.exact, + } + : null, }) break @@ -124,9 +142,15 @@ function emitLocatorNode(context: IntermediateContext, node: m.LocatorNode) { type: 'NewAltTextLocatorExpression', text: { type: 'StringLiteral', - value: node.selector.text, + value: node.selector.text.value, }, page, + options: node.selector.text.exact + ? { + type: 'TextLocatorOptionsExpression', + exact: node.selector.text.exact, + } + : null, }) break diff --git a/src/codegen/browser/intermediate/variables.ts b/src/codegen/browser/intermediate/variables.ts index 936f6800a..425be1804 100644 --- a/src/codegen/browser/intermediate/variables.ts +++ b/src/codegen/browser/intermediate/variables.ts @@ -55,6 +55,7 @@ function substituteExpression( case 'ClickOptionsExpression': case 'WaitForOptionsExpression': case 'RoleLocatorOptionsExpression': + case 'TextLocatorOptionsExpression': return node case 'ClosePageExpression': @@ -84,6 +85,9 @@ function substituteExpression( type: 'NewLabelLocatorExpression', text: substituteExpression(node.text, substitutions), page: substituteExpression(node.page, substitutions), + options: node.options + ? substituteExpression(node.options, substitutions) + : null, } case 'NewCssLocatorExpression': @@ -96,8 +100,11 @@ function substituteExpression( case 'NewAltTextLocatorExpression': return { type: 'NewAltTextLocatorExpression', - page: substituteExpression(node.page, substitutions), text: substituteExpression(node.text, substitutions), + page: substituteExpression(node.page, substitutions), + options: node.options + ? substituteExpression(node.options, substitutions) + : null, } case 'NewPlaceholderLocatorExpression': @@ -105,6 +112,9 @@ function substituteExpression( type: 'NewPlaceholderLocatorExpression', text: substituteExpression(node.text, substitutions), page: substituteExpression(node.page, substitutions), + options: node.options + ? substituteExpression(node.options, substitutions) + : null, } case 'NewTitleLocatorExpression': @@ -112,6 +122,9 @@ function substituteExpression( type: 'NewTitleLocatorExpression', text: substituteExpression(node.text, substitutions), page: substituteExpression(node.page, substitutions), + options: node.options + ? substituteExpression(node.options, substitutions) + : null, } case 'NewTestIdLocatorExpression': diff --git a/src/codegen/browser/selectors.ts b/src/codegen/browser/selectors.ts index a76831b92..515ec79c9 100644 --- a/src/codegen/browser/selectors.ts +++ b/src/codegen/browser/selectors.ts @@ -37,7 +37,7 @@ function getAltTextSelector( return { type: 'alt', - text: selectors.alt, + text: { value: selectors.alt, exact: true }, } } @@ -50,7 +50,7 @@ function getLabelSelector( return { type: 'label', - text: selectors.label, + text: { value: selectors.label, exact: true }, } } @@ -63,7 +63,7 @@ function getPlaceholderSelector( return { type: 'placeholder', - text: selectors.placeholder, + text: { value: selectors.placeholder, exact: true }, } } @@ -76,7 +76,7 @@ function getTitleSelector( return { type: 'title', - text: selectors.title, + text: { value: selectors.title, exact: true }, } } @@ -141,31 +141,31 @@ export function toNodeSelector(locator: ActionLocator): NodeSelector { case 'alt': return { type: 'alt', - text: locator.text, + text: { value: locator.text, exact: locator.options?.exact }, } case 'label': return { type: 'label', - text: locator.label, + text: { value: locator.label, exact: locator.options?.exact }, } case 'placeholder': return { type: 'placeholder', - text: locator.placeholder, + text: { value: locator.placeholder, exact: locator.options?.exact }, } case 'title': return { type: 'title', - text: locator.title, + text: { value: locator.title, exact: locator.options?.exact }, } case 'text': return { type: 'text', - text: locator.text, + text: { value: locator.text, exact: locator.options?.exact }, } default: @@ -190,19 +190,39 @@ export function isSelectorEqual(a: NodeSelector, b: NodeSelector): boolean { ) case 'alt': - return b.type === 'alt' && a.text === b.text + return ( + b.type === 'alt' && + a.text.value === b.text.value && + a.text.exact === b.text.exact + ) case 'label': - return b.type === 'label' && a.text === b.text + return ( + b.type === 'label' && + a.text.value === b.text.value && + a.text.exact === b.text.exact + ) case 'placeholder': - return b.type === 'placeholder' && a.text === b.text + return ( + b.type === 'placeholder' && + a.text.value === b.text.value && + a.text.exact === b.text.exact + ) case 'text': - return b.type === 'text' && a.text === b.text + return ( + b.type === 'text' && + a.text.value === b.text.value && + a.text.exact === b.text.exact + ) case 'title': - return b.type === 'title' && a.text === b.text + return ( + b.type === 'title' && + a.text.value === b.text.value && + a.text.exact === b.text.exact + ) default: return exhaustive(a) diff --git a/src/components/Browser/Locator.tsx b/src/components/Browser/Locator.tsx index 2d5e17bd1..486a43675 100644 --- a/src/components/Browser/Locator.tsx +++ b/src/components/Browser/Locator.tsx @@ -63,19 +63,19 @@ export function LocatorText({ locator }: LocatorComponentProps) { return locator.testId case 'label': - return quote(locator.text) + return quote(locator.text.value) case 'placeholder': - return quote(locator.text) + return quote(locator.text.value) case 'title': - return quote(locator.text) + return quote(locator.text.value) case 'alt': - return quote(locator.text) + return quote(locator.text.value) case 'text': - return quote(locator.text) + return quote(locator.text.value) case 'role': return ( diff --git a/src/main/runner/schema.ts b/src/main/runner/schema.ts index 5c6a4de91..dd02208c5 100644 --- a/src/main/runner/schema.ts +++ b/src/main/runner/schema.ts @@ -34,29 +34,40 @@ const GetByTestIdLocatorSchema = z.object({ testId: z.string(), }) +const TextLocatorOptions = z + .object({ + exact: z.boolean().optional(), + }) + .optional() + const GetByAltTextLocatorSchema = z.object({ type: z.literal('alt'), text: z.string(), + options: TextLocatorOptions, }) const GetByLabelLocatorSchema = z.object({ type: z.literal('label'), label: z.string(), + options: TextLocatorOptions, }) const GetByPlaceholderLocatorSchema = z.object({ type: z.literal('placeholder'), placeholder: z.string(), + options: TextLocatorOptions, }) const GetByTitleLocatorSchema = z.object({ type: z.literal('title'), title: z.string(), + options: TextLocatorOptions, }) const GetByTextLocatorSchema = z.object({ type: z.literal('text'), text: z.string(), + options: TextLocatorOptions, }) const ActionLocatorSchema = z.discriminatedUnion('type', [ diff --git a/src/main/runner/shims/browser/proxies/page.ts b/src/main/runner/shims/browser/proxies/page.ts index ff43e82fd..484946a7e 100644 --- a/src/main/runner/shims/browser/proxies/page.ts +++ b/src/main/runner/shims/browser/proxies/page.ts @@ -87,34 +87,39 @@ export function pageProxy(target: Page): ProxyOptions { }, }) }, - getByAltText(target, text) { + getByAltText(target, text, options) { return locatorProxy(target, { type: 'alt', text: text.toString(), + options, }) }, - getByLabel(target, label) { + getByLabel(target, label, options) { return locatorProxy(target, { type: 'label', label: label.toString(), + options, }) }, - getByPlaceholder(target, placeholder) { + getByPlaceholder(target, placeholder, options) { return locatorProxy(target, { type: 'placeholder', placeholder: placeholder.toString(), + options, }) }, - getByTitle(target, title) { + getByTitle(target, title, options) { return locatorProxy(target, { type: 'title', title: title.toString(), + options, }) }, - getByText(target, text) { + getByText(target, text, options) { return locatorProxy(target, { type: 'text', text: text.toString(), + options, }) }, getByTestId(target, testId) { diff --git a/src/schemas/selectors.ts b/src/schemas/selectors.ts index 5934021e8..3d30cecf3 100644 --- a/src/schemas/selectors.ts +++ b/src/schemas/selectors.ts @@ -5,40 +5,40 @@ export const CssNodeSelectorSchema = z.object({ selector: z.string(), }) +const TextWithExact = z.object({ + value: z.string(), + exact: z.boolean().optional(), +}) + export const GetByRoleNodeSelectorSchema = z.object({ type: z.literal('role'), role: z.string(), - name: z - .object({ - value: z.string(), - exact: z.boolean().optional(), - }) - .optional(), + name: TextWithExact.optional(), }) export const GetByAltTextNodeSelectorSchema = z.object({ type: z.literal('alt'), - text: z.string(), + text: TextWithExact, }) export const GetByLabelNodeSelectorSchema = z.object({ type: z.literal('label'), - text: z.string(), + text: TextWithExact, }) export const GetByPlaceholderNodeSelectorSchema = z.object({ type: z.literal('placeholder'), - text: z.string(), + text: TextWithExact, }) export const GetByTextNodeSelectorSchema = z.object({ type: z.literal('text'), - text: z.string(), + text: TextWithExact, }) export const GetByTitleNodeSelectorSchema = z.object({ type: z.literal('title'), - text: z.string(), + text: TextWithExact, }) export const GetByTestIdNodeSelectorSchema = z.object({ diff --git a/src/utils/selectors.ts b/src/utils/selectors.ts index 643635c45..55730fa75 100644 --- a/src/utils/selectors.ts +++ b/src/utils/selectors.ts @@ -31,19 +31,29 @@ export function findElementsBySelector( }) case 'alt': - return queryAllByAltText(container, selector.text) + return queryAllByAltText(container, selector.text.value, { + exact: selector.text.exact, + }) case 'label': - return queryAllByLabelText(container, selector.text) + return queryAllByLabelText(container, selector.text.value, { + exact: selector.text.exact, + }) case 'placeholder': - return queryAllByPlaceholderText(container, selector.text) + return queryAllByPlaceholderText(container, selector.text.value, { + exact: selector.text.exact, + }) case 'text': - return queryAllByText(container, selector.text) + return queryAllByText(container, selector.text.value, { + exact: selector.text.exact, + }) case 'title': - return queryAllByTitle(container, selector.text) + return queryAllByTitle(container, selector.text.value, { + exact: selector.text.exact, + }) default: return [] diff --git a/src/views/BrowserTestEditor/ActionForms/components/TextFieldWithExactToggle.tsx b/src/views/BrowserTestEditor/ActionForms/components/TextFieldWithExactToggle.tsx new file mode 100644 index 000000000..695c4589a --- /dev/null +++ b/src/views/BrowserTestEditor/ActionForms/components/TextFieldWithExactToggle.tsx @@ -0,0 +1,48 @@ +import { IconButton, TextField, Tooltip } from '@radix-ui/themes' +import { WholeWordIcon } from 'lucide-react' + +interface TextFieldWithExactToggleProps { + name: string + value: string + exact?: boolean + onValueChange: (value: string) => void + onExactChange: (exact: boolean) => void + onBlur?: () => void +} + +export function TextFieldWithExactToggle({ + name, + value, + exact, + onValueChange, + onExactChange, + onBlur, +}: TextFieldWithExactToggleProps) { + return ( + onValueChange(e.target.value)} + onBlur={onBlur} + > + + + { + onExactChange(!exact) + onBlur?.() + }} + > + + + + + + ) +} diff --git a/src/views/BrowserTestEditor/ActionForms/components/index.ts b/src/views/BrowserTestEditor/ActionForms/components/index.ts index ab3db2506..62d4f436e 100644 --- a/src/views/BrowserTestEditor/ActionForms/components/index.ts +++ b/src/views/BrowserTestEditor/ActionForms/components/index.ts @@ -1,2 +1,3 @@ export * from './ComboBox' +export * from './TextFieldWithExactToggle' export * from './ValuePopoverBadge' diff --git a/src/views/BrowserTestEditor/ActionForms/forms/LocatorForm.tsx b/src/views/BrowserTestEditor/ActionForms/forms/LocatorForm.tsx index 27a45fd49..199d6b536 100644 --- a/src/views/BrowserTestEditor/ActionForms/forms/LocatorForm.tsx +++ b/src/views/BrowserTestEditor/ActionForms/forms/LocatorForm.tsx @@ -1,11 +1,20 @@ import { css } from '@emotion/react' -import { Flex, Grid, Popover, RadioGroup, Separator } from '@radix-ui/themes' +import { + Flex, + Grid, + Popover, + RadioGroup, + Separator, + Tooltip, +} from '@radix-ui/themes' +import { WholeWordIcon } from 'lucide-react' import { useState } from 'react' import { toNodeSelector } from '@/codegen/browser/selectors' import { LocatorIcon, LocatorText } from '@/components/Browser/Locator' import { FieldGroup } from '@/components/Form' import { ActionLocator } from '@/main/runner/schema' +import { NodeSelector } from '@/schemas/selectors' import { exhaustive } from '@/utils/typescript' import { LocatorOptions } from '../../types' @@ -286,7 +295,17 @@ function DisplayValue({ const selector = toNodeSelector(values[current]!) return ( - + + ) } +function ExactMatchIndicator({ locator }: { locator: NodeSelector }) { + if (locator.type === 'test-id' || locator.type === 'css') { + return null + } + + const exact = + locator.type === 'role' ? locator.name?.exact : locator.text.exact + if (exact) { + return ( + + + + ) + } + + return null +} + function initializeLocatorValues(type: ActionLocator['type']): ActionLocator { switch (type) { case 'css': diff --git a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByAltTextForm.tsx b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByAltTextForm.tsx index b1ca4e548..c9a5452cd 100644 --- a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByAltTextForm.tsx +++ b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByAltTextForm.tsx @@ -1,8 +1,7 @@ -import { TextField } from '@radix-ui/themes' - import { FieldGroup } from '@/components/Form' import { ActionLocator } from '@/main/runner/schema' +import { TextFieldWithExactToggle } from '../../components' import { toFieldErrors } from '../utils' type AltLocator = Extract @@ -28,11 +27,14 @@ export function GetByAltTextForm({ mb="0" errors={toFieldErrors('alt', errors?.['alt'])} > - onChange({ ...locator, text: e.target.value })} + exact={locator.options?.exact} + onValueChange={(value) => onChange({ ...locator, text: value })} + onExactChange={(exact) => { + onChange({ ...locator, options: { ...locator.options, exact } }) + }} onBlur={onBlur} /> diff --git a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByLabelForm.tsx b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByLabelForm.tsx index 6f360645f..3763cc823 100644 --- a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByLabelForm.tsx +++ b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByLabelForm.tsx @@ -1,8 +1,7 @@ -import { TextField } from '@radix-ui/themes' - import { FieldGroup } from '@/components/Form' import { ActionLocator } from '@/main/runner/schema' +import { TextFieldWithExactToggle } from '../../components' import { toFieldErrors } from '../utils' type LabelLocator = Extract @@ -28,11 +27,14 @@ export function GetByLabelForm({ mb="0" errors={toFieldErrors('form-label', errors?.['form-label'])} > - onChange({ ...locator, label: e.target.value })} + exact={locator.options?.exact} + onValueChange={(value) => onChange({ ...locator, label: value })} + onExactChange={(exact) => { + onChange({ ...locator, options: { ...locator.options, exact } }) + }} onBlur={onBlur} /> diff --git a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByPlaceholderForm.tsx b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByPlaceholderForm.tsx index 2106e43cf..d37ce3ab1 100644 --- a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByPlaceholderForm.tsx +++ b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByPlaceholderForm.tsx @@ -1,8 +1,7 @@ -import { TextField } from '@radix-ui/themes' - import { FieldGroup } from '@/components/Form' import { ActionLocator } from '@/main/runner/schema' +import { TextFieldWithExactToggle } from '../../components' import { toFieldErrors } from '../utils' type PlaceholderLocator = Extract @@ -28,11 +27,14 @@ export function GetByPlaceholderForm({ mb="0" errors={toFieldErrors('placeholder', errors?.['placeholder'])} > - onChange({ ...locator, placeholder: e.target.value })} + exact={locator.options?.exact} + onValueChange={(value) => onChange({ ...locator, placeholder: value })} + onExactChange={(exact) => { + onChange({ ...locator, options: { ...locator.options, exact } }) + }} onBlur={onBlur} /> diff --git a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByRoleForm.tsx b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByRoleForm.tsx index 24a6c21c1..3643753d2 100644 --- a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByRoleForm.tsx +++ b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByRoleForm.tsx @@ -1,11 +1,9 @@ -import { css } from '@emotion/react' -import { Flex, IconButton, TextField, Tooltip } from '@radix-ui/themes' -import { WholeWordIcon } from 'lucide-react' +import { Flex } from '@radix-ui/themes' import { FieldGroup } from '@/components/Form' import { ActionLocator } from '@/main/runner/schema' -import { ComboBox } from '../../components' +import { ComboBox, TextFieldWithExactToggle } from '../../components' import { toFieldErrors } from '../utils' const ROLE_OPTIONS = [ @@ -62,12 +60,11 @@ export function GetByRoleForm({ mb="0" errors={toFieldErrors('name', errors?.['name'])} > - { - const value = e.target.value + exact={locator.options?.exact} + onValueChange={(value) => { onChange({ ...locator, options: { @@ -76,36 +73,17 @@ export function GetByRoleForm({ }, }) }} + onExactChange={(exact) => { + onChange({ + ...locator, + options: { + ...locator.options, + exact, + }, + }) + }} onBlur={onBlur} - > - - - { - onChange({ - ...locator, - options: { - ...locator.options, - exact: !locator.options?.exact, - }, - }) - onBlur?.() - }} - css={css` - margin: 0; - `} - > - - - - - + /> ) diff --git a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByTextForm.tsx b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByTextForm.tsx index d67b43cec..b1721fed9 100644 --- a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByTextForm.tsx +++ b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByTextForm.tsx @@ -1,8 +1,7 @@ -import { TextField } from '@radix-ui/themes' - import { FieldGroup } from '@/components/Form' import { ActionLocator } from '@/main/runner/schema' +import { TextFieldWithExactToggle } from '../../components' import { toFieldErrors } from '../utils' type TextLocator = Extract @@ -28,11 +27,14 @@ export function GetByTextForm({ mb="0" errors={toFieldErrors('text-content', errors?.['text-content'])} > - onChange({ ...locator, text: e.target.value })} + exact={locator.options?.exact} + onValueChange={(value) => onChange({ ...locator, text: value })} + onExactChange={(exact) => { + onChange({ ...locator, options: { ...locator.options, exact } }) + }} onBlur={onBlur} /> diff --git a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByTitleForm.tsx b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByTitleForm.tsx index b3cb9a05d..b0b322b31 100644 --- a/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByTitleForm.tsx +++ b/src/views/BrowserTestEditor/ActionForms/forms/locators/GetByTitleForm.tsx @@ -1,8 +1,7 @@ -import { TextField } from '@radix-ui/themes' - import { FieldGroup } from '@/components/Form' import { ActionLocator } from '@/main/runner/schema' +import { TextFieldWithExactToggle } from '../../components' import { toFieldErrors } from '../utils' type TitleLocator = Extract @@ -28,11 +27,14 @@ export function GetByTitleForm({ mb="0" errors={toFieldErrors('title', errors?.['title'])} > - onChange({ ...locator, title: e.target.value })} + exact={locator.options?.exact} + onValueChange={(value) => onChange({ ...locator, title: value })} + onExactChange={(exact) => { + onChange({ ...locator, options: { ...locator.options, exact } }) + }} onBlur={onBlur} />