add cypress testing

Signed-off-by: Robin Appelman <robin@icewind.nl>
imp/composer-improvements
Robin Appelman 2020-10-01 17:29:37 +02:00
rodzic f77cf749c0
commit d20d14cb42
15 zmienionych plików z 1672 dodań i 2 usunięć

67
.github/workflows/cypress.yml vendored 100644
Wyświetl plik

@ -0,0 +1,67 @@
name: Cypress
on:
pull_request:
push:
branches:
- master
- stable*
env:
APP_NAME: social
BRANCH: ${{ github.base_ref }}
CYPRESS_baseUrl: http://127.0.0.1:8082/index.php
jobs:
cypress:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# run 2 copies of the current job in parallel
containers: [1, 2]
node-version: ['12']
php-versions: ['7.4']
name: Runner ${{ matrix.containers }}
steps:
- name: Checkout app
uses: actions/checkout@v2
- name: Setup server
run: |
cd cypress
docker-compose up -d
- name: Set up node ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies & build app
run: |
npm ci
composer install
TESTING=true npm run build --if-present
- name: Wait for server
run: |
npm install -g wait-on
wait-on -i 500 -t 240000 $CYPRESS_baseUrl
- name: Enable app & configure server
run: |
cd cypress
docker-compose exec --env APP_NAME=${{ env.APP_NAME }} -T nextcloud bash /initserver.sh
- name: Cypress run
uses: cypress-io/github-action@v1
with:
record: true
parallel: true
# cypress env
ci-build-id: ${{ github.sha }}-${{ github.run_number }}
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 }}

3
.gitignore vendored
Wyświetl plik

@ -4,3 +4,6 @@ node_modules/
vendor/
img/twemoji/
vendor/
cypress/screenshots
cypress/snapshots

7
cypress.json 100644
Wyświetl plik

@ -0,0 +1,7 @@
{
"baseUrl": "http://localhost:8082/index.php/",
"projectId": "7mqhfh",
"viewportWidth": 1280,
"viewportHeight": 720,
"defaultCommandTimeout": 6000
}

Wyświetl plik

@ -0,0 +1,16 @@
version: '3'
services:
nextcloud:
image: nextcloudci/server
ports:
- 8082:80
environment:
CYPRESS_baseUrl: "http://127.0.0.1:8082/index.php"
BRANCH: master
volumes:
- ../:/var/www/html/apps/social
- ./initserver.sh:/initserver.sh

Wyświetl plik

@ -0,0 +1,10 @@
#!/usr/bin/env bash
echo "APP_NAME: $APP_NAME"
chown -R www-data:www-data /var/www/html/data
su www-data -c "
php occ config:system:set force_language --value en
php occ app:enable $APP_NAME
php occ app:list
"

Wyświetl plik

@ -0,0 +1,26 @@
describe('Social app setup', function() {
before(function() {
cy.login('admin', 'admin')
})
it('See the welcome message', function() {
cy.visit('/apps/social/')
cy.get('.social__welcome').should('contain', 'Nextcloud becomes part of the federated social networks!')
cy.get('.social__welcome').find('.icon-close').click()
cy.get('.social__welcome').should('not.exist')
})
it('See the home section in the sidebar', function() {
cy.get('.app-navigation').contains('Home').click()
cy.get('.emptycontent').should('be.visible')
})
it('See the empty content illustration', function() {
cy.get('.app-navigation').contains('Direct messages').click()
cy.get('.emptycontent').should('be.visible').contains('No direct messages found')
cy.get('.app-navigation').contains('Profile').click()
cy.get('.emptycontent').should('be.visible').contains('You haven\'t tooted yet')
})
})

Wyświetl plik

