diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 356ea0970c9bc..7ce5e72045eaf 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -29,6 +29,8 @@ jobs: npmVersion: ${{ steps.versions.outputs.npmVersion }} env: + # We'll install cypress in the cypress job + CYPRESS_INSTALL_BINARY: 0 PUPPETEER_SKIP_DOWNLOAD: true steps: @@ -115,9 +117,14 @@ 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: + # We already installed the dependencies in the init job + install: false component: ${{ matrix.containers == 'component' }} group: ${{ matrix.use-cypress-cloud && matrix.containers == 'component' && 'Run component' || matrix.use-cypress-cloud && 'Run E2E' || '' }} # cypress env diff --git a/cypress/dockerNode.ts b/cypress/dockerNode.ts index 33c2829c9d0bb..a9e8c7c6c4574 100644 --- a/cypress/dockerNode.ts +++ b/cypress/dockerNode.ts @@ -41,10 +41,6 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc // https://github.com/apocas/dockerode/issues/357 docker.modem.followProgress(stream, onFinished) - /** - * - * @param err - */ function onFinished(err) { if (!err) { resolve(true) @@ -82,14 +78,27 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc Image: SERVER_IMAGE, name: CONTAINER_NAME, HostConfig: { - Binds: [], + Mounts: [{ + Target: '/var/www/html/data', + Source: '', + Type: 'tmpfs', + ReadOnly: false, + }], }, Env: [ `BRANCH=${branch}`, + 'APCU=1', ], }) await container.start() + // Set proper permissions for the data folder + await runExec(container, ['chown', '-R', 'www-data:www-data', '/var/www/html/data'], false, 'root') + await runExec(container, ['chmod', '0770', '/var/www/html/data'], false, 'root') + + // Init Nextcloud + // await runExec(container, ['initnc.sh'], true, 'root') + // Get container's IP const ip = await getContainerIP(container) @@ -117,9 +126,28 @@ export const configureNextcloud = async function() { 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) + // Speed up test and make them less flaky. If a cron execution is needed, it can be triggered manually. await runExec(container, ['php', 'occ', 'background:cron'], true) + // Checking apcu + const distributed = await runExec(container, ['php', 'occ', 'config:system:get', 'memcache.distributed']) + const local = await runExec(container, ['php', 'occ', 'config:system:get', 'memcache.local']) + const hashing = await runExec(container, ['php', 'occ', 'config:system:get', 'hashing_default_password']) + + console.log('ā”œā”€ Checking APCu configuration... šŸ‘€') + if (!distributed.trim().includes('Memcache\\APCu') + || !local.trim().includes('Memcache\\APCu') + || !hashing.trim().includes('true')) { + console.log('└─ APCu is not properly configured šŸ›‘') + throw new Error('APCu is not properly configured') + } + console.log('│ └─ OK !') + + // Saving DB state + console.log('ā”œā”€ Creating init DB snapshot...') + await runExec(container, ['cp', '/var/www/html/data/owncloud.db', '/var/www/html/data/owncloud.db-init'], true) + console.log('└─ Nextcloud is now ready to use šŸŽ‰') } @@ -249,7 +277,7 @@ const runExec = async function( command: string[], verbose = false, user = 'www-data', -) { +): Promise { const exec = await container.exec({ Cmd: command, AttachStdout: true, @@ -258,6 +286,7 @@ const runExec = async function( }) return new Promise((resolve, reject) => { + let output = '' exec.start({}, (err, stream) => { if (err) { reject(err) @@ -265,11 +294,17 @@ const runExec = async function( if (stream) { stream.setEncoding('utf-8') stream.on('data', str => { - if (verbose && str.trim() !== '') { - console.log(`ā”œā”€ ${str.trim().replace(/\n/gi, '\nā”œā”€ ')}`) + str = str.trim() + // Remove non printable characters + .replace(/[^\x20-\x7E]+/g, '') + // Remove non alphanumeric leading characters + .replace(/^[^a-z]/gi, '') + output += str + if (verbose && str !== '') { + console.log(`ā”œā”€ ${str.replace(/\n/gi, '\nā”œā”€ ')}`) } }) - stream.on('end', resolve) + stream.on('end', () => resolve(output)) } }) }) diff --git a/cypress/e2e/settings/personal-info.cy.ts b/cypress/e2e/settings/personal-info.cy.ts index 491d8206b5e4d..746717b5fb48e 100644 --- a/cypress/e2e/settings/personal-info.cy.ts +++ b/cypress/e2e/settings/personal-info.cy.ts @@ -102,11 +102,23 @@ const genericProperties = ['Location', 'X (formerly Twitter)', 'Fediverse'] const nonfederatedProperties = ['Organisation', 'Role', 'Headline', 'About'] describe('Settings: Change personal information', { testIsolation: true }, () => { + let snapshot: string = '' before(() => { // ensure we can set locale and language cy.runOccCommand('config:system:delete force_language') cy.runOccCommand('config:system:delete force_locale') + cy.createRandomUser().then(($user) => { + user = $user + cy.modifyUser(user, 'language', 'en') + cy.modifyUser(user, 'locale', 'en_US') + }) + + cy.wait(500) + + cy.backupDB().then(($snapshot) => { + snapshot = $snapshot + }) }) after(() => { @@ -115,16 +127,15 @@ describe('Settings: Change personal information', { testIsolation: true }, () => }) beforeEach(() => { - cy.createRandomUser().then(($user) => { - user = $user - cy.modifyUser(user, 'language', 'en') - cy.modifyUser(user, 'locale', 'en_US') - cy.login($user) - cy.visit('/settings/user') - }) + cy.login(user) + cy.visit('/settings/user') cy.intercept('PUT', /ocs\/v2.php\/cloud\/users\//).as('submitSetting') }) + afterEach(() => { + cy.restoreDB(snapshot) + }) + it('Can dis- and enable the profile', () => { cy.visit(`/u/${user.userId}`) cy.contains('h2', user.userId).should('be.visible') @@ -132,6 +143,7 @@ describe('Settings: Change personal information', { testIsolation: true }, () => cy.visit('/settings/user') cy.contains('Enable profile').click() handlePasswordConfirmation(user.password) + cy.wait('@submitSetting') cy.visit(`/u/${user.userId}`, { failOnStatusCode: false }) cy.contains('h2', 'Profile not found').should('be.visible') @@ -139,6 +151,7 @@ describe('Settings: Change personal information', { testIsolation: true }, () => cy.visit('/settings/user') cy.contains('Enable profile').click() handlePasswordConfirmation(user.password) + cy.wait('@submitSetting') cy.visit(`/u/${user.userId}`, { failOnStatusCode: false }) cy.contains('h2', user.userId).should('be.visible') diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 6ca0388a0a0b8..1574a03705f53 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -65,6 +65,17 @@ declare global { * Run an occ command in the docker container. */ runOccCommand(command: string, options?: Partial): Cypress.Chainable, + + /** + * Create a snapshot of the current database + */ + backupDB(): Cypress.Chainable, + + /** + * Restore a snapshot of the database + * Default is the post-setup state + */ + restoreDB(snapshot?: string): Cypress.Chainable, } } } @@ -276,3 +287,15 @@ Cypress.Commands.add('runOccCommand', (command: string, options?: Partial `-e '${name}=${value}'`).join(' ') return cy.exec(`docker exec --user www-data ${env} nextcloud-cypress-tests-server php ./occ ${command}`, options) }) + +Cypress.Commands.add('backupDB', (): Cypress.Chainable => { + const randomString = Math.random().toString(36).substring(7) + cy.exec(`docker exec --user www-data nextcloud-cypress-tests-server cp /var/www/html/data/owncloud.db /var/www/html/data/owncloud.db-${randomString}`) + cy.log(`Created snapshot ${randomString}`) + return cy.wrap(randomString) +}) + +Cypress.Commands.add('restoreDB', (snapshot: string = 'init') => { + cy.exec(`docker exec --user www-data nextcloud-cypress-tests-server cp /var/www/html/data/owncloud.db-${snapshot} /var/www/html/data/owncloud.db`) + cy.log(`Restored snapshot ${snapshot}`) +})