Skip to content

Commit 2513821

Browse files
authored
Merge pull request #260 from ExaltedBagel/video-support
Video support for mp4 format
2 parents 23186cd + 588c41b commit 2513821

File tree

4 files changed

+103
-27
lines changed

4 files changed

+103
-27
lines changed

pytest_html/extras.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
FORMAT_JSON = "json"
88
FORMAT_TEXT = "text"
99
FORMAT_URL = "url"
10+
FORMAT_VIDEO = "video"
1011

1112

1213
def extra(content, format, name=None, mime_type=None, extension=None):
@@ -49,3 +50,11 @@ def text(content, name="Text"):
4950

5051
def url(content, name="URL"):
5152
return extra(content, FORMAT_URL, name)
53+
54+
55+
def video(content, name="Video", mime_type="video/mp4", extension="mp4"):
56+
return extra(content, FORMAT_VIDEO, name, mime_type, extension)
57+
58+
59+
def mp4(content, name="Video"):
60+
return video(content, name)

pytest_html/plugin.py

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -181,33 +181,7 @@ def create_asset(
181181
def append_extra_html(self, extra, extra_index, test_index):
182182
href = None
183183
if extra.get("format") == extras.FORMAT_IMAGE:
184-
content = extra.get("content")
185-
try:
186-
is_uri_or_path = content.startswith(("file", "http")) or isfile(
187-
content
188-
)
189-
except ValueError:
190-
# On Windows, os.path.isfile throws this exception when
191-
# passed a b64 encoded image.
192-
is_uri_or_path = False
193-
if is_uri_or_path:
194-
if self.self_contained:
195-
warnings.warn(
196-
"Self-contained HTML report "
197-
"includes link to external "
198-
"resource: {}".format(content)
199-
)
200-
html_div = html.a(html.img(src=content), href=content)
201-
elif self.self_contained:
202-
src = "data:{};base64,{}".format(extra.get("mime_type"), content)
203-
html_div = html.img(src=src)
204-
else:
205-
content = b64decode(content.encode("utf-8"))
206-
href = src = self.create_asset(
207-
content, extra_index, test_index, extra.get("extension"), "wb"
208-
)
209-
html_div = html.a(html.img(src=src), href=href)
210-
self.additional_html.append(html.div(html_div, class_="image"))
184+
self._append_image(extra, extra_index, test_index)
211185

212186
elif extra.get("format") == extras.FORMAT_HTML:
213187
self.additional_html.append(html.div(raw(extra.get("content"))))
@@ -235,6 +209,9 @@ def append_extra_html(self, extra, extra_index, test_index):
235209
elif extra.get("format") == extras.FORMAT_URL:
236210
href = extra.get("content")
237211

212+
elif extra.get("format") == extras.FORMAT_VIDEO:
213+
self._append_video(extra, extra_index, test_index)
214+
238215
if href is not None:
239216
self.links_html.append(
240217
html.a(
@@ -276,6 +253,52 @@ def append_log_html(self, report, additional_html):
276253
log.append("No log output captured.")
277254
additional_html.append(log)
278255

256+
def _make_media_html_div(
257+
self, extra, extra_index, test_index, base_extra_string, base_extra_class
258+
):
259+
content = extra.get("content")
260+
try:
261+
is_uri_or_path = content.startswith(("file", "http")) or isfile(content)
262+
except ValueError:
263+
# On Windows, os.path.isfile throws this exception when
264+
# passed a b64 encoded image.
265+
is_uri_or_path = False
266+
if is_uri_or_path:
267+
if self.self_contained:
268+
warnings.warn(
269+
"Self-contained HTML report "
270+
"includes link to external "
271+
f"resource: {content}"
272+
)
273+
274+
html_div = html.a(
275+
raw(base_extra_string.format(extra.get("content"))), href=content
276+
)
277+
elif self.self_contained:
278+
src = f"data:{extra.get('mime_type')};base64,{content}"
279+
html_div = raw(base_extra_string.format(src))
280+
else:
281+
content = b64decode(content.encode("utf-8"))
282+
href = src = self.create_asset(
283+
content, extra_index, test_index, extra.get("extension"), "wb"
284+
)
285+
html_div = html.a(class_=base_extra_class, target="_blank", href=href)
286+
return html_div
287+
288+
def _append_image(self, extra, extra_index, test_index):
289+
image_base = '<img src="{}"/>'
290+
html_div = self._make_media_html_div(
291+
extra, extra_index, test_index, image_base, "image"
292+
)
293+
self.additional_html.append(html.div(html_div, class_="image"))
294+
295+
def _append_video(self, extra, extra_index, test_index):
296+
video_base = '<video controls><source src="{}" type="video/mp4"></video>'
297+
html_div = self._make_media_html_div(
298+
extra, extra_index, test_index, video_base, "video"
299+
)
300+
self.additional_html.append(html.div(html_div, class_="video"))
301+
279302
def _appendrow(self, outcome, report):
280303
result = self.TestResult(outcome, report, self.logfile, self.config)
281304
if result.row_table is not None:

pytest_html/resources/style.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,19 @@ div.image {
113113
div.image img {
114114
width: 320px
115115
}
116+
div.video {
117+
border: 1px solid #e6e6e6;
118+
float: right;
119+
height: 240px;
120+
margin-left: 5px;
121+
overflow: hidden;
122+
width: 320px
123+
}
124+
div.video video {
125+
overflow: hidden;
126+
width: 320px;
127+
height: 240px;
128+
}
116129
.collapsed {
117130
display: none;
118131
}

testing/test_pytest_html.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,37 @@ def test_extra_image_windows(self, mocker, testdir):
418418
self.test_extra_image(testdir, "image/png", "png")
419419
assert mock_isfile.call_count == 1
420420

421+
@pytest.mark.parametrize(
422+
"mime_type, extension", [("video/mp4", "mp4")],
423+
)
424+
def test_extra_video(self, testdir, mime_type, extension):
425+
content = str(random.random())
426+
testdir.makeconftest(
427+
f"""
428+
import pytest
429+
@pytest.hookimpl(hookwrapper=True)
430+
def pytest_runtest_makereport(item, call):
431+
outcome = yield
432+
report = outcome.get_result()
433+
if report.when == 'call':
434+
from pytest_html import extras
435+
report.extra = [extras.{extension}('{content}')]
436+
"""
437+
)
438+
testdir.makepyfile("def test_pass(): pass")
439+
result, html = run(testdir, "report.html", "--self-contained-html")
440+
assert result.ret == 0
441+
src = f"data:{mime_type};base64,{content}"
442+
assert (
443+
f'<video controls><source src="{src}" type="{mime_type}"></video>' in html
444+
)
445+
446+
def test_extra_video_windows(self, mocker, testdir):
447+
mock_isfile = mocker.patch("pytest_html.plugin.isfile")
448+
mock_isfile.side_effect = ValueError("stat: path too long for Windows")
449+
self.test_extra_video(testdir, "video/mp4", "mp4")
450+
assert mock_isfile.call_count == 1
451+
421452
@pytest.mark.parametrize(
422453
"content", [("u'\u0081'"), ("'foo'"), ("b'\\xe2\\x80\\x93'")]
423454
)

0 commit comments

Comments
 (0)