diff --git a/.github/workflows/tests-ui-e2e.yml b/.github/workflows/tests-ui-e2e.yml new file mode 100644 index 000000000..3e82c39da --- /dev/null +++ b/.github/workflows/tests-ui-e2e.yml @@ -0,0 +1,72 @@ +--- +name: 'UI E2E Tests' + +on: + pull_request: + paths: + - 'core/http/**' + - 'tests/e2e-ui/**' + - 'tests/e2e/mock-backend/**' + push: + branches: + - master + +concurrency: + group: ci-tests-ui-e2e-${{ github.head_ref || github.ref }}-${{ github.repository }} + cancel-in-progress: true + +jobs: + tests-ui-e2e: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: ['1.26.x'] + steps: + - name: Clone + uses: actions/checkout@v6 + with: + submodules: true + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + cache: false + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + - name: Proto Dependencies + run: | + curl -L -s https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-linux-x86_64.zip -o protoc.zip && \ + unzip -j -d /usr/local/bin protoc.zip bin/protoc && \ + rm protoc.zip + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.2 + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@1958fcbe2ca8bd93af633f11e97d44e567e945af + - name: System Dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential libopus-dev + - name: Build UI test server + run: PATH="$PATH:$HOME/go/bin" make build-ui-test-server + - name: Install Playwright + working-directory: core/http/react-ui + run: | + npm install + npx playwright install --with-deps chromium + - name: Run Playwright tests + working-directory: core/http/react-ui + run: npx playwright test + - name: Upload Playwright report + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: core/http/react-ui/playwright-report/ + retention-days: 7 + - name: Setup tmate session if tests fail + if: ${{ failure() }} + uses: mxschmitt/action-tmate@v3.23 + with: + detached: true + connect-timeout-seconds: 180 + limit-access-to-actor: true diff --git a/.gitignore b/.gitignore index 2e1a924d0..25252eada 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,8 @@ core/http/react-ui/dist # Extracted backend binaries for container-based testing local-backends/ + +# UI E2E test artifacts +tests/e2e-ui/ui-test-server +core/http/react-ui/playwright-report/ +core/http/react-ui/test-results/ diff --git a/Makefile b/Makefile index f997aa55f..c429097b6 100644 --- a/Makefile +++ b/Makefile @@ -646,6 +646,23 @@ build-mock-backend: protogen-go clean-mock-backend: rm -f tests/e2e/mock-backend/mock-backend +######################################################## +### UI E2E Test Server +######################################################## + +build-ui-test-server: build-mock-backend react-ui protogen-go + $(GOCMD) build -o tests/e2e-ui/ui-test-server ./tests/e2e-ui + +test-ui-e2e: build-ui-test-server + cd core/http/react-ui && npm install && npx playwright install --with-deps chromium && npx playwright test + +test-ui-e2e-docker: + docker build -t localai-ui-e2e -f tests/e2e-ui/Dockerfile . + docker run --rm localai-ui-e2e + +clean-ui-test-server: + rm -f tests/e2e-ui/ui-test-server + ######################################################## ### END Backends ######################################################## diff --git a/core/http/react-ui/e2e/navigation.spec.js b/core/http/react-ui/e2e/navigation.spec.js new file mode 100644 index 000000000..99b5bb919 --- /dev/null +++ b/core/http/react-ui/e2e/navigation.spec.js @@ -0,0 +1,23 @@ +import { test, expect } from '@playwright/test' + +test.describe('Navigation', () => { + test('/ redirects to /app', async ({ page }) => { + await page.goto('/') + await expect(page).toHaveURL(/\/app/) + }) + + test('/app shows home page with LocalAI title', async ({ page }) => { + await page.goto('/app') + await expect(page.locator('.sidebar')).toBeVisible() + await expect(page.getByRole('heading', { name: 'How can I help you today?' })).toBeVisible() + }) + + test('sidebar traces link navigates to /app/traces', async ({ page }) => { + await page.goto('/app') + const tracesLink = page.locator('a.nav-item[href="/app/traces"]') + await expect(tracesLink).toBeVisible() + await tracesLink.click() + await expect(page).toHaveURL(/\/app\/traces/) + await expect(page.getByRole('heading', { name: 'Traces', exact: true })).toBeVisible() + }) +}) diff --git a/core/http/react-ui/e2e/traces.spec.js b/core/http/react-ui/e2e/traces.spec.js new file mode 100644 index 000000000..788be95ba --- /dev/null +++ b/core/http/react-ui/e2e/traces.spec.js @@ -0,0 +1,96 @@ +import { test, expect } from '@playwright/test' + +test.describe('Traces Settings', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/app/traces') + // Wait for settings panel to load + await expect(page.locator('text=Tracing is')).toBeVisible({ timeout: 10_000 }) + }) + + test('settings panel is visible on page load', async ({ page }) => { + await expect(page.locator('text=Tracing is')).toBeVisible() + }) + + test('expand and collapse settings', async ({ page }) => { + // The test server starts with tracing enabled, so the panel starts collapsed + const settingsHeader = page.locator('button', { hasText: 'Tracing is' }) + + // Click to expand + await settingsHeader.click() + await expect(page.locator('text=Enable Tracing')).toBeVisible() + + // Click to collapse + await settingsHeader.click() + await expect(page.locator('text=Enable Tracing')).not.toBeVisible() + }) + + test('toggle tracing on and off', async ({ page }) => { + // Expand settings + const settingsHeader = page.locator('button', { hasText: 'Tracing is' }) + await settingsHeader.click() + await expect(page.locator('text=Enable Tracing')).toBeVisible() + + // The Toggle component is a