chore: place overlay inside the glass pane (#28026)

This commit is contained in:
Pavel Feldman 2023-11-07 19:36:12 -08:00 committed by GitHub
parent 1a8b61199f
commit 061ded19b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 252 additions and 200 deletions

View File

@ -0,0 +1,172 @@
/**
* 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.
*/
export const highlightCSS = `
x-pw-tooltip {
backdrop-filter: blur(5px);
background-color: white;
color: #222;
border-radius: 6px;
box-shadow: 0 0.5rem 1.2rem rgba(0,0,0,.3);
display: none;
font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono',
'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace;
font-size: 12.8px;
font-weight: normal;
left: 0;
line-height: 1.5;
max-width: 600px;
position: absolute;
top: 0;
}
x-pw-tooltip-body {
align-items: center;
padding: 3.2px 5.12px 3.2px;
}
x-pw-highlight {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
}
x-pw-action-point {
position: absolute;
width: 20px;
height: 20px;
background: red;
border-radius: 10px;
margin: -10px 0 0 -10px;
z-index: 2;
}
x-pw-separator {
height: 1px;
margin: 6px 9px;
background: rgb(148 148 148 / 90%);
}
x-pw-tool-gripper {
height: 28px;
width: 24px;
margin: 2px 0;
cursor: grab;
}
x-pw-tool-gripper:active {
cursor: grabbing;
}
x-pw-tool-gripper > x-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,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' /></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' /></svg>");
background-color: #555555;
}
x-pw-tools-list {
display: flex;
width: 100%;
border-bottom: 1px solid #dddddd;
}
x-pw-tool-item {
pointer-events: auto;
cursor: pointer;
height: 28px;
width: 28px;
border-radius: 3px;
}
x-pw-tool-item:not(.disabled):hover {
background-color: hsl(0, 0%, 86%);
}
x-pw-tool-item > x-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;
background-color: #3a3a3a;
}
x-pw-tool-item.disabled > x-div {
background-color: rgba(97, 97, 97, 0.5);
cursor: default;
}
x-pw-tool-item.active > x-div {
background-color: #006ab1;
}
x-pw-tool-item.record.active > x-div {
background-color: #a1260d;
}
x-pw-tool-item.accept > x-div {
background-color: #388a34;
}
x-pw-tool-item.cancel > x-div {
background-color: #e51400;
}
x-pw-tool-item.record > x-div {
/* codicon: circle-large-filled */
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z'/></svg>");
}
x-pw-tool-item.pick-locator > x-div {
/* codicon: inspect */
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z'/></svg>");
}
x-pw-tool-item.assert > x-div {
/* codicon: check-all */
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M15.62 3.596L7.815 12.81l-.728-.033L4 8.382l.754-.53 2.744 3.907L14.917 3l.703.596z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M7.234 8.774l4.386-5.178L10.917 3l-4.23 4.994.547.78zm-1.55.403l.548.78-.547-.78zm-1.617 1.91l.547.78-.799.943-.728-.033L0 8.382l.754-.53 2.744 3.907.57-.672z'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M15.62 3.596L7.815 12.81l-.728-.033L4 8.382l.754-.53 2.744 3.907L14.917 3l.703.596z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M7.234 8.774l4.386-5.178L10.917 3l-4.23 4.994.547.78zm-1.55.403l.548.78-.547-.78zm-1.617 1.91l.547.78-.799.943-.728-.033L0 8.382l.754-.53 2.744 3.907.57-.672z'/></svg>");
}
x-pw-tool-item.accept > x-div {
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>");
}
x-pw-tool-item.cancel > x-div {
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>");
}
x-pw-overlay {
position: absolute;
top: 0;
max-width: min-content;
z-index: 2147483647;
background: transparent;
pointer-events: auto;
}
x-pw-overlay x-pw-tools-list {
background-color: #ffffffdd;
box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px;
border-radius: 3px;
border-bottom: none;
}
x-pw-overlay x-pw-tool-item {
margin: 2px;
}
x-div {
display: block;
}
* {
box-sizing: border-box;
}
*[hidden] {
display: none !important;
}`;

View File

@ -19,6 +19,7 @@ import type { ParsedSelector } from '../../utils/isomorphic/selectorParser';
import type { InjectedScript } from './injectedScript';
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
import type { Language } from '../../utils/isomorphic/locatorGenerators';
import { highlightCSS } from './highlight.css';
type HighlightEntry = {
targetElement: Element,
@ -34,7 +35,8 @@ export type HighlightOptions = {
tooltipText?: string;
color?: string;
anchorGetter?: (element: Element) => DOMRect;
decorateTooltip?: (tooltip: Element) => void;
toolbar?: Element[];
interactive?: boolean;
};
export class Highlight {
@ -67,44 +69,7 @@ export class Highlight {
this._glassPaneShadow = this._glassPaneElement.attachShadow({ mode: this._isUnderTest ? 'open' : 'closed' });
this._glassPaneShadow.appendChild(this._actionPointElement);
const styleElement = document.createElement('style');
styleElement.textContent = `
x-pw-tooltip {
align-items: center;
backdrop-filter: blur(5px);
background-color: rgba(0, 0, 0, 0.7);
border-radius: 2px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 3.6px 3.7px,
rgba(0, 0, 0, 0.15) 0px 12.1px 12.3px,
rgba(0, 0, 0, 0.1) 0px -2px 4px,
rgba(0, 0, 0, 0.15) 0px -12.1px 24px,
rgba(0, 0, 0, 0.25) 0px 54px 55px;
color: rgb(204, 204, 204);
display: none;
font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono',
'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace;
font-size: 12.8px;
font-weight: normal;
left: 0;
line-height: 1.5;
max-width: 600px;
padding: 3.2px 5.12px 3.2px;
position: absolute;
top: 0;
}
x-pw-action-point {
position: absolute;
width: 20px;
height: 20px;
background: red;
border-radius: 10px;
pointer-events: none;
margin: -10px 0 0 -10px;
z-index: 2;
}
*[hidden] {
display: none !important;
}
`;
styleElement.textContent = highlightCSS;
this._glassPaneShadow.appendChild(styleElement);
}
@ -180,13 +145,26 @@ export class Highlight {
let tooltipElement;
if (options.tooltipText) {
tooltipElement = this._injectedScript.document.createElement('x-pw-tooltip');
this._glassPaneShadow.appendChild(tooltipElement);
const suffix = elements.length > 1 ? ` [${i + 1} of ${elements.length}]` : '';
tooltipElement.textContent = options.tooltipText + suffix;
tooltipElement.style.top = '0';
tooltipElement.style.left = '0';
tooltipElement.style.display = 'flex';
options.decorateTooltip?.(tooltipElement);
tooltipElement.style.flexDirection = 'column';
tooltipElement.style.alignItems = 'start';
if (options.interactive)
tooltipElement.style.pointerEvents = 'auto';
if (options.toolbar) {
const toolbar = this._injectedScript.document.createElement('x-pw-tools-list');
tooltipElement.appendChild(toolbar);
for (const toolbarElement of options.toolbar)
toolbar.appendChild(toolbarElement);
}
const bodyElement = this._injectedScript.document.createElement('x-pw-tooltip-body');
tooltipElement.appendChild(bodyElement);
this._glassPaneShadow.appendChild(tooltipElement);
const suffix = elements.length > 1 ? ` [${i + 1} of ${elements.length}]` : '';
bodyElement.textContent = options.tooltipText + suffix;
}
this._highlightEntries.push({ targetElement: elements[i], tooltipElement, highlightElement, tooltipText: options.tooltipText });
}
@ -260,13 +238,10 @@ export class Highlight {
}
private _createHighlightElement(): HTMLElement {
const highlightElement = this._injectedScript.document.createElement('x-pw-highlight');
highlightElement.style.position = 'absolute';
highlightElement.style.top = '0';
highlightElement.style.left = '0';
highlightElement.style.width = '0';
highlightElement.style.height = '0';
highlightElement.style.boxSizing = 'border-box';
return highlightElement;
return this._injectedScript.document.createElement('x-pw-highlight');
}
appendChild(element: HTMLElement) {
this._glassPaneShadow.appendChild(element);
}
}

View File

@ -36,8 +36,7 @@ interface RecorderDelegate {
interface RecorderTool {
cursor(): string;
enable?(): void;
disable?(): void;
cleanup?(): void;
onClick?(event: MouseEvent): void;
onDragStart?(event: DragEvent): void;
onInput?(event: Event): void;
@ -70,7 +69,7 @@ class InspectTool implements RecorderTool {
return 'pointer';
}
disable() {
cleanup() {
this._hoveredModel = null;
this._hoveredElement = null;
}
@ -151,7 +150,7 @@ class RecordActionTool implements RecorderTool {
return 'pointer';
}
disable() {
cleanup() {
this._hoveredModel = null;
this._hoveredElement = null;
this._activeModel = null;
@ -447,25 +446,25 @@ class TextAssertionTool implements RecorderTool {
private _selectionText: { selectedText: string, fullText: string } | null = null;
private _inputHighlight: HighlightModel | null = null;
private _acceptButton: HTMLElement;
private _cancelButton: HTMLElement;
constructor(private _recorder: Recorder) {
this._acceptButton = this._recorder.document.createElement('button');
this._acceptButton.textContent = 'Accept';
this._acceptButton.style.cursor = 'pointer';
this._acceptButton.style.pointerEvents = 'auto';
this._acceptButton = this._recorder.document.createElement('x-pw-tool-item');
this._acceptButton.classList.add('accept');
this._acceptButton.appendChild(this._recorder.document.createElement('x-div'));
this._acceptButton.addEventListener('click', () => this._commitAction());
this._cancelButton = this._recorder.document.createElement('x-pw-tool-item');
this._cancelButton.classList.add('cancel');
this._cancelButton.appendChild(this._recorder.document.createElement('x-div'));
this._cancelButton.addEventListener('click', () => this._cancelAction());
}
cursor() {
return 'text';
}
enable() {
this._recorder.injectedScript.document.designMode = 'on';
}
disable() {
this._recorder.injectedScript.document.designMode = 'off';
cleanup() {
this._hoverHighlight = null;
this._selectionHighlight = null;
this._selectionText = null;
@ -473,11 +472,6 @@ class TextAssertionTool implements RecorderTool {
}
onClick(event: MouseEvent) {
// Hack: work around highlight's glass pane having a closed shadow root.
const box = this._acceptButton.getBoundingClientRect();
if (box.left <= event.clientX && event.clientX <= box.right && box.top <= event.clientY && event.clientY <= box.bottom)
return;
consumeEvent(event);
const selection = this._recorder.document.getSelection();
if (event.detail === 1 && selection && !selection.toString() && !this._inputHighlight) {
@ -612,6 +606,10 @@ class TextAssertionTool implements RecorderTool {
}
}
private _cancelAction() {
this._resetSelectionAndHighlight();
}
private _resetSelectionAndHighlight() {
this._selectionHighlight = null;
this._selectionText = null;
@ -643,7 +641,12 @@ class TextAssertionTool implements RecorderTool {
}
private _showHighlight(userGesture: boolean) {
const options: HighlightOptions = { color: '#6fdcbd38', tooltipText: this._generateActionPreview(), decorateTooltip: tooltip => tooltip.appendChild(this._acceptButton) };
const options: HighlightOptions = {
color: '#6fdcbd38',
tooltipText: this._generateActionPreview(),
toolbar: [this._acceptButton, this._cancelButton],
interactive: true,
};
if (this._inputHighlight) {
this._recorder.updateHighlight(this._inputHighlight, userGesture, options);
} else {
@ -665,119 +668,21 @@ class Overlay {
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');
styleElement.textContent = `
:host {
position: fixed;
max-width: min-content;
z-index: 2147483647;
background: transparent;
}
x-pw-tools-list {
box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px;
backdrop-filter: blur(5px);
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;
border-radius: 3px;
}
x-pw-separator {
height: 1px;
margin: 6px 9px;
background: rgb(148 148 148 / 90%);
}
x-pw-tool-item {
cursor: pointer;
height: 28px;
width: 28px;
margin: 2px 4px;
border-radius: 3px;
}
x-pw-tool-item:not(.disabled):hover {
background-color: hsl(0, 0%, 86%);
}
x-pw-tool-item > 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;
background-color: #3a3a3a;
}
x-pw-tool-item.disabled > div {
background-color: rgba(97, 97, 97, 0.5);
cursor: default;
}
x-pw-tool-item.active > div {
background-color: #006ab1;
}
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,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' /></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' /></svg>");
background-color: #555555;
}
x-pw-tool-item.record > div {
/* codicon: circle-large-filled */
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z'/></svg>");
}
x-pw-tool-item.pick-locator > div {
/* codicon: inspect */
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z'/></svg>");
}
x-pw-tool-item.assert > div {
/* codicon: check-all */
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M15.62 3.596L7.815 12.81l-.728-.033L4 8.382l.754-.53 2.744 3.907L14.917 3l.703.596z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M7.234 8.774l4.386-5.178L10.917 3l-4.23 4.994.547.78zm-1.55.403l.548.78-.547-.78zm-1.617 1.91l.547.78-.799.943-.728-.033L0 8.382l.754-.53 2.744 3.907.57-.672z'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M15.62 3.596L7.815 12.81l-.728-.033L4 8.382l.754-.53 2.744 3.907L14.917 3l.703.596z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M7.234 8.774l4.386-5.178L10.917 3l-4.23 4.994.547.78zm-1.55.403l.548.78-.547-.78zm-1.617 1.91l.547.78-.799.943-.728-.033L0 8.382l.754-.53 2.744 3.907.57-.672z'/></svg>");
}
`;
shadow.appendChild(styleElement);
const toolsListElement = document.createElement('x-pw-tools-list');
shadow.appendChild(toolsListElement);
this._overlayElement.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'));
dragHandle.appendChild(document.createElement('x-div'));
toolsListElement.appendChild(dragHandle);
this._recordToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
this._recordToggle.title = 'Record';
this._recordToggle.classList.add('record');
this._recordToggle.appendChild(this._recorder.injectedScript.document.createElement('div'));
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');
});
@ -786,7 +691,7 @@ class Overlay {
this._pickLocatorToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
this._pickLocatorToggle.title = 'Pick locator';
this._pickLocatorToggle.classList.add('pick-locator');
this._pickLocatorToggle.appendChild(this._recorder.injectedScript.document.createElement('div'));
this._pickLocatorToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div'));
this._pickLocatorToggle.addEventListener('click', () => {
const newMode: Record<Mode, Mode> = {
'inspecting': 'none',
@ -802,7 +707,7 @@ class Overlay {
this._assertToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
this._assertToggle.title = 'Assert text and values';
this._assertToggle.classList.add('assert');
this._assertToggle.appendChild(this._recorder.injectedScript.document.createElement('div'));
this._assertToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div'));
this._assertToggle.addEventListener('click', () => {
if (!this._assertToggle.classList.contains('disabled'))
this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingText' ? 'recording' : 'assertingText');
@ -813,7 +718,7 @@ class Overlay {
}
install() {
this._recorder.injectedScript.document.documentElement.appendChild(this._overlayElement);
this._recorder.highlight.appendChild(this._overlayElement);
this._measure = this._overlayElement.getBoundingClientRect();
}
@ -877,7 +782,7 @@ export class Recorder {
private _currentTool: RecorderTool;
private _tools: Record<Mode, RecorderTool>;
private _actionSelectorModel: HighlightModel | null = null;
private _highlight: Highlight;
readonly highlight: Highlight;
private _overlay: Overlay | undefined;
private _styleElement: HTMLStyleElement;
state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } };
@ -887,7 +792,7 @@ export class Recorder {
constructor(injectedScript: InjectedScript) {
this.document = injectedScript.document;
this.injectedScript = injectedScript;
this._highlight = new Highlight(injectedScript);
this.highlight = new Highlight(injectedScript);
this._tools = {
'none': new NoneTool(),
'inspecting': new InspectTool(this),
@ -913,7 +818,7 @@ export class Recorder {
installListeners() {
// Ensure we are attached to the current document, and we are on top (last element);
if (this._highlight.isInstalled())
if (this.highlight.isInstalled())
return;
removeEventListeners(this._listeners);
this._listeners = [
@ -932,7 +837,7 @@ export class Recorder {
addEventListener(this.document, 'focus', event => this._onFocus(event), true),
addEventListener(this.document, 'scroll', event => this._onScroll(event), true),
];
this._highlight.install();
this.highlight.install();
this._overlay?.install();
this.injectedScript.document.head.appendChild(this._styleElement);
}
@ -941,10 +846,9 @@ export class Recorder {
const newTool = this._tools[this.state.mode];
if (newTool === this._currentTool)
return;
this._currentTool.disable?.();
this._currentTool.cleanup?.();
this.clearHighlight();
this._currentTool = newTool;
this._currentTool.enable?.();
this.injectedScript.document.body?.setAttribute('data-pw-cursor', newTool.cursor());
}
@ -957,13 +861,13 @@ export class Recorder {
// All good.
} else {
if (state.actionPoint)
this._highlight.showActionPoint(state.actionPoint.x, state.actionPoint.y);
this.highlight.showActionPoint(state.actionPoint.x, state.actionPoint.y);
else
this._highlight.hideActionPoint();
this.highlight.hideActionPoint();
}
this.state = state;
this._highlight.setLanguage(state.language);
this.highlight.setLanguage(state.language);
this._switchCurrentTool();
this._overlay?.setUIState(state);
@ -977,7 +881,7 @@ export class Recorder {
}
clearHighlight() {
this._currentTool.disable?.();
this._currentTool.cleanup?.();
this.updateHighlight(null, false);
}
@ -1062,7 +966,7 @@ export class Recorder {
private _onScroll(event: Event) {
if (!event.isTrusted)
return;
this._highlight.hideActionPoint();
this.highlight.hideActionPoint();
this._currentTool.onScroll?.(event);
}
@ -1091,13 +995,14 @@ export class Recorder {
updateHighlight(model: HighlightModel | null, userGesture: boolean, options: HighlightOptions = {}) {
if (options.tooltipText === undefined && model?.selector)
options.tooltipText = asLocator(this.state.language, model.selector);
this._highlight.updateHighlight(model?.elements || [], options);
this.highlight.updateHighlight(model?.elements || [], options);
if (userGesture)
this.delegate.highlightUpdated?.();
}
private _ignoreOverlayEvent(event: Event) {
return this._overlay?.contains(event.composedPath()[0] as Element);
const target = event.composedPath()[0] as Element;
return target.nodeName.toLowerCase() === 'x-pw-glass';
}
deepEventTarget(event: Event): HTMLElement {

View File

@ -176,9 +176,9 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
this._recorder.setMode('recording');
}
}
await this._page.mainFrame().evaluateExpression(((selector: string) => {
window.playwrightSetSelector(selector);
}).toString(), { isFunction: true }, selector).catch(() => {});
await this._page.mainFrame().evaluateExpression(((data: { selector: string, userGesture?: boolean }) => {
window.playwrightSetSelector(data.selector, data.userGesture);
}).toString(), { isFunction: true }, { selector, userGesture }).catch(() => {});
}
async updateCallLogs(callLogs: CallLog[]): Promise<void> {

View File

@ -66,8 +66,10 @@ export const Recorder: React.FC<RecorderProps> = ({
};
const [locator, setLocator] = React.useState('');
window.playwrightSetSelector = (selector: string) => {
window.playwrightSetSelector = (selector: string, focus?: boolean) => {
const language = source.language;
if (focus)
setSelectedTab('locator');
setLocator(asLocator(language, selector));
};
@ -126,7 +128,6 @@ export const Recorder: React.FC<RecorderProps> = ({
'assertingText': 'recording-inspecting',
}[mode];
window.dispatch({ event: 'setMode', params: { mode: newMode } }).catch(() => { });
setSelectedTab('locator');
}}>Pick locator</ToolbarButton>
<ToolbarButton icon='check-all' title='Assert text and values' toggled={mode === 'assertingText'} disabled={mode === 'none' || mode === 'inspecting'} onClick={() => {
window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'recording' : 'assertingText' } });
@ -155,7 +156,7 @@ export const Recorder: React.FC<RecorderProps> = ({
}}></ToolbarButton>
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
</Toolbar>
<SplitView sidebarSize={200} sidebarHidden={mode === 'recording'}>
<SplitView sidebarSize={200}>
<CodeMirrorWrapper text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine} readOnly={true} lineNumbers={true}/>
<TabbedPane
rightToolbar={selectedTab === 'locator' ? [<ToolbarButton icon='files' title='Copy' onClick={() => copy(locator)} />] : []}
@ -163,7 +164,7 @@ export const Recorder: React.FC<RecorderProps> = ({
{
id: 'locator',
title: 'Locator',
render: () => <CodeMirrorWrapper text={locator} language={source.language} readOnly={false} focusOnChange={true} onChange={onEditorChange} />
render: () => <CodeMirrorWrapper text={locator} language={source.language} readOnly={false} focusOnChange={true} onChange={onEditorChange} wrapLines={true}/>
},
{
id: 'log',

View File

@ -16,6 +16,10 @@
@import '../third_party/vscode/colors.css';
.cm-wrapper {
line-height: 18px;
}
.cm-wrapper, .cm-wrapper > div {
width: 100%;
height: 100%;

View File

@ -93,7 +93,6 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
// Either configuration is different or we don't have a codemirror yet.
codemirrorRef.current?.cm?.getWrapperElement().remove();
const cm = CodeMirror(element, {
value: '',
mode,

View File

@ -43,10 +43,6 @@
color: var(--vscode-notificationLink-foreground);
}
.toolbar-button.toggled .codicon {
font-weight: bold;
}
.toolbar-separator {
flex: none;
background-color: var(--vscode-menu-separatorBackground);