DEV Community

Viktorija Filipov
Viktorija Filipov

Posted on

Cypress Workshop Part 3: Test structure and test execution

LESSON 3: Test structure and test execution

For the purpose of this lesson, we will not go into the explanation of all project folders and files that can be used in Cypress for writing tests, but rather we will focus only on the main test file structure. That being said, main constructs which we majorly use in Cypress test development are:

describe(): It is is simply a way to group our tests. It takes two arguments, the first is the name of the test group, and the second is a callback function. (note: context() is is just an alias for describe()).

it(): We use it for an individual test case. It takes two arguments, a string explaining what the test should do, and a callback function which contains our actual test.

before(): It runs once before all tests in the block.

after(): It runs once after all tests in the block.

beforeEach(): It runs before each test in the block.

afterEach(): It runs after each test in the block.

.only(): To run a specified suite or test, append ".only" to the function.

.skip(): To skip a specified suite or test, append ".skip()" to the function. (you can also put “x” in front of it block to skip it)

Read more about test structure

So let’s put this into action.

1: Create new folder under e2e folder, call it for example intro, and create new test file under that folder, and call it for example testStructure.cy.js. Write the following code in the test file:

/// <reference types="Cypress" />

describe('Context: My First Test', () => {
  before(() => {
    // runs once before all tests in the block (before all "it" blocks)
  });

  beforeEach('Clear Cookies', () => {
    // runs before each test in the block (before each "it" blocks)
    cy.clearCookies();
  });

  after('Log something after all test runs', () => {
    // runs once after all tests in the block (after all "it" blocks)
    cy.log('we completed this test run!');
  });

  afterEach(() => {
    // runs after each test in the block (after each "it" blocks)
  });

  it('Test 1', () => {
    cy.visit('/automation-practice-form');
    expect(true).to.equal(true);
  });

  it('Test 2', () => {
    expect(true).to.equal(true);
  });

  xit('Test 3', () => {
    expect(true).to.equal(true);
  });

  it.skip('Test 4', () => {
    expect(true).to.equal(true);
  });

  it.only('Test 5', () => {
    expect(true).to.equal(true);
  });
});

Enter fullscreen mode Exit fullscreen mode

Test Structure

Code explanation:

As you can see, in this test file we have one describe block, one before block, one beforeEach, one after block, one afterEach and multiple it blocks. It is possible to write test cases in different order and also do nesting one block inside of other, for example multiple describe blocks under other describe block, multiple it blocks under other it block etc, but for the sake of maintaining test file as simple as possible and as readable as possible always try to find one context of the test case - have one describe block, and have multiple it blocks for your sub-scenarios.

If you need Cypress hooks (before, after, beforeEach, afterEach) you and also use them and try to use one or group them in meaningful way for your test case. These are not mandatory but they are useful and it will be explained later why.

In this example, we have:

  • empty before hook (empty hook will not run, and try NOT to have them in your code),

  • beforeEach hook that will clear cookies of our app

  • after hook that will log: 'we completed this test run!'

  • afterEach hook that is empty

  • Test 1: that will visit one page of our app and assert that true is true

  • Test 2: one assertion that true is true

  • Test 3: one assertion that true is true (marked as skipped)

  • Test 4: one assertion that true is true (marked as skipped)

  • Test 5: one assertion that true is true (marked as the only test that will run)

Giving all this, can you try to guess what will be the output of this test?

If you said that execution order will be:

  1. beforeEach: cy.clearCookies();

  2. Test 5: expect(true).to.equal(true);

  3. after: cy.log('we completed this test run!');

you are correct! So, let’s prove it. You can run this test in several different ways:

  • look at package.json file and execute in VS terminal command:

npm run cypress-cli

This will open Cypress client app and over there you can choose Chrome and test that you want to run. Refer to LESSON 1 for Cypress client explanation.

  • you can also search for Cypress client app on your computer and start it yourself and you will have the same result as previous way.

  • look at package.json file and execute in VS terminal command:

