test: add e2e automated tests
This commit is contained in:
parent
50d724eb6a
commit
0819304e1e
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