Cypress executes assertion immediately on function that returns a handle - cypress

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);

Related

Verify number of times request was made

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
})
})

Cypress Should fails test keeps running

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");
});
});

How to wait for the api call to finish and then check if element present using cypress?

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.

access the output of get in then block

how to access the element that is yielded from .get().contains() in .then() function
My code is not entering the Then block. where am i doing wrong ?
cy.get(".c-header-listItem").contains("My Account").should(($link) => {
expect(localStorage.read("CD-SessionId")).to.be.not.null;`enter code here`
}).then(($link) => {
$link.click();
});
I got the login from Cypress documentation https://docs.cypress.io/api/commands/should.html#Subjects
.should(elem => {}) behaves exactly as .then(elem => {}), except that the function passed to the should will retry until it doesn't throw any exceptions. With that in mind, the following code should work:
cy.get(".c-header-listItem").contains("My Account").should(($link) => {
expect(localStorage.read("CD-SessionId")).to.be.not.null;
// Notice I have to wrap this to perform a Cypress
// click on it. $link is a native DOM object.
cy.wrap($link).click();
});
This would also work, but the separation isn't necessary.
cy.get(".c-header-listItem").contains("My Account").should(($link) => {
expect(localStorage.read("CD-SessionId")).to.be.not.null;
});
cy.get(".c-header-listItem").contains("My Account").click();

How to test an inner Observable that will not complete?

I'm using jest to test a redux-observable epic that forks off an inner observable created using Observable.fromEvent and listens for a specific keypress before emitting an action.
I'm struggling to test for when the inner Observable does not receive this specific keypress and therefore does not emit an action.
Using jest, the following times out:
import { Observable, Subject } from 'rxjs'
import { ActionsObservable } from 'redux-observable'
import keycode from 'keycode'
const closeOnEscKeyEpic = action$ =>
action$.ofType('LISTEN_FOR_ESC').switchMapTo(
Observable.fromEvent(document, 'keyup')
.first(event => keycode(event) === 'esc')
.mapTo({ type: 'ESC_PRESSED' })
)
const testEpic = ({ setup, test, expect }) =>
new Promise(resolve => {
const input$ = new Subject()
setup(new ActionsObservable(input$))
.toArray()
.subscribe(resolve)
test(input$)
}).then(expect)
// This times out
it('no action emitted if esc key is not pressed', () => {
expect.assertions(1)
return testEpic({
setup: input$ => closeOnEscKeyEpic(input$),
test: input$ => {
// start listening
input$.next({ type: 'LISTEN_FOR_ESC' })
// press the wrong keys
const event = new KeyboardEvent('keyup', {
keyCode: keycode('p'),
})
const event2 = new KeyboardEvent('keyup', {
keyCode: keycode('1'),
})
global.document.dispatchEvent(event)
global.document.dispatchEvent(event2)
// end test
input$.complete()
},
expect: actions => {
expect(actions).toEqual([])
},
})
})
My expectation was that calling input$.complete() would cause the promise in testEpic to resolve, but for this test it does not.
I feel like I'm missing something. Does anyone understand why this is not working?
I'm still new to Rx/RxJS, so my apologies if the terminology of this answer is off. I was able to reproduce your scenario, though.
The inner observable (Observable.fromEvent) is blocking the outer observable. The completed event on your ActionsObservable doesn't propagate through until after the inner observable is completed.
Try out the following code snippet with this test script:
Run the code snippet.
Press a non-Escape key.
Nothing should be printed to the console.
Select the "Listen for Escape!" button.
Press a non-Escape key.
The keyCode should be printed to the console.
Select the "Complete!" button.
Press a non-Escape key.
The keyCode should be printed to the console.
Press the Escape key.
The keyCode should be printed to the console
The onNext callback should print the ESC_PRESSED action to the console.
The onComplete callback should print to the console.
document.getElementById('complete').onclick = onComplete
document.getElementById('listenForEsc').onclick = onListenForEsc
const actions = new Rx.Subject()
const epic = action$ =>
action$.pipe(
Rx.operators.filter(action => action.type === 'LISTEN_FOR_ESC'),
Rx.operators.switchMapTo(
Rx.Observable.fromEvent(document, 'keyup').pipe(
Rx.operators.tap(event => { console.log('keyup: %s', event.keyCode) }),
Rx.operators.first(event => event.keyCode === 27), // escape
Rx.operators.mapTo({ type: 'ESC_PRESSED' }),
)
)
)
epic(actions.asObservable()).subscribe(
action => { console.log('next: %O', action) },
error => { console.log('error: %O', error) },
() => { console.log('complete') },
)
function onListenForEsc() {
actions.next({ type: 'LISTEN_FOR_ESC' })
}
function onComplete() {
actions.complete()
}
<script src="https://unpkg.com/rxjs#5.5.0/bundles/Rx.min.js"></script>
<button id="complete">Complete!</button>
<button id="listenForEsc">Listen for Escape!</button>
Neither the switchMapTo marble diagram nor its textual documentation) clearly indicate what happens when the source observable completes before the inner observable. However, the above code snippet demonstrates exactly what you observed in the Jest test.
I believe this answers your "why" question, but I'm not sure I have a clear solution for you. One option could be to hook in a cancellation action and use takeUntil on the inner observable. But, that might feel awkward if that's only ever used in your Jest test.
I can see how this epic/pattern wouldn't be a problem in a real application as, commonly, epics are created and subscribed to once without ever being unsubscribed from. However, depending on the specific scenario (e.g. creating/destroying the store multiple times in a single application), I could see this leading to hung subscriptions and potential memory leaks. Good to keep in mind!

Resources