Finding elements: different syntax -> different results - Explanation needed - cypress

When I use:
cy.get('b').contains('xdz') // find 1 element
but when I use:
cy.get('b:contains("xdz")') // find 2 elements
Can someone explain me what is the difference?

cy.get('b').contains('xdz') is invoking a Cypress command, which is designed to only return a single element. This is by design so that you can narrow a search by text content.
cy.get('b:contains("xdz")') is using a jquery pseudo-selector :contains() to test the text inside element <b> and is designed to return all matching elements.
Pseudo-selectors are extensions to the CSS selector syntax that apply jQuery methods during the selection. In this case :contains(sometext) is shorthand for $el.text().contains('sometext'). Becuase it's part of the selector, it returns all matching elements.
It's worth while understanding jquery selector variations, as this example illustrates - it can give you different results for different situations.

contains('xdz') is a cypress command which always yields only the first element containing the text. You can read more about it from this Github Thread.
:contains("xdz") is a Jquery command and it returns all elements containing the text. You can read more about it from the Jquery Docs.

Related

Cypress xpath plugin does not work with cy.type()

I am unable to figure out why the cypress xpath is not working with the type(). I have two command functions: one that looks for the element using cy.get() and one that uses cy.xpath(). Unfortunately, this is a dynamic field so I have to use xpath.
(https://i.stack.imgur.com/wXl9q.png)
This is how I am using the above command.
(https://i.stack.imgur.com/snVn7.png)
Error:
(https://i.stack.imgur.com/08G32.png)
I tried to reading the cypress docs and searching on the internet, however the examples for solutions did not work. I am on on Electron version: 21.0.0, Bundled Node version:
16.16.0
I think you are mistaken about the results of your xpath expression.
You have used //div[5]/div[1]/input which can return multiple elements.
The // predicate will "select all" if they are present.
Only / predicate is guaranteed to return a single element.
Since Cypress is telling you it found multiple elements, it is more likely that your selector is wrong than the Cypress library.
You will have to change the xpath selector.
The answer is in your error message - you're trying to use cy.type() but the previous command (cy.xpath()) is yielding more than one element. I would focus on figuring out what differentiates the field you want to type in from the others found by cy.xpath(). If there is nothing different between them, then you can simply select the correct element by the 0-index of the element.
cy.xpath(element).eq(0).type(value); // assumes the first element yielded by cy.xpath

Capybara / Ruby - Trying to get only the Text from all ambiguous css selector and convert it to string

I'm trying to get all Texts from a specific CSS Selector that are ambiguous in the HTML. I would like to access these ambiguous css and get the Text and then return all that info.
I've figured out how to find all ambiguous selectors but I dont know how to get just the text from each selector.
The ambiguous selector is (it finds 3 matchers)
.list-card-title .js-card-name
I've already tried commands like:
arr = Array(3)
arr = find_all('.list-card-title.js-card-name').to_a
puts arr.to_s
When I use puts arr
I got the following output
[#<Capybara::Node::Element tag="span" path="/HTML/BODY[1]/DIV[2]/DIV[2]/DIV[1]/DIV[2]/DIV[3]/DIV[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[2]/A[1]/DIV[3]/SPAN[1]">, #<Capybara::Node::Element tag="span" path="/HTML/BODY[1]/DIV[2]/DIV[2]/DIV[1]/DIV[2]/DIV[3]/DIV[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[2]/A[2]/DIV[3]/SPAN[1]">, #<Capybara::Node::Element tag="span" path="/HTML/BODY[1]/DIV[2]/DIV[2]/DIV[1]/DIV[2]/DIV[3]/DIV[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[2]/A[3]/DIV[3]/SPAN[1]">]
To get the text of elements you need to call text on each of the elements. In your case the easiest way to do that would be
find_all('.list-card-title.js-card-name').map(&:text)
which will return an array of the text contained in each of the elements. If you then want all of that concatenated into one string you could do
find_all('.list-card-title.js-card-name').map(&:text).join
Note: you have tagged your questions with automated-tests, are you actually testing an app/site, or are you instead doing web scraping? If you are testing you'd be much better off writing your tests using Capybaras expectation/assertion methods (and the :text options they accept) rather than finding elements, extracting/manipulating contained text and then doing something (I assume asserting on) with that.

Is it possible to use Following and preceding in combination in Selenium?

On this page
https://en.wikipedia.org/wiki/Trinity_Seven#Episode_list
I have:
//*[text()='Reception']//preceding::th[contains(#id, 'ep')]//following::I
But it only registers following.
The default firepath selector is: .//*[#id='mw-content-text']/div/table[5]/tbody/tr/td[1]/I but this kind of selector is known to break quite frequently. Just wondering if there is a better way of doing this and I thought this might be a way.
Thanks!
:)
- You can see that it's getting stuff under the table which is not what I want :S
Try to use below XPath to match required elements:
//th[contains(#id, 'ep')]/following::I[./following::*[text()='Reception']]
This looks more simple
//tr[contains(#class, 'vevent')]//i
Don't overcomplicate things. You need I tag inside each row. So just find row locator tr[contains(#class, 'vevent')] and get it's I
Another good approach in case you want to check that inside of parent element is located some special element, but you want to find some 3rd element is to use such style: //element[./specific]//child , so in your case:
//tr[contains(#class, 'vevent')][./th[contains(#id,'ep')]]//i
so it's I tag inside row that contains #id,'ep' in header

Can't get nth node in Selenium

I try to write xpath expressions so that my tests won't be broken by small design changes. So instead of the expressions that Selenium IDE generates, I write my own.
Here's an issue:
//input[#name='question'][7]
This expression doesn't work at all. Input nodes named 'question' are spread across the page. They're not siblings.
I've tried using intermediate expression, but it also fails.
(//input[#name='question'])[2]
error = Error: Element (//input[#name='question'])[2] not found
That's why I suppose Seleniun has a wrong implementation of XPath.
According to XPath docs, the position predicate must filter by the position in the nodeset, so it must find the seventh input with the name 'question'. In Selenium this doesn't work. CSS selectors (:nth-of-kind) neither.
I had to write an expression that filters their common parents:
//*[contains(#class, 'question_section')][7]//input[#name='question']
Is this a Selenium specific issue, or I'm reading the specs wrong way? What can I do to make a shorter expression?
Here's an issue:
//input[#name='question'][7]
This expression doesn't work at all.
This is a FAQ.
[] has a higher priority than //.
The above expression selects every input element with #name = 'question', which is the 7th child of its parent -- and aparently the parents of input elements in the document that is not shown don't have so many input children.
Use (note the brackets):
(//input[#name='question'])[7]
This selects the 7th element input in the document that satisfies the conditions in the predicate.
Edit:
People, who know Selenium (Dave Hunt) suggest that the above expression is written in Selenium as:
xpath=(//input[#name='question'])[7]
If you want the 7th input with name attribute with a value of question in the source then try the following:
/descendant::input[#name='question'][7]

Selenium RC locators - referring to subsequent elements?

When there is more than a single element with the same locator in a page, how should the next elements be referenced?
Using Xpath locators it's possible to add array notation, e.g. xpath=(//span/div)[1]
But with simple locators?
For example, if there are 3 links identified by "link=Click Here", simply appending [3] won't get the 3rd element.
And where is the authoritative reference for addressing array of elements? I couldn't find any.
Selenium doesn't handle arrays of locators by itself. It just returns the first element that meets your query, so if you want to do that, you have to use xpath, dom or even better, css.
So for the link example you should use:
selenium.click("css=a:contains('Click Here'):nth-child(3)")
Santi is correct that Selenium returns the first element matching your specified locator and you have to apply the appropriate expression of the locator type you use. I thought it would be useful to give the details here, though, for in this case they do border on being "gory details":
CSS
The :nth-child pseudo-class is tricky to use; it has subtleties that are little-known and not clearly documented, even on the W3C pages. Consider a list such as this:
<ul>
<li class="bird">petrel</li>
<li class="mammal">platypus</li>
<li class="bird">albatross</li>
<li class="bird">shearwater</li>
</ul>
Then the selector css=li.bird:nth-child(3) returns the albatross element not the shearwater! The reason for this is that it uses your index (3) into the list of elements that are siblings of the first matching element--unfiltered by the .bird class! Once it has the correct element, in this example the third one, it then applies the bird class filter: if the element in hand matches, it returns it. If it does not, it fails to match.
Now consider the selector css=li.bird:nth-child(2). This starts with the second element--platypus--sees it is not a bird and comes up empty. This manifests as your code throwing a "not found" exception!
What might fit the typical mental model of finding an indexed entry is the CSS :nth-of-type pseudo-class which applies the filter before indexing. Unfortunately, this is not supported by Selenium, according to the official documentation on locators.
XPath
Your question already showed that you know how to do this in XPath. Add an array reference at any point in the expression with square brackets. You could, for example use something like this: //*[#id='abc']/div[3]/p[2]/span to find a span in the second paragraph under the 3rd div under the specified id.
DOM
DOM uses the same square bracket notation as XPath except that DOM indexes from zero while XPath indexes from 1: document.getElementsByTagName("div")[1] returns the second div, not the first div! DOM offers an alternate syntax as well: document.getElementsByTagName("div").item(0) is exactly equivalent. And note that with getElementsByTagName you always have to use an index since it returns a node set, not a single node.

Resources