learning_ai_invt_trdg/docs/UX_TESTING_SETUP_GUIDE.md
Saravana Achu Mac 72624aa8fc docs: add comprehensive UX testing setup guide
Created detailed documentation for UX testing infrastructure that can be replicated across all ByteLyst products:

- Complete Playwright setup instructions
- Test suite structure and patterns
- Test runner script with server lifecycle management
- AI-friendly report generation
- Common platform UI integration
- Design token usage patterns
- Cipher design system integration
- Step-by-step implementation guide
- Troubleshooting and best practices
- CI integration examples

This guide enables other products to follow the exact same steps for UX testing
using common platform packages, design tokens, and Cipher design system principles.
2026-05-09 14:12:58 -07:00

17 KiB

UX Testing Setup Guide

Purpose: Comprehensive guide for setting up UX testing infrastructure across ByteLyst products using common platform UI packages, design tokens, and Cipher design system integration.

Target Audience: Product teams implementing E2E testing for their applications.


Overview

This guide documents the complete UX testing infrastructure setup for the trading dashboard, which can be replicated across all ByteLyst products. The approach leverages:

  • Playwright for end-to-end testing
  • Common Platform UI packages (@bytelyst/ui)
  • Design Tokens from @bytelyst/design-tokens
  • Cipher Design System principles
  • AI-friendly reporting for automated analysis

Prerequisites

Common Platform Integration

Ensure your product is integrated with the common platform:

# Use local packages by default
BYTELYST_PACKAGE_SOURCE=common-plat

# Install common platform packages
pnpm install @bytelyst/ui @bytelyst/design-tokens

Design Tokens

Design tokens should be consumed from the common platform:

// Use CSS variables from design tokens
background: var(--bl-surface-card);
color: var(--bl-text-primary);
border-color: var(--bl-border);

Step 1: Playwright Setup

Install Dependencies

cd web
pnpm add -D @playwright/test
pnpm exec playwright install chromium

Create Playwright Config

Create web/playwright.config.ts:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3050',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    },
    {
      name: 'Desktop Chrome HiDPI',
      use: { ...devices['Desktop Chrome HiDPI'] },
    },
  ],
  // webServer is disabled - tests should run against already-running server
  // Test runner script handles server lifecycle
});

Add Test Scripts to package.json

{
  "scripts": {
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:e2e:viewport": "playwright test e2e/viewport-matrix.spec.ts",
    "test:e2e:overflow": "playwright test e2e/horizontal-overflow.spec.ts"
  }
}

Step 2: Create Test Suite Structure

Test Categories

Create tests in web/e2e/ directory following this structure:

web/e2e/
├── viewport-matrix.spec.ts      # Viewport compliance
├── horizontal-overflow.spec.ts  # Overflow detection
├── alert-positioning.spec.ts    # Critical alerts positioning
├── assistant-positioning.spec.ts # Assistant widget positioning
├── destructive-actions.spec.ts  # Destructive actions confirmation
├── feedback.spec.ts             # Save/delete/update feedback
├── page-states.spec.ts          # Loading/empty/error/success states
├── form-validation.spec.ts      # Form validation
└── keyboard-navigation.spec.ts   # Keyboard navigation

Step 3: Test Implementation Patterns

Viewport Matrix Test

Tests that all routes pass viewport matrix for desktop, tablet, and mobile:

import { test, expect } from '@playwright/test';

const routes = ['/', '/portfolio', '/research', '/plans', '/markets'];
const viewports = [
  { name: 'Desktop', width: 1200, height: 800 },
  { name: 'Tablet', width: 768, height: 1024 },
  { name: 'Mobile', width: 375, height: 667 },
];

routes.forEach((route) => {
  viewports.forEach((viewport) => {
    test(`${route} - ${viewport.name} viewport`, async ({ page }) => {
      await page.setViewportSize({ width: viewport.width, height: viewport.height });
      await page.goto(route);
      
      // Check for horizontal overflow
      const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
      expect(bodyWidth).toBeLessThanOrEqual(viewport.width + 10);
      
      // Check main content visibility
      const mainContent = page.locator('main');
      await expect(mainContent).toBeVisible();
    });
  });
});

