Files
michaelschiemer/tests/e2e/livecomponents/INTEGRATION-TESTS.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

19 KiB

LiveComponents E2E Integration Tests

Comprehensive end-to-end integration tests for LiveComponents cross-cutting features: Partial Rendering, Batch Operations, and Server-Sent Events (SSE).

Overview

This test suite validates the seamless integration of multiple LiveComponents features working together in real-world scenarios. It ensures that partial rendering, batch operations, and real-time updates work harmoniously without conflicts or state inconsistencies.

Test Categories

1. Partial Rendering Tests (6 tests)

Purpose: Validate fragment-based updates without full component re-rendering

Tests:

  1. Update only targeted fragment without full component re-render

    • Triggers fragment-specific action
    • Verifies only fragment HTML updated
    • Ensures component timestamp unchanged (no full render)
    • Validates DOM patch efficiency
  2. Update multiple fragments in single request

    • Updates 2+ fragments simultaneously
    • Verifies all fragments updated correctly
    • Ensures only 1 HTTP request sent
    • Validates network efficiency
  3. Preserve component state during partial render

    • Sets component state before fragment update
    • Triggers fragment update
    • Verifies state preserved after update
    • Validates state consistency
  4. Handle nested fragment updates

    • Updates parent fragment containing child fragment
    • Verifies parent and child both updated
    • Ensures sibling fragments NOT updated
    • Validates selective rendering
  5. Apply morphing algorithm for minimal DOM changes

    • Counts DOM nodes before update
    • Applies small content change
    • Verifies node count unchanged (morphing, not replacement)
    • Validates morphing stats (added/removed/updated)
  6. Handle fragment-not-found gracefully

    • Calls action with non-existent fragment ID
    • Verifies error message displayed
    • Ensures component remains functional
    • Validates error recovery

2. Batch Operations Tests (6 tests)

Purpose: Validate efficient execution of multiple actions in single request

Tests:

  1. Execute multiple actions in single batch request

    • Batches 3 different actions
    • Verifies only 1 HTTP request sent
    • Ensures all actions executed successfully
    • Validates batch efficiency
  2. Maintain action execution order in batch

    • Batches 3 actions in specific order
    • Logs execution sequence
    • Verifies actions executed in correct order
    • Validates deterministic execution
  3. Rollback batch on action failure

    • Batches successful + failing + successful actions
    • Verifies entire batch rolled back on failure
    • Ensures no partial state changes
    • Validates transactional behavior
  4. Handle partial batch execution with continueOnError flag

    • Batches actions with continueOnError: true
    • Includes failing action in middle
    • Verifies successful actions still execute
    • Validates partial execution mode
  5. Support batch with mixed action types

    • Batches sync + async + fragment update actions
    • Verifies all action types execute correctly
    • Ensures type compatibility
    • Validates heterogeneous batching
  6. Batch state updates efficiently

    • Batches 10 state update actions
    • Verifies final state correct
    • Ensures only 1 state update event fired (optimization)
    • Validates state batching

3. Server-Sent Events (SSE) Tests (8 tests)

Purpose: Validate real-time server-to-client communication

Tests:

  1. Establish SSE connection for real-time updates

    • Enables SSE
    • Verifies connection state OPEN (readyState === 1)
    • Ensures connection indicator visible
    • Validates connection establishment
  2. Receive and apply server-pushed updates

    • Establishes SSE connection
    • Simulates server push event
    • Verifies component updated automatically
    • Validates push handling
  3. Handle SSE reconnection on connection loss

    • Establishes connection
    • Simulates connection close
    • Verifies reconnecting indicator shown
    • Ensures automatic reconnection after retry period
  4. Support multiple SSE event types

    • Sends different event types (update, notification, sync)
    • Logs received events
    • Verifies all types processed correctly
    • Validates event type handling
  5. Batch SSE updates for performance

    • Sends 20 rapid SSE updates
    • Verifies final value correct
    • Ensures renders < updates (batching optimization)
    • Validates performance optimization
  6. Close SSE connection when component unmounts

    • Establishes connection
    • Unmounts component
    • Verifies connection closed (readyState === 2)
    • Validates cleanup
  7. Handle SSE authentication and authorization

    • Tests SSE with valid auth token
    • Verifies authenticated connection
    • Tests SSE with invalid token
    • Ensures auth error displayed
  8. Support SSE with custom event filters

    • Enables SSE with priority filter
    • Sends events with different priorities
    • Verifies only filtered events processed
    • Validates client-side filtering

