How to wait for an element to be visible without erroring out? - nightwatch.js

I want to wait for an element to be visible, and if it is do X without erroring it out. I trying using the nightwatch.js waitForElementVisible but that errors out if the element is not visible. What should I do?

waitForElementVisible takes a param abortOnFailure to control if command should fail or not
http://nightwatchjs.org/api#waitForElementVisible

Possible workaround is to use the function below, but the drawback is that we are setting static timeout,
client.pause(1500).element('css selector', '.css_selector', result => {
if (result.status > -1) {
browser.expect.element('.element_to_check').text.to.contain('YES')
}
})
This way nigtwatch will check your element and won't throw an error if it's not in DOM.

Related

Conditional Tests in Cypress using data-cy attributes as selectors

I see some posts about this exact topic, but none of them using data classes like I am as selectors, so it makes this conditional test a bit harder to write.
The idea is that I have a table with pagination on it. My idea is to check if the [data-cy-pagination-next] has or doesn't have the disabled attribute on it, which would mean there's more than one page and therefore the test can continue.
Most posts I see use a syntax like this:
cy.get('my-button')
.then($button => {
if ($button.is(':enabled')) {
cy.wrap($button).click()
}
})
But I don't have the $button like they described. What I would be clicking on is a button, but does that really matter?
It doesn't seem like I can write
cy.get('[data-cy=pagination-next]')
.then('[data-cy=pagination-next]' => {
if ('[data-cy=pagination-next]'.is(':enabled')) {
cy.wrap('[data-cy=pagination-next]').click()
}
})
How can I get this conditional to work?
If there is more than one page, this test works great, but in the cases that there is no second page, I just want the test to end there.
Any tips would be greatly appreciated!
Cheers!
Here is the test currently
it('Data Source has Pagination and test functionality', () => {
cy.get('[data-cy=pagination]').should('exist')
// assert that we are at the first page and the start and back button is disabled
cy.get('[data-cy=pagination-page-list]').contains('Page 1 of')
// If there are multiple pages then do the following tests
// click next button and assert that the current page is page 2
cy.get('[data-cy=pagination-next]').click()
cy.get('[data-cy=pagination-page-list]').contains('Page 2 of')
// click end button and assert that the end and next buttons are disabled
cy.get('[data-cy=pagination-end]').click()
cy.get('[data-cy=pagination-next]').should('be.disabled')
cy.get('[data-cy=pagination-end]').should('be.disabled')
// click start button button and assert that the current page is page 1 and next and start buttons are disabled
cy.get('[data-cy=pagination-start]').click()
cy.get('[data-cy=pagination-page-list]').contains('Page 1 of')
cy.get('[data-cy=pagination-start]').should('be.disabled')
cy.get('[data-cy=pagination-back]').should('be.disabled')
})
You can use the page indicator to split the test logic
it('Data Source has Pagination and test functionality', () => {
cy.get('[data-cy=pagination]').should('exist')
cy.get('[data-cy=pagination-page-list]')
.then($pageList => {
if ($pageList.text() === 'Page 1 of 1')
// single page assertions
cy.get('[data-cy=pagination-next]').should('be.disabled')
cy.get('[data-cy=pagination-end]').should('be.disabled')
cy.get('[data-cy=pagination-start]').should('be.disabled')
cy.get('[data-cy=pagination-back]').should('be.disabled')
} else {
// multi page assertions
cy.get('[data-cy=pagination-next]').click()
cy.get('[data-cy=pagination-page-list]')
.should('contain', 'Page 2 of') // assert on second page
cy.get('[data-cy=pagination-end]').click()
cy.get('[data-cy=pagination-next]').should('be.disabled')
cy.get('[data-cy=pagination-start]').click()
cy.get('[data-cy=pagination-page-list]').contains('Page 1 of')
cy.get('[data-cy=pagination-start]').should('be.disabled')
cy.get('[data-cy=pagination-back]').should('be.disabled')
}
})
Better still, control the test data so that only a single page exists, then run two tests under known conditions and eliminate flaky conditional testing.
I think you're misunderstanding how yielding and callbacks work. The reason there is $button in the .then() is because it is yielded by cy.get(). It could be named anything, so long as it is a valid name (note: a string literal, like you are trying to do, is not valid).
So, $button is just the yielded element from your cy.get('my-button'). Which is why we can then use JQuery functions and Chai assertions on it.
cy.get('[data-cy=pagination-next]')
.then($el => { // naming the yielded object from `cy.get()` to $el
if ($el.is(':enabled')) { // using JQuery function `.is` to check if the element is enabled
cy.wrap($el).click() // Cypress requires the JQuery element to be wrapped before it can click it.
}
})
You can do something like this. You can use an each to loop over all the pagination elements. So in case, you don't have only 2 buttons the loop will check for only 2 buttons and then terminate.
cy.get('[data-cy=pagination-page-list]').should('contain.text', 'Page 1 of')
cy.get('[data-cy=pagination-next]').each(($ele, index) => {
if ($ele.is(':enabled')) {
cy.wrap($ele).click()
cy.wrap($ele).should('contain.text', `Page ${index + 2} of`) //index starts from 0
}
})

Cypress : The submit button is disabled even after all the text fields have filled

I was trying to automate a text submit form using cypress. The 'Create student' button is disabled even after all the fields have been filled
Please see the cypress error
code :
it('should be able to add a new student and update the details, remove from the class and delete the account', function () {
cy.visit(
'https://readingeggs.blake-staging.com/district_ui#/reading/manage-schools/students/195286/new'
)
cy.findByLabelText('First Name').type('ark')
cy.get('#first-name').should('have.value', 'ark')
cy.findByLabelText('Last Name').type('last')
cy.get('#last-name').should('have.value', 'last')
cy.get('[data-test-select-grade]').select('1')
cy.get('#grade-dropdown').should('have.value', '1')
cy.get('[data-test-select-teacher]').select('Lehner, Abbey')
cy.get('#teacher-dropdown').should('have.value', '3068134')
cy.get('[data-test-submit-new-student]').click()
cy.get('#main')
.findByRole('alert')
.should('include.text', `Successfully created a student`)
})
})
Be careful using click({force:true}) as suggested in the error message, there may be another problem that your test will now ignore!
You can first try an assertion that the button is not disabled.
Sometimes the test can run too quickly, and the web page has not yet enabled the button before the test tries to click it.
Adding .should('not.be.disabled') will retry this check for up to 4 seconds, which should be enough time for the page to complete changes.
cy.get('[data-test-submit-new-student]')
.should('not.be.disabled')
.click()
If using .should('not.be.disabled') does not work (I agree, it should be the first thing to try), try adding a trigger event to each input - in case the .type() command is not triggering the validation change.
cy.findByLabelText('First Name').type('ark').trigger('change')
cy.get('#first-name').should('have.value', 'ark')
cy.findByLabelText('Last Name').type('last').trigger('change')
cy.get('#last-name').should('have.value', 'last')
cy.get('[data-test-select-grade]').select('1').trigger('change')
cy.get('#grade-dropdown').should('have.value', '1')
cy.get('[data-test-select-teacher]').select('Lehner, Abbey').trigger('change')
cy.get('#teacher-dropdown').should('have.value', '3068134')
cy.get('[data-test-submit-new-student]').click()
If still no joy, use .click({force:true})
By the way, cy.get('[data-test-select-grade]').select('1') looks a bit suspicious. The select command can take a display value as a string or a position value as a number. The screenshot shows "K" is selected, so I would expect either of these to work
cy.get('[data-test-select-grade]').select(1) // number passed
// or
cy.get('[data-test-select-grade]').select('K') // string passed
One option would be to use {force: true} with click().
it('should be able to add a new student and update the details, remove from the class and delete the account', function () {
cy.visit(
'https://readingeggs.blake-staging.com/district_ui#/reading/manage-schools/students/195286/new'
)
cy.findByLabelText('First Name').type('ark')
cy.get('#first-name').should('have.value', 'ark')
cy.findByLabelText('Last Name').type('last')
cy.get('#last-name').should('have.value', 'last')
cy.get('[data-test-select-grade]').select('1')
cy.get('#grade-dropdown').should('have.value', '1')
cy.get('[data-test-select-teacher]').select('Lehner, Abbey')
cy.get('#teacher-dropdown').should('have.value', '3068134')
cy.get('[data-test-submit-new-student]').click({force: true})
cy.get('#main')
.findByRole('alert')
.should('include.text', 'Successfully created a student')
})

