feat: make React and Vue selectors experimental (#8106)

This commit is contained in:
Andrey Lushnikov 2021-08-11 03:21:16 +03:00 committed by GitHub
parent 792986c92d
commit 4975f4179e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 122 deletions

View File

@ -179,40 +179,40 @@ methods accept [`param: selector`] as their first argument.
await page.ClickAsync("xpath=//button");
```
Learn more about [XPath selector][xpath].
- React selector
- React selector (experimental)
```js
await page.click('react=ListItem[text *= "milk" i]');
await page.click('_react=ListItem[text *= "milk" i]');
```
```java
page.click("react=ListItem[text *= 'milk' i]");
page.click("_react=ListItem[text *= 'milk' i]");
```
```python async
await page.click("react=ListItem[text *= 'milk' i]")
await page.click("_react=ListItem[text *= 'milk' i]")
```
```python sync
page.click("react=ListItem[text *= 'milk' i]")
page.click("_react=ListItem[text *= 'milk' i]")
```
```csharp
await page.ClickAsync("react=ListItem[text *= 'milk' i]");
await page.ClickAsync("_react=ListItem[text *= 'milk' i]");
```
Learn more about [React selector][react].
- Vue selector
Learn more about [React selectors][react].
- Vue selector (experimental)
```js
await page.click('vue=list-item[text *= "milk" i]');
await page.click('_vue=list-item[text *= "milk" i]');
```
```java
page.click("vue=list-item[text *= 'milk' i]");
page.click("_vue=list-item[text *= 'milk' i]");
```
```python async
await page.click("vue=list-item[text *= "milk" i]")
await page.click("_vue=list-item[text *= "milk" i]")
```
```python sync
page.click("vue=list-item[text *= 'milk' i]")
page.click("_vue=list-item[text *= 'milk' i]")
```
```csharp
await page.ClickAsync("vue=list-item[text *= 'milk' i]");
await page.ClickAsync("_vue=list-item[text *= 'milk' i]");
```
Learn more about [Vue selector][vue].
Learn more about [Vue selectors][vue].
@ -663,22 +663,26 @@ converts `'//html/body'` to `'xpath=//html/body'`.
## React selectors
:::note
React selectors are experimental and prefixed with `_`. The functionality might change in future.
:::
React selectors allow selecting elements by its component name and property values. The syntax is very similar to [attribute selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) and supports all attribute selector operators.
In react selectors, component names are transcribed with **CamelCase**.
Selector examples:
- match by **component**: `react=BookItem`
- match by component and **exact property value**, case-sensetive: `react=BookItem[author = "Steven King"]`
- match by property value only, **case-insensetive**: `react=[author = "steven king" i]`
- match by component and **truthy property value**: `react=MyButton[enabled]`
- match by component and **boolean value**: `react=MyButton[enabled = false]`
- match by property **value substring**: `react=[author *= "King"]`
- match by component and **multiple properties**: `react=BookItem[author *= "king" i][year = 1990]`
- match by **nested** property value: `react=[some.nested.value = 12]`
- match by component and property value **prefix**: `react=BookItem[author ^= "Steven"]`
- match by component and property value **suffix**: `react=BookItem[author $= "Steven"]`
- match by **component**: `_react=BookItem`
- match by component and **exact property value**, case-sensetive: `_react=BookItem[author = "Steven King"]`
- match by property value only, **case-insensetive**: `_react=[author = "steven king" i]`
- match by component and **truthy property value**: `_react=MyButton[enabled]`
- match by component and **boolean value**: `_react=MyButton[enabled = false]`
- match by property **value substring**: `_react=[author *= "King"]`
- match by component and **multiple properties**: `_react=BookItem[author *= "king" i][year = 1990]`
- match by **nested** property value: `_react=[some.nested.value = 12]`
- match by component and property value **prefix**: `_react=BookItem[author ^= "Steven"]`
- match by component and property value **suffix**: `_react=BookItem[author $= "Steven"]`
@ -695,22 +699,26 @@ React selectors, as well as [React DevTools](https://chrome.google.com/webstore/
## Vue selectors
:::note
Vue selectors are experimental and prefixed with `_`. The functionality might change in future.
:::
Vue selectors allow selecting elements by its component name and property values. The syntax is very similar to [attribute selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) and supports all attribute selector operators.
In vue selectors, component names are transcribed with **kebab-case**.
Selector examples:
- match by **component**: `vue=book-item`
- match by component and **exact property value**, case-sensetive: `vue=book-item[author = "Steven King"]`
- match by property value only, **case-insensetive**: `vue=[author = "steven king" i]`
- match by component and **truthy property value**: `vue=my-button[enabled]`
- match by component and **boolean value**: `vue=my-button[enabled = false]`
- match by property **value substring**: `vue=[author *= "King"]`
- match by component and **multiple properties**: `vue=book-item[author *= "king" i][year = 1990]`
- match by **nested** property value: `vue=[some.nested.value = 12]`
- match by component and property value **prefix**: `vue=book-item[author ^= "Steven"]`
- match by component and property value **suffix**: `vue=book-item[author $= "Steven"]`
- match by **component**: `_vue=book-item`
- match by component and **exact property value**, case-sensetive: `_vue=book-item[author = "Steven King"]`
- match by property value only, **case-insensetive**: `_vue=[author = "steven king" i]`
- match by component and **truthy property value**: `_vue=my-button[enabled]`
- match by component and **boolean value**: `_vue=my-button[enabled = false]`
- match by property **value substring**: `_vue=[author *= "King"]`
- match by component and **multiple properties**: `_vue=book-item[author *= "king" i][year = 1990]`
- match by **nested** property value: `_vue=[some.nested.value = 12]`
- match by component and property value **prefix**: `_vue=book-item[author ^= "Steven"]`
- match by component and property value **suffix**: `_vue=book-item[author $= "Steven"]`
To find Vue element names in a tree use [Vue DevTools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=en).

View File

@ -64,8 +64,8 @@ export class InjectedScript {
this._engines = new Map();
this._engines.set('xpath', XPathEngine);
this._engines.set('xpath:light', XPathEngine);
this._engines.set('react', ReactEngine);
this._engines.set('vue', VueEngine);
this._engines.set('_react', ReactEngine);
this._engines.set('_vue', VueEngine);
this._engines.set('text', this._createTextEngine(true));
this._engines.set('text:light', this._createTextEngine(false));
this._engines.set('id', this._createAttributeEngine('id', true));

View File

@ -39,7 +39,7 @@ export class Selectors {
this._builtinEngines = new Set([
'css', 'css:light',
'xpath', 'xpath:light',
'react', 'vue',
'_react', '_vue',
'text', 'text:light',
'id', 'id:light',
'data-testid', 'data-testid:light',
@ -48,7 +48,7 @@ export class Selectors {
'_visible', '_nth'
]);
this._builtinEnginesInMainWorld = new Set([
'react', 'vue',
'_react', '_vue',
]);
this._engines = new Map();
}

View File

@ -30,79 +30,79 @@ for (const [name, url] of Object.entries(reacts)) {
});
it('should work with single-root elements', async ({page}) => {
expect(await page.$$eval(`react=BookList`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=BookItem`, els => els.length)).toBe(3);
expect(await page.$$eval(`react=BookList >> react=BookItem`, els => els.length)).toBe(3);
expect(await page.$$eval(`react=BookItem >> react=BookList`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=BookList`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=BookItem`, els => els.length)).toBe(3);
expect(await page.$$eval(`_react=BookList >> _react=BookItem`, els => els.length)).toBe(3);
expect(await page.$$eval(`_react=BookItem >> _react=BookList`, els => els.length)).toBe(0);
});
it('should work with multi-root elements (fragments)', async ({page}) => {
it.skip(name === 'react15', 'React 15 does not support fragments');
expect(await page.$$eval(`react=App`, els => els.length)).toBe(14);
expect(await page.$$eval(`react=AppHeader`, els => els.length)).toBe(2);
expect(await page.$$eval(`react=NewBook`, els => els.length)).toBe(2);
expect(await page.$$eval(`_react=App`, els => els.length)).toBe(14);
expect(await page.$$eval(`_react=AppHeader`, els => els.length)).toBe(2);
expect(await page.$$eval(`_react=NewBook`, els => els.length)).toBe(2);
});
it('should not crash when there is no match', async ({page}) => {
expect(await page.$$eval(`react=Apps`, els => els.length)).toBe(0);
expect(await page.$$eval(`react=BookLi`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=Apps`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=BookLi`, els => els.length)).toBe(0);
});
it('should compose', async ({page}) => {
expect(await page.$eval(`react=NewBook >> react=button`, el => el.textContent)).toBe('new book');
expect(await page.$eval(`react=NewBook >> react=input`, el => el.tagName)).toBe('INPUT');
expect(await page.$eval(`react=BookItem >> text=Gatsby`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$eval(`_react=NewBook >> _react=button`, el => el.textContent)).toBe('new book');
expect(await page.$eval(`_react=NewBook >> _react=input`, el => el.tagName)).toBe('INPUT');
expect(await page.$eval(`_react=BookItem >> text=Gatsby`, el => el.textContent)).toBe('The Great Gatsby');
});
it('should query by props combinations', async ({page}) => {
expect(await page.$$eval(`react=BookItem[name="The Great Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=BookItem[name="the great gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=ColorButton[nested.index = 0]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=ColorButton[nested.nonexisting.index = 0]`, els => els.length)).toBe(0);
expect(await page.$$eval(`react=ColorButton[nested.index.nonexisting = 0]`, els => els.length)).toBe(0);
expect(await page.$$eval(`react=ColorButton[nested.index.nonexisting = 1]`, els => els.length)).toBe(0);
expect(await page.$$eval(`react=ColorButton[nested.value = 4.1]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=ColorButton[enabled = false]`, els => els.length)).toBe(4);
expect(await page.$$eval(`react=ColorButton[enabled = true] `, els => els.length)).toBe(5);
expect(await page.$$eval(`react=ColorButton[enabled = true][color = "red"]`, els => els.length)).toBe(2);
expect(await page.$$eval(`react=ColorButton[enabled = true][color = "red"i][nested.index = 6]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=BookItem[name="The Great Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=BookItem[name="the great gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=ColorButton[nested.index = 0]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=ColorButton[nested.nonexisting.index = 0]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=ColorButton[nested.index.nonexisting = 0]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=ColorButton[nested.index.nonexisting = 1]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=ColorButton[nested.value = 4.1]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=ColorButton[enabled = false]`, els => els.length)).toBe(4);
expect(await page.$$eval(`_react=ColorButton[enabled = true] `, els => els.length)).toBe(5);
expect(await page.$$eval(`_react=ColorButton[enabled = true][color = "red"]`, els => els.length)).toBe(2);
expect(await page.$$eval(`_react=ColorButton[enabled = true][color = "red"i][nested.index = 6]`, els => els.length)).toBe(1);
});
it('should exact match by props', async ({page}) => {
expect(await page.$eval(`react=BookItem[name = "The Great Gatsby"]`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$$eval(`react=BookItem[name = "The Great Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$eval(`_react=BookItem[name = "The Great Gatsby"]`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$$eval(`_react=BookItem[name = "The Great Gatsby"]`, els => els.length)).toBe(1);
// case sensetive by default
expect(await page.$$eval(`react=BookItem[name = "the great gatsby"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`react=BookItem[name = "the great gatsby" s]`, els => els.length)).toBe(0);
expect(await page.$$eval(`react=BookItem[name = "the great gatsby" S]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=BookItem[name = "the great gatsby"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=BookItem[name = "the great gatsby" s]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=BookItem[name = "the great gatsby" S]`, els => els.length)).toBe(0);
// case insensetive with flag
expect(await page.$$eval(`react=BookItem[name = "the great gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=BookItem[name = "the great gatsby" I]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=BookItem[name = " The Great Gatsby "]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=BookItem[name = "the great gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=BookItem[name = "the great gatsby" I]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=BookItem[name = " The Great Gatsby "]`, els => els.length)).toBe(0);
});
it('should partially match by props', async ({page}) => {
// Check partial matching
expect(await page.$eval(`react=BookItem[name *= "Gatsby"]`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$$eval(`react=BookItem[name *= "Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=[name *= "Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$eval(`_react=BookItem[name *= "Gatsby"]`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$$eval(`_react=BookItem[name *= "Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=[name *= "Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=BookItem[name = "Gatsby"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=BookItem[name = "Gatsby"]`, els => els.length)).toBe(0);
});
it('should support all string operators', async ({page}) => {
expect(await page.$$eval(`react=ColorButton[color = "red"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`react=ColorButton[color |= "red"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`react=ColorButton[color $= "ed"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`react=ColorButton[color ^= "gr"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`react=ColorButton[color ~= "e"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`react=BookItem[name ~= "gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`react=BookItem[name *= " gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=ColorButton[color = "red"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`_react=ColorButton[color |= "red"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`_react=ColorButton[color $= "ed"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`_react=ColorButton[color ^= "gr"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`_react=ColorButton[color ~= "e"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_react=BookItem[name ~= "gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_react=BookItem[name *= " gatsby" i]`, els => els.length)).toBe(1);
});
it('should support truthy querying', async ({page}) => {
expect(await page.$$eval(`react=ColorButton[enabled]`, els => els.length)).toBe(5);
expect(await page.$$eval(`_react=ColorButton[enabled]`, els => els.length)).toBe(5);
});
});
}

View File

@ -29,77 +29,77 @@ for (const [name, url] of Object.entries(vues)) {
});
it('should work with single-root elements', async ({page}) => {
expect(await page.$$eval(`vue=book-list`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=book-item`, els => els.length)).toBe(3);
expect(await page.$$eval(`vue=book-list >> vue=book-item`, els => els.length)).toBe(3);
expect(await page.$$eval(`vue=book-item >> vue=book-list`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=book-list`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=book-item`, els => els.length)).toBe(3);
expect(await page.$$eval(`_vue=book-list >> _vue=book-item`, els => els.length)).toBe(3);
expect(await page.$$eval(`_vue=book-item >> _vue=book-list`, els => els.length)).toBe(0);
});
it('should work with multi-root elements (fragments)', async ({page}) => {
it.skip(name === 'vue2', 'vue2 does not support fragments');
expect(await page.$$eval(`vue=Root`, els => els.length)).toBe(14);
expect(await page.$$eval(`vue=app-header`, els => els.length)).toBe(2);
expect(await page.$$eval(`vue=new-book`, els => els.length)).toBe(2);
expect(await page.$$eval(`_vue=Root`, els => els.length)).toBe(14);
expect(await page.$$eval(`_vue=app-header`, els => els.length)).toBe(2);
expect(await page.$$eval(`_vue=new-book`, els => els.length)).toBe(2);
});
it('should not crash when there is no match', async ({page}) => {
expect(await page.$$eval(`vue=apps`, els => els.length)).toBe(0);
expect(await page.$$eval(`vue=book-li`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=apps`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=book-li`, els => els.length)).toBe(0);
});
it('should compose', async ({page}) => {
expect(await page.$eval(`vue=book-item >> text=Gatsby`, el => el.textContent.trim())).toBe('The Great Gatsby');
expect(await page.$eval(`_vue=book-item >> text=Gatsby`, el => el.textContent.trim())).toBe('The Great Gatsby');
});
it('should query by props combinations', async ({page}) => {
expect(await page.$$eval(`vue=book-item[name="The Great Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=book-item[name="the great gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=color-button[nested.index = 0]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=color-button[nested.nonexisting.index = 0]`, els => els.length)).toBe(0);
expect(await page.$$eval(`vue=color-button[nested.index.nonexisting = 0]`, els => els.length)).toBe(0);
expect(await page.$$eval(`vue=color-button[nested.index.nonexisting = 1]`, els => els.length)).toBe(0);
expect(await page.$$eval(`vue=color-button[nested.value = 4.1]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=color-button[enabled = false]`, els => els.length)).toBe(4);
expect(await page.$$eval(`vue=color-button[enabled = true] `, els => els.length)).toBe(5);
expect(await page.$$eval(`vue=color-button[enabled = true][color = "red"]`, els => els.length)).toBe(2);
expect(await page.$$eval(`vue=color-button[enabled = true][color = "red"i][nested.index = 6]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=book-item[name="The Great Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=book-item[name="the great gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=color-button[nested.index = 0]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=color-button[nested.nonexisting.index = 0]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=color-button[nested.index.nonexisting = 0]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=color-button[nested.index.nonexisting = 1]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=color-button[nested.value = 4.1]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=color-button[enabled = false]`, els => els.length)).toBe(4);
expect(await page.$$eval(`_vue=color-button[enabled = true] `, els => els.length)).toBe(5);
expect(await page.$$eval(`_vue=color-button[enabled = true][color = "red"]`, els => els.length)).toBe(2);
expect(await page.$$eval(`_vue=color-button[enabled = true][color = "red"i][nested.index = 6]`, els => els.length)).toBe(1);
});
it('should exact match by props', async ({page}) => {
expect(await page.$eval(`vue=book-item[name = "The Great Gatsby"]`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$$eval(`vue=book-item[name = "The Great Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$eval(`_vue=book-item[name = "The Great Gatsby"]`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$$eval(`_vue=book-item[name = "The Great Gatsby"]`, els => els.length)).toBe(1);
// case sensetive by default
expect(await page.$$eval(`vue=book-item[name = "the great gatsby"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`vue=book-item[name = "the great gatsby" s]`, els => els.length)).toBe(0);
expect(await page.$$eval(`vue=book-item[name = "the great gatsby" S]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=book-item[name = "the great gatsby"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=book-item[name = "the great gatsby" s]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=book-item[name = "the great gatsby" S]`, els => els.length)).toBe(0);
// case insensetive with flag
expect(await page.$$eval(`vue=book-item[name = "the great gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=book-item[name = "the great gatsby" I]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=book-item[name = " The Great Gatsby "]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=book-item[name = "the great gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=book-item[name = "the great gatsby" I]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=book-item[name = " The Great Gatsby "]`, els => els.length)).toBe(0);
});
it('should partially match by props', async ({page}) => {
// Check partial matching
expect(await page.$eval(`vue=book-item[name *= "Gatsby"]`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$$eval(`vue=book-item[name *= "Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=[name *= "Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$eval(`_vue=book-item[name *= "Gatsby"]`, el => el.textContent)).toBe('The Great Gatsby');
expect(await page.$$eval(`_vue=book-item[name *= "Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=[name *= "Gatsby"]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=book-item[name = "Gatsby"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=book-item[name = "Gatsby"]`, els => els.length)).toBe(0);
});
it('should support all string operators', async ({page}) => {
expect(await page.$$eval(`vue=color-button[color = "red"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`vue=color-button[color |= "red"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`vue=color-button[color $= "ed"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`vue=color-button[color ^= "gr"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`vue=color-button[color ~= "e"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`vue=book-item[name ~= "gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`vue=book-item[name *= " gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=color-button[color = "red"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`_vue=color-button[color |= "red"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`_vue=color-button[color $= "ed"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`_vue=color-button[color ^= "gr"]`, els => els.length)).toBe(3);
expect(await page.$$eval(`_vue=color-button[color ~= "e"]`, els => els.length)).toBe(0);
expect(await page.$$eval(`_vue=book-item[name ~= "gatsby" i]`, els => els.length)).toBe(1);
expect(await page.$$eval(`_vue=book-item[name *= " gatsby" i]`, els => els.length)).toBe(1);
});
it('should support truthy querying', async ({page}) => {
expect(await page.$$eval(`vue=color-button[enabled]`, els => els.length)).toBe(5);
expect(await page.$$eval(`_vue=color-button[enabled]`, els => els.length)).toBe(5);
});
});
}