Skip to content

Commit 124aaf3

Browse files
author
DavertMik
committed
better aria locators and parsing
1 parent 95894d3 commit 124aaf3

File tree

10 files changed

+518
-238
lines changed

10 files changed

+518
-238
lines changed

lib/helper/Playwright.js

Lines changed: 74 additions & 75 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 { findReact, findVue } from './extras/PlaywrightLocator.js'
3434
import WebElement from '../element/WebElement.js'
3535

3636
let playwright
@@ -2489,7 +2489,19 @@ class Playwright extends Helper {
24892489
* {{> selectOption }}
24902490
*/
24912491
async selectOption(select, option) {
2492-
const els = await findFields.call(this, select)
2492+
const selectLocator = Locator.from(select, 'css')
2493+
let els = null
2494+
2495+
if (selectLocator.isFuzzy()) {
2496+
els = await findByRole(this.page, { role: 'listbox', name: selectLocator.value })
2497+
if (!els || els.length === 0) {
2498+
els = await findByRole(this.page, { role: 'combobox', name: selectLocator.value })
2499+
}
2500+
}
2501+
2502+
if (!els || els.length === 0) {
2503+
els = await findFields.call(this, select)
2504+
}
24932505
assertElementExists(els, select, 'Selectable field')
24942506
const el = els[0]
24952507

@@ -2790,17 +2802,6 @@ class Playwright extends Helper {
27902802
*
27912803
*/
27922804
async grabTextFrom(locator) {
2793-
// Handle role locators with text/exact options
2794-
if (isRoleLocatorObject(locator)) {
2795-
const elements = await handleRoleLocator(this.page, locator)
2796-
if (elements && elements.length > 0) {
2797-
const text = await elements[0].textContent()
2798-
assertElementExists(text, JSON.stringify(locator))
2799-
this.debugSection('Text', text)
2800-
return text
2801-
}
2802-
}
2803-
28042805
const locatorObj = new Locator(locator, 'css')
28052806

28062807
if (locatorObj.isCustom()) {
@@ -2813,21 +2814,32 @@ class Playwright extends Helper {
28132814
assertElementExists(text, locatorObj.toString())
28142815
this.debugSection('Text', text)
28152816
return text
2816-
} else {
2817-
locator = this._contextLocator(locator)
2818-
try {
2819-
const text = await this.page.textContent(locator)
2820-
assertElementExists(text, locator)
2817+
}
2818+
2819+
if (locatorObj.isRole()) {
2820+
// Handle role locators with text/exact options
2821+
const roleElements = await findByRole(this.page, locator)
2822+
if (roleElements && roleElements.length > 0) {
2823+
const text = await roleElements[0].textContent()
2824+
assertElementExists(text, JSON.stringify(locator))
28212825
this.debugSection('Text', text)
28222826
return text
2823-
} catch (error) {
2824-
// Convert Playwright timeout errors to ElementNotFound for consistency
2825-
if (error.message && error.message.includes('Timeout')) {
2826-
throw new ElementNotFound(locator, 'text')
2827-
}
2828-
throw error
28292827
}
28302828
}
2829+
2830+
locator = this._contextLocator(locator)
2831+
try {
2832+
const text = await this.page.textContent(locator)
2833+
assertElementExists(text, locator)
2834+
this.debugSection('Text', text)
2835+
return text
2836+
} catch (error) {
2837+
// Convert Playwright timeout errors to ElementNotFound for consistency
2838+
if (error.message && error.message.includes('Timeout')) {
2839+
throw new ElementNotFound(locator, 'text')
2840+
}
2841+
throw error
2842+
}
28312843
}
28322844

28332845
/**
@@ -4306,50 +4318,26 @@ function buildLocatorString(locator) {
43064318
if (locator.isXPath()) {
43074319
return `xpath=${locator.value}`
43084320
}
4309-
return locator.simplify()
4321+
return locator.simplify()
43104322
}
43114323

4312-
/**
4313-
* Checks if a locator is a role locator object (e.g., {role: 'button', text: 'Submit', exact: true})
4314-
*/
4315-
function isRoleLocatorObject(locator) {
4316-
return locator && typeof locator === 'object' && locator.role && !locator.type
4317-
}
4318-
4319-
/**
4320-
* Handles role locator objects by converting them to Playwright's getByRole() API
4321-
* Returns elements array if role locator, null otherwise
4322-
*/
4323-
async function handleRoleLocator(context, locator) {
4324-
if (!isRoleLocatorObject(locator)) return null
4325-
4326-
const options = {}
4327-
if (locator.text) options.name = locator.text
4328-
if (locator.exact !== undefined) options.exact = locator.exact
4329-
4330-
return context.getByRole(locator.role, Object.keys(options).length > 0 ? options : undefined).all()
4324+
async function findByRole(context, locator) {
4325+
const matchedLocator = Locator.from(locator)
4326+
if (!matchedLocator.isRole()) return null
4327+
const roleOptions = matchedLocator.getRoleOptions()
4328+
return context.getByRole(roleOptions.role, roleOptions.options).all()
43314329
}
43324330

43334331
async function findElements(matcher, locator) {
4334-
// Check if locator is a Locator object with react/vue type, or a raw object with react/vue property
4335-
const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
4336-
const isVueLocator = locator.type === 'vue' || (locator.locator && locator.locator.vue) || locator.vue
4337-
const isPwLocator = locator.type === 'pw' || (locator.locator && locator.locator.pw) || locator.pw
4338-
4339-
if (isReactLocator) return findReact(matcher, locator)
4340-
if (isVueLocator) return findVue(matcher, locator)
4341-
if (isPwLocator) return findByPlaywrightLocator.call(this, matcher, locator)
4342-
4343-
// Handle role locators with text/exact options (e.g., {role: 'button', text: 'Submit', exact: true})
4344-
const roleElements = await handleRoleLocator(matcher, locator)
4332+
const matchedLocator = Locator.from(locator)
4333+
const roleElements = await findByRole(matcher, matchedLocator)
43454334
if (roleElements) return roleElements
43464335

4347-
locator = new Locator(locator, 'css')
4336+
const isReactLocator = matchedLocator.type === 'react'
4337+
const isVueLocator = matchedLocator.type === 'vue'
43484338

4349-
// Handle custom locators directly instead of relying on Playwright selector engines
4350-
if (locator.isCustom()) {
4351-
return findCustomElements.call(this, matcher, locator)
4352-
}
4339+
if (isReactLocator) return findReact(matcher, matchedLocator)
4340+
if (isVueLocator) return findVue(matcher, matchedLocator)
43534341

43544342
// Check if we have a custom context locator and need to search within it
43554343
if (this.contextLocator) {
@@ -4362,12 +4350,12 @@ async function findElements(matcher, locator) {
43624350
}
43634351

43644352
// Search within the first context element
4365-
const locatorString = buildLocatorString(locator)
4353+
const locatorString = buildLocatorString(matchedLocator)
43664354
return contextElements[0].locator(locatorString).all()
43674355
}
43684356
}
43694357

4370-
const locatorString = buildLocatorString(locator)
4358+
const locatorString = buildLocatorString(matchedLocator)
43714359

43724360
return matcher.locator(locatorString).all()
43734361
}
@@ -4460,13 +4448,17 @@ async function findCustomElements(matcher, locator) {
44604448
}
44614449

44624450
async function findElement(matcher, locator) {
4463-
if (locator.react) return findReact(matcher, locator)
4464-
if (locator.vue) return findVue(matcher, locator)
4465-
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
4451+
const matchedLocator = Locator.from(locator)
4452+
const roleElements = await findByRole(matcher, matchedLocator)
4453+
if (roleElements && roleElements.length > 0) return roleElements[0]
4454+
4455+
const isReactLocator = matchedLocator.type === 'react'
4456+
const isVueLocator = matchedLocator.type === 'vue'
44664457

4467-
locator = new Locator(locator, 'css')
4458+
if (isReactLocator) return findReact(matcher, matchedLocator)
4459+
if (isVueLocator) return findVue(matcher, matchedLocator)
44684460

4469-
return matcher.locator(buildLocatorString(locator)).first()
4461+
return matcher.locator(buildLocatorString(matchedLocator)).first()
44704462
}
44714463

44724464
async function getVisibleElements(elements) {
@@ -4518,8 +4510,14 @@ async function proceedClick(locator, context = null, options = {}) {
45184510
}
45194511

45204512
async function findClickable(matcher, locator) {
4513+
// Convert to Locator first to handle JSON strings properly
45214514
const matchedLocator = new Locator(locator)
45224515

4516+
// Handle role locators from Locator
4517+
if (matchedLocator.isRole()) {
4518+
return findByRole(matcher, matchedLocator)
4519+
}
4520+
45234521
if (!matchedLocator.isFuzzy()) return findElements.call(this, matcher, matchedLocator)
45244522

45254523
let els
@@ -4592,14 +4590,17 @@ async function findCheckable(locator, context) {
45924590
}
45934591

45944592
// Handle role locators with text/exact options
4595-
const roleElements = await handleRoleLocator(contextEl, locator)
4593+
const roleElements = await findByRole(contextEl, locator)
45964594
if (roleElements) return roleElements
45974595

4598-
const matchedLocator = new Locator(locator)
4596+
const matchedLocator = Locator.from(locator)
45994597
if (!matchedLocator.isFuzzy()) {
46004598
return findElements.call(this, contextEl, matchedLocator)
46014599
}
46024600

4601+
const checkboxByRole = await findByRole(contextEl, { role: 'checkbox', name: matchedLocator.value })
4602+
if (checkboxByRole) return checkboxByRole
4603+
46034604
const literal = xpathLocator.literal(matchedLocator.value)
46044605
let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal))
46054606
if (els.length) {
@@ -4621,17 +4622,15 @@ async function proceedIsChecked(assertType, option) {
46214622
}
46224623

46234624
async function findFields(locator) {
4624-
// Handle role locators with text/exact options
4625-
if (isRoleLocatorObject(locator)) {
4626-
const page = await this.page
4627-
const roleElements = await handleRoleLocator(page, locator)
4628-
if (roleElements) return roleElements
4629-
}
4625+
const page = await this.page
4626+
const roleElements = await findByRole(page, locator)
4627+
if (roleElements) return roleElements
46304628

46314629
const matchedLocator = new Locator(locator)
46324630
if (!matchedLocator.isFuzzy()) {
46334631
return this._locate(matchedLocator)
46344632
}
4633+
46354634
const literal = xpathLocator.literal(locator)
46364635

46374636
let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })

lib/helper/extras/PlaywrightLocator.js

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,20 @@ function buildLocatorString(locator) {
1111
}
1212

1313
async function findElements(matcher, locator) {
14-
const matchedLocator = new Locator(locator, 'css')
14+
const matchedLocator = Locator.from(locator, 'css')
1515

1616
if (matchedLocator.type === 'react') return findReact(matcher, matchedLocator)
1717
if (matchedLocator.type === 'vue') return findVue(matcher, matchedLocator)
18-
if (matchedLocator.type === 'pw') return findByPlaywrightLocator(matcher, matchedLocator)
1918
if (matchedLocator.isRole()) return findByRole(matcher, matchedLocator)
2019

2120
return matcher.locator(buildLocatorString(matchedLocator)).all()
2221
}
2322

2423
async function findElement(matcher, locator) {
25-
const matchedLocator = new Locator(locator, 'css')
24+
const matchedLocator = Locator.from(locator, 'css')
2625

2726
if (matchedLocator.type === 'react') return findReact(matcher, matchedLocator)
2827
if (matchedLocator.type === 'vue') return findVue(matcher, matchedLocator)
29-
if (matchedLocator.type === 'pw') return findByPlaywrightLocator(matcher, matchedLocator, { first: true })
3028
if (matchedLocator.isRole()) return findByRole(matcher, matchedLocator, { first: true })
3129

3230
return matcher.locator(buildLocatorString(matchedLocator)).first()
@@ -46,49 +44,30 @@ async function getVisibleElements(elements) {
4644
}
4745

4846
async function findReact(matcher, locator) {
49-
const details = locator.locator ?? { react: locator.value }
50-
let locatorString = `_react=${details.react}`
47+
const props = locator.locator?.props
48+
let locatorString = `_react=${locator.value}`
5149

52-
if (details.props) {
53-
locatorString += propBuilder(details.props)
50+
if (props) {
51+
locatorString += propBuilder(props)
5452
}
5553

5654
return matcher.locator(locatorString).all()
5755
}
5856

5957
async function findVue(matcher, locator) {
60-
const details = locator.locator ?? { vue: locator.value }
61-
let locatorString = `_vue=${details.vue}`
58+
const props = locator.locator?.props
59+
let locatorString = `_vue=${locator.value}`
6260

63-
if (details.props) {
64-
locatorString += propBuilder(details.props)
61+
if (props) {
62+
locatorString += propBuilder(props)
6563
}
6664

6765
return matcher.locator(locatorString).all()
6866
}
6967

70-
async function findByPlaywrightLocator(matcher, locator, { first = false } = {}) {
71-
const details = locator.locator ?? { pw: locator.value }
72-
const locatorValue = details.pw
73-
74-
const handle = matcher.locator(locatorValue)
75-
return first ? handle.first() : handle.all()
76-
}
77-
7868
async function findByRole(matcher, locator, { first = false } = {}) {
79-
const details = locator.locator ?? { role: locator.value }
80-
const { role, text, name, exact, includeHidden, ...rest } = details
81-
const options = { ...rest }
82-
83-
if (includeHidden !== undefined) options.includeHidden = includeHidden
84-
85-
const accessibleName = name ?? text
86-
if (accessibleName !== undefined) {
87-
options.name = accessibleName
88-
if (exact === true) options.exact = true
89-
}
90-
91-
const roleLocator = matcher.getByRole(role, options)
69+
const roleOptions = locator.getRoleOptions()
70+
const roleLocator = matcher.getByRole(roleOptions.role, roleOptions.options)
9271
return first ? roleLocator.first() : roleLocator.all()
9372
}
9473

@@ -107,4 +86,4 @@ function propBuilder(props) {
10786
return _props
10887
}
10988

110-
export { buildLocatorString, findElements, findElement, getVisibleElements, findReact, findVue, findByPlaywrightLocator, findByRole }
89+
export { buildLocatorString, findElements, findElement, getVisibleElements, findReact, findVue, findByRole }

lib/helper/extras/PlaywrightReactVueLocator.js

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)