diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index ca7c886ff..b57f5b893 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -2,7 +2,7 @@ name: 'Alpha Build' on: push: - branches: [v4150/esm] + branches: [v4160/more-integration-tests] jobs: build: diff --git a/frontend/src/auth/AccountMenu.tsx b/frontend/src/auth/AccountMenu.tsx index c455da73b..a8cc5a807 100644 --- a/frontend/src/auth/AccountMenu.tsx +++ b/frontend/src/auth/AccountMenu.tsx @@ -65,7 +65,11 @@ const AccountMenu = () => { if (user) { return (
- + {user.name} @@ -76,7 +80,7 @@ const AccountMenu = () => { onClose={closeMenu} > {user && !user.pro && user.accountType !== 'anonymous' ? ( - + { ) : null} {isNotAnon ? ( - + @@ -98,7 +102,7 @@ const AccountMenu = () => { ) : null} {isAdmin ? ( - + @@ -106,7 +110,7 @@ const AccountMenu = () => { ) : null} {isAdmin || isNotAnon ? : null} - + diff --git a/frontend/src/auth/modal/AnonAuth.tsx b/frontend/src/auth/modal/AnonAuth.tsx index a4ea70964..27f2a7fcf 100644 --- a/frontend/src/auth/modal/AnonAuth.tsx +++ b/frontend/src/auth/modal/AnonAuth.tsx @@ -71,7 +71,7 @@ const AnonAuth = ({ onClose, onUser }: AnonAuthProps) => { placeholder={loginTranslations.namePlaceholder} fullWidth style={{ marginTop: 20 }} - data-cy="anon-input" + inputProps={{ 'data-cy': 'anon-input' }} /> ); diff --git a/frontend/src/auth/modal/account/Login.tsx b/frontend/src/auth/modal/account/Login.tsx index ae0e48818..dae3c6de3 100644 --- a/frontend/src/auth/modal/account/Login.tsx +++ b/frontend/src/auth/modal/account/Login.tsx @@ -94,8 +94,10 @@ const Login = ({ />
- {translations.registerLink} - + + {translations.registerLink} + + {translations.forgotPasswordLink} diff --git a/frontend/src/auth/modal/account/Register.tsx b/frontend/src/auth/modal/account/Register.tsx index eb6c06870..ef2c8f60b 100644 --- a/frontend/src/auth/modal/account/Register.tsx +++ b/frontend/src/auth/modal/account/Register.tsx @@ -99,6 +99,7 @@ const Register = ({ onClose }: RegisterProps) => { color="primary" autoFocus disabled={!validEmail || passwordScore < 3 || !validName} + data-cy="register-button" > {translations.registerButton} @@ -127,6 +128,7 @@ const Register = ({ onClose }: RegisterProps) => { style={{ marginTop: 20 }} leftIcon={} required + data-cy="register-name" /> { ? translations.errorInvalidEmail : undefined } + data-cy="register-email" /> { style={{ marginTop: 20 }} leftIcon={} required + data-cy="register-password" /> }> {translations.deleteAccount.deleteData} diff --git a/frontend/src/views/account/delete/DeleteModal.tsx b/frontend/src/views/account/delete/DeleteModal.tsx index 6a642dfaf..58b30ce26 100644 --- a/frontend/src/views/account/delete/DeleteModal.tsx +++ b/frontend/src/views/account/delete/DeleteModal.tsx @@ -65,12 +65,17 @@ export function DeleteModal({ deleteSessions, deleteVotes, }; + const buttonProps = { + color: 'error', + variant: 'contained', + 'data-cy': 'delete-modal-confirm', + }; confirm({ title: translations.confirm.title, description: translations.confirm.description, confirmationText: translations.confirm.confirmation, cancellationText: translations.confirm.cancellation, - confirmationButtonProps: { color: 'error', variant: 'contained' }, + confirmationButtonProps: buttonProps as any, }) .then(async () => { trackEvent('account/gdpr/delete-account'); @@ -146,6 +151,7 @@ export function DeleteModal({ checked={deleteSessions} onToggle={setDeleteSessions} icon={} + cy="delete-modal-sessions" >

{translations.deleteSessions.main}

{deleteSessions ? ( @@ -161,6 +167,7 @@ export function DeleteModal({ checked={deletePosts} onToggle={setDeletePosts} icon={} + cy="delete-modal-posts" >

{translations.deletePosts.main}

{deletePosts ? ( @@ -176,6 +183,7 @@ export function DeleteModal({ checked={deleteVotes} onToggle={setDeleteVotes} icon={} + cy="delete-modal-votes" >

{translations.deleteVotes.main}

{deleteVotes ? ( @@ -190,7 +198,12 @@ export function DeleteModal({ - @@ -203,6 +216,7 @@ type DeleteItemProps = { disabled?: boolean; checked: boolean; icon: React.ReactNode; + cy?: string; onToggle?: (value: boolean) => void; }; @@ -211,6 +225,7 @@ function DeleteItem({ disabled, icon, checked, + cy, onToggle, }: React.PropsWithChildren) { return ( @@ -225,6 +240,7 @@ function DeleteItem({ edge="end" disabled={disabled} checked={checked} + data-cy={cy} onChange={(_, v) => (onToggle ? onToggle(v) : noop)} /> diff --git a/frontend/src/views/game/GameFooter.tsx b/frontend/src/views/game/GameFooter.tsx index fff05cff6..95acc64e5 100644 --- a/frontend/src/views/game/GameFooter.tsx +++ b/frontend/src/views/game/GameFooter.tsx @@ -99,7 +99,10 @@ function GameFooter({ onReady, onMessage, messages }: GameFooterProps) { ) : null} {user ? ( - + diff --git a/frontend/src/views/game/board/Column.tsx b/frontend/src/views/game/board/Column.tsx index 35c1aed24..6644586c3 100644 --- a/frontend/src/views/game/board/Column.tsx +++ b/frontend/src/views/game/board/Column.tsx @@ -109,7 +109,7 @@ const Column: React.FC = ({ } - data-cy="column-input" + inputProps={{ 'data-cy': 'column-input' }} /> {permissions.canCreateGroup ? ( diff --git a/frontend/src/views/game/chat/Chat.tsx b/frontend/src/views/game/chat/Chat.tsx index 6e6434e62..84b58762c 100644 --- a/frontend/src/views/game/chat/Chat.tsx +++ b/frontend/src/views/game/chat/Chat.tsx @@ -26,7 +26,7 @@ export default function Chat({ messages, onMessage }: ChatProps) { [encrypt, onMessage] ); return ( - + {sortedMessages.map((m) => ( @@ -37,6 +37,7 @@ export default function Chat({ messages, onMessage }: ChatProps) { ); diff --git a/frontend/src/views/game/chat/Input.tsx b/frontend/src/views/game/chat/Input.tsx index ddcfa468b..f3b1c5a54 100644 --- a/frontend/src/views/game/chat/Input.tsx +++ b/frontend/src/views/game/chat/Input.tsx @@ -3,6 +3,7 @@ import { IconButton, Input as BaseInput, InputAdornment } from '@mui/material'; import React, { useCallback, useState } from 'react'; type InputProps = { + cy?: string; placeholder?: string; onNewMessage: (value: string) => void; }; @@ -14,6 +15,7 @@ function isEnter(code: string) { export default function Input({ placeholder, onNewMessage: onAdd, + cy, }: InputProps) { const [value, setValue] = useState(''); const [valid, setValid] = useState(false); @@ -56,6 +58,7 @@ export default function Input({ onKeyPress={handleKeyPress} style={{ border: 'none', outline: 'none', margin: 10 }} multiline + inputProps={{ 'data-cy': cy }} endAdornment={ { it('Should login and write a post', () => { cy.visit('/'); // We need to wait until the backend is ready cy.wait(+Cypress.env('backend_delay')); + + // Close cookie banner + cy.get('a.wpcc-btn').click(); get('login-button').click(); get('anon-tab').click(); - get('anon-input', ' > input').focus().type('Zelensky'); + get('anon-input').focus().type('Zelensky'); get('anon-login-button').click(); // Home page should display the user name @@ -31,7 +35,7 @@ describe('Post workflow', () => { get('new-session-button').click(); // And write a post - get('column-input', ' > input').first().focus().type('Slava Ukraini!{enter}'); + get('column-input').first().focus().type('Slava Ukraini!{enter}'); // Reload the page cy.reload(); @@ -43,9 +47,12 @@ describe('Post workflow', () => { it('Should change language and translate the app', () => { cy.visit('/'); + // Close cookie banner + cy.get('a.wpcc-btn').click(); + get('login-button').click(); get('anon-tab').click(); - get('anon-input', ' > input').focus().type('Zelensky'); + get('anon-input').focus().type('Zelensky'); get('anon-login-button').click(); // Home page should display the user name @@ -56,7 +63,70 @@ describe('Post workflow', () => { get('language-picker').click(); get('language-picker-item-fr').click(); + // Exit panel + cy.get('body').type('{esc}'); + // Home page should now be in French cy.get('#content').should('contain', 'Bienvenue, Zelensky'); + + // Logout + get('account-menu').click(); + get('account-menu-logout').click(); + + }); + + it('Should be able to create a new account', () => { + const id = Date.now(); + + cy.visit('/'); + + // Close cookie banner + cy.get('a.wpcc-btn').click(); + + // Login + get('login-button').click(); + + // Select the account tab + get('account-tab').click(); + + // Select register + get('register').click(); + + // Add some data + get('register-name').type('V Zelensky'); + get('register-email').type(`vlad.zelensky.${id}@ukraine.ua`); + get('register-password').type('A-str0ng-Pa33!รงร '); + + // Register + get('register-button').click(); + + // Create a new session, and add some messages + get('new-session-button').click(); + + // And write a post + get('column-input').first().focus().type('Slava Ukraini!{enter}'); + cy.get('#content').should('contain', 'Slava Ukraini!'); + + // And some chat + get('open-chat-button').click({force: true}); + get('chat-input').focus().type('This is a message{enter}'); + cy.get('#content').should('contain', 'This is a message'); + + // Close + get('open-chat-button').click({force: true}); + + // Go to the user admin and delete the account + get('account-menu').click(); + get('account-menu-account').click(); + get('delete-account-button').click(); + get('delete-modal-sessions').click(); + get('delete-modal-posts').click(); + get('delete-modal-votes').click(); + get('delete-modal-delete-button').click(); + get('delete-modal-confirm').click(); + + // We should be back to the home page + cy.get('div.marketing-content') + .should('contain', 'Real-time Retrospectives') }); }); diff --git a/integration/docker-compose.local.master.yml b/integration/docker-compose.local.master.yml new file mode 100644 index 000000000..10c8810a8 --- /dev/null +++ b/integration/docker-compose.local.master.yml @@ -0,0 +1,58 @@ +version: '3' +services: + postgres: + image: postgres:11.6 + hostname: postgres + environment: + POSTGRES_PASSWORD: some-password + POSTGRES_USER: postgres + POSTGRES_DB: retroboard + volumes: + - database:/var/lib/postgresql/data + restart: unless-stopped + logging: + driver: 'json-file' + options: + max-size: '50m' + + backend: + image: retrospected/backend:latest + depends_on: + - redis + environment: + SELF_HOSTED_ADMIN: 'your@email.com' + DB_PASSWORD: some-password + SESSION_SECRET: im-a-secret + + restart: unless-stopped + logging: + driver: 'json-file' + options: + max-size: '50m' + + frontend: + image: retrospected/frontend:latest + ports: + - '3000:80' + depends_on: + - backend + restart: unless-stopped + logging: + driver: 'json-file' + options: + max-size: '50m' + + redis: + image: redis:latest + depends_on: + - postgres + restart: unless-stopped + logging: + driver: 'json-file' + options: + max-size: '50m' + + +volumes: + database: + pgadmin: \ No newline at end of file