I am spying on an element that has a canvas, which draws a canvas and clears it every second with context.clearRect(...), and I'm trying to spy on that with cypress:
describe('canvas clears', () => {
let context;
let spy;
beforeEach(() => {
cy.visitHome();
cy.sendStudyUpdateWithSingleSeries(seriesWithNoFrames); // this places the canvas on the screen
cy.get('[data-testid=data]').then(([canvas]) => {
context = (canvas as HTMLCanvasElement).getContext('2d');
spy = cy.spy(context, 'clearRect');
});
});
it('works', () => {
expect(spy).to.be.called;
});
});
Cypress makes its way throught he beforeEach section and immediately goes on to the test which immediately fails. However, after the failure is printed, the Spy shows up as called, and indeed updates its call count every second. But the test isn't looking for it anymore, I guess?
How do I make this spy work?
(note: I get the same results with expect(spy).to.have.been.called)
Related
I just started playing with cypress and I am trying to write down some tests in my sandbox application. In my first test user should click on a 'button' to make the toolbar to appear, then click on a button to activate the feature, then click a couple of times on a leaflet map to draw a line.
As you can see: click on 'Tools', then click on 'draw route' button and then click on map to draw.
This dummy app is wrapped inside a web component, here is the code:
And here is my test code:
describe('Draw geometries on map', ()=>{
beforeEach(() => {
cy.visit('http://192.168.49.2:30000/scouter/');
})
...
it('can draw after clicking draw button', ()=>{
cy.get('scouter-web').shadow().find('.scouter-tools-main-button').click()
cy.get('scouter-web').shadow().find('draw route').click()
console.log('new cy')
cy.get('scouter-web').shadow().get('scouter-web').shadow().find('#map')
.click(400, 400)
.click(400, 600)
.click(500, 600)
})
});
Problem is I, after clicking 'Tools', I can't 'find' the 'draw route' button. What am I missing? The whole stuff can be found here, subproject is scouter-web.
Instead of using the shadow() repeatedly, you can mention includeShadowDom: true once in your cypress.json file.
With find you can just use selector but I think you are using text. If you just want to use text, you can use contains.
cy.contains('draw route').click()
And if your application is throwing Uncaught Exceptions you can add to your cypress/support/index.js to globally turn off all uncaught exception handling. But a fair bit of warning, do this only when you are sure that the exceptions generated can be ignored.
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})
I need to close the tab/window after each test so I can start the next from scratch
describe('theImplementationIamTesting', () => {
after(() => {
// CLOSE THE TAB AFTER THE TEST...
});
});
I am looking a way to close the current tab after the test. I am not talking about closing a child tab/window. I am talking about the initial tab.
In selenium, it will be something like webdriver.close().
I cannot find a single place online, including the cypress website, where it said how to close the tab browser.
Thanks for helping
If you separate the cases in different test files it will close the whole browser and reopen it every time. This is the only way I had found so far and works for me very well to start every case from scratch since sometimes it continues to run unfinished API requests from the first case after the start of the second case.
The downside is you need to make the initial preparation of the system every time and it increases the runtime.
The way I resolved this was to actually add an extra line at the end of each test which would click to navigate to a page from where the other tests could continue, say the 'home page'.
describe('Test Inline Text Entry Interactions', () => {
beforeEach('Log in as CypressEditor', () => {
cy.MockLoginUser('cypressEditor');
cy.visit('http://localhost:4200/homepage');
})
it('should test 1st thing', () => {
//Test something, then...
cy.get('#logo-label').click(); //To navigate back to http://localhost:4200/homepage
});
it('should test the 2nd thing', () => {
//Test something else...
cy.get('#logo-label').click(); //To navigate back to http://localhost:4200/homepage
});
it('should test the 3rd thing', () => {
//Test some more stuff, then...
cy.get('#logo-label').click(); //this might not be necessary since it's the last one.
});
For me this ensured that each test could finish and continue with the next.
I have a map-based application (like Google Maps) and I am writing tests for the zoom-in option. The test covering all the zoom levels when zooming in. My test is working but the result is not consistent and it is flaky.
My code:
static verifyAllAvailableZoomInZoomSizeInNM() {
var expectedValues = [500,200,100,50,20,10,5,2,1,0.5,0.2,0.1,0.05,0.02,0.01,0.005,0.002,0.001,0.0005,0.0002,0.0001,0.00005,0.00002];
cy.getCurrentZoomSizes({
numValues: 26,//I expect to give 23 but I just gave 26 in order to capture all sizes
waitBetween: 1000,
elementLocator: BTN_MAP_ZOOMIN,
}).should("be.eql", expectedValues);
}
Cypress Commands:
/* Get the numeric value of zoom size in nm */
Cypress.Commands.add("getCurrentZoomSize", () => {
cy.get('div[class="ol-scale-line-inner"]').then(
($el) => +$el[0].innerText.replace(" nm", "")
);
});
/* Get a sequence of zoom size values */
Cypress.Commands.add("getCurrentZoomSizes", ({ numValues, waitBetween, elementLocator }) => {
const values = [];
Cypress._.times(numValues, () => {
cy.getCurrentZoomSize()
.then((value) => values.push(value))
cy.get(elementLocator)
.click()
.wait(waitBetween);
});
return cy.wrap(values);
});
And the test result1:
test result2:
As you can see in the screenshots, a few of the zoom sizes had duplicated. I tried giving enough wait between each zoom-in click but it is not helping either. Is there any way, I can fix this flaky test?
The loop executes a lot faster than the Cypress commands or the zoom operation, you can see it if you add a console.log() just inside the loop
Cypress._.times(numValues, (index) => {
console.log(index)
That's not necessarily a problem, it just fills up the command queue really quickly and the commands then chug away.
But in between getCurrentZoomSize() calls you need to slow things down so that the zoom completes, and using .wait(waitBetween) is probably why thing get flaky.
If you apply the .should() to each zoom level, you'll get retry and wait in between each zoom action.
The problem is figuring out how to arrange things so that the proper retry occurs.
If you do
cy.getCurrentZoomSize()
.should('eq', currentZoom);
which is equivalent to
cy.get('div[class="ol-scale-line-inner"]')
.then($el => +$el[0].innerText.replace(" nm", "") )
.should('eq', currentZoom);
it doesn't work, the conversion inside the .then() gets in the way of the retry.
This works,
cy.get('div[class="ol-scale-line-inner"]')
.should($el => {
const value = +$el[0].innerText.replace(" nm", "")
expect(value).to.eq(expectedValue)
})
or this
cy.get('div[class="ol-scale-line-inner"]')
.invoke('text')
.should('eq', `${currentZoom} nm`);
So the full test might be
Cypress.Commands.add("getCurrentZoomSizes", (expectedValues, elementLocator) => {
const numValues = expectedValues.length;
Cypress._.times(numValues, (index) => {
const currentZoom = expectedValues[index];
cy.get('div[class="ol-scale-line-inner"]')
.invoke('text')
.should('eq', ${currentZoom} nm`); // repeat scale read until zoom finishes
// or fail if never gets there
cy.get(elementLocator).click(); // go to next level
});
});
const expectedValues = [...
cy.getCurrentZoomSizes(expectedValues, BTN_MAP_ZOOMIN)
I've a unpredictable list of rows to delete
I simply want to click each .fa-times icon
The problem is that, after each click, the vue.js app re-render the remaining rows.
I also tried to use .each, but in this cas I got an error because element (the parent element, I think) has been detached from DOM; cypress.io suggest to use a guard to prevent this error but I've no idea of what does it mean
How to
- get a list of icons
- click on first
- survive at app rerender
- click on next
- survive at app rerender
... etch...
?
Before showing one possible solution, I'd like to preface with a recommendation that tests should be predictable. You should create a defined number of items every time so that you don't have to do hacks like these.
You can also read more on conditional testing, here: https://docs.cypress.io/guides/core-concepts/conditional-testing.html#Definition
That being said, maybe you have a valid use case (some fuzz testing perhaps?), so let's go.
What I'm doing in the following example is (1) set up a rendering/removing behavior that does what you describe happens in your app. The actual solution (2) is this: find out how many items you need to remove by querying the DOM and checking the length, and then enqueue that same number of cypress commands that query the DOM every time so that you get a fresh reference to an element.
Caveat: After each remove, I'm waiting for the element (its remove button to be precise) to not exist in DOM before continuing. If your app re-renders the rest of the items separately, after the target item is removed from DOM, you'll need to assert on something else --- such as that a different item (not the one being removed) is removed (detached) from DOM.
describe('test', () => {
it('test', () => {
// -------------------------------------------------------------------------
// (1) Mock rendering/removing logic, just for the purpose of this
// demonstration.
// -------------------------------------------------------------------------
cy.window().then( win => {
let items = ['one', 'two', 'three'];
win.remove = item => {
items = items.filter( _item => _item !== item );
setTimeout(() => {
render();
}, 100 )
};
function render () {
win.document.body.innerHTML = items.map( item => {
return `
<div class="item">
${item}
<button class="remove" onclick="remove('${item}')">Remove</button>
</div>
`;
}).join('');
}
render();
});
// -------------------------------------------------------------------------
// (2) The actual solution
// -------------------------------------------------------------------------
cy.get('.item').then( $elems => {
// using Lodash to invoke the callback N times
Cypress._.times($elems.length, () => {
cy.get('.item:first').find('.remove').click()
// ensure we wait for the element to be actually removed from DOM
// before continuing
.should('not.exist');
});
});
});
});
This is my protractor test:
it("should check email validity", function(){
var resetButton = element(by.id('reset-button'));
element(by.model('Contact.email')).sendKeys('nick');
element.all(by.css('.form-control-error')).each(function (elem, index) {
if (index===1) {
expect(elem.isPresent()).toBe(true);
element(by.model('Contact.email')).sendKeys('#gmail.com').then(
function(){
expect(elem.isPresent()).toBe(false);
}
)
}
});
});
Behind that code there is a form with some input texts. The second one includes the email.form-control-erroris an error message which appears whenever the email format is not correct. The first time expect(elem.isPresent()).toBe(true);passes the test, the second time it does not, even if the error message disappears from the UI. It seems that Protractor does not perceive the fast change; however, it should because it is inside a promise. Do you have any explanation for that?
You should make things more reliable by adding a wait for the element to become not present ("stale") after sending the keys:
element(by.model('Contact.email')).sendKeys('#gmail.com');
var EC = protractor.ExpectedConditions;
browser.wait(EC.stalenessOf(elem), 5000);
expect(elem.isPresent()).toBe(false);