diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index de0d84cf1f..a9bb86a892 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -367,6 +367,9 @@ scheme.DebugControllerInspectRequestedEvent = tObject({ selector: tString, locator: tString, }); +scheme.DebugControllerSetModeRequestedEvent = tObject({ + mode: tString, +}); scheme.DebugControllerStateChangedEvent = tObject({ pageCount: tNumber, }); diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index fb0bc2053c..f084ed1ece 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -35,6 +35,7 @@ export class DebugController extends SdkObject { InspectRequested: 'inspectRequested', SourceChanged: 'sourceChanged', Paused: 'paused', + SetModeRequested: 'setModeRequested', }; private _autoCloseTimer: NodeJS.Timeout | undefined; @@ -240,4 +241,8 @@ class InspectingRecorderApp extends EmptyRecorderApp { override async setPaused(paused: boolean) { this._debugController.emit(DebugController.Events.Paused, { paused }); } + + override async setMode(mode: Mode) { + this._debugController.emit(DebugController.Events.SetModeRequested, { mode }); + } } diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index 251ed1e3c7..34c4d3b4ca 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -40,7 +40,10 @@ export class DebugControllerDispatcher extends Dispatcher { this._dispatchEvent('paused', ({ paused })); - }) + }), + eventsHelper.addEventListener(this._object, DebugController.Events.SetModeRequested, ({ mode }) => { + this._dispatchEvent('setModeRequested', ({ mode })); + }), ]; } diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index baca6d957b..1ff89efe39 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -740,7 +740,7 @@ class Overlay { this._recordToggle.classList.add('record'); this._recordToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._recordToggle.addEventListener('click', () => { - this._recorder.delegate.setMode?.(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'none'); + this._recorder.delegate.setMode?.(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'standby' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'standby'); }); toolsListElement.appendChild(this._recordToggle); @@ -750,8 +750,9 @@ class Overlay { this._pickLocatorToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._pickLocatorToggle.addEventListener('click', () => { const newMode: Record = { - 'inspecting': 'none', + 'inspecting': 'standby', 'none': 'inspecting', + 'standby': 'inspecting', 'recording': 'recording-inspecting', 'recording-inspecting': 'recording', 'assertingText': 'recording-inspecting', @@ -786,11 +787,15 @@ class Overlay { this._recordToggle.classList.toggle('active', state.mode === 'recording' || state.mode === 'assertingText' || state.mode === 'recording-inspecting'); this._pickLocatorToggle.classList.toggle('active', state.mode === 'inspecting' || state.mode === 'recording-inspecting'); this._assertToggle.classList.toggle('active', state.mode === 'assertingText'); - this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'inspecting'); + this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); if (this._offsetX !== state.overlay.offsetX) { this._offsetX = state.overlay.offsetX; this._updateVisualPosition(); } + if (state.mode === 'none') + this._overlayElement.setAttribute('hidden', 'true'); + else + this._overlayElement.removeAttribute('hidden'); } private _updateVisualPosition() { @@ -851,6 +856,7 @@ export class Recorder { this.highlight = new Highlight(injectedScript); this._tools = { 'none': new NoneTool(), + 'standby': new NoneTool(), 'inspecting': new InspectTool(this), 'recording': new RecordActionTool(this), 'recording-inspecting': new InspectTool(this), @@ -929,7 +935,7 @@ export class Recorder { this._actionSelectorModel = null; if (state.actionSelector !== this._actionSelectorModel?.selector) this._actionSelectorModel = state.actionSelector ? querySelector(this.injectedScript, state.actionSelector, this.document) : null; - if (this.state.mode === 'none') + if (this.state.mode === 'none' || this.state.mode === 'standby') this.updateHighlight(this._actionSelectorModel, false); } diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index f5329072f9..b2b5e50014 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -248,7 +248,7 @@ export class Recorder implements InstrumentationListener { this._recorderApp?.setMode(this._mode); this._contextRecorder.setEnabled(this._mode === 'recording' || this._mode === 'assertingText'); this._debugger.setMuted(this._mode === 'recording' || this._mode === 'assertingText'); - if (this._mode !== 'none' && this._context.pages().length === 1) + if (this._mode !== 'none' && this._mode !== 'standby' && this._context.pages().length === 1) this._context.pages()[0].bringToFront().catch(() => {}); this._refreshOverlay(); } diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 37ff723dff..1324393a06 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -170,7 +170,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { async setSelector(selector: string, userGesture?: boolean): Promise { if (userGesture) { if (this._recorder.mode() === 'inspecting') { - this._recorder.setMode('none'); + this._recorder.setMode('standby'); this._page.bringToFront(); } else { this._recorder.setMode('recording'); diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 497fed66d0..68d28335e1 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -636,6 +636,7 @@ export type RecorderSource = { export type DebugControllerInitializer = {}; export interface DebugControllerEventTarget { on(event: 'inspectRequested', callback: (params: DebugControllerInspectRequestedEvent) => void): this; + on(event: 'setModeRequested', callback: (params: DebugControllerSetModeRequestedEvent) => void): this; on(event: 'stateChanged', callback: (params: DebugControllerStateChangedEvent) => void): this; on(event: 'sourceChanged', callback: (params: DebugControllerSourceChangedEvent) => void): this; on(event: 'paused', callback: (params: DebugControllerPausedEvent) => void): this; @@ -658,6 +659,9 @@ export type DebugControllerInspectRequestedEvent = { selector: string, locator: string, }; +export type DebugControllerSetModeRequestedEvent = { + mode: string, +}; export type DebugControllerStateChangedEvent = { pageCount: number, }; @@ -732,6 +736,7 @@ export type DebugControllerCloseAllBrowsersResult = void; export interface DebugControllerEvents { 'inspectRequested': DebugControllerInspectRequestedEvent; + 'setModeRequested': DebugControllerSetModeRequestedEvent; 'stateChanged': DebugControllerStateChangedEvent; 'sourceChanged': DebugControllerSourceChangedEvent; 'paused': DebugControllerPausedEvent; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 75b3f19d40..aac64bdc50 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -763,6 +763,10 @@ DebugController: selector: string locator: string + setModeRequested: + parameters: + mode: string + stateChanged: parameters: pageCount: number diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 7892791183..a6d1e0571b 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -116,20 +116,21 @@ export const Recorder: React.FC = ({ return
{ - window.dispatch({ event: 'setMode', params: { mode: mode === 'none' || mode === 'inspecting' ? 'recording' : 'none' } }); + window.dispatch({ event: 'setMode', params: { mode: mode === 'none' || mode === 'standby' || mode === 'inspecting' ? 'recording' : 'none' } }); }}>Record { const newMode = { - 'inspecting': 'none', + 'inspecting': 'standby', 'none': 'inspecting', + 'standby': 'inspecting', 'recording': 'recording-inspecting', 'recording-inspecting': 'recording', 'assertingText': 'recording-inspecting', }[mode]; window.dispatch({ event: 'setMode', params: { mode: newMode } }).catch(() => { }); }}>Pick locator - { + { window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'recording' : 'assertingText' } }); }}>Assert diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index 0d4523d09f..144ff5fdbc 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -18,7 +18,7 @@ import type { Language } from '../../playwright-core/src/utils/isomorphic/locato export type Point = { x: number, y: number }; -export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting'; +export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting' | 'standby'; export type EventData = { event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged';