diff --git a/.github/workflows/ui-pull-request.yml b/.github/workflows/ui-pull-request.yml index f217f31c3b..847c423780 100644 --- a/.github/workflows/ui-pull-request.yml +++ b/.github/workflows/ui-pull-request.yml @@ -34,15 +34,64 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: './ui/package-lock.json' - name: Install dependencies working-directory: ./ui - run: npm install + run: npm ci - name: Run Healthcheck working-directory: ./ui run: npm run healthcheck - name: Build the application working-directory: ./ui run: npm run build + + e2e-tests: + runs-on: ubuntu-latest + env: + AUTH_SECRET: 'fallback-ci-secret-for-testing' + AUTH_TRUST_HOST: true + NEXTAUTH_URL: http://localhost:3000 + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '20.x' + cache: 'npm' + cache-dependency-path: './ui/package-lock.json' + - name: Install dependencies + working-directory: ./ui + run: npm ci + - name: Cache Playwright browsers + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('ui/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-playwright- + - name: Install Playwright browsers + working-directory: ./ui + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npm run test:e2e:install + - name: Build the application + working-directory: ./ui + run: npm run build + - name: Run Playwright tests + working-directory: ./ui + run: npm run test:e2e + - name: Upload Playwright report + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: failure() + with: + name: playwright-report + path: ui/playwright-report/ + retention-days: 30 + test-container-build: runs-on: ubuntu-latest steps: diff --git a/ui/package-lock.json b/ui/package-lock.json index 720803b5e4..b6885ee7fe 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -62,6 +62,7 @@ }, "devDependencies": { "@iconify/react": "^5.2.0", + "@playwright/test": "^1.53.1", "@types/bcryptjs": "^2.4.6", "@types/node": "20.5.7", "@types/react": "18.3.3", @@ -4716,6 +4717,22 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/test": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz", + "integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.53.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", @@ -11373,6 +11390,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -14626,6 +14657,38 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz", + "integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.53.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz", + "integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", diff --git a/ui/package.json b/ui/package.json index 00793efbf0..d983f05127 100644 --- a/ui/package.json +++ b/ui/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@iconify/react": "^5.2.0", + "@playwright/test": "^1.53.1", "@types/bcryptjs": "^2.4.6", "@types/node": "20.5.7", "@types/react": "18.3.3", @@ -90,13 +91,20 @@ "dev": "next dev", "build": "next build", "start": "next start", + "start:standalone": "node .next/standalone/server.js", "typecheck": "tsc", "healthcheck": "npm run typecheck && npm run lint:check", "lint:check": "./node_modules/.bin/eslint ./app", "lint:fix": "eslint . --ext .ts,.tsx -c .eslintrc.cjs --fix", "format:check": "./node_modules/.bin/prettier --check ./app", "format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app", - "prepare": "husky" + "prepare": "husky", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:debug": "playwright test --debug", + "test:e2e:headed": "playwright test --headed", + "test:e2e:report": "playwright show-report", + "test:e2e:install": "playwright install" }, "overrides": { "@react-types/shared": "3.26.0" diff --git a/ui/playwright.config.ts b/ui/playwright.config.ts new file mode 100644 index 0000000000..eab99e3c63 --- /dev/null +++ b/ui/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [["list"]], + outputDir: "/tmp/playwright-tests", + + use: { + baseURL: "http://localhost:3000", + trace: "off", + screenshot: "off", + video: "off", + }, + + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + + webServer: { + command: process.env.CI + ? "npm run build && npm run start:standalone" + : "npm run dev", + url: "http://localhost:3000", + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, + }, +}); diff --git a/ui/tests/example.spec.ts b/ui/tests/example.spec.ts new file mode 100644 index 0000000000..4b8d55810b --- /dev/null +++ b/ui/tests/example.spec.ts @@ -0,0 +1,14 @@ +import { test, expect } from "@playwright/test"; + +test("has title", async ({ page }) => { + await page.goto("https://playwright.dev/"); + await expect(page).toHaveTitle(/Playwright/); +}); + +test("get started link", async ({ page }) => { + await page.goto("https://playwright.dev/"); + await page.getByRole("link", { name: "Get started" }).click(); + await expect( + page.getByRole("heading", { name: "Installation" }), + ).toBeVisible(); +});