Actions sequence is great... but
Tags: cypress, programming, discuss, testing
Published: 2023-03-08 09:30:30

> Let's keep this introduction brief. I had a lot of things on my mind after reading this fantastic article by [Filip Hric](https://www.linkedin.com/in/filip-hric/) about [how to structure a big project in Cypress](https://filiphric.com/how-to-structure-a-big-project-in-cypress).
I concur with some, disagree with others. His article covers a lot of ground despite being very brief. Hence, I'd want to start with a topic that is important to me: his actions sequence methodology. I'm hoping that this article may serve as a dialogue opener in some way. So let's get started!

## Beginning

To begin with, let's address some fundamental questions regarding test writing, such as the available approaches, the separation of logic from pages, and the existence (if any) of "bad" techniques. Regarding the first question of whether we should divide our testing project into complex logic, while **it's not mandatory**, it **can greatly simplify** the lives of devs/QAs. Adopting a standard method for writing test logic is crucial for ease of writing, reading, and managing tests in the long run, especially for larger projects, as mentioned in Filip's article.

### Methodologies / design patterns

> _Ok, you convinced me. What I can do? What I can slap into project and what will work for me?_

Numerous effective methods exist for organizing test logic (_I'm not referring to organizing tests or the file structure for helper methods; this can be covered in another article as it's also a rather big topic_). The optimal technique relies on factors such as the project's pace, team size, and what works best for the team. However, industry consider the following recommendations as the "golden trio" of methodologies used to maintain tests.

#### <u>Page object model (POM)</u>
Page Object Model (POM) is a design pattern used to organize and maintain test automation code. In this methodology, each page of the application under test is represented by a separate class or module containing all the necessary elements and methods to interact with that page.

**Advantages**:

- POM promotes reusability of code by keeping page-specific code separate from test code
- It makes tests more readable and easy to maintain by encapsulating page functionality into separate classes
- It helps in minimizing code duplication and improves test maintainability
- It enhances test scalability as new pages can be added to the framework with ease

**Disadvantages**:

- It may not be the best approach for small-scale applications with a limited number of pages, as its designed for large and complex applications
- This methodology can be memory (RAM) demanding in more complex test setups, because it loads entire page classes and init (so also store them in-memory)them during the test configuration phase

#### <u>App actions</u>

App Actions is a well-known Cypress design pattern that allows for the creation of tests at a higher degree of abstraction. Rather than engaging with specific components on a page, tests are built by describing a sequence of user activities that mimic user behavior from any point in the app. It's because it uses public app methods to configure tests to our specifications before the test is done. 

**Advantages**:

- App Actions increase test code readability and maintainability by abstracting low-level UI interactions to independent methods
- Enables us to prepare the app for testing purposes
- It decreases the complexity of test code and makes tests less prone to failure due to small UI changes
- It saves time in test creation and debugging as tests can be written faster with less boilerplate code like in POM

**Disadvantages**:

- It requires a good understanding of the application's user flows and may be more challenging for new devs/QAs/testers to learn
- It requires a good knowledge how the app is built, and how to expose certain functionalities of the app. Or it needs assistance from the dev's team

#### <u>BDD via Cucumber</u>

Behavior Driven Development (BDD) is a software development methodology that emphasizes collaboration between developers, testers, and stakeholders. Cucumber is a popular BDD tool that enables writing tests in a natural language format that can be easily understood by non-technical stakeholders.

**Advantages**:

- "BDD via Cucumber" promotes collaboration between different teams and improves communication between developers, QAs, testers, and stakeholders
- It improves test traceability and provides better coverage of requirements
- Easy to understand tests building blocks
- It helps in identifying defects early in the development cycle, as cases usually written much earlier than in other methodologies

**Disadvantages**:

- It requires additional setup and configuration to use Cucumber with Cypress
- It may not be the best approach for small-scale applications with a limited number of stakeholders (burning resources)
- It may require additional effort to maintain test code as requirements change

> _Hey, but Filip mentioned "actions sequence", where does it fit? It's not in your list. I thought it's main reason why this article even exists!_

As far as I understand Filip's article, he's writing about mix, of app actions with "native" BDD of Cypress, and idea of page object model. So we operate on certain page... with usage of native Cypress commands, in BDD manner.

## Actions sequence is great... but

> I was surprised that someone is doing actions sequence. I'm (with colleagues) already using it across projects, much before the article showed up.

But first of all, as before, let's dive into, what it exactly is, its advantages and disadvantages.

Actions Sequence is a test automation methodology that combines the principles of BDD, Page Object Model, and App Actions. In this methodology, tests are written as a sequence of user actions that operate on a particular page/view, using the native Cypress commands. Unlike POM, this methodology doesn't define page objects as classes, but instead, it focuses on defining separate actions on pages/views that can be reused across tests.

**Advantages**:

- Actions Sequence improves test reusability and maintainability by abstracting page interactions into separate actions
- It promotes a high level of readability and maintainability of test code
- It saves time in test creation and debugging as tests can be written faster with less boilerplate code
- It combines the best of both worlds: BDD for _almost_ natural language test writing and App Actions for higher-level interaction with the UI
- Eliminates POM's memory issue (but does it really? I mean in terms of Filip's article... I'll write about it later in this article)

