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() 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 ## 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 }); 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 }; const params: channels.FrameExpectParams = { selector: this._selector, expression, ...options, isNot: !!options.isNot };
if (options.expectedValue) if (options.expectedValue)
params.expectedValue = serializeArgument(options.expectedValue); params.expectedValue = serializeArgument(options.expectedValue);

View file

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

View file

@ -24,7 +24,7 @@ import { toEqual } from './toEqual';
import { callLogText, toExpectedTextValues, toMatchText } from './toMatchText'; import { callLogText, toExpectedTextValues, toMatchText } from './toMatchText';
interface LocatorEx extends Locator { 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 { interface APIResponseEx extends APIResponse {
@ -34,10 +34,11 @@ interface APIResponseEx extends APIResponse {
export function toBeChecked( export function toBeChecked(
this: ReturnType<Expect['getState']>, this: ReturnType<Expect['getState']>,
locator: LocatorEx, locator: LocatorEx,
options?: { timeout?: number }, options?: { checked?: boolean, timeout?: number },
) { ) {
return toBeTruthy.call(this, 'toBeChecked', locator, 'Locator', async (isNot, timeout) => { 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); }, options);
} }

View file

@ -1,9 +1,17 @@
/** /**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. * Copyright (c) Microsoft Corporation.
* Modifications copyright (c) Microsoft Corporation.
* *
* This source code is licensed under the MIT license found in the * Licensed under the Apache License, Version 2.0 (the "License");
* LICENSE file in the root directory of this source tree. * 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'; import type * as expect from 'expect';
@ -69,9 +77,9 @@ declare global {
}): R; }): 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. * Asserts input is disabled.

View file

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