mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-27 05:46:28 +03:00
fix: input events triggered by playwright actions should have composed=true (#28730)
Fixes https://github.com/microsoft/playwright/issues/28726
This commit is contained in:
parent
207585ef63
commit
ff99aa33b0
@ -720,8 +720,8 @@ export class InjectedScript {
|
|||||||
select.value = undefined as any;
|
select.value = undefined as any;
|
||||||
selectedOptions.forEach(option => option.selected = true);
|
selectedOptions.forEach(option => option.selected = true);
|
||||||
progress.log(' selected specified option(s)');
|
progress.log(' selected specified option(s)');
|
||||||
select.dispatchEvent(new Event('input', { 'bubbles': true }));
|
select.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||||
select.dispatchEvent(new Event('change', { 'bubbles': true }));
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
return selectedOptions.map(option => option.value);
|
return selectedOptions.map(option => option.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,7 +732,7 @@ export class InjectedScript {
|
|||||||
if (element.nodeName.toLowerCase() === 'input') {
|
if (element.nodeName.toLowerCase() === 'input') {
|
||||||
const input = element as HTMLInputElement;
|
const input = element as HTMLInputElement;
|
||||||
const type = input.type.toLowerCase();
|
const type = input.type.toLowerCase();
|
||||||
const kInputTypesToSetValue = new Set(['color', 'date', 'time', 'datetime', 'datetime-local', 'month', 'range', 'week']);
|
const kInputTypesToSetValue = new Set(['color', 'date', 'time', 'datetime-local', 'month', 'range', 'week']);
|
||||||
const kInputTypesToTypeInto = new Set(['', 'email', 'number', 'password', 'search', 'tel', 'text', 'url']);
|
const kInputTypesToTypeInto = new Set(['', 'email', 'number', 'password', 'search', 'tel', 'text', 'url']);
|
||||||
if (!kInputTypesToTypeInto.has(type) && !kInputTypesToSetValue.has(type)) {
|
if (!kInputTypesToTypeInto.has(type) && !kInputTypesToSetValue.has(type)) {
|
||||||
progress.log(` input of type "${type}" cannot be filled`);
|
progress.log(` input of type "${type}" cannot be filled`);
|
||||||
@ -749,8 +749,8 @@ export class InjectedScript {
|
|||||||
input.value = value;
|
input.value = value;
|
||||||
if (input.value !== value)
|
if (input.value !== value)
|
||||||
throw this.createStacklessError('Malformed value');
|
throw this.createStacklessError('Malformed value');
|
||||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
element.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
element.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
return 'done'; // We have already changed the value, no need to input it.
|
return 'done'; // We have already changed the value, no need to input it.
|
||||||
}
|
}
|
||||||
} else if (element.nodeName.toLowerCase() === 'textarea') {
|
} else if (element.nodeName.toLowerCase() === 'textarea') {
|
||||||
@ -852,8 +852,8 @@ export class InjectedScript {
|
|||||||
for (const file of files)
|
for (const file of files)
|
||||||
dt.items.add(file);
|
dt.items.add(file);
|
||||||
input.files = dt.files;
|
input.files = dt.files;
|
||||||
input.dispatchEvent(new Event('input', { 'bubbles': true }));
|
input.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||||
input.dispatchEvent(new Event('change', { 'bubbles': true }));
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
expectHitTarget(hitPoint: { x: number, y: number }, targetElement: Element) {
|
expectHitTarget(hitPoint: { x: number, y: number }, targetElement: Element) {
|
||||||
|
@ -89,6 +89,52 @@ it('should fill date input after clicking', async ({ page, server }) => {
|
|||||||
expect(await page.$eval('input', input => input.value)).toBe('2020-03-02');
|
expect(await page.$eval('input', input => input.value)).toBe('2020-03-02');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const [type, value] of Object.entries({
|
||||||
|
'color': '#aaaaaa',
|
||||||
|
'date': '2020-03-02',
|
||||||
|
'time': '13:15',
|
||||||
|
'datetime-local': '2020-03-02T13:15:30',
|
||||||
|
'month': '2020-03',
|
||||||
|
'range': '42',
|
||||||
|
'week': '2020-W50'
|
||||||
|
})) {
|
||||||
|
it(`input event.composed should be true and cross shadow dom boundary - ${type}`, async ({ page, server, browserName }) => {
|
||||||
|
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28726' });
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await page.setContent(`<body><script>
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const shadowRoot = div.attachShadow({mode: 'open'});
|
||||||
|
shadowRoot.innerHTML = '<input type=${type}></input>';
|
||||||
|
document.body.appendChild(div);
|
||||||
|
</script></body>`);
|
||||||
|
await page.locator('body').evaluate(select => {
|
||||||
|
(window as any).firedBodyEvents = [];
|
||||||
|
for (const event of ['input', 'change']) {
|
||||||
|
select.addEventListener(event, e => {
|
||||||
|
(window as any).firedBodyEvents.push(e.type + ':' + e.composed);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.locator('input').evaluate(select => {
|
||||||
|
(window as any).firedEvents = [];
|
||||||
|
for (const event of ['input', 'change']) {
|
||||||
|
select.addEventListener(event, e => {
|
||||||
|
(window as any).firedEvents.push(e.type + ':' + e.composed);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await page.locator('input').fill(value);
|
||||||
|
|
||||||
|
expect(await page.evaluate(() => window['firedEvents'])).toEqual(
|
||||||
|
(browserName !== 'chromium' && (type === 'month' || type === 'week')) ?
|
||||||
|
['input:true'] :
|
||||||
|
['input:true', 'change:false']
|
||||||
|
);
|
||||||
|
expect(await page.evaluate(() => window['firedBodyEvents'])).toEqual(['input:true']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it('should throw on incorrect date', async ({ page, browserName }) => {
|
it('should throw on incorrect date', async ({ page, browserName }) => {
|
||||||
it.skip(browserName === 'webkit', 'WebKit does not support date inputs');
|
it.skip(browserName === 'webkit', 'WebKit does not support date inputs');
|
||||||
|
|
||||||
|
@ -287,3 +287,37 @@ it('should wait for multiple options to be present', async ({ page, server }) =>
|
|||||||
const items = await selectPromise;
|
const items = await selectPromise;
|
||||||
expect(items).toStrictEqual(['green', 'scarlet']);
|
expect(items).toStrictEqual(['green', 'scarlet']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('input event.composed should be true and cross shadow dom boundary', async ({ page, server }) => {
|
||||||
|
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28726' });
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await page.setContent(`<body><script>
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const shadowRoot = div.attachShadow({mode: 'open'});
|
||||||
|
shadowRoot.innerHTML = \`<select>
|
||||||
|
<option value="black">Black</option>
|
||||||
|
<option value="blue">Blue</option>
|
||||||
|
</select>\`;
|
||||||
|
document.body.appendChild(div);
|
||||||
|
</script></body>`);
|
||||||
|
await page.locator('body').evaluate(select => {
|
||||||
|
(window as any).firedBodyEvents = [];
|
||||||
|
for (const event of ['input', 'change']) {
|
||||||
|
select.addEventListener(event, e => {
|
||||||
|
(window as any).firedBodyEvents.push(e.type + ':' + e.composed);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.locator('select').evaluate(select => {
|
||||||
|
(window as any).firedEvents = [];
|
||||||
|
for (const event of ['input', 'change']) {
|
||||||
|
select.addEventListener(event, e => {
|
||||||
|
(window as any).firedEvents.push(e.type + ':' + e.composed);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await page.selectOption('select', 'blue');
|
||||||
|
expect(await page.evaluate(() => window['firedEvents'])).toEqual(['input:true', 'change:false']);
|
||||||
|
expect(await page.evaluate(() => window['firedBodyEvents'])).toEqual(['input:true']);
|
||||||
|
});
|
||||||
|
@ -533,6 +533,41 @@ it('should emit input and change events', async ({ page, asset }) => {
|
|||||||
expect(events[1].type).toBe('change');
|
expect(events[1].type).toBe('change');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('input event.composed should be true and cross shadow dom boundary', async ({ page, server, asset }) => {
|
||||||
|
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28726' });
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await page.setContent(`<body><script>
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const shadowRoot = div.attachShadow({mode: 'open'});
|
||||||
|
shadowRoot.innerHTML = '<input type=file></input>';
|
||||||
|
document.body.appendChild(div);
|
||||||
|
</script></body>`);
|
||||||
|
await page.locator('body').evaluate(select => {
|
||||||
|
(window as any).firedBodyEvents = [];
|
||||||
|
for (const event of ['input', 'change']) {
|
||||||
|
select.addEventListener(event, e => {
|
||||||
|
(window as any).firedBodyEvents.push(e.type + ':' + e.composed);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.locator('input').evaluate(select => {
|
||||||
|
(window as any).firedEvents = [];
|
||||||
|
for (const event of ['input', 'change']) {
|
||||||
|
select.addEventListener(event, e => {
|
||||||
|
(window as any).firedEvents.push(e.type + ':' + e.composed);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await page.locator('input').setInputFiles({
|
||||||
|
name: 'test.txt',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
buffer: Buffer.from('this is a test')
|
||||||
|
});
|
||||||
|
expect(await page.evaluate(() => window['firedEvents'])).toEqual(['input:true', 'change:false']);
|
||||||
|
expect(await page.evaluate(() => window['firedBodyEvents'])).toEqual(['input:true']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should work for single file pick', async ({ page, server }) => {
|
it('should work for single file pick', async ({ page, server }) => {
|
||||||
await page.setContent(`<input type=file>`);
|
await page.setContent(`<input type=file>`);
|
||||||
const [fileChooser] = await Promise.all([
|
const [fileChooser] = await Promise.all([
|
||||||
|
Loading…
Reference in New Issue
Block a user