Skip to content

Commit

Permalink
196 e2e testing (#230)
Browse files Browse the repository at this point in the history
* Add Dockerfile for end-to-end testing

A new Dockerfile, Dockerfile.e2e, has been added to set up end-to-end testing in a docker environment. In this Dockerfile, a container with Node.js and playwright preinstalled is initialized and the project dependencies are installed. The README.md has also been updated to include instructions for building and running this Dockerfile in a docker container.

* Iterate on Dockerfile to expose port and limit what is being run on startup

* Add nginx for static file serving

* Use node for file serving

* match docker container version with playwright version

* update readme

* initialize playwright in project

* Add 'slug' property to Route type in FormManager to facilitate testing

* First real e2e test in project

* Change pattern dropdown to use buttons for a11y and e2e testing reasons.

* add constants for testing purposes

* add debugging info for playwright tests

* make uuid pattern regex reusable and build out the add questions test

* rename file

* remove redundant classname

* Create a test for the add questions and get ready for drag-drop testing once bug is fixed

* Dry out end to end tests with reusable functions.

* Add multi-stage build for e2e test docker image.

* update gh actions to include playwright

* update playwright action

* update playwright tests and docker files to set and use env variable

* update playwright test and URL

* remove build step

* update tests with simple 200 check and add instructions to readme

* Move root route test to MyForms file

* Expose port for testing purposes

* remove local storage testing. ci/cd was eating the localStorage window object

* test run on deploy

* update env var name

* Revert "update env var name"

This reverts commit d7dd8b0.

* Revert "test run on deploy"

This reverts commit 2a4bfb7.

* add husky to project

* add test to precommit

* remove example tests file added by playwright scaffolding command

* move files out of project root and into e2e package

* dry up dockerfile

* reorganize docker and tests for e2e

* move playwright config file and update gitignore

* bump up concurrency

* install new dependencies and correct playwright directory

* update gh action

* remove slug from tests

* remove slug from routes
  • Loading branch information
ethangardner authored Jul 1, 2024
1 parent 8925ec4 commit 701d4eb
Show file tree
Hide file tree
Showing 16 changed files with 14,879 additions and 11,321 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/_playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Playwright Tests
on:
# push:
# branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
end_to_end:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm install -g pnpm && pnpm install
- name: Install Playwright Browsers
run: pnpm --filter=end-to-end-tests exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm --filter=end-to-end-tests exec playwright test
env:
E2E_ENDPOINT: ${{ secrets.PULL_REQUEST_PREVIEW_URL }}/${{ github.head_ref }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
5 changes: 5 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ jobs:
secrets: inherit
with:
deploy-env: ${{ github.ref_name }}

e2e:
needs: [deploy]
uses: ./.github/workflows/_playwright.yml
secrets: inherit
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ node_modules/
NOTES.md
tsconfig.tsbuildinfo
*storybook.log
packages/form-service
packages/form-service
/e2e/test-results/
/e2e/playwright-report/
/e2e/blob-report/
/e2e/playwright/.cache/
3 changes: 3 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pnpm lint
pnpm format
pnpm test
22 changes: 22 additions & 0 deletions e2e/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# base image with Node.js and playwright preinstalled
FROM mcr.microsoft.com/playwright:v1.43.1-jammy as base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
ENV NODE_ENV=test
WORKDIR /srv/apps/atj-platform
COPY . .
RUN corepack enable
RUN pnpm install --filter=@atj/spotlight --frozen-lockfile

FROM base as test
ENV E2E_ENDPOINT=http://localhost:9090
RUN npm install -g serve
EXPOSE 9090
RUN pnpm build --filter=@atj/spotlight
WORKDIR ./e2e
RUN serve ../apps/spotlight/dist -p 9090 & sleep 5; pnpm playwright test;

FROM base as serve
ENV E2E_ENDPOINT=http://localhost:4321
EXPOSE 4321 9292 9323
CMD ["pnpm", "dev", "--filter=@atj/spotlight", "--", "--host"]
34 changes: 34 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# End-to-end testing
E2E testing runs in a docker container.

```bash
# run from project root
docker build --tag 'playwright' . -f ./e2e/Dockerfile
```

To see the output of the tests and run everything when the docker container is built, run the command below:
```bash
# run from project root
docker build --tag 'playwright' . -f ./e2e/Dockerfile --progress=plain --target test
```
You can add the `--no-cache` flag to build from scratch.

To run the container (best for development):

```bash
# run from project root
docker run -p 4321:4321 -it --name e2e --rm playwright
```

```bash
# run from project root
docker exec -it e2e pnpm playwright test
```

### Debugging
To debug and follow the flow of a test in a browser, you can run:

```bash
# run from this directory
export E2E_ENDPOINT=http://localhost:4321; pnpm playwright test --ui-port=8080 --ui-host=0.0.0.0
```
12 changes: 12 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "end-to-end-tests",
"version": "1.0.0",
"scripts": {
"dev": "tsc -w",
"test": "export E2E_ENDPOINT=http://localhost:4321; pnpm playwright test --ui-port=8080 --ui-host=0.0.0.0"
},
"devDependencies": {
"@playwright/test": "^1.43.1",
"path-to-regexp": "^7.0.0"
}
}
78 changes: 78 additions & 0 deletions e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { defineConfig, devices } from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'pnpm dev --filter=@atj/spotlight',
// url: 'http://localhost:4321',
// reuseExistingServer: !process.env.CI,
// },
});
1 change: 1 addition & 0 deletions e2e/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BASE_URL = process.env.E2E_ENDPOINT;
52 changes: 52 additions & 0 deletions e2e/src/create.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { test, expect, Page } from '@playwright/test';
import { GuidedFormCreation, Create } from '../../packages/design/src/FormManager/routes';
import { BASE_URL } from './constants';
import { pathToRegexp } from 'path-to-regexp';


