Skip to content

Commit 7e1be39

Browse files
committed
test(appstore): adjust cypress tests for app split
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 3b6ccaf commit 7e1be39

12 files changed

Lines changed: 318 additions & 221 deletions

cypress/e2e/appstore/apps.cy.ts

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2023-2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { User } from '@nextcloud/e2e-test-server/cypress'
7+
import { handlePasswordConfirmation } from '../core-utils.ts'
8+
9+
const admin = new User('admin', 'admin')
10+
11+
describe('Settings: App management', { testIsolation: true }, () => {
12+
beforeEach(() => {
13+
// disable QA if already enabled
14+
cy.runOccCommand('app:disable -n testing')
15+
// enable notification if already disabled
16+
cy.runOccCommand('app:enable -n updatenotification')
17+
18+
// I am logged in as the admin
19+
cy.login(admin)
20+
21+
// Intercept the apps list request
22+
cy.intercept('GET', '/ocs/v2.php/apps/appstore/api/v1/apps').as('fetchAppsList')
23+
24+
// I open the Apps management
25+
cy.visit('/settings/apps/installed')
26+
27+
// Wait for the apps list to load
28+
cy.wait('@fetchAppsList')
29+
})
30+
31+
it('Can enable an installed app', () => {
32+
cy.intercept('POST', '/ocs/v2.php/apps/appstore/api/v1/apps/enable').as('enableApp')
33+
34+
cy.findByRole('table').should('exist')
35+
// Wait for the app list to load
36+
.contains('tr', 'QA testing', { timeout: 10000 })
37+
.should('exist')
38+
.findByRole('button', { name: 'Enable' })
39+
// I enable the "QA testing" app
40+
.click({ force: true })
41+
42+
handlePasswordConfirmation(admin.password)
43+
44+
cy.wait('@enableApp')
45+
46+
// Wait until we see the disable button for the app
47+
cy.findByRole('table').should('exist')
48+
.contains('tr', 'QA testing')
49+
.should('exist')
50+
// I see the disable button for the app
51+
.findByRole('button', { name: 'Disable' })
52+
.should('be.visible')
53+
54+
// Change to enabled apps view
55+
cy.findByRole('navigation', { name: 'Appstore categories' })
56+
.within(() => {
57+
cy.findByRole('link', { name: 'Active apps' })
58+
.should('be.visible')
59+
.click({ force: true })
60+
})
61+
62+
cy.url().should('match', /settings\/apps\/enabled$/)
63+
// I see that the "QA testing" app has been enabled
64+
cy.findByRole('table')
65+
.contains('tr', 'QA testing')
66+
})
67+
68+
it('Can disable an installed app', () => {
69+
cy.intercept('POST', '/ocs/v2.php/apps/appstore/api/v1/apps/disable').as('disableApp')
70+
71+
cy.findByRole('table')
72+
.should('exist')
73+
// Wait for the app list to load
74+
.contains('tr', 'Update notification', { timeout: 10000 })
75+
.should('exist')
76+
// I disable the "Update notification" app
77+
.findByRole('button', { name: 'Disable' })
78+
.click({ force: true })
79+
80+
handlePasswordConfirmation(admin.password)
81+
cy.wait('@disableApp')
82+
83+
// Wait until we see the disable button for the app
84+
cy.findByRole('table').should('exist')
85+
.contains('tr', 'Update notification')
86+
.should('exist')
87+
// I see the enable button for the app
88+
.findByRole('button', { name: 'Enable' })
89+
.should('exist')
90+
91+
// Change to disabled apps view
92+
cy.findByRole('navigation', { name: 'Appstore categories' })
93+
.within(() => {
94+
cy.findByRole('link', { name: 'Disabled apps' }).click({ force: true })
95+
})
96+
cy.url().should('match', /settings\/apps\/disabled$/)
97+
98+
// I see that the "Update notification" app has been disabled
99+
cy.findByRole('table')
100+
.contains('tr', 'Update notification')
101+
})
102+
103+
it('Browse enabled apps', () => {
104+
// When I open the "Active apps" section
105+
cy.findByRole('navigation', { name: 'Appstore categories' })
106+
.within(() => {
107+
cy.findByRole('link', { name: 'Active apps' })
108+
.should('be.visible')
109+
.click({ force: true })
110+
})
111+
112+
// Then I see that the current section is "Active apps"
113+
cy.url().should('match', /settings\/apps\/enabled$/)
114+
cy.findByRole('navigation', { name: 'Appstore categories' })
115+
.within(() => {
116+
cy.findByRole('link', { name: 'Active apps', current: 'page' })
117+
.should('be.visible')
118+
})
119+
120+
// I see that there are only enabled apps
121+
cy.findByRole('table')
122+
.should('exist')
123+
.find('tr button')
124+
.each(($action) => {
125+
cy.wrap($action).should('not.contain', 'Enable')
126+
})
127+
})
128+
129+
it('Browse disabled apps', () => {
130+
// When I open the "Active Disabled" section
131+
cy.findByRole('navigation', { name: 'Appstore categories' })
132+
.within(() => {
133+
cy.findByRole('link', { name: 'Disabled apps' })
134+
.as('disabledAppsLink')
135+
.should('be.visible')
136+
.and('not.have.attr', 'aria-current')
137+
cy.get('@disabledAppsLink')
138+
.click({ force: true })
139+
})
140+
141+
// Then I see that the current section is "Disabled apps"
142+
cy.url().should('match', /settings\/apps\/disabled$/)
143+
cy.findByRole('navigation', { name: 'Appstore categories' })
144+
.within(() => {
145+
cy.findByRole('link', { name: 'Disabled apps', current: 'page' })
146+
.should('be.visible')
147+
})
148+
149+
// I see that there are only disabled apps
150+
cy.findByRole('table')
151+
.should('exist')
152+
.find('tr button')
153+
.each(($action) => {
154+
cy.wrap($action).should('not.contain', 'Disable')
155+
})
156+
})
157+
158+
it('Browse app bundles', () => {
159+
// When I open the "App bundles" section
160+
cy.findByRole('navigation', { name: 'Appstore categories' })
161+
.within(() => {
162+
cy.findByRole('link', { name: 'App bundles' })
163+
.as('appBundlesLink')
164+
.should('be.visible')
165+
.and('not.have.attr', 'aria-current')
166+
cy.get('@appBundlesLink')
167+
.click({ force: true })
168+
})
169+
170+
// Then I see that the current section is "App bundles"
171+
cy.url().should('match', /settings\/apps\/bundles$/)
172+
cy.findByRole('navigation', { name: 'Appstore categories' })
173+
.within(() => {
174+
cy.findByRole('link', { name: 'App bundles', current: 'page' })
175+
.should('be.visible')
176+
})
177+
178+
// I see the app bundles
179+
cy.findByRole('heading', { name: 'Enterprise bundle' })
180+
.should('be.visible')
181+
cy.findByRole('heading', { name: 'Education bundle' })
182+
.should('be.visible')
183+
})
184+
185+
it('View app details', () => {
186+
// When I click on the "QA testing" app
187+
cy.findByRole('table')
188+
.contains('a', 'QA testing')
189+
.click({ force: true })
190+
// I see that the app details are shown
191+
cy.get('#app-sidebar-vue')
192+
.should('be.visible')
193+
.find('.app-sidebar-header__info')
194+
.should('contain', 'QA testing')
195+
cy.get('#app-sidebar-vue').contains('a', 'View in store').should('exist')
196+
cy.get('#app-sidebar-vue')
197+
.findByRole('button', { name: 'Enable' })
198+
.should('be.visible')
199+
cy.get('#app-sidebar-vue')
200+
.findByRole('button', { name: 'Remove' })
201+
.should('be.visible')
202+
cy.get('#app-sidebar-vue').contains(/Version \d+\.\d+\.\d+/).should('be.visible')
203+
})
204+
205+
it('Limit app usage to group', () => {
206+
// When I open the "Active apps" section
207+
cy.findByRole('navigation', { name: 'Appstore categories' })
208+
.within(() => {
209+
cy.findByRole('link', { name: 'Active apps' })
210+
.should('be.visible')
211+
.click({ force: true })
212+
})
213+
214+
// Then I see that the current section is "Active apps"
215+
cy.url().should('match', /settings\/apps\/enabled$/)
216+
217+
// Then I select the app
218+
cy.findByRole('table')
219+
.should('exist')
220+
.contains('tr a', 'Dashboard', { timeout: 10000 })
221+
.click()
222+
223+
// Then I enable "limit app to group"
224+
cy.findByRole('button', { name: 'Limit to groups' })
225+
.click()
226+
227+
// Then I select a group
228+
cy.findByRole('dialog')
229+
.should('be.visible')
230+
.within(() => {
231+
cy.get('input')
232+
.should('be.focused')
233+
.type('admin')
234+
})
235+
cy.findByRole('option', { name: /admin/ })
236+
.click()
237+
cy.findByRole('button', { name: 'Save' })
238+
.click()
239+
240+
handlePasswordConfirmation(admin.password)
241+
242+
cy.get('#app-sidebar-vue')
243+
.findByRole('list', { name: 'Limited to groups' })
244+
.findByRole('listitem', { name: /admin/ })
245+
.should('be.visible')
246+
247+
// Then I disable the group limitation
248+
cy.get('#app-sidebar-vue')
249+
.findByRole('button', { name: 'Limit to groups' })
250+
.click()
251+
cy.findByRole('dialog')
252+
.should('be.visible')
253+
.within(() => {
254+
cy.findByRole('button', { name: 'Deselect admin' })
255+
.should('be.visible')
256+
.click()
257+
cy.findByRole('button', { name: 'Save' })
258+
.click()
259+
})
260+
261+
handlePasswordConfirmation(admin.password)
262+
263+
cy.get('#app-sidebar-vue')
264+
.findByRole('list', { name: 'Limited to groups' })
265+
.should('not.exist')
266+
})
267+
268+
/*
269+
* TODO: Improve testing with app store as external API
270+
* The following scenarios require the files_antivirus and calendar app
271+
* being present in the app store with support for the current server version
272+
* Ideally we would have either a dummy app store endpoint with some test apps
273+
* or even an app store instance running somewhere to properly test this.
274+
* This is also a requirement to properly test updates of apps
275+
*/
276+
// TODO: View app details for app store apps
277+
// TODO: Install an app from the app store
278+
// TODO: Show section from app store
279+
})

