Add visual regression testing with cypress
Signed-off-by: Raimund Schlüßler <raimund.schluessler@mailbox.org>
|
@ -0,0 +1,53 @@
|
|||
name: Cypress
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
env:
|
||||
APP_NAME: nextcloud-vue
|
||||
|
||||
jobs:
|
||||
cypress:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# run x copies of the current job in parallel
|
||||
containers: [1]
|
||||
node-version: ['12']
|
||||
|
||||
name: Runner ${{ matrix.containers }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v1
|
||||
with:
|
||||
record: true
|
||||
parallel: true
|
||||
tag: ${{ github.event_name }}
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
# https://github.com/cypress-io/github-action/issues/124
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||
|
||||
- name: Upload snapshots
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: snapshots
|
||||
path: cypress/snapshots
|
|
@ -25,3 +25,8 @@ coverage
|
|||
# Docs compiled build
|
||||
styleguide/build/
|
||||
styleguide/index.html
|
||||
|
||||
# Cypress files
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
cypress/snapshots/actual
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"projectId": "3paxvy",
|
||||
"viewportWidth": 1920,
|
||||
"viewportHeight": 1080,
|
||||
"defaultCommandTimeout": 6000,
|
||||
"experimentalComponentTesting": true,
|
||||
"componentFolder": "tests/visual",
|
||||
"nodeVersion": "system",
|
||||
"env": {
|
||||
"failSilently": false,
|
||||
"type": "actual"
|
||||
},
|
||||
"screenshotsFolder": "cypress/snapshots/actual",
|
||||
"trashAssetsBeforeRuns": true
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// ***********************************************************
|
||||
// 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)
|
||||
|
||||
const getCompareSnapshotsPlugin = require('cypress-visual-regression/dist/plugin')
|
||||
|
||||
const webpack = require('@cypress/webpack-preprocessor')
|
||||
|
||||
const webpackOptions = require('../../webpack.dev.js')
|
||||
webpackOptions.externals = {}
|
||||
|
||||
const options = {
|
||||
// send in the options from your webpack.config.js, so it works the same
|
||||
// as your app's code
|
||||
webpackOptions,
|
||||
watchOptions: {},
|
||||
}
|
||||
|
||||
module.exports = (on, config) => {
|
||||
getCompareSnapshotsPlugin(on)
|
||||
on('file:preprocessor', webpack(options))
|
||||
}
|
После Ширина: | Высота: | Размер: 7.7 KiB |
После Ширина: | Высота: | Размер: 6.6 KiB |
После Ширина: | Высота: | Размер: 5.7 KiB |
После Ширина: | Высота: | Размер: 4.7 KiB |
После Ширина: | Высота: | Размер: 5.0 KiB |
После Ширина: | Высота: | Размер: 4.8 KiB |
После Ширина: | Высота: | Размер: 5.7 KiB |
После Ширина: | Высота: | Размер: 4.7 KiB |
После Ширина: | Высота: | Размер: 7.4 KiB |
После Ширина: | Высота: | Размер: 6.3 KiB |
После Ширина: | Высота: | Размер: 5.4 KiB |
После Ширина: | Высота: | Размер: 4.4 KiB |
После Ширина: | Высота: | Размер: 4.9 KiB |
После Ширина: | Высота: | Размер: 4.7 KiB |
После Ширина: | Высота: | Размер: 5.4 KiB |
После Ширина: | Высота: | Размер: 4.4 KiB |
После Ширина: | Высота: | Размер: 7.7 KiB |
После Ширина: | Высота: | Размер: 6.5 KiB |
После Ширина: | Высота: | Размер: 5.7 KiB |
После Ширина: | Высота: | Размер: 4.7 KiB |
После Ширина: | Высота: | Размер: 5.2 KiB |
После Ширина: | Высота: | Размер: 5.0 KiB |
После Ширина: | Высота: | Размер: 5.7 KiB |
После Ширина: | Высота: | Размер: 4.7 KiB |
После Ширина: | Высота: | Размер: 5.8 KiB |
После Ширина: | Высота: | Размер: 5.2 KiB |
После Ширина: | Высота: | Размер: 4.1 KiB |
После Ширина: | Высота: | Размер: 3.5 KiB |
После Ширина: | Высота: | Размер: 3.7 KiB |
После Ширина: | Высота: | Размер: 3.6 KiB |
После Ширина: | Высота: | Размер: 4.1 KiB |
После Ширина: | Высота: | Размер: 3.5 KiB |
После Ширина: | Высота: | Размер: 5.6 KiB |
После Ширина: | Высота: | Размер: 5.0 KiB |
После Ширина: | Высота: | Размер: 3.9 KiB |
После Ширина: | Высота: | Размер: 3.3 KiB |
После Ширина: | Высота: | Размер: 3.7 KiB |
После Ширина: | Высота: | Размер: 3.5 KiB |
После Ширина: | Высота: | Размер: 3.9 KiB |
После Ширина: | Высота: | Размер: 3.3 KiB |
После Ширина: | Высота: | Размер: 5.8 KiB |
После Ширина: | Высота: | Размер: 5.2 KiB |
После Ширина: | Высота: | Размер: 4.0 KiB |
После Ширина: | Высота: | Размер: 3.5 KiB |
После Ширина: | Высота: | Размер: 3.9 KiB |
После Ширина: | Высота: | Размер: 3.8 KiB |
После Ширина: | Высота: | Размер: 4.0 KiB |
После Ширина: | Высота: | Размер: 3.5 KiB |
|
@ -0,0 +1,3 @@
|
|||
const compareSnapshotCommand = require('cypress-visual-regression/dist/command')
|
||||
|
||||
compareSnapshotCommand()
|
|
@ -0,0 +1,17 @@
|
|||
// ***********************************************************
|
||||
// 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 'cypress-vue-unit-test/dist/support'
|
||||
import './commands'
|
10
package.json
|
@ -23,7 +23,10 @@
|
|||
"stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css",
|
||||
"stylelint:fix": "stylelint src/**/*.vue src/**/*.scss src/**/*.css --fix",
|
||||
"styleguide": "vue-styleguidist server",
|
||||
"styleguide:build": "vue-styleguidist build"
|
||||
"styleguide:build": "vue-styleguidist build",
|
||||
"cypress": "cypress run",
|
||||
"cypress:gui": "cypress open",
|
||||
"cypress:update-snapshots": "cypress run --env type=base --config screenshotsFolder=cypress/snapshots/base"
|
||||
},
|
||||
"main": "dist/ncvuecomponents.js",
|
||||
"files": [
|
||||
|
@ -70,15 +73,20 @@
|
|||
"babel-jest": "^26.3.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"css-loader": "^3.5.2",
|
||||
"cypress": "^5.0.0",
|
||||
"cypress-visual-regression": "^1.5.0",
|
||||
"cypress-vue-unit-test": "^3.5.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-cypress": "^2.11.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"file-loader": "^6.0.0",
|
||||
"fontsource-roboto": "^3.0.3",
|
||||
"gettext-extractor": "^3.5.2",
|
||||
"gettext-parser": "^4.0.3",
|
||||
"glob": "^7.1.6",
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
jest: true,
|
||||
"cypress/globals": true
|
||||
},
|
||||
extends: [
|
||||
"plugin:cypress/recommended"
|
||||
],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2020 Raimund Schlüßler <raimund.schluessler@mailbox.org>
|
||||
*
|
||||
* @author Raimund Schlüßler <raimund.schluessler@mailbox.org>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import { mount } from 'cypress-vue-unit-test'
|
||||
|
||||
// import Vue from 'vue'
|
||||
import AppSidebar from '../../../../src/components/AppSidebar/AppSidebar.vue'
|
||||
import ActionButton from '../../../../src/components/ActionButton/ActionButton.vue'
|
||||
|
||||
// Server CSS styles
|
||||
import server from '../../../../styleguide/assets/server.css'
|
||||
import icons from '../../../../styleguide/assets/icons.css'
|
||||
import variables from '../../../../styleguide/assets/variables.css'
|
||||
|
||||
// Import font so CI has the same
|
||||
import 'fontsource-roboto'
|
||||
|
||||
describe('AppSidebar.vue', () => {
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* We need this custom style because we run the AppSidebar component without a Content component,
|
||||
* which applies this rule:
|
||||
* https://github.com/nextcloud/nextcloud-vue/blob/master/src/components/Content/Content.vue#L73-L75
|
||||
*/
|
||||
const style = `
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`
|
||||
// Load the server CSS styles
|
||||
const cssFiles = [server, icons, variables]
|
||||
|
||||
// Possible props and actions
|
||||
const starred = [null, false, true]
|
||||
const compact = [false, true]
|
||||
const header = ['', '<div style="background: no-repeat center/contain var(--icon-folder-000); height: 100%;" />']
|
||||
const secondary = ['', '<ActionButton icon="icon-delete">Action1</ActionButton><ActionButton icon="icon-delete">Action2</ActionButton>']
|
||||
|
||||
const components = {
|
||||
ActionButton,
|
||||
}
|
||||
// extend Vue with global components
|
||||
const extensions = {
|
||||
components,
|
||||
}
|
||||
|
||||
starred.forEach(star => {
|
||||
compact.forEach(comp => {
|
||||
header.forEach(head => {
|
||||
secondary.forEach(second => {
|
||||
const fileName = `AppSidebar.vue
|
||||
- starred_${star === null ? 'null' : star ? 'true' : 'false'}
|
||||
- compact_${comp ? 'true' : 'false'}
|
||||
- header_${head ? 'image' : 'none'}
|
||||
- secondary_${second ? 'button' : 'none'}
|
||||
`.replace(/(\n|\t)/gi, '')
|
||||
|
||||
const defaultOptions = {
|
||||
propsData: {
|
||||
title: 'Sidebar title.',
|
||||
subtitle: 'subtitle',
|
||||
starred: star,
|
||||
compact: comp,
|
||||
},
|
||||
slots: {
|
||||
default: ['<div />'],
|
||||
header: head,
|
||||
'secondary-actions': second,
|
||||
},
|
||||
style,
|
||||
cssFiles,
|
||||
extensions,
|
||||
}
|
||||
|
||||
it('Renders ' + fileName, () => {
|
||||
mount(AppSidebar, defaultOptions)
|
||||
cy.get('.app-sidebar-header').compareSnapshot(fileName)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -13,7 +13,8 @@ const { DefinePlugin } = require('webpack')
|
|||
const nodeExternals = require('webpack-node-externals')
|
||||
|
||||
// scope variable
|
||||
const appVersion = JSON.stringify(process.env.npm_package_version)
|
||||
// fallback for cypress testing
|
||||
const appVersion = JSON.stringify(process.env.npm_package_version || 'nextcloud-vue')
|
||||
const versionHash = md5(appVersion).substr(0, 7)
|
||||
const SCOPE_VERSION = JSON.stringify(versionHash)
|
||||
const ICONFONT_NAME = `iconfont-vue-${versionHash}`
|
||||
|
|