Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions docs/src/api/class-browsercontext.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]>
Expand Down
21 changes: 21 additions & 0 deletions docs/src/api/class-fetchrequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-%%
22 changes: 22 additions & 0 deletions docs/src/api/class-playwright.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]>

Expand Down
7 changes: 7 additions & 0 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]>

Expand Down
23 changes: 17 additions & 6 deletions src/client/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
* 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';
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; },
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
Expand Down
7 changes: 6 additions & 1 deletion src/client/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
});
}
Expand Down
9 changes: 7 additions & 2 deletions src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
};
19 changes: 1 addition & 18 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
2 changes: 1 addition & 1 deletion src/dispatchers/browserContextDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
4 changes: 4 additions & 0 deletions src/dispatchers/networkDispatchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
15 changes: 15 additions & 0 deletions src/protocol/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -346,6 +353,10 @@ export type PlaywrightNewRequestParams = {
password?: string,
},
timeout?: number,
storageState?: {
cookies: NetworkCookie[],
origins: OriginStorage[],
},
};
export type PlaywrightNewRequestOptions = {
baseURL?: string,
Expand All @@ -363,6 +374,10 @@ export type PlaywrightNewRequestOptions = {
password?: string,
},
timeout?: number,
storageState?: {
cookies: NetworkCookie[],
origins: OriginStorage[],
},
};
export type PlaywrightNewRequestResult = {
request: FetchRequestChannel,
Expand Down
18 changes: 18 additions & 0 deletions src/protocol/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,15 @@ FetchRequest:
returns:
binary?: binary

storageState:
returns:
cookies:
type: array
items: NetworkCookie
origins:
type: array
items: OriginStorage

disposeFetchResponse:
parameters:
fetchUid: string
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/server/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
Expand Down
11 changes: 9 additions & 2 deletions src/server/cookieStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down
Loading