mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(fill): make fill work with date/time inputs (#1676)
Date/time inputs are locale-specific, and also do not work with insertText. We just set the value on them and emulate input/change events. Note that some browsers do not support these input types just yet.
This commit is contained in:
parent
e0c8fbf1a6
commit
e683c086de
16
src/dom.ts
16
src/dom.ts
@ -243,13 +243,15 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
async fill(value: string, options?: types.NavigatingActionWaitOptions): Promise<void> {
|
||||
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
|
||||
const error = await this._evaluateInUtility(({ injected, node }, value) => injected.fill(node, value), value);
|
||||
if (error)
|
||||
throw new Error(error);
|
||||
if (value)
|
||||
await this._page.keyboard.insertText(value);
|
||||
else
|
||||
await this._page.keyboard.press('Delete');
|
||||
const errorOrNeedsInput = await this._evaluateInUtility(({ injected, node }, value) => injected.fill(node, value), value);
|
||||
if (typeof errorOrNeedsInput === 'string')
|
||||
throw new Error(errorOrNeedsInput);
|
||||
if (errorOrNeedsInput) {
|
||||
if (value)
|
||||
await this._page.keyboard.insertText(value);
|
||||
else
|
||||
await this._page.keyboard.press('Delete');
|
||||
}
|
||||
}, options, true);
|
||||
}
|
||||
|
||||
|
||||
@ -159,11 +159,12 @@ class Injected {
|
||||
return 'Element is not visible';
|
||||
if (element.nodeName.toLowerCase() === 'input') {
|
||||
const input = element as HTMLInputElement;
|
||||
const type = input.getAttribute('type') || '';
|
||||
const type = (input.getAttribute('type') || '').toLowerCase();
|
||||
const kDateTypes = new Set(['date', 'time', 'datetime', 'datetime-local']);
|
||||
const kTextInputTypes = new Set(['', 'email', 'number', 'password', 'search', 'tel', 'text', 'url']);
|
||||
if (!kTextInputTypes.has(type.toLowerCase()))
|
||||
if (!kTextInputTypes.has(type) && !kDateTypes.has(type))
|
||||
return 'Cannot fill input of type "' + type + '".';
|
||||
if (type.toLowerCase() === 'number') {
|
||||
if (type === 'number') {
|
||||
value = value.trim();
|
||||
if (!value || isNaN(Number(value)))
|
||||
return 'Cannot type text into input[type=number].';
|
||||
@ -172,9 +173,21 @@ class Injected {
|
||||
return 'Cannot fill a disabled input.';
|
||||
if (input.readOnly)
|
||||
return 'Cannot fill a readonly input.';
|
||||
if (kDateTypes.has(type)) {
|
||||
value = value.trim();
|
||||
input.focus();
|
||||
input.value = value;
|
||||
if (input.value !== value)
|
||||
return `Malformed ${type} "${value}"`;
|
||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||
return false; // We have already changed the value, no need to input it.
|
||||
}
|
||||
input.select();
|
||||
input.focus();
|
||||
} else if (element.nodeName.toLowerCase() === 'textarea') {
|
||||
return true;
|
||||
}
|
||||
if (element.nodeName.toLowerCase() === 'textarea') {
|
||||
const textarea = element as HTMLTextAreaElement;
|
||||
if (textarea.disabled)
|
||||
return 'Cannot fill a disabled textarea.';
|
||||
@ -183,7 +196,9 @@ class Injected {
|
||||
textarea.selectionStart = 0;
|
||||
textarea.selectionEnd = textarea.value.length;
|
||||
textarea.focus();
|
||||
} else if (element.isContentEditable) {
|
||||
return true;
|
||||
}
|
||||
if (element.isContentEditable) {
|
||||
const range = element.ownerDocument!.createRange();
|
||||
range.selectNodeContents(element);
|
||||
const selection = element.ownerDocument!.defaultView!.getSelection();
|
||||
@ -192,10 +207,9 @@ class Injected {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
element.focus();
|
||||
} else {
|
||||
return 'Element is not an <input>, <textarea> or [contenteditable] element.';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return 'Element is not an <input>, <textarea> or [contenteditable] element.';
|
||||
}
|
||||
|
||||
isCheckboxChecked(node: Node) {
|
||||
|
||||
@ -918,7 +918,7 @@ module.exports.describe = function({playwright, FFOX, CHROMIUM, WEBKIT}) {
|
||||
});
|
||||
it('should throw on unsupported inputs', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
for (const type of ['color', 'date']) {
|
||||
for (const type of ['color', 'file']) {
|
||||
await page.$eval('input', (input, type) => input.setAttribute('type', type), type);
|
||||
let error = null;
|
||||
await page.fill('input', '').catch(e => error = e);
|
||||
@ -933,6 +933,37 @@ module.exports.describe = function({playwright, FFOX, CHROMIUM, WEBKIT}) {
|
||||
expect(await page.evaluate(() => result)).toBe('text ' + type);
|
||||
}
|
||||
});
|
||||
it('should fill date input after clicking', async({page, server}) => {
|
||||
await page.setContent('<input type=date>');
|
||||
await page.click('input');
|
||||
await page.fill('input', '2020-03-02');
|
||||
expect(await page.$eval('input', input => input.value)).toBe('2020-03-02');
|
||||
});
|
||||
it.skip(WEBKIT)('should throw on incorrect date', async({page, server}) => {
|
||||
await page.setContent('<input type=date>');
|
||||
const error = await page.fill('input', '2020-13-05').catch(e => e);
|
||||
expect(error.message).toBe('Malformed date "2020-13-05"');
|
||||
});
|
||||
it('should fill time input', async({page, server}) => {
|
||||
await page.setContent('<input type=time>');
|
||||
await page.fill('input', '13:15');
|
||||
expect(await page.$eval('input', input => input.value)).toBe('13:15');
|
||||
});
|
||||
it.skip(WEBKIT)('should throw on incorrect time', async({page, server}) => {
|
||||
await page.setContent('<input type=time>');
|
||||
const error = await page.fill('input', '25:05').catch(e => e);
|
||||
expect(error.message).toBe('Malformed time "25:05"');
|
||||
});
|
||||
it('should fill datetime-local input', async({page, server}) => {
|
||||
await page.setContent('<input type=datetime-local>');
|
||||
await page.fill('input', '2020-03-02T05:15');
|
||||
expect(await page.$eval('input', input => input.value)).toBe('2020-03-02T05:15');
|
||||
});
|
||||
it.skip(WEBKIT || FFOX)('should throw on incorrect datetime-local', async({page, server}) => {
|
||||
await page.setContent('<input type=datetime-local>');
|
||||
const error = await page.fill('input', 'abc').catch(e => e);
|
||||
expect(error.message).toBe('Malformed datetime-local "abc"');
|
||||
});
|
||||
it('should fill contenteditable', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
await page.fill('div[contenteditable]', 'some value');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user