4. Integration Tests (4 tests)

Purpose: Validate combined usage of multiple features

Tests:

  1. Combine partial rendering with batch operations

    • Batches multiple fragment updates + state changes
    • Verifies only 1 request sent
    • Ensures all fragments and state updated
    • Validates feature synergy
  2. Push partial updates via SSE

    • Sends SSE event with fragment update
    • Verifies fragment updated via SSE
    • Ensures no full component render
    • Validates SSE + partial rendering
  3. Batch SSE-triggered actions efficiently

    • Sends 5 rapid SSE events triggering actions
    • Verifies actions batched (< 5 executions)
    • Ensures final state correct
    • Validates SSE + batching
  4. Maintain consistency across all integration features

    • Complex scenario: batch + partial + SSE simultaneously
    • Starts batch with fragment updates
    • Sends SSE update during batch execution
    • Verifies all updates applied correctly
    • Validates state consistency

Quick Start

Prerequisites

# Ensure Playwright is installed
npm install

# Install browsers
npx playwright install chromium

# Ensure development server is running
make up

Running Integration Tests

# Run all integration tests
npm run test:integration

# Run with visible browser (debugging)
npm run test:integration:headed

# Run in debug mode (step through tests)
npm run test:integration:debug

# Run specific test category
npx playwright test integration.spec.js --grep "Partial Rendering"
npx playwright test integration.spec.js --grep "Batch Operations"
npx playwright test integration.spec.js --grep "Server-Sent Events"
npx playwright test integration.spec.js --grep "Integration:"

Test Page Requirements

HTML Structure

The test page /livecomponents/test/integration must include:

<!DOCTYPE html>
<html>
<head>
    <title>LiveComponents Integration Tests</title>
    <script src="/assets/livecomponents.js"></script>
</head>
<body>
    <!-- Component Container -->
    <div data-component="integration:test" data-component-id="integration:test">
        <!-- Component Timestamp (für Full Render Detection) -->
        <div id="component-timestamp" data-timestamp="initial">initial</div>

        <!-- Fragment Rendering Tests -->
        <div id="target-fragment">Original Content</div>
        <div id="fragment-1">Fragment 1</div>
        <div id="fragment-2">Fragment 2</div>

        <!-- Nested Fragments -->
        <div id="parent-fragment">
            <div id="child-fragment">Child</div>
        </div>
        <div id="sibling-fragment" data-timestamp="original">Sibling</div>

        <!-- Morphing Test -->
        <div id="morph-fragment">
            <p>Paragraph 1</p>
            <p>Paragraph 2</p>
        </div>

        <!-- Batch Operation Tests -->
        <div id="counter-value">0</div>
        <div id="text-value">Original</div>
        <div id="flag-value">false</div>

        <!-- SSE Tests -->
        <div id="live-value">Initial</div>
        <div id="sse-counter">0</div>
        <div class="sse-connected" style="display:none">Connected</div>
        <div class="sse-reconnecting" style="display:none">Reconnecting...</div>
        <div class="sse-authenticated" style="display:none">Authenticated</div>
        <div class="sse-auth-error" style="display:none">Auth Error</div>

        <!-- State Management -->
        <input type="text" id="state-input" />

        <!-- Action Buttons -->
        <button id="update-fragment">Update Fragment</button>
        <button id="update-multiple-fragments">Update Multiple</button>
        <button id="save-state">Save State</button>
        <button id="update-nested-fragment">Update Nested</button>
        <button id="small-update">Small Update</button>
        <button id="enable-sse">Enable SSE</button>
        <button id="trigger-action">Trigger Action</button>

        <!-- Error Display -->
        <div class="fragment-error" style="display:none"></div>
        <div class="batch-error" style="display:none"></div>
        <div class="partial-success" style="display:none"></div>

        <!-- Results -->
        <div id="sync-result" style="display:none"></div>
        <div id="async-result" style="display:none"></div>
        <div id="fragment-result" style="display:none"></div>
        <div class="action-success" style="display:none"></div>
    </div>

    <script>
        // Global tracking variables
        window.__fragmentUpdateCount = 0;
        window.__requestCount = 0;
        window.__renderCount = 0;
        window.__stateUpdateCount = 0;
        window.__actionCount = 0;
        window.__originalSiblingTimestamp = document.getElementById('sibling-fragment').getAttribute('data-timestamp');
        window.__originalComponentTimestamp = document.getElementById('component-timestamp').textContent;

        // Morphing stats tracking
        window.__morphingStats = {
            nodesAdded: 0,
            nodesRemoved: 0,
            nodesUpdated: 0
        };

        // Initialize LiveComponents
        document.addEventListener('DOMContentLoaded', () => {
            window.LiveComponents.init();
        });
    </script>
