chore(e2e): add cypress integration test of editorial workflow (#1573)
This commit is contained in:
parent
4d8e176797
commit
b79c042e9e
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ yarn-error.log
|
||||
manifest.yml
|
||||
.imdone/
|
||||
website/data/contributors.json
|
||||
cypress/videos
|
||||
/coverage/
|
||||
.cache
|
||||
*.log
|
||||
|
4
cypress.json
Normal file
4
cypress.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:8080",
|
||||
"projectId": "dzqjxb"
|
||||
}
|
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
@ -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"
|
||||
}
|
231
cypress/integration/editorial_workflow_spec.js
Normal file
231
cypress/integration/editorial_workflow_spec.js
Normal file
@ -0,0 +1,231 @@
|
||||
describe('Editorial Workflow', () => {
|
||||
const workflowStatus = { draft: 'Drafts', review: 'In Review', ready: 'Ready' }
|
||||
const editorStatus = { draft: 'Draft', review: 'In review', ready: 'Ready' }
|
||||
const entry1 = { title: 'first title', body: 'first body' }
|
||||
const entry2 = { title: 'second title', body: 'second body' }
|
||||
const entry3 = { title: 'third title', body: 'third body' }
|
||||
const notifications = {
|
||||
saved: 'Entry saved',
|
||||
published: 'Entry published',
|
||||
updated: 'Entry status updated',
|
||||
deletedUnpublished: 'Unpublished changes deleted',
|
||||
}
|
||||
|
||||
describe('Test Backend', () => {
|
||||
function login() {
|
||||
cy.viewport(1200, 1200)
|
||||
cy.visit('/')
|
||||
cy.contains('button', 'Login').click()
|
||||
}
|
||||
|
||||
function createPost({ title, body }) {
|
||||
cy.contains('a', 'New Post').click()
|
||||
cy.get('input').first().type(title)
|
||||
cy.get('[data-slate-editor]').click().type(body)
|
||||
cy.get('input').first().click()
|
||||
cy.contains('button', 'Save').click()
|
||||
assertNotification(notifications.saved)
|
||||
}
|
||||
|
||||
function exitEditor() {
|
||||
cy.contains('a[href^="#/collections/"]', 'Writing in').click()
|
||||
}
|
||||
|
||||
function deleteEntryInEditor() {
|
||||
cy.contains('button', 'Delete').click()
|
||||
assertNotification(notifications.deletedUnpublished)
|
||||
}
|
||||
|
||||
function assertEntryDeleted(entry) {
|
||||
if (Array.isArray(entry)) {
|
||||
const titles = entry.map(e => e.title)
|
||||
cy.get('a h2').each((el, idx) => {
|
||||
expect(titles).not.to.include(el.text())
|
||||
})
|
||||
} else {
|
||||
cy.get('a h2').each((el, idx) => {
|
||||
expect(entry.title).not.to.equal(el.text())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createAndDeletePost(entry) {
|
||||
createPost(entry)
|
||||
deleteEntryInEditor()
|
||||
}
|
||||
|
||||
function createPostAndExit(entry) {
|
||||
createPost(entry)
|
||||
exitEditor()
|
||||
}
|
||||
|
||||
function createPublishedPost(entry) {
|
||||
createPost(entry)
|
||||
updateWorkflowStatusInEditor(editorStatus.ready)
|
||||
publishInEditor()
|
||||
}
|
||||
|
||||
function createPublishedPostAndExit(entry) {
|
||||
createPublishedPost(entry)
|
||||
exitEditor()
|
||||
}
|
||||
|
||||
function goToWorkflow() {
|
||||
cy.contains('a', 'Workflow').click()
|
||||
}
|
||||
|
||||
function goToCollections() {
|
||||
cy.contains('a', 'Content').click()
|
||||
}
|
||||
|
||||
function updateWorkflowStatus({ title }, fromColumnHeading, toColumnHeading) {
|
||||
cy.contains('h2', fromColumnHeading)
|
||||
.parent()
|
||||
.contains('a', title)
|
||||
.trigger('dragstart', {
|
||||
dataTransfer: {},
|
||||
force: true,
|
||||
})
|
||||
cy.contains('h2', toColumnHeading)
|
||||
.parent()
|
||||
.trigger('drop', {
|
||||
dataTransfer: {},
|
||||
force: true,
|
||||
})
|
||||
assertNotification(notifications.updated)
|
||||
}
|
||||
|
||||
function assertWorkflowStatus({ title }, status) {
|
||||
cy.contains('h2', status)
|
||||
.parent()
|
||||
.contains('a', title)
|
||||
}
|
||||
|
||||
function updateWorkflowStatusInEditor(newStatus) {
|
||||
cy.contains('[role="button"]', 'Set status').as('setStatusButton')
|
||||
cy.get('@setStatusButton').parent().within(() => {
|
||||
cy.get('@setStatusButton').click()
|
||||
cy.contains('[role="menuitem"] span', newStatus).click()
|
||||
})
|
||||
assertNotification(notifications.updated)
|
||||
}
|
||||
|
||||
function assertWorkflowStatusInEditor(status) {
|
||||
cy.contains('[role="button"]', 'Set status').as('setStatusButton')
|
||||
cy.get('@setStatusButton').parent().within(() => {
|
||||
cy.get('@setStatusButton').click()
|
||||
cy.contains('[role="menuitem"] span', status).parent().within(() => {
|
||||
cy.get('svg')
|
||||
})
|
||||
cy.get('@setStatusButton').click()
|
||||
})
|
||||
}
|
||||
|
||||
function publishWorkflowEntry({ title }) {
|
||||
cy.contains('h2', workflowStatus.ready).parent().within(() => {
|
||||
cy.contains('a', title).parent().within(() => {
|
||||
cy.contains('button', 'Publish new entry').click({ force: true })
|
||||
})
|
||||
})
|
||||
assertNotification(notifications.published)
|
||||
}
|
||||
|
||||
function assertPublishedEntry(entry) {
|
||||
if (Array.isArray(entry)) {
|
||||
const entries = entry.reverse()
|
||||
cy.get('a h2').then(els => {
|
||||
cy.wrap(els.slice(0, entries.length)).each((el, idx) => {
|
||||
cy.wrap(el).contains(entries[idx].title)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
cy.get('a h2').first().contains(entry.title)
|
||||
}
|
||||
}
|
||||
|
||||
function assertNotification(message) {
|
||||
if (Array.isArray(message)) {
|
||||
const messages = message.reverse()
|
||||
cy.get('.notif__container div')
|
||||
.should('have.length.of', messages.length, )
|
||||
.each((el, idx) => {
|
||||
cy.wrap(el).contains(messages[idx]).invoke('hide')
|
||||
})
|
||||
} else {
|
||||
cy.get('.notif__container').within(() => {
|
||||
cy.contains(message).invoke('hide')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function assertOnCollectionsPage() {
|
||||
cy.url().should('contain', '/#/collections/posts')
|
||||
cy.contains('h1', 'Collections')
|
||||
}
|
||||
|
||||
it('successfully loads', () => {
|
||||
login()
|
||||
})
|
||||
|
||||
it('can create an entry', () => {
|
||||
login()
|
||||
createPostAndExit(entry1)
|
||||
})
|
||||
|
||||
it('can publish an editorial workflow entry', () => {
|
||||
login()
|
||||
createPostAndExit(entry1)
|
||||
goToWorkflow()
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready)
|
||||
publishWorkflowEntry(entry1)
|
||||
})
|
||||
|
||||
it('can change workflow status', () => {
|
||||
login()
|
||||
createPostAndExit(entry1)
|
||||
goToWorkflow()
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.review)
|
||||
updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.ready)
|
||||
updateWorkflowStatus(entry1, workflowStatus.ready, workflowStatus.review)
|
||||
updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.draft)
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready)
|
||||
})
|
||||
|
||||
it('can change status on and publish multiple entries', () => {
|
||||
login()
|
||||
createPostAndExit(entry1)
|
||||
createPostAndExit(entry2)
|
||||
createPostAndExit(entry3)
|
||||
goToWorkflow()
|
||||
updateWorkflowStatus(entry3, workflowStatus.draft, workflowStatus.ready)
|
||||
updateWorkflowStatus(entry2, workflowStatus.draft, workflowStatus.ready)
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready)
|
||||
publishWorkflowEntry(entry3)
|
||||
publishWorkflowEntry(entry2)
|
||||
publishWorkflowEntry(entry1)
|
||||
goToCollections()
|
||||
assertPublishedEntry([entry3, entry2, entry1])
|
||||
})
|
||||
|
||||
it('can delete an entry', () => {
|
||||
login()
|
||||
createPost(entry1)
|
||||
deleteEntryInEditor()
|
||||
assertOnCollectionsPage()
|
||||
assertEntryDeleted(entry1)
|
||||
})
|
||||
|
||||
it('can update workflow status from within the editor', () => {
|
||||
login()
|
||||
createPost(entry1)
|
||||
assertWorkflowStatusInEditor(editorStatus.draft)
|
||||
updateWorkflowStatusInEditor(editorStatus.review)
|
||||
assertWorkflowStatusInEditor(editorStatus.review)
|
||||
updateWorkflowStatusInEditor(editorStatus.ready)
|
||||
assertWorkflowStatusInEditor(editorStatus.ready)
|
||||
exitEditor()
|
||||
goToWorkflow()
|
||||
assertWorkflowStatus(entry1, workflowStatus.ready)
|
||||
})
|
||||
})
|
||||
})
|
17
cypress/plugins/index.js
Normal file
17
cypress/plugins/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// ***********************************************************
|
||||
// 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
cypress/support/commands.js
Normal file
25
cypress/support/commands.js
Normal file
@ -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
cypress/support/index.js
Normal file
20
cypress/support/index.js
Normal file
@ -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')
|
@ -4,7 +4,6 @@
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Netlify CMS Development Test</title>
|
||||
<script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
|
||||
<script>
|
||||
window.repoFiles = {
|
||||
_posts: {
|
||||
|
34
package.json
34
package.json
@ -1,16 +1,29 @@
|
||||
{
|
||||
"scripts": {
|
||||
"bootstrap": "yarn && lerna bootstrap",
|
||||
"start": "npm run bootstrap && npm run develop",
|
||||
"start": "run-s bootstrap develop",
|
||||
"watch": "lerna run watch --parallel",
|
||||
"develop": "lerna run develop --parallel",
|
||||
"build": "npm run clean && lerna run build",
|
||||
"build-preview": "npm run build && lerna run build-preview",
|
||||
"clean": "rimraf packages/*/dist",
|
||||
"clean": "rimraf packages/*/dist dev-test/*.js",
|
||||
"reset": "npm run clean && lerna clean --yes",
|
||||
"test": "cross-env NODE_ENV=test jest --no-cache",
|
||||
"cache-ci": "node scripts/cache.js",
|
||||
"test": "run-s jest e2e",
|
||||
"test-ci": "run-s cache-ci jest e2e-ci",
|
||||
"jest": "cross-env NODE_ENV=test jest --no-cache",
|
||||
"e2e-prep": "npm run build && cp -r packages/netlify-cms/dist dev-test/",
|
||||
"e2e-serve": "http-server dev-test",
|
||||
"e2e-exec": "cypress run",
|
||||
"e2e-exec-ci": "cypress run --record",
|
||||
"e2e-exec-dev": "cypress open",
|
||||
"e2e-run": "start-test e2e-serve 8080 e2e-exec",
|
||||
"e2e-run-ci": "start-test e2e-serve 8080 e2e-exec-ci",
|
||||
"e2e": "run-s e2e-prep e2e-run",
|
||||
"e2e-ci": "run-s e2e-prep e2e-run-ci",
|
||||
"e2e-dev": "start-test develop 8080 e2e-exec-dev",
|
||||
"dryrun": "lerna publish --skip-npm --skip-git",
|
||||
"publish": "npm run bootstrap && npm run dryrun && npm run build && git checkout . && lerna publish",
|
||||
"publish": "run-s bootstrap dryrun build && git checkout . && lerna publish",
|
||||
"add-contributor": "all-contributors add"
|
||||
},
|
||||
"browserslist": [
|
||||
@ -41,25 +54,28 @@
|
||||
"babel-plugin-transform-builtin-extend": "^1.1.2",
|
||||
"babel-plugin-transform-export-extensions": "^6.22.0",
|
||||
"babel-plugin-transform-inline-environment-variables": "^0.4.3",
|
||||
"cache-me-outside": "^0.0.4",
|
||||
"cross-env": "^5.1.4",
|
||||
"cypress": "^3.0.3",
|
||||
"deep-equal": "^1.0.1",
|
||||
"enzyme": "^3.1.0",
|
||||
"enzyme-adapter-react-16": "^1.0.2",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"http-server": "^0.11.1",
|
||||
"jest": "^23.4.0",
|
||||
"jest-cli": "^23.4.0",
|
||||
"jest-emotion": "^9.2.7",
|
||||
"lerna": "^2.11.0",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"raf": "^3.4.0",
|
||||
"react-test-renderer": "^16.0.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"start-server-and-test": "^1.7.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"svg-inline-loader": "^0.8.0"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"jest-emotion": "^9.2.6"
|
||||
}
|
||||
}
|
||||
"private": true
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
"watch": "webpack -w",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"build-preview": "cross-env NODE_ENV=production webpack-dev-server --open",
|
||||
"develop": "webpack-dev-server --hot --open"
|
||||
"develop": "webpack-dev-server --hot"
|
||||
},
|
||||
"keywords": [
|
||||
"netlify",
|
||||
|
15
scripts/cache.js
Normal file
15
scripts/cache.js
Normal file
@ -0,0 +1,15 @@
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const cache = require('cache-me-outside')
|
||||
|
||||
cache({
|
||||
cacheFolder: path.join('/', 'opt', 'build', 'cache', 'fast-cache'),
|
||||
contents: [
|
||||
{
|
||||
path: path.join(os.homedir(), '.cache', 'Cypress'),
|
||||
invalidateOn: __filename,
|
||||
command: 'echo noop',
|
||||
},
|
||||
],
|
||||
ignoreIfFolderExists: false,
|
||||
})
|
@ -42,5 +42,5 @@
|
||||
"react-sidecar": "^0.1.1",
|
||||
"smooth-scroll": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"private": true
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user