test(e2e): replace integrated tests with standalone repository
This commit is contained in:
committed by
Benedikt Rötsch
parent
761f18e1a0
commit
d1b1d507bc
3
.gitignore
vendored
3
.gitignore
vendored
@@ -62,3 +62,6 @@ package-lock.json
|
|||||||
|
|
||||||
# cypress test result files
|
# cypress test result files
|
||||||
cypress
|
cypress
|
||||||
|
|
||||||
|
# e2e test directory
|
||||||
|
test/e2e
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ machine:
|
|||||||
version: "8"
|
version: "8"
|
||||||
dependencies:
|
dependencies:
|
||||||
pre:
|
pre:
|
||||||
- curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
- git clone https://github.com/contentful/the-example-app-e2e-tests.git ./test/e2e
|
||||||
- sudo dpkg -i google-chrome.deb
|
- cd ./test/e2e && npm install
|
||||||
- sudo sed -i 's|HERE/chrome\"|HERE/chrome\" --disable-setuid-sandbox|g' /opt/google/chrome/google-chrome
|
|
||||||
- rm google-chrome.deb
|
|
||||||
test:
|
test:
|
||||||
post:
|
post:
|
||||||
- tar -czf $CIRCLE_ARTIFACTS/cypress-result_`date +%Y-%m-%d_%H-%M-%S`.tar.gz ./cypress
|
- tar -czf $CIRCLE_ARTIFACTS/cypress-result_`date +%Y-%m-%d_%H-%M-%S`.tar.gz ./cypress
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"format": "eslint --fix . bin --ignore public node_modules",
|
"format": "eslint --fix . bin --ignore public node_modules",
|
||||||
"pretest": "npm run lint",
|
"pretest": "npm run lint",
|
||||||
"test": "npm run test:unit && npm run test:integration && npm run test:e2e",
|
"test": "npm run test:unit && npm run test:integration && npm run test:e2e",
|
||||||
"test:e2e": "node test/e2e/run-e2e-test.js",
|
"test:e2e": "node test/run-e2e-test.js",
|
||||||
"test:integration": "jest test/integration",
|
"test:integration": "jest test/integration",
|
||||||
"test:integration:watch": "jest test/integration --watch",
|
"test:integration:watch": "jest test/integration --watch",
|
||||||
"test:unit": "jest test/unit",
|
"test:unit": "jest test/unit",
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"contentful": "^5.0.2",
|
"contentful": "^5.0.2",
|
||||||
"cookie-parser": "~1.4.3",
|
"cookie-parser": "~1.4.3",
|
||||||
"dotenv": "^4.0.0",
|
"dotenv": "^4.0.0",
|
||||||
|
"execa": "^0.8.0",
|
||||||
"express": "~4.14.0",
|
"express": "~4.14.0",
|
||||||
"helmet": "^3.9.0",
|
"helmet": "^3.9.0",
|
||||||
"loadash": "^1.0.0",
|
"loadash": "^1.0.0",
|
||||||
@@ -37,7 +38,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cheerio": "^1.0.0-rc.2",
|
"cheerio": "^1.0.0-rc.2",
|
||||||
"cookie": "^0.3.1",
|
"cookie": "^0.3.1",
|
||||||
"cypress": "^1.0.3",
|
|
||||||
"eslint": "^3.16.0",
|
"eslint": "^3.16.0",
|
||||||
"eslint-config-standard": "^6.2.1",
|
"eslint-config-standard": "^6.2.1",
|
||||||
"eslint-plugin-promise": "^3.4.2",
|
"eslint-plugin-promise": "^3.4.2",
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
const http = require('http')
|
|
||||||
const { resolve } = require('path')
|
|
||||||
|
|
||||||
require('dotenv').config({ path: 'variables.env' })
|
|
||||||
|
|
||||||
const cypress = require('cypress')
|
|
||||||
const app = require('../../app')
|
|
||||||
|
|
||||||
const TEST_PORT = 3007
|
|
||||||
|
|
||||||
app.set('port', TEST_PORT)
|
|
||||||
|
|
||||||
const server = http.createServer(app)
|
|
||||||
|
|
||||||
const { CONTENTFUL_SPACE_ID, CONTENTFUL_DELIVERY_TOKEN, CONTENTFUL_PREVIEW_TOKEN } = process.env
|
|
||||||
|
|
||||||
server.on('error', console.error)
|
|
||||||
server.listen(TEST_PORT, function () {
|
|
||||||
cypress.run({
|
|
||||||
spec: resolve(__dirname, 'specs', 'the-example-app-spec.js'),
|
|
||||||
headed: !process.env.CI,
|
|
||||||
env: {
|
|
||||||
CONTENTFUL_SPACE_ID, CONTENTFUL_DELIVERY_TOKEN, CONTENTFUL_PREVIEW_TOKEN
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
server.close()
|
|
||||||
if (result.failures > 0) {
|
|
||||||
process.exit(1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
process.exit(0)
|
|
||||||
}).catch(() => {
|
|
||||||
server.close()
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
'env': {
|
|
||||||
'node': true,
|
|
||||||
'mocha': true
|
|
||||||
},
|
|
||||||
'globals': {
|
|
||||||
'Cypress': true,
|
|
||||||
'cy': true,
|
|
||||||
'expect': true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
describe('The Example App', () => {
|
|
||||||
context('basics', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('/')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('meta tags', () => {
|
|
||||||
cy.title().should('equals', 'Home — The Example App', 'Home page should have correct meta title')
|
|
||||||
cy.get('meta[name="description"]').should('attr', 'content', 'This is "The Example App", a reference for building your own applications using Contentful.')
|
|
||||||
|
|
||||||
cy.get('meta[name="twitter:card"]').should('attr', 'content', 'This is "The Example App", a reference for building your own applications using Contentful.')
|
|
||||||
|
|
||||||
cy.get('meta[property="og:title"]').should('attr', 'content', 'Home — The Example App')
|
|
||||||
cy.get('meta[property="og:type"]').should('attr', 'content', 'article')
|
|
||||||
cy.get('meta[property="og:url"]').should('exist')
|
|
||||||
cy.get('meta[property="og:image"]').should('exist')
|
|
||||||
cy.get('meta[property="og:image:type"]').should('attr', 'content', 'image/jpeg')
|
|
||||||
cy.get('meta[property="og:image:width"]').should('attr', 'content', '1200')
|
|
||||||
cy.get('meta[property="og:image:height"]').should('attr', 'content', '1200')
|
|
||||||
cy.get('meta[property="og:description"]').should('attr', 'content', 'This is "The Example App", a reference for building your own applications using Contentful.')
|
|
||||||
|
|
||||||
cy.get('link[rel="apple-touch-icon"]')
|
|
||||||
.should('attr', 'sizes', '120x120')
|
|
||||||
.should('attr', 'href', '/apple-touch-icon.png')
|
|
||||||
cy.get('link[rel="icon"]').should('have.length.gte', 2, 'containts at least 2 favicons')
|
|
||||||
cy.get('link[rel="manifest"]').should('attr', 'href', '/manifest.json')
|
|
||||||
cy.get('link[rel="mask-icon"]')
|
|
||||||
.should('attr', 'href', '/safari-pinned-tab.svg')
|
|
||||||
.should('attr', 'color', '#4a90e2')
|
|
||||||
cy.get('meta[name="theme-color"]').should('attr', 'content', '#ffffff')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('global elements', () => {
|
|
||||||
cy.get('.header__upper')
|
|
||||||
.should('contain', 'Help')
|
|
||||||
.should('contain', 'GitHub')
|
|
||||||
|
|
||||||
cy.get('.main__footer .footer__lower')
|
|
||||||
.should('contain', 'Powered by Contentful')
|
|
||||||
.should('contain', 'GitHub')
|
|
||||||
.should('contain', 'Imprint')
|
|
||||||
.should('contain', 'Contact us')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('about modal', () => {
|
|
||||||
cy.get('section.modal .modal__wrapper').should('hidden')
|
|
||||||
cy.get('.header__upper-title a').click()
|
|
||||||
cy.get('section.modal .modal__wrapper').should('visible')
|
|
||||||
cy.get('section.modal .modal__title').should('contain', 'A reference for developers using Contentful')
|
|
||||||
cy.get('section.modal .modal__content').should('contain', 'This is "The Example App" in node.js. While building your own apps with Contentful,')
|
|
||||||
|
|
||||||
// Close on background
|
|
||||||
cy.get('section.modal .modal__overlay').click({force: true})
|
|
||||||
cy.get('section.modal .modal__wrapper').should('hidden')
|
|
||||||
cy.get('.header__upper-title a').click()
|
|
||||||
cy.get('section.modal .modal__wrapper').should('visible')
|
|
||||||
|
|
||||||
// Close on X
|
|
||||||
cy.get('section.modal .modal__close-button').click()
|
|
||||||
cy.get('section.modal .modal__wrapper').should('hidden')
|
|
||||||
cy.get('.header__upper-title a').click()
|
|
||||||
cy.get('section.modal .modal__wrapper').should('visible')
|
|
||||||
|
|
||||||
// Close on "Got this" button
|
|
||||||
cy.get('section.modal .modal__cta').click()
|
|
||||||
cy.get('section.modal .modal__wrapper').should('hidden')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('header dropdowns show and hide', () => {
|
|
||||||
cy.get('.header__controls > *:first-child .header__controls_dropdown').should('have.css', 'opacity').and('be', 0)
|
|
||||||
cy.get('.header__controls > *:first-child .header__controls_label').click()
|
|
||||||
cy.get('.header__controls > *:first-child .header__controls_dropdown').should('have.css', 'opacity').and('be', 1)
|
|
||||||
// Should hide dropdown after a while
|
|
||||||
cy.wait(300)
|
|
||||||
cy.get('.header__controls > *:first-child .header__controls_dropdown').should('have.css', 'opacity').and('be', 0)
|
|
||||||
|
|
||||||
cy.get('.header__controls > *:last-child .header__controls_dropdown').should('have.css', 'opacity').and('be', 0)
|
|
||||||
cy.get('.header__controls > *:last-child .header__controls_label').click()
|
|
||||||
cy.get('.header__controls > *:last-child .header__controls_dropdown').should('have.css', 'opacity').and('be', 1)
|
|
||||||
// Should hide dropdown after a while
|
|
||||||
cy.wait(300)
|
|
||||||
cy.get('.header__controls > *:last-child .header__controls_dropdown').should('have.css', 'opacity').and('be', 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('header dropdowns change app context', () => {
|
|
||||||
cy.get('.header__controls > *:first-child .header__controls_label').click()
|
|
||||||
cy.get('.header__controls > *:first-child .header__controls_dropdown button:last-child').click()
|
|
||||||
cy.location('search').should('contain', 'api=cpa')
|
|
||||||
|
|
||||||
cy.get('.header__controls > *:last-child .header__controls_label').click()
|
|
||||||
cy.get('.header__controls > *:last-child .header__controls_dropdown button:last-child').click()
|
|
||||||
cy.location('search')
|
|
||||||
.should('contain', 'locale=de')
|
|
||||||
.should('contain', 'api=cpa')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Home', () => {
|
|
||||||
it('renders home page', () => {
|
|
||||||
cy.visit('/')
|
|
||||||
cy.get('main .module-highlighted-course').should('have.length.gte', 1, 'should have at least one highlighted course')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Courses', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
cy.title().should('match', / — The Example App$/, 'Title has contextual suffix (appname)')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders course overview', () => {
|
|
||||||
cy.visit('/courses')
|
|
||||||
cy.get('.course-card').should('have.length.gte', 2, 'renders at least 2 courses')
|
|
||||||
cy.get('.layout-sidebar__sidebar-header > h2').should('contain', 'Categories', 'Shows category title in sidebar')
|
|
||||||
cy.get('.sidebar-menu__list > .sidebar-menu__item:first-child').should('contain', 'All courses', 'Shows all courses link')
|
|
||||||
cy.get('.sidebar-menu__list > .sidebar-menu__item').should('have.length.gte', 2, 'renders at least one category selector')
|
|
||||||
cy.get('.sidebar-menu__list > .sidebar-menu__item:first-child > a').should('have.class', 'active', 'All courses is selected by default')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can filter course overview', () => {
|
|
||||||
cy.visit('/courses')
|
|
||||||
|
|
||||||
cy.get('.sidebar-menu__list > .sidebar-menu__item:nth-child(2) > a').click()
|
|
||||||
cy.get('.sidebar-menu__list > .sidebar-menu__item:nth-child(1) > a').should('not.have.class', 'active', 'All courses link is no more active')
|
|
||||||
cy.get('.sidebar-menu__list > .sidebar-menu__item:nth-child(2) > a').should('have.class', 'active', 'First category filter link should be active')
|
|
||||||
cy.get('main h1').invoke('text').then((text) => console.log('headline content:', text))
|
|
||||||
|
|
||||||
cy.get('.sidebar-menu__list > .sidebar-menu__item:nth-child(2) > a').invoke('text').then((firstCategoryTitle) => {
|
|
||||||
cy.get('main h1').invoke('text').then((headline) => {
|
|
||||||
expect(headline).to.match(new RegExp(`^${firstCategoryTitle} \\([0-9]+\\)$`), 'Title now contains the category name with number of courses')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
cy.get('.course-card').should('have.length.gte', 1, 'filtered courses contain at least one course')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('tracks the watched state of lessons', () => {
|
|
||||||
// Home
|
|
||||||
cy.visit('/')
|
|
||||||
|
|
||||||
// Navigate to courses
|
|
||||||
cy.get('header.header .main-navigation ul > li:last-child a').click()
|
|
||||||
|
|
||||||
// Click title of course card to open it
|
|
||||||
cy.get('.course-card .course-card__title a').first().click()
|
|
||||||
|
|
||||||
// Check that overview link is visited and active
|
|
||||||
cy.get('.table-of-contents .table-of-contents__list .table-of-contents__item:nth-child(1) a')
|
|
||||||
.should('have.class', 'active')
|
|
||||||
.should('have.class', 'visited')
|
|
||||||
// Check that lesson link is neither visited nor active
|
|
||||||
cy.get('.table-of-contents .table-of-contents__list .table-of-contents__item:nth-child(2) a')
|
|
||||||
.should('not.have.class', 'active')
|
|
||||||
.should('not.have.class', 'visited')
|
|
||||||
|
|
||||||
// Start first lesson
|
|
||||||
cy.get('.course__overview a.course__overview-cta').click()
|
|
||||||
// Check that overview link is visited but not active
|
|
||||||
cy.get('.table-of-contents .table-of-contents__list .table-of-contents__item:nth-child(1) a')
|
|
||||||
.should('not.have.class', 'active')
|
|
||||||
.should('have.class', 'visited')
|
|
||||||
// Check that lesson link is visited and active
|
|
||||||
cy.get('.table-of-contents .table-of-contents__list .table-of-contents__item:nth-child(2) a')
|
|
||||||
.should('have.class', 'active')
|
|
||||||
.should('have.class', 'visited')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Settings', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('/settings')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders setting with default values', () => {
|
|
||||||
cy.title().should('equals', 'Settings — The Example App')
|
|
||||||
cy.get('main h1').should('have.text', 'Settings')
|
|
||||||
cy.get('.status-block')
|
|
||||||
.should('have.length', 1)
|
|
||||||
.invoke('text').then((text) => {
|
|
||||||
expect(text).to.match(/Connected to space “.+”/)
|
|
||||||
})
|
|
||||||
cy.get('input#input-space-id').should('have.value', Cypress.env('CONTENTFUL_SPACE_ID'))
|
|
||||||
cy.get('input#input-delivery-token').should('have.value', Cypress.env('CONTENTFUL_DELIVERY_TOKEN'))
|
|
||||||
cy.get('input#input-preview-token').should('have.value', Cypress.env('CONTENTFUL_PREVIEW_TOKEN'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('checks for required fields', () => {
|
|
||||||
cy.get('input#input-space-id').clear()
|
|
||||||
cy.get('input#input-delivery-token').clear()
|
|
||||||
cy.get('input#input-preview-token').clear()
|
|
||||||
cy.get('input[type=submit]').click()
|
|
||||||
|
|
||||||
cy.get('.status-block--info').should('not.exist')
|
|
||||||
cy.get('.status-block--success').should('not.exist')
|
|
||||||
cy.get('.status-block--error').should('exist')
|
|
||||||
|
|
||||||
cy.get('input#input-space-id').parent().children('.form-item__error-wrapper')
|
|
||||||
.should('exist')
|
|
||||||
.find('.form-item__error-message')
|
|
||||||
.should('have.text', 'This field is required')
|
|
||||||
cy.get('input#input-delivery-token').parent().children('.form-item__error-wrapper')
|
|
||||||
.should('exist')
|
|
||||||
.find('.form-item__error-message')
|
|
||||||
.should('have.text', 'This field is required')
|
|
||||||
cy.get('input#input-preview-token').parent().children('.form-item__error-wrapper')
|
|
||||||
.should('exist')
|
|
||||||
.find('.form-item__error-message')
|
|
||||||
.should('have.text', 'This field is required')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('validates field with actual client', () => {
|
|
||||||
cy.get('input#input-space-id').clear().type(Math.random().toString(36).substring(12))
|
|
||||||
cy.get('input#input-delivery-token').clear().type(Math.random().toString(36))
|
|
||||||
cy.get('input#input-preview-token').clear().type(Math.random().toString(36))
|
|
||||||
cy.get('input[type=submit]').click()
|
|
||||||
|
|
||||||
cy.get('.status-block--info').should('not.exist')
|
|
||||||
cy.get('.status-block--success').should('not.exist')
|
|
||||||
cy.get('.status-block--error').should('exist')
|
|
||||||
|
|
||||||
cy.get('input#input-delivery-token').parent().children('.form-item__error-wrapper')
|
|
||||||
.should('exist')
|
|
||||||
.find('.form-item__error-message')
|
|
||||||
.should('have.text', 'Your Delivery API key is invalid.')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows success message when valid credentials are supplied', () => {
|
|
||||||
cy.get('input#input-space-id').clear().type(Cypress.env('CONTENTFUL_SPACE_ID'))
|
|
||||||
cy.get('input#input-delivery-token').clear().type(Cypress.env('CONTENTFUL_DELIVERY_TOKEN'))
|
|
||||||
cy.get('input#input-preview-token').clear().type(Cypress.env('CONTENTFUL_PREVIEW_TOKEN'))
|
|
||||||
cy.get('input[type=submit]').click()
|
|
||||||
|
|
||||||
cy.get('.status-block--info').should('exist')
|
|
||||||
cy.get('.status-block--success').should('exist')
|
|
||||||
cy.get('.status-block--error').should('not.exist')
|
|
||||||
|
|
||||||
cy.get('.form-item__error-wrapper').should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('enables editorial features and displays them on home', () => {
|
|
||||||
cy.get('input#input-editorial-features').check()
|
|
||||||
cy.get('input[type=submit]').click()
|
|
||||||
|
|
||||||
cy.get('.status-block--info').should('exist')
|
|
||||||
cy.get('.status-block--success').should('exist')
|
|
||||||
cy.get('.status-block--error').should('not.exist')
|
|
||||||
|
|
||||||
cy.get('input#input-editorial-features').should('checked')
|
|
||||||
|
|
||||||
cy.visit('/')
|
|
||||||
|
|
||||||
cy.get('.editorial-features').should('exist')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
44
test/run-e2e-test.js
Normal file
44
test/run-e2e-test.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const http = require('http')
|
||||||
|
const { resolve } = require('path')
|
||||||
|
const execa = require('execa')
|
||||||
|
|
||||||
|
require('dotenv').config({ path: 'variables.env' })
|
||||||
|
|
||||||
|
const app = require('../app')
|
||||||
|
|
||||||
|
const TEST_PORT = 3007
|
||||||
|
|
||||||
|
app.set('port', TEST_PORT)
|
||||||
|
|
||||||
|
const server = http.createServer(app)
|
||||||
|
|
||||||
|
const { CONTENTFUL_SPACE_ID, CONTENTFUL_DELIVERY_TOKEN, CONTENTFUL_PREVIEW_TOKEN } = process.env
|
||||||
|
|
||||||
|
server.on('error', console.error)
|
||||||
|
server.listen(TEST_PORT, function () {
|
||||||
|
const cypressBin = resolve(__dirname, 'e2e', 'node_modules', '.bin', 'cypress')
|
||||||
|
const spec = resolve(__dirname, 'e2e', 'specs', 'the-example-app-spec.js')
|
||||||
|
execa(cypressBin, [
|
||||||
|
'run',
|
||||||
|
'--spec',
|
||||||
|
spec,
|
||||||
|
!process.env.CI ? '--headed' : null,
|
||||||
|
'--env',
|
||||||
|
`CONTENTFUL_SPACE_ID=${CONTENTFUL_SPACE_ID},CONTENTFUL_DELIVERY_TOKEN=${CONTENTFUL_DELIVERY_TOKEN},CONTENTFUL_PREVIEW_TOKEN=${CONTENTFUL_PREVIEW_TOKEN}`
|
||||||
|
].filter(Boolean))
|
||||||
|
.then((result) => {
|
||||||
|
console.log('✔ e2e test succeeded:')
|
||||||
|
console.log(result.cmd)
|
||||||
|
console.log(result.stdout)
|
||||||
|
server.close()
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`✖ e2e test failed with status code ${error.code}:`)
|
||||||
|
console.error(error.cmd)
|
||||||
|
console.error(error.stdout)
|
||||||
|
console.error(error.stderr)
|
||||||
|
server.close()
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user