aboutsummaryrefslogtreecommitdiffstats
path: root/cypress
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2022-10-20 16:03:19 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2022-11-29 11:23:05 +0100
commit064fa10ecfe4725398895a21ab8bafd171e2eadd (patch)
tree5f2d8124eb131a65eac207edee560c49ea7835f3 /cypress
parentcedae7c6d74e11c8aaa59b09a38db04dbebc818d (diff)
downloadnextcloud-server-064fa10ecfe4725398895a21ab8bafd171e2eadd.tar.gz
nextcloud-server-064fa10ecfe4725398895a21ab8bafd171e2eadd.zip
Extract colour from custom background
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'cypress')
-rw-r--r--cypress/dockerNode.ts243
-rw-r--r--cypress/e2e/files.cy.ts37
-rw-r--r--cypress/e2e/theming/user-background.cy.ts164
-rw-r--r--cypress/fixtures/image.jpgbin0 -> 1538878 bytes
-rw-r--r--cypress/support/commands.ts86
-rw-r--r--cypress/support/e2e.ts22
-rw-r--r--cypress/tsconfig.json7
7 files changed, 559 insertions, 0 deletions
diff --git a/cypress/dockerNode.ts b/cypress/dockerNode.ts
new file mode 100644
index 00000000000..13e75c605b2
--- /dev/null
+++ b/cypress/dockerNode.ts
@@ -0,0 +1,243 @@
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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/>.
+ *
+ */
+/* eslint-disable no-console */
+/* eslint-disable node/no-unpublished-import */
+
+import Docker from 'dockerode'
+import waitOn from 'wait-on'
+import tar from 'tar'
+
+export const docker = new Docker()
+
+const CONTAINER_NAME = 'nextcloud-cypress-tests-server'
+const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server'
+
+/**
+ * Start the testing container
+ *
+ * @param {string} branch the branch of your current work
+ */
+export const startNextcloud = async function(branch: string = 'master'): Promise<any> {
+
+ try {
+ // Pulling images
+ console.log('\nPulling images... ⏳')
+ await new Promise((resolve, reject): any => docker.pull(SERVER_IMAGE, (err, stream) => {
+ if (err) {
+ reject(err)
+ }
+ // https://github.com/apocas/dockerode/issues/357
+ docker.modem.followProgress(stream, onFinished)
+
+ function onFinished(err) {
+ if (!err) {
+ resolve(true)
+ return
+ }
+ reject(err)
+ }
+ }))
+ console.log('└─ Done')
+
+ // Remove old container if exists
+ console.log('\nChecking running containers... 🔍')
+ try {
+ const oldContainer = docker.getContainer(CONTAINER_NAME)
+ const oldContainerData = await oldContainer.inspect()
+ if (oldContainerData) {
+ console.log('├─ Existing running container found')
+ console.log('├─ Removing... ⏳')
+ // Forcing any remnants to be removed just in case
+ await oldContainer.remove({ force: true })
+ console.log('└─ Done')
+ }
+ } catch (error) {
+ console.log('└─ None found!')
+ }
+
+ // Starting container
+ console.log('\nStarting Nextcloud container... 🚀')
+ console.log(`├─ Using branch '${branch}'`)
+ const container = await docker.createContainer({
+ Image: SERVER_IMAGE,
+ name: CONTAINER_NAME,
+ HostConfig: {
+ Binds: [],
+ },
+ })
+ await container.start()
+
+ // Get container's IP
+ const ip = await getContainerIP(container)
+
+ console.log(`├─ Nextcloud container's IP is ${ip} 🌏`)
+ return ip
+ } catch (err) {
+ console.log('└─ Unable to start the container 🛑')
+ console.log(err)
+ stopNextcloud()
+ throw new Error('Unable to start the container')
+ }
+}
+
+/**
+ * Configure Nextcloud
+ */
+export const configureNextcloud = async function() {
+ console.log('\nConfiguring nextcloud...')
+ const container = docker.getContainer(CONTAINER_NAME)
+ await runExec(container, ['php', 'occ', '--version'], true)
+
+ // Be consistent for screenshots
+ await runExec(container, ['php', 'occ', 'config:system:set', 'default_language', '--value', 'en'], true)
+ await runExec(container, ['php', 'occ', 'config:system:set', 'force_language', '--value', 'en'], true)
+ await runExec(container, ['php', 'occ', 'config:system:set', 'default_locale', '--value', 'en_US'], true)
+ await runExec(container, ['php', 'occ', 'config:system:set', 'force_locale', '--value', 'en_US'], true)
+ await runExec(container, ['php', 'occ', 'config:system:set', 'enforce_theme', '--value', 'light'], true)
+
+ // Enable the app and give status
+ await runExec(container, ['php', 'occ', 'app:enable', '--force', 'viewer'], true)
+ // await runExec(container, ['php', 'occ', 'app:list'], true)
+
+ console.log('└─ Nextcloud is now ready to use 🎉')
+}
+
+/**
+ * Applying local changes to the container
+ * Only triggered if we're not in CI. Otherwise the
+ * continuous-integration-shallow-server image will
+ * already fetch the proper branch.
+ */
+export const applyChangesToNextcloud = async function() {
+ console.log('\nApply local changes to nextcloud...')
+ const container = docker.getContainer(CONTAINER_NAME)
+
+ const htmlPath = '/var/www/html'
+ const folderPaths = [
+ './apps',
+ './core',
+ './dist',
+ './lib',
+ './ocs',
+ ]
+
+ // Tar-streaming the above folder sinto the container
+ const serverTar = tar.c({ gzip: false }, folderPaths)
+ await container.putArchive(serverTar, {
+ path: htmlPath,
+ })
+
+ // Making sure we have the proper permissions
+ await runExec(container, ['chown', '-R', 'www-data:www-data', htmlPath], false, 'root')
+
+ console.log('└─ Changes applied successfully 🎉')
+}
+
+/**
+ * Force stop the testing container
+ */
+export const stopNextcloud = async function() {
+ try {
+ const container = docker.getContainer(CONTAINER_NAME)
+ console.log('Stopping Nextcloud container...')
+ container.remove({ force: true })
+ console.log('└─ Nextcloud container removed 🥀')
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+/**
+ * Get the testing container's IP
+ *
+ * @param {Docker.Container} container the container to get the IP from
+ */
+export const getContainerIP = async function(
+ container = docker.getContainer(CONTAINER_NAME)
+): Promise<string> {
+ let ip = ''
+ let tries = 0
+ while (ip === '' && tries < 10) {
+ tries++
+
+ await container.inspect(function(err, data) {
+ if (err) {
+ throw err
+ }
+ ip = data?.NetworkSettings?.IPAddress || ''
+ })
+
+ if (ip !== '') {
+ break
+ }
+
+ await sleep(1000 * tries)
+ }
+
+ return ip
+}
+
+// Would be simpler to start the container from cypress.config.ts,
+// but when checking out different branches, it can take a few seconds
+// Until we can properly configure the baseUrl retry intervals,
+// We need to make sure the server is already running before cypress
+// https://github.com/cypress-io/cypress/issues/22676
+export const waitOnNextcloud = async function(ip: string) {
+ console.log('├─ Waiting for Nextcloud to be ready... ⏳')
+ await waitOn({ resources: [`http://${ip}/index.php`] })
+ console.log('└─ Done')
+}
+
+const runExec = async function(
+ container: Docker.Container,
+ command: string[],
+ verbose = false,
+ user = 'www-data'
+) {
+ const exec = await container.exec({
+ Cmd: command,
+ AttachStdout: true,
+ AttachStderr: true,
+ User: user,
+ })
+
+ return new Promise((resolve, reject) => {
+ exec.start({}, (err, stream) => {
+ if (err) {
+ reject(err)
+ }
+ if (stream) {
+ stream.setEncoding('utf-8')
+ stream.on('data', str => {
+ if (verbose && str.trim() !== '') {
+ console.log(`├─ ${str.trim().replace(/\n/gi, '\n├─ ')}`)
+ }
+ })
+ stream.on('end', resolve)
+ }
+ })
+ })
+}
+
+const sleep = function(milliseconds: number) {
+ return new Promise((resolve) => setTimeout(resolve, milliseconds))
+}
diff --git a/cypress/e2e/files.cy.ts b/cypress/e2e/files.cy.ts
new file mode 100644
index 00000000000..7b3a4ff7a56
--- /dev/null
+++ b/cypress/e2e/files.cy.ts
@@ -0,0 +1,37 @@
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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('Login with a new user and open the files app', function() {
+ before(function() {
+ cy.createRandomUser().then((user) => {
+ cy.login(user)
+ })
+ })
+
+ after(function() {
+ cy.logout()
+ })
+
+ it('See the default file welcome.txt in the files list', function() {
+ cy.visit('/apps/files')
+ cy.get('.files-fileList tr').should('contain', 'welcome.txt')
+ })
+})
diff --git a/cypress/e2e/theming/user-background.cy.ts b/cypress/e2e/theming/user-background.cy.ts
new file mode 100644
index 00000000000..aaa20134471
--- /dev/null
+++ b/cypress/e2e/theming/user-background.cy.ts
@@ -0,0 +1,164 @@
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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 type { User } from '@nextcloud/cypress'
+
+const defaultPrimary = '#006aa3'
+const defaultBackground = 'kamil-porembinski-clouds.jpg'
+
+const validateThemingCss = function(expectedPrimary = '#0082c9', expectedBackground = 'kamil-porembinski-clouds.jpg', bright = false) {
+ return cy.window().then((win) => {
+ const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary')
+ const background = getComputedStyle(win.document.body).getPropertyValue('--image-background')
+ const invertIfBright = getComputedStyle(win.document.body).getPropertyValue('--background-image-invert-if-bright')
+
+ // Returning boolean for cy.waitUntil usage
+ return primary === expectedPrimary
+ && background.includes(expectedBackground)
+ && invertIfBright === (bright ? 'invert(100%)' : 'no')
+ })
+}
+
+describe('User default background settings', function() {
+ before(function() {
+ cy.createRandomUser().then((user: User) => {
+ cy.login(user)
+ })
+ })
+
+ it('See the user background settings', function() {
+ cy.visit('/settings/user/theming')
+ cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
+ })
+
+ // Default cloud background is not rendered if admin theming background remains unchanged
+ it('Default cloud background is not rendered', function() {
+ cy.get(`[data-user-theming-background-shipped="${defaultBackground}"]`).should('not.exist')
+ })
+
+ it('Default is selected on new users', function() {
+ cy.get('[data-user-theming-background-default]').should('be.visible')
+ cy.get('[data-user-theming-background-default]').should('have.class', 'background--active')
+ })
+})
+
+describe('User select shipped backgrounds', function() {
+ before(function() {
+ cy.createRandomUser().then((user: User) => {
+ cy.login(user)
+ })
+ })
+
+ it('See the user background settings', function() {
+ cy.visit('/settings/user/theming')
+ cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
+ })
+
+ it('Select a shipped background', function() {
+ const background = 'anatoly-mikhaltsov-butterfly-wing-scale.jpg'
+ cy.intercept('*/apps/theming/background/shipped').as('setBackground')
+
+ // Select background
+ cy.get(`[data-user-theming-background-shipped="${background}"]`).click()
+
+ // Validate changed background and primary
+ cy.wait('@setBackground')
+ cy.waitUntil(() => validateThemingCss('#a53c17', background))
+ })
+
+ it('Select a bright shipped background', function() {
+ const background = 'bernie-cetonia-aurata-take-off-composition.jpg'
+ cy.intercept('*/apps/theming/background/shipped').as('setBackground')
+
+ // Select background
+ cy.get(`[data-user-theming-background-shipped="${background}"]`).click()
+
+ // Validate changed background and primary
+ cy.wait('@setBackground')
+ cy.waitUntil(() => validateThemingCss('#56633d', background, true))
+ })
+
+ it('Remove background', function() {
+ cy.intercept('*/apps/theming/background/custom').as('clearBackground')
+
+ // Clear background
+ cy.get('[data-user-theming-background-clear]').click()
+
+ // Validate clear background
+ cy.wait('@clearBackground')
+ cy.waitUntil(() => validateThemingCss('#56633d', ''))
+ })
+})
+
+describe('User select a custom color', function() {
+ before(function() {
+ cy.createRandomUser().then((user: User) => {
+ cy.login(user)
+ })
+ })
+
+ it('See the user background settings', function() {
+ cy.visit('/settings/user/theming')
+ cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
+ })
+
+ it('Select a custom color', function() {
+ cy.intercept('*/apps/theming/background/color').as('setColor')
+
+ cy.get('[data-user-theming-background-color]').click()
+ cy.get('.color-picker__simple-color-circle:eq(3)').click()
+
+ // Validate clear background
+ cy.wait('@setColor')
+ cy.waitUntil(() => cy.window().then((win) => {
+ const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary')
+ return primary !== defaultPrimary
+ }))
+ })
+})
+
+describe('User select a custom background', function() {
+ const image = 'image.jpg'
+ before(function() {
+ cy.createRandomUser().then((user: User) => {
+ cy.uploadFile(user, image, 'image/jpeg')
+ cy.login(user)
+ })
+ })
+
+ it('See the user background settings', function() {
+ cy.visit('/settings/user/theming')
+ cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
+ })
+
+ it('Select a custom background', function() {
+ cy.intercept('*/apps/theming/background/custom').as('setBackground')
+
+ // Pick background
+ cy.get('[data-user-theming-background-custom]').click()
+ cy.get(`#picker-filestable tr[data-entryname="${image}"]`).click()
+ cy.get('#oc-dialog-filepicker-content ~ .oc-dialog-buttonrow button.primary').click()
+
+ // Wait for background to be set
+ cy.wait('@setBackground')
+ cy.waitUntil(() => validateThemingCss('#4c0c04', 'apps/theming/background?v='))
+ })
+})
diff --git a/cypress/fixtures/image.jpg b/cypress/fixtures/image.jpg
new file mode 100644
index 00000000000..46dac8cc283
--- /dev/null
+++ b/cypress/fixtures/image.jpg
Binary files differ
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
new file mode 100644
index 00000000000..2b51f719452
--- /dev/null
+++ b/cypress/support/commands.ts
@@ -0,0 +1,86 @@
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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/>.
+ *
+ */
+/* eslint-disable node/no-unpublished-import */
+import axios from '@nextcloud/axios'
+import { addCommands, type User} from '@nextcloud/cypress'
+import { basename } from 'path'
+
+// Add custom commands
+import 'cypress-wait-until'
+addCommands()
+
+// Register this file's custom commands types
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Cypress {
+ interface Chainable<Subject = any> {
+ uploadFile(user: User, fixture: string, mimeType: string, target ?: string): Cypress.Chainable<void>
+ }
+ }
+}
+
+const url = (Cypress.config('baseUrl') || '').replace(/\/index.php\/?$/g, '')
+Cypress.env('baseUrl', url)
+
+/**
+ * cy.uploadedFile - uploads a file from the fixtures folder
+ * TODO: standardise in @nextcloud/cypress
+ *
+ * @param {User} user the owner of the file, e.g. admin
+ * @param {string} fixture the fixture file name, e.g. image1.jpg
+ * @param {string} mimeType e.g. image/png
+ * @param {string} [target] the target of the file relative to the user root
+ */
+Cypress.Commands.add('uploadFile', (user, fixture, mimeType, target = `/${fixture}`) => {
+ cy.clearCookies()
+ const fileName = basename(target)
+
+ // get fixture
+ return cy.fixture(fixture, 'base64').then(async file => {
+ // convert the base64 string to a blob
+ const blob = Cypress.Blob.base64StringToBlob(file, mimeType)
+
+ // Process paths
+ const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}`
+ const filePath = target.split('/').map(encodeURIComponent).join('/')
+ try {
+ const file = new File([blob], fileName, { type: mimeType })
+ await axios({
+ url: `${rootPath}${filePath}`,
+ method: 'PUT',
+ data: file,
+ headers: {
+ 'Content-Type': mimeType,
+ },
+ auth: {
+ username: user.userId,
+ password: user.password,
+ },
+ }).then(response => {
+ cy.log(`Uploaded ${fixture} as ${fileName}`, response)
+ })
+ } catch (error) {
+ cy.log('error', error)
+ throw new Error(`Unable to process fixture ${fixture}`)
+ }
+ })
+})
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
new file mode 100644
index 00000000000..ad3b70e8910
--- /dev/null
+++ b/cypress/support/e2e.ts
@@ -0,0 +1,22 @@
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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 './commands' \ No newline at end of file
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
new file mode 100644
index 00000000000..7aeacf0778e
--- /dev/null
+++ b/cypress/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../tsconfig.json",
+ "include": ["./**/*.ts"],
+ "compilerOptions": {
+ "types": ["cypress", "dockerode", "cypress-wait-until"],
+ }
+}