/** * E2E tests for dashboard navigation and data visualization * * Tests the dashboard user experience: * 1. Navigation between dashboard sections * 2. Data visualization components * 3. Interactive filtering and search * 4. Responsive design * 5. Accessibility features */ import { test, expect, type Page } from "@playwright/test"; // Test data const testUser = { email: "dashboard@test.com", password: "DashboardTest123!", }; // Helper functions async function loginUser(page: Page) { await page.goto("http://localhost:3000/login"); await page.fill('[data-testid="email"]', testUser.email); await page.fill('[data-testid="password"]', testUser.password); await page.click('[data-testid="login-button"]'); await expect(page).toHaveURL(/\/dashboard/); } async function waitForChartLoad(page: Page, chartSelector: string) { await page.waitForSelector(chartSelector); await page.waitForFunction((selector) => { const chart = document.querySelector(selector); return chart && chart.children.length > 0; }, chartSelector); } test.describe("Dashboard Navigation", () => { test.beforeEach(async ({ page }) => { await loginUser(page); }); test.describe("Navigation Menu", () => { test("should display main navigation menu", async ({ page }) => { // Check navigation sidebar const nav = page.locator('[data-testid="main-navigation"]'); await expect(nav).toBeVisible(); // Check navigation items await expect(page.locator('[data-testid="nav-overview"]')).toBeVisible(); await expect(page.locator('[data-testid="nav-sessions"]')).toBeVisible(); await expect(page.locator('[data-testid="nav-users"]')).toBeVisible(); await expect(page.locator('[data-testid="nav-company"]')).toBeVisible(); }); test("should highlight active navigation item", async ({ page }) => { // Overview should be active by default await expect(page.locator('[data-testid="nav-overview"]')).toHaveClass( /active/ ); // Navigate to sessions await page.click('[data-testid="nav-sessions"]'); await expect(page.locator('[data-testid="nav-sessions"]')).toHaveClass( /active/ ); await expect( page.locator('[data-testid="nav-overview"]') ).not.toHaveClass(/active/); }); test("should navigate between sections correctly", async ({ page }) => { // Navigate to Sessions await page.click('[data-testid="nav-sessions"]'); await expect(page).toHaveURL(/\/dashboard\/sessions/); await expect(page.locator("h1")).toContainText("Sessions"); // Navigate to Users await page.click('[data-testid="nav-users"]'); await expect(page).toHaveURL(/\/dashboard\/users/); await expect(page.locator("h1")).toContainText("Users"); // Navigate to Company await page.click('[data-testid="nav-company"]'); await expect(page).toHaveURL(/\/dashboard\/company/); await expect(page.locator("h1")).toContainText("Company Settings"); // Navigate back to Overview await page.click('[data-testid="nav-overview"]'); await expect(page).toHaveURL(/\/dashboard\/overview/); await expect(page.locator("h1")).toContainText("Dashboard Overview"); }); test("should support breadcrumb navigation", async ({ page }) => { // Navigate to sessions and then a specific session await page.click('[data-testid="nav-sessions"]'); // Mock a session item click (assuming sessions exist) const sessionItems = page.locator('[data-testid="session-item"]'); const sessionCount = await sessionItems.count(); if (sessionCount > 0) { await sessionItems.first().click(); // Check breadcrumbs await expect(page.locator('[data-testid="breadcrumb"]')).toBeVisible(); await expect( page.locator('[data-testid="breadcrumb-home"]') ).toContainText("Dashboard"); await expect( page.locator('[data-testid="breadcrumb-sessions"]') ).toContainText("Sessions"); await expect( page.locator('[data-testid="breadcrumb-current"]') ).toContainText("Session Details"); } }); }); test.describe("Header Navigation", () => { test("should display user menu", async ({ page }) => { // Check user menu trigger const userMenu = page.locator('[data-testid="user-menu"]'); await expect(userMenu).toBeVisible(); // Open user menu await userMenu.click(); // Check menu items await expect(page.locator('[data-testid="user-profile"]')).toBeVisible(); await expect(page.locator('[data-testid="user-settings"]')).toBeVisible(); await expect(page.locator('[data-testid="logout-button"]')).toBeVisible(); }); test("should display notifications", async ({ page }) => { const notifications = page.locator('[data-testid="notifications"]'); if (await notifications.isVisible()) { await notifications.click(); await expect( page.locator('[data-testid="notifications-dropdown"]') ).toBeVisible(); } }); test("should display search functionality", async ({ page }) => { const searchInput = page.locator('[data-testid="global-search"]'); if (await searchInput.isVisible()) { await searchInput.fill("test search"); await expect( page.locator('[data-testid="search-results"]') ).toBeVisible(); } }); }); test.describe("Page Titles and Metadata", () => { test("should update page title for each section", async ({ page }) => { // Overview page await page.goto("http://localhost:3000/dashboard/overview"); await expect(page).toHaveTitle(/Dashboard Overview/); // Sessions page await page.goto("http://localhost:3000/dashboard/sessions"); await expect(page).toHaveTitle(/Sessions/); // Users page await page.goto("http://localhost:3000/dashboard/users"); await expect(page).toHaveTitle(/Users/); }); }); }); test.describe("Data Visualization", () => { test.beforeEach(async ({ page }) => { await loginUser(page); await page.goto("http://localhost:3000/dashboard/overview"); }); test.describe("Overview Dashboard", () => { test("should display key metrics cards", async ({ page }) => { // Check metric cards await expect( page.locator('[data-testid="total-sessions-card"]') ).toBeVisible(); await expect( page.locator('[data-testid="avg-sentiment-card"]') ).toBeVisible(); await expect( page.locator('[data-testid="escalation-rate-card"]') ).toBeVisible(); await expect( page.locator('[data-testid="avg-response-time-card"]') ).toBeVisible(); // Check that metrics have values const totalSessions = page.locator( '[data-testid="total-sessions-value"]' ); await expect(totalSessions).toContainText(/\d+/); // Should contain numbers }); test("should display sentiment distribution chart", async ({ page }) => { const sentimentChart = page.locator('[data-testid="sentiment-chart"]'); await expect(sentimentChart).toBeVisible(); await waitForChartLoad(page, '[data-testid="sentiment-chart"]'); // Check chart has data await expect( page.locator('[data-testid="positive-sentiment"]') ).toBeVisible(); await expect( page.locator('[data-testid="neutral-sentiment"]') ).toBeVisible(); await expect( page.locator('[data-testid="negative-sentiment"]') ).toBeVisible(); }); test("should display category distribution chart", async ({ page }) => { const categoryChart = page.locator('[data-testid="category-chart"]'); await expect(categoryChart).toBeVisible(); await waitForChartLoad(page, '[data-testid="category-chart"]'); // Should show category data const categories = page.locator('[data-testid="category-item"]'); const count = await categories.count(); expect(count).toBeGreaterThan(0); }); test("should display geographic distribution map", async ({ page }) => { const geoMap = page.locator('[data-testid="geographic-map"]'); await expect(geoMap).toBeVisible(); // Wait for map to load - wait for map container or country data to be rendered await page.waitForSelector('[data-testid="country-data"], .leaflet-container, .geo-map-loaded', { timeout: 10000, state: 'visible' }).catch(() => { // Fallback: wait for any map-related element to indicate map is loaded return page.waitForSelector('.map, [class*="map"], [data-map]', { timeout: 5000 }).catch(() => null); }); // Check if country data is displayed const countryData = page.locator('[data-testid="country-data"]'); if (await countryData.isVisible()) { expect(await countryData.count()).toBeGreaterThan(0); } }); test("should display top questions list", async ({ page }) => { const topQuestions = page.locator('[data-testid="top-questions"]'); await expect(topQuestions).toBeVisible(); // Check if questions are displayed const questionItems = page.locator('[data-testid="question-item"]'); const count = await questionItems.count(); if (count > 0) { // Should show question text and count const firstQuestion = questionItems.first(); await expect( firstQuestion.locator('[data-testid="question-text"]') ).toBeVisible(); await expect( firstQuestion.locator('[data-testid="question-count"]') ).toBeVisible(); } }); test("should display time series chart", async ({ page }) => { const timeChart = page.locator('[data-testid="time-series-chart"]'); if (await timeChart.isVisible()) { await waitForChartLoad(page, '[data-testid="time-series-chart"]'); // Check chart axes await expect( page.locator('[data-testid="chart-x-axis"]') ).toBeVisible(); await expect( page.locator('[data-testid="chart-y-axis"]') ).toBeVisible(); } }); }); test.describe("Chart Interactions", () => { test("should allow chart filtering interactions", async ({ page }) => { const sentimentChart = page.locator('[data-testid="sentiment-chart"]'); if (await sentimentChart.isVisible()) { // Click on positive sentiment section const positiveSection = page.locator( '[data-testid="positive-segment"]' ); if (await positiveSection.isVisible()) { await positiveSection.click(); // Should filter data or show details await expect( page.locator('[data-testid="chart-filter-active"]') ).toBeVisible(); } } }); test("should show chart tooltips on hover", async ({ page }) => { const chart = page.locator('[data-testid="sentiment-chart"]'); if (await chart.isVisible()) { await chart.hover(); // Check for tooltip const tooltip = page.locator('[data-testid="chart-tooltip"]'); if (await tooltip.isVisible()) { await expect(tooltip).toContainText(/\d+/); // Should show numeric data } } }); test("should support chart zoom and pan", async ({ page }) => { const timeChart = page.locator('[data-testid="time-series-chart"]'); if (await timeChart.isVisible()) { // Test zoom (scroll) await timeChart.hover(); await page.mouse.wheel(0, -100); // Test pan (drag) const box = await timeChart.boundingBox(); if (box) { await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); await page.mouse.down(); await page.mouse.move( box.x + box.width / 2 + 50, box.y + box.height / 2 ); await page.mouse.up(); } } }); }); test.describe("Data Filtering", () => { test("should filter data by date range", async ({ page }) => { // Open date picker const dateFilter = page.locator('[data-testid="date-range-picker"]'); if (await dateFilter.isVisible()) { await dateFilter.click(); // Select date range await page.click('[data-testid="date-last-week"]'); // Wait for charts to update after date filter application await page.waitForSelector('[data-testid="filter-applied"], [data-testid="charts-updated"], .loading:not(.visible)', { timeout: 5000, state: 'visible' }).catch(() => { // Fallback: wait for any indication that filtering is complete return page.waitForFunction(() => !document.querySelector('.loading, [data-loading="true"]'), { timeout: 3000 }).catch(() => null); }); // Check that data is filtered await expect( page.locator('[data-testid="filter-applied"]') ).toBeVisible(); } }); test("should filter data by sentiment", async ({ page }) => { const sentimentFilter = page.locator('[data-testid="sentiment-filter"]'); if (await sentimentFilter.isVisible()) { await sentimentFilter.selectOption("POSITIVE"); // Wait for visualizations to update after sentiment filter await page.waitForSelector('[data-testid="active-filters"], [data-testid="sentiment-applied"], .charts-container:not(.updating)', { timeout: 5000, state: 'visible' }).catch(() => { // Fallback: wait for filter processing to complete return page.waitForFunction(() => !document.querySelector('.updating, [data-updating="true"], .filter-loading'), { timeout: 3000 }).catch(() => null); }); // Check filter is applied await expect( page.locator('[data-testid="active-filters"]') ).toContainText("Sentiment: Positive"); } }); test("should clear all filters", async ({ page }) => { // Apply some filters first const sentimentFilter = page.locator('[data-testid="sentiment-filter"]'); if (await sentimentFilter.isVisible()) { await sentimentFilter.selectOption("POSITIVE"); } // Clear filters const clearButton = page.locator('[data-testid="clear-filters"]'); if (await clearButton.isVisible()) { await clearButton.click(); // Should reset all data await expect( page.locator('[data-testid="active-filters"]') ).toHaveCount(0); } }); }); test.describe("Data Export", () => { test("should export chart data as CSV", async ({ page }) => { const exportButton = page.locator('[data-testid="export-csv"]'); if (await exportButton.isVisible()) { // Start download const downloadPromise = page.waitForEvent("download"); await exportButton.click(); const download = await downloadPromise; // Verify download expect(download.suggestedFilename()).toContain(".csv"); } }); test("should export chart as image", async ({ page }) => { const exportButton = page.locator('[data-testid="export-image"]'); if (await exportButton.isVisible()) { const downloadPromise = page.waitForEvent("download"); await exportButton.click(); const download = await downloadPromise; expect(download.suggestedFilename()).toMatch(/\.(png|jpg|svg)$/); } }); }); }); test.describe("Responsive Design", () => { test.beforeEach(async ({ page }) => { await loginUser(page); }); test.describe("Mobile Layout", () => { test("should adapt navigation for mobile", async ({ page }) => { // Set mobile viewport await page.setViewportSize({ width: 375, height: 667 }); await page.goto("http://localhost:3000/dashboard/overview"); // Should show mobile menu button const mobileMenu = page.locator('[data-testid="mobile-menu-toggle"]'); await expect(mobileMenu).toBeVisible(); // Open mobile menu await mobileMenu.click(); await expect( page.locator('[data-testid="mobile-navigation"]') ).toBeVisible(); // Check navigation items in mobile menu await expect( page.locator('[data-testid="mobile-nav-overview"]') ).toBeVisible(); await expect( page.locator('[data-testid="mobile-nav-sessions"]') ).toBeVisible(); }); test("should stack charts vertically on mobile", async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto("http://localhost:3000/dashboard/overview"); // Charts should be stacked const chartContainer = page.locator('[data-testid="charts-container"]'); await expect(chartContainer).toHaveCSS("flex-direction", "column"); }); test("should show simplified metrics on mobile", async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto("http://localhost:3000/dashboard/overview"); // Should show condensed metric cards const metricCards = page.locator('[data-testid="metric-card"]'); const count = await metricCards.count(); // Should show fewer cards or smaller layout for (let i = 0; i < count; i++) { const card = metricCards.nth(i); const box = await card.boundingBox(); if (box) { expect(box.width).toBeLessThan(300); // Smaller cards on mobile } } }); }); test.describe("Tablet Layout", () => { test("should adapt layout for tablet", async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); await page.goto("http://localhost:3000/dashboard/overview"); // Should show sidebar but possibly collapsed const sidebar = page.locator('[data-testid="sidebar"]'); await expect(sidebar).toBeVisible(); // Charts should adapt to medium screen const chartGrid = page.locator('[data-testid="chart-grid"]'); await expect(chartGrid).toHaveCSS("grid-template-columns", /repeat\(2,/); }); }); }); test.describe("Accessibility", () => { test.beforeEach(async ({ page }) => { await loginUser(page); }); test.describe("Keyboard Navigation", () => { test("should support keyboard navigation in dashboard", async ({ page, }) => { await page.goto("http://localhost:3000/dashboard/overview"); // Test tab navigation await page.keyboard.press("Tab"); // Should focus on first interactive element const focused = page.locator(":focus"); await expect(focused).toBeVisible(); // Navigate through elements for (let i = 0; i < 5; i++) { await page.keyboard.press("Tab"); const currentFocus = page.locator(":focus"); await expect(currentFocus).toBeVisible(); } }); test("should support keyboard shortcuts", async ({ page }) => { await page.goto("http://localhost:3000/dashboard/overview"); // Test keyboard shortcuts (if implemented) await page.keyboard.press("Alt+1"); // Navigate to overview await expect(page).toHaveURL(/\/dashboard\/overview/); await page.keyboard.press("Alt+2"); // Navigate to sessions await expect(page).toHaveURL(/\/dashboard\/sessions/); }); }); test.describe("Screen Reader Support", () => { test("should have proper ARIA labels", async ({ page }) => { await page.goto("http://localhost:3000/dashboard/overview"); // Check main landmarks await expect(page.locator("main")).toHaveAttribute("role", "main"); await expect(page.locator("nav")).toHaveAttribute("role", "navigation"); // Check chart accessibility const sentimentChart = page.locator('[data-testid="sentiment-chart"]'); if (await sentimentChart.isVisible()) { await expect(sentimentChart).toHaveAttribute("role", "img"); await expect(sentimentChart).toHaveAttribute("aria-label"); } }); test("should provide alternative text for charts", async ({ page }) => { await page.goto("http://localhost:3000/dashboard/overview"); // Check chart descriptions const charts = page.locator('[role="img"]'); const count = await charts.count(); for (let i = 0; i < count; i++) { const chart = charts.nth(i); const ariaLabel = await chart.getAttribute("aria-label"); expect(ariaLabel).toBeTruthy(); expect(ariaLabel?.length).toBeGreaterThan(10); // Should be descriptive } }); test("should announce dynamic content changes", async ({ page }) => { await page.goto("http://localhost:3000/dashboard/overview"); // Check for live regions const liveRegions = page.locator("[aria-live]"); const count = await liveRegions.count(); if (count > 0) { // Should have appropriate aria-live settings for (let i = 0; i < count; i++) { const region = liveRegions.nth(i); const ariaLive = await region.getAttribute("aria-live"); expect(["polite", "assertive"]).toContain(ariaLive); } } }); }); test.describe("Color and Contrast", () => { test("should maintain accessibility in dark mode", async ({ page }) => { await page.goto("http://localhost:3000/dashboard/overview"); // Toggle dark mode (if available) const darkModeToggle = page.locator('[data-testid="theme-toggle"]'); if (await darkModeToggle.isVisible()) { await darkModeToggle.click(); // Check that elements are still visible await expect( page.locator('[data-testid="total-sessions-card"]') ).toBeVisible(); await expect( page.locator('[data-testid="sentiment-chart"]') ).toBeVisible(); } }); test("should work without color", async ({ page }) => { // Test with forced colors (simulates high contrast mode) await page.emulateMedia({ colorScheme: "dark", forcedColors: "active" }); await page.goto("http://localhost:3000/dashboard/overview"); // Elements should still be distinguishable await expect( page.locator('[data-testid="total-sessions-card"]') ).toBeVisible(); await expect( page.locator('[data-testid="sentiment-chart"]') ).toBeVisible(); }); }); });