chore: migrate component testing to server-side page reuse (#15477)

This commit is contained in:
Pavel Feldman 2022-07-12 13:30:24 -08:00 committed by GitHub
parent e4debd0bf6
commit 3939b9f36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 69 additions and 215 deletions

View File

@ -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']> {

View File

@ -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> {

View File

@ -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,

View File

@ -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?

View File

@ -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')),

View File

@ -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'
];

View File

@ -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);

View File

@ -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 };

View File

@ -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);

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
});

View File

@ -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);

View File

@ -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);
});
});
`,