Xpath: How to combine these two child node to get parent node? - javascript

I am new to XPath and confused. Anyone can have a quick glance and see what is wrong in my syntax?
I'm trying to select all direct child div's of id="list-overview" which has two child nodes somewhere down their tree containing data-price<=20 and a div containing "Orange" text
let xy = $x(`//*[#id="list-overview"]/div[./div/a/div/div/div[#data-price<=20]][./div/a/div/div[#class='fruit'][contains(.,'Orange')]])`)
to break it up. I have tested these two separately and they worked.
`//*[#id="list-overview"]/div[./div/a/div/div/div[#data-price<=20]]`
`//*[#id="list-overview"]/div[./div/a/div/div[#class='fruit'][contains(.,'Orange')])]`
I just can't seem to combine them somehow and not sure what I'm doing wrong?
EDIT:
Tried the suggestions and the following xpath doesn't throw an exception anymore. But it returns empty Array while there are elements matching price < 20 and fruit="Orange"
$x(`//*[#id="list-overview"]/div[./div/a/div/div/div[#data-price<=20] and ./div/div/a/div/div[#class='fruit'][contains(.,'Orange')]]`)

If I understand your idea, you are missing the logical and between these 2 conditions. I corrected the XPath expression accordingly. Please try this:
let xy = $x(`//*[#id="list-overview"]/div[./div/a/div/div/div[#data-price<=20] and ./div/a/div/div[#class='fruit'][contains(.,'Orange')]])`)
UPD
Please try this:
let xy = $x(`//*[#id="list-overview"]/div[.//div[#data-price<=20] and .//div[#class='fruit'][contains(.,'Orange')]])`)

