From a3b30387096c1376ca4b7b4d70dbf25291296239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= <43352493+ridvanakca@users.noreply.github.com> Date: Fri, 25 Aug 2023 22:31:02 +0300 Subject: [PATCH] test(user-interface-configuration): write initial tests (#1242) * test(user-interface): add tests with playwright * test: refactor UI configuration tests --------- Co-authored-by: Ali BARIN --- .../e2e-tests/fixtures/applications-page.js | 14 +- .../e2e-tests/fixtures/authenticated-page.js | 21 +++ packages/e2e-tests/fixtures/base-page.js | 9 +- .../e2e-tests/fixtures/connections-page.js | 12 +- .../e2e-tests/fixtures/executions-page.js | 12 +- .../e2e-tests/fixtures/flow-editor-page.js | 14 +- packages/e2e-tests/fixtures/index.js | 8 +- packages/e2e-tests/fixtures/login-page.js | 4 +- .../e2e-tests/fixtures/user-interface-page.js | 53 ++++++ .../user-interface-configuration.spec.js | 176 ++++++++++++++++++ .../components/AdminSettingsLayout/index.tsx | 5 + packages/web/src/components/AppBar/index.tsx | 2 +- .../ColorInput/ColorButton/index.tsx | 40 ++++ .../ColorInput/ColorButton/style.tsx | 15 ++ .../web/src/components/ColorInput/index.tsx | 5 +- .../src/components/CustomLogo/index.ee.tsx | 5 +- packages/web/src/components/Drawer/index.tsx | 29 +-- packages/web/src/components/FlowRow/index.tsx | 25 ++- packages/web/src/components/Logo/index.tsx | 4 +- .../web/src/pages/UserInterface/index.tsx | 5 + 20 files changed, 389 insertions(+), 69 deletions(-) create mode 100644 packages/e2e-tests/fixtures/authenticated-page.js create mode 100644 packages/e2e-tests/fixtures/user-interface-page.js create mode 100644 packages/e2e-tests/tests/user-interface/user-interface-configuration.spec.js create mode 100644 packages/web/src/components/ColorInput/ColorButton/index.tsx create mode 100644 packages/web/src/components/ColorInput/ColorButton/style.tsx diff --git a/packages/e2e-tests/fixtures/applications-page.js b/packages/e2e-tests/fixtures/applications-page.js index 1456de19..f7b6abc4 100644 --- a/packages/e2e-tests/fixtures/applications-page.js +++ b/packages/e2e-tests/fixtures/applications-page.js @@ -1,7 +1,9 @@ const path = require('node:path'); -const { BasePage } = require('./base-page'); +const { AuthenticatedPage } = require('./authenticated-page'); + +export class ApplicationsPage extends AuthenticatedPage { + screenshotPath = '/applications'; -export class ApplicationsPage extends BasePage { /** * @param {import('@playwright/test').Page} page */ @@ -11,12 +13,4 @@ export class ApplicationsPage extends BasePage { this.drawerLink = this.page.getByTestId('apps-page-drawer-link'); this.addConnectionButton = this.page.getByTestId('add-connection-button'); } - - async screenshot(options = {}) { - const { path: plainPath, ...restOptions } = options; - - const computedPath = path.join('applications', plainPath); - - return await super.screenshot({ path: computedPath, ...restOptions }); - } } diff --git a/packages/e2e-tests/fixtures/authenticated-page.js b/packages/e2e-tests/fixtures/authenticated-page.js new file mode 100644 index 00000000..58fc4d44 --- /dev/null +++ b/packages/e2e-tests/fixtures/authenticated-page.js @@ -0,0 +1,21 @@ +const path = require('node:path'); +const { expect } = require('@playwright/test'); +const { BasePage } = require('./base-page'); +const { LoginPage } = require('./login-page'); + +export class AuthenticatedPage extends BasePage { + /** + * @param {import('@playwright/test').Page} page + */ + constructor(page) { + super(page); + + this.profileMenuButton = this.page.getByTestId('profile-menu-button'); + this.adminMenuItem = this.page.getByRole('menuitem', { name: 'Admin' }); + this.userInterfaceDrawerItem = this.page.getByTestId('user-interface-drawer-link'); + this.appBar = this.page.getByTestId('app-bar'); + this.goToDashboardButton = this.page.getByTestId('go-back-drawer-link'); + this.typographyLogo = this.page.getByTestId('typography-logo'); + this.customLogo = this.page.getByTestId('custom-logo'); + } +} diff --git a/packages/e2e-tests/fixtures/base-page.js b/packages/e2e-tests/fixtures/base-page.js index 6e4b251f..b298a41d 100644 --- a/packages/e2e-tests/fixtures/base-page.js +++ b/packages/e2e-tests/fixtures/base-page.js @@ -1,11 +1,14 @@ const path = require('node:path'); export class BasePage { + screenshotPath = '/'; + /** * @param {import('@playwright/test').Page} page */ constructor(page) { this.page = page; + this.snackbar = this.page.locator('#notistack-snackbar'); } async clickAway() { @@ -15,7 +18,11 @@ export class BasePage { async screenshot(options = {}) { const { path: plainPath, ...restOptions } = options; - const computedPath = path.join('output/screenshots', plainPath); + const computedPath = path.join( + 'output/screenshots', + this.screenshotPath, + plainPath + ); return await this.page.screenshot({ path: computedPath, ...restOptions }); } diff --git a/packages/e2e-tests/fixtures/connections-page.js b/packages/e2e-tests/fixtures/connections-page.js index 4f03cadb..ad2c7fc8 100644 --- a/packages/e2e-tests/fixtures/connections-page.js +++ b/packages/e2e-tests/fixtures/connections-page.js @@ -1,14 +1,8 @@ const path = require('node:path'); -const { BasePage } = require('./base-page'); +const { AuthenticatedPage } = require('./authenticated-page'); -export class ConnectionsPage extends BasePage { - async screenshot(options = {}) { - const { path: plainPath, ...restOptions } = options; - - const computedPath = path.join('connections', plainPath); - - return await super.screenshot({ path: computedPath, ...restOptions }); - } +export class ConnectionsPage extends AuthenticatedPage { + screenshotPath = '/connections'; async clickAddConnectionButton() { await this.page.getByTestId('add-connection-button').click(); diff --git a/packages/e2e-tests/fixtures/executions-page.js b/packages/e2e-tests/fixtures/executions-page.js index 2d5fc4f0..4a00782a 100644 --- a/packages/e2e-tests/fixtures/executions-page.js +++ b/packages/e2e-tests/fixtures/executions-page.js @@ -1,12 +1,6 @@ const path = require('node:path'); -const { BasePage } = require('./base-page'); +const { AuthenticatedPage } = require('./authenticated-page'); -export class ExecutionsPage extends BasePage { - async screenshot(options = {}) { - const { path: plainPath, ...restOptions } = options; - - const computedPath = path.join('executions', plainPath); - - return await super.screenshot({ path: computedPath, ...restOptions }); - } +export class ExecutionsPage extends AuthenticatedPage { + screenshotPath = '/executions'; } diff --git a/packages/e2e-tests/fixtures/flow-editor-page.js b/packages/e2e-tests/fixtures/flow-editor-page.js index 270a8347..d0383525 100644 --- a/packages/e2e-tests/fixtures/flow-editor-page.js +++ b/packages/e2e-tests/fixtures/flow-editor-page.js @@ -1,7 +1,9 @@ const path = require('node:path'); -const { BasePage } = require('./base-page'); +const { AuthenticatedPage } = require('./authenticated-page'); + +export class FlowEditorPage extends AuthenticatedPage { + screenshotPath = '/flow-editor'; -export class FlowEditorPage extends BasePage { /** * @param {import('@playwright/test').Page} page */ @@ -21,12 +23,4 @@ export class FlowEditorPage extends BasePage { this.trigger = this.page.getByLabel('Trigger on weekends?'); this.stepCircularLoader = this.page.getByTestId('step-circular-loader'); } - - async screenshot(options = {}) { - const { path: plainPath, ...restOptions } = options; - - const computedPath = path.join('flow-editor', plainPath); - - return await super.screenshot({ path: computedPath, ...restOptions }); - } } diff --git a/packages/e2e-tests/fixtures/index.js b/packages/e2e-tests/fixtures/index.js index c4388aa4..2b228cd0 100644 --- a/packages/e2e-tests/fixtures/index.js +++ b/packages/e2e-tests/fixtures/index.js @@ -1,8 +1,9 @@ -const { test, expect} = require('@playwright/test'); +const { test, expect } = require('@playwright/test'); const { ApplicationsPage } = require('./applications-page'); const { ConnectionsPage } = require('./connections-page'); const { ExecutionsPage } = require('./executions-page'); const { FlowEditorPage } = require('./flow-editor-page'); +const { UserInterfacePage } = require('./user-interface-page'); const { LoginPage } = require('./login-page'); exports.test = test.extend({ @@ -23,6 +24,9 @@ exports.test = test.extend({ flowEditorPage: async ({ page }, use) => { await use(new FlowEditorPage(page)); }, + userInterfacePage: async ({ page }, use) => { + await use(new UserInterfacePage(page)); + }, }); expect.extend({ @@ -30,7 +34,7 @@ expect.extend({ await expect(locator).not.toHaveAttribute('aria-disabled', 'true'); return { pass: true }; - } + }, }); exports.expect = expect; diff --git a/packages/e2e-tests/fixtures/login-page.js b/packages/e2e-tests/fixtures/login-page.js index 78647fc5..5ab37cb6 100644 --- a/packages/e2e-tests/fixtures/login-page.js +++ b/packages/e2e-tests/fixtures/login-page.js @@ -3,6 +3,8 @@ const { expect } = require('@playwright/test'); const { BasePage } = require('./base-page'); export class LoginPage extends BasePage { + path = '/login'; + /** * @param {import('@playwright/test').Page} page */ @@ -15,8 +17,6 @@ export class LoginPage extends BasePage { this.loginButton = this.page.getByTestId('login-button'); } - path = '/login'; - async login() { await this.page.goto(this.path); await this.emailTextField.fill(process.env.LOGIN_EMAIL); diff --git a/packages/e2e-tests/fixtures/user-interface-page.js b/packages/e2e-tests/fixtures/user-interface-page.js new file mode 100644 index 00000000..119b92b1 --- /dev/null +++ b/packages/e2e-tests/fixtures/user-interface-page.js @@ -0,0 +1,53 @@ +const path = require('node:path'); +const { AuthenticatedPage } = require('./authenticated-page'); + +export class UserInterfacePage extends AuthenticatedPage { + screenshotPath = '/user-interface'; + + /** + * @param {import('@playwright/test').Page} page + */ + constructor(page) { + super(page); + + this.flowRowCardActionArea = this.page + .getByTestId('flow-row') + .first() + .getByTestId('card-action-area'); + this.updateButton = this.page.getByTestId('update-button'); + this.primaryMainColorInput = this.page + .getByTestId('primary-main-color-input') + .getByTestId('color-text-field'); + this.primaryDarkColorInput = this.page + .getByTestId('primary-dark-color-input') + .getByTestId('color-text-field'); + this.primaryLightColorInput = this.page + .getByTestId('primary-light-color-input') + .getByTestId('color-text-field'); + this.logoSvgCodeInput = this.page.getByTestId('logo-svg-data-text-field'); + this.primaryMainColorButton = this.page + .getByTestId('primary-main-color-input') + .getByTestId('color-button'); + this.primaryDarkColorButton = this.page + .getByTestId('primary-dark-color-input') + .getByTestId('color-button'); + this.primaryLightColorButton = this.page + .getByTestId('primary-light-color-input') + .getByTestId('color-button'); + } + + hexToRgb(hexColor) { + hexColor = hexColor.replace('#', ''); + const r = parseInt(hexColor.substring(0, 2), 16); + const g = parseInt(hexColor.substring(2, 4), 16); + const b = parseInt(hexColor.substring(4, 6), 16); + + return `rgb(${r}, ${g}, ${b})`; + } + + encodeSVG(svgCode) { + const encoded = encodeURIComponent(svgCode); + + return `data:image/svg+xml;utf8,${encoded}`; + } +} diff --git a/packages/e2e-tests/tests/user-interface/user-interface-configuration.spec.js b/packages/e2e-tests/tests/user-interface/user-interface-configuration.spec.js new file mode 100644 index 00000000..ea97c8b2 --- /dev/null +++ b/packages/e2e-tests/tests/user-interface/user-interface-configuration.spec.js @@ -0,0 +1,176 @@ +// @ts-check +const { test, expect } = require('../../fixtures/index'); + +test.describe('User interface page', () => { + test.beforeEach(async ({ userInterfacePage }) => { + await userInterfacePage.profileMenuButton.click(); + await userInterfacePage.adminMenuItem.click(); + await expect(userInterfacePage.page).toHaveURL(/\/admin-settings\/users/); + await userInterfacePage.userInterfaceDrawerItem.click(); + await expect(userInterfacePage.page).toHaveURL( + /\/admin-settings\/user-interface/ + ); + await userInterfacePage.page.waitForURL(/\/admin-settings\/user-interface/); + }); + + test.describe('checks if the shown values are used', async () => { + test('checks primary main color', async ({ userInterfacePage }) => { + await userInterfacePage.primaryMainColorInput.waitFor({ + state: 'attached', + }); + const initialPrimaryMainColor = + await userInterfacePage.primaryMainColorInput.inputValue(); + const initialRgbColor = userInterfacePage.hexToRgb( + initialPrimaryMainColor + ); + await expect(userInterfacePage.updateButton).toHaveCSS( + 'background-color', + initialRgbColor + ); + }); + + test('checks primary dark color', async ({ userInterfacePage }) => { + await userInterfacePage.primaryDarkColorInput.waitFor({ + state: 'attached', + }); + const initialPrimaryDarkColor = + await userInterfacePage.primaryDarkColorInput.inputValue(); + const initialRgbColor = userInterfacePage.hexToRgb( + initialPrimaryDarkColor + ); + await expect(userInterfacePage.appBar).toHaveCSS( + 'background-color', + initialRgbColor + ); + }); + + test('checks custom logo', async ({ userInterfacePage }) => { + const initialLogoSvgCode = + await userInterfacePage.logoSvgCodeInput.inputValue(); + const logoSrcAttribute = await userInterfacePage.customLogo.getAttribute( + 'src' + ); + const svgCode = userInterfacePage.encodeSVG(initialLogoSvgCode); + expect(logoSrcAttribute).toMatch(svgCode); + }); + }); + + test.describe( + 'fill fields and check if the inputs reflect them properly', + async () => { + test('fill primary main color and check the color input', async ({ + userInterfacePage, + }) => { + await userInterfacePage.primaryMainColorInput.fill('#FF5733'); + const rgbColor = userInterfacePage.hexToRgb('#FF5733'); + const button = await userInterfacePage.primaryMainColorButton; + const styleAttribute = await button.getAttribute('style'); + expect(styleAttribute).toEqual(`background-color: ${rgbColor};`); + }); + + test('fill primary dark color and check the color input', async ({ + userInterfacePage, + }) => { + await userInterfacePage.primaryDarkColorInput.fill('#12F63F'); + const rgbColor = userInterfacePage.hexToRgb('#12F63F'); + const button = await userInterfacePage.primaryDarkColorButton; + const styleAttribute = await button.getAttribute('style'); + expect(styleAttribute).toEqual(`background-color: ${rgbColor};`); + }); + + test('fill primary light color and check the color input', async ({ + userInterfacePage, + }) => { + await userInterfacePage.primaryLightColorInput.fill('#1D0BF5'); + const rgbColor = userInterfacePage.hexToRgb('#1D0BF5'); + const button = await userInterfacePage.primaryLightColorButton; + const styleAttribute = await button.getAttribute('style'); + expect(styleAttribute).toEqual(`background-color: ${rgbColor};`); + }); + } + ); + + test.describe( + 'update form based on input values and check if the inputs still reflect them', + async () => { + test('update primary main color and check color input', async ({ + userInterfacePage, + }) => { + await userInterfacePage.primaryMainColorInput.fill('#00adef'); + await userInterfacePage.updateButton.click(); + const rgbColor = userInterfacePage.hexToRgb('#00adef'); + const button = await userInterfacePage.primaryMainColorButton; + const styleAttribute = await button.getAttribute('style'); + expect(styleAttribute).toBe(`background-color: ${rgbColor};`); + }); + + test('update primary dark color and check color input', async ({ + userInterfacePage, + }) => { + await userInterfacePage.primaryDarkColorInput.fill('#222222'); + await userInterfacePage.updateButton.click(); + const rgbColor = userInterfacePage.hexToRgb('#222222'); + const button = await userInterfacePage.primaryDarkColorButton; + const styleAttribute = await button.getAttribute('style'); + expect(styleAttribute).toBe(`background-color: ${rgbColor};`); + }); + + test('update primary light color and check color input', async ({ + userInterfacePage, + }) => { + await userInterfacePage.primaryLightColorInput.fill('#f90707'); + await userInterfacePage.updateButton.click(); + const rgbColor = userInterfacePage.hexToRgb('#f90707'); + const button = await userInterfacePage.primaryLightColorButton; + const styleAttribute = await button.getAttribute('style'); + expect(styleAttribute).toBe(`background-color: ${rgbColor};`); + }); + } + ); + + test.describe('update form based on input values', async () => { + test('fill primary main color', async ({ userInterfacePage }) => { + await userInterfacePage.primaryMainColorInput.fill('#00adef'); + await userInterfacePage.updateButton.click(); + await userInterfacePage.snackbar.waitFor({ state: 'visible' }); + await userInterfacePage.screenshot({ + path: 'updated primary main color.png', + }); + }); + + test('fill primary dark color', async ({ userInterfacePage }) => { + await userInterfacePage.primaryDarkColorInput.fill('#222222'); + await userInterfacePage.updateButton.click(); + await userInterfacePage.snackbar.waitFor({ state: 'visible' }); + await userInterfacePage.screenshot({ + path: 'updated primary dark color.png', + }); + }); + + test('fill primary light color', async ({ userInterfacePage }) => { + await userInterfacePage.primaryLightColorInput.fill('#f90707'); + await userInterfacePage.updateButton.click(); + await userInterfacePage.goToDashboardButton.click(); + await expect(userInterfacePage.page).toHaveURL('/flows'); + const span = await userInterfacePage.flowRowCardActionArea; + await span.waitFor({ state: 'visible' }); + await span.hover(); + await userInterfacePage.screenshot({ + path: 'updated primary light color.png', + }); + }); + + test('fill logo svg code', async ({ userInterfacePage }) => { + await userInterfacePage.logoSvgCodeInput + .fill(` + + A + `); + await userInterfacePage.updateButton.click(); + await userInterfacePage.snackbar.waitFor({ state: 'visible' }); + await userInterfacePage.screenshot({ + path: 'updated svg code.png', + }); + }); + }); +}); diff --git a/packages/web/src/components/AdminSettingsLayout/index.tsx b/packages/web/src/components/AdminSettingsLayout/index.tsx index 26445457..c56adf84 100644 --- a/packages/web/src/components/AdminSettingsLayout/index.tsx +++ b/packages/web/src/components/AdminSettingsLayout/index.tsx @@ -42,6 +42,7 @@ function createDrawerLinks({ Icon: GroupIcon, primary: 'adminSettingsDrawer.users', to: URLS.USERS, + dataTest: 'users-drawer-link', } : null, canReadRole @@ -49,6 +50,7 @@ function createDrawerLinks({ Icon: GroupsIcon, primary: 'adminSettingsDrawer.roles', to: URLS.ROLES, + dataTest: 'roles-drawer-link', } : null, canUpdateConfig @@ -56,6 +58,7 @@ function createDrawerLinks({ Icon: BrushIcon, primary: 'adminSettingsDrawer.userInterface', to: URLS.USER_INTERFACE, + dataTest: 'user-interface-drawer-link', } : null, canManageSamlAuthProvider @@ -63,6 +66,7 @@ function createDrawerLinks({ Icon: LockIcon, primary: 'adminSettingsDrawer.authentication', to: URLS.AUTHENTICATION, + dataTest: 'authentication-drawer-link', } : null, ].filter(Boolean) as DrawerLink[]; @@ -75,6 +79,7 @@ const drawerBottomLinks = [ Icon: ArrowBackIosNewIcon, primary: 'adminSettingsDrawer.goBack', to: '/', + dataTest: 'go-back-drawer-link', }, ]; diff --git a/packages/web/src/components/AppBar/index.tsx b/packages/web/src/components/AppBar/index.tsx index 66b00634..abadd456 100644 --- a/packages/web/src/components/AppBar/index.tsx +++ b/packages/web/src/components/AppBar/index.tsx @@ -46,7 +46,7 @@ export default function AppBar(props: AppBarProps): React.ReactElement { }; return ( - + & { + bgColor: string; + isBgColorValid: boolean; + disablePopover: boolean; +}; + +export type ColorButtonElement = (props: ColorButtonProps) => JSX.Element; + +const ColorButton = (props: ColorButtonProps) => { + const { + bgColor, + className, + disablePopover, + isBgColorValid, + ...restButtonProps + } = props; + + return ( +