Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,56 @@ jobs:
matrix:
# Run multiple copies of the current job in parallel
# Please increase the number or runners as your tests suite grows (0 based index for e2e tests)
containers: ["component", '0', '1', '2', '3', '4', '5', '6', '7']
containers: ['component', 'setup', '0', '1', '2', '3', '4', '5', '6', '7']
# Hack as strategy.job-total includes the component and GitHub does not allow math expressions
# Always align this number with the total of e2e runners (max. index + 1)
total-containers: [8]

services:
mysql:
# Only start mysql if we are running the setup tests
image: ${{matrix.containers == 'setup' && 'ghcr.io/nextcloud/continuous-integration-mysql-8.4:latest' || ''}}
ports:
- '3306/tcp'
env:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_USER: oc_autotest
MYSQL_PASSWORD: nextcloud
MYSQL_DATABASE: oc_autotest
options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10

mariadb:
# Only start mariadb if we are running the setup tests
image: ${{matrix.containers == 'setup' && 'mariadb:11.4' || ''}}
ports:
- '3306/tcp'
env:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_USER: oc_autotest
MYSQL_PASSWORD: nextcloud
MYSQL_DATABASE: oc_autotest
options: --health-cmd="mariadb-admin ping" --health-interval 5s --health-timeout 2s --health-retries 5

postgres:
# Only start postgres if we are running the setup tests
image: ${{matrix.containers == 'setup' && 'ghcr.io/nextcloud/continuous-integration-postgres-17:latest' || ''}}
ports:
- '5432/tcp'
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: rootpassword
POSTGRES_DB: nextcloud
options: --mount type=tmpfs,destination=/var/lib/postgresql/data --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5

oracle:
# Only start oracle if we are running the setup tests
image: ${{matrix.containers == 'setup' && 'ghcr.io/gvenzl/oracle-free:23' || ''}}
ports:
- '1521'
env:
ORACLE_PASSWORD: oracle
options: --health-cmd healthcheck.sh --health-interval 20s --health-timeout 10s --health-retries 10

name: runner ${{ matrix.containers }}

steps:
Expand Down Expand Up @@ -141,6 +186,7 @@ jobs:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
SPLIT: ${{ matrix.total-containers }}
SPLIT_INDEX: ${{ matrix.containers == 'component' && 0 || matrix.containers }}
SETUP_TESTING: ${{ matrix.containers == 'setup' && 'true' || '' }}

- name: Upload snapshots and videos
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
Expand Down
6 changes: 4 additions & 2 deletions core/src/components/setup/RecommendedApps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
-->

<template>
<div class="guest-box">
<div class="guest-box" data-cy-setup-recommended-apps>
<h2>{{ t('core', 'Recommended apps') }}</h2>
<p v-if="loadingApps" class="loading text-center">
{{ t('core', 'Loading apps …') }}
Expand Down Expand Up @@ -40,13 +40,15 @@
<NcButton v-if="showInstallButton && !installingApps"
type="tertiary"
role="link"
:href="defaultPageUrl">
:href="defaultPageUrl"
data-cy-setup-recommended-apps-skip>
{{ t('core', 'Skip') }}
</NcButton>

<NcButton v-if="showInstallButton"
type="primary"
:disabled="installingApps || !isAnyAppSelected"
data-cy-setup-recommended-apps-install>
@click.stop.prevent="installApps">
{{ installingApps ? t('core', 'Installing apps …') : t('core', 'Install recommended apps') }}
</NcButton>
Expand Down
24 changes: 17 additions & 7 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Configuration } from 'webpack'
import { defineConfig } from 'cypress'
import { join } from 'path'
import { removeDirectory } from 'cypress-delete-downloads-folder'

import cypressSplit from 'cypress-split'
import webpackPreprocessor from '@cypress/webpack-preprocessor'

import {
applyChangesToNextcloud,
configureNextcloud,
startNextcloud,
stopNextcloud,
waitOnNextcloud,
} from './cypress/dockerNode'
import { defineConfig } from 'cypress'
import cypressSplit from 'cypress-split'
import { removeDirectory } from 'cypress-delete-downloads-folder'
import webpackPreprocessor from '@cypress/webpack-preprocessor'

import webpackConfig from './webpack.config.js'

export default defineConfig({
Expand Down Expand Up @@ -62,8 +64,6 @@ export default defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
async setupNodeEvents(on, config) {
cypressSplit(on, config)

on('file:preprocessor', webpackPreprocessor({ webpackOptions: webpackConfig as Configuration }))

on('task', { removeDirectory })
Expand Down Expand Up @@ -106,6 +106,16 @@ export default defineConfig({
}
})

// Check if we are running the setup checks
if (process.env.SETUP_TESTING === 'true') {
console.log('Adding setup tests to specPattern 🧮')
config.specPattern = [join(__dirname, 'cypress/e2e/core/setup.ts')]
console.log('└─ Done')
} else {
// If we are not running the setup tests, we need to remove the setup tests from the specPattern
cypressSplit(on, config)
}

// Before the browser launches
// starting Nextcloud testing container
const ip = await startNextcloud(process.env.BRANCH)
Expand Down
25 changes: 22 additions & 3 deletions cypress/dockerNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc
HostPort: '8083',
}],
},
// If running the setup tests, let's bind to host
// to communicate with the github actions DB services
NetworkMode: process.env.SETUP_TESTING === 'true' ? await getGithubNetwork() : undefined,
},
Env: [
`BRANCH=${branch}`,
Expand All @@ -106,9 +109,6 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc
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)