Alert Positioning Test

Tests that critical alerts don't cover primary content:

import { test, expect } from '@playwright/test';

test.describe('Critical Alerts Positioning', () => {
  test('AlertBanner does not cover primary content', async ({ page }) => {
    await page.goto('/');
    
    // Find alert banners
    const alerts = page.locator('[role="alert"], .alert, [class*="alert"]');
    const alertCount = await alerts.count();
    
    if (alertCount > 0) {
      const alert = alerts.first();
      const alertBox = await alert.boundingBox();
      const mainContent = page.locator('main');
      const mainBox = await mainContent.boundingBox();
      
      // Alert should be inline, not covering main content
      expect(alertBox).toBeTruthy();
    }
  });

  test('AlertBanner has proper z-index', async ({ page }) => {
    await page.goto('/');
    const alerts = page.locator('[role="alert"]');
    
    if (await alerts.count() > 0) {
      const zIndex = await alerts.first().evaluate((el) => {
        return window.getComputedStyle(el).zIndex;
      });
      expect(parseInt(zIndex)).toBeLessThan(1000);
    }
  });
});

Form Validation Test

Tests that forms have labels, hints, validation, and disabled-state explanations:

import { test, expect } from '@playwright/test';

test.describe('Form Validation', () => {
  test('Forms have labels for inputs', async ({ page }) => {
    await page.goto('/plans');
    
    const inputs = page.locator('input, select, textarea');
    const inputCount = await inputs.count();
    
    if (inputCount > 0) {
      for (let i = 0; i < Math.min(inputCount, 10); i++) {
        const input = inputs.nth(i);
        const isVisible = await input.isVisible();
        
        if (isVisible) {
          const id = await input.getAttribute('id');
          let hasLabel = false;
          
          if (id) {
            const label = page.locator(`label[for="${id}"]`);
            hasLabel = await label.count() > 0;
          }
          
          const ariaLabel = await input.getAttribute('aria-label');
          const ariaLabelledby = await input.getAttribute('aria-labelledby');
          const hasAriaLabel = ariaLabel !== null || ariaLabelledby !== null;
          
          expect(hasLabel || hasAriaLabel).toBeTruthy();
        }
      }
    }
  });
});

Keyboard Navigation Test

Tests that primary workflows pass keyboard navigation:

import { test, expect } from '@playwright/test';

test.describe('Keyboard Navigation', () => {
  test('Tab order is logical', async ({ page }) => {
    await page.goto('/');
    
    const focusableElements = await page.evaluate(() => {
      const focusable = ['button', '[href]', 'input', 'select', 'textarea', '[tabindex]:not([tabindex="-1"])'];
      return Array.from(document.querySelectorAll(focusable.join(',')))
        .filter(el => {
          const style = window.getComputedStyle(el);
          return style.display !== 'none' && style.visibility !== 'hidden';
        })
        .length;
    });
    
    expect(focusableElements).toBeGreaterThan(0);
  });

  test('Enter and Space activate buttons', async ({ page }) => {
    await page.goto('/');
    
    const button = page.locator('button').first();
    const buttonCount = await button.count();
    
    if (buttonCount > 0) {
      await button.focus();
      await page.keyboard.press('Space');
      await page.waitForTimeout(500);
      
      const isVisible = await button.isVisible();
      expect(isVisible).toBeTruthy();
    }
  });
});

Step 4: Test Runner Script

Create Test Runner Script

Create scripts/tests/run-e2e.sh with server lifecycle management:

#!/bin/bash
set -e

# Colors for visual output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
WEB_DIR="$PROJECT_ROOT/web"
REPORTS_DIR="$SCRIPT_DIR/reports"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
PORT=3050

cd "$WEB_DIR"

# Function to kill any process on the specified port
kill_port() {
    local port=$1
    local pid=$(lsof -ti:$port 2>/dev/null || echo "")
    if [ -n "$pid" ]; then
        echo -e "${YELLOW}Killing process $pid on port $port...${NC}"
        kill -9 $pid 2>/dev/null || true
        sleep 2
    fi
}

