sphinxcontrib-screenshot 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sphinxcontrib-screenshot might be problematic. Click here for more details.
- {sphinxcontrib_screenshot-0.2.0/sphinxcontrib_screenshot.egg-info → sphinxcontrib_screenshot-0.2.1}/PKG-INFO +1 -1
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/pyproject.toml +2 -1
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/sphinxcontrib/screenshot.py +80 -22
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1/sphinxcontrib_screenshot.egg-info}/PKG-INFO +1 -1
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/sphinxcontrib_screenshot.egg-info/SOURCES.txt +2 -0
- sphinxcontrib_screenshot-0.2.1/tests/test_locale.py +48 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_root.py +15 -2
- sphinxcontrib_screenshot-0.2.1/tests/test_timezone.py +48 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/LICENSE +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/README.md +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/setup.cfg +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/sphinxcontrib_screenshot.egg-info/dependency_links.txt +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/sphinxcontrib_screenshot.egg-info/requires.txt +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/sphinxcontrib_screenshot.egg-info/top_level.txt +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_browsers.py +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_color_scheme.py +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_contexts.py +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_full_page.py +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_headers.py +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_pdf.py +0 -0
- {sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_wsgi_apps.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sphinxcontrib-screenshot"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "A Sphinx extension to embed webpage screenshots."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -69,6 +69,7 @@ include-package-data = true
|
|
|
69
69
|
|
|
70
70
|
[tool.yapf]
|
|
71
71
|
based_on_style = "yapf"
|
|
72
|
+
column_limit = 79
|
|
72
73
|
|
|
73
74
|
[tool.flake8]
|
|
74
75
|
indent-size = 2
|
{sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/sphinxcontrib/screenshot.py
RENAMED
|
@@ -40,6 +40,9 @@ Meta = typing.TypedDict('Meta', {
|
|
|
40
40
|
'parallel_write_safe': bool
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
+
ContextBuilder = typing.Optional[typing.Callable[[Browser, str, str],
|
|
44
|
+
BrowserContext]]
|
|
45
|
+
|
|
43
46
|
|
|
44
47
|
class ScreenshotDirective(SphinxDirective, Figure):
|
|
45
48
|
"""Sphinx Screenshot Dirctive.
|
|
@@ -79,6 +82,24 @@ class ScreenshotDirective(SphinxDirective, Figure):
|
|
|
79
82
|
.. screenshot:: http://www.example.com
|
|
80
83
|
:pdf:
|
|
81
84
|
```
|
|
85
|
+
|
|
86
|
+
You can take a screenshot of a local file using a root-relative path.
|
|
87
|
+
|
|
88
|
+
```rst
|
|
89
|
+
.. screenshot:: /static/example.html
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Or you can use a document-relative path.
|
|
93
|
+
|
|
94
|
+
```rst
|
|
95
|
+
.. screenshot:: ./example.html
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `file://` protocol is also supported.
|
|
99
|
+
|
|
100
|
+
```rst
|
|
101
|
+
.. screenshot:: file:///path/to/your/file.html
|
|
102
|
+
```
|
|
82
103
|
"""
|
|
83
104
|
|
|
84
105
|
required_arguments = 1 # URL
|
|
@@ -93,21 +114,24 @@ class ScreenshotDirective(SphinxDirective, Figure):
|
|
|
93
114
|
'full-page': directives.flag,
|
|
94
115
|
'context': str,
|
|
95
116
|
'headers': directives.unchanged,
|
|
117
|
+
'locale': str,
|
|
118
|
+
'timezone': str,
|
|
96
119
|
}
|
|
97
120
|
pool = ThreadPoolExecutor()
|
|
98
121
|
|
|
99
122
|
@staticmethod
|
|
100
|
-
def take_screenshot(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
123
|
+
def take_screenshot(url: str, browser_name: str, viewport_width: int,
|
|
124
|
+
viewport_height: int, filepath: str, init_script: str,
|
|
125
|
+
interactions: str, generate_pdf: bool,
|
|
126
|
+
color_scheme: ColorScheme, full_page: bool,
|
|
127
|
+
context_builder: ContextBuilder, headers: dict,
|
|
128
|
+
locale: typing.Optional[str],
|
|
129
|
+
timezone: typing.Optional[str]):
|
|
107
130
|
"""Takes a screenshot with Playwright's Chromium browser.
|
|
108
131
|
|
|
109
132
|
Args:
|
|
110
133
|
url (str): The HTTP/HTTPS URL of the webpage to screenshot.
|
|
134
|
+
browser_name (str): Browser to use ('chromium', 'firefox' or 'webkit').
|
|
111
135
|
viewport_width (int): The width of the screenshot in pixels.
|
|
112
136
|
viewport_height (int): The height of the screenshot in pixels.
|
|
113
137
|
filepath (str): The path to save the screenshot to.
|
|
@@ -118,10 +142,14 @@ class ScreenshotDirective(SphinxDirective, Figure):
|
|
|
118
142
|
after the page was loaded.
|
|
119
143
|
generate_pdf (bool): Generate a PDF file along with the screenshot.
|
|
120
144
|
color_scheme (str): The preferred color scheme. Can be 'light' or 'dark'.
|
|
145
|
+
full_page (bool): Take a full page screenshot.
|
|
121
146
|
context: A method to build the Playwright context.
|
|
147
|
+
headers (dict): Custom request header.
|
|
148
|
+
locale (str, optional): User locale for the request.
|
|
149
|
+
timezone (str, optional): User timezone for the request.
|
|
122
150
|
"""
|
|
123
151
|
with sync_playwright() as playwright:
|
|
124
|
-
browser = getattr(playwright, browser_name).launch()
|
|
152
|
+
browser: Browser = getattr(playwright, browser_name).launch()
|
|
125
153
|
|
|
126
154
|
if context_builder:
|
|
127
155
|
try:
|
|
@@ -131,7 +159,8 @@ class ScreenshotDirective(SphinxDirective, Figure):
|
|
|
131
159
|
'Timeout error occured at %s in executing py init script %s' %
|
|
132
160
|
(url, context_builder.__name__))
|
|
133
161
|
else:
|
|
134
|
-
context = browser.new_context(
|
|
162
|
+
context = browser.new_context(
|
|
163
|
+
color_scheme=color_scheme, locale=locale, timezone_id=timezone)
|
|
135
164
|
|
|
136
165
|
page = context.new_page()
|
|
137
166
|
page.set_default_timeout(10000)
|
|
@@ -173,14 +202,33 @@ class ScreenshotDirective(SphinxDirective, Figure):
|
|
|
173
202
|
|
|
174
203
|
def run(self) -> typing.Sequence[nodes.Node]:
|
|
175
204
|
screenshot_init_script: str = self.env.config.screenshot_init_script or ''
|
|
205
|
+
docdir = os.path.dirname(self.env.doc2path(self.env.docname))
|
|
176
206
|
|
|
177
207
|
# Ensure the screenshots directory exists
|
|
178
208
|
ss_dirpath = os.path.join(self.env.app.outdir, '_static', 'screenshots')
|
|
179
209
|
os.makedirs(ss_dirpath, exist_ok=True)
|
|
180
210
|
|
|
181
211
|
# Parse parameters
|
|
182
|
-
|
|
183
|
-
|
|
212
|
+
raw_path = self.arguments[0]
|
|
213
|
+
url_or_filepath = self.evaluate_substitutions(raw_path)
|
|
214
|
+
|
|
215
|
+
# Check if the path is a local file path.
|
|
216
|
+
if urlparse(url_or_filepath).scheme == '':
|
|
217
|
+
# root-relative path
|
|
218
|
+
if url_or_filepath.startswith('/'):
|
|
219
|
+
url_or_filepath = os.path.join(self.env.srcdir,
|
|
220
|
+
url_or_filepath.lstrip('/'))
|
|
221
|
+
# document-relative path
|
|
222
|
+
else:
|
|
223
|
+
url_or_filepath = os.path.join(docdir, url_or_filepath)
|
|
224
|
+
url_or_filepath = "file://" + os.path.normpath(url_or_filepath)
|
|
225
|
+
|
|
226
|
+
if urlparse(url_or_filepath).scheme not in {'http', 'https', 'file'}:
|
|
227
|
+
raise RuntimeError(
|
|
228
|
+
f'Invalid URL: {url_or_filepath}. ' +
|
|
229
|
+
'Only HTTP/HTTPS/FILE URLs or root/document-relative file paths ' +
|
|
230
|
+
'are supported.')
|
|
231
|
+
|
|
184
232
|
interactions = self.options.get('interactions', '')
|
|
185
233
|
browser = self.options.get('browser',
|
|
186
234
|
self.env.config.screenshot_default_browser)
|
|
@@ -193,6 +241,10 @@ class ScreenshotDirective(SphinxDirective, Figure):
|
|
|
193
241
|
pdf = 'pdf' in self.options
|
|
194
242
|
full_page = ('full-page' in self.options or
|
|
195
243
|
self.env.config.screenshot_default_full_page)
|
|
244
|
+
locale = self.options.get('locale',
|
|
245
|
+
self.env.config.screenshot_default_locale)
|
|
246
|
+
timezone = self.options.get('timezone',
|
|
247
|
+
self.env.config.screenshot_default_timezone)
|
|
196
248
|
context = self.options.get('context', '')
|
|
197
249
|
headers = self.options.get('headers', '')
|
|
198
250
|
|
|
@@ -202,13 +254,9 @@ class ScreenshotDirective(SphinxDirective, Figure):
|
|
|
202
254
|
name, value = header.split(" ", 1)
|
|
203
255
|
request_headers[name] = value
|
|
204
256
|
|
|
205
|
-
if urlparse(url).scheme not in {'http', 'https'}:
|
|
206
|
-
raise RuntimeError(
|
|
207
|
-
f'Invalid URL: {url}. Only HTTP/HTTPS URLs are supported.')
|
|
208
|
-
|
|
209
257
|
# Generate filename based on hash of parameters
|
|
210
258
|
hash_input = "_".join([
|
|
211
|
-
|
|
259
|
+
raw_path, browser,
|
|
212
260
|
str(viewport_height),
|
|
213
261
|
str(viewport_width), color_scheme, context, interactions,
|
|
214
262
|
str(full_page)
|
|
@@ -224,15 +272,15 @@ class ScreenshotDirective(SphinxDirective, Figure):
|
|
|
224
272
|
|
|
225
273
|
# Check if the file already exists. If not, take a screenshot
|
|
226
274
|
if not os.path.exists(filepath):
|
|
227
|
-
fut = self.pool.submit(ScreenshotDirective.take_screenshot,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
color_scheme, full_page,
|
|
231
|
-
request_headers
|
|
275
|
+
fut = self.pool.submit(ScreenshotDirective.take_screenshot,
|
|
276
|
+
url_or_filepath, browser, viewport_width,
|
|
277
|
+
viewport_height, filepath, screenshot_init_script,
|
|
278
|
+
interactions, pdf, color_scheme, full_page,
|
|
279
|
+
context_builder, request_headers, locale,
|
|
280
|
+
timezone)
|
|
232
281
|
fut.result()
|
|
233
282
|
|
|
234
283
|
# Create image and figure nodes
|
|
235
|
-
docdir = os.path.dirname(self.env.doc2path(self.env.docname))
|
|
236
284
|
rel_ss_dirpath = os.path.relpath(ss_dirpath, start=docdir)
|
|
237
285
|
rel_filepath = os.path.join(rel_ss_dirpath, filename).replace(os.sep, '/')
|
|
238
286
|
|
|
@@ -311,6 +359,16 @@ def setup(app: Sphinx) -> Meta:
|
|
|
311
359
|
'screenshot_default_headers', {},
|
|
312
360
|
'env',
|
|
313
361
|
description="The default headers to pass in requests")
|
|
362
|
+
app.add_config_value(
|
|
363
|
+
'screenshot_default_locale',
|
|
364
|
+
None,
|
|
365
|
+
'env',
|
|
366
|
+
description="The default locale in requests")
|
|
367
|
+
app.add_config_value(
|
|
368
|
+
'screenshot_default_timezone',
|
|
369
|
+
None,
|
|
370
|
+
'env',
|
|
371
|
+
description="The default timezone in requests")
|
|
314
372
|
app.add_config_value(
|
|
315
373
|
'screenshot_apps', {},
|
|
316
374
|
'env',
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from io import StringIO
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
from bs4 import BeautifulSoup
|
|
18
|
+
from sphinx.testing.util import SphinxTestApp
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.sphinx('html', testroot="locale")
|
|
22
|
+
def test_locale(app: SphinxTestApp, status: StringIO, warning: StringIO,
|
|
23
|
+
image_regression) -> None:
|
|
24
|
+
app.build()
|
|
25
|
+
out_html = app.outdir / "index.html"
|
|
26
|
+
|
|
27
|
+
soup = BeautifulSoup(out_html.read_text(), "html.parser")
|
|
28
|
+
imgs = soup.find_all('img')
|
|
29
|
+
|
|
30
|
+
img_path = app.outdir / imgs[0]['src']
|
|
31
|
+
with open(img_path, "rb") as fd:
|
|
32
|
+
# Tolerance of 0.5%
|
|
33
|
+
image_regression.check(fd.read(), diff_threshold=0.5)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.sphinx('html', testroot="default-locale")
|
|
37
|
+
def test_default_locale(app: SphinxTestApp, status: StringIO,
|
|
38
|
+
warning: StringIO, image_regression) -> None:
|
|
39
|
+
app.build()
|
|
40
|
+
out_html = app.outdir / "index.html"
|
|
41
|
+
|
|
42
|
+
soup = BeautifulSoup(out_html.read_text(), "html.parser")
|
|
43
|
+
imgs = soup.find_all('img')
|
|
44
|
+
|
|
45
|
+
img_path = app.outdir / imgs[0]['src']
|
|
46
|
+
with open(img_path, "rb") as fd:
|
|
47
|
+
# Tolerance of 0.5%
|
|
48
|
+
image_regression.check(fd.read(), diff_threshold=0.5)
|
|
@@ -23,8 +23,7 @@ from sphinx.testing.util import SphinxTestApp
|
|
|
23
23
|
@pytest.mark.sphinx('html', testroot='root')
|
|
24
24
|
def test_default(app: SphinxTestApp) -> None:
|
|
25
25
|
app.build()
|
|
26
|
-
|
|
27
|
-
soup = BeautifulSoup(out_html.read_text(), "html.parser")
|
|
26
|
+
soup = BeautifulSoup((app.outdir / "index.html").read_text(), "html.parser")
|
|
28
27
|
|
|
29
28
|
# Every screenshot directive should become an image.
|
|
30
29
|
imgs = soup.find_all('img')
|
|
@@ -43,6 +42,20 @@ def test_default(app: SphinxTestApp) -> None:
|
|
|
43
42
|
assert list(Image.open(imgsrc_before_interaction).getdata()) != list(
|
|
44
43
|
Image.open(imgsrc_after_interaction).getdata())
|
|
45
44
|
|
|
45
|
+
soup = BeautifulSoup((app.outdir / "sections" / "index.html").read_text(),
|
|
46
|
+
"html.parser")
|
|
47
|
+
|
|
48
|
+
# Every screenshot directive should become an image.
|
|
49
|
+
imgs = soup.find_all('img')
|
|
50
|
+
assert len(list(imgs)) == 2
|
|
51
|
+
|
|
52
|
+
# The images should be the same.
|
|
53
|
+
imgsrc_relative = app.outdir / "sections" / imgs[0]['src']
|
|
54
|
+
imgsrc_absolute = app.outdir / "sections" / imgs[1]['src']
|
|
55
|
+
assert imgsrc_relative != imgsrc_absolute
|
|
56
|
+
assert list(Image.open(imgsrc_relative).getdata()) == list(
|
|
57
|
+
Image.open(imgsrc_absolute).getdata())
|
|
58
|
+
|
|
46
59
|
|
|
47
60
|
@pytest.mark.sphinx('html', testroot="default-size")
|
|
48
61
|
def test_default_size(app: SphinxTestApp, status: StringIO, warning: StringIO,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from io import StringIO
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
from bs4 import BeautifulSoup
|
|
18
|
+
from sphinx.testing.util import SphinxTestApp
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.sphinx('html', testroot="timezone")
|
|
22
|
+
def test_timezone(app: SphinxTestApp, status: StringIO, warning: StringIO,
|
|
23
|
+
image_regression) -> None:
|
|
24
|
+
app.build()
|
|
25
|
+
out_html = app.outdir / "index.html"
|
|
26
|
+
|
|
27
|
+
soup = BeautifulSoup(out_html.read_text(), "html.parser")
|
|
28
|
+
imgs = soup.find_all('img')
|
|
29
|
+
|
|
30
|
+
img_path = app.outdir / imgs[0]['src']
|
|
31
|
+
with open(img_path, "rb") as fd:
|
|
32
|
+
# Tolerance of 0.5%
|
|
33
|
+
image_regression.check(fd.read(), diff_threshold=0.5)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.sphinx('html', testroot="default-timezone")
|
|
37
|
+
def test_default_timezone(app: SphinxTestApp, status: StringIO,
|
|
38
|
+
warning: StringIO, image_regression) -> None:
|
|
39
|
+
app.build()
|
|
40
|
+
out_html = app.outdir / "index.html"
|
|
41
|
+
|
|
42
|
+
soup = BeautifulSoup(out_html.read_text(), "html.parser")
|
|
43
|
+
imgs = soup.find_all('img')
|
|
44
|
+
|
|
45
|
+
img_path = app.outdir / imgs[0]['src']
|
|
46
|
+
with open(img_path, "rb") as fd:
|
|
47
|
+
# Tolerance of 0.5%
|
|
48
|
+
image_regression.check(fd.read(), diff_threshold=0.5)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinxcontrib_screenshot-0.2.0 → sphinxcontrib_screenshot-0.2.1}/tests/test_color_scheme.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|