diff --git a/src/client/locator.ts b/src/client/locator.ts index 57712c1813..e5fc4138a3 100644 --- a/src/client/locator.ts +++ b/src/client/locator.ts @@ -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 { - return this._frame.isHidden(this._visibleSelector, { strict: true, ...options }); + return this._frame.isHidden(this._selector, { strict: true, ...options }); } async isVisible(options?: TimeoutOptions): Promise { - 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 { diff --git a/src/test/expect.ts b/src/test/expect.ts index 32ddf53359..57548413e5 100644 --- a/src/test/expect.ts +++ b/src/test/expect.ts @@ -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, +}); diff --git a/src/test/matchers/toBeTruthy.ts b/src/test/matchers/toBeTruthy.ts new file mode 100644 index 0000000000..9509859dc8 --- /dev/null +++ b/src/test/matchers/toBeTruthy.ts @@ -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, + matcherName: string, + query: (timeout: number) => Promise, + 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, + locator: Locator, + options?: { timeout?: number }, +) { + return toBeTruthyImpl.call(this, 'toBeChecked', async timeout => { + return await locator.isChecked({ timeout }); + }, options); +} + +export async function toBeEditable( + this: ReturnType, + locator: Locator, + options?: { timeout?: number }, +) { + return toBeTruthyImpl.call(this, 'toBeEditable', async timeout => { + return await locator.isEditable({ timeout }); + }, options); +} + +export async function toBeEnabled( + this: ReturnType, + locator: Locator, + options?: { timeout?: number }, +) { + return toBeTruthyImpl.call(this, 'toBeEnabled', async timeout => { + return await locator.isEnabled({ timeout }); + }, options); +} + +export async function toBeDisabled( + this: ReturnType, + locator: Locator, + options?: { timeout?: number }, +) { + return toBeTruthyImpl.call(this, 'toBeDisabled', async timeout => { + return await locator.isDisabled({ timeout }); + }, options); +} + +export async function toBeEmpty( + this: ReturnType, + 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, + locator: Locator, + options?: { timeout?: number }, +) { + return toBeTruthyImpl.call(this, 'toBeHidden', async timeout => { + return await locator.isHidden({ timeout }); + }, options); +} + +export async function toBeVisible( + this: ReturnType, + locator: Locator, + options?: { timeout?: number }, +) { + return toBeTruthyImpl.call(this, 'toBeVisible', async timeout => { + return await locator.isVisible({ timeout }); + }, options); +} + +export async function toBeFocused( + this: ReturnType, + locator: Locator, + options?: { timeout?: number }, +) { + return toBeTruthyImpl.call(this, 'toBeFocused', async timeout => { + return await locator.evaluate(element => { + return document.activeElement === element; + }, { timeout }); + }, options); +} diff --git a/src/test/matchers/toMatchText.ts b/src/test/matchers/toMatchText.ts index 22dc2ceb12..bbe2389a4c 100644 --- a/src/test/matchers/toMatchText.ts +++ b/src/test/matchers/toMatchText.ts @@ -35,16 +35,15 @@ import { monotonicTime, pollUntilDeadline } from '../util'; async function toMatchTextImpl( this: ReturnType, - locator: Locator, + matcherName: string, + query: (timeout: number) => Promise, 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, 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, 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, + 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, + 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, + 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, + 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, + locator: Locator, + expected: string | RegExp, + options?: { timeout?: number }, +) { + return toMatchTextImpl.call(this, 'toHaveValue', async timeout => { + return await locator.inputValue({ timeout }); + }, expected, options); } \ No newline at end of file diff --git a/tests/playwright-test/playwright.expect.text.spec.ts b/tests/playwright-test/playwright.expect.text.spec.ts index 5f04475024..5934e4a01e 100644 --- a/tests/playwright-test/playwright.expect.text.spec.ts +++ b/tests/playwright-test/playwright.expect.text.spec.ts @@ -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('
Text content
'); - 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('
Text content
'); - 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('
Text content
'); - 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('
Text content
'); + const locator = page.locator('#node'); + await expect(locator).toContainText('Text'); }); test('fail', async ({ page }) => { await page.setContent('
Text content
'); - 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('
Text content
'); - 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('
Text content
'); - 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('
Text content
'); + 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('
Text content
'); + 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('
Text content
'); + 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('
Text content
'); + 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(''); + 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); +}); diff --git a/tests/playwright-test/playwright.expect.true.spec.ts b/tests/playwright-test/playwright.expect.true.spec.ts new file mode 100644 index 0000000000..b6662d8815 --- /dev/null +++ b/tests/playwright-test/playwright.expect.true.spec.ts @@ -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(''); + const locator = page.locator('input'); + await expect(locator).toBeChecked(); + }); + + test('pass not', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('input'); + await expect(locator).not.toBeChecked(); + }); + + test('fail', async ({ page }) => { + await page.setContent(''); + 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(''); + const locator = page.locator('input'); + await expect(locator).toBeEditable(); + }); + + test('enabled', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('button'); + await expect(locator).toBeEnabled(); + }); + + test('disabled', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('button'); + await expect(locator).toBeDisabled(); + }); + + test('empty input', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('input'); + await expect(locator).toBeEmpty(); + }); + + test('non-empty input', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('input'); + await expect(locator).not.toBeEmpty(); + }); + + test('empty DOM', async ({ page }) => { + await page.setContent('
'); + 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(''); + const locator = page.locator('input'); + await expect(locator).toBeVisible(); + }); + + test('not visible', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('button'); + await expect(locator).not.toBeVisible(); + }); + + test('hidden', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('button'); + await expect(locator).toBeHidden(); + }); + + test('not hidden', async ({ page }) => { + await page.setContent(''); + 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(''); + 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); +}); diff --git a/types/testExpect.d.ts b/types/testExpect.d.ts index 29f91b11bf..a015649648 100644 --- a/types/testExpect.d.ts +++ b/types/testExpect.d.ts @@ -72,13 +72,78 @@ declare global { /** * Asserts element's exact text content. */ - toHaveText(expected: string, options?: { timeout?: number, useInnerText?: boolean }): Promise; + toHaveText(expected: string | RegExp, options?: { timeout?: number, useInnerText?: boolean }): Promise; - /** + /** * Asserts element's text content matches given pattern or contains given substring. */ - toMatchText(expected: string | RegExp, options?: { timeout?: number, useInnerText?: boolean }): Promise; - } + toContainText(expected: string, options?: { timeout?: number, useInnerText?: boolean }): Promise; + + /** + * Asserts element's attributes `name` matches expected value. + */ + toHaveAttr(expected: string | RegExp, name: string, options?: { timeout?: number }): Promise; + + /** + * Asserts element's data attribute data-`name` matches expected value. + */ + toHaveData(expected: string | RegExp, name: string, options?: { timeout?: number }): Promise; + + /** + * Asserts element's computed CSS property `name` matches expected value. + */ + toHaveCSS(expected: string | RegExp, name: string, options?: { timeout?: number }): Promise; + + /** + * Asserts element's `id` attribute matches expected value. + */ + toHaveId(expected: string | RegExp, options?: { timeout?: number }): Promise; + + /** + * Asserts input element's value. + */ + toHaveValue(expected: string | RegExp, options?: { timeout?: number }): Promise; + + /** + * Asserts input is checked. + */ + toBeChecked(options?: { timeout?: number }): Promise; + + /** + * Asserts input is editable. + */ + toBeEditable(options?: { timeout?: number }): Promise; + + /** + * Asserts input is enabled. + */ + toBeEnabled(options?: { timeout?: number }): Promise; + + /** + * Asserts input is disabled. + */ + toBeDisabled(options?: { timeout?: number }): Promise; + + /** + * Asserts given DOM node or input has no text content or no input value. + */ + toBeEmpty(options?: { timeout?: number }): Promise; + + /** + * Asserts given DOM node is hidden or detached from DOM. + */ + toBeHidden(options?: { timeout?: number }): Promise; + + /** + * Asserts given DOM node visible on the screen. + */ + toBeVisible(options?: { timeout?: number }): Promise; + + /** + * Asserts given DOM is a focused (active) in document. + */ + toBeFocused(options?: { timeout?: number }): Promise; + } } } diff --git a/types/types.d.ts b/types/types.d.ts index f1bd441e1d..13bfec7562 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -6968,18 +6968,6 @@ export interface ElementHandle extends JSHandle { * */ 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>; - /** - * Resolves given locator to all matching DOM elements. - */ - elementHandles(): Promise[]>; /** * Returns the return value of `pageFunction`. * @@ -6999,8 +6987,12 @@ export interface Locator { * @param arg Optional argument to pass to `pageFunction`. * @param options */ - evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; - evaluate(pageFunction: PageFunctionOn): Promise; + evaluate(pageFunction: PageFunctionOn, arg: Arg, options?: { + timeout?: number; + }): Promise; + evaluate(pageFunction: PageFunctionOn, options?: { + timeout?: number; + }): Promise; /** * 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(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], Arg, R>, arg: Arg): Promise; evaluateAll(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], void, R>): Promise; + /** + * 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>; /** * 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; + /** + * Resolves given locator to all matching DOM elements. + */ + elementHandles(): Promise>; + /** * Returns the return value of `pageFunction` as a [JSHandle]. * diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts index 830449693a..d179892a9b 100644 --- a/utils/generate_types/overrides.d.ts +++ b/utils/generate_types/overrides.d.ts @@ -141,14 +141,17 @@ export interface ElementHandle extends JSHandle { } export interface Locator { + evaluate(pageFunction: PageFunctionOn, arg: Arg, options?: { + timeout?: number; + }): Promise; + evaluate(pageFunction: PageFunctionOn, options?: { + timeout?: number; + }): Promise; + evaluateAll(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], Arg, R>, arg: Arg): Promise; + evaluateAll(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], void, R>): Promise; elementHandle(options?: { timeout?: number; }): Promise>; - elementHandles(): Promise[]>; - evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; - evaluate(pageFunction: PageFunctionOn): Promise; - evaluateAll(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], Arg, R>, arg: Arg): Promise; - evaluateAll(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], void, R>): Promise; } export interface BrowserType {