feat(exposeBinding): a more powerful exposeFunction with source attribution (#2263)
This commit is contained in:
		
							parent
							
								
									40ea0dd23b
								
							
						
					
					
						commit
						2bd427ad1d
					
				
							
								
								
									
										87
									
								
								docs/api.md
								
								
								
								
							
							
						
						
									
										87
									
								
								docs/api.md
								
								
								
								
							|  | @ -297,6 +297,7 @@ await context.close(); | ||||||
| - [browserContext.clearPermissions()](#browsercontextclearpermissions) | - [browserContext.clearPermissions()](#browsercontextclearpermissions) | ||||||
| - [browserContext.close()](#browsercontextclose) | - [browserContext.close()](#browsercontextclose) | ||||||
| - [browserContext.cookies([urls])](#browsercontextcookiesurls) | - [browserContext.cookies([urls])](#browsercontextcookiesurls) | ||||||
|  | - [browserContext.exposeBinding(name, playwrightBinding)](#browsercontextexposebindingname-playwrightbinding) | ||||||
| - [browserContext.exposeFunction(name, playwrightFunction)](#browsercontextexposefunctionname-playwrightfunction) | - [browserContext.exposeFunction(name, playwrightFunction)](#browsercontextexposefunctionname-playwrightfunction) | ||||||
| - [browserContext.grantPermissions(permissions[][, options])](#browsercontextgrantpermissionspermissions-options) | - [browserContext.grantPermissions(permissions[][, options])](#browsercontextgrantpermissionspermissions-options) | ||||||
| - [browserContext.newPage()](#browsercontextnewpage) | - [browserContext.newPage()](#browsercontextnewpage) | ||||||
|  | @ -421,20 +422,54 @@ will be closed. | ||||||
| If no URLs are specified, this method returns all cookies. | If no URLs are specified, this method returns all cookies. | ||||||
| If URLs are specified, only cookies that affect those URLs are returned. | If URLs are specified, only cookies that affect those URLs are returned. | ||||||
| 
 | 
 | ||||||
|  | #### browserContext.exposeBinding(name, playwrightBinding) | ||||||
|  | - `name` <[string]> Name of the function on the window object. | ||||||
|  | - `playwrightBinding` <[function]> Callback function that will be called in the Playwright's context. | ||||||
|  | - returns: <[Promise]> | ||||||
|  | 
 | ||||||
|  | The method adds a function called `name` on the `window` object of every frame in every page in the context. | ||||||
|  | When called, the function executes `playwrightBinding` in Node.js and returns a [Promise] which resolves to the return value of `playwrightBinding`. | ||||||
|  | If the `playwrightBinding` returns a [Promise], it will be awaited. | ||||||
|  | 
 | ||||||
|  | The first argument of the `playwrightBinding` function contains information about the caller: | ||||||
|  | `{ browserContext: BrowserContext, page: Page, frame: Frame }`. | ||||||
|  | 
 | ||||||
|  | See [page.exposeBinding(name, playwrightBinding)](#pageexposebindingname-playwrightbinding) for page-only version. | ||||||
|  | 
 | ||||||
|  | An example of exposing page URL to all frames in all pages in the context: | ||||||
|  | ```js | ||||||
|  | const { webkit } = require('playwright');  // Or 'chromium' or 'firefox'. | ||||||
|  | 
 | ||||||
|  | (async () => { | ||||||
|  |   const browser = await webkit.launch({ headless: false }); | ||||||
|  |   const context = await browser.newContext(); | ||||||
|  |   await context.exposeBinding('pageURL', ({ page }) => page.url()); | ||||||
|  |   const page = await context.newPage(); | ||||||
|  |   await page.setContent(` | ||||||
|  |     <script> | ||||||
|  |       async function onClick() { | ||||||
|  |         document.querySelector('div').textContent = await window.pageURL(); | ||||||
|  |       } | ||||||
|  |     </script> | ||||||
|  |     <button onclick="onClick()">Click me</button> | ||||||
|  |     <div></div> | ||||||
|  |   `); | ||||||
|  |   await page.click('button'); | ||||||
|  | })(); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| #### browserContext.exposeFunction(name, playwrightFunction) | #### browserContext.exposeFunction(name, playwrightFunction) | ||||||
| - `name` <[string]> Name of the function on the window object. | - `name` <[string]> Name of the function on the window object. | ||||||
| - `playwrightFunction` <[function]> Callback function that will be called in the Playwright's context. | - `playwrightFunction` <[function]> Callback function that will be called in the Playwright's context. | ||||||
| - returns: <[Promise]> | - returns: <[Promise]> | ||||||
| 
 | 
 | ||||||
| The method adds a function called `name` on the `window` object of every frame in every page in the context. | The method adds a function called `name` on the `window` object of every frame in every page in the context. | ||||||
| When called, the function executes `playwrightFunction` in node.js and returns a [Promise] which resolves to the return value of `playwrightFunction`. | When called, the function executes `playwrightFunction` in Node.js and returns a [Promise] which resolves to the return value of `playwrightFunction`. | ||||||
| 
 | 
 | ||||||
| If the `playwrightFunction` returns a [Promise], it will be awaited. | If the `playwrightFunction` returns a [Promise], it will be awaited. | ||||||
| 
 | 
 | ||||||
| See [page.exposeFunction(name, playwrightFunction)](#pageexposefunctionname-playwrightfunction) for page-only version. | See [page.exposeFunction(name, playwrightFunction)](#pageexposefunctionname-playwrightfunction) for page-only version. | ||||||
| 
 | 
 | ||||||
| > **NOTE** Functions installed via `page.exposeFunction` survive navigations. |  | ||||||
| 
 |  | ||||||
| An example of adding an `md5` function to all pages in the context: | An example of adding an `md5` function to all pages in the context: | ||||||
| ```js | ```js | ||||||
| const { webkit } = require('playwright');  // Or 'chromium' or 'firefox'. | const { webkit } = require('playwright');  // Or 'chromium' or 'firefox'. | ||||||
|  | @ -678,6 +713,7 @@ page.removeListener('request', logRequest); | ||||||
| - [page.emulateMedia(options)](#pageemulatemediaoptions) | - [page.emulateMedia(options)](#pageemulatemediaoptions) | ||||||
| - [page.evaluate(pageFunction[, arg])](#pageevaluatepagefunction-arg) | - [page.evaluate(pageFunction[, arg])](#pageevaluatepagefunction-arg) | ||||||
| - [page.evaluateHandle(pageFunction[, arg])](#pageevaluatehandlepagefunction-arg) | - [page.evaluateHandle(pageFunction[, arg])](#pageevaluatehandlepagefunction-arg) | ||||||
|  | - [page.exposeBinding(name, playwrightBinding)](#pageexposebindingname-playwrightbinding) | ||||||
| - [page.exposeFunction(name, playwrightFunction)](#pageexposefunctionname-playwrightfunction) | - [page.exposeFunction(name, playwrightFunction)](#pageexposefunctionname-playwrightfunction) | ||||||
| - [page.fill(selector, value[, options])](#pagefillselector-value-options) | - [page.fill(selector, value[, options])](#pagefillselector-value-options) | ||||||
| - [page.focus(selector[, options])](#pagefocusselector-options) | - [page.focus(selector[, options])](#pagefocusselector-options) | ||||||
|  | @ -1165,13 +1201,51 @@ console.log(await resultHandle.jsonValue()); | ||||||
| await resultHandle.dispose(); | await resultHandle.dispose(); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | #### page.exposeBinding(name, playwrightBinding) | ||||||
|  | - `name` <[string]> Name of the function on the window object. | ||||||
|  | - `playwrightBinding` <[function]> Callback function that will be called in the Playwright's context. | ||||||
|  | - returns: <[Promise]> | ||||||
|  | 
 | ||||||
|  | The method adds a function called `name` on the `window` object of every frame in this page. | ||||||
|  | When called, the function executes `playwrightBinding` in Node.js and returns a [Promise] which resolves to the return value of `playwrightBinding`. | ||||||
|  | If the `playwrightBinding` returns a [Promise], it will be awaited. | ||||||
|  | 
 | ||||||
|  | The first argument of the `playwrightBinding` function contains information about the caller: | ||||||
|  | `{ browserContext: BrowserContext, page: Page, frame: Frame }`. | ||||||
|  | 
 | ||||||
|  | See [browserContext.exposeBinding(name, playwrightBinding)](#browsercontextexposebindingname-playwrightbinding) for the context-wide version. | ||||||
|  | 
 | ||||||
|  | > **NOTE** Functions installed via `page.exposeBinding` survive navigations. | ||||||
|  | 
 | ||||||
|  | An example of exposing page URL to all frames in a page: | ||||||
|  | ```js | ||||||
|  | const { webkit } = require('playwright');  // Or 'chromium' or 'firefox'. | ||||||
|  | 
 | ||||||
|  | (async () => { | ||||||
|  |   const browser = await webkit.launch({ headless: false }); | ||||||
|  |   const context = await browser.newContext(); | ||||||
|  |   const page = await context.newPage(); | ||||||
|  |   await page.exposeBinding('pageURL', ({ page }) => page.url()); | ||||||
|  |   await page.setContent(` | ||||||
|  |     <script> | ||||||
|  |       async function onClick() { | ||||||
|  |         document.querySelector('div').textContent = await window.pageURL(); | ||||||
|  |       } | ||||||
|  |     </script> | ||||||
|  |     <button onclick="onClick()">Click me</button> | ||||||
|  |     <div></div> | ||||||
|  |   `); | ||||||
|  |   await page.click('button'); | ||||||
|  | })(); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| #### page.exposeFunction(name, playwrightFunction) | #### page.exposeFunction(name, playwrightFunction) | ||||||
| - `name` <[string]> Name of the function on the window object | - `name` <[string]> Name of the function on the window object | ||||||
| - `playwrightFunction` <[function]> Callback function which will be called in Playwright's context. | - `playwrightFunction` <[function]> Callback function which will be called in Playwright's context. | ||||||
| - returns: <[Promise]> | - returns: <[Promise]> | ||||||
| 
 | 
 | ||||||
| The method adds a function called `name` on the `window` object of every frame in the page. | The method adds a function called `name` on the `window` object of every frame in the page. | ||||||
| When called, the function executes `playwrightFunction` in node.js and returns a [Promise] which resolves to the return value of `playwrightFunction`. | When called, the function executes `playwrightFunction` in Node.js and returns a [Promise] which resolves to the return value of `playwrightFunction`. | ||||||
| 
 | 
 | ||||||
| If the `playwrightFunction` returns a [Promise], it will be awaited. | If the `playwrightFunction` returns a [Promise], it will be awaited. | ||||||
| 
 | 
 | ||||||
|  | @ -1720,7 +1794,7 @@ const { webkit } = require('playwright');  // Or 'chromium' or 'firefox'. | ||||||
| })(); | })(); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| To pass an argument from node.js to the predicate of `page.waitForFunction` function: | To pass an argument from Node.js to the predicate of `page.waitForFunction` function: | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| const selector = '.foo'; | const selector = '.foo'; | ||||||
|  | @ -2389,7 +2463,7 @@ const { firefox } = require('playwright');  // Or 'chromium' or 'webkit'. | ||||||
| })(); | })(); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| To pass an argument from node.js to the predicate of `frame.waitForFunction` function: | To pass an argument from Node.js to the predicate of `frame.waitForFunction` function: | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| const selector = '.foo'; | const selector = '.foo'; | ||||||
|  | @ -4027,6 +4101,7 @@ const backgroundPage = await context.waitForEvent('backgroundpage'); | ||||||
| - [browserContext.clearPermissions()](#browsercontextclearpermissions) | - [browserContext.clearPermissions()](#browsercontextclearpermissions) | ||||||
| - [browserContext.close()](#browsercontextclose) | - [browserContext.close()](#browsercontextclose) | ||||||
| - [browserContext.cookies([urls])](#browsercontextcookiesurls) | - [browserContext.cookies([urls])](#browsercontextcookiesurls) | ||||||
|  | - [browserContext.exposeBinding(name, playwrightBinding)](#browsercontextexposebindingname-playwrightbinding) | ||||||
| - [browserContext.exposeFunction(name, playwrightFunction)](#browsercontextexposefunctionname-playwrightfunction) | - [browserContext.exposeFunction(name, playwrightFunction)](#browsercontextexposefunctionname-playwrightfunction) | ||||||
| - [browserContext.grantPermissions(permissions[][, options])](#browsercontextgrantpermissionspermissions-options) | - [browserContext.grantPermissions(permissions[][, options])](#browsercontextgrantpermissionspermissions-options) | ||||||
| - [browserContext.newPage()](#browsercontextnewpage) | - [browserContext.newPage()](#browsercontextnewpage) | ||||||
|  |  | ||||||
|  | @ -25,6 +25,7 @@ import { ExtendedEventEmitter } from './extendedEventEmitter'; | ||||||
| import { Download } from './download'; | import { Download } from './download'; | ||||||
| import { BrowserBase } from './browser'; | import { BrowserBase } from './browser'; | ||||||
| import { Log, InnerLogger, Logger, RootLogger } from './logger'; | import { Log, InnerLogger, Logger, RootLogger } from './logger'; | ||||||
|  | import { FunctionWithSource } from './frames'; | ||||||
| 
 | 
 | ||||||
| export type BrowserContextOptions = { | export type BrowserContextOptions = { | ||||||
|   viewport?: types.Size | null, |   viewport?: types.Size | null, | ||||||
|  | @ -62,6 +63,7 @@ export interface BrowserContext extends InnerLogger { | ||||||
|   setOffline(offline: boolean): Promise<void>; |   setOffline(offline: boolean): Promise<void>; | ||||||
|   setHTTPCredentials(httpCredentials: types.Credentials | null): Promise<void>; |   setHTTPCredentials(httpCredentials: types.Credentials | null): Promise<void>; | ||||||
|   addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void>; |   addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void>; | ||||||
|  |   exposeBinding(name: string, playwrightBinding: FunctionWithSource): Promise<void>; | ||||||
|   exposeFunction(name: string, playwrightFunction: Function): Promise<void>; |   exposeFunction(name: string, playwrightFunction: Function): Promise<void>; | ||||||
|   route(url: types.URLMatch, handler: network.RouteHandler): Promise<void>; |   route(url: types.URLMatch, handler: network.RouteHandler): Promise<void>; | ||||||
|   unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise<void>; |   unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise<void>; | ||||||
|  | @ -126,11 +128,27 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements | ||||||
|   abstract setExtraHTTPHeaders(headers: network.Headers): Promise<void>; |   abstract setExtraHTTPHeaders(headers: network.Headers): Promise<void>; | ||||||
|   abstract setOffline(offline: boolean): Promise<void>; |   abstract setOffline(offline: boolean): Promise<void>; | ||||||
|   abstract addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, arg?: any): Promise<void>; |   abstract addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, arg?: any): Promise<void>; | ||||||
|   abstract exposeFunction(name: string, playwrightFunction: Function): Promise<void>; |   abstract _doExposeBinding(binding: PageBinding): Promise<void>; | ||||||
|   abstract route(url: types.URLMatch, handler: network.RouteHandler): Promise<void>; |   abstract route(url: types.URLMatch, handler: network.RouteHandler): Promise<void>; | ||||||
|   abstract unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise<void>; |   abstract unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise<void>; | ||||||
|   abstract close(): Promise<void>; |   abstract close(): Promise<void>; | ||||||
| 
 | 
 | ||||||
|  |   async exposeFunction(name: string, playwrightFunction: Function): Promise<void> { | ||||||
|  |     await this.exposeBinding(name, (options, ...args: any) => playwrightFunction(...args)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async exposeBinding(name: string, playwrightBinding: FunctionWithSource): Promise<void> { | ||||||
|  |     for (const page of this.pages()) { | ||||||
|  |       if (page._pageBindings.has(name)) | ||||||
|  |         throw new Error(`Function "${name}" has been already registered in one of the pages`); | ||||||
|  |     } | ||||||
|  |     if (this._pageBindings.has(name)) | ||||||
|  |       throw new Error(`Function "${name}" has been already registered`); | ||||||
|  |     const binding = new PageBinding(name, playwrightBinding); | ||||||
|  |     this._pageBindings.set(name, binding); | ||||||
|  |     this._doExposeBinding(binding); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async grantPermissions(permissions: string[], options?: { origin?: string }) { |   async grantPermissions(permissions: string[], options?: { origin?: string }) { | ||||||
|     let origin = '*'; |     let origin = '*'; | ||||||
|     if (options && options.origin) { |     if (options && options.origin) { | ||||||
|  |  | ||||||
|  | @ -405,15 +405,7 @@ export class CRBrowserContext extends BrowserContextBase { | ||||||
|       await (page._delegate as CRPage).evaluateOnNewDocument(source); |       await (page._delegate as CRPage).evaluateOnNewDocument(source); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async exposeFunction(name: string, playwrightFunction: Function): Promise<void> { |   async _doExposeBinding(binding: PageBinding) { | ||||||
|     for (const page of this.pages()) { |  | ||||||
|       if (page._pageBindings.has(name)) |  | ||||||
|         throw new Error(`Function "${name}" has been already registered in one of the pages`); |  | ||||||
|     } |  | ||||||
|     if (this._pageBindings.has(name)) |  | ||||||
|       throw new Error(`Function "${name}" has been already registered`); |  | ||||||
|     const binding = new PageBinding(name, playwrightFunction); |  | ||||||
|     this._pageBindings.set(name, binding); |  | ||||||
|     for (const page of this.pages()) |     for (const page of this.pages()) | ||||||
|       await (page._delegate as CRPage).exposeBinding(binding); |       await (page._delegate as CRPage).exposeBinding(binding); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -302,16 +302,8 @@ export class FFBrowserContext extends BrowserContextBase { | ||||||
|     await this._browser._connection.send('Browser.addScriptToEvaluateOnNewDocument', { browserContextId: this._browserContextId || undefined, script: source }); |     await this._browser._connection.send('Browser.addScriptToEvaluateOnNewDocument', { browserContextId: this._browserContextId || undefined, script: source }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async exposeFunction(name: string, playwrightFunction: Function): Promise<void> { |   async _doExposeBinding(binding: PageBinding) { | ||||||
|     for (const page of this.pages()) { |     await this._browser._connection.send('Browser.addBinding', { browserContextId: this._browserContextId || undefined, name: binding.name, script: binding.source }); | ||||||
|       if (page._pageBindings.has(name)) |  | ||||||
|         throw new Error(`Function "${name}" has been already registered in one of the pages`); |  | ||||||
|     } |  | ||||||
|     if (this._pageBindings.has(name)) |  | ||||||
|       throw new Error(`Function "${name}" has been already registered`); |  | ||||||
|     const binding = new PageBinding(name, playwrightFunction); |  | ||||||
|     this._pageBindings.set(name, binding); |  | ||||||
|     await this._browser._connection.send('Browser.addBinding', { browserContextId: this._browserContextId || undefined, name, script: binding.source }); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async route(url: types.URLMatch, handler: network.RouteHandler): Promise<void> { |   async route(url: types.URLMatch, handler: network.RouteHandler): Promise<void> { | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ import { Page } from './page'; | ||||||
| import { selectors } from './selectors'; | import { selectors } from './selectors'; | ||||||
| import * as types from './types'; | import * as types from './types'; | ||||||
| import { waitForTimeoutWasUsed } from './hints'; | import { waitForTimeoutWasUsed } from './hints'; | ||||||
|  | import { BrowserContext } from './browserContext'; | ||||||
| 
 | 
 | ||||||
| type ContextType = 'main' | 'utility'; | type ContextType = 'main' | 'utility'; | ||||||
| type ContextData = { | type ContextData = { | ||||||
|  | @ -46,6 +47,8 @@ export type GotoResult = { | ||||||
| 
 | 
 | ||||||
| type ConsoleTagHandler = () => void; | type ConsoleTagHandler = () => void; | ||||||
| 
 | 
 | ||||||
|  | export type FunctionWithSource = (source: { context: BrowserContext, page: Page, frame: Frame}, ...args: any) => any; | ||||||
|  | 
 | ||||||
| export class FrameManager { | export class FrameManager { | ||||||
|   private _page: Page; |   private _page: Page; | ||||||
|   private _frames = new Map<string, Frame>(); |   private _frames = new Map<string, Frame>(); | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								src/page.ts
								
								
								
								
							
							
						
						
									
										16
									
								
								src/page.ts
								
								
								
								
							|  | @ -253,11 +253,15 @@ export class Page extends ExtendedEventEmitter implements InnerLogger { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async exposeFunction(name: string, playwrightFunction: Function) { |   async exposeFunction(name: string, playwrightFunction: Function) { | ||||||
|  |     await this.exposeBinding(name, (options, ...args: any) => playwrightFunction(...args)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async exposeBinding(name: string, playwrightBinding: frames.FunctionWithSource) { | ||||||
|     if (this._pageBindings.has(name)) |     if (this._pageBindings.has(name)) | ||||||
|       throw new Error(`Function "${name}" has been already registered`); |       throw new Error(`Function "${name}" has been already registered`); | ||||||
|     if (this._browserContext._pageBindings.has(name)) |     if (this._browserContext._pageBindings.has(name)) | ||||||
|       throw new Error(`Function "${name}" has been already registered in the browser context`); |       throw new Error(`Function "${name}" has been already registered in the browser context`); | ||||||
|     const binding = new PageBinding(name, playwrightFunction); |     const binding = new PageBinding(name, playwrightBinding); | ||||||
|     this._pageBindings.set(name, binding); |     this._pageBindings.set(name, binding); | ||||||
|     await this._delegate.exposeBinding(binding); |     await this._delegate.exposeBinding(binding); | ||||||
|   } |   } | ||||||
|  | @ -267,7 +271,7 @@ export class Page extends ExtendedEventEmitter implements InnerLogger { | ||||||
|     return this._delegate.updateExtraHTTPHeaders(); |     return this._delegate.updateExtraHTTPHeaders(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async _onBindingCalled(payload: string, context: js.ExecutionContext) { |   async _onBindingCalled(payload: string, context: dom.FrameExecutionContext) { | ||||||
|     await PageBinding.dispatch(this, payload, context); |     await PageBinding.dispatch(this, payload, context); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -580,23 +584,23 @@ export class Worker extends EventEmitter { | ||||||
| 
 | 
 | ||||||
| export class PageBinding { | export class PageBinding { | ||||||
|   readonly name: string; |   readonly name: string; | ||||||
|   readonly playwrightFunction: Function; |   readonly playwrightFunction: frames.FunctionWithSource; | ||||||
|   readonly source: string; |   readonly source: string; | ||||||
| 
 | 
 | ||||||
|   constructor(name: string, playwrightFunction: Function) { |   constructor(name: string, playwrightFunction: frames.FunctionWithSource) { | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     this.playwrightFunction = playwrightFunction; |     this.playwrightFunction = playwrightFunction; | ||||||
|     this.source = helper.evaluationString(addPageBinding, name); |     this.source = helper.evaluationString(addPageBinding, name); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static async dispatch(page: Page, payload: string, context: js.ExecutionContext) { |   static async dispatch(page: Page, payload: string, context: dom.FrameExecutionContext) { | ||||||
|     const {name, seq, args} = JSON.parse(payload); |     const {name, seq, args} = JSON.parse(payload); | ||||||
|     let expression = null; |     let expression = null; | ||||||
|     try { |     try { | ||||||
|       let binding = page._pageBindings.get(name); |       let binding = page._pageBindings.get(name); | ||||||
|       if (!binding) |       if (!binding) | ||||||
|         binding = page._browserContext._pageBindings.get(name); |         binding = page._browserContext._pageBindings.get(name); | ||||||
|       const result = await binding!.playwrightFunction(...args); |       const result = await binding!.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, ...args); | ||||||
|       expression = helper.evaluationString(deliverResult, name, seq, result); |       expression = helper.evaluationString(deliverResult, name, seq, result); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       if (error instanceof Error) |       if (error instanceof Error) | ||||||
|  |  | ||||||
|  | @ -308,15 +308,7 @@ export class WKBrowserContext extends BrowserContextBase { | ||||||
|       await (page._delegate as WKPage)._updateBootstrapScript(); |       await (page._delegate as WKPage)._updateBootstrapScript(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async exposeFunction(name: string, playwrightFunction: Function): Promise<void> { |   async _doExposeBinding(binding: PageBinding) { | ||||||
|     for (const page of this.pages()) { |  | ||||||
|       if (page._pageBindings.has(name)) |  | ||||||
|         throw new Error(`Function "${name}" has been already registered in one of the pages`); |  | ||||||
|     } |  | ||||||
|     if (this._pageBindings.has(name)) |  | ||||||
|       throw new Error(`Function "${name}" has been already registered`); |  | ||||||
|     const binding = new PageBinding(name, playwrightFunction); |  | ||||||
|     this._pageBindings.set(name, binding); |  | ||||||
|     for (const page of this.pages()) |     for (const page of this.pages()) | ||||||
|       await (page._delegate as WKPage).exposeBinding(binding); |       await (page._delegate as WKPage).exposeBinding(binding); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -326,6 +326,26 @@ describe('BrowserContext.pages()', function() { | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | describe('BrowserContext.exposeBinding', () => { | ||||||
|  |   it('should work', async({browser}) => { | ||||||
|  |     const context = await browser.newContext(); | ||||||
|  |     let bindingSource; | ||||||
|  |     await context.exposeBinding('add', (source, a, b) => { | ||||||
|  |       bindingSource = source; | ||||||
|  |       return a + b; | ||||||
|  |     }); | ||||||
|  |     const page = await context.newPage(); | ||||||
|  |     const result = await page.evaluate(async function() { | ||||||
|  |       return add(5, 6); | ||||||
|  |     }); | ||||||
|  |     expect(bindingSource.context).toBe(context); | ||||||
|  |     expect(bindingSource.page).toBe(page); | ||||||
|  |     expect(bindingSource.frame).toBe(page.mainFrame()); | ||||||
|  |     expect(result).toEqual(11); | ||||||
|  |     await context.close(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| describe('BrowserContext.exposeFunction', () => { | describe('BrowserContext.exposeFunction', () => { | ||||||
|   it('should work', async({browser, server}) => { |   it('should work', async({browser, server}) => { | ||||||
|     const context = await browser.newContext(); |     const context = await browser.newContext(); | ||||||
|  |  | ||||||
|  | @ -405,6 +405,26 @@ describe('Page.waitForResponse', function() { | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | describe('Page.exposeBinding', () => { | ||||||
|  |   it('should work', async({browser}) => { | ||||||
|  |     const context = await browser.newContext(); | ||||||
|  |     const page = await context.newPage(); | ||||||
|  |     let bindingSource; | ||||||
|  |     await page.exposeBinding('add', (source, a, b) => { | ||||||
|  |       bindingSource = source; | ||||||
|  |       return a + b; | ||||||
|  |     }); | ||||||
|  |     const result = await page.evaluate(async function() { | ||||||
|  |       return add(5, 6); | ||||||
|  |     }); | ||||||
|  |     expect(bindingSource.context).toBe(context); | ||||||
|  |     expect(bindingSource.page).toBe(page); | ||||||
|  |     expect(bindingSource.frame).toBe(page.mainFrame()); | ||||||
|  |     expect(result).toEqual(11); | ||||||
|  |     await context.close(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| describe('Page.exposeFunction', function() { | describe('Page.exposeFunction', function() { | ||||||
|   it('should work', async({page, server}) => { |   it('should work', async({page, server}) => { | ||||||
|     await page.exposeFunction('compute', function(a, b) { |     await page.exposeFunction('compute', function(a, b) { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue