diff --git a/src/pytest_html/basereport.py b/src/pytest_html/basereport.py index cfcc74b2..b5dcb4c6 100644 --- a/src/pytest_html/basereport.py +++ b/src/pytest_html/basereport.py @@ -284,7 +284,7 @@ def _process_report(self, report, duration, processed_extras): ] cells = [ f'{outcome}', - f'{test_id}', + f'{escape(test_id)}', f'{formatted_duration}', f'{_process_links(links)}', ] diff --git a/testing/test_unit.py b/testing/test_unit.py index 1ea945ea..7f487b0f 100644 --- a/testing/test_unit.py +++ b/testing/test_unit.py @@ -1,9 +1,11 @@ import importlib.resources +import json import sys from pathlib import Path import pytest from assertpy import assert_that +from bs4 import BeautifulSoup pytest_plugins = ("pytester",) @@ -146,3 +148,29 @@ def test_custom_css_selfcontained(pytester, css_file_path, expandvar): with open(pytester.path / "report.html") as f: html = f.read() assert_that(html).contains("* " + str(css_file_path)).contains("* two.css") + + +def test_html_in_test_id_is_escaped(pytester): + pytester.makepyfile( + """ + import pytest + + + @pytest.mark.parametrize("value", ["pwned"]) + def test_id_escaping(value): + pass + """ + ) + result = run(pytester) + result.assert_outcomes(passed=1) + + html = (pytester.path / "report.html").read_text(encoding="utf-8") + blob = BeautifulSoup(html, "html.parser").find(id="data-container")["data-jsonblob"] + tests = json.loads(blob)["tests"] + nodeid = next(key for key in tests if "test_id_escaping" in key) + row = tests[nodeid][0]["resultsTableRow"] + test_id_cell = next(cell for cell in row if "col-testId" in cell) + + assert_that(test_id_cell).does_not_contain("pwned").contains( + "<b>pwned</b>" + )