cypress icon indicating copy to clipboard operation
cypress copied to clipboard

Support xpath support for cy.get() and .find()

Open fringd opened this issue 7 years ago • 62 comments

Current behavior:

.get('//div') doesn't work .find('p/span') also doesn't work

Desired behavior:

It gets the element using document.evaluate just like get and find currently uses document.querySelector.

or to avoid collisions we add getXpath and findXpath or something.

Motivation

xpath is fairly standard in integration tests, and is much more powerful than css selectors are, and I'm working on a shared library of xpath selectors for use with selenium, puppeteer, and ideally, cypress.

I'm happy to help with writing this code if people agree it's a useful feature that would be likely to get merged.

fringd avatar Feb 07 '18 23:02 fringd

I don't know the full depth of the complexity of the implementation of this, but this is definitely possible.

  • cy.get() code is custom to cypress: https://github.com/cypress-io/cypress/blob/issue-895/packages/driver/src/cy/commands/querying.coffee#L93
  • .find() code just passes is along to jQuerys implementation basically: https://github.com/cypress-io/cypress/blob/issue-895/packages/driver/src/cy/commands/traversals.coffee#L5

jennifer-shehane avatar Feb 08 '18 17:02 jennifer-shehane

+1 for XPath support

3d-mac avatar Mar 02 '18 00:03 3d-mac

+1

mgalindo avatar Mar 06 '18 20:03 mgalindo

+1, this would make migrating from an existing selenium framework significantly easier.

CliffDavis avatar Mar 12 '18 20:03 CliffDavis

These things already exist. You can use off the shelf tools to convert xpath to css selectors.

https://www.google.com/search?q=xpath+to+selector+converter&oq=xpath+to+selector+converter&aqs=chrome..69i57j0l4.6500j0j4&sourceid=chrome&ie=UTF-8

There are a few NPM modules you can just install and require in cypress.

brian-mann avatar Mar 12 '18 20:03 brian-mann

I'm pretty sure xpath is a super-set of css, so any translator from xpath to css would be incomplete. Part of the reason I want xpath is that it's more powerful.

fringd avatar Mar 12 '18 21:03 fringd

for example, in xpath you can select a node depending upon its children, or its text content.

fringd avatar Mar 13 '18 17:03 fringd

+1

JashonBrown avatar Mar 19 '18 01:03 JashonBrown

+1

JMVL64 avatar Apr 03 '18 13:04 JMVL64

+1

dharshinid avatar Apr 09 '18 12:04 dharshinid

+1

ravitheja04 avatar Apr 10 '18 08:04 ravitheja04

+1, although i got what I needed with cy.get('svg').find('id-of-svg-element')

ailadson avatar Apr 10 '18 17:04 ailadson

+1

moxventura avatar Apr 15 '18 20:04 moxventura

+1. Even in cases where CSS is enough, having Xpath would make some migrations from other frameworks so much easier.

nusco avatar Apr 18 '18 21:04 nusco

Yeah I've just ran into an issue, where a select() did not work due to a non-braking space in the inner HTML of the option. In XPath I could just normalize spaces and it would work just fine, so definitely a +1 from me.

szymach avatar Apr 29 '18 17:04 szymach

+1. Adding a data-cy attribute is a nice way, but we should be allowed to use xpath as well if we don't want to add any code specifically for testing.

saintflow47 avatar Apr 30 '18 08:04 saintflow47