npm run cypress-headed

This command will run your test WITH GUI mode, meaning in the background, cypress will run the test in headed Chrome browser.

  • look at package.json file and execute in VS terminal command:

npm run cypress-headless

This command will run your test WITHOUT GUI mode, meaning cypress will run the test in headless Chrome browser.

Everyone has their own preference and reasons why they run tests one way or another while they are developing, but the easiest one is just to open cypress client and run test manually during development, so that you can easily debug it and manipulate it. So, let’s do it that way now, open cypress cli app and run the test by clicking on it. Which result did you get?

Test run results

If you got this, then your test is running correctly.

2: So, let’s play a little bit, and edit our test. Remove .only mark from your 5th test, and run the test again.

Second test run

If you got something like this on the picture above, then you run the test correctly. You can see that once we removed .only mark from the 5th test, other tests were taken into consideration. You can see that test 3 and 4 are skipped because we marked it as skipped. Now, let’s try to enable them as well. Remove .skip and x in front of 3rd and 4th test and now all your tests should be executed in the order that you wrote them.

/// <reference types="Cypress" />

describe('Context: My First Test', () => {
  before(() => {
    // runs once before all tests in the block (before all "it" blocks)
  });

  beforeEach('Clear Cookies', () => {
    // runs before each test in the block (before each "it" blocks)
    cy.clearCookies();
  });

  after('Log something after all test runs', () => {
    // runs once after all tests in the block (after all "it" blocks)
    cy.log('we completed this test run!');
  });

  afterEach(() => {
    // runs after each test in the block (after each "it" blocks)
  });

  it('Test 1', () => {
    cy.visit('/automation-practice-form');
    expect(true).to.equal(true);
  });

  it('Test 2', () => {
    expect(true).to.equal(true);
  });

  it('Test 3', () => {
    expect(true).to.equal(true);
  });

  it('Test 4', () => {
    expect(true).to.equal(true);
  });

  it('Test 5', () => {
    expect(true).to.equal(true);
  });
});

Enter fullscreen mode Exit fullscreen mode

Third test run results

You can open each test and see what happened during its execution and debug if needed.

Test debug

Now, let’s try to fail one test intentionally. Choose for example second test and assert that true is equal to false. And you should get something like this:

  it('Test 2', () => {
    expect(true).to.equal(false);
  });
Enter fullscreen mode Exit fullscreen mode

Failed test

You can see that second test has failed, Cypress tried to run it 4 times (as per our configuration, see - LESSON 2, the end). Now if you return it to the previous state, true === true, it will pass again.

So, how does this apply to a real life test scenario?

Let’s say you need to write the test to prove that email field of a login form works correctly. For this test you will usually have to test some form of validation.

Test context: Check email input field validation in the login form (this will be your describe block)

Test 1: Check if user is able to login with correct email format (first it block)

Test 2: Check if user is not able to login if email field is empty (second it block)

Test 3: Check if user is not able to login if email is incomplete (third it block)

… and so on.

For this example, it would be good to clear cookies if your app is storing some, in order for test data not to interfere with other tests. Cypress should automatically clear cookies after each test, but depending on your test subject you might want to clear them yourself. Good practice is to clear any test state BEFORE each test. Not after. Read about this anti-pattern here: Cypress hooks anti-pattern

Another example, let’s say you need to write test for some form inside your application, but you need to login first in order to do that. Since login is not the subject of that test, it is a preparation step, you should put that login part into before or beforeEach hooks. There is a newly introduced feature where you can use cypress session to set up test data: Session.

✏️ HOMEWORK

Play with test structure and try to guess execution order.

Don’t forget to push everything you did today on Github 😉 Remember git commands? 😉

git add .

git commit -am "add: first test"

git push

CONGRATS ON WRITING YOUR FIRST TEST, AND SEE YOU IN LESSON 4!

Completed code for this lesson

If you have learned something new, feel free to support my work by buying me a coffee ☕

Buy Me A Coffee

Top comments (0)