test-web: Add run date and "copy run command" button to results header

This commit is contained in:
Jonathan Gamble 2026-04-11 16:09:25 -05:00 committed by Shannon Booth
parent 1af74d1a7c
commit 778402640a
3 changed files with 59 additions and 8 deletions

View file

@ -8,6 +8,7 @@
#include <AK/ByteString.h>
#include <AK/Error.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibWebView/Application.h>
@ -37,6 +38,7 @@ public:
Vector<ByteString> test_globs;
ByteString python_executable_path;
String invocation_command_line;
bool dump_gc_graph { false };
bool fail_fast { false };

View file

@ -452,6 +452,7 @@ static ErrorOr<void> generate_result_files(ReadonlySpan<Test> tests, ReadonlySpa
{
auto& app = Application::the();
bool const has_helper_logs = FileSystem::exists(LexicalPath::join(app.results_directory, "helper-process-logs.html"sv).string());
auto const generated_at = UnixDateTime::now();
// Count results
size_t fail_count = 0;
@ -482,6 +483,8 @@ static ErrorOr<void> generate_result_files(ReadonlySpan<Test> tests, ReadonlySpa
js.append("const RESULTS_DATA = {\n"sv);
js.appendff(" \"summary\": {{ \"total\": {}, \"fail\": {}, \"timeout\": {}, \"crashed\": {}, \"skipped\": {} }},\n",
s_total_tests, fail_count, timeout_count, crashed_count, skipped_count);
js.appendff(" \"generatedAt\": {},\n", generated_at.seconds_since_epoch());
js.appendff(" \"invocationCommandLine\": {},\n", JsonValue(app.invocation_command_line).serialized());
js.appendff(" \"hasLogs\": {},\n", has_helper_logs ? "true" : "false");
js.append(" \"tests\": [\n"sv);
@ -1724,6 +1727,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
#else
auto app = TRY(TestWeb::Application::create(arguments, OptionalNone {}));
#endif
app->invocation_command_line = MUST(String::join(' ', arguments.strings));
if (app->repeat_count > 1 && app->rebaseline) {
warnln("Error: --repeat cannot be used together with --rebaseline.");

View file

@ -45,7 +45,7 @@
header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border-primary);
@ -60,6 +60,12 @@
gap: 12px;
}
.title-timestamp {
color: var(--text-secondary);
font-size: 18px;
font-weight: normal;
}
h1::before {
content: '';
display: inline-block;
@ -274,6 +280,25 @@
border-color: var(--accent-green);
}
#copy-run-command-btn {
margin-left: auto;
width: auto;
height: auto;
padding: 10px;
gap: 8px;
border-radius: 6px;
}
#copy-run-command-btn .stat-label {
margin-top: 0;
}
.copy-btn-icon {
display: inline-flex;
align-items: center;
justify-content: center;
}
.expand-icon {
color: var(--text-muted);
transition: transform 0.2s ease;
@ -582,7 +607,11 @@
<body>
<div class="container">
<header>
<h1>Test Results</h1>
<h1>
<span>Test Results</span>
<span class="title-timestamp" id="page-timestamp"></span>
</h1>
<button class="copy-btn" id="copy-run-command-btn"></button>
<div class="search-box">
<span class="search-icon">&#128269;</span>
<input type="text" id="search" placeholder="Filter tests..." autocomplete="off">
@ -603,11 +632,27 @@
let currentIndex = -1;
let testElements = [];
function setCopyButtonContents(btn, icon) {
if (btn.id !== 'copy-run-command-btn') return btn.innerHTML = icon;
btn.innerHTML = `<span class="stat-label">Copy Run Cmd</span><span class="copy-btn-icon">${icon}</span>`;
}
function renderHeader(data) {
const date = new Date(data.generatedAt * 1000);
const pad2 = (value) => String(value).padStart(2, '0');
document.getElementById('page-timestamp').textContent = `${date.getFullYear()}/${pad2(date.getMonth() + 1)}/${pad2(date.getDate())} at ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`;
const copyRunCommandButton = document.getElementById('copy-run-command-btn');
setCopyButtonContents(copyRunCommandButton, copySvg);
copyRunCommandButton.title = data.invocationCommandLine;
copyRunCommandButton.onclick = e => copyText(e, copyRunCommandButton, data.invocationCommandLine);
}
function loadResults() {
if (typeof RESULTS_DATA === 'undefined') {
document.getElementById('content').innerHTML = '<div class="empty-state"><h2>No Results</h2><p>results.js not found</p></div>';
return;
}
renderHeader(RESULTS_DATA);
renderResults(RESULTS_DATA);
setupKeyboardNav();
}
@ -665,7 +710,7 @@
${item.result ? `<span class="result-badge ${item.result}">${item.result}</span>` : ''}
${item.mode ? `<span class="mode-badge">${item.mode}</span>` : ''}
<span class="test-name">${item.name}</span>
${item.result ? `<button class="copy-btn" onclick="copyTestName(event, this, '${item.name}')" title="Copy test path">${copySvg}</button>` : ''}
${item.result ? `<button class="copy-btn" onclick="copyText(event, this, '${item.name}')" title="Copy test path">${copySvg}</button>` : ''}
<span class="expand-icon">&#9654;</span>
</div>
<div class="test-content">`;
@ -701,13 +746,13 @@
return { bar, tabs };
}
function copyTestName(event, btn, name) {
event.stopPropagation();
navigator.clipboard.writeText(name).then(function() {
btn.innerHTML = checkSvg;
function copyText(event, btn, text) {
event?.stopPropagation();
navigator.clipboard.writeText(text).then(function() {
setCopyButtonContents(btn, checkSvg);
btn.classList.add('copied');
setTimeout(function() {
btn.innerHTML = copySvg;
setCopyButtonContents(btn, copySvg);
btn.classList.remove('copied');
}, 1500);
});