diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 0ea49ec60..73a281772 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -112,14 +112,16 @@ jobs: - name: Set up npm ${{ needs.init.outputs.npmVersion }} run: npm i -g 'npm@${{ needs.init.outputs.npmVersion }}' + - name: Install cypress + run: ./node_modules/cypress/bin/cypress install + - name: Run ${{ matrix.containers == 'component' && 'component' || 'E2E' }} cypress tests uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2 with: - record: ${{ secrets.CYPRESS_RECORD_KEY && true }} - parallel: ${{ secrets.CYPRESS_RECORD_KEY && true }} - # cypress run type + # We already installed the dependencies in the init job + install: false component: ${{ matrix.containers == 'component' }} - group: ${{ secrets.CYPRESS_RECORD_KEY && env.CYPRESS_GROUP }} + group: ${{ matrix.use-cypress-cloud && matrix.containers == 'component' && 'Run component' || matrix.use-cypress-cloud && 'Run E2E' || '' }} # cypress env ci-build-id: ${{ secrets.CYPRESS_RECORD_KEY && env.CYPRESS_BUILD_ID }} tag: ${{ secrets.CYPRESS_RECORD_KEY && github.event_name }} diff --git a/cypress.config.ts b/cypress.config.ts index 5c6693293..779214604 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,5 +1,4 @@ import { configureNextcloud, startNextcloud, stopNextcloud, waitOnNextcloud } from '@nextcloud/cypress/docker' -import { configureVisualRegression } from 'cypress-visual-regression/dist/plugin' import { defineConfig } from 'cypress' import cypressSplit from 'cypress-split' @@ -27,14 +26,6 @@ export default defineConfig({ // https://github.com/cypress-io/cypress/issues/871 scrollBehavior: 'center', - // Visual regression testing - env: { - failSilently: false, - visualRegressionType: 'regression', - }, - screenshotsFolder: 'cypress/snapshots/actual', - trashAssetsBeforeRuns: true, - e2e: { // Disable session isolation testIsolation: false, @@ -43,25 +34,6 @@ export default defineConfig({ // You may want to clean this up later by importing these. async setupNodeEvents(on, config) { cypressSplit(on, config) - configureVisualRegression(on) - - // Disable spell checking to prevent rendering differences - on('before:browser:launch', (browser, launchOptions) => { - if (browser.family === 'chromium' && browser.name !== 'electron') { - launchOptions.preferences.default['browser.enable_spellchecking'] = false - return launchOptions - } - - if (browser.family === 'firefox') { - launchOptions.preferences['layout.spellcheckDefault'] = 0 - return launchOptions - } - - if (browser.name === 'electron') { - launchOptions.preferences.spellcheck = false - return launchOptions - } - }) // Remove container after run on('after:run', () => { @@ -76,7 +48,7 @@ export default defineConfig({ // Setting container's IP as base Url config.baseUrl = `http://${ip}/index.php` await waitOnNextcloud(ip) - await configureNextcloud([]) // pass empty array as WE are already the viewer + await configureNextcloud(['groupfolders']) // pass empty array as WE are already the viewer return config }, }, diff --git a/cypress/dockerNode.ts b/cypress/dockerNode.ts deleted file mode 100644 index 9effe4b56..000000000 --- a/cypress/dockerNode.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -/* eslint-disable no-console */ -import Docker from 'dockerode' -import path from 'path' -import waitOn from 'wait-on' - -import pkg from '../package.json' - -const APP_PATH = path.resolve(__dirname, '../') -const APP_NAME = pkg.name - -const CONTAINER_NAME = 'nextcloud-cypress-tests-' + APP_NAME -const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server' - -export const docker = new Docker() - -/** - * Start the testing container - * - * @param branch the current git branch - */ -export const startNextcloud = async function(branch = 'master'): Promise { - try { - // Pulling images - console.log('Pulling images... ā³') - await new Promise((resolve, reject) => docker.pull(SERVER_IMAGE, (_err, stream: NodeJS.ReadableStream) => { - const onFinished = function(err: Error | null) { - if (!err) { - return resolve(true) - } - reject(err) - } - // https://github.com/apocas/dockerode/issues/357 - docker.modem.followProgress(stream, onFinished) - })) - console.log('ā””ā”€ Done') - - // Getting latest image - console.log('\nChecking running containers... šŸ”') - const localImage = await docker.listImages({ filters: `{"reference": ["${SERVER_IMAGE}"]}` }) - - // Remove old container if exists and not initialized by us - try { - const oldContainer = docker.getContainer(CONTAINER_NAME) - const oldContainerData = await oldContainer.inspect() - if (oldContainerData.State.Running) { - console.log('ā”œā”€ Existing running container found') - if (localImage[0].Id !== oldContainerData.Image) { - console.log('ā””ā”€ But running container is outdated, replacing...') - } else { - // Get container's IP - console.log('ā”œā”€ Reusing that container') - const ip = await getContainerIP(oldContainer) - return ip - } - } else { - console.log('ā””ā”€ None found!') - } - // Forcing any remnants to be removed just in case - await oldContainer.remove({ force: true }) - } catch (error) { - console.log('ā””ā”€ None found!') - } - - // Starting container - console.log('\nStarting Nextcloud container... šŸš€') - console.log(`ā”œā”€ Using branch '${branch}'`) - console.log(`ā”œā”€ And binding app '${APP_NAME}' from '${APP_PATH}'`) - const container = await docker.createContainer({ - Image: SERVER_IMAGE, - name: CONTAINER_NAME, - HostConfig: { - Binds: [`${APP_PATH}:/var/www/html/apps/${APP_NAME}`], - }, - Env: [ - `BRANCH=${branch}`, - ], - }) - 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 - * - * @param branch - */ -export const configureNextcloud = async function(branch = 'master') { - console.log('\nConfiguring nextcloud...') - const container = docker.getContainer(CONTAINER_NAME) - await runExec(container, ['php', 'occ', '--version'], true) - - // Clone the viewer app - await runExec(container, ['git', 'clone', '--depth', '1', '--branch', branch, 'https://github.com/nextcloud/viewer.git', '/var/www/html/apps/viewer'], 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:enable', 'groupfolders', '--force'], true) - await runExec(container, ['php', 'occ', 'app:enable', 'files_trashbin', '--force'], true) - // await runExec(container, ['php', 'occ', 'app:list'], true) - - console.log('ā””ā”€ Nextcloud is now ready to use šŸŽ‰') -} - -/** - * Force stop the testing nextcloud 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 address - * - * @param container the container to get the ip from - */ -export const getContainerIP = async function( - container: Docker.Container = docker.getContainer(CONTAINER_NAME), -): Promise { - 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 - * - * @param {string} ip the ip to wait for - * @see 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/support/commands.ts b/cypress/support/commands.ts index 65cbaf684..19c2f6840 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -129,5 +129,5 @@ Cypress.Commands.add('uploadContent', (user, blob, mimeType, target) => { Cypress.Commands.add('runOccCommand', (command: string, options?: Partial) => { const env = Object.entries(options?.env ?? {}).map(([name, value]) => `-e '${name}=${value}'`).join(' ') - return cy.exec(`docker exec --user www-data ${env} nextcloud-cypress-tests-groupfolders php ./occ ${command}`, options) + return cy.exec(`docker exec --user www-data ${env} nextcloud-cypress-tests_groupfolders php ./occ ${command}`, options) }) diff --git a/package-lock.json b/package-lock.json index ab8eefc69..036f5337b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,6 @@ "@nextcloud/typings": "^1.9.1", "@nextcloud/webpack-vue-config": "^5.5.1", "@types/bootstrap": "^5.2.10", - "@types/dockerode": "^3.3.30", "@types/jest": "^29.5.12", "@types/jquery": "^3.5.29", "@types/react": "^17.0.43", @@ -55,7 +54,6 @@ "cypress-split": "^1.24.0", "cypress-visual-regression": "^5.2.0", "cypress-wait-until": "^3.0.2", - "dockerode": "^4.0.2", "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-react": "^7.34.1", "jest": "^29.4.1", @@ -65,8 +63,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.2", "tslib": "^2.7.0", - "typescript": "^5.5.4", - "wait-on": "^7.0.1" + "typescript": "^5.5.4" }, "engines": { "node": "^20.0.0", @@ -4895,27 +4892,6 @@ "@types/ms": "*" } }, - "node_modules/@types/docker-modem": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", - "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/ssh2": "*" - } - }, - "node_modules/@types/dockerode": { - "version": "3.3.30", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.30.tgz", - "integrity": "sha512-MuxQQ7yQ+VzLbrIV2D8K2YqOYBd5Mz4yGbauEipFcn894bLnGwewNKXGKLlb7wpXTG4dn+13lk51qPXmKJ2+YA==", - "dev": true, - "dependencies": { - "@types/docker-modem": "*", - "@types/node": "*", - "@types/ssh2": "*" - } - }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -5255,24 +5231,6 @@ "@types/node": "*" } }, - "node_modules/@types/ssh2": { - "version": "1.11.18", - "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.18.tgz", - "integrity": "sha512-7eH4ppQMFlzvn//zhwD54MWaITR1aSc1oFBye9vb76GZ2Y9PSFYdwVIwOlxRXWs5+1hifntXyt+8a6SUbOD7Hg==", - "dev": true, - "dependencies": { - "@types/node": "^18.11.18" - } - }, - "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.6.tgz", - "integrity": "sha512-X36s5CXMrrJOs2lQCdDF68apW4Rfx9ixYMawlepwmE4Anezv/AV2LSpKD1Ub8DAc+urp5bk0BGZ6NtmBitfnsg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", diff --git a/package.json b/package.json index a1860c356..ab1bbc9d7 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,9 @@ "lint:fix": "eslint --ext .js,.vue src --fix", "stylelint": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue", "stylelint:fix": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue --fix", - "cypress": "npm run cypress:component && npm run cypress:e2e", - "cypress:component": "cypress run --component", + "cypress": "npm run cypress:e2e", "cypress:e2e": "cypress run --e2e", - "cypress:gui": "cypress open", - "precypress:update-snapshots": "TESTING=true npm run dev" + "cypress:gui": "cypress open" }, "engines": { "node": "^20.0.0", @@ -28,7 +26,6 @@ "@nextcloud/typings": "^1.9.1", "@nextcloud/webpack-vue-config": "^5.5.1", "@types/bootstrap": "^5.2.10", - "@types/dockerode": "^3.3.30", "@types/jest": "^29.5.12", "@types/jquery": "^3.5.29", "@types/react": "^17.0.43", @@ -43,7 +40,6 @@ "cypress-split": "^1.24.0", "cypress-visual-regression": "^5.2.0", "cypress-wait-until": "^3.0.2", - "dockerode": "^4.0.2", "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-react": "^7.34.1", "jest": "^29.4.1", @@ -53,8 +49,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.2", "tslib": "^2.7.0", - "typescript": "^5.5.4", - "wait-on": "^7.0.1" + "typescript": "^5.5.4" }, "dependencies": { "@nextcloud/auth": "^2.4.0",