diff --git a/lib/locator.js b/lib/locator.js
index a2f0ea0a3..b1acb016e 100644
--- a/lib/locator.js
+++ b/lib/locator.js
@@ -591,13 +591,24 @@ Locator.clickable = {
`.//*[@title = ${literal}]`,
`.//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id ]`,
`.//*[@role='button'][normalize-space(.)=${literal}]`,
+ `.//*[@role='tab' or @role='link' or @role='menuitem' or @role='menuitemcheckbox' or @role='menuitemradio' or @role='option' or @role='treeitem'][contains(normalize-space(string(.)), ${literal})]`,
]),
/**
* @param {string} literal
* @returns {string}
*/
- self: literal => `./self::*[contains(normalize-space(string(.)), ${literal}) or contains(normalize-space(@value), ${literal})]`,
+ self: literal => {
+ // Narrowest-match: prefer the deepest descendant whose string-value contains the literal.
+ // Falling back to `self` without the `not(descendant...)` guard would match a container
+ // whose concatenated text happens to include the literal (e.g. a
whose
+ // tab labels all sit in its string-value) and click the container itself.
+ const narrowest = `contains(normalize-space(string(.)), ${literal}) and not(.//*[contains(normalize-space(string(.)), ${literal})])`
+ return xpathLocator.combine([
+ `.//*[${narrowest}]`,
+ `./self::*[${narrowest} or contains(normalize-space(@value), ${literal})]`,
+ ])
+ },
}
Locator.field = {
diff --git a/test/data/app/view/form/tablist.php b/test/data/app/view/form/tablist.php
new file mode 100644
index 000000000..ae669c2c7
--- /dev/null
+++ b/test/data/app/view/form/tablist.php
@@ -0,0 +1,33 @@
+
+
+
+ Tablist click regression (#5530)
+
+
+
+
Tablist
+
+
Description
+
Code template
+
Attachments
+
Runs
+
History
+
+
description
+
+
+
diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js
index e5edb5eb3..b1a20bb25 100644
--- a/test/helper/Playwright_test.js
+++ b/test/helper/Playwright_test.js
@@ -1192,6 +1192,36 @@ describe('Playwright', function () {
I.see('Information')
})
})
+
+ describe('#click - with tag selector as context', () => {
+ it('clicks text when context is the tag "a"', async () => {
+ await I.amOnPage('/form/example7')
+ await I.click('Buy Chocolate Bar', 'a')
+ await I.seeCurrentUrlEquals('/')
+ })
+ })
+
+ describe('#click - tablist regression', () => {
+ // https://github.com/codeceptjs/CodeceptJS — click(text, container) was matching
+ // the container itself when its concatenated string-value contained the text,
+ // clicking the