Skip to content

Commit

Permalink
feat: support XPATH selectors (#321)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oniurain committed Feb 23, 2020
1 parent 49d313c commit 949027b
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 4 deletions.
12 changes: 11 additions & 1 deletion packages/expect-puppeteer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ await page.toClick('button', { text: 'My button' })
Expect an element to be in the page or element, then click on it.

- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to click on
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to click on.
- `options` <[Object]> Optional parameters
- `button` <"left"|"right"|"middle"> Defaults to `left`.
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
Expand All @@ -88,6 +88,7 @@ Expect an element to be in the page or element, then click on it.

```js
await expect(page).toClick('button', { text: 'Home' })
await expect(page).toClick({type:'xpath', value:'\\a'}, { text: 'Click' })
```

### <a name="toDisplayDialog"></a>expect(page).toDisplayDialog(block)
Expand Down Expand Up @@ -202,6 +203,15 @@ await expect(page).toUploadFile(
path.join(__dirname, 'file.txt'),
)
```
### <a name="MatchSelector"></a>{type: [string], value: [string]}
An object used as parameter in order to select an element.
- `type` <"xpath"|"css"> The type of the selector
- `value` <[string]> The value of the selector

```js
{type:'css', value:'form[name="myForm"]'}
{type:'xpath', value:'.\\a'}
```

## Configure default options

Expand Down
71 changes: 71 additions & 0 deletions packages/expect-puppeteer/src/matchers/toClick.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,53 @@ describe('toClick', () => {
expect(pathname).toBe('/page2.html')
})

it('should click using xpath selector', async () => {
await expect(page).toClick({
value: '//a[contains(@href,"/page2.html")]',
type: "xpath"
})
await page.waitForNavigation()
const pathname = await page.evaluate(() => document.location.pathname)
expect(pathname).toBe('/page2.html')
})

it('should click using css selector with object param', async () => {
await expect(page).toClick({
value: 'a[href="/page2.html"]',
type: "css"
})
await page.waitForNavigation()
const pathname = await page.evaluate(() => document.location.pathname)
expect(pathname).toBe('/page2.html')
})

it('should click using text', async () => {
await expect(page).toClick('a', { text: 'Page 2' })
await page.waitForNavigation()
const pathname = await page.evaluate(() => document.location.pathname)
expect(pathname).toBe('/page2.html')
})

it('should click using text with xpath selector', async () => {
await expect(page).toClick({
value: '//a',
type: 'xpath'
}, { text: 'Page 2' })
await page.waitForNavigation()
const pathname = await page.evaluate(() => document.location.pathname)
expect(pathname).toBe('/page2.html')
})

it('should click using text with css selector', async () => {
await expect(page).toClick({
value: 'a',
type: 'css'
}, { text: 'Page 2' })
await page.waitForNavigation()
const pathname = await page.evaluate(() => document.location.pathname)
expect(pathname).toBe('/page2.html')
})

it('should return an error if element is not found', async () => {
expect.assertions(2)

Expand All @@ -33,6 +73,26 @@ describe('toClick', () => {
expect(error.message).toMatch('Element a (text: "Nop") not found')
}
})

it('should return an error if element is not found with xpath selector', async () => {
expect.assertions(2)

try {
await expect(page).toClick({ value: '//a', type: 'xpath' }, { text: 'Nop' })
} catch (error) {
expect(error.message).toMatch('Element //a (text: "Nop") not found')
}
})

it('should return an error if element is not found with css selector as object', async () => {
expect.assertions(2)

try {
await expect(page).toClick({ value: 'a', type: 'css' }, { text: 'Nop' })
} catch (error) {
expect(error.message).toMatch('Element a (text: "Nop") not found')
}
})
})

describe('ElementHandle', () => {
Expand All @@ -44,6 +104,17 @@ describe('toClick', () => {
expect(pathname).toBe('/page2.html')
})

it('should click using xpath selector', async () => {
const body = await page.$('body')
await expect(body).toClick({
value: './/a[contains(@href,"/page2.html")]',
type: 'xpath'
})
await page.waitForSelector('html')
const pathname = await page.evaluate(() => document.location.pathname)
expect(pathname).toBe('/page2.html')
})

it('should click using text', async () => {
const body = await page.$('body')
await expect(body).toClick('a', { text: 'Page 2' })
Expand Down
27 changes: 24 additions & 3 deletions packages/expect-puppeteer/src/matchers/toMatchElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ async function toMatchElement(
{ text: searchExpr, visible = false, ...options } = {},
) {
options = defaultOptions(options)
selector = selector instanceof Object ? { ...selector } : { type: 'css', value: selector };

const { page, handle } = await getContext(instance, () => document)

const { text, regexp } = expandSearchExpr(searchExpr)
Expand All @@ -30,7 +32,26 @@ async function toMatchElement(
return true
}

const elements = [...handle.querySelectorAll(selector)].filter(isVisible)
let nodes = [];
switch (selector.type) {
case 'xpath': {
const xpathResults = document.evaluate(selector.value, handle)
let currentXpathResult = xpathResults.iterateNext();

while (currentXpathResult) {
nodes.push(currentXpathResult)
currentXpathResult = xpathResults.iterateNext();
}
break;
}
case 'css':
nodes = handle.querySelectorAll(selector.value)
break;
default:
throw new Error(`${selector.type} is not implemented`)
}

const elements = [...nodes].filter(isVisible)
if (regexp !== null) {
const [, pattern, flags] = regexp.match(/\/(.*)\/(.*)?/)
return elements.find(({ textContent }) =>
Expand Down Expand Up @@ -64,8 +85,8 @@ async function toMatchElement(
} catch (error) {
throw enhanceError(
error,
`Element ${selector}${
text !== null || regexp !== null ? ` (text: "${text || regexp}") ` : ' '
`Element ${selector.value}${
text !== null || regexp !== null ? ` (text: "${text || regexp}") ` : ' '
}not found`,
)
}
Expand Down

0 comments on commit 949027b

Please sign in to comment.