From f6b22a99ba1e419178226b9ad79894e9052d6655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sat, 28 Dec 2024 22:03:11 +0100 Subject: [PATCH] feat: implement :header: and screenshot_default_headers --- .github/workflows/unit-test.yml | 22 ++++++++++-- pyproject.toml | 1 + sphinxcontrib/screenshot.py | 18 ++++++++-- tests/roots/test-headers/__init__.py | 0 tests/roots/test-headers/conf.py | 21 ++++++++++++ .../roots/test-headers/example_headers_app.py | 32 ++++++++++++++++++ tests/roots/test-headers/index.rst | 6 ++++ tests/test_headers.py | 32 ++++++++++++++++++ tests/test_headers/test_headers.png | Bin 0 -> 7019 bytes 9 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 tests/roots/test-headers/__init__.py create mode 100644 tests/roots/test-headers/conf.py create mode 100644 tests/roots/test-headers/example_headers_app.py create mode 100644 tests/roots/test-headers/index.rst create mode 100644 tests/test_headers.py create mode 100644 tests/test_headers/test_headers.png diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index e3f4310..2a0400f 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -27,11 +27,27 @@ jobs: - name: Playwright install run: playwright install - name: Run unittest - run: pytest + id: pytest + shell: python + run: | + import os + import subprocess + import sys + + result = subprocess.run(['pytest'], capture_output=True, text=True) + output = result.stdout + print(output) + if result.returncode > 0: + with open(os.getenv("GITHUB_OUTPUT"), "a") as fd: + for line in output.splitlines(): + if "obtained" in line: + fd.write(f"{line}\n") + + sys.exit(result.returncode) - name: Upload failed image artifact if: failure() uses: actions/upload-artifact@v4 with: name: failed-images-${{ matrix.os }}-${{ matrix.python-version }} - # https://docs.pytest.org/en/stable/how-to/tmp_path.html#temporary-directory-location-and-retention - path: /tmp/pytest-of-*/pytest-*/*/*/*.obtained.png + path: | + ${{ steps.pytest.outputs.pytest }} diff --git a/pyproject.toml b/pyproject.toml index 50132b9..1855316 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ dev = [ "pre-commit", "pytest", "pytest-regressions[image]", + "pytesseract", "sphinx[test]", "toml", "tox", diff --git a/sphinxcontrib/screenshot.py b/sphinxcontrib/screenshot.py index f23a189..2d998e9 100644 --- a/sphinxcontrib/screenshot.py +++ b/sphinxcontrib/screenshot.py @@ -107,6 +107,7 @@ class ScreenshotDirective(SphinxDirective): 'color-scheme': str, 'full-page': directives.flag, 'context': str, + 'headers': directives.unchanged, } pool = ThreadPoolExecutor() @@ -116,7 +117,8 @@ def take_screenshot( init_script: str, interactions: str, generate_pdf: bool, color_scheme: ColorScheme, full_page: bool, context_builder: typing.Optional[typing.Callable[[Browser, str, str], - BrowserContext]]): + BrowserContext]], + headers: dict): """Takes a screenshot with Playwright's Chromium browser. Args: @@ -153,6 +155,7 @@ def take_screenshot( try: if init_script: page.add_init_script(init_script) + page.set_extra_http_headers(headers) page.goto(url) page.wait_for_load_state('networkidle') @@ -201,6 +204,13 @@ def run(self) -> typing.List[nodes.Node]: self.env.config.screenshot_default_full_page) context = self.options.get('context', '') interactions = '\n'.join(self.content) + headers = self.options.get('headers', '') + + request_headers = {**self.env.config.screenshot_default_headers} + if headers: + for header in headers.strip().split("\n"): + name, value = header.split(" ", 1) + request_headers[name] = value if urlparse(url).scheme not in {'http', 'https'}: raise RuntimeError( @@ -227,7 +237,7 @@ def run(self) -> typing.List[nodes.Node]: fut = self.pool.submit(ScreenshotDirective.take_screenshot, url, browser, width, height, filepath, screenshot_init_script, interactions, pdf, color_scheme, full_page, - context_builder) + context_builder, request_headers) fut.result() # Create image and figure nodes @@ -317,6 +327,10 @@ def setup(app: Sphinx) -> Meta: 'env', types=[dict[str, str]], description="A dict of paths to Playwright context build methods") + app.add_config_value( + 'screenshot_default_headers', {}, + 'env', + description="The default headers to pass in requests") app.add_config_value( 'screenshot_apps', {}, 'env', diff --git a/tests/roots/test-headers/__init__.py b/tests/roots/test-headers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/roots/test-headers/conf.py b/tests/roots/test-headers/conf.py new file mode 100644 index 0000000..d82268b --- /dev/null +++ b/tests/roots/test-headers/conf.py @@ -0,0 +1,21 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pathlib +import sys + +sys.path.insert(0, str(pathlib.Path(__file__).parent.resolve())) + +extensions = ['sphinxcontrib.screenshot'] +screenshot_apps = {"example": "example_headers_app:create_app"} +screenshot_default_headers = {"Accept-Language": "fr-FR,fr"} diff --git a/tests/roots/test-headers/example_headers_app.py b/tests/roots/test-headers/example_headers_app.py new file mode 100644 index 0000000..248a90d --- /dev/null +++ b/tests/roots/test-headers/example_headers_app.py @@ -0,0 +1,32 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +def create_app(sphinx_app): + + def hello_world_app(environ, start_response): + headers = [('Content-type', 'text/html; charset=utf-8')] + start_response('200 OK', headers) + style = b"font-family: arial; font-size: 10px; margin: 0; padding: 0" + + accept_encoding = environ.get('HTTP_ACCEPT_ENCODING', 'unset').encode() + accept_language = environ.get('HTTP_ACCEPT_LANGUAGE', 'unset').encode() + authorization = environ.get('HTTP_AUTHORIZATION', 'unset').encode() + + return [ + b"" + b'' + b"Accept-Encoding: " + + accept_encoding + b"
" + b"Accept-Language: " + accept_language + + b"
" + b"Authorization: " + authorization + b"
" + b"" + b"" + ] + + return hello_world_app diff --git a/tests/roots/test-headers/index.rst b/tests/roots/test-headers/index.rst new file mode 100644 index 0000000..c19e729 --- /dev/null +++ b/tests/roots/test-headers/index.rst @@ -0,0 +1,6 @@ +.. screenshot:: |example| + :width: 800 + :height: 600 + :headers: + Authorization Bearer foobar + Accept-Encoding gzip, deflate, br diff --git a/tests/test_headers.py b/tests/test_headers.py new file mode 100644 index 0000000..657d7fe --- /dev/null +++ b/tests/test_headers.py @@ -0,0 +1,32 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from io import StringIO + +import pytest +from bs4 import BeautifulSoup +from sphinx.testing.util import SphinxTestApp + + +@pytest.mark.sphinx('html', testroot="headers") +def test_headers(app: SphinxTestApp, status: StringIO, warning: StringIO, + image_regression) -> None: + app.build() + out_html = app.outdir / "index.html" + + soup = BeautifulSoup(out_html.read_text(), "html.parser") + imgs = soup.find_all('img') + + img_path = app.outdir / imgs[0]['src'] + with open(img_path, "rb") as fd: + image_regression.check(fd.read()) diff --git a/tests/test_headers/test_headers.png b/tests/test_headers/test_headers.png new file mode 100644 index 0000000000000000000000000000000000000000..0eea87e0533a5f012e0620541e4daec4d02cf3ab GIT binary patch literal 7019 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 literal 0 HcmV?d00001