diff --git a/tests/page/page-click.spec.ts b/tests/page/page-click.spec.ts index 855d6722aa..e84f66e1d9 100644 --- a/tests/page/page-click.spec.ts +++ b/tests/page/page-click.spec.ts @@ -858,3 +858,29 @@ it('should not hang when frame is detached', async ({ page, server, mode }) => { expect(error).toBeTruthy(); expect(error.message).toMatch(/frame got detached|Frame was detached/); }); + +it('should climb dom for inner label with pointer-events:none', async ({ page }) => { + await page.setContent(''); + await page.click('text=Click target'); + expect(await page.evaluate('__CLICKED')).toBe(true); +}); + +it('should climb up to [role=button]', async ({ page }) => { + await page.setContent('
Click target
'); + await page.click('text=Click target'); + expect(await page.evaluate('__CLICKED')).toBe(true); +}); + +it('should climb up to a anchor', async ({ page }) => { + // For Firefox its not allowed to return anything: https://bugzilla.mozilla.org/show_bug.cgi?id=1392046 + // Note the intermediate div - it is necessary, otherwise is not recognized as a clickable link. + await page.setContent(`
Inner
`); + await page.click('#inner'); + expect(await page.evaluate('__CLICKED')).toBe(true); +}); + +it('should climb up to a [role=link]', async ({ page }) => { + await page.setContent(`
Inner
`); + await page.click('#inner'); + expect(await page.evaluate('__CLICKED')).toBe(true); +}); diff --git a/tests/page/retarget.spec.ts b/tests/page/retarget.spec.ts index b331949725..d955271852 100644 --- a/tests/page/retarget.spec.ts +++ b/tests/page/retarget.spec.ts @@ -71,52 +71,6 @@ it('should wait for enclosing enabled button', async ({ page, server }) => { await promise; }); -it('inputValue should work on label', async ({ page, server }) => { - await page.setContent(``); - await page.fill('input', 'foo'); - expect(await page.locator('label').inputValue()).toBe('foo'); -}); - -it('should get value of input with label', async ({ page }) => { - await page.setContent(``); - expect(await page.inputValue('text=Fill me')).toBe('some value'); - await expect(page.locator('text=Fill me')).toHaveValue('some value'); -}); - -it('should get value of input with span inside the label', async ({ page }) => { - await page.setContent(``); - expect(await page.inputValue('text=Fill me')).toBe('some value'); - await expect(page.locator('text=Fill me')).toHaveValue('some value'); -}); - -it('should get value of textarea with label', async ({ page }) => { - await page.setContent(``); - expect(await page.inputValue('text=Fill me')).toBe('hey'); - await expect(page.locator('text=Fill me')).toHaveValue('hey'); - - await page.fill('textarea', 'Look at this'); - expect(await page.inputValue('text=Fill me')).toBe('Look at this'); - await expect(page.locator('text=Fill me')).toHaveValue('Look at this'); -}); - -it('should check the box by label', async ({ page }) => { - await page.setContent(``); - await page.check('label'); - expect(await page.evaluate(() => window['checkbox'].checked)).toBe(true); -}); - -it('should check the box outside label', async ({ page }) => { - await page.setContent(`
`); - await page.check('label'); - expect(await page.evaluate(() => window['checkbox'].checked)).toBe(true); -}); - -it('should check the box inside label w/o id', async ({ page }) => { - await page.setContent(``); - await page.check('label'); - expect(await page.evaluate(() => window['checkbox'].checked)).toBe(true); -}); - it('should check the box outside shadow dom label', async ({ page }) => { await page.setContent('
'); await page.$eval('div', div => { @@ -134,86 +88,294 @@ it('should check the box outside shadow dom label', async ({ page }) => { expect(await page.$eval('input', input => input.checked)).toBe(true); }); -it('click should climb dom for inner label with pointer-events:none', async ({ page }) => { - await page.setContent(''); - await page.click('text=Click target'); - expect(await page.evaluate('__CLICKED')).toBe(true); -}); - -it('click should climb up to [role=button]', async ({ page }) => { - await page.setContent('
Click target
'); - await page.click('text=Click target'); - expect(await page.evaluate('__CLICKED')).toBe(true); -}); - -it('click should climb up to a anchor', async ({ page }) => { - // For Firefox its not allowed to return anything: https://bugzilla.mozilla.org/show_bug.cgi?id=1392046 - // Note the intermediate div - it is necessary, otherwise is not recognized as a clickable link. - await page.setContent(`
Inner
`); - await page.click('#inner'); - expect(await page.evaluate('__CLICKED')).toBe(true); -}); - -it('click should climb up to a [role=link]', async ({ page }) => { - await page.setContent(`
Inner
`); - await page.click('#inner'); - expect(await page.evaluate('__CLICKED')).toBe(true); -}); - - -it('should fill input with label', async ({ page }) => { - await page.setContent(``); - await page.fill('text=Fill me', 'some value'); - expect(await page.$eval('input', input => input.value)).toBe('some value'); -}); - -it('should fill input with label 2', async ({ page }) => { - await page.setContent(``); - await page.fill('text=Fill me', 'some value'); - expect(await page.$eval('input', input => input.value)).toBe('some value'); -}); - -it('should fill input with span inside the label', async ({ page }) => { - await page.setContent(``); - await page.fill('text=Fill me', 'some value'); - expect(await page.$eval('input', input => input.value)).toBe('some value'); -}); - -it('should fill input inside the label', async ({ page }) => { - await page.setContent(``); - await page.fill('input', 'some value'); - expect(await page.$eval('input', input => input.value)).toBe('some value'); -}); - -it('should fill textarea with label', async ({ page }) => { - await page.setContent(``); - await page.fill('text=Fill me', 'some value'); - expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some value'); -}); - -it('should selectOption with sibling label', async ({ page, server }) => { - await page.setContent(` - `); - await page.selectOption('text=Choose a pet', 'cat'); - expect(await page.$eval('select', select => select.options[select.selectedIndex].text)).toEqual('Cat'); -}); - -it('should selectOption with outer label', async ({ page, server }) => { - await page.setContent(``); - await page.selectOption('text=Choose a pet', 'cat'); - expect(await page.$eval('select', select => select.options[select.selectedIndex].text)).toEqual('Cat'); -}); - it('setInputFiles should work with label', async ({ page, asset }) => { await page.setContent(``); await page.setInputFiles('text=Choose a file', asset('file-to-upload.txt')); expect(await page.$eval('input', input => input.files.length)).toBe(1); expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); }); + +type Options = { disabled?: boolean, hidden?: boolean, readonly?: boolean }; +const optionsToAttributes = (options: Options | undefined) => ` ${options?.disabled ? 'disabled' : ''} ${options?.hidden ? 'hidden' : ''} ${options?.readonly ? 'readonly' : ''} `; +const domInLabel = (dom: string, options?: Options) => ``; +const domLabelFor = (dom: string, options?: Options) => `${dom}`; +const domStandalone = (dom: string) => dom; +const domInButton = (dom: string, options?: Options) => ``; +const domInLink = (dom: string, options?: Options) => ``; + +it('enabled/disabled retargeting', async ({ page, asset }) => { + const cases = [ + { dom: domInLabel(``), enabled: true, locator: 'label' }, + { dom: domLabelFor(``), enabled: true, locator: 'label' }, + { dom: domStandalone(``), enabled: true, locator: 'input' }, + { dom: domInButton(``), enabled: true, locator: 'input' }, + { dom: domInLink(``), enabled: true, locator: 'input' }, + { dom: domInButton(``, { disabled: true }), enabled: true, locator: 'input' }, + + { dom: domInLabel(``), enabled: false, locator: 'label' }, + { dom: domLabelFor(``), enabled: false, locator: 'label' }, + { dom: domStandalone(``), enabled: false, locator: 'input' }, + { dom: domInButton(``), enabled: false, locator: 'input' }, + { dom: domInLink(``), enabled: false, locator: 'input' }, + { dom: domInButton(``, { disabled: true }), enabled: false, locator: 'input' }, + ]; + for (const { dom, enabled, locator } of cases) { + await it.step(`"${locator}" in "${dom}" should be enabled=${enabled}`, async () => { + await page.setContent(dom); + const target = page.locator(locator); + const handle = await page.$(locator); + expect(await target.isEnabled()).toBe(enabled); + expect(await target.isDisabled()).toBe(!enabled); + if (enabled) { + await expect(target).toBeEnabled(); + await expect(target).not.toBeDisabled(); + await handle.waitForElementState('enabled'); + } else { + await expect(target).not.toBeEnabled(); + await expect(target).toBeDisabled(); + await handle.waitForElementState('disabled'); + } + }); + } +}); + +it('visible/hidden retargeting', async ({ page, asset }) => { + const cases = [ + { dom: domInLabel(`content`), visible: true, locator: 'label' }, + { dom: domInLabel(``), visible: true, locator: 'label' }, + { dom: domLabelFor(`content`), visible: true, locator: 'label' }, + { dom: domLabelFor(``), visible: true, locator: 'label' }, + { dom: domStandalone(`content`), visible: true, locator: 'span' }, + { dom: domInButton(`content`), visible: true, locator: 'span' }, + { dom: domInLink(`content`), visible: true, locator: 'span' }, + + { dom: domInLabel(`content`, { hidden: true }), visible: false, locator: 'label' }, + { dom: domLabelFor(`content`, { hidden: true }), visible: false, locator: 'label' }, + { dom: domStandalone(``), visible: false, locator: 'span' }, + { dom: domInButton(``), visible: false, locator: 'span' }, + { dom: domInButton(`content`, { hidden: true }), visible: false, locator: 'span' }, + { dom: domInLink(``), visible: false, locator: 'span' }, + { dom: domInLink(`content`, { hidden: true }), visible: false, locator: 'span' }, + ]; + for (const { dom, visible, locator } of cases) { + await it.step(`"${locator}" in "${dom}" should be visible=${visible}`, async () => { + await page.setContent(dom); + const target = page.locator(locator); + const handle = await page.$(locator); + expect(await target.isVisible()).toBe(visible); + expect(await target.isHidden()).toBe(!visible); + if (visible) { + await expect(target).toBeVisible(); + await expect(target).not.toBeHidden(); + await handle.waitForElementState('visible'); + } else { + await expect(target).not.toBeVisible(); + await expect(target).toBeHidden(); + await handle.waitForElementState('hidden'); + } + }); + } +}); + +it('editable retargeting', async ({ page, asset }) => { + const cases = [ + { dom: domInLabel(``), editable: true, locator: 'label' }, + { dom: domLabelFor(``), editable: true, locator: 'label' }, + { dom: domStandalone(``), editable: true, locator: 'input' }, + { dom: domInButton(``), editable: true, locator: 'input' }, + { dom: domInLink(``), editable: true, locator: 'input' }, + { dom: domInButton(``, { readonly: true }), editable: true, locator: 'input' }, + + { dom: domInLabel(``), editable: false, locator: 'label' }, + { dom: domLabelFor(``), editable: false, locator: 'label' }, + { dom: domStandalone(``), editable: false, locator: 'input' }, + { dom: domInButton(``), editable: false, locator: 'input' }, + { dom: domInLink(``), editable: false, locator: 'input' }, + { dom: domInButton(``, { readonly: true }), editable: false, locator: 'input' }, + ]; + for (const { dom, editable, locator } of cases) { + await it.step(`"${locator}" in "${dom}" should be editable=${editable}`, async () => { + await page.setContent(dom); + const target = page.locator(locator); + const handle = await page.$(locator); + expect(await target.isEditable()).toBe(editable); + if (editable) { + await expect(target).toBeEditable(); + await handle.waitForElementState('editable'); + } else { + await expect(target).not.toBeEditable(); + } + }); + } +}); + +it('input value retargeting', async ({ page, browserName }) => { + const cases = [ + { dom: domInLabel(``), locator: 'label' }, + { dom: domLabelFor(``), locator: 'label' }, + { dom: domStandalone(``), locator: 'input' }, + { dom: domInButton(``), locator: 'input' }, + { dom: domInLink(``), locator: 'input' }, + { dom: domInButton(``), locator: 'input' }, + ]; + for (const { dom, locator } of cases) { + await it.step(`"${locator}" in "${dom}" input value`, async () => { + await page.setContent(dom); + const target = page.locator(locator); + const handle = await page.$(locator); + + expect(await target.inputValue()).toBe(''); + expect(await handle.inputValue()).toBe(''); + await expect(target).toHaveValue(''); + + await target.fill('foo'); + expect(await target.inputValue()).toBe('foo'); + expect(await handle.inputValue()).toBe('foo'); + await expect(target).toHaveValue('foo'); + + await page.$eval('#target', (input: HTMLInputElement) => input.value = 'bar'); + expect(await target.inputValue()).toBe('bar'); + expect(await handle.inputValue()).toBe('bar'); + await expect(target).toHaveValue('bar'); + + await target.selectText(); + if (browserName === 'firefox') { + expect(await page.locator('#target').evaluate((el: HTMLInputElement) => el.selectionStart)).toBe(0); + expect(await page.locator('#target').evaluate((el: HTMLInputElement) => el.selectionEnd)).toBe(3); + } else { + expect(await page.evaluate(() => window.getSelection().toString())).toBe('bar'); + } + }); + } +}); + +it.fixme('selection retargeting', async ({ page, browserName }) => { + const cases = [ + { dom: domStandalone(`
content
`), locator: 'div' }, + { dom: domInButton(`
content
`), locator: 'div' }, + { dom: domInLink(`
content
`), locator: 'div' }, + { dom: domInButton(`
content
`), locator: 'div' }, + ]; + for (const { dom, locator } of cases) { + await it.step(`"${locator}" in "${dom}" text selection`, async () => { + await page.setContent(dom); + const target = page.locator(locator); + const handle = await page.$(locator); + + expect(await target.isEditable()).toBe(true); + expect(await handle.isEditable()).toBe(true); + await expect(page.locator('#target')).toHaveText('content'); + + await target.fill('foo'); + await expect(page.locator('#target')).toHaveText('foo'); + + await target.selectText(); + if (browserName === 'firefox') { + expect(await page.$eval('#target', target => { + const selection = window.getSelection(); + return selection.anchorNode === target && selection.focusNode === target; + })).toBe(true); + } else { + expect(await page.evaluate(() => window.getSelection().toString())).toBe('foo'); + } + }); + } +}); + +it('select options retargeting', async ({ page }) => { + const cases = [ + { dom: domInLabel(``), locator: 'label' }, + { dom: domLabelFor(``), locator: 'label' }, + { dom: domStandalone(``), locator: 'select' }, + { dom: domInButton(``), locator: 'select' }, + { dom: domInLink(``), locator: 'select' }, + { dom: domInButton(``), locator: 'select' }, + ]; + for (const { dom, locator } of cases) { + await it.step(`"${locator}" in "${dom}" select option`, async () => { + await page.setContent(dom); + const target = page.locator(locator); + const handle = await page.$(locator); + + expect(await target.inputValue()).toBe('dog'); + expect(await handle.inputValue()).toBe('dog'); + await expect(target).toHaveValue('dog'); + await expect(target).toHaveValues(['dog']); + + await target.selectOption('cat'); + expect(await target.inputValue()).toBe('cat'); + expect(await handle.inputValue()).toBe('cat'); + await expect(target).toHaveValue('cat'); + await expect(target).toHaveValues(['cat']); + }); + } +}); + +it('direct actions retargeting', async ({ page }) => { + const cases = [ + { dom: domInLabel(`
content
`), locator: 'div' }, + { dom: domLabelFor(`
content
`), locator: 'div' }, + { dom: domStandalone(`
content
`), locator: 'div' }, + { dom: domInButton(`
content
`), locator: 'div' }, + { dom: domInLink(`
content
`), locator: 'div' }, + { dom: domInButton(`
content
`), locator: 'div' }, + ]; + for (const { dom, locator } of cases) { + await it.step(`"${locator}" in "${dom}" direct actions`, async () => { + await page.setContent(dom); + const target = page.locator(locator); + + expect(await target.innerText()).toBe('content'); + expect(await target.textContent()).toBe('content'); + await expect(target).toHaveText('content'); + await expect(target).toContainText('content'); + await expect(target).not.toBeFocused(); + await expect(target).toHaveCount(1); + + await page.$eval('div', div => (div as any).foo = 'bar'); + await expect(target).toHaveJSProperty('foo', 'bar'); + + await page.$eval('div', div => div.classList.add('cls')); + await expect(target).toHaveClass('cls'); + + await page.$eval('div', div => div.id = 'myid'); + await expect(target).toHaveId('myid'); + await expect(target).toHaveAttribute('id', 'myid'); + expect(await target.getAttribute('id')).toBe('myid'); + }); + } +}); + +it('check retargeting', async ({ page, asset }) => { + const cases = [ + { dom: domInLabel(``), locator: 'label' }, + { dom: domLabelFor(``), locator: 'label' }, + { dom: domStandalone(``), locator: 'input' }, + { dom: domInButton(``), locator: 'input' }, + { dom: domInLink(``), locator: 'input' }, + { dom: domInButton(``), locator: 'input' }, + ]; + for (const { dom, locator } of cases) { + await it.step(`"${locator}" in "${dom}" check`, async () => { + await page.setContent(dom); + const target = page.locator(locator); + expect(await target.isChecked()).toBe(false); + await expect(target).not.toBeChecked(); + await expect(target).toBeChecked({ checked: false }); + + await page.$eval('input', (input: HTMLInputElement) => input.checked = true); + expect(await target.isChecked()).toBe(true); + await expect(target).toBeChecked(); + await expect(target).toBeChecked({ checked: true }); + + await target.uncheck(); + expect(await page.$eval('input', (input: HTMLInputElement) => input.checked)).toBe(false); + + await target.check(); + expect(await page.$eval('input', (input: HTMLInputElement) => input.checked)).toBe(true); + + await target.setChecked(false); + expect(await page.$eval('input', (input: HTMLInputElement) => input.checked)).toBe(false); + }); + } +});