mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: do not reset internal bindings for reuse (#14019)
This commit is contained in:
parent
98945a81a8
commit
a052211dbf
@ -253,7 +253,10 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
}
|
||||
|
||||
async _removeExposedBindings() {
|
||||
this._bindings.clear();
|
||||
for (const key of this._bindings.keys()) {
|
||||
if (!key.startsWith('__pw_'))
|
||||
this._bindings.delete(key);
|
||||
}
|
||||
await this._channel.removeExposedBindings();
|
||||
}
|
||||
|
||||
|
||||
@ -333,7 +333,10 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||
}
|
||||
|
||||
async _removeExposedBindings() {
|
||||
this._bindings.clear();
|
||||
for (const key of this._bindings.keys()) {
|
||||
if (!key.startsWith('__pw_'))
|
||||
this._bindings.delete(key);
|
||||
}
|
||||
await this._channel.removeExposedBindings();
|
||||
}
|
||||
|
||||
|
||||
@ -196,7 +196,10 @@ export abstract class BrowserContext extends SdkObject {
|
||||
}
|
||||
|
||||
async removeExposedBindings() {
|
||||
this._pageBindings.clear();
|
||||
for (const key of this._pageBindings.keys()) {
|
||||
if (!key.startsWith('__pw'))
|
||||
this._pageBindings.delete(key);
|
||||
}
|
||||
await this.doRemoveExposedBindings();
|
||||
}
|
||||
|
||||
|
||||
@ -825,13 +825,17 @@ class FrameSession {
|
||||
this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: binding.source })
|
||||
]);
|
||||
this._exposedBindingNames.push(binding.name);
|
||||
this._evaluateOnNewDocumentIdentifiers.push(response.identifier);
|
||||
if (!binding.name.startsWith('__pw'))
|
||||
this._evaluateOnNewDocumentIdentifiers.push(response.identifier);
|
||||
}
|
||||
|
||||
async _removeExposedBindings() {
|
||||
const names = this._exposedBindingNames;
|
||||
this._exposedBindingNames = [];
|
||||
await Promise.all(names.map(name => this._client.send('Runtime.removeBinding', { name })));
|
||||
const toRetain: string[] = [];
|
||||
const toRemove: string[] = [];
|
||||
for (const name of this._exposedBindingNames)
|
||||
(name.startsWith('__pw_') ? toRetain : toRemove).push(name);
|
||||
this._exposedBindingNames = toRetain;
|
||||
await Promise.all(toRemove.map(name => this._client.send('Runtime.removeBinding', { name })));
|
||||
}
|
||||
|
||||
async _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) {
|
||||
|
||||
@ -335,6 +335,8 @@ export class FFBrowserContext extends BrowserContext {
|
||||
|
||||
async doRemoveExposedBindings() {
|
||||
// TODO: implement me.
|
||||
// This is not a critical problem, what ends up happening is
|
||||
// an old binding will be restored upon page reload and will point nowhere.
|
||||
}
|
||||
|
||||
async doUpdateRequestInterception(): Promise<void> {
|
||||
|
||||
@ -60,7 +60,7 @@ declare global {
|
||||
interface Window {
|
||||
playwright?: ConsoleAPIInterface;
|
||||
inspect: (element: Element | undefined) => void;
|
||||
_playwrightResume: () => Promise<void>;
|
||||
__pw_resume: () => Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ class ConsoleAPI {
|
||||
}
|
||||
|
||||
private _resume() {
|
||||
window._playwrightResume().catch(() => {});
|
||||
window.__pw_resume().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,11 +23,11 @@ import { Highlight } from '../injected/highlight';
|
||||
|
||||
|
||||
declare module globalThis {
|
||||
let _playwrightRecorderPerformAction: (action: actions.Action) => Promise<void>;
|
||||
let _playwrightRecorderRecordAction: (action: actions.Action) => Promise<void>;
|
||||
let _playwrightRecorderState: () => Promise<UIState>;
|
||||
let _playwrightRecorderSetSelector: (selector: string) => Promise<void>;
|
||||
let _playwrightRefreshOverlay: () => void;
|
||||
let __pw_recorderPerformAction: (action: actions.Action) => Promise<void>;
|
||||
let __pw_recorderRecordAction: (action: actions.Action) => Promise<void>;
|
||||
let __pw_recorderState: () => Promise<UIState>;
|
||||
let __pw_recorderSetSelector: (selector: string) => Promise<void>;
|
||||
let __pw_refreshOverlay: () => void;
|
||||
}
|
||||
|
||||
class Recorder {
|
||||
@ -51,10 +51,10 @@ class Recorder {
|
||||
this._refreshListenersIfNeeded();
|
||||
injectedScript.onGlobalListenersRemoved.add(() => this._refreshListenersIfNeeded());
|
||||
|
||||
globalThis._playwrightRefreshOverlay = () => {
|
||||
globalThis.__pw_refreshOverlay = () => {
|
||||
this._pollRecorderMode().catch(e => console.log(e)); // eslint-disable-line no-console
|
||||
};
|
||||
globalThis._playwrightRefreshOverlay();
|
||||
globalThis.__pw_refreshOverlay();
|
||||
if (injectedScript.isUnderTest)
|
||||
console.error('Recorder script ready for test'); // eslint-disable-line no-console
|
||||
}
|
||||
@ -88,7 +88,7 @@ class Recorder {
|
||||
const pollPeriod = 1000;
|
||||
if (this._pollRecorderModeTimer)
|
||||
clearTimeout(this._pollRecorderModeTimer);
|
||||
const state = await globalThis._playwrightRecorderState().catch(e => null);
|
||||
const state = await globalThis.__pw_recorderState().catch(e => null);
|
||||
if (!state) {
|
||||
this._pollRecorderModeTimer = setTimeout(() => this._pollRecorderMode(), pollPeriod);
|
||||
return;
|
||||
@ -154,7 +154,7 @@ class Recorder {
|
||||
|
||||
private _onClick(event: MouseEvent) {
|
||||
if (this._mode === 'inspecting')
|
||||
globalThis._playwrightRecorderSetSelector(this._hoveredModel ? this._hoveredModel.selector : '');
|
||||
globalThis.__pw_recorderSetSelector(this._hoveredModel ? this._hoveredModel.selector : '');
|
||||
if (this._shouldIgnoreMouseEvent(event))
|
||||
return;
|
||||
if (this._actionInProgress(event))
|
||||
@ -276,7 +276,7 @@ class Recorder {
|
||||
}
|
||||
|
||||
if (elementType === 'file') {
|
||||
globalThis._playwrightRecorderRecordAction({
|
||||
globalThis.__pw_recorderRecordAction({
|
||||
name: 'setInputFiles',
|
||||
selector: this._activeModel!.selector,
|
||||
signals: [],
|
||||
@ -288,7 +288,7 @@ class Recorder {
|
||||
// Non-navigating actions are simply recorded by Playwright.
|
||||
if (this._consumedDueWrongTarget(event))
|
||||
return;
|
||||
globalThis._playwrightRecorderRecordAction({
|
||||
globalThis.__pw_recorderRecordAction({
|
||||
name: 'fill',
|
||||
selector: this._activeModel!.selector,
|
||||
signals: [],
|
||||
@ -388,7 +388,7 @@ class Recorder {
|
||||
private async _performAction(action: actions.Action) {
|
||||
this._clearHighlight();
|
||||
this._performingAction = true;
|
||||
await globalThis._playwrightRecorderPerformAction(action).catch(() => {});
|
||||
await globalThis.__pw_recorderPerformAction(action).catch(() => {});
|
||||
this._performingAction = false;
|
||||
|
||||
// Action could have changed DOM, update hovered model selectors.
|
||||
|
||||
@ -316,7 +316,10 @@ export class Page extends SdkObject {
|
||||
}
|
||||
|
||||
async removeExposedBindings() {
|
||||
this._pageBindings.clear();
|
||||
for (const key of this._pageBindings.keys()) {
|
||||
if (!key.startsWith('__pw'))
|
||||
this._pageBindings.delete(key);
|
||||
}
|
||||
await this._delegate.removeExposedBindings();
|
||||
}
|
||||
|
||||
|
||||
@ -130,7 +130,7 @@ export class Recorder implements InstrumentationListener {
|
||||
this._recorderApp?.setFileIfNeeded(data.primaryFileName);
|
||||
});
|
||||
|
||||
await this._context.exposeBinding('_playwrightRecorderState', false, source => {
|
||||
await this._context.exposeBinding('__pw_recorderState', false, source => {
|
||||
let actionSelector = this._highlightedSelector;
|
||||
let actionPoint: Point | undefined;
|
||||
for (const [metadata, sdkObject] of this._currentCallsMetadata) {
|
||||
@ -147,13 +147,13 @@ export class Recorder implements InstrumentationListener {
|
||||
return uiState;
|
||||
});
|
||||
|
||||
await this._context.exposeBinding('_playwrightRecorderSetSelector', false, async (_, selector: string) => {
|
||||
await this._context.exposeBinding('__pw_recorderSetSelector', false, async (_, selector: string) => {
|
||||
this._setMode('none');
|
||||
await this._recorderApp?.setSelector(selector, true);
|
||||
await this._recorderApp?.bringToFront();
|
||||
});
|
||||
|
||||
await this._context.exposeBinding('_playwrightResume', false, () => {
|
||||
await this._context.exposeBinding('__pw_resume', false, () => {
|
||||
this._debugger.resume(false);
|
||||
});
|
||||
await this._context.extendInjectedScript(consoleApiSource.source);
|
||||
@ -189,7 +189,7 @@ export class Recorder implements InstrumentationListener {
|
||||
|
||||
private _refreshOverlay() {
|
||||
for (const page of this._context.pages())
|
||||
page.mainFrame().evaluateExpression('window._playwrightRefreshOverlay()', false, undefined, 'main').catch(() => {});
|
||||
page.mainFrame().evaluateExpression('window.__pw_refreshOverlay()', false, undefined, 'main').catch(() => {});
|
||||
}
|
||||
|
||||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||
@ -359,11 +359,11 @@ class ContextRecorder extends EventEmitter {
|
||||
|
||||
// Input actions that potentially lead to navigation are intercepted on the page and are
|
||||
// performed by the Playwright.
|
||||
await this._context.exposeBinding('_playwrightRecorderPerformAction', false,
|
||||
await this._context.exposeBinding('__pw_recorderPerformAction', false,
|
||||
(source: BindingSource, action: actions.Action) => this._performAction(source.frame, action));
|
||||
|
||||
// Other non-essential actions are simply being recorded.
|
||||
await this._context.exposeBinding('_playwrightRecorderRecordAction', false,
|
||||
await this._context.exposeBinding('__pw_recorderRecordAction', false,
|
||||
(source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action));
|
||||
|
||||
await this._context.extendInjectedScript(recorderSource.source);
|
||||
|
||||
@ -15,13 +15,17 @@
|
||||
*/
|
||||
|
||||
import type { Fixtures, Locator, Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, ViewportSize } from './types';
|
||||
import { createGuid } from 'playwright-core/lib/utils';
|
||||
|
||||
let boundCallbacksForMount: Function[] = [];
|
||||
|
||||
export const fixtures: Fixtures<PlaywrightTestArgs & PlaywrightTestOptions & { mount: (component: any, options: any) => Promise<Locator> }, PlaywrightWorkerArgs & { _workerPage: Page }> = {
|
||||
_workerPage: [async ({ browser }, use) => {
|
||||
const page = await (browser as any)._wrapApiCall(async () => {
|
||||
const page = await browser.newPage();
|
||||
await page.addInitScript('navigator.serviceWorker.register = () => {}');
|
||||
await page.exposeFunction('__pw_dispatch', (ordinal: number, args: any[]) => {
|
||||
boundCallbacksForMount[ordinal](...args);
|
||||
});
|
||||
return page;
|
||||
});
|
||||
await use(page);
|
||||
@ -42,6 +46,7 @@ export const fixtures: Fixtures<PlaywrightTestArgs & PlaywrightTestOptions & { m
|
||||
}, true);
|
||||
return page.locator(selector);
|
||||
});
|
||||
boundCallbacksForMount = [];
|
||||
},
|
||||
};
|
||||
|
||||
@ -58,24 +63,18 @@ async function innerMount(page: Page, jsxOrType: any, options: any, viewport: Vi
|
||||
else
|
||||
component = jsxOrType;
|
||||
|
||||
const callbacks: Function[] = [];
|
||||
wrapFunctions(component, page, callbacks);
|
||||
|
||||
const dispatchMethod = `__pw_dispatch_${createGuid()}`;
|
||||
await page.exposeFunction(dispatchMethod, (ordinal: number, args: any[]) => {
|
||||
callbacks[ordinal](...args);
|
||||
});
|
||||
wrapFunctions(component, page, boundCallbacksForMount);
|
||||
|
||||
// WebKit does not wait for deferred scripts.
|
||||
await page.waitForFunction(() => !!(window as any).playwrightMount);
|
||||
|
||||
const selector = await page.evaluate(async ({ component, dispatchMethod }) => {
|
||||
const selector = await page.evaluate(async ({ component }) => {
|
||||
const unwrapFunctions = (object: any) => {
|
||||
for (const [key, value] of Object.entries(object)) {
|
||||
if (typeof value === 'string' && (value as string).startsWith('__pw_func_')) {
|
||||
const ordinal = +value.substring('__pw_func_'.length);
|
||||
object[key] = (...args: any[]) => {
|
||||
(window as any)[dispatchMethod](ordinal, args);
|
||||
(window as any)['__pw_dispatch'](ordinal, args);
|
||||
};
|
||||
} else if (typeof value === 'object' && value) {
|
||||
unwrapFunctions(value);
|
||||
@ -85,7 +84,7 @@ async function innerMount(page: Page, jsxOrType: any, options: any, viewport: Vi
|
||||
|
||||
unwrapFunctions(component);
|
||||
return await (window as any).playwrightMount(component);
|
||||
}, { component, dispatchMethod });
|
||||
}, { component });
|
||||
return selector;
|
||||
}
|
||||
|
||||
|
||||
@ -94,3 +94,26 @@ it('should work with CSP', async ({ page, context, server }) => {
|
||||
await page.evaluate(() => (window as any).hi());
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('should re-add binding after reset', async ({ page, context }) => {
|
||||
await context.exposeFunction('add', function(a, b) {
|
||||
return Promise.resolve(a - b);
|
||||
});
|
||||
expect(await page.evaluate('add(7, 6)')).toBe(1);
|
||||
|
||||
await (context as any)._removeExposedBindings();
|
||||
await context.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, 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);
|
||||
});
|
||||
|
||||
@ -261,3 +261,26 @@ it('should work with setContent', async ({ page, server }) => {
|
||||
await page.setContent('<script>window.result = compute(3, 2)</script>');
|
||||
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);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user