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
https://github.com/csi-lk/cypress-gwt-example

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