kopia lustrzana https://github.com/nextcloud/social
add cypress testing
Signed-off-by: Robin Appelman <robin@icewind.nl>pull/243/head
rodzic
f77cf749c0
commit
d20d14cb42
|
@ -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 }}
|
|
@ -4,3 +4,6 @@ node_modules/
|
|||
vendor/
|
||||
img/twemoji/
|
||||
vendor/
|
||||
|
||||
cypress/screenshots
|
||||
cypress/snapshots
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:8082/index.php/",
|
||||
"projectId": "7mqhfh",
|
||||
"viewportWidth": 1280,
|
||||
"viewportHeight": 720,
|
||||
"defaultCommandTimeout": 6000
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
"
|
|
@ -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')
|
||||
})
|
||||
|
||||
})
|
|
@ -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')
|
||||
|
||||
})
|
||||
|
||||
})
|
|
@ -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)
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
})
|
|
@ -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')
|
|
@ -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 }
|
Plik diff jest za duży
Load Diff
|
@ -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",
|
||||
|
|
Ładowanie…
Reference in New Issue