cherry-pick(#28198): feat(recorder): UX updates for assertion tools (#28202)

- No locator editor.
- No value editor for `toHaveValue`.
- Visual feedback for `toBeVisible`/`toHaveValue`.
- UI tweaks.
This commit is contained in:
Dmitry Gozman 2023-11-16 13:30:01 -08:00 committed by GitHub
parent 59e8f4815d
commit b8949166dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 103 additions and 160 deletions

View file

@ -8,7 +8,7 @@ toc_max_heading_level: 2
### Test Generator Update ### Test Generator Update
![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) ![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190)
New tools to generate assertions: New tools to generate assertions:
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. - "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].

View file

@ -8,7 +8,7 @@ toc_max_heading_level: 2
### Test Generator Update ### Test Generator Update
![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) ![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190)
New tools to generate assertions: New tools to generate assertions:
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. - "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].

View file

@ -10,7 +10,7 @@ import LiteYouTube from '@site/src/components/LiteYouTube';
### Test Generator Update ### Test Generator Update
![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) ![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190)
New tools to generate assertions: New tools to generate assertions:
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. - "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].

View file

@ -8,7 +8,7 @@ toc_max_heading_level: 2
### Test Generator Update ### Test Generator Update
![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) ![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190)
New tools to generate assertions: New tools to generate assertions:
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. - "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].

View file

@ -44,9 +44,10 @@ x-pw-dialog {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: absolute; position: absolute;
width: 500px; width: 400px;
height: 200px; height: 150px;
z-index: 10; z-index: 10;
font-size: 13px;
} }
x-pw-dialog-body { x-pw-dialog-body {
@ -217,6 +218,15 @@ x-pw-tool-item.cancel > x-div {
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-tool-item.succeeded > x-div {
/* codicon: pass */
-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='M6.27 10.87h.71l4.56-4.56-.71-.71-4.2 4.21-1.92-1.92L4 8.6l2.27 2.27z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M8.6 1c1.6.1 3.1.9 4.2 2 1.3 1.4 2 3.1 2 5.1 0 1.6-.6 3.1-1.6 4.4-1 1.2-2.4 2.1-4 2.4-1.6.3-3.2.1-4.6-.7-1.4-.8-2.5-2-3.1-3.5C.9 9.2.8 7.5 1.3 6c.5-1.6 1.4-2.9 2.8-3.8C5.4 1.3 7 .9 8.6 1zm.5 12.9c1.3-.3 2.5-1 3.4-2.1.8-1.1 1.3-2.4 1.2-3.8 0-1.6-.6-3.2-1.7-4.3-1-1-2.2-1.6-3.6-1.7-1.3-.1-2.7.2-3.8 1-1.1.8-1.9 1.9-2.3 3.3-.4 1.3-.4 2.7.2 4 .6 1.3 1.5 2.3 2.7 3 1.2.7 2.6.9 3.9.6z'/></svg>") !important;
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='M6.27 10.87h.71l4.56-4.56-.71-.71-4.2 4.21-1.92-1.92L4 8.6l2.27 2.27z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M8.6 1c1.6.1 3.1.9 4.2 2 1.3 1.4 2 3.1 2 5.1 0 1.6-.6 3.1-1.6 4.4-1 1.2-2.4 2.1-4 2.4-1.6.3-3.2.1-4.6-.7-1.4-.8-2.5-2-3.1-3.5C.9 9.2.8 7.5 1.3 6c.5-1.6 1.4-2.9 2.8-3.8C5.4 1.3 7 .9 8.6 1zm.5 12.9c1.3-.3 2.5-1 3.4-2.1.8-1.1 1.3-2.4 1.2-3.8 0-1.6-.6-3.2-1.7-4.3-1-1-2.2-1.6-3.6-1.7-1.3-.1-2.7.2-3.8 1-1.1.8-1.9 1.9-2.3 3.3-.4 1.3-.4 2.7.2 4 .6 1.3 1.5 2.3 2.7 3 1.2.7 2.6.9 3.9.6z'/></svg>") !important;
background-color: #388a34 !important;
-webkit-mask-size: 18px !important;
mask-size: 18px !important;
}
x-pw-overlay { x-pw-overlay {
position: absolute; position: absolute;
top: 0; top: 0;
@ -238,13 +248,15 @@ x-pw-overlay x-pw-tool-item {
} }
textarea.text-editor { textarea.text-editor {
font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; font-family: system-ui,Ubuntu,Droid Sans,sans-serif;
flex: auto; flex: auto;
border: none; border: none;
margin: 6px; margin: 6px 10px;
color: #333; color: #333;
outline: 1px solid transparent !important; outline: 1px solid transparent!important;
resize: none; resize: none;
padding: 0;
font-size: 13px;
} }
textarea.text-editor.does-not-match { textarea.text-editor.does-not-match {

View file

@ -24,28 +24,8 @@ import { isInsideScope } from './domUtils';
import { elementText } from './selectorUtils'; import { elementText } from './selectorUtils';
import type { ElementText } from './selectorUtils'; import type { ElementText } from './selectorUtils';
import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import { asLocator } from '../../utils/isomorphic/locatorGenerators';
import type { Language } from '../../utils/isomorphic/locatorGenerators';
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
import { parseSelector } from '@isomorphic/selectorParser';
import { normalizeWhiteSpace } from '@isomorphic/stringUtils'; import { normalizeWhiteSpace } from '@isomorphic/stringUtils';
// @ts-ignore @no-check-deps
import CodeMirrorImpl from 'codemirror-shadow-1';
import type CodeMirrorType from 'codemirror';
// @no-check-deps
import codemirrorCSS from 'codemirror-shadow-1/lib/codemirror.css?inline';
// @no-check-deps
import 'codemirror-shadow-1/mode/css/css';
// @no-check-deps
import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed';
// @no-check-deps
import 'codemirror-shadow-1/mode/javascript/javascript';
// @no-check-deps
import 'codemirror-shadow-1/mode/python/python';
// @no-check-deps
import 'codemirror-shadow-1/mode/clike/clike';
const CodeMirror = CodeMirrorImpl as typeof CodeMirrorType;
interface RecorderDelegate { interface RecorderDelegate {
performAction?(action: actions.Action): Promise<void>; performAction?(action: actions.Action): Promise<void>;
recordAction?(action: actions.Action): Promise<void>; recordAction?(action: actions.Action): Promise<void>;
@ -68,6 +48,7 @@ interface RecorderTool {
onMouseDown?(event: MouseEvent): void; onMouseDown?(event: MouseEvent): void;
onMouseUp?(event: MouseEvent): void; onMouseUp?(event: MouseEvent): void;
onMouseMove?(event: MouseEvent): void; onMouseMove?(event: MouseEvent): void;
onMouseEnter?(event: MouseEvent): void;
onMouseLeave?(event: MouseEvent): void; onMouseLeave?(event: MouseEvent): void;
onFocus?(event: Event): void; onFocus?(event: Event): void;
onScroll?(event: Event): void; onScroll?(event: Event): void;
@ -109,6 +90,7 @@ class InspectTool implements RecorderTool {
signals: [], signals: [],
}); });
this._recorder.delegate.setMode?.('recording'); this._recorder.delegate.setMode?.('recording');
this._recorder.overlay?.flashToolSucceeded('assertingVisibility');
} }
} else { } else {
this._recorder.delegate.setSelector?.(this._hoveredModel ? this._hoveredModel.selector : ''); this._recorder.delegate.setSelector?.(this._hoveredModel ? this._hoveredModel.selector : '');
@ -146,6 +128,10 @@ class InspectTool implements RecorderTool {
this._recorder.updateHighlight(model, true, { color: this._assertVisibility ? '#8acae480' : undefined }); this._recorder.updateHighlight(model, true, { color: this._assertVisibility ? '#8acae480' : undefined });
} }
onMouseEnter(event: MouseEvent) {
consumeEvent(event);
}
onMouseLeave(event: MouseEvent) { onMouseLeave(event: MouseEvent) {
consumeEvent(event); consumeEvent(event);
const window = this._recorder.injectedScript.window; const window = this._recorder.injectedScript.window;
@ -518,14 +504,23 @@ class TextAssertionTool implements RecorderTool {
} }
onClick(event: MouseEvent) { onClick(event: MouseEvent) {
if (!this._dialogElement)
this._showDialog();
consumeEvent(event); consumeEvent(event);
if (this._kind === 'value') {
const action = this._generateAction();
if (action) {
this._recorder.delegate.recordAction?.(action);
this._recorder.delegate.setMode?.('recording');
this._recorder.overlay?.flashToolSucceeded('assertingValue');
}
} else {
if (!this._dialogElement)
this._showDialog();
}
} }
onMouseDown(event: MouseEvent) { onMouseDown(event: MouseEvent) {
const target = this._recorder.deepEventTarget(event); const target = this._recorder.deepEventTarget(event);
if (target.nodeName === 'SELECT') if (this._elementHasValue(target))
event.preventDefault(); event.preventDefault();
} }
@ -618,7 +613,7 @@ class TextAssertionTool implements RecorderTool {
if (!this._hoverHighlight?.elements[0]) if (!this._hoverHighlight?.elements[0])
return; return;
this._action = this._generateAction(); this._action = this._generateAction();
if (!this._action) if (!this._action || this._action.name !== 'assertText')
return; return;
this._dialogElement = this._recorder.document.createElement('x-pw-dialog'); this._dialogElement = this._recorder.document.createElement('x-pw-dialog');
@ -636,122 +631,41 @@ class TextAssertionTool implements RecorderTool {
this._recorder.document.addEventListener('keydown', this._keyboardListener, true); this._recorder.document.addEventListener('keydown', this._keyboardListener, true);
const toolbarElement = this._recorder.document.createElement('x-pw-tools-list'); const toolbarElement = this._recorder.document.createElement('x-pw-tools-list');
toolbarElement.appendChild(this._createLabel(this._action)); const labelElement = this._recorder.document.createElement('label');
labelElement.textContent = 'Assert that element contains text';
toolbarElement.appendChild(labelElement);
toolbarElement.appendChild(this._recorder.document.createElement('x-spacer')); toolbarElement.appendChild(this._recorder.document.createElement('x-spacer'));
toolbarElement.appendChild(this._acceptButton); toolbarElement.appendChild(this._acceptButton);
toolbarElement.appendChild(this._cancelButton); toolbarElement.appendChild(this._cancelButton);
this._dialogElement.appendChild(toolbarElement); this._dialogElement.appendChild(toolbarElement);
const bodyElement = this._recorder.document.createElement('x-pw-dialog-body'); const bodyElement = this._recorder.document.createElement('x-pw-dialog-body');
const cmStyle = this._recorder.document.createElement('style');
const cmElement = this._recorder.document.createElement('x-locator-editor');
cmStyle.textContent = codemirrorCSS;
bodyElement.appendChild(cmStyle);
bodyElement.appendChild(cmElement);
const cm = CodeMirror(cmElement, {
value: asLocator(this._recorder.state.language, this._action.selector),
mode: cmModeForLanguage(this._recorder.state.language),
readOnly: false,
lineNumbers: false,
lineWrapping: true,
});
cm.on('keydown', (_, event) => {
if (event.key === 'Tab')
(event as any).codemirrorIgnore = true;
});
cm.on('change', () => {
if (this._action) {
const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName);
let elements: Element[] = [];
try {
elements = this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document);
} catch {
}
cmElement.classList.toggle('does-not-match', !elements.length);
this._hoverHighlight = elements.length ? {
selector,
elements,
} : null;
this._action.selector = selector;
this._recorder.updateHighlight(this._hoverHighlight, true);
}
});
let elementToFocus: HTMLElement | null = null;
const action = this._action; const action = this._action;
if (action.name === 'assertText') { const textElement = this._recorder.document.createElement('textarea');
const textElement = this._recorder.document.createElement('textarea'); textElement.setAttribute('spellcheck', 'false');
textElement.setAttribute('spellcheck', 'false'); textElement.value = this._renderValue(this._action);
textElement.value = this._renderValue(this._action); textElement.classList.add('text-editor');
textElement.classList.add('text-editor');
const updateAndValidate = () => { const updateAndValidate = () => {
const newValue = normalizeWhiteSpace(textElement.value); const newValue = normalizeWhiteSpace(textElement.value);
const target = this._hoverHighlight?.elements[0]; const target = this._hoverHighlight?.elements[0];
if (!target) if (!target)
return; return;
action.text = newValue; action.text = newValue;
const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full); const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full);
const matches = action.substring ? newValue && targetText.includes(newValue) : targetText === newValue; const matches = newValue && targetText.includes(newValue);
textElement.classList.toggle('does-not-match', !matches); textElement.classList.toggle('does-not-match', !matches);
}; };
textElement.addEventListener('input', updateAndValidate); textElement.addEventListener('input', updateAndValidate);
bodyElement.appendChild(textElement); bodyElement.appendChild(textElement);
// Add a toolbar substring checkbox.
const substringElement = this._recorder.document.createElement('label');
substringElement.style.cursor = 'pointer';
const checkboxElement = this._recorder.document.createElement('input');
substringElement.appendChild(checkboxElement);
substringElement.appendChild(this._recorder.document.createTextNode('Substring'));
checkboxElement.type = 'checkbox';
checkboxElement.style.cursor = 'pointer';
checkboxElement.checked = action.substring;
checkboxElement.addEventListener('change', () => {
action.substring = checkboxElement.checked;
updateAndValidate();
});
toolbarElement.insertBefore(substringElement, this._acceptButton);
elementToFocus = textElement;
} else if (action.name === 'assertValue') {
const textElement = this._recorder.document.createElement('textarea');
textElement.setAttribute('spellcheck', 'false');
textElement.value = this._renderValue(this._action);
textElement.classList.add('text-editor');
textElement.addEventListener('input', () => {
action.value = textElement.value;
});
bodyElement.appendChild(textElement);
elementToFocus = textElement;
} else if (action.name === 'assertChecked') {
const labelElement = this._recorder.document.createElement('label');
labelElement.textContent = 'Value:';
const checkboxElement = this._recorder.document.createElement('input');
labelElement.appendChild(checkboxElement);
checkboxElement.type = 'checkbox';
checkboxElement.checked = action.checked;
checkboxElement.addEventListener('change', () => {
action.checked = checkboxElement.checked;
});
bodyElement.appendChild(labelElement);
elementToFocus = labelElement;
}
this._dialogElement.appendChild(bodyElement); this._dialogElement.appendChild(bodyElement);
this._recorder.highlight.appendChild(this._dialogElement); this._recorder.highlight.appendChild(this._dialogElement);
const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement); const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement);
this._dialogElement.style.top = position.anchorTop + 'px'; this._dialogElement.style.top = position.anchorTop + 'px';
this._dialogElement.style.left = position.anchorLeft + 'px'; this._dialogElement.style.left = position.anchorLeft + 'px';
elementToFocus?.focus(); textElement.focus();
cm.refresh();
}
private _createLabel(action: actions.AssertAction) {
const labelElement = this._recorder.document.createElement('label');
labelElement.textContent = action.name === 'assertText' ? 'Assert text' : action.name === 'assertValue' ? 'Assert value' : 'Assert checked';
return labelElement;
} }
private _closeDialog() { private _closeDialog() {
@ -829,7 +743,7 @@ class Overlay {
toolsListElement.appendChild(this._assertVisibilityToggle); toolsListElement.appendChild(this._assertVisibilityToggle);
this._assertTextToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); this._assertTextToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
this._assertTextToggle.title = 'Assert text and values'; this._assertTextToggle.title = 'Assert text';
this._assertTextToggle.classList.add('text'); this._assertTextToggle.classList.add('text');
this._assertTextToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._assertTextToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div'));
this._assertTextToggle.addEventListener('click', () => { this._assertTextToggle.addEventListener('click', () => {
@ -853,7 +767,7 @@ class Overlay {
install() { install() {
this._recorder.highlight.appendChild(this._overlayElement); this._recorder.highlight.appendChild(this._overlayElement);
this._measure = this._overlayElement.getBoundingClientRect(); this._updateVisualPosition();
} }
contains(element: Element) { contains(element: Element) {
@ -874,13 +788,31 @@ class Overlay {
this._updateVisualPosition(); this._updateVisualPosition();
} }
if (state.mode === 'none') if (state.mode === 'none')
this._overlayElement.setAttribute('hidden', 'true'); this._hideOverlay();
else else
this._overlayElement.removeAttribute('hidden'); this._showOverlay();
}
flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue') {
const element = tool === 'assertingVisibility' ? this._assertVisibilityToggle : this._assertValuesToggle;
element.classList.add('succeeded');
setTimeout(() => element.classList.remove('succeeded'), 2000);
}
private _hideOverlay() {
this._overlayElement.setAttribute('hidden', 'true');
}
private _showOverlay() {
if (!this._overlayElement.hasAttribute('hidden'))
return;
this._overlayElement.removeAttribute('hidden');
this._updateVisualPosition();
} }
private _updateVisualPosition() { private _updateVisualPosition() {
this._overlayElement.style.left = (this._recorder.injectedScript.window.innerWidth / 2 + this._offsetX) + 'px'; this._measure = this._overlayElement.getBoundingClientRect();
this._overlayElement.style.left = ((this._recorder.injectedScript.window.innerWidth - this._measure.width) / 2 + this._offsetX) + 'px';
} }
onMouseMove(event: MouseEvent) { onMouseMove(event: MouseEvent) {
@ -890,8 +822,8 @@ class Overlay {
} }
if (this._dragState) { if (this._dragState) {
this._offsetX = this._dragState.offsetX + event.clientX - this._dragState.dragStart.x; 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); const halfGapSize = (this._recorder.injectedScript.window.innerWidth - this._measure.width) / 2 - 10;
this._offsetX = Math.max(10 - this._recorder.injectedScript.window.innerWidth / 2, this._offsetX); this._offsetX = Math.max(-halfGapSize, Math.min(halfGapSize, this._offsetX));
this._updateVisualPosition(); this._updateVisualPosition();
this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX }); this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX });
consumeEvent(event); consumeEvent(event);
@ -925,7 +857,7 @@ export class Recorder {
private _tools: Record<Mode, RecorderTool>; private _tools: Record<Mode, RecorderTool>;
private _actionSelectorModel: HighlightModel | null = null; private _actionSelectorModel: HighlightModel | null = null;
readonly highlight: Highlight; readonly highlight: Highlight;
private _overlay: Overlay | undefined; readonly overlay: Overlay | undefined;
private _styleElement: HTMLStyleElement; private _styleElement: HTMLStyleElement;
state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } }; state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } };
readonly document: Document; readonly document: Document;
@ -947,8 +879,8 @@ export class Recorder {
}; };
this._currentTool = this._tools.none; this._currentTool = this._tools.none;
if (injectedScript.window.top === injectedScript.window) { if (injectedScript.window.top === injectedScript.window) {
this._overlay = new Overlay(this); this.overlay = new Overlay(this);
this._overlay.setUIState(this.state); this.overlay.setUIState(this.state);
} }
this._styleElement = this.document.createElement('style'); this._styleElement = this.document.createElement('style');
this._styleElement.textContent = ` this._styleElement.textContent = `
@ -976,11 +908,12 @@ export class Recorder {
addEventListener(this.document, 'mouseup', event => this._onMouseUp(event as MouseEvent), true), addEventListener(this.document, 'mouseup', event => this._onMouseUp(event as MouseEvent), true),
addEventListener(this.document, 'mousemove', event => this._onMouseMove(event as MouseEvent), true), addEventListener(this.document, 'mousemove', event => this._onMouseMove(event as MouseEvent), true),
addEventListener(this.document, 'mouseleave', event => this._onMouseLeave(event as MouseEvent), true), addEventListener(this.document, 'mouseleave', event => this._onMouseLeave(event as MouseEvent), true),
addEventListener(this.document, 'mouseenter', event => this._onMouseEnter(event as MouseEvent), true),
addEventListener(this.document, 'focus', event => this._onFocus(event), true), addEventListener(this.document, 'focus', event => this._onFocus(event), true),
addEventListener(this.document, 'scroll', event => this._onScroll(event), true), addEventListener(this.document, 'scroll', event => this._onScroll(event), true),
]; ];
this.highlight.install(); this.highlight.install();
this._overlay?.install(); this.overlay?.install();
this.injectedScript.document.head.appendChild(this._styleElement); this.injectedScript.document.head.appendChild(this._styleElement);
} }
@ -1011,7 +944,7 @@ export class Recorder {
this.state = state; this.state = state;
this.highlight.setLanguage(state.language); this.highlight.setLanguage(state.language);
this._switchCurrentTool(); this._switchCurrentTool();
this._overlay?.setUIState(state); this.overlay?.setUIState(state);
// Race or scroll. // Race or scroll.
if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length) if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length)
@ -1030,7 +963,7 @@ export class Recorder {
private _onClick(event: MouseEvent) { private _onClick(event: MouseEvent) {
if (!event.isTrusted) if (!event.isTrusted)
return; return;
if (this._overlay?.onClick(event)) if (this.overlay?.onClick(event))
return; return;
if (this._ignoreOverlayEvent(event)) if (this._ignoreOverlayEvent(event))
return; return;
@ -1072,7 +1005,7 @@ export class Recorder {
private _onMouseUp(event: MouseEvent) { private _onMouseUp(event: MouseEvent) {
if (!event.isTrusted) if (!event.isTrusted)
return; return;
if (this._overlay?.onMouseUp(event)) if (this.overlay?.onMouseUp(event))
return; return;
if (this._ignoreOverlayEvent(event)) if (this._ignoreOverlayEvent(event))
return; return;
@ -1082,13 +1015,21 @@ export class Recorder {
private _onMouseMove(event: MouseEvent) { private _onMouseMove(event: MouseEvent) {
if (!event.isTrusted) if (!event.isTrusted)
return; return;
if (this._overlay?.onMouseMove(event)) if (this.overlay?.onMouseMove(event))
return; return;
if (this._ignoreOverlayEvent(event)) if (this._ignoreOverlayEvent(event))
return; return;
this._currentTool.onMouseMove?.(event); this._currentTool.onMouseMove?.(event);
} }
private _onMouseEnter(event: MouseEvent) {
if (!event.isTrusted)
return;
if (this._ignoreOverlayEvent(event))
return;
this._currentTool.onMouseEnter?.(event);
}
private _onMouseLeave(event: MouseEvent) { private _onMouseLeave(event: MouseEvent) {
if (!event.isTrusted) if (!event.isTrusted)
return; return;
@ -1149,7 +1090,7 @@ export class Recorder {
deepEventTarget(event: Event): HTMLElement { deepEventTarget(event: Event): HTMLElement {
for (const element of event.composedPath()) { for (const element of event.composedPath()) {
if (!this._overlay?.contains(element as Element)) if (!this.overlay?.contains(element as Element))
return element as HTMLElement; return element as HTMLElement;
} }
return event.composedPath()[0] as HTMLElement; return event.composedPath()[0] as HTMLElement;
@ -1301,14 +1242,4 @@ export class PollingRecorder implements RecorderDelegate {
} }
} }
function cmModeForLanguage(language: Language): string {
if (language === 'python')
return 'python';
if (language === 'java')
return 'text/x-java';
if (language === 'csharp')
return 'text/x-csharp';
return 'javascript';
}
export default PollingRecorder; export default PollingRecorder;