@@ -78,7 +78,7 @@ const consoleLogStore = new Console();
7878 * @prop {string } [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
7979 * @prop {object } [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
8080 * @prop {boolean } [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
81- */
81+ */
8282const config = { } ;
8383
8484/**
@@ -369,7 +369,7 @@ class Puppeteer extends Helper {
369369 this . debugSection ( 'Incognito Tab' , 'opened' ) ;
370370 this . activeSessionName = name ;
371371
372- const bc = await this . browser . createIncognitoBrowserContext ( ) ;
372+ const bc = await this . browser . createBrowserContext ( ) ;
373373 await bc . newPage ( ) ;
374374
375375 // Create a new page inside context.
@@ -599,14 +599,7 @@ class Puppeteer extends Helper {
599599 }
600600
601601 async _evaluateHandeInContext ( ...args ) {
602- let context = await this . _getContext ( ) ;
603-
604- if ( context . constructor . name === 'Frame' ) {
605- // Currently there is no evalateHandle for the Frame object
606- // https://github.com/GoogleChrome/puppeteer/issues/1051
607- context = await context . executionContext ( ) ;
608- }
609-
602+ const context = await this . _getContext ( ) ;
610603 return context . evaluateHandle ( ...args ) ;
611604 }
612605
@@ -884,7 +877,8 @@ class Puppeteer extends Helper {
884877 * {{ react }}
885878 */
886879 async _locate ( locator ) {
887- return findElements ( await this . context , locator ) ;
880+ const context = await this . context ;
881+ return findElements . call ( this , context , locator ) ;
888882 }
889883
890884 /**
@@ -910,7 +904,7 @@ class Puppeteer extends Helper {
910904 * ```
911905 */
912906 async _locateClickable ( locator ) {
913- const context = await this . _getContext ( ) ;
907+ const context = await this . context ;
914908 return findClickable . call ( this , context , locator ) ;
915909 }
916910
@@ -1687,8 +1681,8 @@ class Puppeteer extends Helper {
16871681 * {{> executeScript }}
16881682 */
16891683 async executeScript ( ...args ) {
1690- let context = this . page ;
1691- if ( this . context && this . context . constructor . name === 'Frame ' ) {
1684+ let context = await this . _getContext ( ) ;
1685+ if ( this . context && this . context . constructor . name === 'CdpFrame ' ) {
16921686 context = this . context ; // switching to iframe context
16931687 }
16941688 return context . evaluate . apply ( context , args ) ;
@@ -1769,7 +1763,7 @@ class Puppeteer extends Helper {
17691763 */
17701764 async grabHTMLFromAll ( locator ) {
17711765 const els = await this . _locate ( locator ) ;
1772- const values = await Promise . all ( els . map ( el => el . executionContext ( ) . evaluate ( element => element . innerHTML , el ) ) ) ;
1766+ const values = await Promise . all ( els . map ( el => el . evaluate ( element => element . innerHTML , el ) ) ) ;
17731767 return values ;
17741768 }
17751769
@@ -1792,7 +1786,7 @@ class Puppeteer extends Helper {
17921786 */
17931787 async grabCssPropertyFromAll ( locator , cssProperty ) {
17941788 const els = await this . _locate ( locator ) ;
1795- const res = await Promise . all ( els . map ( el => el . executionContext ( ) . evaluate ( el => JSON . parse ( JSON . stringify ( getComputedStyle ( el ) ) ) , el ) ) ) ;
1789+ const res = await Promise . all ( els . map ( el => el . evaluate ( el => JSON . parse ( JSON . stringify ( getComputedStyle ( el ) ) ) , el ) ) ) ;
17961790 const cssValues = res . map ( props => props [ toCamelCase ( cssProperty ) ] ) ;
17971791
17981792 return cssValues ;
@@ -1854,35 +1848,34 @@ class Puppeteer extends Helper {
18541848 * {{ react }}
18551849 */
18561850 async seeAttributesOnElements ( locator , attributes ) {
1857- const res = await this . _locate ( locator ) ;
1858- assertElementExists ( res , locator ) ;
1851+ const elements = await this . _locate ( locator ) ;
1852+ assertElementExists ( elements , locator ) ;
18591853
1860- const elemAmount = res . length ;
1861- const commands = [ ] ;
1862- res . forEach ( ( el ) => {
1863- Object . keys ( attributes ) . forEach ( ( prop ) => {
1864- commands . push ( el
1865- . executionContext ( )
1866- . evaluateHandle ( ( el , attr ) => el [ attr ] || el . getAttribute ( attr ) , el , prop )
1867- . then ( el => el . jsonValue ( ) ) ) ;
1868- } ) ;
1869- } ) ;
1870- let attrs = await Promise . all ( commands ) ;
1871- const values = Object . keys ( attributes ) . map ( key => attributes [ key ] ) ;
1872- if ( ! Array . isArray ( attrs ) ) attrs = [ attrs ] ;
1873- let chunked = chunkArray ( attrs , values . length ) ;
1874- chunked = chunked . filter ( ( val ) => {
1875- for ( let i = 0 ; i < val . length ; ++ i ) {
1876- const _actual = Number . isNaN ( val [ i ] ) || ( typeof values [ i ] ) === 'string' ? val [ i ] : Number . parseInt ( values [ i ] , 10 ) ;
1877- const _expected = Number . isNaN ( values [ i ] ) || ( typeof values [ i ] ) === 'string' ? values [ i ] : Number . parseInt ( values [ i ] , 10 ) ;
1878- // the attribute could be a boolean
1879- if ( typeof _actual === 'boolean' ) return _actual === _expected ;
1880- // if the attribute doesn't exist, returns false as well
1881- if ( ! _actual || ! _actual . includes ( _expected ) ) return false ;
1882- }
1883- return true ;
1854+ const expectedAttributes = Object . entries ( attributes ) ;
1855+
1856+ const valuesPromises = elements . map ( async ( element ) => {
1857+ const elementAttributes = { } ;
1858+ await Promise . all ( expectedAttributes . map ( async ( [ attribute , expectedValue ] ) => {
1859+ const actualValue = await element . evaluate ( ( el , attr ) => el [ attr ] || el . getAttribute ( attr ) , attribute ) ;
1860+ elementAttributes [ attribute ] = actualValue ;
1861+ } ) ) ;
1862+ return elementAttributes ;
18841863 } ) ;
1885- return equals ( `all elements (${ ( new Locator ( locator ) ) } ) to have attributes ${ JSON . stringify ( attributes ) } ` ) . assert ( chunked . length , elemAmount ) ;
1864+
1865+ const actualAttributes = await Promise . all ( valuesPromises ) ;
1866+
1867+ const matchingElements = actualAttributes . filter ( ( attrs ) => expectedAttributes . every ( ( [ attribute , expectedValue ] ) => {
1868+ const actualValue = attrs [ attribute ] ;
1869+ if ( ! actualValue ) return false ;
1870+ if ( actualValue . toString ( ) . match ( new RegExp ( expectedValue . toString ( ) ) ) ) return true ;
1871+ return expectedValue === actualValue ;
1872+ } ) ) ;
1873+
1874+ const elementsCount = elements . length ;
1875+ const matchingCount = matchingElements . length ;
1876+
1877+ return equals ( `all elements (${ ( new Locator ( locator ) ) } ) to have attributes ${ JSON . stringify ( attributes ) } ` )
1878+ . assert ( matchingCount , elementsCount ) ;
18861879 }
18871880
18881881 /**
@@ -2138,7 +2131,7 @@ class Puppeteer extends Helper {
21382131 if ( locator . isCSS ( ) ) {
21392132 waiter = context . waitForSelector ( locator . simplify ( ) , { timeout : waitTimeout } ) ;
21402133 } else {
2141- waiter = context . waitForXPath ( locator . value , { timeout : waitTimeout } ) ;
2134+ waiter = _waitForElement . call ( this , locator , { timeout : waitTimeout } ) ;
21422135 }
21432136 return waiter . catch ( ( err ) => {
21442137 throw new Error ( `element (${ locator . toString ( ) } ) still not present on page after ${ waitTimeout / 1000 } sec\n${ err . message } ` ) ;
@@ -2159,7 +2152,7 @@ class Puppeteer extends Helper {
21592152 if ( locator . isCSS ( ) ) {
21602153 waiter = context . waitForSelector ( locator . simplify ( ) , { timeout : waitTimeout , visible : true } ) ;
21612154 } else {
2162- waiter = context . waitForXPath ( locator . value , { timeout : waitTimeout , visible : true } ) ;
2155+ waiter = _waitForElement . call ( this , locator , { timeout : waitTimeout , visible : true } ) ;
21632156 }
21642157 return waiter . catch ( ( err ) => {
21652158 throw new Error ( `element (${ locator . toString ( ) } ) still not visible after ${ waitTimeout / 1000 } sec\n${ err . message } ` ) ;
@@ -2178,7 +2171,7 @@ class Puppeteer extends Helper {
21782171 if ( locator . isCSS ( ) ) {
21792172 waiter = context . waitForSelector ( locator . simplify ( ) , { timeout : waitTimeout , hidden : true } ) ;
21802173 } else {
2181- waiter = context . waitForXPath ( locator . value , { timeout : waitTimeout , hidden : true } ) ;
2174+ waiter = _waitForElement . call ( this , locator , { timeout : waitTimeout , hidden : true } ) ;
21822175 }
21832176 return waiter . catch ( ( err ) => {
21842177 throw new Error ( `element (${ locator . toString ( ) } ) still visible after ${ waitTimeout / 1000 } sec\n${ err . message } ` ) ;
@@ -2196,7 +2189,7 @@ class Puppeteer extends Helper {
21962189 if ( locator . isCSS ( ) ) {
21972190 waiter = context . waitForSelector ( locator . simplify ( ) , { timeout : waitTimeout , hidden : true } ) ;
21982191 } else {
2199- waiter = context . waitForXPath ( locator . value , { timeout : waitTimeout , hidden : true } ) ;
2192+ waiter = _waitForElement . call ( this , locator , { timeout : waitTimeout , hidden : true } ) ;
22002193 }
22012194 return waiter . catch ( ( err ) => {
22022195 throw new Error ( `element (${ locator . toString ( ) } ) still not hidden after ${ waitTimeout / 1000 } sec\n${ err . message } ` ) ;
@@ -2222,7 +2215,7 @@ class Puppeteer extends Helper {
22222215 }
22232216
22242217 async _getContext ( ) {
2225- if ( this . context && this . context . constructor . name === 'Frame ' ) {
2218+ if ( this . context && this . context . constructor . name === 'CdpFrame ' ) {
22262219 return this . context ;
22272220 }
22282221 return this . page ;
@@ -2345,35 +2338,37 @@ class Puppeteer extends Helper {
23452338 async switchTo ( locator ) {
23462339 if ( Number . isInteger ( locator ) ) {
23472340 // Select by frame index of current context
2348-
2349- let childFrames = null ;
2341+ let frames = [ ] ;
23502342 if ( this . context && typeof this . context . childFrames === 'function' ) {
2351- childFrames = this . context . childFrames ( ) ;
2343+ frames = await this . context . childFrames ( ) ;
23522344 } else {
2353- childFrames = this . page . mainFrame ( ) . childFrames ( ) ;
2345+ frames = await this . page . mainFrame ( ) . childFrames ( ) ;
23542346 }
23552347
2356- if ( locator >= 0 && locator < childFrames . length ) {
2357- this . context = childFrames [ locator ] ;
2348+ if ( locator >= 0 && locator < frames . length ) {
2349+ this . context = frames [ locator ] ;
23582350 } else {
2359- throw new Error ( 'Element #invalidIframeSelector was not found by text|CSS|XPath ' ) ;
2351+ throw new Error ( 'Frame index out of bounds ' ) ;
23602352 }
23612353 return ;
23622354 }
2355+
23632356 if ( ! locator ) {
2364- this . context = await this . page . mainFrame ( ) . $ ( 'body' ) ;
2357+ this . context = await this . page . mainFrame ( ) ;
23652358 return ;
23662359 }
23672360
2368- // iframe by selector
2361+ // Select iframe by selector
23692362 const els = await this . _locate ( locator ) ;
23702363 assertElementExists ( els , locator ) ;
2371- const contentFrame = await els [ 0 ] . contentFrame ( ) ;
2364+
2365+ const iframeElement = els [ 0 ] ;
2366+ const contentFrame = await iframeElement . contentFrame ( ) ;
23722367
23732368 if ( contentFrame ) {
23742369 this . context = contentFrame ;
23752370 } else {
2376- this . context = els [ 0 ] ;
2371+ throw new Error ( 'Element "#invalidIframeSelector" was not found by text|CSS|XPath' ) ;
23772372 }
23782373 }
23792374
@@ -2469,7 +2464,7 @@ class Puppeteer extends Helper {
24692464module . exports = Puppeteer ;
24702465
24712466async function findElements ( matcher , locator ) {
2472- if ( locator . react ) return findReact ( matcher . executionContext ( ) , locator ) ;
2467+ if ( locator . react ) return findReactElements . call ( this , locator ) ;
24732468 locator = new Locator ( locator , 'css' ) ;
24742469 if ( ! locator . isXPath ( ) ) return matcher . $$ ( locator . simplify ( ) ) ;
24752470 // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
@@ -2506,7 +2501,7 @@ async function proceedClick(locator, context = null, options = {}) {
25062501}
25072502
25082503async function findClickable ( matcher , locator ) {
2509- if ( locator . react ) return findReact ( matcher . executionContext ( ) , locator ) ;
2504+ if ( locator . react ) return findReactElements . call ( this , locator ) ;
25102505 locator = new Locator ( locator ) ;
25112506 if ( ! locator . isFuzzy ( ) ) return findElements . call ( this , matcher , locator ) ;
25122507
@@ -2855,3 +2850,70 @@ function highlightActiveElement(element, context) {
28552850 highlightElement ( element , context ) ;
28562851 }
28572852}
2853+
2854+ function _waitForElement ( locator , options ) {
2855+ try {
2856+ return this . context . waitForXPath ( locator . value , options ) ;
2857+ } catch ( e ) {
2858+ return this . context . waitForSelector ( `::-p-xpath(${ locator . value } )` , options ) ;
2859+ }
2860+ }
2861+
2862+ async function findReactElements ( locator , props = { } , state = { } ) {
2863+ const resqScript = await fs . promises . readFile ( require . resolve ( 'resq' ) , 'utf-8' ) ;
2864+ await this . page . evaluate ( resqScript . toString ( ) ) ;
2865+
2866+ await this . page . evaluate ( ( ) => window . resq . waitToLoadReact ( ) ) ;
2867+ const arrayHandle = await this . page . evaluateHandle ( ( obj ) => {
2868+ const { selector, props, state } = obj ;
2869+ let elements = window . resq . resq$$ ( selector ) ;
2870+ if ( Object . keys ( props ) . length ) {
2871+ elements = elements . byProps ( props ) ;
2872+ }
2873+ if ( Object . keys ( state ) . length ) {
2874+ elements = elements . byState ( state ) ;
2875+ }
2876+
2877+ if ( ! elements . length ) {
2878+ return [ ] ;
2879+ }
2880+
2881+ // resq returns an array of HTMLElements if the React component is a fragment
2882+ // this avoids having nested arrays of nodes which the driver does not understand
2883+ // [[div, div], [div, div]] => [div, div, div, div]
2884+ let nodes = [ ] ;
2885+
2886+ elements . forEach ( ( element ) => {
2887+ let { node, isFragment } = element ;
2888+
2889+ if ( ! node ) {
2890+ isFragment = true ;
2891+ node = element . children ;
2892+ }
2893+
2894+ if ( isFragment ) {
2895+ nodes = nodes . concat ( node ) ;
2896+ } else {
2897+ nodes . push ( node ) ;
2898+ }
2899+ } ) ;
2900+
2901+ return [ ...nodes ] ;
2902+ } , {
2903+ selector : locator . react ,
2904+ props : locator . props || { } ,
2905+ state : locator . state || { } ,
2906+ } ) ;
2907+
2908+ const properties = await arrayHandle . getProperties ( ) ;
2909+ const result = [ ] ;
2910+ for ( const property of properties . values ( ) ) {
2911+ const elementHandle = property . asElement ( ) ;
2912+ if ( elementHandle ) {
2913+ result . push ( elementHandle ) ;
2914+ }
2915+ }
2916+
2917+ await arrayHandle . dispose ( ) ;
2918+ return result ;
2919+ }
0 commit comments