From aac2593702b8bb283a54ceb8178eaf032f10b1a3 Mon Sep 17 00:00:00 2001 From: Morgan Ney Date: Thu, 18 Jan 2024 13:05:16 -0600 Subject: [PATCH] test: add more e2e specs. (#179) --- .github/workflows/e2e.yml | 15 ++++- Dockerfile | 6 ++ compose.override.yaml | 24 +++++++ copilot/api/addons/bm-cluster.yml | 4 +- package.json | 3 +- playwright.config.ts | 101 +++++++++++++--------------- tests/basic.spec.ts | 6 -- tests/geolocation/deny.spec.ts | 20 ++++++ tests/geolocation/omnitrans.spec.ts | 40 +++++++++++ tests/geolocation/ttc.spec.ts | 39 +++++++++++ tests/selector/agencyStop.spec.ts | 15 +++++ 11 files changed, 208 insertions(+), 65 deletions(-) delete mode 100644 tests/basic.spec.ts create mode 100644 tests/geolocation/deny.spec.ts create mode 100644 tests/geolocation/omnitrans.spec.ts create mode 100644 tests/geolocation/ttc.spec.ts create mode 100644 tests/selector/agencyStop.spec.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f3c7df9..cc377fa 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -2,6 +2,12 @@ name: E2E Tests on: workflow_dispatch: + inputs: + testPath: + description: 'Files to test.' + required: true + default: 'tests' + type: string jobs: test: @@ -34,7 +40,7 @@ jobs: - name: Install Playwright run: npx playwright install --with-deps - name: Run Tests - run: npm test + run: npm test -- ${{ inputs.testPath }} env: CI: true SERVER_NAME: localhost @@ -52,3 +58,10 @@ jobs: BM_POSTGRES_DB: ${{ secrets.BM_POSTGRES_DB }} SSO_GOOG_CLIENT_ID: ${{ secrets.SSO_GOOG_CLIENT_ID }} SSO_GOOG_CLIENT_SECRET: ${{ secrets.SSO_GOOG_CLIENT_SECRET }} + - name: Upload Test Results + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: test-results + retention-days: 15 diff --git a/Dockerfile b/Dockerfile index 82380a2..d4beece 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,5 +53,11 @@ COPY packages/web/templates/core/upstreams.conf.template /etc/nginx/templates/co COPY packages/web/nginx.dev.conf /etc/nginx/nginx.conf EXPOSE 80 443 +FROM builder AS playwright +RUN apt-get update +RUN apt-get install -y vim +RUN npx playwright install +RUN npx playwright install-deps + FROM adminer:4.8.1 as adminer EXPOSE 8080 diff --git a/compose.override.yaml b/compose.override.yaml index 73c6076..83150b7 100644 --- a/compose.override.yaml +++ b/compose.override.yaml @@ -46,6 +46,30 @@ services: - dev - -w - ui + playwright: + depends_on: + - api + build: + context: . + target: playwright + container_name: playwright + volumes: + - ./packages/ui:/app/packages/ui + - ./packages/common/dist:/app/packages/common/dist + - ./packages/components/dist:/app/packages/components/dist + - ./tests:/app/tests + - ./playwright.config.ts:/app/playwright.config.ts + - ./.env:/app/.env # used to inject VITE_ env vars + ports: + - '5173:5173' + environment: + API_HOST: ${API_HOST} + command: + - npm + - run + - dev + - -w + - ui storybook: build: context: . diff --git a/copilot/api/addons/bm-cluster.yml b/copilot/api/addons/bm-cluster.yml index 118c613..604ed54 100644 --- a/copilot/api/addons/bm-cluster.yml +++ b/copilot/api/addons/bm-cluster.yml @@ -23,11 +23,11 @@ Mappings: bmclusterEnvScalingConfigurationMap: test: 'DBMinCapacity': 2 # AllowedValues: [2, 4, 8, 16, 32, 64, 192, 384] - 'DBMaxCapacity': 4 # AllowedValues: [2, 4, 8, 16, 32, 64, 192, 384] + 'DBMaxCapacity': 2 # AllowedValues: [2, 4, 8, 16, 32, 64, 192, 384] All: 'DBMinCapacity': 2 # AllowedValues: [2, 4, 8, 16, 32, 64, 192, 384] - 'DBMaxCapacity': 4 # AllowedValues: [2, 4, 8, 16, 32, 64, 192, 384] + 'DBMaxCapacity': 2 # AllowedValues: [2, 4, 8, 16, 32, 64, 192, 384] Resources: bmclusterDBSubnetGroup: diff --git a/package.json b/package.json index 3297c00..bc3b3d9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "clean:all": "npm run clean --workspaces --include-workspace-root", "cycles": "npm run cycles --workspaces", "prettier": "prettier . -w", - "lint": "npm run lint --workspaces", + "lint": "npm run lint:tests && npm run lint --workspaces", + "lint:tests": "eslint . tests --ext .ts,.tsx --max-warnings 0", "build:deps": "npm run build -w @busmap/components && npm run build -w @busmap/common", "test": "playwright test --reporter=list" }, diff --git a/playwright.config.ts b/playwright.config.ts index 7742d4f..0652320 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,11 +1,5 @@ import { defineConfig, devices } from '@playwright/test' -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - /** * See https://playwright.dev/docs/test-configuration. */ @@ -16,7 +10,7 @@ export default defineConfig({ /* 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, + retries: process.env.CI ? 1 : 0, /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ @@ -24,61 +18,58 @@ export default defineConfig({ /* 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: 'https://localhost', + baseURL: process.env.TEST_BASE_URL ?? 'https://localhost', /* 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 - ? [ - /** - * Ubuntu and mkcert issues with Chrome / Firefox - * @see https://github.com/FiloSottile/mkcert/issues/447 - * - * Other option is to use macos GitHub action runner and - * install docker, etc. Could also, use `ignoreHTTPSErrors` - * from playwright in the test specs to cover more browsers. - */ - { - name: 'webkit', - use: { ...devices['Desktop Safari'] } - }, - - /* Test against mobile viewports. */ - { - name: 'Mobile Safari', - use: { ...devices['iPhone 12'] } - } - ] - : [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] } - }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] } - }, + projects: [ + /** + * Ubuntu and mkcert issues with Chrome / Firefox + * @see https://github.com/FiloSottile/mkcert/issues/447 + * + * Other option is to use macos GitHub action runner and + * install docker, etc. Could also, use `ignoreHTTPSErrors` + * from playwright in the test specs to cover more browsers. + */ + { + name: 'webkit', + use: { ...devices['Desktop Safari'] } + }, + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + ignoreHTTPSErrors: Boolean(process.env.CI) + } + }, - /* Test against mobile viewports. */ - { - name: 'Mobile Chrome', - use: { ...devices['Pixel 5'] } - }, - { - name: 'Mobile Safari', - use: { ...devices['iPhone 12'] } - } - ], + /* Test against mobile viewports. */ + { + name: 'Mobile Chrome', + use: { + ...devices['Pixel 5'], + ignoreHTTPSErrors: Boolean(process.env.CI) + } + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] } + } + ], /* Run your local dev server before starting the tests */ - webServer: { - command: 'docker compose -f compose.yaml up --attach-dependencies stage', - ignoreHTTPSErrors: true, - url: 'https://localhost/healthcheck', - reuseExistingServer: !process.env.CI, - timeout: 60_000 * 7 - } + webServer: process.env.TEST_BASE_URL + ? undefined + : { + command: process.env.CI + ? 'docker compose -f compose.yaml up --attach-dependencies stage' + : 'docker compose up --attach-dependencies dev', + ignoreHTTPSErrors: true, + url: 'https://localhost/healthcheck', + reuseExistingServer: !process.env.CI, + timeout: 60_000 * 7 + } }) diff --git a/tests/basic.spec.ts b/tests/basic.spec.ts deleted file mode 100644 index dea9bf1..0000000 --- a/tests/basic.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { test, expect } from '@playwright/test' - -test('has title', async ({ page }) => { - await page.goto('/') - await expect(page).toHaveTitle(/Busmap/) -}) diff --git a/tests/geolocation/deny.spec.ts b/tests/geolocation/deny.spec.ts new file mode 100644 index 0000000..ecc6ed7 --- /dev/null +++ b/tests/geolocation/deny.spec.ts @@ -0,0 +1,20 @@ +import { test, expect } from '@playwright/test' + +test.beforeEach(async ({ context }) => { + await context.clearPermissions() +}) + +test('Deny geolocation use selector to find stop.', async ({ page }) => { + await page.goto('/') + await expect( + page.getByText('Location permission denied. Check your OS or browser settings.') + ).toBeVisible() + await page.getByRole('listitem', { name: 'Selector' }).getByRole('button').click() + await expect(page.getByRole('heading', { name: 'Bus Selector' })).toBeVisible() + await page.getByText('Agency').click() + await page.getByPlaceholder('Agencies ... (64)').fill('toronto') + await page.getByRole('option', { name: 'Toronto Transit Commission' }).click() + await page.getByText('Stop', { exact: true }).click() + await page.getByRole('option', { name: 'Bathurst St At Neptune Dr' }).click() + await expect(page.getByRole('heading', { name: 'Next Arrivals' })).toBeVisible() +}) diff --git a/tests/geolocation/omnitrans.spec.ts b/tests/geolocation/omnitrans.spec.ts new file mode 100644 index 0000000..6cb9130 --- /dev/null +++ b/tests/geolocation/omnitrans.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test' + +test.use({ + geolocation: { + latitude: 34.10865, + longitude: -117.325531 + }, + permissions: ['geolocation'] +}) + +test('test', async ({ page, context }) => { + await context.grantPermissions(['geolocation']) + // Navigate to the homepage and select the Nearby stops tab, check for OmniTrans + await page.goto('/') + await page.getByRole('listitem', { name: 'Nearby' }).getByRole('button').click() + await expect(page.getByRole('heading', { name: 'Nearby Stops' })).toBeVisible() + await expect(page.getByRole('heading', { name: 'Omnitrans' })).toBeVisible({ + timeout: 10_000 + }) + + // Check that route headings are visible + await expect(page.getByRole('heading', { level: 4 }).first()).toBeVisible() + + // Get the stop title link and save the innerText + const link = page.getByRole('heading', { level: 5 }).first().getByRole('link') + const linkText = await link.innerText() + + // Click the stop link and check that predictions are rendered with users distance from stop + await link.click() + await expect(page.getByRole('heading', { name: 'Next Arrivals' })).toBeVisible({ + timeout: 7_000 + }) + await expect(page.getByText(/You are .+ miles away/)).toBeVisible() + + // Close the flyout menu and check the map for user and stop markers + await page.locator('button').filter({ hasText: 'Close' }).click() + await page.getByRole('button', { name: 'Marker' }).click() + await expect(page.locator('.leaflet-marker-icon').first()).toBeVisible() + await expect(page.locator('#map').getByText(linkText)).toBeVisible() +}) diff --git a/tests/geolocation/ttc.spec.ts b/tests/geolocation/ttc.spec.ts new file mode 100644 index 0000000..de873ff --- /dev/null +++ b/tests/geolocation/ttc.spec.ts @@ -0,0 +1,39 @@ +import { test, expect } from '@playwright/test' + +test.use({ + geolocation: { + latitude: 43.69786, + longitude: -79.39712 + }, + permissions: ['geolocation'] +}) + +test('Nearby stops for Toronto Transit Commission.', async ({ page }) => { + await page.goto('/') + await page.getByRole('listitem', { name: 'Nearby' }).getByRole('button').click() + await expect(page.getByRole('heading', { name: 'Nearby Stops' })).toBeVisible() + await expect(page.getByText('Monitoring your location.')).toBeVisible() + await expect(page.getByRole('heading', { name: 'Toronto TTC' })).toBeVisible({ + timeout: 10_000 + }) + + // Check that route headings are visible + await expect(page.getByRole('heading', { level: 4 }).first()).toBeVisible() + + // Get the stop title link and save the innerText + const link = page.getByRole('heading', { level: 5 }).first().getByRole('link') + const linkText = await link.innerText() + + // Click the stop link and check that predictions are rendered with users distance from stop + await link.click() + await expect(page.getByRole('heading', { name: 'Next Departures' })).toBeVisible({ + timeout: 7_000 + }) + await expect(page.getByText(/You are .+ miles away/)).toBeVisible() + + // Close the flyout menu and check the map for user and stop markers + await page.locator('button').filter({ hasText: 'Close' }).click() + await page.getByRole('button', { name: 'Marker' }).click() + await expect(page.locator('.leaflet-marker-icon').first()).toBeVisible() + await expect(page.locator('#map').getByText(linkText)).toBeVisible() +}) diff --git a/tests/selector/agencyStop.spec.ts b/tests/selector/agencyStop.spec.ts new file mode 100644 index 0000000..26eecce --- /dev/null +++ b/tests/selector/agencyStop.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '@playwright/test' + +test('Select an agency and stop to see arrivals.', async ({ page }) => { + await page.goto('/') + await page.getByRole('listitem', { name: 'Selector' }).getByRole('button').click() + await expect(page.getByRole('heading', { name: 'Bus Selector' })).toBeVisible() + await page.getByText('Agency').click() + await page.getByRole('option', { name: 'Toronto Transit Commission' }).click() + await page.getByText('Stop', { exact: true }).click() + await page.getByRole('option', { name: 'Bathurst St At Neptune Dr' }).click() + await expect(page.getByRole('heading', { name: 'Next Arrivals' })).toBeVisible() + await page.locator('button').filter({ hasText: 'Close' }).click() + await page.getByRole('button', { name: 'Marker' }).click() + await page.locator('#map').getByText('Bathurst St At Neptune Dr').click() +})