Skip to content

MAINT - Ensure Playwright tests use test sites and are run in CI #2133

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

Merged
merged 17 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
11 changes: 8 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ doc = [
"ipywidgets",
"graphviz",
]
test = ["pytest", "pytest-cov", "pytest-regressions", "sphinx[test]"]
dev = [
"pyyaml",
"pre-commit",
Expand All @@ -90,7 +89,13 @@ dev = [
"pandoc",
"sphinx-theme-builder[cli]",
]
a11y = ["pytest-playwright"]
test = [
"pytest",
"pytest-cov",
"pytest-regressions",
"sphinx[test]",
"pytest-playwright"
]
i18n = ["Babel", "jinja2"]

[project.entry-points]
Expand All @@ -109,7 +114,7 @@ indent-width = 4
ignore = [
"D107", # Missing docstring in `__init__` | set the docstring in the class
"D205", # 1 blank line required between summary line and description,
"D212", # docstring summary must be on first physical line
"D212", # docstring summary must be on first physical line
"W291", # let pre-commit handle trailing whitespace

]
Expand Down
20 changes: 20 additions & 0 deletions tests/sites/version_switcher/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
11 changes: 11 additions & 0 deletions tests/sites/version_switcher/_static/switcher.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{
"version": "dev",
"url": "https://pydata-sphinx-theme.readthedocs.io/en/latest/"
},
{
"name": "0.16.1 (stable)",
"version": "v0.16.1",
"url": "https://pydata-sphinx-theme.readthedocs.io/en/stable/"
}
]
22 changes: 22 additions & 0 deletions tests/sites/version_switcher/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Test conf file - basic site with version switcher."""

# -- Project information -----------------------------------------------------
project = "PyData Tests"
copyright = "2020, Pydata community"
author = "Pydata community"

root_doc = "index"

# -- General configuration ---------------------------------------------------
html_theme = "pydata_sphinx_theme"

html_static_path = ["_static"]

# Add version switcher
html_theme_options = {
"switcher": {
"json_url": "_static/switcher.json",
"version_match": "dev",
},
"navbar_start": ["navbar-logo", "version-switcher"],
}
15 changes: 15 additions & 0 deletions tests/sites/version_switcher/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Simple site with version switcher
=================================

.. toctree::
:caption: Caption 1
:numbered:

page1
page2
section1/index

Other content
-------------

Nothing to see here
33 changes: 33 additions & 0 deletions tests/sites/version_switcher/page1.rst
Copy link
Collaborator

Choose a reason for hiding this comment

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

[nitpick] having all this link-shortening-related content here is a bit confusing; a future maintainer looking at this site might mistake the test site's purpose. I'm assuming it's a copy-paste job; although it's a bit more work I would tend toward making test sites as bare-bones as possible, with all content reinforcing the test site's purpose. For example, this page might say

Page 1
======

Meaningless content; the point of this test site is the version switcher.

That said, I'm also not opposed to grouping several tests into a single test site (to reduce the ratio of build time to test time), and have 1 page on the site for each test (e.g., a page for link shortening, a page for code blocks, a page for breadcrumbs, etc). But I wouldn't expect that to happen in a test site with the name version_switcher.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I basically copied the test site from the base one and made some adjustments.
I think we could simplify it somehow or even fold this into the base test site but that can be done in a separate pr.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Page 1
======

**normal link**

- https://pydata-sphinx-theme.readthedocs.io/en/latest/

**GitHub**

.. container:: github-container

https://github.com
https://github.com/pydata
https://github.com/pydata/pydata-sphinx-theme
https://github.com/pydata/pydata-sphinx-theme/pull/1012
https://github.com/orgs/pydata/projects/2

**GitLab**

.. container:: gitlab-container

https://gitlab.com
https://gitlab.com/gitlab-org
https://gitlab.com/gitlab-org/gitlab
https://gitlab.com/gitlab-org/gitlab/-/issues/375583
https://gitlab.com/gitlab-org/gitlab/issues/375583
https://gitlab.com/gitlab-org/gitlab/-/issues/
https://gitlab.com/gitlab-org/gitlab/issues/
https://gitlab.com/gitlab-org/gitlab/-/issues
https://gitlab.com/gitlab-org/gitlab/issues
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84669
https://gitlab.com/gitlab-org/gitlab/-/pipelines/511894707
https://gitlab.com/gitlab-com/gl-infra/production/-/issues/6788
9 changes: 9 additions & 0 deletions tests/sites/version_switcher/page2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:html_theme.sidebar_secondary.remove: true
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto the prior [nitpick] comment; a site that only tests the version switcher probably only needs a single page, and if it needs multiple pages, they shouldn't be doing unrelated things like hiding sidebars.


Page :math:`\beta`
==================

.. code-block:: python

import pydata_sphinx_theme as pst
raise RuntimeError('Test of pygments highlighting')
6 changes: 6 additions & 0 deletions tests/sites/version_switcher/section1/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Section 1 index
===============
.. toctree::

page1
https://google.com
2 changes: 2 additions & 0 deletions tests/sites/version_switcher/section1/page1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Section 1 page1
===============
17 changes: 5 additions & 12 deletions tests/test_a11y.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"""Using Axe-core, scan the Kitchen Sink pages for accessibility violations."""
"""
Using Axe-core, scan the Kitchen Sink pages for accessibility violations.
Note that in contrast with the rest of our tests, the accessibility tests in this file
are run against a build of our PST documentation, not purposedly-built test sites.
"""

from urllib.parse import urljoin

Expand Down Expand Up @@ -282,17 +286,6 @@ def test_notebook_ipywidget_output_tab_stop(page: Page, url_base: str) -> None:
assert ipywidget.evaluate("el => el.tabIndex") == 0


def test_breadcrumb_expansion(page: Page, url_base: str) -> None:
"""Foo."""
# page.goto(urljoin(url_base, "community/practices/merge.html"))
# expect(page.get_by_label("Breadcrumb").get_by_role("list")).to_contain_text("Merge and review policy") # noqa: E501
page.set_viewport_size({"width": 1440, "height": 720})
page.goto(urljoin(url_base, "community/topics/config.html"))
expect(page.get_by_label("Breadcrumb").get_by_role("list")).to_contain_text(
"Update Sphinx configuration during the build"
)


@pytest.mark.a11y
def test_search_as_you_type(page: Page, url_base: str) -> None:
"""Search-as-you-type feature should support keyboard navigation.
Expand Down
136 changes: 83 additions & 53 deletions tests/test_playwright.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"""Build minimal test sites with sphinx_build_factory and test them with Playwright."""
"""
Build minimal test sites with sphinx_build_factory and test them with Playwright.
When adding new tests to this file, remember to also add the corresponding test site
to `tests/sites/` or use an existing one.
"""
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is fine for now, but the advice "or use an existing one" might change depending on what others think of my (admittedly opinionated) suggestions to whittle down to a bare-bones one-purpose-per-test-site approach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TBF I like your proposal of simplifying the tests by reducing the number of test sites.
I added this comment for now as we struggled at first to identify why the site was not being built or on what sites we were running tests