@ -0,0 +1,93 @@
/*
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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/>.
*
*/
describe('Create posts', function() {
before(function() {
cy.visit('/apps/social/')
cy.logout()
cy.login('admin', 'admin')
cy.nextcloudCreateUser('janedoe', 'p4ssw0rd')
cy.logout()
cy.login('janedoe', 'p4ssw0rd', '/apps/social/')
cy.get('.app-content').should('be.visible')
})
afterEach(function() {
cy.screenshot()
})
it('Write a post to followers', function() {
cy.server()
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
cy.get('.new-post input[type=submit]')
.should('be.disabled')
cy.get('.new-post').find('[contenteditable]').type('Hello world')
cy.get('.new-post input[type=submit]')
.should('not.be.disabled')
cy.get('.new-post input[type=submit]')
.click()
cy.wait('@postMessage')
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world')
})
it('Write a post to followers with shift enter', function() {
cy.server()
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
cy.get('.new-post').find('[contenteditable]').type('Hello world 2{shift}{enter}')
cy.wait('@postMessage')
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world 2')
})
it('Write a post to @admin', function() {
cy.server()
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
cy.get('.new-post').find('[contenteditable]').type('@adm', {delay: 500})
cy.get('.tribute-container').should('be.visible')
cy.get('.tribute-container ul li:first').contains('admin')
cy.get('.new-post').find('[contenteditable]').type('{enter} Hello there', {delay: 100, force: true})
cy.get('.new-post input[type=submit]')
.click()
cy.wait('@postMessage')
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'admin@localhost')
})
it('Opens the menu and shows that followers is selected by default', function() {
cy.get('.new-post').find('[contenteditable]').click({force: true}).type('@adm{enter} Hello world', {delay: 500, force: true})
cy.wait(500)
cy.get('.new-post input[type=submit]').should('not.be.disabled')
const visibilityButton = cy.get('.new-post .options > div > button')
visibilityButton.should('have.class', 'icon-contacts-dark')
visibilityButton.click()
cy.get('.new-post-form .popovermenu').should('be.visible')
cy.get('.new-post-form .popovermenu .active').contains('Followers')
visibilityButton.click()
cy.get('.new-post-form .popovermenu').should('not.be.visible')
cy.get('.new-post input[type=submit]')
.click()
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello there').should('contain', 'admin@localhost')
})
})

Wyświetl plik

@ -0,0 +1,20 @@
// ***********************************************************
// 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 {
addMatchImageSnapshotPlugin
} = require('cypress-image-snapshot/plugin')
module.exports = (on, config) => {
addMatchImageSnapshotPlugin(on, config)
}

