feat(expect): even more matchers (#7902)
This commit is contained in:
parent
600d82b17c
commit
1807142eb7
|
|
@ -28,12 +28,14 @@ import {
|
||||||
toContainText,
|
toContainText,
|
||||||
toHaveAttr,
|
toHaveAttr,
|
||||||
toHaveClass,
|
toHaveClass,
|
||||||
|
toHaveCount,
|
||||||
toHaveCSS,
|
toHaveCSS,
|
||||||
toHaveData,
|
toHaveData,
|
||||||
toHaveId,
|
toHaveId,
|
||||||
toHaveLength,
|
|
||||||
toHaveProp,
|
toHaveProp,
|
||||||
toHaveText,
|
toHaveText,
|
||||||
|
toHaveTitle,
|
||||||
|
toHaveURL,
|
||||||
toHaveValue
|
toHaveValue
|
||||||
} from './matchers/matchers';
|
} from './matchers/matchers';
|
||||||
import { toMatchSnapshot } from './matchers/toMatchSnapshot';
|
import { toMatchSnapshot } from './matchers/toMatchSnapshot';
|
||||||
|
|
@ -54,12 +56,14 @@ expectLibrary.extend({
|
||||||
toContainText,
|
toContainText,
|
||||||
toHaveAttr,
|
toHaveAttr,
|
||||||
toHaveClass,
|
toHaveClass,
|
||||||
|
toHaveCount,
|
||||||
toHaveCSS,
|
toHaveCSS,
|
||||||
toHaveData,
|
toHaveData,
|
||||||
toHaveId,
|
toHaveId,
|
||||||
toHaveLength,
|
|
||||||
toHaveProp,
|
toHaveProp,
|
||||||
toHaveText,
|
toHaveText,
|
||||||
|
toHaveTitle,
|
||||||
|
toHaveURL,
|
||||||
toHaveValue,
|
toHaveValue,
|
||||||
toMatchSnapshot,
|
toMatchSnapshot,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,49 +14,48 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import matchers from 'expect/build/matchers';
|
import { Locator, Page } from '../../..';
|
||||||
import { Locator } from '../../..';
|
|
||||||
import type { Expect } from '../types';
|
import type { Expect } from '../types';
|
||||||
import { toBeTruthy } from './toBeTruthy';
|
import { toBeTruthy } from './toBeTruthy';
|
||||||
import { toEqual } from './toEqual';
|
import { toEqual } from './toEqual';
|
||||||
import { toMatchText } from './toMatchText';
|
import { toMatchText } from './toMatchText';
|
||||||
|
|
||||||
export async function toBeChecked(
|
export function toBeChecked(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeChecked', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeChecked', locator, 'Locator', async timeout => {
|
||||||
return await locator.isChecked({ timeout });
|
return await locator.isChecked({ timeout });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toBeDisabled(
|
export function toBeDisabled(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeDisabled', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeDisabled', locator, 'Locator', async timeout => {
|
||||||
return await locator.isDisabled({ timeout });
|
return await locator.isDisabled({ timeout });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toBeEditable(
|
export function toBeEditable(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeEditable', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeEditable', locator, 'Locator', async timeout => {
|
||||||
return await locator.isEditable({ timeout });
|
return await locator.isEditable({ timeout });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toBeEmpty(
|
export function toBeEmpty(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeEmpty', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeEmpty', locator, 'Locator', async timeout => {
|
||||||
return await locator.evaluate(element => {
|
return await locator.evaluate(element => {
|
||||||
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA')
|
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA')
|
||||||
return !(element as HTMLInputElement).value;
|
return !(element as HTMLInputElement).value;
|
||||||
|
|
@ -65,178 +64,212 @@ export async function toBeEmpty(
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toBeEnabled(
|
export function toBeEnabled(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeEnabled', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeEnabled', locator, 'Locator', async timeout => {
|
||||||
return await locator.isEnabled({ timeout });
|
return await locator.isEnabled({ timeout });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toBeFocused(
|
export function toBeFocused(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeFocused', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeFocused', locator, 'Locator', async timeout => {
|
||||||
return await locator.evaluate(element => {
|
return await locator.evaluate(element => {
|
||||||
return document.activeElement === element;
|
return document.activeElement === element;
|
||||||
}, { timeout });
|
}, { timeout });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toBeHidden(
|
export function toBeHidden(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeHidden', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeHidden', locator, 'Locator', async timeout => {
|
||||||
return await locator.isHidden({ timeout });
|
return await locator.isHidden({ timeout });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toBeSelected(
|
export function toBeSelected(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeSelected', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeSelected', locator, 'Locator', async timeout => {
|
||||||
return await locator.evaluate(element => {
|
return await locator.evaluate(element => {
|
||||||
return (element as HTMLOptionElement).selected;
|
return (element as HTMLOptionElement).selected;
|
||||||
}, { timeout });
|
}, { timeout });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toBeVisible(
|
export function toBeVisible(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toBeTruthy.call(this, 'toBeVisible', locator, async timeout => {
|
return toBeTruthy.call(this, 'toBeVisible', locator, 'Locator', async timeout => {
|
||||||
return await locator.isVisible({ timeout });
|
return await locator.isVisible({ timeout });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toContainText(
|
export function toContainText(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
expected: string,
|
expected: string,
|
||||||
options?: { timeout?: number, useInnerText?: boolean },
|
options?: { timeout?: number, useInnerText?: boolean },
|
||||||
) {
|
) {
|
||||||
return toMatchText.call(this, 'toContainText', locator, async timeout => {
|
return toMatchText.call(this, 'toContainText', locator, 'Locator', async timeout => {
|
||||||
if (options?.useInnerText)
|
if (options?.useInnerText)
|
||||||
return await locator.innerText({ timeout });
|
return await locator.innerText({ timeout });
|
||||||
return await locator.textContent() || '';
|
return await locator.textContent() || '';
|
||||||
}, expected, { ...options, matchSubstring: true });
|
}, expected, { ...options, matchSubstring: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toHaveAttr(
|
export function toHaveAttr(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
name: string,
|
name: string,
|
||||||
expected: string | RegExp,
|
expected: string | RegExp,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toMatchText.call(this, 'toHaveAttr', locator, async timeout => {
|
return toMatchText.call(this, 'toHaveAttr', locator, 'Locator', async timeout => {
|
||||||
return await locator.getAttribute(name, { timeout }) || '';
|
return await locator.getAttribute(name, { timeout }) || '';
|
||||||
}, expected, options);
|
}, expected, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toHaveClass(
|
export function toHaveClass(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
expected: string,
|
expected: string | RegExp | string[],
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toMatchText.call(this, 'toHaveClass', locator, async timeout => {
|
if (Array.isArray(expected)) {
|
||||||
return await locator.evaluate(element => element.className, { timeout });
|
return toEqual.call(this, 'toHaveClass', locator, 'Locator', async () => {
|
||||||
}, expected, { ...options, matchSubstring: true });
|
return await locator.evaluateAll(ee => ee.map(e => e.className));
|
||||||
|
}, expected, options);
|
||||||
|
} else {
|
||||||
|
return toMatchText.call(this, 'toHaveClass', locator, 'Locator', async timeout => {
|
||||||
|
return await locator.evaluate(element => element.className, { timeout });
|
||||||
|
}, expected, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toHaveCSS(
|
export function toHaveCount(
|
||||||
|
this: ReturnType<Expect['getState']>,
|
||||||
|
locator: Locator,
|
||||||
|
expected: number,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
) {
|
||||||
|
return toEqual.call(this, 'toHaveCount', locator, 'Locator', async timeout => {
|
||||||
|
return await locator.count();
|
||||||
|
}, expected, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHaveCSS(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
name: string,
|
name: string,
|
||||||
expected: string | RegExp,
|
expected: string | RegExp,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toMatchText.call(this, 'toHaveCSS', locator, async timeout => {
|
return toMatchText.call(this, 'toHaveCSS', locator, 'Locator', async timeout => {
|
||||||
return await locator.evaluate(async (element, name) => {
|
return await locator.evaluate(async (element, name) => {
|
||||||
return (window.getComputedStyle(element) as any)[name];
|
return (window.getComputedStyle(element) as any)[name];
|
||||||
}, name, { timeout });
|
}, name, { timeout });
|
||||||
}, expected, options);
|
}, expected, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toHaveData(
|
export function toHaveData(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
name: string,
|
name: string,
|
||||||
expected: string | RegExp,
|
expected: string | RegExp,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toMatchText.call(this, 'toHaveData', locator, async timeout => {
|
return toMatchText.call(this, 'toHaveData', locator, 'Locator', async timeout => {
|
||||||
return await locator.getAttribute('data-' + name, { timeout }) || '';
|
return await locator.getAttribute('data-' + name, { timeout }) || '';
|
||||||
}, expected, options);
|
}, expected, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toHaveId(
|
export function toHaveId(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
expected: string | RegExp,
|
expected: string | RegExp,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toMatchText.call(this, 'toHaveId', locator, async timeout => {
|
return toMatchText.call(this, 'toHaveId', locator, 'Locator', async timeout => {
|
||||||
return await locator.getAttribute('id', { timeout }) || '';
|
return await locator.getAttribute('id', { timeout }) || '';
|
||||||
}, expected, options);
|
}, expected, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toHaveLength(
|
export function toHaveProp(
|
||||||
this: ReturnType<Expect['getState']>,
|
|
||||||
locator: Locator,
|
|
||||||
expected: number,
|
|
||||||
options?: { timeout?: number },
|
|
||||||
) {
|
|
||||||
if (typeof locator !== 'object' || locator.constructor.name !== 'Locator')
|
|
||||||
return matchers.toHaveLength.call(this, locator, expected);
|
|
||||||
return toEqual.call(this, 'toHaveLength', locator, async timeout => {
|
|
||||||
return await locator.count();
|
|
||||||
}, expected, { expectedType: 'number', ...options });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function toHaveProp(
|
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
name: string,
|
name: string,
|
||||||
expected: number,
|
expected: number,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toEqual.call(this, 'toHaveProp', locator, async timeout => {
|
return toEqual.call(this, 'toHaveProp', locator, 'Locator', async timeout => {
|
||||||
return await locator.evaluate((element, name) => (element as any)[name], name, { timeout });
|
return await locator.evaluate((element, name) => (element as any)[name], name, { timeout });
|
||||||
}, expected, { expectedType: 'number', ...options });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function toHaveText(
|
|
||||||
this: ReturnType<Expect['getState']>,
|
|
||||||
locator: Locator,
|
|
||||||
expected: string | RegExp,
|
|
||||||
options?: { timeout?: number, useInnerText?: boolean },
|
|
||||||
) {
|
|
||||||
return toMatchText.call(this, 'toHaveText', locator, async timeout => {
|
|
||||||
if (options?.useInnerText)
|
|
||||||
return await locator.innerText({ timeout });
|
|
||||||
return await locator.textContent() || '';
|
|
||||||
}, expected, options);
|
}, expected, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toHaveValue(
|
export function toHaveText(
|
||||||
|
this: ReturnType<Expect['getState']>,
|
||||||
|
locator: Locator,
|
||||||
|
expected: string | RegExp | string[],
|
||||||
|
options?: { timeout?: number, useInnerText?: boolean },
|
||||||
|
) {
|
||||||
|
if (Array.isArray(expected)) {
|
||||||
|
return toEqual.call(this, 'toHaveText', locator, 'Locator', async () => {
|
||||||
|
return locator.evaluateAll((ee, useInnerText) => {
|
||||||
|
return ee.map(e => useInnerText ? (e as HTMLElement).innerText : e.textContent || '');
|
||||||
|
}, options?.useInnerText);
|
||||||
|
}, expected, options);
|
||||||
|
} else {
|
||||||
|
return toMatchText.call(this, 'toHaveText', locator, 'Locator', async timeout => {
|
||||||
|
if (options?.useInnerText)
|
||||||
|
return await locator.innerText({ timeout });
|
||||||
|
return await locator.textContent() || '';
|
||||||
|
}, expected, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHaveTitle(
|
||||||
|
this: ReturnType<Expect['getState']>,
|
||||||
|
page: Page,
|
||||||
|
expected: string | RegExp,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
) {
|
||||||
|
return toMatchText.call(this, 'toHaveTitle', page, 'Page', async () => {
|
||||||
|
return await page.title();
|
||||||
|
}, expected, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHaveURL(
|
||||||
|
this: ReturnType<Expect['getState']>,
|
||||||
|
page: Page,
|
||||||
|
expected: string | RegExp,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
) {
|
||||||
|
return toMatchText.call(this, 'toHaveURL', page, 'Page', async () => {
|
||||||
|
return page.url();
|
||||||
|
}, expected, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHaveValue(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
expected: string | RegExp,
|
expected: string | RegExp,
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toMatchText.call(this, 'toHaveValue', locator, async timeout => {
|
return toMatchText.call(this, 'toHaveValue', locator, 'Locator', async timeout => {
|
||||||
return await locator.inputValue({ timeout });
|
return await locator.inputValue({ timeout });
|
||||||
}, expected, options);
|
}, expected, options);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,22 +18,22 @@ import {
|
||||||
matcherHint,
|
matcherHint,
|
||||||
MatcherHintOptions
|
MatcherHintOptions
|
||||||
} from 'jest-matcher-utils';
|
} from 'jest-matcher-utils';
|
||||||
import { Locator } from '../../..';
|
|
||||||
import { currentTestInfo } from '../globals';
|
import { currentTestInfo } from '../globals';
|
||||||
import type { Expect } from '../types';
|
import type { Expect } from '../types';
|
||||||
import { expectLocator, monotonicTime, pollUntilDeadline } from '../util';
|
import { expectType, monotonicTime, pollUntilDeadline } from '../util';
|
||||||
|
|
||||||
export async function toBeTruthy<T>(
|
export async function toBeTruthy<T>(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
matcherName: string,
|
matcherName: string,
|
||||||
locator: Locator,
|
receiver: any,
|
||||||
|
receiverType: string,
|
||||||
query: (timeout: number) => Promise<T>,
|
query: (timeout: number) => Promise<T>,
|
||||||
options: { timeout?: number } = {},
|
options: { timeout?: number } = {},
|
||||||
) {
|
) {
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
throw new Error(`${matcherName} must be called during the test`);
|
throw new Error(`${matcherName} must be called during the test`);
|
||||||
expectLocator(locator, matcherName);
|
expectType(receiver, receiverType, matcherName);
|
||||||
|
|
||||||
const matcherOptions: MatcherHintOptions = {
|
const matcherOptions: MatcherHintOptions = {
|
||||||
isNot: this.isNot,
|
isNot: this.isNot,
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,9 @@ import {
|
||||||
printReceived,
|
printReceived,
|
||||||
stringify
|
stringify
|
||||||
} from 'jest-matcher-utils';
|
} from 'jest-matcher-utils';
|
||||||
import { Locator } from '../../..';
|
|
||||||
import { currentTestInfo } from '../globals';
|
import { currentTestInfo } from '../globals';
|
||||||
import type { Expect } from '../types';
|
import type { Expect } from '../types';
|
||||||
import { expectLocator, monotonicTime, pollUntilDeadline } from '../util';
|
import { expectType, monotonicTime, pollUntilDeadline } from '../util';
|
||||||
|
|
||||||
// Omit colon and one or more spaces, so can call getLabelPrinter.
|
// Omit colon and one or more spaces, so can call getLabelPrinter.
|
||||||
const EXPECTED_LABEL = 'Expected';
|
const EXPECTED_LABEL = 'Expected';
|
||||||
|
|
@ -40,7 +39,8 @@ const isExpand = (expand?: boolean): boolean => expand !== false;
|
||||||
export async function toEqual<T>(
|
export async function toEqual<T>(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
matcherName: string,
|
matcherName: string,
|
||||||
locator: Locator,
|
receiver: any,
|
||||||
|
receiverType: string,
|
||||||
query: (timeout: number) => Promise<T>,
|
query: (timeout: number) => Promise<T>,
|
||||||
expected: T,
|
expected: T,
|
||||||
options: { timeout?: number } = {},
|
options: { timeout?: number } = {},
|
||||||
|
|
@ -48,7 +48,7 @@ export async function toEqual<T>(
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
throw new Error(`${matcherName} must be called during the test`);
|
throw new Error(`${matcherName} must be called during the test`);
|
||||||
expectLocator(locator, matcherName);
|
expectType(receiver, receiverType, matcherName);
|
||||||
|
|
||||||
const matcherOptions: MatcherHintOptions = {
|
const matcherOptions: MatcherHintOptions = {
|
||||||
comment: 'deep equality',
|
comment: 'deep equality',
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,15 @@ import {
|
||||||
printReceived,
|
printReceived,
|
||||||
printWithType,
|
printWithType,
|
||||||
} from 'jest-matcher-utils';
|
} from 'jest-matcher-utils';
|
||||||
import { Locator } from '../../..';
|
|
||||||
import { currentTestInfo } from '../globals';
|
import { currentTestInfo } from '../globals';
|
||||||
import type { Expect } from '../types';
|
import type { Expect } from '../types';
|
||||||
import { expectLocator, monotonicTime, pollUntilDeadline } from '../util';
|
import { expectType, monotonicTime, pollUntilDeadline } from '../util';
|
||||||
|
|
||||||
export async function toMatchText(
|
export async function toMatchText(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
matcherName: string,
|
matcherName: string,
|
||||||
locator: Locator,
|
receiver: any,
|
||||||
|
receiverType: string,
|
||||||
query: (timeout: number) => Promise<string>,
|
query: (timeout: number) => Promise<string>,
|
||||||
expected: string | RegExp,
|
expected: string | RegExp,
|
||||||
options: { timeout?: number, matchSubstring?: boolean } = {},
|
options: { timeout?: number, matchSubstring?: boolean } = {},
|
||||||
|
|
@ -44,7 +44,7 @@ export async function toMatchText(
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
throw new Error(`${matcherName} must be called during the test`);
|
throw new Error(`${matcherName} must be called during the test`);
|
||||||
expectLocator(locator, matcherName);
|
expectType(receiver, receiverType, matcherName);
|
||||||
|
|
||||||
const matcherOptions: MatcherHintOptions = {
|
const matcherOptions: MatcherHintOptions = {
|
||||||
isNot: this.isNot,
|
isNot: this.isNot,
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ export function errorWithLocation(location: Location, message: string) {
|
||||||
return new Error(`${formatLocation(location)}: ${message}`);
|
return new Error(`${formatLocation(location)}: ${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expectLocator(receiver: any, matcherName: string) {
|
export function expectType(receiver: any, type: string, matcherName: string) {
|
||||||
if (typeof receiver !== 'object' || receiver.constructor.name !== 'Locator')
|
if (typeof receiver !== 'object' || receiver.constructor.name !== type)
|
||||||
throw new Error(`${matcherName} can be only used with Locator object`);
|
throw new Error(`${matcherName} can be only used with ${type} object`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import { test, expect, stripAscii } from './playwright-test-fixtures';
|
import { test, expect, stripAscii } from './playwright-test-fixtures';
|
||||||
|
|
||||||
test('should support toHaveLength', async ({ runInlineTest }) => {
|
test('should support toHaveCount', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
const { test } = pwt;
|
const { test } = pwt;
|
||||||
|
|
@ -24,8 +24,7 @@ test('should support toHaveLength', async ({ runInlineTest }) => {
|
||||||
test('pass', async ({ page }) => {
|
test('pass', async ({ page }) => {
|
||||||
await page.setContent('<select><option>One</option><option>Two</option></select>');
|
await page.setContent('<select><option>One</option><option>Two</option></select>');
|
||||||
const locator = page.locator('option');
|
const locator = page.locator('option');
|
||||||
await expect(locator).toHaveLength(2);
|
await expect(locator).toHaveCount(2);
|
||||||
await expect([1, 2]).toHaveLength(2);
|
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { workers: 1 });
|
}, { workers: 1 });
|
||||||
|
|
@ -68,19 +67,93 @@ test('should support toHaveClass', async ({ runInlineTest }) => {
|
||||||
test('pass', async ({ page }) => {
|
test('pass', async ({ page }) => {
|
||||||
await page.setContent('<div class="foo bar baz"></div>');
|
await page.setContent('<div class="foo bar baz"></div>');
|
||||||
const locator = page.locator('div');
|
const locator = page.locator('div');
|
||||||
await expect(locator).toHaveClass('foo');
|
await expect(locator).toHaveClass('foo bar baz');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fail', async ({ page }) => {
|
test('fail', async ({ page }) => {
|
||||||
await page.setContent('<div class="bar baz"></div>');
|
await page.setContent('<div class="bar baz"></div>');
|
||||||
const locator = page.locator('div');
|
const locator = page.locator('div');
|
||||||
await expect(locator).toHaveClass('foo', { timeout: 1000 });
|
await expect(locator).toHaveClass('foo bar baz', { timeout: 1000 });
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { workers: 1 });
|
}, { workers: 1 });
|
||||||
const output = stripAscii(result.output);
|
const output = stripAscii(result.output);
|
||||||
expect(output).toContain('expect(locator).toHaveClass');
|
expect(output).toContain('expect(locator).toHaveClass');
|
||||||
expect(output).toContain('Expected substring: \"foo\"');
|
expect(output).toContain('Expected string: \"foo bar baz\"');
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.failed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support toHaveClass w/ array', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent('<div class="foo"></div><div class="bar"></div><div class="baz"></div>');
|
||||||
|
const locator = page.locator('div');
|
||||||
|
await expect(locator).toHaveClass(['foo', 'bar', 'baz']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fail', async ({ page }) => {
|
||||||
|
await page.setContent('<div class="foo"></div><div class="bar"></div><div class="bar"></div>');
|
||||||
|
const locator = page.locator('div');
|
||||||
|
await expect(locator).toHaveClass(['foo', 'bar', 'baz'], { timeout: 1000 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
const output = stripAscii(result.output);
|
||||||
|
expect(output).toContain('expect(received).toHaveClass(expected)');
|
||||||
|
expect(output).toContain('- \"baz\",');
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.failed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support toHaveTitle', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent('<title>Hello</title>');
|
||||||
|
await expect(page).toHaveTitle('Hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fail', async ({ page }) => {
|
||||||
|
await page.setContent('<title>Bye</title>');
|
||||||
|
await expect(page).toHaveTitle('Hello', { timeout: 100 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
const output = stripAscii(result.output);
|
||||||
|
expect(output).toContain('expect(page).toHaveTitle');
|
||||||
|
expect(output).toContain('Expected string: \"Hello\"');
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.failed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support toHaveURL', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.goto('data:text/html,<div>A</div>');
|
||||||
|
await expect(page).toHaveURL('data:text/html,<div>A</div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fail', async ({ page }) => {
|
||||||
|
await page.goto('data:text/html,<div>B</div>');
|
||||||
|
await expect(page).toHaveURL('wrong', { timeout: 100 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
const output = stripAscii(result.output);
|
||||||
|
expect(output).toContain('expect(page).toHaveURL');
|
||||||
|
expect(output).toContain('Expected string: \"wrong\"');
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,33 @@ test('should support toHaveText w/ text', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should support toHaveText w/ array', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent('<div>Text 1</div><div>Text 2</div>');
|
||||||
|
const locator = page.locator('div');
|
||||||
|
await expect(locator).toHaveText(['Text 1', 'Text 2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fail', async ({ page }) => {
|
||||||
|
await page.setContent('<div>Text 1</div><div>Text 3</div>');
|
||||||
|
const locator = page.locator('div');
|
||||||
|
await expect(locator).toHaveText(['Text 1', 'Text 2'], { timeout: 1000 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
const output = stripAscii(result.output);
|
||||||
|
expect(output).toContain('Error: expect(received).toHaveText(expected) // deep equality');
|
||||||
|
expect(output).toContain('await expect(locator).toHaveText');
|
||||||
|
expect(output).toContain('- \"Text 2\"');
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.failed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should support toHaveText eventually', async ({ runInlineTest }) => {
|
test('should support toHaveText eventually', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
|
|
|
||||||
23
types/testExpect.d.ts
vendored
23
types/testExpect.d.ts
vendored
|
|
@ -127,7 +127,12 @@ declare global {
|
||||||
/**
|
/**
|
||||||
* Asserts that DOM node has a given CSS class.
|
* Asserts that DOM node has a given CSS class.
|
||||||
*/
|
*/
|
||||||
toHaveClass(className: string, options?: { timeout?: number }): Promise<R>;
|
toHaveClass(className: string | RegExp | string[], options?: { timeout?: number }): Promise<R>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts number of DOM nodes matching given locator.
|
||||||
|
*/
|
||||||
|
toHaveCount(expected: number, options?: { timeout?: number }): Promise<R>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts element's computed CSS property `name` matches expected value.
|
* Asserts element's computed CSS property `name` matches expected value.
|
||||||
|
|
@ -150,11 +155,21 @@ declare global {
|
||||||
toHaveProp(name: string, value: any, options?: { timeout?: number }): Promise<R>;
|
toHaveProp(name: string, value: any, options?: { timeout?: number }): Promise<R>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts element's exact text content.
|
* Asserts element's text content.
|
||||||
*/
|
*/
|
||||||
toHaveText(expected: string | RegExp, options?: { timeout?: number, useInnerText?: boolean }): Promise<R>;
|
toHaveText(expected: string | RegExp | string[], options?: { timeout?: number, useInnerText?: boolean }): Promise<R>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Asserts page's title.
|
||||||
|
*/
|
||||||
|
toHaveTitle(expected: string | RegExp, options?: { timeout?: number }): Promise<R>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts page's title.
|
||||||
|
*/
|
||||||
|
toHaveURL(expected: string | RegExp, options?: { timeout?: number }): Promise<R>;
|
||||||
|
|
||||||
|
/**
|
||||||
* Asserts input element's value.
|
* Asserts input element's value.
|
||||||
*/
|
*/
|
||||||
toHaveValue(expected: string | RegExp, options?: { timeout?: number }): Promise<R>;
|
toHaveValue(expected: string | RegExp, options?: { timeout?: number }): Promise<R>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue