From 39e32ce0b2817f489cc47326eb9e8eb054715c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Fri, 17 Jul 2026 17:53:06 +0100 Subject: [PATCH] Add playwright to e2e tests workflow (#176520) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/e2e-tests.yml | 56 ++++++++++++++++++++------------- .gitignore | 4 +++ .prettierignore | 1 + tests/e2e/onboarding.spec.ts | 10 ++++++ tests/e2e/package.json | 13 ++++++++ tests/e2e/playwright.config.ts | 21 +++++++++++++ tests/e2e/pnpm-lock.yaml | 52 ++++++++++++++++++++++++++++++ 7 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 tests/e2e/onboarding.spec.ts create mode 100644 tests/e2e/package.json create mode 100644 tests/e2e/playwright.config.ts create mode 100644 tests/e2e/pnpm-lock.yaml diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index b3784dca600a..97fd2dfc6fdf 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -31,7 +31,6 @@ jobs: runs-on: ubuntu-24.04-arm env: BASE_URL: http://localhost:8123 - CURL_OPTS: --silent --max-time 10 services: homeassistant: image: ghcr.io/home-assistant/home-assistant${{ startsWith(inputs.version, 'sha256:') && '@' || ':' }}${{ inputs.version }} # zizmor: ignore[unpinned-images] @@ -44,28 +43,43 @@ jobs: --health-interval=5s --health-retries=60 steps: - - name: Check frontend is served - run: | - # Pre-onboarding, / redirects to /onboarding.html; --location follows it - status=$(curl $CURL_OPTS --location --output /dev/null --write-out '%{http_code}' "$BASE_URL/") - if [ "$status" -ne 200 ]; then - echo "::error::Expected HTTP 200 from frontend, got $status" - exit 1 - fi + - name: Check out code from GitHub + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - - name: Check onboarding API responds - run: | - curl $CURL_OPTS --fail "$BASE_URL/api/onboarding" \ - | jq -e 'type == "array" and length > 0' + - name: Set up pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + with: + package_json_file: tests/e2e/package.json - - name: Check container is still running - env: - CONTAINER: ${{ job.services.homeassistant.id }} - run: | - if [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER")" != "true" ]; then - echo "::error::Container is no longer running after checks" - exit 1 - fi + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + cache: pnpm + cache-dependency-path: tests/e2e/pnpm-lock.yaml + + - name: Install E2E test dependencies + working-directory: tests/e2e + run: pnpm install --frozen-lockfile + + - name: Install Playwright browser + working-directory: tests/e2e + run: pnpm exec playwright install --with-deps chromium + + - name: Run Playwright E2E tests + working-directory: tests/e2e + run: pnpm exec playwright test + + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: playwright-report-${{ matrix.arch }} + path: | + tests/e2e/playwright-report/ + tests/e2e/test-results/ - name: Dump container logs if: always() diff --git a/.gitignore b/.gitignore index 9d8cbaf15e09..5fb2ad904d14 100644 --- a/.gitignore +++ b/.gitignore @@ -145,3 +145,7 @@ pytest_buckets.txt .claude/worktrees/ .serena/ +# Playwright e2e tests +tests/e2e/node_modules/ +tests/e2e/playwright-report/ +tests/e2e/test-results/ diff --git a/.prettierignore b/.prettierignore index c63290996661..54c2d65e4d62 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,4 @@ homeassistant/generated/* tests/components/lidarr/fixtures/initialize.js tests/components/lidarr/fixtures/initialize-wrong.js tests/fixtures/core/config/yaml_errors/ +tests/e2e/pnpm-lock.yaml diff --git a/tests/e2e/onboarding.spec.ts b/tests/e2e/onboarding.spec.ts new file mode 100644 index 000000000000..d6431b3d1ae6 --- /dev/null +++ b/tests/e2e/onboarding.spec.ts @@ -0,0 +1,10 @@ +import { expect, test } from "@playwright/test"; + +test("fresh instance redirects to onboarding and renders the UI", async ({ + page, +}) => { + await page.goto("/"); + + await expect(page).toHaveURL(/\/onboarding\.html/); + await expect(page.locator("ha-onboarding")).toBeVisible(); +}); diff --git a/tests/e2e/package.json b/tests/e2e/package.json new file mode 100644 index 000000000000..bb69b43f05bd --- /dev/null +++ b/tests/e2e/package.json @@ -0,0 +1,13 @@ +{ + "name": "home-assistant-e2e-tests", + "version": "1.0.0", + "description": "End-to-end browser tests for Home Assistant Core", + "private": true, + "packageManager": "pnpm@11.13.0", + "scripts": { + "test": "playwright test" + }, + "devDependencies": { + "@playwright/test": "1.61.1" + } +} diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts new file mode 100644 index 000000000000..f130b643c4c0 --- /dev/null +++ b/tests/e2e/playwright.config.ts @@ -0,0 +1,21 @@ +import { defineConfig, devices } from "@playwright/test"; + +const baseURL = process.env.BASE_URL ?? "http://localhost:8123"; + +export default defineConfig({ + testDir: ".", + timeout: 30_000, + // Reruns a failed test once in CI to absorb transient startup flakiness. + retries: process.env.CI ? 1 : 0, + reporter: [["list"], ["html", { open: "never" }]], + use: { + baseURL, + trace: "retain-on-failure", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/tests/e2e/pnpm-lock.yaml b/tests/e2e/pnpm-lock.yaml new file mode 100644 index 000000000000..51cd78654eff --- /dev/null +++ b/tests/e2e/pnpm-lock.yaml @@ -0,0 +1,52 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@playwright/test': + specifier: 1.61.1 + version: 1.61.1 + +packages: + + '@playwright/test@1.61.1': + resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} + engines: {node: '>=18'} + hasBin: true + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + playwright-core@1.61.1: + resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.61.1: + resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} + engines: {node: '>=18'} + hasBin: true + +snapshots: + + '@playwright/test@1.61.1': + dependencies: + playwright: 1.61.1 + + fsevents@2.3.2: + optional: true + + playwright-core@1.61.1: {} + + playwright@1.61.1: + dependencies: + playwright-core: 1.61.1 + optionalDependencies: + fsevents: 2.3.2