describe('Dashboard E2E Tests', () => { beforeEach(() => { // Mock API responses cy.intercept('GET', '/api/devices', { statusCode: 200, body: [ { id: 1, device_id: 'sensor-001', name: '테스트 센서', status: 'active', last_seen: '2025-08-01T10:00:00Z' } ] }).as('getDevices'); cy.intercept('GET', '/api/sensors/latest/*', { statusCode: 200, body: { device_id: 'sensor-001', temperature: 25.5, humidity: 60.0, pm10: 15.2, pm25: 8.1, pressure: 1013.25, illumination: 500, tvoc: 120, co2: 450, o2: 20.9, co: 0.5, recorded_time: '2025-08-01T10:00:00Z' } }).as('getLatestData'); cy.visit('/'); }); it('should load dashboard and display sensor data', () => { // Wait for API calls cy.wait('@getDevices'); cy.wait('@getLatestData'); // Check main title cy.get('h1').should('contain', '센서 대시보드'); // Check sensor cards cy.get('[data-testid="sensor-card"]').should('have.length.at.least', 4); // Check specific sensor values cy.contains('현재 온도').should('be.visible'); cy.contains('25.5°C').should('be.visible'); cy.contains('현재 습도').should('be.visible'); cy.contains('60.0%').should('be.visible'); }); it('should display loading state initially', () => { // Intercept with delay to test loading state cy.intercept('GET', '/api/devices', { statusCode: 200, body: [], delay: 1000 }).as('getDevicesDelayed'); cy.visit('/'); // Should show loading spinner cy.contains('데이터를 불러오는 중...').should('be.visible'); }); it('should handle API errors gracefully', () => { // Mock API error cy.intercept('GET', '/api/devices', { statusCode: 500, body: { error: 'Internal Server Error' } }).as('getDevicesError'); cy.visit('/'); cy.wait('@getDevicesError'); // Should show error message cy.contains('오류가 발생했습니다').should('be.visible'); cy.contains('다시 시도').should('be.visible'); }); it('should retry data loading when retry button is clicked', () => { // Mock initial error cy.intercept('GET', '/api/devices', { statusCode: 500, body: { error: 'Internal Server Error' } }).as('getDevicesError'); cy.visit('/'); cy.wait('@getDevicesError'); // Mock successful response for retry cy.intercept('GET', '/api/devices', { statusCode: 200, body: [ { id: 1, device_id: 'sensor-001', name: '테스트 센서', status: 'active', last_seen: '2025-08-01T10:00:00Z' } ] }).as('getDevicesRetry'); // Click retry button cy.contains('다시 시도').click(); cy.wait('@getDevicesRetry'); // Should show dashboard content cy.contains('센서 대시보드').should('be.visible'); }); it('should display device status table', () => { cy.wait('@getDevices'); cy.wait('@getLatestData'); // Check device status table cy.contains('디바이스 상태').should('be.visible'); cy.contains('sensor-001').should('be.visible'); cy.contains('테스트 센서').should('be.visible'); cy.contains('active').should('be.visible'); }); it('should display summary cards', () => { cy.wait('@getDevices'); cy.wait('@getLatestData'); // Check summary cards cy.contains('활성 디바이스').should('be.visible'); cy.contains('1').should('be.visible'); // active device count cy.contains('총 데이터').should('be.visible'); cy.contains('1').should('be.visible'); // total data count }); it('should handle invalid sensor data', () => { // Mock invalid sensor data cy.intercept('GET', '/api/sensors/latest/*', { statusCode: 200, body: { device_id: 'sensor-001', temperature: 1e-40, // very small value humidity: undefined, pm10: null, pm25: Infinity, pressure: NaN, illumination: 500, tvoc: 120, co2: 450, o2: 20.9, co: 0.5, recorded_time: '2025-08-01T10:00:00Z' } }).as('getInvalidData'); cy.visit('/'); cy.wait('@getDevices'); cy.wait('@getInvalidData'); // Should display formatted values for invalid data cy.contains('0.0°C').should('be.visible'); // very small value cy.contains('N/A%').should('be.visible'); // undefined cy.contains('N/Aμg/m³').should('be.visible'); // null/Infinity cy.contains('N/AhPa').should('be.visible'); // NaN }); it('should display all sensor types', () => { cy.wait('@getDevices'); cy.wait('@getLatestData'); // Check all sensor cards are rendered cy.contains('현재 온도').should('be.visible'); cy.contains('현재 습도').should('be.visible'); cy.contains('PM10').should('be.visible'); cy.contains('PM2.5').should('be.visible'); cy.contains('기압').should('be.visible'); cy.contains('조도').should('be.visible'); cy.contains('TVOC').should('be.visible'); cy.contains('CO2').should('be.visible'); cy.contains('O2').should('be.visible'); cy.contains('CO').should('be.visible'); }); it('should navigate to other pages', () => { cy.wait('@getDevices'); cy.wait('@getLatestData'); // Navigate to History page cy.contains('히스토리').click(); cy.url().should('include', '/history'); // Navigate to Devices page cy.contains('디바이스').click(); cy.url().should('include', '/devices'); // Navigate back to Dashboard cy.contains('대시보드').click(); cy.url().should('include', '/dashboard'); }); it('should be responsive on different screen sizes', () => { cy.wait('@getDevices'); cy.wait('@getLatestData'); // Test mobile viewport cy.viewport(375, 667); cy.get('[data-testid="sensor-card"]').should('be.visible'); // Test tablet viewport cy.viewport(768, 1024); cy.get('[data-testid="sensor-card"]').should('be.visible'); // Test desktop viewport cy.viewport(1920, 1080); cy.get('[data-testid="sensor-card"]').should('be.visible'); }); it('should handle WebSocket connection status', () => { // Mock WebSocket connection cy.window().then((win) => { cy.stub(win, 'WebSocket').returns({ readyState: 1, // OPEN send: cy.stub(), close: cy.stub(), addEventListener: cy.stub() }); }); cy.visit('/'); cy.wait('@getDevices'); cy.wait('@getLatestData'); // Check connection status indicator cy.get('[data-testid="status-indicator"]').should('be.visible'); }); it('should export data functionality', () => { cy.wait('@getDevices'); cy.wait('@getLatestData'); // Mock file download cy.window().then((win) => { cy.stub(win, 'open').as('windowOpen'); }); // Click export button if exists cy.get('button').contains('내보내기').click({ force: true }); // Verify export functionality cy.get('@windowOpen').should('have.been.called'); }); });