diff --git a/.changeset/fresh-trainers-sleep.md b/.changeset/fresh-trainers-sleep.md new file mode 100644 index 0000000000..506d96e26d --- /dev/null +++ b/.changeset/fresh-trainers-sleep.md @@ -0,0 +1,5 @@ +--- +"e2e-tests": patch +--- + +Add end to end tests for `Event` type field report diff --git a/.changeset/smart-cars-promise.md b/.changeset/smart-cars-promise.md new file mode 100644 index 0000000000..8b3bd8e612 --- /dev/null +++ b/.changeset/smart-cars-promise.md @@ -0,0 +1,7 @@ +--- +"e2e-tests": major +--- + +- Implemented Playwright for automated end-to-end testing in [#345](https://github.com/IFRCGo/go-web-app/issues/345) + - CI Integration: Integrated Playwright testing into the GitHub Actions CI + - Field Report e2e Tests: Implemented Playwright-based end-to-end tests for the Field Report module diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000000..ed2ed0419e --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,114 @@ +name: Playwright + +env: + PLAYWRIGHT_APP_BASE_URL: ${{ vars.PLAYWRIGHT_APP_BASE_URL }} + PLAYWRIGHT_USER_EMAIL: ${{ secrets.PLAYWRIGHT_USER_EMAIL }} + PLAYWRIGHT_USER_PASSWORD: ${{ secrets.PLAYWRIGHT_USER_PASSWORD }} + +on: + push: + branches: + - develop + pull_request: + branches: + - develop + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "playwright" + cancel-in-progress: false + +jobs: + typecheck-lint: + name: Typecheck & Lint + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/e2e-tests + steps: + - uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + - name: Install dependencies + run: pnpm install + - name: Run Typecheck + run: pnpm typecheck + - name: Run Biome Check + run: pnpm biome:ci + test-e2e: + name: E2E Tests + environment: 'test' + timeout-minutes: 60 + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/e2e-tests + steps: + - uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + - name: Install dependencies + run: pnpm install + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run Playwright Tests + run: pnpm exec playwright test + - uses: actions/upload-artifact@v4 + id: playwright-report-artifact + if: ${{ !cancelled() }} + with: + name: playwright-report + path: ./packages/e2e-tests/playwright-report/ + - uses: actions/upload-pages-artifact@v3 + id: playwright-report-github-pages-artifact + if: ${{ !cancelled() }} + with: + name: github-pages + path: ./packages/e2e-tests/playwright-report/ + deploy: + name: Deploy Playwright Test Report to GitHub Pages + needs: test-e2e + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 + - name: Output Job Summary + run: | + if [ "${{ github.event_name }}" == "pull_request" ]; then + BRANCH=${{ github.head_ref }} + else + BRANCH=${{ github.ref_name }} + fi + echo "## 🚀 Playwright Test Run Summary 🚀">> $GITHUB_STEP_SUMMARY + echo "✅ Deployed to GitHub Pages Successful!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "👤 **Triggered by**: @${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📄 Commit Info" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: $BRANCH" >> $GITHUB_STEP_SUMMARY + echo "- **Commit SHA**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 🔗 Links" >> $GITHUB_STEP_SUMMARY + echo "- 🌐 [View Test Results](https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }})" >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index 6f710ff8f7..a1af21ed8a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ build-ssr *.njsproj *.sln *.sw? - .env* !.env.example .eslintcache @@ -41,3 +40,7 @@ storybook-static/ # Helm .helm-charts/ + +#tests +test-results/ + diff --git a/app/.env.example b/app/.env.example new file mode 100644 index 0000000000..8a4da013f7 --- /dev/null +++ b/app/.env.example @@ -0,0 +1,22 @@ +APP_TITLE=IFRC GO +APP_ENVIRONMENT=testing +APP_API_ENDPOINT= +APP_ADMIN_URL= +APP_SHOW_ENV_BANNER= + +# Mapbox +APP_MAPBOX_ACCESS_TOKEN= + +# TinyMCE +APP_TINY_API_KEY= + +# GO Risk Server +APP_RISK_API_ENDPOINT= + +# Sentry +APP_SENTRY_DSN= +APP_SENTRY_TRACES_SAMPLE_RATE= +APP_SENTRY_REPLAYS_SESSION_SAMPLE_RATE= +APP_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE= +# Google Analytics +APP_GOOGLE_ANALYTICS_ID= diff --git a/app/env.ts b/app/env.ts index e7a9542de1..bdaf4fb823 100644 --- a/app/env.ts +++ b/app/env.ts @@ -15,6 +15,7 @@ export default defineConfig({ } return value as ('production' | 'staging' | 'testing' | `alpha-${number}` | 'development' | 'APP_ENVIRONMENT_PLACEHOLDER'); }, + APP_BASE_URL: Schema.string.optional(), APP_API_ENDPOINT: Schema.string({ format: 'url', protocol: true, tld: false }), APP_ADMIN_URL: Schema.string.optional({ format: 'url', protocol: true }), APP_MAPBOX_ACCESS_TOKEN: Schema.string(), diff --git a/knip.jsonc b/knip.jsonc index 4f55ee58ad..9d4866abeb 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -10,7 +10,9 @@ // TODO: need to fix issues before un-ignoring "packages/ui", // TODO: need to fix issues before un-ignoring - "packages/go-ui-storybook" + "packages/go-ui-storybook", + // TODO: need to fix issues before un-ignoring + "packages/e2e-tests" ], "workspaces": { "app": { diff --git a/package.json b/package.json index dcc67c7949..0844c2e320 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "preview": "pnpm -F go-web-app preview", "storybook": "pnpm -F go-ui-storybook storybook", "build-storybook": "pnpm -F go-ui-storybook build-storybook", - "chromatic": "pnpm -F go-ui-storybook chromatic" + "chromatic": "pnpm -F go-ui-storybook chromatic", + "test:e2e": "pnpm -F e2e-tests test" }, "engines": { "node": "20", diff --git a/packages/e2e-tests/.env.example b/packages/e2e-tests/.env.example new file mode 100644 index 0000000000..ccd2132df6 --- /dev/null +++ b/packages/e2e-tests/.env.example @@ -0,0 +1,6 @@ +# Base URL for the application where Playwright will run tests +PLAYWRIGHT_APP_BASE_URL= +# User name of the test user for Playwright +PLAYWRIGHT_USER_EMAIL= +# Password of the test user for Playwright +PLAYWRIGHT_USER_PASSWORD= diff --git a/packages/e2e-tests/.gitignore b/packages/e2e-tests/.gitignore new file mode 100644 index 0000000000..cfaa9a41bd --- /dev/null +++ b/packages/e2e-tests/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ + +playwright/.auth diff --git a/packages/e2e-tests/biome.json b/packages/e2e-tests/biome.json new file mode 100644 index 0000000000..5e343342d7 --- /dev/null +++ b/packages/e2e-tests/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "indentStyle": "space", + "quoteStyle": "single", + "indentWidth": 4 + } + }, + "json": { + "formatter": { + "indentStyle": "space", + "indentWidth": 4 + } + } +} diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json new file mode 100644 index 0000000000..f4358fabd6 --- /dev/null +++ b/packages/e2e-tests/package.json @@ -0,0 +1,32 @@ +{ + "name": "e2e-tests", + "version": "0.0.1", + "description": "End-to-End (E2E) tests using Playwright for IFRC GO", + "main": "index.js", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/IFRCGo/go-web-app.git", + "directory": "packages/e2e-tests" + }, + "devDependencies": { + "@biomejs/biome": "1.7.3", + "@playwright/test": "^1.49.1", + "@types/node": "^20.14.11", + "typescript": "^5.7.2" + }, + "scripts": { + "install": "playwright install", + "typecheck": "tsc --noEmit", + "test": "playwright test", + "format": "biome format ./tests/* ./utils/*", + "lint": "pnpm biome lint ./tests/* ./utils/*", + "biome:check": "biome check ./tests/* ./utils/*", + "biome:ci": "biome ci ./tests/* ./utils/*", + "biome:fix": "biome check --apply ./tests/* ./utils/*" + }, + "dependencies": { + "@togglecorp/fujs": "^2.1.1", + "dotenv": "^16.4.5" + } +} diff --git a/packages/e2e-tests/playwright.config.ts b/packages/e2e-tests/playwright.config.ts new file mode 100644 index 0000000000..2376914df9 --- /dev/null +++ b/packages/e2e-tests/playwright.config.ts @@ -0,0 +1,96 @@ +import { defineConfig, devices } from '@playwright/test'; +import 'dotenv/config'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +require('dotenv').config(); + +/** + * 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: process.env.PLAYWRIGHT_APP_BASE_URL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: process.env.CI + ? [ + + { + name: 'setup', + testMatch: /.*\.setup\.ts/, + }, + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + dependencies: ['setup'], + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + dependencies: ['setup'], + }, + + // FIXME: tests not working in webkit + // { + // 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' }, + // }, + ] + : [ + // NOTE: WebKit is only supported on Debian and Ubuntu in Playwright. + { + name: 'setup', + testMatch: /.*\.setup\.ts/, + }, + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + dependencies: ['setup'], + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + dependencies: ['setup'], + }, + ], +}); diff --git a/packages/e2e-tests/tests/auth.setup.ts b/packages/e2e-tests/tests/auth.setup.ts new file mode 100644 index 0000000000..df48eab44c --- /dev/null +++ b/packages/e2e-tests/tests/auth.setup.ts @@ -0,0 +1,20 @@ +import { test as setup } from '@playwright/test'; +import { login } from '#utils/auth'; +const authFile = 'playwright/.auth/user.json'; + +/** @knipignore */ +setup('authenticate', async ({ page }) => { + if ( + process.env.PLAYWRIGHT_USER_EMAIL && + process.env.PLAYWRIGHT_USER_PASSWORD + ) { + await login( + page, + process.env.PLAYWRIGHT_USER_EMAIL, + process.env.PLAYWRIGHT_USER_PASSWORD, + ); + } + + // End of authentication steps. + await page.context().storageState({ path: authFile }); +}); diff --git a/packages/e2e-tests/tests/field-report/earlyWarning.spec.ts b/packages/e2e-tests/tests/field-report/earlyWarning.spec.ts new file mode 100644 index 0000000000..9f2038652f --- /dev/null +++ b/packages/e2e-tests/tests/field-report/earlyWarning.spec.ts @@ -0,0 +1,707 @@ +import { expect, test } from '@playwright/test'; +import { formatNumber } from '#utils/common'; +import earlyWarning from './fixtures/earlyWarning.json'; + +test.use({ storageState: 'playwright/.auth/user.json' }); + +test.describe('Field Report early warning flow', () => { + test('creates an early warning field report and assert the submitted values', async ({ + page, + }) => { + const { + actionCash, + actionEvacuation, + actionHealth, + actionInteragency, + actionMonitor, + actionMovement, + actionNfi, + actionOther, + actionShelter, + actionWash, + country, + date, + disasterType, + drefRequested, + emergencyAppeal, + emergencyResponse, + fedSummary, + forecastAction, + generalSummary, + govNumAssisted, + govRequest, + ifrcEmail, + ifrcName, + ifrcPhone, + ifrcTitle, + informationBulletin, + interventionOptionOne, + interventionOptionThree, + interventionOptionTwo, + likelyToBeAffectedGov, + likelyToBeAffectedOther, + likelyToBeAffectedRc, + mediaEmail, + mediaName, + mediaPhone, + mediaTitle, + nationalEmail, + nationalName, + nationalPhone, + nationalSocietyRequest, + nationalTitle, + originatorEmail, + originatorName, + originatorPhone, + originatorTitle, + peopleAtRiskGov, + peopleAtRiskOther, + peopleAtRiskRc, + potentiallyAffectedGov, + potentiallyAffectedOther, + potentiallyAffectedRc, + province, + rapidResponse, + rcrcAssisted, + rcrcSummary, + riskAnalysis, + sourceDetails, + title, + visibilityOptTwo, + } = earlyWarning; + await page.goto('/'); + await page.getByRole('button', { name: 'Create a Report' }).click(); + await page.getByRole('link', { name: 'New Field Report' }).click(); + // Context Page + await page + .locator('label') + .filter({ hasText: 'Early Warning / Early Action' }) + .click(); + await page.locator('input[name="country"]').fill(country); + await page.getByRole('button', { name: country }).click(); + await page.locator('input[name="districts"]').fill(province); + await page.getByRole('button', { name: province }).click(); + await page.locator('input[name="dtype"]').fill(disasterType); + await page.getByRole('button', { name: disasterType }).click(); + await expect(page.locator('input[name="dtype"]')).toHaveValue( + disasterType, + ); + await page.locator('input[name="start_date"]').fill(date); + await page.getByPlaceholder('Example: Cyclone Cody').fill(title); + const newTitle = await page.inputValue('input[type="text"]'); + await page + .locator('label') + .filter({ hasText: govRequest }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: nationalSocietyRequest }) + .nth(2) + .click(); + await page.getByRole('button', { name: 'Continue' }).click(); + // Risk Analysis Page + await page + .locator('input[name="num_potentially_affected"]') + .fill(potentiallyAffectedRc); + await page + .locator('input[name="gov_num_potentially_affected"]') + .fill(potentiallyAffectedGov); + await page + .locator('input[name="other_num_potentially_affected"]') + .fill(potentiallyAffectedOther); + await page + .locator('input[name="num_highest_risk"]') + .fill(peopleAtRiskRc); + await page + .locator('input[name="gov_num_highest_risk"]') + .fill(peopleAtRiskGov); + await page + .locator('input[name="other_num_highest_risk"]') + .fill(peopleAtRiskOther); + await page + .locator('input[name="affected_pop_centres"]') + .fill(likelyToBeAffectedRc); + await page + .locator('input[name="gov_affected_pop_centres"]') + .fill(likelyToBeAffectedGov); + await page + .locator('input[name="other_affected_pop_centres"]') + .fill(likelyToBeAffectedOther); + await page + .locator('textarea[name="other_sources"]') + .fill(sourceDetails); + await page.locator('textarea[name="description"]').fill(riskAnalysis); + await page.getByRole('button', { name: 'Continue' }).click(); + // Early Action Page + await page + .locator('input[name="gov_num_assisted"]') + .fill(govNumAssisted); + await page.locator('input[name="num_assisted"]').fill(rcrcAssisted); + await page + .locator('label') + .filter({ hasText: actionWash }) + .first() + .click(); + await page + .locator('label') + .filter({ hasText: actionEvacuation }) + .first() + .click(); + await page + .locator('label') + .filter({ hasText: actionHealth }) + .first() + .click(); + await page + .locator('textarea[name="summary"]') + .first() + .fill(generalSummary); + await page.locator('label').filter({ hasText: actionMovement }).click(); + await page.locator('label').filter({ hasText: actionMonitor }).click(); + await page + .locator('label') + .filter({ hasText: actionInteragency }) + .click(); + await page.locator('textarea[name="summary"]').nth(1).fill(fedSummary); + await page + .locator('label') + .filter({ hasText: actionShelter }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: actionCash }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: actionNfi }) + .nth(1) + .click(); + await page.locator('textarea[name="summary"]').nth(2).fill(rcrcSummary); + await page + .locator('label') + .filter({ hasText: informationBulletin }) + .click(); + await page.locator('textarea[name="actions_others"]').fill(actionOther); + await page.getByRole('button', { name: 'Continue' }).click(); + // Response Page + await page + .locator('label') + .filter({ hasText: interventionOptionOne }) + .first() + .click(); + await page.locator('input[name="dref_amount"]').fill(drefRequested); + await page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(1) + .click(); + await page.locator('input[name="appeal_amount"]').fill(emergencyAppeal); + await page + .locator('label') + .filter({ hasText: interventionOptionThree }) + .nth(2) + .click(); + await page.locator('input[name="num_fact"]').fill(rapidResponse); + await page + .locator('label') + .filter({ hasText: interventionOptionOne }) + .nth(3) + .click(); + await page + .locator('input[name="num_ifrc_staff"]') + .fill(emergencyResponse); + await page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(4) + .click(); + await page + .locator('input[name="forecast_based_action_amount"]') + .fill(forecastAction); + const contactDetails = [ + { + name: originatorName, + title: originatorTitle, + email: originatorEmail, + phone: originatorPhone, + }, + { + name: nationalName, + title: nationalTitle, + email: nationalEmail, + phone: nationalPhone, + }, + { + name: ifrcName, + title: ifrcTitle, + email: ifrcEmail, + phone: ifrcPhone, + }, + { + name: mediaName, + title: mediaTitle, + email: mediaEmail, + phone: mediaPhone, + }, + ]; + for (let i = 0; i < contactDetails.length; i++) { + const details = contactDetails[i]; + await page.locator('input[name="name"]').nth(i).fill(details.name); + await page + .locator('input[name="title"]') + .nth(i) + .fill(details.title); + await page + .locator('input[name="email"]') + .nth(i) + .fill(details.email); + await page + .locator('input[name="phone"]') + .nth(i) + .fill(details.phone); + } + await page + .locator('label') + .filter({ hasText: visibilityOptTwo }) + .click(); + await page.getByRole('button', { name: 'Submit' }).click(); + // Wait for redirection to field reports listing page + await page.waitForURL(/\/field-reports\/\d+/); + await expect(page.locator('h1')).toContainText( + `${newTitle} - ${title}`, + ); + // Assertion for Early Warning Type of Field Report + const parentElement = page + .getByText('Visibility') + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(parentElement).toContainText(visibilityOptTwo); + const parent = page + .getByText('Forecasted Date of Impact') + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(parent).toContainText(date); + // Assertions for Numeric Details + const elements = [ + { + text: 'Potentially Affected (RC)', + expectedText: formatNumber(potentiallyAffectedRc), + }, + { + text: 'People at Highest Risk (Government)', + expectedText: formatNumber(peopleAtRiskGov), + }, + { + text: 'Affected Pop Centres (RC)', + expectedText: formatNumber(likelyToBeAffectedRc), + }, + { + text: 'Assisted (RC)', + expectedText: formatNumber(rcrcAssisted), + }, + { + text: 'Potentially Affected (Government)', + expectedText: formatNumber(potentiallyAffectedGov), + }, + { + text: 'People at Highest Risk (Other)', + expectedText: formatNumber(peopleAtRiskOther), + }, + { + text: 'Affected (Government)', + expectedText: formatNumber(likelyToBeAffectedGov), + }, + { + text: 'Assisted (Government)', + expectedText: formatNumber(govNumAssisted), + }, + { + text: 'Potentially Affected (Other)', + expectedText: formatNumber(potentiallyAffectedOther), + }, + { + text: 'People at Highest Risk (RC)', + expectedText: formatNumber(peopleAtRiskRc), + }, + { + text: 'Affected Pop Centres (Other)', + expectedText: formatNumber(likelyToBeAffectedOther), + }, + ]; + for (const element of elements) { + const pElement = page + .getByText(element.text, { exact: true }) + .locator('..'); + const cElement = await pElement.nth(0).innerText(); + expect(cElement).toContain(element.expectedText); + } + // Assertions for Sources for data marked as other + const sourceElement = page.getByText( + 'Sources for data marked as Other', + { exact: true }, + ); + const sElement = sourceElement + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(sElement).toContainText(sourceDetails); + // Assertion for Risk Analysis + const riskElement = page.getByText('Risk Analysis', { exact: true }); + const rElement = riskElement + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(rElement).toContainText(riskAnalysis); + //Assertion for Request for Assistance + const govRequestElement = page + .getByText('Government Requests International Assistance', { + exact: true, + }) + .locator('..'); + await expect(govRequestElement).toContainText(govRequest); + const nsRequestElement = page + .getByText('NS Requests International Assistance', { exact: true }) + .locator('..'); + await expect(nsRequestElement).toContainText(nationalSocietyRequest); + // Assertions for Information Bulletin Published + const infoElement = page.getByText('Information Bulletin Published', { + exact: true, + }); + const iElement = infoElement + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(iElement).toContainText(informationBulletin); + // Assertions for Action taken by National Society + const actionNsElement = page + .getByText('Actions taken by National Society', { exact: true }) + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(actionNsElement).toContainText(actionWash); + await expect(actionNsElement).toContainText(actionEvacuation); + await expect(actionNsElement).toContainText(actionHealth); + await expect(actionNsElement).toContainText(generalSummary); + // Assertion for Action Taken by Federation + const actionFederationElement = page + .getByText('Actions taken by Federation', { exact: true }) + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(actionFederationElement).toContainText(actionMovement); + await expect(actionFederationElement).toContainText(actionMonitor); + await expect(actionFederationElement).toContainText(actionInteragency); + await expect(actionFederationElement).toContainText(fedSummary); + // Assertion for Action Taken by RCRC + const actionRcrcElement = page + .getByText('Actions taken by RCRC', { exact: true }) + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(actionRcrcElement).toContainText(actionShelter); + await expect(actionRcrcElement).toContainText(actionCash); + await expect(actionRcrcElement).toContainText(actionNfi); + await expect(actionRcrcElement).toContainText(rcrcSummary); + // Assertions for Action Taken by Others + const otherActionElement = page + .getByText('Actions taken by others', { exact: true }) + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(otherActionElement).toContainText(actionOther); + // Assertions for Planned International Response + const interventions = [ + { label: 'DREF', value: interventionOptionOne }, + { label: 'Emergency Appeal', value: interventionOptionTwo }, + { + label: 'Rapid Response Personnel', + value: interventionOptionThree, + }, + { label: 'Emergency Response Units', value: interventionOptionOne }, + { label: 'Forecast Based Action', value: interventionOptionTwo }, + ]; + for (const intervention of interventions) { + const element = page + .getByText(intervention.label, { exact: true }) + .locator('..'); + await expect(element).toHaveText( + intervention.label + intervention.value, + ); + } + //Assertion for Contacts + const details = [ + { + label: 'Originator', + name: originatorName, + title: originatorTitle, + email: originatorEmail, + phone: originatorPhone, + }, + { + label: 'NationalSociety', + name: nationalName, + title: nationalTitle, + email: nationalEmail, + phone: nationalPhone, + }, + { + label: 'Federation', + name: ifrcName, + title: ifrcTitle, + email: ifrcEmail, + phone: ifrcPhone, + }, + { + label: 'Media', + name: mediaName, + title: mediaTitle, + email: mediaEmail, + phone: mediaPhone, + }, + ]; + for (const detail of details) { + const detailLocator = page + .getByText(detail.label, { exact: true }) + .locator('..'); + await expect(detailLocator).toContainText(detail.name); + await expect(detailLocator).toContainText(detail.title); + await expect(detailLocator).toContainText(detail.email); + await expect(detailLocator).toContainText(detail.phone); + } + // Value Assertions + await page.getByRole('link', { name: 'Edit Report' }).click(); + // Context Page + const statusValue = page + .locator('label') + .filter({ hasText: 'Early Warning / Early Action' }); + await expect(statusValue).toBeChecked(); + // Assertions for Country, Region, Disaster Type, Date and Title value + const countryValue = page.locator('input[name="country"]'); + await expect(countryValue).toHaveValue(country); + const regionValue = page.locator('input[name="districts"]'); + await expect(regionValue).toHaveValue(province); + const disasterValue = page.locator('input[name="dtype"]'); + await expect(disasterValue).toHaveValue(disasterType); + const dateValue = page.locator('input[name="start_date"]'); + await expect(dateValue).toHaveValue(date); + const titleValue = page.getByPlaceholder('Example: Cyclone Cody'); + await expect(titleValue).toHaveValue(`${newTitle} - ${title}`); + // Government request international assistance + const govReqValue = page + .locator('label') + .filter({ hasText: govRequest }) + .nth(1); + await expect(govReqValue).toBeChecked(); + // National Society requests international assistance? + const nsReqValue = page + .locator('label') + .filter({ hasText: nationalSocietyRequest }) + .nth(2); + await expect(nsReqValue).toBeChecked(); + await page.getByRole('button', { name: 'Continue' }).click(); + // Risk Analysis Page + // Assertions for Numeric Details Value + const fields = [ + { name: 'num_potentially_affected', value: potentiallyAffectedRc }, + { + name: 'gov_num_potentially_affected', + value: potentiallyAffectedGov, + }, + { + name: 'other_num_potentially_affected', + value: potentiallyAffectedOther, + }, + { name: 'num_highest_risk', value: peopleAtRiskRc }, + { name: 'gov_num_highest_risk', value: peopleAtRiskGov }, + { name: 'other_num_highest_risk', value: peopleAtRiskOther }, + { name: 'affected_pop_centres', value: likelyToBeAffectedRc }, + { name: 'gov_affected_pop_centres', value: likelyToBeAffectedGov }, + { + name: 'other_affected_pop_centres', + value: likelyToBeAffectedOther, + }, + ]; + for (const field of fields) { + const valueLocator = page.locator(`input[name="${field.name}"]`); + await expect(valueLocator).toHaveValue(field.value); + } + // Assertions for Source Details Value + const sourceValue = page.locator('textarea[name="other_sources"]'); + await expect(sourceValue).toHaveValue(sourceDetails); + // Assertions for Risk Analysis Value + const riskAnalysisValue = page.locator('textarea[name="description"]'); + await expect(riskAnalysisValue).toHaveValue(riskAnalysis); + await page.getByRole('button', { name: 'Continue' }).click(); + // Early Actions Page + // Assertions for Acton Taken Value + const govNumAssistedValue = page.locator( + 'input[name="gov_num_assisted"]', + ); + await expect(govNumAssistedValue).toHaveValue(govNumAssisted); + const numAssistedValue = page.locator('input[name="num_assisted"]'); + await expect(numAssistedValue).toHaveValue(rcrcAssisted); + // Assertion for Early Action Taken by NS Value + const nsActions = [actionWash, actionEvacuation, actionHealth]; + for (const action of nsActions) { + const actionLocator = page + .locator('label') + .filter({ hasText: action }) + .first(); + await expect(actionLocator).toBeChecked(); + } + const nsActionsValue = page.locator('textarea[name="summary"]').first(); + await expect(nsActionsValue).toHaveValue(generalSummary); + // Assertions for Early Actions Taken by IFRC Value + const ifrcActions = [actionMovement, actionMonitor, actionInteragency]; + for (const action of ifrcActions) { + const actionLocator = page + .locator('label') + .filter({ hasText: action }); + await expect(actionLocator).toBeChecked(); + } + const ifrcActionsValue = page + .locator('textarea[name="summary"]') + .nth(1); + await expect(ifrcActionsValue).toHaveValue(fedSummary); + // Assertions for Early Action Taken by Other RCRC Movement Value + const rcrcActions = [actionShelter, actionCash, actionNfi]; + for (const action of rcrcActions) { + const actionLocator = page + .locator('label') + .filter({ hasText: action }) + .nth(1); + await expect(actionLocator).toBeChecked(); + } + const rcrcActionsValue = page + .locator('textarea[name="summary"]') + .nth(2); + await expect(rcrcActionsValue).toHaveValue(rcrcSummary); + // Assertions for Information Bulletin Value + const informationBulletinValue = page + .locator('label') + .filter({ hasText: informationBulletin }); + await expect(informationBulletinValue).toBeChecked(); + // Assertions for Action Taken by Other Value + const otherActionValue = page.locator( + 'textarea[name="actions_others"]', + ); + await expect(otherActionValue).toHaveValue(actionOther); + await page.getByRole('button', { name: 'Continue' }).click(); + // Response Page + // Assertions for Planned Interventions Value + // DREF Requested + const drefValue = page + .locator('label') + .filter({ hasText: interventionOptionOne }) + .nth(0); + await expect(drefValue).toBeChecked(); + const drefSummaryValue = page.locator('input[name="dref_amount"]'); + await expect(drefSummaryValue).toHaveValue(drefRequested); + // Emergency Appeal + const emergencyAppealValue = page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(1); + await expect(emergencyAppealValue).toBeChecked(); + const emergencyAppealSummaryValue = page.locator( + 'input[name="appeal_amount"]', + ); + await expect(emergencyAppealSummaryValue).toHaveValue(emergencyAppeal); + // Rapid Response Personnel + const rapidResponseValue = page + .locator('label') + .filter({ hasText: interventionOptionThree }) + .nth(2); + await expect(rapidResponseValue).toBeChecked(); + const rapidResponseSummaryValue = page.locator( + 'input[name="num_fact"]', + ); + await expect(rapidResponseSummaryValue).toHaveValue(rapidResponse); + // Emergency Response Unit + const emergencyResponseValue = page + .locator('label') + .filter({ hasText: interventionOptionOne }) + .nth(3); + await expect(emergencyResponseValue).toBeChecked(); + const emergencyResponseSummaryValue = page.locator( + 'input[name="num_ifrc_staff"]', + ); + await expect(emergencyResponseSummaryValue).toHaveValue( + emergencyResponse, + ); + // Forecast Based Action + const forecastValue = page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(4); + await expect(forecastValue).toBeChecked(); + const forecastSummaryValue = page.locator( + 'input[name="forecast_based_action_amount"]', + ); + await expect(forecastSummaryValue).toHaveValue(forecastAction); + // Contacts + // Assertion for Originator Contacts value + const originatorValue = [ + { name: 'name', value: originatorName }, + { name: 'title', value: originatorTitle }, + { name: 'email', value: originatorEmail }, + { name: 'phone', value: originatorPhone }, + ]; + for (const { name, value } of originatorValue) { + const locator = page.locator(`input[name="${name}"]`).nth(0); + await expect(locator).toHaveValue(value); + } + // Assertion for National Society values + const nationalValue = [ + { name: 'name', value: nationalName }, + { name: 'title', value: nationalTitle }, + { name: 'email', value: nationalEmail }, + { name: 'phone', value: nationalPhone }, + ]; + for (const { name, value } of nationalValue) { + const locator = page.locator(`input[name="${name}"]`).nth(1); + await expect(locator).toHaveValue(value); + } + // Assertions for IFRC Focal Points for the Emergency Values + const ifrcValue = [ + { name: 'name', value: ifrcName }, + { name: 'title', value: ifrcTitle }, + { name: 'email', value: ifrcEmail }, + { name: 'phone', value: ifrcPhone }, + ]; + for (const { name, value } of ifrcValue) { + const locator = page.locator(`input[name="${name}"]`).nth(2); + await expect(locator).toHaveValue(value); + } + // Assertions for Emergency Response Units Values + const mediaValue = [ + { name: 'name', value: mediaName }, + { name: 'title', value: mediaTitle }, + { name: 'email', value: mediaEmail }, + { name: 'phone', value: mediaPhone }, + ]; + for (const { name, value } of mediaValue) { + const locator = page.locator(`input[name="${name}"]`).nth(3); + await expect(locator).toHaveValue(value); + } + // Assertions for Field Report Visibility Value + const frVisibilityValue = page + .locator('label') + .filter({ hasText: visibilityOptTwo }); + await expect(frVisibilityValue).toBeChecked(); + }); +}); diff --git a/packages/e2e-tests/tests/field-report/epidemic.spec.ts b/packages/e2e-tests/tests/field-report/epidemic.spec.ts new file mode 100644 index 0000000000..4a5359be21 --- /dev/null +++ b/packages/e2e-tests/tests/field-report/epidemic.spec.ts @@ -0,0 +1,682 @@ +import { expect, test } from '@playwright/test'; +import { formatNumber } from '#utils/common'; +import epidemic from './fixtures/epidemic.json'; + +test.use({ storageState: 'playwright/.auth/user.json' }); + +test.describe('Field report for an epidemic disaster', async () => { + test('creates a field report for an epidemic disaster and assert the submitted values', async ({ + page, + }) => { + const { + actionAid, + actionAmbulance, + actionOther, + actionQuarantine, + actionReadiness, + actionSanitation, + actionVaccination, + actionVector, + actionVolunteer, + actionWater, + cases, + confirmedCases, + country, + date, + description, + disasterType, + drefRequested, + emergencyAppeal, + emergencyResponse, + epiDate, + epiNotes, + federationSummary, + formName, + govNumAssisted, + govRequest, + ifrcEmail, + ifrcName, + ifrcPhone, + ifrcTitle, + informationBulletin, + interventionOptionOne, + interventionOptionThree, + interventionOptionTwo, + mediaEmail, + mediaName, + mediaPhone, + mediaTitle, + nationalEmail, + nationalName, + nationalPhone, + nationalSocietyRequest, + nationalSocietySummary, + nationalTitle, + numAssisted, + numDead, + numExpatsDelegates, + numLocalstaff, + numVolunteers, + originatorEmail, + originatorName, + originatorPhone, + originatorTitle, + otherSources, + probableCases, + rapidResponse, + rcrcSummary, + region, + source, + suspectedCases, + title, + visibilityOptTwo, + } = epidemic; + + await page.goto('/'); + await page.getByRole('button', { name: 'Create a Report' }).click(); + await page.getByRole('link', { name: 'New Field Report' }).click(); + await expect(page.locator('h1')).toContainText(formName); + // Context Page + await page.locator('input[name="country"]').fill(country); + await page.getByRole('button', { name: country }).click(); + await page.locator('input[name="districts"]').fill(region); + await page.getByRole('button', { name: region }).click(); + await page.locator('input[name="dtype"]').fill(disasterType); + await page.getByRole('button', { name: disasterType }).click(); + await expect(page.locator('input[name="dtype"]')).toHaveValue( + disasterType, + ); + await page.locator('input[name="start_date"]').fill(date); + const newTitle = await page.inputValue('input[type="text"]'); + await page.locator('input[name="summary"]').fill(title); + await page + .locator('label') + .filter({ hasText: govRequest }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: nationalSocietyRequest }) + .nth(2) + .click(); + await page.getByRole('button', { name: 'Continue' }).click(); + // Situation Page + await page.locator('input[name="epi_cases"]').fill(cases); + await page + .locator('input[name="epi_suspected_cases"]') + .fill(suspectedCases); + await page + .locator('input[name="epi_probable_cases"]') + .fill(probableCases); + await page + .locator('input[name="epi_confirmed_cases"]') + .fill(confirmedCases); + await page.locator('input[name="epi_num_dead"]').fill(numDead); + await page.locator('input[name="epi_figures_source"]').click(); + await page.getByRole('button', { name: source }).click(); + await page + .locator('textarea[name="epi_notes_since_last_fr"]') + .fill(epiNotes); + await page.locator('input[name="sit_fields_date"]').fill(epiDate); + await page.locator('textarea[name="other_sources"]').fill(otherSources); + await page.locator('textarea[name="description"]').fill(description); + await page.getByRole('button', { name: 'Continue' }).click(); + // Action Page + await page + .locator('input[name="gov_num_assisted"]') + .fill(govNumAssisted); + await page.locator('input[name="num_assisted"]').fill(numAssisted); + await page.locator('input[name="num_localstaff"]').fill(numLocalstaff); + await page.locator('input[name="num_volunteers"]').fill(numVolunteers); + await page + .locator('input[name="num_expats_delegates"]') + .fill(numExpatsDelegates); + // Action taken by National red cross society + await page + .locator('label') + .filter({ hasText: actionVaccination }) + .first() + .click(); + await page + .locator('label') + .filter({ hasText: actionQuarantine }) + .first() + .click(); + await page + .locator('label') + .filter({ hasText: actionWater }) + .first() + .click(); + await page + .getByPlaceholder('Brief description of the action') + .first() + .fill(nationalSocietySummary); + // Action Taken by IFRC + await page + .locator('label') + .filter({ hasText: actionSanitation }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: actionVector }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: actionAid }) + .nth(1) + .click(); + await page + .getByPlaceholder('Brief description of the') + .nth(1) + .fill(federationSummary); + // Action Taken By any other RCRC movement actors + await page + .locator('label') + .filter({ hasText: actionAmbulance }) + .nth(2) + .click(); + await page + .locator('label') + .filter({ hasText: actionVolunteer }) + .nth(2) + .click(); + await page + .locator('label') + .filter({ hasText: actionReadiness }) + .nth(2) + .click(); + await page + .getByPlaceholder('Brief description of the') + .nth(2) + .fill(rcrcSummary); + await page + .locator('label') + .filter({ hasText: informationBulletin }) + .click(); + await page.locator('textarea[name="actions_others"]').fill(actionOther); + await page.getByRole('button', { name: 'Continue' }).click(); + // Response Page + // DREF Requested + await page + .locator('label') + .filter({ hasText: interventionOptionOne }) + .first() + .click(); + await page.locator('input[name="dref_amount"]').fill(drefRequested); + //Emergency Appeal + await page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(1) + .click(); + await page.locator('input[name="appeal_amount"]').fill(emergencyAppeal); + //Rapid Response Personnel + await page + .locator('label') + .filter({ hasText: interventionOptionThree }) + .nth(2) + .click(); + await page.locator('input[name="num_fact"]').fill(rapidResponse); + // Emergency Response Units + await page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(3) + .click(); + await page + .locator('input[name="num_ifrc_staff"]') + .fill(emergencyResponse); + // Originator + await page.locator('input[name="name"]').nth(0).fill(originatorName); + await page.locator('input[name="title"]').nth(0).fill(originatorTitle); + await page.locator('input[name="email"]').nth(0).fill(originatorEmail); + await page.locator('input[name="phone"]').nth(0).fill(originatorPhone); + // National Society Contact + await page.locator('input[name="name"]').nth(1).fill(nationalName); + await page.locator('input[name="title"]').nth(1).fill(nationalTitle); + await page.locator('input[name="email"]').nth(1).fill(nationalEmail); + await page.locator('input[name="phone"]').nth(1).fill(nationalPhone); + // IFRC Focal Point for the Emergency + await page.locator('input[name="name"]').nth(2).fill(ifrcName); + await page.locator('input[name="title"]').nth(2).fill(ifrcTitle); + await page.locator('input[name="email"]').nth(2).fill(ifrcEmail); + await page.locator('input[name="phone"]').nth(2).fill(ifrcPhone); + // Media Contact + await page.locator('input[name="name"]').nth(3).fill(mediaName); + await page.locator('input[name="title"]').nth(3).fill(mediaTitle); + await page.locator('input[name="email"]').nth(3).fill(mediaEmail); + await page.locator('input[name="phone"]').nth(3).fill(mediaPhone); + // Field report visible + await page + .locator('label') + .filter({ hasText: visibilityOptTwo }) + .click(); + await page.getByRole('button', { name: 'Submit' }).click(); + // Wait for redirection to field reports listing page + await page.waitForURL(/\/field-reports\/\d+/); + // Title Assertion + await expect(page.locator('h1')).toContainText( + `${newTitle} - ${title}`, + ); + // Data Assertion + await expect(page.getByRole('banner')).toContainText(disasterType); + await expect(page.getByRole('banner')).toContainText(country); + const fields = [ + { label: 'Visibility', value: visibilityOptTwo }, + { label: 'Start Date', value: date }, + { label: 'Date of Data', value: epiDate }, + { label: 'Source', value: source }, + ]; + for (const field of fields) { + const element = page + .getByText(field.label, { exact: true }) + .locator('..'); + await expect(element).toHaveText(field.label + field.value); + } + // Assertions to verify whether the data inserted on the form are displayed on the UI + // Numeric Details + const numericDetails = [ + { label: 'Cumulative Cases', value: formatNumber(cases) }, + { label: 'Suspected Cases', value: formatNumber(suspectedCases) }, + { label: 'Probable Cases', value: formatNumber(probableCases) }, + { label: 'Confirmed Cases', value: formatNumber(confirmedCases) }, + { label: 'Dead', value: formatNumber(numDead) }, + { label: 'Assisted (RC)', value: formatNumber(numAssisted) }, + { + label: 'Assisted (Government)', + value: formatNumber(govNumAssisted), + }, + // { label: 'Staff', value: formatNumber(num_localstaff)}, + // { label: 'Volunteers', value: formatNumber(num_volunteers) }, + { label: 'Delegates', value: formatNumber(numExpatsDelegates) }, + ]; + for (const detail of numericDetails) { + const parentElement = page.getByText(detail.label).locator('..'); + const textContent = parentElement.nth(0); + await expect(textContent).toContainText(detail.value); + } + // Notes + const noteChild = page.getByText('Notes', { exact: true }); + const noteParent = noteChild + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(noteParent).toContainText(epiNotes); + // Source Marked as Others Assertions + const sourceChild = page.getByText('Sources for data marked as Other', { + exact: true, + }); + const sourceParent = sourceChild + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(sourceParent).toContainText(otherSources); + // Description + const descriptionChild = page.getByText( + 'Sources for data marked as Other', + { exact: true }, + ); + const descriptionParent = descriptionChild + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(descriptionParent).toContainText(otherSources); + // Request for Assistance Assertions + const govReq = page + .getByText('Government Requests International Assistance', { + exact: true, + }) + .locator('..'); + await expect(govReq).toHaveText( + `Government Requests International Assistance${govRequest}`, + ); + const nsReq = page + .getByText('NS Requests International Assistance', { exact: true }) + .locator('..'); + await expect(nsReq).toHaveText( + `NS Requests International Assistance${nationalSocietyRequest}`, + ); + // Information Bulletin Published Assertions + const infoBulletin = page.getByText('Information Bulletin Published', { + exact: true, + }); + const bulletin = infoBulletin + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(bulletin).toContainText(informationBulletin); + // Assertions to Verify Action taken by National Society, RCRC and Federation + const sections = [ + { + childText: 'Actions taken by National Society', + actions: [ + actionVaccination, + actionQuarantine, + actionWater, + nationalSocietySummary, + ], + }, + { + childText: 'Actions Taken by Federation', + actions: [ + actionSanitation, + actionVector, + actionAid, + federationSummary, + ], + }, + { + childText: 'Actions Taken by RCRC', + actions: [ + actionAmbulance, + actionVolunteer, + actionReadiness, + rcrcSummary, + ], + }, + ]; + + for (const section of sections) { + const sectionChild = page.getByText(section.childText); + const sectionParent = sectionChild + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + + for (const action of section.actions) { + await expect(sectionParent).toContainText(action); + } + } + // Assertions for Actions taken by others + const actionParent = page.getByText('Actions taken by others', { + exact: true, + }); + const actionChild = actionParent + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(actionChild).toContainText(actionOther); + // Assertions to verify Planned Intervention + const interventions = [ + { label: 'DREF', value: interventionOptionOne }, + { label: 'Emergency Appeal', value: interventionOptionTwo }, + { + label: 'Rapid Response Personnel', + value: interventionOptionThree, + }, + { label: 'Emergency Response Units', value: interventionOptionTwo }, + ]; + for (const intervention of interventions) { + const element = page + .getByText(intervention.label, { exact: true }) + .locator('..'); + await expect(element).toHaveText( + intervention.label + intervention.value, + ); + } + // Assertions to verify Contacts + const details = [ + { + label: 'Originator', + name: originatorName, + title: originatorTitle, + email: originatorEmail, + phone: originatorPhone, + }, + { + label: 'NationalSociety', + name: nationalName, + title: nationalTitle, + email: nationalEmail, + phone: nationalPhone, + }, + { + label: 'Federation', + name: ifrcName, + title: ifrcTitle, + email: ifrcEmail, + phone: ifrcPhone, + }, + { + label: 'Media', + name: mediaName, + title: mediaTitle, + email: mediaEmail, + phone: mediaPhone, + }, + ]; + for (const detail of details) { + const detailLocator = page + .getByText(detail.label, { exact: true }) + .locator('..'); + await expect(detailLocator).toContainText(detail.name); + await expect(detailLocator).toContainText(detail.title); + await expect(detailLocator).toContainText(detail.email); + await expect(detailLocator).toContainText(detail.phone); + } + await page.getByRole('link', { name: 'Edit Report' }).click(); + // Input Value Assertions + // Context Page + const statusValue = page + .locator('label') + .filter({ hasText: 'First report for this disaster' }); + await expect(statusValue).toBeChecked(); + // Assertions for Country, Region, Disaster Type, Date and Title + const countryValue = page.locator('input[name="country"]'); + await expect(countryValue).toHaveValue(country); + const regionValue = page.locator('input[name="districts"]'); + await expect(regionValue).toHaveValue(region); + const disasterValue = page.locator('input[name="dtype"]'); + await expect(disasterValue).toHaveValue(disasterType); + const dateValue = page.locator('input[name="start_date"]'); + await expect(dateValue).toHaveValue(date); + const titleValue = page.getByPlaceholder('Example: Cyclone Cody'); + await expect(titleValue).toHaveValue(`${newTitle} - ${title}`); + // Government request international assistance + const govReqValue = page + .locator('label') + .filter({ hasText: govRequest }) + .nth(1); + await expect(govReqValue).toBeChecked(); + // National Society requests international assistance? + const nsReqValue = page + .locator('label') + .filter({ hasText: nationalSocietyRequest }) + .nth(2); + await expect(nsReqValue).toBeChecked(); + await page.getByRole('button', { name: 'Continue' }).click(); + // Situation Page + // Assertion for Numeric Details Value + const numericDetailCases = [ + { name: 'epi_cases', value: cases }, + { name: 'epi_suspected_cases', value: suspectedCases }, + { name: 'epi_probable_cases', value: probableCases }, + { name: 'epi_confirmed_cases', value: confirmedCases }, + { name: 'epi_num_dead', value: numDead }, + ]; + for (const caseData of numericDetailCases) { + const locator = page.locator(`input[name="${caseData.name}"]`); + await expect(locator).toHaveValue(caseData.value); + } + // Assertions for Source Value + const sourceValue = page.locator('input[name="epi_figures_source"]'); + await expect(sourceValue).toHaveValue(source); + // Assertions for Notes Value + const notesValue = page.locator( + 'textarea[name="epi_notes_since_last_fr"]', + ); + await expect(notesValue).toHaveValue(epiNotes); + // Assertions for Date of Data Value + const dataDateValue = page.locator('input[name="sit_fields_date"]'); + await expect(dataDateValue).toHaveValue(epiDate); + // Assertions for Source Details Value + const otherSourcesValue = page.locator( + 'textarea[name="other_sources"]', + ); + await expect(otherSourcesValue).toHaveValue(otherSources); + // Assertions for Situational Overview Value + const overviewValue = page.locator('textarea[name="description"]'); + await expect(overviewValue).toHaveValue(description); + await page.getByRole('button', { name: 'Continue' }).click(); + // Actions Page + // Assertions for Actions Taken Value + const inputValues = [ + { name: 'gov_num_assisted', value: govNumAssisted }, + { name: 'num_assisted', value: numAssisted }, + { name: 'num_localstaff', value: numLocalstaff }, + { name: 'num_volunteers', value: numVolunteers }, + { name: 'num_expats_delegates', value: numExpatsDelegates }, + ]; + for (const { name, value } of inputValues) { + const inputValue = page.locator(`input[name="${name}"]`); + await expect(inputValue).toHaveValue(value); + } + // Assertions for Actions Taken by National Red Cross Society Value + const nsActions = [actionVaccination, actionQuarantine, actionWater]; + for (const action of nsActions) { + const actionLocator = page + .locator('label') + .filter({ hasText: action }) + .first(); + await expect(actionLocator).toBeChecked(); + } + const nsActionsValue = page.locator('textarea[name="summary"]').first(); + await expect(nsActionsValue).toHaveValue(nationalSocietySummary); + // Assertions for Actions Taken by IFRC Value + const ifrcActions = [actionSanitation, actionVector, actionAid]; + for (const action of ifrcActions) { + const actionLocator = page + .locator('label') + .filter({ hasText: action }) + .nth(1); + await expect(actionLocator).toBeChecked(); + } + const ifrcActionsValue = page + .locator('textarea[name="summary"]') + .nth(1); + await expect(ifrcActionsValue).toHaveValue(federationSummary); + // Assertions for Actions Taken by Other RCRC Movements Actors Value + const rcrcActions = [actionAmbulance, actionVolunteer, actionReadiness]; + for (const action of rcrcActions) { + const actionLocator = page + .locator('label') + .filter({ hasText: action }) + .nth(2); + await expect(actionLocator).toBeChecked(); + } + const rcrcActionsValue = page + .locator('textarea[name="summary"]') + .nth(2); + await expect(rcrcActionsValue).toHaveValue(rcrcSummary); + // Assertions for Information Bulletin Value + const informationBulletinValue = page + .locator('label') + .filter({ hasText: informationBulletin }); + await expect(informationBulletinValue).toBeChecked(); + // Actions Taken by Others + const actionOthersValue = page.locator( + 'textarea[name="actions_others"]', + ); + await expect(actionOthersValue).toHaveValue(actionOther); + await page.getByRole('button', { name: 'Continue' }).click(); + // Response Page + // Assertions for Planned Interventions Value + // DREF Requested + const drefValue = page + .locator('label') + .filter({ hasText: interventionOptionOne }) + .nth(0); + await expect(drefValue).toBeChecked(); + const drefSummaryValue = page.locator('input[name="dref_amount"]'); + await expect(drefSummaryValue).toHaveValue(drefRequested); + const emergencyAppealValue = page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(1); + await expect(emergencyAppealValue).toBeChecked(); + const emergencyAppealSummaryValue = page.locator( + 'input[name="appeal_amount"]', + ); + await expect(emergencyAppealSummaryValue).toHaveValue(emergencyAppeal); + // Rapid Response Personnel + const rapidResponseValue = page + .locator('label') + .filter({ hasText: interventionOptionThree }) + .nth(2); + await expect(rapidResponseValue).toBeChecked(); + const rapidResponseSummaryValue = page.locator( + 'input[name="num_fact"]', + ); + await expect(rapidResponseSummaryValue).toHaveValue(rapidResponse); + // Emergency Response Unit + const emergencyResponseValue = page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(3); + await expect(emergencyResponseValue).toBeChecked(); + const emergencyResponseSummaryValue = page.locator( + 'input[name="num_ifrc_staff"]', + ); + await expect(emergencyResponseSummaryValue).toHaveValue( + emergencyResponse, + ); + // Contacts + // Assertion for Originator Contacts value + const originatorValue = [ + { name: 'name', value: originatorName }, + { name: 'title', value: originatorTitle }, + { name: 'email', value: originatorEmail }, + { name: 'phone', value: originatorPhone }, + ]; + for (const { name, value } of originatorValue) { + const locator = page.locator(`input[name="${name}"]`).nth(0); + await expect(locator).toHaveValue(value); + } + // Assertion for National Society values + const nationalValue = [ + { name: 'name', value: nationalName }, + { name: 'title', value: nationalTitle }, + { name: 'email', value: nationalEmail }, + { name: 'phone', value: nationalPhone }, + ]; + for (const { name, value } of nationalValue) { + const locator = page.locator(`input[name="${name}"]`).nth(1); + await expect(locator).toHaveValue(value); + } + // Assertions for IFRC Focal Points for the Emergency Values + const ifrcValue = [ + { name: 'name', value: ifrcName }, + { name: 'title', value: ifrcTitle }, + { name: 'email', value: ifrcEmail }, + { name: 'phone', value: ifrcPhone }, + ]; + for (const { name, value } of ifrcValue) { + const locator = page.locator(`input[name="${name}"]`).nth(2); + await expect(locator).toHaveValue(value); + } + // Assertions for Emergency Response Units Values + const mediaValue = [ + { name: 'name', value: mediaName }, + { name: 'title', value: mediaTitle }, + { name: 'email', value: mediaEmail }, + { name: 'phone', value: mediaPhone }, + ]; + for (const { name, value } of mediaValue) { + const locator = page.locator(`input[name="${name}"]`).nth(3); + await expect(locator).toHaveValue(value); + } + // Assertions for Field Report Visibility Value + const frVisibilityValue = page + .locator('label') + .filter({ hasText: visibilityOptTwo }); + await expect(frVisibilityValue).toBeChecked(); + }); +}); diff --git a/packages/e2e-tests/tests/field-report/event.spec.ts b/packages/e2e-tests/tests/field-report/event.spec.ts new file mode 100644 index 0000000000..216dfaad63 --- /dev/null +++ b/packages/e2e-tests/tests/field-report/event.spec.ts @@ -0,0 +1,698 @@ +import { expect, test } from '@playwright/test'; +import { formatNumber } from '#utils/common'; +import eventData from './fixtures/event.json'; + +test.use({ storageState: 'playwright/.auth/user.json' }); + +test.describe('Field Report', () => { + test('should create a new event type field report', async ({ page }) => { + const { + actionCamp, + actionEvacuation, + actionFirst, + actionFood, + actionHealth, + actionHuman, + actionOther, + actionPsychosocial, + actionShelter, + country, + date, + disasterType, + district, + drefRequested, + emergencyAppeal, + emergencyResponse, + federationSummary, + formName, + govNumAffected, + govNumAssisted, + govNumDead, + govNumDisplaced, + govNumInjured, + govNumMissing, + govRequest, + ifrcEmail, + ifrcName, + ifrcPhone, + ifrcTitle, + informationBulletin, + interventionOptionOne, + interventionOptionThree, + interventionOptionTwo, + mediaEmail, + mediaName, + mediaPhone, + mediaTitle, + nationalEmail, + nationalName, + nationalPhone, + nationalSocietyRequest, + nationalSocietySummary, + nationalTitle, + numAffected, + numAssisted, + numDead, + numDisplaced, + numExpatsDelegates, + numInjured, + numLocalstaff, + numMissing, + numVolunteers, + originatorEmail, + originatorName, + originatorPhone, + originatorTitle, + otherNumAffected, + otherNumDead, + otherNumDisplaced, + otherNumMissing, + otherSources, + rapidResponse, + rcrcSummary, + title, + visibilityOptTwo, + otherNumInjured, + } = eventData; + + await page.goto('/'); + await page.getByRole('button', { name: 'Create a Report' }).click(); + await page.getByRole('link', { name: 'New Field Report' }).click(); + await expect(page.locator('h1')).toContainText(formName); + // Context Page + await page.locator('input[name="country"]').fill(country); + await page.getByRole('button', { name: country }).click(); + await page.locator('input[name="districts"]').fill(district); + await page.getByRole('button', { name: district }).click(); + await page.locator('input[name="dtype"]').fill(disasterType); + await page.getByRole('button', { name: disasterType }).click(); + await page.locator('input[name="start_date"]').fill(date); + const newTitle = await page.inputValue('input[type="text"]'); + await page.getByPlaceholder('Example: Cyclone Cody').fill(title); + await page + .locator('label') + .filter({ hasText: govRequest }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: nationalSocietyRequest }) + .nth(2) + .click(); + await page.getByRole('button', { name: 'Continue' }).click(); + // Situation Page + await page.locator('input[name="num_injured"]').fill(numInjured); + await page.locator('input[name="gov_num_injured"]').fill(govNumInjured); + await page + .locator('input[name="other_num_injured"]') + .fill(otherNumInjured); + await page.locator('input[name="num_dead"]').fill(numDead); + await page.locator('input[name="gov_num_dead"]').fill(govNumDead); + await page.locator('input[name="other_num_dead"]').fill(otherNumDead); + await page.locator('input[name="num_missing"]').fill(numMissing); + await page.locator('input[name="gov_num_missing"]').fill(govNumMissing); + await page + .locator('input[name="other_num_missing"]') + .fill(otherNumMissing); + await page.locator('input[name="num_affected"]').fill(numAffected); + await page + .locator('input[name="gov_num_affected"]') + .fill(govNumAffected); + await page + .locator('input[name="other_num_affected"]') + .fill(otherNumAffected); + await page.locator('input[name="num_displaced"]').fill(numDisplaced); + await page + .locator('input[name="gov_num_displaced"]') + .fill(govNumDisplaced); + await page + .locator('input[name="other_num_displaced"]') + .fill(otherNumDisplaced); + await page.locator('textarea[name="other_sources"]').fill(otherSources); + // await page.frameLocator('iframe[title="Rich Text Area"]').locator('html').fill("Just the random data"); + // issue in Situational overview textbox + await page.getByRole('button', { name: 'Continue' }).click(); + // Action Page + await page + .locator('input[name="gov_num_assisted"]') + .fill(govNumAssisted); + await page.locator('input[name="num_assisted"]').fill(numAssisted); + await page.locator('input[name="num_localstaff"]').fill(numLocalstaff); + await page.locator('input[name="num_volunteers"]').fill(numVolunteers); + await page + .locator('input[name="num_expats_delegates"]') + .fill(numExpatsDelegates); + // Action taken by National red cross society + await page + .locator('label') + .filter({ hasText: actionHuman }) + .nth(0) + .click(); + await page + .locator('label') + .filter({ hasText: actionShelter }) + .nth(0) + .click(); + await page + .locator('label') + .filter({ hasText: actionEvacuation }) + .nth(0) + .click(); + await page + .getByPlaceholder('Brief description of the action') + .nth(0) + .fill(nationalSocietySummary); + // Action Taken by IFRC + await page + .locator('label') + .filter({ hasText: actionHealth }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: actionShelter }) + .nth(1) + .click(); + await page + .locator('label') + .filter({ hasText: actionCamp }) + .nth(1) + .click(); + await page + .getByPlaceholder('Brief description of the') + .nth(1) + .fill(federationSummary); + // Action Taken By any other RCRC movement actors + await page + .locator('label') + .filter({ hasText: actionFirst }) + .nth(2) + .click(); + await page + .locator('label') + .filter({ hasText: actionPsychosocial }) + .nth(2) + .click(); + await page + .locator('label') + .filter({ hasText: actionFood }) + .nth(2) + .click(); + await page + .getByPlaceholder('Brief description of the') + .nth(2) + .fill(rcrcSummary); + await page + .locator('label') + .filter({ hasText: informationBulletin }) + .click(); + await page.locator('textarea[name="actions_others"]').fill(actionOther); + await page.getByRole('button', { name: 'Continue' }).click(); + // Response Page + // DREF Requested + await page + .locator('label') + .filter({ hasText: interventionOptionOne }) + .nth(0) + .click(); + await page.locator('input[name="dref_amount"]').fill(drefRequested); + //Emergency Appeal + await page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(1) + .click(); + await page.locator('input[name="appeal_amount"]').fill(emergencyAppeal); + //Rapid Response Personnel + await page + .locator('label') + .filter({ hasText: interventionOptionThree }) + .nth(2) + .click(); + await page.locator('input[name="num_fact"]').fill(rapidResponse); + // Emergency Response Units + await page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(3) + .click(); + await page + .locator('input[name="num_ifrc_staff"]') + .fill(emergencyResponse); + // Originator + await page.locator('input[name="name"]').nth(0).fill(originatorName); + await page.locator('input[name="title"]').nth(0).fill(originatorTitle); + await page.locator('input[name="email"]').nth(0).fill(originatorEmail); + await page.locator('input[name="phone"]').nth(0).fill(originatorPhone); + // National Society Contact + await page.locator('input[name="name"]').nth(1).fill(nationalName); + await page.locator('input[name="title"]').nth(1).fill(nationalTitle); + await page.locator('input[name="email"]').nth(1).fill(nationalEmail); + await page.locator('input[name="phone"]').nth(1).fill(nationalPhone); + // IFRC Focal Point for the Emergency + await page.locator('input[name="name"]').nth(2).fill(ifrcName); + await page.locator('input[name="title"]').nth(2).fill(ifrcTitle); + await page.locator('input[name="email"]').nth(2).fill(ifrcEmail); + await page.locator('input[name="phone"]').nth(2).fill(ifrcPhone); + // Media Contact + await page.locator('input[name="name"]').nth(3).fill(mediaName); + await page.locator('input[name="title"]').nth(3).fill(mediaTitle); + await page.locator('input[name="email"]').nth(3).fill(mediaEmail); + await page.locator('input[name="phone"]').nth(3).fill(mediaPhone); + // Field report visible + await page + .locator('label') + .filter({ hasText: visibilityOptTwo }) + .click(); + await page.getByRole('button', { name: 'Submit' }).click(); + // Wait for redirection to field reports listing page + await page.waitForURL(/\/field-reports\/\d+/); + // Title Assertion + await expect(page.locator('h1')).toContainText( + `${newTitle} - ${title}`, + ); + // Data Assertion + await expect(page.getByRole('banner')).toContainText(disasterType); + await expect(page.getByRole('banner')).toContainText(country); + const frVisibility = page + .getByText('Visibility', { exact: true }) + .locator('..'); + await expect(frVisibility).toHaveText(`Visibility${visibilityOptTwo}`); + const frDate = page + .getByText('Start Date', { exact: true }) + .locator('..'); + await expect(frDate).toHaveText(`Start Date${date}`); + // Numeric Details Assertions + const numericDetails = [ + { label: 'Injured (RC)', value: formatNumber(numInjured) }, + { + label: 'Injured (Government)', + value: formatNumber(govNumInjured), + }, + { + label: 'Injured (Other)', + value: formatNumber(otherNumInjured), + }, + { label: 'Missing (RC)', value: formatNumber(numMissing) }, + { + label: 'Missing (Government)', + value: formatNumber(govNumMissing), + }, + { + label: 'Missing (Other)', + value: formatNumber(otherNumMissing), + }, + { label: 'Dead (RC)', value: formatNumber(numDead) }, + { label: 'Dead (Government)', value: formatNumber(govNumDead) }, + { label: 'Dead (Other)', value: formatNumber(otherNumDead) }, + { label: 'Displaced (RC)', value: formatNumber(numDisplaced) }, + { + label: 'Displaced (Government)', + value: formatNumber(govNumDisplaced), + }, + { + label: 'Displaced (Other)', + value: formatNumber(otherNumDisplaced), + }, + { label: 'Affected (RC)', value: formatNumber(numAffected) }, + { + label: 'Affected (Government)', + value: formatNumber(govNumAffected), + }, + { + label: 'Affected (Other)', + value: formatNumber(otherNumAffected), + }, + { label: 'Assisted (RC)', value: formatNumber(numAssisted) }, + { + label: 'Assisted (Government)', + value: formatNumber(govNumAssisted), + }, + { label: 'Local Staff', value: formatNumber(numLocalstaff) }, + { label: 'Volunteers', value: formatNumber(numVolunteers) }, + { label: 'IFRC Staff', value: formatNumber(emergencyResponse) }, + { label: 'Delegates', value: formatNumber(numExpatsDelegates) }, + ]; + for (const detail of numericDetails) { + const parentElement = page.getByText(detail.label).locator('..'); + const textContent = parentElement.nth(0); + await expect(textContent).toContainText(detail.value); + } + // Source Marked as Others Assertions + const sourceChild = page.getByText('Sources for data marked as Other', { + exact: true, + }); + const sourceParent = sourceChild + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(sourceParent).toContainText(otherSources); + // Request for Assistance Assertions + const govReq = page + .getByText('Government Requests International Assistance', { + exact: true, + }) + .locator('..'); + await expect(govReq).toHaveText( + `Government Requests International Assistance${govRequest}`, + ); + const nsReq = page + .getByText('NS Requests International Assistance', { exact: true }) + .locator('..'); + await expect(nsReq).toHaveText( + `NS Requests International Assistance${nationalSocietyRequest}`, + ); + // Information Bulletin Published Assertions + const infoBulletin = page.getByText('Information Bulletin Published', { + exact: true, + }); + const bulletin = infoBulletin + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(bulletin).toContainText(informationBulletin); + // Actions taken by National Society, Federation and RCRC Assertions + const sections = [ + { + childText: 'Actions taken by National Society', + actions: [ + actionHuman, + actionShelter, + actionEvacuation, + nationalSocietySummary, + ], + }, + { + childText: 'Actions Taken by Federation', + actions: [ + actionHealth, + actionShelter, + actionCamp, + federationSummary, + ], + }, + { + childText: 'Actions Taken by RCRC', + actions: [ + actionFirst, + actionPsychosocial, + actionFood, + rcrcSummary, + ], + }, + ]; + + for (const section of sections) { + const sectionChild = page.getByText(section.childText); + const sectionParent = sectionChild + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + + for (const action of section.actions) { + await expect(sectionParent).toContainText(action); + } + } + // Actions taken by others assertions + const actionParent = page.getByText('Actions taken by others', { + exact: true, + }); + const actionChild = actionParent + .locator('..') + .locator('..') + .locator('..') + .locator('..'); + await expect(actionChild).toContainText(actionOther); + // Planned Intervention Assertions + const drefPI = page.getByText('DREF', { exact: true }).locator('..'); + await expect(drefPI).toHaveText(`DREF${interventionOptionOne}`); + const emergencyPI = page + .getByText('Emergency Appeal', { exact: true }) + .locator('..'); + await expect(emergencyPI).toHaveText( + `Emergency Appeal${interventionOptionTwo}`, + ); + const rapidPI = page + .getByText('Rapid Response Personnel', { exact: true }) + .locator('..'); + await expect(rapidPI).toHaveText( + `Rapid Response Personnel${interventionOptionThree}`, + ); + const emergencyResponsePI = page + .getByText('Emergency Response Units', { exact: true }) + .locator('..'); + await expect(emergencyResponsePI).toHaveText( + `Emergency Response Units${interventionOptionTwo}`, + ); + + // Contacts Assertions + const details = [ + { + label: 'Originator', + name: originatorName, + title: originatorTitle, + email: originatorEmail, + phone: originatorPhone, + }, + { + label: 'NationalSociety', + name: nationalName, + title: nationalTitle, + email: nationalEmail, + phone: nationalPhone, + }, + { + label: 'Federation', + name: ifrcName, + title: ifrcTitle, + email: ifrcEmail, + phone: ifrcPhone, + }, + { + label: 'Media', + name: mediaName, + title: mediaTitle, + email: mediaEmail, + phone: mediaPhone, + }, + ]; + + for (const detail of details) { + const detailLocator = page + .getByText(detail.label, { exact: true }) + .locator('..'); + await expect(detailLocator).toContainText(detail.name); + await expect(detailLocator).toContainText(detail.title); + await expect(detailLocator).toContainText(detail.email); + await expect(detailLocator).toContainText(detail.phone); + } + await page.getByRole('link', { name: 'Edit Report' }).click(); + // Input Value Assertions + // Context Page + // Status + const statusValue = page + .locator('label') + .filter({ hasText: 'EventFirst report for this disaster' }); + await expect(statusValue).toBeChecked(); + // Assertions for Country, Region, Disaster Type, Date and Title value + const countryValue = page.locator('input[name="country"]'); + await expect(countryValue).toHaveValue(country); + const regionValue = page.locator('input[name="districts"]'); + await expect(regionValue).toHaveValue(district); + const disasterValue = page.locator('input[name="dtype"]'); + await expect(disasterValue).toHaveValue(disasterType); + const dateValue = page.locator('input[name="start_date"]'); + await expect(dateValue).toHaveValue(date); + const titleValue = page.getByPlaceholder('Example: Cyclone Cody'); + await expect(titleValue).toHaveValue(`${newTitle} - ${title}`); + // Government request international assistance + const govReqValue = page + .locator('label') + .filter({ hasText: govRequest }) + .nth(1); + await expect(govReqValue).toBeChecked(); + // National Society requests international assistance? + const nsReqValue = page + .locator('label') + .filter({ hasText: nationalSocietyRequest }) + .nth(2); + await expect(nsReqValue).toBeChecked(); + await page.getByRole('button', { name: 'Continue' }).click(); + // Situation Page + // Assertions for Numeric Details Value + const numericDetailValues = [ + { name: 'num_injured', value: numInjured }, + { name: 'gov_num_injured', value: govNumInjured }, + { name: 'other_num_injured', value: otherNumInjured }, + { name: 'num_dead', value: numDead }, + { name: 'gov_num_dead', value: govNumDead }, + { name: 'other_num_dead', value: otherNumDead }, + { name: 'num_missing', value: numMissing }, + { name: 'gov_num_missing', value: govNumMissing }, + { name: 'other_num_missing', value: otherNumMissing }, + { name: 'num_affected', value: numAffected }, + { name: 'gov_num_affected', value: govNumAffected }, + { name: 'other_num_affected', value: otherNumAffected }, + { name: 'num_displaced', value: numDisplaced }, + { name: 'gov_num_displaced', value: govNumDisplaced }, + { name: 'other_num_displaced', value: otherNumDisplaced }, + ]; + for (const { name, value } of numericDetailValues) { + const inputValue = page.locator(`input[name="${name}"]`); + await expect(inputValue).toHaveValue(value); + } + // Assertions for Source Details value + const sourceValue = page.locator('textarea[name="other_sources"]'); + await expect(sourceValue).toHaveValue(otherSources); + await page.getByRole('button', { name: 'Continue' }).click(); + // Actions Page + // Assertions for Actions taken Value + const assistedValues = [ + { name: 'gov_num_assisted', value: govNumAssisted }, + { name: 'num_assisted', value: numAssisted }, + { name: 'num_localstaff', value: numLocalstaff }, + { name: 'num_volunteers', value: numVolunteers }, + { name: 'num_expats_delegates', value: numExpatsDelegates }, + ]; + for (const { name, value } of assistedValues) { + const inputValue = page.locator(`input[name="${name}"]`); + await expect(inputValue).toHaveValue(value); + } + // Assertions for Actions Taken by National Society Red Cross Value + const nsActions = [actionHuman, actionShelter, actionEvacuation]; + for (const action of nsActions) { + const label = page + .locator('label') + .filter({ hasText: action }) + .nth(0); + await expect(label).toBeChecked(); + } + const nsValue = page + .getByPlaceholder('Brief description of the action') + .nth(0); + await expect(nsValue).toHaveText(nationalSocietySummary); + // Assertions for Actions Taken by IFRC Value + const ifrcActions = [actionHealth, actionShelter, actionCamp]; + for (const action of ifrcActions) { + const label = page + .locator('label') + .filter({ hasText: action }) + .nth(1); + await expect(label).toBeChecked(); + } + const ifrcValue = page + .getByPlaceholder('Brief description of the action') + .nth(1); + await expect(ifrcValue).toHaveText(federationSummary); + // Assertions for Actions Taken by RCRC Movements Value + const rcrcActions = [actionFirst, actionPsychosocial, actionFood]; + for (const action of rcrcActions) { + const label = page + .locator('label') + .filter({ hasText: action }) + .nth(2); + await expect(label).toBeChecked(); + } + const rcrcValue = page + .getByPlaceholder('Brief description of the action') + .nth(2); + await expect(rcrcValue).toHaveText(rcrcSummary); + // Assertions for Information Bulletin + const bulletinValue = page + .locator('label') + .filter({ hasText: informationBulletin }); + await expect(bulletinValue).toBeChecked(); + const actionsOtherValue = page.locator( + 'textarea[name="actions_others"]', + ); + await expect(actionsOtherValue).toHaveText(actionOther); + await page.getByRole('button', { name: 'Continue' }).click(); + // Response Page + // Assertions for Planned Assertions value + // DREF Requested + const drefValue = page + .locator('label') + .filter({ hasText: interventionOptionOne }) + .nth(0); + await expect(drefValue).toBeChecked(); + const drefSummaryValue = page.locator('input[name="dref_amount"]'); + await expect(drefSummaryValue).toHaveValue(drefRequested); + // Emergency Appeal + const emergencyAppealValue = page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(1); + await expect(emergencyAppealValue).toBeChecked(); + const emergencyAppealSummaryValue = page.locator( + 'input[name="appeal_amount"]', + ); + await expect(emergencyAppealSummaryValue).toHaveValue(emergencyAppeal); + // Rapid Response Personnel + const rapidResponseValue = page + .locator('label') + .filter({ hasText: interventionOptionThree }) + .nth(2); + await expect(rapidResponseValue).toBeChecked(); + const rapidResponseSummaryValue = page.locator( + 'input[name="num_fact"]', + ); + await expect(rapidResponseSummaryValue).toHaveValue(rapidResponse); + // Emergency Response Unit + const emergencyResponseValue = page + .locator('label') + .filter({ hasText: interventionOptionTwo }) + .nth(3); + await expect(emergencyResponseValue).toBeChecked(); + const emergencyResponseSummaryValue = page.locator( + 'input[name="num_ifrc_staff"]', + ); + await expect(emergencyResponseSummaryValue).toHaveValue( + emergencyResponse, + ); + // Contacts + // Assertion for Originator Contacts value + const originatorNameValue = page.locator('input[name="name"]').nth(0); + await expect(originatorNameValue).toHaveValue(originatorName); + const originatorTitleValue = page.locator('input[name="title"]').nth(0); + await expect(originatorTitleValue).toHaveValue(originatorTitle); + const originatorEmailValue = page.locator('input[name="email"]').nth(0); + await expect(originatorEmailValue).toHaveValue(originatorEmail); + const originatorPhoneValue = page.locator('input[name="phone"]').nth(0); + await expect(originatorPhoneValue).toHaveValue(originatorPhone); + // Assertion for Emergency Appeal values + const nationalNameValue = page.locator('input[name="name"]').nth(1); + await expect(nationalNameValue).toHaveValue(nationalName); + const nationalTitleValue = page.locator('input[name="title"]').nth(1); + await expect(nationalTitleValue).toHaveValue(nationalTitle); + const nationalEmailValue = page.locator('input[name="email"]').nth(1); + await expect(nationalEmailValue).toHaveValue(nationalEmail); + const nationalPhoneValue = page.locator('input[name="phone"]').nth(1); + await expect(nationalPhoneValue).toHaveValue(nationalPhone); + // Assertions for Rapid Response Personnel Values + const ifrcNameValue = page.locator('input[name="name"]').nth(2); + await expect(ifrcNameValue).toHaveValue(ifrcName); + const ifrcTitleValue = page.locator('input[name="title"]').nth(2); + await expect(ifrcTitleValue).toHaveValue(ifrcTitle); + const ifrcEmailValue = page.locator('input[name="email"]').nth(2); + await expect(ifrcEmailValue).toHaveValue(ifrcEmail); + const ifrcPhoneValue = page.locator('input[name="phone"]').nth(2); + await expect(ifrcPhoneValue).toHaveValue(ifrcPhone); + // Assertions for Emergency Response Units Values + const mediaNameValue = page.locator('input[name="name"]').nth(3); + await expect(mediaNameValue).toHaveValue(mediaName); + const mediaTitleValue = page.locator('input[name="title"]').nth(3); + await expect(mediaTitleValue).toHaveValue(mediaTitle); + const mediaEmailValue = page.locator('input[name="email"]').nth(3); + await expect(mediaEmailValue).toHaveValue(mediaEmail); + const mediaPhoneValue = page.locator('input[name="phone"]').nth(3); + await expect(mediaPhoneValue).toHaveValue(mediaPhone); + // Assertions for Field Report Visibility Value + const frVisibilityValue = page + .locator('label') + .filter({ hasText: visibilityOptTwo }); + await expect(frVisibilityValue).toBeChecked(); + }); +}); diff --git a/packages/e2e-tests/tests/field-report/fixtures/earlyWarning.json b/packages/e2e-tests/tests/field-report/fixtures/earlyWarning.json new file mode 100644 index 0000000000..8a1c660b62 --- /dev/null +++ b/packages/e2e-tests/tests/field-report/fixtures/earlyWarning.json @@ -0,0 +1,62 @@ +{ + "actionCash": "Cash & Voucher", + "actionEvacuation": "Evacuation", + "actionHealth": "Health", + "actionInteragency": "Interagency Coordination", + "actionMonitor": "Monitor Situation", + "actionMovement": "Movement Coordination", + "actionNfi": "NFI Distribution", + "actionOther": "This is Other Other Description", + "actionShelter": "Shelter", + "actionWash": "WASH", + "country": "Nepal", + "date": "2020-08-09", + "disasterType": "Biological Emergency", + "drefRequested": "500000", + "emergencyAppeal": "600000", + "emergencyResponse": "90000", + "fedSummary": "This is a brief description field", + "forecastAction": "60000", + "generalSummary": "Description of the field", + "govNumAssisted": "8000", + "govRequest": "Yes", + "ifrcEmail": "ankit@sir.com", + "ifrcName": "Ankit", + "ifrcPhone": "4444444444", + "ifrcTitle": "Developer", + "informationBulletin": "Planned", + "interventionOptionOne": "Requested", + "interventionOptionThree": "Completed", + "interventionOptionTwo": "Planned", + "likelyToBeAffectedGov": "36", + "likelyToBeAffectedOther": "686", + "likelyToBeAffectedRc": "566", + "mediaEmail": "shreya@ok.com", + "mediaName": "Shreya", + "mediaPhone": "6666666666", + "mediaTitle": "Shreya uffu", + "nationalEmail": "navin@theone.com", + "nationalName": "Navin", + "nationalPhone": "3333333333", + "nationalSocietyRequest": "Yes", + "nationalTitle": "Network Engineer", + "originatorEmail": "uday@sunshine.com", + "originatorName": "Uday", + "originatorPhone": "1122334455", + "originatorTitle": "Project Manager", + "peopleAtRiskGov": "45", + "peopleAtRiskOther": "758", + "peopleAtRiskRc": "678", + "potentiallyAffectedGov": "56", + "potentiallyAffectedOther": "65", + "potentiallyAffectedRc": "60", + "province": "Bagmati", + "rapidResponse": "400000", + "rcrcAssisted": "6876", + "rcrcSummary": "This is a RCRC description", + "riskAnalysis": "Content for risk analysis", + "sourceDetails": "Content for source content", + "title": "Biological early warning", + "visibilityOptOne": "RCRC Movement", + "visibilityOptTwo": "Public" +} diff --git a/packages/e2e-tests/tests/field-report/fixtures/epidemic.json b/packages/e2e-tests/tests/field-report/fixtures/epidemic.json new file mode 100644 index 0000000000..b1a4a40d66 --- /dev/null +++ b/packages/e2e-tests/tests/field-report/fixtures/epidemic.json @@ -0,0 +1,64 @@ +{ + "actionAid": "First aid", + "actionAmbulance": "Ambulance services for epidemic disease cases", + "actionOther": "The environment encompasses all living and non-living things, forming a complex and interconnected web that sustains life. It requires careful stewardship to ensure ecological balance and biodiversity", + "actionQuarantine": "Quarantine support", + "actionReadiness": "NS Institutional readiness", + "actionSanitation": "Sanitation provision", + "actionVaccination": "Vaccination", + "actionVector": "Vector control", + "actionVolunteer": "Volunteer Support", + "actionWater": "Water provision", + "cases": "100000", + "confirmedCases": "101000", + "country": "Nepal", + "date": "2024-02-19", + "description": "An epidemic occurs when an infectious disease rapidly spreads within a population, causing a significant increase in cases beyond what is normally expected in that area", + "disasterType": "Epidemic", + "drefRequested": "700000", + "emergencyAppeal": "800000", + "emergencyResponse": "90000", + "epiDate": "2024-02-23", + "epiNotes": "The English-Quotient (EQ) test measures your proficiency in the English language, including vocabulary, grammar, and comprehension. Designed for learners and professionals, it provides a benchmark for assessing language skills", + "federationSummary": "Quarantine is the isolation of individuals or groups to prevent the spread of contagious diseases, ensuring public health safety", + "formName": "Create Field Report", + "govNumAssisted": "8000", + "govRequest": "Yes", + "ifrcEmail": "aditya@gmail.com", + "ifrcName": "Aditya", + "ifrcPhone": "1111111111", + "ifrcTitle": "Cofounder", + "informationBulletin": "Planned", + "interventionOptionOne": "Requested", + "interventionOptionThree": "Completed", + "interventionOptionTwo": "Planned", + "mediaEmail": "shubh@qat.com", + "mediaName": "Shubh", + "mediaPhone": "2222222222", + "mediaTitle": "QAT", + "nationalEmail": "navin@theone.com", + "nationalName": "Navin", + "nationalPhone": "4444444444", + "nationalSocietyRequest": "No", + "nationalSocietySummary": "Food is any substance consumed to provide nutritional support, energy, and sustenance for living organisms, essential for survival", + "nationalTitle": "System Engineer", + "numAssisted": "7000", + "numDead": "2000", + "numExpatsDelegates": "4000", + "numLocalstaff": "6000", + "numVolunteers": "5000", + "originatorEmail": "keyur@sunshine.com", + "originatorName": "Keyur", + "originatorPhone": "3333333333", + "originatorTitle": "Project Manager", + "otherSources": "An epidemic is a rapid spread of infectious disease within a community, affecting a large number of individuals", + "probableCases": "200000", + "rapidResponse": "900000", + "rcrcSummary": "A volunteer is a person who offers their time and skills freely to help others or support a cause without payment", + "region": "Bagmati", + "source": "WHO", + "suspectedCases": "150000", + "title": "flu", + "visibilityOptOne": "RCRC Movement", + "visibilityOptTwo": "Public" +} diff --git a/packages/e2e-tests/tests/field-report/fixtures/event.json b/packages/e2e-tests/tests/field-report/fixtures/event.json new file mode 100644 index 0000000000..5bfec16d06 --- /dev/null +++ b/packages/e2e-tests/tests/field-report/fixtures/event.json @@ -0,0 +1,69 @@ +{ + "actionCamp": "Camp management", + "actionEvacuation": "Evacuation", + "actionFirst": "First Aid", + "actionFood": "Food aid", + "actionHealth": "Health", + "actionHuman": "Human remains management and identification", + "actionOther": "Who else was involved? UN agencies? NGOs? Government? Describe what other actors did. Also mention who the other actors are.", + "actionPsychosocial": "Psychosocial support services", + "actionShelter": "Shelter", + "country": "Albania", + "date": "2023-06-20", + "disasterType": "Biological Emergency", + "district": "Berat", + "drefRequested": "500000", + "emergencyAppeal": "600000", + "emergencyResponse": "90000", + "federationSummary": "A shelter provides safety and protection, offering a secure place for people or animals, often during emergencies or harsh conditions", + "formName": "Create Field Report", + "govNumAffected": "8000", + "govNumAssisted": "8000", + "govNumDead": "800", + "govNumDisplaced": "14000", + "govNumInjured": "4000", + "govNumMissing": "3000", + "govRequest": "Yes", + "ifrcEmail": "ankit@sir.com", + "ifrcName": "Ankit", + "ifrcPhone": "3333333333", + "ifrcTitle": "CEO", + "informationBulletin": "Planned", + "interventionOptionOne": "Requested", + "interventionOptionThree": "Completed", + "interventionOptionTwo": "Planned", + "mediaEmail": "shreya@ok.com", + "mediaName": "Shreya", + "mediaPhone": "4444444444", + "mediaTitle": "Shreya uffu", + "nationalEmail": "navin@theone.com", + "nationalName": "Navin", + "nationalPhone": "2222222222", + "nationalSocietyRequest": "No", + "nationalSocietySummary": "Evacuation is the urgent removal of people from dangerous areas to safety, often during natural disasters or emergencies", + "nationalTitle": "Network Engineer", + "numAffected": "12000", + "numAssisted": "7000", + "numDead": "1200", + "numDisplaced": "18000", + "numExpatsDelegates": "4000", + "numInjured": "8000", + "numLocalstaff": "6000", + "numMissing": "6000", + "numVolunteers": "5000", + "originatorEmail": "uday@sunshine.com", + "originatorName": "Uday", + "originatorPhone": "1111111111", + "originatorTitle": "Project Manager", + "otherNumAffected": "4000", + "otherNumDead": "500", + "otherNumDisplaced": "11000", + "otherNumInjured": "5000", + "otherNumMissing": "2500", + "otherSources": "The English-Quotient (EQ) test measures your proficiency in the English language, including vocabulary, grammar, and comprehension. Designed for learners and professionals, it provides a benchmark for assessing language skills", + "rapidResponse": "400000", + "rcrcSummary": "Food sustains life, providing essential nutrients and energy. It can be a source of pleasure, culture, and community, nourishing body and soul", + "title": "bio hazard", + "visibilityOptOne": "RCRC Movement", + "visibilityOptTwo": "Public" +} diff --git a/packages/e2e-tests/tests/loginLogout.spec.ts b/packages/e2e-tests/tests/loginLogout.spec.ts new file mode 100644 index 0000000000..cbb4f2ca3f --- /dev/null +++ b/packages/e2e-tests/tests/loginLogout.spec.ts @@ -0,0 +1,31 @@ +import { expect, test } from '@playwright/test'; +import { login } from '#utils/auth'; + +test.describe('Authentication Tests', () => { + test('should login', async ({ page }) => { + await login( + page, + process.env.PLAYWRIGHT_USER_EMAIL, + process.env.PLAYWRIGHT_USER_PASSWORD, + ); + await page.waitForURL('/'); + await expect( + page.getByRole('button', { name: 'Create a Report' }), + ).toBeVisible(); + }); + + test('should logout', async ({ page }) => { + await login( + page, + process.env.PLAYWRIGHT_USER_EMAIL, + process.env.PLAYWRIGHT_USER_PASSWORD, + ); + await page.waitForURL('/'); + const name = process.env.PLAYWRIGHT_USER_EMAIL.match(/^[^@]+(?=@)/); + await page.getByRole('button', { name: name[0] }).click(); + await page.getByRole('button', { name: 'Logout' }).click(); + await page.getByRole('button', { name: 'Ok' }).click(); + await expect(page.getByRole('navigation')).toContainText('Login'); + await expect(page.getByRole('navigation')).toContainText('Register'); + }); +}); diff --git a/packages/e2e-tests/tsconfig.json b/packages/e2e-tests/tsconfig.json new file mode 100644 index 0000000000..bb1f38a367 --- /dev/null +++ b/packages/e2e-tests/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "#utils/*": ["./utils/*"], + }, + "esModuleInterop": true, + "resolveJsonModule": true, + }, + "include": ["tests"] +} diff --git a/packages/e2e-tests/utils/auth.ts b/packages/e2e-tests/utils/auth.ts new file mode 100644 index 0000000000..8e79693299 --- /dev/null +++ b/packages/e2e-tests/utils/auth.ts @@ -0,0 +1,19 @@ +import { type Page, expect } from '@playwright/test'; +export async function login(page: Page, username: string, password: string) { + await page.goto('/login'); + + //FIXME: page.fill is discouraged. We should use locator based fill. + // @ifrc/go-ui should be updated to support locators + await page.locator('input[name="username"]').fill(username); + await page.locator('input[name="password"]').fill(password); + + await page.getByRole('button', { name: 'Login' }).click(); + // Wait until the page receives the cookies. + // Sometimes login flow sets cookies in the process of several redirects. + // Wait for the final URL to ensure that the cookies are actually set. + await page.waitForURL('/'); + // Alternatively, you can wait until the page reaches a state where all cookies are set. + await expect( + page.getByRole('button', { name: 'Create a Report' }), + ).toBeVisible(); +} diff --git a/packages/e2e-tests/utils/common.ts b/packages/e2e-tests/utils/common.ts new file mode 100644 index 0000000000..bc1893de0a --- /dev/null +++ b/packages/e2e-tests/utils/common.ts @@ -0,0 +1,89 @@ +import { + isDefined, + isFalsyString, + isNotDefined, + isTruthyString, +} from '@togglecorp/fujs'; + +function getMaximumFractionDigits(value: number) { + if (value < 1000) { + return 2; + } + + const formatter = new Intl.NumberFormat('default', { notation: 'compact' }); + const formattedParts = formatter.formatToParts(value); + const fraction = formattedParts.find(({ type }) => type === 'fraction'); + + if (isNotDefined(fraction) || isFalsyString(fraction.value)) { + return 0; + } + + if (Number(fraction.value) > 0.1) { + return 1; + } + + return 0; +} + +interface FormatNumberOptions { + currency?: boolean; + unit?: Intl.NumberFormatOptions['unit']; + maximumFractionDigits?: Intl.NumberFormatOptions['maximumFractionDigits']; + compact?: boolean; + separatorHidden?: boolean; + language?: string; +} + +export function formatNumber( + value: number | string, + options?: FormatNumberOptions, +) { + const formattingOptions: Intl.NumberFormatOptions = {}; + + const safeNumber = typeof value === 'string' ? Number(value) : value; + + if (isNotDefined(options)) { + formattingOptions.maximumFractionDigits = + getMaximumFractionDigits(safeNumber); + return new Intl.NumberFormat('default', formattingOptions).format( + safeNumber, + ); + } + + const { + currency, + unit, + maximumFractionDigits, + compact, + separatorHidden, + language, + } = options; + + if (isTruthyString(unit)) { + formattingOptions.unit = unit; + formattingOptions.unitDisplay = 'short'; + } + if (currency) { + formattingOptions.currencyDisplay = 'narrowSymbol'; + formattingOptions.style = 'currency'; + } + if (compact) { + formattingOptions.notation = 'compact'; + formattingOptions.compactDisplay = 'short'; + } + + formattingOptions.useGrouping = !separatorHidden; + + if (isDefined(maximumFractionDigits)) { + formattingOptions.maximumFractionDigits = maximumFractionDigits; + } else { + formattingOptions.maximumFractionDigits = + getMaximumFractionDigits(safeNumber); + } + + const newValue = new Intl.NumberFormat(language, formattingOptions).format( + safeNumber, + ); + + return newValue; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08f914a331..a8841f379b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,7 +30,7 @@ importers: version: 2.27.10 knip: specifier: ^5.36.3 - version: 5.36.3(@types/node@20.14.11)(typescript@5.6.3) + version: 5.36.3(@types/node@20.14.11)(typescript@5.7.2) app: dependencies: @@ -261,6 +261,28 @@ importers: specifier: ^17.7.2 version: 17.7.2 + packages/e2e-tests: + dependencies: + '@togglecorp/fujs': + specifier: ^2.1.1 + version: 2.1.1 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + devDependencies: + '@biomejs/biome': + specifier: 1.7.3 + version: 1.7.3 + '@playwright/test': + specifier: ^1.49.1 + version: 1.49.1 + '@types/node': + specifier: ^20.14.11 + version: 20.14.11 + typescript: + specifier: ^5.7.2 + version: 5.7.2 + packages/go-ui-storybook: dependencies: '@ifrc-go/icons': @@ -1691,6 +1713,94 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@biomejs/biome@1.7.3: + resolution: {integrity: sha512-ogFQI+fpXftr+tiahA6bIXwZ7CSikygASdqMtH07J2cUzrpjyTMVc9Y97v23c7/tL1xCZhM+W9k4hYIBm7Q6cQ==} + engines: {node: '>=14.21.3'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.7.3 + '@biomejs/cli-darwin-x64': 1.7.3 + '@biomejs/cli-linux-arm64': 1.7.3 + '@biomejs/cli-linux-arm64-musl': 1.7.3 + '@biomejs/cli-linux-x64': 1.7.3 + '@biomejs/cli-linux-x64-musl': 1.7.3 + '@biomejs/cli-win32-arm64': 1.7.3 + '@biomejs/cli-win32-x64': 1.7.3 + dev: true + + /@biomejs/cli-darwin-arm64@1.7.3: + resolution: {integrity: sha512-eDvLQWmGRqrPIRY7AIrkPHkQ3visEItJKkPYSHCscSDdGvKzYjmBJwG1Gu8+QC5ed6R7eiU63LEC0APFBobmfQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-darwin-x64@1.7.3: + resolution: {integrity: sha512-JXCaIseKRER7dIURsVlAJacnm8SG5I0RpxZ4ya3dudASYUc68WGl4+FEN03ABY3KMIq7hcK1tzsJiWlmXyosZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64-musl@1.7.3: + resolution: {integrity: sha512-c8AlO45PNFZ1BYcwaKzdt46kYbuP6xPGuGQ6h4j3XiEDpyseRRUy/h+6gxj07XovmyxKnSX9GSZ6nVbZvcVUAw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64@1.7.3: + resolution: {integrity: sha512-phNTBpo7joDFastnmZsFjYcDYobLTx4qR4oPvc9tJ486Bd1SfEVPHEvJdNJrMwUQK56T+TRClOQd/8X1nnjA9w==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64-musl@1.7.3: + resolution: {integrity: sha512-UdEHKtYGWEX3eDmVWvQeT+z05T9/Sdt2+F/7zmMOFQ7boANeX8pcO6EkJPK3wxMudrApsNEKT26rzqK6sZRTRA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64@1.7.3: + resolution: {integrity: sha512-vnedYcd5p4keT3iD48oSKjOIRPYcjSNNbd8MO1bKo9ajg3GwQXZLAH+0Cvlr+eMsO67/HddWmscSQwTFrC/uPA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-arm64@1.7.3: + resolution: {integrity: sha512-unNCDqUKjujYkkSxs7gFIfdasttbDC4+z0kYmcqzRk6yWVoQBL4dNLcCbdnJS+qvVDNdI9rHp2NwpQ0WAdla4Q==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-x64@1.7.3: + resolution: {integrity: sha512-ZmByhbrnmz/UUFYB622CECwhKIPjJLLPr5zr3edhu04LzbfcOrz16VYeNq5dpO1ADG70FORhAJkaIGdaVBG00w==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@changesets/apply-release-plan@7.0.6: resolution: {integrity: sha512-TKhVLtiwtQOgMAC0fCJfmv93faiViKSDqr8oMEqrnNs99gtSC1sZh/aEMS9a+dseU1ESZRCK+ofLgGY7o0fw/Q==} dependencies: @@ -3063,6 +3173,14 @@ packages: requiresBuild: true optional: true + /@playwright/test@1.49.1: + resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} + engines: {node: '>=18'} + hasBin: true + dependencies: + playwright: 1.49.1 + dev: true + /@poppinss/cliui@6.4.1: resolution: {integrity: sha512-tdV3QpAfrPFRLPOh98F8QxWBvwYF3ziWGGtpVqfZtFNTFkC7nQnVQlUW55UtQ7rkeMmFohxfDI+2JNWScGJ1jQ==} engines: {node: '>=18.16.0'} @@ -8838,6 +8956,14 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -9998,7 +10124,7 @@ packages: engines: {node: '>=6'} dev: true - /knip@5.36.3(@types/node@20.14.11)(typescript@5.6.3): + /knip@5.36.3(@types/node@20.14.11)(typescript@5.7.2): resolution: {integrity: sha512-pjHOGbaa09317IvnNU7EsSA8mXhBrY+A0LnrCOcRLPx5rZVmg10cVkxaoiZP7EKuA6vo0HHQ0I/VkJiUbq6d/w==} engines: {node: '>=18.6.0'} hasBin: true @@ -10021,7 +10147,7 @@ packages: smol-toml: 1.3.0 strip-json-comments: 5.0.1 summary: 2.1.0 - typescript: 5.6.3 + typescript: 5.7.2 zod: 3.23.8 zod-validation-error: 3.4.0(zod@3.23.8) dev: true @@ -11130,6 +11256,22 @@ packages: pathe: 1.1.2 dev: true + /playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + engines: {node: '>=18'} + hasBin: true + dev: true + + /playwright@1.49.1: + resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} + engines: {node: '>=18'} + hasBin: true + dependencies: + playwright-core: 1.49.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /pn@1.1.0: resolution: {integrity: sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==} dev: true @@ -13545,8 +13687,8 @@ packages: hasBin: true dev: true - /typescript@5.6.3: - resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + /typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} hasBin: true dev: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9fd6eedd81..76ec51d045 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,3 +2,4 @@ packages: - "app" - "packages/ui" - "packages/go-ui-storybook" + - "packages/e2e-tests"