</body>
</html>

Component Actions

The IntegrationTestComponent must implement:

use App\Framework\LiveComponents\LiveComponent;
use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\Attributes\Fragment;

final readonly class IntegrationTestComponent extends LiveComponent
{
    #[Action]
    #[Fragment('target-fragment')]
    public function updateFragment(array $params = []): array
    {
        $fragmentId = $params['fragmentId'] ?? 'target-fragment';

        if (!$this->hasFragment($fragmentId)) {
            return ['error' => 'Fragment not found'];
        }

        return [
            'fragments' => [
                $fragmentId => '<div id="' . $fragmentId . '">Updated</div>'
            ]
        ];
    }

    #[Action]
    public function updateMultipleFragments(): array
    {
        return [
            'fragments' => [
                'fragment-1' => '<div id="fragment-1">Fragment 1 Updated</div>',
                'fragment-2' => '<div id="fragment-2">Fragment 2 Updated</div>'
            ]
        ];
    }

    #[Action]
    public function saveState(string $value): void
    {
        $this->state->set('savedValue', $value);
    }

    #[Action]
    #[Fragment('parent-fragment')]
    public function updateNestedFragment(): array
    {
        return [
            'fragments' => [
                'parent-fragment' => '
                    <div id="parent-fragment">Parent Updated
                        <div id="child-fragment">Child Updated</div>
                    </div>
                '
            ]
        ];
    }

    #[Action]
    #[Fragment('morph-fragment')]
    public function smallUpdate(): array
    {
        return [
            'fragments' => [
                'morph-fragment' => '
                    <div id="morph-fragment">
                        <p>Paragraph 1 Updated</p>
                        <p>Paragraph 2</p>
                    </div>
                '
            ]
        ];
    }

    #[Action]
    public function incrementCounter(): void
    {
        $current = $this->state->get('counter', 0);
        $this->state->set('counter', $current + 1);
    }

    #[Action]
    public function updateText(string $text): void
    {
        $this->state->set('text', $text);
    }

    #[Action]
    public function toggleFlag(): void
    {
        $current = $this->state->get('flag', false);
        $this->state->set('flag', !$current);
    }

    #[Action]
    public function action1(): void
    {
        // Log execution
        $this->logExecution('action1');
    }

    #[Action]
    public function action2(): void
    {
        $this->logExecution('action2');
    }

    #[Action]
    public function action3(): void
    {
        $this->logExecution('action3');
    }

    #[Action]
    public function failingAction(): void
    {
        throw new \RuntimeException('Intentional failure for testing');
    }

    #[Action]
    public function syncAction(): array
    {
        return ['success' => true];
    }

    #[Action]
    public function asyncAction(): array
    {
        // Simulate async operation
        usleep(100000); // 100ms
        return ['success' => true];
    }

    #[Action]
    #[Fragment('fragment-result')]
    public function fragmentAction(): array
    {
        return [
            'fragments' => [
                'fragment-result' => '<div id="fragment-result">Fragment Action Complete</div>'
            ]
        ];
    }

    public function enableSSE(array $options = []): void
    {
        // SSE setup logic
        $this->sseEnabled = true;
    }

    public function validateStateConsistency(): bool
    {
        // State validation logic
        return true;
    }
}

SSE Endpoint

The SSE endpoint /live-component/sse/{componentId} must support:

