test(webapp): provide tests in CI

- add url and page as env variables to choice where to test and update the others
- To pass a variable to a job's service, they have to be declared in the "variables" global -> doesn't work (see: [#37])
- use of the deployed image to do E2E test instead of services of gitlab
This commit is contained in:
fastium
2025-06-21 16:22:07 +02:00
parent cecd04dd20
commit e436eb354b
10 changed files with 55 additions and 270 deletions

View File

@@ -7,4 +7,6 @@ MQTT_URL=
MQTT_USERNAME= MQTT_USERNAME=
MQTT_PASSWORD= MQTT_PASSWORD=
REST_USERNAME= REST_USERNAME=
REST_PASSWORD= REST_PASSWORD=
REST_URL=
REST_PAGE=

View File

@@ -109,8 +109,10 @@ services:
- "8080:8080" - "8080:8080"
environment: environment:
- VUE_APP_INFLUXDB_USER=$REST_USERNAME - VUE_APP_REST_USER=$REST_USERNAME
- VUE_APP_INFLUXDB_PASSWORD=$REST_PASSWORD - VUE_APP_REST_PASSWORD=$REST_PASSWORD
- VUE_APP_REST_URL=$REST_URL
- VUE_APP_REST_PAGE=$REST_PAGE
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"

View File

@@ -2,6 +2,11 @@ variables:
DOCKER_IMAGE: registry.forge.hefr.ch/team-raclette/project-softweng/web-app:latest DOCKER_IMAGE: registry.forge.hefr.ch/team-raclette/project-softweng/web-app:latest
NODE_IMAGE: cypress/included:cypress-14.4.1-node-22.16.0-chrome-137.0.7151.68-1-ff-139.0.1-edge-137.0.3296.62-1 NODE_IMAGE: cypress/included:cypress-14.4.1-node-22.16.0-chrome-137.0.7151.68-1-ff-139.0.1-edge-137.0.3296.62-1
VUE_APP_REST_USER: $REST_USER
VUE_APP_REST_PASSWORD: $REST_PASSWORD
VUE_APP_REST_URL: $REST_URL
VUE_APP_REST_PAGE: $TEST_PAGE
default: default:
image: $DOCKER_IMAGE image: $DOCKER_IMAGE
@@ -35,9 +40,10 @@ web-app-unit-tests:
web-app-e2e-tests: web-app-e2e-tests:
image: $NODE_IMAGE image: $NODE_IMAGE
stage: web-app-tests stage: web-app-tests
services:
- name: $DOCKER_IMAGE # services:
alias: app # - name: $DOCKER_IMAGE
# alias: app
script: script:
- echo "Running e2e tests" - echo "Running e2e tests"
- apt-get update - apt-get update
@@ -45,6 +51,6 @@ web-app-e2e-tests:
- cd ./web-app - cd ./web-app
- npm install --include=dev - npm install --include=dev
- echo "Wait web-app app is running" - echo "Wait web-app app is running"
- for i in {1..6}; do curl -f http://app:8080 && break || sleep 5; done - for i in {1..6}; do curl -f https://app.mse.kb28.ch/ && break || sleep 5; done
- echo "Flask app is running" - echo "App is running"
- npm run test:e2e - npm run test:e2e

View File

@@ -5,5 +5,9 @@ export default defineConfig({
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
// implement node event listeners here // implement node event listeners here
}, },
specPattern: [
"cypress/e2e/*.cy.{js,jsx,ts,tsx}",
"cypress/unit/*.cy.{js,jsx,ts,tsx}",
],
}, },
}); });

View File

