0

I'm fairly new to Cypress and I'm using it with TypeScript. For each persona, I have a file like this:

{desired-persona}.ts.cy

In each of these files, I have 1 describe and one it that includes the whole flow (from sign-up to the result). I've seen that some people use multiple ìt commands and break down the flow, but I'm not sure whether I should use the same structure since all the process relies on each other.

I'd like to know in which cases I should break a flow into a couple of it statements instead of one, and in which cases I should avoid it. What are the best practices for structuring tests?

2 Answers 2

0

Ultimately, your question is: "How can I setup test cases and test suites in a better way?"

This question is NOT about TypeScript or Cypress, but about a pattern or architecture that is repeatable in your setup regardless of language or test automation tool!

How many it() should I use in my test files?

The quick answer: as many as you want! You can have 100 it() if you wanted. It's all about context!

The old-school thought used to be "one assert per automated test." While this can make more sense for unit tests, it does not make sense for UI/e2e tests! Also, as you're probably experiencing, having 1 describe/it per file creates a LOT of files, lots of overhead, lots of repeatable code, and lots of maintenance issues! This is very common to people new to coding and test automation.

Why create a separate file based on persona? Look at your code and understand the details of each persona. What is common between them? What are you repeating? Do you find yourself copying/pasting the same code between files? This is you chance to create a function or utilize built-in methods! There are better ways!

For example, let's talk about a login test where you have a user persona and an admin persona. Yes, eventually, they have access to different features once they login. But the act of logging in is the same! So instead of:

admin-login.ts.cy, user1-login.ts.cy, user2-login.ts.cy, user3-login.ts.cy

use

login.ts.cy or login.spec.ts.cy

It's common practice in Java/TypeScript use .spec.ts. for files containing tests (or specs/specifications). It helps to differentiate them from other non-test files you have in your framework!

This way, you know all login tests are in this file, as a test suite! File name = test suite! 1 file is better than multiple files for essentially the same test. You're only changing test data and assert data!

Your describe here is likely:

describe("As a user, I want to login:")

and your it is:

it('using a non-admin user type')

it('using an admin user type')

Here, you can also use multiple it's for e2e tests.

it('login') > assert something to prove you've logged in. it('logout') > assert something to prove you've logged out.

My best recommendation is to understand how to use your test library tool, which we don't know. Are you using Jasmine? Jest? Mocha/Chai? They all have good documentation and they can all be used in Cypress, WebdriverIO, Playwright, etc

For example, you can have multiple and nested describe() functions per test. And more then 1 it() or test() per describe()'s. it and test are usually synonymous and interchangeable depending on the tool.

Also, are you using setup and teardown methods? before(), beforeEach(), beforeAll(), after(), afterEach(), afterAll()? These are where you setup repeatable code! (Sometimes before() and beforeAll() are the same; it's tool dependent).

For Jest, look at their docs for setup/teardown. They show examples of nested describes and how to user setup/teardown and the order they operate. If you're not using Jest, the concept is the same, just extrapolate to the test tool you're using.

For login, it could look something like (pseudo-code):

describe("As a user, I want to login:")
before()

  • open URL. This is usually the same regardless of user type.

it('using a non-admin user type')

  • steps to login with this user type
  • assert successful login
  • assert something for only this user

it('using an admin user type')

  • steps to login with this user type
  • assert successful login
  • assert something for only this admin user

afterEach()

  • steps to logout of app

after()

  • close browser

You can even reduce this down to 1 it() by using variables for each user and password. Look in your test library tool documentation for parameterize tests. There are multiple ways to parameterize tests.

An example of that could be (pseudo-code):

describe("As a user, I want to login:")
before()

  • open URL.

const users = ['normalUser', 'adminUser']
for (const user of users)
{
it('login as $(user)')

  • steps to login. I'd recommend using a password variable and every test user uses the same password!
  • assert successful login
  • assert something for only this user
    }

afterEach()

  • steps to logout of app

after()

  • close browser

Parameterizing your tests, using multiple describes/its, nested describes will allow you to use less code, make it easier to edit and maintain, and can make it faster to run. You have a lot of flexibility in how you setup your test framework!

Another example for ecommerce:

describe(buy a product) before()

  • open browser
  • open url

it(search product)

  • steps to add query to search bar, press search button
  • wait for page load to search results page
  • assert you are on search results page

it(go to product details page, aka, pdp)

  • on search page, click a product (usually first in list)
  • wait for page load to product details page
  • assert something about the product (page header, product title, etc)

it (add product to cart)

  • click the add to cart button (other test can change quantity or other product specifics like color, size, etc). Keep it basic by adding 1 default product to cart.
  • assert product is in cart (does the app have a cart icon in header that changes on adding?)

it (go to cart)

  • click the shopping cart icon
  • wait for page load to car
  • assert something on cart page (page header, number of items in cart, etc)

it (checkout)

  • on cart page, click checkout button
  • wait for page load to checkout page
  • assert something on checkout page (page header, url)
  • fill out checkout form (shipping address, billing address, payment info, etc)
  • click checkout button

it (checkout status)

  • wait for page load to receipt/final status page
  • assert something on page (page title, url)
  • assert order number is present (you don't care about the extact number, just that a number is present)

You can stop here for add more:

it (order is shown in order history page)

  • on final checkout page, copy the order number into a variable
  • navigate to order history page
  • assert something on order history page (page name, title, url)
  • search for order number you copied in previous variable
  • assert order number variable is found

after()

  • logout of app
  • close browser

This example shows:

  • file name = checkout-e2e.spec.ts.cy
  • 1 describe in file
  • multiple it's to show a complete end-to-end test of an ecommerce purchase!
2

Remember the goal of automated check: To provide quickly feedback on the behavior of the software, so the developers can iterate over it to make it better.

Think about the risks you are trying to check and take advantage of the architecture of your software.

from sign-up to the result

tends to not be optimal for it, because you are dealing with many moving pieces, essentially coupling them.

For instances, if you have the following components:

Authentication <- Role-based Landing Page <- Role-based capability

where the arrow points the dependency direction (the landing page depends on auth, but capability doesn't depend on the auth), you can have scenarios that express such dependency:

Given I am a guest user
When I authenticate as an admin
Then I can access the admin capabilities

And

Given I am an admin (here you force the authentication, not using the Authentication component)
When I trigger the employee payment reporting
Then I receive a notification of the report delivery after a while

This way, if the authentication component fails, only the first scenario fails. And if the reporting capability fails, only the second scenario fails.

And also, if you want to change either the Authentication or the report components, you also will change only one of the scenarios.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.