Skip to content

Commit ff1dfc6

Browse files
authored
Merge pull request #52703 from nextcloud/feat/setup-checks
chore(cypress): add setup tests
2 parents 0827645 + 3dbc479 commit ff1dfc6

File tree

7 files changed

+207
-16
lines changed

7 files changed

+207
-16
lines changed

.github/workflows/cypress.yml

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,56 @@ jobs:
9494
matrix:
9595
# Run multiple copies of the current job in parallel
9696
# Please increase the number or runners as your tests suite grows (0 based index for e2e tests)
97-
containers: ["component", '0', '1', '2', '3', '4', '5', '6', '7']
97+
containers: ['component', 'setup', '0', '1', '2', '3', '4', '5', '6', '7']
9898
# Hack as strategy.job-total includes the component and GitHub does not allow math expressions
9999
# Always align this number with the total of e2e runners (max. index + 1)
100100
total-containers: [8]
101101

102+
services:
103+
mysql:
104+
# Only start mysql if we are running the setup tests
105+
image: ${{matrix.containers == 'setup' && 'ghcr.io/nextcloud/continuous-integration-mysql-8.4:latest' || ''}}
106+
ports:
107+
- '3306/tcp'
108+
env:
109+
MYSQL_ROOT_PASSWORD: rootpassword
110+
MYSQL_USER: oc_autotest
111+
MYSQL_PASSWORD: nextcloud
112+
MYSQL_DATABASE: oc_autotest
113+
options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10
114+
115+
mariadb:
116+
# Only start mariadb if we are running the setup tests
117+
image: ${{matrix.containers == 'setup' && 'mariadb:11.4' || ''}}
118+
ports:
119+
- '3306/tcp'
120+
env:
121+
MYSQL_ROOT_PASSWORD: rootpassword
122+
MYSQL_USER: oc_autotest
123+
MYSQL_PASSWORD: nextcloud
124+
MYSQL_DATABASE: oc_autotest
125+
options: --health-cmd="mariadb-admin ping" --health-interval 5s --health-timeout 2s --health-retries 5
126+
127+
postgres:
128+
# Only start postgres if we are running the setup tests
129+
image: ${{matrix.containers == 'setup' && 'ghcr.io/nextcloud/continuous-integration-postgres-17:latest' || ''}}
130+
ports:
131+
- '5432/tcp'
132+
env:
133+
POSTGRES_USER: root
134+
POSTGRES_PASSWORD: rootpassword
135+
POSTGRES_DB: nextcloud
136+
options: --mount type=tmpfs,destination=/var/lib/postgresql/data --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5
137+
138+
oracle:
139+
# Only start oracle if we are running the setup tests
140+
image: ${{matrix.containers == 'setup' && 'ghcr.io/gvenzl/oracle-free:23' || ''}}
141+
ports:
142+
- '1521'
143+
env:
144+
ORACLE_PASSWORD: oracle
145+
options: --health-cmd healthcheck.sh --health-interval 20s --health-timeout 10s --health-retries 10
146+
102147
name: runner ${{ matrix.containers }}
103148

104149
steps:
@@ -141,6 +186,7 @@ jobs:
141186
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
142187
SPLIT: ${{ matrix.total-containers }}
143188
SPLIT_INDEX: ${{ matrix.containers == 'component' && 0 || matrix.containers }}
189+
SETUP_TESTING: ${{ matrix.containers == 'setup' && 'true' || '' }}
144190

145191
- name: Upload snapshots and videos
146192
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2

core/src/components/setup/RecommendedApps.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
-->
55

66
<template>
7-
<div class="guest-box">
7+
<div class="guest-box" data-cy-setup-recommended-apps>
88
<h2>{{ t('core', 'Recommended apps') }}</h2>
99
<p v-if="loadingApps" class="loading text-center">
1010
{{ t('core', 'Loading apps …') }}
@@ -40,13 +40,15 @@
4040
<NcButton v-if="showInstallButton && !installingApps"
4141
type="tertiary"
4242
role="link"
43-
:href="defaultPageUrl">
43+
:href="defaultPageUrl"
44+
data-cy-setup-recommended-apps-skip>
4445
{{ t('core', 'Skip') }}
4546
</NcButton>
4647

4748
<NcButton v-if="showInstallButton"
4849
type="primary"
4950
:disabled="installingApps || !isAnyAppSelected"
51+
data-cy-setup-recommended-apps-install>
5052
@click.stop.prevent="installApps">
5153
{{ installingApps ? t('core', 'Installing apps …') : t('core', 'Install recommended apps') }}
5254
</NcButton>