# Function to wait for server to be ready
wait_for_server() {
    local max_attempts=30
    local attempt=0
    echo -e "${CYAN}Waiting for server to be ready on port $PORT...${NC}"
    
    while [ $attempt -lt $max_attempts ]; do
        if curl -s http://localhost:$PORT > /dev/null 2>&1; then
            echo -e "${GREEN}✅ Server is ready${NC}"
            return 0
        fi
        attempt=$((attempt + 1))
        echo -e "${YELLOW}Attempt $attempt/$max_attempts...${NC}"
        sleep 2
    done
    
    echo -e "${RED}❌ Server failed to start after $max_attempts attempts${NC}"
    return 1
}

# Kill existing server
echo -e "${YELLOW}🔍 Checking for existing server on port $PORT...${NC}"
kill_port $PORT
echo -e "${GREEN}✓ Port $PORT is clear${NC}"

# Start dev server
echo -e "${CYAN}🚀 Starting dev server...${NC}"
pnpm dev --port $PORT --strict-port > "$REPORTS_DIR/server-$TIMESTAMP.log" 2>&1 &
SERVER_PID=$!

# Wait for server
if ! wait_for_server; then
    echo -e "${RED}❌ Server failed to start${NC}"
    kill_port $PORT
    exit 1
fi

# Run tests
echo -e "${CYAN}🎭 Running E2E tests...${NC}"
pnpm exec playwright test \
    --reporter=json \
    --reporter=list \
    --output="$REPORTS_DIR/results-$TIMESTAMP.json" \
    2>&1 | tee "$REPORTS_DIR/test-output-$TIMESTAMP.log"
TEST_EXIT_CODE=$?

# Cleanup
echo -e "${CYAN}🧹 Cleaning up...${NC}"
kill $SERVER_PID 2>/dev/null || true
kill_port $PORT
echo -e "${GREEN}✓ Server stopped${NC}"

exit $TEST_EXIT_CODE

Make Script Executable

chmod +x scripts/tests/run-e2e.sh

Step 5: AI-Friendly Report Generation

Report Structure

The test runner generates three types of reports:

  1. JSON Report (test-report-{timestamp}.json) - Full machine-readable data
  2. Markdown Report (test-report-{timestamp}.md) - Human-readable summary
  3. AI Summary (summary-{timestamp}.json) - Optimized for AI agents

AI Summary Format

{
  "timestamp": "20260509_135006",
  "test_status": "passed",
  "test_exit_code": 0,
  "tests_total": 333,
  "tests_passed": 333,
  "tests_failed": 0,
  "duration_seconds": 447,
  "health_check": {
    "status": "healthy",
    "app_running": true,
    "app_url": "http://localhost:3050"
  },
  "for_ai_agents": {
    "quick_summary": "passed - 333/333 tests passed in 447s",
    "if_failed_check": "scripts/tests/reports/test-output-20260509_135006.log",
    "html_report": "web/playwright-report/index.html",
    "next_action": "Review HTML report for performance optimization"
  }
}

Report README

Create scripts/tests/reports/README.md:

# E2E Test Reports

This directory contains detailed E2E test reports.

## For AI Agents

Parse `latest-summary.json` for automated action:

```bash
cat scripts/tests/reports/latest-summary.json | jq .for_ai_agents.next_action

Running Tests

./scripts/tests/run-e2e.sh

This will:

  • Install dependencies if needed
  • Kill existing server on port 3050
  • Start fresh dev server
  • Run all E2E tests
  • Generate reports
  • Cleanup server

---

## Step 6: Common Platform UI Integration

### Using Shared Components

Import UI components from common platform:

```typescript
import { AlertBanner, Button, Input, Select } from '../components/ui/Primitives';

Design Token Usage

Use CSS variables from design tokens:

.my-component {
  background: var(--bl-surface-card);
  color: var(--bl-text-primary);
  border-color: var(--bl-border);
  padding: var(--bl-spacing-md);
}

Product Adapter Pattern

Create a product adapter to normalize imports:

