A Cypress test file (a spec) has a shape you will write hundreds of times. Here it is, testing the Nimbus login page:
describe('Nimbus login', () => {
it('shows validation errors for an empty form', () => {
cy.visit('/');
cy.get('[data-testid="login-button"]').click();
cy.get('[data-testid="email-error"]').should('be.visible');
cy.get('[data-testid="password-error"]').should('be.visible');
});
});
Read it as four layers:
describegroups related tests. One describe block per page or feature is a good start.itis one test. Its name is a sentence stating what the app should do. You are writing a claim, not a label. "shows validation errors for an empty form" tells a reader exactly what broke when it fails.- Arrange and act.
cy.visit('/')loads the page and waits for it to finish loading. Then you do what a user would do. Here, click Sign in without typing anything. - Assert.
.should(...)states what must now be true. This line is the test. Without it, the code runs, clicks, finishes, and passes whether the app works or not.
That last point is the difference between code that runs and a test that proves something. A test with no check passes against a completely broken app.
I once reviewed a test file with fourteen tests and no checks at all. Every test clicked through a form and finished. The suite was green for three months. It had never tested anything.
When you solve problems here, we check this for real: we break the app on purpose, and your test must turn red.
Two small habits that pay off from day one:
- One behaviour per
it. When it fails, the name points straight at the problem. - Name tests as claims about the app, not steps ("shows an error", not "click button and check").
The official guide to your first spec: Writing your first end-to-end test.
Now prove it: write this exact test yourself in the problem below, and we will check it catches the bug.