Skip to content

internal(browser): Add exact option to text-based locators#1152

Merged
going-confetti merged 8 commits intomainfrom
internal/locators-exact-toggle
Apr 9, 2026
Merged

internal(browser): Add exact option to text-based locators#1152
going-confetti merged 8 commits intomainfrom
internal/locators-exact-toggle

Conversation

@going-confetti
Copy link
Copy Markdown
Collaborator

@going-confetti going-confetti commented Apr 7, 2026

Description

  • Add a way to toggle exact matching on and off in getByAltText, getByTitle, getByText, and getByLabel
  • When the exact option is set, display it in the inline preview
grafik

⚠️ The icon used for the exact match toggle is the same as the one we user for placeholder locator. Since this isn't user-facing yet, I decided to address this in a separate PR.

How to Test

  • Create a new "Wait for element" action and configure one of the locators mentioned above
  • Toggle exact matching on and off and verify that the changes are reflected in the generated script

Checklist

  • I have performed a self-review of my code.
  • I have added tests for my changes.
  • I have commented on my code, particularly in hard-to-understand areas.

Related PR(s)/Issue(s)


Note

Medium Risk
Updates locator data shapes and codegen to pass through optional exact matching for multiple locator types; risk is mainly around compatibility with existing serialized locator payloads and generated scripts changing behavior when exact is set.

Overview
Adds optional exact matching support across text-based locators (alt/label/placeholder/title/text) end-to-end: runner schemas and page proxies now accept/preserve options.exact, the selector model is updated to carry { value, exact }, and DOM querying honors the flag when resolving selectors.

Code generation is updated to emit locator option objects (via new IR TextLocatorOptionsExpression) instead of forcing exact matching, and the test snapshots are updated accordingly. The Browser Test Editor UI gains a reusable TextFieldWithExactToggle and shows an inline exact match indicator in the locator preview.

Reviewed by Cursor Bugbot for commit 5e84ddd. Bugbot is set up for automated code reviews on this repo. Configure here.

@going-confetti going-confetti force-pushed the internal/locators-exact-toggle branch from e957118 to 5b99b3f Compare April 7, 2026 11:13
@going-confetti going-confetti force-pushed the internal/locators-exact-toggle branch from 5b99b3f to 58cd8ea Compare April 7, 2026 14:54
}

export interface TextLocatorOptionsExpression {
type: 'TextLocatorOptionsExpression'
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like TextBasedLocatorOptionsExpression is a better choice, since getByText exists?

@going-confetti going-confetti marked this pull request as ready for review April 8, 2026 09:11
@going-confetti going-confetti requested a review from a team as a code owner April 8, 2026 09:11
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Invalid CSS syntax for negating a variable
    • Replaced the invalid -var(--space-1) expression with calc(var(--space-1) * -1) so the negative margin-right is applied correctly.

Create PR

Or push these changes by commenting:

@cursor push 9c69db7e29
Preview (9c69db7e29)
diff --git a/src/views/BrowserTestEditor/ActionForms/components/TextFieldWithExactToggle.tsx b/src/views/BrowserTestEditor/ActionForms/components/TextFieldWithExactToggle.tsx
--- a/src/views/BrowserTestEditor/ActionForms/components/TextFieldWithExactToggle.tsx
+++ b/src/views/BrowserTestEditor/ActionForms/components/TextFieldWithExactToggle.tsx
@@ -41,7 +41,7 @@
                 onBlur?.()
               }}
               css={css`
-                margin-right: -var(--space-1);
+                margin-right: calc(var(--space-1) * -1);
               `}
             >
               <WholeWordIcon />

You can send follow-ups to the cloud agent here.

Comment thread src/views/BrowserTestEditor/ActionForms/components/TextFieldWithExactToggle.tsx Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Element highlighting ignores the new exact matching option
    • findElementsBySelector now passes exact: selector.text.exact ?? false to text-based testing-library queries so highlight matching aligns with generated Playwright locator behavior.

Create PR

Or push these changes by commenting:

@cursor push 3d3e95dd43
Preview (3d3e95dd43)
diff --git a/src/utils/selectors.ts b/src/utils/selectors.ts
--- a/src/utils/selectors.ts
+++ b/src/utils/selectors.ts
@@ -31,19 +31,29 @@
       })
 
     case 'alt':
-      return queryAllByAltText(container, selector.text.value)
+      return queryAllByAltText(container, selector.text.value, {
+        exact: selector.text.exact ?? false,
+      })
 
     case 'label':
