diff --git a/lib/locator.js b/lib/locator.js
index 913fdd2dd..6a054d4fb 100644
--- a/lib/locator.js
+++ b/lib/locator.js
@@ -608,6 +608,9 @@ Locator.clickable = {
},
}
+const fieldRoles = '|textbox|searchbox|combobox|spinbutton|slider|listbox|checkbox|radio|switch|radiogroup|'
+const fieldLike = `(self::input | self::textarea | self::select) or @contenteditable = 'true' or contains('${fieldRoles}', concat('|', normalize-space(@role), '|'))`
+
Locator.field = {
/**
* @param {string} literal
@@ -627,9 +630,7 @@ Locator.field = {
xpathLocator.combine([
`.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = ${literal}) or ./@id = //label[@for][contains(normalize-space(string(.)), ${literal})]/@for) or ./@placeholder = ${literal})]`,
`.//label[contains(normalize-space(string(.)), ${literal})]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
- `.//*[@aria-label = ${literal}]`,
- `.//*[@title = ${literal}]`,
- `.//*[@aria-labelledby][@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id]`,
+ `.//*[${fieldLike}][@aria-label = ${literal} or @title = ${literal} or @aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id]`,
]),
/**
diff --git a/test/data/app/view/form/field_containers.php b/test/data/app/view/form/field_containers.php
new file mode 100644
index 000000000..ab05fa2e9
--- /dev/null
+++ b/test/data/app/view/form/field_containers.php
@@ -0,0 +1,26 @@
+
+
+
+ Labelled containers around fields
+
+
+ Labelled containers
+
+
+
+
+
+
diff --git a/test/helper/webapi.js b/test/helper/webapi.js
index 5d89be690..fb2bf80b1 100644
--- a/test/helper/webapi.js
+++ b/test/helper/webapi.js
@@ -723,6 +723,25 @@ export function tests() {
await I.see('tags: review,later', '#result')
})
})
+
+ it('should not resolve a labelled tablist container as a field', async function () {
+ // Puppeteer's findFields falls back to `::-p-aria()`, which resolves the tablist
+ // by accessible name regardless of the XPath strategies asserted here.
+ if (isHelper('Puppeteer')) this.skip()
+
+ await I.amOnPage('/form/field_containers')
+
+ let err
+ try {
+ await I.selectOption('Settings tabs', 'Password')
+ } catch (e) {
+ err = e
+ }
+
+ if (!err) assert.fail('selected an option on a tablist')
+ assert.include(err.message, 'was not found')
+ assert.notInclude(err.message, '