Webdriver.io browser.getText() sometimes returns undefined - webdriver-io

I have this piece of code:
getText: (selector) => {
browser.waitUntil(function () {
return browser.isExisting(selector) === true;
},timeout,
'Could not find element after: ' + timeout,
pollingTime);
return browser.getText(selector);
}
And sometimes this function (getText(selector), but in deep browser.getText(selector)) returns undefined for the selector that looks like this:
article[data-product-id="test-00020"] li.product-entry__summary__item.is-price span
This doesn't happen every time test is run, but it happens occasionally. It is driving me nuts because the behavior is inconsistent. Sometimes it works and sometimes it doesn't.
Did anybody have similar problems? Please help! Thank you.

getText is dependent on the element being visible in the viewport of the page (so if it's scrolled off the page it will return an empty string)
Instead, you can use getHTML(false) to get the text content of an element (just ensure it's the inner-most element, otherwise you'll get HTML elements in the returned content)

If you use getHTML which I also did, you can strip out the HTML tags if they are not innerHTML:
var strArray = browser.getHTML("//div[myxpath]");
for(var i =0; i<strArray.length; i++){
strArray[i]=strArray[i].replace(/(<([^>]+)>)/ig, "");
strArray[i] = strArray[i].trim();
}
sorry for the Hungarian notation.

Related

How to get cypress to return children length of 0 when there isnt any children

So I am writing a test that will add a card to a container(payment-card-container) and I want to confirm an element was added later by seeing if the children have increased by 1. But I am having issues when we try to count the children length when there isnt any. I am currently using the below:
cy.get('[data-test-id="payment-card-container"]')
.children()
.its('length')
.then(length => {
const childrenLength = length;
})
But Cypress seems to get an error because it cant find the children (Error below).
Timed out retrying: Expected to find element: ``, but never found it.
Is there a way this can work when there isnt any children and it returns the value of 0?
The problem with using a jQuery expression like
Cypress.$('[data-test-id="payment-card-container"]').children().length
is you don't get the Cypress retry for async updates.
If adding a payment card calls an API, the above expression will falsely report 0 children instead of waiting for the DOM to update.
There's really no good way to handle the no-cards situation,
Except
set up your test scenario such that there are no cards initially
add a card
confirm that there is now exactly one card
If you must test for zero children, a trailing .should() will remove the error message.
cy.get('[data-test-id="payment-card-container"]')
.children()
.should('have.length', 0); // no error when should expression passes
// Add card here
cy.get('[data-test-id="payment-card-container"]')
.children()
.should('have.length', 1); // waits for async add-card operation
Tested with
<body>
<div data-test-id="payment-card-container"></div>
<script>
setTimeout(() => {
const div = document.querySelector('[data-test-id="payment-card-container"]');
const p = document.createElement('p')
div.appendChild(p)
}, 2000)
</script>
</body>
One hacky way that I could think of is this. You can use the jQuery length and children() property to check the length:
cy.get('body').then(() = > {
if (Cypress.$('[data-test-id="payment-card-container"]').children().length == 0) {
//Do Something
}
else {
//Do Something
}
})

In Testcafe, how can I wait for a 2nd element of the same selector to appear?

I have a scenario in which multiple elements of the same className appear one after the other (it depends on a server response).
What I'm trying to achieve is passing the test only after 2 elements of the same selector are present, but currently, it seems like the test fails because it keeps recognizing 1 element and then straight up fails without waiting for a 2nd one.
This is my code (called from the outside with a count argument of, say, 2) -
import { Selector } from 'testcafe';
export const validateMsg = async (t, headlineText, count = 1) => {
const msgHeadline = Selector('.myClassName').withText(headlineText).exists;
const msgHeadLineExists = await t
.expect(msgHeadline.count)
.gte(count, `Received less than ${count} desired messages with headline ${headlineText}`);
return msgHeadLineExists;
};
I assume this happens because I'm checking whether msgHeadline exists, and it sees the first element when it gets rendered, and immediately fails. I'd like to wait for a 2nd one.
Any ideas?
Just remove the .exists from your selector it returns boolean and then calling .count on it will fail the test.
const msgHeadline = Selector('.myClassName').withText(headlineText);
const msgHeadLineExists = await t
.expect(msgHeadline.count)
.gte(count, `Received less than ${count} desired messages with headline ${headlineText}`);
return msgHeadLineExists;
You can read more here
https://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors/using-selectors.html#check-if-an-element-exists
If both elements have same text and only this elements have this specific className then you can use nth() function
const msgHeadline = Selector('.myClassName')..withText(headlineText).nth(1);
await t
.expect(msgHeadline.exists).ok(`Received less than ${count} desired messages with headline ${headlineText}`)
Here you take second element with headlineText and then assert, that it exists. Though i think you should check that it exists and displayed(visible)

Checking button text matches a certain string in Nightwatch.js

I'm having a heck of a time trying to write a test where I check that text on a button matches a certain string. I tried ".valueContains", ".attributeContains" and got blank or null, and I've tried getText(), but that only seems to return an object.
I feel like it's something obvious I'm missing, so any help would be appreciated!
Based on what you have written so far in your question, I am wondering if there is there a reason you cannot use .containsText?
.waitForElementVisible('.yourclass', this.timeout)
.assert.containsText('.yourclass', 'Text of Button you expect to match')
http://nightwatchjs.org/api#assert-containsText
Without actually looking at the code its little difficult to predict whats going on. However all of the methods in selenium return a promise, so you need to wait for it to resolve.
function async getTextOfButton() {
const element = await driver.findElement(By.className('item-class'));
const text = await element.getText();
}
If you are not using async/await you could do
driver.findElement(By.className('item-class')).then(function(element) {
element.getText().then(function(text) {
console.log(text);
});
});

Iterating elements using NightWatchJS

How do i click a button returned by elements command in night watch
client.elements('xpath', ".//a[#class='abcd')]", function (allButtons){
console.log('Element value is '+element)
allButtons.value.forEach(function (element) {
this.elementIdClick(element, function(res){});
}
}
While running i am getting an error as
Element value is [object Object]
TypeError: Object #<Object> has no method 'elementIdClick'
So how do i get each element from the element list returned by client.elements
I realized the parameters for elementIdClick is wrong, i updated the code as
client.elements('xpath', ".//a[#class='abcd')]", function (allButtons){
allButtons.value.forEach(function (element) {
console.log('Element value is '+element)
this.elementIdClick(this.elementIdAttribute(allButtons.value[element].ELEMENT, 'id'), function(res){});
Now the error is
Element value is [object Object]
TypeError: Cannot read property 'ELEMENT' of undefined
So again back to original question. How do i get individual elements from a list of webelements using nightwatchJS
The following worked for me:
function iter(elems) {
elems.value.forEach(function(element) {
client.elementIdClick(element.ELEMENT)
})
};
client.elements('css selector', 'button.my-button.to-iterate', iter);
Each element is a JSON object of the form { ELEMENT: string } (so, has no method itself.)
this in forEach does not point to the element, nor client: you need to invoke client.elementIdClick() or will get a TypeError.
Hope it helps.
I used the following strategy to iterate over DOM elements using Nightwatch:
// Executing a function in the application context.
client.execute(function () {
// Get elements by CSS selector.
var elements = document.querySelectorAll('.elements');
// Iterate over them.
[].forEach.call(elements, function (element) {
// Manipulate each element.
element.click();
});
});
That snippet is inside a test of course.
If you use jQuery or something similar you can use that too.
I think the error is getting generated by your console.log() statement.
From the elements() command, allButtons.value will be an array of several objects. To access key pairs in that array, you need need to specify where in the array and then reference the object: allButtons.value[index].ELEMENT
Because you gave your .forEach() loop only one arg, it's interpreting that as the index for the array, and in my code sample below I replaced your local variable element with index for clarity. There is also no need to use the .elementIdAttribute() function; the number returned by allButtons.value[0].ELEMENT will work as the id.
client.elements('xpath', ".//a[#class='abcd')]", function (allButtons){
allButtons.value.forEach(function (index) {
console.log('Element value is '+index.ELEMENT)
client.elementIdClick(index.ELEMENT);}})
Hope that helps.

KoLite asyncCommand accessing element data

So I'm displaying a observable array in my view, and I want to be able to remove an element from that list using asyncCommand. However, I'm not sure how I should be getting that element. Is there a way of accessing or passing the selected element into the asyncCommand method?
Thanks for the input
addGroupCmd = ko.asyncCommand({
execute: function (data, complete) {
//access your observable here with the data object
//EX. var demo = data.id();
},
canExecute: function (isExecuting) {
return !isExecuting && isEditing();
}
}),
Ok, so I figured it out with it little bit of google's help. All you have to do is pass in the data parameter and ko.lite will figure out what object your talking about. pretty nice, not really sure how it works, but it does.