summaryrefslogtreecommitdiffstats
path: root/cypress
diff options
context:
space:
mode:
authorRobin Appelman <robin@icewind.nl>2020-10-01 17:29:37 +0200
committerRobin Appelman <robin@icewind.nl>2020-10-01 19:53:33 +0200
commitd20d14cb42b53ea5322447da28618ec2d796ebc5 (patch)
tree3d43f257016378aeb92e24c10932d3d93f5de825 /cypress
parentf77cf749c0ba5094c88e207b7b660ece8dd44cbe (diff)
add cypress testing
Signed-off-by: Robin Appelman <robin@icewind.nl>
Diffstat (limited to 'cypress')
-rw-r--r--cypress/docker-compose.yml16
-rwxr-xr-xcypress/initserver.sh10
-rw-r--r--cypress/integration/0.setup.spec.js26
-rw-r--r--cypress/integration/post.spec.js93
-rw-r--r--cypress/plugins/index.js20
-rwxr-xr-xcypress/start.sh20
-rwxr-xr-xcypress/stop.sh12
-rw-r--r--cypress/support/commands.js161
-rw-r--r--cypress/support/index.js20
-rw-r--r--cypress/utils/index.js35
10 files changed, 413 insertions, 0 deletions
diff --git a/cypress/docker-compose.yml b/cypress/docker-compose.yml
new file mode 100644
index 00000000..59712508
--- /dev/null
+++ b/cypress/docker-compose.yml
@@ -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
diff --git a/cypress/initserver.sh b/cypress/initserver.sh
new file mode 100755
index 00000000..4d997b58
--- /dev/null
+++ b/cypress/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
+"
diff --git a/cypress/integration/0.setup.spec.js b/cypress/integration/0.setup.spec.js
new file mode 100644
index 00000000..4c60f8bb
--- /dev/null
+++ b/cypress/integration/0.setup.spec.js
@@ -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')
+ })
+
+})
diff --git a/cypress/integration/post.spec.js b/cypress/integration/post.spec.js
new file mode 100644
index 00000000..1de7688f
--- /dev/null
+++ b/cypress/integration/post.spec.js
@@ -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')
+
+ })
+
+})
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
new file mode 100644
index 00000000..e1d51dd1
--- /dev/null
+++ b/cypress/plugins/index.js
@@ -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)
+}
diff --git a/cypress/start.sh b/cypress/start.sh
new file mode 100755
index 00000000..b55fef2b
--- /dev/null
+++ b/cypress/start.sh
@@ -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
diff --git a/cypress/stop.sh b/cypress/stop.sh
new file mode 100755
index 00000000..aea0338a
--- /dev/null
+++ b/cypress/stop.sh
@@ -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
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
new file mode 100644
index 00000000..283f3a19
--- /dev/null
+++ b/cypress/support/commands.js
@@ -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)
+})
diff --git a/cypress/support/index.js b/cypress/support/index.js
new file mode 100644
index 00000000..d68db96d
--- /dev/null
+++ b/cypress/support/index.js
@@ -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')
diff --git a/cypress/utils/index.js b/cypress/utils/index.js
new file mode 100644
index 00000000..cee81c0f
--- /dev/null
+++ b/cypress/utils/index.js
@@ -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 }