Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessibility test Kitchen Sink with Playwright #1260

Merged
merged 56 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e3ed101
Add core-js so dev build works
gabalafou Mar 20, 2023
a16cc88
add more pages, test server
gabalafou Mar 21, 2023
68079b2
add documentation
gabalafou Mar 21, 2023
5030606
add prerequisites to nox test
gabalafou Mar 21, 2023
423ed5d
update package-lock.json via stb
gabalafou Mar 21, 2023
de4e2dd
update package-lock.json via nox -s compile
gabalafou Mar 22, 2023
310f171
streamline, update docs
gabalafou Mar 22, 2023
09906c1
typo
gabalafou Mar 22, 2023
2480b00
sync lockfile (remove core-js)
gabalafou Mar 22, 2023
6f938ba
Update tests/utils.py
gabalafou Mar 27, 2023
b5b57ca
Update tests/README.md
gabalafou Mar 27, 2023
f47a60a
Update docs/community/topics/accessibility.md
gabalafou Mar 27, 2023
62b6ae7
Update tests/README.md
gabalafou Mar 27, 2023
fc04acf
Update tests/utils.py
gabalafou Mar 27, 2023
23be214
Update tests/utils.py
gabalafou Mar 27, 2023
66fac74
- nox session a11y
gabalafou Mar 28, 2023
bc311db
update docs
gabalafou Mar 28, 2023
94fb49f
Merge branch 'main' into playwright-a11y-test-kitchen-sink
gabalafou Mar 28, 2023
9436844
uncomment commented-out tests
gabalafou Mar 28, 2023
e5a8814
regenerate package-lock.json via:
gabalafou Mar 28, 2023
3f86b11
oops finish updating tests
gabalafou Mar 28, 2023
700b64a
do not include a11y tests in main test session
gabalafou Mar 28, 2023
c57011d
remove redundant decorator
gabalafou Mar 29, 2023
ced281d
Update tests/README.md
gabalafou Apr 12, 2023
33b10ac
Update tests/README.md
gabalafou Apr 12, 2023
f8cee2c
Update tests/README.md
gabalafou Apr 12, 2023
9df077a
Remove base-url Pytest plugin
gabalafou Apr 12, 2023
1c2b8d5
Update tests/utils/pretty_axe_results.py
gabalafou Apr 12, 2023
a4325f5
Apply suggestions from code review
gabalafou Apr 12, 2023
72dfedc
Merge branch 'main' into playwright-a11y-test-kitchen-sink
gabalafou Apr 12, 2023
f1c8007
fix pretty_axe_results
gabalafou Apr 12, 2023
0317729
Generate each test run from parameters
gabalafou Apr 12, 2023
744fc94
remove unused marker url_path
gabalafou Apr 12, 2023
5e06478
undo multiline
gabalafou Apr 12, 2023
6820864
update doc string
gabalafou Apr 12, 2023
419d9e8
Add CI check for --with-deps suggested by Tony
gabalafou Apr 12, 2023
c18dbd6
oops, rm extra playwright install line
gabalafou Apr 12, 2023
e63d3e7
update comment
gabalafou Apr 12, 2023
83af9ac
update docstring
gabalafou Apr 12, 2023
178a689
Merge remote-tracking branch 'upstream/main' into playwright-a11y-tes…
drammock Apr 12, 2023
f7b92ea
testing the pytest-axe mark
drammock Apr 12, 2023
926d426
run prettier
drammock Apr 12, 2023
49c15a9
Revert "testing the pytest-axe mark"
drammock Apr 12, 2023
036506b
Fix D104
drammock Apr 12, 2023
0f0d599
skip a11y tests on CI runs
drammock Apr 12, 2023
2176326
run a11y tests on one CI worker only
drammock Apr 12, 2023
ec6a9d7
:white_check_mark: Register a11y mark and separate dependencies
trallard Apr 14, 2023
452ef73
:construction_worker: Update CI - delegate playwright install and cache
trallard Apr 14, 2023
71a86ac
📝 Update test docs
trallard Apr 14, 2023
645202c
:white_check_mark: Ensure a11y tests are skipped unles called
trallard Apr 14, 2023
07b9566
:construction_worker: Ensure CI installs all deps
trallard Apr 14, 2023
19ccd1b
:rewind: Revert to installing playwright
trallard Apr 14, 2023
112a27a
:construction_worker: Forgot to re-add a11y deps
trallard Apr 14, 2023
00005d2
:pencil2: Fix typo
trallard Apr 14, 2023
b9d83f6
:wrench: Fix path - seems to have been messed up when moving things
trallard Apr 14, 2023
22953ed
Update tests/test_a11y.py
drammock Apr 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:

- name: Check that there are no unexpected Sphinx warnings
if: matrix.python-version == '3.10'
run: python tests/check_warnings.py
run: python tests/utils/check_warnings.py

- name: Run the tests
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ jobs:
- name: Build docs
run: sphinx-build -b html docs/ docs/_build/html --keep-going -w warnings.txt
- name: Check for unexpected Sphinx warnings
run: python tests/check_warnings.py
run: python tests/utils/check_warnings.py

# Run local Lighthouse audit against built site
audit:
Expand Down
21 changes: 15 additions & 6 deletions docs/community/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,28 @@ will cause the development server to do the following:

## Run the tests

This theme uses `pytest` for its testing, with a lightweight fixture defined
This theme uses `pytest` for its testing. There is a lightweight fixture defined
in the `test_build.py` script that makes it easy to run a Sphinx build using
this theme and inspect the results.
this theme and inspect the results. There are also a number of automated accessibility checks in
`test_a11y.py`.

In addition, we use [pytest-regressions](https://pytest-regressions.readthedocs.io/en/latest/)
to ensure that the HTML generated by the theme is what we'd expect. This module
In addition, we use
[pytest-regressions](https://pytest-regressions.readthedocs.io/en/latest/) to
ensure that the HTML generated by the theme is what we'd expect. This module
provides a `file_regression` fixture that will check the contents of an object
against a reference file on disk. If the structure of the two differs, then the
test will fail. If we _expect_ the structure to differ, then delete the file on
disk and run the test. A new file will be created, and subsequent tests will pass.
disk and run the test. A new file will be created, and subsequent tests will
pass.

To run the tests with `nox`, run the following command:
To run the build tests with `nox`, run the following command:

```console
$ nox -s test
```

To run the accessibility checks:

```console
$ nox -s a11y
```
5 changes: 5 additions & 0 deletions docs/community/topics/accessibility.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Accessibility checks

```{note}
22-March-2023: we are currently
[re-evaluating how we do accessibility checks](https://github.com/pydata/pydata-sphinx-theme/issues/1168)
and reporting, so this may change soon.
gabalafou marked this conversation as resolved.
Show resolved Hide resolved

The accessibility checking tools can find a number of common HTML patterns which
assistive technology can't help users understand.
We run a [Lighthouse](https://developers.google.com/web/tools/lighthouse) job in our CI/CD, which generates a "score" for all pages in our **Kitchen Sink** example documentation.
Expand Down
24 changes: 23 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

nox -s docs -- -r
"""
import os
import shutil as sh
import tempfile
from pathlib import Path
Expand Down Expand Up @@ -87,7 +88,28 @@ def test(session: nox.Session) -> None:
if _should_install(session):
session.install("-e", ".[test]")
session.run(*split("pybabel compile -d src/pydata_sphinx_theme/locale -D sphinx"))
session.run("pytest", *session.posargs)
session.run("pytest", "-k", "not a11y", *session.posargs)


@nox.session()
def a11y(session: nox.Session) -> None:
"""Run the accessibility test suite."""
if _should_install(session):
session.install("-e", ".[test]")
# Install the drivers that Playwright needs to control the browsers.
if os.environ.get("CI") or os.environ.get("GITPOD_WORKSPACE_ID"):
# CI and other cloud environments are potentially missing system
# dependencies, so we tell Playwright to also install the system
# dependencies
session.run("playwright", "install", "--with-deps")
Comment on lines +100 to +104
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had initially removed this until I had to update the CI tests and realised I duplicated a lot of code trying to use pytest directly like in other tests.
So, for now, I am leaving this here and calling tests through nox.

Note: I am unsure if we should keep the GITPOD_WORKSPACE_ID bit. I am a Gitpod user, but I wonder if it will confuse the rest of the folks who do not use Gitpod.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't heard of Gitpod, but I wasn't confused :) I figured "probably some containerized thing" (I've since looked it up).

else:
# But most dev environments have the needed system dependencies
session.run("playwright", "install")
# Build the docs so we can run accessibility tests against them.
session.run("nox", "-s", "docs")
# The next step would be to open a server to the docs for Playwright, but
# that is done in the test file, along with the accessibility checks.
session.run("pytest", "-k", "a11y", *session.posargs)


@nox.session(name="test-sphinx")
Expand Down
56 changes: 38 additions & 18 deletions package-lock.json
gabalafou marked this conversation as resolved.
Show resolved Hide resolved
gabalafou marked this conversation as resolved.
Show resolved Hide resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"build": "webpack"
},
"devDependencies": {
"axe-core": "^4.6.3",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^3.4.2",
"css-minimizer-webpack-plugin": "^4.2.2",
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ test = [
"pytest",
"pytest-cov",
"pytest-regressions",
"pytest-playwright",
gabalafou marked this conversation as resolved.
Show resolved Hide resolved
"codecov",
]
dev = [
Expand Down
28 changes: 28 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# PyData Sphinx tests

This directory contains the Python tests for the theme. These tests are built with [pytest](https://docs.pytest.org/en/stable/) and are called through `nox`.

`test_build.py` checks that the static HTML output of the build process conforms
gabalafou marked this conversation as resolved.
Show resolved Hide resolved
to various expectations. It builds static HTML pages based on configurations in
the `sites/` directory. The tests run various assertions on the static HTML
output, including snapshot comparisons with previously compiled outputs that are
stored in `test_build/`. In other words, it uses
[`pytest-regressions`](https://pytest-regressions.readthedocs.io/) to compare
the output created during the test run with a previous known and verified output
(stored under `test_build`) to make sure nothing has changed.

`test_a11y.py` checks PyData Sphinx Theme components for accessibility issues.
It's important to note that [only a fraction of accessibility issues can be
caught with automated
testing](https://accessibility.blog.gov.uk/2017/02/24/what-we-found-when-we-tested-tools-on-the-worlds-least-accessible-webpage/).
In contrast to the build test suite, the accessibility suite checks components as
they appear in the browser, meaning with any CSS and JavaScript applied. It does
this by building the PyData Sphinx Theme docs, launching a local server to the
docs, then checking the "Kitchen Sink" example pages with
[Playwright](https://playwright.dev), a program for developers that allows
loading and manipulating pages with various browsers, such as Chrome (chromium),
Firefox (gecko), Safari (WebKit).

The ["Kitchen Sink" examples](https://pydata-sphinx-theme.readthedocs.io/en/stable/examples/kitchen-sink/index.html)
are taken from [sphinx-themes.org](https://sphinx-themes.org/) and showcase
components of the PyData Sphinx Theme, such as admonitions, lists, and headings.
Empty file added tests/__init__.py
Empty file.
110 changes: 110 additions & 0 deletions tests/test_a11y.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Using Axe-core, scan the Kitchen Sink pages for accessibility violations."""

import time
from http.client import HTTPConnection
from pathlib import Path
from subprocess import PIPE, Popen
from urllib.parse import urljoin

import pytest
from playwright.sync_api import Page

from .utils.pretty_axe_results import pretty_axe_results
trallard marked this conversation as resolved.
Show resolved Hide resolved

# Important note: automated accessibility scans can only find a fraction of
# potential accessibility issues.
#
# This test file scans pages from the Kitchen Sink examples with a JavaScript
# library called Axe-core, which checks the page for accessibility violations,
# such as places on the page with poor color contrast that would be hard for
# people with low vision to see.
#
# Just because a page passes the scan with no accessibility violations does
# *not* mean that it will be generally usable by a broad range of disabled
# people. It just means that page is free of common testable accessibility
# pitfalls.

path_repo = Path(__file__).parent.parent
path_docs_build = path_repo / "docs" / "_build" / "html"


@pytest.fixture(scope="module")
def url_base():
"""Start local server on built docs and return the localhost URL as the base URL."""
# Use a port that is not commonly used during development or else you will
# force the developer to stop running their dev server in order to run the
# tests.
port = "8213"
host = "localhost"
url = f"http://{host}:{port}"

# Try starting the server
process = Popen(
["python", "-m", "http.server", port, "--directory", path_docs_build],
stdout=PIPE,
)

# Try connecting to the server
retries = 5
while retries > 0:
conn = HTTPConnection(host, port)
try:
conn.request("HEAD", "/")
response = conn.getresponse()
if response is not None:
yield url
break
except ConnectionRefusedError:
time.sleep(1)
retries -= 1

# If the code above never yields a URL, then we were never able to connect
# to the server and retries == 0.
if not retries:
raise RuntimeError("Failed to start http server in 5 seconds")
else:
# Otherwise the server started and this fixture is done now and we clean
# up by stopping the server.
process.terminate()
process.wait()


@pytest.mark.parametrize("theme", ["light", "dark"])
@pytest.mark.parametrize(
"url_page,selector",
[
("admonitions.html", "#admonitions"),
("api.html", "#api-documentation"),
("blocks.html", "#blocks"),
("generic.html", "#generic-items"),
("images.html", "#images-figures"),
("lists.html", "#lists"),
("structure.html", "#structural-elements"),
("structure.html", "#structural-elements-2"),
("tables.html", "#tables"),
("typography.html", "#typography"),
],
Comment on lines +73 to +86
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

)
def test_axe_core_kitchen_sink(
page: Page, theme: str, url_base: str, url_page: str, selector: str
):
"""Should have no Axe-core violations at the provided theme and page section."""
# Load the page at the provided path
url_base_kitchen_sink = urljoin(url_base, "/examples/kitchen-sink/")
url_full = urljoin(url_base_kitchen_sink, url_page)
page.goto(url_full)

# Run a line of JavaScript that sets the light/dark theme on the page
page.evaluate(f"document.documentElement.dataset.theme = '{theme}'")

# Inject the Axe-core JavaScript library into the page
page.add_script_tag(path="node_modules/axe-core/axe.min.js")

# Run the Axe-core library against a section of the page. (Don't run it
# against the whole page because in this test we're not trying to find
# accessibility violations in the nav, sidebar, footer, or other parts of
# the PyData Sphinx Theme documentation website.)
results = page.evaluate(f"axe.run('{selector}')")

# Expect Axe-core to have found 0 accessibility violations
assert len(results["violations"]) == 0, pretty_axe_results(results)
Empty file added tests/utils/__init__.py
Empty file.
File renamed without changes.
Loading