Skip to content

Commit

Permalink
Merge pull request #5922 from rschlaefli/5846-UI-Testing-v2
Browse files Browse the repository at this point in the history
5846 Initial Framework for UI Testing
  • Loading branch information
kcondon authored Jun 6, 2019
2 parents 889c0bf + 4a1c9d8 commit 3c697b1
Show file tree
Hide file tree
Showing 14 changed files with 400 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ scripts/installer/default.config
.idea
**/*.iml
/bin/

# do not track Visual Studio Code files
.vscode

# ignore UI testing related files
tests/node_modules
tests/package-lock.json
42 changes: 42 additions & 0 deletions .travis.yml.future
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
services:
- docker

jobs:
include:
# Execute java unit- and integration tests
- stage: test
language: java
jdk:
- oraclejdk8
script: mvn -DcompilerArgument=-Xlint:unchecked test -P all-unit-tests
after_success: mvn jacoco:report coveralls:report

# Execute Cypress for UI testing
# see https://docs.cypress.io/guides/guides/continuous-integration.html
- stage: test
language: node_js
node_js:
- "10"
addons:
apt:
packages:
# Ubuntu 16+ does not install this dependency by default, so we need to install it ourselves
- libgconf-2-4
cache:
# Caches $HOME/.npm when npm ci is default script command
# Caches node_modules in all other cases
npm: true
directories:
# we also need to cache folder with Cypress binary
- ~/.cache
# we want to cache the Glassfish and Solr dependencies as well
- conf/docker-aio/dv/deps
before_install:
- cd tests
install:
- npm ci
before_script:
- ./run_docker_dataverse.sh
script:
# --key needs to be injected using CYPRESS_RECORD_KEY to keep it secret
- $(npm bin)/cypress run --record
4 changes: 4 additions & 0 deletions tests/cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"baseUrl": "http://localhost:8084",
"projectId": "ufkudc"
}
5 changes: 5 additions & 0 deletions tests/cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
75 changes: 75 additions & 0 deletions tests/cypress/integration/test_dv.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { login } from '../util'

describe('Dataverse', function() {
beforeEach(function() {
login(cy)
})

it('can be created with minimal customization', function() {
cy.visit('/dataverse.xhtml?ownerId=1')

// setup aliases for used fields
cy.get('input[id="dataverseForm:name"]').as('name')
cy.get('input[id="dataverseForm:identifier"]').as('identifier')
cy.get('select[id="dataverseForm:dataverseCategory"]').as(
'categorySelection'
)
cy.get('input[id="dataverseForm:affiliation"]').as('affiliation')
cy.get('textarea[id="dataverseForm:description"]').as('description')
cy.get('input[id="dataverseForm:metadataRoot"]').as('metadataRoot')
cy.get('input[id="dataverseForm:facetsRoot"]').as('facetsRoot')

// assert that there are no alerts in the initial state
cy.get('.alert:not(.alert-success)').should('not.exist')
cy.get('.ui-message-error-detail').should('not.exist')

// assert that the initial values have been set correctly
cy.get('@name').should('have.value', 'Dataverse Admin Dataverse')
cy.get('@identifier').should('be.empty')
cy.get('@categorySelection').should('have.value', '')
cy.get('@affiliation').should('have.value', 'Dataverse.org')
cy.get('@description').should('be.empty')
cy.get('@metadataRoot').should('be.checked')
cy.get('@facetsRoot').should('be.checked')

// try to create a dataverse
cy.contains('Create Dataverse').click()

// expect validation to fail for some key fields
cy.get('.alert').should('exist')
cy.get('.ui-message-error-detail').should('have.length', 2)

// fill in the remaining forms
cy.get('@identifier').type('testDv')
cy.get('@categorySelection').select('DEPARTMENT')

// create a dataverse
cy.contains('Create Dataverse').click()

// ensure that we have been redirected to the correct dataverse page
cy.url().should('contain', '/dataverse/testDv')
cy.get('.dataverseHeaderCell').should('contain', 'Unpublished')
})

it('allows datasets to be created', function() {
cy.visit('/dataverse/testDv')

// click through to the new dataset page
// TODO: this should all be tested in a different spec
cy.contains('Add Data').click()
cy.contains('New Dataset').click()

// fill in the necessary fields for the simplest new dataset
cy.get('.panel-body').within(() => {
cy.get('> :nth-child(1) input[type="text"]').type('Some dataset')
cy.get('> :nth-child(4) textarea').type('It is about some data')
cy.get('> :nth-child(5) tr:nth-child(5) div.ui-chkbox-box').click()
})

// save the new dataset
cy.contains('Save Dataset').click()

// ensure that we have been redirected to the correct dataverse page
cy.url().should('contain', '/dataset.xhtml')
})
})
76 changes: 76 additions & 0 deletions tests/cypress/integration/test_dv_user.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { login } from '../util'

describe('Dataverse User', function() {
beforeEach(function() {
login(cy)
})

it('allows the user to read and remove notifications', function() {
cy.visit('/dataverseuser.xhtml?selectTab=notifications')

// expect there to be one notification
cy.get('.notification-item').should('have.length.of', 1)

// remove the notification
cy.get('.notification-item')
.eq(0)
.find('.notification-item-cell')
.eq(1)
.click()

// expect there to be no more notifications
cy.get('.notification-item').should('have.length.of', 0)
})

it('allows looking up account information', function() {
cy.visit('/dataverseuser.xhtml?selectTab=accountInfo')

cy.get('.form-control-static')
.eq(0)
.should('contain', 'dataverseAdmin')

cy.get('.form-control-static')
.eq(1)
.should('contain', 'Dataverse')

cy.get('.form-control-static')
.eq(2)
.should('contain', 'Admin')

cy.get('.form-control-static')
.eq(3)
.should('contain', 'dataverse@mailinator.com')

cy.get('.form-control-static')
.eq(4)
.should('contain', 'Not Verified')

cy.get('.form-control-static')
.eq(5)
.should('contain', 'Dataverse.org')

cy.get('.form-control-static')
.eq(6)
.should('contain', 'Admin')
})

it('enables recreation of the API token', function() {
const tokenTabId =
'div[id="dataverseUserForm:dataRelatedToMeView:apiTokenTab"]'
cy.visit('/dataverseuser.xhtml?selectTab=apiTokenTab')

cy.get(`${tokenTabId} pre code`)
.should('not.be.empty')
.then(oldToken => {
// click the token recreation button
cy.get(`${tokenTabId} button[type=submit]`).click()

cy.get(`${tokenTabId} pre code`)
.should('not.be.empty')
.then(newToken => {
// assert that the new token is different from the old one
expect(newToken[0].textContent).to.not.eq(oldToken[0].textContent)
})
})
})
})
61 changes: 61 additions & 0 deletions tests/cypress/integration/test_login.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
describe('Log In', function() {
beforeEach(function() {
// visit the log in page
cy.visit('/loginpage.xhtml')

// should create a session cookie
cy.getCookie('JSESSIONID').should('exist')

// fill out the login form inputs
cy.get('input[name="loginForm:credentialsContainer:0:credValue"]').type(
'dataverseAdmin'
)
cy.get('input[name="loginForm:credentialsContainer:1:sCredValue"]').type(
'admin1'
)
})

it('enables logging in with the enter key', function() {
// press enter
cy.get('input[name="loginForm:credentialsContainer:1:sCredValue"]').type(
'{enter}'
)

// should redirect to the dataverse root page
cy.url().should('include', '/dataverse/root')

// should show the user name in the navbar
cy.get('span[id=userDisplayInfoTitle]').should('contain', 'Dataverse Admin')
})

it('enables logging in by clicking the submit button', function() {
// click the login button
cy.get('button[name="loginForm:login').click()

// should redirect to the dataverse root page
cy.url().should('include', '/dataverse/root')

// should show the user name in the navbar
cy.get('span[id=userDisplayInfoTitle]').should('contain', 'Dataverse Admin')
})

it('prevents an invalid login', function() {
// add some additional characters to the valid credentials
cy.get('input[name="loginForm:credentialsContainer:0:credValue"]').type(
'Invalid'
)
cy.get('input[name="loginForm:credentialsContainer:1:sCredValue"]').type(
'Invalid'
)
// click the login button
cy.get('button[name="loginForm:login').click()

cy.url().should('contain', '/loginpage.xhtml')
cy.get('.alert').should('contain', 'Error')
})

it('allows jumping to the password reset page', function() {
cy.get('a[href="passwordreset.xhtml"]').click()
cy.url().should('contain', '/passwordreset.xhtml')
})
})
21 changes: 21 additions & 0 deletions tests/cypress/integration/test_password_reset.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
describe('Password Reset', function() {
it('allows a valid user to request its password', function() {
cy.visit('/passwordreset.xhtml')

cy.get('input[name="passwordReset:email"]').type(
'dataverse@mailinator.com{enter}'
)

cy.get('.alert').should('contain', 'Password Reset Initiated')
})

it('does not treat invalid users differently', function() {
cy.visit('/passwordreset.xhtml')

cy.get('input[name="passwordReset:email"]').type(
'invalid_user@gmail.com{enter}'
)

cy.get('.alert').should('contain', 'Password Reset Initiated')
})
})
18 changes: 18 additions & 0 deletions tests/cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)

module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}
25 changes: 25 additions & 0 deletions tests/cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
20 changes: 20 additions & 0 deletions tests/cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')
31 changes: 31 additions & 0 deletions tests/cypress/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Function that performs a programmatic login
* @param {Cypress} cy
*/
export function login(cy, cb) {
// need to visit the login page to get the CSRF token
cy.visit('/loginpage.xhtml')

// a session needs to have been setup
cy.getCookie('JSESSIONID').should('exist')

// extract the CSRF token from the view state
cy.get('#loginForm input[name="javax.faces.ViewState"]').then(viewState => {
cy.request({
method: 'POST',
url: 'http://localhost:8084/loginpage.xhtml',
form: true,
body: {
'javax.faces.partial.ajax': true,
'javax.faces.source': 'loginForm:login',
'javax.faces.partial.execute': '@all',
'javax.faces.partial.render': '@all',
'loginForm:login': 'loginForm:login',
loginForm: 'loginForm',
'loginForm:credentialsContainer:0:credValue': 'dataverseAdmin',
'loginForm:credentialsContainer:1:sCredValue': 'admin1',
'javax.faces.ViewState': viewState[0].value,
},
}).then(cb ? cb : () => null)
})
}
Loading

0 comments on commit 3c697b1

Please sign in to comment.