**Disadvantages**:

- Test framework is much more modular.
  - There might be question "_Why it's a problem, doesn't it bring flexibility, so it's advantage?_" - let me explain my thought process:
    - Yes, a more modular test framework with different actions for pages/views can improve test reusability and make the test code more maintainable from a mid/senior standpoint.
It can also make the structure of the test framework easier to grasp and reduce code duplication.
    - However, the modular nature of the framework, can be more challenging for newcomers and necessitate a higher level of understanding to navigate. They may be more comfortable with Cypress Commands than with designing different actions for pages/views. Maintaining the correct folder/file structure might sometimes be difficult for novice devs/QAs. **This series is designed for beginners who wish to break out of their shells.**
- Increased likelihood of code duplication by dev/QA who don't understand the structure of the test framework
- It necessitates a thorough understanding of the application's user flows and may be more difficult for inexperienced testers to master
- It might not be the best solution for super small-scale applications with a modest number of pages

> _If you think it's working (so it must be great), why you used "but" in article's title?_

It wasn't "but" in meaning "I'm against it". It was because I work with such approach for some time, I think I may have some ideas and approaches which I think can be... filling out some gaps in such approach Filip showed up in his article.

**Selecting elements**
I **strongly agree** that using the testing attribute, such as 'data-cy' in the article's example, is beneficial. However, I have a concern regarding repeating these attributes. It's possible that the author only intended to demonstrate the process and avoid delving into the selection process. Nevertheless, I recommend a personalized approach to selecting elements on the page by separating and customizing them. I have already detailed this process in a "how-to" article, which can be found [here](https://dev.to/inanoniloquent/selectors-and-locators-544c).

**Using Cypress Commands**
As previously stated, this method has the benefit of being lightweight and using less RAM. However, the example provided by the author involves creating new Cypress Commands for every action, which I believe could result in even more memory usage than the Page Object Model.

To illustrate, let's consider a scenario where we have two pages. With the current approach, all actions would be written as Cypress Commands. This means that when we want to test something on just one page, we end up loading the entire hefty Cypress API into memory, even though we only use a portion of it for the test (excluding the literal Cypress API operations). So, how would I go about it?

Actions could be carried with using modular approaches. 
Instead of:
```ts
// cypress/commands/actions.ts -> imported to cypress/commands/e2e.ts
Cypress.Commands.add('pickSidebarItem', (item: 'Settings' | 'Account' | 'My profile' | 'Log out') => {
  cy.get('[data-cy=hamburger-menu]')
    .click();

  cy.contains('[data-cy=side-menu]', item)
    .click();
});

// cypress/e2e/app1/some-test.spec.ts
describe("Sample", () => {
  it("should pick sidebar item", () => {
    cy.pickSidebarItem("Settings");
    cy.url().should("include", "/settings");
  });
});
```
I'd suggest such approach:
```ts
// cypress/e2e/app1/utils/actions.ts
export const pickSidebarItem = (item: 'Settings' | 'Account' | 'My profile' | 'Log out') => {
  cy.get('[data-cy=hamburger-menu]')
    .click();

  cy.contains('[data-cy=side-menu]', item)
    .click();
});

// cypress/e2e/app1/some-test.spec.ts
import { pickSidebarItem } from 'utils/actions'
describe("Sample", () => {
  it("should pick sidebar item", () => {
    pickSidebarItem("Settings");
    cy.url().should("include", "/settings");
  });
});
```
Thanks to this we divided responsibilities of methods. Actions are kept close to app/page/view we test. Also from my point of view, Cypress Commands should expand usability of Cypress API (like new way of selecting elements), not adding specific interactions or steps that a user would take **within an app** (or to setup tests for testing).

**Clutter of Cypress namespace**
This is something completely separate from the second point I made earlier. If we don't use Cypress Commands, we don't have to clutter up the Cypress namespace. But guess what? We can still make chainable methods (and give them types). Isn't that great?

From this:
```ts
// cypress/commands/actions.ts -> imported to cypress/commands/e2e.ts
declare global {
  namespace Cypress {
    interface Chainable {
      addBoardApi: typeof addBoardApi;
    }
  }
}

/**
 * Creates a new board using the API
 * @param name name of the board
 * @example
 * cy.addBoardApi('new board')
 *
 */
export const addBoardApi = function(this: any, name: string): Cypress.Chainable<any> {

  return cy
    .request('POST', '/api/boards', { name })
    .its('body', { log: false }).as('board');
    
};
```
to this
```ts
// cypress/e2e/app1/utils/actions.ts

/**
 * Creates a new board using the API
 * @param name name of the board
 * @example
 * addBoardApi('new board')
 *
 */
export const addBoardApi = function(this: any, name: string): Cypress.Chainable<any> {

  return cy
    .request('POST', '/api/boards', { name })
    .its('body', { log: false }).as('board');
    
};
```

So, to sum it up, Filip came up with this cool way to blend App Actions with native BDD from Cypress and the Page Object Model concept. It's pretty awesome, but there's room for customization to make it even better for our specific needs. So, what do you think? Do you have any ideas for how we could tweak it to make it work even better for us? Open to discussion!