feat(toBeChecked): allow passing checked: false (#10665)

This commit is contained in:
Pavel Feldman 2021-12-02 10:31:26 -08:00 committed by GitHub
parent 2ac9c08d0c
commit 31e0a63fcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 21 deletions

View File

@ -280,8 +280,11 @@ locator = page.locator(".subscribe")
expect(locator).to_be_checked()
```
### option: LocatorAssertions.toBeChecked.timeout = %%-assertions-timeout-%%
### option: LocatorAssertions.toBeChecked.checked
* langs: js
- `checked` <[boolean]>
### option: LocatorAssertions.toBeChecked.timeout = %%-assertions-timeout-%%
## method: LocatorAssertions.toBeDisabled

View File

@ -234,7 +234,7 @@ export class Locator implements api.Locator {
await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, omitReturnValue: true, ...options });
}
async _expect(expression: string, options: FrameExpectOptions): Promise<{ matches: boolean, received?: any, log?: string[] }> {
async _expect(expression: string, options: Omit<FrameExpectOptions, 'expectedValue'> & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[] }> {
const params: channels.FrameExpectParams = { selector: this._selector, expression, ...options, isNot: !!options.isNot };
if (options.expectedValue)
params.expectedValue = serializeArgument(options.expectedValue);

View File

@ -51,7 +51,7 @@ export type InjectedScriptPoll<T> = {
cancel: () => void,
};
export type ElementStateWithoutStable = 'visible' | 'hidden' | 'enabled' | 'disabled' | 'editable' | 'checked';
export type ElementStateWithoutStable = 'visible' | 'hidden' | 'enabled' | 'disabled' | 'editable' | 'checked' | 'unchecked';
export type ElementState = ElementStateWithoutStable | 'stable';
export interface SelectorEngineV2 {
@ -502,14 +502,17 @@ export class InjectedScript {
if (state === 'editable')
return !disabled && editable;
if (state === 'checked') {
if (['checkbox', 'radio'].includes(element.getAttribute('role') || ''))
return element.getAttribute('aria-checked') === 'true';
if (state === 'checked' || state === 'unchecked') {
if (['checkbox', 'radio'].includes(element.getAttribute('role') || '')) {
const result = element.getAttribute('aria-checked') === 'true';
return state === 'checked' ? result : !result;
}
if (element.nodeName !== 'INPUT')
throw this.createStacklessError('Not a checkbox or radio button');
if (!['radio', 'checkbox'].includes((element as HTMLInputElement).type.toLowerCase()))
throw this.createStacklessError('Not a checkbox or radio button');
return (element as HTMLInputElement).checked;
const result = (element as HTMLInputElement).checked;
return state === 'checked' ? result : !result;
}
throw this.createStacklessError(`Unexpected element state "${state}"`);
}
@ -899,6 +902,8 @@ export class InjectedScript {
let elementState: boolean | 'error:notconnected' | 'error:notcheckbox' | undefined;
if (expression === 'to.be.checked') {
elementState = progress.injectedScript.elementState(element, 'checked');
} else if (expression === 'to.be.unchecked') {
elementState = progress.injectedScript.elementState(element, 'unchecked');
} else if (expression === 'to.be.disabled') {
elementState = progress.injectedScript.elementState(element, 'disabled');
} else if (expression === 'to.be.editable') {

View File

@ -24,7 +24,7 @@ import { toEqual } from './toEqual';
import { callLogText, toExpectedTextValues, toMatchText } from './toMatchText';
interface LocatorEx extends Locator {
_expect(expression: string, options: FrameExpectOptions): Promise<{ matches: boolean, received?: any, log?: string[] }>;
_expect(expression: string, options: Omit<FrameExpectOptions, 'expectedValue'> & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[] }>;
}
interface APIResponseEx extends APIResponse {
@ -34,10 +34,11 @@ interface APIResponseEx extends APIResponse {
export function toBeChecked(
this: ReturnType<Expect['getState']>,
locator: LocatorEx,
options?: { timeout?: number },
options?: { checked?: boolean, timeout?: number },
) {
return toBeTruthy.call(this, 'toBeChecked', locator, 'Locator', async (isNot, timeout) => {
return await locator._expect('to.be.checked', { isNot, timeout });
const checked = !options || options.checked === undefined || options.checked === true;
return await locator._expect(checked ? 'to.be.checked' : 'to.be.unchecked', { isNot, timeout });
}, options);
}

View File

@ -1,9 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
* Modifications copyright (c) Microsoft Corporation.
* Copyright (c) Microsoft Corporation.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* 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 * as expect from 'expect';
@ -69,9 +77,9 @@ declare global {
}): R;
/**
* Asserts input is checked.
* Asserts input is checked (or unchecked if { checked: false } is passed).
*/
toBeChecked(options?: { timeout?: number }): Promise<R>;
toBeChecked(options?: { checked?: boolean, timeout?: number }): Promise<R>;
/**
* Asserts input is disabled.

View File

@ -27,6 +27,18 @@ test('should support toBeChecked', async ({ runInlineTest }) => {
await expect(locator).toBeChecked();
});
test('pass 2', async ({ page }) => {
await page.setContent('<input type=checkbox checked></input>');
const locator = page.locator('input');
await expect(locator).toBeChecked({ checked: true });
});
test('pass 3', async ({ page }) => {
await page.setContent('<input type=checkbox checked></input>');
const locator = page.locator('input');
await expect(locator).not.toBeChecked({ checked: false });
});
test('fail', async ({ page }) => {
await page.setContent('<input type=checkbox></input>');
const locator = page.locator('input');
@ -37,7 +49,7 @@ test('should support toBeChecked', async ({ runInlineTest }) => {
const output = stripAscii(result.output);
expect(output).toContain('Error: expect(received).toBeChecked()');
expect(output).toContain('expect(locator).toBeChecked');
expect(result.passed).toBe(1);
expect(result.passed).toBe(3);
expect(result.failed).toBe(1);
expect(result.exitCode).toBe(1);
});
@ -53,22 +65,34 @@ test('should support toBeChecked w/ not', async ({ runInlineTest }) => {
await expect(locator).not.toBeChecked();
});
test('pass 2', async ({ page }) => {
await page.setContent('<input type=checkbox></input>');
const locator = page.locator('input');
await expect(locator).toBeChecked({ checked: false });
});
test('fail not', async ({ page }) => {
await page.setContent('<input type=checkbox checked></input>');
const locator = page.locator('input');
await expect(locator).not.toBeChecked({ timeout: 1000 });
await expect(locator).not.toBeChecked({ timeout: 500 });
});
test('fail 2', async ({ page }) => {
await page.setContent('<input type=checkbox checked></input>');
const locator = page.locator('input');
await expect(locator).toBeChecked({ checked: false, timeout: 500 });
});
test('fail missing', async ({ page }) => {
await page.setContent('<div>no inputs here</div>');
const locator2 = page.locator('input2');
await expect(locator2).not.toBeChecked({ timeout: 1000 });
await expect(locator2).not.toBeChecked({ timeout: 500 });
});
`,
}, { workers: 1 });
const output = stripAscii(result.output);
expect(result.passed).toBe(1);
expect(result.failed).toBe(2);
expect(result.passed).toBe(2);
expect(result.failed).toBe(3);
expect(result.exitCode).toBe(1);
// fail not
expect(output).toContain('Error: expect(received).not.toBeChecked()');