Skip to content

Commit

Permalink
Merge pull request #281 from alliance-genome/KANBAN-601_ui-unit-testi…
Browse files Browse the repository at this point in the history
…ng-setup

UI unit and E2E testing setup (KANBAN-601)
  • Loading branch information
mluypaert authored Sep 4, 2024
2 parents 19407ce + fe2992f commit 27433d0
Show file tree
Hide file tree
Showing 38 changed files with 11,183 additions and 2,804 deletions.
63 changes: 63 additions & 0 deletions .github/workflows/PR-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,8 @@ jobs:
- name: Code style test
run: |
make run-style-checks
- name: Unit tests
run: make run-unit-tests
webui-container-image-build:
name: webUI container-image build
needs:
Expand Down Expand Up @@ -945,6 +947,67 @@ jobs:
push: false
tags: agr_pavi/webui:latest
outputs: type=docker,dest=/tmp/pavi_webui_docker_image.tar
- name: Upload image as artifact (share between jobs)
uses: actions/upload-artifact@v4
with:
name: webui_image
path: /tmp/pavi_webui_docker_image.tar
e2e-testing:
name: end-to-end testing
needs:
- webui-update-dependency-lock-files
- webui-container-image-build
- api-container-image-build
runs-on: ubuntu-22.04
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Download API image artifact
uses: actions/download-artifact@v4
with:
name: api_image
path: /tmp
- name: Load API Docker image
run: |
docker load --input /tmp/pavi_api_docker_image.tar
- name: Run local API container to run E2E tests on
working-directory: api/
run: |
make run-container-dev
- name: Download webUI image artifact
uses: actions/download-artifact@v4
with:
name: webui_image
path: /tmp
- name: Load webUI Docker image
run: |
docker load --input /tmp/pavi_webui_docker_image.tar
- name: Run local webUI container to run E2E tests on
working-directory: webui/
run: |
PAVI_API_PORT=8080 make run-container-dev
- name: Download updated webui dependencies lock file
uses: actions/download-artifact@v4
with:
name: webui_deps_lock
path: webui
- name: setup webui-compatible node.js version
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Run cypress E2E tests
uses: cypress-io/github-action@v6
with:
working-directory: webui/
wait-on: 'http://localhost:3000, http://localhost:8080/api/health'
- name: Cleanup webUI server (running container)
working-directory: webui/
run: |
make stop-container-dev
- name: Cleanup API server (running container)
working-directory: api/
run: |
make stop-container-dev
stage-deps-lock-updates:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-deps-lock-updates') }}
runs-on: ubuntu-22.04
Expand Down
6 changes: 5 additions & 1 deletion api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ container-image:

run-container-dev:
export API_PIPELINE_IMAGE_TAG=main && \
docker compose -f docker-compose-dev.yml --env-file dev.env up agr.pavi.dev-local.api
docker compose -f docker-compose-dev.yml --env-file dev.env up -d agr.pavi.dev-local.api

stop-container-dev:
export API_PIPELINE_IMAGE_TAG=main && \
docker compose -f docker-compose-dev.yml --env-file dev.env down agr.pavi.dev-local.api

nextflow.sh:
make -f ../pipeline/workflow/Makefile nextflow.sh
Expand Down
1 change: 0 additions & 1 deletion shared_aws_infra/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
SUPPORTED_NODE := ^v18\.
EXTRA_PIP_COMPILE_ARGS ?=

.PHONY: check-% clean install install-% pip-tools run-% update-% _vars-% _write-lock-file
Expand Down
11 changes: 11 additions & 0 deletions webui/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,16 @@
"extends": [
"next/core-web-vitals",
"eslint:recommended"
],
"overrides": [
{
"files": ["**/__tests__/**/*.[jt]s?(x)"],
"plugins": [ "jest" ],
"env": { "jest/globals": true }
}, {
"files": ["cypress/**/*.cy.[jt]s?(x)"],
"plugins": [ "cypress" ],
"env": { "cypress/globals": true }
}
]
}
1 change: 1 addition & 0 deletions webui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

# testing
/coverage
/cypress/screenshots/

# next.js
/.next/
Expand Down
19 changes: 18 additions & 1 deletion webui/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ container-image:
install-deps:
npm install

install-cypress-deps:
sudo apt-get install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb

