diff options
author | John Molakvoæ <skjnldsv@protonmail.com> | 2022-10-20 16:03:19 +0200 |
---|---|---|
committer | John Molakvoæ <skjnldsv@protonmail.com> | 2022-11-29 11:23:05 +0100 |
commit | 064fa10ecfe4725398895a21ab8bafd171e2eadd (patch) | |
tree | 5f2d8124eb131a65eac207edee560c49ea7835f3 /cypress | |
parent | cedae7c6d74e11c8aaa59b09a38db04dbebc818d (diff) | |
download | nextcloud-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.ts | 243 | ||||
-rw-r--r-- | cypress/e2e/files.cy.ts | 37 | ||||
-rw-r--r-- | cypress/e2e/theming/user-background.cy.ts | 164 | ||||
-rw-r--r-- | cypress/fixtures/image.jpg | bin | 0 -> 1538878 bytes | |||
-rw-r--r-- | cypress/support/commands.ts | 86 | ||||
-rw-r--r-- | cypress/support/e2e.ts | 22 | ||||
-rw-r--r-- | cypress/tsconfig.json | 7 |
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 Binary files differnew file mode 100644 index 00000000000..46dac8cc283 --- /dev/null +++ b/cypress/fixtures/image.jpg 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"], + } +} |