const createNewForm = async (page: Page) => {
await page.goto(`${BASE_URL}/${GuidedFormCreation.getUrl()}`);
await page.getByRole('button', { name: 'Create New' }).click();
}

test('Create form from scratch', async ({ page }) => {
const regexp = pathToRegexp(Create.path);
await createNewForm(page);
let pageUrl = page.url();
let pagePath = '';

if(Create.getUrl().indexOf('#') === 0) {
const parts = pageUrl.split('#');
if(parts.length === 2) {
pagePath = parts[1];
}
} else {
pagePath = new URL(pageUrl).pathname;
}

expect(regexp.test(pagePath)).toBe(true);
});

test('Add questions', async ({ page }) => {
await createNewForm(page);

const menuButton = page.getByRole('button', { name: 'Question' });
await menuButton.click();
await page.getByRole('button', { name: 'Short Answer' }).click();
await menuButton.click();
await page.getByRole('button', { name: 'Radio Buttons' }).click();

// Create locators for both elements
const fields = page.locator('.usa-label');
const element1 = fields.filter({ hasText: 'Field Label' })
const element2 = fields.filter({ hasText: 'Radio group label' });
expect(element1.first()).toBeTruthy();
expect(element2.first()).toBeTruthy();

const htmlContent = await page.content();

const element1Index = htmlContent.indexOf((await element1.textContent() as string));
const element2Index = htmlContent.indexOf((await element2.textContent() as string));
expect(element1Index).toBeLessThan(element2Index);

});
8 changes: 8 additions & 0 deletions e2e/src/my-forms.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { test, expect } from '@playwright/test';
import { MyForms } from '../../packages/design/src/FormManager/routes';
import { BASE_URL } from './constants';

test('Go to MyForms', async ({ page }) => {
const response = await page.goto(`${BASE_URL}/${MyForms.getUrl()}`);
expect(response?.ok()).toBe(true);
});
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
"scripts": {
"build": "turbo run build --filter=!infra",
"clean": "turbo run clean",
"dev": "turbo run dev --concurrency 12",
"dev": "turbo run dev --concurrency 14",
"format": "prettier --write \"packages/*/src/**/*.{js,jsx,ts,tsx,scss}\"",
"lint": "turbo run lint",
"pages": "rm -rf node_modules && npm i -g pnpm turbo && pnpm i && pnpm build && ln -sf ./apps/spotlight/dist _site",
"test": "vitest run --coverage.enabled --coverage.reporter=text --coverage.reporter=json-summary --coverage.reporter=json --coverage.reportOnFailure --reporter vitest-github-actions-reporter",
"test:infra": "turbo run --filter=infra test",
"typecheck": "tsc --build"
"typecheck": "tsc --build",
"prepare": "husky"
},
"hooks": {
"pre-commit": "pnpm format"
Expand All @@ -25,6 +26,7 @@
"@vitest/coverage-v8": "^1.2.2",
"@vitest/ui": "^1.2.2",
"eslint": "^8.56.0",
"husky": "^9.0.11",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
Expand Down
32 changes: 18 additions & 14 deletions packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,25 @@ export const AddPatternDropdown = ({ uswdsRoot }: { uswdsRoot: string }) => {
<li
key={index}
className={`${styles.dropdownItem} padding-1 cursor-pointer margin-left-1`}
onClick={() => {
addPattern(patternType);
setIsOpen(false);
}}
>
<img
className="display-inline-block text-ttop margin-right-1"
src={getIconPath(pattern.iconPath || 'block-icon.svg')}
alt=""
width="24"
height="24"
/>
<span className="display-inline-block text-ttop">
{pattern.displayName}
</span>
<button
className="bg-transparent padding-0 border-0"
onClick={() => {
addPattern(patternType);
setIsOpen(false);
}}
>
<img
className="display-inline-block text-ttop margin-right-1"
src={getIconPath(pattern.iconPath || 'block-icon.svg')}
alt=""
width="24"
height="24"
/>
<span className="display-inline-block text-ttop">
{pattern.displayName}
</span>
</button>
</li>
))}
</ul>
Expand Down
Loading

0 comments on commit 701d4eb

Please sign in to comment.