// src/components/ui/Primitives.tsx
export { Button } from '@bytelyst/ui/Button';
export { AlertBanner } from '@bytelyst/ui/AlertBanner';
export { Input } from '@bytelyst/ui/Input';
// ... other components

Step 7: Cipher Design System Integration

Design Principles

Follow Cipher design system principles:

  1. Visual Hierarchy - Clear hierarchy with size, weight, and color
  2. Spacing - Consistent spacing using design tokens
  3. Typography - Use design token font sizes and weights
  4. Color - Use semantic color tokens (success, warning, error)
  5. Accessibility - WCAG AA compliance with proper contrast ratios

Implementation Example

<div style={{
  background: 'var(--bl-surface-card)',
  color: 'var(--bl-text-primary)',
  padding: 'var(--bl-spacing-lg)',
  borderRadius: 'var(--bl-radius-md)',
  border: '1px solid var(--bl-border)'
}}>
  <AlertBanner tone="info" title="Information">
    Content with proper visual hierarchy
  </AlertBanner>
</div>

Step 8: Roadmap Documentation

Update Launch Readiness Checklist

Document progress in docs/inprogress/LAUNCH_READY_UI_UX_ROADMAP.md:

## Launch Readiness Checklist

- [x] No production route has accidental horizontal overflow
- [x] Critical alerts never cover primary content
- [x] Assistant widget never covers primary actions
- [x] All destructive actions require confirmation
- [x] All saves/deletes/updates produce feedback
- [x] All pages have loading/empty/error/success states
- [x] All forms have labels, hints, validation, and disabled-state explanations
- [x] All primary workflows pass keyboard navigation
- [x] All routes pass the viewport matrix
- [x] Playwright screenshots and accessibility checks run in CI

Step 9: Verification Commands

Run All Tests

./scripts/tests/run-e2e.sh

Run Specific Test Suites

cd web
pnpm test:e2e:viewport
pnpm test:e2e:overflow

Check AI Summary

cat scripts/tests/reports/latest-summary.json | jq .for_ai_agents

Step 10: CI Integration (Optional)

GitHub Actions Example

- name: Run E2E Tests
  run: ./scripts/tests/run-e2e.sh

- name: Upload Reports
  uses: actions/upload-artifact@v3
  with:
    name: e2e-test-reports
    path: scripts/tests/reports/

Troubleshooting

Server Won't Start

Issue: Port 3050 already in use

Solution:

lsof -ti:3050 | xargs kill -9

Missing Dependencies

Issue: Playwright or dependencies not installed

Solution:

pnpm install
pnpm exec playwright install chromium

Tests Fail on VM

Issue: Missing dependencies on VM

Solution:

cd web
pnpm install

Best Practices

  1. Test Organization - Group related tests in describe blocks
  2. Selectors - Use stable selectors (data-test attributes when needed)
  3. Wait Strategies - Use Playwright's auto-waiting features
  4. Screenshots - Capture screenshots on failure for debugging
  5. Reports - Review HTML reports for detailed timing and coverage
  6. Accessibility - Include accessibility checks in tests
  7. Maintenance - Update tests when UI changes

References

  • Playwright Documentation: https://playwright.dev
  • Common Platform UI: @bytelyst/ui package
  • Design Tokens: @bytelyst/design-tokens package
  • Cipher Design System: Internal design system documentation
  • Launch Readiness Roadmap: docs/inprogress/LAUNCH_READY_UI_UX_ROADMAP.md

Summary

This guide provides a complete blueprint for setting up UX testing infrastructure across ByteLyst products. By following these steps, products can:

  1. Establish comprehensive E2E testing with Playwright
  2. Leverage common platform UI packages and design tokens
  3. Follow Cipher design system principles
  4. Generate AI-friendly reports for automated analysis
  5. Ensure consistent UX quality across all products

The approach prioritizes:

  • Automation - Server lifecycle management in test runner
  • Visibility - Rich, colorful console output
  • Actionability - AI-friendly reports with next actions
  • Reusability - Common platform integration
  • Maintainability - Clear test organization and documentation