Expand Down Expand Up @@ -252,6 +252,7 @@ export const getContainerIP = async function(
if (err) {
throw err
}

if (data?.HostConfig.PortBindings?.['80/tcp']?.[0]?.HostPort) {
ip = `localhost:${data.HostConfig.PortBindings['80/tcp'][0].HostPort}`
} else {
Expand Down Expand Up @@ -335,3 +336,21 @@ const sleep = function(milliseconds: number) {
const getCurrentGitBranch = function() {
return execSync('git rev-parse --abbrev-ref HEAD').toString().trim() || 'master'
}

/**
* Get the network name of the github actions network
* This is used to connect to the database services
* started by github actions
*/
const getGithubNetwork = async function(): Promise<string|undefined> {
console.log('├─ Looking for github actions network... 🔍')
const networks = await docker.listNetworks()
const network = networks.find((network) => network.Name.startsWith('github_network'))
if (network) {
console.log('│ └─ Found github actions network: ' + network.Name)
return network.Name
}

console.log('│ └─ No github actions network found')
return undefined
}
114 changes: 114 additions & 0 deletions cypress/e2e/core/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

/**
* DO NOT RENAME THIS FILE to .cy.ts ⚠️
* This is not following the pattern of the other files in this folder
* because it is manually added to the tests by the cypress config.
*/
describe('Can install Nextcloud', { testIsolation: true, retries: 0 }, () => {
beforeEach(() => {
// Move the config file and data folder
cy.runCommand('rm /var/www/html/config/config.php', { failOnNonZeroExit: false })
cy.runCommand('rm /var/www/html/data/owncloud.db', { failOnNonZeroExit: false })
})

it('Sqlite', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
cy.get('[data-cy-setup-form-field="adminlogin"]').should('be.visible')
cy.get('[data-cy-setup-form-field="adminpass"]').should('be.visible')
cy.get('[data-cy-setup-form-field="directory"]').should('have.value', '/var/www/html/data')

// Select the SQLite database
cy.get('[data-cy-setup-form-field="dbtype-sqlite"] input').check({ force: true })

sharedSetup()
})

it('MySQL', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
cy.get('[data-cy-setup-form-field="adminlogin"]').should('be.visible')
cy.get('[data-cy-setup-form-field="adminpass"]').should('be.visible')
cy.get('[data-cy-setup-form-field="directory"]').should('have.value', '/var/www/html/data')

// Select the SQLite database
cy.get('[data-cy-setup-form-field="dbtype-mysql"] input').check({ force: true })

// Fill in the DB form
cy.get('[data-cy-setup-form-field="dbuser"]').type('{selectAll}oc_autotest')
cy.get('[data-cy-setup-form-field="dbpass"]').type('{selectAll}nextcloud')
cy.get('[data-cy-setup-form-field="dbname"]').type('{selectAll}oc_autotest')
cy.get('[data-cy-setup-form-field="dbhost"]').type('{selectAll}mysql:3306')

sharedSetup()
})

it('MariaDB', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
cy.get('[data-cy-setup-form-field="adminlogin"]').should('be.visible')
cy.get('[data-cy-setup-form-field="adminpass"]').should('be.visible')
cy.get('[data-cy-setup-form-field="directory"]').should('have.value', '/var/www/html/data')

// Select the SQLite database
cy.get('[data-cy-setup-form-field="dbtype-mysql"] input').check({ force: true })

// Fill in the DB form
cy.get('[data-cy-setup-form-field="dbuser"]').type('{selectAll}oc_autotest')
cy.get('[data-cy-setup-form-field="dbpass"]').type('{selectAll}nextcloud')
cy.get('[data-cy-setup-form-field="dbname"]').type('{selectAll}oc_autotest')
cy.get('[data-cy-setup-form-field="dbhost"]').type('{selectAll}mariadb:3306')

sharedSetup()
})

it('PostgreSQL', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
cy.get('[data-cy-setup-form-field="adminlogin"]').should('be.visible')
cy.get('[data-cy-setup-form-field="adminpass"]').should('be.visible')
cy.get('[data-cy-setup-form-field="directory"]').should('have.value', '/var/www/html/data')

// Select the SQLite database
cy.get('[data-cy-setup-form-field="dbtype-pgsql"] input').check({ force: true })

// Fill in the DB form
cy.get('[data-cy-setup-form-field="dbuser"]').type('{selectAll}root')
cy.get('[data-cy-setup-form-field="dbpass"]').type('{selectAll}rootpassword')
cy.get('[data-cy-setup-form-field="dbname"]').type('{selectAll}nextcloud')
cy.get('[data-cy-setup-form-field="dbhost"]').type('{selectAll}postgres:5432')

sharedSetup()
})

})

/**
* Shared admin setup function for the Nextcloud setup
*/
function sharedSetup() {
const randAdmin = 'admin-' + Math.random().toString(36).substring(2, 15)

// Fill in the form
cy.get('[data-cy-setup-form-field="adminlogin"]').type(randAdmin)
cy.get('[data-cy-setup-form-field="adminpass"]').type(randAdmin)

// Nothing more to do on sqlite, let's continue
cy.get('[data-cy-setup-form-submit]').click()

// Wait for the setup to finish
cy.location('pathname', { timeout: 10000 })
.should('include', '/core/apps/recommended')

// Skip the setup apps
cy.get('[data-cy-setup-recommended-apps]').should('be.visible')
cy.get('[data-cy-setup-recommended-apps-skip]').click()

// Go to files
cy.visit('/apps/files/')
cy.get('[data-cy-files-content]').should('be.visible')
}
Loading
Loading