I'm using cypress to write some tests against an html site..
The following selects me correctly a single tr elements from a table on my HTML site.
The site contents looks like this:
<tr data-recordid="theId">
<td...><div ..>Text 1</div></td>
<td...><div ..>Text 2</div></td>
<td...><div ..>Text 3</div></td>
</tr>
The following test script snippet selects me correctly the single <tr..> part.
cy.get('tr[data-recordid="theId"]').contains('Text')
Now I want to select the text within the <div>..</div> tags..The first thing I have tried to chain a single call for the first <div>..</div> tag like this:
cy.get('tr[data-recordid="theId"]').get('div').contains('Text')
which does not work as I expected. The get() calls a chained jQuery calls (Based on the Docs of cypress). So it looks like I misunderstand how things work in JQuery.
What I'm expecting is how I can check all div elements like this (Not working):
cy.get('tr[data-recordid="theId"]')..SomeHowMagic
.get('td[alt="xyz"]".get('div').contains('Text 1')
.get('td...').get('div').contains('Text 2')
.get('td...').get('div').contains('Text 3')
Any idea how to get forward a step? Missing any information just make a comment.
Let's clarify a few things:
1) If you are just wanting to ASSERT that the div's contain the given text then this is the best possible and most precise way to do this:
cy.get('tr[data-recordid="theId"]').should(($tr) => {
const $divs = $tr.find('div') // find all the divs
expect($divs.eq(0)).to.contain('Text 1')
expect($divs.eq(1)).to.contain('Text 2')
expect($divs.eq(2)).to.contain('Text 2')
})
I can't tell if things need to be this specific. If you only want to ensure that the $tr contains text you could simplify it down to be:
cy.get('tr[data-recordid="theId"]').should(($tr) => {
expect($tr).to.contain('Text 1')
expect($tr).to.contain('Text 2')
expect($tr).to.contain('Text 2')
})
Why do it this way?
Using a .should() function will not change the subject. Your $tr will continue to be the subject going forward.
Cypress will wait until all of the assertions in the .should() callback pass, and continually retry until they do. That guarantees you the state of multiple elements is correct before proceeding.
2) However if you just care that Cypress finds the text and you don't mind the subject being changed you could do this:
cy.get('tr[data-recordid="theId"]').within(() => {
cy.contains('Text 1') // changes the subject to the <div>
cy.contains('Text 2') // changes the subject to the <div>
cy.contains('Text 3') // changes the subject to the <div>
})
This is different than the first example because instead of an explicit assertion you are simply changing the subject to whatever element the text is found in. Cypress's default assertion on cy.contains() is to retry so ultimately the behavior is the same, except you are additionally changing the subject.
If even this is too complicated you could also just do this:
cy.get('tr[data-recordid="theId"] div').contains('Text 1')
cy.get('tr[data-recordid="theId"] div').contains('Text 2')
cy.get('tr[data-recordid="theId"] div').contains('Text 3')
Your original question was also using chained cy.get() which does not drill into subjects. For that to happen use .find()
cy.get('a').get('span') // each cy.get() queries from the root
cy.get('a').find('span') // the .find() queries from the <a>
One final note: you suggested solution does not work. cy.get() does not accept a callback function, and if you look at your Command Log you will not see those 3 cy.contains from ever being invoked. In other words, they are not running. That's why its passing.
So after more experimenting I found a solution:
cy.get('tr[data-recordid="TheId"]>td> div', function() {
cy.contains('Text 1').end()
cy.contains('Text 2').end()
cy.contains('Text 3').end()
})
If someone else has a better solution please post it here.
Related
I've been working with Nightwatch for a few months now, using the page object method to try and make my tests as modular as possible. I've ran into a block on my knowledge over javascript syntax, and haven't been able to move past it.
I have a calendar table bracket, which displays in a block, that houses a plethora of child objects. Each object has individual attributes which all have similar values, each differing based on the date respective to that object.
I have been unable to select any date just based on its class values, so I opted to create an iterative loop that would go through all of the visible fields, and click on one with a highlighted class; as a means of selecting the current date automatically. My only issue is that I have had no success creating this loop after reading all of the Nightwatch documentation, and going through many previous users' issues before me.
This is the basic HTML breakdown for each column located within the container's body:
<div id=[container class] class=[container class] <div class=[container header class] <table class=[table class] <tbody> <tr> <td>
Based on some of the web articles I've read, the loop structure to use would be to use the .elements() command to call the elements in question, and then use a .forEach() command to loop through it, and eventually call the value to click on it. I believed I had come to the correct syntax on writing this, but I get a lot of different errors ranging from unexpected '.' to noSuchElement.
The following code snippet is what I have been trying to use, but have had no success.
.elements('css selector', 'table.ui-datepicker-calendar > tbody > tr > td', function(elements) { elements.value.forEach(function(elementsObj, index) { .elementIdAttribute(elementsObj.ELEMENT, 'class', function(result) { if (index == 'ui-datepicker-today') { browser.elementIdClick(result.value) } }) }) })
Is there a better way to try and achieve this that I am unaware of?
Update: As I work through this and try different strategies, I have been able to narrow down some of the issue here. I have edited the code block I am trying to run here:
browser.elements('css selector', '.ui-datepicker-calendar > tbody > tr > td', function (elements) {
elements.value.forEach(function (elementsObj, index) {
browser.elementIdAttribute('elementsObj.ELEMENT', 'class', function (result) {
if (index == '.ui-datepicker-today') {
browser.elementIdClick('result.value')
}
})
})
})
and get an error right around:
browser.elementIdAttribute('elementsObj.ELEMENT', 'class', function (result)
The error reads:
value: {
error: 'no such element',
message: 'no such element: Element_id length is invalid\n' +
' (Session info: chrome=99.0.4844.84)',
stacktrace: ''
}
I'll be honest, I have never seen an error message say that in my life. I am unsure what exactly it means, and the only documentation I can find through web search is related to another testing framework separate to Nightwatch.
I have the following XML:
<building>
<id>1</id>
<name>Annex</name>
<rooms>
<room>
<number>100</number>
<type>conference room</type>
<capacity>4</capacity>
</room>
<room>
<number>203</number>
<type>computer lab</type>
<capacity>30</capacity>
</room>
</rooms>
</building>
I have some blocks in a jQuery function that parse particular parent nodes of an XML file, <number> for example. I have some other .on(click) function that passes <number>'s .text() as a parameter called getNum(), usd to display whatever <number> was desired from each click. Anyway, this is working successfully, however I am now trying to display more than just the <number> node, as you can see. I am trying to also display the <number> node's sibling text, <type> and <capacity>. I have tried using the .next() function but it seems to skip a node. So far the only thing I've gotten to half-way work is by using the .nextAll(arg) function; I say half-way because, depending on the argument, I can either get only the <type> node or the <capacity> node to print, not both. Even stranger, with .nextAll() the only parameters that seem to work make no logical sense (see code comments below); instead of 1 and 2, I found that -1 and 0 only display the nodes accordingly. These values don't make sense to me, can anyone see why?
$room.find('rooms room number').each(function () {
if (getNum() == $(this).text()) {
var $numDiv = $('<div>', {
text: '- Room ' + $(this).text(),
id: $(this).text()
}).appendTo($div);
///////////////////////////////////////////
// This SKIPS <type> node and displays <capacity> node
// $(this).next().appendTo($div);
///////////////////////////////////////////
// This displays <type> node
// $(this).nextAll().eq(-1).appendTo($div);
///////////////////////////////////////////
// This displays <capacity> node
// $(this).nextAll().eq(0).appendTo($div);
///////////////////////////////////////////
// This displays neither node. Why?
// $(this).nextAll().eq(-1).appendTo($div);
// $(this).nextAll().eq(0).appendTo($div);
}
});
return $div;
None the less, really my main concern and overall question is how to declare multiple statements; as I can get only one or the other to show up, why can't I declare both this: $(this).nextAll().eq(-1).appendTo($div); and this: $(this).nextAll().eq(0).appendTo($div); back to back?
I found the solution, however I still don't completely understand why the prior statements don't work. Anyway, here's what I had to do:
// Declare each node in its on HTML tag.
$('<h4>', { text: $(this).nextAll().eq(0).text(), class: 'roomText' }).appendTo($div);
$('<h4>', { text: $(this).nextAll().eq(1).text(), class: 'roomText' }).appendTo($div);
If anyone can explain the logic that would be great.
I would like to test a filtering function in my angularJS app.
In fact when I click on the filter the number of search results displayed on the page should decrease
Here is is my code so far :
element(by.id('foundNumber')).getText().then (function(text){console.log(text); })
element(by.repeater('term in facets.uproctype').row(0)).click ()
element(by.id('foundNumber')).getText().then (function(text){console.log(text); })
And here is my console log :
Using the selenium server at http://localhost:4444/wd/hub
6209
6195
....
I don't know how can I compare theses two values in an expect line as I can't use them inside their function.
Any help?
Thanks
Zied
I believe you would have to nest your then functions to ensure the original value is available.
element(by.id('foundNumber')).getText().then( function(original_text) {
element(by.repeater('term in facets.uproctype').row(0)).click ();
element(by.id('foundNumber')).getText().then( function(new_text){
expect(original_text).not.toBe(new_text);
});
});
This link also might be helpful.
https://code.google.com/p/selenium/wiki/WebDriverJs#Control_Flows
Another way is to schedule the comparison at the protractor control flow so it will be executed after all values are available.
var oldValue,newValue;
element(by.id('foundNumber')).getText().then(function(text){oldValue=text})
element(by.repeater('term in facets.uproctype').row(0)).click()
element(by.id('foundNumber')).getText().then(function(text){newValue=text})
protractor.promise.controlFlow()
.execute(function(){return protractor.promise.fulfilled()},'wait for control flow')
.then(function(){
expect(oldValue).not.toEqual(newValue);
});
There is a nice explanation of flow.execute() in this blog post.
I suspect you can just compare the results of .getText() from different points using expect
var text1 = element(by.id('foundNumber')).getText();
element(by.repeater('term in facets.uproctype').row(0)).click();
var text2 = element(by.id('foundNumber')).getText();
expect(text1).not.toBe(text2);
expect handles unwrapping of the promises passed to it and actually compares resolved values.
I need a function that adds and removes a column in a table if a checkbox is checked or not. The code I got at the moment works fine to an extend. The console messages come up at the right time and the table column is correctly added to the HTML.
The problem lies in the else{} part: The columns are not removed when checkbox is unchecked. In firebug I get a message that my selector is wrong but I am not sure how to fix it. And assuming the selector was correct, is my application of .detach(this) correct?
function makevisible(idsandclasses,object,folder){
$('#checkbox'+idsandclasses).change(function() {
console.log(idsandclasses, "before if checked")
if($('#checkbox'+idsandclasses).is(':checked')){
console.log(idsandclasses, "if checked")
$('tr:contains("Details")').append('<td id="rowdetails'+folder+'">'+object.name+'</td>')
}else{
console.log("unchecked")
$('#rowdetails'+folder).detach(this)
}
})
};
Here I use the function:
$(document).ready(function() {
$("#dot0001").hover(makevisible("info0001", object0001,"object0001"))
})
This is the firebug message:
TypeError: expr.replace is not a function
expr = expr.replace( rattributeQuotes, "='$1']" )
And this is the part in my jquery-1.9.0.js file my code has problems with:
expr = expr.replace( rattributeQuotes, "='$1']" );
I assumed there is no need for the HTML. If that should be the case though let me know.
Remove this from detach. Just .detach() is sufficient.
You need to pass a selector to detach (or nothing at all its optional). You are passing this which is a DOM element. Ideally, $('#rowdetails'+folder) will match the element you are trying to detach.
Source:
http://api.jquery.com/detach/
i suppose the problem is
$(document).ready(function() {
$("#dot0001").hover(function(){
makevisible("info0001", object0001,"object0001")
})
})
Being new to JavaScript I have not been able to come up with a solution to this issue.
I want each "Add to Cart" button to invoke the same function "AddtoCart". I have achieved this but at the cost of inline JavaScript - something I would like to avoid.
onclick=
"AddToCart(document.getElementById('toy_title1').innerHTML,document.getElementById('toy_quantity1').value,document.getElementById('toy_price1').innerHTML)
So how would I achieve including this as part of the external JavaScript file, bearing in mind I have to be able to apply this to all 4 unique items
You could change your function that way:
function AddToCart(toyId) {
var title = document.getElementById('toy_title'+toyId).innerHTML;
var quantity = document.getElementById('toy_quantity'+toyId).value;
var price = document.getElementById('toy_price')+toyId).innerHTML
}
Then on each button you just pass the toy's ID
Just be carefull about sensitive data like price, leaving it on Javascript(I'm supposing you will send it to your back-end after this) is dangerous, it could be easily manipulated.
But if your intention is just a test or something like that, its ok.
EDIT:
to call your this function you would do something like that:
onclick="AddToCart(1)"
Where 1 is your toy's ID, you should change it to 2,3... depending on your toy.
then you should read more about addEventListener(standard) and attachEvent(IE)
//assume element means the button
//you can use getElementsByTagName, getElementsByClassName, querySelectorAll etc.
//to fetch your elements
//DRY, store the operation in a function so it's reusabe and not written twice
function thisFunction(){
AddToCart(document.getElementById('toy_title1').innerHTML,
document.getElementById('toy_quantity1').value,
document.getElementById('toy_price1').innerHTML)
}
if(element.addEventListener){ //check if the standard is supported
element.addEventListener('click',function(){ //use it to add the handler
thisFunction();
});
} else {
element.attachEvent('onclick',function(){ //else, we use IE's version
thisFunction();
}, false);
}