2020-08-04 15:09:24 -07:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
* Modifications copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2020-08-19 21:32:12 -07:00
2021-05-06 07:08:22 -07:00
import { test as it , expect } from './pageTest' ;
2021-10-11 10:52:17 -04:00
import type { ElementHandle } from 'playwright-core' ;
2020-08-04 15:09:24 -07:00
2022-03-10 19:42:52 +01:00
it ( 'exposeBinding should work @smoke' , async ( { page } ) = > {
2020-08-04 15:09:24 -07:00
let bindingSource ;
await page . exposeBinding ( 'add' , ( source , a , b ) = > {
bindingSource = source ;
return a + b ;
} ) ;
const result = await page . evaluate ( async function ( ) {
2020-08-11 15:50:53 -07:00
return window [ 'add' ] ( 5 , 6 ) ;
2020-08-04 15:09:24 -07:00
} ) ;
2020-12-16 16:28:44 +01:00
expect ( bindingSource . context ) . toBe ( page . context ( ) ) ;
2020-08-04 15:09:24 -07:00
expect ( bindingSource . page ) . toBe ( page ) ;
expect ( bindingSource . frame ) . toBe ( page . mainFrame ( ) ) ;
expect ( result ) . toEqual ( 11 ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should work' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . exposeFunction ( 'compute' , function ( a , b ) {
return a * b ;
} ) ;
const result = await page . evaluate ( async function ( ) {
2020-08-11 15:50:53 -07:00
return await window [ 'compute' ] ( 9 , 4 ) ;
2020-08-04 15:09:24 -07:00
} ) ;
expect ( result ) . toBe ( 36 ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should work with handles and complex objects' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
const fooHandle = await page . evaluateHandle ( ( ) = > {
2020-08-11 15:50:53 -07:00
window [ 'fooValue' ] = { bar : 2 } ;
return window [ 'fooValue' ] ;
2020-08-04 15:09:24 -07:00
} ) ;
await page . exposeFunction ( 'handle' , ( ) = > {
return [ { foo : fooHandle } ] ;
} ) ;
const equals = await page . evaluate ( async function ( ) {
2020-08-11 15:50:53 -07:00
const value = await window [ 'handle' ] ( ) ;
2020-08-04 15:09:24 -07:00
const [ { foo } ] = value ;
2020-08-11 15:50:53 -07:00
return foo === window [ 'fooValue' ] ;
2020-08-04 15:09:24 -07:00
} ) ;
expect ( equals ) . toBe ( true ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should throw exception in page context' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . exposeFunction ( 'woof' , function ( ) {
throw new Error ( 'WOOF WOOF' ) ;
} ) ;
2021-09-27 18:58:08 +02:00
const { message , stack } = await page . evaluate ( async ( ) = > {
2020-08-04 15:09:24 -07:00
try {
2020-08-28 04:20:29 -07:00
await window [ 'woof' ] ( ) ;
2020-08-04 15:09:24 -07:00
} catch ( e ) {
2021-09-27 18:58:08 +02:00
return { message : e.message , stack : e.stack } ;
2020-08-04 15:09:24 -07:00
}
} ) ;
expect ( message ) . toBe ( 'WOOF WOOF' ) ;
expect ( stack ) . toContain ( __filename ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should support throwing "null"' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . exposeFunction ( 'woof' , function ( ) {
throw null ;
} ) ;
2020-08-28 04:20:29 -07:00
const thrown = await page . evaluate ( async ( ) = > {
2020-08-04 15:09:24 -07:00
try {
2020-08-28 04:20:29 -07:00
await window [ 'woof' ] ( ) ;
2020-08-04 15:09:24 -07:00
} catch ( e ) {
return e ;
}
} ) ;
expect ( thrown ) . toBe ( null ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should be callable from-inside addInitScript' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
let called = false ;
await page . exposeFunction ( 'woof' , function ( ) {
called = true ;
} ) ;
2020-08-28 04:20:29 -07:00
await page . addInitScript ( ( ) = > window [ 'woof' ] ( ) ) ;
2020-08-04 15:09:24 -07:00
await page . reload ( ) ;
2025-05-01 14:14:17 +00:00
await expect . poll ( ( ) = > called ) . toBe ( true ) ;
2020-08-04 15:09:24 -07:00
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should survive navigation' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . exposeFunction ( 'compute' , function ( a , b ) {
return a * b ;
} ) ;
await page . goto ( server . EMPTY_PAGE ) ;
const result = await page . evaluate ( async function ( ) {
2020-08-11 15:50:53 -07:00
return await window [ 'compute' ] ( 9 , 4 ) ;
2020-08-04 15:09:24 -07:00
} ) ;
expect ( result ) . toBe ( 36 ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should await returned promise' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . exposeFunction ( 'compute' , function ( a , b ) {
return Promise . resolve ( a * b ) ;
} ) ;
const result = await page . evaluate ( async function ( ) {
2020-08-11 15:50:53 -07:00
return await window [ 'compute' ] ( 3 , 5 ) ;
2020-08-04 15:09:24 -07:00
} ) ;
expect ( result ) . toBe ( 15 ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should work on frames' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . exposeFunction ( 'compute' , function ( a , b ) {
return Promise . resolve ( a * b ) ;
} ) ;
await page . goto ( server . PREFIX + '/frames/nested-frames.html' ) ;
const frame = page . frames ( ) [ 1 ] ;
const result = await frame . evaluate ( async function ( ) {
2020-08-11 15:50:53 -07:00
return await window [ 'compute' ] ( 3 , 5 ) ;
2020-08-04 15:09:24 -07:00
} ) ;
expect ( result ) . toBe ( 15 ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should work on frames before navigation' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . goto ( server . PREFIX + '/frames/nested-frames.html' ) ;
await page . exposeFunction ( 'compute' , function ( a , b ) {
return Promise . resolve ( a * b ) ;
} ) ;
const frame = page . frames ( ) [ 1 ] ;
const result = await frame . evaluate ( async function ( ) {
2020-08-11 15:50:53 -07:00
return await window [ 'compute' ] ( 3 , 5 ) ;
2020-08-04 15:09:24 -07:00
} ) ;
expect ( result ) . toBe ( 15 ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should work after cross origin navigation' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . goto ( server . EMPTY_PAGE ) ;
await page . exposeFunction ( 'compute' , function ( a , b ) {
return a * b ;
} ) ;
await page . goto ( server . CROSS_PROCESS_PREFIX + '/empty.html' ) ;
const result = await page . evaluate ( async function ( ) {
2020-08-11 15:50:53 -07:00
return await window [ 'compute' ] ( 9 , 4 ) ;
2020-08-04 15:09:24 -07:00
} ) ;
expect ( result ) . toBe ( 36 ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should work with complex objects' , async ( { page , server } ) = > {
2020-08-04 15:09:24 -07:00
await page . exposeFunction ( 'complexObject' , function ( a , b ) {
2021-09-27 18:58:08 +02:00
return { x : a.x + b . x } ;
2020-08-04 15:09:24 -07:00
} ) ;
2021-09-27 18:58:08 +02:00
const result = await page . evaluate ( async ( ) = > window [ 'complexObject' ] ( { x : 5 } , { x : 2 } ) ) ;
2020-08-04 15:09:24 -07:00
expect ( result . x ) . toBe ( 7 ) ;
} ) ;
2020-10-01 22:47:31 -07:00
2021-09-27 18:58:08 +02:00
it ( 'exposeBindingHandle should work' , async ( { page } ) = > {
2020-10-01 22:47:31 -07:00
let target ;
await page . exposeBinding ( 'logme' , ( source , t ) = > {
target = t ;
return 17 ;
} , { handle : true } ) ;
const result = await page . evaluate ( async function ( ) {
return window [ 'logme' ] ( { foo : 42 } ) ;
} ) ;
expect ( await target . evaluate ( x = > x . foo ) ) . toBe ( 42 ) ;
expect ( result ) . toEqual ( 17 ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'exposeBindingHandle should not throw during navigation' , async ( { page , server } ) = > {
2020-10-01 22:47:31 -07:00
await page . exposeBinding ( 'logme' , ( source , t ) = > {
return 17 ;
} , { handle : true } ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await Promise . all ( [
page . evaluate ( async url = > {
window [ 'logme' ] ( { foo : 42 } ) ;
window . location . href = url ;
} , server . PREFIX + '/one-style.html' ) ,
page . waitForNavigation ( { waitUntil : 'load' } ) ,
] ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should throw for duplicate registrations' , async ( { page } ) = > {
2020-10-01 22:47:31 -07:00
await page . exposeFunction ( 'foo' , ( ) = > { } ) ;
const error = await page . exposeFunction ( 'foo' , ( ) = > { } ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( 'page.exposeFunction: Function "foo" has been already registered' ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'exposeBindingHandle should throw for multiple arguments' , async ( { page } ) = > {
2020-10-01 22:47:31 -07:00
await page . exposeBinding ( 'logme' , ( source , t ) = > {
return 17 ;
} , { handle : true } ) ;
expect ( await page . evaluate ( async function ( ) {
return window [ 'logme' ] ( { foo : 42 } ) ;
} ) ) . toBe ( 17 ) ;
expect ( await page . evaluate ( async function ( ) {
return window [ 'logme' ] ( { foo : 42 } , undefined , undefined ) ;
} ) ) . toBe ( 17 ) ;
expect ( await page . evaluate ( async function ( ) {
return window [ 'logme' ] ( undefined , undefined , undefined ) ;
} ) ) . toBe ( 17 ) ;
const error = await page . evaluate ( async function ( ) {
return window [ 'logme' ] ( 1 , 2 ) ;
} ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( 'exposeBindingHandle supports a single argument, 2 received' ) ;
} ) ;
2020-12-03 10:51:59 -08:00
2022-09-07 20:09:22 +02:00
it ( 'should not result in unhandled rejection' , async ( { page , isAndroid , isWebView2 } ) = > {
2021-04-09 07:59:09 -07:00
it . fixme ( isAndroid ) ;
2022-09-07 20:09:22 +02:00
it . skip ( isWebView2 , 'Page.close() is not supported in WebView2' ) ;
2021-04-04 19:32:14 -07:00
2020-12-04 14:48:20 -08:00
const closedPromise = page . waitForEvent ( 'close' ) ;
2020-12-03 10:51:59 -08:00
await page . exposeFunction ( 'foo' , async ( ) = > {
await page . close ( ) ;
} ) ;
await page . evaluate ( ( ) = > {
2025-03-25 13:49:28 +00:00
window . builtins . setTimeout ( ( ) = > ( window as any ) . foo ( ) , 0 ) ;
2020-12-04 14:48:20 -08:00
return undefined ;
2020-12-03 10:51:59 -08:00
} ) ;
2020-12-04 14:48:20 -08:00
await closedPromise ;
// Make a round-trip to be sure we did not throw immediately after closing.
expect ( await page . evaluate ( '1 + 1' ) . catch ( e = > e ) ) . toBeInstanceOf ( Error ) ;
2020-12-03 10:51:59 -08:00
} ) ;
2020-12-08 08:38:29 -08:00
2021-09-27 18:58:08 +02:00
it ( 'exposeBinding(handle) should work with element handles' , async ( { page } ) = > {
2021-01-04 13:54:55 -08:00
let cb ;
const promise = new Promise ( f = > cb = f ) ;
await page . exposeBinding ( 'clicked' , async ( source , element : ElementHandle ) = > {
cb ( await element . innerText ( ) . catch ( e = > e ) ) ;
} , { handle : true } ) ;
await page . goto ( 'about:blank' ) ;
await page . setContent ( `
< script >
document . addEventListener ( 'click' , event = > window . clicked ( event . target ) ) ;
< / script >
< div id = "a1" > Click me < / div >
` );
await page . click ( '#a1' ) ;
expect ( await promise ) . toBe ( 'Click me' ) ;
} ) ;
2021-02-26 14:16:32 -08:00
2021-09-27 18:58:08 +02:00
it ( 'should work with setContent' , async ( { page , server } ) = > {
2021-02-26 14:16:32 -08:00
await page . exposeFunction ( 'compute' , function ( a , b ) {
return Promise . resolve ( a * b ) ;
} ) ;
await page . setContent ( '<script>window.result = compute(3, 2)</script>' ) ;
expect ( await page . evaluate ( 'window.result' ) ) . toBe ( 6 ) ;
} ) ;
2022-05-09 06:44:20 -08:00
2022-05-09 14:07:04 -08:00
it ( 'should alias Window, Document and Node' , async ( { page } ) = > {
let object : any ;
await page . exposeBinding ( 'log' , ( source , obj ) = > object = obj ) ;
await page . evaluate ( 'window.log([window, document, document.body])' ) ;
expect ( object ) . toEqual ( [ 'ref: <Window>' , 'ref: <Document>' , 'ref: <Node>' ] ) ;
} ) ;
2022-05-09 17:51:53 -08:00
it ( 'should serialize cycles' , async ( { page } ) = > {
2022-05-09 14:07:04 -08:00
let object : any ;
await page . exposeBinding ( 'log' , ( source , obj ) = > object = obj ) ;
2022-05-09 17:51:53 -08:00
await page . evaluate ( 'const a = {}; a.b = a; window.log(a)' ) ;
const a : any = { } ;
a . b = a ;
expect ( object ) . toEqual ( a ) ;
2022-05-09 14:07:04 -08:00
} ) ;
2022-07-08 15:11:29 -07:00
it ( 'should work with overridden console object' , async ( { page } ) = > {
await page . evaluate ( ( ) = > window . console = null ) ;
expect ( page . evaluate ( ( ) = > window . console === null ) ) . toBeTruthy ( ) ;
await page . exposeFunction ( 'add' , ( a , b ) = > a + b ) ;
expect ( await page . evaluate ( 'add(5, 6)' ) ) . toBe ( 11 ) ;
} ) ;
2023-04-21 19:52:13 +02:00
it ( 'should work with busted Array.prototype.map/push' , async ( { page , server } ) = > {
server . setRoute ( '/test' , ( req , res ) = > {
res . writeHead ( 200 , {
'content-type' : 'text/html' ,
} ) ;
res . end ( ` <script>
Array . prototype . map = null ;
Array . prototype . push = null ;
< / script > ` );
} ) ;
await page . goto ( server . PREFIX + '/test' ) ;
await page . exposeFunction ( 'add' , ( a , b ) = > a + b ) ;
expect ( await page . evaluate ( 'add(5, 6)' ) ) . toBe ( 11 ) ;
} ) ;
2024-09-10 12:14:24 +01:00
it ( 'should fail with busted Array.prototype.toJSON' , async ( { page } ) = > {
await page . evaluateHandle ( ( ) = > ( Array . prototype as any ) . toJSON = ( ) = > '"[]"' ) ;
await page . exposeFunction ( 'add' , ( a , b ) = > a + b ) ;
await expect ( ( ) = > page . evaluate ( ` add(5, 6) ` ) ) . rejects . toThrowError ( 'serializedArgs is not an array. This can happen when Array.prototype.toJSON is defined incorrectly' ) ;
expect . soft ( await page . evaluate ( ( ) = > ( [ ] as any ) . toJSON ( ) ) ) . toBe ( '"[]"' ) ;
} ) ;