feat(fetch): import/export storageState (#9244)
This commit is contained in:
parent
a1d0878fa1
commit
4e372dccb5
|
|
@ -1170,12 +1170,7 @@ Returns storage state for this browser context, contains current cookies and loc
|
|||
* langs: csharp, java
|
||||
- returns: <[string]>
|
||||
|
||||
### option: BrowserContext.storageState.path
|
||||
- `path` <[path]>
|
||||
|
||||
The file path to save the storage state to. If [`option: path`] is a relative path, then it is resolved relative to
|
||||
current working directory. If no path is provided, storage
|
||||
state is still returned, but won't be saved to the disk.
|
||||
### option: BrowserContext.storageState.path = %%-storagestate-option-path-%%
|
||||
|
||||
## property: BrowserContext.tracing
|
||||
- type: <[Tracing]>
|
||||
|
|
|
|||
|
|
@ -134,3 +134,24 @@ Whether to throw on response codes other than 2xx and 3xx. By default response o
|
|||
for all status codes.
|
||||
|
||||
### option: FetchRequest.post.ignoreHTTPSErrors = %%-context-option-ignorehttpserrors-%%
|
||||
|
||||
## async method: FetchRequest.storageState
|
||||
- returns: <[Object]>
|
||||
- `cookies` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
- `domain` <[string]>
|
||||
- `path` <[string]>
|
||||
- `expires` <[float]> Unix time in seconds.
|
||||
- `httpOnly` <[boolean]>
|
||||
- `secure` <[boolean]>
|
||||
- `sameSite` <[SameSiteAttribute]<"Strict"|"Lax"|"None">>
|
||||
- `origins` <[Array]<[Object]>>
|
||||
- `origin` <[string]>
|
||||
- `localStorage` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
|
||||
Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to the constructor.
|
||||
|
||||
### option: FetchRequest.storageState.path = %%-storagestate-option-path-%%
|
||||
|
|
|
|||
|
|
@ -112,6 +112,28 @@ When using [`method: FetchRequest.get`], [`method: FetchRequest.post`], [`method
|
|||
* baseURL: `http://localhost:3000` and sending rquest to `/bar.html` results in `http://localhost:3000/bar.html`
|
||||
* baseURL: `http://localhost:3000/foo/` and sending rquest to `./bar.html` results in `http://localhost:3000/foo/bar.html`
|
||||
|
||||
### option: Playwright._newRequest.storageState
|
||||
- `storageState` <[path]|[Object]>
|
||||
- `cookies` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
- `domain` <[string]>
|
||||
- `path` <[string]>
|
||||
- `expires` <[float]> Unix time in seconds.
|
||||
- `httpOnly` <[boolean]>
|
||||
- `secure` <[boolean]>
|
||||
- `sameSite` <[SameSiteAttribute]<"Strict"|"Lax"|"None">>
|
||||
- `origins` <[Array]<[Object]>>
|
||||
- `origin` <[string]>
|
||||
- `localStorage` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
|
||||
Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
obtained via [`method: BrowserContext.storageState`] or [`method: FetchRequest.storageState`]. Either a path to the
|
||||
file with saved storage, or the value returned by one of [`method: BrowserContext.storageState`] or
|
||||
[`method: FetchRequest.storageState`] methods.
|
||||
|
||||
## property: Playwright.chromium
|
||||
- type: <[BrowserType]>
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,13 @@ obtained via [`method: BrowserContext.storageState`].
|
|||
Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
obtained via [`method: BrowserContext.storageState`]. Path to the file with saved storage state.
|
||||
|
||||
## storagestate-option-path
|
||||
- `path` <[path]>
|
||||
|
||||
The file path to save the storage state to. If [`option: path`] is a relative path, then it is resolved relative to
|
||||
current working directory. If no path is provided, storage
|
||||
state is still returned, but won't be saved to the disk.
|
||||
|
||||
## context-option-acceptdownloads
|
||||
- `acceptDownloads` <[boolean]>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReadStream } from 'fs';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import * as mime from 'mime';
|
||||
import { Serializable } from '../../types/structs';
|
||||
|
|
@ -22,11 +22,11 @@ import * as api from '../../types/types';
|
|||
import { HeadersArray } from '../common/types';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { kBrowserOrContextClosedError } from '../utils/errors';
|
||||
import { assert, headersObjectToArray, isFilePayload, isString, objectToArray } from '../utils/utils';
|
||||
import { assert, headersObjectToArray, isFilePayload, isString, mkdirIfNeeded, objectToArray } from '../utils/utils';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import * as network from './network';
|
||||
import { RawHeaders } from './network';
|
||||
import { FilePayload, Headers } from './types';
|
||||
import { FilePayload, Headers, StorageState } from './types';
|
||||
|
||||
export type FetchOptions = {
|
||||
params?: { [key: string]: string; },
|
||||
|
|
@ -110,8 +110,8 @@ export class FetchRequest extends ChannelOwner<channels.FetchRequestChannel, cha
|
|||
if (!Buffer.isBuffer(payload.buffer))
|
||||
throw new Error(`Unexpected buffer type of 'data.${name}'`);
|
||||
formData[name] = filePayloadToJson(payload);
|
||||
} else if (value instanceof ReadStream) {
|
||||
formData[name] = await readStreamToJson(value as ReadStream);
|
||||
} else if (value instanceof fs.ReadStream) {
|
||||
formData[name] = await readStreamToJson(value as fs.ReadStream);
|
||||
} else {
|
||||
formData[name] = value;
|
||||
}
|
||||
|
|
@ -139,6 +139,17 @@ export class FetchRequest extends ChannelOwner<channels.FetchRequestChannel, cha
|
|||
return new FetchResponse(this, result.response!);
|
||||
});
|
||||
}
|
||||
|
||||
async storageState(options: { path?: string } = {}): Promise<StorageState> {
|
||||
return await this._wrapApiCall(async (channel: channels.FetchRequestChannel) => {
|
||||
const state = await channel.storageState();
|
||||
if (options.path) {
|
||||
await mkdirIfNeeded(options.path);
|
||||
await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
|
||||
}
|
||||
return state;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchResponse implements api.FetchResponse {
|
||||
|
|
@ -226,7 +237,7 @@ function filePayloadToJson(payload: FilePayload): ServerFilePayload {
|
|||
};
|
||||
}
|
||||
|
||||
async function readStreamToJson(stream: ReadStream): Promise<ServerFilePayload> {
|
||||
async function readStreamToJson(stream: fs.ReadStream): Promise<ServerFilePayload> {
|
||||
const buffer = await new Promise<Buffer>((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on('data', chunk => chunks.push(chunk));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
import dns from 'dns';
|
||||
import fs from 'fs';
|
||||
import net from 'net';
|
||||
import util from 'util';
|
||||
import * as channels from '../protocol/channels';
|
||||
|
|
@ -72,9 +73,13 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel, channel
|
|||
|
||||
async _newRequest(options: NewRequestOptions = {}): Promise<FetchRequest> {
|
||||
return await this._wrapApiCall(async (channel: channels.PlaywrightChannel) => {
|
||||
const storageState = typeof options.storageState === 'string' ?
|
||||
JSON.parse(await fs.promises.readFile(options.storageState, 'utf8')) :
|
||||
options.storageState;
|
||||
return FetchRequest.from((await channel.newRequest({
|
||||
...options,
|
||||
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined
|
||||
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
||||
storageState,
|
||||
})).request);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import * as channels from '../protocol/channels';
|
||||
import type { Size } from '../common/types';
|
||||
export { Size, Point, Rect, Quad, URLMatch, TimeoutOptions, HeadersArray, NewRequestOptions } from '../common/types';
|
||||
export { Size, Point, Rect, Quad, URLMatch, TimeoutOptions, HeadersArray } from '../common/types';
|
||||
|
||||
type LoggerSeverity = 'verbose' | 'info' | 'warning' | 'error';
|
||||
export interface Logger {
|
||||
|
|
@ -58,7 +58,7 @@ export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'vie
|
|||
logger?: Logger,
|
||||
videosPath?: string,
|
||||
videoSize?: Size,
|
||||
storageState?: string | channels.BrowserNewContextOptions['storageState'],
|
||||
storageState?: string | SetStorageState,
|
||||
};
|
||||
|
||||
type LaunchOverrides = {
|
||||
|
|
@ -118,3 +118,8 @@ export type SelectorEngine = {
|
|||
|
||||
export type RemoteAddr = channels.RemoteAddr;
|
||||
export type SecurityDetails = channels.SecurityDetails;
|
||||
|
||||
export type NewRequestOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'storageState'> & {
|
||||
extraHTTPHeaders?: Headers,
|
||||
storageState?: string | StorageState,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,21 +21,4 @@ export type Quad = [ Point, Point, Point, Point ];
|
|||
export type URLMatch = string | RegExp | ((url: URL) => boolean);
|
||||
export type TimeoutOptions = { timeout?: number };
|
||||
export type NameValue = { name: string, value: string };
|
||||
export type HeadersArray = NameValue[];
|
||||
export type NewRequestOptions = {
|
||||
baseURL?: string;
|
||||
extraHTTPHeaders?: { [key: string]: string; };
|
||||
httpCredentials?: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
ignoreHTTPSErrors?: boolean;
|
||||
proxy?: {
|
||||
server: string;
|
||||
bypass?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
timeout?: number;
|
||||
userAgent?: string;
|
||||
};
|
||||
export type HeadersArray = NameValue[];
|
||||
|
|
@ -163,7 +163,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
|||
}
|
||||
|
||||
async storageState(params: channels.BrowserContextStorageStateParams, metadata: CallMetadata): Promise<channels.BrowserContextStorageStateResult> {
|
||||
return await this._context.storageState(metadata);
|
||||
return await this._context.storageState();
|
||||
}
|
||||
|
||||
async close(params: channels.BrowserContextCloseParams, metadata: CallMetadata): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -177,6 +177,10 @@ export class FetchRequestDispatcher extends Dispatcher<FetchRequest, channels.Fe
|
|||
});
|
||||
}
|
||||
|
||||
async storageState(params?: channels.FetchRequestStorageStateParams): Promise<channels.FetchRequestStorageStateResult> {
|
||||
return this._object.storageState();
|
||||
}
|
||||
|
||||
async dispose(params?: channels.FetchRequestDisposeParams): Promise<void> {
|
||||
this._object.dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ export type FetchRequestInitializer = {};
|
|||
export interface FetchRequestChannel extends Channel {
|
||||
fetch(params: FetchRequestFetchParams, metadata?: Metadata): Promise<FetchRequestFetchResult>;
|
||||
fetchResponseBody(params: FetchRequestFetchResponseBodyParams, metadata?: Metadata): Promise<FetchRequestFetchResponseBodyResult>;
|
||||
storageState(params?: FetchRequestStorageStateParams, metadata?: Metadata): Promise<FetchRequestStorageStateResult>;
|
||||
disposeFetchResponse(params: FetchRequestDisposeFetchResponseParams, metadata?: Metadata): Promise<FetchRequestDisposeFetchResponseResult>;
|
||||
dispose(params?: FetchRequestDisposeParams, metadata?: Metadata): Promise<FetchRequestDisposeResult>;
|
||||
}
|
||||
|
|
@ -199,6 +200,12 @@ export type FetchRequestFetchResponseBodyOptions = {
|
|||
export type FetchRequestFetchResponseBodyResult = {
|
||||
binary?: Binary,
|
||||
};
|
||||
export type FetchRequestStorageStateParams = {};
|
||||
export type FetchRequestStorageStateOptions = {};
|
||||
export type FetchRequestStorageStateResult = {
|
||||
cookies: NetworkCookie[],
|
||||
origins: OriginStorage[],
|
||||
};
|
||||
export type FetchRequestDisposeFetchResponseParams = {
|
||||
fetchUid: string,
|
||||
};
|
||||
|
|
@ -346,6 +353,10 @@ export type PlaywrightNewRequestParams = {
|
|||
password?: string,
|
||||
},
|
||||
timeout?: number,
|
||||
storageState?: {
|
||||
cookies: NetworkCookie[],
|
||||
origins: OriginStorage[],
|
||||
},
|
||||
};
|
||||
export type PlaywrightNewRequestOptions = {
|
||||
baseURL?: string,
|
||||
|
|
@ -363,6 +374,10 @@ export type PlaywrightNewRequestOptions = {
|
|||
password?: string,
|
||||
},
|
||||
timeout?: number,
|
||||
storageState?: {
|
||||
cookies: NetworkCookie[],
|
||||
origins: OriginStorage[],
|
||||
},
|
||||
};
|
||||
export type PlaywrightNewRequestResult = {
|
||||
request: FetchRequestChannel,
|
||||
|
|
|
|||
|
|
@ -256,6 +256,15 @@ FetchRequest:
|
|||
returns:
|
||||
binary?: binary
|
||||
|
||||
storageState:
|
||||
returns:
|
||||
cookies:
|
||||
type: array
|
||||
items: NetworkCookie
|
||||
origins:
|
||||
type: array
|
||||
items: OriginStorage
|
||||
|
||||
disposeFetchResponse:
|
||||
parameters:
|
||||
fetchUid: string
|
||||
|
|
@ -484,6 +493,15 @@ Playwright:
|
|||
username: string?
|
||||
password: string?
|
||||
timeout: number?
|
||||
storageState:
|
||||
type: object?
|
||||
properties:
|
||||
cookies:
|
||||
type: array
|
||||
items: NetworkCookie
|
||||
origins:
|
||||
type: array
|
||||
items: OriginStorage
|
||||
|
||||
returns:
|
||||
request: FetchRequest
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
scheme.FetchRequestFetchResponseBodyParams = tObject({
|
||||
fetchUid: tString,
|
||||
});
|
||||
scheme.FetchRequestStorageStateParams = tOptional(tObject({}));
|
||||
scheme.FetchRequestDisposeFetchResponseParams = tObject({
|
||||
fetchUid: tString,
|
||||
});
|
||||
|
|
@ -217,6 +218,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
password: tOptional(tString),
|
||||
})),
|
||||
timeout: tOptional(tNumber),
|
||||
storageState: tOptional(tObject({
|
||||
cookies: tArray(tType('NetworkCookie')),
|
||||
origins: tArray(tType('OriginStorage')),
|
||||
})),
|
||||
});
|
||||
scheme.SelectorsRegisterParams = tObject({
|
||||
name: tString,
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
this._origins.add(origin);
|
||||
}
|
||||
|
||||
async storageState(metadata: CallMetadata): Promise<types.StorageState> {
|
||||
async storageState(): Promise<types.StorageState> {
|
||||
const result: types.StorageState = {
|
||||
cookies: (await this.cookies()).filter(c => c.value !== ''),
|
||||
origins: []
|
||||
|
|
|
|||
|
|
@ -68,13 +68,20 @@ export class CookieStore {
|
|||
|
||||
cookies(url: URL): types.NetworkCookie[] {
|
||||
const result = [];
|
||||
for (const cookie of this._allCookies()) {
|
||||
for (const cookie of this._cookiesIterator()) {
|
||||
if (cookie.matches(url))
|
||||
result.push(cookie.networkCookie());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
allCookies(): types.NetworkCookie[] {
|
||||
const result = [];
|
||||
for (const cookie of this._cookiesIterator())
|
||||
result.push(cookie.networkCookie());
|
||||
return result;
|
||||
}
|
||||
|
||||
private _addCookie(cookie: Cookie) {
|
||||
if (cookie.expired())
|
||||
return;
|
||||
|
|
@ -94,7 +101,7 @@ export class CookieStore {
|
|||
set.add(cookie);
|
||||
}
|
||||
|
||||
private *_allCookies(): IterableIterator<Cookie> {
|
||||
private *_cookiesIterator(): IterableIterator<Cookie> {
|
||||
for (const [name, cookies] of this._nameToCookies) {
|
||||
CookieStore.pruneExpired(cookies);
|
||||
for (const cookie of cookies)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { pipeline, Readable, Transform } from 'stream';
|
|||
import url from 'url';
|
||||
import zlib from 'zlib';
|
||||
import { HTTPCredentials } from '../../types/types';
|
||||
import { NameValue, NewRequestOptions } from '../common/types';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||
import { assert, createGuid, getPlaywrightVersion, isFilePayload, monotonicTime } from '../utils/utils';
|
||||
import { BrowserContext } from './browserContext';
|
||||
|
|
@ -76,6 +76,7 @@ export abstract class FetchRequest extends SdkObject {
|
|||
abstract _defaultOptions(): FetchRequestOptions;
|
||||
abstract _addCookies(cookies: types.NetworkCookie[]): Promise<void>;
|
||||
abstract _cookies(url: URL): Promise<types.NetworkCookie[]>;
|
||||
abstract storageState(): Promise<channels.FetchRequestStorageStateResult>;
|
||||
|
||||
private _storeResponseBody(body: Buffer): string {
|
||||
const uid = createGuid();
|
||||
|
|
@ -337,13 +338,19 @@ export class BrowserContextFetchRequest extends FetchRequest {
|
|||
async _cookies(url: URL): Promise<types.NetworkCookie[]> {
|
||||
return await this._context.cookies(url.toString());
|
||||
}
|
||||
|
||||
override async storageState(): Promise<channels.FetchRequestStorageStateResult> {
|
||||
return this._context.storageState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class GlobalFetchRequest extends FetchRequest {
|
||||
private readonly _cookieStore: CookieStore = new CookieStore();
|
||||
private readonly _options: FetchRequestOptions;
|
||||
constructor(playwright: Playwright, options: Omit<NewRequestOptions, 'extraHTTPHeaders'> & { extraHTTPHeaders?: NameValue[] }) {
|
||||
private readonly _origins: channels.OriginStorage[] | undefined;
|
||||
|
||||
constructor(playwright: Playwright, options: channels.PlaywrightNewRequestOptions) {
|
||||
super(playwright);
|
||||
const timeoutSettings = new TimeoutSettings();
|
||||
if (options.timeout !== undefined)
|
||||
|
|
@ -355,6 +362,10 @@ export class GlobalFetchRequest extends FetchRequest {
|
|||
url = 'http://' + url;
|
||||
proxy.server = url;
|
||||
}
|
||||
if (options.storageState) {
|
||||
this._origins = options.storageState.origins;
|
||||
this._cookieStore.addCookies(options.storageState.cookies);
|
||||
}
|
||||
this._options = {
|
||||
baseURL: options.baseURL,
|
||||
userAgent: options.userAgent || `Playwright/${getPlaywrightVersion()}`,
|
||||
|
|
@ -382,6 +393,13 @@ export class GlobalFetchRequest extends FetchRequest {
|
|||
async _cookies(url: URL): Promise<types.NetworkCookie[]> {
|
||||
return this._cookieStore.cookies(url);
|
||||
}
|
||||
|
||||
override async storageState(): Promise<channels.FetchRequestStorageStateResult> {
|
||||
return {
|
||||
cookies: this._cookieStore.allCookies(),
|
||||
origins: this._origins || []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function toHeadersArray(rawHeaders: string[]): types.HeadersArray {
|
||||
|
|
|
|||
|
|
@ -876,3 +876,17 @@ it('should throw when data passed for unsupported request', async function({ con
|
|||
}).catch(e => e);
|
||||
expect(error.message).toContain(`Method GET does not accept post data`);
|
||||
});
|
||||
|
||||
it('context request should export same storage state as context', async ({ context, page, server }) => {
|
||||
server.setRoute('/setcookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', ['a=b', 'c=d']);
|
||||
res.end();
|
||||
});
|
||||
await context._request.get(server.PREFIX + '/setcookie.html');
|
||||
const contextState = await context.storageState();
|
||||
expect(contextState.cookies.length).toBe(2);
|
||||
const requestState = await context._request.storageState();
|
||||
expect(requestState).toEqual(contextState);
|
||||
const pageState = await page._request.storageState();
|
||||
expect(pageState).toEqual(contextState);
|
||||
});
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import http from 'http';
|
||||
import { FetchRequest } from '../index';
|
||||
import { expect, playwrightTest } from './config/browserTest';
|
||||
|
|
@ -30,6 +31,9 @@ const it = playwrightTest.extend<GlobalFetchFixtures>({
|
|||
},
|
||||
});
|
||||
|
||||
type PromiseArg<T> = T extends Promise<infer R> ? R : never;
|
||||
type StorageStateType = PromiseArg<ReturnType<FetchRequest['storageState']>>;
|
||||
|
||||
it.skip(({ mode }) => mode !== 'default');
|
||||
|
||||
let prevAgent: http.Agent;
|
||||
|
|
@ -162,3 +166,153 @@ it('should remove expired cookies', async ({ request, server }) => {
|
|||
expect(serverRequest.headers.cookie).toBe('a=v');
|
||||
});
|
||||
|
||||
it('should export cookies to storage state', async ({ request, server }) => {
|
||||
const expires = new Date('12/31/2100 PST');
|
||||
server.setRoute('/setcookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', ['a=b', `c=d; expires=${expires.toUTCString()}; domain=b.one.com; path=/input`, 'e=f; domain=b.one.com; path=/input/subfolder']);
|
||||
res.end();
|
||||
});
|
||||
await request.get(`http://a.b.one.com:${server.PORT}/setcookie.html`);
|
||||
const state = await request.storageState();
|
||||
expect(state).toEqual({
|
||||
'cookies': [
|
||||
{
|
||||
'name': 'a',
|
||||
'value': 'b',
|
||||
'domain': 'a.b.one.com',
|
||||
'path': '/',
|
||||
'expires': -1,
|
||||
'httpOnly': false,
|
||||
'secure': false,
|
||||
'sameSite': 'Lax'
|
||||
},
|
||||
{
|
||||
'name': 'c',
|
||||
'value': 'd',
|
||||
'domain': '.b.one.com',
|
||||
'path': '/input',
|
||||
'expires': +expires / 1000,
|
||||
'httpOnly': false,
|
||||
'secure': false,
|
||||
'sameSite': 'Lax'
|
||||
},
|
||||
{
|
||||
'name': 'e',
|
||||
'value': 'f',
|
||||
'domain': '.b.one.com',
|
||||
'path': '/input/subfolder',
|
||||
'expires': -1,
|
||||
'httpOnly': false,
|
||||
'secure': false,
|
||||
'sameSite': 'Lax'
|
||||
}
|
||||
],
|
||||
'origins': []
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve local storage on import/export of storage state', async ({ playwright, server }) => {
|
||||
const storageState: StorageStateType = {
|
||||
cookies: [
|
||||
{
|
||||
'name': 'a',
|
||||
'value': 'b',
|
||||
'domain': 'a.b.one.com',
|
||||
'path': '/',
|
||||
'expires': -1,
|
||||
'httpOnly': false,
|
||||
'secure': false,
|
||||
'sameSite': 'Lax'
|
||||
}
|
||||
],
|
||||
origins: [
|
||||
{
|
||||
origin: 'https://www.example.com',
|
||||
localStorage: [{
|
||||
name: 'name1',
|
||||
value: 'value1'
|
||||
}]
|
||||
},
|
||||
]
|
||||
};
|
||||
const request = await playwright._newRequest({ storageState });
|
||||
await request.get(server.EMPTY_PAGE);
|
||||
const exportedState = await request.storageState();
|
||||
expect(exportedState).toEqual(storageState);
|
||||
await request.dispose();
|
||||
});
|
||||
|
||||
it('should send cookies from storage state', async ({ playwright, server }) => {
|
||||
const expires = new Date('12/31/2099 PST');
|
||||
const storageState: StorageStateType = {
|
||||
'cookies': [
|
||||
{
|
||||
'name': 'a',
|
||||
'value': 'b',
|
||||
'domain': 'a.b.one.com',
|
||||
'path': '/',
|
||||
'expires': -1,
|
||||
'httpOnly': false,
|
||||
'secure': false,
|
||||
'sameSite': 'Lax'
|
||||
},
|
||||
{
|
||||
'name': 'c',
|
||||
'value': 'd',
|
||||
'domain': '.b.one.com',
|
||||
'path': '/first/',
|
||||
'expires': +expires / 1000,
|
||||
'httpOnly': false,
|
||||
'secure': false,
|
||||
'sameSite': 'Lax'
|
||||
},
|
||||
{
|
||||
'name': 'e',
|
||||
'value': 'f',
|
||||
'domain': '.b.one.com',
|
||||
'path': '/first/second',
|
||||
'expires': -1,
|
||||
'httpOnly': false,
|
||||
'secure': false,
|
||||
'sameSite': 'Lax'
|
||||
}
|
||||
],
|
||||
'origins': []
|
||||
};
|
||||
const request = await playwright._newRequest({ storageState });
|
||||
const [serverRequest] = await Promise.all([
|
||||
server.waitForRequest('/first/second/third/not_found.html'),
|
||||
request.get(`http://www.a.b.one.com:${server.PORT}/first/second/third/not_found.html`)
|
||||
]);
|
||||
expect(serverRequest.headers.cookie).toBe('c=d; e=f');
|
||||
});
|
||||
|
||||
it('storage state should round-trip through file', async ({ playwright, server }, testInfo) => {
|
||||
const storageState: StorageStateType = {
|
||||
'cookies': [
|
||||
{
|
||||
'name': 'a',
|
||||
'value': 'b',
|
||||
'domain': 'a.b.one.com',
|
||||
'path': '/',
|
||||
'expires': -1,
|
||||
'httpOnly': false,
|
||||
'secure': false,
|
||||
'sameSite': 'Lax'
|
||||
}
|
||||
],
|
||||
'origins': []
|
||||
};
|
||||
|
||||
const request1 = await playwright._newRequest({ storageState });
|
||||
const path = testInfo.outputPath('storage-state.json');
|
||||
const state1 = await request1.storageState({ path });
|
||||
expect(state1).toEqual(storageState);
|
||||
|
||||
const written = await fs.promises.readFile(path, 'utf8');
|
||||
expect(JSON.stringify(state1, undefined, 2)).toBe(written);
|
||||
|
||||
const request2 = await playwright._newRequest({ storageState: path });
|
||||
const state2 = await request2.storageState();
|
||||
expect(state2).toEqual(storageState);
|
||||
});
|
||||
|
|
|
|||
89
types/types.d.ts
vendored
89
types/types.d.ts
vendored
|
|
@ -12797,6 +12797,50 @@ export interface FetchRequest {
|
|||
*/
|
||||
timeout?: number;
|
||||
}): Promise<FetchResponse>;
|
||||
|
||||
/**
|
||||
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
|
||||
* the constructor.
|
||||
* @param options
|
||||
*/
|
||||
storageState(options?: {
|
||||
/**
|
||||
* The file path to save the storage state to. If `path` is a relative path, then it is resolved relative to current
|
||||
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
|
||||
*/
|
||||
path?: string;
|
||||
}): Promise<{
|
||||
cookies: Array<{
|
||||
name: string;
|
||||
|
||||
value: string;
|
||||
|
||||
domain: string;
|
||||
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Unix time in seconds.
|
||||
*/
|
||||
expires: number;
|
||||
|
||||
httpOnly: boolean;
|
||||
|
||||
secure: boolean;
|
||||
|
||||
sameSite: "Strict"|"Lax"|"None";
|
||||
}>;
|
||||
|
||||
origins: Array<{
|
||||
origin: string;
|
||||
|
||||
localStorage: Array<{
|
||||
name: string;
|
||||
|
||||
value: string;
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -13335,6 +13379,51 @@ export const _newRequest: (options?: {
|
|||
password?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via
|
||||
* [browserContext.storageState([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state)
|
||||
* or
|
||||
* [fetchRequest.storageState([options])](https://playwright.dev/docs/api/class-fetchrequest#fetch-request-storage-state).
|
||||
* Either a path to the file with saved storage, or the value returned by one of
|
||||
* [browserContext.storageState([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state)
|
||||
* or
|
||||
* [fetchRequest.storageState([options])](https://playwright.dev/docs/api/class-fetchrequest#fetch-request-storage-state)
|
||||
* methods.
|
||||
*/
|
||||
storageState?: string|{
|
||||
cookies: Array<{
|
||||
name: string;
|
||||
|
||||
value: string;
|
||||
|
||||
domain: string;
|
||||
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Unix time in seconds.
|
||||
*/
|
||||
expires: number;
|
||||
|
||||
httpOnly: boolean;
|
||||
|
||||
secure: boolean;
|
||||
|
||||
sameSite: "Strict"|"Lax"|"None";
|
||||
}>;
|
||||
|
||||
origins: Array<{
|
||||
origin: string;
|
||||
|
||||
localStorage: Array<{
|
||||
name: string;
|
||||
|
||||
value: string;
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the response. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue