feat(locators): reland locator.and(locator) (#22850)
Removed in #22223. Fixes #22585.
This commit is contained in:
parent
42328478ea
commit
160888df99
|
|
@ -105,6 +105,45 @@ var texts = await page.GetByRole(AriaRole.Link).AllTextContentsAsync();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## method: Locator.and
|
||||||
|
* since: v1.33
|
||||||
|
* langs:
|
||||||
|
- alias-python: and_
|
||||||
|
- returns: <[Locator]>
|
||||||
|
|
||||||
|
Creates a locator that matches both this locator and the argument locator.
|
||||||
|
|
||||||
|
**Usage**
|
||||||
|
|
||||||
|
The following example finds a button with a specific title.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const button = page.getByRole('button').and(page.getByTitle('Subscribe'));
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));
|
||||||
|
```
|
||||||
|
|
||||||
|
```python async
|
||||||
|
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
|
||||||
|
```
|
||||||
|
|
||||||
|
```python sync
|
||||||
|
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
|
||||||
|
```
|
||||||
|
|
||||||
|
### param: Locator.and.locator
|
||||||
|
* since: v1.33
|
||||||
|
- `locator` <[Locator]>
|
||||||
|
|
||||||
|
Additional locator to match.
|
||||||
|
|
||||||
|
|
||||||
## async method: Locator.blur
|
## async method: Locator.blur
|
||||||
* since: v1.28
|
* since: v1.28
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1056,6 +1056,25 @@ await Expect(page
|
||||||
|
|
||||||
Note that the inner locator is matched starting from the outer one, not from the document root.
|
Note that the inner locator is matched starting from the outer one, not from the document root.
|
||||||
|
|
||||||
|
### Filter by matching an additional locator
|
||||||
|
|
||||||
|
Method [`method: Locator.and`] narrows down an existing locator by matching an additional locator. For example, you can combine [`method: Page.getByRole`] and [`method: Page.getByTitle`] to match by both role and title.
|
||||||
|
```js
|
||||||
|
const button = page.getByRole('button').and(page.getByTitle('Subscribe'));
|
||||||
|
```
|
||||||
|
```java
|
||||||
|
Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));
|
||||||
|
```
|
||||||
|
```python async
|
||||||
|
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
|
||||||
|
```
|
||||||
|
```python sync
|
||||||
|
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
|
||||||
|
```
|
||||||
|
```csharp
|
||||||
|
var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
|
||||||
|
```
|
||||||
|
|
||||||
## Chaining Locators
|
## Chaining Locators
|
||||||
|
|
||||||
You can chain methods that create a locator, like [`method: Page.getByText`] or [`method: Locator.getByRole`], to narrow down the search to a particular part of the page.
|
You can chain methods that create a locator, like [`method: Page.getByText`] or [`method: Locator.getByRole`], to narrow down the search to a particular part of the page.
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,12 @@ export class Locator implements api.Locator {
|
||||||
return new Locator(this._frame, this._selector + ` >> nth=${index}`);
|
return new Locator(this._frame, this._selector + ` >> nth=${index}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
and(locator: Locator): Locator {
|
||||||
|
if (locator._frame !== this._frame)
|
||||||
|
throw new Error(`Locators must belong to the same frame.`);
|
||||||
|
return new Locator(this._frame, this._selector + ` >> internal:and=` + JSON.stringify(locator._selector));
|
||||||
|
}
|
||||||
|
|
||||||
or(locator: Locator): Locator {
|
or(locator: Locator): Locator {
|
||||||
if (locator._frame !== this._frame)
|
if (locator._frame !== this._frame)
|
||||||
throw new Error(`Locators must belong to the same frame.`);
|
throw new Error(`Locators must belong to the same frame.`);
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ class Locator {
|
||||||
self.first = (): Locator => self.locator('nth=0');
|
self.first = (): Locator => self.locator('nth=0');
|
||||||
self.last = (): Locator => self.locator('nth=-1');
|
self.last = (): Locator => self.locator('nth=-1');
|
||||||
self.nth = (index: number): Locator => self.locator(`nth=${index}`);
|
self.nth = (index: number): Locator => self.locator(`nth=${index}`);
|
||||||
|
self.and = (locator: Locator): Locator => new Locator(injectedScript, selectorBase + ` >> internal:and=` + JSON.stringify((locator as any)[selectorSymbol]));
|
||||||
self.or = (locator: Locator): Locator => new Locator(injectedScript, selectorBase + ` >> internal:or=` + JSON.stringify((locator as any)[selectorSymbol]));
|
self.or = (locator: Locator): Locator => new Locator(injectedScript, selectorBase + ` >> internal:or=` + JSON.stringify((locator as any)[selectorSymbol]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +94,7 @@ class ConsoleAPI {
|
||||||
delete this._injectedScript.window.playwright.first;
|
delete this._injectedScript.window.playwright.first;
|
||||||
delete this._injectedScript.window.playwright.last;
|
delete this._injectedScript.window.playwright.last;
|
||||||
delete this._injectedScript.window.playwright.nth;
|
delete this._injectedScript.window.playwright.nth;
|
||||||
|
delete this._injectedScript.window.playwright.and;
|
||||||
delete this._injectedScript.window.playwright.or;
|
delete this._injectedScript.window.playwright.or;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ export class InjectedScript {
|
||||||
this._engines.set('internal:control', this._createControlEngine());
|
this._engines.set('internal:control', this._createControlEngine());
|
||||||
this._engines.set('internal:has', this._createHasEngine());
|
this._engines.set('internal:has', this._createHasEngine());
|
||||||
this._engines.set('internal:has-not', this._createHasNotEngine());
|
this._engines.set('internal:has-not', this._createHasNotEngine());
|
||||||
|
this._engines.set('internal:and', { queryAll: () => [] });
|
||||||
this._engines.set('internal:or', { queryAll: () => [] });
|
this._engines.set('internal:or', { queryAll: () => [] });
|
||||||
this._engines.set('internal:label', this._createInternalLabelEngine());
|
this._engines.set('internal:label', this._createInternalLabelEngine());
|
||||||
this._engines.set('internal:text', this._createTextEngine(true, true));
|
this._engines.set('internal:text', this._createTextEngine(true, true));
|
||||||
|
|
@ -212,6 +213,9 @@ export class InjectedScript {
|
||||||
for (const part of selector.parts) {
|
for (const part of selector.parts) {
|
||||||
if (part.name === 'nth') {
|
if (part.name === 'nth') {
|
||||||
roots = this._queryNth(roots, part);
|
roots = this._queryNth(roots, part);
|
||||||
|
} else if (part.name === 'internal:and') {
|
||||||
|
const andElements = this.querySelectorAll((part.body as NestedSelectorBody).parsed, root);
|
||||||
|
roots = new Set(andElements.filter(e => roots.has(e)));
|
||||||
} else if (part.name === 'internal:or') {
|
} else if (part.name === 'internal:or') {
|
||||||
const orElements = this.querySelectorAll((part.body as NestedSelectorBody).parsed, root);
|
const orElements = this.querySelectorAll((part.body as NestedSelectorBody).parsed, root);
|
||||||
roots = new Set(sortInDOMOrder(new Set([...roots, ...orElements])));
|
roots = new Set(sortInDOMOrder(new Set([...roots, ...orElements])));
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export class Selectors {
|
||||||
'nth', 'visible', 'internal:control',
|
'nth', 'visible', 'internal:control',
|
||||||
'internal:has', 'internal:has-not',
|
'internal:has', 'internal:has-not',
|
||||||
'internal:has-text', 'internal:has-not-text',
|
'internal:has-text', 'internal:has-not-text',
|
||||||
'internal:or',
|
'internal:and', 'internal:or',
|
||||||
'role', 'internal:attr', 'internal:label', 'internal:text', 'internal:role', 'internal:testid',
|
'role', 'internal:attr', 'internal:label', 'internal:text', 'internal:role', 'internal:testid',
|
||||||
]);
|
]);
|
||||||
this._builtinEnginesInMainWorld = new Set([
|
this._builtinEnginesInMainWorld = new Set([
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import { type NestedSelectorBody, parseAttributeSelector, parseSelector, stringi
|
||||||
import type { ParsedSelector } from './selectorParser';
|
import type { ParsedSelector } from './selectorParser';
|
||||||
|
|
||||||
export type Language = 'javascript' | 'python' | 'java' | 'csharp';
|
export type Language = 'javascript' | 'python' | 'java' | 'csharp';
|
||||||
export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text' | 'has-not-text' | 'has' | 'hasNot' | 'frame' | 'or';
|
export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text' | 'has-not-text' | 'has' | 'hasNot' | 'frame' | 'and' | 'or';
|
||||||
export type LocatorBase = 'page' | 'locator' | 'frame-locator';
|
export type LocatorBase = 'page' | 'locator' | 'frame-locator';
|
||||||
|
|
||||||
type LocatorOptions = { attrs?: { name: string, value: string | boolean | number}[], exact?: boolean, name?: string | RegExp };
|
type LocatorOptions = { attrs?: { name: string, value: string | boolean | number}[], exact?: boolean, name?: string | RegExp };
|
||||||
|
|
@ -99,6 +99,11 @@ function innerAsLocator(factory: LocatorFactory, parsed: ParsedSelector, isFrame
|
||||||
tokens.push(factory.generateLocator(base, 'hasNot', inner));
|
tokens.push(factory.generateLocator(base, 'hasNot', inner));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (part.name === 'internal:and') {
|
||||||
|
const inner = innerAsLocator(factory, (part.body as NestedSelectorBody).parsed);
|
||||||
|
tokens.push(factory.generateLocator(base, 'and', inner));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (part.name === 'internal:or') {
|
if (part.name === 'internal:or') {
|
||||||
const inner = innerAsLocator(factory, (part.body as NestedSelectorBody).parsed);
|
const inner = innerAsLocator(factory, (part.body as NestedSelectorBody).parsed);
|
||||||
tokens.push(factory.generateLocator(base, 'or', inner));
|
tokens.push(factory.generateLocator(base, 'or', inner));
|
||||||
|
|
@ -217,6 +222,8 @@ export class JavaScriptLocatorFactory implements LocatorFactory {
|
||||||
return `filter({ has: ${body} })`;
|
return `filter({ has: ${body} })`;
|
||||||
case 'hasNot':
|
case 'hasNot':
|
||||||
return `filter({ hasNot: ${body} })`;
|
return `filter({ hasNot: ${body} })`;
|
||||||
|
case 'and':
|
||||||
|
return `and(${body})`;
|
||||||
case 'or':
|
case 'or':
|
||||||
return `or(${body})`;
|
return `or(${body})`;
|
||||||
case 'test-id':
|
case 'test-id':
|
||||||
|
|
@ -291,6 +298,8 @@ export class PythonLocatorFactory implements LocatorFactory {
|
||||||
return `filter(has=${body})`;
|
return `filter(has=${body})`;
|
||||||
case 'hasNot':
|
case 'hasNot':
|
||||||
return `filter(has_not=${body})`;
|
return `filter(has_not=${body})`;
|
||||||
|
case 'and':
|
||||||
|
return `and_(${body})`;
|
||||||
case 'or':
|
case 'or':
|
||||||
return `or_(${body})`;
|
return `or_(${body})`;
|
||||||
case 'test-id':
|
case 'test-id':
|
||||||
|
|
@ -374,6 +383,8 @@ export class JavaLocatorFactory implements LocatorFactory {
|
||||||
return `filter(new ${clazz}.FilterOptions().setHas(${body}))`;
|
return `filter(new ${clazz}.FilterOptions().setHas(${body}))`;
|
||||||
case 'hasNot':
|
case 'hasNot':
|
||||||
return `filter(new ${clazz}.FilterOptions().setHasNot(${body}))`;
|
return `filter(new ${clazz}.FilterOptions().setHasNot(${body}))`;
|
||||||
|
case 'and':
|
||||||
|
return `and(${body})`;
|
||||||
case 'or':
|
case 'or':
|
||||||
return `or(${body})`;
|
return `or(${body})`;
|
||||||
case 'test-id':
|
case 'test-id':
|
||||||
|
|
@ -451,6 +462,8 @@ export class CSharpLocatorFactory implements LocatorFactory {
|
||||||
return `Filter(new() { Has = ${body} })`;
|
return `Filter(new() { Has = ${body} })`;
|
||||||
case 'hasNot':
|
case 'hasNot':
|
||||||
return `Filter(new() { HasNot = ${body} })`;
|
return `Filter(new() { HasNot = ${body} })`;
|
||||||
|
case 'and':
|
||||||
|
return `And(${body})`;
|
||||||
case 'or':
|
case 'or':
|
||||||
return `Or(${body})`;
|
return `Or(${body})`;
|
||||||
case 'test-id':
|
case 'test-id':
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ function parseLocator(locator: string, testIdAttributeName: string): string {
|
||||||
.replace(/new[\w]+\.[\w]+options\(\)/g, '')
|
.replace(/new[\w]+\.[\w]+options\(\)/g, '')
|
||||||
.replace(/\.set([\w]+)\(([^)]+)\)/g, (_, group1, group2) => ',' + group1.toLowerCase() + '=' + group2.toLowerCase())
|
.replace(/\.set([\w]+)\(([^)]+)\)/g, (_, group1, group2) => ',' + group1.toLowerCase() + '=' + group2.toLowerCase())
|
||||||
.replace(/\.or_\(/g, 'or(') // Python has "or_" instead of "or".
|
.replace(/\.or_\(/g, 'or(') // Python has "or_" instead of "or".
|
||||||
|
.replace(/\.and_\(/g, 'and(') // Python has "and_" instead of "and".
|
||||||
.replace(/:/g, '=')
|
.replace(/:/g, '=')
|
||||||
.replace(/,re\.ignorecase/g, 'i')
|
.replace(/,re\.ignorecase/g, 'i')
|
||||||
.replace(/,pattern.case_insensitive/g, 'i')
|
.replace(/,pattern.case_insensitive/g, 'i')
|
||||||
|
|
@ -104,7 +105,7 @@ function shiftParams(template: string, sub: number) {
|
||||||
|
|
||||||
function transform(template: string, params: TemplateParams, testIdAttributeName: string): string {
|
function transform(template: string, params: TemplateParams, testIdAttributeName: string): string {
|
||||||
// Recursively handle filter(has=, hasnot=).
|
// Recursively handle filter(has=, hasnot=).
|
||||||
// TODO: handle or(locator).
|
// TODO: handle and(locator), or(locator).
|
||||||
while (true) {
|
while (true) {
|
||||||
const hasMatch = template.match(/filter\(,?(has|hasnot)=/);
|
const hasMatch = template.match(/filter\(,?(has|hasnot)=/);
|
||||||
if (!hasMatch)
|
if (!hasMatch)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import { InvalidSelectorError, parseCSS } from './cssParser';
|
||||||
export { InvalidSelectorError, isInvalidSelectorError } from './cssParser';
|
export { InvalidSelectorError, isInvalidSelectorError } from './cssParser';
|
||||||
|
|
||||||
export type NestedSelectorBody = { parsed: ParsedSelector, distance?: number };
|
export type NestedSelectorBody = { parsed: ParsedSelector, distance?: number };
|
||||||
const kNestedSelectorNames = new Set(['internal:has', 'internal:has-not', 'internal:or', 'left-of', 'right-of', 'above', 'below', 'near']);
|
const kNestedSelectorNames = new Set(['internal:has', 'internal:has-not', 'internal:and', 'internal:or', 'left-of', 'right-of', 'above', 'below', 'near']);
|
||||||
const kNestedSelectorNamesWithDistance = new Set(['left-of', 'right-of', 'above', 'below', 'near']);
|
const kNestedSelectorNamesWithDistance = new Set(['left-of', 'right-of', 'above', 'below', 'near']);
|
||||||
|
|
||||||
export type ParsedSelectorPart = {
|
export type ParsedSelectorPart = {
|
||||||
|
|
|
||||||
15
packages/playwright-core/types/types.d.ts
vendored
15
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -10453,6 +10453,21 @@ export interface Locator {
|
||||||
*/
|
*/
|
||||||
allTextContents(): Promise<Array<string>>;
|
allTextContents(): Promise<Array<string>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a locator that matches both this locator and the argument locator.
|
||||||
|
*
|
||||||
|
* **Usage**
|
||||||
|
*
|
||||||
|
* The following example finds a button with a specific title.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* const button = page.getByRole('button').and(page.getByTitle('Subscribe'));
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param locator Additional locator to match.
|
||||||
|
*/
|
||||||
|
and(locator: Locator): Locator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur) on the element.
|
* Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur) on the element.
|
||||||
* @param options
|
* @param options
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,11 @@ it('should support playwright.locator({ hasNot })', async ({ page }) => {
|
||||||
expect(await page.evaluate(`playwright.locator('div', { hasNot: playwright.locator('text=Hello') }).element.innerHTML`)).toContain('Hi');
|
expect(await page.evaluate(`playwright.locator('div', { hasNot: playwright.locator('text=Hello') }).element.innerHTML`)).toContain('Hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support locator.and()', async ({ page }) => {
|
||||||
|
await page.setContent('<div data-testid=Hey>Hi</div>');
|
||||||
|
expect(await page.evaluate(`playwright.locator('div').and(playwright.getByTestId('Hey')).elements.map(e => e.innerHTML)`)).toEqual(['Hi']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support locator.or()', async ({ page }) => {
|
it('should support locator.or()', async ({ page }) => {
|
||||||
await page.setContent('<div>Hi</div><span>Hello</span>');
|
await page.setContent('<div>Hi</div><span>Hello</span>');
|
||||||
expect(await page.evaluate(`playwright.locator('div').or(playwright.locator('span')).elements.map(e => e.innerHTML)`)).toEqual(['Hi', 'Hello']);
|
expect(await page.evaluate(`playwright.locator('div').or(playwright.locator('span')).elements.map(e => e.innerHTML)`)).toEqual(['Hi', 'Hello']);
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,13 @@ it.describe(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('asLocator internal:and', async () => {
|
||||||
|
expect.soft(asLocator('javascript', 'div >> internal:and="span >> article"', false)).toBe(`locator('div').and(locator('span').locator('article'))`);
|
||||||
|
expect.soft(asLocator('python', 'div >> internal:and="span >> article"', false)).toBe(`locator("div").and_(locator("span").locator("article"))`);
|
||||||
|
expect.soft(asLocator('java', 'div >> internal:and="span >> article"', false)).toBe(`locator("div").and(locator("span").locator("article"))`);
|
||||||
|
expect.soft(asLocator('csharp', 'div >> internal:and="span >> article"', false)).toBe(`Locator("div").And(Locator("span").Locator("article"))`);
|
||||||
|
});
|
||||||
|
|
||||||
it('asLocator internal:or', async () => {
|
it('asLocator internal:or', async () => {
|
||||||
expect.soft(asLocator('javascript', 'div >> internal:or="span >> article"', false)).toBe(`locator('div').or(locator('span').locator('article'))`);
|
expect.soft(asLocator('javascript', 'div >> internal:or="span >> article"', false)).toBe(`locator('div').or(locator('span').locator('article'))`);
|
||||||
expect.soft(asLocator('python', 'div >> internal:or="span >> article"', false)).toBe(`locator("div").or_(locator("span").locator("article"))`);
|
expect.soft(asLocator('python', 'div >> internal:or="span >> article"', false)).toBe(`locator("div").or_(locator("span").locator("article"))`);
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,19 @@ it('should support locator.filter', async ({ page, trace }) => {
|
||||||
await expect(page.locator(`div`).filter({ hasNotText: 'foo' })).toHaveCount(2);
|
await expect(page.locator(`div`).filter({ hasNotText: 'foo' })).toHaveCount(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support locator.and', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div data-testid=foo>hello</div><div data-testid=bar>world</div>
|
||||||
|
<span data-testid=foo>hello2</span><span data-testid=bar>world2</span>
|
||||||
|
`);
|
||||||
|
await expect(page.locator('div').and(page.locator('div'))).toHaveCount(2);
|
||||||
|
await expect(page.locator('div').and(page.getByTestId('foo'))).toHaveText(['hello']);
|
||||||
|
await expect(page.locator('div').and(page.getByTestId('bar'))).toHaveText(['world']);
|
||||||
|
await expect(page.getByTestId('foo').and(page.locator('div'))).toHaveText(['hello']);
|
||||||
|
await expect(page.getByTestId('bar').and(page.locator('span'))).toHaveText(['world2']);
|
||||||
|
await expect(page.locator('span').and(page.getByTestId(/bar|foo/))).toHaveCount(2);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support locator.or', async ({ page }) => {
|
it('should support locator.or', async ({ page }) => {
|
||||||
await page.setContent(`<div>hello</div><span>world</span>`);
|
await page.setContent(`<div>hello</div><span>world</span>`);
|
||||||
await expect(page.locator('div').or(page.locator('span'))).toHaveCount(2);
|
await expect(page.locator('div').or(page.locator('span'))).toHaveCount(2);
|
||||||
|
|
|
||||||
|
|
@ -409,6 +409,19 @@ it('should work with internal:has-not=', async ({ page }) => {
|
||||||
expect(await page.$$eval(`section >> internal:has-not="article"`, els => els.length)).toBe(2);
|
expect(await page.$$eval(`section >> internal:has-not="article"`, els => els.length)).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work with internal:and=', async ({ page, server }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div class=foo>hello</div><div class=bar>world</div>
|
||||||
|
<span class=foo>hello2</span><span class=bar>world2</span>
|
||||||
|
`);
|
||||||
|
expect(await page.$$eval(`div >> internal:and="span"`, els => els.map(e => e.textContent))).toEqual([]);
|
||||||
|
expect(await page.$$eval(`div >> internal:and=".foo"`, els => els.map(e => e.textContent))).toEqual(['hello']);
|
||||||
|
expect(await page.$$eval(`div >> internal:and=".bar"`, els => els.map(e => e.textContent))).toEqual(['world']);
|
||||||
|
expect(await page.$$eval(`span >> internal:and="span"`, els => els.map(e => e.textContent))).toEqual(['hello2', 'world2']);
|
||||||
|
expect(await page.$$eval(`.foo >> internal:and="div"`, els => els.map(e => e.textContent))).toEqual(['hello']);
|
||||||
|
expect(await page.$$eval(`.bar >> internal:and="span"`, els => els.map(e => e.textContent))).toEqual(['world2']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should work with internal:or=', async ({ page, server }) => {
|
it('should work with internal:or=', async ({ page, server }) => {
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
<div>hello</div>
|
<div>hello</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue