mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(codegen): add button to generate toHaveScreenshot statement (#29996)
Fixes #29250.
This commit is contained in:
parent
54aca430b0
commit
1bb463163b
@ -231,6 +231,12 @@ x-pw-tool-item.value > x-div {
|
|||||||
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='M4 6h8v1H4V6zm8 3H4v1h8V9z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M1 4l1-1h12l1 1v8l-1 1H2l-1-1V4zm1 0v8h12V4H2z'/></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='M4 6h8v1H4V6zm8 3H4v1h8V9z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M1 4l1-1h12l1 1v8l-1 1H2l-1-1V4zm1 0v8h12V4H2z'/></svg>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x-pw-tool-item.screenshot > x-div {
|
||||||
|
/* codicon: device-camera */
|
||||||
|
-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='M10.707 3H14.5l.5.5v9l-.5.5h-13l-.5-.5v-9l.5-.5h3.793l.853-.854L6.5 2h3l.354.146.853.854zM2 12h12V4h-3.5l-.354-.146L9.293 3H6.707l-.853.854L5.5 4H2v8zm1.5-7a.5.5 0 1 0 0 1 .5.5 0 0 0 0-1zM8 6a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm0-1a3 3 0 1 0 0 6 3 3 0 0 0 0-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 fill-rule='evenodd' clip-rule='evenodd' d='M10.707 3H14.5l.5.5v9l-.5.5h-13l-.5-.5v-9l.5-.5h3.793l.853-.854L6.5 2h3l.354.146.853.854zM2 12h12V4h-3.5l-.354-.146L9.293 3H6.707l-.853.854L5.5 4H2v8zm1.5-7a.5.5 0 1 0 0 1 .5.5 0 0 0 0-1zM8 6a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm0-1a3 3 0 1 0 0 6 3 3 0 0 0 0-6z'/></svg>");
|
||||||
|
}
|
||||||
|
|
||||||
x-pw-tool-item.accept > x-div {
|
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>");
|
-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>");
|
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>");
|
||||||
|
|||||||
@ -761,6 +761,7 @@ class Overlay {
|
|||||||
private _assertVisibilityToggle: HTMLElement;
|
private _assertVisibilityToggle: HTMLElement;
|
||||||
private _assertTextToggle: HTMLElement;
|
private _assertTextToggle: HTMLElement;
|
||||||
private _assertValuesToggle: HTMLElement;
|
private _assertValuesToggle: HTMLElement;
|
||||||
|
private _assertScreenshotButton: HTMLElement;
|
||||||
private _offsetX = 0;
|
private _offsetX = 0;
|
||||||
private _dragState: { offsetX: number, dragStart: { x: number, y: number } } | undefined;
|
private _dragState: { offsetX: number, dragStart: { x: number, y: number } } | undefined;
|
||||||
private _measure: { width: number, height: number } = { width: 0, height: 0 };
|
private _measure: { width: number, height: number } = { width: 0, height: 0 };
|
||||||
@ -807,6 +808,12 @@ class Overlay {
|
|||||||
this._assertValuesToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div'));
|
this._assertValuesToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div'));
|
||||||
toolsListElement.appendChild(this._assertValuesToggle);
|
toolsListElement.appendChild(this._assertValuesToggle);
|
||||||
|
|
||||||
|
this._assertScreenshotButton = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
|
||||||
|
this._assertScreenshotButton.title = 'Assert screenshot';
|
||||||
|
this._assertScreenshotButton.classList.add('screenshot');
|
||||||
|
this._assertScreenshotButton.appendChild(this._recorder.injectedScript.document.createElement('x-div'));
|
||||||
|
toolsListElement.appendChild(this._assertScreenshotButton);
|
||||||
|
|
||||||
this._updateVisualPosition();
|
this._updateVisualPosition();
|
||||||
this._refreshListeners();
|
this._refreshListeners();
|
||||||
}
|
}
|
||||||
@ -845,6 +852,15 @@ class Overlay {
|
|||||||
if (!this._assertValuesToggle.classList.contains('disabled'))
|
if (!this._assertValuesToggle.classList.contains('disabled'))
|
||||||
this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingValue' ? 'recording' : 'assertingValue');
|
this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingValue' ? 'recording' : 'assertingValue');
|
||||||
}),
|
}),
|
||||||
|
addEventListener(this._assertScreenshotButton, 'click', () => {
|
||||||
|
if (!this._assertScreenshotButton.classList.contains('disabled')) {
|
||||||
|
this._recorder.delegate.recordAction?.({
|
||||||
|
name: 'assertScreenshot',
|
||||||
|
signals: [],
|
||||||
|
});
|
||||||
|
this.flashToolSucceeded('assertScreenshot');
|
||||||
|
}
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -867,6 +883,7 @@ class Overlay {
|
|||||||
this._assertTextToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting');
|
this._assertTextToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting');
|
||||||
this._assertValuesToggle.classList.toggle('active', state.mode === 'assertingValue');
|
this._assertValuesToggle.classList.toggle('active', state.mode === 'assertingValue');
|
||||||
this._assertValuesToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting');
|
this._assertValuesToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting');
|
||||||
|
this._assertScreenshotButton.classList.toggle('disabled', state.mode !== 'recording');
|
||||||
if (this._offsetX !== state.overlay.offsetX) {
|
if (this._offsetX !== state.overlay.offsetX) {
|
||||||
this._offsetX = state.overlay.offsetX;
|
this._offsetX = state.overlay.offsetX;
|
||||||
this._updateVisualPosition();
|
this._updateVisualPosition();
|
||||||
@ -877,8 +894,12 @@ class Overlay {
|
|||||||
this._showOverlay();
|
this._showOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue') {
|
flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue' | 'assertScreenshot') {
|
||||||
const element = tool === 'assertingVisibility' ? this._assertVisibilityToggle : this._assertValuesToggle;
|
const element = {
|
||||||
|
'assertingVisibility': this._assertVisibilityToggle,
|
||||||
|
'assertingValue': this._assertValuesToggle,
|
||||||
|
'assertScreenshot': this._assertScreenshotButton,
|
||||||
|
}[tool];
|
||||||
element.classList.add('succeeded');
|
element.classList.add('succeeded');
|
||||||
setTimeout(() => element.classList.remove('succeeded'), 2000);
|
setTimeout(() => element.classList.remove('succeeded'), 2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -164,6 +164,8 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
|||||||
const assertion = action.value ? `ToHaveValueAsync(${quote(action.value)})` : `ToBeEmptyAsync()`;
|
const assertion = action.value ? `ToHaveValueAsync(${quote(action.value)})` : `ToBeEmptyAsync()`;
|
||||||
return `await Expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
return `await Expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
||||||
}
|
}
|
||||||
|
case 'assertScreenshot':
|
||||||
|
return `// AssertScreenshot(await ${subject}.ScreenshotAsync());`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -152,6 +152,8 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
|||||||
const assertion = action.value ? `hasValue(${quote(action.value)})` : `isEmpty()`;
|
const assertion = action.value ? `hasValue(${quote(action.value)})` : `isEmpty()`;
|
||||||
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${assertion};`;
|
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${assertion};`;
|
||||||
}
|
}
|
||||||
|
case 'assertScreenshot':
|
||||||
|
return `// assertScreenshot(${subject}.screenshot());`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -135,6 +135,8 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
|||||||
const assertion = action.value ? `toHaveValue(${quote(action.value)})` : `toBeEmpty()`;
|
const assertion = action.value ? `toHaveValue(${quote(action.value)})` : `toBeEmpty()`;
|
||||||
return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
||||||
}
|
}
|
||||||
|
case 'assertScreenshot':
|
||||||
|
return `${this._isTest ? '' : '// '}await expect(${subject}).toHaveScreenshot();`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
|||||||
if (signals.dialog)
|
if (signals.dialog)
|
||||||
formatter.add(` ${pageAlias}.once("dialog", lambda dialog: dialog.dismiss())`);
|
formatter.add(` ${pageAlias}.once("dialog", lambda dialog: dialog.dismiss())`);
|
||||||
|
|
||||||
let code = `${this._awaitPrefix}${this._generateActionCall(subject, action)}`;
|
let code = this._generateActionCall(subject, action);
|
||||||
|
|
||||||
if (signals.popup) {
|
if (signals.popup) {
|
||||||
code = `${this._asyncPrefix}with ${pageAlias}.expect_popup() as ${signals.popup.popupAlias}_info {
|
code = `${this._asyncPrefix}with ${pageAlias}.expect_popup() as ${signals.popup.popupAlias}_info {
|
||||||
@ -99,7 +99,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
|||||||
case 'openPage':
|
case 'openPage':
|
||||||
throw Error('Not reached');
|
throw Error('Not reached');
|
||||||
case 'closePage':
|
case 'closePage':
|
||||||
return `${subject}.close()`;
|
return `${this._awaitPrefix}${subject}.close()`;
|
||||||
case 'click': {
|
case 'click': {
|
||||||
let method = 'click';
|
let method = 'click';
|
||||||
if (action.clickCount === 2)
|
if (action.clickCount === 2)
|
||||||
@ -115,35 +115,37 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
|||||||
if (action.position)
|
if (action.position)
|
||||||
options.position = action.position;
|
options.position = action.position;
|
||||||
const optionsString = formatOptions(options, false);
|
const optionsString = formatOptions(options, false);
|
||||||
return `${subject}.${this._asLocator(action.selector)}.${method}(${optionsString})`;
|
return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.${method}(${optionsString})`;
|
||||||
}
|
}
|
||||||
case 'check':
|
case 'check':
|
||||||
return `${subject}.${this._asLocator(action.selector)}.check()`;
|
return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.check()`;
|
||||||
case 'uncheck':
|
case 'uncheck':
|
||||||
return `${subject}.${this._asLocator(action.selector)}.uncheck()`;
|
return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.uncheck()`;
|
||||||
case 'fill':
|
case 'fill':
|
||||||
return `${subject}.${this._asLocator(action.selector)}.fill(${quote(action.text)})`;
|
return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.fill(${quote(action.text)})`;
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `${subject}.${this._asLocator(action.selector)}.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`;
|
return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`;
|
||||||
case 'press': {
|
case 'press': {
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const modifiers = toModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)})`;
|
return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)})`;
|
||||||
}
|
}
|
||||||
case 'navigate':
|
case 'navigate':
|
||||||
return `${subject}.goto(${quote(action.url)})`;
|
return `${this._awaitPrefix}${subject}.goto(${quote(action.url)})`;
|
||||||
case 'select':
|
case 'select':
|
||||||
return `${subject}.${this._asLocator(action.selector)}.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`;
|
return `${this._awaitPrefix}${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 `${this._awaitPrefix}expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'to_contain_text' : 'to_have_text'}(${quote(action.text)})`;
|
||||||
case 'assertChecked':
|
case 'assertChecked':
|
||||||
return `expect(${subject}.${this._asLocator(action.selector)}).${action.checked ? 'to_be_checked()' : 'not_to_be_checked()'}`;
|
return `${this._awaitPrefix}expect(${subject}.${this._asLocator(action.selector)}).${action.checked ? 'to_be_checked()' : 'not_to_be_checked()'}`;
|
||||||
case 'assertVisible':
|
case 'assertVisible':
|
||||||
return `expect(${subject}.${this._asLocator(action.selector)}).to_be_visible()`;
|
return `${this._awaitPrefix}expect(${subject}.${this._asLocator(action.selector)}).to_be_visible()`;
|
||||||
case 'assertValue': {
|
case 'assertValue': {
|
||||||
const assertion = action.value ? `to_have_value(${quote(action.value)})` : `to_be_empty()`;
|
const assertion = action.value ? `to_have_value(${quote(action.value)})` : `to_be_empty()`;
|
||||||
return `expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
return `${this._awaitPrefix}expect(${subject}.${this._asLocator(action.selector)}).${assertion}`;
|
||||||
}
|
}
|
||||||
|
case 'assertScreenshot':
|
||||||
|
return `# assert_screenshot(${this._awaitPrefix}${subject}.screenshot())`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export type ActionName =
|
|||||||
'assertText' |
|
'assertText' |
|
||||||
'assertValue' |
|
'assertValue' |
|
||||||
'assertChecked' |
|
'assertChecked' |
|
||||||
|
'assertScreenshot' |
|
||||||
'assertVisible';
|
'assertVisible';
|
||||||
|
|
||||||
export type ActionBase = {
|
export type ActionBase = {
|
||||||
@ -119,7 +120,11 @@ export type AssertVisibleAction = ActionBase & {
|
|||||||
selector: string,
|
selector: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction | AssertVisibleAction;
|
export type AssertScreenshotAction = ActionBase & {
|
||||||
|
name: 'assertScreenshot',
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction | AssertVisibleAction | AssertScreenshotAction;
|
||||||
export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction | AssertVisibleAction;
|
export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction | AssertVisibleAction;
|
||||||
|
|
||||||
// Signals.
|
// Signals.
|
||||||
|
|||||||
@ -648,4 +648,18 @@ await page.GetByLabel("Coun\\"try").ClickAsync();`);
|
|||||||
expect.soft(sources1.get('Java')!.text).toContain(`assertThat(page.getByRole(AriaRole.TEXTBOX)).isVisible()`);
|
expect.soft(sources1.get('Java')!.text).toContain(`assertThat(page.getByRole(AriaRole.TEXTBOX)).isVisible()`);
|
||||||
expect.soft(sources1.get('C#')!.text).toContain(`await Expect(page.GetByRole(AriaRole.Textbox)).ToBeVisibleAsync()`);
|
expect.soft(sources1.get('C#')!.text).toContain(`await Expect(page.GetByRole(AriaRole.Textbox)).ToBeVisibleAsync()`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should assert screenshot', async ({ openRecorder }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
await recorder.setContentAndWait(`<div>Hello, world</div>`);
|
||||||
|
const [sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'toHaveScreenshot'),
|
||||||
|
recorder.page.click('x-pw-tool-item.screenshot'),
|
||||||
|
]);
|
||||||
|
expect.soft(sources.get('JavaScript')!.text).toContain(`await expect(page).toHaveScreenshot()`);
|
||||||
|
expect.soft(sources.get('Python')!.text).toContain(`# assert_screenshot(page.screenshot())`);
|
||||||
|
expect.soft(sources.get('Python Async')!.text).toContain(`# assert_screenshot(await page.screenshot())`);
|
||||||
|
expect.soft(sources.get('Java')!.text).toContain(`// assertScreenshot(page.screenshot());`);
|
||||||
|
expect.soft(sources.get('C#')!.text).toContain(`// AssertScreenshot(await page.ScreenshotAsync())`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user