from pathlib import Path
from typing import Callable
Expand All @@ -21,6 +25,7 @@
test_sites_dir = repo_path / "docs" / "_build" / "html" / "playwright_tests"


# ------------------------- Helper functions -------------------------
def _is_overflowing(element):
"""Check if an element is being shortened via CSS due to text-overflow property.

Expand All @@ -42,7 +47,8 @@ def _build_test_site(site_name: str, sphinx_build_factory: Callable) -> None:

def _check_test_site(site_name: str, site_path: Path, test_func: Callable):
"""Make the built test site available to Playwright, then run `test_func` on it."""
test_sites_dir.mkdir(exist_ok=True)
# Need to ensure parent directories exist in CI
test_sites_dir.mkdir(exist_ok=True, parents=True)
symlink_path = test_sites_dir / site_name
Copy link
Contributor

Choose a reason for hiding this comment

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

I ran into this as well while debugging; strictly speaking parent=True should not be necessary if the docs have been built. So I think in the event that the parent directories of test_sites_dir don't exist, you've got a build problem that will make the tests fail anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

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

strictly speaking parent=True should not be necessary if the docs have been built [...] in the event that the parent directories of test_sites_dir don't exist, you've got a build problem that will make the tests fail anyway

Is the reason for keeping it that site_path isn't a child of test_sites_dir? That would explain the symlinking a few lines below. (I know I wrote that code but I don't recall exactly why I did it that way)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I thought there might be a reason for the symlink 🤷🏽‍♀️ but I do not see a reason why we would have to keep this as is. I can go and refactor this.

try:
symlink_path.symlink_to(site_path, True)
Expand All @@ -55,65 +61,39 @@ def _check_test_site(site_name: str, site_path: Path, test_func: Callable):
test_sites_dir.rmdir()


def test_version_switcher_highlighting(page: Page, url_base: str) -> None:
# ------------------------- Test functions: style -------------------------
def test_version_switcher_highlighting(
sphinx_build_factory: Callable, page: Page, url_base: str
) -> None:
"""
In sidebar and topbar - version switcher should apply highlight color to currently
selected version.
"""
page.goto(url=url_base)
# no need to include_hidden here ↓↓↓, we just need to get the active version name
button = page.get_by_role("button").filter(has_text="dev")
active_version_name = button.get_attribute("data-active-version-name")
# here we do include_hidden, so sidebar & topbar menus should each have a
# matching entry:
entries = page.get_by_role("option", include_hidden=True).filter(
has_text=active_version_name
)
assert entries.count() == 2
# make sure they're highlighted
for entry in entries.all():
light_mode = "rgb(10, 125, 145)" # pst-color-primary
# dark_mode = "rgb(63, 177, 197)"
expect(entry).to_have_css("color", light_mode)


def test_breadcrumb_expansion(page: Page, url_base: str) -> None:
"""Test breadcrumb text-overflow."""
# wide viewport width → no truncation
page.set_viewport_size({"width": 1440, "height": 720})
page.goto(urljoin(url_base, "community/topics/config.html"))
expect(page.get_by_label("Breadcrumb").get_by_role("list")).to_contain_text(
"Update Sphinx configuration during the build"
)
el = page.get_by_text("Update Sphinx configuration during the build").nth(1)
expect(el).to_have_css("overflow-x", "hidden")
expect(el).to_have_css("text-overflow", "ellipsis")
assert not _is_overflowing(el)
# narrow viewport width → truncation
page.set_viewport_size({"width": 150, "height": 720})
assert _is_overflowing(el)


def test_breadcrumbs_everywhere(
sphinx_build_factory: Callable, page: Page, url_base: str
) -> None:
"""Test breadcrumbs truncate properly when placed in various parts of the layout."""
site_name = "breadcrumbs"
site_name = "version_switcher"
site_path = _build_test_site(site_name, sphinx_build_factory=sphinx_build_factory)

def check_breadcrumb_truncation():
page.goto(
urljoin(url_base, f"playwright_tests/{site_name}/hansel/gretel/house.html")
def check_version_switcher_highlighting():
page.goto(urljoin(url_base, f"playwright_tests/{site_name}/index.html"))
# no need to include_hidden here ↓↓↓, we just need to get the active
# version name
button = page.get_by_role("button").filter(has_text="dev")
active_version_name = button.get_attribute("data-active-version-name")

active_version_name = button.get_attribute("data-active-version-name")
print(active_version_name)
# here we do include_hidden, since we are not adding this in the sidebar
# we should only get one entry
entries = page.get_by_role("option", include_hidden=True).filter(
has_text=active_version_name
)
# sidebar should overflow
text = "In the oven with my sister, so hot right now. Soooo. Hotttt."
el = page.locator("#main-content").get_by_text(text).last
assert _is_overflowing(el)
# footer containers never trigger ellipsis overflow because min-width is content
el = page.locator(".footer-items__center > .footer-item")
assert not _is_overflowing(el)
assert entries.count() == 1
# make sure they're highlighted
for entry in entries.all():
light_mode = "rgb(10, 125, 145)" # pst-color-primary
# dark_mode = "rgb(63, 177, 197)"
expect(entry).to_have_css("color", light_mode)

_check_test_site(site_name, site_path, check_breadcrumb_truncation)
_check_test_site(site_name, site_path, check_version_switcher_highlighting)


def test_colors(sphinx_build_factory: Callable, page: Page, url_base: str) -> None:
Expand Down Expand Up @@ -143,3 +123,53 @@ def check_colors():
expect(el).to_have_css("color", hover_color)

_check_test_site(site_name, site_path, check_colors)


# ------------------------- Test functions: layout & components -----------------------


def test_breadcrumb_expansion(
sphinx_build_factory: Callable, page: Page, url_base: str
) -> None:
"""Test breadcrumb text-overflow."""
site_name = "breadcrumbs"
site_path = _build_test_site(site_name, sphinx_build_factory=sphinx_build_factory)

def check_breadcrumb_expansion():
# wide viewport width → no truncation
page.set_viewport_size({"width": 1440, "height": 720})
page.goto(urljoin(url_base, "community/topics/config.html"))
expect(page.get_by_label("Breadcrumb").get_by_role("list")).to_contain_text(
"Update Sphinx configuration during the build"
)
el = page.get_by_text("Update Sphinx configuration during the build").nth(1)
expect(el).to_have_css("overflow-x", "hidden")
expect(el).to_have_css("text-overflow", "ellipsis")
assert not _is_overflowing(el)
# narrow viewport width → truncation
page.set_viewport_size({"width": 150, "height": 720})
assert _is_overflowing(el)

_check_test_site(site_name, site_path, check_breadcrumb_expansion)


def test_breadcrumbs_everywhere(
sphinx_build_factory: Callable, page: Page, url_base: str
) -> None:
"""Test breadcrumbs truncate properly when placed in various parts of the layout."""
site_name = "breadcrumbs"
site_path = _build_test_site(site_name, sphinx_build_factory=sphinx_build_factory)

def check_breadcrumb_truncation():
page.goto(
urljoin(url_base, f"playwright_tests/{site_name}/hansel/gretel/house.html")
)
# sidebar should overflow
text = "In the oven with my sister, so hot right now. Soooo. Hotttt."
el = page.locator("#main-content").get_by_text(text).last
assert _is_overflowing(el)
# footer containers never trigger ellipsis overflow because min-width is content
el = page.locator(".footer-items__center > .footer-item")
assert not _is_overflowing(el)

_check_test_site(site_name, site_path, check_breadcrumb_truncation)
12 changes: 8 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env_list =
# helping contributors run common tasks without needing to call all the steps
# For example to run the tests: tox run -m tests
labels =
tests = compile-assets, i18n-compile, py312-tests
tests = compile-assets, i18n-compile, py312-tests
a11y = compile-assets, i18n-compile, py312-docs, a11y-tests
i18n = i18n-extract, i18n-compile
live-server = compile-assets, i18n-compile, docs-live
Expand Down Expand Up @@ -62,15 +62,20 @@ description = "Run tests Python and Sphinx versions. If a Sphinx version is spec
# need to ensure the package is installed in editable mode
package = editable
extras =
test # install dependencies - defined in pyproject.toml
test # install dependencies, includes pytest-playwright - defined in pyproject.toml
pass_env = GITHUB_ACTIONS # so we can check if this is run on GitHub Actions
deps =
coverage[toml]
py39-sphinx61-tests: sphinx~=6.1.0
py312-sphinxdev: sphinx[test] @ git+https://github.com/sphinx-doc/sphinx.git@master
depends =
compile-assets,
i18n-compile
allowlist_externals=
playwright
bash
commands =
bash -c 'if [[ "{env:GITHUB_ACTIONS:}" == "true" ]]; then playwright install --with-deps; else playwright install; fi'
py3{9,10,11,12}{,-sphinx61,-sphinxdev,}-tests: coverage run -m pytest -m "not a11y" {posargs}
py3{9,10,11,12}{,-sphinx61,-sphinxdev,}-tests-no-cov: pytest -m "not a11y" {posargs}

Expand All @@ -82,8 +87,7 @@ description = run accessibility tests with Playwright and axe-core
base_python = py312 # keep in sync with tests.yml
pass_env = GITHUB_ACTIONS # so we can check if this is run on GitHub Actions
extras =
test
a11y
test # install dependencies, includes pytest-playwright - defined in pyproject.toml
depends =
compile-assets,
i18n-compile
Expand Down
Loading