chore: extract polling recorder (#32749)

We are reusing recorder in a snapshot tab, no need for the polling
harness to be there.
This commit is contained in:
Pavel Feldman 2024-09-23 08:42:18 -07:00 committed by GitHub
parent 99895005e2
commit 0cdc7ee1a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 95 additions and 75 deletions

View File

@ -0,0 +1,91 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes';
import type * as actions from '../../recorder/recorderActions';
import type { InjectedScript } from '../injectedScript';
import { Recorder } from './recorder';
import type { RecorderDelegate } from './recorder';
interface Embedder {
__pw_recorderPerformAction(action: actions.PerformOnRecordAction): Promise<void>;
__pw_recorderRecordAction(action: actions.Action): Promise<void>;
__pw_recorderState(): Promise<UIState>;
__pw_recorderSetSelector(selector: string): Promise<void>;
__pw_recorderSetMode(mode: Mode): Promise<void>;
__pw_recorderSetOverlayState(state: OverlayState): Promise<void>;
__pw_refreshOverlay(): void;
}
export class PollingRecorder implements RecorderDelegate {
private _recorder: Recorder;
private _embedder: Embedder;
private _pollRecorderModeTimer: number | undefined;
constructor(injectedScript: InjectedScript) {
this._recorder = new Recorder(injectedScript);
this._embedder = injectedScript.window as any;
injectedScript.onGlobalListenersRemoved.add(() => this._recorder.installListeners());
const refreshOverlay = () => {
this._pollRecorderMode().catch(e => console.log(e)); // eslint-disable-line no-console
};
this._embedder.__pw_refreshOverlay = refreshOverlay;
refreshOverlay();
}
private async _pollRecorderMode() {
const pollPeriod = 1000;
if (this._pollRecorderModeTimer)
clearTimeout(this._pollRecorderModeTimer);
const state = await this._embedder.__pw_recorderState().catch(() => {});
if (!state) {
this._pollRecorderModeTimer = this._recorder.injectedScript.builtinSetTimeout(() => this._pollRecorderMode(), pollPeriod);
return;
}
const win = this._recorder.document.defaultView!;
if (win.top !== win) {
// Only show action point in the main frame, since it is relative to the page's viewport.
// Otherwise we'll see multiple action points at different locations.
state.actionPoint = undefined;
}
this._recorder.setUIState(state, this);
this._pollRecorderModeTimer = this._recorder.injectedScript.builtinSetTimeout(() => this._pollRecorderMode(), pollPeriod);
}
async performAction(action: actions.PerformOnRecordAction) {
await this._embedder.__pw_recorderPerformAction(action);
}
async recordAction(action: actions.Action): Promise<void> {
await this._embedder.__pw_recorderRecordAction(action);
}
async setSelector(selector: string): Promise<void> {
await this._embedder.__pw_recorderSetSelector(selector);
}
async setMode(mode: Mode): Promise<void> {
await this._embedder.__pw_recorderSetMode(mode);
}
async setOverlayState(state: OverlayState): Promise<void> {
await this._embedder.__pw_recorderSetOverlayState(state);
}
}
export default PollingRecorder;

View File

@ -21,9 +21,8 @@ import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes';
import type { ElementText } from '../selectorUtils'; import type { ElementText } from '../selectorUtils';
import type { Highlight, HighlightOptions } from '../highlight'; import type { Highlight, HighlightOptions } from '../highlight';
import clipPaths from './clipPaths'; import clipPaths from './clipPaths';
import type { SimpleDomNode } from '../simpleDom';
interface RecorderDelegate { export interface RecorderDelegate {
performAction?(action: actions.PerformOnRecordAction): Promise<void>; performAction?(action: actions.PerformOnRecordAction): Promise<void>;
recordAction?(action: actions.Action): Promise<void>; recordAction?(action: actions.Action): Promise<void>;
setSelector?(selector: string): Promise<void>; setSelector?(selector: string): Promise<void>;
@ -1457,73 +1456,3 @@ function createSvgElement(doc: Document, { tagName, attrs, children }: SvgJson):
return elem; return elem;
} }
interface Embedder {
__pw_recorderPerformAction(action: actions.PerformOnRecordAction, simpleDomNode?: SimpleDomNode): Promise<void>;
__pw_recorderRecordAction(action: actions.Action, simpleDomNode?: SimpleDomNode): Promise<void>;
__pw_recorderState(): Promise<UIState>;
__pw_recorderSetSelector(selector: string): Promise<void>;
__pw_recorderSetMode(mode: Mode): Promise<void>;
__pw_recorderSetOverlayState(state: OverlayState): Promise<void>;
__pw_refreshOverlay(): void;
}
export class PollingRecorder implements RecorderDelegate {
private _recorder: Recorder;
private _embedder: Embedder;
private _pollRecorderModeTimer: number | undefined;
constructor(injectedScript: InjectedScript) {
this._recorder = new Recorder(injectedScript);
this._embedder = injectedScript.window as any;
injectedScript.onGlobalListenersRemoved.add(() => this._recorder.installListeners());
const refreshOverlay = () => {
this._pollRecorderMode().catch(e => console.log(e)); // eslint-disable-line no-console
};
this._embedder.__pw_refreshOverlay = refreshOverlay;
refreshOverlay();
}
private async _pollRecorderMode() {
const pollPeriod = 1000;
if (this._pollRecorderModeTimer)
clearTimeout(this._pollRecorderModeTimer);
const state = await this._embedder.__pw_recorderState().catch(() => {});
if (!state) {
this._pollRecorderModeTimer = this._recorder.injectedScript.builtinSetTimeout(() => this._pollRecorderMode(), pollPeriod);
return;
}
const win = this._recorder.document.defaultView!;
if (win.top !== win) {
// Only show action point in the main frame, since it is relative to the page's viewport.
// Otherwise we'll see multiple action points at different locations.
state.actionPoint = undefined;
}
this._recorder.setUIState(state, this);
this._pollRecorderModeTimer = this._recorder.injectedScript.builtinSetTimeout(() => this._pollRecorderMode(), pollPeriod);
}
async performAction(action: actions.PerformOnRecordAction, simpleDomNode?: SimpleDomNode) {
await this._embedder.__pw_recorderPerformAction(action, simpleDomNode);
}
async recordAction(action: actions.Action, simpleDomNode?: SimpleDomNode): Promise<void> {
await this._embedder.__pw_recorderRecordAction(action, simpleDomNode);
}
async setSelector(selector: string): Promise<void> {
await this._embedder.__pw_recorderSetSelector(selector);
}
async setMode(mode: Mode): Promise<void> {
await this._embedder.__pw_recorderSetMode(mode);
}
async setOverlayState(state: OverlayState): Promise<void> {
await this._embedder.__pw_recorderSetOverlayState(state);
}
}
export default PollingRecorder;

View File

@ -5,7 +5,7 @@
../isomorphic/** ../isomorphic/**
../registry/** ../registry/**
../../common/ ../../common/
../../generated/recorderSource.ts ../../generated/pollingRecorderSource.ts
../../protocol/ ../../protocol/
../../utils/** ../../utils/**
../../utilsBundle.ts ../../utilsBundle.ts

View File

@ -17,7 +17,7 @@
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import type { Source } from '@recorder/recorderTypes'; import type { Source } from '@recorder/recorderTypes';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as recorderSource from '../../generated/recorderSource'; import * as recorderSource from '../../generated/pollingRecorderSource';
import { eventsHelper, monotonicTime, quoteCSSAttributeValue, type RegisteredListener } from '../../utils'; import { eventsHelper, monotonicTime, quoteCSSAttributeValue, type RegisteredListener } from '../../utils';
import { raceAgainstDeadline } from '../../utils/timeoutRunner'; import { raceAgainstDeadline } from '../../utils/timeoutRunner';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';

View File

@ -45,7 +45,7 @@ const injectedScripts = [
true, true,
], ],
[ [
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'injected', 'recorder', 'recorder.ts'), path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'injected', 'recorder', 'pollingRecorder.ts'),
path.join(ROOT, 'packages', 'playwright-core', 'lib', 'server', 'injected', 'packed'), path.join(ROOT, 'packages', 'playwright-core', 'lib', 'server', 'injected', 'packed'),
path.join(ROOT, 'packages', 'playwright-core', 'src', 'generated'), path.join(ROOT, 'packages', 'playwright-core', 'src', 'generated'),
true, true,