test: add e2e automated tests
This commit is contained in:
		
							
								
								
									
										23
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,3 +23,26 @@ jobs: | ||||
|  | ||||
|       - name: 'Test' | ||||
|         run: 'npm run test' | ||||
|  | ||||
|   test-e2e: | ||||
|     runs-on: 'ubuntu-latest' | ||||
|     steps: | ||||
|       - uses: 'actions/checkout@v3.0.0' | ||||
|  | ||||
|       - name: 'Use Node.js' | ||||
|         uses: 'actions/setup-node@v3.1.0' | ||||
|         with: | ||||
|           node-version: 'lts/*' | ||||
|           cache: 'npm' | ||||
|  | ||||
|       - name: 'Install' | ||||
|         run: 'npm install' | ||||
|  | ||||
|       - name: 'Build Package' | ||||
|         run: 'npm run build' | ||||
|  | ||||
|       - name: 'Build Example' | ||||
|         run: 'cd example && npm install && npm run build' | ||||
|  | ||||
|       - name: 'End To End (e2e) Test Example' | ||||
|         run: 'npm run test:e2e' | ||||
|   | ||||
							
								
								
									
										3
									
								
								example/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								example/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -7,6 +7,9 @@ | ||||
|  | ||||
| # testing | ||||
| /coverage | ||||
| cypress/screenshots | ||||
| cypress/videos | ||||
| cypress/downloads | ||||
|  | ||||
| # next.js | ||||
| /.next/ | ||||
|   | ||||
| @@ -18,7 +18,7 @@ export const About: React.FC = () => { | ||||
|         </Link> | ||||
|       </h2> | ||||
|  | ||||
|       <p className='max-w-lg mt-6 text-base'> | ||||
|       <p className='max-w-lg mt-6 text-base' data-cy='main-description'> | ||||
|         <Translation | ||||
|           i18nKey='common:about' | ||||
|           components={[<TextSpecial key='special' />]} | ||||
|   | ||||
| @@ -22,7 +22,7 @@ export const FormExample: React.FC = () => { | ||||
|     formData, | ||||
|     formElement | ||||
|   ) => { | ||||
|     await simulateServerRequest(4000) | ||||
|     await simulateServerRequest(2000) | ||||
|     console.log('onSubmit:', formData) | ||||
|     formElement.reset() | ||||
|     return { | ||||
| @@ -54,7 +54,7 @@ export const FormExample: React.FC = () => { | ||||
|           error={getFirstErrorTranslation(errors.email)} | ||||
|         /> | ||||
|  | ||||
|         <Button className='mt-6 w-full' type='submit'> | ||||
|         <Button className='mt-6 w-full' type='submit' data-cy='submit'> | ||||
|           Submit | ||||
|         </Button> | ||||
|       </Form> | ||||
|   | ||||
| @@ -24,8 +24,13 @@ export const Input: React.FC<InputProps> = (props) => { | ||||
|           {...rest} | ||||
|           id={name} | ||||
|           name={name} | ||||
|           data-cy={`input-${name}`} | ||||
|         /> | ||||
|         <FormState | ||||
|           id={`error-${name ?? 'input'}`} | ||||
|           state={error == null ? 'idle' : 'error'} | ||||
|           message={error} | ||||
|         /> | ||||
|         <FormState state={error == null ? 'idle' : 'error'} message={error} /> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
|   | ||||
							
								
								
									
										13
									
								
								example/cypress.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								example/cypress.config.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| import { defineConfig } from 'cypress' | ||||
|  | ||||
| export default defineConfig({ | ||||
|   fixturesFolder: false, | ||||
|   video: false, | ||||
|   downloadsFolder: undefined, | ||||
|   screenshotOnRunFailure: false, | ||||
|  | ||||
|   e2e: { | ||||
|     baseUrl: 'http://localhost:3000', | ||||
|     supportFile: false | ||||
|   } | ||||
| }) | ||||
							
								
								
									
										65
									
								
								example/cypress/e2e/Form.cy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								example/cypress/e2e/Form.cy.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| describe('Form', () => { | ||||
|   beforeEach(() => { | ||||
|     cy.visit('/') | ||||
|   }) | ||||
|  | ||||
|   it('suceeds, reset input values and display the global success message', () => { | ||||
|     cy.get('[data-cy=input-name]').type('John') | ||||
|     cy.get('[data-cy=input-email]').type('john@john.com') | ||||
|     cy.get('#error-name').should('not.exist') | ||||
|     cy.get('#error-email').should('not.exist') | ||||
|     cy.get('[data-cy=submit]').click() | ||||
|     cy.get('[data-cy=input-name]').should('have.value', '') | ||||
|     cy.get('[data-cy=input-email]').should('have.value', '') | ||||
|     cy.get('#message').should( | ||||
|       'have.text', | ||||
|       'Success: The form has been submitted.' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('fails with all inputs as required with error messages and update error messages when updating language (translation)', () => { | ||||
|     const requiredErrorMessage = { | ||||
|       en: 'Error: Oops, this field is required 🙈.', | ||||
|       fr: 'Erreur: Oups, ce champ est obligatoire 🙈.' | ||||
|     } | ||||
|     cy.get('#error-name').should('not.exist') | ||||
|     cy.get('#error-email').should('not.exist') | ||||
|     cy.get('[data-cy=submit]').click() | ||||
|     cy.get('#error-name').should('have.text', requiredErrorMessage.en) | ||||
|     cy.get('#error-email').should('have.text', requiredErrorMessage.en) | ||||
|     cy.get('[data-cy=language-click]').click() | ||||
|     cy.get('[data-cy=languages-list] > li:first-child').contains('FR').click() | ||||
|     cy.get('#error-name').should('have.text', requiredErrorMessage.fr) | ||||
|     cy.get('#error-email').should('have.text', requiredErrorMessage.fr) | ||||
|   }) | ||||
|  | ||||
|   it('fails with invalid name (less than 3 characters)', () => { | ||||
|     cy.get('[data-cy=input-name]').type('a') | ||||
|     cy.get('[data-cy=submit]').click() | ||||
|     cy.get('#error-name').should( | ||||
|       'have.text', | ||||
|       'Error: The field must contain at least 3 characters.' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('fails with invalid name (more than 10 characters)', () => { | ||||
|     cy.get('[data-cy=input-name]').type('12345678910aaaa') | ||||
|     cy.get('[data-cy=submit]').click() | ||||
|     cy.get('#error-name').should( | ||||
|       'have.text', | ||||
|       'Error: The field must contain at most 10 characters.' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('fails with wrong email format', () => { | ||||
|     cy.get('#error-email').should('not.exist') | ||||
|     cy.get('[data-cy=input-email]').type('test') | ||||
|     cy.get('[data-cy=submit]').click() | ||||
|     cy.get('#error-email').should( | ||||
|       'have.text', | ||||
|       'Error: Mmm… It seems that this email is not valid 🤔.' | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| export {} | ||||
							
								
								
									
										49
									
								
								example/cypress/e2e/Header.cy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								example/cypress/e2e/Header.cy.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| describe('Header', () => { | ||||
|   beforeEach(() => cy.visit('/')) | ||||
|  | ||||
|   describe('Switch theme color (dark/light)', () => { | ||||
|     it('should switch theme from `dark` (default) to `light`', () => { | ||||
|       cy.get('[data-cy=switch-theme-dark]').should('be.visible') | ||||
|       cy.get('[data-cy=switch-theme-light]').should('not.be.visible') | ||||
|       cy.get('body').should( | ||||
|         'not.have.css', | ||||
|         'background-color', | ||||
|         'rgb(255, 255, 255)' | ||||
|       ) | ||||
|  | ||||
|       cy.get('[data-cy=switch-theme-click]').click() | ||||
|  | ||||
|       cy.get('[data-cy=switch-theme-dark]').should('not.be.visible') | ||||
|       cy.get('[data-cy=switch-theme-light]').should('be.visible') | ||||
|       cy.get('body').should( | ||||
|         'have.css', | ||||
|         'background-color', | ||||
|         'rgb(255, 255, 255)' | ||||
|       ) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   describe('Switch Language', () => { | ||||
|     it('should switch language from EN (default) to FR', () => { | ||||
|       cy.get('[data-cy=main-description]').contains('This is an example') | ||||
|       cy.get('[data-cy=language-flag-text]').contains('EN') | ||||
|       cy.get('[data-cy=languages-list]').should('not.be.visible') | ||||
|       cy.get('[data-cy=language-click]').click() | ||||
|       cy.get('[data-cy=languages-list]').should('be.visible') | ||||
|       cy.get('[data-cy=languages-list] > li:first-child').contains('FR').click() | ||||
|       cy.get('[data-cy=languages-list]').should('not.be.visible') | ||||
|       cy.get('[data-cy=language-flag-text]').contains('FR') | ||||
|       cy.get('[data-cy=main-description]').contains('Ceci est un exemple') | ||||
|     }) | ||||
|  | ||||
|     it('should close the language list menu when clicking outside', () => { | ||||
|       cy.get('[data-cy=languages-list]').should('not.be.visible') | ||||
|       cy.get('[data-cy=language-click]').click() | ||||
|       cy.get('[data-cy=languages-list]').should('be.visible') | ||||
|       cy.get('[data-cy=main-description]').click() | ||||
|       cy.get('[data-cy=languages-list]').should('not.be.visible') | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| export {} | ||||
							
								
								
									
										3007
									
								
								example/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3007
									
								
								example/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -6,9 +6,12 @@ | ||||
|     "dev": "next dev", | ||||
|     "build": "next build", | ||||
|     "start": "next start", | ||||
|     "lint": "next lint" | ||||
|     "lint": "next lint", | ||||
|     "test:e2e": "start-server-and-test \"start\" \"http://localhost:3000\" \"cypress run\"", | ||||
|     "test:dev": "start-server-and-test \"dev\" \"http://localhost:3000\" \"cypress open\"" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@sinclair/typebox": "0.24.28", | ||||
|     "clsx": "1.2.1", | ||||
|     "next": "12.2.5", | ||||
|     "next-themes": "0.2.0", | ||||
| @@ -22,9 +25,11 @@ | ||||
|     "@types/react": "18.0.17", | ||||
|     "@types/react-dom": "18.0.6", | ||||
|     "autoprefixer": "10.4.8", | ||||
|     "cypress": "10.6.0", | ||||
|     "eslint": "8.22.0", | ||||
|     "eslint-config-next": "12.2.5", | ||||
|     "postcss": "8.4.16", | ||||
|     "start-server-and-test": "1.14.0", | ||||
|     "tailwindcss": "3.1.8", | ||||
|     "typescript": "4.8.2" | ||||
|   } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import '../styles/globals.css' | ||||
|  | ||||
| const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => { | ||||
|   return ( | ||||
|     <ThemeProvider attribute='class' defaultTheme='light'> | ||||
|     <ThemeProvider attribute='class' defaultTheme='dark'> | ||||
|       <Component {...pageProps} /> | ||||
|     </ThemeProvider> | ||||
|   ) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|     "allowJs": true, | ||||
|     "skipLibCheck": true, | ||||
|     "strict": true, | ||||
|     "types": ["cypress"], | ||||
|     "forceConsistentCasingInFileNames": true, | ||||
|     "noEmit": true, | ||||
|     "esModuleInterop": true, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user