/**
* 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 type { Page } from '@playwright/test';
import { test as it, expect } from './pageTest';
async function giveItAChanceToResolve(page: 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(`
Click me
`);
// 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('Target ');
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('
Target ');
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: HTMLElement) => (span.parentElement!.parentElement as HTMLFieldSetElement).disabled = false);
await promise;
});
it('should wait for enclosing enabled button', async ({ page, server }) => {
await page.setContent('Target ');
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(`Choose a file `);
await page.setInputFiles('text=Choose a file', asset('file-to-upload.txt'));
expect(await page.$eval('input', (input: HTMLInputElement) => input.files!.length)).toBe(1);
expect(await page.$eval('input', (input: HTMLInputElement) => 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) => `Text ${dom} `;
const domLabelFor = (dom: string, options?: Options) => `Text ${dom}`;
const domStandalone = (dom: string) => dom;
const domInButton = (dom: string, options?: Options) => `Button ${dom} `;
const domInLink = (dom: string, options?: Options) => `Button ${dom} `;
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(`content `), visible: true, locator: 'label' },
{ dom: domLabelFor(`content `), visible: true, locator: 'label' },
{ dom: domLabelFor(`content `), 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(`content `), visible: false, locator: 'span' },
{ dom: domInButton(`content `), visible: false, locator: 'span' },
{ dom: domInButton(`content `, { hidden: true }), visible: false, locator: 'span' },
{ dom: domInLink(`content `), 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(`Dog Cat `), locator: 'label' },
{ dom: domLabelFor(`Dog Cat `), locator: 'label' },
{ dom: domStandalone(`Dog Cat `), locator: 'select' },
{ dom: domInButton(`Dog Cat `), locator: 'select' },
{ dom: domInLink(`Dog Cat `), locator: 'select' },
{ dom: domInButton(`Dog Cat `), 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);
});
}
});