From 2c921cd9b286ad8b25e8b7560be2d538b1e8ff98 Mon Sep 17 00:00:00 2001 From: NopeCharon <115794254+NopeCharon@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:29:41 +0000 Subject: [PATCH] Sort numeric values in sortable columns numerically Sortable columns other than result and duration were always compared as strings, so custom columns holding numbers (e.g. a "speedup" column added through pytest_html_results_table_header/row) were ordered lexicographically: "10.5" came before "9.25". Compare two values numerically when both are numbers, and keep the existing string comparison otherwise. When a column mixes numeric and non-numeric values, numbers are placed before the rest so the comparison stays a consistent total order. Add a JavaScript unit test and an end-to-end test for sorting a custom numeric column, and mention numeric sorting in the user guide. Fixes #818 --- docs/user_guide.rst | 3 +++ src/pytest_html/scripts/sort.js | 20 ++++++++++++++++++- testing/test_e2e.py | 35 +++++++++++++++++++++++++++++++++ testing/unittest.js | 16 +++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 2a29cb42..f426eb2d 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -326,6 +326,9 @@ Note that the values are case *sensitive*. If tests are run in parallel (with `pytest-xdist`_ for example), then the order may not be in the correct order. +Columns containing numbers, such as a custom sortable column (see :ref:`modifying-results-table`), +are sorted numerically rather than alphabetically. + Formatting the Duration Column ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/pytest_html/scripts/sort.js b/src/pytest_html/scripts/sort.js index bc94147a..0a3c05c5 100644 --- a/src/pytest_html/scripts/sort.js +++ b/src/pytest_html/scripts/sort.js @@ -1,6 +1,24 @@ const { manager } = require('./datamanager.js') const storageModule = require('./storage.js') +const toNumber = (value) => typeof value === 'string' && value.trim() !== '' ? Number(value) : NaN + +const compareValues = (a, b) => { + const aNumber = toNumber(a) + const bNumber = toNumber(b) + const aIsNumber = Number.isFinite(aNumber) + const bIsNumber = Number.isFinite(bNumber) + + if (aIsNumber && bIsNumber) { + return aNumber - bNumber + } + if (aIsNumber !== bIsNumber) { + // Numeric values are sorted before non-numeric ones + return aIsNumber ? -1 : 1 + } + return a === b ? 0 : a > b ? 1 : -1 +} + const genericSort = (list, key, ascending, customOrder) => { let sorted if (customOrder) { @@ -15,7 +33,7 @@ const genericSort = (list, key, ascending, customOrder) => { return aIndex - bIndex }) } else { - sorted = list.sort((a, b) => a[key] === b[key] ? 0 : a[key] > b[key] ? 1 : -1) + sorted = list.sort((a, b) => compareValues(a[key], b[key])) } if (ascending) { diff --git a/testing/test_e2e.py b/testing/test_e2e.py index dbca2074..d5ee4cef 100644 --- a/testing/test_e2e.py +++ b/testing/test_e2e.py @@ -135,3 +135,38 @@ def test_BBB(): pass assert_that(rows[0]["alpha"]).is_equal_to("BBB") assert_that(rows[1]["test"]).contains("AAA") assert_that(rows[1]["alpha"]).is_equal_to("AAA") + + +def test_custom_numeric_sorting(pytester, path, driver): + pytester.makeconftest( + """ + def pytest_html_results_table_header(cells): + cells.append( + '