update-deps-lock:
npm update --package-lock-only

Expand All @@ -32,7 +35,7 @@ endif

run-container-dev:
@export PAVI_API_BASE_URL=${PAVI_API_BASE_URL} && \
docker compose -f docker-compose-dev.yml --env-file dev.env up agr.pavi.dev-local.webui
docker compose -f docker-compose-dev.yml --env-file dev.env up -d agr.pavi.dev-local.webui

run-server-dev: install-deps
@export PAVI_API_BASE_URL=${PAVI_API_BASE_URL} && \
Expand All @@ -43,3 +46,17 @@ run-style-checks: install-deps

run-type-checks: install-deps
npm run typecheck

run-unit-tests: install-deps
npm run test

run-e2e-tests: install-deps
$(MAKE) --no-print-directory run-container-dev
npx cypress run --e2e || true
$(MAKE) --no-print-directory stop-container-dev

run-e2e-tests-dev: install-deps
npx cypress open --e2e

stop-container-dev:
@docker compose -f docker-compose-dev.yml --env-file dev.env down agr.pavi.dev-local.webui
8 changes: 8 additions & 0 deletions webui/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from "cypress";

export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: false
},
});
82 changes: 82 additions & 0 deletions webui/cypress/e2e/submit-workflow.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/// <reference types="cypress" />

// To learn more about how Cypress works,
// please read our getting started guide:
// https://on.cypress.io/introduction-to-cypress

import formInput from '../fixtures/test-submit-success-input.json'

describe('submit form behaviour', () => {
beforeEach(() => {
// Cypress starts out with a blank slate for each test
// so we must tell it to visit our website with the `cy.visit()` command
// before each test
cy.visit('/')
})

it('tests job submission success', () => {
// We use the `cy.get()` command to get all elements that match the selector.
// There should only be one cell with a inputgroup.
// cy.get('table tbody tr td .p-inputgroup').should('have.length', 1)

// Should displays one alignmentEntry by default.
cy.get('.p-inputgroup').should('have.length', 1)

// There should be excactly one submit button
cy.get('button').filter('[aria-label="Submit"]').as('submitBtn')
cy.get('@submitBtn').should('have.length', 1)

// and it should be disabled by default (on incomplete input).
cy.get('@submitBtn').should('be.disabled')

// There should be excactly one element to click to add records
cy.get('button#add-record').as('addRecordBtn')
cy.get('@addRecordBtn').should('have.length', 1)

// add as many records as there are entries in formInput
for(let i = 1, len = formInput.length; i < len; ++i){
cy.get('@addRecordBtn').click()
}
cy.get('.p-inputgroup').should('have.length', formInput.length)

// Input all data into form
for(let i = 0, len = formInput.length; i < len; ++i){

// Form should be able to receive gene as user input.
cy.get('.p-inputgroup').eq(i).find('input#gene').focus().type(formInput[i].gene)

// Once the transcript list loaded, from should enable selecting the relevant transcripts.
cy.get('.p-inputgroup').eq(i).find('#transcripts').find('input').focus()
cy.get('.p-multiselect-panel').as('openTranscriptsSelectBox').should('be.visible')

// A list of transcript should be available
cy.get('@openTranscriptsSelectBox').find('li.p-multiselect-item').as('openTranscriptsList')
cy.get('@openTranscriptsList').should('have.length.at.least', 1)

// And the relevant transcripts should be selectable
formInput[i].transcripts.forEach((transcript) => {
cy.get('@openTranscriptsList').contains(transcript).click()
})
cy.get('@openTranscriptsSelectBox').find('button.p-multiselect-close').click()

cy.focused().blur()

// Submit button should stay disabled as long a last entry was not submitted
if ( i < len - 1 ) {
cy.get('@submitBtn').should('be.disabled')

if (i === 0) {
cy.wait(5000)
cy.get('@submitBtn').should('be.disabled')
}
}
}

// Submit button should become active after completing all input
cy.get('@submitBtn').should('be.enabled')

// Submitting the analysis should report a UUID
cy.get('@submitBtn').click()
cy.contains('div#display-message', /^job .+ is now pending\.$/)
})
})
143 changes: 143 additions & 0 deletions webui/cypress/example-e2e/1-getting-started/todo.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/// <reference types="cypress" />

