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.
Related
I am going to do live data streaming on ag-grid datatable, so I used DeltaRowData for gridOptions and added getRowNodeId method as well which return unique value 'id'.
After all, I got a live update result on my grid table within some period I set, but some rows are duplicated so I can notice total count is a bit increased each time it loads updated data. The question title is warning message from browser console, I got bunch of these messages with different id number. Actually it is supposed not to do this from below docs. This is supposed to detect dups and smartly added new ones if not exist. Ofc, there are several ways to get refreshed data live, but I chose this one, since it says it helps to persist grid info like selected rows, current position of scroll on the grid etc. I am using vanilla js, not going to use any frameworks.
How do I make live data updated periodically without changing any current grid stuff? There is no error on the code, so do not try to speak about any bug. Maybe I am wrong with current implementation, Anyway, I want to know the idea or hear any implementation experience on this.
let gridOptions = {
....
deltaRowDataMode: true,
getRowNodeId = (data) => {
return data.id; // return the property you want set as the id.
}
}
fetch(loadUrl).then((res) => {
return res.json()
}).then((data) => {
gridOptions.api.setRowData(data);
})
...
If you get:
duplicated node warning
it means your getRowNodeId() has 1 value for 2 different rows.
here is part from source:
if (this.allNodesMap[node.id]) {
console.warn("ag-grid: duplicate node id '" + node.id + "' detected from getRowNodeId callback, this could cause issues in your grid.");
}
so try to check your data again.
if u 100% sure there is an error not related with your data - cut oof the private data, create a plinkr/stackblitz examples to reproduce your issue and then it would be simpler to check and help you.
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.
I have a list being generated from ng-repeat and it's rendering a component tag on each iteration. And I'm giving each iteration a unique id utilizing the $index value.
That looks like this
<div ng-if="$ctrl.myArr.length > 0" ng-repeat="obj in $ctrl.myArr">
<myCustomComponentTag id="L1-{{$index}}" obj="obj"></myCustomComponentTag>
</div>
I need to run some jquery on these unique ids, which are populating just fine. The view will successfully show the tags to have ids of #L1-0, #L1-1, #L1-2
The snag is that my function running my jquery is executing before the view fully loads and the id values actually populate with the value of $index
My jQuery is searching for $(`#L1-${i}`) in a loop. If I store #L1-${i} in a string variable and output its value it will return '#L1-0'.
But '#L1-0' does not exist yet, as the view has not been fully populated. When I break on my function the ids on the elements only read L1-{{$index}} and have yet to populate.
I've read in several places to try this directive. This is still not working. It seems that just because my ng-repeat has reached its last element, it does not mean the view has populated and my ids are fully loaded and in place.
How can I execute my jQuery function only after the view is fully populated with my data?
Edit:
This is the jQuery function
jqFn= () => {
if (this.myArr.length > 0) {
for (var i: number = 0; i < this.myArr.length; i++) {
$(`#L1-${i}`).css({
/*
Add various styles here
*/
});
}
}
};
Perhaps you can try something like this
Disclaimer: I have found it one of the comments on the link that you referenced in the post.
The solution I used for this was a recursive function that set a timeout and searched for the id of the last object in the repeat statement until it exuisted. The id could not exist until the dynamic data from the $watch fully loaded in. This guaranteed all my data was accessible.
Code looked similar to this
setListener = (el, cb) => {
if ($(el).length) {
cb();
} else {
setTimeout(() => {
this.setListener(el, cb);
}, 500);
}
};
PageObject.js(Select_Organization.js)
this.selectOrganization = function() {
organizationLocator.each(function(element) {
FunctionLibrary.getText(element, organizationName).then(function(text) {
logger.info(text);
if (text.includes('ImageResizingOrg')) {
FunctionLibrary.click(element, organizationName);
}
})
})
};
Spec.js
it('should be able to select the required organization', function() {
Select_Organization.selectOrganization();
});
Scenario:
I want to store all the elements having the same class name in a list and then iterate through it using for-each loop and click on the element if the text of the elementFinder is same as that required.
Issue I am facing: Due to async nature of the javascript this is not getting handled properly as sometimes stale element exception is getting thrown. I know the reason which is because all the actions are getting stored in control flow queue.
I just need a solution for this :) Any help will be highly appreciated
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.