Skip to content

Commit

Permalink
Fix: Borken HTML in jinja template (pytest-dev#712)
Browse files Browse the repository at this point in the history
  • Loading branch information
BeyondEvil authored Aug 13, 2023
1 parent 8bc9a5d commit bd0c530
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 70 deletions.
75 changes: 41 additions & 34 deletions src/pytest_html/resources/index.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,27 @@
<td></td>
</tr>
</template>
<template id="template_results-table__head">
<thead id="results-table-head">
<tr>
{%- for th in table_head %}
{{ th|safe }}
{%- endfor %}
</tr>
</thead>
</template>
<template id="template_results-table__body--empty">
<tbody class="results-table-row">
<tr id="not-found-message">
<td colspan="{{ table_head|length }}">No results found. Check the filters.</th>
</tr>
</template>
<template id="template_results-table__tbody">
<tbody class="results-table-row">
<tr class="collapsible">
</tr>
<tr class="extras-row">
<td class="extra" colspan="4">
<td class="extra" colspan="{{ table_head|length }}">
<div class="extraHTML"></div>
<div class="media">
<div class="media-container">
Expand All @@ -52,27 +67,15 @@
</tr>
</tbody>
</template>
<template id="template_results-table__head">
<thead id="results-table-head">
<tr>
{%- for th in table_head %}
{{ th|safe }}
{%- endfor %}
</tr>
</thead>
</template>
<template id="template_results-table__head--empty">
<tr id="not-found-message">
<th colspan="4">No results found. Check the filters.</th>
</tr>
</template>
<!-- END TEMPLATES -->
<div class="summary">
<div class="summary__data">
<h2>Summary</h2>
{%- for p in additional_summary['prefix'] %}
{{ p|safe }}
{%- endfor %}
<div class="additional-summary prefix">
{%- for p in additional_summary['prefix'] %}
{{ p|safe }}
{%- endfor %}
</div>
<p class="run-count">{{ run_count }}</p>
<p class="filter">(Un)check the boxes to filter the results.</p>
<div class="summary__reload">
Expand All @@ -81,25 +84,29 @@
</div>
</div>
<div class="summary__spacer"></div>
<div class="controls">
<div class="filters">
{%- for result, values in outcomes.items() %}
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="{{ result }}" {{ "disabled" if values["value"] == 0 }}/>
<span class="{{ result }}">{{ values["value"] }} {{ values["label"] }}{{ "," if result != "rerun" }}</span>
{%- endfor %}
</div>
<div class="collapse">
<button id="show_all_details">Show all details</button>&nbsp;/&nbsp;<button id="hide_all_details">Hide all details</button>
<div>
<div class="controls">
<div class="filters">
{%- for result, values in outcomes.items() %}
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="{{ result }}" {{ "disabled" if values["value"] == 0 }}/>
<span class="{{ result }}">{{ values["value"] }} {{ values["label"] }}{{ "," if result != "rerun" }}</span>
{%- endfor %}
</div>
<div class="collapse">
<button id="show_all_details">Show all details</button>&nbsp;/&nbsp;<button id="hide_all_details">Hide all details</button>
</div>
</div>
</div>
{%- for s in additional_summary['summary'] %}
{{ s|safe }}
{%- endfor %}
{%- for p in additional_summary['postfix'] %}
{{ p|safe }}
{%- endfor %}
<div class="additional-summary summary">
{%- for s in additional_summary['summary'] %}
{{ s|safe }}
{%- endfor %}
</div>
<div class="additional-summary postfix">
{%- for p in additional_summary['postfix'] %}
{{ p|safe }}
{%- endfor %}
</div>
</div>
<table id="results-table"></table>
</body>
<footer>
Expand Down
2 changes: 0 additions & 2 deletions src/pytest_html/scripts/dom.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const mediaViewer = require('./mediaviewer.js')
const templateEnvRow = document.getElementById('template_environment_row')
const templateResult = document.getElementById('template_results-table__tbody')
const listHeaderEmpty = document.getElementById('template_results-table__head--empty')

function htmlToElements(html) {
const temp = document.createElement('template')
Expand Down Expand Up @@ -37,7 +36,6 @@ const dom = {

return envRow
},
getListHeaderEmpty: () => listHeaderEmpty.content.cloneNode(true),
getResultTBody: ({ testId, id, log, duration, extras, resultsTableRow, tableHtml, result, collapsed }) => {
const resultLower = result.toLowerCase()
const resultBody = templateResult.content.cloneNode(true)
Expand Down
9 changes: 3 additions & 6 deletions src/pytest_html/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,14 @@ const renderContent = (tests) => {
removeChildren(table)

tableHeader.querySelector(`.sortable[data-column-type="${sortAttr}"]`)?.classList.add(sortAsc ? 'desc' : 'asc')
table.appendChild(tableHeader)
if (!rows.length) {
tableHeader.appendChild(dom.getListHeaderEmpty())
const emptyTable = document.getElementById('template_results-table__body--empty').content.cloneNode(true)
table.appendChild(emptyTable)
}
table.appendChild(tableHeader)

rows.forEach((row) => !!row && table.appendChild(row))

table.querySelectorAll('.extra').forEach((item) => {
item.colSpan = document.querySelectorAll('th').length
})

findAll('.sortable').forEach((elem) => {
elem.addEventListener('click', (evt) => {
const { target: element } = evt
Expand Down
58 changes: 30 additions & 28 deletions testing/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ def run(pytester, path="report.html", cmd_flags=None, query_params=None):
# End workaround

driver.get(f"file:///reports{path}?{query_params}")
return BeautifulSoup(driver.page_source, "html.parser")
soup = BeautifulSoup(driver.page_source, "html.parser")

# remove all templates as they bork the BS parsing
for template in soup("template"):
template.decompose()

return soup
finally:
driver.quit()

Expand Down Expand Up @@ -88,15 +94,15 @@ def get_text(page, selector):


def is_collapsed(page, test_name):
return get_element(page, f".summary tbody[id$='{test_name}'] .collapsed")
return get_element(page, f"tbody[id$='{test_name}'] .collapsed")


def get_log(page, test_id=None):
# TODO(jim) move to get_text (use .contents)
if test_id:
log = get_element(page, f".summary tbody[id$='{test_id}'] div[class='log']")
log = get_element(page, f"tbody[id$='{test_id}'] div[class='log']")
else:
log = get_element(page, ".summary div[class='log']")
log = get_element(page, "div[class='log']")
all_text = ""
for text in log.strings:
all_text += text
Expand Down Expand Up @@ -195,7 +201,7 @@ def test_skip():
page = run(pytester)
assert_results(page, skipped=1, total_tests=0)

log = get_text(page, ".summary div[class='log']")
log = get_text(page, "div[class='log']")
assert_that(log).contains(reason)

def test_skip_function_marker(self, pytester):
Expand All @@ -211,7 +217,7 @@ def test_skip():
page = run(pytester)
assert_results(page, skipped=1, total_tests=0)

log = get_text(page, ".summary div[class='log']")
log = get_text(page, "div[class='log']")
assert_that(log).contains(reason)

def test_skip_class_marker(self, pytester):
Expand All @@ -228,15 +234,15 @@ def test_skip():
page = run(pytester)
assert_results(page, skipped=1, total_tests=0)

log = get_text(page, ".summary div[class='log']")
log = get_text(page, "div[class='log']")
assert_that(log).contains(reason)

def test_fail(self, pytester):
pytester.makepyfile("def test_fail(): assert False")
page = run(pytester)
assert_results(page, failed=1)
assert_that(get_log(page)).contains("AssertionError")
assert_that(get_text(page, ".summary div[class='log'] span.error")).matches(
assert_that(get_text(page, "div[class='log'] span.error")).matches(
r"^E\s+assert False$"
)

Expand Down Expand Up @@ -352,7 +358,7 @@ def test_function(arg):
page = run(pytester)
assert_results(page, error=1, total_tests=0)

col_name = get_text(page, ".summary td[class='col-name']")
col_name = get_text(page, "td[class='col-name']")
assert_that(col_name).contains("::setup")
assert_that(get_log(page)).contains("ValueError")

Expand Down Expand Up @@ -411,7 +417,9 @@ def pytest_html_results_summary(prefix, summary, postfix):
pytester.makepyfile("def test_pass(): pass")
page = run(pytester)

elements = page.select(".summary__data p:not(.run-count):not(.filter)")
elements = page.select(
".additional-summary p"
) # ".summary__data p:not(.run-count):not(.filter)")
assert_that(elements).is_length(3)
for element in elements:
key = re.search(r"(\w+).*", element.string).group(1)
Expand All @@ -437,7 +445,7 @@ def pytest_runtest_makereport(item, call):
pytester.makepyfile("def test_pass(): pass")
page = run(pytester)

assert_that(page.select_one(".summary .extraHTML").string).is_equal_to(content)
assert_that(page.select_one(".extraHTML").string).is_equal_to(content)

@pytest.mark.parametrize(
"content, encoded",
Expand All @@ -460,7 +468,7 @@ def pytest_runtest_makereport(item, call):
pytester.makepyfile("def test_pass(): pass")
page = run(pytester, cmd_flags=["--self-contained-html"])

element = page.select_one(".summary a[class='col-links__extra text']")
element = page.select_one("a[class='col-links__extra text']")
assert_that(element.string).is_equal_to("Text")
assert_that(element["href"]).is_equal_to(
f"data:text/plain;charset=utf-8;base64,{encoded}"
Expand Down Expand Up @@ -488,7 +496,7 @@ def pytest_runtest_makereport(item, call):
content_str = json.dumps(content)
data = b64encode(content_str.encode("utf-8")).decode("ascii")

element = page.select_one(".summary a[class='col-links__extra json']")
element = page.select_one("a[class='col-links__extra json']")
assert_that(element.string).is_equal_to("JSON")
assert_that(element["href"]).is_equal_to(
f"data:application/json;charset=utf-8;base64,{data}"
Expand All @@ -512,7 +520,7 @@ def pytest_runtest_makereport(item, call):
pytester.makepyfile("def test_pass(): pass")
page = run(pytester)

element = page.select_one(".summary a[class='col-links__extra url']")
element = page.select_one("a[class='col-links__extra url']")
assert_that(element.string).is_equal_to("URL")
assert_that(element["href"]).is_equal_to(content)

Expand Down Expand Up @@ -551,7 +559,7 @@ def pytest_runtest_makereport(item, call):
# assert_that(element.string).is_equal_to("Image")
# assert_that(element["href"]).is_equal_to(src)

element = page.select_one(".summary .media img")
element = page.select_one(".media img")
assert_that(str(element)).is_equal_to(f'<img src="{src}"/>')

@pytest.mark.parametrize("mime_type, extension", [("video/mp4", "mp4")])
Expand Down Expand Up @@ -579,7 +587,7 @@ def pytest_runtest_makereport(item, call):
# assert_that(element.string).is_equal_to("Video")
# assert_that(element["href"]).is_equal_to(src)

element = page.select_one(".summary .media video")
element = page.select_one(".media video")
assert_that(str(element)).is_equal_to(
f'<video controls="">\n<source src="{src}" type="{mime_type}"/>\n</video>'
)
Expand All @@ -590,10 +598,8 @@ def test_xdist(self, pytester):
assert_results(page, passed=1)

def test_results_table_hook_append(self, pytester):
header_selector = (
".summary #results-table-head tr:nth-child(1) th:nth-child({})"
)
row_selector = ".summary #results-table tr:nth-child(1) td:nth-child({})"
header_selector = "#results-table-head tr:nth-child(1) th:nth-child({})"
row_selector = "#results-table tr:nth-child(1) td:nth-child({})"

pytester.makeconftest(
"""
Expand Down Expand Up @@ -628,10 +634,8 @@ def pytest_html_results_table_row(report, cells):
)

def test_results_table_hook_insert(self, pytester):
header_selector = (
".summary #results-table-head tr:nth-child(1) th:nth-child({})"
)
row_selector = ".summary #results-table tr:nth-child(1) td:nth-child({})"
header_selector = "#results-table-head tr:nth-child(1) th:nth-child({})"
row_selector = "#results-table tr:nth-child(1) td:nth-child({})"

pytester.makeconftest(
"""
Expand Down Expand Up @@ -700,12 +704,10 @@ def pytest_html_results_table_row(report, cells):
pytester.makepyfile("def test_pass(): pass")
page = run(pytester)

header_columns = page.select(".summary #results-table-head th")
header_columns = page.select("#results-table-head th")
assert_that(header_columns).is_length(3)

row_columns = page.select_one(".summary .results-table-row").select(
"td:not(.extra)"
)
row_columns = page.select_one(".results-table-row").select("td:not(.extra)")
assert_that(row_columns).is_length(3)

@pytest.mark.parametrize("no_capture", ["", "-s"])
Expand Down

0 comments on commit bd0c530

Please sign in to comment.