Instead of this
$x(//*[#id="list-overview"]/div[./div/a/div/div/div[#data-price<=20]][./div/a/div/div[#class='fruit'][contains(.,'Orange')]]))
You could do this to combine both of them,
to tightly couple both the child
$x(//*[#id="list-overview"]/div[./descendant::div[#data-price<=20]] and [./div/a/div/div[#class='fruit'][contains(.,'Orange')]])
or to either have one of them and still you would want to proceed further.
$x(//*[#id="list-overview"]/div[./descendant::div[#data-price<=20]] or [./div/a/div/div[#class='fruit'][contains(.,'Orange')]])
Also, I have included descendant to make it more readable.

Related

How to use 'toHaveClass' to match a substring in Jest?

There's an element to which I'm giving a class success. But React-Mui appends a text to it on DoM, say, something like mui-AbcXYZ-success. So when I'm doing this
expect( getByTestId('thirdCheck')).toHaveClass("success")
I don't get this test passing as it expects the complete name mui-AbcXYZ-success. I’m getting this passed only when I provide the exact name (wirh hyphenated text and random number that mui adds)
How do I test that?
I tried doing the following without reasult:
expect( getByTestId('thirdCheck')).toHaveClass(/success/)
I also tried applying .className or .classList but that doesn’t give me the list of classes on the element.
After hours of frustrations, this is how I did and if any one has a better solution, feel free to post. I shall accept it.
let classes = getByTestId('thirdCheck').getAttribute('class'); //returns something like "Mui-root mui-AbcXYZ-success"
classes=classes.split(' ')[1].split('-'); //first split is to split on the basis of spaces and the second one to do on the bases of hyphen
expect(classes.includes('success'));
Looks like a bit verbose for a trivial-looing thing. But that's how did.
UPDATE:
#Fyodor has a great point in the comment.
We can simply do this as follows:
expect(getByTestId('thirdCheck').getAttribute('class')).toMatch(/success/gi)
With CSS-in-JS, classNames will sometimes get appended with generic identifiers. So it is nice to have a quick way of verifying your custom class is on an element. This is a loose one line approach for verifying the substring of a custom class without regex.
Note: It does not ignore casing. If you need that, then the answer with toMatch is a better solution for you.
expect(yourSelectedElement.getAttribute("class")).toContain("yourClassSubstring");

Difference between .getAttribute and dataset in JS

I have been using .getAttribute and today found out about .dataset, so i was wondering what the differences are and when should each be used.
So here is an example. Let's say we have a paragraph:
<p class="test" data-something="this is a test">some text</p>
if we use .getAttribute
let testText = document.querySelector('.test');
let testGetAttribute = testText.getAttribute('data-something');
console.log(testGetAttribute);
we get as output "this is a test".
if we use .dataset
let testText = document.querySelector('.test');
let testDataset = testText.dataset.something;
console.log(testDataset);
we also get "this is a test".
So, is there a difference between this two approaches? Are there some benefits in using one over the other?
Thank you!
I'm only replying this because I faced a difference between the two methods that actually affected the functioning of my application.
I did getAttribute('data-id') and dataset.id to collect a todo item id.
For getAttribute, if I ran through the debugger line by line, it worked fine. If I didn't, all sorts of wonky things would happen. For dataset.id it worked fine either way.
If you're curious about it, you can check lines 201 and 202 in my code:
https://glitch.com/~wnc-reading-exercise-3 Comment out line 201 and uncomment line 201.
When running the app, try toggling complete on a todo item and see what happens to the DOM. If you go around toggling a few at a go you'll see some strange values show up.

Cypress: Test if element does not exist

I want to be able to click on a check box and test that an element is no longer in the DOM in Cypress. Can someone suggest how you do it?
// This is the Test when the checkbox is clicked and the element is there
cy.get('[type="checkbox"]').click();
cy.get('.check-box-sub-text').contains('Some text in this div.')
I want to do the opposite of the test above.
So when I click it again the div with the class check-box-sub-text should not be in the DOM.
Well this seems to work, so it tells me I have some more to learn about .should()
cy.get('.check-box-sub-text').should('not.exist');
You can also search for a text which is not supposed to exist:
cy.contains('test_invite_member#gmail.com').should('not.exist')
Here you have the result in Cypress: 0 matched elements
Reference: Docs - Assertions, Existence
Use .should('not.exist') to assert that an element does not exist in the DOM.
Do not use not.visible assertion. It would falsely pass in < 6.0, but properly fail now:
// for element that was removed from the DOM
// assertions below pass in < 6.0, but properly fail in 6.0+
.should('not.be.visible')
.should('not.contain', 'Text')
Migration Docs here: Migrating-to-Cypress-6-0
Cypress 6.x+ Migration
According to cypress docs on Existence
The very popular attempt which is a bit naive will work until it doesn't and then you'll have to rewrite it again... and again...
// retry until loading spinner no longer exists
cy.get('#loading').should('not.exist')
This doesn't really work for the title problem which is what most people will be looking for.
This works for the case that it is being removed. but in the case that you want it to never exist... It will retry until it goes away.
However, if you want to test that the element never exists in our case.
Yes lol. This is what you really want unless you want to just have your headache again another day.
// Goes through all the like elements, and says this object doesn't exist ever
cy.get(`img[src]`)
.then(($imageSection) => {
$imageSection.map((x, i) => { expect($imageSection[x].getAttribute('src')).to.not.equal(`${Cypress.config().baseUrl}/assets/images/imageName.jpg`) });
})
cy.get('[data-e2e="create-entity-field-relation-contact-name"]').should('not.exist');
might lead to some false results, as some error messages get hidden. It might be better to use
.should('not.visible');
in that case.
Here's what worked for me:
cy.get('[data-cy=parent]').should('not.have.descendants', 'img')
I check that some <div data-cy="parent"> has no images inside.
Regarding original question, you can set data-cy="something, i.e. child" attribute on inner nodes and use this assertion:
cy.get('[data-cy=parent]').should('not.have.descendants', '[data-cy=child]')
You can use get and contains together to differentiate HTML elements as well.
<button type='button'>Text 1</button>
<button type='button'>Text 2</button>
Let's say you have 2 buttons with different texts and you want to check if the first button doesn't exist then you can use;
cy.get('button').contains('Text 1').should('not.exist')
Could be done also using jQuery mode in cypress:
assert(Cypress.$('.check-box-sub-text').length==0)
I closed an element and checked should('not.exist') but the assertion failed as it existed in the DOM. It just that it is not visible anymore.
In such cases, should('not.visible') worked for me. I have just started using cypress. A lot to learn.
No try-catch flow in cypress
In java-selenium, we usually add the NoSuchElementException and do our cases. if UI is not displaying element for some Role based access cases.
You can also query for the matched elements inside the body or inside the element's parent container, and then do some assertions on its length:
cy.get("body").find(".check-box-sub-text").should("have.length", 0);
In case anyone comes across this, I was having the issue that neither .should('not.exist') nor .should('have.length', 0) worked - even worse: If the element I was querying was actually there right from the get-go, both asserts still returned true.
In my case this lead to the very strange situation that these three assertions, executed right after each other, were true, even though asserts 1+2 and 3 contradict each other:
cy.get('[data-cy="foobar"]').should('not.exist')
cy.get('[data-cy="foobar"]').should('have.length', 0)
cy.get('[data-cy="foobar"]').should('have.text', 'Foobar')
After extensive testing, I found out that this was simply a race condition problem. I was waiting on a backend call to finish before running the above 3 lines. Like so:
cy.wait('#someBackendCall')
cy.get('[data-cy="foobar"]').should('not.exist')
However once the backend called finished Cypress immediately ran the first two assertions and both were still true, because the DOM hadn't yet caught up rerendering based on the backend-data.
I added an explicit wait on an element that I knew was gonna be there in any case, so my code now looks something like this:
cy.wait('#someBackendCall')
cy.get('[data-cy="some-element"]').should('contain', 'I am always here after loading')
cy.get('[data-cy="foobar"]').should('not.exist')
You can also use below code
expect(opportunitynametext.include("Addon")).to.be.false
or
should('be.not.be.visible')
or
should('have.attr','minlength','2')
Voted element is correct but I highly recommend not to using anti-pattern saving you from a lot of headaches. Why? Yes, because;
Your application may use dynamic classes or ID's that change
Your selectors break from development changes to CSS styles or JS behavior
Luckily, it is possible to avoid both of these problems.
Don't target elements based on CSS attributes such as: id, class, tag
Don't target elements that may change their textContent
Add data-* attributes to make it easier to target elements
Example:
<button id="main" name="submission" role="button" data-cy="submit">Submit</button>
And if you want to be more specific and want to indentify more than one selector, it is always good to use .shouldchainer.
Example:
cy.get("ul").should(($li) => {
expect($li).to.be.visible
expect($li).to.contain("[data-cy=attribute-name]")
expect($li).to.not.contain("text or another selector")
})
If there is no element, we can use simple line like:
cy.get('[type="checkbox"]').should('not.exist')
In my case, Cypress was so fast, that simple .should('not.be.visible') was passing the test and after that, loader appears and test failed.
I've manage to success with this:
cy.get('.loader__wrapper')
.should('be.visible')
cy.get('.loader__wrapper', { timeout: 10000 })
.should('not.be.visible')
Also nice to set the timeout on 10 seconds when your application loads more than 4s.
I would use :
cy.get('.check-box-sub-text').should('not.be.visible');
This is safer than
cy.get('.check-box-sub-text').should('not.exist');
( The element can be present in the DOM but not visible with display: none or opacity: 0 )

JavaScript Error, Missing Name After . Operator

I am trying to get the innerHTML of a hidden span. The JavaScript is from an iframe HTML page, and the hidden span resides in the parent page. A different function works when accessing contents of a list from the parent, but I can't seem to get at my span...
WORKS
document.getElementById(parent.genL[i]);
DOESNT WORK
document.getElementById(parent."span"+i).innerHTML;
- SyntaxError: missing name after . operator
The above line of code resides in a for loop and as it iterates through i it will grab data from each separate span. the hidden spans start at ID "span1" through upwards of 10-40k different hidden spans.
Anyways, I have a feeling that it has to do something with trying to concatenate the string int i. I assume i is an int anyways. Any thoughts? Thanks so much everyone!
Edit - Words, and added the innerHTML portion to the doesn't work line of code. Not sure if that will be making a difference or not...
Edit2 - Great answers everyone, learned some good syntactical tricks :) I simply moved the parent. portion to the front of the code as reccomend by the comment of mplungjan and the answer from Jacob T. Nielson. For some reason I still got the error using the brackets as suggested, but I will definitely tuck the brackets into my memory for future similar situations!
parent.document.getElementById("span"+i).innerHTML;
:)
Try changing it to an indexer.
document.getElementById(parent["span"+i]);
If the parent in the brackets is an object and you're trying to access something like parent.span1 then you need to use bracket notation instead of the dot.
document.getElementById(parent["span"+i]); should work fine.
I think what you are trying to do is get the i-th span element on the parent page. Correct?
You can do it like this
var s = parent.document.getElementsByTagName('span')[i];
s.innerHTML // <-- access innerHTML

how to search and replace through an entire block of text?

I'm trying to replace every image tag in a block of text with a unique string. So far I've tried to get the index of the beginning and end of a tag, create a substring, and then replace the substring. The problem is that I cannot do this an infinite number of times (the text block itself can be long with an n number of image tags).
Here is my code so far:
var txtBlock = currBlock.getElementsByClassName("txtContent")[0];
var imgStartPoint = txtBlock.indexOf("<img ");
var imgEndPoint = txtBlock.indexOf(" />");
var imgstring = txtBlock.substring(imgStartPoint, imgEndPoint);
How can I repeat this process n number of times?
The best way to approach this problem, and most programming problems in general, is to think about what you need to do and write out the steps that you need to perform in order to solve your problem in plain English.
To get you started, you should probably think about the following:
How many times does the code need to execute? How do you determine this?
How does the algorithm know that it is done? Can you think of a couple ways to achieve this?
Once you have a decent logical plan, the code will be much easier to write.
In general, break the problem down to smaller tasks and you should be able to tackle almost any programming problem, regardless of language, etc.
Let me know if you need further help.
It seems that you get your data from a DOM. So you can make yourself familiarly with the DOM operations and replace all image nodes with text nodes.
Helpful methodes:
DOM Document getElementsByTagName Method -
http://w3schools.com/jsref/met_document_getelementsbytagname.asp
DOM Node replaceChild Method -
http://w3schools.com/jsref/met_node_replacechild.asp
DOM Document createTextNode Method -
http://w3schools.com/jsref/met_document_createtextnode.asp

Categories