mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03:00
fix(fill): throw when the element isn't fillable (#160)
An element is fillable if its: - In dom - Not display:none or visibility:hidden - textarea or input or contenteditable if textarea or input it must also be - not readOnly - not disabled #133
This commit is contained in:
parent
37540179de
commit
e3f34bd69a
@ -335,8 +335,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||||||
const error = await this.evaluate(input.fillFunction);
|
const error = await this.evaluate(input.fillFunction);
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
await this.focus();
|
|
||||||
// TODO: we should check that focus() succeeded.
|
|
||||||
await this._world.delegate.keyboard.sendCharacters(value);
|
await this._world.delegate.keyboard.sendCharacters(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
23
src/input.ts
23
src/input.ts
@ -320,26 +320,45 @@ export const fillFunction = (node: Node) => {
|
|||||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||||
return 'Node is not of type HTMLElement';
|
return 'Node is not of type HTMLElement';
|
||||||
const element = node as HTMLElement;
|
const element = node as HTMLElement;
|
||||||
|
if (!element.isConnected)
|
||||||
|
return 'Element is not attached to the DOM';
|
||||||
|
if (!element.ownerDocument || !element.ownerDocument.defaultView)
|
||||||
|
return 'Element does not belong to a window';
|
||||||
|
|
||||||
|
const style = element.ownerDocument.defaultView.getComputedStyle(element);
|
||||||
|
if (!style || style.visibility === 'hidden')
|
||||||
|
return 'Element is hidden';
|
||||||
|
if (!element.offsetParent && element.tagName !== 'BODY')
|
||||||
|
return 'Element is not visible';
|
||||||
if (element.nodeName.toLowerCase() === 'input') {
|
if (element.nodeName.toLowerCase() === 'input') {
|
||||||
const input = element as HTMLInputElement;
|
const input = element as HTMLInputElement;
|
||||||
const type = input.getAttribute('type') || '';
|
const type = input.getAttribute('type') || '';
|
||||||
const kTextInputTypes = new Set(['', 'password', 'search', 'tel', 'text', 'url']);
|
const kTextInputTypes = new Set(['', 'password', 'search', 'tel', 'text', 'url']);
|
||||||
if (!kTextInputTypes.has(type.toLowerCase()))
|
if (!kTextInputTypes.has(type.toLowerCase()))
|
||||||
return 'Cannot fill input of type "' + type + '".';
|
return 'Cannot fill input of type "' + type + '".';
|
||||||
|
if (input.disabled)
|
||||||
|
return 'Cannot fill a disabled input.';
|
||||||
|
if (input.readOnly)
|
||||||
|
return 'Cannot fill a readonly input.';
|
||||||
input.selectionStart = 0;
|
input.selectionStart = 0;
|
||||||
input.selectionEnd = input.value.length;
|
input.selectionEnd = input.value.length;
|
||||||
|
input.focus();
|
||||||
} else if (element.nodeName.toLowerCase() === 'textarea') {
|
} else if (element.nodeName.toLowerCase() === 'textarea') {
|
||||||
const textarea = element as HTMLTextAreaElement;
|
const textarea = element as HTMLTextAreaElement;
|
||||||
|
if (textarea.disabled)
|
||||||
|
return 'Cannot fill a disabled textarea.';
|
||||||
|
if (textarea.readOnly)
|
||||||
|
return 'Cannot fill a readonly textarea.';
|
||||||
textarea.selectionStart = 0;
|
textarea.selectionStart = 0;
|
||||||
textarea.selectionEnd = textarea.value.length;
|
textarea.selectionEnd = textarea.value.length;
|
||||||
|
textarea.focus();
|
||||||
} else if (element.isContentEditable) {
|
} else if (element.isContentEditable) {
|
||||||
if (!element.ownerDocument || !element.ownerDocument.defaultView)
|
|
||||||
return 'Element does not belong to a window';
|
|
||||||
const range = element.ownerDocument.createRange();
|
const range = element.ownerDocument.createRange();
|
||||||
range.selectNodeContents(element);
|
range.selectNodeContents(element);
|
||||||
const selection = element.ownerDocument.defaultView.getSelection();
|
const selection = element.ownerDocument.defaultView.getSelection();
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
|
element.focus();
|
||||||
} else {
|
} else {
|
||||||
return 'Element is not an <input>, <textarea> or [contenteditable] element.';
|
return 'Element is not an <input>, <textarea> or [contenteditable] element.';
|
||||||
}
|
}
|
||||||
|
@ -1133,11 +1133,42 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
|
|||||||
await page.$eval('input', i => i.style.display = 'none');
|
await page.$eval('input', i => i.style.display = 'none');
|
||||||
await page.fill({selector: 'input', visible: true}, 'some value').catch(e => error = e);
|
await page.fill({selector: 'input', visible: true}, 'some value').catch(e => error = e);
|
||||||
expect(error.message).toBe('No node found for selector: [visible] input');
|
expect(error.message).toBe('No node found for selector: [visible] input');
|
||||||
|
});
|
||||||
|
it('should throw on disabled and readonly elements', async({page, server}) => {
|
||||||
|
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||||
|
await page.$eval('input', i => i.disabled = true);
|
||||||
|
const disabledError = await page.fill('input', 'some value').catch(e => e);
|
||||||
|
expect(disabledError.message).toBe('Cannot fill a disabled input.');
|
||||||
|
|
||||||
|
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||||
|
await page.$eval('textarea', i => i.readOnly = true);
|
||||||
|
const readonlyError = await page.fill('textarea', 'some value').catch(e => e);
|
||||||
|
expect(readonlyError.message).toBe('Cannot fill a readonly textarea.');
|
||||||
|
});
|
||||||
|
it('should throw on hidden and invisible elements', async({page, server}) => {
|
||||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||||
await page.$eval('input', i => i.style.display = 'none');
|
await page.$eval('input', i => i.style.display = 'none');
|
||||||
await page.fill({selector: 'input', visible: false}, 'some value');
|
const invisibleError = await page.fill('input', 'some value').catch(e => e);
|
||||||
expect(await page.evaluate(() => result)).toBe('');
|
expect(invisibleError.message).toBe('Element is not visible');
|
||||||
|
|
||||||
|
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||||
|
await page.$eval('input', i => i.style.visibility = 'hidden');
|
||||||
|
const hiddenError = await page.fill('input', 'some value').catch(e => e);
|
||||||
|
expect(hiddenError.message).toBe('Element is hidden');
|
||||||
|
});
|
||||||
|
it('should be able to fill the body', async({page}) => {
|
||||||
|
await page.setContent(`<body contentEditable="true"></body>`);
|
||||||
|
await page.fill('body', 'some value');
|
||||||
|
expect(await page.evaluate(() => document.body.textContent)).toBe('some value');
|
||||||
|
});
|
||||||
|
it('should be able to fill when focus is in the wrong frame', async({page}) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div contentEditable="true"></div>
|
||||||
|
<iframe></iframe>
|
||||||
|
`);
|
||||||
|
await page.focus('iframe');
|
||||||
|
await page.fill('div', 'some value');
|
||||||
|
expect(await page.$eval('div', d => d.textContent)).toBe('some value');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user