mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(expect): add text and true matchers (#7873)
This commit is contained in:
parent
74cd7584ac
commit
49e9f8c15e
@ -22,17 +22,14 @@ import { monotonicTime } from '../utils/utils';
|
||||
import { ElementHandle } from './elementHandle';
|
||||
import { Frame } from './frame';
|
||||
import { FilePayload, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types';
|
||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||
|
||||
export class Locator implements api.Locator {
|
||||
private _frame: Frame;
|
||||
private _selector: string;
|
||||
private _visibleSelector: string;
|
||||
private _timeoutSettings: TimeoutSettings;
|
||||
|
||||
constructor(frame: Frame, selector: string) {
|
||||
this._frame = frame;
|
||||
this._timeoutSettings = this._frame.page()._timeoutSettings;
|
||||
this._selector = selector;
|
||||
this._visibleSelector = selector + ' >> _visible=true';
|
||||
}
|
||||
@ -158,11 +155,11 @@ export class Locator implements api.Locator {
|
||||
}
|
||||
|
||||
async isHidden(options?: TimeoutOptions): Promise<boolean> {
|
||||
return this._frame.isHidden(this._visibleSelector, { strict: true, ...options });
|
||||
return this._frame.isHidden(this._selector, { strict: true, ...options });
|
||||
}
|
||||
|
||||
async isVisible(options?: TimeoutOptions): Promise<boolean> {
|
||||
return this._frame.isVisible(this._visibleSelector, { strict: true, ...options });
|
||||
return this._frame.isVisible(this._selector, { strict: true, ...options });
|
||||
}
|
||||
|
||||
async press(key: string, options: channels.ElementHandlePressOptions = {}): Promise<void> {
|
||||
|
||||
@ -14,11 +14,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Expect } from './types';
|
||||
import expectLibrary from 'expect';
|
||||
import { toBeChecked, toBeDisabled, toBeEditable, toBeEmpty, toBeEnabled, toBeFocused, toBeHidden, toBeVisible } from './matchers/toBeTruthy';
|
||||
import { toMatchSnapshot } from './matchers/toMatchSnapshot';
|
||||
import { toMatchText, toHaveText } from './matchers/toMatchText';
|
||||
import { toContainText, toHaveAttr, toHaveCSS, toHaveData, toHaveId, toHaveText, toHaveValue } from './matchers/toMatchText';
|
||||
import type { Expect } from './types';
|
||||
|
||||
export const expect: Expect = expectLibrary as any;
|
||||
expectLibrary.setState({ expand: false });
|
||||
expectLibrary.extend({ toMatchSnapshot, toMatchText, toHaveText });
|
||||
expectLibrary.extend({
|
||||
toBeChecked,
|
||||
toBeDisabled,
|
||||
toBeEditable,
|
||||
toBeEmpty,
|
||||
toBeEnabled,
|
||||
toBeFocused,
|
||||
toBeHidden,
|
||||
toBeVisible,
|
||||
toContainText,
|
||||
toHaveAttr,
|
||||
toHaveCSS,
|
||||
toHaveData,
|
||||
toHaveId,
|
||||
toHaveText,
|
||||
toHaveValue,
|
||||
toMatchSnapshot,
|
||||
});
|
||||
|
||||
151
src/test/matchers/toBeTruthy.ts
Normal file
151
src/test/matchers/toBeTruthy.ts
Normal file
@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* 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 {
|
||||
matcherHint,
|
||||
MatcherHintOptions,
|
||||
printReceived
|
||||
} from 'jest-matcher-utils';
|
||||
import { Locator } from '../../..';
|
||||
import { currentTestInfo } from '../globals';
|
||||
import type { Expect } from '../types';
|
||||
import { monotonicTime, pollUntilDeadline } from '../util';
|
||||
|
||||
|
||||
async function toBeTruthyImpl(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
matcherName: string,
|
||||
query: (timeout: number) => Promise<boolean>,
|
||||
options: { timeout?: number } = {},
|
||||
) {
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo)
|
||||
throw new Error(`toMatchSnapshot() must be called during the test`);
|
||||
|
||||
const matcherOptions: MatcherHintOptions = {
|
||||
isNot: this.isNot,
|
||||
promise: this.promise,
|
||||
};
|
||||
|
||||
let received: boolean;
|
||||
let pass = false;
|
||||
const timeout = options.timeout === 0 ? 0 : options.timeout || testInfo.timeout;
|
||||
const deadline = timeout ? monotonicTime() + timeout : 0;
|
||||
|
||||
try {
|
||||
await pollUntilDeadline(async () => {
|
||||
const remainingTime = deadline ? deadline - monotonicTime() : 0;
|
||||
received = await query(remainingTime);
|
||||
pass = !!received;
|
||||
return pass === !matcherOptions.isNot;
|
||||
}, deadline, 100);
|
||||
} catch (e) {
|
||||
pass = false;
|
||||
}
|
||||
|
||||
const message = () =>
|
||||
matcherHint(matcherName, undefined, '', matcherOptions) +
|
||||
'\n\n' +
|
||||
`Received: ${printReceived(received)}`;
|
||||
|
||||
return { message, pass };
|
||||
}
|
||||
|
||||
export async function toBeChecked(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthyImpl.call(this, 'toBeChecked', async timeout => {
|
||||
return await locator.isChecked({ timeout });
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function toBeEditable(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthyImpl.call(this, 'toBeEditable', async timeout => {
|
||||
return await locator.isEditable({ timeout });
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function toBeEnabled(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthyImpl.call(this, 'toBeEnabled', async timeout => {
|
||||
return await locator.isEnabled({ timeout });
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function toBeDisabled(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthyImpl.call(this, 'toBeDisabled', async timeout => {
|
||||
return await locator.isDisabled({ timeout });
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function toBeEmpty(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthyImpl.call(this, 'toBeEmpty', async timeout => {
|
||||
return await locator.evaluate(element => {
|
||||
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA')
|
||||
return !(element as HTMLInputElement).value;
|
||||
return !element.textContent?.trim();
|
||||
}, { timeout });
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function toBeHidden(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthyImpl.call(this, 'toBeHidden', async timeout => {
|
||||
return await locator.isHidden({ timeout });
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function toBeVisible(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthyImpl.call(this, 'toBeVisible', async timeout => {
|
||||
return await locator.isVisible({ timeout });
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function toBeFocused(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toBeTruthyImpl.call(this, 'toBeFocused', async timeout => {
|
||||
return await locator.evaluate(element => {
|
||||
return document.activeElement === element;
|
||||
}, { timeout });
|
||||
}, options);
|
||||
}
|
||||
@ -35,16 +35,15 @@ import { monotonicTime, pollUntilDeadline } from '../util';
|
||||
|
||||
async function toMatchTextImpl(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
matcherName: string,
|
||||
query: (timeout: number) => Promise<string>,
|
||||
expected: string | RegExp,
|
||||
exactMatch: boolean,
|
||||
options: { timeout?: number, useInnerText?: boolean } = {},
|
||||
options: { timeout?: number, matchSubstring?: boolean } = {},
|
||||
) {
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo)
|
||||
throw new Error(`toMatchSnapshot() must be called during the test`);
|
||||
|
||||
const matcherName = exactMatch ? 'toHaveText' : 'toMatchText';
|
||||
const matcherOptions: MatcherHintOptions = {
|
||||
isNot: this.isNot,
|
||||
promise: this.promise,
|
||||
@ -72,18 +71,22 @@ async function toMatchTextImpl(
|
||||
|
||||
try {
|
||||
await pollUntilDeadline(async () => {
|
||||
received = options?.useInnerText ? await locator.innerText() : await locator.textContent() || '';
|
||||
if (exactMatch)
|
||||
pass = expected === received;
|
||||
const remainingTime = deadline ? deadline - monotonicTime() : 0;
|
||||
received = await query(remainingTime);
|
||||
if (options.matchSubstring)
|
||||
pass = received.includes(expected as string);
|
||||
else if (typeof expected === 'string')
|
||||
pass = received === expected;
|
||||
else
|
||||
pass = typeof expected === 'string' ? received.includes(expected) : new RegExp(expected).test(received);
|
||||
pass = expected.test(received);
|
||||
|
||||
return pass === !matcherOptions.isNot;
|
||||
}, deadline, 100);
|
||||
} catch (e) {
|
||||
pass = false;
|
||||
}
|
||||
|
||||
const stringSubstring = exactMatch ? 'string' : 'substring';
|
||||
const stringSubstring = options.matchSubstring ? 'substring' : 'string';
|
||||
const message = pass
|
||||
? () =>
|
||||
typeof expected === 'string'
|
||||
@ -121,20 +124,88 @@ async function toMatchTextImpl(
|
||||
return { message, pass };
|
||||
}
|
||||
|
||||
export async function toMatchText(
|
||||
export async function toHaveText(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
expected: string | RegExp,
|
||||
options?: { timeout?: number, useInnerText?: boolean },
|
||||
) {
|
||||
return toMatchTextImpl.call(this, locator, expected, false, options);
|
||||
return toMatchTextImpl.call(this, 'toHaveText', async timeout => {
|
||||
if (options?.useInnerText)
|
||||
return await locator.innerText({ timeout });
|
||||
return await locator.textContent() || '';
|
||||
}, expected, options);
|
||||
}
|
||||
|
||||
export async function toHaveText(
|
||||
export async function toContainText(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
expected: string,
|
||||
options?: { timeout?: number, useInnerText?: boolean },
|
||||
) {
|
||||
return toMatchTextImpl.call(this, locator, expected, true, options);
|
||||
return toMatchTextImpl.call(this, 'toContainText', async timeout => {
|
||||
if (options?.useInnerText)
|
||||
return await locator.innerText({ timeout });
|
||||
return await locator.textContent() || '';
|
||||
}, expected, { ...options, matchSubstring: true });
|
||||
}
|
||||
|
||||
export async function toHaveAttr(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
name: string,
|
||||
expected: string | RegExp,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toMatchTextImpl.call(this, 'toHaveAttr', async timeout => {
|
||||
return await locator.getAttribute(name, { timeout }) || '';
|
||||
}, expected, options);
|
||||
}
|
||||
|
||||
export async function toHaveData(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
name: string,
|
||||
expected: string | RegExp,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toMatchTextImpl.call(this, 'toHaveData', async timeout => {
|
||||
return await locator.getAttribute('data-' + name, { timeout }) || '';
|
||||
}, expected, options);
|
||||
}
|
||||
|
||||
export async function toHaveCSS(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
name: string,
|
||||
expected: string | RegExp,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toMatchTextImpl.call(this, 'toHaveCSS', async timeout => {
|
||||
return await locator.evaluate(async (element, name) => {
|
||||
return (window.getComputedStyle(element) as any)[name];
|
||||
}, name, { timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
|
||||
export async function toHaveId(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
expected: string | RegExp,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toMatchTextImpl.call(this, 'toHaveId', async timeout => {
|
||||
return await locator.getAttribute('id', { timeout }) || '';
|
||||
}, expected, options);
|
||||
}
|
||||
|
||||
export async function toHaveValue(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: Locator,
|
||||
expected: string | RegExp,
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
return toMatchTextImpl.call(this, 'toHaveValue', async timeout => {
|
||||
return await locator.inputValue({ timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
@ -16,49 +16,55 @@
|
||||
|
||||
import { test, expect, stripAscii } from './playwright-test-fixtures';
|
||||
|
||||
test('should support toMatchText', async ({ runInlineTest }) => {
|
||||
test('should support toHaveText w/ regex', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const handle = page.locator('#node');
|
||||
await expect(handle).toMatchText(/Text/);
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveText(/Text/);
|
||||
});
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const handle = page.locator('#node');
|
||||
await expect(handle).toMatchText(/Text 2/, { timeout: 100 });
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveText(/Text 2/, { timeout: 100 });
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
const output = stripAscii(result.output);
|
||||
expect(output).toContain('Error: expect(received).toMatchText(expected)');
|
||||
expect(output).toContain('Error: expect(received).toHaveText(expected)');
|
||||
expect(output).toContain('Expected pattern: /Text 2/');
|
||||
expect(output).toContain('Received string: "Text content"');
|
||||
expect(output).toContain('expect(handle).toMatchText');
|
||||
expect(output).toContain('expect(locator).toHaveText');
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test('should support toHaveText', async ({ runInlineTest }) => {
|
||||
test('should support toHaveText w/ text', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const handle = page.locator('#node');
|
||||
await expect(handle).toHaveText('Text content');
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveText('Text content');
|
||||
});
|
||||
|
||||
test('pass contain', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toContainText('Text');
|
||||
});
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const handle = page.locator('#node');
|
||||
await expect(handle).toHaveText('Text', { timeout: 100 });
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveText('Text', { timeout: 100 });
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
@ -66,23 +72,23 @@ test('should support toHaveText', async ({ runInlineTest }) => {
|
||||
expect(output).toContain('Error: expect(received).toHaveText(expected)');
|
||||
expect(output).toContain('Expected string: "Text"');
|
||||
expect(output).toContain('Received string: "Text content"');
|
||||
expect(output).toContain('expect(handle).toHaveText');
|
||||
expect(result.passed).toBe(1);
|
||||
expect(output).toContain('expect(locator).toHaveText');
|
||||
expect(result.passed).toBe(2);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test('should support toMatchText eventually', async ({ runInlineTest }) => {
|
||||
test('should support toHaveText eventually', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass eventually', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const handle = page.locator('#node');
|
||||
const locator = page.locator('#node');
|
||||
await Promise.all([
|
||||
expect(handle).toMatchText(/Text 2/),
|
||||
page.waitForTimeout(1000).then(() => handle.evaluate(element => element.textContent = 'Text 2 content')),
|
||||
expect(locator).toHaveText(/Text 2/),
|
||||
page.waitForTimeout(1000).then(() => locator.evaluate(element => element.textContent = 'Text 2 content')),
|
||||
]);
|
||||
});
|
||||
`,
|
||||
@ -92,15 +98,15 @@ test('should support toMatchText eventually', async ({ runInlineTest }) => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should support toMatchText with innerText', async ({ runInlineTest }) => {
|
||||
test('should support toHaveText with innerText', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const handle = page.locator('#node');
|
||||
await expect(handle).toHaveText('Text content', { useInnerText: true });
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveText('Text content', { useInnerText: true });
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
@ -108,3 +114,83 @@ test('should support toMatchText with innerText', async ({ runInlineTest }) => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should support toHaveAttr', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveAttr('id', 'node');
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should support toHaveData', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveAttr('id', 'node');
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should support toHaveCSS', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<div id=node style="color: rgb(255, 0, 0)">Text content</div>');
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveCSS('color', 'rgb(255, 0, 0)');
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should support toHaveId', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).toHaveId('node');
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should support toHaveValue', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<input id=node></input>');
|
||||
const locator = page.locator('#node');
|
||||
await locator.fill('Text content');
|
||||
await expect(locator).toHaveValue('Text content');
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
146
tests/playwright-test/playwright.expect.true.spec.ts
Normal file
146
tests/playwright-test/playwright.expect.true.spec.ts
Normal file
@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 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 { test, expect, stripAscii } from './playwright-test-fixtures';
|
||||
|
||||
test('should support toBeChecked', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('pass', async ({ page }) => {
|
||||
await page.setContent('<input type=checkbox checked></input>');
|
||||
const locator = page.locator('input');
|
||||
await expect(locator).toBeChecked();
|
||||
});
|
||||
|
||||
test('pass not', async ({ page }) => {
|
||||
await page.setContent('<input type=checkbox></input>');
|
||||
const locator = page.locator('input');
|
||||
await expect(locator).not.toBeChecked();
|
||||
});
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
await page.setContent('<input type=checkbox></input>');
|
||||
const locator = page.locator('input');
|
||||
await expect(locator).toBeChecked({ timeout: 100 });
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
const output = stripAscii(result.output);
|
||||
expect(output).toContain('Error: expect(received).toBeChecked()');
|
||||
expect(output).toContain('expect(locator).toBeChecked');
|
||||
expect(result.passed).toBe(2);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test('should support toBeEditable, toBeEnabled, toBeDisabled, toBeEmpty', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('editable', async ({ page }) => {
|
||||
await page.setContent('<input></input>');
|
||||
const locator = page.locator('input');
|
||||
await expect(locator).toBeEditable();
|
||||
});
|
||||
|
||||
test('enabled', async ({ page }) => {
|
||||
await page.setContent('<button>Text</button>');
|
||||
const locator = page.locator('button');
|
||||
await expect(locator).toBeEnabled();
|
||||
});
|
||||
|
||||
test('disabled', async ({ page }) => {
|
||||
await page.setContent('<button disabled>Text</button>');
|
||||
const locator = page.locator('button');
|
||||
await expect(locator).toBeDisabled();
|
||||
});
|
||||
|
||||
test('empty input', async ({ page }) => {
|
||||
await page.setContent('<input></inpput>');
|
||||
const locator = page.locator('input');
|
||||
await expect(locator).toBeEmpty();
|
||||
});
|
||||
|
||||
test('non-empty input', async ({ page }) => {
|
||||
await page.setContent('<input value=text></input>');
|
||||
const locator = page.locator('input');
|
||||
await expect(locator).not.toBeEmpty();
|
||||
});
|
||||
|
||||
test('empty DOM', async ({ page }) => {
|
||||
await page.setContent('<div style="width: 50; height: 50px"></div>');
|
||||
const locator = page.locator('div');
|
||||
await expect(locator).toBeEmpty();
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(6);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should support toBeVisible, toBeHidden', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('visible', async ({ page }) => {
|
||||
await page.setContent('<input></input>');
|
||||
const locator = page.locator('input');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('not visible', async ({ page }) => {
|
||||
await page.setContent('<button style="display: none"></button>');
|
||||
const locator = page.locator('button');
|
||||
await expect(locator).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('hidden', async ({ page }) => {
|
||||
await page.setContent('<button style="display: none"></button>');
|
||||
const locator = page.locator('button');
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('not hidden', async ({ page }) => {
|
||||
await page.setContent('<input></input>');
|
||||
const locator = page.locator('input');
|
||||
await expect(locator).not.toBeHidden();
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(4);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should support toBeFocused', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('focused', async ({ page }) => {
|
||||
await page.setContent('<input></input>');
|
||||
const locator = page.locator('input');
|
||||
await locator.focus();
|
||||
await expect(locator).toBeFocused({ timeout: 1000 });
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
73
types/testExpect.d.ts
vendored
73
types/testExpect.d.ts
vendored
@ -72,13 +72,78 @@ declare global {
|
||||
/**
|
||||
* Asserts element's exact text content.
|
||||
*/
|
||||
toHaveText(expected: string, options?: { timeout?: number, useInnerText?: boolean }): Promise<R>;
|
||||
toHaveText(expected: string | RegExp, options?: { timeout?: number, useInnerText?: boolean }): Promise<R>;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asserts element's text content matches given pattern or contains given substring.
|
||||
*/
|
||||
toMatchText(expected: string | RegExp, options?: { timeout?: number, useInnerText?: boolean }): Promise<R>;
|
||||
}
|
||||
toContainText(expected: string, options?: { timeout?: number, useInnerText?: boolean }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts element's attributes `name` matches expected value.
|
||||
*/
|
||||
toHaveAttr(expected: string | RegExp, name: string, options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts element's data attribute data-`name` matches expected value.
|
||||
*/
|
||||
toHaveData(expected: string | RegExp, name: string, options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts element's computed CSS property `name` matches expected value.
|
||||
*/
|
||||
toHaveCSS(expected: string | RegExp, name: string, options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts element's `id` attribute matches expected value.
|
||||
*/
|
||||
toHaveId(expected: string | RegExp, options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts input element's value.
|
||||
*/
|
||||
toHaveValue(expected: string | RegExp, options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts input is checked.
|
||||
*/
|
||||
toBeChecked(options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts input is editable.
|
||||
*/
|
||||
toBeEditable(options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts input is enabled.
|
||||
*/
|
||||
toBeEnabled(options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts input is disabled.
|
||||
*/
|
||||
toBeDisabled(options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts given DOM node or input has no text content or no input value.
|
||||
*/
|
||||
toBeEmpty(options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts given DOM node is hidden or detached from DOM.
|
||||
*/
|
||||
toBeHidden(options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts given DOM node visible on the screen.
|
||||
*/
|
||||
toBeVisible(options?: { timeout?: number }): Promise<R>;
|
||||
|
||||
/**
|
||||
* Asserts given DOM is a focused (active) in document.
|
||||
*/
|
||||
toBeFocused(options?: { timeout?: number }): Promise<R>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
types/types.d.ts
vendored
33
types/types.d.ts
vendored
@ -6968,18 +6968,6 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
|
||||
*
|
||||
*/
|
||||
export interface Locator {
|
||||
/**
|
||||
* Resolves given locator to the first matching DOM element. If no elements matching the query are visible, waits for them
|
||||
* up to a given timeout. If multiple elements match the selector, throws.
|
||||
* @param options
|
||||
*/
|
||||
elementHandle(options?: {
|
||||
timeout?: number;
|
||||
}): Promise<null|ElementHandle<SVGElement | HTMLElement>>;
|
||||
/**
|
||||
* Resolves given locator to all matching DOM elements.
|
||||
*/
|
||||
elementHandles(): Promise<null|ElementHandle<SVGElement | HTMLElement>[]>;
|
||||
/**
|
||||
* Returns the return value of `pageFunction`.
|
||||
*
|
||||
@ -6999,8 +6987,12 @@ export interface Locator {
|
||||
* @param arg Optional argument to pass to `pageFunction`.
|
||||
* @param options
|
||||
*/
|
||||
evaluate<R, Arg>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, Arg, R>, arg: Arg): Promise<R>;
|
||||
evaluate<R>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, void, R>): Promise<R>;
|
||||
evaluate<R, Arg>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, Arg, R>, arg: Arg, options?: {
|
||||
timeout?: number;
|
||||
}): Promise<R>;
|
||||
evaluate<R>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, void, R>, options?: {
|
||||
timeout?: number;
|
||||
}): Promise<R>;
|
||||
/**
|
||||
* The method finds all elements matching the specified locator and passes an array of matched elements as a first argument
|
||||
* to `pageFunction`. Returns the result of `pageFunction` invocation.
|
||||
@ -7020,6 +7012,14 @@ export interface Locator {
|
||||
*/
|
||||
evaluateAll<R, Arg>(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], Arg, R>, arg: Arg): Promise<R>;
|
||||
evaluateAll<R>(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], void, R>): Promise<R>;
|
||||
/**
|
||||
* Resolves given locator to the first matching DOM element. If no elements matching the query are visible, waits for them
|
||||
* up to a given timeout. If multiple elements match the selector, throws.
|
||||
* @param options
|
||||
*/
|
||||
elementHandle(options?: {
|
||||
timeout?: number;
|
||||
}): Promise<null|ElementHandle<SVGElement | HTMLElement>>;
|
||||
/**
|
||||
* This method returns the bounding box of the element, or `null` if the element is not visible. The bounding box is
|
||||
* calculated relative to the main frame viewport - which is usually the same as the browser window.
|
||||
@ -7317,6 +7317,11 @@ export interface Locator {
|
||||
timeout?: number;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Resolves given locator to all matching DOM elements.
|
||||
*/
|
||||
elementHandles(): Promise<Array<ElementHandle>>;
|
||||
|
||||
/**
|
||||
* Returns the return value of `pageFunction` as a [JSHandle].
|
||||
*
|
||||
|
||||
13
utils/generate_types/overrides.d.ts
vendored
13
utils/generate_types/overrides.d.ts
vendored
@ -141,14 +141,17 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
|
||||
}
|
||||
|
||||
export interface Locator {
|
||||
evaluate<R, Arg>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, Arg, R>, arg: Arg, options?: {
|
||||
timeout?: number;
|
||||
}): Promise<R>;
|
||||
evaluate<R>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, void, R>, options?: {
|
||||
timeout?: number;
|
||||
}): Promise<R>;
|
||||
evaluateAll<R, Arg>(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], Arg, R>, arg: Arg): Promise<R>;
|
||||
evaluateAll<R>(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], void, R>): Promise<R>;
|
||||
elementHandle(options?: {
|
||||
timeout?: number;
|
||||
}): Promise<null|ElementHandle<SVGElement | HTMLElement>>;
|
||||
elementHandles(): Promise<null|ElementHandle<SVGElement | HTMLElement>[]>;
|
||||
evaluate<R, Arg>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, Arg, R>, arg: Arg): Promise<R>;
|
||||
evaluate<R>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, void, R>): Promise<R>;
|
||||
evaluateAll<R, Arg>(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], Arg, R>, arg: Arg): Promise<R>;
|
||||
evaluateAll<R>(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], void, R>): Promise<R>;
|
||||
}
|
||||
|
||||
export interface BrowserType<Unused = {}> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user