Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions src/pytest_html/nextgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self, title, duration_format):
self._data = {
"title": title,
"collectedItems": 0,
"runningState": "not_started",
"durationFormat": duration_format,
"environment": {},
"tests": [],
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -165,20 +187,23 @@ 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(
prefix=self._report.data["additionalSummary"]["prefix"],
summary=self._report.data["additionalSummary"]["summary"],
postfix=self._report.data["additionalSummary"]["postfix"],
)
self._report.data["runningState"] = "Finished"
self._generate_report()

@pytest.hookimpl(trylast=True)
Expand All @@ -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)
Expand All @@ -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()

Expand Down Expand Up @@ -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()
3 changes: 3 additions & 0 deletions src/pytest_html/scripts/datamanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class DataManager {
get durationFormat() {
return this.renderData.durationFormat
}
get isFinished() {
return this.data.runningState === 'Finished'
}
}

module.exports = {
Expand Down
4 changes: 2 additions & 2 deletions src/pytest_html/scripts/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
52 changes: 15 additions & 37 deletions src/pytest_html/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
Expand All @@ -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' },
Expand All @@ -78,31 +63,26 @@ 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}`

input.disabled = !count
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`
}
}

Expand All @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be written cleaner using a destruct:
const { filteredTests, allTests, collectedItems, runningState } = manager

renderStatic()
renderContent(filteredTests)
renderDerived(allTests, collectedItems)
renderContent(testSubset)
renderDerived(allTests, collectedItems, isFinished)
}

const redraw = () => {
Expand Down