feat(recorder): generate toHaveValue/toBeEmpty/toBeChecked (#27913)
This commit is contained in:
parent
0f2de59b7c
commit
07da88dcf1
|
|
@ -433,6 +433,29 @@ class TextAssertionTool implements RecorderTool {
|
||||||
if (event.detail !== 1 || this._getSelectionText())
|
if (event.detail !== 1 || this._getSelectionText())
|
||||||
return;
|
return;
|
||||||
const target = this._recorder.deepEventTarget(event);
|
const target = this._recorder.deepEventTarget(event);
|
||||||
|
|
||||||
|
if (['INPUT', 'TEXTAREA'].includes(target.nodeName) || target.isContentEditable) {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._recorder.updateHighlight(highlight, true, '#6fdcbd38');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const text = target ? elementText(new Map(), target).full : '';
|
const text = target ? elementText(new Map(), target).full : '';
|
||||||
if (text) {
|
if (text) {
|
||||||
this._selectionModel = { anchor: { node: target, offset: 0 }, focus: { node: target, offset: target.childNodes.length }, highlight: null };
|
this._selectionModel = { anchor: { node: target, offset: 0 }, focus: { node: target, offset: target.childNodes.length }, highlight: null };
|
||||||
|
|
@ -443,6 +466,14 @@ class TextAssertionTool implements RecorderTool {
|
||||||
|
|
||||||
onMouseDown(event: MouseEvent) {
|
onMouseDown(event: MouseEvent) {
|
||||||
consumeEvent(event);
|
consumeEvent(event);
|
||||||
|
const target = this._recorder.deepEventTarget(event);
|
||||||
|
if (['INPUT', 'TEXTAREA'].includes(target.nodeName) || target.isContentEditable) {
|
||||||
|
this._selectionModel = null;
|
||||||
|
this._syncDocumentSelection();
|
||||||
|
const highlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName });
|
||||||
|
this._recorder.updateHighlight(highlight, true, '#6fdcbd38');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const pos = this._selectionPosition(event);
|
const pos = this._selectionPosition(event);
|
||||||
if (pos && event.detail <= 1) {
|
if (pos && event.detail <= 1) {
|
||||||
this._selectionModel = { anchor: pos, focus: pos, highlight: null };
|
this._selectionModel = { anchor: pos, focus: pos, highlight: null };
|
||||||
|
|
@ -538,7 +569,7 @@ class TextAssertionTool implements RecorderTool {
|
||||||
if (highlight?.selector === this._selectionModel.highlight?.selector)
|
if (highlight?.selector === this._selectionModel.highlight?.selector)
|
||||||
return;
|
return;
|
||||||
this._selectionModel.highlight = highlight;
|
this._selectionModel.highlight = highlight;
|
||||||
this._recorder.updateHighlight(highlight, false, '#6fdcbd38');
|
this._recorder.updateHighlight(highlight, true, '#6fdcbd38');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -644,7 +675,7 @@ class Overlay {
|
||||||
none: this._createToolElement(toolsListElement, 'none', 'Disable'),
|
none: this._createToolElement(toolsListElement, 'none', 'Disable'),
|
||||||
inspecting: this._createToolElement(toolsListElement, 'inspecting', 'Pick locator'),
|
inspecting: this._createToolElement(toolsListElement, 'inspecting', 'Pick locator'),
|
||||||
recording: this._createToolElement(toolsListElement, 'recording', 'Record actions'),
|
recording: this._createToolElement(toolsListElement, 'recording', 'Record actions'),
|
||||||
assertingText: this._createToolElement(toolsListElement, 'assertingText', 'Assert text'),
|
assertingText: this._createToolElement(toolsListElement, 'assertingText', 'Assert text and values'),
|
||||||
};
|
};
|
||||||
|
|
||||||
this._overlayElement.addEventListener('mousedown', event => {
|
this._overlayElement.addEventListener('mousedown', event => {
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,12 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
||||||
return `await ${subject}.${this._asLocator(action.selector)}.SelectOptionAsync(${formatObject(action.options)});`;
|
return `await ${subject}.${this._asLocator(action.selector)}.SelectOptionAsync(${formatObject(action.options)});`;
|
||||||
case 'assertText':
|
case 'assertText':
|
||||||
return `await Expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'ToContainTextAsync' : 'ToHaveTextAsync'}(${quote(action.text)});`;
|
return `await Expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'ToContainTextAsync' : 'ToHaveTextAsync'}(${quote(action.text)});`;
|
||||||
|
case 'assertChecked':
|
||||||
|
return `await Expect(${subject}.${this._asLocator(action.selector)})${action.checked ? '' : '.Not'}.ToBeCheckedAsync();`;
|
||||||
|
case 'assertValue': {
|
||||||
|
const assertion = action.value ? `ToHaveValueAsync(${quote(action.value)})` : `ToBeEmpty()`;
|
||||||
|
return `await Expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,12 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
||||||
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.selectOption(${formatSelectOption(action.options.length > 1 ? action.options : action.options[0])});`;
|
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.selectOption(${formatSelectOption(action.options.length > 1 ? action.options : action.options[0])});`;
|
||||||
case 'assertText':
|
case 'assertText':
|
||||||
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${action.substring ? 'containsText' : 'hasText'}(${quote(action.text)});`;
|
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${action.substring ? 'containsText' : 'hasText'}(${quote(action.text)});`;
|
||||||
|
case 'assertChecked':
|
||||||
|
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)})${action.checked ? '' : '.not()'}.isChecked();`;
|
||||||
|
case 'assertValue': {
|
||||||
|
const assertion = action.value ? `hasValue(${quote(action.value)})` : `isEmpty()`;
|
||||||
|
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${assertion};`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,12 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
||||||
return `await ${subject}.${this._asLocator(action.selector)}.selectOption(${formatObject(action.options.length > 1 ? action.options : action.options[0])});`;
|
return `await ${subject}.${this._asLocator(action.selector)}.selectOption(${formatObject(action.options.length > 1 ? action.options : action.options[0])});`;
|
||||||
case 'assertText':
|
case 'assertText':
|
||||||
return `await expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${quote(action.text)});`;
|
return `await expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${quote(action.text)});`;
|
||||||
|
case 'assertChecked':
|
||||||
|
return `await expect(${subject}.${this._asLocator(action.selector)})${action.checked ? '' : '.not'}.toBeChecked();`;
|
||||||
|
case 'assertValue': {
|
||||||
|
const assertion = action.value ? `toHaveValue(${quote(action.value)})` : `toBeEmpty()`;
|
||||||
|
return `await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,12 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
||||||
return `${subject}.${this._asLocator(action.selector)}.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`;
|
return `${subject}.${this._asLocator(action.selector)}.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`;
|
||||||
case 'assertText':
|
case 'assertText':
|
||||||
return `expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'to_contain_text' : 'to_have_text'}(${quote(action.text)})`;
|
return `expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'to_contain_text' : 'to_have_text'}(${quote(action.text)})`;
|
||||||
|
case 'assertChecked':
|
||||||
|
return `expect(${subject}.${this._asLocator(action.selector)}).${action.checked ? 'to_be_checked()' : 'not_to_be_checked()'}`;
|
||||||
|
case 'assertValue': {
|
||||||
|
const assertion = action.value ? `to_have_value(${quote(action.value)})` : `to_be_empty()`;
|
||||||
|
return `expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ export type ActionName =
|
||||||
'select' |
|
'select' |
|
||||||
'uncheck' |
|
'uncheck' |
|
||||||
'setInputFiles' |
|
'setInputFiles' |
|
||||||
'assertText';
|
'assertText' |
|
||||||
|
'assertValue' |
|
||||||
|
'assertChecked';
|
||||||
|
|
||||||
export type ActionBase = {
|
export type ActionBase = {
|
||||||
name: ActionName,
|
name: ActionName,
|
||||||
|
|
@ -99,7 +101,19 @@ export type AssertTextAction = ActionBase & {
|
||||||
substring: boolean,
|
substring: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction;
|
export type AssertValueAction = ActionBase & {
|
||||||
|
name: 'assertValue',
|
||||||
|
selector: string,
|
||||||
|
value: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AssertCheckedAction = ActionBase & {
|
||||||
|
name: 'assertChecked',
|
||||||
|
selector: string,
|
||||||
|
checked: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction;
|
||||||
|
|
||||||
// Signals.
|
// Signals.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
<ToolbarButton icon='record' title='Record actions' toggled={mode === 'recording'} onClick={() => {
|
<ToolbarButton icon='record' title='Record actions' toggled={mode === 'recording'} onClick={() => {
|
||||||
window.dispatch({ event: 'setMode', params: { mode: mode === 'recording' ? 'none' : 'recording' } });
|
window.dispatch({ event: 'setMode', params: { mode: mode === 'recording' ? 'none' : 'recording' } });
|
||||||
}}>Record</ToolbarButton>
|
}}>Record</ToolbarButton>
|
||||||
<ToolbarButton icon='text-size' title='Assert text' toggled={mode === 'assertingText'} onClick={() => {
|
<ToolbarButton icon='text-size' title='Assert text and values' toggled={mode === 'assertingText'} onClick={() => {
|
||||||
window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'none' : 'assertingText' } });
|
window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'none' : 'assertingText' } });
|
||||||
}}>Assert</ToolbarButton>
|
}}>Assert</ToolbarButton>
|
||||||
<ToolbarButton icon='files' title='Copy' disabled={!source || !source.text} onClick={() => {
|
<ToolbarButton icon='files' title='Copy' disabled={!source || !source.text} onClick={() => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue