Skip to content

Commit

Permalink
Activity Report Stepper cucumber tests (#53)
Browse files Browse the repository at this point in the history
* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Test drive cucumber

* Consolidated steps

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Enable cucumber testing by env

* Debug config.yml

* Debug cucumber start

* Debug cucumber start

* Debug cucumber start

* Debug cucumber start

* Debug cucumber start

* Debug cucumber start

* Debug cucumber start

* Debug cucumber start

* Debug cucumber start

* Add tests for the activity report stepper

* Add auth bypass

* Remove debugging stmt

* Add env variables to cucumber job

* Set redirect variable for cucumber

* Set redirect variable for cucumber

* Add screenshots, add tests

* Add ADR for BDD testing

* Update Readme

* Adjust the ADR; remove dead code

* Add a prod env check
  • Loading branch information
kryswisnaskas authored Oct 27, 2020
1 parent 729df05 commit 2448e9d
Show file tree
Hide file tree
Showing 15 changed files with 1,573 additions and 1,115 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ jobs:
command: |
mkdir reports
sleep 5
yarn cucumber
yarn cucumber:ci
- store_artifacts:
path: reports/
test_frontend:
Expand Down
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ TTA_SMART_HUB_URI=http://localhost:3000
AUTH_BASE=https://uat.hsesinfo.org
# This env variable should go away soon in favor of TTA_SMART_HUB_URI
REDIRECT_URI_HOST=http://localhost:8080
CUCUMBER_USER_ID=999999;
CUCUMBER_USER=this_is_secret
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ You may run into some issues running the docker commands on Windows:
| | Run the linter for the the backend with results output to xml files | `yarn lint:ci`| |
| | Run `yarn lint:ci` for both the frontend and backend | `yarn lint:all`| |
| | Host the open api 3 spec using [redoc](https://github.com/Redocly/redoc) at `localhost:5000` | `yarn docs:serve` | |
| | Run cucumber tests | `yarn cucumber` | |

## Infrastructure

Expand Down
28 changes: 28 additions & 0 deletions cucumber/features/activityReportStepper.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Feature: Activity Report Stepper

Scenario: Activity Summary
Given I am logged in
And I am on the activity reports page
Then I see the Stepper
And the first step is the Activity Summary
Scenario: Navigation buttons
Given I am logged in
And I am on the activity reports page
Then I see two navigation buttons
And the "Previous" button is disabled
When I click the "Next" button
Then the "Previous" button is no longer disabled
Scenario: Progress
Given I am logged in
And I am on the activity reports page
When I click the "Next" button
Then I moved past the "Activity Summary" step
And I am on the "Participants" step
When I click the "Next" button again
Then the "Participants" step is still current, but I am on page 2
And I have not advanced to the "Goals & Objectives" step yet
When I click the "Previous" button
Then the "Participants" step is still current, but I am on page 1
When I click the "Previous" button again
Then I am no longer on the "Participants" step
And I am on the "Activity Summary" step
9 changes: 0 additions & 9 deletions cucumber/features/devLoginPup.feature

This file was deleted.

11 changes: 11 additions & 0 deletions cucumber/features/homePage.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Feature: TTA Smarthub Home Page

Scenario: Welcome page is displayed
Given I am logged in
And I am on the Smart Hub home page
Then I see "Welcome to the TTA Smart Hub" message
And I see "Activity Reports" link
# Scenario: Login is redirected to HSES
# Given the home page of tta-smarthub
# When pressing login
# Then we should see "Head Start Enterprise System" page
152 changes: 152 additions & 0 deletions cucumber/features/steps/activityReportStepperSteps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
const {
Given, When, Then,
} = require('@cucumber/cucumber');
const assertTrue = require('assert');
const assert = require('assert');
const select = require('puppeteer-select');
const scope = require('../support/scope');

Given('I am on the activity reports page', async () => {
const page = scope.context.currentPage;
const selector = 'a[href$="activity-reports"]';
await Promise.all([
page.waitForNavigation(),
page.click(selector),
]);
await page.screenshot({ path: 'reports/givenOnTheActivityReports.png' });
});

Then('I see the Stepper', async () => {
const page = scope.context.currentPage;
const selector = '[aria-label="progress"]';

const value = await page.$eval(selector, (el) => el.textContent);

assertTrue(value);
assertTrue(value.startsWith('Activity Summary'));
});

Then('the first step is the Activity Summary', async () => {
const page = scope.context.currentPage;
const selector = '[aria-label="progress"] ol li span[aria-current="true"]';

const value = await page.$eval(selector, (el) => el.textContent);

assert.equal(value, 'Activity Summary');
});

Then('I see two navigation buttons', async () => {
const page = scope.context.currentPage;

const buttonOne = await select(page).getElement('button:contains("Previous")');
const buttonTwo = await select(page).getElement('button:contains("Next")');

assertTrue(buttonOne);
assertTrue(buttonTwo);

let value = await page.evaluate((el) => el.textContent, buttonOne);
assert.equal(value, 'Previous');

value = await page.evaluate((el) => el.textContent, buttonTwo);
assert.equal(value, 'Next');
});

Then('the {string} button is disabled', async (string) => {
const page = scope.context.currentPage;
const buttonOneSelector = 'button[disabled]';

const buttonPrevious = await page.$(buttonOneSelector);

assertTrue(buttonPrevious);

const value = await page.evaluate((el) => el.textContent, buttonPrevious);
assert.equal(value, string);
});

When('I click the {string} button', async (string) => {
const page = scope.context.currentPage;
const buttonTwo = await select(page).getElement(`button:contains(${string})`);
await buttonTwo.click();
});

Then('the {string} button is no longer disabled', async (string) => {
const page = scope.context.currentPage;
const buttonOneSelector = 'button[disabled]';

const buttonPrevious = await page.$(buttonOneSelector);

const value = await page.evaluate((el) => el, buttonPrevious);
assert.equal(value, null);
});

Then('I moved past the {string} step', async (string) => {
const page = scope.context.currentPage;
assert.equal(string, 'Activity Summary');
const selector = `[data-testid="${string}"] > [aria-current="false"]`;

const value = await page.$eval(selector, (el) => el.textContent);

assertTrue(value);
assertTrue(value.startsWith(string));
});

Then('I am on the {string} step', async (string) => {
const page = scope.context.currentPage;
const selector = `[data-testid="${string}"] > [aria-current="true"]`;

const value = await page.$eval(selector, (el) => el.textContent);

assertTrue(value);
assert.equal(value, string);
});

When('I click the {string} button again', async (string) => {
const page = scope.context.currentPage;
const buttonTwo = await select(page).getElement(`button:contains(${string})`);
await buttonTwo.click();
});

Then('the {string} step is still current, but I am on page 2', async (string) => {
const page = scope.context.currentPage;
assert.equal(string, 'Participants');
const selector = '[data-testid="form"] > h1';

const value = await page.$eval(selector, (el) => el.textContent);

assertTrue(value);
assert.equal(value, `${string} - Page 2`);
});

Then('I have not advanced to the {string} step yet', async (string) => {
const page = scope.context.currentPage;
assert.equal(string, 'Goals & Objectives');
const selector = `[data-testid="${string}"] > [aria-current="false"]`;

const value = await page.$eval(selector, (el) => el.textContent);

assertTrue(value);
assertTrue(value.startsWith(string));
await page.screenshot({ path: 'reports/notOnGoalsYet.png' });
});

Then('the {string} step is still current, but I am on page 1', async (string) => {
const page = scope.context.currentPage;
assert.equal(string, 'Participants');
const selector = '[data-testid="form"] > h1';

const value = await page.$eval(selector, (el) => el.textContent);

assertTrue(value);
assert.equal(value, `${string} - Page 1`);
});

Then('I am no longer on the {string} step', async (string) => {
const page = scope.context.currentPage;
assert.equal(string, 'Participants');
const selector = `[data-testid="${string}"] > [aria-current="false"]`;

const value = await page.$eval(selector, (el) => el.textContent);

assertTrue(value);
assertTrue(value.startsWith(string));
});
35 changes: 0 additions & 35 deletions cucumber/features/steps/devLoginPupSteps.js

This file was deleted.

76 changes: 76 additions & 0 deletions cucumber/features/steps/homePageSteps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require('dotenv').config();
const {
Given, Then,
} = require('@cucumber/cucumber');
const assertTrue = require('assert');
const assert = require('assert');
const scope = require('../support/scope');

Given('I am logged in', async () => {
if (!scope.browser) {
const width = 1024;
const height = 1600;

scope.browser = await scope.driver.launch({
defaultViewport: { width, height },
headless: true,
// slowMo: 250, // can be used in conjunction with headless: false to slow down the browser
});
}
scope.context.currentPage = await scope.browser.newPage();
const page = scope.context.currentPage;

const domain = process.env.TTA_SMART_HUB_URI.split('//')[1];

const cookies = [{
name: 'CUCUMBER_USER',
value: `${process.env.CUCUMBER_USER}`,
domain,
path: '/',
httpOnly: true,
secure: false,
session: true,
sameSite: 'Strict',
}];

await page.setCookie(...cookies);

const loginLinkSelector = 'a[href$="api/login"]';
// const homeLinkSelector = 'a[href$="/"]';
const activityReportsSelector = 'a[href$="activity-reports"]';

await page.goto(scope.uri);
await page.waitForSelector('em'); // Page title
const name = await page.$eval('em', (el) => el.innerText);

assert.equal(name, 'TTA Smart Hub');
// Check if actually logged in. If not login
const result = await page.$(loginLinkSelector);

if (result) {
await page.click(loginLinkSelector);
await page.waitForSelector(activityReportsSelector); // Activity Reports link
await page.screenshot({ path: 'reports/givenLoggedIn.png' });
}
});

Given('I am on the Smart Hub home page', async () => {
await scope.context.currentPage.waitForSelector('h1');
});

Then('I see {string} message', async (string) => {
const page = scope.context.currentPage;
const value = await page.$eval('h1', (el) => el.textContent);

assertTrue(value.includes(string));
});

Then('I see {string} link', async (string) => {
const page = scope.context.currentPage;
const selector = 'a[href$="activity-reports"]';

await page.waitForSelector(selector);
const value = await page.$eval(selector, (el) => el.textContent);

assert.equal(value, string);
});
19 changes: 19 additions & 0 deletions docs/adr/0010-bdd-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 10. BDD testing

Date: 2020-10-25

## Status

Pending

## Context

Behavior-driven development (BDD) allows for a broader team to collaborate on software development. The business stakeholders have insight into how well the software meets the requirements. Through automated tests they have a way to validate the functionality in a user friendly way. Cucumber or in our case cucumber-js is a top tool to provide a friendly language for all team members. To implement cucumber-js, additional tools are needed to provide a way to run automated browser tests. Here, Puppeteer as well as selenium webdriver both of which are popular tools to enable browser automation were evalueated.

## Decision

To go along with cucumber-js Puppeteer was selected. Puppeteer offers more control over Chromium based browsers, very fast, headless by default, run mode, and it can take screenshots of the pages, both in an image and PDF formats. Additionally, it measures rendering and load times by Chrome Performance Analysis tool and it is easier to set up than selenium webdriver. The drawback to using Puppeteer is it's lack of full cross-browser support, which is offered by the selenium webdriver, since Puppeteer is specialized for Chromium.

## Consequences

Investing time into enabling BDD with cucumber-js will have a positive impact on collaboration with the business stakeholders and provide a way to validate application's functionality. This was viewed as a worthwile investment considering that schedules might be affected. By using Puppeteer with cucumber-js we will have a solution that is fast and easier to set up with a limitation of not providing a full cross-browser support (Microsoft Edge, which is a default on Windows 10, is based on Chromium). Since the browsers to support include Chrome and IE this poses somewhat of a limitation, however the bulk of the benefits from using BDD comes from validating the application's behavior against the requirements.
Loading

0 comments on commit 2448e9d

Please sign in to comment.