@@ -78,7 +78,7 @@ const consoleLogStore = new Console();
78
78
* @prop {string } [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
79
79
* @prop {object } [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
80
80
* @prop {boolean } [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
81
- */
81
+ */
82
82
const config = { } ;
83
83
84
84
/**
@@ -369,7 +369,7 @@ class Puppeteer extends Helper {
369
369
this . debugSection ( 'Incognito Tab' , 'opened' ) ;
370
370
this . activeSessionName = name ;
371
371
372
- const bc = await this . browser . createIncognitoBrowserContext ( ) ;
372
+ const bc = await this . browser . createBrowserContext ( ) ;
373
373
await bc . newPage ( ) ;
374
374
375
375
// Create a new page inside context.
@@ -599,14 +599,7 @@ class Puppeteer extends Helper {
599
599
}
600
600
601
601
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 ( ) ;
610
603
return context . evaluateHandle ( ...args ) ;
611
604
}
612
605
@@ -884,7 +877,8 @@ class Puppeteer extends Helper {
884
877
* {{ react }}
885
878
*/
886
879
async _locate ( locator ) {
887
- return findElements ( await this . context , locator ) ;
880
+ const context = await this . context ;
881
+ return findElements . call ( this , context , locator ) ;
888
882
}
889
883
890
884
/**
@@ -910,7 +904,7 @@ class Puppeteer extends Helper {
910
904
* ```
911
905
*/
912
906
async _locateClickable ( locator ) {
913
- const context = await this . _getContext ( ) ;
907
+ const context = await this . context ;
914
908
return findClickable . call ( this , context , locator ) ;
915
909
}
916
910
@@ -1687,8 +1681,8 @@ class Puppeteer extends Helper {
1687
1681
* {{> executeScript }}
1688
1682
*/
1689
1683
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 ' ) {
1692
1686
context = this . context ; // switching to iframe context
1693
1687
}
1694
1688
return context . evaluate . apply ( context , args ) ;
@@ -1769,7 +1763,7 @@ class Puppeteer extends Helper {
1769
1763
*/
1770
1764
async grabHTMLFromAll ( locator ) {
1771
1765
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 ) ) ) ;
1773
1767
return values ;
1774
1768
}
1775
1769
@@ -1792,7 +1786,7 @@ class Puppeteer extends Helper {
1792
1786
*/
1793
1787
async grabCssPropertyFromAll ( locator , cssProperty ) {
1794
1788
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 ) ) ) ;
1796
1790
const cssValues = res . map ( props => props [ toCamelCase ( cssProperty ) ] ) ;
1797
1791
1798
1792
return cssValues ;
@@ -1854,35 +1848,34 @@ class Puppeteer extends Helper {
1854
1848
* {{ react }}
1855
1849
*/
1856
1850
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 ) ;
1859
1853
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 ;
1884
1863
} ) ;
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 ) ;
1886
1879
}
1887
1880
1888
1881
/**
@@ -2138,7 +2131,7 @@ class Puppeteer extends Helper {
2138
2131
if ( locator . isCSS ( ) ) {
2139
2132
waiter = context . waitForSelector ( locator . simplify ( ) , { timeout : waitTimeout } ) ;
2140
2133
} else {
2141
- waiter = context . waitForXPath ( locator . value , { timeout : waitTimeout } ) ;
2134
+ waiter = _waitForElement . call ( this , locator , { timeout : waitTimeout } ) ;
2142
2135
}
2143
2136
return waiter . catch ( ( err ) => {
2144
2137
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 {
2159
2152
if ( locator . isCSS ( ) ) {
2160
2153
waiter = context . waitForSelector ( locator . simplify ( ) , { timeout : waitTimeout , visible : true } ) ;
2161
2154
} else {
2162
- waiter = context . waitForXPath ( locator . value , { timeout : waitTimeout , visible : true } ) ;
2155
+ waiter = _waitForElement . call ( this , locator , { timeout : waitTimeout , visible : true } ) ;
2163
2156
}
2164
2157
return waiter . catch ( ( err ) => {
2165
2158
throw new Error ( `element (${ locator . toString ( ) } ) still not visible after ${ waitTimeout / 1000 } sec\n${ err . message } ` ) ;
@@ -2178,7 +2171,7 @@ class Puppeteer extends Helper {
2178
2171
if ( locator . isCSS ( ) ) {
2179
2172
waiter = context . waitForSelector ( locator . simplify ( ) , { timeout : waitTimeout , hidden : true } ) ;
2180
2173
} else {
2181
- waiter = context . waitForXPath ( locator . value , { timeout : waitTimeout , hidden : true } ) ;
2174
+ waiter = _waitForElement . call ( this , locator , { timeout : waitTimeout , hidden : true } ) ;
2182
2175
}
2183
2176
return waiter . catch ( ( err ) => {
2184
2177
throw new Error ( `element (${ locator . toString ( ) } ) still visible after ${ waitTimeout / 1000 } sec\n${ err . message } ` ) ;
@@ -2196,7 +2189,7 @@ class Puppeteer extends Helper {
2196
2189
if ( locator . isCSS ( ) ) {
2197
2190
waiter = context . waitForSelector ( locator . simplify ( ) , { timeout : waitTimeout , hidden : true } ) ;
2198
2191
} else {
2199
- waiter = context . waitForXPath ( locator . value , { timeout : waitTimeout , hidden : true } ) ;
2192
+ waiter = _waitForElement . call ( this , locator , { timeout : waitTimeout , hidden : true } ) ;
2200
2193
}
2201
2194
return waiter . catch ( ( err ) => {
2202
2195
throw new Error ( `element (${ locator . toString ( ) } ) still not hidden after ${ waitTimeout / 1000 } sec\n${ err . message } ` ) ;
@@ -2222,7 +2215,7 @@ class Puppeteer extends Helper {
2222
2215
}
2223
2216
2224
2217
async _getContext ( ) {
2225
- if ( this . context && this . context . constructor . name === 'Frame ' ) {
2218
+ if ( this . context && this . context . constructor . name === 'CdpFrame ' ) {
2226
2219
return this . context ;
2227
2220
}
2228
2221
return this . page ;
@@ -2345,35 +2338,37 @@ class Puppeteer extends Helper {
2345
2338
async switchTo ( locator ) {
2346
2339
if ( Number . isInteger ( locator ) ) {
2347
2340
// Select by frame index of current context
2348
-
2349
- let childFrames = null ;
2341
+ let frames = [ ] ;
2350
2342
if ( this . context && typeof this . context . childFrames === 'function' ) {
2351
- childFrames = this . context . childFrames ( ) ;
2343
+ frames = await this . context . childFrames ( ) ;
2352
2344
} else {
2353
- childFrames = this . page . mainFrame ( ) . childFrames ( ) ;
2345
+ frames = await this . page . mainFrame ( ) . childFrames ( ) ;
2354
2346
}
2355
2347
2356
- if ( locator >= 0 && locator < childFrames . length ) {
2357
- this . context = childFrames [ locator ] ;
2348
+ if ( locator >= 0 && locator < frames . length ) {
2349
+ this . context = frames [ locator ] ;
2358
2350
} else {
2359
- throw new Error ( 'Element #invalidIframeSelector was not found by text|CSS|XPath ' ) ;
2351
+ throw new Error ( 'Frame index out of bounds ' ) ;
2360
2352
}
2361
2353
return ;
2362
2354
}
2355
+
2363
2356
if ( ! locator ) {
2364
- this . context = await this . page . mainFrame ( ) . $ ( 'body' ) ;
2357
+ this . context = await this . page . mainFrame ( ) ;
2365
2358
return ;
2366
2359
}
2367
2360
2368
- // iframe by selector
2361
+ // Select iframe by selector
2369
2362
const els = await this . _locate ( locator ) ;
2370
2363
assertElementExists ( els , locator ) ;
2371
- const contentFrame = await els [ 0 ] . contentFrame ( ) ;
2364
+
2365
+ const iframeElement = els [ 0 ] ;
2366
+ const contentFrame = await iframeElement . contentFrame ( ) ;
2372
2367
2373
2368
if ( contentFrame ) {
2374
2369
this . context = contentFrame ;
2375
2370
} else {
2376
- this . context = els [ 0 ] ;
2371
+ throw new Error ( 'Element "#invalidIframeSelector" was not found by text|CSS|XPath' ) ;
2377
2372
}
2378
2373
}
2379
2374
@@ -2469,7 +2464,7 @@ class Puppeteer extends Helper {
2469
2464
module . exports = Puppeteer ;
2470
2465
2471
2466
async function findElements ( matcher , locator ) {
2472
- if ( locator . react ) return findReact ( matcher . executionContext ( ) , locator ) ;
2467
+ if ( locator . react ) return findReactElements . call ( this , locator ) ;
2473
2468
locator = new Locator ( locator , 'css' ) ;
2474
2469
if ( ! locator . isXPath ( ) ) return matcher . $$ ( locator . simplify ( ) ) ;
2475
2470
// 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 = {}) {
2506
2501
}
2507
2502
2508
2503
async function findClickable ( matcher , locator ) {
2509
- if ( locator . react ) return findReact ( matcher . executionContext ( ) , locator ) ;
2504
+ if ( locator . react ) return findReactElements . call ( this , locator ) ;
2510
2505
locator = new Locator ( locator ) ;
2511
2506
if ( ! locator . isFuzzy ( ) ) return findElements . call ( this , matcher , locator ) ;
2512
2507
@@ -2855,3 +2850,70 @@ function highlightActiveElement(element, context) {
2855
2850
highlightElement ( element , context ) ;
2856
2851
}
2857
2852
}
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