@@ -30,7 +30,7 @@ import ElementNotFound from './errors/ElementNotFound.js'
3030import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
3131import Popup from './extras/Popup.js'
3232import 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
3535let playwright
3636let perfTiming
@@ -2068,7 +2068,7 @@ class Playwright extends Helper {
20682068 * ```
20692069 *
20702070 */
2071- async click ( locator , context = null , options = { } ) {
2071+ async click ( locator = '//body' , context = null , options = { } ) {
20722072 return proceedClick . call ( this , locator , context , options )
20732073 }
20742074
@@ -2666,17 +2666,28 @@ class Playwright extends Helper {
26662666 *
26672667 */
26682668 async grabTextFrom ( locator ) {
2669- locator = this . _contextLocator ( locator )
2669+ const originalLocator = locator
2670+ const matchedLocator = new Locator ( locator )
2671+
2672+ if ( ! matchedLocator . isFuzzy ( ) ) {
2673+ const els = await this . _locate ( matchedLocator )
2674+ assertElementExists ( els , locator )
2675+ const text = await els [ 0 ] . innerText ( )
2676+ this . debugSection ( 'Text' , text )
2677+ return text
2678+ }
2679+
2680+ const contextAwareLocator = this . _contextLocator ( matchedLocator . value )
26702681 let text
26712682 try {
2672- text = await this . page . textContent ( locator )
2683+ text = await this . page . textContent ( contextAwareLocator )
26732684 } catch ( err ) {
26742685 if ( err . message . includes ( 'Timeout' ) || err . message . includes ( 'exceeded' ) ) {
2675- throw new Error ( `Element ${ new Locator ( locator ) . toString ( ) } was not found by text|CSS|XPath` )
2686+ throw new Error ( `Element ${ new Locator ( originalLocator ) . toString ( ) } was not found by text|CSS|XPath` )
26762687 }
26772688 throw err
26782689 }
2679- assertElementExists ( text , locator )
2690+ assertElementExists ( text , contextAwareLocator )
26802691 this . debugSection ( 'Text' , text )
26812692 return text
26822693 }
@@ -2866,6 +2877,33 @@ class Playwright extends Helper {
28662877 return array
28672878 }
28682879
2880+ /**
2881+ * Retrieves the ARIA snapshot for an element using Playwright's [`locator.ariaSnapshot`](https://playwright.dev/docs/api/class-locator#locator-aria-snapshot).
2882+ * This method returns a YAML representation of the accessibility tree that can be used for assertions.
2883+ * If no locator is provided, it captures the snapshot of the entire page body.
2884+ *
2885+ * ```js
2886+ * const snapshot = await I.grabAriaSnapshot();
2887+ * expect(snapshot).toContain('heading "Sign up"');
2888+ *
2889+ * const formSnapshot = await I.grabAriaSnapshot('#login-form');
2890+ * expect(formSnapshot).toContain('textbox "Email"');
2891+ * ```
2892+ *
2893+ * [Learn more about ARIA snapshots](https://playwright.dev/docs/aria-snapshots)
2894+ *
2895+ * @param {string|object } [locator='//body'] element located by CSS|XPath|strict locator. Defaults to body element.
2896+ * @return {Promise<string> } YAML representation of the accessibility tree
2897+ */
2898+ async grabAriaSnapshot ( locator = '//body' ) {
2899+ const matchedLocator = new Locator ( locator )
2900+ const els = await this . _locate ( matchedLocator )
2901+ assertElementExists ( els , locator )
2902+ const snapshot = await els [ 0 ] . ariaSnapshot ( )
2903+ this . debugSection ( 'Aria Snapshot' , snapshot )
2904+ return snapshot
2905+ }
2906+
28692907 /**
28702908 * {{> saveElementScreenshot }}
28712909 *
@@ -4123,157 +4161,6 @@ class Playwright extends Helper {
41234161
41244162export default Playwright
41254163
4126- function buildCustomLocatorString ( locator ) {
4127- // Note: this.debug not available in standalone function, using console.log
4128- console . log ( `Building custom locator string: ${ locator . type } =${ locator . value } ` )
4129- return `${ locator . type } =${ locator . value } `
4130- }
4131-
4132- function buildLocatorString ( locator ) {
4133- if ( locator . isCustom ( ) ) {
4134- return buildCustomLocatorString ( locator )
4135- }
4136- if ( locator . isXPath ( ) ) {
4137- return `xpath=${ locator . value } `
4138- }
4139- return locator . simplify ( )
4140- }
4141-
4142- async function findElements ( matcher , locator ) {
4143- if ( locator . react ) return findReact ( matcher , locator )
4144- if ( locator . vue ) return findVue ( matcher , locator )
4145- if ( locator . pw ) return findByPlaywrightLocator . call ( this , matcher , locator )
4146-
4147- locator = new Locator ( locator , 'css' )
4148-
4149- // Handle custom locators directly instead of relying on Playwright selector engines
4150- if ( locator . isCustom ( ) ) {
4151- return findCustomElements . call ( this , matcher , locator )
4152- }
4153-
4154- // Check if we have a custom context locator and need to search within it
4155- if ( this . contextLocator ) {
4156- const contextLocatorObj = new Locator ( this . contextLocator , 'css' )
4157- if ( contextLocatorObj . isCustom ( ) ) {
4158- // Find the context elements first
4159- const contextElements = await findCustomElements . call ( this , matcher , contextLocatorObj )
4160- if ( contextElements . length === 0 ) {
4161- return [ ]
4162- }
4163-
4164- // Search within the first context element
4165- const locatorString = buildLocatorString ( locator )
4166- return contextElements [ 0 ] . locator ( locatorString ) . all ( )
4167- }
4168- }
4169-
4170- const locatorString = buildLocatorString ( locator )
4171-
4172- return matcher . locator ( locatorString ) . all ( )
4173- }
4174-
4175- async function findCustomElements ( matcher , locator ) {
4176- const customLocatorStrategies = this . customLocatorStrategies || globalCustomLocatorStrategies
4177- const strategyFunction = customLocatorStrategies . get ? customLocatorStrategies . get ( locator . type ) : customLocatorStrategies [ locator . type ]
4178-
4179- if ( ! strategyFunction ) {
4180- throw new Error ( `Custom locator strategy "${ locator . type } " is not defined. Please define "customLocatorStrategies" in your configuration.` )
4181- }
4182-
4183- // Execute the custom locator function in the browser context using page.evaluate
4184- const page = matcher . constructor . name === 'Page' ? matcher : await matcher . page ( )
4185-
4186- const elements = await page . evaluate (
4187- ( { strategyCode, selector } ) => {
4188- const strategy = new Function ( 'return ' + strategyCode ) ( )
4189- const result = strategy ( selector , document )
4190-
4191- // Convert NodeList or single element to array
4192- if ( result && result . nodeType ) {
4193- return [ result ]
4194- } else if ( result && result . length !== undefined ) {
4195- return Array . from ( result )
4196- } else if ( Array . isArray ( result ) ) {
4197- return result
4198- }
4199-
4200- return [ ]
4201- } ,
4202- {
4203- strategyCode : strategyFunction . toString ( ) ,
4204- selector : locator . value ,
4205- } ,
4206- )
4207-
4208- // Convert the found elements back to Playwright locators
4209- if ( elements . length === 0 ) {
4210- return [ ]
4211- }
4212-
4213- // Create CSS selectors for the found elements and return as locators
4214- const locators = [ ]
4215- const timestamp = Date . now ( )
4216-
4217- for ( let i = 0 ; i < elements . length ; i ++ ) {
4218- // Use a unique attribute approach to target specific elements
4219- const uniqueAttr = `data-codecept-custom-${ timestamp } -${ i } `
4220-
4221- await page . evaluate (
4222- ( { index, uniqueAttr, strategyCode, selector } ) => {
4223- // Re-execute the strategy to find elements and mark the specific one
4224- const strategy = new Function ( 'return ' + strategyCode ) ( )
4225- const result = strategy ( selector , document )
4226-
4227- let elementsArray = [ ]
4228- if ( result && result . nodeType ) {
4229- elementsArray = [ result ]
4230- } else if ( result && result . length !== undefined ) {
4231- elementsArray = Array . from ( result )
4232- } else if ( Array . isArray ( result ) ) {
4233- elementsArray = result
4234- }
4235-
4236- if ( elementsArray [ index ] ) {
4237- elementsArray [ index ] . setAttribute ( uniqueAttr , 'true' )
4238- }
4239- } ,
4240- {
4241- index : i ,
4242- uniqueAttr,
4243- strategyCode : strategyFunction . toString ( ) ,
4244- selector : locator . value ,
4245- } ,
4246- )
4247-
4248- locators . push ( page . locator ( `[${ uniqueAttr } ="true"]` ) )
4249- }
4250-
4251- return locators
4252- }
4253-
4254- async function findElement ( matcher , locator ) {
4255- if ( locator . react ) return findReact ( matcher , locator )
4256- if ( locator . vue ) return findVue ( matcher , locator )
4257- if ( locator . pw ) return findByPlaywrightLocator . call ( this , matcher , locator )
4258-
4259- locator = new Locator ( locator , 'css' )
4260-
4261- return matcher . locator ( buildLocatorString ( locator ) ) . first ( )
4262- }
4263-
4264- async function getVisibleElements ( elements ) {
4265- const visibleElements = [ ]
4266- for ( const element of elements ) {
4267- if ( await element . isVisible ( ) ) {
4268- visibleElements . push ( element )
4269- }
4270- }
4271- if ( visibleElements . length === 0 ) {
4272- return elements
4273- }
4274- return visibleElements
4275- }
4276-
42774164async function proceedClick ( locator , context = null , options = { } ) {
42784165 let matcher = await this . _getContext ( )
42794166 if ( context ) {
@@ -4310,15 +4197,26 @@ async function proceedClick(locator, context = null, options = {}) {
43104197}
43114198
43124199async function findClickable ( matcher , locator ) {
4313- if ( locator . react ) return findReact ( matcher , locator )
4314- if ( locator . vue ) return findVue ( matcher , locator )
4315- if ( locator . pw ) return findByPlaywrightLocator . call ( this , matcher , locator )
4200+ const matchedLocator = new Locator ( locator )
43164201
4317- locator = new Locator ( locator )
4318- if ( ! locator . isFuzzy ( ) ) return findElements . call ( this , matcher , locator )
4202+ if ( ! matchedLocator . isFuzzy ( ) ) return findElements . call ( this , matcher , matchedLocator )
43194203
43204204 let els
4321- const literal = xpathLocator . literal ( locator . value )
4205+ const literal = xpathLocator . literal ( matchedLocator . value )
4206+
4207+ try {
4208+ els = await matcher . getByRole ( 'button' , { name : matchedLocator . value } ) . all ( )
4209+ if ( els . length ) return els
4210+ } catch ( err ) {
4211+ // getByRole not supported or failed
4212+ }
4213+
4214+ try {
4215+ els = await matcher . getByRole ( 'link' , { name : matchedLocator . value } ) . all ( )
4216+ if ( els . length ) return els
4217+ } catch ( err ) {
4218+ // getByRole not supported or failed
4219+ }
43224220
43234221 els = await findElements . call ( this , matcher , Locator . clickable . narrow ( literal ) )
43244222 if ( els . length ) return els
@@ -4333,7 +4231,7 @@ async function findClickable(matcher, locator) {
43334231 // Do nothing
43344232 }
43354233
4336- return findElements . call ( this , matcher , locator . value ) // by css or xpath
4234+ return findElements . call ( this , matcher , matchedLocator . value ) // by css or xpath
43374235}
43384236
43394237async function proceedSee ( assertType , text , context , strict = false ) {
@@ -4374,10 +4272,10 @@ async function findCheckable(locator, context) {
43744272
43754273 const matchedLocator = new Locator ( locator )
43764274 if ( ! matchedLocator . isFuzzy ( ) ) {
4377- return findElements . call ( this , contextEl , matchedLocator . simplify ( ) )
4275+ return findElements . call ( this , contextEl , matchedLocator )
43784276 }
43794277
4380- const literal = xpathLocator . literal ( locator )
4278+ const literal = xpathLocator . literal ( matchedLocator . value )
43814279 let els = await findElements . call ( this , contextEl , Locator . checkable . byText ( literal ) )
43824280 if ( els . length ) {
43834281 return els
@@ -4386,7 +4284,7 @@ async function findCheckable(locator, context) {
43864284 if ( els . length ) {
43874285 return els
43884286 }
4389- return findElements . call ( this , contextEl , locator )
4287+ return findElements . call ( this , contextEl , matchedLocator . value )
43904288}
43914289
43924290async function proceedIsChecked ( assertType , option ) {
0 commit comments