20
cypress/start.sh 100755
Wyświetl plik

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# RUN THIS SCRIPT FROM THE ROOT FOLDER OF YOUR APP
APP_NAME=${PWD##*/}
if [[ $APP_NAME == "cypress" ]]
then
echo "Please run this app from your app root folder."
else
echo "Launching docker server for the $APP_NAME app"
cd cypress
docker-compose up -d
echo -n "Waiting for server start "
until [[ $(docker-compose exec -u www-data -T nextcloud php ./occ status --output=json) == *"\"installed\":true"* ]]
do
echo -n "."
done
echo ""
docker-compose exec --env APP_NAME=$APP_NAME -T nextcloud bash /initserver.sh
docker-compose exec -u www-data -T nextcloud php ./occ social:reset -n
fi

12
cypress/stop.sh 100755
Wyświetl plik

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# RUN THIS SCRIPT FROM THE ROOT FOLDER OF YOUR APP
appname=${PWD##*/}
if [[ $appname == "cypress" ]]
then
echo "Please run this app from your app root folder."
else
echo "Killing server for the $appname app"
cd cypress
docker-compose down
fi

Wyświetl plik

@ -0,0 +1,161 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'
import axios from '@nextcloud/axios'
addMatchImageSnapshotCommand()
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
Cypress.env('baseUrl', url)
Cypress.Commands.add('login', (user, password, route = '/apps/files') => {
cy.clearCookies()
Cypress.Cookies.defaults({
preserve: /^(oc|nc)/,
})
cy.visit(route)
cy.get('input[name=user]').type(user)
cy.get('input[name=password]').type(password)
cy.get('#submit-wrapper input[type=submit]').click()
cy.url().should('include', route)
})
Cypress.Commands.add('logout', () => {
if (Cypress.$("input[name=user]").length > 0) {
// already logged out
} else {
cy.get('#expanddiv li[data-id="logout"] a').then(logout => {
if (logout) {
cy.visit(logout[0].href)
}
})
}
})
Cypress.Commands.add('nextcloudCreateUser', (user, password) => {
cy.clearCookies()
cy.request({
method: 'POST',
url: `${Cypress.env('baseUrl')}/ocs/v1.php/cloud/users?format=json`,
form: true,
body: {
userid: user,
password,
},
auth: { user: 'admin', pass: 'admin' },
headers: {
'OCS-ApiRequest': 'true',
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${btoa('admin:admin')}`,
},
}).then(response => {
cy.log(`Created user ${user}`, response.status)
})
})
Cypress.Commands.add('uploadFile', (fileName, mimeType, path = '') => {
// get fixture
return cy.fixture(fileName, 'base64').then(file => {
// convert the logo base64 string to a blob
const blob = Cypress.Blob.base64StringToBlob(file, mimeType)
try {
const file = new File([blob], fileName, { type: mimeType })
return cy.window().then(async window => {
await axios.put(`${Cypress.env('baseUrl')}/remote.php/webdav${path}/${fileName}`, file, {
headers: {
requesttoken: window.OC.requestToken,
'Content-Type': mimeType,
}
}).then(response => {
cy.log(`Uploaded ${fileName}`, response)
})
})
} catch (error) {
cy.log(error)
throw new Error(`Unable to process file ${fileName}`)
}
})
})
Cypress.Commands.add('createFolder', dirName => {
cy.get('#controls .actions > .button.new').click()
cy.get('#controls .actions .newFileMenu a[data-action="folder"]').click()
cy.get('#controls .actions .newFileMenu a[data-action="folder"] input[type="text"]').type(dirName)
cy.get('#controls .actions .newFileMenu a[data-action="folder"] input.icon-confirm').click()
cy.log('Created folder', dirName)
})
Cypress.Commands.add('openFile', fileName => {
cy.get(`#fileList tr[data-file="${fileName}"] a.name`).click()
cy.wait(250)
})
Cypress.Commands.add('getFileId', fileName => {
return cy.get(`#fileList tr[data-file="${fileName}"]`)
.should('have.attr', 'data-id')
})
Cypress.Commands.add('deleteFile', fileName => {
cy.get(`#fileList tr[data-file="${fileName}"] a.name .action-menu`).click()
cy.get(`#fileList tr[data-file="${fileName}"] a.name + .popovermenu .action-delete`).click()
})
/**
* Create a share link and return the share url
*
* @param {string} path the file/folder path
* @returns {string} the share link url
*/
Cypress.Commands.add('createLinkShare', path => {
return cy.window().then(async window => {
try {
const request = await axios.post(`${Cypress.env('baseUrl')}/ocs/v2.php/apps/files_sharing/api/v1/shares`, {
path,
shareType: window.OC.Share.SHARE_TYPE_LINK,
}, {
headers: {
requesttoken: window.OC.requestToken,
}
})
if (!('ocs' in request.data) || !('token' in request.data.ocs.data && request.data.ocs.data.token.length > 0)) {
throw request
}
cy.log('Share link created', request.data.ocs.data.token)
return cy.wrap(request.data.ocs.data.token)
} catch(error) {
console.error(error)
}
}).should('have.length', 15)
})
Cypress.Commands.overwrite('matchImageSnapshot', (originalFn, subject, name, options) => {
// hide avatar because random colour break the visual regression tests
cy.window().then(window => {
const avatarDiv = window.document.querySelector('.avatardiv')
if (avatarDiv) {
avatarDiv.remove()
}
})
return originalFn(subject, name, options)
})

Wyświetl plik

@ -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')

Wyświetl plik

@ -0,0 +1,35 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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/>.
*
*/
const getSearchParams = url => {
return url
.split(/[?&]/)
.reduce((acc, cur) => {
const parts = cur.split('=')
parts[1] && (acc[parts[0]] = parts[1])
return acc
}, {})
}
const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10)
export default { getSearchParams, randHash }

1175
package-lock.json wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -24,7 +24,9 @@
"lint": "eslint --ext .js,.vue src",
"lint:fix": "eslint --ext .js,.vue src --fix",
"test": "jest",
"test:coverage": "jest --coverage"
"test:coverage": "jest --coverage",
"cypress": "cypress run",
"cypress:gui": "cypress open"
},
"dependencies": {
"@babel/runtime": "^7.11.2",
@ -63,15 +65,18 @@
"@babel/core": "^7.11.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.11.5",
"@vue/test-utils": "^1.1.0",
"@nextcloud/browserslist-config": "^1.0.0",
"@nextcloud/eslint-config": "^2.1.0",
"@nextcloud/eslint-plugin": "^1.4.0",
"@vue/test-utils": "^1.1.0",
"acorn": "^8.0.1",
"babel-eslint": "^10.0.3",
"babel-jest": "^26.3.0",
"babel-loader": "^8.0.6",
"css-loader": "^4.3.0",
"cypress": "^5.3.0",
"cypress-image-snapshot": "^3.1.1",
"cypress-testing-library": "^4.0.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^12.0.0",
"eslint-friendly-formatter": "^4.0.1",