-      return queryAllByLabelText(container, selector.text.value)
+      return queryAllByLabelText(container, selector.text.value, {
+        exact: selector.text.exact ?? false,
+      })
 
     case 'placeholder':
-      return queryAllByPlaceholderText(container, selector.text.value)
+      return queryAllByPlaceholderText(container, selector.text.value, {
+        exact: selector.text.exact ?? false,
+      })
 
     case 'text':
-      return queryAllByText(container, selector.text.value)
+      return queryAllByText(container, selector.text.value, {
+        exact: selector.text.exact ?? false,
+      })
 
     case 'title':
-      return queryAllByTitle(container, selector.text.value)
+      return queryAllByTitle(container, selector.text.value, {
+        exact: selector.text.exact ?? false,
+      })
 
     default:
       return []

You can send follow-ups to the cloud agent here.

Comment thread src/utils/selectors.ts Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Existing saved locators silently lose exact matching behavior
    • toNodeSelector now defaults text-based locator exact to true when legacy saved locator options are missing, preserving prior exact-matching behavior while still honoring explicit false.

Create PR

Or push these changes by commenting:

@cursor push 775b53775b
Preview (775b53775b)
diff --git a/src/codegen/browser/selectors.ts b/src/codegen/browser/selectors.ts
--- a/src/codegen/browser/selectors.ts
+++ b/src/codegen/browser/selectors.ts
@@ -141,25 +141,28 @@
     case 'alt':
       return {
         type: 'alt',
-        text: { value: locator.text, exact: locator.options?.exact },
+        text: { value: locator.text, exact: locator.options?.exact ?? true },
       }
 
     case 'label':
       return {
         type: 'label',
-        text: { value: locator.label, exact: locator.options?.exact },
+        text: { value: locator.label, exact: locator.options?.exact ?? true },
       }
 
     case 'placeholder':
       return {
         type: 'placeholder',
-        text: { value: locator.placeholder, exact: locator.options?.exact },
+        text: {
+          value: locator.placeholder,
+          exact: locator.options?.exact ?? true,
+        },
       }
 
     case 'title':
       return {
         type: 'title',
-        text: { value: locator.title, exact: locator.options?.exact },
+        text: { value: locator.title, exact: locator.options?.exact ?? true },
       }
 
     case 'text':

diff --git a/src/codegen/browser/test.test.ts b/src/codegen/browser/test.test.ts
--- a/src/codegen/browser/test.test.ts
+++ b/src/codegen/browser/test.test.ts
@@ -1,5 +1,6 @@
 import { describe, expect, it } from 'vitest'
 
+import { toNodeSelector } from './selectors'
 import { convertEventsToTest } from './test'
 
 describe('convertEventsToTest', () => {
@@ -81,3 +82,20 @@
     })
   })
 })
+
+describe('toNodeSelector', () => {
+  it('should default text locator exact matching to true when options are missing', () => {
+    const selector = toNodeSelector({
+      type: 'label',
+      label: 'Username',
+    })
+
+    expect(selector).toEqual({
+      type: 'label',
+      text: {
+        value: 'Username',
+        exact: true,
+      },
+    })
+  })
+})

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 053e705. Configure here.

type: 'TextLocatorOptionsExpression',
exact: node.selector.text.exact,
}
: null,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing saved locators silently lose exact matching behavior

Medium Severity

Previously, the code generation hardcoded { exact: true } for all text-based locators. Now it only emits { exact: true } when node.selector.text.exact is truthy. For existing saved ActionLocator data that predates this PR (which lacks the options field), toNodeSelector maps locator.options?.exact to undefined, causing the intermediate layer to set options: null. This silently changes the generated script from page.getByLabel("text", { exact: true }) to page.getByLabel("text"), switching from exact to substring matching in Playwright/k6 — a behavioral regression that could cause tests to match unintended elements.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 053e705. Configure here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, because it's not a user-facing feature yet. The user-facing recording-to-script conversion should not be affected.

onChange={(e) => onValueChange(e.target.value)}
onBlur={onBlur}
>
{value.trim().length > 0 && (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it a bit weird that exact toggle dissapears when I clear input, what's the reasoning behind this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I thought it didn't make sense to have this option while the input is empty, but I can see how it may look confusing when the button appears out of nowhere. I'll push a fix 👀

e-fisher
e-fisher previously approved these changes Apr 9, 2026
Copy link
Copy Markdown
Collaborator

@e-fisher e-fisher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀 Left a non-blocking question

@going-confetti going-confetti merged commit 53dec50 into main Apr 9, 2026
13 checks passed
@going-confetti going-confetti deleted the internal/locators-exact-toggle branch April 9, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants