feat(state): allow getting / setting context state (#4412)

This commit is contained in:
Pavel Feldman 2020-11-13 14:24:53 -08:00 committed by GitHub
parent a35d207091
commit d20e56e197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 445 additions and 224 deletions

View File

@ -240,6 +240,22 @@ Indicates that the browser is connected.
- `size` <[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary to fit the specified size. - `size` <[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary to fit the specified size.
- `width` <[number]> Video frame width. - `width` <[number]> Video frame width.
- `height` <[number]> Video frame height. - `height` <[number]> Video frame height.
- `storageState` <[Object]> Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via [browserContext.storageState()](#browsercontextstoragestate).
- `cookies` <[Array]<[Object]>> Optional cookies to set for context
- `name` <[string]> **required**
- `value` <[string]> **required**
- `url` <[string]> Optional either url or domain / path are required
- `domain` <[string]> Optional either url or domain / path are required
- `path` <[string]> Optional either url or domain / path are required
- `expires` <[number]> Optional Unix time in seconds.
- `httpOnly` <[boolean]> Optional httpOnly flag
- `secure` <[boolean]> Optional secure flag
- `sameSite` <"Strict"|"Lax"|"None"> Optional sameSite flag
- `origins` <[Array]<[Object]>> Optional localStorage to set for context
- `origin` <[string]>
- `localStorage` <[Array]<[Object]>>
- `name` <[string]>
- `value` <[string]>
- returns: <[Promise]<[BrowserContext]>> - returns: <[Promise]<[BrowserContext]>>
Creates a new browser context. It won't share cookies/cache with other browser contexts. Creates a new browser context. It won't share cookies/cache with other browser contexts.
@ -299,6 +315,22 @@ Creates a new browser context. It won't share cookies/cache with other browser c
- `size` <[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary to fit the specified size. - `size` <[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary to fit the specified size.
- `width` <[number]> Video frame width. - `width` <[number]> Video frame width.
- `height` <[number]> Video frame height. - `height` <[number]> Video frame height.
- `storageState` <[Object]> Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via [browserContext.storageState()](#browsercontextstoragestate).
- `cookies` <[Array]<[Object]>> Optional cookies to set for context
- `name` <[string]> **required**
- `value` <[string]> **required**
- `url` <[string]> Optional either url or domain / path are required
- `domain` <[string]> Optional either url or domain / path are required
- `path` <[string]> Optional either url or domain / path are required
- `expires` <[number]> Optional Unix time in seconds.
- `httpOnly` <[boolean]> Optional httpOnly flag
- `secure` <[boolean]> Optional secure flag
- `sameSite` <"Strict"|"Lax"|"None"> Optional sameSite flag
- `origins` <[Array]<[Object]>> Optional localStorage to set for context
- `origin` <[string]>
- `localStorage` <[Array]<[Object]>>
- `name` <[string]>
- `value` <[string]>
- returns: <[Promise]<[Page]>> - returns: <[Promise]<[Page]>>
Creates a new page in a new browser context. Closing this page will close the context as well. Creates a new page in a new browser context. Closing this page will close the context as well.
@ -355,6 +387,7 @@ await context.close();
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation) - [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials) - [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline) - [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.storageState()](#browsercontextstoragestate)
- [browserContext.unroute(url[, handler])](#browsercontextunrouteurl-handler) - [browserContext.unroute(url[, handler])](#browsercontextunrouteurl-handler)
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate) - [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
<!-- GEN:stop --> <!-- GEN:stop -->
@ -680,6 +713,25 @@ Provide credentials for [HTTP authentication](https://developer.mozilla.org/en-U
- `offline` <[boolean]> Whether to emulate network being offline for the browser context. - `offline` <[boolean]> Whether to emulate network being offline for the browser context.
- returns: <[Promise]> - returns: <[Promise]>
#### browserContext.storageState()
- returns: <[Promise]<[Object]>>
- `cookies` <[Array]<[Object]>>
- `name` <[string]>
- `value` <[string]>
- `domain` <[string]>
- `path` <[string]>
- `expires` <[number]> Unix time in seconds.
- `httpOnly` <[boolean]>
- `secure` <[boolean]>
- `sameSite` <"Strict"|"Lax"|"None">
- `origins` <[Array]<[Object]>>
- `origin` <[string]>
- `localStorage` <[Array]<[Object]>>
- `name` <[string]>
- `value` <[string]>
Returns storage state for this browser context, contains current cookies and local storage snapshot.
#### browserContext.unroute(url[, handler]) #### browserContext.unroute(url[, handler])
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with [browserContext.route(url, handler)](#browsercontextrouteurl-handler). - `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with [browserContext.route(url, handler)](#browsercontextrouteurl-handler).
- `handler` <[function]\([Route], [Request]\)> Handler function used to register a routing with [browserContext.route(url, handler)](#browsercontextrouteurl-handler). - `handler` <[function]\([Route], [Request]\)> Handler function used to register a routing with [browserContext.route(url, handler)](#browsercontextrouteurl-handler).
@ -4686,6 +4738,7 @@ const backgroundPage = await context.waitForEvent('backgroundpage');
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation) - [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials) - [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline) - [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.storageState()](#browsercontextstoragestate)
- [browserContext.unroute(url[, handler])](#browsercontextunrouteurl-handler) - [browserContext.unroute(url[, handler])](#browsercontextunrouteurl-handler)
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate) - [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
<!-- GEN:stop --> <!-- GEN:stop -->

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "playwright-internal", "name": "playwright-internal",
"version": "1.6.0-next", "version": "1.7.0-next",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -24,7 +24,7 @@ import { Browser } from './browser';
import { Events } from './events'; import { Events } from './events';
import { TimeoutSettings } from '../utils/timeoutSettings'; import { TimeoutSettings } from '../utils/timeoutSettings';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions } from './types'; import { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions, StorageState } from './types';
import { isUnderTest, headersObjectToArray } from '../utils/utils'; import { isUnderTest, headersObjectToArray } from '../utils/utils';
import { isSafeCloseError } from '../utils/errors'; import { isSafeCloseError } from '../utils/errors';
@ -219,6 +219,12 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
return result; return result;
} }
async storageState(): Promise<StorageState> {
return await this._wrapApiCall('browserContext.storageState', async () => {
return await this._channel.storageState();
});
}
async _onClose() { async _onClose() {
if (this._browser) if (this._browser)
this._browser._contexts.delete(this); this._browser._contexts.delete(this);

View File

@ -36,6 +36,14 @@ export type WaitForFunctionOptions = { timeout?: number, polling?: 'raf' | numbe
export type SelectOption = { value?: string, label?: string, index?: number }; export type SelectOption = { value?: string, label?: string, index?: number };
export type SelectOptionOptions = { timeout?: number, noWaitAfter?: boolean }; export type SelectOptionOptions = { timeout?: number, noWaitAfter?: boolean };
export type FilePayload = { name: string, mimeType: string, buffer: Buffer }; export type FilePayload = { name: string, mimeType: string, buffer: Buffer };
export type StorageState = {
cookies: channels.NetworkCookie[],
origins: channels.OriginStorage[]
};
export type SetStorageState = {
cookies?: channels.SetNetworkCookie[],
origins?: channels.OriginStorage[]
};
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle'; export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle';
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle']); export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle']);
@ -58,7 +66,7 @@ type FirefoxUserPrefs = {
}; };
type LaunchOptionsBase = Omit<channels.BrowserTypeLaunchOptions, 'ignoreAllDefaultArgs' | 'ignoreDefaultArgs' | 'env' | 'firefoxUserPrefs'> & LaunchOverrides; type LaunchOptionsBase = Omit<channels.BrowserTypeLaunchOptions, 'ignoreAllDefaultArgs' | 'ignoreDefaultArgs' | 'env' | 'firefoxUserPrefs'> & LaunchOverrides;
export type LaunchOptions = LaunchOptionsBase & FirefoxUserPrefs; export type LaunchOptions = LaunchOptionsBase & FirefoxUserPrefs;
export type LaunchPersistentContextOptions = LaunchOptionsBase & BrowserContextOptions; export type LaunchPersistentContextOptions = Omit<LaunchOptionsBase & BrowserContextOptions, 'storageState'>;
export type ConnectOptions = { export type ConnectOptions = {
wsEndpoint: string, wsEndpoint: string,

View File

@ -117,6 +117,10 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
}); });
} }
async storageState(): Promise<channels.BrowserContextStorageStateResult> {
return await this._context.storageState();
}
async close(): Promise<void> { async close(): Promise<void> {
await this._context.close(); await this._context.close();
} }

View File

@ -34,7 +34,10 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserIniti
} }
async newContext(params: channels.BrowserNewContextParams): Promise<channels.BrowserNewContextResult> { async newContext(params: channels.BrowserNewContextParams): Promise<channels.BrowserNewContextResult> {
return { context: new BrowserContextDispatcher(this._scope, await this._object.newContext(params)) }; const context = await this._object.newContext(params);
if (params.storageState)
await context.setStorageState(params.storageState);
return { context: new BrowserContextDispatcher(this._scope, context) };
} }
async close(): Promise<void> { async close(): Promise<void> {

View File

@ -138,10 +138,10 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
async setNetworkInterceptionEnabled(params: channels.PageSetNetworkInterceptionEnabledParams): Promise<void> { async setNetworkInterceptionEnabled(params: channels.PageSetNetworkInterceptionEnabledParams): Promise<void> {
if (!params.enabled) { if (!params.enabled) {
await this._page._setRequestInterceptor(undefined); await this._page._setClientRequestInterceptor(undefined);
return; return;
} }
this._page._setRequestInterceptor((route, request) => { this._page._setClientRequestInterceptor((route, request) => {
this._dispatchEvent('route', { route: new RouteDispatcher(this._scope, route), request: RequestDispatcher.from(this._scope, request) }); this._dispatchEvent('route', { route: new RouteDispatcher(this._scope, route), request: RequestDispatcher.from(this._scope, request) });
}); });
} }

View File

@ -80,6 +80,39 @@ export type AXNode = {
children?: AXNode[], children?: AXNode[],
}; };
export type SetNetworkCookie = {
name: string,
value: string,
url?: string,
domain?: string,
path?: string,
expires?: number,
httpOnly?: boolean,
secure?: boolean,
sameSite?: 'Strict' | 'Lax' | 'None',
};
export type NetworkCookie = {
name: string,
value: string,
domain: string,
path: string,
expires: number,
httpOnly: boolean,
secure: boolean,
sameSite: 'Strict' | 'Lax' | 'None',
};
export type NameValue = {
name: string,
value: string,
};
export type OriginStorage = {
origin: string,
localStorage: NameValue[],
};
export type SerializedError = { export type SerializedError = {
error?: { error?: {
message: string, message: string,
@ -162,10 +195,7 @@ export type BrowserTypeLaunchParams = {
handleSIGTERM?: boolean, handleSIGTERM?: boolean,
handleSIGHUP?: boolean, handleSIGHUP?: boolean,
timeout?: number, timeout?: number,
env?: { env?: NameValue[],
name: string,
value: string,
}[],
headless?: boolean, headless?: boolean,
devtools?: boolean, devtools?: boolean,
proxy?: { proxy?: {
@ -188,10 +218,7 @@ export type BrowserTypeLaunchOptions = {
handleSIGTERM?: boolean, handleSIGTERM?: boolean,
handleSIGHUP?: boolean, handleSIGHUP?: boolean,
timeout?: number, timeout?: number,
env?: { env?: NameValue[],
name: string,
value: string,
}[],
headless?: boolean, headless?: boolean,
devtools?: boolean, devtools?: boolean,
proxy?: { proxy?: {
@ -218,10 +245,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
handleSIGTERM?: boolean, handleSIGTERM?: boolean,
handleSIGHUP?: boolean, handleSIGHUP?: boolean,
timeout?: number, timeout?: number,
env?: { env?: NameValue[],
name: string,
value: string,
}[],
headless?: boolean, headless?: boolean,
devtools?: boolean, devtools?: boolean,
proxy?: { proxy?: {
@ -250,10 +274,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
accuracy?: number, accuracy?: number,
}, },
permissions?: string[], permissions?: string[],
extraHTTPHeaders?: { extraHTTPHeaders?: NameValue[],
name: string,
value: string,
}[],
offline?: boolean, offline?: boolean,
httpCredentials?: { httpCredentials?: {
username: string, username: string,
@ -287,10 +308,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
handleSIGTERM?: boolean, handleSIGTERM?: boolean,
handleSIGHUP?: boolean, handleSIGHUP?: boolean,
timeout?: number, timeout?: number,
env?: { env?: NameValue[],
name: string,
value: string,
}[],
headless?: boolean, headless?: boolean,
devtools?: boolean, devtools?: boolean,
proxy?: { proxy?: {
@ -319,10 +337,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
accuracy?: number, accuracy?: number,
}, },
permissions?: string[], permissions?: string[],
extraHTTPHeaders?: { extraHTTPHeaders?: NameValue[],
name: string,
value: string,
}[],
offline?: boolean, offline?: boolean,
httpCredentials?: { httpCredentials?: {
username: string, username: string,
@ -386,10 +401,7 @@ export type BrowserNewContextParams = {
accuracy?: number, accuracy?: number,
}, },
permissions?: string[], permissions?: string[],
extraHTTPHeaders?: { extraHTTPHeaders?: NameValue[],
name: string,
value: string,
}[],
offline?: boolean, offline?: boolean,
httpCredentials?: { httpCredentials?: {
username: string, username: string,
@ -419,6 +431,10 @@ export type BrowserNewContextParams = {
username?: string, username?: string,
password?: string, password?: string,
}, },
storageState?: {
cookies?: SetNetworkCookie[],
origins?: OriginStorage[],
},
}; };
export type BrowserNewContextOptions = { export type BrowserNewContextOptions = {
noDefaultViewport?: boolean, noDefaultViewport?: boolean,
@ -438,10 +454,7 @@ export type BrowserNewContextOptions = {
accuracy?: number, accuracy?: number,
}, },
permissions?: string[], permissions?: string[],
extraHTTPHeaders?: { extraHTTPHeaders?: NameValue[],
name: string,
value: string,
}[],
offline?: boolean, offline?: boolean,
httpCredentials?: { httpCredentials?: {
username: string, username: string,
@ -471,6 +484,10 @@ export type BrowserNewContextOptions = {
username?: string, username?: string,
password?: string, password?: string,
}, },
storageState?: {
cookies?: SetNetworkCookie[],
origins?: OriginStorage[],
},
}; };
export type BrowserNewContextResult = { export type BrowserNewContextResult = {
context: BrowserContextChannel, context: BrowserContextChannel,
@ -526,6 +543,7 @@ export interface BrowserContextChannel extends Channel {
setHTTPCredentials(params: BrowserContextSetHTTPCredentialsParams, metadata?: Metadata): Promise<BrowserContextSetHTTPCredentialsResult>; setHTTPCredentials(params: BrowserContextSetHTTPCredentialsParams, metadata?: Metadata): Promise<BrowserContextSetHTTPCredentialsResult>;
setNetworkInterceptionEnabled(params: BrowserContextSetNetworkInterceptionEnabledParams, metadata?: Metadata): Promise<BrowserContextSetNetworkInterceptionEnabledResult>; setNetworkInterceptionEnabled(params: BrowserContextSetNetworkInterceptionEnabledParams, metadata?: Metadata): Promise<BrowserContextSetNetworkInterceptionEnabledResult>;
setOffline(params: BrowserContextSetOfflineParams, metadata?: Metadata): Promise<BrowserContextSetOfflineResult>; setOffline(params: BrowserContextSetOfflineParams, metadata?: Metadata): Promise<BrowserContextSetOfflineResult>;
storageState(params?: BrowserContextStorageStateParams, metadata?: Metadata): Promise<BrowserContextStorageStateResult>;
crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextCrNewCDPSessionResult>; crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextCrNewCDPSessionResult>;
} }
export type BrowserContextBindingCallEvent = { export type BrowserContextBindingCallEvent = {
@ -546,17 +564,7 @@ export type BrowserContextCrServiceWorkerEvent = {
worker: WorkerChannel, worker: WorkerChannel,
}; };
export type BrowserContextAddCookiesParams = { export type BrowserContextAddCookiesParams = {
cookies: { cookies: SetNetworkCookie[],
name: string,
value: string,
url?: string,
domain?: string,
path?: string,
expires?: number,
httpOnly?: boolean,
secure?: boolean,
sameSite?: 'Strict' | 'Lax' | 'None',
}[],
}; };
export type BrowserContextAddCookiesOptions = { export type BrowserContextAddCookiesOptions = {
@ -585,16 +593,7 @@ export type BrowserContextCookiesOptions = {
}; };
export type BrowserContextCookiesResult = { export type BrowserContextCookiesResult = {
cookies: { cookies: NetworkCookie[],
name: string,
value: string,
domain: string,
path: string,
expires: number,
httpOnly: boolean,
secure: boolean,
sameSite: 'Strict' | 'Lax' | 'None',
}[],
}; };
export type BrowserContextExposeBindingParams = { export type BrowserContextExposeBindingParams = {
name: string, name: string,
@ -632,10 +631,7 @@ export type BrowserContextSetDefaultTimeoutNoReplyOptions = {
}; };
export type BrowserContextSetDefaultTimeoutNoReplyResult = void; export type BrowserContextSetDefaultTimeoutNoReplyResult = void;
export type BrowserContextSetExtraHTTPHeadersParams = { export type BrowserContextSetExtraHTTPHeadersParams = {
headers: { headers: NameValue[],
name: string,
value: string,
}[],
}; };
export type BrowserContextSetExtraHTTPHeadersOptions = { export type BrowserContextSetExtraHTTPHeadersOptions = {
@ -683,6 +679,12 @@ export type BrowserContextSetOfflineOptions = {
}; };
export type BrowserContextSetOfflineResult = void; export type BrowserContextSetOfflineResult = void;
export type BrowserContextStorageStateParams = {};
export type BrowserContextStorageStateOptions = {};
export type BrowserContextStorageStateResult = {
cookies: NetworkCookie[],
origins: OriginStorage[],
};
export type BrowserContextCrNewCDPSessionParams = { export type BrowserContextCrNewCDPSessionParams = {
page: PageChannel, page: PageChannel,
}; };
@ -938,10 +940,7 @@ export type PageScreenshotResult = {
binary: Binary, binary: Binary,
}; };
export type PageSetExtraHTTPHeadersParams = { export type PageSetExtraHTTPHeadersParams = {
headers: { headers: NameValue[],
name: string,
value: string,
}[],
}; };
export type PageSetExtraHTTPHeadersOptions = { export type PageSetExtraHTTPHeadersOptions = {
@ -2141,36 +2140,24 @@ export type RouteAbortOptions = {
export type RouteAbortResult = void; export type RouteAbortResult = void;
export type RouteContinueParams = { export type RouteContinueParams = {
method?: string, method?: string,
headers?: { headers?: NameValue[],
name: string,
value: string,
}[],
postData?: Binary, postData?: Binary,
}; };
export type RouteContinueOptions = { export type RouteContinueOptions = {
method?: string, method?: string,
headers?: { headers?: NameValue[],
name: string,
value: string,
}[],
postData?: Binary, postData?: Binary,
}; };
export type RouteContinueResult = void; export type RouteContinueResult = void;
export type RouteFulfillParams = { export type RouteFulfillParams = {
status?: number, status?: number,
headers?: { headers?: NameValue[],
name: string,
value: string,
}[],
body?: string, body?: string,
isBase64?: boolean, isBase64?: boolean,
}; };
export type RouteFulfillOptions = { export type RouteFulfillOptions = {
status?: number, status?: number,
headers?: { headers?: NameValue[],
name: string,
value: string,
}[],
body?: string, body?: string,
isBase64?: boolean, isBase64?: boolean,
}; };
@ -2401,10 +2388,7 @@ export type ElectronLaunchParams = {
executablePath: string, executablePath: string,
args?: string[], args?: string[],
cwd?: string, cwd?: string,
env?: { env?: NameValue[],
name: string,
value: string,
}[],
handleSIGINT?: boolean, handleSIGINT?: boolean,
handleSIGTERM?: boolean, handleSIGTERM?: boolean,
handleSIGHUP?: boolean, handleSIGHUP?: boolean,
@ -2413,10 +2397,7 @@ export type ElectronLaunchParams = {
export type ElectronLaunchOptions = { export type ElectronLaunchOptions = {
args?: string[], args?: string[],
cwd?: string, cwd?: string,
env?: { env?: NameValue[],
name: string,
value: string,
}[],
handleSIGINT?: boolean, handleSIGINT?: boolean,
handleSIGTERM?: boolean, handleSIGTERM?: boolean,
handleSIGHUP?: boolean, handleSIGHUP?: boolean,

View File

@ -113,6 +113,59 @@ AXNode:
items: AXNode items: AXNode
SetNetworkCookie:
type: object
properties:
name: string
value: string
url: string?
domain: string?
path: string?
expires: number?
httpOnly: boolean?
secure: boolean?
sameSite:
type: enum?
literals:
- Strict
- Lax
- None
NetworkCookie:
type: object
properties:
name: string
value: string
domain: string
path: string
expires: number
httpOnly: boolean
secure: boolean
sameSite:
type: enum
literals:
- Strict
- Lax
- None
NameValue:
type: object
properties:
name: string
value: string
OriginStorage:
type: object
properties:
origin: string
localStorage:
type: array
items: NameValue
SerializedError: SerializedError:
type: object type: object
properties: properties:
@ -216,11 +269,7 @@ BrowserType:
timeout: number? timeout: number?
env: env:
type: array? type: array?
items: items: NameValue
type: object
properties:
name: string
value: string
headless: boolean? headless: boolean?
devtools: boolean? devtools: boolean?
proxy: proxy:
@ -254,11 +303,7 @@ BrowserType:
timeout: number? timeout: number?
env: env:
type: array? type: array?
items: items: NameValue
type: object
properties:
name: string
value: string
headless: boolean? headless: boolean?
devtools: boolean? devtools: boolean?
proxy: proxy:
@ -294,11 +339,7 @@ BrowserType:
items: string items: string
extraHTTPHeaders: extraHTTPHeaders:
type: array? type: array?
items: items: NameValue
type: object
properties:
name: string
value: string
offline: boolean? offline: boolean?
httpCredentials: httpCredentials:
type: object? type: object?
@ -371,11 +412,7 @@ Browser:
items: string items: string
extraHTTPHeaders: extraHTTPHeaders:
type: array? type: array?
items: items: NameValue
type: object
properties:
name: string
value: string
offline: boolean? offline: boolean?
httpCredentials: httpCredentials:
type: object? type: object?
@ -415,6 +452,15 @@ Browser:
bypass: string? bypass: string?
username: string? username: string?
password: string? password: string?
storageState:
type: object?
properties:
cookies:
type: array?
items: SetNetworkCookie
origins:
type: array?
items: OriginStorage
returns: returns:
context: BrowserContext context: BrowserContext
@ -453,23 +499,7 @@ BrowserContext:
parameters: parameters:
cookies: cookies:
type: array type: array
items: items: SetNetworkCookie
type: object
properties:
name: string
value: string
url: string?
domain: string?
path: string?
expires: number?
httpOnly: boolean?
secure: boolean?
sameSite:
type: enum?
literals:
- Strict
- Lax
- None
addInitScript: addInitScript:
parameters: parameters:
@ -489,22 +519,7 @@ BrowserContext:
returns: returns:
cookies: cookies:
type: array type: array
items: items: NetworkCookie
type: object
properties:
name: string
value: string
domain: string
path: string
expires: number
httpOnly: boolean
secure: boolean
sameSite:
type: enum
literals:
- Strict
- Lax
- None
exposeBinding: exposeBinding:
parameters: parameters:
@ -534,11 +549,7 @@ BrowserContext:
parameters: parameters:
headers: headers:
type: array type: array
items: items: NameValue
type: object
properties:
name: string
value: string
setGeolocation: setGeolocation:
parameters: parameters:
@ -565,6 +576,15 @@ BrowserContext:
parameters: parameters:
offline: boolean offline: boolean
storageState:
returns:
cookies:
type: array
items: NetworkCookie
origins:
type: array
items: OriginStorage
crNewCDPSession: crNewCDPSession:
parameters: parameters:
page: Page page: Page
@ -721,11 +741,7 @@ Page:
parameters: parameters:
headers: headers:
type: array type: array
items: items: NameValue
type: object
properties:
name: string
value: string
setNetworkInterceptionEnabled: setNetworkInterceptionEnabled:
parameters: parameters:
@ -1797,11 +1813,7 @@ Route:
method: string? method: string?
headers: headers:
type: array? type: array?
items: items: NameValue
type: object
properties:
name: string
value: string
postData: binary? postData: binary?
fulfill: fulfill:
@ -1810,11 +1822,7 @@ Route:
status: number? status: number?
headers: headers:
type: array? type: array?
items: items: NameValue
type: object
properties:
name: string
value: string
body: string? body: string?
isBase64: boolean? isBase64: boolean?
@ -2038,11 +2046,7 @@ Electron:
cwd: string? cwd: string?
env: env:
type: array? type: array?
items: items: NameValue
type: object
properties:
name: string
value: string
handleSIGINT: boolean? handleSIGINT: boolean?
handleSIGTERM: boolean? handleSIGTERM: boolean?
handleSIGHUP: boolean? handleSIGHUP: boolean?

View File

@ -86,6 +86,35 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
orientation: tOptional(tString), orientation: tOptional(tString),
children: tOptional(tArray(tType('AXNode'))), children: tOptional(tArray(tType('AXNode'))),
}); });
scheme.SetNetworkCookie = tObject({
name: tString,
value: tString,
url: tOptional(tString),
domain: tOptional(tString),
path: tOptional(tString),
expires: tOptional(tNumber),
httpOnly: tOptional(tBoolean),
secure: tOptional(tBoolean),
sameSite: tOptional(tEnum(['Strict', 'Lax', 'None'])),
});
scheme.NetworkCookie = tObject({
name: tString,
value: tString,
domain: tString,
path: tString,
expires: tNumber,
httpOnly: tBoolean,
secure: tBoolean,
sameSite: tEnum(['Strict', 'Lax', 'None']),
});
scheme.NameValue = tObject({
name: tString,
value: tString,
});
scheme.OriginStorage = tObject({
origin: tString,
localStorage: tArray(tType('NameValue')),
});
scheme.SerializedError = tObject({ scheme.SerializedError = tObject({
error: tOptional(tObject({ error: tOptional(tObject({
message: tString, message: tString,
@ -108,10 +137,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
handleSIGTERM: tOptional(tBoolean), handleSIGTERM: tOptional(tBoolean),
handleSIGHUP: tOptional(tBoolean), handleSIGHUP: tOptional(tBoolean),
timeout: tOptional(tNumber), timeout: tOptional(tNumber),
env: tOptional(tArray(tObject({ env: tOptional(tArray(tType('NameValue'))),
name: tString,
value: tString,
}))),
headless: tOptional(tBoolean), headless: tOptional(tBoolean),
devtools: tOptional(tBoolean), devtools: tOptional(tBoolean),
proxy: tOptional(tObject({ proxy: tOptional(tObject({
@ -135,10 +161,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
handleSIGTERM: tOptional(tBoolean), handleSIGTERM: tOptional(tBoolean),
handleSIGHUP: tOptional(tBoolean), handleSIGHUP: tOptional(tBoolean),
timeout: tOptional(tNumber), timeout: tOptional(tNumber),
env: tOptional(tArray(tObject({ env: tOptional(tArray(tType('NameValue'))),
name: tString,
value: tString,
}))),
headless: tOptional(tBoolean), headless: tOptional(tBoolean),
devtools: tOptional(tBoolean), devtools: tOptional(tBoolean),
proxy: tOptional(tObject({ proxy: tOptional(tObject({
@ -167,10 +190,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
accuracy: tOptional(tNumber), accuracy: tOptional(tNumber),
})), })),
permissions: tOptional(tArray(tString)), permissions: tOptional(tArray(tString)),
extraHTTPHeaders: tOptional(tArray(tObject({ extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
name: tString,
value: tString,
}))),
offline: tOptional(tBoolean), offline: tOptional(tBoolean),
httpCredentials: tOptional(tObject({ httpCredentials: tOptional(tObject({
username: tString, username: tString,
@ -214,10 +234,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
accuracy: tOptional(tNumber), accuracy: tOptional(tNumber),
})), })),
permissions: tOptional(tArray(tString)), permissions: tOptional(tArray(tString)),
extraHTTPHeaders: tOptional(tArray(tObject({ extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
name: tString,
value: tString,
}))),
offline: tOptional(tBoolean), offline: tOptional(tBoolean),
httpCredentials: tOptional(tObject({ httpCredentials: tOptional(tObject({
username: tString, username: tString,
@ -247,6 +264,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
username: tOptional(tString), username: tOptional(tString),
password: tOptional(tString), password: tOptional(tString),
})), })),
storageState: tOptional(tObject({
cookies: tOptional(tArray(tType('SetNetworkCookie'))),
origins: tOptional(tArray(tType('OriginStorage'))),
})),
}); });
scheme.BrowserCrNewBrowserCDPSessionParams = tOptional(tObject({})); scheme.BrowserCrNewBrowserCDPSessionParams = tOptional(tObject({}));
scheme.BrowserCrStartTracingParams = tObject({ scheme.BrowserCrStartTracingParams = tObject({
@ -257,17 +278,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
}); });
scheme.BrowserCrStopTracingParams = tOptional(tObject({})); scheme.BrowserCrStopTracingParams = tOptional(tObject({}));
scheme.BrowserContextAddCookiesParams = tObject({ scheme.BrowserContextAddCookiesParams = tObject({
cookies: tArray(tObject({ cookies: tArray(tType('SetNetworkCookie')),
name: tString,
value: tString,
url: tOptional(tString),
domain: tOptional(tString),
path: tOptional(tString),
expires: tOptional(tNumber),
httpOnly: tOptional(tBoolean),
secure: tOptional(tBoolean),
sameSite: tOptional(tEnum(['Strict', 'Lax', 'None'])),
})),
}); });
scheme.BrowserContextAddInitScriptParams = tObject({ scheme.BrowserContextAddInitScriptParams = tObject({
source: tString, source: tString,
@ -294,10 +305,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
timeout: tNumber, timeout: tNumber,
}); });
scheme.BrowserContextSetExtraHTTPHeadersParams = tObject({ scheme.BrowserContextSetExtraHTTPHeadersParams = tObject({
headers: tArray(tObject({ headers: tArray(tType('NameValue')),
name: tString,
value: tString,
})),
}); });
scheme.BrowserContextSetGeolocationParams = tObject({ scheme.BrowserContextSetGeolocationParams = tObject({
geolocation: tOptional(tObject({ geolocation: tOptional(tObject({
@ -318,6 +326,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.BrowserContextSetOfflineParams = tObject({ scheme.BrowserContextSetOfflineParams = tObject({
offline: tBoolean, offline: tBoolean,
}); });
scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
scheme.BrowserContextCrNewCDPSessionParams = tObject({ scheme.BrowserContextCrNewCDPSessionParams = tObject({
page: tChannel('Page'), page: tChannel('Page'),
}); });
@ -371,10 +380,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
})), })),
}); });
scheme.PageSetExtraHTTPHeadersParams = tObject({ scheme.PageSetExtraHTTPHeadersParams = tObject({
headers: tArray(tObject({ headers: tArray(tType('NameValue')),
name: tString,
value: tString,
})),
}); });
scheme.PageSetNetworkInterceptionEnabledParams = tObject({ scheme.PageSetNetworkInterceptionEnabledParams = tObject({
enabled: tBoolean, enabled: tBoolean,
@ -840,18 +846,12 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
}); });
scheme.RouteContinueParams = tObject({ scheme.RouteContinueParams = tObject({
method: tOptional(tString), method: tOptional(tString),
headers: tOptional(tArray(tObject({ headers: tOptional(tArray(tType('NameValue'))),
name: tString,
value: tString,
}))),
postData: tOptional(tBinary), postData: tOptional(tBinary),
}); });
scheme.RouteFulfillParams = tObject({ scheme.RouteFulfillParams = tObject({
status: tOptional(tNumber), status: tOptional(tNumber),
headers: tOptional(tArray(tObject({ headers: tOptional(tArray(tType('NameValue'))),
name: tString,
value: tString,
}))),
body: tOptional(tString), body: tOptional(tString),
isBase64: tOptional(tBoolean), isBase64: tOptional(tBoolean),
}); });
@ -898,10 +898,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
executablePath: tString, executablePath: tString,
args: tOptional(tArray(tString)), args: tOptional(tArray(tString)),
cwd: tOptional(tString), cwd: tOptional(tString),
env: tOptional(tArray(tObject({ env: tOptional(tArray(tType('NameValue'))),
name: tString,
value: tString,
}))),
handleSIGINT: tOptional(tBoolean), handleSIGINT: tOptional(tBoolean),
handleSIGTERM: tOptional(tBoolean), handleSIGTERM: tOptional(tBoolean),
handleSIGHUP: tOptional(tBoolean), handleSIGHUP: tOptional(tBoolean),

View File

@ -109,6 +109,7 @@ export abstract class BrowserContext extends EventEmitter {
readonly _browserContextId: string | undefined; readonly _browserContextId: string | undefined;
private _selectors?: Selectors; private _selectors?: Selectors;
readonly _actionListeners = new Set<ActionListener>(); readonly _actionListeners = new Set<ActionListener>();
private _origins = new Set<string>();
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) { constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
super(); super();
@ -321,6 +322,56 @@ export abstract class BrowserContext extends EventEmitter {
} }
throw pageOrError; throw pageOrError;
} }
addVisitedOrigin(origin: string) {
this._origins.add(origin);
}
async storageState(): Promise<types.StorageState> {
const result: types.StorageState = {
cookies: (await this.cookies()).filter(c => c.value !== ''),
origins: []
};
if (this._origins.size) {
const page = await this.newPage();
await page._setServerRequestInterceptor(handler => {
handler.fulfill({ body: '<html></html>' }).catch(() => {});
});
for (const origin of this._origins) {
const originStorage: types.OriginStorage = { origin, localStorage: [] };
result.origins.push(originStorage);
const frame = page.mainFrame();
await frame.goto(new ProgressController(), origin);
const storage = await frame._evaluateExpression(`({
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
})`, false, undefined, 'utility');
originStorage.localStorage = storage.localStorage;
}
await page.close();
}
return result;
}
async setStorageState(state: types.SetStorageState) {
if (state.cookies)
await this.addCookies(state.cookies);
if (state.origins && state.origins.length) {
const page = await this.newPage();
await page._setServerRequestInterceptor(handler => {
handler.fulfill({ body: '<html></html>' }).catch(() => {});
});
for (const originState of state.origins) {
const frame = page.mainFrame();
await frame.goto(new ProgressController(), originState.origin);
await frame._evaluateExpression(`
originState => {
for (const { name, value } of (originState.localStorage || []))
localStorage.setItem(name, value);
}`, true, originState, 'utility');
}
await page.close();
}
}
} }
export function assertBrowserContextIsNotOwned(context: BrowserContext) { export function assertBrowserContextIsNotOwned(context: BrowserContext) {

View File

@ -199,7 +199,7 @@ export class FrameManager {
frame.emit(Frame.Events.Navigation, navigationEvent); frame.emit(Frame.Events.Navigation, navigationEvent);
if (!initial) { if (!initial) {
debugLogger.log('api', ` navigated to "${url}"`); debugLogger.log('api', ` navigated to "${url}"`);
this._page.emit(Page.Events.FrameNavigated, frame); this._page.frameNavigated(frame);
} }
// Restore pending if any - see comments above about keepPending. // Restore pending if any - see comments above about keepPending.
frame._pendingDocument = keepPending; frame._pendingDocument = keepPending;
@ -213,7 +213,7 @@ export class FrameManager {
const navigationEvent: NavigationEvent = { url, name: frame._name }; const navigationEvent: NavigationEvent = { url, name: frame._name };
frame.emit(Frame.Events.Navigation, navigationEvent); frame.emit(Frame.Events.Navigation, navigationEvent);
debugLogger.log('api', ` navigated to "${url}"`); debugLogger.log('api', ` navigated to "${url}"`);
this._page.emit(Page.Events.FrameNavigated, frame); this._page.frameNavigated(frame);
} }
frameAbortedNavigation(frameId: string, errorText: string, documentId?: string) { frameAbortedNavigation(frameId: string, errorText: string, documentId?: string) {

View File

@ -143,7 +143,8 @@ export class Page extends EventEmitter {
private _workers = new Map<string, Worker>(); private _workers = new Map<string, Worker>();
readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined; readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined;
readonly coverage: any; readonly coverage: any;
private _requestInterceptor?: network.RouteHandler; private _clientRequestInterceptor: network.RouteHandler | undefined;
private _serverRequestInterceptor: network.RouteHandler | undefined;
_ownedContext: BrowserContext | undefined; _ownedContext: BrowserContext | undefined;
readonly selectors: Selectors; readonly selectors: Selectors;
_video: Video | null = null; _video: Video | null = null;
@ -362,11 +363,16 @@ export class Page extends EventEmitter {
} }
_needsRequestInterception(): boolean { _needsRequestInterception(): boolean {
return !!this._requestInterceptor || !!this._browserContext._requestInterceptor; return !!this._clientRequestInterceptor || !!this._serverRequestInterceptor || !!this._browserContext._requestInterceptor;
} }
async _setRequestInterceptor(handler: network.RouteHandler | undefined): Promise<void> { async _setClientRequestInterceptor(handler: network.RouteHandler | undefined): Promise<void> {
this._requestInterceptor = handler; this._clientRequestInterceptor = handler;
await this._delegate.updateRequestInterception();
}
async _setServerRequestInterceptor(handler: network.RouteHandler | undefined): Promise<void> {
this._serverRequestInterceptor = handler;
await this._delegate.updateRequestInterception(); await this._delegate.updateRequestInterception();
} }
@ -375,8 +381,12 @@ export class Page extends EventEmitter {
const route = request._route(); const route = request._route();
if (!route) if (!route)
return; return;
if (this._requestInterceptor) { if (this._serverRequestInterceptor) {
this._requestInterceptor(route, request); this._serverRequestInterceptor(route, request);
return;
}
if (this._clientRequestInterceptor) {
this._clientRequestInterceptor(route, request);
return; return;
} }
if (this._browserContext._requestInterceptor) { if (this._browserContext._requestInterceptor) {
@ -444,6 +454,14 @@ export class Page extends EventEmitter {
this._video = video; this._video = video;
this.emit(Page.Events.VideoStarted, video); this.emit(Page.Events.VideoStarted, video);
} }
frameNavigated(frame: frames.Frame) {
this.emit(Page.Events.FrameNavigated, frame);
const url = frame.url();
if (!url.startsWith('http'))
return;
this._browserContext.addVisitedOrigin(new URL(url).origin);
}
} }
export class Worker extends EventEmitter { export class Worker extends EventEmitter {

View File

@ -328,3 +328,23 @@ export type Error = {
export type UIOptions = { export type UIOptions = {
slowMo?: number; slowMo?: number;
}; };
export type NameValueList = {
name: string;
value: string;
}[];
export type OriginStorage = {
origin: string;
localStorage: NameValueList;
};
export type StorageState = {
cookies: NetworkCookie[],
origins: OriginStorage[]
}
export type SetStorageState = {
cookies?: SetNetworkCookieParam[],
origins?: OriginStorage[]
}

View File

@ -0,0 +1,76 @@
/**
* Copyright 2018 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.
*/
import { it, expect } from './fixtures';
it('should capture local storage', (test, { browserName, platform }) => {
test.fixme(browserName === 'webkit' && platform === 'win32');
}, async ({ context }) => {
const page1 = await context.newPage();
await page1.route('**/*', route => {
route.fulfill({ body: '<html></html>' }).catch(() => {});
});
await page1.goto('https://www.example.com');
await page1.evaluate(() => {
localStorage['name1'] = 'value1';
});
await page1.goto('https://www.domain.com');
await page1.evaluate(() => {
localStorage['name2'] = 'value2';
});
const { origins } = await context.storageState();
expect(origins).toEqual([{
origin: 'https://www.example.com',
localStorage: [{
name: 'name1',
value: 'value1'
}],
}, {
origin: 'https://www.domain.com',
localStorage: [{
name: 'name2',
value: 'value2'
}],
}]);
});
it('should set local storage', (test, { browserName, platform }) => {
test.fixme(browserName === 'webkit' && platform === 'win32');
}, async ({ browser }) => {
const context = await browser.newContext({
storageState: {
origins: [
{
origin: 'https://www.example.com',
localStorage: [{
name: 'name1',
value: 'value1'
}]
},
]
}
});
// await new Promise(f => setTimeout(f, 1000));
const page = await context.newPage();
await page.route('**/*', route => {
route.fulfill({ body: '<html></html>' }).catch(() => {});
});
await page.goto('https://www.example.com');
const localStorage = await page.evaluate('window.localStorage');
expect(localStorage).toEqual({ name1: 'value1' });
await context.close();
});