@@ -2,7 +2,7 @@
describe("Test fetch measurments button in the main page", () => { describe("Test fetch measurments button in the main page", () => {
beforeEach(() => { beforeEach(() => {
cy.visit("http://localhost:8080/"); cy.visit("https://app.mse.kb28.ch");
}); });
it("Fetch timeseries with valid user-room-device", () => { it("Fetch timeseries with valid user-room-device", () => {

View File

@@ -7,7 +7,9 @@
"build": "vue-cli-service build", "build": "vue-cli-service build",
"cypress:open": "cypress open", "cypress:open": "cypress open",
"cypress:run": "cypress run", "cypress:run": "cypress run",
"test": "cypress run --spec 'cypress/e2e/*.cy.ts'" "test:e2e": "cypress run --spec 'cypress/e2e/*.cy.ts'",
"test:unit": "cypress run --spec 'cypress/unit/*.cy.ts'",
"test": "test:unit | test:e2e"
}, },
"dependencies": { "dependencies": {
"axios": "^1.9.0", "axios": "^1.9.0",

View File

@@ -1,5 +1,5 @@
import axios, { AxiosResponse } from "axios"; import axios, { AxiosResponse } from "axios";
import { BASE, PING, RACLETTE, PONG } from "../const"; import { BASE, PING, PAGE, PONG } from "../const";
export class HttpClient { export class HttpClient {
private _url: string; private _url: string;
@@ -45,7 +45,7 @@ export class HttpClient {
room: string, room: string,
device: string device: string
): Promise<AxiosResponse<any, any>> { ): Promise<AxiosResponse<any, any>> {
const response = await axios.get(`${BASE}${this._url}/${RACLETTE}`, { const response = await axios.get(`${BASE}${this._url}/${PAGE}`, {
headers: this.getAuthHeader(), headers: this.getAuthHeader(),
params: { params: {
user: user, user: user,
@@ -62,7 +62,7 @@ export class HttpClient {
device: string device: string
): Promise<AxiosResponse<any, any>> { ): Promise<AxiosResponse<any, any>> {
const response = await axios.post( const response = await axios.post(
`${BASE}${this._url}/${RACLETTE}`, `${BASE}${this._url}/${PAGE}`,
{ {
command: "MEASURE_NEW", command: "MEASURE_NEW",
}, },

View File

@@ -1,10 +1,10 @@
export const APP_NAME = "Home Monitor"; export const APP_NAME = "Home Monitor";
export const URL = "rest.mse.kb28.ch"; export const URL = process.env.VUE_APP_REST_URL;
// load environment varaibles - need to have the prefix VUE_APP // load environment varaibles - need to have the prefix VUE_APP
export const USERNAME = process.env.VUE_APP_INFLUXDB_USER; export const USERNAME = process.env.VUE_APP_REST_USER;
export const PASSWORD = process.env.VUE_APP_INFLUXDB_PASSWORD; export const PASSWORD = process.env.VUE_APP_REST_PASSWORD;
export const HUMIDITY = "humidity"; export const HUMIDITY = "humidity";
export const TEMPERATURE = "temperature"; export const TEMPERATURE = "temperature";
@@ -17,5 +17,5 @@ export const DEVICE = "Shed";
export const BASE = "https://"; export const BASE = "https://";
export const PING = "ping"; export const PING = "ping";
export const RACLETTE = "raclette"; export const PAGE = process.env.VUE_APP_REST_PAGE;
export const PONG = "pong"; export const PONG = "pong";

View File

@@ -1,274 +1,43 @@
# End-to-End Testing Documentation for Home Monitor # End-to-End and Unit Testing Documentation for Home Monitor
## Table of Contents ## Test Description
- [Introduction](#introduction)
- [Testing Strategy](#testing-strategy)
- [Test Environment Setup](#test-environment-setup)
- [Running Tests](#running-tests)
- [Test Structure](#test-structure)
- [Test Cases Overview](#test-cases-overview)
- [Writing New Tests](#writing-new-tests)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)
- [References](#references)
## Introduction The Home Monitor project uses Cypress to automate both End-to-End (E2E) and unit tests. These tests ensure that the main features of the application work correctly, both from the users and the developers perspectives.
End-to-End (E2E) testing verifies that the application functions correctly from a user's perspective by testing the complete application workflow. This document outlines the E2E testing approach for the Home Monitor application. ### Types of Tests
### Goals of E2E Testing - **End-to-End (E2E) Tests**: Simulate real user behavior on the web interface. They check critical user flows, data display, error handling, and the responsiveness of the application.
- **Unit Tests**: Verify the correct functioning of JavaScript/TypeScript components and functions in isolation.
- Validate critical user flows function correctly ### Test Structure
- Ensure components work together as expected
- Detect regression issues before production deployment
- Verify application behavior in real-world scenarios
## Testing Strategy - E2E tests are located in `web-app/cypress/e2e/`
- Unit tests are located in `web-app/cypress/unit/`
- Custom commands and global setup are in `web-app/cypress/support/`
- Cypress configuration is in `web-app/cypress.config.ts`
Home Monitor uses Cypress for E2E testing with two primary approaches: ### Example Scenarios Covered
1. **Mock Testing (Without Server)**: Fast, reliable tests using mocked DOM elements and data - Application loading and display of main sections
2. **Real Environment Testing (With Server)**: Full application testing with a running development server - Control panel functionality (buttons, filters)
- User, room, and device selection
- Display and update of measurement charts
- Handling of error or empty data states
- Responsive layout for mobile and desktop
### Mock vs. Real Testing Comparison ## How to Run the Tests
| Aspect | Mock Testing | Real Environment Testing |
|--------|-------------|--------------------------|
| Speed | Very fast (< 1s per test) | Slower (depends on app loading time) |
| Reliability | Highly stable | May be affected by server/network issues |
| Coverage | Limited to structure and basic interactions | Tests actual rendering and behaviors |
| Dependencies | None (no server needed) | Requires development server |
| Best for | CI/CD, quick verification | Final validation, regression testing |
## Test Environment Setup
### Prerequisites ### Prerequisites
- Node.js (v14+) - Node.js (v14+)
- npm or yarn - npm or yarn
- Chrome browser (for visual testing) - Chrome browser
### Installation ### Install Dependencies
```bash ```bash
# Install dependencies
cd project-softweng/web-app cd project-softweng/web-app
npm install npm install
``` ```
## Running Tests ### Run unit Tests
### Option 1: Tests with Mock Environment (No Server)
```bash
# Run all tests in headless mode
export CYPRESS_SKIP_SERVER_CHECK=true
npm run test:e2e
# Run specific test file
export CYPRESS_SKIP_SERVER_CHECK=true
npx cypress run --spec "tests/e2e/app.spec.ts"
```
### Option 2: Tests with Real Application (Server Required)
#### Automated Script Method
```bash
# Use the automated script (starts server, runs tests, stops server)
./tests/e2e/run-e2e-tests.sh
```
#### Manual Method
1. Start the development server:
```bash
# Terminal 1
npm run serve
```
2. Run the tests:
```bash
# Terminal 2
# Headless mode
npx cypress run --spec "tests/e2e/app.spec.ts"
# Interactive mode
npx cypress open
```
### Interactive Mode (Development)
```bash
npm run cypress:open
```
This opens the Cypress Test Runner where you can:
- Select individual tests to run
- See test execution in real-time
- Debug failing tests with time-travel debugging
## Test Structure
### Directory Structure
```
web-app/
├── tests/
│ ├── e2e/ # E2E test files
│ │ ├── app.spec.ts # Main application tests
│ │ └── run-e2e-tests.sh # Script for running tests with server
│ └── support/ # Support files
│ ├── commands.ts # Custom Cypress commands
│ └── e2e.ts # Global setup for E2E tests
└── cypress.config.ts # Cypress configuration
```
### Test Files Organization
Each test file follows this structure:
1. **Setup**: Import dependencies and set up the test environment
2. **Beforehooks**: Prepare the application state before each test
3. **Test Cases**: Individual test scenarios grouped by feature
4. **Helper Functions**: Support functions for test cases
## Test Cases Overview
The E2E test suite covers the following scenarios:
1. **Application Loading**
- Verify application loads with correct title
- Confirm main layout sections are visible
2. **Control Panel Functionality**
- Verify control panel buttons are present
- Test "Fetch Measurements" button works
- Test "New Measurements" button works
3. **Filtering Controls**
- Test user selection dropdown
- Test room selection dropdown
- Test device selection dropdown
4. **Data Visualization**
- Verify chart displays correctly
- Test chart updates when data changes
5. **Error Handling**
- Test error state display
- Test empty data state display
6. **Responsive Design**
- Verify layout adapts to desktop viewport
- Verify layout adapts to mobile viewport
## Writing New Tests
### Adding a New Test Case
1. Identify the feature or flow to test
2. Determine the expected behavior
3. Add a new test case to the appropriate spec file:
```typescript
it('should [describe expected behavior]', () => {
// Setup any preconditions
// Perform actions
// Assert expected outcomes
});
```
### Custom Commands
Custom commands are available to simplify test writing:
```typescript
// Select an option from a multiselect dropdown
cy.selectMultiselectOption('user-select', 'user1');
// Wait for chart to load and be visible
cy.waitForChart();
// Check application loading state
cy.checkLoadingState(false);
```
### API Mocking
Use Cypress's intercept feature to mock API responses:
```typescript
// Mock GET request
cy.intercept('GET', '**/api/measurements*', {
statusCode: 200,
body: {
data: [
{ time: new Date(2023, 0, 1, 10, 0).getTime(), value: 22.5, type: 'temperature' }
]
}
}).as('getMeasurements');
// Wait for the intercepted request
cy.wait('@getMeasurements');
```
## Best Practices
1. **Test Independence**
- Each test should be able to run independently
- Avoid dependencies between tests
- Reset state between tests
2. **Selector Strategy**
- Prefer data attributes for test selectors (e.g., `data-cy`, `data-testid`)
- Avoid using CSS classes that might change with styling updates
- Establish a consistent selector naming convention
3. **Handling Asynchronous Operations**
- Use explicit waits rather than arbitrary timeouts
- Wait for specific elements or network requests rather than fixed delays
- Handle loading states appropriately
4. **Test Data Management**
- Use consistent test data
- Mock external dependencies
- Consider using fixtures for complex data structures
5. **Error Handling**
- Add proper error handling in tests
- Use `Cypress.on('uncaught:exception')` for expected application errors
## Troubleshooting
### Common Issues and Solutions
| Issue | Solution |
|-------|----------|
| Tests fail with 401 errors | Add `Cypress.on('uncaught:exception', () => false)` to handle authentication errors |
| Elements not found | Increase timeouts or check selectors; ensure elements are in the DOM |
| Click actions failing | Use `{ force: true }` option if elements might be covered by overlays |
| Tests passing locally but failing in CI | Check environment differences; ensure CI has all required dependencies |
| Timeouts on waiting for elements | Increase `defaultCommandTimeout` in cypress.config.ts |
### Debugging Strategies
1. **Use Cypress's Debug Tools**
- Add `.debug()` to pause execution at a specific point
- Use the time-travel debugger in interactive mode
2. **Add Logging**
- Use `cy.log()` to add informative messages in the test
- Check browser console for application errors
3. **Visualize Test State**
- Enable screenshots and videos for failed tests
- Use `cy.screenshot()` at critical points
## References
- [Cypress Documentation](https://docs.cypress.io/)
- [Vue Test Utils](https://vue-test-utils.vuejs.org/)
- [Testing Library](https://testing-library.com/)
- [Cypress Best Practices](https://docs.cypress.io/guides/references/best-practices)