// Welcome to Cypress!
//
// This spec file contains a variety of sample tests
// for a todo list app that are designed to demonstrate
// the power of writing tests in Cypress.
//
// To learn more about how Cypress works and
// what makes it such an awesome testing tool,
// please read our getting started guide:
// https://on.cypress.io/introduction-to-cypress

describe('example to-do app', () => {
beforeEach(() => {
// Cypress starts out with a blank slate for each test
// so we must tell it to visit our website with the `cy.visit()` command.
// Since we want to visit the same URL at the start of all our tests,
// we include it in our beforeEach function so that it runs before each test
cy.visit('https://example.cypress.io/todo')
})

it('displays two todo items by default', () => {
// We use the `cy.get()` command to get all elements that match the selector.
// Then, we use `should` to assert that there are two matched items,
// which are the two default items.
cy.get('.todo-list li').should('have.length', 2)

// We can go even further and check that the default todos each contain
// the correct text. We use the `first` and `last` functions
// to get just the first and last matched elements individually,
// and then perform an assertion with `should`.
cy.get('.todo-list li').first().should('have.text', 'Pay electric bill')
cy.get('.todo-list li').last().should('have.text', 'Walk the dog')
})

it('can add new todo items', () => {
// We'll store our item text in a variable so we can reuse it
const newItem = 'Feed the cat'

// Let's get the input element and use the `type` command to
// input our new list item. After typing the content of our item,
// we need to type the enter key as well in order to submit the input.
// This input has a data-test attribute so we'll use that to select the
// element in accordance with best practices:
// https://on.cypress.io/selecting-elements
cy.get('[data-test=new-todo]').type(`${newItem}{enter}`)

// Now that we've typed our new item, let's check that it actually was added to the list.
// Since it's the newest item, it should exist as the last element in the list.
// In addition, with the two default items, we should have a total of 3 elements in the list.
// Since assertions yield the element that was asserted on,
// we can chain both of these assertions together into a single statement.
cy.get('.todo-list li')
.should('have.length', 3)
.last()
.should('have.text', newItem)
})

it('can check off an item as completed', () => {
// In addition to using the `get` command to get an element by selector,
// we can also use the `contains` command to get an element by its contents.
// However, this will yield the <label>, which is lowest-level element that contains the text.
// In order to check the item, we'll find the <input> element for this <label>
// by traversing up the dom to the parent element. From there, we can `find`
// the child checkbox <input> element and use the `check` command to check it.
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()

// Now that we've checked the button, we can go ahead and make sure
// that the list element is now marked as completed.
// Again we'll use `contains` to find the <label> element and then use the `parents` command
// to traverse multiple levels up the dom until we find the corresponding <li> element.
// Once we get that element, we can assert that it has the completed class.
cy.contains('Pay electric bill')
.parents('li')
.should('have.class', 'completed')
})

context('with a checked task', () => {
beforeEach(() => {
// We'll take the command we used above to check off an element
// Since we want to perform multiple tests that start with checking
// one element, we put it in the beforeEach hook
// so that it runs at the start of every test.
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
})

it('can filter for uncompleted tasks', () => {
// We'll click on the "active" button in order to
// display only incomplete items
cy.contains('Active').click()

// After filtering, we can assert that there is only the one
// incomplete item in the list.
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Walk the dog')

// For good measure, let's also assert that the task we checked off
// does not exist on the page.
cy.contains('Pay electric bill').should('not.exist')
})

it('can filter for completed tasks', () => {
// We can perform similar steps as the test above to ensure
// that only completed tasks are shown
cy.contains('Completed').click()

cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Pay electric bill')

cy.contains('Walk the dog').should('not.exist')
})

it('can delete all completed tasks', () => {
// First, let's click the "Clear completed" button
// `contains` is actually serving two purposes here.
// First, it's ensuring that the button exists within the dom.
// This button only appears when at least one task is checked
// so this command is implicitly verifying that it does exist.
// Second, it selects the button so we can click it.
cy.contains('Clear completed').click()

// Then we can make sure that there is only one element
// in the list and our element does not exist
cy.get('.todo-list li')
.should('have.length', 1)
.should('not.have.text', 'Pay electric bill')

// Finally, make sure that the clear button no longer exists.
cy.contains('Clear completed').should('not.exist')
})
})
})
Loading

0 comments on commit 27433d0

Please sign in to comment.