diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml new file mode 100644 index 0000000..209adbb --- /dev/null +++ b/.github/workflows/run-e2e-tests.yml @@ -0,0 +1,100 @@ +name: Run E2e Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +env: + PROJECT_ID: 'benefit-decision-toolkit-play' + WORKLOAD_IDENTITY_PROVIDER: 'projects/1034049717668/locations/global/workloadIdentityPools/github-actions-google-cloud/providers/github' + +jobs: + run-e2e-tests: + runs-on: 'ubuntu-latest' + + # Add these permissions for Workload Identity Federation + permissions: + contents: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + # Devbox Setup # + - name: 'Create .env file' # Devbox needs a .env file to exist, even if it's empty + run: touch .env + + - name: Rename env files + run: | + mv builder-frontend/.env.example builder-frontend/.env + mv builder-api/.env.example builder-api/.env + + - name: 'Install devbox' # Setup devbox which includes Node.js, Firebase CLI, and Google Cloud SDK + uses: 'jetify-com/devbox-install-action@v0.12.0' + with: + enable-cache: true + + - id: 'auth' # Configure Workload Identity Federation and generate an access token + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@v2' + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + service_account: cicd-build-deploy-api@benefit-decision-toolkit-play.iam.gserviceaccount.com + project_id: ${{ env.PROJECT_ID }} + + - name: Cache node modules + uses: actions/cache@v4 + with: + path: builder-frontend/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('builder-frontend/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install frontend dependencies + working-directory: builder-frontend + run: devbox run install-builder-frontend-ci + + - name: Run all Devbox services + run: devbox services up -b + continue-on-error: true + + # E2E Testing # + - name: Install Playwright dependencies + run: npm ci + working-directory: e2e + + - name: Install Playwright Browsers + run: npx playwright install --with-deps + working-directory: e2e + + - name: Wait for App to be available + uses: nev7n/wait_for_response@v1 + with: + url: 'http://localhost:5173/' + responseCode: 200 + timeout: 90000 + interval: 1000 + continue-on-error: true + + - name: Run Playwright tests + run: npx playwright test + working-directory: e2e + continue-on-error: true + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: e2e/playwright-report/ + retention-days: 30 + + # Devbox Cleanup # + - name: Stop all Devbox services + run: devbox services stop + continue-on-error: true diff --git a/devbox.json b/devbox.json index 276b7bf..3719688 100644 --- a/devbox.json +++ b/devbox.json @@ -7,7 +7,8 @@ "firebase-tools@latest", "google-cloud-sdk@latest", "nodejs@22", - "bruno-cli@latest" + "bruno-cli@latest", + "process-compose@latest" ], "env_from": ".env", "shell": { diff --git a/devbox.lock b/devbox.lock index f731282..07cb54a 100644 --- a/devbox.lock +++ b/devbox.lock @@ -314,6 +314,54 @@ } } }, + "process-compose@latest": { + "last_modified": "2025-11-23T21:50:36Z", + "resolved": "github:NixOS/nixpkgs/ee09932cedcef15aaf476f9343d1dea2cb77e261#process-compose", + "source": "devbox-search", + "version": "1.78.0", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/d00iad83k88x0fq045h5pzhfj9ibdd3a-process-compose-1.78.0", + "default": true + } + ], + "store_path": "/nix/store/d00iad83k88x0fq045h5pzhfj9ibdd3a-process-compose-1.78.0" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/zx59c11mchqfjpl0yvv2fad87pf3p8mn-process-compose-1.78.0", + "default": true + } + ], + "store_path": "/nix/store/zx59c11mchqfjpl0yvv2fad87pf3p8mn-process-compose-1.78.0" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/hwipxpsgzpv8d51xvqdjyx3vbzaaf3rr-process-compose-1.78.0", + "default": true + } + ], + "store_path": "/nix/store/hwipxpsgzpv8d51xvqdjyx3vbzaaf3rr-process-compose-1.78.0" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/vw9d8v9r0kp44lmizysj7idmqyf9747l-process-compose-1.78.0", + "default": true + } + ], + "store_path": "/nix/store/vw9d8v9r0kp44lmizysj7idmqyf9747l-process-compose-1.78.0" + } + } + }, "quarkus@latest": { "last_modified": "2025-08-11T16:06:55Z", "resolved": "github:NixOS/nixpkgs/4e942f9ef5b35526597c354d1ded817d1c285ef1#quarkus", diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 0000000..335bd46 --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,8 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ diff --git a/e2e/package-lock.json b/e2e/package-lock.json new file mode 100644 index 0000000..2d07d32 --- /dev/null +++ b/e2e/package-lock.json @@ -0,0 +1,97 @@ +{ + "name": "e2e", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "e2e", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.57.0", + "@types/node": "^24.10.1" + } + }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000..9922da5 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,14 @@ +{ + "name": "e2e", + "version": "1.0.0", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@playwright/test": "^1.57.0", + "@types/node": "^24.10.1" + } +} diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts new file mode 100644 index 0000000..3b86f94 --- /dev/null +++ b/e2e/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { browserName: 'chromium' }, + }, + + // { + // name: 'firefox', + // use: { browserName: 'firefox' }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/e2e/tests/example.spec.ts b/e2e/tests/example.spec.ts new file mode 100644 index 0000000..5120c02 --- /dev/null +++ b/e2e/tests/example.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('http://localhost:5173/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Benefit Decision Toolkit/); +}); + +test('get started link', async ({ page }) => { + await page.goto('http://localhost:5173/'); + + // Click the get started link. + await page.getByRole('button', { name: "Continue with Google" }); +});