diff --git a/src/browser.ts b/src/browser.ts index bdd5d312a9a9b..bf761360d736b 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { BrowserContext, BrowserContextOptions, BrowserContextBase, PersistentContextOptions } from './browserContext'; +import * as types from './types'; +import { BrowserContext, BrowserContextBase } from './browserContext'; import { Page } from './page'; import { EventEmitter } from 'events'; import { Download } from './download'; @@ -22,17 +23,20 @@ import type { BrowserServer } from './server/browserServer'; import { Events } from './events'; import { Loggers } from './logger'; import { ProxySettings } from './types'; +import { LoggerSink } from './loggerSink'; export type BrowserOptions = { loggers: Loggers, downloadsPath?: string, headful?: boolean, - persistent?: PersistentContextOptions, // Undefined means no persistent context. + persistent?: types.BrowserContextOptions, // Undefined means no persistent context. slowMo?: number, ownedServer?: BrowserServer, proxy?: ProxySettings, }; +export type BrowserContextOptions = types.BrowserContextOptions & { logger?: LoggerSink }; + export interface Browser extends EventEmitter { newContext(options?: BrowserContextOptions): Promise; contexts(): BrowserContext[]; diff --git a/src/browserContext.ts b/src/browserContext.ts index 76521ae8a13f1..d9423c93341fd 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -31,43 +31,18 @@ import { ProgressController } from './progress'; import { DebugController } from './debug/debugController'; import { LoggerSink } from './loggerSink'; -type CommonContextOptions = { - viewport?: types.Size | null, - ignoreHTTPSErrors?: boolean, - javaScriptEnabled?: boolean, - bypassCSP?: boolean, - userAgent?: string, - locale?: string, - timezoneId?: string, - geolocation?: types.Geolocation, - permissions?: string[], - extraHTTPHeaders?: network.Headers, - offline?: boolean, - httpCredentials?: types.Credentials, - deviceScaleFactor?: number, - isMobile?: boolean, - hasTouch?: boolean, - colorScheme?: types.ColorScheme, - acceptDownloads?: boolean, -}; - -export type PersistentContextOptions = CommonContextOptions; -export type BrowserContextOptions = CommonContextOptions & { - logger?: LoggerSink, -}; - export interface BrowserContext { setDefaultNavigationTimeout(timeout: number): void; setDefaultTimeout(timeout: number): void; pages(): Page[]; newPage(): Promise; - cookies(urls?: string | string[]): Promise; - addCookies(cookies: network.SetNetworkCookieParam[]): Promise; + cookies(urls?: string | string[]): Promise; + addCookies(cookies: types.SetNetworkCookieParam[]): Promise; clearCookies(): Promise; grantPermissions(permissions: string[], options?: { origin?: string }): Promise; clearPermissions(): Promise; setGeolocation(geolocation: types.Geolocation | null): Promise; - setExtraHTTPHeaders(headers: network.Headers): Promise; + setExtraHTTPHeaders(headers: types.Headers): Promise; setOffline(offline: boolean): Promise; setHTTPCredentials(httpCredentials: types.Credentials | null): Promise; addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise; @@ -79,6 +54,8 @@ export interface BrowserContext { close(): Promise; } +type BrowserContextOptions = types.BrowserContextOptions & { logger?: LoggerSink }; + export abstract class BrowserContextBase extends EventEmitter implements BrowserContext { readonly _timeoutSettings = new TimeoutSettings(); readonly _pageBindings = new Map(); @@ -141,21 +118,27 @@ export abstract class BrowserContextBase extends EventEmitter implements Browser // BrowserContext methods. abstract pages(): Page[]; abstract newPage(): Promise; - abstract cookies(...urls: string[]): Promise; - abstract addCookies(cookies: network.SetNetworkCookieParam[]): Promise; + abstract _doCookies(urls: string[]): Promise; + abstract addCookies(cookies: types.SetNetworkCookieParam[]): Promise; abstract clearCookies(): Promise; abstract _doGrantPermissions(origin: string, permissions: string[]): Promise; abstract _doClearPermissions(): Promise; abstract setGeolocation(geolocation: types.Geolocation | null): Promise; abstract setHTTPCredentials(httpCredentials: types.Credentials | null): Promise; - abstract setExtraHTTPHeaders(headers: network.Headers): Promise; + abstract setExtraHTTPHeaders(headers: types.Headers): Promise; abstract setOffline(offline: boolean): Promise; - abstract addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, arg?: any): Promise; + abstract _doAddInitScript(expression: string): Promise; abstract _doExposeBinding(binding: PageBinding): Promise; abstract route(url: types.URLMatch, handler: network.RouteHandler): Promise; abstract unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise; abstract close(): Promise; + async cookies(urls: string | string[] | undefined = []): Promise { + if (urls && !Array.isArray(urls)) + urls = [ urls ]; + return await this._doCookies(urls as string[]); + } + async exposeFunction(name: string, playwrightFunction: Function): Promise { await this.exposeBinding(name, (options, ...args: any) => playwrightFunction(...args)); } @@ -172,6 +155,11 @@ export abstract class BrowserContextBase extends EventEmitter implements Browser this._doExposeBinding(binding); } + async addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, arg?: any): Promise { + const source = await helper.evaluationScript(script, arg); + await this._doAddInitScript(source); + } + async grantPermissions(permissions: string[], options?: { origin?: string }) { let origin = '*'; if (options && options.origin) { diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 49e1f8496e867..3c57f4bea7210 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -15,10 +15,10 @@ * limitations under the License. */ -import { BrowserBase, BrowserOptions } from '../browser'; -import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions, verifyGeolocation } from '../browserContext'; +import { BrowserBase, BrowserOptions, BrowserContextOptions } from '../browser'; +import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, validateBrowserContextOptions, verifyGeolocation } from '../browserContext'; import { Events as CommonEvents } from '../events'; -import { assert, helper } from '../helper'; +import { assert } from '../helper'; import * as network from '../network'; import { Page, PageBinding, Worker } from '../page'; import { ConnectionTransport, SlowMoTransport } from '../transport'; @@ -280,7 +280,7 @@ export class CRBrowserContext extends BrowserContextBase { readonly _browserContextId: string | null; readonly _evaluateOnNewDocumentSources: string[]; - constructor(browser: CRBrowser, browserContextId: string | null, options: BrowserContextOptions) { + constructor(browser: CRBrowser, browserContextId: string | null, options: types.BrowserContextOptions) { super(browser, options); this._browser = browser; this._browserContextId = browserContextId; @@ -325,18 +325,18 @@ export class CRBrowserContext extends BrowserContextBase { throw result; } - async cookies(urls?: string | string[]): Promise { + async _doCookies(urls: string[]): Promise { const { cookies } = await this._browser._session.send('Storage.getCookies', { browserContextId: this._browserContextId || undefined }); return network.filterCookies(cookies.map(c => { const copy: any = { sameSite: 'None', ...c }; delete copy.size; delete copy.priority; delete copy.session; - return copy as network.NetworkCookie; + return copy as types.NetworkCookie; }), urls); } - async addCookies(cookies: network.SetNetworkCookieParam[]) { + async addCookies(cookies: types.SetNetworkCookieParam[]) { await this._browser._session.send('Storage.setCookies', { cookies: network.rewriteCookies(cookies), browserContextId: this._browserContextId || undefined }); } @@ -384,7 +384,7 @@ export class CRBrowserContext extends BrowserContextBase { await (page._delegate as CRPage).updateGeolocation(); } - async setExtraHTTPHeaders(headers: network.Headers): Promise { + async setExtraHTTPHeaders(headers: types.Headers): Promise { this._options.extraHTTPHeaders = network.verifyHeaders(headers); for (const page of this.pages()) await (page._delegate as CRPage).updateExtraHTTPHeaders(); @@ -402,8 +402,7 @@ export class CRBrowserContext extends BrowserContextBase { await (page._delegate as CRPage).updateHttpCredentials(); } - async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { - const source = await helper.evaluationScript(script, arg); + async _doAddInitScript(source: string) { this._evaluateOnNewDocumentSources.push(source); for (const page of this.pages()) await (page._delegate as CRPage).evaluateOnNewDocument(source); diff --git a/src/chromium/crNetworkManager.ts b/src/chromium/crNetworkManager.ts index ad4d63f3bb912..1fca1ffac0780 100644 --- a/src/chromium/crNetworkManager.ts +++ b/src/chromium/crNetworkManager.ts @@ -21,7 +21,7 @@ import { assert, helper, RegisteredListener } from '../helper'; import { Protocol } from './protocol'; import * as network from '../network'; import * as frames from '../frames'; -import { Credentials } from '../types'; +import * as types from '../types'; import { CRPage } from './crPage'; export class CRNetworkManager { @@ -63,7 +63,7 @@ export class CRNetworkManager { helper.removeEventListeners(this._eventListeners); } - async authenticate(credentials: Credentials | null) { + async authenticate(credentials: types.Credentials | null) { this._credentials = credentials; await this._updateProtocolRequestInterception(); } @@ -350,7 +350,7 @@ class InterceptableRequest implements network.RouteDelegate { this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postData, headersObject(headers)); } - async continue(overrides: { method?: string; headers?: network.Headers; postData?: string } = {}) { + async continue(overrides: { method?: string; headers?: types.Headers; postData?: string } = {}) { // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. await this._client._sendMayFail('Fetch.continueRequest', { @@ -361,7 +361,7 @@ class InterceptableRequest implements network.RouteDelegate { }); } - async fulfill(response: network.FulfillResponse) { + async fulfill(response: types.FulfillResponse) { const responseBody = response.body && helper.isString(response.body) ? Buffer.from(response.body) : (response.body || null); const responseHeaders: { [s: string]: string; } = {}; @@ -423,8 +423,8 @@ function headersArray(headers: { [s: string]: string; }): { name: string; value: return result; } -function headersObject(headers: Protocol.Network.Headers): network.Headers { - const result: network.Headers = {}; +function headersObject(headers: Protocol.Network.Headers): types.Headers { + const result: types.Headers = {}; for (const key of Object.keys(headers)) result[key.toLowerCase()] = headers[key]; return result; diff --git a/src/cli/index.ts b/src/cli/index.ts index efd6729bff950..73b4b7bb671e5 100755 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -20,10 +20,10 @@ import * as program from 'commander'; import { Playwright } from '../server/playwright'; -import { BrowserType, LaunchOptions } from '../server/browserType'; +import { BrowserType } from '../server/browserType'; import { DeviceDescriptors } from '../deviceDescriptors'; -import { BrowserContextOptions } from '../browserContext'; import { helper } from '../helper'; +import { LaunchOptions, BrowserContextOptions } from '../types'; const playwright = new Playwright(__dirname, require('../../browsers.json')['browsers']); diff --git a/src/dom.ts b/src/dom.ts index 8847fbbf7588e..12cd205264813 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -19,7 +19,7 @@ import * as mime from 'mime'; import * as path from 'path'; import * as util from 'util'; import * as frames from './frames'; -import { assert, helper } from './helper'; +import { assert, helper, assertMaxArguments } from './helper'; import InjectedScript from './injected/injectedScript'; import * as injectedScriptSource from './generated/injectedScriptSource'; import * as debugScriptSource from './generated/debugScriptSource'; @@ -55,6 +55,12 @@ export class FrameExecutionContext extends js.ExecutionContext { }); } + async evaluateExpressionInternal(expression: string, isFunction: boolean, ...args: any[]): Promise { + return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => { + return js.evaluateExpression(this, true /* returnByValue */, expression, isFunction, ...args); + }); + } + async evaluateHandleInternal(pageFunction: js.Func0): Promise>; async evaluateHandleInternal(pageFunction: js.Func1, arg: Arg): Promise>; async evaluateHandleInternal(pageFunction: never, ...args: never[]): Promise { @@ -63,6 +69,12 @@ export class FrameExecutionContext extends js.ExecutionContext { }); } + async evaluateExpressionHandleInternal(expression: string, isFunction: boolean, ...args: any[]): Promise { + return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => { + return js.evaluateExpression(this, false /* returnByValue */, expression, isFunction, ...args); + }); + } + createHandle(remoteObject: js.RemoteObject): js.JSHandle { if (this.frame._page._delegate.isElementHandle(remoteObject)) return new ElementHandle(this, remoteObject.objectId!); @@ -618,10 +630,15 @@ export class ElementHandle extends js.JSHandle { async $eval(selector: string, pageFunction: js.FuncOn, arg: Arg): Promise; async $eval(selector: string, pageFunction: js.FuncOn, arg?: any): Promise; async $eval(selector: string, pageFunction: js.FuncOn, arg: Arg): Promise { + assertMaxArguments(arguments.length, 3); + return this._$evalExpression(selector, String(pageFunction), typeof pageFunction === 'function', arg); + } + + async _$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise { const handle = await selectors._query(this._context.frame, selector, this); if (!handle) throw new Error(`Error: failed to find element matching selector "${selector}"`); - const result = await handle.evaluate(pageFunction, arg); + const result = await handle._evaluateExpression(expression, isFunction, true, arg); handle.dispose(); return result; } @@ -629,8 +646,13 @@ export class ElementHandle extends js.JSHandle { async $$eval(selector: string, pageFunction: js.FuncOn, arg: Arg): Promise; async $$eval(selector: string, pageFunction: js.FuncOn, arg?: any): Promise; async $$eval(selector: string, pageFunction: js.FuncOn, arg: Arg): Promise { + assertMaxArguments(arguments.length, 3); + return this._$$evalExpression(selector, String(pageFunction), typeof pageFunction === 'function', arg); + } + + async _$$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise { const arrayHandle = await selectors._queryArray(this._context.frame, selector, this); - const result = await arrayHandle.evaluate(pageFunction, arg); + const result = await arrayHandle._evaluateExpression(expression, isFunction, true, arg); arrayHandle.dispose(); return result; } diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index cd97cde8f3c3d..e57c4618d9322 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { BrowserBase, BrowserOptions } from '../browser'; -import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions, verifyGeolocation } from '../browserContext'; +import { BrowserBase, BrowserOptions, BrowserContextOptions } from '../browser'; +import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, validateBrowserContextOptions, verifyGeolocation } from '../browserContext'; import { Events } from '../events'; import { assert, helper, RegisteredListener } from '../helper'; import * as network from '../network'; @@ -146,7 +146,7 @@ export class FFBrowserContext extends BrowserContextBase { readonly _browser: FFBrowser; readonly _browserContextId: string | null; - constructor(browser: FFBrowser, browserContextId: string | null, options: BrowserContextOptions) { + constructor(browser: FFBrowser, browserContextId: string | null, options: types.BrowserContextOptions) { super(browser, options); this._browser = browser; this._browserContextId = browserContextId; @@ -237,17 +237,17 @@ export class FFBrowserContext extends BrowserContextBase { throw pageOrError; } - async cookies(urls?: string | string[]): Promise { + async _doCookies(urls: string[]): Promise { const { cookies } = await this._browser._connection.send('Browser.getCookies', { browserContextId: this._browserContextId || undefined }); return network.filterCookies(cookies.map(c => { const copy: any = { ... c }; delete copy.size; delete copy.session; - return copy as network.NetworkCookie; + return copy as types.NetworkCookie; }), urls); } - async addCookies(cookies: network.SetNetworkCookieParam[]) { + async addCookies(cookies: types.SetNetworkCookieParam[]) { await this._browser._connection.send('Browser.setCookies', { browserContextId: this._browserContextId || undefined, cookies: network.rewriteCookies(cookies) }); } @@ -282,7 +282,7 @@ export class FFBrowserContext extends BrowserContextBase { await this._browser._connection.send('Browser.setGeolocationOverride', { browserContextId: this._browserContextId || undefined, geolocation }); } - async setExtraHTTPHeaders(headers: network.Headers): Promise { + async setExtraHTTPHeaders(headers: types.Headers): Promise { this._options.extraHTTPHeaders = network.verifyHeaders(headers); const allHeaders = { ...this._options.extraHTTPHeaders }; if (this._options.locale) @@ -300,8 +300,7 @@ export class FFBrowserContext extends BrowserContextBase { await this._browser._connection.send('Browser.setHTTPCredentials', { browserContextId: this._browserContextId || undefined, credentials: httpCredentials }); } - async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { - const source = await helper.evaluationScript(script, arg); + async _doAddInitScript(source: string) { await this._browser._connection.send('Browser.addScriptToEvaluateOnNewDocument', { browserContextId: this._browserContextId || undefined, script: source }); } diff --git a/src/firefox/ffNetworkManager.ts b/src/firefox/ffNetworkManager.ts index 205bc681df7b3..a3c8e58d95f5c 100644 --- a/src/firefox/ffNetworkManager.ts +++ b/src/firefox/ffNetworkManager.ts @@ -20,6 +20,7 @@ import { FFSession } from './ffConnection'; import { Page } from '../page'; import * as network from '../network'; import * as frames from '../frames'; +import * as types from '../types'; import { Protocol } from './protocol'; export class FFNetworkManager { @@ -74,7 +75,7 @@ export class FFNetworkManager { throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was evicted!`); return Buffer.from(response.base64body, 'base64'); }; - const headers: network.Headers = {}; + const headers: types.Headers = {}; for (const {name, value} of event.headers) headers[name.toLowerCase()] = value; const response = new network.Response(request.request, event.status, event.statusText, headers, getResponseBody); @@ -149,7 +150,7 @@ class InterceptableRequest implements network.RouteDelegate { this._id = payload.requestId; this._session = session; - const headers: network.Headers = {}; + const headers: types.Headers = {}; for (const {name, value} of payload.headers) headers[name.toLowerCase()] = value; @@ -157,7 +158,7 @@ class InterceptableRequest implements network.RouteDelegate { payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, payload.postData || null, headers); } - async continue(overrides: { method?: string; headers?: network.Headers; postData?: string }) { + async continue(overrides: { method?: string; headers?: types.Headers; postData?: string }) { const { method, headers, @@ -171,7 +172,7 @@ class InterceptableRequest implements network.RouteDelegate { }); } - async fulfill(response: network.FulfillResponse) { + async fulfill(response: types.FulfillResponse) { const responseBody = response.body && helper.isString(response.body) ? Buffer.from(response.body) : (response.body || null); const responseHeaders: { [s: string]: string; } = {}; @@ -201,7 +202,7 @@ class InterceptableRequest implements network.RouteDelegate { } } -export function headersArray(headers: network.Headers): Protocol.Network.HTTPHeader[] { +export function headersArray(headers: types.Headers): Protocol.Network.HTTPHeader[] { const result: Protocol.Network.HTTPHeader[] = []; for (const name in headers) { if (!Object.is(headers[name], undefined)) diff --git a/src/frames.ts b/src/frames.ts index 798d882cfca02..4234f8c2ce173 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -36,9 +36,6 @@ type ContextData = { rerunnableTasks: Set>; }; -export type GotoOptions = types.NavigateOptions & { - referer?: string, -}; export type GotoResult = { newDocumentId?: string, }; @@ -347,7 +344,7 @@ export class Frame { return `${subject}.${method}`; } - async goto(url: string, options: GotoOptions = {}): Promise { + async goto(url: string, options: types.GotoOptions = {}): Promise { const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options), this._apiName('goto')); abortProgressOnFrameDetach(progressController, this); return progressController.run(async progress => { @@ -439,6 +436,11 @@ export class Frame { return context.evaluateHandleInternal(pageFunction, arg); } + async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any): Promise { + const context = await this._mainContext(); + return context.evaluateExpressionHandleInternal(expression, isFunction, arg); + } + async evaluate(pageFunction: js.Func1, arg: Arg): Promise; async evaluate(pageFunction: js.Func1, arg?: any): Promise; async evaluate(pageFunction: js.Func1, arg: Arg): Promise { @@ -447,6 +449,11 @@ export class Frame { return context.evaluateInternal(pageFunction, arg); } + async _evaluateExpression(expression: string, isFunction: boolean, arg: any): Promise { + const context = await this._mainContext(); + return context.evaluateExpressionHandleInternal(expression, isFunction, arg); + } + async $(selector: string): Promise | null> { return selectors._query(this, selector); } @@ -493,10 +500,14 @@ export class Frame { async $eval(selector: string, pageFunction: js.FuncOn, arg?: any): Promise; async $eval(selector: string, pageFunction: js.FuncOn, arg: Arg): Promise { assertMaxArguments(arguments.length, 3); + return this._$evalExpression(selector, String(pageFunction), typeof pageFunction === 'function', arg); + } + + async _$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise { const handle = await this.$(selector); if (!handle) throw new Error(`Error: failed to find element matching selector "${selector}"`); - const result = await handle.evaluate(pageFunction, arg); + const result = await handle._evaluateExpression(expression, isFunction, true, arg); handle.dispose(); return result; } @@ -505,8 +516,12 @@ export class Frame { async $$eval(selector: string, pageFunction: js.FuncOn, arg?: any): Promise; async $$eval(selector: string, pageFunction: js.FuncOn, arg: Arg): Promise { assertMaxArguments(arguments.length, 3); + return this._$$evalExpression(selector, String(pageFunction), typeof pageFunction === 'function', arg); + } + + async _$$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise { const arrayHandle = await selectors._queryArray(this, selector); - const result = await arrayHandle.evaluate(pageFunction, arg); + const result = await arrayHandle._evaluateExpression(expression, isFunction, true, arg); arrayHandle.dispose(); return result; } @@ -804,6 +819,11 @@ export class Frame { async waitForFunction(pageFunction: js.Func1, arg: Arg, options?: types.WaitForFunctionOptions): Promise>; async waitForFunction(pageFunction: js.Func1, arg?: any, options?: types.WaitForFunctionOptions): Promise>; async waitForFunction(pageFunction: js.Func1, arg: Arg, options: types.WaitForFunctionOptions = {}): Promise> { + return this._waitForFunctionExpression(String(pageFunction), typeof pageFunction === 'function', arg, options); + } + + + async _waitForFunctionExpression(expression: string, isFunction: boolean, arg: any, options: types.WaitForFunctionOptions = {}): Promise> { const { polling = 'raf' } = options; if (helper.isString(polling)) assert(polling === 'raf', 'Unknown polling option: ' + polling); @@ -811,7 +831,7 @@ export class Frame { assert(polling > 0, 'Cannot poll with non-positive interval: ' + polling); else throw new Error('Unknown polling option: ' + polling); - const predicateBody = helper.isString(pageFunction) ? 'return (' + pageFunction + ')' : 'return (' + pageFunction + ')(arg)'; + const predicateBody = isFunction ? 'return (' + expression + ')(arg)' : 'return (' + expression + ')'; const task = async (context: dom.FrameExecutionContext) => { const injectedScript = await context.injectedScript(); return context.evaluateHandleInternal(({ injectedScript, predicateBody, polling, arg }) => { diff --git a/src/helper.ts b/src/helper.ts index acd19c9fe3783..9d3d1f871f3e8 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -42,8 +42,11 @@ class Helper { assert(args.length === 0 || (args.length === 1 && args[0] === undefined), 'Cannot evaluate a string with arguments'); return fun; } - return `(${fun})(${args.map(serializeArgument).join(',')})`; + return Helper.evaluationStringForFunctionBody(String(fun), ...args); + } + static evaluationStringForFunctionBody(functionBody: string, ...args: any[]): string { + return `(${functionBody})(${args.map(serializeArgument).join(',')})`; function serializeArgument(arg: any): string { if (Object.is(arg, undefined)) return 'undefined'; diff --git a/src/javascript.ts b/src/javascript.ts index fd226b5a1aa3d..e4b7e3184d446 100644 --- a/src/javascript.ts +++ b/src/javascript.ts @@ -18,7 +18,6 @@ import * as dom from './dom'; import * as utilityScriptSource from './generated/utilityScriptSource'; import * as sourceMap from './utils/sourceMap'; import { serializeAsCallArgument } from './common/utilityScriptSerializers'; -import { helper } from './helper'; import UtilityScript from './injected/utilityScript'; type ObjectId = string; @@ -106,6 +105,10 @@ export class JSHandle { return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg); } + _evaluateExpression(expression: string, isFunction: boolean, returnByValue: boolean, arg: any) { + return evaluateExpression(this._context, returnByValue, expression, isFunction, this, arg); + } + async getProperty(propertyName: string): Promise { const objectHandle = await this.evaluateHandle((object: any, propertyName) => { const result: any = {__proto__: null}; @@ -147,16 +150,17 @@ export class JSHandle { } export async function evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise { + return evaluateExpression(context, returnByValue, String(pageFunction), typeof pageFunction === 'function', ...args); +} + +export async function evaluateExpression(context: ExecutionContext, returnByValue: boolean, expression: string, isFunction: boolean, ...args: any[]): Promise { const utilityScript = await context.utilityScript(); - if (helper.isString(pageFunction)) { + if (!isFunction) { const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)` + sourceMap.generateSourceUrl(); - return context._delegate.evaluateWithArguments(script, returnByValue, utilityScript, [returnByValue, sourceMap.ensureSourceUrl(pageFunction)], []); + return context._delegate.evaluateWithArguments(script, returnByValue, utilityScript, [returnByValue, sourceMap.ensureSourceUrl(expression)], []); } - if (typeof pageFunction !== 'function') - throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`); - const originalText = pageFunction.toString(); - let functionText = originalText; + let functionText = expression; try { new Function('(' + functionText + ')'); } catch (e1) { @@ -203,7 +207,7 @@ export async function evaluate(context: ExecutionContext, returnByValue: boolean utilityScriptObjectIds.push(handle._objectId!); } - functionText += await sourceMap.generateSourceMapUrl(originalText, functionText); + functionText += await sourceMap.generateSourceMapUrl(expression, functionText); // See UtilityScript for arguments. const utilityScriptValues = [returnByValue, functionText, args.length, ...args]; diff --git a/src/network.ts b/src/network.ts index 26986c5a25b93..f764986a472e0 100644 --- a/src/network.ts +++ b/src/network.ts @@ -18,35 +18,11 @@ import * as fs from 'fs'; import * as mime from 'mime'; import * as util from 'util'; import * as frames from './frames'; +import * as types from './types'; import { assert, helper } from './helper'; import { URLSearchParams } from 'url'; -export type NetworkCookie = { - name: string, - value: string, - domain: string, - path: string, - expires: number, - httpOnly: boolean, - secure: boolean, - sameSite: 'Strict' | 'Lax' | 'None' -}; - -export type SetNetworkCookieParam = { - name: string, - value: string, - url?: string, - domain?: string, - path?: string, - expires?: number, - httpOnly?: boolean, - secure?: boolean, - sameSite?: 'Strict' | 'Lax' | 'None' -}; - -export function filterCookies(cookies: NetworkCookie[], urls: string | string[] = []): NetworkCookie[] { - if (!Array.isArray(urls)) - urls = [ urls ]; +export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] { const parsedURLs = urls.map(s => new URL(s)); // Chromiums's cookies are missing sameSite when it is 'None' return cookies.filter(c => { @@ -65,7 +41,7 @@ export function filterCookies(cookies: NetworkCookie[], urls: string | string[] }); } -export function rewriteCookies(cookies: SetNetworkCookieParam[]): SetNetworkCookieParam[] { +export function rewriteCookies(cookies: types.SetNetworkCookieParam[]): types.SetNetworkCookieParam[] { return cookies.map(c => { assert(c.name, 'Cookie should have a name'); assert(c.value, 'Cookie should have a value'); @@ -93,8 +69,6 @@ function stripFragmentFromUrl(url: string): string { return parsed.href; } -export type Headers = { [key: string]: string }; - export class Request { readonly _routeDelegate: RouteDelegate | null; private _response: Response | null = null; @@ -107,13 +81,13 @@ export class Request { private _resourceType: string; private _method: string; private _postData: string | null; - private _headers: Headers; + private _headers: types.Headers; private _frame: frames.Frame; private _waitForResponsePromise: Promise; private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {}; constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined, - url: string, resourceType: string, method: string, postData: string | null, headers: Headers) { + url: string, resourceType: string, method: string, postData: string | null, headers: types.Headers) { assert(!url.startsWith('data:'), 'Data urls should not fire requests'); assert(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects'); this._routeDelegate = routeDelegate; @@ -243,7 +217,7 @@ export class Route { await this._delegate.abort(errorCode); } - async fulfill(response: FulfillResponse & { path?: string }) { + async fulfill(response: types.FulfillResponse & { path?: string }) { assert(!this._handled, 'Route is already handled!'); this._handled = true; if (response.path) { @@ -257,7 +231,7 @@ export class Route { await this._delegate.fulfill(response); } - async continue(overrides: { method?: string; headers?: Headers; postData?: string } = {}) { + async continue(overrides: { method?: string; headers?: types.Headers; postData?: string } = {}) { assert(!this._handled, 'Route is already handled!'); await this._delegate.continue(overrides); } @@ -275,10 +249,10 @@ export class Response { private _status: number; private _statusText: string; private _url: string; - private _headers: Headers; + private _headers: types.Headers; private _getResponseBodyCallback: GetResponseBodyCallback; - constructor(request: Request, status: number, statusText: string, headers: Headers, getResponseBodyCallback: GetResponseBodyCallback) { + constructor(request: Request, status: number, statusText: string, headers: types.Headers, getResponseBodyCallback: GetResponseBodyCallback) { this._request = request; this._status = status; this._statusText = statusText; @@ -349,17 +323,10 @@ export class Response { } } -export type FulfillResponse = { - status?: number, - headers?: Headers, - contentType?: string, - body?: string | Buffer, -}; - export interface RouteDelegate { abort(errorCode: string): Promise; - fulfill(response: FulfillResponse): Promise; - continue(overrides: { method?: string; headers?: Headers; postData?: string; }): Promise; + fulfill(response: types.FulfillResponse): Promise; + continue(overrides: { method?: string; headers?: types.Headers; postData?: string; }): Promise; } // List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes. @@ -429,8 +396,8 @@ export const STATUS_TEXTS: { [status: string]: string } = { '511': 'Network Authentication Required', }; -export function verifyHeaders(headers: Headers): Headers { - const result: Headers = {}; +export function verifyHeaders(headers: types.Headers): types.Headers { + const result: types.Headers = {}; for (const key of Object.keys(headers)) { const value = headers[key]; assert(helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`); @@ -439,7 +406,7 @@ export function verifyHeaders(headers: Headers): Headers { return result; } -export function mergeHeaders(headers: (Headers | undefined | null)[]): Headers { +export function mergeHeaders(headers: (types.Headers | undefined | null)[]): types.Headers { const lowerCaseToValue = new Map(); const lowerCaseToOriginalCase = new Map(); for (const h of headers) { @@ -451,7 +418,7 @@ export function mergeHeaders(headers: (Headers | undefined | null)[]): Headers { lowerCaseToValue.set(lower, h[key]); } } - const result: Headers = {}; + const result: types.Headers = {}; for (const [lower, value] of lowerCaseToValue) result[lowerCaseToOriginalCase.get(lower)!] = value; return result; diff --git a/src/page.ts b/src/page.ts index 8a2cc4a41aa1b..4cc1e25d74c0e 100644 --- a/src/page.ts +++ b/src/page.ts @@ -84,7 +84,7 @@ type PageState = { viewportSize: types.Size | null; mediaType: types.MediaType | null; colorScheme: types.ColorScheme | null; - extraHTTPHeaders: network.Headers | null; + extraHTTPHeaders: types.Headers | null; }; export class Page extends EventEmitter { @@ -265,7 +265,7 @@ export class Page extends EventEmitter { await this._delegate.exposeBinding(binding); } - setExtraHTTPHeaders(headers: network.Headers) { + setExtraHTTPHeaders(headers: types.Headers) { this._state.extraHTTPHeaders = network.verifyHeaders(headers); return this._delegate.updateExtraHTTPHeaders(); } @@ -295,7 +295,7 @@ export class Page extends EventEmitter { return this._attributeToPage(() => this.mainFrame().setContent(html, options)); } - async goto(url: string, options?: frames.GotoOptions): Promise { + async goto(url: string, options?: types.GotoOptions): Promise { return this._attributeToPage(() => this.mainFrame().goto(url, options)); } @@ -386,6 +386,10 @@ export class Page extends EventEmitter { async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { const source = await helper.evaluationScript(script, arg); + await this._addInitScriptExpression(source); + } + + async _addInitScriptExpression(source: string) { this._evaluateOnNewDocumentSources.push(source); await this._delegate.evaluateOnNewDocument(source); } @@ -445,11 +449,11 @@ export class Page extends EventEmitter { return this._attributeToPage(() => this.mainFrame().title()); } - async close(options: { runBeforeUnload: (boolean | undefined); } = {runBeforeUnload: undefined}) { + async close(options?: { runBeforeUnload?: boolean }) { if (this._closed) return; assert(!this._disconnected, 'Protocol error: Connection closed. Most likely the page has been closed.'); - const runBeforeUnload = !!options.runBeforeUnload; + const runBeforeUnload = !!options && !!options.runBeforeUnload; await this._delegate.closePage(runBeforeUnload); if (!runBeforeUnload) await this._closedPromise; diff --git a/src/server/browserType.ts b/src/server/browserType.ts index df6a1cd50a6c9..85d1cc4f7418e 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -18,7 +18,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as util from 'util'; -import { BrowserContext, PersistentContextOptions, verifyProxySettings, validateBrowserContextOptions } from '../browserContext'; +import { BrowserContext, verifyProxySettings, validateBrowserContextOptions } from '../browserContext'; import { BrowserServer } from './browserServer'; import * as browserPaths from '../install/browserPaths'; import { Loggers, Logger } from '../logger'; @@ -34,41 +34,21 @@ import { TimeoutSettings } from '../timeoutSettings'; import { WebSocketServer } from './webSocketServer'; import { LoggerSink } from '../loggerSink'; -export type FirefoxUserPrefsOptions = { - firefoxUserPrefs?: { [key: string]: string | number | boolean }, -}; +type FirefoxPrefsOptions = { firefoxUserPrefs?: { [key: string]: string | number | boolean } }; +type LaunchOptions = types.LaunchOptions & { logger?: LoggerSink }; +type ConnectOptions = types.ConnectOptions & { logger?: LoggerSink }; -export type LaunchOptionsBase = { - executablePath?: string, - args?: string[], - ignoreDefaultArgs?: boolean | string[], - handleSIGINT?: boolean, - handleSIGTERM?: boolean, - handleSIGHUP?: boolean, - timeout?: number, - logger?: LoggerSink, - env?: Env, - headless?: boolean, - devtools?: boolean, - proxy?: types.ProxySettings, - downloadsPath?: string, -}; -type ConnectOptions = { - wsEndpoint: string, - slowMo?: number, - logger?: LoggerSink, - timeout?: number, -}; -export type LaunchOptions = LaunchOptionsBase & { slowMo?: number }; -type LaunchServerOptions = LaunchOptionsBase & { port?: number }; +export type LaunchNonPersistentOptions = LaunchOptions & FirefoxPrefsOptions; +type LaunchPersistentOptions = LaunchOptions & types.BrowserContextOptions; +type LaunchServerOptions = types.LaunchServerOptions & { logger?: LoggerSink } & FirefoxPrefsOptions; export interface BrowserType { executablePath(): string; name(): string; - launch(options?: LaunchOptions & FirefoxUserPrefsOptions): Promise; - launchServer(options?: LaunchServerOptions & FirefoxUserPrefsOptions): Promise; - launchPersistentContext(userDataDir: string, options?: LaunchOptions & PersistentContextOptions): Promise; + launch(options?: LaunchNonPersistentOptions): Promise; + launchServer(options?: LaunchServerOptions): Promise; + launchPersistentContext(userDataDir: string, options?: LaunchPersistentOptions): Promise; connect(options: ConnectOptions): Promise; } @@ -102,7 +82,7 @@ export abstract class BrowserTypeBase implements BrowserType { return this._name; } - async launch(options: LaunchOptions = {}): Promise { + async launch(options: LaunchNonPersistentOptions = {}): Promise { assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); options = validateLaunchOptions(options); @@ -111,7 +91,7 @@ export abstract class BrowserTypeBase implements BrowserType { return browser; } - async launchPersistentContext(userDataDir: string, options: LaunchOptions & PersistentContextOptions = {}): Promise { + async launchPersistentContext(userDataDir: string, options: LaunchPersistentOptions = {}): Promise { assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); options = validateLaunchOptions(options); const persistent = validateBrowserContextOptions(options); @@ -120,7 +100,7 @@ export abstract class BrowserTypeBase implements BrowserType { return browser._defaultContext!; } - async _innerLaunch(progress: Progress, options: LaunchOptions, logger: Loggers, persistent: PersistentContextOptions | undefined, userDataDir?: string): Promise { + async _innerLaunch(progress: Progress, options: LaunchOptions, logger: Loggers, persistent: types.BrowserContextOptions | undefined, userDataDir?: string): Promise { options.proxy = options.proxy ? verifyProxySettings(options.proxy) : undefined; const { browserServer, downloadsPath, transport } = await this._launchServer(progress, options, !!persistent, logger, userDataDir); if ((options as any).__testHookBeforeCreateBrowser) @@ -246,7 +226,7 @@ export abstract class BrowserTypeBase implements BrowserType { return { browserServer, downloadsPath, transport }; } - abstract _defaultArgs(options: LaunchOptionsBase, isPersistent: boolean, userDataDir: string): string[]; + abstract _defaultArgs(options: types.LaunchOptionsBase, isPersistent: boolean, userDataDir: string): string[]; abstract _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise; abstract _startWebSocketServer(transport: ConnectionTransport, logger: Logger, port: number): WebSocketServer; abstract _amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env; @@ -260,7 +240,7 @@ function copyTestHooks(from: object, to: object) { } } -function validateLaunchOptions(options: Options): Options { +function validateLaunchOptions(options: Options): Options { const { devtools = false, headless = !helper.isDebugMode() && !devtools } = options; return { ...options, devtools, headless }; } diff --git a/src/server/chromium.ts b/src/server/chromium.ts index 79c61bc675c78..a2b62796050e0 100644 --- a/src/server/chromium.ts +++ b/src/server/chromium.ts @@ -21,13 +21,14 @@ import { CRBrowser } from '../chromium/crBrowser'; import * as ws from 'ws'; import { Env } from './processLauncher'; import { kBrowserCloseMessageId } from '../chromium/crConnection'; -import { LaunchOptionsBase, BrowserTypeBase } from './browserType'; +import { BrowserTypeBase } from './browserType'; import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import { Logger } from '../logger'; import { BrowserDescriptor } from '../install/browserPaths'; import { CRDevTools } from '../chromium/crDevTools'; import { BrowserOptions } from '../browser'; import { WebSocketServer } from './webSocketServer'; +import { LaunchOptionsBase } from '../types'; export class Chromium extends BrowserTypeBase { private _devtools: CRDevTools | undefined; diff --git a/src/server/firefox.ts b/src/server/firefox.ts index a9d27627167f2..bfb593c335aa8 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -21,7 +21,7 @@ import * as path from 'path'; import * as ws from 'ws'; import { FFBrowser } from '../firefox/ffBrowser'; import { kBrowserCloseMessageId } from '../firefox/ffConnection'; -import { LaunchOptionsBase, BrowserTypeBase, FirefoxUserPrefsOptions } from './browserType'; +import { BrowserTypeBase, LaunchNonPersistentOptions } from './browserType'; import { Env } from './processLauncher'; import { ConnectionTransport, ProtocolResponse, ProtocolRequest } from '../transport'; import { Logger } from '../logger'; @@ -56,7 +56,7 @@ export class Firefox extends BrowserTypeBase { return startWebSocketServer(transport, logger, port); } - _defaultArgs(options: LaunchOptionsBase & FirefoxUserPrefsOptions, isPersistent: boolean, userDataDir: string): string[] { + _defaultArgs(options: LaunchNonPersistentOptions, isPersistent: boolean, userDataDir: string): string[] { const { args = [], proxy, devtools, headless } = options; if (devtools) console.warn('devtools parameter is not supported as a launch argument in Firefox. You can launch the devtools window manually.'); diff --git a/src/server/webkit.ts b/src/server/webkit.ts index 4b9df8895bdc3..bcdecbfe54aee 100644 --- a/src/server/webkit.ts +++ b/src/server/webkit.ts @@ -19,7 +19,7 @@ import { WKBrowser } from '../webkit/wkBrowser'; import { Env } from './processLauncher'; import * as path from 'path'; import { kBrowserCloseMessageId } from '../webkit/wkConnection'; -import { LaunchOptionsBase, BrowserTypeBase } from './browserType'; +import { BrowserTypeBase } from './browserType'; import { ConnectionTransport, ProtocolResponse, ProtocolRequest } from '../transport'; import * as ws from 'ws'; import { Logger } from '../logger'; @@ -27,6 +27,7 @@ import { BrowserOptions } from '../browser'; import { BrowserDescriptor } from '../install/browserPaths'; import { WebSocketServer } from './webSocketServer'; import { assert } from '../helper'; +import { LaunchOptionsBase } from '../types'; export class WebKit extends BrowserTypeBase { constructor(packagePath: string, browser: BrowserDescriptor) { diff --git a/src/types.ts b/src/types.ts index dac1b37b72151..2c4d4ea3d4767 100644 --- a/src/types.ts +++ b/src/types.ts @@ -180,3 +180,85 @@ export type MouseMultiClickOptions = PointerActionOptions & { }; export type World = 'main' | 'utility'; + +export type Headers = { [key: string]: string }; + +export type GotoOptions = NavigateOptions & { + referer?: string, +}; + +export type FulfillResponse = { + status?: number, + headers?: Headers, + contentType?: string, + body?: string | Buffer, +}; + +export type NetworkCookie = { + name: string, + value: string, + domain: string, + path: string, + expires: number, + httpOnly: boolean, + secure: boolean, + sameSite: 'Strict' | 'Lax' | 'None' +}; + +export type SetNetworkCookieParam = { + name: string, + value: string, + url?: string, + domain?: string, + path?: string, + expires?: number, + httpOnly?: boolean, + secure?: boolean, + sameSite?: 'Strict' | 'Lax' | 'None' +}; + +export type BrowserContextOptions = { + viewport?: Size | null, + ignoreHTTPSErrors?: boolean, + javaScriptEnabled?: boolean, + bypassCSP?: boolean, + userAgent?: string, + locale?: string, + timezoneId?: string, + geolocation?: Geolocation, + permissions?: string[], + extraHTTPHeaders?: Headers, + offline?: boolean, + httpCredentials?: Credentials, + deviceScaleFactor?: number, + isMobile?: boolean, + hasTouch?: boolean, + colorScheme?: ColorScheme, + acceptDownloads?: boolean, +}; + +export type Env = {[key: string]: string | number | boolean | undefined}; + +export type LaunchOptionsBase = { + executablePath?: string, + args?: string[], + ignoreDefaultArgs?: boolean | string[], + handleSIGINT?: boolean, + handleSIGTERM?: boolean, + handleSIGHUP?: boolean, + timeout?: number, + env?: Env, + headless?: boolean, + devtools?: boolean, + proxy?: ProxySettings, + downloadsPath?: string, +}; + +export type LaunchOptions = LaunchOptionsBase & { slowMo?: number }; +export type LaunchServerOptions = LaunchOptionsBase & { port?: number }; + +export type ConnectOptions = { + wsEndpoint: string, + slowMo?: number, + timeout?: number, +}; diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index f4adfaa5f376a..e72c8f0a96f68 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { BrowserBase, BrowserOptions } from '../browser'; -import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions, verifyGeolocation } from '../browserContext'; +import { BrowserBase, BrowserOptions, BrowserContextOptions } from '../browser'; +import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, validateBrowserContextOptions, verifyGeolocation } from '../browserContext'; import { Events } from '../events'; import { helper, RegisteredListener, assert } from '../helper'; import * as network from '../network'; @@ -202,7 +202,7 @@ export class WKBrowserContext extends BrowserContextBase { readonly _browserContextId: string | undefined; readonly _evaluateOnNewDocumentSources: string[]; - constructor(browser: WKBrowser, browserContextId: string | undefined, options: BrowserContextOptions) { + constructor(browser: WKBrowser, browserContextId: string | undefined, options: types.BrowserContextOptions) { super(browser, options); this._browser = browser; this._browserContextId = browserContextId; @@ -257,17 +257,17 @@ export class WKBrowserContext extends BrowserContextBase { throw result; } - async cookies(urls?: string | string[]): Promise { + async _doCookies(urls: string[]): Promise { const { cookies } = await this._browser._browserSession.send('Playwright.getAllCookies', { browserContextId: this._browserContextId }); - return network.filterCookies(cookies.map((c: network.NetworkCookie) => { + return network.filterCookies(cookies.map((c: types.NetworkCookie) => { const copy: any = { ... c }; copy.expires = c.expires === -1 ? -1 : c.expires / 1000; delete copy.session; - return copy as network.NetworkCookie; + return copy as types.NetworkCookie; }), urls); } - async addCookies(cookies: network.SetNetworkCookieParam[]) { + async addCookies(cookies: types.SetNetworkCookieParam[]) { const cc = network.rewriteCookies(cookies).map(c => ({ ...c, session: c.expires === -1 || c.expires === undefined, @@ -296,7 +296,7 @@ export class WKBrowserContext extends BrowserContextBase { await this._browser._browserSession.send('Playwright.setGeolocationOverride', { browserContextId: this._browserContextId, geolocation: payload }); } - async setExtraHTTPHeaders(headers: network.Headers): Promise { + async setExtraHTTPHeaders(headers: types.Headers): Promise { this._options.extraHTTPHeaders = network.verifyHeaders(headers); for (const page of this.pages()) await (page._delegate as WKPage).updateExtraHTTPHeaders(); @@ -314,8 +314,7 @@ export class WKBrowserContext extends BrowserContextBase { await (page._delegate as WKPage).updateHttpCredentials(); } - async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { - const source = await helper.evaluationScript(script, arg); + async _doAddInitScript(source: string) { this._evaluateOnNewDocumentSources.push(source); for (const page of this.pages()) await (page._delegate as WKPage)._updateBootstrapScript(); diff --git a/src/webkit/wkInterceptableRequest.ts b/src/webkit/wkInterceptableRequest.ts index ff8a7f58bf3dc..5695e1404f687 100644 --- a/src/webkit/wkInterceptableRequest.ts +++ b/src/webkit/wkInterceptableRequest.ts @@ -18,6 +18,7 @@ import * as frames from '../frames'; import { assert, helper } from '../helper'; import * as network from '../network'; +import * as types from '../types'; import { Protocol } from './protocol'; import { WKSession } from './wkConnection'; @@ -65,7 +66,7 @@ export class WKInterceptableRequest implements network.RouteDelegate { await this._session.sendMayFail('Network.interceptRequestWithError', { requestId: this._requestId, errorType }); } - async fulfill(response: network.FulfillResponse) { + async fulfill(response: types.FulfillResponse) { await this._interceptedPromise; const base64Encoded = !!response.body && !helper.isString(response.body); @@ -101,7 +102,7 @@ export class WKInterceptableRequest implements network.RouteDelegate { }); } - async continue(overrides: { method?: string; headers?: network.Headers; postData?: string }) { + async continue(overrides: { method?: string; headers?: types.Headers; postData?: string }) { await this._interceptedPromise; // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. @@ -122,8 +123,8 @@ export class WKInterceptableRequest implements network.RouteDelegate { } } -function headersObject(headers: Protocol.Network.Headers): network.Headers { - const result: network.Headers = {}; +function headersObject(headers: Protocol.Network.Headers): types.Headers { + const result: types.Headers = {}; for (const key of Object.keys(headers)) result[key.toLowerCase()] = headers[key]; return result; diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index 565ccb1339221..a1f62da5391dc 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -552,7 +552,7 @@ export class WKPage implements PageDelegate { await this._updateState('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() }); } - _calculateExtraHTTPHeaders(): network.Headers { + _calculateExtraHTTPHeaders(): types.Headers { const headers = network.mergeHeaders([ this._browserContext._options.extraHTTPHeaders, this._page._state.extraHTTPHeaders