use App\Framework\LiveComponents\SSE\SseStream;

final readonly class LiveComponentSseController
{
    #[Route('/live-component/sse/{componentId}', method: Method::GET)]
    public function stream(string $componentId, Request $request): Response
    {
        $authToken = $request->headers->get('Authorization');

        if (!$this->validateAuthToken($authToken)) {
            return new Response(status: Status::UNAUTHORIZED);
        }

        $stream = new SseStream();

        // Set headers
        $response = new Response(
            status: Status::OK,
            headers: [
                'Content-Type' => 'text/event-stream',
                'Cache-Control' => 'no-cache',
                'Connection' => 'keep-alive'
            ]
        );

        // Event loop
        while ($connection->isAlive()) {
            $event = $this->eventQueue->poll($componentId);

            if ($event) {
                $stream->send($event->type, $event->data);
            }

            usleep(100000); // 100ms polling interval
        }

        return $response;
    }
}

Performance Expectations

Partial Rendering

  • Fragment Update Latency: <50ms for small fragments (<5KB)
  • DOM Morphing Efficiency: 0 node additions/removals for content-only changes
  • Memory Impact: <1MB per fragment update

Batch Operations

  • Network Savings: 1 request for N actions (vs. N requests)
  • Execution Time: Linear O(N) for N batched actions
  • Rollback Overhead: <10ms for transaction rollback

Server-Sent Events

  • Connection Establishment: <500ms
  • Event Latency: <100ms from server send to client receive
  • Reconnection Time: <2s after connection loss
  • Batching Window: 50-100ms for update batching

Troubleshooting

Fragment Updates Not Working

Symptoms:

  • Fragment content doesn't update
  • Full component re-renders instead

Solutions:

  1. Verify Fragment Attribute:
#[Fragment('target-fragment')]
public function updateFragment(): array
  1. Check Fragment ID in Response:
return [
    'fragments' => [
        'target-fragment' => '<div id="target-fragment">Updated</div>'
    ]
];
  1. Ensure Fragment Exists in DOM:
<div id="target-fragment">Original</div>

Batch Operations Failing

Symptoms:

  • Individual actions execute separately
  • Multiple HTTP requests sent

Solutions:

  1. Use Batch API Correctly:
component.batch()
    .call('action1')
    .call('action2')
    .execute();  // Don't forget .execute()
  1. Check Batch Support:
if (component.supportsBatch) {
    // Batching available
}

SSE Connection Issues

Symptoms:

  • SSE connection not established
  • Events not received

Solutions:

  1. Check SSE Endpoint:
curl -N https://localhost/live-component/sse/integration:test \
  -H "Authorization: Bearer token"
  1. Verify Headers:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
  1. Test Auth Token:
component.enableSSE({ authToken: 'valid-token-123' });
  1. Check Browser Console:
component.sse.addEventListener('error', (e) => {
    console.error('SSE Error:', e);
});

CI/CD Integration

GitHub Actions

name: Integration Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  integration-tests:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright
        run: npx playwright install --with-deps chromium

      - name: Start dev server
        run: |
          make up
          sleep 10

      - name: Run Integration Tests
        run: npm run test:integration

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: integration-test-results
          path: test-results/

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: playwright-report
          path: playwright-report/

Best Practices

1. Test Organization

  • Group related tests with test.describe()
  • Use descriptive test names
  • Follow AAA pattern (Arrange, Act, Assert)

2. State Management

  • Reset component state between tests
  • Use beforeEach for consistent setup
  • Avoid test interdependencies

3. Async Handling

  • Use page.waitForTimeout() conservatively
  • Prefer page.waitForSelector() or page.waitForFunction()
  • Set appropriate timeouts for network operations

4. Error Handling

  • Test both success and failure paths
  • Verify error messages and recovery
  • Check graceful degradation

5. Performance Testing

  • Track request counts
  • Measure render times
  • Monitor memory usage
  • Validate batching efficiency

Resources

Support

For issues or questions:

  1. Check troubleshooting section above
  2. Review LiveComponents documentation
  3. Inspect browser console and network tab
  4. Run tests in headed mode for visual debugging
  5. Create GitHub issue with test output and environment details