diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
index c8ac9648c..ff497a107 100644
--- a/.github/workflows/codespell.yml
+++ b/.github/workflows/codespell.yml
@@ -12,4 +12,6 @@ jobs:
- uses: codespell-project/actions-codespell@master
with:
check_filenames: true
- skip: ./.git,yarn.lock
+ # The a11y test file has a false positive and the ignore list does not work
+ # see https://github.com/opentripplanner/otp-react-redux/pull/436/checks?check_run_id=3369380014
+ skip: ./.git,yarn.lock,./a11y/a11y.test.js
diff --git a/a11y/a11y.test.js b/a11y/a11y.test.js
index 1a3719e8c..c23576814 100644
--- a/a11y/a11y.test.js
+++ b/a11y/a11y.test.js
@@ -4,15 +4,39 @@ import path from 'path'
import puppeteer from 'puppeteer'
import execa from 'execa'
+import { routes } from '../lib/components/app/responsive-webapp'
+
import { mockServer } from './mock-server'
const OTP_RR_CONFIG_FILE_PATH = './config.yml'
const OTP_RR_CONFIG_BACKUP_PATH = './config.non-test.yml'
const OTP_RR_TEST_CONFIG_PATH = './a11y/test-config.yml'
-let server
+let browser, server
+// These rules aren't relevant to this project
+const disabledRules = [
+ 'region', // Leaflet does not comply
+ 'meta-viewport', // Leaflet does not comply
+ 'page-has-heading-one' // Heading is provided by logo
+]
+
+/**
+ * Runs a11y tests on a given OTP-RR path using the test build. Relies on
+ * the puppeteer browser running
+ */
+async function runAxeTestOnPath (otpPath) {
+ const page = await browser.newPage()
+ const filePath = `file://${path.resolve(__dirname, '../index-for-puppeteer.html')}#${otpPath}`
+ await Promise.all([
+ page.goto(filePath),
+ page.waitForNavigation({ waitUntil: 'networkidle2' })
+ ])
+
+ await expect(page).toPassAxeTests({ disabledRules })
+ return page
+}
-beforeEach(() => {
+beforeAll(async () => {
// backup current config file
if (fs.existsSync(OTP_RR_CONFIG_FILE_PATH)) {
fs.renameSync(
@@ -37,9 +61,11 @@ beforeEach(() => {
server = mockServer.listen(MOCK_SERVER_PORT, () => {
console.log(`Mock response server running on http://localhost:${MOCK_SERVER_PORT}`)
})
+ // Web security is disabled to allow requests to the mock OTP server
+ browser = await puppeteer.launch({args: ['--disable-web-security']})
})
-afterEach(async () => {
+afterAll(async () => {
fs.unlinkSync(OTP_RR_CONFIG_FILE_PATH)
if (fs.existsSync(OTP_RR_CONFIG_BACKUP_PATH)) {
fs.renameSync(
@@ -49,35 +75,38 @@ afterEach(async () => {
}
console.log('Restored original OTP-RR config file')
await server.close()
- console.log('Closed mock server')
+ await browser.close()
+ console.log('Closed mock server and headless browser')
})
-test('checks the test page with Axe', async () => {
- jest.setTimeout(600000)
- // These rules aren't relevant to this project
- const disabledRules = [
- 'region', // Leaflet does not comply
- 'meta-viewport', // Leaflet does not comply
- 'page-has-heading-one' // Heading is provided by logo
- ]
+jest.setTimeout(600000)
+routes.forEach(route => {
+ const {a11yIgnore, path: pathsToTest} = route
+ if (a11yIgnore) {
+ return
+ }
- // Web security is disabled to allow requests to the mock OTP server
- const browser = await puppeteer.launch({args: ['--disable-web-security']})
- let page = await browser.newPage()
- // Test trip planner
- await page.goto(`file://${path.resolve(__dirname, '../index-for-puppeteer.html')}#/?ui_activeSearch=0qoydlnut&ui_activeItinerary=0&fromPlace=1900%20Main%20Street%2C%20Houston%2C%20TX%2C%20USA%3A%3A29.750144%2C-95.370998&toPlace=800%20Congress%2C%20Houston%2C%20TX%2C%20USA%3A%3A29.76263%2C-95.362178&date=2021-08-04&time=08%3A14&arriveBy=false&mode=WALK%2CBUS%2CTRAM&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&numItineraries=3&otherThanPreferredRoutesPenalty=900`)
+ if (Array.isArray(pathsToTest)) {
+ // Run test on each path in list.
+ pathsToTest.forEach(async (p) => {
+ test(`${p} should pass Axe Tests`, async () => runAxeTestOnPath(p))
+ })
+ } else {
+ // Otherwise run test on individual path
+ test(`${pathsToTest} should pass Axe Tests`, async () => runAxeTestOnPath(pathsToTest))
+ }
+})
- await expect(page).toPassAxeTests({
- disabledRules: disabledRules
- })
- // Test stop viewer
- page = await browser.newPage()
- await page.goto(`file://${path.resolve(__dirname, '../index-for-puppeteer.html')}#/stop/exampleStop?ui_activeSearch=u9dwdhmyo&ui_activeItinerary=2&fromPlace=945 Columbia Street%2C Houston%2C TX%2C USA%3A%3A29.78881282532108%2C-95.3932571411133&toPlace=Hardy Street Yard%2C Houston%2C TX%2C USA%3A%3A29.772125846370574%2C-95.3551483154297&date=2021-08-18&time=17%3A07&arriveBy=false&mode=WALK%2CBUS%2CTRAM&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&numItineraries=3&otherThanPreferredRoutesPenalty=900`)
- await page.waitForTimeout(4000)
- await page.click('.expansion-button')
- await expect(page).toPassAxeTests({
- disabledRules: disabledRules
- })
+test('Mocked Main Trip planner page should pass Axe Tests', async () => {
+ await runAxeTestOnPath('/?ui_activeSearch=0qoydlnut&ui_activeItinerary=0&fromPlace=1900%20Main%20Street%2C%20Houston%2C%20TX%2C%20USA%3A%3A29.750144%2C-95.370998&toPlace=800%20Congress%2C%20Houston%2C%20TX%2C%20USA%3A%3A29.76263%2C-95.362178&date=2021-08-04&time=08%3A14&arriveBy=false&mode=WALK%2CBUS%2CTRAM&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&numItineraries=3&otherThanPreferredRoutesPenalty=900')
+})
- await browser.close()
+test('Mocked Stop Viewer and Dropdown should pass Axe tests', async () => {
+ jest.setTimeout(600000)
+ // Test stop viewer
+ const stopViewerPage = await runAxeTestOnPath('/stop/Agency')
+ await stopViewerPage.waitForTimeout(2000)
+ await stopViewerPage.click('.expansion-button')
+ await stopViewerPage.waitForTimeout(2000)
+ await expect(stopViewerPage).toPassAxeTests({ disabledRules })
})
diff --git a/a11y/mock-server.js b/a11y/mock-server.js
index 47d56151d..cad363d84 100644
--- a/a11y/mock-server.js
+++ b/a11y/mock-server.js
@@ -3,6 +3,7 @@ const express = require('express')
const PLAN_REALTIME = require('./mocks/plan.json')
const STOPS_FIRST = require('./mocks/stops.json')
const PARK_AND_RIDE = require('./mocks/pr.json')
+const ROUTES = require('./mocks/routes.json')
const STOP_VIEWER_STOPTIMES = require('./mocks/stopviewer/stoptimes.json')
const STOP_VIEWER_STOP = require('./mocks/stopviewer/stop.json')
const STOP_VIEWER_ROUTES = require('./mocks/stopviewer/routes.json')
@@ -18,13 +19,16 @@ app.get('/otp/routers/default/index/stops', (req, res) => {
app.get('/otp/routers/default/park_and_ride', (req, res) => {
res.send(PARK_AND_RIDE)
})
-app.get('/otp/routers/default/index/stops/exampleStop', (req, res) => {
+app.get('/otp/routers/default/index/stops/Agency', (req, res) => {
res.send(STOP_VIEWER_STOP)
})
-app.get('/otp/routers/default/index/stops/exampleStop/routes', (req, res) => {
+app.get('/otp/routers/default/index/stops/Agency/routes', (req, res) => {
res.send(STOP_VIEWER_ROUTES)
})
-app.get('/otp/routers/default/index/stops/exampleStop/stoptimes', (req, res) => {
+app.get('/otp/routers/default/index/stops/Agency/stoptimes', (req, res) => {
res.send(STOP_VIEWER_STOPTIMES)
})
+app.get('/otp/routers/default/index/routes', (req, res) => {
+ res.send(ROUTES)
+})
module.exports.mockServer = app
diff --git a/a11y/mocks/routes.json b/a11y/mocks/routes.json
new file mode 100644
index 000000000..81abba968
--- /dev/null
+++ b/a11y/mocks/routes.json
@@ -0,0 +1 @@
+[{"id":"Houston:41281","shortName":"002","longName":"BELLAIRE","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41282","shortName":"003","longName":"LANGLEY - LITTLE YORK","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41328","shortName":"072","longName":"WESTVIEW","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41329","shortName":"073","longName":"BELLFORT","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"1:16","longName":"Route 2 Baytown Central","mode":"BUS","color":"ffd342","agencyId":"140","agencyName":"Harris County","sortOrder":-999},{"id":"Houston:41324","shortName":"067","longName":"DAIRY ASHFORD","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"1:15","longName":"Route 1 Garth Rd","mode":"BUS","color":"5555eb","agencyId":"140","agencyName":"Harris County","sortOrder":-999},{"id":"Houston:41325","shortName":"068","longName":"BRAESWOOD","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41326","shortName":"070","longName":"MEMORIAL","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41327","shortName":"071","longName":"COTTAGE GROVE","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41293","shortName":"023","longName":"CLAY - W 43RD","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41339","shortName":"085","longName":"ANTOINE / WASHINGTON","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41335","shortName":"080","longName":"MLK / LOCKWOOD","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41336","shortName":"082","longName":"WESTHEIMER","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41337","shortName":"083","longName":"LEE ROAD - JFK","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41338","shortName":"084","longName":"BUFFALO SPEEDWAY","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41298","shortName":"029","longName":"CULLEN / HIRSCH","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41331","shortName":"076","longName":"EVERGREEN","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41332","shortName":"077","longName":"HOMESTEAD","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41299","shortName":"030","longName":"CLINTON / ELLA","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41333","shortName":"078","longName":"WAYSIDE","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41334","shortName":"079","longName":"IRVINGTON","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41294","shortName":"025","longName":"RICHMOND","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41295","shortName":"026","longName":"LONG POINT / CAVALCADE","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41296","shortName":"027","longName":"SHEPHERD","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41297","shortName":"028","longName":"OST - WAYSIDE","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41330","shortName":"075","longName":"ELDRIDGE","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41346","shortName":"098","longName":"BRIARGATE","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41347","shortName":"099","longName":"ELLA - FM 1960","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41348","shortName":"102","longName":"BUSH IAH EXPRESS","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41349","shortName":"108","longName":"VETERANS MEMORIAL EXPRESS","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41342","shortName":"088","longName":"SAGEMONT","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41343","shortName":"089","longName":"DACOMA","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41344","shortName":"096","longName":"VETERANS MEMORIAL","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41345","shortName":"097","longName":"SETTEGAST","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41340","shortName":"086","longName":"FM 1960 / IMPERIAL VALLEY","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41341","shortName":"087","longName":"SUNNYSIDE","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"1:23","longName":"Route 1 Garth Rd (early bird)","mode":"BUS","color":"5a5afa","agencyId":"140","agencyName":"Harris County","sortOrder":-999},{"id":"1:27","longName":"Baytown/La Porte Shuttle","mode":"BUS","color":"a26dad","agencyId":"140","agencyName":"Harris County","sortOrder":-999},{"id":"Houston:41357","shortName":"170","longName":"MISSOURI CITY EXPRESS","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41358","shortName":"171","longName":"FORTBEND TOWN CENTER","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41359","shortName":"209","longName":"KUYKENDAHL/SPRING P&R","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"1:28","longName":"Route 5 City of La Porte","mode":"BUS","color":"F0563d","agencyId":"140","agencyName":"Harris County","sortOrder":-999},{"id":"Houston:41353","shortName":"153","longName":"HARWIN EXPRESS","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999},{"id":"Houston:41354","shortName":"160","longName":"MEMORIAL CITY EXPRESS","mode":"BUS","color":"004080","agencyId":"HOU","agencyName":"Metropolitan Transit Authority of Harris County","sortOrder":-999}]
\ No newline at end of file
diff --git a/a11y/mocks/stopviewer/routes.json b/a11y/mocks/stopviewer/routes.json
index 9316d3917..84332e678 100644
--- a/a11y/mocks/stopviewer/routes.json
+++ b/a11y/mocks/stopviewer/routes.json
@@ -1,5 +1,5 @@
[{
- "id": "exampleStop:1",
+ "id": "Agency:1",
"shortName": "066",
"longName": "QUITMAN",
"mode": "BUS",
diff --git a/a11y/mocks/stopviewer/stop.json b/a11y/mocks/stopviewer/stop.json
index 9ceb8c410..73ef8320d 100644
--- a/a11y/mocks/stopviewer/stop.json
+++ b/a11y/mocks/stopviewer/stop.json
@@ -1,5 +1,5 @@
{
- "id": "exampleStop",
+ "id": "Agency",
"name": "White Oak Dr @ Oxford St",
"lat": 29.78149,
"lon": -95.391953,
diff --git a/a11y/mocks/stopviewer/stoptimes.json b/a11y/mocks/stopviewer/stoptimes.json
index b28210193..a83bb7313 100644
--- a/a11y/mocks/stopviewer/stoptimes.json
+++ b/a11y/mocks/stopviewer/stoptimes.json
@@ -1,12 +1,12 @@
[
{
"pattern": {
- "id": "exampleStop:1:01",
+ "id": "Agency:1:01",
"desc": "040 to Glencrest St @ Airport Blvd (Houston:743)"
},
"times": [
{
- "stopId": "exampleStop:6478",
+ "stopId": "Agency:12345",
"stopIndex": 44,
"stopCount": 141,
"scheduledArrival": 44621,
@@ -27,7 +27,7 @@
"serviceAreaRadius": 0.0
},
{
- "stopId": "exampleStop:6478",
+ "stopId": "Agency:12345",
"stopIndex": 44,
"stopCount": 141,
"scheduledArrival": 44521,
@@ -48,7 +48,7 @@
"serviceAreaRadius": 0.0
},
{
- "stopId": "exampleStop:6478",
+ "stopId": "Agency:12345",
"stopIndex": 44,
"stopCount": 141,
"scheduledArrival": 46321,
diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js
index 09eb22e4c..b9d3b5220 100644
--- a/lib/components/app/responsive-webapp.js
+++ b/lib/components/app/responsive-webapp.js
@@ -64,6 +64,7 @@ class ResponsiveWebapp extends Component {
/** Lifecycle methods **/
+ /* eslint-disable-next-line complexity */
componentDidUpdate (prevProps) {
const {
activeSearchId,
@@ -285,6 +286,90 @@ const WebappWithRouter = withRouter(
)
)
+// TODO: A number of these routes are ignored during a11y testing as no server mocks are available
+export const routes = [
+ {
+ exact: true,
+ path: [
+ // App root
+ '/',
+ // Load app with preset lat/lon/zoom and optional router
+ // NOTE: All params will be cast to :id in matchContentToUrl due
+ // to a quirk with react-router.
+ // https://github.com/ReactTraining/react-router/issues/5870#issuecomment-394194338
+ '/@/:latLonZoomRouter',
+ '/start/:latLonZoomRouter',
+ // Route viewer (and route ID).
+ '/route',
+ '/route/:id',
+ // Stop viewer (and stop ID).
+ '/stop',
+ '/stop/:id'
+ ],
+ shouldRenderWebApp: true
+ },
+ {
+ a11yIgnore: true,
+ component: FavoritePlaceScreen,
+ path: [`${CREATE_ACCOUNT_PLACES_PATH}/:id`, `${PLACES_PATH}/:id`]
+ },
+ {
+ a11yIgnore: true,
+ component: SavedTripScreen,
+ path: `${TRIPS_PATH}/:id`
+ },
+ {
+ a11yIgnore: true,
+ children: ,
+ exact: true,
+ path: ACCOUNT_PATH
+ },
+ {
+ a11yIgnore: true,
+ children: ,
+ exact: true,
+ path: CREATE_ACCOUNT_PATH
+ },
+ {
+ a11yIgnore: true,
+ // This route lets new or existing users edit or set up their account.
+ component: UserAccountScreen,
+ path: [`${CREATE_ACCOUNT_PATH}/:step`, ACCOUNT_SETTINGS_PATH]
+ },
+ {
+ getContextComponent: (components) => frame(components.TermsOfService),
+ path: TERMS_OF_SERVICE_PATH
+ },
+ {
+ getContextComponent: (components) => frame(components.TermsOfStorage),
+ path: TERMS_OF_STORAGE_PATH
+ },
+ {
+ a11yIgnore: true,
+ component: SavedTripList,
+ path: TRIPS_PATH
+ },
+ {
+ a11yIgnore: true,
+ // This route is called immediately after login by Auth0
+ // and by the onRedirectCallback function from /lib/util/auth.js.
+ // For new users, it displays the account setup form.
+ // For existing users, it takes the browser back to the itinerary search prior to login.
+ component: AfterSignInScreen,
+ path: '/signedin'
+ },
+ {
+ a11yIgnore: true,
+ component: PrintLayout,
+ path: '/print'
+ },
+ {
+ a11yIgnore: true,
+ component: PrintFieldTripLayout,
+ path: '/printFieldTrip'
+ }
+]
+
/**
* The routing component for the application.
* This is the top-most "standard" component,
@@ -316,73 +401,26 @@ class RouterWrapperWithAuth0 extends Component {
basename={routerConfig && routerConfig.basename}
history={history}>
- }
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {routes.map((props, index) => {
+ const {
+ getContextComponent,
+ shouldRenderWebApp,
+ ...routerProps
+ } = props
+
+ return (
+
+ : undefined
+ }
+ {...routerProps} />
+ )
+ })}
{/* For any other route, simply return the web app. */}
}
diff --git a/lib/components/viewers/realtime-status-label.js b/lib/components/viewers/realtime-status-label.js
index c2eecbf49..8525e62a2 100644
--- a/lib/components/viewers/realtime-status-label.js
+++ b/lib/components/viewers/realtime-status-label.js
@@ -67,7 +67,7 @@ const RealtimeStatusLabel = ({
const isEarlyOrLate = status === REALTIME_STATUS.EARLY || status === REALTIME_STATUS.LATE
// Use a default background color if the status object doesn't set a color
// (e.g. for 'Scheduled' status), but only in withBackground mode.
- const color = STATUS[status].color || (withBackground && '#6D6C6Cb')
+ const color = STATUS[status].color || (withBackground && '#6D6C6C')
// Render time if provided.
let renderedTime
if (time) {
diff --git a/package.json b/package.json
index 87479045c..0e8451e3e 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"description": "A library for writing modern OpenTripPlanner-compatible multimodal journey planning web applications using React and Redux",
"main": "build/index.js",
"scripts": {
- "a11y-test": "mastarm test a11y --force-exit",
+ "a11y-test": "mastarm test a11y --runInBand --force-exit",
"build": "mastarm build --env production",
"cover": "mastarm test -e test --coverage",
"jest": "mastarm test -e test __tests__",