diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts
index d78b92d938..f262fa19bc 100644
--- a/packages/playwright-core/src/server/injected/recorder.ts
+++ b/packages/playwright-core/src/server/injected/recorder.ts
@@ -658,13 +658,15 @@ class Overlay {
private _recordToggle: HTMLElement;
private _pickLocatorToggle: HTMLElement;
private _assertToggle: HTMLElement;
- private _position: { x: number, y: number } = { x: 0, y: 0 };
- private _dragState: { position: { x: number, y: number }, dragStart: { x: number, y: number } } | undefined;
+ private _offsetX = 0;
+ private _dragState: { offsetX: number, dragStart: { x: number, y: number } } | undefined;
private _measure: { width: number, height: number } = { width: 0, height: 0 };
constructor(private _recorder: Recorder) {
const document = this._recorder.injectedScript.document;
this._overlayElement = document.createElement('x-pw-overlay');
+ this._overlayElement.style.top = '0';
+ this._overlayElement.style.position = 'absolute';
const shadow = this._overlayElement.attachShadow({ mode: 'closed' });
const styleElement = document.createElement('style');
@@ -682,23 +684,7 @@ class Overlay {
background-color: hsla(0 0% 100% / .9);
font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace;
display: flex;
- flex-direction: column;
- margin: 10px;
- padding: 3px 0;
- border-radius: 17px;
- }
-
- x-pw-drag-handle {
- cursor: grab;
- padding: 6px 9px;
- }
- x-pw-drag-handle > div {
- height: 1px;
- margin-top: 2px;
- background: rgb(148 148 148 / 90%);
- }
- x-pw-drag-handle:active {
- cursor: grabbing;
+ border-radius: 3px;
}
x-pw-separator {
@@ -712,7 +698,7 @@ class Overlay {
height: 28px;
width: 28px;
margin: 2px 4px;
- border-radius: 50%;
+ border-radius: 3px;
}
x-pw-tool-item:not(.disabled):hover {
background-color: hsl(0, 0%, 86%);
@@ -738,7 +724,28 @@ class Overlay {
x-pw-tool-item.record.active > div {
background-color: #a1260d;
}
-
+ x-pw-tool-gripper {
+ height: 28px;
+ width: 24px;
+ margin: 2px 0;
+ cursor: grab;
+ }
+ x-pw-tool-gripper:active {
+ cursor: grabbing;
+ }
+ x-pw-tool-gripper > div {
+ width: 100%;
+ height: 100%;
+ -webkit-mask-repeat: no-repeat;
+ -webkit-mask-position: center;
+ -webkit-mask-size: 20px;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: 16px;
+ -webkit-mask-image: url("data:image/svg+xml;utf8,");
+ mask-image: url("data:image/svg+xml;utf8,");
+ background-color: #555555;
+ }
x-pw-tool-item.record > div {
/* codicon: circle-large-filled */
-webkit-mask-image: url("data:image/svg+xml;utf8,");
@@ -754,17 +761,19 @@ class Overlay {
-webkit-mask-image: url("data:image/svg+xml;utf8,");
mask-image: url("data:image/svg+xml;utf8,");
}
- x-pw-tool-item.close > div {
- /* codicon: close */
- -webkit-mask-image: url("data:image/svg+xml;utf8,");
- mask-image: url("data:image/svg+xml;utf8,");
- }
`;
shadow.appendChild(styleElement);
const toolsListElement = document.createElement('x-pw-tools-list');
shadow.appendChild(toolsListElement);
+ const dragHandle = document.createElement('x-pw-tool-gripper');
+ dragHandle.addEventListener('mousedown', event => {
+ this._dragState = { offsetX: this._offsetX, dragStart: { x: event.clientX, y: 0 } };
+ });
+ dragHandle.appendChild(document.createElement('div'));
+ toolsListElement.appendChild(dragHandle);
+
this._recordToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
this._recordToggle.title = 'Record';
this._recordToggle.classList.add('record');
@@ -774,15 +783,6 @@ class Overlay {
});
toolsListElement.appendChild(this._recordToggle);
- const dragHandle = document.createElement('x-pw-drag-handle');
- dragHandle.addEventListener('mousedown', event => {
- this._dragState = { position: this._position, dragStart: { x: event.clientX, y: event.clientY } };
- });
- dragHandle.append(document.createElement('div'));
- dragHandle.append(document.createElement('div'));
- dragHandle.append(document.createElement('div'));
- toolsListElement.appendChild(dragHandle);
-
this._pickLocatorToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
this._pickLocatorToggle.title = 'Pick locator';
this._pickLocatorToggle.classList.add('pick-locator');
@@ -809,16 +809,6 @@ class Overlay {
});
toolsListElement.appendChild(this._assertToggle);
- const closeButton = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
- closeButton.title = 'Hide this overlay';
- closeButton.classList.add('close');
- closeButton.appendChild(this._recorder.injectedScript.document.createElement('div'));
- closeButton.addEventListener('click', () => {
- this._overlayElement.style.display = 'none';
- this._recorder.delegate.setOverlayState?.({ position: this._position, visible: false });
- });
- toolsListElement.appendChild(closeButton);
-
this._updateVisualPosition();
}
@@ -836,16 +826,14 @@ class Overlay {
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');
- if (this._position.x !== state.overlay.position.x || this._position.y !== state.overlay.position.y) {
- this._position = state.overlay.position;
+ if (this._offsetX !== state.overlay.offsetX) {
+ this._offsetX = state.overlay.offsetX;
this._updateVisualPosition();
}
- this._overlayElement.style.display = state.overlay.visible ? 'block' : 'none';
}
private _updateVisualPosition() {
- this._overlayElement.style.left = this._position.x + 'px';
- this._overlayElement.style.top = this._position.y + 'px';
+ this._overlayElement.style.left = (this._recorder.injectedScript.window.innerWidth / 2 + this._offsetX) + 'px';
}
onMouseMove(event: MouseEvent) {
@@ -854,14 +842,11 @@ class Overlay {
return false;
}
if (this._dragState) {
- this._position = {
- x: this._dragState.position.x + event.clientX - this._dragState.dragStart.x,
- y: this._dragState.position.y + event.clientY - this._dragState.dragStart.y,
- };
- this._position.x = Math.max(0, Math.min(this._recorder.injectedScript.window.innerWidth - this._measure.width, this._position.x));
- this._position.y = Math.max(0, Math.min(this._recorder.injectedScript.window.innerHeight - this._measure.height, this._position.y));
+ this._offsetX = this._dragState.offsetX + event.clientX - this._dragState.dragStart.x;
+ this._offsetX = Math.min(this._recorder.injectedScript.window.innerWidth / 2 - 10 - this._measure.width, this._offsetX);
+ this._offsetX = Math.max(10 - this._recorder.injectedScript.window.innerWidth / 2, this._offsetX);
this._updateVisualPosition();
- this._recorder.delegate.setOverlayState?.({ position: this._position, visible: true });
+ this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX });
consumeEvent(event);
return true;
}
@@ -869,6 +854,14 @@ class Overlay {
}
onMouseUp(event: MouseEvent) {
+ if (this._dragState) {
+ consumeEvent(event);
+ return true;
+ }
+ return false;
+ }
+
+ onClick(event: MouseEvent) {
if (this._dragState) {
this._dragState = undefined;
consumeEvent(event);
@@ -887,7 +880,7 @@ export class Recorder {
private _highlight: Highlight;
private _overlay: Overlay | undefined;
private _styleElement: HTMLStyleElement;
- state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { position: { x: 0, y: 0 }, visible: true } };
+ state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } };
readonly document: Document;
delegate: RecorderDelegate = {};
@@ -991,6 +984,8 @@ export class Recorder {
private _onClick(event: MouseEvent) {
if (!event.isTrusted)
return;
+ if (this._overlay?.onClick(event))
+ return;
if (this._ignoreOverlayEvent(event))
return;
this._currentTool.onClick?.(event);
diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts
index 553d12fcc7..f5329072f9 100644
--- a/packages/playwright-core/src/server/recorder.ts
+++ b/packages/playwright-core/src/server/recorder.ts
@@ -55,7 +55,7 @@ export class Recorder implements InstrumentationListener {
private _context: BrowserContext;
private _mode: Mode;
private _highlightedSelector = '';
- private _overlayState: OverlayState = { position: { x: 0, y: 0 }, visible: true };
+ private _overlayState: OverlayState = { offsetX: 0 };
private _recorderApp: IRecorderApp | null = null;
private _currentCallsMetadata = new Map();
private _recorderSources: Source[] = [];
@@ -101,7 +101,7 @@ export class Recorder implements InstrumentationListener {
if (isUnderTest()) {
// Most of our tests put elements at the top left, so get out of the way.
- this._overlayState.position = { x: 350, y: 350 };
+ this._overlayState.offsetX = 200;
}
}
@@ -123,12 +123,6 @@ export class Recorder implements InstrumentationListener {
this.setMode(data.params.mode);
return;
}
- if (data.event === 'setOverlayVisible') {
- this._overlayState.visible = data.params.visible;
- this._recorderApp?.setOverlayVisible(this._overlayState.visible);
- this._refreshOverlay();
- return;
- }
if (data.event === 'selectorUpdated') {
this.setHighlightedSelector(this._currentLanguage, data.params.selector);
return;
@@ -219,7 +213,6 @@ export class Recorder implements InstrumentationListener {
if (frame.parentFrame())
return;
this._overlayState = state;
- this._recorderApp?.setOverlayVisible(state.visible);
});
await this._context.exposeBinding('__pw_resume', false, () => {
diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts
index de108c8fc4..6f56db67b7 100644
--- a/packages/playwright-core/src/server/recorder/recorderApp.ts
+++ b/packages/playwright-core/src/server/recorder/recorderApp.ts
@@ -46,7 +46,6 @@ export interface IRecorderApp extends EventEmitter {
close(): Promise;
setPaused(paused: boolean): Promise;
setMode(mode: Mode): Promise;
- setOverlayVisible(visible: boolean): Promise;
setFileIfNeeded(file: string): Promise;
setSelector(selector: string, userGesture?: boolean): Promise;
updateCallLogs(callLogs: CallLog[]): Promise;
@@ -57,7 +56,6 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp {
async close(): Promise {}
async setPaused(paused: boolean): Promise {}
async setMode(mode: Mode): Promise {}
- async setOverlayVisible(visible: boolean): Promise {}
async setFileIfNeeded(file: string): Promise {}
async setSelector(selector: string, userGesture?: boolean): Promise {}
async updateCallLogs(callLogs: CallLog[]): Promise {}
@@ -147,12 +145,6 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
}).toString(), { isFunction: true }, mode).catch(() => {});
}
- async setOverlayVisible(visible: boolean): Promise {
- await this._page.mainFrame().evaluateExpression(((visible: boolean) => {
- window.playwrightSetOverlayVisible(visible);
- }).toString(), { isFunction: true }, visible).catch(() => {});
- }
-
async setFileIfNeeded(file: string): Promise {
await this._page.mainFrame().evaluateExpression(((file: string) => {
window.playwrightSetFileIfNeeded(file);
diff --git a/packages/recorder/src/main.tsx b/packages/recorder/src/main.tsx
index be5791eb15..5980716d1e 100644
--- a/packages/recorder/src/main.tsx
+++ b/packages/recorder/src/main.tsx
@@ -25,12 +25,10 @@ export const Main: React.FC = ({
const [paused, setPaused] = React.useState(false);
const [log, setLog] = React.useState(new Map());
const [mode, setMode] = React.useState('none');
- const [overlayVisible, setOverlayVisible] = React.useState(true);
window.playwrightSetMode = setMode;
window.playwrightSetSources = setSources;
window.playwrightSetPaused = setPaused;
- window.playwrightSetOverlayVisible = setOverlayVisible;
window.playwrightUpdateLogs = callLogs => {
const newLog = new Map(log);
for (const callLog of callLogs) {
@@ -41,5 +39,5 @@ export const Main: React.FC = ({
};
window.playwrightSourcesEchoForTest = sources;
- return ;
+ return ;
};
diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx
index 65d5628b54..6a1c6eb325 100644
--- a/packages/recorder/src/recorder.tsx
+++ b/packages/recorder/src/recorder.tsx
@@ -40,7 +40,6 @@ export interface RecorderProps {
paused: boolean,
log: Map,
mode: Mode,
- overlayVisible: boolean,
}
export const Recorder: React.FC = ({
@@ -48,7 +47,6 @@ export const Recorder: React.FC = ({
paused,
log,
mode,
- overlayVisible,
}) => {
const [fileId, setFileId] = React.useState();
const [selectedTab, setSelectedTab] = React.useState('log');
@@ -156,9 +154,6 @@ export const Recorder: React.FC = ({
window.dispatch({ event: 'clear' });
}}>
toggleTheme()}>
- {
- window.dispatch({ event: 'setOverlayVisible', params: { visible: !overlayVisible } });
- }}>
diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts
index d8ac13240e..0d4523d09f 100644
--- a/packages/recorder/src/recorderTypes.ts
+++ b/packages/recorder/src/recorderTypes.ts
@@ -21,13 +21,12 @@ export type Point = { x: number, y: number };
export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting';
export type EventData = {
- event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged' | 'setOverlayVisible';
+ event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged';
params: any;
};
export type OverlayState = {
- position: Point;
- visible: boolean;
+ offsetX: number;
};
export type UIState = {
diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx
index 793fb93908..a292fd6785 100644
--- a/packages/trace-viewer/src/ui/snapshotTab.tsx
+++ b/packages/trace-viewer/src/ui/snapshotTab.tsx
@@ -242,7 +242,7 @@ export const InspectModeController: React.FunctionComponent<{
actionSelector: actionSelector.startsWith(frameSelector) ? actionSelector.substring(frameSelector.length).trim() : undefined,
language: sdkLanguage,
testIdAttributeName,
- overlay: { position: { x: 0, y: 0 }, visible: false },
+ overlay: { offsetX: 0 },
}, {
async setSelector(selector: string) {
setHighlightedLocator(asLocator(sdkLanguage, frameSelector + selector, false /* isFrameLocator */, true /* playSafe */));