mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 11:50:22 +03:00
feat(api): make withText an option (#10922)
This commit is contained in:
parent
34b84841b0
commit
04e82ce71c
@ -989,6 +989,7 @@ The method returns an element locator that can be used to perform actions in the
|
||||
Locator is resolved to the element immediately before performing an action, so a series of actions on the same locator can in fact be performed on different DOM elements. That would happen if the DOM structure between those actions has changed.
|
||||
|
||||
### param: Frame.locator.selector = %%-find-selector-%%
|
||||
### option: Frame.locator.-inline- = %%-locator-options-list-%%
|
||||
|
||||
## method: Frame.name
|
||||
- returns: <[string]>
|
||||
|
@ -122,6 +122,7 @@ Returns locator to the last matching frame.
|
||||
The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||
|
||||
### param: FrameLocator.locator.selector = %%-find-selector-%%
|
||||
### option: FrameLocator.locator.-inline- = %%-locator-options-list-%%
|
||||
|
||||
|
||||
## method: FrameLocator.nth
|
||||
|
@ -541,6 +541,7 @@ Returns locator to the last matching element.
|
||||
The method finds an element matching the specified selector in the `Locator`'s subtree.
|
||||
|
||||
### param: Locator.locator.selector = %%-find-selector-%%
|
||||
### option: Locator.locator.-inline- = %%-locator-options-list-%%
|
||||
|
||||
## method: Locator.nth
|
||||
- returns: <[Locator]>
|
||||
@ -908,15 +909,3 @@ orderSent.WaitForAsync();
|
||||
|
||||
### option: Locator.waitFor.state = %%-wait-for-selector-state-%%
|
||||
### option: Locator.waitFor.timeout = %%-input-timeout-%%
|
||||
|
||||
## method: Locator.withText
|
||||
- returns: <[Locator]>
|
||||
|
||||
Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example, `"Playwright"`
|
||||
matches `<article><div>Playwright</div></article>`.
|
||||
|
||||
|
||||
### param: Locator.withText.text
|
||||
- `text` <[string]|[RegExp]>
|
||||
|
||||
Text to filter by as a string or as a regular expression.
|
||||
|
@ -2113,6 +2113,7 @@ Locator is resolved to the element immediately before performing an action, so a
|
||||
Shortcut for main frame's [`method: Frame.locator`].
|
||||
|
||||
### param: Page.locator.selector = %%-find-selector-%%
|
||||
### option: Page.locator.-inline- = %%-locator-options-list-%%
|
||||
|
||||
## method: Page.mainFrame
|
||||
- returns: <[Frame]>
|
||||
|
@ -868,3 +868,12 @@ Slows down Playwright operations by the specified amount of milliseconds. Useful
|
||||
- %%-browser-option-proxy-%%
|
||||
- %%-browser-option-timeout-%%
|
||||
- %%-browser-option-tracesdir-%%
|
||||
|
||||
## locator-option-has-text
|
||||
- `hasText` <[string]|[RegExp]>
|
||||
|
||||
Matches elements containing specified text somewhere inside, possibly in a child or a descendant element.
|
||||
For example, `"Playwright"` matches `<article><div>Playwright</div></article>`.
|
||||
|
||||
## locator-options-list
|
||||
- %%-locator-option-has-text-%%
|
||||
|
@ -432,7 +432,7 @@ Reveal element in the Elements panel (if DevTools of the respective browser supp
|
||||
Query Playwright element using the actual Playwright query engine, for example:
|
||||
|
||||
```js
|
||||
> playwright.locator('.auth-form').withText('Log in');
|
||||
> playwright.locator('.auth-form', { hasText: 'Log in' });
|
||||
|
||||
> Locator ()
|
||||
> - element: button
|
||||
|
@ -188,7 +188,7 @@ Reveal element in the Elements panel (if DevTools of the respective browser supp
|
||||
Query Playwright element using the actual Playwright query engine, for example:
|
||||
|
||||
```js
|
||||
> playwright.locator('.auth-form').withText('Log in');
|
||||
> playwright.locator('.auth-form', { hasText: 'Log in' });
|
||||
|
||||
> Locator ()
|
||||
> - element: button
|
||||
|
@ -30,11 +30,11 @@ test('should render counters', async ({ renderComponent }) => {
|
||||
duration: 100000
|
||||
};
|
||||
const component = await renderComponent('HeaderView', { stats });
|
||||
await expect(component.locator('a').withText('All').locator('.counter')).toHaveText('100');
|
||||
await expect(component.locator('a').withText('Passed').locator('.counter')).toHaveText('42');
|
||||
await expect(component.locator('a').withText('Failed').locator('.counter')).toHaveText('31');
|
||||
await expect(component.locator('a').withText('Flaky').locator('.counter')).toHaveText('17');
|
||||
await expect(component.locator('a').withText('Skipped').locator('.counter')).toHaveText('10');
|
||||
await expect(component.locator('a', { hasText: 'All' }).locator('.counter')).toHaveText('100');
|
||||
await expect(component.locator('a', { hasText: 'Passed' }).locator('.counter')).toHaveText('42');
|
||||
await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31');
|
||||
await expect(component.locator('a', { hasText: 'Flaky' }).locator('.counter')).toHaveText('17');
|
||||
await expect(component.locator('a', { hasText: 'Skipped' }).locator('.counter')).toHaveText('10');
|
||||
});
|
||||
|
||||
test('should toggle filters', async ({ page, renderComponent }) => {
|
||||
@ -52,14 +52,14 @@ test('should toggle filters', async ({ page, renderComponent }) => {
|
||||
stats,
|
||||
setFilterText: (filterText: string) => filters.push(filterText)
|
||||
});
|
||||
await component.locator('a').withText('All').click();
|
||||
await component.locator('a').withText('Passed').click();
|
||||
await component.locator('a', { hasText: 'All' }).click();
|
||||
await component.locator('a', { hasText: 'Passed' }).click();
|
||||
await expect(page).toHaveURL(/#\?q=s:passed/);
|
||||
await component.locator('a').withText('Failed').click();
|
||||
await component.locator('a', { hasText: 'Failed' }).click();
|
||||
await expect(page).toHaveURL(/#\?q=s:failed/);
|
||||
await component.locator('a').withText('Flaky').click();
|
||||
await component.locator('a', { hasText: 'Flaky' }).click();
|
||||
await expect(page).toHaveURL(/#\?q=s:flaky/);
|
||||
await component.locator('a').withText('Skipped').click();
|
||||
await component.locator('a', { hasText: 'Skipped' }).click();
|
||||
await expect(page).toHaveURL(/#\?q=s:skipped/);
|
||||
expect(filters).toEqual(['', 's:passed', 's:failed', 's:flaky', 's:skipped']);
|
||||
});
|
||||
|
@ -280,8 +280,8 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
||||
return await this._channel.fill({ selector, value, ...options });
|
||||
}
|
||||
|
||||
locator(selector: string): Locator {
|
||||
return new Locator(this, selector);
|
||||
locator(selector: string, options?: { hasText?: string | RegExp }): Locator {
|
||||
return new Locator(this, selector, options);
|
||||
}
|
||||
|
||||
frameLocator(selector: string): FrameLocator {
|
||||
|
@ -29,9 +29,17 @@ export class Locator implements api.Locator {
|
||||
private _frame: Frame;
|
||||
private _selector: string;
|
||||
|
||||
constructor(frame: Frame, selector: string) {
|
||||
constructor(frame: Frame, selector: string, options?: { hasText?: string | RegExp }) {
|
||||
this._frame = frame;
|
||||
this._selector = selector;
|
||||
|
||||
if (options?.hasText) {
|
||||
const text = options.hasText;
|
||||
if (isRegExp(text))
|
||||
this._selector += ` >> :scope:text-matches(${escapeWithQuotes(text.source, '"')}, "${text.flags}")`;
|
||||
else
|
||||
this._selector += ` >> :scope:has-text(${escapeWithQuotes(text, '"')})`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _withElement<R>(task: (handle: ElementHandle<SVGElement | HTMLElement>, timeout?: number) => Promise<R>, timeout?: number): Promise<R> {
|
||||
@ -94,14 +102,8 @@ export class Locator implements api.Locator {
|
||||
return this._frame.fill(this._selector, value, { strict: true, ...options });
|
||||
}
|
||||
|
||||
locator(selector: string): Locator {
|
||||
return new Locator(this._frame, this._selector + ' >> ' + selector);
|
||||
}
|
||||
|
||||
withText(text: string | RegExp): Locator {
|
||||
if (isRegExp(text))
|
||||
return new Locator(this._frame, this._selector + ` >> :scope:text-matches(${escapeWithQuotes(text.source, '"')}, "${text.flags}")`);
|
||||
return new Locator(this._frame, this._selector + ` >> :scope:has-text(${escapeWithQuotes(text, '"')})`);
|
||||
locator(selector: string, options?: { hasText?: string | RegExp }): Locator {
|
||||
return new Locator(this._frame, this._selector + ' >> ' + selector, options);
|
||||
}
|
||||
|
||||
frameLocator(selector: string): FrameLocator {
|
||||
@ -269,8 +271,8 @@ export class FrameLocator implements api.FrameLocator {
|
||||
this._frameSelector = selector;
|
||||
}
|
||||
|
||||
locator(selector: string): Locator {
|
||||
return new Locator(this._frame, this._frameSelector + ' >> control=enter-frame >> ' + selector);
|
||||
locator(selector: string, options?: { hasText?: string | RegExp }): Locator {
|
||||
return new Locator(this._frame, this._frameSelector + ' >> control=enter-frame >> ' + selector, options);
|
||||
}
|
||||
|
||||
frameLocator(selector: string): FrameLocator {
|
||||
|
@ -505,8 +505,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||
return this._mainFrame.fill(selector, value, options);
|
||||
}
|
||||
|
||||
locator(selector: string): Locator {
|
||||
return this.mainFrame().locator(selector);
|
||||
locator(selector: string, options?: { hasText?: string | RegExp }): Locator {
|
||||
return this.mainFrame().locator(selector, options);
|
||||
}
|
||||
|
||||
frameLocator(selector: string): FrameLocator {
|
||||
|
@ -18,30 +18,30 @@ import { escapeWithQuotes } from '../../../utils/stringUtils';
|
||||
import type InjectedScript from '../../injected/injectedScript';
|
||||
import { generateSelector } from '../../injected/selectorGenerator';
|
||||
|
||||
function createLocator(injectedScript: InjectedScript, initial: string) {
|
||||
function createLocator(injectedScript: InjectedScript, initial: string, options?: { hasText?: string | RegExp }) {
|
||||
class Locator {
|
||||
selector: string;
|
||||
element: Element | undefined;
|
||||
elements: Element[];
|
||||
|
||||
constructor(selector: string) {
|
||||
constructor(selector: string, options?: { hasText?: string | RegExp }) {
|
||||
this.selector = selector;
|
||||
if (options?.hasText) {
|
||||
const text = options.hasText;
|
||||
const matcher = text instanceof RegExp ? 'text-matches' : 'has-text';
|
||||
const source = escapeWithQuotes(text instanceof RegExp ? text.source : text, '"');
|
||||
this.selector += ` >> :scope:${matcher}(${source})`;
|
||||
}
|
||||
const parsed = injectedScript.parseSelector(this.selector);
|
||||
this.element = injectedScript.querySelector(parsed, document, false);
|
||||
this.elements = injectedScript.querySelectorAll(parsed, document);
|
||||
}
|
||||
|
||||
locator(selector: string): Locator {
|
||||
return new Locator(this.selector ? this.selector + ' >> ' + selector : selector);
|
||||
}
|
||||
|
||||
withText(text: string | RegExp): Locator {
|
||||
const matcher = text instanceof RegExp ? 'text-matches' : 'has-text';
|
||||
const source = escapeWithQuotes(text instanceof RegExp ? text.source : text, '"');
|
||||
return new Locator(this.selector + ` >> :scope:${matcher}(${source})`);
|
||||
locator(selector: string, options?: { hasText: string | RegExp }): Locator {
|
||||
return new Locator(this.selector ? this.selector + ' >> ' + selector : selector, options);
|
||||
}
|
||||
}
|
||||
return new Locator(initial);
|
||||
return new Locator(initial, options);
|
||||
}
|
||||
|
||||
type ConsoleAPIInterface = {
|
||||
@ -71,7 +71,7 @@ export class ConsoleAPI {
|
||||
window.playwright = {
|
||||
$: (selector: string, strict?: boolean) => this._querySelector(selector, !!strict),
|
||||
$$: (selector: string) => this._querySelectorAll(selector),
|
||||
locator: (selector: string) => createLocator(this._injectedScript, selector),
|
||||
locator: (selector: string, options?: { hasText?: string | RegExp }) => createLocator(this._injectedScript, selector, options),
|
||||
inspect: (selector: string) => this._inspect(selector),
|
||||
selector: (element: Element) => this._selector(element),
|
||||
resume: () => this._resume(),
|
||||
|
50
packages/playwright-core/types/types.d.ts
vendored
50
packages/playwright-core/types/types.d.ts
vendored
@ -2556,10 +2556,18 @@ export interface Page {
|
||||
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
|
||||
* different DOM elements. That would happen if the DOM structure between those actions has changed.
|
||||
*
|
||||
* Shortcut for main frame's [frame.locator(selector)](https://playwright.dev/docs/api/class-frame#frame-locator).
|
||||
* Shortcut for main frame's
|
||||
* [frame.locator(selector[, options])](https://playwright.dev/docs/api/class-frame#frame-locator).
|
||||
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
|
||||
* @param options
|
||||
*/
|
||||
locator(selector: string): Locator;
|
||||
locator(selector: string, options?: {
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* `"Playwright"` matches `<article><div>Playwright</div></article>`.
|
||||
*/
|
||||
hasText?: string|RegExp;
|
||||
}): Locator;
|
||||
|
||||
/**
|
||||
* The page's main frame. Page is guaranteed to have a main frame which persists during navigations.
|
||||
@ -5324,8 +5332,15 @@ export interface Frame {
|
||||
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
|
||||
* different DOM elements. That would happen if the DOM structure between those actions has changed.
|
||||
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
|
||||
* @param options
|
||||
*/
|
||||
locator(selector: string): Locator;
|
||||
locator(selector: string, options?: {
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* `"Playwright"` matches `<article><div>Playwright</div></article>`.
|
||||
*/
|
||||
hasText?: string|RegExp;
|
||||
}): Locator;
|
||||
|
||||
/**
|
||||
* Returns frame's name attribute as specified in the tag.
|
||||
@ -8469,7 +8484,7 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
|
||||
/**
|
||||
* Locators are the central piece of Playwright's auto-waiting and retry-ability. In a nutshell, locators represent a way
|
||||
* to find element(s) on the page at any moment. Locator can be created with the
|
||||
* [page.locator(selector)](https://playwright.dev/docs/api/class-page#page-locator) method.
|
||||
* [page.locator(selector[, options])](https://playwright.dev/docs/api/class-page#page-locator) method.
|
||||
*
|
||||
* [Learn more about locators](https://playwright.dev/docs/locators).
|
||||
*/
|
||||
@ -9230,8 +9245,15 @@ export interface Locator {
|
||||
/**
|
||||
* The method finds an element matching the specified selector in the `Locator`'s subtree.
|
||||
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
|
||||
* @param options
|
||||
*/
|
||||
locator(selector: string): Locator;
|
||||
locator(selector: string, options?: {
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* `"Playwright"` matches `<article><div>Playwright</div></article>`.
|
||||
*/
|
||||
hasText?: string|RegExp;
|
||||
}): Locator;
|
||||
|
||||
/**
|
||||
* Returns locator to the n-th matching element.
|
||||
@ -9753,14 +9775,7 @@ export interface Locator {
|
||||
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
|
||||
*/
|
||||
timeout?: number;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* `"Playwright"` matches `<article><div>Playwright</div></article>`.
|
||||
* @param text Text to filter by as a string or as a regular expression.
|
||||
*/
|
||||
withText(text: string|RegExp): Locator;}
|
||||
}): Promise<void>;}
|
||||
|
||||
/**
|
||||
* BrowserType provides methods to launch a specific browser instance or connect to an existing one. The following is a
|
||||
@ -13639,8 +13654,15 @@ export interface FrameLocator {
|
||||
/**
|
||||
* The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
|
||||
* @param options
|
||||
*/
|
||||
locator(selector: string): Locator;
|
||||
locator(selector: string, options?: {
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* `"Playwright"` matches `<article><div>Playwright</div></article>`.
|
||||
*/
|
||||
hasText?: string|RegExp;
|
||||
}): Locator;
|
||||
|
||||
/**
|
||||
* Returns locator to the n-th matching frame.
|
||||
|
@ -48,12 +48,12 @@ it('should support playwright.selector', async ({ page }) => {
|
||||
|
||||
it('should support playwright.locator.value', async ({ page }) => {
|
||||
await page.setContent('<div>Hello<div>');
|
||||
const handle = await page.evaluateHandle(`playwright.locator('div').withText('Hello').element`);
|
||||
const handle = await page.evaluateHandle(`playwright.locator('div', { hasText: 'Hello' }).element`);
|
||||
expect(await handle.evaluate<string, HTMLDivElement>((node: HTMLDivElement) => node.nodeName)).toBe('DIV');
|
||||
});
|
||||
|
||||
it('should support playwright.locator.values', async ({ page }) => {
|
||||
await page.setContent('<div>Hello<div>');
|
||||
const length = await page.evaluate(`playwright.locator('div').withText('Hello').elements.length`);
|
||||
await page.setContent('<div>Hello<div>Bar</div></div>');
|
||||
const length = await page.evaluate(`playwright.locator('div', { hasText: 'Hello' }).elements.length`);
|
||||
expect(length).toBe(1);
|
||||
});
|
||||
|
@ -62,30 +62,30 @@ it('should throw on due to strictness 2', async ({ page }) => {
|
||||
|
||||
it('should filter by text', async ({ page }) => {
|
||||
await page.setContent(`<div>Foobar</div><div>Bar</div>`);
|
||||
await expect(page.locator('div').withText('Foo')).toHaveText('Foobar');
|
||||
await expect(page.locator('div', { hasText: 'Foo' })).toHaveText('Foobar');
|
||||
});
|
||||
|
||||
it('should filter by text 2', async ({ page }) => {
|
||||
await page.setContent(`<div>foo <span>hello world</span> bar</div>`);
|
||||
await expect(page.locator('div').withText('hello world')).toHaveText('foo hello world bar');
|
||||
await expect(page.locator('div', { hasText: 'hello world' })).toHaveText('foo hello world bar');
|
||||
});
|
||||
|
||||
it('should filter by regex', async ({ page }) => {
|
||||
await page.setContent(`<div>Foobar</div><div>Bar</div>`);
|
||||
await expect(page.locator('div').withText(/Foo.*/)).toHaveText('Foobar');
|
||||
await expect(page.locator('div', { hasText: /Foo.*/ })).toHaveText('Foobar');
|
||||
});
|
||||
|
||||
it('should filter by text with quotes', async ({ page }) => {
|
||||
await page.setContent(`<div>Hello "world"</div><div>Hello world</div>`);
|
||||
await expect(page.locator('div').withText('Hello "world"')).toHaveText('Hello "world"');
|
||||
await expect(page.locator('div', { hasText: 'Hello "world"' })).toHaveText('Hello "world"');
|
||||
});
|
||||
|
||||
it('should filter by regex with quotes', async ({ page }) => {
|
||||
await page.setContent(`<div>Hello "world"</div><div>Hello world</div>`);
|
||||
await expect(page.locator('div').withText(/Hello "world"/)).toHaveText('Hello "world"');
|
||||
await expect(page.locator('div', { hasText: /Hello "world"/ })).toHaveText('Hello "world"');
|
||||
});
|
||||
|
||||
it('should filter by regex and regexp flags', async ({ page }) => {
|
||||
await page.setContent(`<div>Hello "world"</div><div>Hello world</div>`);
|
||||
await expect(page.locator('div').withText(/hElLo "world"/i)).toHaveText('Hello "world"');
|
||||
await expect(page.locator('div', { hasText: /hElLo "world"/i })).toHaveText('Hello "world"');
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user