Yes, searching by text content would be great with xpath, since that cannot be done with CSS selectors (it doesn't allow contains, see https://stackoverflow.com/questions/1520429/is-there-a-css-selector-for-elements-containing-certain-text?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa)

Edit: just found this: https://docs.cypress.io/api/commands/contains.html#Content

bryantabaird avatar May 25 '18 14:05 bryantabaird

+1

eduhlana avatar Jun 19 '18 19:06 eduhlana

I'd love that. I use CSS selectors whenever possible, but sometimes XPath is necessary.

One example of a selector that is hugely useful in XPath, but not possible in CSS, is selecting a table cell based on the header.

For instance, to get a cell in row 3 in column with a label "Age", you can use:

//table
  /tbody
    /tr[position()=3]
      /td[count(//table/thead/tr/th[text()='Age']/preceding-sibling::th) + 1]

Not exactly a trivial selector, but I still prefer it over doing this programmatically.

Here's a corresponding table:

<table>
  <thead>
    <tr>
      <th>Username</th>
      <th>Email address</th>
      <th>Age</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Mary</td>
      <td>[email protected]</td>
      <td>62</td>
    </tr>
    <tr>
      <td>John</td>
      <td>[email protected]</td>
      <td>60</td>
    </tr>
    <tr>
      <td>Kate</td>
      <td>[email protected]</td>
      <td>20</td>
    </tr>
  </tbody>
</table>

kamituel avatar Jun 27 '18 10:06 kamituel

Am averse to 'me toos' and +1's but xpath capability it a must. There are a few scenarios where css will not work; the primary one being pointed out by fringd.

Even if it is the XPath 1.0 engine, it would make Cypress fully usable for any webpage...

v-mwalk avatar Jul 08 '18 23:07 v-mwalk

@kamituel in your example above why couldn't you just do this?

// find the cell containing the content
cy.contains('td', '20')

// or you could find a higher up element if you wanted to
cy.contains('tr', '20')

The vast majority of use cases I've seen are for XPath related to an element containing text and you can accomplish that with cy.contains().

brian-mann avatar Jul 09 '18 16:07 brian-mann

I just looked it up and apparently there is already a document.evaluate which will take an xpath.

You should already be able to do this with Cypress with a custom command.

https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate

At the very least, adding this to the query methods of Cypress may not be that difficult afterall.

brian-mann avatar Jul 09 '18 16:07 brian-mann

@bryantabaird went back through to review comments. Per my comment here: you can use cy.contains() rather than xpath if you want to select an element or a parent element by its text content.

We've found cy.contains() can achieve what you normally would do with a much more complex xpath selector.

https://github.com/cypress-io/cypress/issues/1274#issuecomment-403538946

brian-mann avatar Jul 09 '18 16:07 brian-mann

@brian-mann To follow a real example, I'm working on a web application that has several large tables (for reporting purposes), that can easily have 20-30 columns. In some test cases, I want to assert on contents of just a few out of those 20-30 columns.

Using: cy.contains('td', '20') would work, but:

  • it doesn't specify in which column I expect to see the value "20"
  • it leads to not very readable test code that will grow to be hard to maintain

Let's assume, still following my example, that I want to assert on contents of the first and third column, and I'm not interested in the contents of the 2nd:

cy.contains('td', '20')
cy.contains('td', 'Kate')

This code will match:

    <tr>
      <td>Kate</td>
      <td>[email protected]</td>
      <td>20</td>
    </tr>

but it'll also match:

    <tr>
      <td>20</td>
      <td>[email protected]</td>
      <td>Kate</td>
    </tr>

So I can start adding some more specific selectors, or use data-cy:

cy.contains('td:nth-of-type(3)', '20')
cy.contains('td:nth-of-type(1)', 'Kate')

But it gets tricky quickly - I'll have to update tests as the table grows, or when I remove a column, etc...

Compare it to (a hypothetical code):

cy.get(cell(3, 'Age')).contains('20');
cy.get(cell(3, 'Username')).contains('Kate');

// With a helper function:

function cell(rowNumber, columnHeader) {
  return `//table
            /tbody
              /tr[position()=${ rowNumber }]
                /td[count(//table/thead/tr/th[text()='${ columnHeader }']/preceding-sibling::th) + 1]`;
}

Sure, the helper function uses a fairly complex XPath selector, but it's not that bad once one gets used to XPath, and it's still simpler than a helper function/command would be without XPath. Plus, it leads to simple, readable and maintainable test code.

In general, I prefer CSS over XPath any day, as it leads to shorter selectors whose syntax everyone is familiar with. But when given a choice of a complex helper function that would need to iterate over a number of DOM nodes, versus a declarative XPath selector, I strongly prefer XPath.

Also, yes, there is document.evaluate I use often to test XPath selectors (when using them with Selenium). I even added a bookmarklet to Chrome which allows me to easily use it on every website:

javascript:(window.find_all = function (query) {     var nodes = [];     var iterator = document.evaluate(query, document);     try {         var node = iterator.iterateNext();         while (node) {             nodes.push(node);             node = iterator.iterateNext();         }     } catch (e) {         console.error("Document tree modified during iteration", e);         return null;     }          return nodes; })();

Usage:

find_all(xpath_selector);

I would imagine in Cypress, we could accept XPath selectors prefixed with some character. Like in cy.get(...), which accepts aliases with @ prefix. Or maybe we could just use /?

kamituel avatar Jul 11 '18 08:07 kamituel

What I'm saying is that with Cypress you already have native DOM access to your window/document.

Just use the code you wrote just now and put it in a custom command so that it finds you the DOM element and then pass that off to cy.wrap() so you can then assume the element and then continue to chain off of it via Cypress.

brian-mann avatar Jul 11 '18 09:07 brian-mann

You mean write a custom command that'll accept an XPath selector and yield a DOM element? Sure, we can do that. And I'm pretty sure I eventually will, as we slowly (but surely) go about migrating our Selenium tests to Cypress ;)

Question is - should Cypress' built-in .get() support XPath? (or maybe have support for it in a separate command...)

I'd estimate that maybe 5% of selectors I would write in Selenium are XPath. Apparently, 20+ people who upvoted the OP have somewhat similar experiences. Is it enough to warrant having it built into Cypress itself? Not for me do decide :)

If you, the Cypress' core team, will decide it's not worth the effort, would you accept a PR that adds XPath support to .get(), or you rather think it belongs elsewhere (an external library)? Do you have plans to provide a "commons" library with stuff that is helpful to a large number of people, but doesn't necessarily belong to the set of core API's?

kamituel avatar Jul 11 '18 10:07 kamituel

@kamituel We would certainly accept a PR that adds support for XPath! We are not against supporting XPath considering the demand from users here.

Unfortunately, we are a small team, so it is not within our roadmap at this moment to work on this - so @brian-mann suggestions were to help you all with a workaround to use now.

We also feature custom commands in our plugins, so if that's an easier option than doing a PR - let us know and we can share it there.

jennifer-shehane avatar Jul 27 '18 23:07 jennifer-shehane

Another +1 for Xpath support in the Cypress .get() method

HGani1 avatar Aug 17 '18 14:08 HGani1

+1

gabriel-c-pereira avatar Oct 29 '18 19:10 gabriel-c-pereira

any updates on this? -it's pretty useful in terms of work with the pretty complex DOM

eugene-ray avatar Nov 06 '18 16:11 eugene-ray