feat(toBeChecked): allow passing checked: false (#10665)
This commit is contained in:
parent
2ac9c08d0c
commit
31e0a63fcd
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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') {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
20
packages/playwright-test/types/testExpect.d.ts
vendored
20
packages/playwright-test/types/testExpect.d.ts
vendored
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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()');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue