Skip to content

Commit 9232480

Browse files
bollwyvlcholdgraf
andauthored
Add pa11y testing and reporting (#294)
Co-authored-by: Chris Holdgraf <[email protected]>
1 parent 50fa477 commit 9232480

27 files changed

+951
-37
lines changed

.github/workflows/tests.yml

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ jobs:
6969

7070
steps:
7171
- uses: actions/checkout@v2
72+
7273
- name: Set up Python ${{ matrix.python-version }}
7374
uses: actions/setup-python@v1
7475
with:
@@ -120,14 +121,21 @@ jobs:
120121
HOST: 127.0.0.1
121122
# the base url
122123
URL: http://127.0.0.1:8000
124+
PA11Y_BUILD: /tmp/pa11y/pa11y-${{ github.run_number }}
123125

124126
steps:
125127
- uses: actions/checkout@v2
128+
126129
- name: Set up Python ${{ matrix.python-version }}
127130
uses: actions/setup-python@v1
128131
with:
129132
python-version: ${{ matrix.python-version }}
130133

134+
- name: Set up Node/yarn
135+
uses: actions/setup-node@v1
136+
with:
137+
node-version: '10.x'
138+
131139
- name: Cache python wheels
132140
uses: actions/cache@v2
133141
with:
@@ -138,10 +146,18 @@ jobs:
138146
${{ runner.os }}-pip-${{ matrix.python-version }}-
139147
${{ runner.os }}-pip-
140148
149+
- name: Cache node_modules
150+
uses: actions/cache@v2
151+
with:
152+
path: 'node_modules'
153+
key: |
154+
${{ runner.os }}-node-modules-${{ hashFiles('yarn.lock') }}
155+
141156
- name: Install dependencies
142157
run: |
143158
python -m pip install --upgrade pip wheel setuptools
144159
python -m pip install -e .[coverage]
160+
yarn --frozen-lockfile
145161
146162
# Build the docs
147163
- name: Build docs to store
@@ -159,6 +175,7 @@ jobs:
159175
# TODO: use the hosted API with a secret? would allow for comparison over time...
160176
- name: Make folder for Lighthouse reports
161177
run: mkdir -p /tmp/lighthouse/lighthouse-${{ github.run_number }}
178+
162179
- name: Run Lighthouse on Site
163180
id: lighthouse
164181
uses: foo-software/[email protected]
@@ -168,16 +185,13 @@ jobs:
168185
${{ env.URL }}/index.html,
169186
${{ env.URL }}/demo/api.html,
170187
${{ env.URL }}/demo/demo.html,
171-
${{ env.URL }}/demo/example_pandas.html
188+
${{ env.URL }}/demo/example_pandas.html,
189+
${{ env.URL }}/user_guide/accessibility.html
172190
outputDirectory: /tmp/lighthouse/lighthouse-${{ github.run_number }}
173191
verbose: true
174192

175-
# Store the audit
176-
- name: Upload Lighthouse Reports
177-
uses: actions/upload-artifact@v2
178-
with:
179-
name: Lighthouse Report ${{ github.run_number }}
180-
path: /tmp/lighthouse
193+
- name: Run the accessibility audit
194+
run: python docs/a11y.py --no-serve
181195

182196
# Check the audit for threshold values
183197
# TODO: write this someplace after a PR is merged, and load?
@@ -189,6 +203,16 @@ jobs:
189203
minBestPracticesScore: "85"
190204
minPerformanceScore: "10"
191205
minSeoScore: "80"
206+
if: always()
207+
208+
- name: Publish Audit reports
209+
uses: actions/upload-artifact@v2
210+
with:
211+
name: Pa11y and Lighthouse ${{ github.run_number }}
212+
path: |
213+
/tmp/pa11y
214+
/tmp/lighthouse
215+
if: always()
192216

193217
publish:
194218

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,5 @@ envs/
104104

105105
node_modules/
106106
.vscode
107+
.yarn-packages
107108
.idea

.yarnrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
yarn-offline-mirror "./.yarn-packages"
2+
yarn-offline-mirror-pruning true
3+
ignore-optional true
4+
prefer-offline true

docs/a11y-roadmap.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# These are the pa11y error codes we'd like to fix. PRs welcome!
2+
3+
WCAG2AA.Principle1.Guideline1_3.1_3_1.H39.3.LayoutTable
4+
WCAG2AA.Principle1.Guideline1_3.1_3_1.H43,H63
5+
WCAG2AA.Principle1.Guideline1_3.1_3_1.H43.HeadersRequired
6+
WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail
7+
WCAG2AA.Principle3.Guideline3_2.3_2_2.H32.2
8+
WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId
9+
WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent

docs/a11y.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
""" use pa11y-ci to generate an accessibility report
2+
"""
3+
import json
4+
import os
5+
import shutil
6+
import subprocess
7+
import sys
8+
import time
9+
from collections import defaultdict
10+
from pathlib import Path
11+
from urllib.error import URLError
12+
from urllib.request import urlopen
13+
from yaml import safe_dump
14+
15+
HERE = Path(__file__).parent.resolve()
16+
ROOT = HERE.parent
17+
BUILD = HERE / "_build"
18+
PA11Y_BUILD = Path(os.environ.get("PA11Y_BUILD", BUILD / "pa11y"))
19+
PA11Y_JSON = PA11Y_BUILD / "pa11y-ci-results.json"
20+
PA11Y_ROADMAP = HERE / "a11y-roadmap.txt"
21+
YARN = [shutil.which("yarn"), "--silent"]
22+
SITEMAP = "http://127.0.0.1:8000/sitemap.xml"
23+
REPORT_INDEX_URL = (PA11Y_BUILD / "index.html").as_uri()
24+
25+
26+
def clean():
27+
if PA11Y_BUILD.exists():
28+
shutil.rmtree(PA11Y_BUILD)
29+
PA11Y_BUILD.mkdir(parents=True)
30+
31+
32+
def serve():
33+
"""start the local server"""
34+
server = subprocess.Popen(
35+
[sys.executable, HERE / "serve.py"],
36+
)
37+
ready = 0
38+
retries = 10
39+
while retries and not ready:
40+
retries -= 1
41+
try:
42+
time.sleep(1)
43+
ready = urlopen(SITEMAP)
44+
except URLError:
45+
pass
46+
47+
assert ready, "server did not start in 10 seconds"
48+
49+
return server
50+
51+
52+
def audit():
53+
"""run audit, generating a raw JSON report"""
54+
audit_rc = subprocess.call(
55+
f"yarn --silent pa11y-ci --json --sitemap {SITEMAP} > {PA11Y_JSON}",
56+
shell=True,
57+
cwd=ROOT,
58+
)
59+
60+
return audit_rc
61+
62+
63+
def report():
64+
"""generate HTML report from raw JSON"""
65+
subprocess.call(
66+
[
67+
*YARN,
68+
"pa11y-ci-reporter-html",
69+
"--source",
70+
PA11Y_JSON,
71+
"--destination",
72+
PA11Y_BUILD,
73+
],
74+
cwd=ROOT,
75+
)
76+
77+
78+
def summary():
79+
"""generate a summary and return the number of errors not ignored explicitly"""
80+
report = dict(
81+
HTML=REPORT_INDEX_URL, Roadmap=str(PA11Y_ROADMAP), JSON=str(PA11Y_JSON)
82+
)
83+
84+
pa11y_json = json.loads(PA11Y_JSON.read_text())
85+
pa11y_roadmap = [
86+
line.split("#")[0].strip()
87+
for line in PA11Y_ROADMAP.read_text().splitlines()
88+
if line.strip() and not line.strip().startswith("#")
89+
]
90+
91+
error_codes = defaultdict(lambda: 0)
92+
93+
for page, results in pa11y_json["results"].items():
94+
for result in results:
95+
if "code" in result:
96+
error_codes[result["code"]] += 1
97+
98+
roadmap_counts = {}
99+
not_roadmap_counts = {}
100+
101+
for code, count in sorted(error_codes.items()):
102+
code_on_roadmap = code in pa11y_roadmap
103+
if code_on_roadmap:
104+
roadmap_counts[code] = count
105+
else:
106+
not_roadmap_counts[code] = count
107+
108+
report.update(
109+
{
110+
"total errors": pa11y_json["errors"],
111+
"on roadmap": roadmap_counts,
112+
"not on roadmap": not_roadmap_counts,
113+
}
114+
)
115+
116+
nrc = sum(not_roadmap_counts.values())
117+
report["passed"] = nrc == 0
118+
report_str = safe_dump(report, default_flow_style=False)
119+
120+
if os.environ.get("CI") and nrc:
121+
print("""::error ::{}""".format(report_str.replace("\n", "%0A")))
122+
else:
123+
print(report_str)
124+
125+
return nrc
126+
127+
128+
def main(no_serve=True):
129+
"""start the server (if needed), then audit, report, and clean up"""
130+
clean()
131+
server = None
132+
try:
133+
if not no_serve:
134+
server = serve()
135+
audit()
136+
report()
137+
finally:
138+
server and server.terminate()
139+
140+
error_count = summary()
141+
142+
return error_count
143+
144+
145+
if __name__ == "__main__":
146+
sys.exit(main(no_serve="--no-serve" in sys.argv))

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ Changelog
33
*********
44

55
See the `GitHub Releases <https://github.com/pydata/pydata-sphinx-theme/releases>`_ for the changelog of each release.
6+
7+
.. meta::
8+
:description lang=en:
9+
The historical releases for pydata-sphinx-theme.

docs/conf.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
# add these directories to sys.path here. If the directory is relative to the
1111
# documentation root, use os.path.abspath to make it absolute, like shown here.
1212
#
13-
# import os
13+
import os
14+
1415
# import sys
1516
# sys.path.insert(0, os.path.abspath('.'))
1617

@@ -39,6 +40,19 @@
3940
"jupyter_sphinx",
4041
]
4142

43+
# -- Internationalization ------------------------------------------------
44+
# specifying the natural language populates some key tags
45+
language = "en"
46+
47+
# ReadTheDocs has its own way of generating sitemaps, etc.
48+
if not os.environ.get("READTHEDOCS"):
49+
extensions += ["sphinx_sitemap"]
50+
51+
# -- Sitemap -------------------------------------------------------------
52+
html_baseurl = os.environ.get("SITEMAP_URL_BASE", "http://127.0.0.1:8000/")
53+
sitemap_locales = [None]
54+
sitemap_url_scheme = "{link}"
55+
4256
autosummary_generate = True
4357

4458
# Add any paths that contain templates here, relative to this directory.
@@ -99,7 +113,7 @@
99113
html_static_path = ["_static"]
100114

101115

102-
# -- Auto-convert markdown pages to demo --------------------------------------
116+
# -- Auto-convert markdown pages to demo -------------------------------------
103117
import recommonmark
104118
from recommonmark.transform import AutoStructify
105119

docs/contributing.rst

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ To preview the frontend assets, from the root of this repo, run:
164164
165165
yarn build:dev
166166
167-
This launches a development server at http://localhost:1919. When working
167+
This launches a development server at http://127.0.0.1:1919. When working
168168
on the theme, saving changes to any of:
169169

170170
- ``src/js/index.js``
@@ -331,8 +331,66 @@ Note that if needed, you can skip these checks with:
331331
git commit --no-verify
332332
333333
334+
Finding accessibility problems
335+
==============================
336+
337+
The accessibility checking tools can find a number of common HTML patterns which
338+
assistive technology can't help users understand.
339+
340+
In addition to `Lighthouse <https://developers.google.com/web/tools/lighthouse>`__
341+
in CI, the ``pa11y`` stack is installed as part of the development environment.
342+
343+
The key components are:
344+
345+
- `pa11y <https://github.com/pa11y/pa11y>`__ which uses a headless browser to analyze
346+
an HTML page with a configurable set of rules based on publish standards
347+
- `Pa11y-CI <https://github.com/pa11y/pa11y-ci>`__ runs ``pa11y`` on multiple pages
348+
- `pa11y-reporter-html <https://github.com/pa11y/pa11y-reporter-html>`__ generates
349+
some nice HTML reports, suitable for review
350+
351+
.. Note::
352+
353+
Presently, the *default* ``pa11y`` ruleset, ``WCAG2AA`` is used, a subset of
354+
the `Web Content Accessibility Guidelines <https://www.w3.org/TR/WCAG21>`__.
355+
The `Quick Reference <https://www.w3.org/WAI/WCAG21/quickref>`__ may provide
356+
lighter reading.
357+
358+
To run the accessibility problem finder locally:
359+
360+
.. code-block:: bash
361+
362+
yarn build:production
363+
cd docs
364+
make html
365+
python a11y.py
366+
367+
The output of the last command includes:
368+
369+
- a short summary of the current state of the accessibility rules we are trying to maintain
370+
- local paths to JSON and HTML reports which contain all of the issues found
371+
372+
373+
Fixing accessibility errors
374+
---------------------------
375+
376+
Start by checking for issues on the
377+
`accessibility roadmap <https://github.com/pandas-dev/pydata-sphinx-theme/blob/master/docs/a11y-roadmap.txt>`__.
378+
These are issues which are currently flagged by the toolset, but that have not yet
379+
been fixed. If that file is empty (or just comments), hooray!
380+
381+
To start working on one of the accessibility roadmap items, comment out one of the
382+
lines in `docs/a11y-roadmap.txt`, and re-run the audit to establish a baseline.
383+
384+
Then, fix the issue in either the HTML templates, CSS, or python code, and re-run
385+
the audit until it is fixed.
386+
387+
334388
Make a release
335389
==============
336390

337391
This theme uses GitHub tags and releases to automatically push new releases to
338392
PyPI. For information on this process, see `the release checklist <https://github.com/pydata/pydata-sphinx-theme/wiki/Release-checklist#release-instructions>`_.
393+
394+
.. meta::
395+
:description lang=en:
396+
How to become a contributor to the pydata-sphinx-theme.

docs/demo/api.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,8 @@ Data
154154
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce congue elit eu hendrerit mattis.
155155

156156
Some data link :data:`Data_item_1`.
157+
158+
159+
.. meta::
160+
:description lang=en:
161+
An example of API documentation with pydata-sphinx-theme.

0 commit comments

Comments
 (0)