This document outlines expectations for contributors to this project with regard to testing.
-
All nontrivial pieces of code should have have unit tests. (And if the code is trivial, perhaps it needs to be rethought.)
-
Tests should be written in Jest and use the existing tooling wherever possible. Please discuss any new dev dependencies with at least one other member of the team before adding them to the project.
-
Tests should always cover the "Happy Path" or paths in their entirety. A reasonable number of failure paths, appropriate to the likelihood and severity of errors, should be anticipated as well; silent failures cause very real issues with static site generation.
-
PRs should aim for 100% coverage of lines, branches, and expressions. PRs should lower test coverage metrics only in exceptional circumstances. Test coverage requirements are set to a couple percent below the current metrics; these should increase briskly over time.
-
Unit tests should incorporate accessibility testing whenever appropriate. Whenever a component is rendered, check it. If there is a rerender, check it again! The
axe
tool is made available in our internaltest-utils
module:
import { axe, render, waitFor } from 'test-utils'
test('correctly renders SomeComponent', async () => {
....
const { container } = render(<SomeComponent />)
await waitFor(async () => expect(await axe(container)).toHaveNoViolations())
....
})
On local environments, every commit will run typechecks, linting and code formatting.
This happens using husky
and lint-staged
. See lint-staged.config.js
for more details.
This is the "fast" part of the testing suite. Our tools for these tasks currently include:
- Typechecking, static typechecking on all .tsx files using the typescript compiler.
- ESLint, an ECMAScript/JavaScript linter and static analysis tool.
- Prettier, a file formatter that ensures consistent code style.
- Jest, a Javascript testing framework with an accessible API
All four of these tools run as part of the pre-commit hooks on staged files (and/or their related tests, in Jest's case).
Typechecking happens at the beginning of the build process, but it can also be run manually at any time using yarn test:types
. If there are incorrect types found (e.g. a function expects a string but is receiving a boolean), errors will be thrown.
See Code Guidelines for more information around ESLint and Prettier.
Run yarn test
to run the full Jest suite. To update snapshots, yarn test -- -u
. We need the double dash in order to pass flags to the underlying command, instead of environment variables. See the env-loader README for more information on how that works.
Run yarn test:coverage
to run the full Jest suite and see the coverage report.
If running yarn test:ci
, Jest will assume a CI environment and fail if snapshots are out of date.
To use Jest in interactive watch mode, run yarn test:watch
. This will keep the Jest process running and it will re-run tests against the files you change. See the console output from this command for a full list of options and usage.
This is the "slow" part of the testing suite, Playwright. Where Jest tests the code itself, Playwright tests that a user can do what they are expected to do in a browser, click buttons, etc.
These tests can be run manually, and they always run in CI. Because of this,
Playwright expects the full site to be built and served.
You can do this locally by first running yarn export
and then yarn export:serve
.
This will start a webserver with all the generated static pages that Playwright can reach.
Run yarn test:playwright
to run all tests (including examples) against generated pages.
You can also run Playwright interactively with yarn test:playwright:interactive
which will load
a browser with a clean UI to monitor and iterate on different test cases.
This project can be tested for a11y compliance in several ways:
-
Individual Jest unit tests using jest-axe (see:
example_tests/06_accessibility_tests/index.test.jsx
). -
Individual Playwright tests using Playwright-axe. (see:
playwright/tests/newsStory.spec.js
).-
Any individual test can call these commands to check a given page:
test('Should render without a11y accessibility errors', async ({ page, makeAxeBuilder, }) => { await page.goto('/your-page-url') const accessibilityScanResults = await makeAxeBuilder().analyze() expect(accessibilityScanResults.violations).toEqual([])
-
-
A full site scan of all urls known to next-build (generated in the sitemap) using Playwright:
The full scan will run daily in CI using a GitHub Workflow and multiple runners, but you can also run it locally for testing and debugging purposes.
- Workflow file:
.github/workflows/a11y.yml
- Test file:
playwright/tests/a11y.spec.ts
- Yarn command with plenty of env var config options:
yarn test:playwright:a11y
We designed the scan to run against the production va.gov/sitemap.xml to ensure accessibility issues facing actual users are caught. Testing locally and in lower environments is great, but to get the most bang for your buck, running it on the actual output ensures that any fixes carry through to production.
The workflow file .github/workflows/a11y.yml
has all the contents of the
test run which currently uses 64 runners in a matrix. Each runner passes in
a SEGMENT_INDEX
that the test uses to split the sitemap into multiple
pages based on the total number of runners used.
Several environmental variables control different parts of the test
configuration and can be found at the top-level of the playwright-tests
job.
BASE_URL
: Where to go look for "/sitemap.xml"USE_PROXY
: Whether to use the proxy in the custom fetcher. We don't want to use it for the a11y scan.PW_BROWSER
: An array of Playwright browser types. These are randomly read per runner and used asPW_BROWSER_VALUE
in test config.PW_WIDTH
: An array of viewport widths to test against. These are randomly read per runner and used asPW_WIDTH_VALUE
in test config.PW_HEIGHT
: An array of viewport widths to test against. These are randomly read per runner and used asPW_WIDTH_VALUE
in test config. The viewport height might not matter for a11y testing, I'm not sure.TOTAL_SEGMENTS
: The number of total segments. This was in the matrix variables but pulled out since it is always one value and not a list of values.
You can alter those values to be picked up by each test run.
Random values are picked in the "Run command with random values from arrays"
step using some shell commands and jq
foo. Then, those are used in the
test run as env vars with _VALUE
as a suffix.
More detail about configuration and how the env vars are used is located in the Test Config section.
After the run the JSON output from the test run is uploaded into a file with
the segment name included, e.g. segment-${{ matrix.shardIndex }}.json
.
To deal with an issue where pages were redirecting when Axe was trying to
scan them, we added a try/catch
to log those to a separate
failed-pages-segment-${{ matrix.shardIndex }}.json
report. Some pages were
still trouble so those are excluded from the scan entirely.
Then the merge-reports
job waits for all the runners to finish before
concatenating the results of both reports and making sure the final JSON
output is valid.
In the future, the report will be uploaded and sent to who needs it, but for now you can download it from GitHub on the Actions summary view for the a11y test workflow.
The playwright.config.ts
file contains all the Playwright config used for
any test using Playwright in the next-build repo. We set up a different
Playwright Project named a11y
to filter tests for accessibility as well as
provide different configuration. Let's go over the configuration differences:
retries
- The test performs no assertions and retrying would scan all the pages again. Plus, there was a weird error on GitHub where the URLs were being scanned multiple times. Might be worth revisiting in the future.browserName
: Allows processingPW_BROWSER_VALUE
to run different browser types.trace
: No need to trace for code coverage.screenshot
: Taking screenshots slowed down the tests and isn't necessary to report violationsvideo
: No need to take videoviewport
: AllowsPW_WIDTH_VALUE
andPW_HEIGHT_VALUE
It might be useful to look over the Playwright config and see if there are more config options that would be helpful to use in testing.
The actual test doesn't actually run any assertions. The reason for this is that we are scanning many pages in one test. We could split it up so each URL is run in an isolated test but that would require more setup and teardown costing time. Also...just being honest...passing data to each test via async/await was finicky, and I never went back to try and investigate further.
Some pages end up redirecting
Test run workflow:
- Get pages of the sitemap.
- Split the pages into segments if the segment index is not zero. This allows to run against all pages locally or use segment indexes on GH.
- Pages are looped through and navigated to as long as the page isn't in the list of excluded pages.
AxeBuilder
analyzes each page.- If violations are found, they are pushed into an array with the URL and violations as keys.
- If an error occurs, then catch it and log to a separate failed pages array.
- After all pages are scanned, the final results are written to a JSON file for pages scanned and failures. The root element is stripped to make merging the reports easier as well as adding a trailing comma.
You don't have to build a site locally, but if you want to test against a fresh next-build instance, you can follow these steps:
- Run all the steps needed to set up next-build listed in the root README.md file.
yarn export
to generate the static pages for the siteyarn build:sitemap
to generate the sitemap for pages from step 1.yarn export:serve
to host the static pages locally- Take note of the port being used for the
BASE_URL
variable you will pass in. BASE_URL=${your-url} yarn test:playwright:a11y
to run the scan. This runsplaywright/tests/a11y.spec.ts
which loops over the sitemap and tests each page individually using@axe-core/playwright
.
You should also add the config values you want locally to end up with
something like: BASE_URL=${...} USE_PROXY=false PW_WIDTH_VALUE=768 PW_HEIGHT_VALUE=720 PW_BROWSER_VALUE=firefox yarn playwright test --project=a11y
As per the advice of platform team, and confirmed by our product stakeholder, we created the ability to load test the reverse proxy. We use locust.io to do this testing.
You will find the test file in the load-testing
project directory. You will need python 3.x to run it. Once you have python installed follow the guide here to install locust. Once you have locust installed go to the load-test directory.
- Open terminal in load testing directory
- Run locusts with
locust
- Open browser to
http://localhost:8089/
- The test file we have will automatically scales up users and will take an hour to complete
- Set number of users to 1
- Set host to the system you want to test. EX:
https://dev.va.gov
,https://www.va.gov/
- Click the start button
- Wait aproximatly 1.5 hours for the test to complete automatically.
- Go to the download data tab and download your full report.
A node script has been added to run lighthouse performance testing. In the lighthouse directory, there is a file called lighthouse.js. This file will test some events listings. Currently it is configured to test specific url's in staging. However, a quick find and replace can change the urls to match whichever target environment we want. We may consider using a command line switch to change the base url.
To run the script a new switch has been added to our package.json to run it simply call yarn lighthouse
To run on WSL you will need to install chrome and set the CHROME_PATH
variable
- To install chrome I did the following on ubuntu , your distro may very.
A.
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
B.sudo dpkg -i google-chrome-stable_current_amd64.deb
C.sudo apt --fix-broken install
- Set the chrome path env variable to the location of google chrome for me I run
export CHROME_PATH="/opt/google/chrome/google-chrome"