/** * Copyright 2017 Google Inc. All rights reserved. * Modifications copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { test as it, expect } from './pageTest'; async function giveItAChanceToResolve(page) { for (let i = 0; i < 5; i++) await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))); } it('element state checks should work as expected for label with zero-sized input', async ({ page, server }) => { await page.setContent(` `); // Visible checks the label. expect(await page.isVisible('text=Click me')).toBe(true); expect(await page.isHidden('text=Click me')).toBe(false); // Enabled checks the input. expect(await page.isEnabled('text=Click me')).toBe(false); expect(await page.isDisabled('text=Click me')).toBe(true); }); it('should wait for enclosing disabled button', async ({ page }) => { await page.setContent(''); const span = await page.$('text=Target'); let done = false; const promise = span.waitForElementState('disabled').then(() => done = true); await giveItAChanceToResolve(page); expect(done).toBe(false); await span.evaluate(span => (span.parentElement as HTMLButtonElement).disabled = true); await promise; }); it('should wait for enclosing button with a disabled fieldset', async ({ page }) => { await page.setContent('
'); const span = await page.$('text=Target'); let done = false; const promise = span.waitForElementState('enabled').then(() => done = true); await giveItAChanceToResolve(page); expect(done).toBe(false); await span.evaluate(span => (span.parentElement.parentElement as HTMLFieldSetElement).disabled = false); await promise; }); it('should wait for enclosing enabled button', async ({ page, server }) => { await page.setContent(''); const span = await page.$('text=Target'); let done = false; const promise = span.waitForElementState('enabled').then(() => done = true); await giveItAChanceToResolve(page); expect(done).toBe(false); await span.evaluate(span => (span.parentElement as HTMLButtonElement).disabled = false); await promise; }); it('should check the box outside shadow dom label', async ({ page }) => { await page.setContent('
'); await page.$eval('div', div => { const root = div.attachShadow({ mode: 'open' }); const label = document.createElement('label'); label.setAttribute('for', 'target'); label.textContent = 'Click me'; root.appendChild(label); const input = document.createElement('input'); input.setAttribute('type', 'checkbox'); input.setAttribute('id', 'target'); root.appendChild(input); }); await page.check('label'); expect(await page.$eval('input', input => input.checked)).toBe(true); }); 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' || browserName === 'webkit') { 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); }); } });