Merge pull request #190 from Human-Connection/use_factories_in_cucumber_features

[WIP] Use factories in cucumber features
This commit is contained in:
Robert Schäfer 2019-02-26 00:47:11 +01:00 committed by GitHub
commit 8d43a32220
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 239 additions and 145 deletions

1
.gitignore vendored
View File

@ -79,3 +79,4 @@ static/uploads
cypress/videos
cypress/screenshots/
cypress.env.json

View File

@ -18,6 +18,7 @@ before_install:
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
- cp cypress.env.template.json cypress.env.json
install:
- docker build --build-arg BUILD_COMMIT=$TRAVIS_COMMIT --target production -t humanconnection/nitro-web .
@ -31,9 +32,8 @@ install:
script:
- docker-compose exec -e NODE_ENV=test webapp yarn run lint
- docker-compose exec -e NODE_ENV=test webapp yarn run test
- docker-compose -f ../Nitro-Backend/docker-compose.yml exec backend yarn run db:seed
- wait-on http://localhost:3000
- cypress run --record --key $CYPRESS_TOKEN
- wait-on http://localhost:7474 && docker-compose -f ../Nitro-Backend/docker-compose.yml exec neo4j migrate
- wait-on http://localhost:3000 && cypress run --record --key $CYPRESS_TOKEN
after_success:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh

View File