cypress/e2e/core-utils.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,29 @@ export function beFullyInViewport($el: JQuery<HTMLElement>, expected = true) {
8989
export function notBeFullyInViewport($el: JQuery<HTMLElement>) {
9090
return beFullyInViewport($el, false)
9191
}
92+
93+
/**
94+
* Handle the confirm password dialog (if needed)
95+
*
96+
* @param adminPassword The admin password for the dialog
97+
*/
98+
export function handlePasswordConfirmation(adminPassword = 'admin') {
99+
const handleModal = (context: Cypress.Chainable) => {
100+
return context.contains('.modal-container', 'Authentication required')
101+
.if()
102+
.within(() => {
103+
cy.get('input[type="password"]')
104+
.type(adminPassword)
105+
cy.findByRole('button', { name: 'Confirm' })
106+
.click()
107+
})
108+
}
109+
110+
return cy.get('body')
111+
.if()
112+
.then(() => handleModal(cy.get('body')))
113+
.else()
114+
// Handle if inside a cy.within
115+
.root().closest('body')
116+
.then(($body) => handleModal(cy.wrap($body)))
117+
}

cypress/e2e/files_external/files-user-credentials.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
import type { User } from '@nextcloud/e2e-test-server/cypress'
77

8+
import { handlePasswordConfirmation } from '../core-utils.ts'
89
import { getInlineActionEntryForFile, getRowForFile, navigateToFolder, triggerInlineActionForFile } from '../files/FilesUtils.ts'
9-
import { handlePasswordConfirmation } from '../settings/usersUtils.ts'
1010
import { AuthBackend, createStorageWithConfig, StorageBackend } from './StorageUtils.ts'
1111

1212
const ACTION_CREDENTIALS_EXTERNAL_STORAGE = 'credentials-external-storage'

cypress/e2e/files_external/settings.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { handlePasswordConfirmation } from '../settings/usersUtils.ts'
6+
import { handlePasswordConfirmation } from '../core-utils.ts'
77

88
describe('files_external settings', () => {
99
before(() => {

0 commit comments

Comments
 (0)