[2] Start: 18/03/2022, 17:10:16
[2] 1) Loading spinner should display first
[2]
[2] 0 passing (7s)
[2] 1 failing
[2]
[2] 1) Common
[2] Page loading
[2] Loading spinner should display first:
[2]
[2] AssertionError: Expected <div.ui.active.transition.visible.inverted.dimmer> not to exist in the DOM, but it was continuously found.
[2] + expected - actual
[2]
[2]
[2] at Context.eval (https://localhost:9090/__cypress/tests?p=integration/common.cypress.js:27567:12)
[2]
[2]
[2]
[2] After Each: 18/03/2022, 17:10:22
So from the Start to the After Each is 6 seconds. However, I've set the timeout value well above 6 seconds:
describe('Page loading', () => {
it('Loading spinner should display first', () => {
let sendResponse;
const trigger = new Promise(resolve => {
sendResponse = resolve;
});
cy.intercept(
{
method: 'GET',
url: '/databases?Namespace=self',
hostname: 'localhost',
},
request => {
return trigger.then(() => {
request.reply();
});
}
);
cy.visit('https://localhost:9090');
log('Start: ' + new Date(Date.now()).toLocaleString());
cy.getByTestId('loader-spinner', { timeout: 1000000000 })
.should('be.visible', { timeout: 1000000000 })
.then(() => {
sendResponse();
cy.getByTestId('loader-spinner', { timeout: 1000000000 }).should('not.exist', {
timeout: 1000000000,
}); // <--- second should
});
});
I don't understand why the second should is not waiting.
You can see in the docs, there're currently these timeouts you can set up:
default command timeout
exec timeout
task timeout
page load timeout
request timeout
response timeout
And in your test, you can use "defaultCommandTimeout" to change default timeout
ex:
cypress.json
{
"defaultCommandTimeout": 5000
}
you can just apply this configuration in a single test.
it('slow test', { defaultCommandTimeout: 5000 }, () => {
// will wait 5 seconds for element to appear in dom
cy.get('slowElement')
})
Cypress.config() documentation
The issue was the testing library I was using: https://stackoverflow.com/a/58857483/1137669
From that answer, the getByTestId immediately fails, what I should have been using was get.
Related
We are using segment in our application and i need to implement an E2E test in order to verify the number of segment calls, i must be sure that every event will be called only once.
I've been searching for a while, i've found this command that verifies the number of api calls:
Cypress.Commands.add(`verifyCallCount`, (alias, expectedNumberOfCalls) => {
const resolvedAlias = alias[0] === `#` ? alias.substring(1) : alias;
cy.get(`${resolvedAlias}.all`, { timeout: 20000 }).then((calls) => {
cy.wrap(calls.length).should(`equal`, expectedNumberOfCalls);
});
});
I use this command after waiting for the api call:
cy.wait(`#${eventAlias}`, { timeout: 20000 })
.then((interception) => {
return JSON.parse(interception.request.body);
})
.then(() => cy.verifyCallCount(eventAlias, 1););
Here is also the place where i add my alias for the api call.
beforeEach(() => {
cy.intercept('POST', 'https://api.segment.io/v1', (req) => {
const body = JSON.parse(req.body);
if (body.hasOwnProperty('type') && body.type === SampleEvent) {
req.alias = eventAlias;
}
});
});
});
Using this approach, when i run the test on local environment, it passes without any problem. but the same test fails on github's actions. and this is the error:
AssertionError: Timed out retrying after 10000ms: Expected to find element: `eventAlias.all`, but never found it.
I think that the .get() command is not being executed after .wait(), i tried to change the order of the commands, but it's not helping.
How can i fix this problem in github actions?
Is there any other way to verify the number of api calls in cypress?
I appreciate any help, thank you.
The answer you used from here Verify number of times request was made is wrong.
The line const resolvedAlias = alias[0] === '#' ? alias.substring(1) : alias removes the initial #, but it needs to be kept.
Also the timeout in cy.get('${resolvedAlias}.all', { timeout: 20000 }) has no effect, it doesn't wait 20 seconds for all calls to happen.
In your test scenario there may be 0, 1, or 2 calls. You want to fail if there is 0 calls or 2 calls, and pass if there is exactly 1 call.
This is enough to fail if there is 0 calls
cy.wait(`#${eventAlias}`, { timeout: 20000 })
To fail if there are 2 calls, you must use a hard wait, then verify the call count
cy.wait(`#${eventAlias}`, { timeout: 20_000 })
cy.wait(2_000) // wait an interval for any extra call to occur
cy.get(`#${eventAlias}.all`)
.its('length')
.should(`equal`, 1); // if two calls happened in interval, fail here
I notice you mention github actions. I had similar problems when testing an API call in CI, the test runs much slower and cause flakiness.
I suggest mocking the response to get better, more consistent performance from your test.
Ref: Controlling the response
As a bonus, there is no need for any long timeout because your mock replies immediately.
beforeEach(() => {
cy.intercept('POST', 'https://api.segment.io/v1', (req) => {
const body = JSON.parse(req.body);
if (body.hasOwnProperty('type') && body.type === SampleEvent) {
req.alias = eventAlias;
// here send mock response without any network delay
req.reply({
headers: {
Set-Cookie: 'newUserName=Peter Pan;'
},
statusCode: 201,
body: {
name: 'Peter Pan'
}
})
}
});
});
})
it('tests there is only a single POST from app', () => {
cy.wait(`#${eventAlias}`)
cy.wait(100)
cy.get(`#${eventAlias}.all`).then((calls) => {
cy.wrap(calls.length).should(`equal`, 1);
});
})
Your goal is to ensure only 1 API call.
You will need the test to wait and see if a 2nd call occurs.
it('accurately test that only one API call happens', () => {
const numOfRequests = 1
cy.intercept('**/api/*', cy.spy().as('api-spy'))
cy.visit('/');
cy.wait(1000)
cy.get('#api-spy').its('callCount').should('equal', numOfRequests)
})
I tested with a simple page that deliberately calls twice, with a delay 100ms between calls,
<script>
fetch('api/1')
setTimeout(() => fetch('api/2'), 100) // delayed 2nd fetch we want to test for
</script>
Without the hard wait the test gives me a false pass.
I also tried inverting the logic, but it still needs a hard wait to test correctly
cy.intercept('**/api/*', cy.spy().as('api-spy'))
cy.visit('/');
cy.wait(1000)
cy.get('#api-spy').its('callCount')
.should('not.equal', 0)
.and('not.equal', 2) // false pass without hard wait
})
Counting inside the routeHandler that checks body.type
2nd alias for call count
before(() => {
cy.wrap(0).as('eventCount')
})
beforeEach(() => {
cy.intercept('POST', 'https://api.segment.io/v1', (req) => {
const body = JSON.parse(req.body);
if (body.hasOwnProperty('type') && body.type === SampleEvent) {
req.alias = eventAlias;
cy.get('#eventCount').then(count => {
cy.wrap(count + 1).as('eventCount')
})
}
});
});
});
it('checks the count', () => {
cy.visit('/');
cy.wait(1000)
cy.get('#eventCount')
.should('equal', 1)
})
Incrementing a global
let eventCount = 0;
beforeEach(() => {
cy.intercept('POST', 'https://api.segment.io/v1', (req) => {
const body = JSON.parse(req.body);
if (body.hasOwnProperty('type') && body.type === SampleEvent) {
req.alias = eventAlias;
eventCount += 1
}
});
});
});
it('checks the count', () => {
cy.visit('/');
cy.wait(1000)
.then(() => {
cy.wrap(eventCount)
.should('equal', 1)
})
})
When you want to get all of the alias calls, you will need to use # to signify the alias. So the custom command will need to be updated.
Cypress.Commands.add(`verifyCallCount`, (registeredAlias, expectedNumberOfCalls) => {
if(alias[0] !== '#') {
throw new Error ('alias does not start with '#')
}
cy.get(`${registeredAlias}.all`, { timeout: 20000 }).then((calls) => {
cy.wrap(calls.length).should(`equal`, expectedNumberOfCalls);
});
});
Usage
cy.intercept('call').as('call')
// some action to trigger call
cy.wait('#call')
// some other actions
cy.verifyCallCount('#call')
Is there any other way to verify the number of api calls in cypress?
This is a concise way to count the api calls and wait for them to finish.
You can pass a cy.spy() in as a "response" and you can use that to count the number of times the intercept was hit.
Using .should() in the Cypress assertion will wait until the expected number of requests to come back.
it('test', () => {
const numOfRequests = 5;
cy.intercept('https://api.segment.io/v1', cy.spy().as('api-spy'));
// Do something to trigger 5 requests
cy.get('#api-spy').its('callCount').should('equal', numOfRequests);
});
If there are a sequence of different endpoints you are waiting for such as /v1/login followed by a /v1/getData etc, the URL in the cy.intercept may need to use a wildcard.
For example:
cy.intercept('https://api.segment.io/v1/**')
I'm writing a test for a page, that loads the content via an AJAX-call. Here is the events:
Step 1: Page loads
Step 2: AJAX-call fetches content
Step 3: AJAX-call retrieves status 200
Step 4: X seconds passes
Step 5: Content is displayed on the page
My test is flaky, since it varies a lot how many seconds that passes in step 4, before the content is displayed.
Here is my code:
it( 'Test content is there', () => {
cy.fixture( 'my-fixture-path/article.json', "utf8" ).as( 'article' );
cy.get( "#article" ).then( ($article) => {
cy.intercept({
method: 'GET',
url: '/wp-json/my-namespace/public/v1/article/' + $article.postId,
}).as('contentApiCall');
});
cy.get( "#article" ).then( ($article) => {
cy.wait( '#contentApiCall' ).then( ({response}) => {
// This expect-line always passes
expect( response.statusCode ).to.equal( 200 );
// This check here is flaky, depending on how quickly the content renders
// after the API-call is returned.
cy.get( '#main .post-content' ).children().its( 'length' ).should( 'be.gte', 4 );
// The "Greater than 4" is because I'm checking for "More than 4 <p>-tags"
});
});
});
Can (and should) I change this line here:
cy.get( '#main .post-content' ).children().its( 'length' ).should( 'be.gte', 4 );
From:
'Are the length of #main .post-content-children more than 4'
To:
'Wait until the length of #main .post-content-children is more than 4'
?
... And if so - how do I do that?
Should assertions will continue to retry until the expected conditions are met. Now by default timeout is 4 seconds in cypress, but you can give custom timeouts for this.
cy.get('#main .post-content', {timeout: 7000})
.children({timeout: 7000})
.its('length')
.should('be.gte', 4)
Some times using .its('length') in a chain will break the retry mechanism.
You can fix it by using the callback version of .should()
cy.get( '#main .post-content' ).children()
.should( $children => {
const count = $children.length // move this inside the callback
expect(count).to.be.gte(4)
})
I have a test where I want to know that a window property is NOT set after an arbitrary timeout period.
So, in pseudo-code:
cy.window().its('msg_1', {
timeout: 3000
}).should.timeoutIn3000
In other words, the test passes if the 3000ms timeout is reached. If the window property msg_1 turns up before 3000ms, the test should fail.
Is that possible? I am probably missing the obvious here.
Thanks
The strategy might be test, wait, test again.
it('does not see the message', () => {
cy.visit('http://example.com').then(win => {
setTimeout(() => {
win.msg_1 = 'hi'
}, 1000)
})
cy.window().then(win => expect(win.msg_1).to.eq(undefined)) // passes
cy.wait(3000)
cy.window().then(win => expect(win.msg_1).to.eq(undefined)) // fails
})
Ok, following #EQQ's helpful reply I found the following works:
// This will pass if it is not there
cy.window().then(win => expect(win.msg_1).to.eq(undefined))
// Wait for the timeout and test again
cy.window().wait(3000).then(win => {
expect(win.msg_1).to.eq(undefined)
})
and as a custom command:
/**
* Use to test that a window property does NOT turn up
* within the specified time period
*/
Cypress.Commands.add("notIts", (prop, timeout) => {
cy.window().then(win => expect(win[prop]).to.eq(undefined))
cy.window().wait(timeout).then(win => {
expect(win[prop]).to.eq(undefined)
})
});
used like:
cy.notIts('msg_1', 3000)
Maybe "notIts" is not a good name for it but anyway, this works for me.
Thanks!
Murray
I'm testing an Angular App with Cypress.
I'm running my test with the Cypress dashboard, that I open using this command:
$(npm bin)/cypress open
I'm calling an API with my test: it works.
But when I change my code, Cypress will rerun the code which will cause my first (and only my first test) to fail. The request calling the API is aborted.
The only way to make it work again is to manually end the process, then start it again.
Has anyone got an idea what is causing this strange behaviour?
Here is my test code:
beforeEach(() => {
cy.visit('/');
cy.server();
cy.route('POST', `myUrl`).as('apiCall');
});
it('should find a doctor when user searches doctor with firstName', () => {
cy.get('#myInput').type('foo');
cy.get('#submitButton]').click();
cy.wait('#apiCall').then((xhr) => {
expect(xhr.status).to.eq(200);
});
});
You can prepare XHR stub like this:
describe('', () => {
let requests = {}; // for store sent request
beforeEach(() => {
cy.server({ delay: 500 }); // cypress will answer for mocked xhr after 0.5s
cy.route({
url: '<URL>',
method: 'POST',
response: 'fixture:response',
onRequest: ({ request }) => {
Object.assign(requests, { someRequest: request.body }); // it has to be mutated
},
});
});
And then in test:
it('', () => {
cy
.doSomeSteps()
.assertEqual(requests, 'someRequest', { data: 42 })
});
There is 2 advantages of this solution: first 0.5s delay make test more realistic because real backend doesn't answer immediately. Second is you can verify if application will send proper payload after doSomeActions() step.
assertEqual is just util to make assertion more readable
Cypress.Commands.add('assertEqual', (obj, key, value) =>
cy
.wrap(obj)
.its(key)
.should('deep.equal', value)
);
I am trying to dispatch 'SET_MIDDLEWARE_TYPE' action with payload.type = 'observable', wait 5 seconds and then make an API call. Currently, the SET_MIDDLEWARE_TYPE action is not being dispatched. If I remove the delay and the mergeMap, it dispatch that action.
Expected:
FETCH_USER_WITH_OBSERVABLE_REQUEST --> SET_MIDDLEWARE_TYPE (wait 5 seconds) --> FETCH_USER_WITH_OBSERVABLE_SUCCESS
Actual:
FETCH_USER_WITH_OBSERVABLE_REQUEST --> (wait 5 seconds) --> FETCH_USER_WITH_OBSERVABLE_SUCCESS
How can I obtained the expected behavior?
Code:
import { Observable } from 'rxjs';
import { githubApi } from './api';
const githubEpic = action$ =>
// Take every request to fetch a user
action$.ofType('FETCH_USER_WITH_OBSERVABLES_REQUEST')
.mapTo(({ type: 'SET_MIDDLEWARE_TYPE', payload: { type: 'observable'} }))
// Delay execution by 5 seconds
.delay(5000)
// Make API call
.mergeMap(action => {
// Create an observable for the async call
return Observable.from(githubApi.getUser('sriverag'))
// Map the response to the SUCCESS action
.map((result) => {
return {
type: 'FETCH_USER_WITH_OBSERVABLES_SUCCESS',
payload: { result } ,
};
});
});
export default githubEpic;
You are abandoning the SET_MIDDLEWARE_TYPE action when you perform the mergeMap. Notice the parameter that you pass into the mergeMap called action? That cannot make it any further downstream unless you propagate it.
You will need to prepend it to the outgoing stream. I suggest you do the following instead:
import { Observable } from 'rxjs';
import { githubApi } from './api';
const githubEpic = action$ =>
// Take every request to fetch a user
action$.ofType('FETCH_USER_WITH_OBSERVABLES_REQUEST')
.mapTo(({ type: 'SET_MIDDLEWARE_TYPE', payload: { type: 'observable'} }))
// Delay execution by 5 seconds
.delay(5000)
// Make API call
.mergeMap(action =>
// Async call
Observable.from(githubApi.getUser('sriverag'))
// Map the response to the SUCCESS action
.map(result => ({
type: 'FETCH_USER_WITH_OBSERVABLES_SUCCESS',
payload: { result }
}))
// Prepend the original action to your async stream so that it gets
// forwarded out of the epic
.startWith(action)
);
export default githubEpic;