@ -25,8 +25,9 @@ $ yarn install
Copy:
```
cp .env.template .env
cp cypress.env.template.json cypress.env.json
```
Configure the file `.env` according to your needs and your local setup.
Configure the files according to your needs and your local setup.
### Development
``` bash

View File

@ -29,7 +29,7 @@
ghost
@click.prevent="$router.back()"
>
{{ $t('actions.cancel') }}
{{ $t('actions.cancel') }}
</ds-button>
<ds-button
icon="check"
@ -38,7 +38,7 @@
:disabled="disabled || errors"
primary
>
{{ $t('actions.save') }}
{{ $t('actions.save') }}
</ds-button>
</div>
</ds-card>

View File

@ -0,0 +1,6 @@
{
"SEED_SERVER_HOST": "http://localhost:4001",
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USERNAME": "neo4j",
"NEO4J_PASSWORD": "letmein"
}

View File

@ -4,11 +4,7 @@ Feature: Authentication
In order to attribute posts and other contributions to their authors
Background:
Given my account has the following details:
| name | email | password | type
| Peter Lustig | admin@example.org | 1234 | Admin
| Bob der Bausmeister | moderator@example.org | 1234 | Moderator
| Jenny Rostock" | user@example.org | 1234 | User
Given I have a user account
Scenario: Log in
When I visit the "/login" page

View File

@ -14,28 +14,27 @@ Feature: Tags and Categories
looking at the popularity of a tag.
Background:
Given we have a selection of tags and categories as well as posts
And my user account has the role "administrator"
Given I am logged in
Given my user account has the role "admin"
And we have a selection of tags and categories as well as posts
And I am logged in
Scenario: See an overview of categories
When I navigate to the administration dashboard
And I click on "Categories"
Then I can see a list of categories ordered by post count:
| Icon | Name | Post Count |
| | Just For Fun | 5 |
| | Happyness & Values | 2 |
| | Health & Wellbeing | 1 |
| Icon | Name | Posts |
| | Just For Fun | 2 |
| | Happyness & Values | 1 |
| | Health & Wellbeing | 0 |
Scenario: See an overview of tags
When I navigate to the administration dashboard
And I click on "Tags"
Then I can see a list of tags ordered by user and post count:
| # | Name | Nutzer | Beiträge |
| 1 | Naturschutz | 2 | 2 |
| 2 | Freiheit | 2 | 2 |
| 3 | Umwelt | 1 | 1 |
| 4 | Demokratie | 1 | 1 |
Then I can see a list of tags ordered by user count:
| # | Name | Users | Posts |
| 1 | Democracy | 2 | 3 |
| 2 | Ecology | 1 | 1 |
| 3 | Nature | 1 | 2 |

View File

@ -7,21 +7,18 @@ Feature: About me and location
to search for users by location.
Background:
Given I am logged in
Given I have a user account
And I am logged in
And I am on the "settings" page
Scenario: Change username
When I save "Hansi" as my new name
Then I can see my new name "Hansi" when I click on my profile picture in the top right
Scenario: Keep changes after refresh
When I changed my username to "Hansi" previously
And I refresh the page
Then my new username is still there
And when I refresh the page
Then the name "Hansi" is still there
Scenario Outline: I set my location to "<location>"
When I save "<location>" as my location
And my username is "Peter Lustig"
When people visit my profile page
Then they can see the location in the info box below my avatar
@ -36,6 +33,5 @@ Feature: About me and location
"""
Ich lebe fettlos, fleischlos, fischlos dahin, fühle mich aber ganz wohl dabei
"""
And my username is "Peter Lustig"
When people visit my profile page
Then they can see the text in the info box below my avatar

View File

@ -9,13 +9,13 @@ Feature: Report and Moderate
Background:
Given we have the following posts in our database:
| Author | Title | Content | Slug |
| David Irving | The Truth about the Holocaust | It never existed! | the-truth-about-the-holocaust |
| Author | id | title | content |
| David Irving | p1 | The Truth about the Holocaust | It never existed! |
Scenario Outline: Report a post from various pages
Given I am logged in with a "user" role
And I see David Irving's post on the <Page>
When I click on "Report Contribution" from the triple dot menu of the post
When I see David Irving's post on the <Page>
And I click on "Report Contribution" from the triple dot menu of the post
And I confirm the reporting dialog because it is a criminal act under German law:
"""
Do you really want to report the contribution "The Truth about the Holocaust"?
@ -45,8 +45,8 @@ Feature: Report and Moderate
Scenario: Review reported content
Given somebody reported the following posts:
| Slug |
| the-truth-about-the-holocaust |
| id |
| p1 |
And I am logged in with a "moderator" role
When I click on the avatar menu in the top right corner
And I click on "Moderation"

View File

@ -4,8 +4,9 @@ Feature: Create a post
To say something to everyone in the community
Background:
Given I am logged in
Given I am on the "landing" page
Given I have a user account
And I am logged in
And I am on the "landing" page
Scenario: Create a post
When I click on the big plus icon in the bottom right corner to create post

View File

@ -2,18 +2,6 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'
/* global cy */
const lastColumnIsSortedInDescendingOrder = () => {
cy.get('tbody')
.find('tr td:last-child')
.then(lastColumn => {
cy.wrap(lastColumn)
const values = lastColumn
.map((i, td) => parseInt(td.textContent))
.toArray()
const orderedDescending = values.slice(0).sort((a, b) => b - a)
return cy.wrap(values).should('deep.eq', orderedDescending)
})
}
When('I navigate to the administration dashboard', () => {
cy.get('.avatar-menu').click()
@ -23,17 +11,27 @@ When('I navigate to the administration dashboard', () => {
})
Then('I can see a list of categories ordered by post count:', table => {
// TODO: match the table in the feature with the html table
cy.get('thead')
.find('tr th')
.should('have.length', 3)
lastColumnIsSortedInDescendingOrder()
table.hashes().forEach(({Name, Posts}, index) => {
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`)
.should('contain', Name)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`)
.should('contain', Posts)
})
})
Then('I can see a list of tags ordered by user and post count:', table => {
// TODO: match the table in the feature with the html table
Then('I can see a list of tags ordered by user count:', table => {
cy.get('thead')
.find('tr th')
.should('have.length', 4)
lastColumnIsSortedInDescendingOrder()
table.hashes().forEach(({Name, Users, Posts}, index) => {
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`)
.should('contain', Name)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`)
.should('contain', Users)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(4)`)
.should('contain', Posts)
})
})

View File

