Skip to content

Commit 799c9f0

Browse files
author
DavertMik
committed
implemented aria selectors for PW/WebDriver/Puppeteer
1 parent 514cc75 commit 799c9f0

File tree

7 files changed

+352
-111
lines changed

7 files changed

+352
-111
lines changed

lib/helper/Playwright.js

Lines changed: 32 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import ElementNotFound from './errors/ElementNotFound.js'
3030
import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
3131
import Popup from './extras/Popup.js'
3232
import Console from './extras/Console.js'
33-
import { findReact, findVue, findByPlaywrightLocator } from './extras/PlaywrightReactVueLocator.js'
33+
import { buildLocatorString, findElements, findElement, getVisibleElements, findReact, findVue, findByPlaywrightLocator, findByRole } from './extras/PlaywrightLocator.js'
3434

3535
let playwright
3636
let perfTiming
@@ -2424,13 +2424,21 @@ class Playwright extends Helper {
24242424
*
24252425
*/
24262426
async grabTextFrom(locator) {
2427+
const originalLocator = locator
2428+
if (typeof locator === 'object' && (locator.role || locator.react || locator.vue || locator.pw)) {
2429+
const els = await this._locate(locator)
2430+
assertElementExists(els, locator)
2431+
const text = await els[0].innerText()
2432+
this.debugSection('Text', text)
2433+
return text
2434+
}
24272435
locator = this._contextLocator(locator)
24282436
let text
24292437
try {
24302438
text = await this.page.textContent(locator)
24312439
} catch (err) {
24322440
if (err.message.includes('Timeout') || err.message.includes('exceeded')) {
2433-
throw new Error(`Element ${new Locator(locator).toString()} was not found by text|CSS|XPath`)
2441+
throw new Error(`Element ${new Locator(originalLocator).toString()} was not found by text|CSS|XPath`)
24342442
}
24352443
throw err
24362444
}
@@ -3821,47 +3829,6 @@ class Playwright extends Helper {
38213829

38223830
export default Playwright
38233831

3824-
function buildLocatorString(locator) {
3825-
if (locator.isCustom()) {
3826-
return `${locator.type}=${locator.value}`
3827-
}
3828-
if (locator.isXPath()) {
3829-
return `xpath=${locator.value}`
3830-
}
3831-
return locator.simplify()
3832-
}
3833-
3834-
async function findElements(matcher, locator) {
3835-
if (locator.react) return findReact(matcher, locator)
3836-
if (locator.vue) return findVue(matcher, locator)
3837-
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
3838-
locator = new Locator(locator, 'css')
3839-
3840-
return matcher.locator(buildLocatorString(locator)).all()
3841-
}
3842-
3843-
async function findElement(matcher, locator) {
3844-
if (locator.react) return findReact(matcher, locator)
3845-
if (locator.vue) return findVue(matcher, locator)
3846-
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
3847-
locator = new Locator(locator, 'css')
3848-
3849-
return matcher.locator(buildLocatorString(locator)).first()
3850-
}
3851-
3852-
async function getVisibleElements(elements) {
3853-
const visibleElements = []
3854-
for (const element of elements) {
3855-
if (await element.isVisible()) {
3856-
visibleElements.push(element)
3857-
}
3858-
}
3859-
if (visibleElements.length === 0) {
3860-
return elements
3861-
}
3862-
return visibleElements
3863-
}
3864-
38653832
async function proceedClick(locator, context = null, options = {}) {
38663833
let matcher = await this._getContext()
38673834
if (context) {
@@ -3901,13 +3868,28 @@ async function findClickable(matcher, locator) {
39013868
if (locator.react) return findReact(matcher, locator)
39023869
if (locator.vue) return findVue(matcher, locator)
39033870
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
3871+
if (locator.role) return findByRole(matcher, locator)
39043872

39053873
locator = new Locator(locator)
39063874
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator)
39073875

39083876
let els
39093877
const literal = xpathLocator.literal(locator.value)
39103878

3879+
try {
3880+
els = await matcher.getByRole('button', { name: locator.value }).all()
3881+
if (els.length) return els
3882+
} catch (err) {
3883+
// getByRole not supported or failed
3884+
}
3885+
3886+
try {
3887+
els = await matcher.getByRole('link', { name: locator.value }).all()
3888+
if (els.length) return els
3889+
} catch (err) {
3890+
// getByRole not supported or failed
3891+
}
3892+
39113893
els = await findElements.call(this, matcher, Locator.clickable.narrow(literal))
39123894
if (els.length) return els
39133895

@@ -3960,6 +3942,10 @@ async function findCheckable(locator, context) {
39603942
contextEl = contextEl[0]
39613943
}
39623944

3945+
if (typeof locator === 'object' && (locator.role || locator.react || locator.vue || locator.pw)) {
3946+
return findElements.call(this, contextEl, locator)
3947+
}
3948+
39633949
const matchedLocator = new Locator(locator)
39643950
if (!matchedLocator.isFuzzy()) {
39653951
return findElements.call(this, contextEl, matchedLocator.simplify())
@@ -3986,6 +3972,9 @@ async function proceedIsChecked(assertType, option) {
39863972
}
39873973

39883974
async function findFields(locator) {
3975+
if (typeof locator === 'object' && (locator.role || locator.react || locator.vue || locator.pw)) {
3976+
return this._locate(locator)
3977+
}
39893978
const matchedLocator = new Locator(locator)
39903979
if (!matchedLocator.isFuzzy()) {
39913980
return this._locate(matchedLocator)

lib/helper/Puppeteer.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,12 @@ class Puppeteer extends Helper {
979979
return this._locate(locator)
980980
}
981981

982+
async grabWebElement(locator) {
983+
const els = await this._locate(locator)
984+
assertElementExists(els, locator)
985+
return els[0]
986+
}
987+
982988
/**
983989
* Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
984990
*
@@ -2740,6 +2746,7 @@ class Puppeteer extends Helper {
27402746

27412747
async function findElements(matcher, locator) {
27422748
if (locator.react) return findReactElements.call(this, locator)
2749+
if (locator.role) return findByRole.call(this, matcher, locator)
27432750
locator = new Locator(locator, 'css')
27442751
if (!locator.isXPath()) return matcher.$$(locator.simplify())
27452752

@@ -2786,6 +2793,7 @@ async function proceedClick(locator, context = null, options = {}) {
27862793

27872794
async function findClickable(matcher, locator) {
27882795
if (locator.react) return findReactElements.call(this, locator)
2796+
if (locator.role) return findByRole.call(this, matcher, locator)
27892797
locator = new Locator(locator)
27902798
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator)
27912799

@@ -2805,6 +2813,14 @@ async function findClickable(matcher, locator) {
28052813
// Do nothing
28062814
}
28072815

2816+
// Try ARIA selector for accessible name
2817+
try {
2818+
els = await matcher.$$(`::-p-aria(${locator.value})`)
2819+
if (els.length) return els
2820+
} catch (err) {
2821+
// ARIA selector not supported or failed
2822+
}
2823+
28082824
return findElements.call(this, matcher, locator.value) // by css or xpath
28092825
}
28102826

@@ -2847,6 +2863,10 @@ async function findCheckable(locator, context) {
28472863
contextEl = contextEl[0]
28482864
}
28492865

2866+
if (typeof locator === 'object' && (locator.role || locator.react || locator.vue || locator.pw)) {
2867+
return findElements.call(this, contextEl, locator)
2868+
}
2869+
28502870
const matchedLocator = new Locator(locator)
28512871
if (!matchedLocator.isFuzzy()) {
28522872
return findElements.call(this, contextEl, matchedLocator.simplify())
@@ -2861,6 +2881,15 @@ async function findCheckable(locator, context) {
28612881
if (els.length) {
28622882
return els
28632883
}
2884+
2885+
// Try ARIA selector for accessible name
2886+
try {
2887+
els = await contextEl.$$(`::-p-aria(${locator})`)
2888+
if (els.length) return els
2889+
} catch (err) {
2890+
// ARIA selector not supported or failed
2891+
}
2892+
28642893
return findElements.call(this, contextEl, locator)
28652894
}
28662895

@@ -2880,6 +2909,9 @@ async function findVisibleFields(locator) {
28802909
}
28812910

28822911
async function findFields(locator) {
2912+
if (typeof locator === 'object' && (locator.role || locator.react || locator.vue || locator.pw)) {
2913+
return this._locate(locator)
2914+
}
28832915
const matchedLocator = new Locator(locator)
28842916
if (!matchedLocator.isFuzzy()) {
28852917
return this._locate(matchedLocator)
@@ -2899,6 +2931,16 @@ async function findFields(locator) {
28992931
if (els.length) {
29002932
return els
29012933
}
2934+
2935+
// Try ARIA selector for accessible name
2936+
try {
2937+
const page = await this.context
2938+
els = await page.$$(`::-p-aria(${locator})`)
2939+
if (els.length) return els
2940+
} catch (err) {
2941+
// ARIA selector not supported or failed
2942+
}
2943+
29022944
return this._locate({ css: locator })
29032945
}
29042946

@@ -3212,4 +3254,29 @@ async function findReactElements(locator, props = {}, state = {}) {
32123254
return result
32133255
}
32143256

3257+
async function findByRole(matcher, locator) {
3258+
const role = locator.role
3259+
3260+
if (!locator.text) {
3261+
return matcher.$$(`::-p-aria([role="${role}"])`)
3262+
}
3263+
3264+
const allElements = await matcher.$$(`::-p-aria([role="${role}"])`)
3265+
const filtered = []
3266+
const isExact = locator.exact === true
3267+
3268+
for (const el of allElements) {
3269+
const texts = await el.evaluate(e => {
3270+
const accessibleName = e.hasAttribute('aria-label') ? e.getAttribute('aria-label') : (e.id && document.querySelector(`label[for="${e.id}"]`)?.textContent.trim()) || ''
3271+
return [accessibleName, e.getAttribute('placeholder') || '', e.innerText ? e.innerText.trim() : '']
3272+
})
3273+
3274+
const matches = isExact ? texts.some(t => t === locator.text) : texts.some(t => t && t.includes(locator.text))
3275+
3276+
if (matches) filtered.push(el)
3277+
}
3278+
3279+
return filtered
3280+
}
3281+
32153282
export { Puppeteer as default }

0 commit comments

Comments
 (0)