How to get product owners to write your functional tests

Posted on:

source
Leveraging Cucumber with Cypress and the structure needed to allow your product owners to write your functional tests

Relevant Links

How to get your product owners to write your functional tests

AKA Using Given / When / Then with Cypress

^ This presentation was written in Markdown for DeckSet (mac) which is why it looks a little weird, see attached PDF output and notes of every slide

^ Have been doing frontend development and trying to take the pain out of testing for >10 years now

^ Not a twitter guy

What are we trying to solve?

^ I started a contract 4 or so years ago that had major issues with their development and deployment pipeline

^ Main goals were to decrease risk in production deployments

^ This is an evolution of that thinking to bring us to this end product

^ So lets dive in

  .─.                      .─.                                                      .─.
 (   )    ┌────────┬─┐    (   )   ┌──────────┐   ┌──────────┐       ┌──────────┐   (   )         ┌──────────┐
 ┌`─'┐    │  Ticket└─┤    ┌`─'┐   │          │   │          │ Merge │    QA    │   ┌`─'┐ Promote │          │
 │Prd│───>│  Story   │───>│Dev│──>│  Branch  │──>│    PR    │──────>│   STG    │──>│QA │────────>│Production│
 │   │    │   Bug    │    │   │   │          │   │          │       │   UAT    │   │   │         │          │
 └───┘    └──────────┘    └───┘   └──────────┘   └┬─────────┘       └──────────┘   └───┘         └──────────┘
                                                  ├>Tests Pass
                                                  └>Approval

^ Your current workflow probably consists of ticket > dev > pr > merge > qa / staging / uat regression > production

^ Shifting your current workflow of putting QA last to putting QA first by chaining a few tools

describe('logging in', () => {
	it('logs in, () => {
		cy.visit('http://localhost:3000/login');
		cy.get('email').type('contact@csi.lk');
		cy.get('password').type('hunter2');
		cy.get('input[type="button"]').click();
		cy.url().should('be', 'http://localhost:3000/dashboard');
	});
});

^ Let's start with Cypress

^ Classic login scenario, most peoples current tests look something like this

^ Visit, type in email / password, click the button, end up on the dashboard

^ instead of writing within cypress write these in feature

 Feature: Testing

   Ewww regression

   Scenario: Lack of automation
      Given I am a Frontend Developer
      When I am forced to manually test
      Then kill me

^ We're going to move them to a BDD approach with an old frield, cucumber

^ I know a lot of people shudder when hearing words like 'gherkins', 'cucumber' and the worst one... 'cukes' but this is quite nice, I promise

^ So if we translate our cypress scenario from before it looks like this

Feature: Logging In

  Tests the user can successfully login and log out

  Scenario: Logging In Sucessfully
    Given I am on the "login" page
    When I input my "email" as "contact@csi.lk"
    And I input my "password" as "hunter2"
    And I click the "login" button
    Then I should be on the "dashboard" page

^ Feature flow of login page > email > password > login button > dashboard page

^ What's up with the quotes? This is where it gets interesting, we are defining params that will be picked up in our function later

^ We are able to start defining these steps globally in a reusable way


# Given I am on the "login" page

import { Given } from "cypress-cucumber-preprocessor/steps"

Given(`I am on the {string} page`, target => {
  cy.visit(`https://localhost:3000/${target.toLowerCase()}`)
})

^ Allows you to define a param as part of the step string which we can pass into our re-usable function

^ Let's switch tracks to a different concept now, using data attributes make it easy for chpres to taget them

    <input
      type="email"
      name="email"
      placeholder="Email"
      data-qa="input-email"
    />
    <input
      type="password"
      name="password"
      placeholder="Password"
      data-qa="input-password"
    />
    <button
      data-qa="button-login"
      onClick={() => {
        setAuthentication(true)
        history.push("/dashboard")
      }}
    >
      Log In
    </button>

^ React, vue, jquery, mootools doesn't matter, what we're doing here is adding a specific data attribute to each input above, i'm using 'data-qa' as it's quite declarative but you can set this to whatever you like

^ You can strip your data-qa's out of your build in prod, some do (twitter) some don't (dropbox)

^ The most important thing is to make sure you have a specific format, see above I have {type} dash {name}, depending on your project you will need to define this but make sure it's consistent

// Then the "navigation logout" button should "not" "exist"

Then(
  `the {string} button should {string} {string}`,
  (id, assert, assertion) =>
    cy
      .get(`[data-qa="${type}-${id.replace(" ", "-").toLowerCase()}"]`)
      .should(assert, assertion)
  //cy.get([data-qa="button-navigation-logout"]).should("not", "exist")
)

// Reuse!

// Then the "login" button should "be" "disabled"

^ This leads up the coolest part, combining the global step definitions and the data qa attributes we can do some cool shit like this

