#!/usr/bin/env bash # ============================================================================= # CI-WEB.SH - Angular Web Testing Utilities # ============================================================================= # Functions for running Angular/Web frontend tests locally. # # Test Types: # - Unit Tests (Karma/Jasmine) # - E2E Tests (Playwright) # - Accessibility Tests (Axe-core) # - Lighthouse Audits # - Storybook Build # # ============================================================================= # Prevent direct execution if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then echo "This script should be sourced, not executed directly." exit 1 fi # ============================================================================= # CONSTANTS # ============================================================================= WEB_DIR="${REPO_ROOT:-$(git rev-parse --show-toplevel)}/src/Web/StellaOps.Web" WEB_NODE_VERSION="20" # Test categories for Web WEB_TEST_CATEGORIES=( "web:unit" # Karma unit tests "web:e2e" # Playwright E2E "web:a11y" # Accessibility "web:lighthouse" # Performance/a11y audit "web:build" # Production build "web:storybook" # Storybook build ) # ============================================================================= # DEPENDENCY CHECKS # ============================================================================= check_node_version() { if ! command -v node &>/dev/null; then log_error "Node.js not found" log_info "Install Node.js $WEB_NODE_VERSION+: https://nodejs.org" return 1 fi local version version=$(node --version | sed 's/v//' | cut -d. -f1) if [[ "$version" -lt "$WEB_NODE_VERSION" ]]; then log_warn "Node.js version $version is below recommended $WEB_NODE_VERSION" else log_debug "Node.js version: $(node --version)" fi return 0 } check_npm() { if ! command -v npm &>/dev/null; then log_error "npm not found" return 1 fi log_debug "npm version: $(npm --version)" return 0 } check_web_dependencies() { log_subsection "Checking Web Dependencies" check_node_version || return 1 check_npm || return 1 # Check if node_modules exists if [[ ! -d "$WEB_DIR/node_modules" ]]; then log_warn "node_modules not found - will install dependencies" fi return 0 } # ============================================================================= # SETUP # ============================================================================= install_web_dependencies() { log_subsection "Installing Web Dependencies" if [[ ! -d "$WEB_DIR" ]]; then log_error "Web directory not found: $WEB_DIR" return 1 fi pushd "$WEB_DIR" > /dev/null || return 1 # Check if package-lock.json exists if [[ -f "package-lock.json" ]]; then log_info "Running npm ci (clean install)..." npm ci --prefer-offline --no-audit --no-fund || { log_error "npm ci failed" popd > /dev/null return 1 } else log_info "Running npm install..." npm install --no-audit --no-fund || { log_error "npm install failed" popd > /dev/null return 1 } fi popd > /dev/null log_success "Web dependencies installed" return 0 } ensure_web_dependencies() { if [[ ! -d "$WEB_DIR/node_modules" ]]; then install_web_dependencies || return 1 fi return 0 } # ============================================================================= # TEST RUNNERS # ============================================================================= run_web_unit_tests() { log_subsection "Running Web Unit Tests (Karma/Jasmine)" if [[ ! -d "$WEB_DIR" ]]; then log_error "Web directory not found: $WEB_DIR" return 1 fi ensure_web_dependencies || return 1 pushd "$WEB_DIR" > /dev/null || return 1 local start_time start_time=$(start_timer) if [[ "$DRY_RUN" == "true" ]]; then log_info "[DRY-RUN] Would run: npm run test:ci" popd > /dev/null return 0 fi # Run tests npm run test:ci local result=$? stop_timer "$start_time" "Web unit tests" popd > /dev/null if [[ $result -eq 0 ]]; then log_success "Web unit tests passed" else log_error "Web unit tests failed" fi return $result } run_web_e2e_tests() { log_subsection "Running Web E2E Tests (Playwright)" if [[ ! -d "$WEB_DIR" ]]; then log_error "Web directory not found: $WEB_DIR" return 1 fi ensure_web_dependencies || return 1 pushd "$WEB_DIR" > /dev/null || return 1 local start_time start_time=$(start_timer) # Install Playwright browsers if needed if [[ ! -d "$HOME/.cache/ms-playwright" ]] && [[ ! -d "node_modules/.cache/ms-playwright" ]]; then log_info "Installing Playwright browsers..." npx playwright install --with-deps chromium || { log_warn "Playwright browser installation failed - E2E tests may fail" } fi if [[ "$DRY_RUN" == "true" ]]; then log_info "[DRY-RUN] Would run: npm run test:e2e" popd > /dev/null return 0 fi # Run E2E tests npm run test:e2e local result=$? stop_timer "$start_time" "Web E2E tests" popd > /dev/null if [[ $result -eq 0 ]]; then log_success "Web E2E tests passed" else log_error "Web E2E tests failed" fi return $result } run_web_a11y_tests() { log_subsection "Running Web Accessibility Tests (Axe)" if [[ ! -d "$WEB_DIR" ]]; then log_error "Web directory not found: $WEB_DIR" return 1 fi ensure_web_dependencies || return 1 pushd "$WEB_DIR" > /dev/null || return 1 local start_time start_time=$(start_timer) if [[ "$DRY_RUN" == "true" ]]; then log_info "[DRY-RUN] Would run: npm run test:a11y" popd > /dev/null return 0 fi # Run accessibility tests npm run test:a11y local result=$? stop_timer "$start_time" "Web accessibility tests" popd > /dev/null if [[ $result -eq 0 ]]; then log_success "Web accessibility tests passed" else log_warn "Web accessibility tests had issues (non-blocking)" fi # A11y tests are non-blocking by default return 0 } run_web_build() { log_subsection "Building Web Application" if [[ ! -d "$WEB_DIR" ]]; then log_error "Web directory not found: $WEB_DIR" return 1 fi ensure_web_dependencies || return 1 pushd "$WEB_DIR" > /dev/null || return 1 local start_time start_time=$(start_timer) if [[ "$DRY_RUN" == "true" ]]; then log_info "[DRY-RUN] Would run: npm run build -- --configuration production" popd > /dev/null return 0 fi # Build production bundle npm run build -- --configuration production --progress=false local result=$? stop_timer "$start_time" "Web build" popd > /dev/null if [[ $result -eq 0 ]]; then log_success "Web build completed" # Check bundle size if [[ -d "$WEB_DIR/dist" ]]; then local size size=$(du -sh "$WEB_DIR/dist" 2>/dev/null | cut -f1) log_info "Bundle size: $size" fi else log_error "Web build failed" fi return $result } run_web_storybook_build() { log_subsection "Building Storybook" if [[ ! -d "$WEB_DIR" ]]; then log_error "Web directory not found: $WEB_DIR" return 1 fi ensure_web_dependencies || return 1 pushd "$WEB_DIR" > /dev/null || return 1 local start_time start_time=$(start_timer) if [[ "$DRY_RUN" == "true" ]]; then log_info "[DRY-RUN] Would run: npm run storybook:build" popd > /dev/null return 0 fi # Build Storybook npm run storybook:build local result=$? stop_timer "$start_time" "Storybook build" popd > /dev/null if [[ $result -eq 0 ]]; then log_success "Storybook build completed" else log_error "Storybook build failed" fi return $result } run_web_lighthouse() { log_subsection "Running Lighthouse Audit" if [[ ! -d "$WEB_DIR" ]]; then log_error "Web directory not found: $WEB_DIR" return 1 fi # Check if lighthouse is available if ! command -v lhci &>/dev/null && ! npx lhci --version &>/dev/null 2>&1; then log_warn "Lighthouse CI not installed - skipping audit" log_info "Install with: npm install -g @lhci/cli" return 0 fi ensure_web_dependencies || return 1 # Build first if not already built if [[ ! -d "$WEB_DIR/dist" ]]; then run_web_build || return 1 fi pushd "$WEB_DIR" > /dev/null || return 1 local start_time start_time=$(start_timer) if [[ "$DRY_RUN" == "true" ]]; then log_info "[DRY-RUN] Would run: lhci autorun" popd > /dev/null return 0 fi # Run Lighthouse npx lhci autorun \ --collect.staticDistDir=./dist/stellaops-web/browser \ --collect.numberOfRuns=1 \ --upload.target=filesystem \ --upload.outputDir=./lighthouse-results 2>/dev/null || { log_warn "Lighthouse audit had issues" } stop_timer "$start_time" "Lighthouse audit" popd > /dev/null log_success "Lighthouse audit completed" return 0 } # ============================================================================= # COMPOSITE RUNNERS # ============================================================================= run_web_smoke() { log_section "Web Smoke Tests" log_info "Running quick web validation" local failed=0 run_web_build || failed=1 if [[ $failed -eq 0 ]]; then run_web_unit_tests || failed=1 fi return $failed } run_web_pr_gating() { log_section "Web PR-Gating Tests" log_info "Running full web PR-gating suite" local failed=0 local results=() # Build run_web_build results+=("Build:$?") [[ ${results[-1]##*:} -ne 0 ]] && failed=1 # Unit tests if [[ $failed -eq 0 ]]; then run_web_unit_tests results+=("Unit:$?") [[ ${results[-1]##*:} -ne 0 ]] && failed=1 fi # E2E tests if [[ $failed -eq 0 ]]; then run_web_e2e_tests results+=("E2E:$?") [[ ${results[-1]##*:} -ne 0 ]] && failed=1 fi # A11y tests (non-blocking) run_web_a11y_tests results+=("A11y:$?") # Print summary log_section "Web Test Results" for result in "${results[@]}"; do local name="${result%%:*}" local status="${result##*:}" if [[ "$status" == "0" ]]; then print_status "Web $name" "true" else print_status "Web $name" "false" fi done return $failed } run_web_full() { log_section "Full Web Test Suite" log_info "Running all web tests including extended categories" local failed=0 # PR-gating tests run_web_pr_gating || failed=1 # Extended tests run_web_storybook_build || log_warn "Storybook build failed (non-blocking)" run_web_lighthouse || log_warn "Lighthouse audit failed (non-blocking)" return $failed } # ============================================================================= # EXPORTS # ============================================================================= export -f check_web_dependencies export -f install_web_dependencies export -f ensure_web_dependencies export -f run_web_unit_tests export -f run_web_e2e_tests export -f run_web_a11y_tests export -f run_web_build export -f run_web_storybook_build export -f run_web_lighthouse export -f run_web_smoke export -f run_web_pr_gating export -f run_web_full