Skip to content

Commit 555d19b

Browse files
committed
TBD
1 parent 316246e commit 555d19b

File tree

13 files changed

+82
-255
lines changed

13 files changed

+82
-255
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ classifiers = [
4242
]
4343
dependencies = [
4444
"pytest>=7.0.0",
45-
"pytest-metadata>=2.0.2",
45+
"pytest-metadata>=3.0.0",
4646
"Jinja2>=3.0.0",
4747
]
4848
dynamic = [

src/pytest_html/basereport.py

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
44
import datetime
55
import json
6+
import math
67
import os
78
import re
89
import warnings
@@ -12,8 +13,6 @@
1213

1314
from pytest_html import __version__
1415
from pytest_html import extras
15-
from pytest_html.table import Header
16-
from pytest_html.table import Row
1716
from pytest_html.util import cleanup_unserializable
1817

1918

@@ -60,8 +59,8 @@ def _generate_report(self, self_contained=False):
6059

6160
self._write_report(rendered_report)
6261

63-
def _generate_environment(self):
64-
metadata = self._config._metadata
62+
def _generate_environment(self, metadata_key):
63+
metadata = self._config.stash[metadata_key] # self._config._metadata
6564
for key in metadata.keys():
6665
value = metadata[key]
6766
if self._is_redactable_environment_variable(key):
@@ -145,16 +144,16 @@ def _write_report(self, rendered_report):
145144

146145
@pytest.hookimpl(trylast=True)
147146
def pytest_sessionstart(self, session):
148-
config = session.config
149-
if hasattr(config, "_metadata") and config._metadata:
150-
self._report.set_data("environment", self._generate_environment())
147+
# config = session.config
148+
from pytest_metadata.plugin import metadata_key
149+
150+
# if hasattr(config, "_metadata") and config._metadata:
151+
self._report.set_data("environment", self._generate_environment(metadata_key))
151152

152153
session.config.hook.pytest_html_report_title(report=self._report)
153154

154-
header_cells = Header()
155-
session.config.hook.pytest_html_results_table_header(cells=header_cells)
156-
self._report.set_data("resultsTableHeader", header_cells.html)
157-
self._report.set_data("headerPops", header_cells.get_pops())
155+
headers = self._report.data["resultsTableHeader"]
156+
session.config.hook.pytest_html_results_table_header(cells=headers)
158157

159158
self._report.set_data("runningState", "Started")
160159
self._generate_report()
@@ -173,7 +172,8 @@ def pytest_sessionfinish(self, session):
173172
@pytest.hookimpl(trylast=True)
174173
def pytest_terminal_summary(self, terminalreporter):
175174
terminalreporter.write_sep(
176-
"-", f"Generated html report: file://{self._report_path.resolve()}"
175+
"-",
176+
f"Generated html report: file://{self._report_path.resolve().as_posix()}",
177177
)
178178

179179
@pytest.hookimpl(trylast=True)
@@ -188,35 +188,58 @@ def pytest_runtest_logreport(self, report):
188188
DeprecationWarning,
189189
)
190190

191+
data = dict()
192+
cells = list()
193+
194+
result = _process_outcome(report)
191195
data = {
192-
"duration": report.duration,
196+
"result": result,
193197
}
198+
cells.append(f'<td class="col-result">{result}</td>')
194199

195200
test_id = report.nodeid
196201
if report.when != "call":
197202
test_id += f"::{report.when}"
198203
data["testId"] = test_id
204+
cells.append(f'<td class="col-name">{test_id}</td>')
205+
206+
total_duration = self._report.data["totalDuration"]
207+
total_duration["total"] += report.duration
208+
total_duration["formatted"] = _format_duration(total_duration["total"])
199209

200-
row_cells = Row()
201-
self._config.hook.pytest_html_results_table_row(report=report, cells=row_cells)
202-
if row_cells.html is None:
210+
duration = _format_duration(report.duration)
211+
cells.append(f'<td class="col-duration">{duration}</td>')
212+
cells.append('<td class="col-links"></td>')
213+
214+
self._config.hook.pytest_html_results_table_row(report=report, cells=cells)
215+
if not cells:
203216
return
204-
data["resultsTableRow"] = row_cells.html
205-
for sortable, value in row_cells.sortables.items():
206-
data[sortable] = value
217+
218+
data["resultsTableRow"] = cells
219+
data["extras"] = self._process_extras(report, test_id)
207220

208221
processed_logs = _process_logs(report)
209222
self._config.hook.pytest_html_results_table_html(
210223
report=report, data=processed_logs
211224
)
212225

213-
data["result"] = _process_outcome(report)
214-
data["extras"] = self._process_extras(report, test_id)
215-
216226
if self._report.add_test(data, report, processed_logs):
217227
self._generate_report()
218228

219229

230+
def _format_duration(duration):
231+
if duration < 1:
232+
return "{} ms".format(round(duration * 1000))
233+
234+
hours = math.floor(duration / 3600)
235+
remaining_seconds = duration % 3600
236+
minutes = math.floor(remaining_seconds / 60)
237+
remaining_seconds = remaining_seconds % 60
238+
seconds = round(remaining_seconds)
239+
240+
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
241+
242+
220243
def _is_error(report):
221244
return report.when in ["setup", "teardown"] and report.outcome == "failed"
222245