@ -3,9 +3,9 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
/* global cy */
let lastReportTitle
let dummyReportedPostTitle = 'Hacker, Freaks und Funktionäre'
let dummyReportedPostSlug = 'hacker-freaks-und-funktionareder-ccc'
let dummyAuthorName = 'Jenny Rostock'
let davidIrvingPostTitle = 'The Truth about the Holocaust'
let davidIrvingPostSlug = 'the-truth-about-the-holocaust'
let davidIrvingName = 'David Irving'
const savePostTitle = $post => {
return $post
@ -23,21 +23,27 @@ Given("I see David Irving's post on the landing page", page => {
})
Given("I see David Irving's post on the post page", page => {
cy.visit(`/post/${dummyReportedPostSlug}`)
cy.contains(dummyReportedPostTitle) // wait
cy.visit(`/post/${davidIrvingPostSlug}`)
cy.contains(davidIrvingPostTitle) // wait
})
Given('I am logged in with a {string} role', role => {
cy.loginAs(role)
cy.factory().create('User', {
email: `${role}@example.org`,
password: '1234',
role
})
cy.login({
email: `${role}@example.org`,
password: '1234'
})
})
When(
'I click on "Report Contribution" from the triple dot menu of the post',
() => {
//TODO: match the created post title, not a dummy post title
cy.contains('.ds-card', dummyReportedPostTitle)
cy.contains('.ds-card', davidIrvingPostTitle)
.find('.content-menu-trigger')
.find(':nth-child(2)')
.click()
cy.get('.popover .ds-menu-item-link')
@ -49,8 +55,7 @@ When(
When(
'I click on "Report User" from the triple dot menu in the user info box',
() => {
//TODO: match the created post author, not a dummy author
cy.contains('.ds-card', dummyAuthorName)
cy.contains('.ds-card', davidIrvingName)
.find('.content-menu-trigger')
.first()
.click()
@ -106,12 +111,8 @@ Then(`I can't see the moderation menu item`, () => {
.should('not.exist')
})
When(/^I confirm the reporting dialog .*:$/, () => {
//TODO: take message from method argument
//TODO: match the right post
const message = 'Do you really want to report the'
When(/^I confirm the reporting dialog .*:$/, (message) => {
cy.contains(message) // wait for element to become visible
//TODO: cy.get('.ds-modal').contains(dummyReportedPostTitle)
cy.get('.ds-modal').within(() => {
cy.get('button')
.contains('Send Report')
@ -120,22 +121,28 @@ When(/^I confirm the reporting dialog .*:$/, () => {
})
Given('somebody reported the following posts:', table => {
table.hashes().forEach(row => {
//TODO: calll factory here
// const options = Object.assign({}, row, { reported: true })
//create('post', options)
table.hashes().forEach(({ id }) => {
const reporter = {
email: `reporter${id}@example.org`,
password: '1234'
}
cy.factory()
.create('User', reporter)
.authenticateAs(reporter)
.create('Report', {
description: "I don't like this post",
resource: { id, type: 'contribution' }
})
})
})
Then('I see all the reported posts including the one from above', () => {
//TODO: match the right post
cy.get('table tbody').within(() => {
cy.contains('tr', dummyReportedPostTitle)
cy.contains('tr', davidIrvingPostTitle)
})
})
Then('each list item links to the post page', () => {
//TODO: match the right post
cy.contains(dummyReportedPostTitle).click()
cy.contains(davidIrvingPostTitle).click()
cy.location('pathname').should('contain', '/post')
})

View File

@ -4,7 +4,6 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'
let aboutMeText
let myLocation
let myName
const matchNameInUserMenu = name => {
cy.get('.avatar-menu').click() // open
@ -12,18 +11,13 @@ const matchNameInUserMenu = name => {
cy.get('.avatar-menu').click() // close again
}
const setUserName = name => {
When('I save {string} as my new name', name => {
cy.get('input[id=name]')
.clear()
.type(name)
cy.get('[type=submit]')
.click()
.not('[disabled]')
myName = name
}
When('I save {string} as my new name', name => {
setUserName(name)
})
When('I save {string} as my location', location => {
@ -47,31 +41,20 @@ When('I have the following self-description:', text => {
aboutMeText = text
})
When('my username is {string}', name => {
if (myName !== name) {
setUserName(name)
}
matchNameInUserMenu(name)
})
When('people visit my profile page', url => {
cy.visitMyProfile()
cy.openPage('/profile/peter-pan')
})
When('they can see the text in the info box below my avatar', () => {
cy.contains(aboutMeText)
})
When('I changed my username to {string} previously', name => {
myName = name
})
Then('they can see the location in the info box below my avatar', () => {
matchNameInUserMenu(myName)
cy.contains(myLocation)
})
Then('my new username is still there', () => {
matchNameInUserMenu(myName)
Then('the name {string} is still there', name => {
matchNameInUserMenu(name)
})
Then(

View File

@ -1,29 +1,87 @@
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
import { getLangByName } from '../../support/helpers'
import users from '../../fixtures/users.json'
/* global cy */
let lastPost = {
let lastPost = {}
const loginCredentials = {
email: 'peterpan@example.org',
password: '1234'
}
const narratorParams = {
name: 'Peter Pan',
...loginCredentials
}
Given('I am logged in', () => {
cy.loginAs('admin')
})
Given('I am logged in as {string}', userType => {
cy.loginAs(userType)
cy.login(loginCredentials)
})
Given('we have a selection of tags and categories as well as posts', () => {
// TODO: use db factories instead of seed data
cy.factory()
.authenticateAs(loginCredentials)
.create('Category', {
id: 'cat1',
name: 'Just For Fun',
slug: 'justforfun',
icon: 'smile'
})
.create('Category', {
id: 'cat2',
name: 'Happyness & Values',
slug: 'happyness-values',
icon: 'heart-o'
})
.create('Category', {
id: 'cat3',
name: 'Health & Wellbeing',
slug: 'health-wellbeing',
icon: 'medkit'
})
.create('Tag', { id: 't1', name: 'Ecology' })
.create('Tag', { id: 't2', name: 'Nature' })
.create('Tag', { id: 't3', name: 'Democracy' })
const someAuthor = {
id: 'authorId',
email: 'author@example.org',
password: '1234'
}
cy.factory()
.create('User', someAuthor)
.authenticateAs(someAuthor)
.create('Post', { id: 'p0' })
.create('Post', { id: 'p1' })
cy.factory()
.authenticateAs(loginCredentials)
.create('Post', { id: 'p2' })
.relate('Post', 'Categories', { from: 'p0', to: 'cat1' })
.relate('Post', 'Categories', { from: 'p1', to: 'cat2' })
.relate('Post', 'Categories', { from: 'p2', to: 'cat1' })
.relate('Post', 'Tags', { from: 'p0', to: 't1' })
.relate('Post', 'Tags', { from: 'p0', to: 't2' })
.relate('Post', 'Tags', { from: 'p0', to: 't3' })
.relate('Post', 'Tags', { from: 'p1', to: 't2' })
.relate('Post', 'Tags', { from: 'p1', to: 't3' })
.relate('Post', 'Tags', { from: 'p2', to: 't3' })
})
Given('my account has the following details:', table => {
// TODO: use db factories instead of seed data
Given('we have the following user accounts:', table => {
table.hashes().forEach(params => {
cy.factory().create('User', params)
})
})
Given('I have a user account', () => {
cy.factory().create('User', narratorParams)
})
Given('my user account has the role {string}', role => {
// TODO: use db factories instead of seed data
cy.factory().create('User', {
role,
...loginCredentials
})
})
When('I log out', cy.logout)
@ -37,10 +95,10 @@ Given('I am on the {string} page', page => {
})
When('I fill in my email and password combination and click submit', () => {
cy.login('admin@example.org', 1234)
cy.login(loginCredentials)
})
When('I refresh the page', () => {
When(/(?:when )?I refresh the page/, () => {
cy.reload()
})
@ -52,7 +110,7 @@ When('I log out through the menu in the top right corner', () => {
})
Then('I can see my name {string} in the dropdown menu', () => {
cy.get('.avatar-menu-popover').should('contain', users.admin.name)
cy.get('.avatar-menu-popover').should('contain', narratorParams.name)
})
Then('I see the login screen again', () => {
@ -66,7 +124,7 @@ Then('I can click on my profile picture in the top right corner', () => {
Then('I am still logged in', () => {
cy.get('.avatar-menu').click()
cy.get('.avatar-menu-popover').contains(users.admin.name)
cy.get('.avatar-menu-popover').contains(narratorParams.name)
})
When('I select {string} in the language menu', name => {
@ -93,9 +151,18 @@ When('I press {string}', label => {
})
Given('we have the following posts in our database:', table => {
table.hashes().forEach(row => {
//TODO: calll factory here
//create('post', row)
table.hashes().forEach(({ Author, id, title, content }) => {
cy.factory()
.create('User', {
name: Author,
email: `${Author}@example.org`,
password: '1234'
})
.authenticateAs({
email: `${Author}@example.org`,
password: '1234'
})
.create('Post', { id, title, content })
})
})
@ -107,36 +174,41 @@ When('I click on the avatar menu in the top right corner', () => {
cy.get('.avatar-menu').click()
})
When('I click on the big plus icon in the bottom right corner to create post', () => {
cy.get('.post-add-button').click()
})
When(
'I click on the big plus icon in the bottom right corner to create post',
() => {
cy.get('.post-add-button').click()
}
)
Given('I previously created a post', () => {
// TODO: create a post in the database
cy.factory()
.authenticateAs(loginCredentials)
.create('Post', lastPost)
})
When('I choose {string} as the title of the post', (title) => {
When('I choose {string} as the title of the post', title => {
lastPost.title = title.replace('\n', ' ')
cy.get('input[name="title"]').type(lastPost.title)
})
When('I type in the following text:', (text) => {
lastPost.text = text.replace('\n', ' ')
cy.get('.ProseMirror').type(lastPost.text)
When('I type in the following text:', text => {
lastPost.content = text.replace('\n', ' ')
cy.get('.ProseMirror').type(lastPost.content)
})
Then('the post shows up on the landing page at position {int}', (index) => {
Then('the post shows up on the landing page at position {int}', index => {
cy.openPage('landing')
const selector = `:nth-child(${index}) > .ds-card > .ds-card-content`
cy.get(selector).should('contain', lastPost.title)
cy.get(selector).should('contain', lastPost.text)
cy.get(selector).should('contain', lastPost.content)
})
Then('I get redirected to {string}', (route) => {
Then('I get redirected to {string}', route => {
cy.location('pathname').should('contain', route)
})
Then('the post was saved successfully', () => {
cy.get('.ds-card-header > .ds-heading').should('contain', lastPost.title)
cy.get('.content').should('contain', lastPost.text)
cy.get('.content').should('contain', lastPost.content)
})

View File

@ -35,14 +35,7 @@ Cypress.Commands.add('switchLanguage', (name, force) => {
}
})
Cypress.Commands.add('visitMyProfile', () => {
cy.get('.avatar-menu').click()
cy.get('.avatar-menu-popover')
.find('a[href^="/profile/"]')
.click()
})
Cypress.Commands.add('login', (email, password) => {
Cypress.Commands.add('login', ({ email, password }) => {
cy.visit(`/login`)
cy.get('input[name=email]')
.trigger('focus')
@ -56,11 +49,6 @@ Cypress.Commands.add('login', (email, password) => {
cy.location('pathname').should('eq', '/') // we're in!
})
Cypress.Commands.add('loginAs', role => {
role = role || 'admin'
cy.login(users[role].email, users[role].password)
})
Cypress.Commands.add('logout', (email, password) => {
cy.visit(`/logout`)
cy.location('pathname').should('contain', '/login') // we're out

View File

@ -0,0 +1,43 @@
// TODO: find a better way how to import the factories
import Factory from '../../../Nitro-Backend/src/seed/factories'
import { getDriver } from '../../../Nitro-Backend/src/bootstrap/neo4j'
const neo4jDriver = getDriver({
uri: Cypress.env('NEO4J_URI'),
username: Cypress.env('NEO4J_USERNAME'),
password: Cypress.env('NEO4J_PASSWORD')
})
const factory = Factory({ neo4jDriver })
const seedServerHost = Cypress.env('SEED_SERVER_HOST')
beforeEach(async () => {
await factory.cleanDatabase({ seedServerHost, neo4jDriver })
})
Cypress.Commands.add('factory', () => {
return Factory({seedServerHost})
})
Cypress.Commands.add(
'create',
{ prevSubject: true },
(factory, node, properties) => {
return factory.create(node, properties)
}
)
Cypress.Commands.add(
'relate',
{ prevSubject: true },
(factory, node, relationship, properties) => {
return factory.relate(node, relationship, properties)
}
)
Cypress.Commands.add(
'authenticateAs',
{ prevSubject: true },
(factory, loginCredentials) => {
return factory.authenticateAs(loginCredentials)
}
)

View File

@ -15,6 +15,7 @@
// Import commands.js using ES2015 syntax:
import './commands'
import './factories'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@ -5,3 +5,5 @@ services:
build:
context: .
target: build-and-test
environment:
- BACKEND_URL=http://backend:4123