Files
leptos-shadcn-ui/tests/e2e/comprehensive-demo.spec.ts
Peter Hanssens a203dad5fc fix: Improve Playwright locator specificity for better test reliability
- Fix sidebar locator to use specific CSS classes instead of text matching
- Fix metric cards locator to target grid container children
- Fix recent activity timestamp locators to be more specific
- Resolve strict mode violations and element count mismatches
2025-09-23 20:10:50 +10:00

369 lines
14 KiB
TypeScript

import { test, expect, Page } from '@playwright/test';
/**
* Comprehensive Demo E2E Tests
*
* These tests ensure the comprehensive dashboard demo functions as expected,
* showcasing all interactive features, responsive design, and component capabilities.
*/
test.describe('Comprehensive Dashboard Demo E2E Tests', () => {
let page: Page;
test.beforeEach(async ({ page: testPage }) => {
page = testPage;
// Navigate to the comprehensive demo
await page.goto('http://localhost:8001');
await page.waitForLoadState('networkidle');
// Wait for WASM to load
await page.waitForSelector('nav', { timeout: 10000 });
});
test.describe('Page Structure and Navigation', () => {
test('should load the comprehensive dashboard successfully', async () => {
await expect(page).toHaveTitle(/Leptos Dashboard - ShadCN UI Demo/);
// Check for main navigation
const nav = page.locator('nav');
await expect(nav).toBeVisible();
// Check for dashboard title
const dashboardTitle = page.locator('h1:has-text("Dashboard")');
await expect(dashboardTitle).toBeVisible();
});
test('should have proper sidebar navigation', async () => {
const sidebar = page.locator('.w-64.bg-card.border-r.border-border');
await expect(sidebar).toBeVisible();
// Check navigation links
const dashboardLink = page.locator('a:has-text("Dashboard")');
const analyticsLink = page.locator('a:has-text("Analytics")');
const projectsLink = page.locator('a:has-text("Projects")');
const teamLink = page.locator('a:has-text("Team")');
const documentsLink = page.locator('a:has-text("Documents")');
const settingsLink = page.locator('a:has-text("Settings")');
await expect(dashboardLink).toBeVisible();
await expect(analyticsLink).toBeVisible();
await expect(projectsLink).toBeVisible();
await expect(teamLink).toBeVisible();
await expect(documentsLink).toBeVisible();
await expect(settingsLink).toBeVisible();
});
test('should have welcome section with proper messaging', async () => {
const welcomeTitle = page.locator('h2:has-text("Welcome back!")');
const welcomeDescription = page.locator('text=Here\'s what\'s happening with your projects today.');
await expect(welcomeTitle).toBeVisible();
await expect(welcomeDescription).toBeVisible();
});
});
test.describe('Metrics Cards', () => {
test('should display all metric cards', async () => {
const metricCards = page.locator('.grid.grid-cols-1.md\\:grid-cols-2.lg\\:grid-cols-4 > div');
await expect(metricCards).toHaveCount(4);
// Check individual cards
await expect(page.locator('text=Total Revenue')).toBeVisible();
await expect(page.locator('text=New Customers')).toBeVisible();
await expect(page.locator('text=Active Accounts')).toBeVisible();
await expect(page.locator('text=Growth Rate')).toBeVisible();
});
test('should have interactive metric cards', async () => {
// Test Total Revenue card
const revenueCard = page.locator('div:has-text("Total Revenue")').first();
await expect(revenueCard).toBeVisible();
// Click the card to update revenue
await revenueCard.click();
// Check that revenue value is displayed
const revenueValue = page.locator('text=/\\$\\d+\\.\\d+/').first();
await expect(revenueValue).toBeVisible();
});
test('should have hover effects on metric cards', async () => {
const metricCard = page.locator('div:has-text("Total Revenue")').first();
await metricCard.hover();
// Card should still be visible after hover
await expect(metricCard).toBeVisible();
});
});
test.describe('Interactive Dashboard Section', () => {
test('should have interactive counter', async () => {
const counterSection = page.locator('text=Interactive Counter');
await expect(counterSection).toBeVisible();
// Test counter buttons
const incrementButton = page.locator('button:has-text("+")');
const decrementButton = page.locator('button:has-text("-")');
const resetButton = page.locator('button:has-text("Reset")');
await expect(incrementButton).toBeVisible();
await expect(decrementButton).toBeVisible();
await expect(resetButton).toBeVisible();
// Test counter functionality
await incrementButton.click();
await incrementButton.click();
// Check that counter value is displayed
const counterValue = page.locator('text=/\\d+/').first();
await expect(counterValue).toBeVisible();
});
test('should have input component', async () => {
const inputSection = page.locator('text=Input Component');
await expect(inputSection).toBeVisible();
// Test input field
const inputField = page.locator('input[placeholder="Type something..."]');
await expect(inputField).toBeVisible();
// Type in the input
await inputField.fill('Test input');
await expect(inputField).toHaveValue('Test input');
});
test('should have Tailwind-RS-WASM demo section', async () => {
const tailwindSection = page.locator('text=Tailwind-RS-WASM Demo');
await expect(tailwindSection).toBeVisible();
// Check for demo elements
const shadcnButton = page.locator('button:has-text("ShadCN Button")');
const dynamicStyling = page.locator('text=Dynamic styling with Tailwind CSS');
const bestOfBothWorlds = page.locator('button:has-text("Best of both worlds!")');
await expect(shadcnButton).toBeVisible();
await expect(dynamicStyling).toBeVisible();
await expect(bestOfBothWorlds).toBeVisible();
});
});
test.describe('Recent Activity Section', () => {
test('should display recent activity feed', async () => {
const activitySection = page.locator('text=Recent Activity');
await expect(activitySection).toBeVisible();
// Check for activity items
const activityItems = page.locator('p:has-text("Eddie Lake completed Cover page")');
await expect(activityItems.first()).toBeVisible();
// Check for timestamps
const timestamps = page.locator('p:has-text("2 hours ago"), p:has-text("4 hours ago"), p:has-text("6 hours ago")');
await expect(timestamps.first()).toBeVisible();
});
});
test.describe('Data Table Section', () => {
test('should display project documents table', async () => {
const tableSection = page.locator('h3:has-text("Project Documents")');
await expect(tableSection).toBeVisible();
// Check table headers
const headers = page.locator('th');
await expect(headers.filter({ hasText: 'Document' })).toBeVisible();
await expect(headers.filter({ hasText: 'Type' })).toBeVisible();
await expect(headers.filter({ hasText: 'Status' })).toBeVisible();
await expect(headers.filter({ hasText: 'Assignee' })).toBeVisible();
await expect(headers.filter({ hasText: 'Actions' })).toBeVisible();
});
test('should have functional open menu buttons', async () => {
const openMenuButtons = page.locator('button:has-text("Open menu")');
await expect(openMenuButtons).toHaveCount(3);
// Click the first open menu button
await openMenuButtons.first().click();
// Check that dropdown menu appears
const dropdownMenu = page.locator('.absolute.top-16.right-4.bg-card');
await expect(dropdownMenu).toBeVisible();
});
test('should have status badges', async () => {
const statusBadges = page.locator('span:has-text("In Process"), span:has-text("Done")');
await expect(statusBadges).toHaveCount(3);
});
});
test.describe('Theme and Sidebar Toggle', () => {
test('should have theme toggle functionality', async () => {
const themeToggle = page.locator('button:has-text("Dark"), button:has-text("Light")');
await expect(themeToggle).toBeVisible();
// Click theme toggle
await themeToggle.click();
// Check that theme changes (dark class should be applied)
const body = page.locator('body');
await expect(body).toBeVisible();
});
test('should have sidebar toggle functionality', async () => {
const sidebarToggle = page.locator('button:has-text("☰")');
await expect(sidebarToggle).toBeVisible();
// Click sidebar toggle
await sidebarToggle.click();
// Check that sidebar is hidden/shown
const sidebar = page.locator('.w-64.bg-card.border-r.border-border');
await expect(sidebar).toBeVisible();
});
});
test.describe('Responsive Design', () => {
test('should be responsive on mobile', async () => {
await page.setViewportSize({ width: 375, height: 667 });
// Check that navigation is still accessible
const nav = page.locator('nav');
await expect(nav).toBeVisible();
// Check that dashboard content is visible
const dashboardTitle = page.locator('h1:has-text("Dashboard")');
await expect(dashboardTitle).toBeVisible();
});
test('should be responsive on tablet', async () => {
await page.setViewportSize({ width: 768, height: 1024 });
// Check that all sections are visible
const sections = page.locator('main');
await expect(sections).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper heading structure', async () => {
const h1 = page.locator('h1');
const h2 = page.locator('h2');
await expect(h1).toHaveCount(1);
const h2Count = await h2.count();
expect(h2Count).toBeGreaterThan(0);
});
test('should have proper button labels', async () => {
const buttons = page.locator('button');
const buttonCount = await buttons.count();
for (let i = 0; i < Math.min(buttonCount, 10); i++) {
const button = buttons.nth(i);
const text = await button.textContent();
expect(text?.trim()).toBeTruthy();
}
});
test('should have proper input labels', async () => {
const inputs = page.locator('input');
const inputCount = await inputs.count();
for (let i = 0; i < inputCount; i++) {
const input = inputs.nth(i);
const placeholder = await input.getAttribute('placeholder');
expect(placeholder).toBeTruthy();
}
});
});
test.describe('Performance and Loading', () => {
test('should load within acceptable time', async () => {
const startTime = Date.now();
await page.goto('http://localhost:8001');
await page.waitForLoadState('networkidle');
const loadTime = Date.now() - startTime;
// Should load within 5 seconds
expect(loadTime).toBeLessThan(5000);
});
test('should have proper WASM loading', async () => {
// Check that WASM is loaded by looking for interactive elements
const interactiveButton = page.locator('button:has-text("+")');
await expect(interactiveButton).toBeVisible();
// Test that WASM interactions work
await interactiveButton.click();
});
});
test.describe('Visual Quality', () => {
test('should have proper styling and colors', async () => {
// Check for proper button styling
const primaryButton = page.locator('button:has-text("+")').first();
await expect(primaryButton).toBeVisible();
// Check for card styling
const card = page.locator('div:has-text("Total Revenue")').first();
await expect(card).toBeVisible();
});
test('should have proper spacing and layout', async () => {
// Check that sections are properly spaced
const main = page.locator('main');
await expect(main).toBeVisible();
// Check for proper grid layouts
const grid = page.locator('.grid').first();
await expect(grid).toBeVisible();
});
});
test.describe('Interactive Features', () => {
test('should handle button interactions smoothly', async () => {
const buttons = page.locator('button');
const buttonCount = await buttons.count();
// Test clicking several buttons
for (let i = 0; i < Math.min(buttonCount, 5); i++) {
const button = buttons.nth(i);
await button.click();
await page.waitForTimeout(100);
}
// Page should still be responsive
const dashboardTitle = page.locator('h1:has-text("Dashboard")');
await expect(dashboardTitle).toBeVisible();
});
test('should handle hover effects', async () => {
const hoverElements = page.locator('div:has-text("Total Revenue")');
const elementCount = await hoverElements.count();
// Test hover effects
for (let i = 0; i < Math.min(elementCount, 3); i++) {
const element = hoverElements.nth(i);
await element.hover();
await page.waitForTimeout(200);
}
// Elements should still be visible after hover
await expect(hoverElements.first()).toBeVisible();
});
});
test.describe('Error Handling', () => {
test('should handle missing resources gracefully', async () => {
// Simulate network failure for external resources
await page.route('**/tailwindcss.com/**', (route) => {
route.abort('failed');
});
// Page should still load and function
await page.reload();
await page.waitForLoadState('networkidle');
const dashboardTitle = page.locator('h1:has-text("Dashboard")');
await expect(dashboardTitle).toBeVisible();
});
});
});