src/pytest_html/report_data.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,26 @@
1010
class ReportData:
1111
def __init__(self, config):
1212
self._config = config
13+
14+
default_headers = [
15+
'<th class="sortable" data-column-type="result">Result</th>',
16+
'<th class="sortable" data-column-type="testId">Test</th>',
17+
'<th class="sortable" data-column-type="duration">Duration</th>',
18+
"<th>Links</th>",
19+
]
20+
1321
self._data = {
1422
"title": "",
1523
"collectedItems": 0,
24+
"totalDuration": {
25+
"total": 0,
26+
"formatted": "",
27+
},
1628
"runningState": "not_started",
1729
"environment": {},
1830
"tests": defaultdict(list),
19-
"resultsTableHeader": {},
2031
"additionalSummary": defaultdict(list),
32+
"resultsTableHeader": default_headers,
2133
}
2234

2335
collapsed = config.getini("render_collapsed")

src/pytest_html/resources/index.jinja2

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@
3232
<template id="template_results-table__tbody">
3333
<tbody class="results-table-row">
3434
<tr class="collapsible">
35-
<td class="col-result"></td>
36-
<td class="col-name"></td>
37-
<td class="col-duration"></td>
38-
<td class="col-links"></td>
3935
</tr>
4036
<tr class="extras-row">
4137
<td class="extra" colspan="4">
@@ -62,10 +58,6 @@
6258
<template id="template_results-table__head">
6359
<thead id="results-table-head">
6460
<tr>
65-
<th class="sortable" data-column-type="result">Result</th>
66-
<th class="sortable" data-column-type="testId">Test</th>
67-
<th class="sortable" data-column-type="duration">Duration</th>
68-
<th>Links</th>
6961
</tr>
7062
</thead>
7163
</template>

src/pytest_html/resources/style.css

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/pytest_html/scripts/datamanager.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class DataManager {
5050
get isFinished() {
5151
return this.data.runningState === 'Finished'
5252
}
53+
get formattedDuration() {
54+
return this.data.totalDuration.formatted
55+
}
5356
}
5457

