diff --git a/cypress/e2e/theming/admin-settings.cy.ts b/cypress/e2e/theming/admin-settings.cy.ts deleted file mode 100644 index af14bbdb0e6..00000000000 --- a/cypress/e2e/theming/admin-settings.cy.ts +++ /dev/null @@ -1,514 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { User } from '@nextcloud/e2e-test-server/cypress' -import { NavigationHeader } from '../../pages/NavigationHeader.ts' -import { - defaultBackground, - defaultPrimary, - expectBackgroundColor, - pickRandomColor, - validateBodyThemingCss, - validateUserThemingDefaultCss, -} from './themingUtils.ts' - -const admin = new User('admin', 'admin') - -describe('Admin theming settings visibility check', function() { - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('See the default settings', function() { - cy.get('[data-admin-theming-setting-color-picker]').should('exist') - cy.get('[data-admin-theming-setting-file-reset]').should('not.exist') - cy.get('[data-admin-theming-setting-file-remove]').should('exist') - - cy.get('[data-admin-theming-setting-primary-color] [data-admin-theming-setting-color]').then(($el) => expectBackgroundColor($el, defaultPrimary)) - - cy.get('[data-admin-theming-setting-background-color] [data-admin-theming-setting-color]').then(($el) => expectBackgroundColor($el, defaultPrimary)) - }) -}) - -describe('Change the primary color and reset it', function() { - let selectedColor = '' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('Change the primary color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - - pickRandomColor('[data-admin-theming-setting-primary-color]').then((color) => { - selectedColor = color - }) - - cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss( - selectedColor, - defaultBackground, - defaultPrimary, - )) - }) - - it('Screenshot the login page and validate login page', function() { - cy.logout() - cy.visit('/') - - cy.waitUntil(() => validateBodyThemingCss( - selectedColor, - defaultBackground, - defaultPrimary, - )) - cy.screenshot() - }) - - it('Undo theming settings and validate login page again', function() { - cy.resetAdminTheming() - cy.visit('/') - - cy.waitUntil(validateBodyThemingCss) - cy.screenshot() - }) -}) - -describe('Remove the default background and restore it', function() { - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') - - cy.get('[data-admin-theming-setting-file-remove]').click() - - cy.wait('@removeBackground') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null)) - cy.waitUntil(() => cy.window().then((win) => { - const backgroundPlain = getComputedStyle(win.document.body).getPropertyValue('--image-background') - return backgroundPlain !== '' - })) - }) - - it('Screenshot the login page and validate login page', function() { - cy.logout() - cy.visit('/') - - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null)) - cy.screenshot() - }) - - it('Undo theming settings and validate login page again', function() { - cy.resetAdminTheming() - cy.visit('/') - - cy.waitUntil(validateBodyThemingCss) - cy.screenshot() - }) -}) - -describe('Remove the default background with a custom background color', function() { - let selectedColor = '' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('Change the background color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - - pickRandomColor('[data-admin-theming-setting-background-color]').then((color) => { - selectedColor = color - }) - - cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss( - defaultPrimary, - defaultBackground, - selectedColor, - )) - }) - - it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') - - cy.get('[data-admin-theming-setting-file-remove]').scrollIntoView() - cy.get('[data-admin-theming-setting-file-remove]').click({ - force: true, - }) - - cy.wait('@removeBackground') - }) - - it('Screenshot the login page and validate login page', function() { - cy.logout() - cy.visit('/') - - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, selectedColor)) - cy.screenshot() - }) - - it('Undo theming settings and validate login page again', function() { - cy.resetAdminTheming() - cy.visit('/') - - cy.waitUntil(validateBodyThemingCss) - cy.screenshot() - }) -}) - -describe('Remove the default background with a bright color', function() { - const navigationHeader = new NavigationHeader() - let selectedColor = '' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.resetUserTheming(admin) - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') - - cy.get('[data-admin-theming-setting-file-remove]').click() - - cy.wait('@removeBackground') - }) - - it('Change the background color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - - // Pick one of the bright color preset - pickRandomColor( - '[data-admin-theming-setting-background-color]', - 4, - ).then((color) => { - selectedColor = color - }) - - cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, selectedColor)) - }) - - it('See the header being inverted', function() { - cy.waitUntil(() => navigationHeader - .getNavigationEntries() - .find('img') - .then((el) => { - let ret = true - el.each(function() { - ret = ret && window.getComputedStyle(this).filter === 'invert(1)' - }) - return ret - })) - }) -}) - -describe('Change the login fields then reset them', function() { - const name = 'ABCdef123' - const url = 'https://example.com' - const slogan = 'Testing is fun' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('Change the name field', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('updateFields') - - // Name - cy.get('[data-admin-theming-setting-field="name"] input[type="text"]').scrollIntoView() - cy.get('[data-admin-theming-setting-field="name"] input[type="text"]').type(`{selectall}${name}{enter}`) - cy.wait('@updateFields') - - // Url - cy.get('[data-admin-theming-setting-field="url"] input[type="url"]').scrollIntoView() - cy.get('[data-admin-theming-setting-field="url"] input[type="url"]').type(`{selectall}${url}{enter}`) - cy.wait('@updateFields') - - // Slogan - cy.get('[data-admin-theming-setting-field="slogan"] input[type="text"]').scrollIntoView() - cy.get('[data-admin-theming-setting-field="slogan"] input[type="text"]').type(`{selectall}${slogan}{enter}`) - cy.wait('@updateFields') - }) - - it('Ensure undo button presence', function() { - cy.get('[data-admin-theming-setting-field="name"] .input-field__trailing-button').scrollIntoView() - cy.get('[data-admin-theming-setting-field="name"] .input-field__trailing-button').should('be.visible') - - cy.get('[data-admin-theming-setting-field="url"] .input-field__trailing-button').scrollIntoView() - cy.get('[data-admin-theming-setting-field="url"] .input-field__trailing-button').should('be.visible') - - cy.get('[data-admin-theming-setting-field="slogan"] .input-field__trailing-button').scrollIntoView() - cy.get('[data-admin-theming-setting-field="slogan"] .input-field__trailing-button').should('be.visible') - }) - - it('Validate login screen changes', function() { - cy.logout() - cy.visit('/') - - cy.get('[data-login-form-headline]').should('contain.text', name) - cy.get('footer p a').should('have.text', name) - cy.get('footer p a').should('have.attr', 'href', url) - cy.get('footer p').should('contain.text', `– ${slogan}`) - }) - - it('Undo theming settings', function() { - cy.resetAdminTheming() - }) - - it('Validate login screen changes again', function() { - cy.visit('/') - - cy.get('[data-login-form-headline]').should('not.contain.text', name) - cy.get('footer p a').should('not.have.text', name) - cy.get('footer p a').should('not.have.attr', 'href', url) - cy.get('footer p').should('not.contain.text', `– ${slogan}`) - }) -}) - -describe('Disable user theming and enable it back', function() { - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('Disable user background theming', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('disableUserTheming') - - cy.get('[data-admin-theming-setting-disable-user-theming]').scrollIntoView() - cy.get('[data-admin-theming-setting-disable-user-theming]').should('be.visible') - cy.get('[data-admin-theming-setting-disable-user-theming] input[type="checkbox"]').check({ force: true }) - cy.get('[data-admin-theming-setting-disable-user-theming] input[type="checkbox"]').should('be.checked') - - cy.wait('@disableUserTheming') - }) - - it('Login as user', function() { - cy.logout() - cy.createRandomUser().then((user) => { - cy.login(user) - }) - }) - - it('User cannot not change background settings', function() { - cy.visit('/settings/user/theming') - cy.contains('Customization has been disabled by your administrator').should('exist') - }) -}) - -describe('The user default background settings reflect the admin theming settings', function() { - let selectedColor = '' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - after(function() { - cy.resetAdminTheming() - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('Change the default background', function() { - cy.intercept('*/apps/theming/ajax/uploadImage').as('setBackground') - - cy.fixture('image.jpg', null).as('background') - cy.get('[data-admin-theming-setting-file="background"] input[type="file"]').selectFile('@background', { force: true }) - - cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss( - defaultPrimary, - '/apps/theming/image/background?v=', - null, - )) - }) - - it('Change the background color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - - pickRandomColor('[data-admin-theming-setting-background-color]').then((color) => { - selectedColor = color - }) - - cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss( - defaultPrimary, - '/apps/theming/image/background?v=', - selectedColor, - )) - }) - - it('Login page should match admin theming settings', function() { - cy.logout() - cy.visit('/') - - cy.waitUntil(() => validateBodyThemingCss( - defaultPrimary, - '/apps/theming/image/background?v=', - selectedColor, - )) - }) - - it('Login as user', function() { - cy.createRandomUser().then((user) => { - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-settings]').scrollIntoView() - cy.get('[data-user-theming-background-settings]').should('be.visible') - }) - - it('Default user background settings should match admin theming settings', function() { - cy.get('[data-user-theming-background-default]').should('be.visible') - cy.get('[data-user-theming-background-default]').should( - 'have.class', - 'background--active', - ) - - cy.waitUntil(() => validateUserThemingDefaultCss( - selectedColor, - '/apps/theming/image/background?v=', - )) - }) -}) - -describe('The user default background settings reflect the admin theming settings with background removed', function() { - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - after(function() { - cy.resetAdminTheming() - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.get('[data-admin-theming-settings]') - .should('exist') - .scrollIntoView() - cy.get('[data-admin-theming-settings]').should('be.visible') - }) - - it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') - - cy.get('[data-admin-theming-setting-file-remove]').click() - - cy.wait('@removeBackground') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null)) - }) - - it('Login page should match admin theming settings', function() { - cy.logout() - cy.visit('/') - - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null)) - }) - - it('Login as user', function() { - cy.createRandomUser().then((user) => { - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-settings]').scrollIntoView() - cy.get('[data-user-theming-background-settings]').should('be.visible') - }) - - it('Default user background settings should match admin theming settings', function() { - cy.get('[data-user-theming-background-default]').should('be.visible') - cy.get('[data-user-theming-background-default]').should( - 'have.class', - 'background--active', - ) - - cy.waitUntil(() => validateUserThemingDefaultCss(defaultPrimary, null)) - }) -}) diff --git a/cypress/e2e/theming/admin-settings_background.cy.ts b/cypress/e2e/theming/admin-settings_background.cy.ts new file mode 100644 index 00000000000..e4ec661486f --- /dev/null +++ b/cypress/e2e/theming/admin-settings_background.cy.ts @@ -0,0 +1,379 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { User } from '@nextcloud/e2e-test-server/cypress' +import { NavigationHeader } from '../../pages/NavigationHeader.ts' +import { + defaultBackground, + defaultPrimary, + pickColor, + validateBodyThemingCss, + validateUserThemingDefaultCss, +} from './themingUtils.ts' + +const admin = new User('admin', 'admin') + +describe('Remove the default background and restore it', { testIsolation: false }, function() { + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + }) + + it('See the admin theming section', function() { + cy.visit('/settings/admin/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .should('exist') + .scrollIntoView() + }) + + it('Remove the default background', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') + cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') + + cy.findByRole('checkbox', { name: /remove background image/i }) + .should('exist') + .should('not.be.checked') + .check({ force: true }) + + cy.wait('@removeBackground') + cy.wait('@cssLoaded') + + cy.window() + .should(() => validateBodyThemingCss(defaultPrimary, null)) + cy.waitUntil(() => cy.window().then((win) => { + const backgroundPlain = getComputedStyle(win.document.body).getPropertyValue('--image-background') + return backgroundPlain !== '' + })) + }) + + it('Screenshot the login page and validate login page', function() { + cy.logout() + cy.visit('/') + + cy.window() + .should(() => validateBodyThemingCss(defaultPrimary, null)) + cy.screenshot() + }) + + it('Undo theming settings and validate login page again', function() { + cy.resetAdminTheming() + cy.visit('/') + + cy.window() + .should(() => validateBodyThemingCss()) + cy.screenshot() + }) +}) + +describe('Remove the default background with a custom background color', function() { + let selectedColor = '' + + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + }) + + it('See the admin theming section', function() { + cy.visit('/settings/admin/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .should('exist') + .scrollIntoView() + }) + + it('Change the background color', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') + cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') + + pickColor(cy.findByRole('button', { name: /Background color/ })) + .then((color) => { + selectedColor = color + }) + + cy.wait('@setColor') + cy.wait('@cssLoaded') + + cy.window() + .should(() => validateBodyThemingCss( + defaultPrimary, + defaultBackground, + selectedColor, + )) + }) + + it('Remove the default background', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') + + cy.findByRole('checkbox', { name: /remove background image/i }) + .should('exist') + .should('not.be.checked') + .check({ force: true }) + cy.wait('@removeBackground') + }) + + it('Screenshot the login page and validate login page', function() { + cy.logout() + cy.visit('/') + + cy.window() + .should(() => validateBodyThemingCss(defaultPrimary, null, selectedColor)) + cy.screenshot() + }) + + it('Undo theming settings and validate login page again', function() { + cy.resetAdminTheming() + cy.visit('/') + + cy.window() + .should(() => validateBodyThemingCss()) + cy.screenshot() + }) +}) + +describe('Remove the default background with a bright color', function() { + const navigationHeader = new NavigationHeader() + let selectedColor = '' + + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.resetUserTheming(admin) + cy.login(admin) + }) + + it('See the admin theming section', function() { + cy.visit('/settings/admin/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .should('exist') + .scrollIntoView() + }) + + it('Remove the default background', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') + cy.findByRole('checkbox', { name: /remove background image/i }) + .check({ force: true }) + cy.wait('@removeBackground') + }) + + it('Change the background color', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') + cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') + + pickColor(cy.findByRole('button', { name: /Background color/ }), 4) + .then((color) => { + selectedColor = color + }) + + cy.wait('@setColor') + cy.wait('@cssLoaded') + + cy.window() + .should(() => validateBodyThemingCss(defaultPrimary, null, selectedColor)) + }) + + it('See the header being inverted', function() { + cy.waitUntil(() => navigationHeader + .getNavigationEntries() + .find('img') + .then((el) => { + let ret = true + el.each(function() { + ret = ret && window.getComputedStyle(this).filter === 'invert(1)' + }) + return ret + })) + }) +}) + +describe('Disable user theming and enable it back', function() { + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + }) + + it('See the admin theming section', function() { + cy.visit('/settings/admin/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .should('exist') + .scrollIntoView() + }) + + it('Disable user background theming', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('disableUserTheming') + + cy.findByRole('checkbox', { name: /Disable user theming/ }) + .should('exist') + .and('not.be.checked') + .check({ force: true }) + + cy.wait('@disableUserTheming') + }) + + it('Login as user', function() { + cy.logout() + cy.createRandomUser().then((user) => { + cy.login(user) + }) + }) + + it('User cannot not change background settings', function() { + cy.visit('/settings/user/theming') + cy.contains('Customization has been disabled by your administrator').should('exist') + }) +}) + +describe('The user default background settings reflect the admin theming settings', function() { + let selectedColor = '' + + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + }) + + after(function() { + cy.resetAdminTheming() + }) + + it('See the admin theming section', function() { + cy.visit('/settings/admin/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .should('exist') + .scrollIntoView() + }) + + it('Change the default background', function() { + cy.intercept('*/apps/theming/ajax/uploadImage').as('setBackground') + cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') + + cy.fixture('image.jpg', null).as('background') + cy.get('input[type="file"][name="background"]') + .should('exist') + .selectFile('@background', { force: true }) + + cy.wait('@setBackground') + cy.wait('@cssLoaded') + + cy.window() + .should(() => validateBodyThemingCss( + defaultPrimary, + '/apps/theming/image/background?v=', + null, + )) + }) + + it('Change the background color', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') + cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') + + pickColor(cy.findByRole('button', { name: /Background color/ })) + .then((color) => { + selectedColor = color + }) + + cy.wait('@setColor') + cy.wait('@cssLoaded') + + cy.window() + .should(() => validateBodyThemingCss( + defaultPrimary, + '/apps/theming/image/background?v=', + selectedColor, + )) + }) + + it('Login page should match admin theming settings', function() { + cy.logout() + cy.visit('/') + + cy.window() + .should(() => validateBodyThemingCss( + defaultPrimary, + '/apps/theming/image/background?v=', + selectedColor, + )) + }) + + it('Login as user', function() { + cy.createRandomUser().then((user) => { + cy.login(user) + }) + }) + + it('See the user background settings', function() { + cy.visit('/settings/user/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .scrollIntoView() + }) + + it('Default user background settings should match admin theming settings', function() { + cy.findByRole('button', { name: 'Default background' }) + .should('exist') + .and('have.attr', 'aria-pressed', 'true') + + cy.window() + .should(() => validateUserThemingDefaultCss( + selectedColor, + '/apps/theming/image/background?v=', + )) + }) +}) + +describe('The user default background settings reflect the admin theming settings with background removed', function() { + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + }) + + after(function() { + cy.resetAdminTheming() + }) + + it('See the admin theming section', function() { + cy.visit('/settings/admin/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .should('exist') + .scrollIntoView() + }) + + it('Remove the default background', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') + cy.findByRole('checkbox', { name: /remove background image/i }) + .check({ force: true }) + cy.wait('@removeBackground') + }) + + it('Login page should match admin theming settings', function() { + cy.logout() + cy.visit('/') + + cy.window() + .should(() => validateBodyThemingCss(defaultPrimary, null)) + }) + + it('Login as user', function() { + cy.createRandomUser().then((user) => { + cy.login(user) + }) + }) + + it('See the user background settings', function() { + cy.visit('/settings/user/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .scrollIntoView() + }) + + it('Default user background settings should match admin theming settings', function() { + cy.findByRole('button', { name: 'Default background' }) + .should('exist') + .and('have.attr', 'aria-pressed', 'true') + + cy.window() + .should(() => validateUserThemingDefaultCss(defaultPrimary, null)) + }) +}) diff --git a/cypress/e2e/theming/admin-settings_urls.cy.ts b/cypress/e2e/theming/admin-settings_branding.cy.ts similarity index 63% rename from cypress/e2e/theming/admin-settings_urls.cy.ts rename to cypress/e2e/theming/admin-settings_branding.cy.ts index 2f093b36d2c..314a7e2224b 100644 --- a/cypress/e2e/theming/admin-settings_urls.cy.ts +++ b/cypress/e2e/theming/admin-settings_branding.cy.ts @@ -2,6 +2,7 @@ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + import { User } from '@nextcloud/e2e-test-server/cypress' const admin = new User('admin', 'admin') @@ -139,3 +140,80 @@ describe('Admin theming: Web link corner cases', function() { .and('have.attr', 'href', 'http://example.com/%22the%20path%22') }) }) + +describe('Admin theming: Change the login fields then reset them', function() { + const name = 'ABCdef123' + const url = 'https://example.com' + const slogan = 'Testing is fun' + + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + }) + + it('See the admin theming section', function() { + cy.visit('/settings/admin/theming') + cy.findByRole('heading', { name: /^Theming/ }) + .should('exist') + .scrollIntoView() + }) + + it('Change the name field', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('updateFields') + + // Name + cy.findByRole('textbox', { name: 'Name' }) + .should('be.visible') + .type(`{selectall}${name}{enter}`) + cy.wait('@updateFields') + + // Url + cy.findByRole('textbox', { name: 'Web link' }) + .should('be.visible') + .type(`{selectall}${url}{enter}`) + cy.wait('@updateFields') + + // Slogan + cy.findByRole('textbox', { name: 'Slogan' }) + .should('be.visible') + .type(`{selectall}${slogan}{enter}`) + cy.wait('@updateFields') + }) + + it('Ensure undo button presence', function() { + cy.findAllByRole('button', { name: /undo changes/i }) + .should('have.length', 3) + }) + + it('Validate login screen changes', function() { + cy.logout() + cy.visit('/') + + cy.get('[data-login-form-headline]').should('contain.text', name) + cy.get('footer p a').should('have.text', name) + cy.get('footer p a').should('have.attr', 'href', url) + cy.get('footer p').should('contain.text', `– ${slogan}`) + }) + + it('Undo theming settings', function() { + cy.login(admin) + cy.visit('/settings/admin/theming') + cy.findAllByRole('button', { name: /undo changes/i }) + .each((button) => { + cy.intercept('*/apps/theming/ajax/undoChanges').as('undoField') + cy.wrap(button).click() + cy.wait('@undoField') + }) + cy.logout() + }) + + it('Validate login screen changes again', function() { + cy.visit('/') + + cy.get('[data-login-form-headline]').should('not.contain.text', name) + cy.get('footer p a').should('not.have.text', name) + cy.get('footer p a').should('not.have.attr', 'href', url) + cy.get('footer p').should('not.contain.text', `– ${slogan}`) + }) +}) diff --git a/cypress/e2e/theming/admin-settings_colors.cy.ts b/cypress/e2e/theming/admin-settings_colors.cy.ts new file mode 100644 index 00000000000..6651c3a4714 --- /dev/null +++ b/cypress/e2e/theming/admin-settings_colors.cy.ts @@ -0,0 +1,67 @@ +/*! + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { User } from '@nextcloud/e2e-test-server/cypress' +import { + defaultBackground, + defaultPrimary, + pickColor, + validateBodyThemingCss, +} from './themingUtils.ts' + +const admin = new User('admin', 'admin') + +describe('Change the primary color and reset it', function() { + let selectedColor = '' + + before(function() { + // Just in case previous test failed + cy.resetAdminTheming() + cy.login(admin) + }) + + it('See the admin theming section', function() { + cy.visit('/settings/admin/theming') + cy.findByRole('heading', { name: 'Background and color' }) + .should('exist') + .scrollIntoView() + }) + + it('Change the primary color', function() { + cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') + + pickColor(cy.findByRole('button', { name: /Primary color/ })) + .then((color) => { + selectedColor = color + }) + + cy.wait('@setColor') + cy.waitUntil(() => validateBodyThemingCss( + selectedColor, + defaultBackground, + defaultPrimary, + )) + }) + + it('Screenshot the login page and validate login page', function() { + cy.logout() + cy.visit('/') + + cy.waitUntil(() => validateBodyThemingCss( + selectedColor, + defaultBackground, + defaultPrimary, + )) + cy.screenshot() + }) + + it('Undo theming settings and validate login page again', function() { + cy.resetAdminTheming() + cy.visit('/') + + cy.waitUntil(validateBodyThemingCss) + cy.screenshot() + }) +}) diff --git a/cypress/e2e/theming/admin-settings_default-app.cy.ts b/cypress/e2e/theming/admin-settings_default-app.cy.ts index cf55f449e50..f83100c8af7 100644 --- a/cypress/e2e/theming/admin-settings_default-app.cy.ts +++ b/cypress/e2e/theming/admin-settings_default-app.cy.ts @@ -31,36 +31,73 @@ describe('Admin theming set default apps', () => { cy.visit('/settings/admin/theming') cy.get('.settings-section').contains('Navigation bar settings').should('exist') - cy.get('[data-cy-switch-default-app]').should('exist') - cy.get('[data-cy-switch-default-app]').scrollIntoView() + getDefaultAppSwitch().should('exist') + getDefaultAppSwitch().scrollIntoView() }) it('Toggle the "use custom default app" switch', () => { - cy.get('[data-cy-switch-default-app] input').should('not.be.checked') - cy.get('[data-cy-switch-default-app] .checkbox-content').click() - cy.get('[data-cy-switch-default-app] input').should('be.checked') + getDefaultAppSwitch().should('not.be.checked') + cy.findByRole('region', { name: 'Global default app' }) + .should('not.exist') + + getDefaultAppSwitch().check({ force: true }) + getDefaultAppSwitch().should('be.checked') + cy.findByRole('region', { name: 'Global default app' }) + .should('exist') + }) + + it('See the default app combobox', () => { + cy.findByRole('region', { name: 'Global default app' }) + .should('exist') + .findByRole('combobox') + .as('defaultAppSelect') + .scrollIntoView() + + cy.get('@defaultAppSelect') + .findByText('Dashboard') + .should('be.visible') + cy.get('@defaultAppSelect') + .findByText('Files') + .should('be.visible') }) it('See the default app order selector', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then((elements) => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['dashboard', 'files']) - }) + cy.findByRole('region', { name: 'Global default app' }) + .should('exist') + cy.findByRole('list', { name: 'Navigation bar app order' }) + .should('exist') + .findAllByRole('listitem') + .should('have.length', 2) + .then((elements) => { + const appIDs = elements.map((idx, el) => el.innerText.trim()).get() + expect(appIDs).to.deep.eq(['Dashboard', 'Files']) + }) }) it('Change the default app', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"]').scrollIntoView() + cy.findByRole('list', { name: 'Navigation bar app order' }) + .should('exist') + .as('appOrderSelector') + .scrollIntoView() - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('be.visible') - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').click() - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.be.visible') + cy.get('@appOrderSelector') + .findAllByRole('listitem') + .filter((_, e) => !!e.innerText.match(/Files/i)) + .findByRole('button', { name: 'Move up' }) + .as('moveFilesUpButton') + + cy.get('@moveFilesUpButton').should('be.visible') + cy.get('@moveFilesUpButton').click() + cy.get('@moveFilesUpButton').should('not.exist') }) it('See the default app is changed', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element]').then((elements) => { - const appIDs = elements.map((idx, el) => el.getAttribute('data-cy-app-order-element')).get() - expect(appIDs).to.deep.eq(['files', 'dashboard']) - }) + cy.findByRole('list', { name: 'Navigation bar app order' }) + .findAllByRole('listitem') + .then((elements) => { + const appIDs = elements.map((idx, el) => el.innerText.trim()).get() + expect(appIDs).to.deep.eq(['Files', 'Dashboard']) + }) // Check the redirect to the default app works cy.request({ url: '/', followRedirect: false }).then((response) => { @@ -72,14 +109,12 @@ describe('Admin theming set default apps', () => { it('Toggle the "use custom default app" switch back to reset the default apps', () => { cy.visit('/settings/admin/theming') - cy.get('[data-cy-switch-default-app]').scrollIntoView() + getDefaultAppSwitch().scrollIntoView() - cy.get('[data-cy-switch-default-app] input').should('be.checked') - cy.get('[data-cy-switch-default-app] .checkbox-content').click() - cy.get('[data-cy-switch-default-app] input').should('be.not.checked') - }) + getDefaultAppSwitch().should('be.checked') + getDefaultAppSwitch().uncheck({ force: true }) + getDefaultAppSwitch().should('be.not.checked') - it('See the default app is changed back to default', () => { // Check the redirect to the default app works cy.request({ url: '/', followRedirect: false }).then((response) => { expect(response.status).to.eq(302) @@ -88,3 +123,7 @@ describe('Admin theming set default apps', () => { }) }) }) + +function getDefaultAppSwitch() { + return cy.findByRole('checkbox', { name: 'Use custom default app' }) +} diff --git a/cypress/e2e/theming/themingUtils.ts b/cypress/e2e/theming/themingUtils.ts index 22da5fa671d..59487920e58 100644 --- a/cypress/e2e/theming/themingUtils.ts +++ b/cypress/e2e/theming/themingUtils.ts @@ -63,13 +63,8 @@ export function expectBackgroundColor(element: JQuery, color: strin * @param expectedBackground the expected background */ export function validateUserThemingDefaultCss(expectedColor = defaultPrimary, expectedBackground: string | null = defaultBackground) { - const defaultSelectButton = Cypress.$('[data-user-theming-background-default]') - if (defaultSelectButton.length === 0) { - return false - } - - const backgroundImage = defaultSelectButton.css('background-image') - const backgroundColor = defaultSelectButton.css('background-color') + const backgroundImage = Cypress.$('body').css('background-image') + const backgroundColor = Cypress.$('body').css('background-color') const isValidBackgroundImage = !expectedBackground ? (backgroundImage === 'none' || Cypress.$('body').css('background-image') === 'none') @@ -86,31 +81,30 @@ export function validateUserThemingDefaultCss(expectedColor = defaultPrimary, ex } /** - * - * @param context - * @param index + * @param trigger - The color picker trigger + * @param index - The color index to pick, if not provided a random one will be picked */ -export function pickRandomColor(context: string, index?: number): Cypress.Chainable { +export function pickColor(trigger: Cypress.Chainable, index?: number): Cypress.Chainable { // Pick one of the first 8 options const randColour = index ?? Math.floor(Math.random() * 8) - const colorPreviewSelector = `${context} [data-admin-theming-setting-color]` - let oldColor = '' - cy.get(colorPreviewSelector).then(($el) => { + trigger.as('trigger').then(($el) => { oldColor = $el.css('background-color') }) - // Open picker - cy.get(`${context} [data-admin-theming-setting-color-picker]`).scrollIntoView() - cy.get(`${context} [data-admin-theming-setting-color-picker]`).click({ force: true }) + cy.get('@trigger').scrollIntoView() + cy.get('@trigger').click({ force: true }) // Click on random color cy.get('.color-picker__simple-color-circle').eq(randColour).click() // Wait for color change - cy.waitUntil(() => Cypress.$(colorPreviewSelector).css('background-color') !== oldColor) + cy.get('@trigger') + .should(($el) => $el.css('background-color') !== oldColor) + + cy.findByRole('button', { name: /Choose/i }).click() // Get the selected color from the color preview block - return cy.get(colorPreviewSelector).then(($el) => $el.css('background-color')) + return cy.get('@trigger').then(($el) => $el.css('background-color')) } diff --git a/cypress/e2e/theming/user-settings_app-order.cy.ts b/cypress/e2e/theming/user-settings_app-order.cy.ts index f30f9c67ce9..3db1a17ac4e 100644 --- a/cypress/e2e/theming/user-settings_app-order.cy.ts +++ b/cypress/e2e/theming/user-settings_app-order.cy.ts @@ -6,19 +6,14 @@ import type { User } from '@nextcloud/e2e-test-server/cypress' import { NavigationHeader } from '../../pages/NavigationHeader.ts' +import { SettingsAppOrderList } from '../../pages/SettingsAppOrderList.ts' import { installTestApp, uninstallTestApp } from '../../support/commonUtils.ts' -/** - * Intercept setting the app order as `updateAppOrder` - */ -function interceptAppOrder() { - cy.intercept('POST', '/ocs/v2.php/apps/provisioning_api/api/v1/config/users/core/apporder').as('updateAppOrder') -} - before(() => uninstallTestApp()) describe('User theming set app order', () => { const navigationHeader = new NavigationHeader() + const appOrderList = new SettingsAppOrderList() let user: User before(() => { @@ -33,49 +28,45 @@ describe('User theming set app order', () => { after(() => cy.deleteUser(user)) it('See the app order settings', () => { - cy.visit('/settings/user/theming') - - cy.get('.settings-section').contains('Navigation bar settings').should('exist') - cy.get('[data-cy-app-order]').scrollIntoView() + visitAppOrderSettings() }) it('See that the dashboard app is the first one', () => { const appOrder = ['Dashboard', 'Files'] - // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]') - .each((element, index) => expect(element).to.contain.text(appOrder[index])) + appOrderList.assertAppOrder(appOrder) // Check the top app menu order navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index])) + .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) }) it('Change the app order', () => { - interceptAppOrder() - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('be.visible') - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').click() - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.be.visible') - cy.wait('@updateAppOrder') + appOrderList.interceptAppOrder() + appOrderList.getAppOrderList() + .scrollIntoView() + appOrderList.getUpButtonForApp('Files') + .should('be.visible') + .click() + appOrderList.waitForAppOrderUpdate() - const appOrder = ['Files', 'Dashboard'] - cy.get('[data-cy-app-order] [data-cy-app-order-element]') - .each((element, index) => expect(element).to.contain.text(appOrder[index])) + appOrderList.assertAppOrder(['Files', 'Dashboard']) }) it('See the app menu order is changed', () => { cy.reload() const appOrder = ['Files', 'Dashboard'] - // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]') - .each((element, index) => expect(element).to.contain.text(appOrder[index])) + appOrderList.getAppOrderList() + .scrollIntoView() + appOrderList.assertAppOrder(appOrder) // Check the top app menu order navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index])) + .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) }) }) describe('User theming set app order with default app', () => { + const appOrderList = new SettingsAppOrderList() const navigationHeader = new NavigationHeader() let user: User @@ -108,43 +99,41 @@ describe('User theming set app order with default app', () => { }) it('See the app order settings: files is the first one', () => { - cy.visit('/settings/user/theming') - cy.get('[data-cy-app-order]').scrollIntoView() + visitAppOrderSettings() const appOrder = ['Files', 'Dashboard', 'Test App 2', 'Test App'] - // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]') - .each((element, index) => expect(element).to.contain.text(appOrder[index])) + appOrderList.getAppOrderList() + .scrollIntoView() + appOrderList.assertAppOrder(appOrder) }) it('Can not change the default app', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.be.visible') - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="down"]').should('not.be.visible') + appOrderList.getUpButtonForApp('Files').should('not.exist') + appOrderList.getDownButtonForApp('Files').should('not.exist') + appOrderList.getUpButtonForApp('Dashboard').should('not.exist') + // but can move down + appOrderList.getDownButtonForApp('Dashboard').should('be.visible') + }) - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').should('not.be.visible') - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').should('be.visible') - - cy.get('[data-cy-app-order] [data-cy-app-order-element="testapp"] [data-cy-app-order-button="up"]').should('be.visible') - cy.get('[data-cy-app-order] [data-cy-app-order-element="testapp"] [data-cy-app-order-button="down"]').should('not.be.visible') + it('Can see the correct buttons for other apps', () => { + appOrderList.getUpButtonForApp('Test App 2').should('be.visible') + appOrderList.getDownButtonForApp('Test App 2').should('be.visible') + appOrderList.getUpButtonForApp('Test App').should('be.visible') + appOrderList.getDownButtonForApp('Test App').should('not.exist') }) it('Change the order of the other apps', () => { - interceptAppOrder() - - // Move the testapp up twice, it should be the first one after files - cy.get('[data-cy-app-order] [data-cy-app-order-element="testapp"] [data-cy-app-order-button="up"]').click() - cy.wait('@updateAppOrder') - cy.get('[data-cy-app-order] [data-cy-app-order-element="testapp"] [data-cy-app-order-button="up"]').click() - cy.wait('@updateAppOrder') + appOrderList.interceptAppOrder() + appOrderList.getUpButtonForApp('Test App').click() + appOrderList.waitForAppOrderUpdate() + appOrderList.getUpButtonForApp('Test App').click() + appOrderList.waitForAppOrderUpdate() // Can't get up anymore, files is enforced as default app - cy.get('[data-cy-app-order] [data-cy-app-order-element="testapp"] [data-cy-app-order-button="up"]').should('not.be.visible') + appOrderList.getUpButtonForApp('Test App').should('not.exist') - // Check the final list order - const appOrder = ['Files', 'Test App', 'Dashboard', 'Test App 2'] // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]') - .each((element, index) => expect(element).to.contain.text(appOrder[index])) + appOrderList.assertAppOrder(['Files', 'Test App', 'Dashboard', 'Test App 2']) }) it('See the app menu order is changed', () => { @@ -153,15 +142,17 @@ describe('User theming set app order with default app', () => { const appOrder = ['Files', 'Test App', 'Dashboard', 'Test App 2'] // Check the top app menu order navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index])) + .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) }) }) describe('User theming app order list accessibility', () => { + const appOrderList = new SettingsAppOrderList() let user: User before(() => { cy.resetAdminTheming() + installTestApp() // Create random user for this test cy.createRandomUser().then(($user) => { user = $user @@ -170,45 +161,44 @@ describe('User theming app order list accessibility', () => { }) after(() => { + uninstallTestApp() cy.deleteUser(user) }) - it('See the app order settings', () => { - cy.visit('/settings/user/theming') - cy.get('[data-cy-app-order]').scrollIntoView() - cy.get('[data-cy-app-order] [data-cy-app-order-element]').should('have.length', 2) - }) - it('click the first button', () => { - interceptAppOrder() - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').should('be.visible').focus() - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').click() - cy.wait('@updateAppOrder') + visitAppOrderSettings() + appOrderList.interceptAppOrder() + appOrderList.getDownButtonForApp('Dashboard') + .should('be.visible') + .scrollIntoView() + appOrderList.getDownButtonForApp('Dashboard') + .focus() + appOrderList.getDownButtonForApp('Dashboard') + .click() + appOrderList.waitForAppOrderUpdate() }) it('see the same app kept the focus', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="down"]').should('not.have.focus') - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.have.focus') - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').should('not.have.focus') - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').should('have.focus') + appOrderList.getDownButtonForApp('Dashboard').should('have.focus') }) it('click the last button', () => { - interceptAppOrder() - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').should('be.visible').focus() - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').click() - cy.wait('@updateAppOrder') + appOrderList.interceptAppOrder() + appOrderList.getUpButtonForApp('Dashboard') + .should('be.visible') + .focus() + appOrderList.getUpButtonForApp('Dashboard').click() + appOrderList.waitForAppOrderUpdate() }) it('see the same app kept the focus', () => { - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="down"]').should('not.have.focus') - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.have.focus') - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').should('not.have.focus') - cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').should('have.focus') + appOrderList.getUpButtonForApp('Dashboard').should('not.exist') + appOrderList.getDownButtonForApp('Dashboard').should('have.focus') }) }) describe('User theming reset app order', () => { + const appOrderList = new SettingsAppOrderList() const navigationHeader = new NavigationHeader() let user: User @@ -223,52 +213,46 @@ describe('User theming reset app order', () => { after(() => cy.deleteUser(user)) - it('See the app order settings', () => { - cy.visit('/settings/user/theming') - - cy.get('.settings-section').contains('Navigation bar settings').should('exist') - cy.get('[data-cy-app-order]').scrollIntoView() - }) - it('See that the dashboard app is the first one', () => { + visitAppOrderSettings() + const appOrder = ['Dashboard', 'Files'] - // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]') - .each((element, index) => expect(element).to.contain.text(appOrder[index])) + appOrderList.assertAppOrder(appOrder) // Check the top app menu order navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index])) + .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) }) it('See the reset button is disabled', () => { - cy.get('[data-test-id="btn-apporder-reset"]').scrollIntoView() - cy.get('[data-test-id="btn-apporder-reset"]').should('be.visible').and('have.attr', 'disabled') + appOrderList.getResetButton() + .scrollIntoView() + appOrderList.getResetButton() + .should('be.disabled') }) it('Change the app order', () => { - interceptAppOrder() - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('be.visible') - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').click() - cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.be.visible') - cy.wait('@updateAppOrder') + appOrderList.interceptAppOrder() + appOrderList.getUpButtonForApp('Files') + .should('be.visible') + .click() + appOrderList.waitForAppOrderUpdate() - // Check the app order settings UI - const appOrder = ['Files', 'Dashboard'] - // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]') - .each((element, index) => expect(element).to.contain.text(appOrder[index])) + appOrderList.assertAppOrder(['Files', 'Dashboard']) }) it('See the reset button is no longer disabled', () => { - cy.get('[data-test-id="btn-apporder-reset"]').scrollIntoView() - cy.get('[data-test-id="btn-apporder-reset"]').should('be.visible').and('not.have.attr', 'disabled') + appOrderList.getResetButton() + .scrollIntoView() + appOrderList.getResetButton() + .should('be.visible') + .and('be.enabled') }) it('Reset the app order', () => { cy.intercept('GET', '/ocs/v2.php/core/navigation/apps').as('loadApps') - interceptAppOrder() - cy.get('[data-test-id="btn-apporder-reset"]').click({ force: true }) + appOrderList.interceptAppOrder() + appOrderList.getResetButton().click({ force: true }) cy.wait('@updateAppOrder') .its('request.body') @@ -278,16 +262,21 @@ describe('User theming reset app order', () => { it('See the app order is restored', () => { const appOrder = ['Dashboard', 'Files'] - // Check the app order settings UI - cy.get('[data-cy-app-order] [data-cy-app-order-element]') - .each((element, index) => expect(element).to.contain.text(appOrder[index])) - + appOrderList.assertAppOrder(appOrder) // Check the top app menu order navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index])) + .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) }) it('See the reset button is disabled again', () => { - cy.get('[data-test-id="btn-apporder-reset"]').should('be.visible').and('have.attr', 'disabled') + appOrderList.getResetButton() + .should('be.disabled') }) }) + +function visitAppOrderSettings() { + cy.visit('/settings/user/theming') + cy.findByRole('heading', { name: /Navigation bar settings/ }) + .should('exist') + .scrollIntoView() +} diff --git a/cypress/e2e/theming/user-settings_background.cy.ts b/cypress/e2e/theming/user-settings_background.cy.ts index 07965ef58b0..639de55f571 100644 --- a/cypress/e2e/theming/user-settings_background.cy.ts +++ b/cypress/e2e/theming/user-settings_background.cy.ts @@ -5,7 +5,7 @@ import { User } from '@nextcloud/e2e-test-server/cypress' import { NavigationHeader } from '../../pages/NavigationHeader.ts' -import { defaultBackground, defaultPrimary, validateBodyThemingCss } from './themingUtils.ts' +import { defaultPrimary, pickColor, validateBodyThemingCss } from './themingUtils.ts' const admin = new User('admin', 'admin') @@ -20,22 +20,14 @@ describe('User default background settings', function() { it('See the user background settings', function() { cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-settings]').scrollIntoView() - cy.get('[data-user-theming-background-settings]').should('be.visible') - }) - - // Default cloud background is not rendered if admin theming background remains unchanged - it('Default cloud background is not rendered', function() { - cy.get(`[data-user-theming-background-shipped="${defaultBackground}"]`).should('not.exist') + cy.findByRole('heading', { name: /Appearance and accessibility settings/ }) + .should('be.visible') }) it('Default is selected on new users', function() { - cy.get('[data-user-theming-background-default]').should('be.visible') - cy.get('[data-user-theming-background-default]').should('have.class', 'background--active') - }) - - it('Default background has accessibility attribute set', function() { - cy.get('[data-user-theming-background-default]').should('have.attr', 'aria-pressed', 'true') + cy.findByRole('button', { name: 'Default background', pressed: true }) + .should('exist') + .scrollIntoView() }) }) @@ -48,19 +40,21 @@ describe('User select shipped backgrounds and remove background', function() { it('See the user background settings', function() { cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-settings]').scrollIntoView() - cy.get('[data-user-theming-background-settings]').should('be.visible') + cy.findByRole('heading', { name: /Background and color/ }) + .should('exist') + .scrollIntoView() }) it('Select a shipped background', function() { const background = 'anatoly-mikhaltsov-butterfly-wing-scale.jpg' + const backgroundName = 'Background picture of a red-ish butterfly wing under microscope' cy.intercept('*/apps/theming/background/shipped').as('setBackground') // Select background - cy.get(`[data-user-theming-background-shipped="${background}"]`).click() - - // Set the accessibility state - cy.get(`[data-user-theming-background-shipped="${background}"]`).should('have.attr', 'aria-pressed', 'true') + cy.findByRole('button', { name: backgroundName, pressed: false }) + .click() + cy.findByRole('button', { name: backgroundName, pressed: true }) + .should('be.visible') // Validate changed background and primary cy.wait('@setBackground') @@ -69,32 +63,18 @@ describe('User select shipped backgrounds and remove background', function() { it('Select a bright shipped background', function() { const background = 'bernie-cetonia-aurata-take-off-composition.jpg' + const backgroundName = 'Montage of a cetonia aurata bug that takes off with white background' cy.intercept('*/apps/theming/background/shipped').as('setBackground') - // Select background - cy.get(`[data-user-theming-background-shipped="${background}"]`).click() - - // Set the accessibility state - cy.get(`[data-user-theming-background-shipped="${background}"]`).should('have.attr', 'aria-pressed', 'true') + cy.findByRole('button', { name: backgroundName, pressed: false }) + .click() + cy.findByRole('button', { name: backgroundName, pressed: true }) + .should('be.visible') // Validate changed background and primary cy.wait('@setBackground') cy.waitUntil(() => validateBodyThemingCss('#56633d', background, '#dee0d3')) }) - - it('Remove background', function() { - cy.intercept('*/apps/theming/background/color').as('clearBackground') - - // Clear background - cy.get('[data-user-theming-background-color]').click() - - // Set the accessibility state - cy.get('[data-user-theming-background-color]').should('have.attr', 'aria-pressed', 'true') - - // Validate clear background - cy.wait('@clearBackground') - cy.waitUntil(() => validateBodyThemingCss('#56633d', null, '#dee0d3')) - }) }) describe('User select a custom color', function() { @@ -106,19 +86,20 @@ describe('User select a custom color', function() { it('See the user background settings', function() { cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-settings]').scrollIntoView() - cy.get('[data-user-theming-background-settings]').should('be.visible') + cy.findByRole('heading', { name: /Background and color/ }) + .should('exist') + .scrollIntoView() }) it('Select a custom color', function() { - cy.intercept('*/apps/theming/background/color').as('setColor') + cy.intercept('*/apps/theming/background/color').as('clearBackground') - cy.get('[data-user-theming-background-color]').click() - cy.get('.color-picker__simple-color-circle').eq(5).click() + // Clear background + pickColor(cy.findByRole('button', { name: 'Plain background' }), 7) - // Validate custom colour change - cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#a5b872')) + // Validate clear background + cy.wait('@clearBackground') + cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#3794ac')) }) }) @@ -133,32 +114,20 @@ describe('User select a bright custom color and remove background', function() { it('See the user background settings', function() { cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-settings]').scrollIntoView() - cy.get('[data-user-theming-background-settings]').should('be.visible') + cy.findByRole('heading', { name: /Background and color/ }) + .should('exist') + .scrollIntoView() }) it('Remove background', function() { cy.intercept('*/apps/theming/background/color').as('clearBackground') // Clear background - cy.get('[data-user-theming-background-color]').click() - cy.get('[data-user-theming-background-color]').click() + pickColor(cy.findByRole('button', { name: 'Plain background' }), 4) // Validate clear background cy.wait('@clearBackground') - cy.waitUntil(() => validateBodyThemingCss(undefined, null)) - }) - - it('Select a custom color', function() { - cy.intercept('*/apps/theming/background/color').as('setColor') - - // Pick one of the bright color preset - cy.get('[data-user-theming-background-color]').scrollIntoView() - cy.get('[data-user-theming-background-color]').click() - cy.get('.color-picker__simple-color-circle:eq(4)').click() - - // Validate custom colour change - cy.wait('@setColor') + cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#ddcb55')) }) it('See the header being inverted', function() { @@ -173,10 +142,14 @@ describe('User select a bright custom color and remove background', function() { it('Select another but non-bright shipped background', function() { const background = 'anatoly-mikhaltsov-butterfly-wing-scale.jpg' + const backgroundName = 'Background picture of a red-ish butterfly wing under microscope' cy.intercept('*/apps/theming/background/shipped').as('setBackground') // Select background - cy.get(`[data-user-theming-background-shipped="${background}"]`).click() + cy.findByRole('button', { name: backgroundName, pressed: false }) + .click() + cy.findByRole('button', { name: backgroundName, pressed: true }) + .should('be.visible') // Validate changed background and primary cy.wait('@setBackground') @@ -205,23 +178,21 @@ describe('User select a custom background', function() { it('See the user background settings', function() { cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-settings]').scrollIntoView() - cy.get('[data-user-theming-background-settings]').should('be.visible') + cy.findByRole('heading', { name: /Background and color/ }) + .should('exist') + .scrollIntoView() }) it('Select a custom background', function() { cy.intercept('*/apps/theming/background/custom').as('setBackground') - cy.on('uncaught:exception', (err) => { - // This can happen because of blink engine & skeleton animation, its not a bug just engine related. - if (err.message.includes('ResizeObserver loop limit exceeded')) { - return false - } - }) - // Pick background - cy.get('[data-user-theming-background-custom]').click() - cy.get('.file-picker__files tr').contains(image).click() + cy.findByRole('button', { name: 'Custom background' }).click() + cy.findByRole('dialog') + .should('be.visible') + .findAllByRole('row') + .contains(image) + .click() cy.findByRole('button', { name: 'Select background' }).click() // Wait for background to be set @@ -232,7 +203,6 @@ describe('User select a custom background', function() { describe('User changes settings and reload the page', function() { const image = 'image.jpg' - const colorFromImage = '#2f2221' before(function() { cy.createRandomUser().then((user: User) => { @@ -243,54 +213,44 @@ describe('User changes settings and reload the page', function() { it('See the user background settings', function() { cy.visit('/settings/user/theming') - cy.get('[data-user-theming-background-settings]').scrollIntoView() - cy.get('[data-user-theming-background-settings]').should('be.visible') + cy.findByRole('heading', { name: /Background and color/ }) + .should('exist') + .scrollIntoView() }) it('Select a custom background', function() { cy.intercept('*/apps/theming/background/custom').as('setBackground') - cy.on('uncaught:exception', (err) => { - // This can happen because of blink engine & skeleton animation, its not a bug just engine related. - if (err.message.includes('ResizeObserver loop limit exceeded')) { - return false - } - }) - // Pick background - cy.get('[data-user-theming-background-custom]').click() - cy.get('.file-picker__files tr').contains(image).click() + cy.findByRole('button', { name: 'Custom background' }).click() + cy.findByRole('dialog') + .should('be.visible') + .findAllByRole('row') + .contains(image) + .click() cy.findByRole('button', { name: 'Select background' }).click() // Wait for background to be set cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, 'apps/theming/background?v=', colorFromImage)) + cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, 'apps/theming/background?v=', '#2f2221')) }) - it('Select a custom color', function() { - cy.intercept('*/apps/theming/background/color').as('setColor') + it('Select a custom background color', function() { + cy.intercept('*/apps/theming/background/color').as('clearBackground') - cy.get('[data-user-theming-background-color]').click() - cy.get('.color-picker__simple-color-circle:eq(5)').click() - cy.get('[data-user-theming-background-color]').click() + // Clear background + pickColor(cy.findByRole('button', { name: 'Plain background' }), 5) // Validate clear background - cy.wait('@setColor') + cy.wait('@clearBackground') cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#a5b872')) }) it('Select a custom primary color', function() { cy.intercept('/ocs/v2.php/apps/provisioning_api/api/v1/config/users/theming/primary_color').as('setPrimaryColor') - cy.get('[data-user-theming-primary-color-trigger]').scrollIntoView() - cy.get('[data-user-theming-primary-color-trigger]').click() - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(500) - cy.get('.color-picker__simple-color-circle').should('be.visible') - cy.get('.color-picker__simple-color-circle:eq(2)').click() - cy.get('[data-user-theming-primary-color-trigger]').click() + pickColor(cy.findByRole('button', { name: 'Primary color' }), 2) - // Validate clear background cy.wait('@setPrimaryColor') cy.waitUntil(() => validateBodyThemingCss('#c98879', null, '#a5b872')) }) diff --git a/cypress/pages/SettingsAppOrderList.ts b/cypress/pages/SettingsAppOrderList.ts new file mode 100644 index 00000000000..f95c3159b23 --- /dev/null +++ b/cypress/pages/SettingsAppOrderList.ts @@ -0,0 +1,43 @@ +/*! + * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +export class SettingsAppOrderList { + getAppOrderList() { + return cy.findByRole('list', { name: 'Navigation bar app order' }) + } + + assertAppOrder(expectedAppOrder: string[]) { + this.getAppOrderList() + .findAllByRole('listitem') + .should('have.length', expectedAppOrder.length) + .each((element, index) => expect(element).to.contain.text(expectedAppOrder[index]!)) + } + + getAppEntryByName(appName: string) { + return this.getAppOrderList() + .findAllByRole('listitem') + .filter((_, el) => el.textContent.trim() === appName) + } + + getUpButtonForApp(appName: string) { + return this.getAppEntryByName(appName).findByRole('button', { name: 'Move up', hidden: true }) + } + + getDownButtonForApp(appName: string) { + return this.getAppEntryByName(appName).findByRole('button', { name: 'Move down', hidden: true }) + } + + getResetButton() { + return cy.findByRole('button', { name: 'Reset default app order', hidden: true }) + } + + interceptAppOrder() { + cy.intercept('POST', '/ocs/v2.php/apps/provisioning_api/api/v1/config/users/core/apporder').as('updateAppOrder') + } + + waitForAppOrderUpdate() { + cy.wait('@updateAppOrder') + } +}