^ Allows us to reuse this over and over

^ going back to our workflow



  .─.                      .─.                    .─.
 (   )    ┌────────┬─┐    (   )   ┌──────────┐   (   )   ┌──────────┐             ┌──────────┐
 ┌`─'┐    │  Ticket└─┤    ┌`─'┐   │          │   ┌`─'┐   │          │    Merge    │          │
 │Prd│───>│  Story   │───>│QA │──>│  Branch  │──>│Dev│──>│    PR    │──────┬─────>│Production│
 │   │    │          │    │   │   │          │   │   │   │          │      │      │          │
 └───┘    └──────────┘    └───┘   └──────────┘   └───┘   └┬─────────┘      │      └──────────┘
                                                          ├>Tests Pass     │      ┌──────────┐
                                                          ├>Approval       │      │ .feature │
                                                          └>Canary for UAT └─────>│  static  │
                                                                                  │   site   │
                                                                                  └──────────┘

^ move qa to start of pipeline

^ Using this approach allows your product owners to write the GWT scenarios in plain english, QA picks up and validates over to dev

^ Also is powerful for Bug flows

        .─.                             .─.
       (   )    ┌────────┬─┐           (   )
       ┌`─'┐    │        └─┤  Validate ┌`─'┐
       │QA │─┬─>│   Bug    │──────────>│Prd│<┐
       │   │ │  │          │           │   │ │
       └───┘ │  └──────────┘           └───┘ │
             │                  .─.          │
             │  ┌──────────┐   (   )   ┌──────────┐             ┌──────────┐
             │  │          │   ┌`─'┐   │          │    Merge    │          │
             └─>│  Branch  │──>│Dev│──>│    PR    │──────┬─────>│Production│
                │          │   │   │   │          │      │      │          │
                └──────────┘   └───┘   └┬─────────┘      │      └──────────┘
                                        ├>Tests Pass     │      ┌──────────┐
                                        ├>Approval       │      │ .feature │
                                        └>Canary for UAT └─────>│  static  │
                                                                │   site   │
                                                                └──────────┘

^ Now enabled QA to create branches of these bugs through the branch / ticket

^ dev proves bug is fixed through the functional tests

^ you can even have a validation step in there for product

^ then straight to prod!


       ┌──────────┐                     ┌──────────┐        .─.
       │   new    │  parse .feature(s)  │  Static  │      │(   )│wow!
       │ .feature │────────────────────>│   Site   │      │┌`─'┐│
       │   test   │                     │Generator │      └┤Prd├┘
       └──────────┘┌──────────┐         └──────────┘       │   │
             │     │ Cypress  │               │            └───┘
             ├────>│Dashboard │<────┐         │              │
             │     │          │     │         ▼              │
             │     └──────────┘     │     ┌─────┬┐       ┌───────┐
             │     ┌──────────┐     │read │     └┤ rsync │  s3   │
             │     │  Visual  │     ├─────│output│──────>│bucket │
             └────>│Regression│<────┘     │      │       │ serve │
                   │          │           └──────┘       └───────┘
                   └──────────┘

^ To complete the loop publish the .feature files to an internal s3 bucket (or something similar) for product to know what is covered

^ Visual regression can be linked too (screenshots)

^ Cypress dashboard showing videos of the flows and screenshots, what is passing and failing

^ Gives product a overall dashboard showing what is covered, even could show build status (if you're feeling confident)

^ Ok you now understand the approach so what are the pros and cons?

Cons

  • Abstracting tests from code
  • Lack of IDE tooling
  • Lots of tests make for slow builds
  • BDD Is hard

^ Your runner is now in the feature files rather than the code itself

^ There's no great intelligent IDE tools yet to bridge that gap

^ Having a ton of tests takes a while to run (although you can counter this by running against only what's changed and running tests across multiple runners)

^ BDD is a difficult state to get to, requires a lot of buy in from your product owners to dev to qa

^ But let's focus on the positive with Pros

Pros

  • Product owners write the tests and see the whole pipeline
  • QA moves to the front of the pipeline
  • Devs have less tests to write
  • Less risk with deployments

^ Everyone is happy

^ Product owners can now see everything happening and the gaps they need to fill, can go to whole business

^ QA is happy as they're moving to proactive rather than reactive

^ Dev is happy because they can focus on code rather than tests

^ Lastly, less risk with production deployments as all major scnearios are covered

Live Tech Demo

^ Show http://github.com/csi-lk/cypress-gwt-example repo

^ Show github action workflow: https://github.com/csi-lk/cypress-gwt-example/blob/master/.github/workflows/run-cypress.yml

^ Show CI working: https://github.com/csi-lk/cypress-gwt-example/actions

^ Show cypress dashboard: https://dashboard.cypress.io/#/projects/syj7cg/runs