5558
module.exports = {

src/pytest_html/scripts/dom.js

Lines changed: 13 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const storageModule = require('./storage.js')
2-
const { formatDuration, transformTableObj } = require('./utils.js')
32
const mediaViewer = require('./mediaviewer.js')
43
const templateEnvRow = document.querySelector('#template_environment_row')
54
const templateCollGroup = document.querySelector('#template_table-colgroup')
@@ -28,12 +27,6 @@ const findAll = (selector, elem) => {
2827
return [...elem.querySelectorAll(selector)]
2928
}
3029

31-
const insertAdditionalHTML = (html, element, selector, position = 'beforebegin') => {
32-
Object.keys(html).map((key) => {
33-
element.querySelectorAll(selector).item(key).insertAdjacentHTML(position, html[key])
34-
})
35-
}
36-
3730
const dom = {
3831
getStaticRow: (key, value) => {
3932
const envRow = templateEnvRow.content.cloneNode(true)
@@ -53,29 +46,14 @@ const dom = {
5346
const sortAttr = storageModule.getSort()
5447
const sortAsc = JSON.parse(storageModule.getSortDirection())
5548

56-
const regex = /data-column-type="(\w+)/
57-
const cols = Object.values(resultsTableHeader).reduce((result, value) => {
58-
if (value.includes('sortable')) {
59-
const matches = regex.exec(value)
60-
if (matches) {
61-
result.push(matches[1])
62-
}
63-
}
64-
return result
65-
}, [])
66-
const sortables = ['result', 'testId', 'duration', ...cols]
67-
68-
// Add custom html from the pytest_html_results_table_header hook
69-
const headers = transformTableObj(resultsTableHeader)
70-
insertAdditionalHTML(headers.inserts, header, 'th')
71-
insertAdditionalHTML(headers.appends, header, 'tr', 'beforeend')
72-
73-
sortables.forEach((sortCol) => {
74-
if (sortCol === sortAttr) {
75-
header.querySelector(`[data-column-type="${sortCol}"]`).classList.add(sortAsc ? 'desc' : 'asc')
76-
}
49+
resultsTableHeader.forEach((html) => {
50+
const t = document.createElement('template')
51+
t.innerHTML = html
52+
header.querySelector('#results-table-head > tr').appendChild(t.content)
7753
})
7854

55+
header.querySelector(`.sortable[data-column-type="${sortAttr}"]`).classList.add(sortAsc ? 'desc' : 'asc')
56+
7957
return header
8058
},
8159
getListHeaderEmpty: () => listHeaderEmpty.content.cloneNode(true),
@@ -86,12 +64,13 @@ const dom = {
8664
resultBody.querySelector('tbody').classList.add(resultLower)
8765
resultBody.querySelector('tbody').id = testId
8866
resultBody.querySelector('.collapsible').dataset.id = id
89-
resultBody.querySelector('.col-result').innerText = result
90-
resultBody.querySelector('.col-result').classList.add(`${collapsed ? 'expander' : 'collapser'}`)
91-
resultBody.querySelector('.col-name').innerText = testId
9267

93-
const formattedDuration = duration < 1 ? formatDuration(duration).ms : formatDuration(duration).formatted
94-
resultBody.querySelector('.col-duration').innerText = formattedDuration
68+
resultsTableRow.forEach((html) => {
69+
const t = document.createElement('template')
70+
t.innerHTML = html
71+
resultBody.querySelector('.collapsible').appendChild(t.content)
72+
})
73+
resultBody.querySelector('.collapsible > td')?.classList.add(`${collapsed ? 'expander' : 'collapser'}`)
9574

9675
if (log) {
9776
// Wrap lines starting with "E" with span.error to color those lines red
@@ -107,7 +86,7 @@ const dom = {
10786

10887
const media = []
10988
extras?.forEach(({ name, format_type, content }) => {
110-
if (['json', 'text', 'url'].includes(format_type)) {
89+
if (resultBody.querySelector('.col-links') && ['json', 'text', 'url'].includes(format_type)) {
11190
const extraLink = aTag.content.cloneNode(true)
11291
const extraLinkItem = extraLink.querySelector('a')
11392

@@ -127,11 +106,6 @@ const dom = {
127106
})
128107
mediaViewer.setUp(resultBody, media)
129108

130-
// Add custom html from the pytest_html_results_table_row hook
131-
const rows = transformTableObj(resultsTableRow)
132-
resultsTableRow && insertAdditionalHTML(rows.inserts, resultBody, 'td')
133-
resultsTableRow && insertAdditionalHTML(rows.appends, resultBody, 'tr', 'beforeend')
134-
135109
// Add custom html from the pytest_html_results_table_html hook
136110
tableHtml?.forEach((item) => {
137111
resultBody.querySelector('td[class="extra"]').insertAdjacentHTML('beforeend', item)

src/pytest_html/scripts/main.js

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const { formatDuration } = require('./utils.js')
21
const { dom, findAll } = require('./dom.js')
32
const { manager } = require('./datamanager.js')
43
const { doSort } = require('./sort.js')
@@ -52,18 +51,6 @@ const renderContent = (tests) => {
5251
item.colSpan = document.querySelectorAll('th').length
5352
})
5453

55-
const { headerPops } = manager.renderData
56-
if (headerPops > 0) {
57-
// remove 'headerPops' number of header columns
58-
findAll('#results-table-head th').splice(-headerPops).forEach((column) => column.remove())
59-
60-
// remove 'headerPops' number of row columns
61-
const resultRows = findAll('.results-table-row')
62-
resultRows.forEach((elem) => {
63-
findAll('td:not(.extra)', elem).splice(-headerPops).forEach((column) => column.remove())
64-
})
65-
}
66-
6754
findAll('.sortable').forEach((elem) => {
6855
elem.addEventListener('click', (evt) => {
6956
const { target: element } = evt
@@ -80,7 +67,7 @@ const renderContent = (tests) => {
8067
})
8168
}
8269

83-
const renderDerived = (tests, collectedItems, isFinished) => {
70+
const renderDerived = (tests, collectedItems, isFinished, formattedDuration) => {
8471
const currentFilter = getVisible()
8572
possibleResults.forEach(({ result, label }) => {
8673
const count = tests.filter((test) => test.result.toLowerCase() === result).length
@@ -100,12 +87,8 @@ const renderDerived = (tests, collectedItems, isFinished) => {
10087
['Passed', 'Failed', 'XPassed', 'XFailed'].includes(result)).length
10188

10289
if (isFinished) {
103-
const accTime = tests.reduce((prev, { duration }) => prev + duration, 0)
104-
const formattedAccTime = formatDuration(accTime)
10590
const testWord = numberOfTests > 1 ? 'tests' : 'test'
106-
const durationText = formattedAccTime.hasOwnProperty('ms') ? formattedAccTime.ms : formattedAccTime.formatted
107-
108-
document.querySelector('.run-count').innerText = `${numberOfTests} ${testWord} took ${durationText}.`
91+
document.querySelector('.run-count').innerText = `${numberOfTests} ${testWord} took ${formattedDuration}.`
10992
document.querySelector('.summary__reload__button').classList.add('hidden')
11093
} else {
11194
document.querySelector('.run-count').innerText = `${numberOfTests} / ${collectedItems} tests done`
@@ -135,11 +118,11 @@ const bindEvents = () => {
135118
}
136119

137120
const redraw = () => {
138-
const { testSubset, allTests, collectedItems, isFinished } = manager
121+
const { testSubset, allTests, collectedItems, isFinished, formattedDuration } = manager
139122

140123
renderStatic()
141124
renderContent(testSubset)
142-
renderDerived(allTests, collectedItems, isFinished)
125+
renderDerived(allTests, collectedItems, isFinished, formattedDuration )
143126
}
144127

145128
exports.redraw = redraw

0 commit comments

Comments
 (0)