diff --git a/src/pytest_html/nextgen.py b/src/pytest_html/nextgen.py
index e471fd1a..ab1d2883 100644
--- a/src/pytest_html/nextgen.py
+++ b/src/pytest_html/nextgen.py
@@ -36,6 +36,7 @@ def __init__(self, title, duration_format):
self._data = {
"title": title,
"collectedItems": 0,
+ "runningState": "not_started",
"durationFormat": duration_format,
"environment": {},
"tests": [],
@@ -122,6 +123,27 @@ def _data_content(self, *args, **kwargs):
def _media_content(self, *args, **kwargs):
pass
+ def _process_extras(self, report):
+ test_id = report.nodeid.encode("utf-8").decode("unicode_escape")
+ test_index = hasattr(report, "rerun") and report.rerun + 1 or 0
+ report_extras = getattr(report, "extras", [])
+ for extra_index, extra in enumerate(report_extras):
+ content = extra["content"]
+ asset_name = self._asset_filename(test_id, extra_index, test_index, extra['extension'])
+ if extra["format_type"] == extras.FORMAT_JSON:
+ content = json.dumps(content)
+ extra["content"] = self._data_content(content, asset_name=asset_name, mime_type=extra["mime_type"])
+
+ if extra["format_type"] == extras.FORMAT_TEXT:
+ if isinstance(content, bytes):
+ content = content.decode("utf-8")
+ extra["content"] = self._data_content(content, asset_name=asset_name, mime_type=extra["mime_type"])
+
+ if extra["format_type"] == extras.FORMAT_IMAGE or extra["format_type"] == extras.FORMAT_VIDEO:
+ extra["content"] = self._media_content(content, asset_name=asset_name, mime_type=extra["mime_type"])
+
+ return report_extras
+
def _read_template(self, search_paths):
env = Environment(
loader=FileSystemLoader(search_paths),
@@ -165,13 +187,15 @@ def pytest_sessionstart(self, session):
if hasattr(config, "_metadata") and config._metadata:
self._report.data["environment"] = self._generate_environment()
- self._generate_report()
session.config.hook.pytest_html_report_title(report=self._report)
header_cells = self.Cells()
session.config.hook.pytest_html_results_table_header(cells=header_cells)
self._report.set_data("resultsTableHeader", header_cells.html)
+ self._report.data["runningState"] = "Started"
+ self._generate_report()
+
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(self, session):
session.config.hook.pytest_html_results_summary(
@@ -179,6 +203,7 @@ def pytest_sessionfinish(self, session):
summary=self._report.data["additionalSummary"]["summary"],
postfix=self._report.data["additionalSummary"]["postfix"],
)
+ self._report.data["runningState"] = "Finished"
self._generate_report()
@pytest.hookimpl(trylast=True)
@@ -191,7 +216,7 @@ def pytest_runtest_logreport(self, report):
config=self._config, report=report
)
- test_id = report.nodeid.encode("utf-8").decode("unicode_escape")
+ data["outcome"] = _process_outcome(report)
row_cells = self.Cells()
self._config.hook.pytest_html_results_table_row(report=report, cells=row_cells)
@@ -201,25 +226,7 @@ def pytest_runtest_logreport(self, report):
self._config.hook.pytest_html_results_table_html(report=report, data=table_html)
data.update({"tableHtml": table_html})
- test_index = hasattr(report, "rerun") and report.rerun + 1 or 0
-
- report_extras = getattr(report, "extras", [])
- for extra_index, extra in enumerate(report_extras):
- content = extra["content"]
- asset_name = self._asset_filename(test_id, extra_index, test_index, extra['extension'])
- if extra["format_type"] == extras.FORMAT_JSON:
- content = json.dumps(content)
- extra["content"] = self._data_content(content, asset_name=asset_name, mime_type=extra["mime_type"])
-
- if extra["format_type"] == extras.FORMAT_TEXT:
- if isinstance(content, bytes):
- content = content.decode("utf-8")
- extra["content"] = self._data_content(content, asset_name=asset_name, mime_type=extra["mime_type"])
-
- if extra["format_type"] == extras.FORMAT_IMAGE or extra["format_type"] == extras.FORMAT_VIDEO:
- extra["content"] = self._media_content(content, asset_name=asset_name, mime_type=extra["mime_type"])
-
- data.update({"extras": report_extras})
+ data.update({"extras": self._process_extras(report)})
self._report.data["tests"].append(data)
self._generate_report()
@@ -287,3 +294,15 @@ def _media_content(self, content, mime_type, *args, **kwargs):
def _generate_report(self, *args, **kwargs):
super()._generate_report(self_contained=True)
+
+
+def _process_outcome(report):
+ if report.when in ["setup", "teardown"] and report.outcome == "failed":
+ return "Error"
+ if hasattr(report, "wasxfail"):
+ if report.outcome in ["passed", "failed"]:
+ return "XPassed"
+ if report.outcome == "skipped":
+ return "XFailed"
+
+ return report.outcome.capitalize()
diff --git a/src/pytest_html/scripts/datamanager.js b/src/pytest_html/scripts/datamanager.js
index a74bae47..287be187 100644
--- a/src/pytest_html/scripts/datamanager.js
+++ b/src/pytest_html/scripts/datamanager.js
@@ -30,6 +30,9 @@ class DataManager {
get durationFormat() {
return this.renderData.durationFormat
}
+ get isFinished() {
+ return this.data.runningState === 'Finished'
+ }
}
module.exports = {
diff --git a/src/pytest_html/scripts/dom.js b/src/pytest_html/scripts/dom.js
index 779f7ec9..afff1b6b 100644
--- a/src/pytest_html/scripts/dom.js
+++ b/src/pytest_html/scripts/dom.js
@@ -64,14 +64,14 @@ const dom = {
return header
},
getListHeaderEmpty: () => listHeaderEmpty.content.cloneNode(true),
- getResultTBody: ({ nodeid, longrepr, duration, extras, resultsTableRow, tableHtml }, outcome) => {
+ getResultTBody: ({ nodeid, longrepr, duration, extras, resultsTableRow, tableHtml, outcome}) => {
const outcomeLower = outcome.toLowerCase()
const resultBody = templateResult.content.cloneNode(true)
resultBody.querySelector('tbody').classList.add(outcomeLower)
resultBody.querySelector('.col-result').innerText = outcome
resultBody.querySelector('.col-name').innerText = nodeid
resultBody.querySelector('.col-duration').innerText = `${formatDuration(duration)}s`
- if (['failed', 'error', 'skipped', 'xfailed', 'xpassed'].includes(outcomeLower)) {
+ if (['failed', 'error', 'xfailed', 'xpassed'].includes(outcomeLower)) {
resultBody.querySelector('.log').innerText = longrepr ?
longrepr.reprtraceback.reprentries[0].data.lines.join('\n') : ''
} else {
diff --git a/src/pytest_html/scripts/main.js b/src/pytest_html/scripts/main.js
index c713bef4..2e14a76d 100644
--- a/src/pytest_html/scripts/main.js
+++ b/src/pytest_html/scripts/main.js
@@ -11,21 +11,6 @@ const removeChildren = (node) => {
}
}
-const getOutcome = ({ nodeid, wasxfail }, tests) => {
- const relatedOutcome = tests
- .filter((test) => test.nodeid === nodeid)
- .map(({ outcome }) => outcome)
- if (relatedOutcome.includes('failed')) {
- return typeof wasxfail === 'undefined' ? 'Failed' : 'XPassed'
- } else if (relatedOutcome.includes('error')) {
- return 'Error'
- } else if (relatedOutcome.includes('skipped')) {
- return typeof wasxfail === 'undefined' ? 'Skipped' : 'XFailed'
- } else {
- return typeof wasxfail === 'undefined' ? 'Passed' : 'XPassed'
- }
-}
-
const renderStatic = () => {
const title = manager.title
const environment = manager.environment
@@ -41,10 +26,10 @@ const renderStatic = () => {
}
const renderContent = (tests) => {
- const renderSet = tests.filter(({ when }) => when === 'call')
+ const renderSet = tests.filter(({ when, outcome }) => when === 'call' || outcome === 'Error' )
const rows = renderSet.map((test) =>
- dom.getResultTBody(test, getOutcome(test, tests))
+ dom.getResultTBody(test)
)
const table = document.querySelector('#results-table')
@@ -63,8 +48,8 @@ const renderContent = (tests) => {
})
}
-const renderDerived = (tests, collectedItems) => {
- const renderSet = tests.filter(({ when }) => when === 'call')
+const renderDerived = (tests, collectedItems, isFinished) => {
+ const renderSet = tests.filter(({ when, outcome }) => when === 'call' || outcome === 'Error')
const possibleOutcomes = [
{ outcome: 'passed', label: 'Passed' },
@@ -78,15 +63,7 @@ const renderDerived = (tests, collectedItems) => {
const currentFilter = getFilter()
possibleOutcomes.forEach(({ outcome, label }) => {
- const count = renderSet.filter((test) => {
- const wasXpassed = outcome === 'xpassed' && ['passed', 'failed'].includes(test.outcome)
- const wasXfailed = outcome === 'xfailed' && test.outcome === 'skipped'
- if (typeof test.wasxfail !== 'undefined') {
- return wasXpassed || wasXfailed
- } else {
- return test.outcome === outcome
- }
- }).length
+ const count = renderSet.filter((test) => test.outcome.toLowerCase() === outcome).length
const input = document.querySelector(`input[data-test-result="${outcome}"]`)
document.querySelector(`.${outcome}`).innerText = `${count} ${label}`
@@ -94,15 +71,18 @@ const renderDerived = (tests, collectedItems) => {
input.checked = !currentFilter.includes(outcome)
})
- if (collectedItems === renderSet.length) {
+ const numberOfTests = renderSet.filter(({outcome}) =>
+ ['Passed', 'Failed', 'XPassed', 'XFailed'].includes(outcome)
+ ).length
+ if (isFinished) {
const accTime = tests.reduce((prev, { duration }) => prev + duration, 0)
const formattedAccTime = formatDuration(accTime)
- const testWord = renderSet.length > 1 ? 'tests' : 'test'
- const innerText = `${renderSet.length} ${testWord} ran in ${formattedAccTime} seconds.`
+ const testWord = numberOfTests > 1 ? 'tests' : 'test'
+ const innerText = `${numberOfTests} ${testWord} ran in ${formattedAccTime} seconds.`
document.querySelector('.run-count').innerText = innerText
document.querySelector('.summary__reload__button').classList.add('hidden')
} else {
- document.querySelector('.run-count').innerText = `${renderSet.length} / ${collectedItems} tests done`
+ document.querySelector('.run-count').innerText = `${numberOfTests} / ${collectedItems} tests done`
}
}
@@ -128,13 +108,11 @@ const bindEvents = () => {
}
const renderPage = () => {
- const filteredTests = manager.testSubset
- const allTests = manager.allTests
- const collectedItems = manager.collectedItems
+ const { testSubset, allTests, collectedItems, isFinished } = manager
renderStatic()
- renderContent(filteredTests)
- renderDerived(allTests, collectedItems)
+ renderContent(testSubset)
+ renderDerived(allTests, collectedItems, isFinished)
}
const redraw = () => {