Cypress, how to check for text typed into a field?

I can verify text appears "somewhere" on the results page with
it.only('can verify an input element has certain text typed into it', function() {
cy.visit('http://google.com')
cy.get("input[name=q]").type('abc123{enter}') // with or without the {enter}
cy.contains('abc123') // Anywhere on the page :(
})
but how can I verify the text I type in the input text box?
I tried chaining to the element with
it.only('can verify an input element has certain text typed into it', function() {
cy.visit('http://google.com')
cy.get("input[name=q]").type('abc123{enter}')
cy.get("input[name=q]").contains('abc123')
})
but I get
CypressError: Timed out retrying: Expected to find content: 'abc123' within the element: <input.gLFyf.gsfi> but never did.
I tried cy.get("input[name=q]").contains('abc123') and
cy.contains('input[name=q]', 'abc123')
but both time out and fail.
Change .contains to use .should('have.value'...
cy.get("input[name=q]").type('abc123{enter}')
cy.get("input[name=q]").should('have.value', 'abc123')
You may not like this idea but here is just a suggestion so you don't have to keep calling cy.get each time.
You could always set a const value for your input name (could be in an external file) so:
export const inputField = () => cy.get('input[name=q]');
This will do the get whenever you call inputField.
so then your call would be:
inputField.type('abc123{enter}').should('have.value', 'abc123');
Thats just more a setup thing than an actual soluton, as I know you solved the issue yourself, but the above is quite a nice way so you don't have to keep doing cy.get on the same field.
Instead of using contains, you can read the text you already entered in the input field using "then()". Here's how:
cy.get("input[name=q]").type('abc123').then(function($input){ const value = $input.text() expect(value.includes('abc123')).to.be.true })

wait for element present without error

is there a way to call waitForElementPresent in nightwatch without erroring out if the element is not there?
Right now, if you pass in false as the third parameter, you still see an error. Ideally, I would like a function that waits for an element, and returns if it didn't find the element.
you can do it :
Browser.waitForElementPresent('#element',3000,false, function(result){
If(result.value === true){
// test if element present
}else{
// test if element not present
}
})
Since the day one, i did this and the problem with this code is nightwatch would count a failed test as a passed test,as you see above code handle both result value.
So i recommend let the nightwatch return error itself, write difference function for difference value.

Selecting Ajax Dropdown suggestion list using Selenium for Firefox

How can i select Ajax Dropdown suggestion list item using selenium code for firefox??
My problem is :the Ajax dropdown list is visible but it is not selected and next steps gets stuck.
May be selenium is waiting for something.
the list that page populates is dynamic and in bla bla tags.
Please help with a example code.
How can i use waitfor* here.
Remember i am not using firefox ide but i am writing a code.
Please help.
I had a similar problem whereby, selenium was able to find the dropdown menu but was unable to click on the visible text. I later found out that there was an Ajax call that was populating the dropdown menu data and as a result selenium seemed to not be able to select the intended visible text because the list items had not been fully populated. That is, by the time the script was selecting my option value, Ajax had not completely loaded the menu options. Here's my solution:
public void nameOfCollegeList(String optionItem) {
// declare the dropdownMenu web element
WebElement dropDownMenu = driver.findElement(By.cssSelector("#CollegeNames"));
// click on the dropdownMenu element to initiate Ajax call
dropDownMenu.click();
// keep checking the drop down menu item list until you find the desired text that indicates that the menu has
// been fully loaded. In this example I always expect "Other (please specify)" to be the last item in the drop down menu.
// If I don't find the expected last item in the list in my if condition, execute the else condition by calling the
// same method(recursively). Please note that if the "if" statement is never satisfied then you'll end up with an
// infinite loop.
if (dropDownMenu.getText().contains("Other (please specify)")) {
new Select(dropDownMenu).selectByVisibleText(optionItem);
}
else {
nameOfCollegeList(optionItem);
}
}
i am little confused with your question at " :the Ajax dropdown list is visible but it is not selected "
this sounds like that the element is disabled. (Java coding)
if so selenium.isElementDisabled()
if not then,
1) programming laguage solution using while loop and isElementPresent() OR isElementDisabled()
//trigger the Ajax request and then
long initialTime = System.currentTimeMillis();
do{
thread.sleep(1000);
}while((!selenium.isElementPresent("AjaxElement")) && (System.getCurrentTimeMillis() - initialTime <= 5000)) ;
//some thing like above for client programming solution...but for,
2) selenium's inbuilt solution
we have a method called waitForCondition("java script to be executed", "time out value");
this method loops the javascript statement until it returns true or the supplied time out occurs
here the important thing is analyzing the application/Ajax element to find out which particular condition of the element changes.
from your explation my guess is this, display=none will be changed to display=block OR
disabled=true will be changed to disabled=false OR isReadOnly will be changed to no such attribute ect.....(you need to figure out this)
and then, use this attribute = value to build a javascript function as ,
selenium.waitForCondition("window.document.getElementById('AJAX ELEMENT').disabled == 'false'", "3000");
you can work out the above statement however you want in your programming language.
try {
//do the action which triggers the Ajax call
selenium.waitForCondition("window.document.getElementById('AJAX ELEMENT[drop down element]').disabled == 'false'", "3000");
//OR
selenium.waitForCondition("window.document.getElementById('AJAX ELEMENT').disabled == 'false'", "3000");
}
catch(SeleniumException se)
{
if((se.getMessage()).toLowerCase().contains("timed out")
throw //..some a custom exception however your organisation requires
}
selenium.select("drop down element id", "option id");
and so on.....

Resources