setup cypress with initial login tests

This commit is contained in:
mahula 2025-09-28 10:34:58 +02:00
parent 0ac046e6da
commit 9206848bd2
7 changed files with 7217 additions and 0 deletions

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
.claude/
data/
cypress/node:modules/
cypress/reports/
cypress/runner-results/

50
cypress/cypress.config.ts Normal file
View File

@ -0,0 +1,50 @@
import { defineConfig } from 'cypress'
const cypressSplit = require('cypress-split')
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:8080',
viewportWidth: 1280,
viewportHeight: 720,
specPattern: 'e2e/**/*.cy.ts',
supportFile: false,
screenshotsFolder: 'reports/screenshots',
videosFolder: 'reports/videos',
video: false,
screenshotOnRunFailure: true,
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 10000,
pageLoadTimeout: 30000,
testIsolation: true,
retries: {
runMode: 2,
openMode: 0
},
env: {
apiUrl: 'http://localhost:8055',
validEmail: 'admin@it4c.dev',
validPassword: 'admin123',
invalidEmail: 'invalid@example.com',
invalidPassword: 'wrongpassword'
},
setupNodeEvents(on, config) {
cypressSplit(on, config)
on('task', {
log(message) {
console.log(message)
return null
}
})
return config
},
},
})

View File

@ -0,0 +1,172 @@
/// <reference types="cypress" />
Cypress.on('uncaught:exception', (err, _runnable) => {
// eslint-disable-next-line no-console
console.log('Uncaught exception:', err.message)
return false
})
describe('Utopia Map Login', () => {
const testData = {
validUser: {
email: Cypress.env('validEmail'),
password: Cypress.env('validPassword')
},
invalidUser: {
email: Cypress.env('invalidEmail'),
password: Cypress.env('invalidPassword')
}
}
beforeEach(() => {
cy.clearCookies()
cy.clearLocalStorage()
cy.window().then((win) => {
win.sessionStorage.clear()
})
cy.visit('/login')
})
it('should successfully login with valid credentials', () => {
cy.intercept('POST', '**/auth/login').as('loginRequest')
cy.get('input[type="email"]').clear()
cy.get('input[type="email"]').type(testData.validUser.email)
cy.get('input[type="password"]').clear()
cy.get('input[type="password"]').type(testData.validUser.password)
cy.get('button:contains("Login")').click()
cy.wait('@loginRequest').then((interception) => {
expect(interception.response?.statusCode).to.eq(200)
expect(interception.request.body).to.deep.include({
email: testData.validUser.email,
password: testData.validUser.password
})
expect(interception.response?.body).to.have.property('data')
expect(interception.response?.body.data).to.have.property('access_token')
expect(interception.response?.body.data.access_token).to.be.a('string')
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(interception.response?.body.data.access_token).to.not.be.empty
})
cy.get('.Toastify__toast--success', { timeout: 10000 }).should('be.visible')
cy.url().should('eq', Cypress.config().baseUrl + '/')
})
it('should show error for missing password', () => {
cy.intercept('POST', '**/auth/login').as('loginRequest')
cy.get('input[type="email"]').type(testData.validUser.email)
cy.get('button:contains("Login")').click()
cy.wait('@loginRequest').then((interception) => {
expect(interception.response?.statusCode).to.eq(400)
expect(interception.request.body).to.deep.include({
email: testData.validUser.email,
password: ''
})
expect(interception.response?.body).to.have.property('errors')
expect(interception.response?.body.errors).to.be.an('array')
expect(interception.response?.body.errors).to.have.length.greaterThan(0)
expect(interception.response?.body.errors[0]).to.have.property('message')
})
cy.get('.Toastify__toast--error', { timeout: 10000 }).should('be.visible')
cy.url().should('include', '/login')
})
it('should show error for missing email', () => {
cy.intercept('POST', '**/auth/login').as('loginRequest')
cy.get('input[type="password"]').type(testData.validUser.password)
cy.get('button:contains("Login")').click()
cy.wait('@loginRequest').then((interception) => {
expect(interception.response?.statusCode).to.eq(400)
expect(interception.request.body).to.deep.include({
email: '',
password: testData.validUser.password
})
expect(interception.response?.body).to.have.property('errors')
expect(interception.response?.body.errors).to.be.an('array')
expect(interception.response?.body.errors).to.have.length.greaterThan(0)
})
cy.get('.Toastify__toast--error', { timeout: 10000 }).should('be.visible')
cy.url().should('include', '/login')
})
it('should show error for missing credentials', () => {
cy.intercept('POST', '**/auth/login').as('loginRequest')
cy.get('button:contains("Login")').click()
cy.wait('@loginRequest').then((interception) => {
expect(interception.response?.statusCode).to.eq(400)
expect(interception.request.body).to.deep.include({
email: '',
password: ''
})
expect(interception.response?.body).to.have.property('errors')
expect(interception.response?.body.errors).to.be.an('array')
expect(interception.response?.body.errors).to.have.length.greaterThan(0)
})
cy.get('.Toastify__toast--error', { timeout: 10000 }).should('be.visible')
cy.url().should('include', '/login')
})
it('should show error for invalid credentials', () => {
cy.intercept('POST', '**/auth/login').as('loginRequest')
cy.get('input[type="email"]').clear()
cy.get('input[type="email"]').type(testData.invalidUser.email)
cy.get('input[type="password"]').clear()
cy.get('input[type="password"]').type(testData.invalidUser.password)
cy.get('button:contains("Login")').click()
cy.wait('@loginRequest').then((interception) => {
expect(interception.response?.statusCode).to.eq(401)
expect(interception.request.body).to.deep.include({
email: testData.invalidUser.email,
password: testData.invalidUser.password
})
expect(interception.response?.body).to.have.property('errors')
expect(interception.response?.body.errors).to.be.an('array')
expect(interception.response?.body.errors[0]).to.have.property('message')
expect(interception.response?.body.errors[0].message).to.contain('Invalid user credentials')
})
cy.get('.Toastify__toast--error', { timeout: 10000 }).should('be.visible')
cy.url().should('include', '/login')
})
it('should show loading state during login', () => {
cy.intercept('POST', '**/auth/login', {
delay: 1000,
statusCode: 200,
body: {
data: {
access_token: 'test_token_123',
expires: 900000,
refresh_token: 'refresh_token_123'
}
}
}).as('loginRequest')
cy.get('input[type="email"]').type(testData.validUser.email)
cy.get('input[type="password"]').type(testData.validUser.password)
cy.get('button:contains("Login")').click()
cy.get('.tw\\:loading-spinner', { timeout: 5000 }).should('be.visible')
cy.wait('@loginRequest')
cy.url().should('eq', Cypress.config().baseUrl + '/')
})
})

