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