diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts
index c338d3b8f0164..cd148f74aaa44 100644
--- a/packages/playwright-core/src/server/injected/recorder/recorder.ts
+++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts
@@ -547,12 +547,7 @@ class TextAssertionTool implements RecorderTool {
onClick(event: MouseEvent) {
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');
- }
+ this._commitAssertValue();
} else {
if (!this._dialogElement)
this._showDialog();
@@ -565,6 +560,15 @@ class TextAssertionTool implements RecorderTool {
event.preventDefault();
}
+ onPointerUp(event: PointerEvent) {
+ const target = this._hoverHighlight?.elements[0];
+ if (this._kind === 'value' && target && target.nodeName === 'INPUT' && (target as HTMLInputElement).disabled) {
+ // Click on a disabled input does not produce a "click" event, but we still want
+ // to assert the value.
+ this._commitAssertValue();
+ }
+ }
+
onMouseMove(event: MouseEvent) {
if (this._dialogElement)
return;
@@ -719,6 +723,17 @@ class TextAssertionTool implements RecorderTool {
this._recorder.document.removeEventListener('keydown', this._keyboardListener!);
this._dialogElement = null;
}
+
+ private _commitAssertValue() {
+ if (this._kind !== 'value')
+ return;
+ const action = this._generateAction();
+ if (!action)
+ return;
+ this._recorder.delegate.recordAction?.(action);
+ this._recorder.delegate.setMode?.('recording');
+ this._recorder.overlay?.flashToolSucceeded('assertingValue');
+ }
}
class Overlay {
@@ -1138,8 +1153,10 @@ export class Recorder {
}
private _ignoreOverlayEvent(event: Event) {
- const target = event.composedPath()[0] as Element;
- return target.nodeName.toLowerCase() === 'x-pw-glass';
+ return event.composedPath().some(e => {
+ const nodeName = (e as Element).nodeName || '';
+ return nodeName.toLowerCase() === 'x-pw-glass';
+ });
}
deepEventTarget(event: Event): HTMLElement {
diff --git a/packages/playwright-core/src/server/recorder/csharp.ts b/packages/playwright-core/src/server/recorder/csharp.ts
index 1dcc6a331adc6..46fadc244aa98 100644
--- a/packages/playwright-core/src/server/recorder/csharp.ts
+++ b/packages/playwright-core/src/server/recorder/csharp.ts
@@ -161,7 +161,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
case 'assertVisible':
return `await Expect(${subject}.${this._asLocator(action.selector)}).ToBeVisibleAsync();`;
case 'assertValue': {
- const assertion = action.value ? `ToHaveValueAsync(${quote(action.value)})` : `ToBeEmpty()`;
+ const assertion = action.value ? `ToHaveValueAsync(${quote(action.value)})` : `ToBeEmptyAsync()`;
return `await Expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
}
}
diff --git a/tests/library/inspector/cli-codegen-3.spec.ts b/tests/library/inspector/cli-codegen-3.spec.ts
index 77d91bb0e2233..96a6295f13a26 100644
--- a/tests/library/inspector/cli-codegen-3.spec.ts
+++ b/tests/library/inspector/cli-codegen-3.spec.ts
@@ -558,4 +558,94 @@ await page.GetByLabel("Coun\\"try").ClickAsync();`);
'click',
]);
});
+
+ test('should assert value', async ({ openRecorder }) => {
+ const recorder = await openRecorder();
+
+ await recorder.setContentAndWait(`
+
+
+
+
+ `);
+
+ await recorder.page.click('x-pw-tool-item.value');
+ await recorder.hoverOverElement('#first');
+ const [sources1] = await Promise.all([
+ recorder.waitForOutput('JavaScript', '#first'),
+ recorder.trustedClick(),
+ ]);
+ expect.soft(sources1.get('JavaScript')!.text).toContain(`await expect(page.locator('#first')).toHaveValue('foo')`);
+ expect.soft(sources1.get('Python')!.text).toContain(`expect(page.locator("#first")).to_have_value("foo")`);
+ expect.soft(sources1.get('Python Async')!.text).toContain(`await expect(page.locator("#first")).to_have_value("foo")`);
+ expect.soft(sources1.get('Java')!.text).toContain(`assertThat(page.locator("#first")).hasValue("foo")`);
+ expect.soft(sources1.get('C#')!.text).toContain(`await Expect(page.Locator("#first")).ToHaveValueAsync("foo")`);
+
+ await recorder.page.click('x-pw-tool-item.value');
+ await recorder.hoverOverElement('#third');
+ const [sources3] = await Promise.all([
+ recorder.waitForOutput('JavaScript', '#third'),
+ recorder.trustedClick(),
+ ]);
+ expect.soft(sources3.get('JavaScript')!.text).toContain(`await expect(page.locator('#third')).toBeEmpty()`);
+ expect.soft(sources3.get('Python')!.text).toContain(`expect(page.locator("#third")).to_be_empty()`);
+ expect.soft(sources3.get('Python Async')!.text).toContain(`await expect(page.locator("#third")).to_be_empty()`);
+ expect.soft(sources3.get('Java')!.text).toContain(`assertThat(page.locator("#third")).isEmpty()`);
+ expect.soft(sources3.get('C#')!.text).toContain(`await Expect(page.Locator("#third")).ToBeEmptyAsync()`);
+
+ await recorder.page.click('x-pw-tool-item.value');
+ await recorder.hoverOverElement('#fourth');
+ const [sources4] = await Promise.all([
+ recorder.waitForOutput('JavaScript', '#fourth'),
+ recorder.trustedClick(),
+ ]);
+ expect.soft(sources4.get('JavaScript')!.text).toContain(`await expect(page.locator('#fourth')).toBeChecked()`);
+ expect.soft(sources4.get('Python')!.text).toContain(`expect(page.locator("#fourth")).to_be_checked()`);
+ expect.soft(sources4.get('Python Async')!.text).toContain(`await expect(page.locator("#fourth")).to_be_checked()`);
+ expect.soft(sources4.get('Java')!.text).toContain(`assertThat(page.locator("#fourth")).isChecked()`);
+ expect.soft(sources4.get('C#')!.text).toContain(`await Expect(page.Locator("#fourth")).ToBeCheckedAsync()`);
+ });
+
+ test('should assert value on disabled input', async ({ openRecorder, browserName }) => {
+ test.fixme(browserName === 'firefox', 'pointerup event is not dispatched on a disabled input');
+
+ const recorder = await openRecorder();
+
+ await recorder.setContentAndWait(`
+
+
+
+
+ `);
+
+ await recorder.page.click('x-pw-tool-item.value');
+ await recorder.hoverOverElement('#second');
+ const [sources2] = await Promise.all([
+ recorder.waitForOutput('JavaScript', '#second'),
+ recorder.trustedClick(),
+ ]);
+ expect.soft(sources2.get('JavaScript')!.text).toContain(`await expect(page.locator('#second')).toHaveValue('bar')`);
+ expect.soft(sources2.get('Python')!.text).toContain(`expect(page.locator("#second")).to_have_value("bar")`);
+ expect.soft(sources2.get('Python Async')!.text).toContain(`await expect(page.locator("#second")).to_have_value("bar")`);
+ expect.soft(sources2.get('Java')!.text).toContain(`assertThat(page.locator("#second")).hasValue("bar")`);
+ expect.soft(sources2.get('C#')!.text).toContain(`await Expect(page.Locator("#second")).ToHaveValueAsync("bar")`);
+ });
+
+ test('should assert visibility', async ({ openRecorder }) => {
+ const recorder = await openRecorder();
+
+ await recorder.setContentAndWait(``);
+
+ await recorder.page.click('x-pw-tool-item.visibility');
+ await recorder.hoverOverElement('input');
+ const [sources1] = await Promise.all([
+ recorder.waitForOutput('JavaScript', 'textbox'),
+ recorder.trustedClick(),
+ ]);
+ expect.soft(sources1.get('JavaScript')!.text).toContain(`await expect(page.getByRole('textbox')).toBeVisible()`);
+ expect.soft(sources1.get('Python')!.text).toContain(`expect(page.get_by_role("textbox")).to_be_visible()`);
+ expect.soft(sources1.get('Python Async')!.text).toContain(`await expect(page.get_by_role("textbox")).to_be_visible()`);
+ 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()`);
+ });
});