View File

@ -0,0 +1,50 @@
/// <reference types="cypress" />
Cypress.on('uncaught:exception', (err, _runnable) => {
// eslint-disable-next-line no-console
console.log('Uncaught exception:', err.message)
return false
})
describe('Utopia Map Login Form Elements', () => {
// TODO: set this data different
const _testData = {
validUser: {
email: Cypress.env('validEmail'),
password: Cypress.env('validPassword')
},
invalidUser: {
email: Cypress.env('invalidEmail'),
password: Cypress.env('invalidPassword')
}
}
beforeEach(() => {
cy.clearCookies()
cy.clearLocalStorage()
cy.window().then((win) => {
win.sessionStorage.clear()
})
cy.visit('/login')
})
it('should be displayed correctly', () => {
cy.get('h2').should('contain.text', 'Login')
cy.get('input[type="email"]')
.should('be.visible')
.should('have.attr', 'placeholder', 'E-Mail')
cy.get('input[type="password"]')
.should('be.visible')
.should('have.attr', 'placeholder', 'Password')
cy.get('button:contains("Login")')
.should('be.visible')
.should('not.be.disabled')
cy.get('a[href="/reset-password"]')
.should('be.visible')
.should('contain.text', 'Forgot Password?')
})
})

6892
cypress/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
cypress/package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "utopia-map-e2e-testing",
"private": true,
"version": "1.0.0",
"description": "Cypress End-to-End Tests for Utopia Map",
"scripts": {
"test": "cypress run --e2e --browser chromium",
"test:open": "cypress open --e2e",
"test:split:auto": "SPEC_COUNT=$(find e2e -name '*.cy.ts' | wc -l) && echo \"Running $SPEC_COUNT chunks in parallel\" && for i in $(seq 0 $((SPEC_COUNT-1))); do SPLIT=$SPEC_COUNT SPLIT_INDEX=$i cypress run --e2e --browser chromium & done; wait",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"keywords": [
"cypress",
"cypress-split",
"e2e",
"testing",
"utopia-map"
],
"devDependencies": {
"@eslint/js": "^9.36.0",
"@types/node": "^24.5.2",
"cypress": "^15.3.0",
"cypress-split": "^1.24.23",
"eslint": "^9.36.0",
"eslint-plugin-cypress": "^5.1.1",
"typescript": "^5.9.2",
"typescript-eslint": "^8.44.1"
}
}

20
cypress/tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node"],
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}