Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for find by attribute and content #2965

Merged
merged 1 commit into from
Sep 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
90 changes: 47 additions & 43 deletions packages/webdriverio/src/utils.js
Expand Up @@ -43,12 +43,12 @@ export const findStrategy = function (value, isW3C) {
value.indexOf('*/') === 0) {
using = 'xpath'

// use link text strategy if value startes with =
// use link text strategy if value starts with =
} else if (value.indexOf('=') === 0) {
using = 'link text'
value = value.slice(1)

// use partial link text strategy if value startes with *=
// use partial link text strategy if value starts with *=
} else if (value.indexOf('*=') === 0) {
using = 'partial link text'
value = value.slice(2)
Expand Down Expand Up @@ -86,50 +86,54 @@ export const findStrategy = function (value, isW3C) {
using = 'name'
value = value.match(/^\[name=("|')([a-zA-z0-9\-_. ]+)("|')]$/)[2]

// any element with given text e.g. h1=Welcome
} else if (value.search(/^[a-z0-9]*=(.)+$/) >= 0) {
let query = value.split(/=/)
let tag = query.shift()

using = 'xpath'
value = `.//${tag.length ? tag : '*'}[normalize-space() = "${query.join('=')}"]`

// any element containing given text
} else if (value.search(/^[a-z0-9]*\*=(.)+$/) >= 0) {
let query = value.split(/\*=/)
let tag = query.shift()

using = 'xpath'
value = `.//${tag.length ? tag : '*'}[contains(., "${query.join('*=')}")]`

// any element with certain class or id + given content
} else if (value.search(/^[a-z0-9]*(\.|#)-?[_a-zA-Z]+[_a-zA-Z0-9-]*=(.)+$/) >= 0) {
let query = value.split(/=/)
let tag = query.shift()

let classOrId = tag.substr(tag.search(/(\.|#)/), 1) === '#' ? 'id' : 'class'
let classOrIdName = tag.slice(tag.search(/(\.|#)/) + 1)

tag = tag.substr(0, tag.search(/(\.|#)/))
using = 'xpath'
value = `.//${tag.length ? tag : '*'}[contains(@${classOrId}, "${classOrIdName}") and normalize-space() = "${query.join('=')}"]`

// any element with certain class or id + has certain content
} else if (value.search(/^[a-z0-9]*(\.|#)-?[_a-zA-Z]+[_a-zA-Z0-9-]*\*=(.)+$/) >= 0) {
let query = value.split(/\*=/)
let tag = query.shift()

let classOrId = tag.substr(tag.search(/(\.|#)/), 1) === '#' ? 'id' : 'class'
let classOrIdName = tag.slice(tag.search(/(\.|#)/) + 1)

tag = tag.substr(0, tag.search(/(\.|#)/))
using = 'xpath'
value = './/' + (tag.length ? tag : '*') + '[contains(@' + classOrId + ', "' + classOrIdName + '") and contains(., "' + query.join('*=') + '")]'
value = `.//${tag.length ? tag : '*'}[contains(@${classOrId}, "${classOrIdName}") and contains(., "${query.join('*=')}")]`

// allow to move up to the parent or select current element
} else if (value === '..' || value === '.') {
using = 'xpath'

// any element with given class, id, or attribute and content
// e.g. h1.header=Welcome or [data-name=table-row]=Item or #content*=Intro
} else {
const match = value.match(new RegExp([
// HTML tag
/^([a-z0-9]*)/,
// optional . or # + class or id
/(?:(\.|#)(-?[_a-zA-Z]+[_a-zA-Z0-9-]*))?/,
// optional [attribute-name="attribute-value"]
/(?:\[(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)(?:=(?:"|')([a-zA-z0-9\-_. ]+)(?:"|'))?\])?/,
// *=query or =query
/(\*)?=(.+)$/,
].map(rx => rx.source).join('')))

if (match) {
gztomas marked this conversation as resolved.
Show resolved Hide resolved
const PREFIX_NAME = { '.': 'class', '#': 'id' }
const conditions = []
const [
tag,
prefix, name,
attrName, attrValue,
partial, query
] = match.slice(1)

if (prefix) {
conditions.push(`contains(@${PREFIX_NAME[prefix]}, "${name}")`)
}
if (attrName) {
conditions.push(
attrValue
? `contains(@${attrName}, "${attrValue}")`
: `@${attrName}`
);
}
if (partial) {
conditions.push(`contains(., "${query}")`)
} else {
conditions.push(`normalize-space() = "${query}"`)
}

using = 'xpath'
value = `.//${tag || '*'}[${conditions.join(' and ')}]`
}
}

/**
Expand Down Expand Up @@ -171,7 +175,7 @@ export const getPrototype = (scope) => {
*/
export const getElementFromResponse = (res) => {
/**
* depcrecated JSONWireProtocol response
* deprecated JSONWireProtocol response
*/
if (res.ELEMENT) {
return res.ELEMENT
Expand Down
30 changes: 30 additions & 0 deletions packages/webdriverio/tests/utils.test.js
Expand Up @@ -146,6 +146,36 @@ describe('utils', () => {
expect(element.value).toBe('.//*[contains(@id, "What-is-WebdriverIO") and contains(., "What")]')
})

it('should find an element by tag name + attribute + content', () => {
const element = findStrategy('div[some-attribute="some-value"]=some random text with "§$%&/()div=or others')
expect(element.using).toBe('xpath')
expect(element.value).toBe('.//div[contains(@some-attribute, "some-value") and normalize-space() = "some random text with "§$%&/()div=or others"]')
})

it('should find an element by attribute + content', () => {
const element = findStrategy('[some-attribute="some-value"]=some random text with "§$%&/()div=or others')
expect(element.using).toBe('xpath')
expect(element.value).toBe('.//*[contains(@some-attribute, "some-value") and normalize-space() = "some random text with "§$%&/()div=or others"]')
})

it('should find an element by attribute existence + content', () => {
const element = findStrategy('[some-attribute]=some random text with "§$%&/()div=or others')
expect(element.using).toBe('xpath')
expect(element.value).toBe('.//*[@some-attribute and normalize-space() = "some random text with "§$%&/()div=or others"]')
})

it('should find an element by tag name + attribute + similar content', () => {
const element = findStrategy('div[some-attribute="some-value"]*=some random text with "§$%&/()div=or others')
expect(element.using).toBe('xpath')
expect(element.value).toBe('.//div[contains(@some-attribute, "some-value") and contains(., "some random text with "§$%&/()div=or others")]')
})

it('should find an element by attribute + similar content', () => {
const element = findStrategy('[some-attribute="some-value"]*=some random text with "§$%&/()div=or others')
expect(element.using).toBe('xpath')
expect(element.value).toBe('.//*[contains(@some-attribute, "some-value") and contains(., "some random text with "§$%&/()div=or others")]')
})

it('should allow to go up and down the DOM tree with xpath', () => {
let element = findStrategy('..')
expect(element.using).toBe('xpath')
Expand Down