cypress.config.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55
import type { Configuration } from 'webpack'
6+
import { defineConfig } from 'cypress'
7+
import { join } from 'path'
8+
import { removeDirectory } from 'cypress-delete-downloads-folder'
9+
10+
import cypressSplit from 'cypress-split'
11+
import webpackPreprocessor from '@cypress/webpack-preprocessor'
12+
613
import {
714
applyChangesToNextcloud,
815
configureNextcloud,
916
startNextcloud,
1017
stopNextcloud,
1118
waitOnNextcloud,
1219
} from './cypress/dockerNode'
13-
import { defineConfig } from 'cypress'
14-
import cypressSplit from 'cypress-split'
15-
import { removeDirectory } from 'cypress-delete-downloads-folder'
16-
import webpackPreprocessor from '@cypress/webpack-preprocessor'
17-
1820
import webpackConfig from './webpack.config.js'
1921

2022
export default defineConfig({
@@ -62,8 +64,6 @@ export default defineConfig({
6264
// We've imported your old cypress plugins here.
6365
// You may want to clean this up later by importing these.
6466
async setupNodeEvents(on, config) {
65-
cypressSplit(on, config)
66-
6767
on('file:preprocessor', webpackPreprocessor({ webpackOptions: webpackConfig as Configuration }))
6868

6969
on('task', { removeDirectory })
@@ -106,6 +106,16 @@ export default defineConfig({
106106
}
107107
})
108108

109+
// Check if we are running the setup checks
110+
if (process.env.SETUP_TESTING === 'true') {
111+
console.log('Adding setup tests to specPattern 🧮')
112+
config.specPattern = [join(__dirname, 'cypress/e2e/core/setup.ts')]
113+
console.log('└─ Done')
114+
} else {
115+
// If we are not running the setup tests, we need to remove the setup tests from the specPattern
116+
cypressSplit(on, config)
117+
}
118+
109119
// Before the browser launches
110120
// starting Nextcloud testing container
111121
const ip = await startNextcloud(process.env.BRANCH)

cypress/dockerNode.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc
9494
HostPort: '8083',
9595
}],
9696
},
97+
// If running the setup tests, let's bind to host
98+
// to communicate with the github actions DB services
99+
NetworkMode: process.env.SETUP_TESTING === 'true' ? await getGithubNetwork() : undefined,
97100
},
98101
Env: [
99102
`BRANCH=${branch}`,
@@ -106,9 +109,6 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc
106109
await runExec(container, ['chown', '-R', 'www-data:www-data', '/var/www/html/data'], false, 'root')
107110
await runExec(container, ['chmod', '0770', '/var/www/html/data'], false, 'root')
108111

109-
// Init Nextcloud
110-
// await runExec(container, ['initnc.sh'], true, 'root')
111-
112112
// Get container's IP
113113
const ip = await getContainerIP(container)
114114

@@ -252,6 +252,7 @@ export const getContainerIP = async function(
252252
if (err) {
253253
throw err
254254
}
255+
255256
if (data?.HostConfig.PortBindings?.['80/tcp']?.[0]?.HostPort) {
256257
ip = `localhost:${data.HostConfig.PortBindings['80/tcp'][0].HostPort}`
257258
} else {
@@ -335,3 +336,21 @@ const sleep = function(milliseconds: number) {
335336
const getCurrentGitBranch = function() {
336337
return execSync('git rev-parse --abbrev-ref HEAD').toString().trim() || 'master'
337338
}
339+
340+
/**
341+
* Get the network name of the github actions network
342+
* This is used to connect to the database services
343+
* started by github actions
344+
*/
345+
const getGithubNetwork = async function(): Promise<string|undefined> {
346+
console.log('├─ Looking for github actions network... 🔍')
347+
const networks = await docker.listNetworks()
348+
const network = networks.find((network) => network.Name.startsWith('github_network'))
349+
if (network) {
350+
console.log('│ └─ Found github actions network: ' + network.Name)
351+
return network.Name
352+
}
353+
354+
console.log('│ └─ No github actions network found')
355+
return undefined
356+
}

cypress/e2e/core/setup.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
/**
7+
* DO NOT RENAME THIS FILE to .cy.ts ⚠️
8+
* This is not following the pattern of the other files in this folder
9+
* because it is manually added to the tests by the cypress config.
10+
*/
11+
describe('Can install Nextcloud', { testIsolation: true, retries: 0 }, () => {
12+
beforeEach(() => {
13+
// Move the config file and data folder
14+
cy.runCommand('rm /var/www/html/config/config.php', { failOnNonZeroExit: false })
15+
cy.runCommand('rm /var/www/html/data/owncloud.db', { failOnNonZeroExit: false })
16+
})
17+
18+
it('Sqlite', () => {
19+
cy.visit('/')
20+
cy.get('[data-cy-setup-form]').should('be.visible')
21+
cy.get('[data-cy-setup-form-field="adminlogin"]').should('be.visible')
22+
cy.get('[data-cy-setup-form-field="adminpass"]').should('be.visible')
23+
cy.get('[data-cy-setup-form-field="directory"]').should('have.value', '/var/www/html/data')
24+
25+
// Select the SQLite database
26+
cy.get('[data-cy-setup-form-field="dbtype-sqlite"] input').check({ force: true })
27+
28+
sharedSetup()
29+
})
30+
31+
it('MySQL', () => {
32+
cy.visit('/')
33+
cy.get('[data-cy-setup-form]').should('be.visible')
34+
cy.get('[data-cy-setup-form-field="adminlogin"]').should('be.visible')
35+
cy.get('[data-cy-setup-form-field="adminpass"]').should('be.visible')
36+
cy.get('[data-cy-setup-form-field="directory"]').should('have.value', '/var/www/html/data')
37+
38+
// Select the SQLite database
39+
cy.get('[data-cy-setup-form-field="dbtype-mysql"] input').check({ force: true })
40+
41+
// Fill in the DB form
42+
cy.get('[data-cy-setup-form-field="dbuser"]').type('{selectAll}oc_autotest')
43+
cy.get('[data-cy-setup-form-field="dbpass"]').type('{selectAll}nextcloud')
44+
cy.get('[data-cy-setup-form-field="dbname"]').type('{selectAll}oc_autotest')
45+
cy.get('[data-cy-setup-form-field="dbhost"]').type('{selectAll}mysql:3306')
46+
47+
sharedSetup()
48+
})
49+
50+
it('MariaDB', () => {
51+
cy.visit('/')
52+
cy.get('[data-cy-setup-form]').should('be.visible')
53+
cy.get('[data-cy-setup-form-field="adminlogin"]').should('be.visible')
54+
cy.get('[data-cy-setup-form-field="adminpass"]').should('be.visible')
55+
cy.get('[data-cy-setup-form-field="directory"]').should('have.value', '/var/www/html/data')
56+
57+
// Select the SQLite database
58+
cy.get('[data-cy-setup-form-field="dbtype-mysql"] input').check({ force: true })
59+
60+
// Fill in the DB form
61+
cy.get('[data-cy-setup-form-field="dbuser"]').type('{selectAll}oc_autotest')
62+
cy.get('[data-cy-setup-form-field="dbpass"]').type('{selectAll}nextcloud')
63+
cy.get('[data-cy-setup-form-field="dbname"]').type('{selectAll}oc_autotest')
64+
cy.get('[data-cy-setup-form-field="dbhost"]').type('{selectAll}mariadb:3306')
65+
66+
sharedSetup()
67+
})
68+
69+
it('PostgreSQL', () => {
70+
cy.visit('/')
71+
cy.get('[data-cy-setup-form]').should('be.visible')
72+
cy.get('[data-cy-setup-form-field="adminlogin"]').should('be.visible')
73+
cy.get('[data-cy-setup-form-field="adminpass"]').should('be.visible')
74+
cy.get('[data-cy-setup-form-field="directory"]').should('have.value', '/var/www/html/data')
75+
76+
// Select the SQLite database
77+
cy.get('[data-cy-setup-form-field="dbtype-pgsql"] input').check({ force: true })
78+
79+
// Fill in the DB form
80+
cy.get('[data-cy-setup-form-field="dbuser"]').type('{selectAll}root')
81+
cy.get('[data-cy-setup-form-field="dbpass"]').type('{selectAll}rootpassword')
82+
cy.get('[data-cy-setup-form-field="dbname"]').type('{selectAll}nextcloud')
83+
cy.get('[data-cy-setup-form-field="dbhost"]').type('{selectAll}postgres:5432')
84+
85+
sharedSetup()
86+
})
87+
88+
})
89+
90+
/**
91+
* Shared admin setup function for the Nextcloud setup
92+
*/
93+
function sharedSetup() {
94+
const randAdmin = 'admin-' + Math.random().toString(36).substring(2, 15)
95+
96+
// Fill in the form
97+
cy.get('[data-cy-setup-form-field="adminlogin"]').type(randAdmin)
98+
cy.get('[data-cy-setup-form-field="adminpass"]').type(randAdmin)
99+
100+
// Nothing more to do on sqlite, let's continue
101+
cy.get('[data-cy-setup-form-submit]').click()
102+
103+
// Wait for the setup to finish
104+
cy.location('pathname', { timeout: 10000 })
105+
.should('include', '/core/apps/recommended')
106+
107+
// Skip the setup apps
108+
cy.get('[data-cy-setup-recommended-apps]').should('be.visible')
109+
cy.get('[data-cy-setup-recommended-apps-skip]').click()
110+
111+
// Go to files
112+
cy.visit('/apps/files/')
113+
cy.get('[data-cy-files-content]').should('be.visible')
114+
}

0 commit comments

Comments
 (0)