I'm trying to write a Cypress test to validate that a hidden input is correctly having a value set.
My application has code which sets the value of a hidden input field:
$(function() {
$('#myField').val('test');
})
A console.log shows this does correctly set the value, with the DOM properties showing a value of 'test'.
My test code is:
it('should record the value', () => {
cy.visit('index.html')
cy.get('#myField').should('have.value', 'test')
})
Yet the test fails:
Timed out retrying after 4000ms
+ expected - actual
+'test'
Am I missing something very obvious here - surely this ought to be straightforward? But I cannot get it to work.
Related
I wrote the following to select values of a dropdown and assert selected value to make sure it is selected, one by one.
When("User should see example Cover option with values {string}", (exampleCovers) => {
const exampleCover = exampleCovers.split("|");
exampleCover.forEach(chooseCoverLimit);
function chooseCoverLimit(item) {
acoSection.popups(`aco_popup`).as('popupACO').then(function () {
cy.get('#popupACO').contains('text');
acoSection.dropDowns(`example_cover`).as('coverLimit').select(item, { force: true }).then(function () {
cy.get('#coverLimit').find(':selected').should('have.text', item)
})
})
}
});
This works locally on Cypress Test Runner, as well as through headless from the CLI.
Cypress results on local machine CLI
But when I run on Jenkins, some tests get failed.
Cypress results on Cypress Cloud
I get the following CypressError on Cypress Cloud.
Timed out retrying after 10000ms: cy.find() failed because the page updated as a result of this command, but you tried to continue the command chain. The subject is no longer attached to the DOM, and Cypress cannot requery the page after commands such as cy.find().
Common situations why this happens:
Your JS framework re-rendered asynchronously
Your app code reacted to an event firing and removed the element
You can typically solve this by breaking up a chain. For example, rewrite:
cy.get('button').click().should('have.class', 'active')
to
cy.get('button').as('btn').click()
cy.get('#btn').should('have.class', 'active')
https://on.cypress.io/element-has-detached-from-dom
As per the suggestion, I tried breaking cy.get('#coverLimit').find(':selected').should('have.text', item) into two but it still didn't solve the problem. The same code works for one environment but not another.
I've made following changes to make it work.
When("User should see example Cover option with values {string}", (exampleCovers) => {
const exampleCover = exampleCovers.split("|");
exampleCover.forEach(chooseCoverLimit);
function chooseCoverLimit(item) {
acoSection.popups(`aco_popup`).as('popupACO').then(function () {
cy.get('#popupACO').contains('text')
acoSection.dropDowns(`example_cover`).select(item, { force: true }).then(function () {
acoSection.dropDowns(`example_cover`).find(':selected').should('have.text', item).wait(1000)
})
})
}
});
I am unable to assert a specific value in json returned by cookie dropped in localstorage object
Here is the json code which is the value of the key
categories: {6: true}
consent_date: "2022-05-12T11:44:37.906Z"
consent_type: 1
cookies: {1: true}
I want to assert categories has value 6 and true
I am able to get hold of key but unable to get hold of values
Below is my cypress code
cy.get('#_evidon-accept-button').wait(5000).click().then(()=>
{
localStorage.getItem('_evidon_consent_ls_32029')
})
You can just use chai's expect to assert on the value.
cy.get('#_evidon-accept-button')
.wait(5000)
.click()
.then(() => {
const data = localStorage.getItem('_evidon_consent_ls_32029')
expect(data.categories).to.equal({6: true});
})
I mocked window.open in my cypress test as
cy.visit('url', {
onBeforeLoad: (window) => {
cy.stub(window, 'open');
}
});
in my application window.open is called as window.open(url,'_self')
I need to check cypress whether proper URL is opened or not
I need to fetch URL used and check if its match the regular expression or not
const match = newRegExp(`.*`,g)
cy.window().its('open').should('be.calledWithMatch', match);
I'm getting error as
CypressError: Timed out retrying: expected open to have been called with arguments matching /.*/g
The following calls were made:
open("https://google.com", "_self") at open (https://localhost:3000/__cypress/runner/cypress_runner.js:59432:22)
I figured out how to do it. You need to capture the spy and then apply Chai assertions due to it's a Chai Spy
cy.window()
.its('open')
.then((fetchSpy) => {
const firstCall = fetchSpy.getCall(0);
const [url, extraParams] = firstCall.args;
expect(url).to.match(/anyStringForThisRegex$/i);
expect(extraParams).to.have.nested.property('headers.Authorization');
expect(extraParams?.headers?.Authorization).to.match(/^Bearer .*/);
});
This is the way
In relation to the following error:
Uncaught Error: Script error.
Cypress detected that an uncaught error was thrown from a cross origin script.
We cannot provide you the stack trace, line number, or file where this error occurred.
Referencing https://docs.cypress.io/api/events/catalog-of-events.html#To-catch-a-single-uncaught-exception
I am trying to run a test that fills out a form and clicks the button to submit:
it('adds biological sample with maximal input', function(){
cy.on('uncaught:exception', (err, runnable) => {
expect(err.message).to.include('of undefined')
done()
return false
});
cy.get('a').contains('Add biological sample').click();
. . .
cy.contains('Action results');
});
I get an error despite my spec containing the following:
cy.on('uncaught:exception', (err, runnable) => {
expect(err.message).to.include('of undefined')
done()
return false
});
Here's an image of the test failing .
The error in the bottom left reads,
Error: Uncaught AssertionError: expected '$f is not defined\n\nThis
error originated from your application code, not from Cypress.
\n\nWhen Cypress detects uncaught errors originating from your
application it will automatically fail the current test.\n\nThis
behavior is configurable, and you can choose to turn this off by
listening to the \'uncaught:exception\'
event.\n\nhttps://on.cypress.io/uncaught-exception-from-application'
to include 'of undefined'
(https://www.flukebook.org/_cypress/runner/cypress_runner.js:49186)
It seems that I am taking Cypress's advice and not getting the desired result. Any suggestions? Has this happened to anyone else?
Can you please remove expect(err.message).to.include('of undefined') and done() from the cypress exception block and add the below piece of code inside the test & run the test again
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})
The easiest way to fix this is to add the following to the top of your spec:
Cypress.on('uncaught:exception', (err, runnable) => {
return false;
});
This gets the same indentation level as your "it" blocks, nested directly under "describe". It will cause cypress to ignore all uncaught JS exceptions.
In the question, Atticus29 expects "of undefined" to be present in the error message, but the error doesn't actually contain that string. He could change
expect(err.message).to.include('of undefined')
to
expect(err.message).to.include('is not defined')
then it will pass.
To turn off all uncaught exception handling in a spec (recommended)
https://docs.cypress.io/api/events/catalog-of-events.html#To-turn-off-all-uncaught-exception-handling
To catch a single uncaught exception and assert that it contains a string
https://docs.cypress.io/api/events/catalog-of-events.html#To-catch-a-single-uncaught-exception
Although the fix of suppressing Cypress.on sometimes fix the problem, it doesn't really reveal the root problem. It's still better to figure out why you are having an unhandled error in your code (even in the test).
In my case, my form submission forward the page to another page (or current page), which causes re-render. To fix it, I need to call preventDefault.
const onSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();
};
Every problem is a bit different, the above is only one example. Try to think about what your test actually does in the real site.
Official docs suggest that the cypress.on method is placed in "cypress/suport/e2e.js"
Docs 👉 https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Support-file
CMD + F "Support File"
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false prevents Cypress from failing the test
if (err.message.includes('Navigation cancelled from')) {
console.log('🚀 TO INFINITY AND BEYOND 🚀')
return false
}
})
Add these lines Before your Test Suit.
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
});
According to the backbone documentation about validation it states:
If validate returns an error, set and save will not continue, and the
model attributes will not be modified.
So the way I read that set or save should never run if the validation fails. But that is not the results I am getting. Even when validation fails it still sends the POST/PUT request. Am I reading the docs wrong or doing something incorrect in my code?
Here is my relevant code:
https://gist.github.com/80f6ef0099fbe96025dc
App.Models.Test = Backbone.Model.extend(
urlRoot: '/api/test'
validate: (attrs) ->
errors = []
if attrs.to is ''
errors.push
name: "to"
field: "js-to"
message: "You must enter a to address"
if attrs.subject is ''
errors.push
name: "subject"
field: "js-subject"
message: "You must enter a subject"
# Return our errors array if it isn't empty
errors if errors.length > 0
)
App.Views.Details = Backbone.View.extend(
initialize: ->
#model.bind "error", #error, this
events:
"click #js-save": "saveItem"
saveItem: (e) ->
e.preventDefault()
# Set the model then save it.
#model.set
subject: $("#js-subject").val()
message: $("#js-message").val()
mailbox_id: $("#js-from").val()
to: $("#js-to").val()
cc: $("#js-cc").val()
bcc: $("#js-bcc").val()
tags: App.Helpers.tagsToObject $('#js-tags').val()
scope: $('#js-scope').val()
attachments: attachments
#model.save null,
success: (model, response) =>
App.Helpers.showAlert "Success!", "Saved Successfully", "alert-success"
#next()
error: (model, response) ->
App.Helpers.showAlert "Error", "An error occurred while trying to save this item", "alert-error"
# Show the errors based on validation failure.
error: (model, error) ->
App.Helpers.displayValidationErrors error
You do this to save your model:
#model.save null,
success: -> ...
error: -> ...
That null is the source of your trouble, use {} and things will start behaving better; if you combine your #model.set and #model.save calls, things will be even better:
attrs =
subject: $("#js-subject").val()
#...
#model.save attrs,
success: -> ...
error: -> ...
A save call looks like this:
save model.save([attributes], [options])
[...]
The attributes hash (as in set) should contain the attributes you'd like to change
So passing a null for attributes means that you want to save the model as it is.
When you save a model, the validation is mostly left up to set, the code looks like this:
if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
return false;
}
Your attrs will be null so set will not be called; however, if you let save handle your set, you will get the behavior you're after. If you passed the wait: true option, save would manually run the validation on the passed attributes:
if (options.wait) {
if (!this._validate(attrs, options)) return false;
...
}
The internal _validate method is a wrapper for validate that does some bookkeeping and error handling. You're not using wait: true so this doesn't apply to you but I thought it was worth mentioning anyway.
Consider a simple example with a model whose validate always fails. If you say this:
#model.on 'error', #error
#model.save attrs,
success: -> console.log 'AJAX success'
error: -> console.log 'AJAX error'
then #error will be called because save will end up calling set with some attributes and set will call validate. Demo: http://jsfiddle.net/ambiguous/HHQ2N/1/
But, if you say:
#model.save null, ...
the null will cause the set call to be skipped. Demo: http://jsfiddle.net/ambiguous/6pX2e/ (the AJAX here will fail).
Your #model.set call right before #model.save should be triggering your error handler but if you don't check what #model.set returns, execution will blindly continue on to the save call and talk to your server.
In summary, you have three things going on here:
You're not calling save they way you should be.
You're ignoring the #model.set return value and losing your chance to trap the validation errors.
Backbone's argument handling for save(null, ...) could be better but I don't know if it is worth the effort to handle a strange way of calling it.
You should combine your set/save pair into just a save or check what set returns.