I am writing a test to confirm page sizing is working properly and after I got the success I purposely configured the test to fail. The should fails, but the test just keeps running instead of failing out.
describe("Customers' Page size changes correctly", function () {
it("Should change the page size properly.", () => {
// SETTING UP THE INTERCEPT DETECTION THAT SAYS THAT THE PAGE HAS BEEN LOADED
cy.intercept(
"/api/v1/customers/?action=customers&pageSize=10&pageNumber=1&searchText=&filterByCustomerName=false&orderBy=CreatedOn&orderDirection=desc&partyDateStart=&partyDateEnd=&customerStatus=Active"
).as("rSearchRequest10");
cy.getToken().then(() => {
cy.visit("customers");
});
// Standard page size of 10
cy.wait("#rSearchRequest10").then(() => {
// Defaults to 10, should get 10 results.
const listResults = cy
.get("[data-testid=customersList]")
.find("[data-testid=customerListItem]");
assert.isNotEmpty(listResults);
listResults.should("have.length", 11);
});
});
});
I get the message
expected [ <div#eu.MuiBox-root.jss166>, 9 more... ] to have a length of 11 but got 10
Then the timer just keeps running. I have no further tests and feel the test should have failed at this point.
What does cy.getToken() look like? –
Cypress.Commands.add("getToken", () => {
cy.intercept('dc.services.visualstudio.com/v2/track', {
fixture: 'External/track-service-response.json'
});
cy.request('GET', 'test/bridge'); });
The solution is below. My final code looks like this. The expect within the then properly throws the error and stops the execution of that test.
it("Should default to page size 10", () => {
cy.intercept(
"/api/v1/customers/?action=customers&pageSize=10&pageNumber=1&searchText=&filterByCustomerName=false&orderBy=CreatedOn&orderDirection=desc&partyDateStart=&partyDateEnd=&customerStatus=Active"
).as("rSearchRequest10");
cy.getToken().then(() => {
cy.visit("customers");
});
// Standard page size of 10
cy.wait("#rSearchRequest10").then(() => {
// Defaults to 10, should get 10 results.
cy.get("[data-testid=customerListItem]").then((listing) => {
expect(listing).to.have.lengthOf(10, "Should be exactly 10 results");
});
});
});
Unsure why it continues to run but suspect part of your problem has to to with async nature of cypress selectors.
Instead of setting const listResults to a var you should chain off of the cy.get or .find with a new .then()
in this case it seems like you could do without the assert.isNotEmpty and just go straight to .should()
cy.get("[data-testid=customersList]")
.find("[data-testid=customerListItem]")
.should("have.length", 11);
You can also try using "expect":
// Standard page size of 10
cy.wait("#rSearchRequest10").then(() => {
// Defaults to 10, should get 10 results.
var searchResponse = cy.get("[data-testid=customerListItem]").then(listing => {
expect(listing).to.have.lengthOf(11, "Your error message for failing");
});
});
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/**')
Using Cypress Intercept to mock the routes and I want to verify the number of times the route was called. So far, I've found nothing in the docs for this. There's mention of cy.spy but it only returns 1, every time. There's a {times:N} object for the intercepted route, but it allows the route to match and succeed any number of times. It doesn't function as a call limit. This is such a common need that I'm sure I'm just missing something, tired eyes and all.
Spy:
cy.intercept({method: 'GET', url:'my-url', middleware:cy.spy().as('myspy')})
Times:
cy.intercept({method: 'GET', url:'my-url'}, {times:0})
Cypress Feature request: https://github.com/cypress-io/cypress/issues/16655
Cypress intercept is the spy.
The problem is when to check the call count.
For example, http://cypress.io/page-data
it('counts intercepts', () => {
let requestCounter = 0;
let responseCounter = 0;
cy.intercept('page-data/**/*', (req) => {
requestCounter += 1; // count requests
req.on('response', () => requestCounter += 1 ) // or count responses
})
cy.visit('https://www.cypress.io/')
cy.wait(5000).then(() => { // arbitrary wait
expect(requestCounter).to.eq(18) // since we don't know exactly
expect(responseCounter).to.eq(18) // what loads last
})
})
The answer given by Jennifer Shehane in the linked feature request shows another way using <alias>.all,
it('counts intercepts', () => {
cy.intercept('page-data/**/*')
.as('pageData')
cy.visit('https://www.cypress.io/')
cy.get('#pageData.all')
.should('have.length', 18);
})
However, it does not consistently pass. About 1 in 5 runs on my machine fail because the cy.get() responds too early.
Ideally you should be able add a timeout, but this currently has no effect.
cy.get('#pageData.all', { timeout: 10000 }) // does not observe the timeout
Using cy.spy() as a routeHandler allows a timeout to be set on the code that checks the call count.
it('counts intercepts', () => {
cy.intercept({ url: 'page-data/**/*', middleware: true }, req => {
req.on('response', (res) => {
res.setDelay(2000) // artificial delay to force failure
})
})
const spy = cy.spy();
cy.intercept('page-data/**/*', spy)
cy.visit('https://www.cypress.io/')
cy.wrap({}, { timeout: 10000 }) // adjust timeout to suit worst case page load
.should(() => {
console.log('testing spy') // see the retry happening (90+ logs)
expect(spy).to.be.callCount(18)
})
})
I was looking for the same thing because we have demo accounts that are not meant to ever reach/call our apis. After doing some research, this is what ended up working for me:
describe('demo accounts', () => {
beforeEach(function spyOnApiCalls() {
cy.intercept(/payment-platform/, cy.spy().as('coreApi'))
cy.intercept(/service-management/, cy.spy().as('mgmtApi'))
})
afterEach(function assertApisWereNotCalled() {
cy.get('#coreApi').its('callCount').should('equal', 0)
cy.get('#mgmtApi').its('callCount').should('equal', 0)
})
it('start test blocks', () => {...})
})
Note how we're passing and aliasing instances of the cy.spy, then later asserting against them. IMO, this also reads pretty well with the its('callCount').should('equal', expectedCallCount).
Here we have beforeEach and afterEach blocks because it made sense in my case, but this approach seems pretty flexible and should work in many other scenarios.
The following command works.
Note that cy.get doesn't work with the leading # but it feels more expected to me, so this will work with or without it.
Add this to /cypress/support/commands.js
Cypress.Commands.add(`verifyCallCount`, (alias, expectedNumberOfCalls) => {
const resolvedAlias = alias[0] === `#` ? alias.substring(1) : alias
cy.get(`${resolvedAlias}.all`).then((calls) => {
cy.wrap(calls.length).should(`equal`, expectedNumberOfCalls)
})
})
Usage:
cy.verifyCallCount(`#createLogEntry`, 3)
// Or
cy.verifyCallCount(`createLogEntry`, 3)
I found this problem to be a rare reason to use a local variable in Cypress:
it('Counts intercepts', () => {
let count = 0
cy.intercept('GET','/route', req => {
count = count+1;
req.reply({})
})
//... your tests
cy.get('body').then( () => {
expect(count,'Number of times intercepted').to.equal(1) //Assert intercepted only once
})
})
i am new to cypress and i am trying to check if the element exists on a page once the api call is finished.
i do a http post to url 'things/thing1' and once this api finishes i want to check if span element is present on page.
i have tried something like below.
const setUp = () => {
cy.apiPatchSomethings(something1)
.then(() => {
cy.reload();
});
}
describe('Suite name', () => {
before(() => {
setUp();
});
it('test case', () => {
cy.contains('span');
}
});
the above code doesnt work. even before span element is seen on page it checks for span element.
if i use cy.wait(10000) like below it works
it('test case', () => {
cy.wait(10000);
cy.contains('span');
});
but i dont want to use cy.wait. is there some other way to solve this. could someone help me with this. thanks.
Cypress command cy.contains() when called with a single argument is looking for content,
Syntax
cy.contains(content)
cy.contains(content, options)
cy.contains(selector, content)
cy.contains(selector, content, options)
but I'm guessing you are looking for a span element, so use
cy.get('span')
or
cy.contains('span', 'my-content-in-span')
Assuming that's not the problem, just some arbitrary sample code...
Your can modify the setup function to return a promise, in order to wait for the reload.
const setUp = () => {
return cy.apiPatchSomethings(something1) // need a return here
.then(() => {
return new Cypress.Promise(resolve => { // inner return also
cy.reload()
resolve(true) // resolve will signal reload is finished
})
});
}
Because setup() is invoked inside before() Cypress will wait for the promise to resolve before proceeding.
Please don't add extra waits or timeouts, which is too often suggested. This will only lead to flaky tests.
Note if you don't mind ditching the setup() function, it becomes a lot simpler
describe('Suite name', () => {
before(() => {
cy.apiPatchSomethings(something1)
.then(() => cy.reload() ); // all commands here will be completed
// before the tests start
});
it('test case', () => {
cy.contains('span', 'my-content-in-span');
}
});
1.You can wait for span to be visible. The default timeout that cypress provides is 4 seconds.
cy.contains('span').should('be.visible')
2.If you want to give a custom timeout(eg. 10 sec) specific to this command, you can use:
cy.contains('span', { timeout: 10000 }).should('be.visible')
3.If you want to increase the timeout globally you mention this in your cypress.json file:
"defaultCommandTimeout": 10000
and, then just use:
cy.contains('span').should('be.visible')
Now, all your commands will have a default timeout for 10 seconds.
Cypress executing the expect assertions immediately. Not even waiting for the view to be opened at all (which takes 500ms+) .. also a cy.wait(1000) or cy.get('.modalbutton').should('be.visible'); (which would make the handle.exists() be truthy) is completely ignored too, nothing I can do to prevent it from immediately asserting.
it('Open and close split view', () => {
cy.window().then(win => {
const handle = win.Luigi.navigation().openAsSplitView('/ext', {title: 'Preserved Split View' });
// using cy.wait(N) or cy.get('#opened-splitview').should('be.visible'); are being ignored
cy.wait(1000);
// expectations are not working since they are executed immediately
cy.expect(handle.exists()).to.be.true;
handle.close();
cy.expect(handle.exists()).to.be.false;
});
});
On the other hand this one works:
cy.window().then(win => {
const handle = win.Luigi.navigation().openAsSplitView('/ext', { title: 'Preserved Split View', collapsed: false});
handle.collapse();
cy.expect(handle.isCollapsed()).to.be.true;
});
Previously it was green when win....openAsSplitView was not saved to the handle constant and close and extra command on the Luigi.navigation() api, which I refactored to align with our client side API.
So I have the feeling it could be a bug with Cypress win interception. Or I am just using it wrong.
The handle is non-async, so immediately usable. Maybe also a refactoring to return a promise which resolves once the view is really opened would help, too.
EDIT:
It is a sort of race condition which is not handled by cypress. I have worked around it with setTimeout.
setTimeout(() => {
handle.close();
setTimeout(() => {
cy.expect(handle.exists()).to.be.false;
cy.get('#splitViewContainer').should('not.be.visible');
done();
}, 50);
}, 50);
describe('my homepage', function() {
var ptor = protractor.getInstance();
beforeEach(function(){
// ptor.ignoreSynchronization = true;
ptor.get('http://localhost/myApp/home.html');
// ptor.sleep(5000);
})
describe('login', function(){
var email = element.all(protractor.By.id('email'))
, pass = ptor.findElement(protractor.By.id('password'))
, loginBtn = ptor.findElement(protractor.By.css('#login button'))
;
it('should input and login', function(){
// email.then(function(obj){
// console.log('email', obj)
// })
email.sendKeys('josephine#hotmail.com');
pass.sendKeys('shakalakabam');
loginBtn.click();
})
})
});
the above code returns
Error: Error while waiting for Protractor to sync with the page: {}
and I have no idea why this is, ptor load the page correctly, it seem to be the selection of the elements that fails.
TO SSHMSH:
Thanks, your almost right, and gave me the right philosophy, so the key is to ptor.sleep(3000) to have each page wait til ptor is in sync with the project.
I got the same error message (Angular 1.2.13). My tests were kicked off too early and Protractor didn't seem to wait for Angular to load.
It appeared that I had misconfigured the protractor config file. When the ng-app directive is not defined on the BODY-element, but on a descendant, you have to adjust the rootElement property in your protractor config file to the selector that defines your angular root element, for example:
// protractor-conf.js
rootElement: '.my-app',
when your HTML is:
<div ng-app="myApp" class="my-app">
I'm using ChromeDriver and the above error usually occurs for the first test. I've managed to get around it like this:
ptor.ignoreSynchronization = true;
ptor.get(targetUrl);
ptor.wait(
function() {
return ptor.driver.getCurrentUrl().then(
function(url) {
return targetUrl == url;
});
}, 2000, 'It\'s taking too long to load ' + targetUrl + '!'
);
Essentially you are waiting for the current URL of the browser to become what you've asked for and allow 2s for this to happen.
You probably want to switch the ignoreSynchronization = false afterwards, possibly wrapping it in a ptor.wait(...). Just wondering, would uncommenting the ptor.sleep(5000); not help?
EDIT:
After some experience with Promise/Deferred I've realised the correct way of doing this would be:
loginBtn.click().then(function () {
ptor.getCurrentUrl(targetUrl).then(function (newURL){
expect(newURL).toBe(whatItShouldBe);
});
});
Please note that if you are changing the URL (that is, moving away from the current AngularJS activated page to another, implying the AngularJS library needs to reload and init) than, at least in my experience, there's no way of avoiding the ptor.sleep(...) call. The above will only work if you are staying on the same Angular page, but changing the part of URL after the hashtag.
In my case, I encountered the error with the following code:
describe("application", function() {
it("should set the title", function() {
browser.getTitle().then(function(title) {
expect(title).toEqual("Welcome");
});
});
});
Fixed it by doing this:
describe("application", function() {
it("should set the title", function() {
browser.get("#/home").then(function() {
return browser.getTitle();
}).then(function(title) {
expect(title).toEqual("Welcome");
});
});
});
In other words, I was forgetting to navigate to the page I wanted to test, so Protractor was having trouble finding Angular. D'oh!
The rootElement param of the exports.config object defined in your protractor configuration file must match the element containing your ng-app directive. This doesn't have to be uniquely identifying the element -- 'div' suffices if the directive is in a div, as in my case.
From referenceConf.js:
// Selector for the element housing the angular app - this defaults to
// body, but is necessary if ng-app is on a descendant of <body>
rootElement: 'div',
I got started with Protractor by watching the otherwise excellent egghead.io lecture, where he uses a condensed exports.config. Since rootElement defaults to body, there is no hint as to what is wrong with your configuration if you don't start with a copy of the provided reference configuration, and even then the
Error while waiting for Protractor to sync with the page: {}
message doesn't give much of a clue.
I had to switch from doing this:
describe('navigation', function(){
browser.get('');
var navbar = element(by.css('#nav'));
it('should have a link to home in the navbar', function(){
//validate
});
it('should have a link to search in the navbar', function(){
//validate
});
});
to doing this:
describe('navigation', function(){
beforeEach(function(){
browser.get('');
});
var navbar = element(by.css('#nav'));
it('should have a link to home in the navbar', function(){
//validate
});
it('should have a link to search in the navbar', function(){
//validate
});
});
the key diff being:
beforeEach(function(){
browser.get('');
});
hope this may help someone.
I was getting this error:
Failed: Error while waiting for Protractor to sync with the page: "window.angular is undefined. This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details"
The solution was to call page.navigateTo() before page.getTitle().
Before:
import { AppPage } from './app.po';
describe('App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should have the correct title', () => {
expect(page.getTitle()).toEqual('...');
})
});
After:
import { AppPage } from './app.po';
describe('App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
page.navigateTo();
});
it('should have the correct title', () => {
expect(page.getTitle()).toEqual('...');
})
});
If you are using
browser.restart()
in your spec some times, it throws the same error.
Try to use
await browser.restart()