|
|
|
@ -29,6 +29,7 @@ interface RecorderDelegate {
|
|
|
|
|
recordAction?(action: actions.Action): Promise<void>;
|
|
|
|
|
setSelector?(selector: string): Promise<void>;
|
|
|
|
|
setMode?(mode: Mode): Promise<void>;
|
|
|
|
|
setOverlayPosition?(position: { x: number, y: number }): Promise<void>;
|
|
|
|
|
highlightUpdated?(): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -441,7 +442,7 @@ class RecordActionTool implements RecorderTool {
|
|
|
|
|
|
|
|
|
|
class TextAssertionTool implements RecorderTool {
|
|
|
|
|
private _selectionHighlight: HighlightModel | null = null;
|
|
|
|
|
private _inputIsFocused = false;
|
|
|
|
|
private _inputHighlight: HighlightModel | null = null;
|
|
|
|
|
|
|
|
|
|
constructor(private _recorder: Recorder) {
|
|
|
|
|
}
|
|
|
|
@ -457,36 +458,15 @@ class TextAssertionTool implements RecorderTool {
|
|
|
|
|
disable() {
|
|
|
|
|
this._recorder.injectedScript.document.designMode = 'off';
|
|
|
|
|
this._selectionHighlight = null;
|
|
|
|
|
this._inputIsFocused = false;
|
|
|
|
|
this._inputHighlight = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onClick(event: MouseEvent) {
|
|
|
|
|
consumeEvent(event);
|
|
|
|
|
|
|
|
|
|
const target = this._recorder.deepEventTarget(event);
|
|
|
|
|
if (event.detail === 1 && ['INPUT', 'TEXTAREA'].includes(target.nodeName)) {
|
|
|
|
|
const highlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName });
|
|
|
|
|
if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes((target as HTMLInputElement).type.toLowerCase())) {
|
|
|
|
|
this._recorder.delegate.recordAction?.({
|
|
|
|
|
name: 'assertChecked',
|
|
|
|
|
selector: highlight.selector,
|
|
|
|
|
signals: [],
|
|
|
|
|
// Interestingly, inputElement.checked is reversed inside this event handler.
|
|
|
|
|
checked: !(target as HTMLInputElement).checked,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this._recorder.delegate.recordAction?.({
|
|
|
|
|
name: 'assertValue',
|
|
|
|
|
selector: highlight.selector,
|
|
|
|
|
signals: [],
|
|
|
|
|
value: target.isContentEditable ? target.innerText : (target as HTMLInputElement).value,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selection = this._recorder.document.getSelection();
|
|
|
|
|
if (event.detail === 1 && selection && !selection.toString()) {
|
|
|
|
|
if (event.detail === 1 && selection && !selection.toString() && !this._inputHighlight) {
|
|
|
|
|
const target = this._recorder.deepEventTarget(event);
|
|
|
|
|
selection.selectAllChildren(target);
|
|
|
|
|
this._updateSelectionHighlight();
|
|
|
|
|
}
|
|
|
|
@ -496,14 +476,13 @@ class TextAssertionTool implements RecorderTool {
|
|
|
|
|
const target = this._recorder.deepEventTarget(event);
|
|
|
|
|
if (['INPUT', 'TEXTAREA'].includes(target.nodeName)) {
|
|
|
|
|
this._recorder.injectedScript.window.getSelection()?.empty();
|
|
|
|
|
this._selectionHighlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName });
|
|
|
|
|
this._inputIsFocused = true;
|
|
|
|
|
this._recorder.updateHighlight(this._selectionHighlight, true, '#6fdcbd38');
|
|
|
|
|
this._inputHighlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName });
|
|
|
|
|
this._recorder.updateHighlight(this._inputHighlight, true, '#6fdcbd38');
|
|
|
|
|
consumeEvent(event);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._inputIsFocused = false;
|
|
|
|
|
this._inputHighlight = null;
|
|
|
|
|
this._updateSelectionHighlight();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -522,13 +501,35 @@ class TextAssertionTool implements RecorderTool {
|
|
|
|
|
onKeyDown(event: KeyboardEvent) {
|
|
|
|
|
if (event.key === 'Escape') {
|
|
|
|
|
this._resetSelectionAndHighlight();
|
|
|
|
|
this._recorder.delegate.setMode?.('recording');
|
|
|
|
|
consumeEvent(event);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.key === 'Enter') {
|
|
|
|
|
const selection = this._recorder.document.getSelection();
|
|
|
|
|
if (selection && this._selectionHighlight) {
|
|
|
|
|
|
|
|
|
|
if (this._inputHighlight) {
|
|
|
|
|
const target = this._inputHighlight.elements[0] as HTMLInputElement;
|
|
|
|
|
if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes(target.type.toLowerCase())) {
|
|
|
|
|
this._recorder.delegate.recordAction?.({
|
|
|
|
|
name: 'assertChecked',
|
|
|
|
|
selector: this._inputHighlight.selector,
|
|
|
|
|
signals: [],
|
|
|
|
|
// Interestingly, inputElement.checked is reversed inside this event handler.
|
|
|
|
|
checked: !(target as HTMLInputElement).checked,
|
|
|
|
|
});
|
|
|
|
|
this._recorder.delegate.setMode?.('recording');
|
|
|
|
|
} else {
|
|
|
|
|
this._recorder.delegate.recordAction?.({
|
|
|
|
|
name: 'assertValue',
|
|
|
|
|
selector: this._inputHighlight.selector,
|
|
|
|
|
signals: [],
|
|
|
|
|
value: target.value,
|
|
|
|
|
});
|
|
|
|
|
this._recorder.delegate.setMode?.('recording');
|
|
|
|
|
}
|
|
|
|
|
} else if (selection && this._selectionHighlight) {
|
|
|
|
|
const selectedText = normalizeWhiteSpace(selection.toString());
|
|
|
|
|
const fullText = normalizeWhiteSpace(elementText(new Map(), this._selectionHighlight.elements[0]).full);
|
|
|
|
|
this._recorder.delegate.recordAction?.({
|
|
|
|
@ -538,6 +539,7 @@ class TextAssertionTool implements RecorderTool {
|
|
|
|
|
text: selectedText,
|
|
|
|
|
substring: fullText !== selectedText,
|
|
|
|
|
});
|
|
|
|
|
this._recorder.delegate.setMode?.('recording');
|
|
|
|
|
this._resetSelectionAndHighlight();
|
|
|
|
|
}
|
|
|
|
|
consumeEvent(event);
|
|
|
|
@ -561,12 +563,13 @@ class TextAssertionTool implements RecorderTool {
|
|
|
|
|
|
|
|
|
|
private _resetSelectionAndHighlight() {
|
|
|
|
|
this._selectionHighlight = null;
|
|
|
|
|
this._inputHighlight = null;
|
|
|
|
|
this._recorder.injectedScript.window.getSelection()?.empty();
|
|
|
|
|
this._recorder.updateHighlight(null, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _updateSelectionHighlight() {
|
|
|
|
|
if (this._inputIsFocused)
|
|
|
|
|
if (this._inputHighlight)
|
|
|
|
|
return;
|
|
|
|
|
const selection = this._recorder.document.getSelection();
|
|
|
|
|
let highlight: HighlightModel | null = null;
|
|
|
|
@ -586,7 +589,9 @@ class TextAssertionTool implements RecorderTool {
|
|
|
|
|
|
|
|
|
|
class Overlay {
|
|
|
|
|
private _overlayElement: HTMLElement;
|
|
|
|
|
private _tools: Record<Mode, HTMLElement>;
|
|
|
|
|
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 _measure: { width: number, height: number } = { width: 0, height: 0 };
|
|
|
|
@ -603,78 +608,77 @@ class Overlay {
|
|
|
|
|
max-width: min-content;
|
|
|
|
|
z-index: 2147483647;
|
|
|
|
|
background: transparent;
|
|
|
|
|
cursor: grab;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x-pw-tools-list {
|
|
|
|
|
box-shadow: rgba(0, 0, 0, 0.1) 0px 0.25em 0.5em;
|
|
|
|
|
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;
|
|
|
|
|
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: 1em;
|
|
|
|
|
padding: 0px;
|
|
|
|
|
border-radius: 2em;
|
|
|
|
|
margin: 10px;
|
|
|
|
|
padding: 3px 0;
|
|
|
|
|
border-radius: 17px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x-pw-drag-handle {
|
|
|
|
|
cursor: grab;
|
|
|
|
|
height: 2px;
|
|
|
|
|
margin: 5px 9px;
|
|
|
|
|
border-top: 1px solid rgb(86 86 86 / 90%);
|
|
|
|
|
border-bottom: 1px solid rgb(86 86 86 / 90%);
|
|
|
|
|
}
|
|
|
|
|
x-pw-drag-handle:active {
|
|
|
|
|
cursor: grabbing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x-pw-tool-item {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
height: 2.25em;
|
|
|
|
|
width: 2.25em;
|
|
|
|
|
margin: 0.05em 0.25em;
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
position: relative;
|
|
|
|
|
height: 28px;
|
|
|
|
|
width: 28px;
|
|
|
|
|
margin: 2px 4px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
}
|
|
|
|
|
x-pw-tool-item:first-child {
|
|
|
|
|
margin-top: 0.25em;
|
|
|
|
|
}
|
|
|
|
|
x-pw-tool-item:last-child {
|
|
|
|
|
margin-bottom: 0.25em;
|
|
|
|
|
}
|
|
|
|
|
x-pw-tool-item:hover {
|
|
|
|
|
background-color: hsl(0, 0%, 95%);
|
|
|
|
|
}
|
|
|
|
|
x-pw-tool-item.active {
|
|
|
|
|
background-color: hsl(0, 0%, 100%);
|
|
|
|
|
x-pw-tool-item:not(.disabled):hover {
|
|
|
|
|
background-color: hsl(0, 0%, 86%);
|
|
|
|
|
}
|
|
|
|
|
x-pw-tool-item > div {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
background-color: black;
|
|
|
|
|
-webkit-mask-repeat: no-repeat;
|
|
|
|
|
-webkit-mask-position: center;
|
|
|
|
|
-webkit-mask-size: 20px;
|
|
|
|
|
mask-repeat: no-repeat;
|
|
|
|
|
mask-position: center;
|
|
|
|
|
mask-size: 20px;
|
|
|
|
|
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: #ff4ca5;
|
|
|
|
|
background-color: #006ab1;
|
|
|
|
|
}
|
|
|
|
|
x-pw-tool-item.none > div {
|
|
|
|
|
/* codicon: close */
|
|
|
|
|
-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='M8 8.707l3.646 3.647.708-.707L8.707 8l3.647-3.646-.707-.708L8 7.293 4.354 3.646l-.707.708L7.293 8l-3.646 3.646.707.708L8 8.707z'/></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='M8 8.707l3.646 3.647.708-.707L8.707 8l3.647-3.646-.707-.708L8 7.293 4.354 3.646l-.707.708L7.293 8l-3.646 3.646.707.708L8 8.707z'/></svg>");
|
|
|
|
|
x-pw-tool-item.record.active > div {
|
|
|
|
|
background-color: #a1260d;
|
|
|
|
|
}
|
|
|
|
|
x-pw-tool-item.inspecting > div {
|
|
|
|
|
/* codicon: target */
|
|
|
|
|
-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 9C8.55228 9 9 8.55228 9 8C9 7.44772 8.55228 7 8 7C7.44772 7 7 7.44772 7 8C7 8.55228 7.44772 9 8 9Z'/><path d='M12 8C12 10.2091 10.2091 12 8 12C5.79086 12 4 10.2091 4 8C4 5.79086 5.79086 4 8 4C10.2091 4 12 5.79086 12 8ZM8 11C9.65685 11 11 9.65685 11 8C11 6.34315 9.65685 5 8 5C6.34315 5 5 6.34315 5 8C5 9.65685 6.34315 11 8 11Z'/><path d='M15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z'/></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 9C8.55228 9 9 8.55228 9 8C9 7.44772 8.55228 7 8 7C7.44772 7 7 7.44772 7 8C7 8.55228 7.44772 9 8 9Z'/><path d='M12 8C12 10.2091 10.2091 12 8 12C5.79086 12 4 10.2091 4 8C4 5.79086 5.79086 4 8 4C10.2091 4 12 5.79086 12 8ZM8 11C9.65685 11 11 9.65685 11 8C11 6.34315 9.65685 5 8 5C6.34315 5 5 6.34315 5 8C5 9.65685 6.34315 11 8 11Z'/><path d='M15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z'/></svg>");
|
|
|
|
|
|
|
|
|
|
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.recording > div {
|
|
|
|
|
/* codicon: record */
|
|
|
|
|
-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 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4z'/><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>");
|
|
|
|
|
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 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4z'/><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>");
|
|
|
|
|
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.assertingText > div {
|
|
|
|
|
/* codicon: text-size */
|
|
|
|
|
-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='M3.36 7L1 13h1.34l.51-1.47h2.26L5.64 13H7L4.65 7H3.36zm-.15 3.53l.78-2.14.78 2.14H3.21zM11.82 4h-1.6L7 13h1.56l.75-2.29h3.36l.77 2.29H15l-3.18-9zM9.67 9.5l1.18-3.59c.059-.185.1-.376.12-.57.027.192.064.382.11.57l1.25 3.59H9.67z'/></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='M3.36 7L1 13h1.34l.51-1.47h2.26L5.64 13H7L4.65 7H3.36zm-.15 3.53l.78-2.14.78 2.14H3.21zM11.82 4h-1.6L7 13h1.56l.75-2.29h3.36l.77 2.29H15l-3.18-9zM9.67 9.5l1.18-3.59c.059-.185.1-.376.12-.57.027.192.064.382.11.57l1.25 3.59H9.67z'/></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);
|
|
|
|
@ -682,34 +686,50 @@ class Overlay {
|
|
|
|
|
const toolsListElement = document.createElement('x-pw-tools-list');
|
|
|
|
|
shadow.appendChild(toolsListElement);
|
|
|
|
|
|
|
|
|
|
this._tools = {
|
|
|
|
|
none: this._createToolElement(toolsListElement, 'none', 'Disable'),
|
|
|
|
|
inspecting: this._createToolElement(toolsListElement, 'inspecting', 'Pick locator'),
|
|
|
|
|
recording: this._createToolElement(toolsListElement, 'recording', 'Record actions'),
|
|
|
|
|
assertingText: this._createToolElement(toolsListElement, 'assertingText', 'Assert text and values'),
|
|
|
|
|
};
|
|
|
|
|
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.addEventListener('click', () => {
|
|
|
|
|
this._recorder.delegate.setMode?.(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'none');
|
|
|
|
|
});
|
|
|
|
|
toolsListElement.appendChild(this._recordToggle);
|
|
|
|
|
|
|
|
|
|
this._overlayElement.addEventListener('mousedown', event => {
|
|
|
|
|
const dragHandle = document.createElement('x-pw-drag-handle');
|
|
|
|
|
dragHandle.addEventListener('mousedown', event => {
|
|
|
|
|
this._dragState = { position: this._position, dragStart: { x: event.clientX, y: event.clientY } };
|
|
|
|
|
});
|
|
|
|
|
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');
|
|
|
|
|
this._pickLocatorToggle.appendChild(this._recorder.injectedScript.document.createElement('div'));
|
|
|
|
|
this._pickLocatorToggle.addEventListener('click', () => {
|
|
|
|
|
const newMode: Record<Mode, Mode> = {
|
|
|
|
|
'inspecting': 'none',
|
|
|
|
|
'none': 'inspecting',
|
|
|
|
|
'recording': 'recording-inspecting',
|
|
|
|
|
'recording-inspecting': 'recording',
|
|
|
|
|
'assertingText': 'recording-inspecting',
|
|
|
|
|
};
|
|
|
|
|
this._recorder.delegate.setMode?.(newMode[this._recorder.state.mode]);
|
|
|
|
|
});
|
|
|
|
|
toolsListElement.appendChild(this._pickLocatorToggle);
|
|
|
|
|
|
|
|
|
|
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.addEventListener('click', () => {
|
|
|
|
|
if (!this._assertToggle.classList.contains('disabled'))
|
|
|
|
|
this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingText' ? 'recording' : 'assertingText');
|
|
|
|
|
});
|
|
|
|
|
toolsListElement.appendChild(this._assertToggle);
|
|
|
|
|
|
|
|
|
|
if (this._recorder.injectedScript.isUnderTest) {
|
|
|
|
|
// Most of our tests put elements at the top left, so get out of the way.
|
|
|
|
|
this._position = { x: 350, y: 350 };
|
|
|
|
|
}
|
|
|
|
|
this._updateVisualPosition();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _createToolElement(parent: Element, mode: Mode, title: string) {
|
|
|
|
|
const element = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
|
|
|
|
|
element.title = title;
|
|
|
|
|
element.classList.add(mode);
|
|
|
|
|
element.appendChild(this._recorder.injectedScript.document.createElement('div'));
|
|
|
|
|
element.addEventListener('click', () => this._recorder.delegate.setMode?.(mode));
|
|
|
|
|
parent.appendChild(element);
|
|
|
|
|
return element;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
install() {
|
|
|
|
|
this._recorder.injectedScript.document.documentElement.appendChild(this._overlayElement);
|
|
|
|
|
this._measure = this._overlayElement.getBoundingClientRect();
|
|
|
|
@ -720,8 +740,14 @@ class Overlay {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setUIState(state: UIState) {
|
|
|
|
|
for (const [mode, tool] of Object.entries(this._tools))
|
|
|
|
|
tool.classList.toggle('active', state.mode === mode);
|
|
|
|
|
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');
|
|
|
|
|
if (this._position.x !== state.overlayPosition.x || this._position.y !== state.overlayPosition.y) {
|
|
|
|
|
this._position = state.overlayPosition;
|
|
|
|
|
this._updateVisualPosition();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _updateVisualPosition() {
|
|
|
|
@ -742,6 +768,7 @@ class Overlay {
|
|
|
|
|
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._updateVisualPosition();
|
|
|
|
|
this._recorder.delegate.setOverlayPosition?.(this._position);
|
|
|
|
|
consumeEvent(event);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
@ -767,7 +794,7 @@ export class Recorder {
|
|
|
|
|
private _highlight: Highlight;
|
|
|
|
|
private _overlay: Overlay | undefined;
|
|
|
|
|
private _styleElement: HTMLStyleElement;
|
|
|
|
|
state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript' };
|
|
|
|
|
state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlayPosition: { x: 0, y: 0 } };
|
|
|
|
|
readonly document: Document;
|
|
|
|
|
delegate: RecorderDelegate = {};
|
|
|
|
|
|
|
|
|
@ -776,10 +803,11 @@ export class Recorder {
|
|
|
|
|
this.injectedScript = injectedScript;
|
|
|
|
|
this._highlight = new Highlight(injectedScript);
|
|
|
|
|
this._tools = {
|
|
|
|
|
none: new NoneTool(),
|
|
|
|
|
inspecting: new InspectTool(this),
|
|
|
|
|
recording: new RecordActionTool(this),
|
|
|
|
|
assertingText: new TextAssertionTool(this),
|
|
|
|
|
'none': new NoneTool(),
|
|
|
|
|
'inspecting': new InspectTool(this),
|
|
|
|
|
'recording': new RecordActionTool(this),
|
|
|
|
|
'recording-inspecting': new InspectTool(this),
|
|
|
|
|
'assertingText': new TextAssertionTool(this),
|
|
|
|
|
};
|
|
|
|
|
this._currentTool = this._tools.none;
|
|
|
|
|
if (injectedScript.window.top === injectedScript.window) {
|
|
|
|
@ -1074,6 +1102,7 @@ interface Embedder {
|
|
|
|
|
__pw_recorderState(): Promise<UIState>;
|
|
|
|
|
__pw_recorderSetSelector(selector: string): Promise<void>;
|
|
|
|
|
__pw_recorderSetMode(mode: Mode): Promise<void>;
|
|
|
|
|
__pw_recorderSetOverlayPosition(position: { x: number, y: number }): Promise<void>;
|
|
|
|
|
__pw_refreshOverlay(): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1122,10 +1151,6 @@ export class PollingRecorder implements RecorderDelegate {
|
|
|
|
|
await this._embedder.__pw_recorderRecordAction(action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async __pw_recorderState(): Promise<UIState> {
|
|
|
|
|
return await this._embedder.__pw_recorderState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setSelector(selector: string): Promise<void> {
|
|
|
|
|
await this._embedder.__pw_recorderSetSelector(selector);
|
|
|
|
|
}
|
|
|
|
@ -1133,6 +1158,10 @@ export class PollingRecorder implements RecorderDelegate {
|
|
|
|
|
async setMode(mode: Mode): Promise<void> {
|
|
|
|
|
await this._embedder.__pw_recorderSetMode(mode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setOverlayPosition(position: { x: number, y: number }): Promise<void> {
|
|
|
|
|
await this._embedder.__pw_recorderSetOverlayPosition(position);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default PollingRecorder;
|
|
|
|
|