I am working on a page which has a form. When the button in the form is clicked, a request is sent to the backend and the page changes to show that it's performing the operation (a progress bar appears, the background color changes, the button is disabled). When the response arrives, the changes on the page are reverted and some fields are updated.
Now, I want to write a Cypress test that intercepts the call to the backend, checks that the button is disabled and all, then respond with a fixture before asserting that the fields have been updated. I know that I may set the delay
field in the StaticResponse
object of cy.intercept
, but I am wondering if there is a way to do this that does not involve waiting for an arbitrary amount of time.
I would like to be able to write something akin to this:
describe('Pseudocode', () => {
it('could be nice', () => {
cy.intercept('GET', '/printers', ['printer1']);
// The 'wait' field would instruct intercept to wait for `cy.wait()` before returning
const hypotheticalStaticResponse = { fixture: 'print-response-ok.json', wait: true };
cy.intercept('POST', '/print', hypotheticalStaticResponse).as('printRequest');
cy.visit('/form');
cy.get('mat-form-field#box-identifier input').as('boxIdentifier');
cy.get('div#print > button').as('printButton');
cy.get('@boxIdentifier').type('1234567');
// The call is made when the button is clicked
cy.get('@printButton').click();
cy.get('app-shipping-label').should('have.class', 'printing');
cy.get('@printButton').should('be.disabled');
cy.get('mat-progress-bar').should('be.visible');
// The request would be answered only once we hit this instruction
cy.wait('@printRequest');
cy.get('app-shipping-label').should('have.class', 'ready');
cy.get('mat-progress-bar').should('not.exist');
});
}
describe('Assertions on my form', () => {
it("doesn't work without delay", () => {
cy.intercept('GET', '/printers', ['printer1']);
cy.intercept('POST', '/print', { fixture: 'print-response-ok.json' }).as('printRequest');
cy.visit('/form');
cy.get('div#print > button').as('printButton');
cy.get('mat-form-field#box-identifier input').type('1234567');
// The call is made when the button is clicked
cy.get('@printButton').click();
// Fails, because the fixture was already returned
cy.get('app-shipping-label').should('have.class', 'printing');
cy.get('@printButton').should('be.disabled');
cy.get('mat-progress-bar').should('be.visible');
cy.wait('@printRequest');
cy.get('app-shipping-label').should('have.class', 'ready');
cy.get('mat-progress-bar').should('not.exist');
});
it("doesn't work with inner gets", () => {
cy.intercept('GET', '/printers', ['printer1']);
cy.intercept('POST', '/print', { fixture: 'print-response-ok.json', delay: 500 }).as('printRequest');
cy.visit('/form');
cy.get('div#print > button').as('printButton');
cy.get('mat-form-field#box-identifier input').type('1234567');
// The call is made when the button is clicked
cy.get('@printButton').click();
cy.get('app-shipping-label').should('have.class', 'printing');
cy.get('@printButton').should('be.disabled');
cy.get('mat-progress-bar').should('be.visible');
cy.wait('@printRequest').then(() => {
// CypressError: `cy.then()` failed because you are mixing up async and sync code.
cy.get('app-shipping-label').should('have.class', 'printing');
cy.get('@printButton').should('be.disabled');
cy.get('mat-progress-bar').should('be.visible');
});
cy.get('app-shipping-label').should('have.class', 'ready');
cy.get('mat-progress-bar').should('not.exist');
});
it('works with a delay, but is there another way?', () => {
cy.intercept('GET', '/printers', ['printer1']);
cy.intercept('POST', '/print', { fixture: 'print-response-ok.json', delay: 500 }).as('printRequest');
cy.visit('/form');
cy.get('div#print > button').as('printButton');
cy.get('mat-form-field#box-identifier input').type('1234567');
// The call is made when the button is clicked
cy.get('@printButton').click();
cy.get('app-shipping-label').should('have.class', 'printing');
cy.get('@printButton').should('be.disabled');
cy.get('mat-progress-bar').should('be.visible');
cy.wait('@printRequest');
cy.get('app-shipping-label').should('have.class', 'ready');
cy.get('mat-progress-bar').should('not.exist');
});
};
If you add a delay to the intercept response (see Controlling the response) your test should work as-is.
This page simulates the behaviours you describe:
<body>
<form>
<div>
<app-shipping-label>Waiting to print</app-shipping-label>
</div>
<mat-progress-bar hidden>Progress...</mat-progress-bar>
<div id="print">
<button>Print</button>
</div>
</form>
<script>
const button = document.querySelector('button')
button.addEventListener("click", (event) => {
button.setAttribute("disabled", "")
const shippingLabel = document.querySelector('app-shipping-label')
shippingLabel.classList.add('printing')
shippingLabel.innerText = 'Printing the label'
const progressBar = document.querySelector('mat-progress-bar')
progressBar.removeAttribute('hidden')
fetch('/print', {
method: "POST",
body: "something"
}).then(response => {
shippingLabel.classList.add('ready')
progressBar.setAttribute('hidden', '')
})
})
</script>
</body>
This is the test
cy.fixture('example.json').then(fixtureData => {
cy.intercept('POST', '/print', (req) => {
req.reply({
statusCode: 201,
body: fixtureData,
delay: 1000, // milliseconds
})
}).as('printRequest')
})
cy.visit('/')
cy.get('div#print > button').as('printButton')
cy.get('@printButton').click()
cy.get('app-shipping-label').should('have.class', 'printing');
cy.get('@printButton').should('be.disabled');
cy.get('mat-progress-bar').should('be.visible');
cy.wait('@printRequest') // added check to verify fixture stubbing
.then(interception => {
expect(interception.response.body.email).to.eq('hello@cypress.io')
})
cy.get('app-shipping-label').should('have.class', 'ready');
cy.get('mat-progress-bar').should('not.be.visible');
The test passes, this is the log