mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: migrate component testing to server-side page reuse (#15477)
This commit is contained in:
parent
e4debd0bf6
commit
3939b9f36e
@ -244,23 +244,11 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
await this._channel.addInitScript({ source });
|
||||
}
|
||||
|
||||
async _removeInitScripts() {
|
||||
await this._channel.removeInitScripts();
|
||||
}
|
||||
|
||||
async exposeBinding(name: string, callback: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}): Promise<void> {
|
||||
await this._channel.exposeBinding({ name, needsHandle: options.handle });
|
||||
this._bindings.set(name, callback);
|
||||
}
|
||||
|
||||
async _removeExposedBindings() {
|
||||
for (const key of this._bindings.keys()) {
|
||||
if (!key.startsWith('__pw_'))
|
||||
this._bindings.delete(key);
|
||||
}
|
||||
await this._channel.removeExposedBindings();
|
||||
}
|
||||
|
||||
async exposeFunction(name: string, callback: Function): Promise<void> {
|
||||
await this._channel.exposeBinding({ name });
|
||||
const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args);
|
||||
@ -301,11 +289,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
await this._disableInterception();
|
||||
}
|
||||
|
||||
async _unrouteAll() {
|
||||
this._routes = [];
|
||||
await this._disableInterception();
|
||||
}
|
||||
|
||||
private async _disableInterception() {
|
||||
await this._channel.setNetworkInterceptionEnabled({ enabled: false });
|
||||
}
|
||||
@ -395,12 +378,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
}) {
|
||||
await this._channel.recorderSupplementEnable(params);
|
||||
}
|
||||
|
||||
async _resetForReuse() {
|
||||
await this._unrouteAll();
|
||||
await this._removeInitScripts();
|
||||
await this._removeExposedBindings();
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareStorageState(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams['storageState']> {
|
||||
|
||||
@ -329,14 +329,6 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||
this._bindings.set(name, callback);
|
||||
}
|
||||
|
||||
async _removeExposedBindings() {
|
||||
for (const key of this._bindings.keys()) {
|
||||
if (!key.startsWith('__pw_'))
|
||||
this._bindings.delete(key);
|
||||
}
|
||||
await this._channel.removeExposedBindings();
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: Headers) {
|
||||
validateHeaders(headers);
|
||||
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
|
||||
@ -457,10 +449,6 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||
await this._channel.addInitScript({ source });
|
||||
}
|
||||
|
||||
async _removeInitScripts() {
|
||||
await this._channel.removeInitScripts();
|
||||
}
|
||||
|
||||
async route(url: URLMatch, handler: RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
|
||||
this._routes.unshift(new RouteHandler(this._browserContext._options.baseURL, url, handler, options.times));
|
||||
if (this._routes.length === 1)
|
||||
@ -482,11 +470,6 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||
await this._disableInterception();
|
||||
}
|
||||
|
||||
async _unrouteAll() {
|
||||
this._routes = [];
|
||||
await this._disableInterception();
|
||||
}
|
||||
|
||||
private async _disableInterception() {
|
||||
await this._channel.setNetworkInterceptionEnabled({ enabled: false });
|
||||
}
|
||||
@ -736,12 +719,6 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||
}
|
||||
return result.pdf;
|
||||
}
|
||||
|
||||
async _resetForReuse() {
|
||||
await this._unrouteAll();
|
||||
await this._removeInitScripts();
|
||||
await this._removeExposedBindings();
|
||||
}
|
||||
}
|
||||
|
||||
export class BindingCall extends ChannelOwner<channels.BindingCallChannel> {
|
||||
|
||||
@ -1227,13 +1227,11 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT
|
||||
_type_BrowserContext: boolean;
|
||||
addCookies(params: BrowserContextAddCookiesParams, metadata?: Metadata): Promise<BrowserContextAddCookiesResult>;
|
||||
addInitScript(params: BrowserContextAddInitScriptParams, metadata?: Metadata): Promise<BrowserContextAddInitScriptResult>;
|
||||
removeInitScripts(params?: BrowserContextRemoveInitScriptsParams, metadata?: Metadata): Promise<BrowserContextRemoveInitScriptsResult>;
|
||||
clearCookies(params?: BrowserContextClearCookiesParams, metadata?: Metadata): Promise<BrowserContextClearCookiesResult>;
|
||||
clearPermissions(params?: BrowserContextClearPermissionsParams, metadata?: Metadata): Promise<BrowserContextClearPermissionsResult>;
|
||||
close(params?: BrowserContextCloseParams, metadata?: Metadata): Promise<BrowserContextCloseResult>;
|
||||
cookies(params: BrowserContextCookiesParams, metadata?: Metadata): Promise<BrowserContextCookiesResult>;
|
||||
exposeBinding(params: BrowserContextExposeBindingParams, metadata?: Metadata): Promise<BrowserContextExposeBindingResult>;
|
||||
removeExposedBindings(params?: BrowserContextRemoveExposedBindingsParams, metadata?: Metadata): Promise<BrowserContextRemoveExposedBindingsResult>;
|
||||
grantPermissions(params: BrowserContextGrantPermissionsParams, metadata?: Metadata): Promise<BrowserContextGrantPermissionsResult>;
|
||||
newPage(params?: BrowserContextNewPageParams, metadata?: Metadata): Promise<BrowserContextNewPageResult>;
|
||||
setDefaultNavigationTimeoutNoReply(params: BrowserContextSetDefaultNavigationTimeoutNoReplyParams, metadata?: Metadata): Promise<BrowserContextSetDefaultNavigationTimeoutNoReplyResult>;
|
||||
@ -1305,9 +1303,6 @@ export type BrowserContextAddInitScriptOptions = {
|
||||
|
||||
};
|
||||
export type BrowserContextAddInitScriptResult = void;
|
||||
export type BrowserContextRemoveInitScriptsParams = {};
|
||||
export type BrowserContextRemoveInitScriptsOptions = {};
|
||||
export type BrowserContextRemoveInitScriptsResult = void;
|
||||
export type BrowserContextClearCookiesParams = {};
|
||||
export type BrowserContextClearCookiesOptions = {};
|
||||
export type BrowserContextClearCookiesResult = void;
|
||||
@ -1334,9 +1329,6 @@ export type BrowserContextExposeBindingOptions = {
|
||||
needsHandle?: boolean,
|
||||
};
|
||||
export type BrowserContextExposeBindingResult = void;
|
||||
export type BrowserContextRemoveExposedBindingsParams = {};
|
||||
export type BrowserContextRemoveExposedBindingsOptions = {};
|
||||
export type BrowserContextRemoveExposedBindingsResult = void;
|
||||
export type BrowserContextGrantPermissionsParams = {
|
||||
permissions: string[],
|
||||
origin?: string,
|
||||
@ -1529,11 +1521,9 @@ export interface PageChannel extends PageEventTarget, EventTargetChannel {
|
||||
setDefaultTimeoutNoReply(params: PageSetDefaultTimeoutNoReplyParams, metadata?: Metadata): Promise<PageSetDefaultTimeoutNoReplyResult>;
|
||||
setFileChooserInterceptedNoReply(params: PageSetFileChooserInterceptedNoReplyParams, metadata?: Metadata): Promise<PageSetFileChooserInterceptedNoReplyResult>;
|
||||
addInitScript(params: PageAddInitScriptParams, metadata?: Metadata): Promise<PageAddInitScriptResult>;
|
||||
removeInitScripts(params?: PageRemoveInitScriptsParams, metadata?: Metadata): Promise<PageRemoveInitScriptsResult>;
|
||||
close(params: PageCloseParams, metadata?: Metadata): Promise<PageCloseResult>;
|
||||
emulateMedia(params: PageEmulateMediaParams, metadata?: Metadata): Promise<PageEmulateMediaResult>;
|
||||
exposeBinding(params: PageExposeBindingParams, metadata?: Metadata): Promise<PageExposeBindingResult>;
|
||||
removeExposedBindings(params?: PageRemoveExposedBindingsParams, metadata?: Metadata): Promise<PageRemoveExposedBindingsResult>;
|
||||
goBack(params: PageGoBackParams, metadata?: Metadata): Promise<PageGoBackResult>;
|
||||
goForward(params: PageGoForwardParams, metadata?: Metadata): Promise<PageGoForwardResult>;
|
||||
reload(params: PageReloadParams, metadata?: Metadata): Promise<PageReloadResult>;
|
||||
@ -1631,9 +1621,6 @@ export type PageAddInitScriptOptions = {
|
||||
|
||||
};
|
||||
export type PageAddInitScriptResult = void;
|
||||
export type PageRemoveInitScriptsParams = {};
|
||||
export type PageRemoveInitScriptsOptions = {};
|
||||
export type PageRemoveInitScriptsResult = void;
|
||||
export type PageCloseParams = {
|
||||
runBeforeUnload?: boolean,
|
||||
};
|
||||
@ -1662,9 +1649,6 @@ export type PageExposeBindingOptions = {
|
||||
needsHandle?: boolean,
|
||||
};
|
||||
export type PageExposeBindingResult = void;
|
||||
export type PageRemoveExposedBindingsParams = {};
|
||||
export type PageRemoveExposedBindingsOptions = {};
|
||||
export type PageRemoveExposedBindingsResult = void;
|
||||
export type PageGoBackParams = {
|
||||
timeout?: number,
|
||||
waitUntil?: LifecycleEvent,
|
||||
|
||||
@ -850,8 +850,6 @@ BrowserContext:
|
||||
parameters:
|
||||
source: string
|
||||
|
||||
removeInitScripts:
|
||||
|
||||
clearCookies:
|
||||
|
||||
clearPermissions:
|
||||
@ -873,8 +871,6 @@ BrowserContext:
|
||||
name: string
|
||||
needsHandle: boolean?
|
||||
|
||||
removeExposedBindings:
|
||||
|
||||
grantPermissions:
|
||||
parameters:
|
||||
permissions:
|
||||
@ -1061,8 +1057,6 @@ Page:
|
||||
parameters:
|
||||
source: string
|
||||
|
||||
removeInitScripts:
|
||||
|
||||
close:
|
||||
parameters:
|
||||
runBeforeUnload: boolean?
|
||||
@ -1104,8 +1098,6 @@ Page:
|
||||
name: string
|
||||
needsHandle: boolean?
|
||||
|
||||
removeExposedBindings:
|
||||
|
||||
goBack:
|
||||
parameters:
|
||||
timeout: number?
|
||||
|
||||
@ -695,8 +695,6 @@ scheme.BrowserContextAddInitScriptParams = tObject({
|
||||
source: tString,
|
||||
});
|
||||
scheme.BrowserContextAddInitScriptResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextRemoveInitScriptsParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextRemoveInitScriptsResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextClearCookiesParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextClearCookiesResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextClearPermissionsParams = tOptional(tObject({}));
|
||||
@ -714,8 +712,6 @@ scheme.BrowserContextExposeBindingParams = tObject({
|
||||
needsHandle: tOptional(tBoolean),
|
||||
});
|
||||
scheme.BrowserContextExposeBindingResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextRemoveExposedBindingsParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextRemoveExposedBindingsResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextGrantPermissionsParams = tObject({
|
||||
permissions: tArray(tString),
|
||||
origin: tOptional(tString),
|
||||
@ -871,8 +867,6 @@ scheme.PageAddInitScriptParams = tObject({
|
||||
source: tString,
|
||||
});
|
||||
scheme.PageAddInitScriptResult = tOptional(tObject({}));
|
||||
scheme.PageRemoveInitScriptsParams = tOptional(tObject({}));
|
||||
scheme.PageRemoveInitScriptsResult = tOptional(tObject({}));
|
||||
scheme.PageCloseParams = tObject({
|
||||
runBeforeUnload: tOptional(tBoolean),
|
||||
});
|
||||
@ -889,8 +883,6 @@ scheme.PageExposeBindingParams = tObject({
|
||||
needsHandle: tOptional(tBoolean),
|
||||
});
|
||||
scheme.PageExposeBindingResult = tOptional(tObject({}));
|
||||
scheme.PageRemoveExposedBindingsParams = tOptional(tObject({}));
|
||||
scheme.PageRemoveExposedBindingsResult = tOptional(tObject({}));
|
||||
scheme.PageGoBackParams = tObject({
|
||||
timeout: tOptional(tNumber),
|
||||
waitUntil: tOptional(tType('LifecycleEvent')),
|
||||
|
||||
@ -140,25 +140,33 @@ export abstract class BrowserContext extends SdkObject {
|
||||
canResetForReuse(): boolean {
|
||||
if (this._closedStatus !== 'open')
|
||||
return false;
|
||||
if (this.pages().length < 1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
async resetForReuse(metadata: CallMetadata) {
|
||||
static reusableContextHash(params: channels.BrowserNewContextForReuseParams): string {
|
||||
const paramsCopy = { ...params };
|
||||
for (const key of paramsThatAllowContextReuse)
|
||||
delete paramsCopy[key];
|
||||
return JSON.stringify(paramsCopy);
|
||||
}
|
||||
|
||||
async resetForReuse(metadata: CallMetadata, params: channels.BrowserNewContextForReuseParams) {
|
||||
this.setDefaultNavigationTimeout(undefined);
|
||||
this.setDefaultTimeout(undefined);
|
||||
|
||||
for (const key of paramsThatAllowContextReuse)
|
||||
(this._options as any)[key] = params[key];
|
||||
|
||||
await this._cancelAllRoutesInFlight();
|
||||
|
||||
const [page, ...otherPages] = this.pages();
|
||||
for (const page of otherPages)
|
||||
await page.close(metadata);
|
||||
// Unless I do this early, setting extra http headers below does not respond.
|
||||
await page._frameManager.closeOpenDialogs();
|
||||
await page.mainFrame().goto(metadata, 'about:blank', { timeout: 0 });
|
||||
await this.removeExposedBindings();
|
||||
await this.removeInitScripts();
|
||||
await page?._frameManager.closeOpenDialogs();
|
||||
await page?.mainFrame().goto(metadata, 'about:blank', { timeout: 0 });
|
||||
await this._removeExposedBindings();
|
||||
await this._removeInitScripts();
|
||||
// TODO: following can be optimized to not perform noops.
|
||||
if (this._options.permissions)
|
||||
await this.grantPermissions(this._options.permissions);
|
||||
@ -168,7 +176,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
await this.setGeolocation(this._options.geolocation);
|
||||
await this.setOffline(!!this._options.offline);
|
||||
|
||||
await page.resetForReuse(metadata);
|
||||
await page?.resetForReuse(metadata);
|
||||
}
|
||||
|
||||
_browserClosed() {
|
||||
@ -236,7 +244,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
await this.doExposeBinding(binding);
|
||||
}
|
||||
|
||||
async removeExposedBindings() {
|
||||
async _removeExposedBindings() {
|
||||
for (const key of this._pageBindings.keys()) {
|
||||
if (!key.startsWith('__pw'))
|
||||
this._pageBindings.delete(key);
|
||||
@ -324,7 +332,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
await this.doAddInitScript(script);
|
||||
}
|
||||
|
||||
async removeInitScripts(): Promise<void> {
|
||||
async _removeInitScripts(): Promise<void> {
|
||||
this.initScripts.splice(0, this.initScripts.length);
|
||||
await this.doRemoveInitScripts();
|
||||
}
|
||||
@ -586,3 +594,11 @@ export function normalizeProxySettings(proxy: types.ProxySettings): types.ProxyS
|
||||
bypass = bypass.split(',').map(t => t.trim()).join(',');
|
||||
return { ...proxy, server, bypass };
|
||||
}
|
||||
|
||||
const paramsThatAllowContextReuse: (keyof channels.BrowserNewContextForReuseParams)[] = [
|
||||
'colorScheme',
|
||||
'forcedColors',
|
||||
'reducedMotion',
|
||||
'screen',
|
||||
'viewport'
|
||||
];
|
||||
|
||||
@ -126,10 +126,6 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
});
|
||||
}
|
||||
|
||||
async removeExposedBindings() {
|
||||
await this._context.removeExposedBindings();
|
||||
}
|
||||
|
||||
async newPage(params: channels.BrowserContextNewPageParams, metadata: CallMetadata): Promise<channels.BrowserContextNewPageResult> {
|
||||
return { page: lookupDispatcher<PageDispatcher>(await this._context.newPage(metadata)) };
|
||||
}
|
||||
@ -174,10 +170,6 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
await this._context.addInitScript(params.source);
|
||||
}
|
||||
|
||||
async removeInitScripts(): Promise<void> {
|
||||
await this._context.removeInitScripts();
|
||||
}
|
||||
|
||||
async setNetworkInterceptionEnabled(params: channels.BrowserContextSetNetworkInterceptionEnabledParams): Promise<void> {
|
||||
if (!params.enabled) {
|
||||
await this._context.setRequestInterceptor(undefined);
|
||||
|
||||
@ -51,7 +51,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
|
||||
* Used for inner loop scenarios where user would like to preserve the browser window, opened page and devtools instance.
|
||||
*/
|
||||
async newContextForReuse(params: channels.BrowserNewContextForReuseParams, metadata: CallMetadata): Promise<channels.BrowserNewContextForReuseResult> {
|
||||
const hash = JSON.stringify(params);
|
||||
const hash = BrowserContext.reusableContextHash(params);
|
||||
if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) {
|
||||
if (this._contextForReuse)
|
||||
await this._contextForReuse.context.close(metadata);
|
||||
@ -59,7 +59,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
|
||||
} else {
|
||||
const oldContextDispatcher = existingDispatcher<BrowserContextDispatcher>(this._contextForReuse.context);
|
||||
oldContextDispatcher._dispose();
|
||||
await this._contextForReuse.context.resetForReuse(metadata);
|
||||
await this._contextForReuse.context.resetForReuse(metadata, params);
|
||||
}
|
||||
const context = new BrowserContextDispatcher(this._scope, this._contextForReuse.context);
|
||||
return { context };
|
||||
|
||||
@ -107,10 +107,6 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel> imple
|
||||
});
|
||||
}
|
||||
|
||||
async removeExposedBindings() {
|
||||
await this._page.removeExposedBindings();
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(params: channels.PageSetExtraHTTPHeadersParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.setExtraHTTPHeaders(params.headers);
|
||||
}
|
||||
@ -144,10 +140,6 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel> imple
|
||||
await this._page.addInitScript(params.source);
|
||||
}
|
||||
|
||||
async removeInitScripts(): Promise<void> {
|
||||
await this._page.removeInitScripts();
|
||||
}
|
||||
|
||||
async setNetworkInterceptionEnabled(params: channels.PageSetNetworkInterceptionEnabledParams, metadata: CallMetadata): Promise<void> {
|
||||
if (!params.enabled) {
|
||||
await this._page.setClientRequestInterceptor(undefined);
|
||||
|
||||
@ -231,11 +231,14 @@ export class Page extends SdkObject {
|
||||
this.setDefaultNavigationTimeout(undefined);
|
||||
this.setDefaultTimeout(undefined);
|
||||
|
||||
// To this first in order to unfreeze evaluates.
|
||||
// Do this first in order to unfreeze evaluates.
|
||||
await this._frameManager.closeOpenDialogs();
|
||||
|
||||
await this.removeExposedBindings();
|
||||
await this.removeInitScripts();
|
||||
await this._removeExposedBindings();
|
||||
await this._removeInitScripts();
|
||||
|
||||
// TODO: handle pending routes.
|
||||
await this.setClientRequestInterceptor(undefined);
|
||||
await this._setServerRequestInterceptor(undefined);
|
||||
await this.setFileChooserIntercepted(false);
|
||||
await this.mainFrame().goto(metadata, 'about:blank');
|
||||
@ -334,7 +337,7 @@ export class Page extends SdkObject {
|
||||
await this._delegate.exposeBinding(binding);
|
||||
}
|
||||
|
||||
async removeExposedBindings() {
|
||||
async _removeExposedBindings() {
|
||||
for (const key of this._pageBindings.keys()) {
|
||||
if (!key.startsWith('__pw'))
|
||||
this._pageBindings.delete(key);
|
||||
@ -431,6 +434,7 @@ export class Page extends SdkObject {
|
||||
this._emulatedMedia.reducedMotion = options.reducedMotion;
|
||||
if (options.forcedColors !== undefined)
|
||||
this._emulatedMedia.forcedColors = options.forcedColors;
|
||||
|
||||
await this._delegate.updateEmulateMedia();
|
||||
await this._doSlowMo();
|
||||
}
|
||||
@ -471,7 +475,7 @@ export class Page extends SdkObject {
|
||||
await this._delegate.addInitScript(source);
|
||||
}
|
||||
|
||||
async removeInitScripts() {
|
||||
async _removeInitScripts() {
|
||||
this.initScripts.splice(0, this.initScripts.length);
|
||||
await this._delegate.removeInitScripts();
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ if ((process as any)['__pw_initiator__']) {
|
||||
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
||||
_combinedContextOptions: BrowserContextOptions,
|
||||
_contextReuseEnabled: boolean,
|
||||
_reuseContext: boolean,
|
||||
_setupContextOptionsAndArtifacts: void;
|
||||
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||
};
|
||||
@ -506,13 +507,15 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
|
||||
testInfo.errors.push({ message: prependToError });
|
||||
}, { scope: 'test', _title: 'context' } as any],
|
||||
|
||||
_contextReuseEnabled: async ({ video, trace }, use, testInfo) => {
|
||||
const reuse = !!process.env.PW_REUSE_CONTEXT && !shouldCaptureVideo(normalizeVideoMode(video), testInfo) && !shouldCaptureTrace(normalizeTraceMode(trace), testInfo);
|
||||
_contextReuseEnabled: !!process.env.PW_REUSE_CONTEXT,
|
||||
|
||||
_reuseContext: async ({ video, trace, _contextReuseEnabled }, use, testInfo) => {
|
||||
const reuse = _contextReuseEnabled && !shouldCaptureVideo(normalizeVideoMode(video), testInfo) && !shouldCaptureTrace(normalizeTraceMode(trace), testInfo);
|
||||
await use(reuse);
|
||||
},
|
||||
|
||||
context: async ({ playwright, browser, _contextReuseEnabled, _contextFactory }, use, testInfo) => {
|
||||
if (!_contextReuseEnabled) {
|
||||
context: async ({ playwright, browser, _reuseContext, _contextFactory }, use, testInfo) => {
|
||||
if (!_reuseContext) {
|
||||
await use(await _contextFactory());
|
||||
return;
|
||||
}
|
||||
@ -522,8 +525,8 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
|
||||
await use(context);
|
||||
},
|
||||
|
||||
page: async ({ context, _contextReuseEnabled }, use) => {
|
||||
if (!_contextReuseEnabled) {
|
||||
page: async ({ context, _reuseContext }, use) => {
|
||||
if (!_reuseContext) {
|
||||
await use(await context.newPage());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { normalizeTraceMode, normalizeVideoMode, shouldCaptureTrace, shouldCaptureVideo } from './index';
|
||||
import type { Fixtures, Locator, Page, BrowserContextOptions, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, BrowserContext } from './types';
|
||||
import type { Component, JsxComponent, ObjectComponentOptions } from '../types/component';
|
||||
|
||||
@ -23,43 +22,19 @@ let boundCallbacksForMount: Function[] = [];
|
||||
export const fixtures: Fixtures<
|
||||
PlaywrightTestArgs & PlaywrightTestOptions & { mount: (component: any, options: any) => Promise<Locator> },
|
||||
PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _ctWorker: { context: BrowserContext | undefined, hash: string } },
|
||||
{ _contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext> }> = {
|
||||
{ _contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>, _contextReuseEnabled: boolean }> = {
|
||||
|
||||
_contextReuseEnabled: true,
|
||||
|
||||
serviceWorkers: 'block',
|
||||
|
||||
_ctWorker: [{ context: undefined, hash: '' }, { scope: 'worker' }],
|
||||
|
||||
context: async ({ playwright, browser, _ctWorker, _contextFactory, video, trace }, use, testInfo) => {
|
||||
const isolateTests = shouldCaptureVideo(normalizeVideoMode(video), testInfo) || shouldCaptureTrace(normalizeTraceMode(trace), testInfo);
|
||||
const defaultContextOptions = (playwright.chromium as any)._defaultContextOptions as BrowserContextOptions;
|
||||
const hash = contextHash(defaultContextOptions);
|
||||
|
||||
if (!_ctWorker.context || _ctWorker.hash !== hash || isolateTests) {
|
||||
if (_ctWorker.context)
|
||||
await _ctWorker.context.close();
|
||||
// Context factory sets up video so we want to use that for isolated contexts.
|
||||
// However, it closes the context after the test, so we don't want to use it
|
||||
// for shared contexts.
|
||||
_ctWorker.context = isolateTests ? await _contextFactory() : await browser.newContext();
|
||||
_ctWorker.hash = hash;
|
||||
await _ctWorker.context.addInitScript('navigator.serviceWorker.register = () => {}');
|
||||
await _ctWorker.context.exposeFunction('__pw_dispatch', (ordinal: number, args: any[]) => {
|
||||
page: async ({ page }, use) => {
|
||||
await (page as any)._wrapApiCall(async () => {
|
||||
await page.exposeFunction('__ct_dispatch', (ordinal: number, args: any[]) => {
|
||||
boundCallbacksForMount[ordinal](...args);
|
||||
});
|
||||
} else {
|
||||
await (_ctWorker.context as any)._resetForReuse();
|
||||
}
|
||||
await use(_ctWorker.context);
|
||||
},
|
||||
|
||||
page: async ({ context, viewport }, use) => {
|
||||
let page = context.pages()[0];
|
||||
await (context as any)._wrapApiCall(async () => {
|
||||
if (!page) {
|
||||
page = await context.newPage();
|
||||
} else {
|
||||
await (page as any)._resetForReuse();
|
||||
await page.goto('about:blank');
|
||||
await page.setViewportSize(viewport || { width: 1280, height: 800 });
|
||||
}
|
||||
await page.goto(process.env.PLAYWRIGHT_VITE_COMPONENTS_BASE_URL!);
|
||||
}, true);
|
||||
await use(page);
|
||||
@ -94,7 +69,7 @@ async function innerMount(page: Page, jsxOrType: JsxComponent | string, options:
|
||||
if (typeof value === 'string' && (value as string).startsWith('__pw_func_')) {
|
||||
const ordinal = +value.substring('__pw_func_'.length);
|
||||
object[key] = (...args: any[]) => {
|
||||
(window as any)['__pw_dispatch'](ordinal, args);
|
||||
(window as any)['__ct_dispatch'](ordinal, args);
|
||||
};
|
||||
} else if (typeof value === 'object' && value) {
|
||||
unwrapFunctions(value);
|
||||
@ -130,29 +105,3 @@ function wrapFunctions(object: any, page: Page, callbacks: Function[]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function contextHash(context: BrowserContextOptions): string {
|
||||
const hash = {
|
||||
acceptDownloads: context.acceptDownloads,
|
||||
bypassCSP: context.bypassCSP,
|
||||
colorScheme: context.colorScheme,
|
||||
extraHTTPHeaders: context.extraHTTPHeaders,
|
||||
forcedColors: context.forcedColors,
|
||||
geolocation: context.geolocation,
|
||||
hasTouch: context.hasTouch,
|
||||
httpCredentials: context.httpCredentials,
|
||||
ignoreHTTPSErrors: context.ignoreHTTPSErrors,
|
||||
isMobile: context.isMobile,
|
||||
javaScriptEnabled: context.javaScriptEnabled,
|
||||
locale: context.locale,
|
||||
offline: context.offline,
|
||||
permissions: context.permissions,
|
||||
proxy: context.proxy,
|
||||
storageState: context.storageState,
|
||||
timezoneId: context.timezoneId,
|
||||
userAgent: context.userAgent,
|
||||
deviceScaleFactor: context.deviceScaleFactor,
|
||||
serviceWorkers: context.serviceWorkers,
|
||||
};
|
||||
return JSON.stringify(hash);
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { BrowserContext } from '@playwright/test';
|
||||
import { contextTest as it, expect } from '../config/browserTest';
|
||||
|
||||
it('expose binding should work', async ({ context }) => {
|
||||
@ -95,25 +96,23 @@ it('should work with CSP', async ({ page, context, server }) => {
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('should re-add binding after reset', async ({ page, context }) => {
|
||||
it('should re-add binding after reset', async ({ browserType, browser }) => {
|
||||
const defaultContextOptions = (browserType as any)._defaultContextOptions;
|
||||
let context: BrowserContext = await (browser as any)._newContextForReuse(defaultContextOptions);
|
||||
|
||||
await context.exposeFunction('add', function(a, b) {
|
||||
return Promise.resolve(a - b);
|
||||
});
|
||||
let page = await context.newPage();
|
||||
expect(await page.evaluate('add(7, 6)')).toBe(1);
|
||||
|
||||
await (context as any)._removeExposedBindings();
|
||||
context = await (browser as any)._newContextForReuse(defaultContextOptions);
|
||||
await context.exposeFunction('add', function(a, b) {
|
||||
return Promise.resolve(a + b);
|
||||
});
|
||||
|
||||
page = context.pages()[0];
|
||||
expect(await page.evaluate('add(5, 6)')).toBe(11);
|
||||
await page.reload();
|
||||
expect(await page.evaluate('add(5, 6)')).toBe(11);
|
||||
});
|
||||
|
||||
it('should retain internal binding after reset', async ({ page, context }) => {
|
||||
await context.exposeFunction('__pw_add', function(a, b) {
|
||||
return Promise.resolve(a + b);
|
||||
});
|
||||
await (context as any)._removeExposedBindings();
|
||||
expect(await page.evaluate('__pw_add(5, 6)')).toBe(11);
|
||||
});
|
||||
|
||||
@ -262,29 +262,6 @@ it('should work with setContent', async ({ page, server }) => {
|
||||
expect(await page.evaluate('window.result')).toBe(6);
|
||||
});
|
||||
|
||||
it('should re-add binding after reset', async ({ page }) => {
|
||||
await page.exposeFunction('add', function(a, b) {
|
||||
return Promise.resolve(a - b);
|
||||
});
|
||||
expect(await page.evaluate('add(7, 6)')).toBe(1);
|
||||
|
||||
await (page as any)._removeExposedBindings();
|
||||
await page.exposeFunction('add', function(a, b) {
|
||||
return Promise.resolve(a + b);
|
||||
});
|
||||
expect(await page.evaluate('add(5, 6)')).toBe(11);
|
||||
await page.reload();
|
||||
expect(await page.evaluate('add(5, 6)')).toBe(11);
|
||||
});
|
||||
|
||||
it('should retain internal binding after reset', async ({ page }) => {
|
||||
await page.exposeFunction('__pw_add', function(a, b) {
|
||||
return Promise.resolve(a + b);
|
||||
});
|
||||
await (page as any)._removeExposedBindings();
|
||||
expect(await page.evaluate('__pw_add(5, 6)')).toBe(11);
|
||||
});
|
||||
|
||||
it('should alias Window, Document and Node', async ({ page }) => {
|
||||
let object: any;
|
||||
await page.exposeBinding('log', (source, obj) => object = obj);
|
||||
|
||||
@ -26,21 +26,21 @@ test('should reuse context', async ({ runInlineTest }) => {
|
||||
'src/reuse.test.tsx': `
|
||||
//@no-header
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
let lastContext;
|
||||
let lastContextGuid;
|
||||
|
||||
test('one', async ({ context }) => {
|
||||
lastContext = context;
|
||||
lastContextGuid = context._guid;
|
||||
});
|
||||
|
||||
test('two', async ({ context }) => {
|
||||
expect(context).toBe(lastContext);
|
||||
expect(context._guid).toBe(lastContextGuid);
|
||||
});
|
||||
|
||||
test.describe('Dark', () => {
|
||||
test.use({ colorScheme: 'dark' });
|
||||
test.use({ userAgent: 'dark' });
|
||||
|
||||
test('three', async ({ context }) => {
|
||||
expect(context).not.toBe(lastContext);
|
||||
expect(context._guid).not.toBe(lastContextGuid);
|
||||
});
|
||||
});
|
||||
`,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user