@@ -6,6 +6,7 @@ let currentRunId = null;
66let eventSource = null ;
77let savedEnvironments = { } ;
88let editingEnvName = null ;
9+ let runStartTime = null ;
910
1011// SVG-Icons (Feather/Lucide-Stil, currentColor)
1112const ICON_EDIT = '<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"></path></svg>' ;
@@ -37,7 +38,8 @@ document.addEventListener("DOMContentLoaded", () => {
3738 loadReports ( ) ;
3839 loadScreenshots ( ) ;
3940 loadJiraConfig ( ) ;
40- renderRunSparkline ( ) ;
41+ applyLatestRunToCards ( ) ;
42+ renderAllSparklines ( ) ;
4143 syncThemeToggleState ( ) ;
4244
4345 // Enter-Taste im URL-Feld startet Tests
@@ -287,6 +289,7 @@ async function runTests() {
287289 setBtnLoading ( "btnRunTests" , true ) ;
288290 document . getElementById ( "btnCancel" ) . style . display = "" ;
289291 setConnectionStatus ( "running" ) ;
292+ runStartTime = Date . now ( ) ;
290293
291294 // Ergebnisse zuruecksetzen
292295 clearResults ( ) ;
@@ -485,12 +488,24 @@ async function onTestsCompleted(data) {
485488
486489 const total = ( data . passed || 0 ) + ( data . failed || 0 ) + ( data . skipped || 0 ) ;
487490 const pct = total > 0 ? Math . round ( ( ( data . passed || 0 ) / total ) * 100 ) : 0 ;
491+ const errorPct = total > 0 ? Math . round ( ( ( data . failed || 0 ) / total ) * 100 ) : 0 ;
492+ const durationMs = runStartTime ? Date . now ( ) - runStartTime : null ;
493+ runStartTime = null ;
494+
488495 document . getElementById ( "lastRunStatus" ) . textContent = `${ pct } % bestanden` ;
496+ document . getElementById ( "lastRunDuration" ) . textContent = durationMs ? formatDuration ( durationMs ) : "--" ;
497+ document . getElementById ( "lastRunErrorRate" ) . textContent = total > 0 ? `${ errorPct } %` : "--" ;
489498
490499 if ( ! cancelled && total > 0 ) {
491- addRunToHistory ( { pct : pct , passed : data . passed || 0 , failed : data . failed || 0 , total : total } ) ;
500+ addRunToHistory ( {
501+ pct : pct ,
502+ passed : data . passed || 0 ,
503+ failed : data . failed || 0 ,
504+ total : total ,
505+ duration_ms : durationMs ,
506+ } ) ;
492507 }
493- renderRunSparkline ( ) ;
508+ renderAllSparklines ( ) ;
494509
495510 stopLiveBrowser ( ) ;
496511
@@ -1321,14 +1336,34 @@ function addRunToHistory(run) {
13211336 try { localStorage . setItem ( HISTORY_KEY , JSON . stringify ( list ) ) ; } catch ( e ) { }
13221337}
13231338
1324- function renderRunSparkline ( ) {
1325- const card = document . getElementById ( "cardLastRun" ) ;
1339+ function formatDuration ( ms ) {
1340+ if ( ms == null || isNaN ( ms ) || ms < 0 ) return "--" ;
1341+ const totalSec = Math . round ( ms / 1000 ) ;
1342+ if ( totalSec < 60 ) return `${ totalSec } s` ;
1343+ const min = Math . floor ( totalSec / 60 ) ;
1344+ const sec = totalSec % 60 ;
1345+ if ( min < 60 ) return `${ min } m ${ sec . toString ( ) . padStart ( 2 , "0" ) } s` ;
1346+ const h = Math . floor ( min / 60 ) ;
1347+ const m = min % 60 ;
1348+ return `${ h } h ${ m . toString ( ) . padStart ( 2 , "0" ) } m` ;
1349+ }
1350+
1351+ function renderAllSparklines ( ) {
1352+ renderSparkline ( "cardLastRun" , r => r . pct , { fixedMin : 0 , fixedMax : 100 , label : "Pass-Rate-Trend" , fmt : v => `${ Math . round ( v ) } %` } ) ;
1353+ renderSparkline ( "cardDuration" , r => ( r . duration_ms != null ? r . duration_ms : null ) , { label : "Dauer-Trend" , fmt : v => formatDuration ( v ) } ) ;
1354+ renderSparkline ( "cardErrorRate" , r => r . total > 0 ? ( r . failed / r . total ) * 100 : 0 , { fixedMin : 0 , fixedMax : 100 , label : "Fehlerraten-Trend" , fmt : v => `${ Math . round ( v ) } %` } ) ;
1355+ }
1356+
1357+ function renderSparkline ( cardId , getValue , opts = { } ) {
1358+ const card = document . getElementById ( cardId ) ;
13261359 if ( ! card ) return ;
13271360 let svg = card . querySelector ( ".stat-sparkline" ) ;
13281361 let empty = card . querySelector ( ".stat-sparkline-empty" ) ;
1362+
13291363 const history = getRunHistory ( ) ;
1364+ const values = history . map ( getValue ) . filter ( v => v != null && ! isNaN ( v ) ) ;
13301365
1331- if ( history . length < 2 ) {
1366+ if ( values . length < 2 ) {
13321367 if ( svg ) svg . remove ( ) ;
13331368 if ( ! empty ) {
13341369 empty = document . createElement ( "span" ) ;
@@ -1341,14 +1376,13 @@ function renderRunSparkline() {
13411376 if ( empty ) empty . remove ( ) ;
13421377
13431378 const w = 80 , h = 28 , pad = 2 ;
1344- const pcts = history . map ( r => r . pct ) ;
1345- const min = Math . min ( ...pcts , 0 ) ;
1346- const max = Math . max ( ...pcts , 100 ) ;
1379+ const min = opts . fixedMin !== undefined ? Math . min ( ...values , opts . fixedMin ) : Math . min ( ...values ) ;
1380+ const max = opts . fixedMax !== undefined ? Math . max ( ...values , opts . fixedMax ) : Math . max ( ...values ) ;
13471381 const range = max - min || 1 ;
1348- const stepX = ( w - pad * 2 ) / ( pcts . length - 1 ) ;
1349- const points = pcts . map ( ( p , i ) => {
1382+ const stepX = ( w - pad * 2 ) / ( values . length - 1 ) ;
1383+ const points = values . map ( ( v , i ) => {
13501384 const x = pad + i * stepX ;
1351- const y = h - pad - ( ( p - min ) / range ) * ( h - pad * 2 ) ;
1385+ const y = h - pad - ( ( v - min ) / range ) * ( h - pad * 2 ) ;
13521386 return [ x , y ] ;
13531387 } ) ;
13541388 const linePath = points . map ( ( [ x , y ] , i ) => `${ i === 0 ? "M" : "L" } ${ x . toFixed ( 1 ) } ${ y . toFixed ( 1 ) } ` ) . join ( " " ) ;
@@ -1359,15 +1393,33 @@ function renderRunSparkline() {
13591393 svg = document . createElementNS ( "http://www.w3.org/2000/svg" , "svg" ) ;
13601394 svg . setAttribute ( "class" , "stat-sparkline" ) ;
13611395 svg . setAttribute ( "viewBox" , `0 0 ${ w } ${ h } ` ) ;
1362- svg . setAttribute ( "aria-label" , "Trend der letzten Testläufe ") ;
1396+ svg . setAttribute ( "aria-label" , opts . label || "Trend ") ;
13631397 card . appendChild ( svg ) ;
13641398 }
13651399 svg . innerHTML = `
13661400 <path class="area" d="${ areaPath } "></path>
13671401 <path class="line" d="${ linePath } "></path>
13681402 <circle cx="${ last [ 0 ] . toFixed ( 1 ) } " cy="${ last [ 1 ] . toFixed ( 1 ) } " r="2.5"></circle>
13691403 ` ;
1370- svg . setAttribute ( "title" , history . map ( r => `${ r . pct } %` ) . join ( " → " ) ) ;
1404+ const fmt = opts . fmt || ( v => v ) ;
1405+ svg . setAttribute ( "title" , values . map ( fmt ) . join ( " → " ) ) ;
1406+ }
1407+
1408+ // Beibehalten als Alias, falls noch irgendwo referenziert
1409+ function renderRunSparkline ( ) {
1410+ renderAllSparklines ( ) ;
1411+ }
1412+
1413+ // Initial-State der Stat-Cards aus History befüllen
1414+ function applyLatestRunToCards ( ) {
1415+ const history = getRunHistory ( ) ;
1416+ if ( history . length === 0 ) return ;
1417+ const last = history [ history . length - 1 ] ;
1418+ const total = last . total || 0 ;
1419+ const failed = last . failed || 0 ;
1420+ document . getElementById ( "lastRunStatus" ) . textContent = `${ last . pct } % bestanden` ;
1421+ document . getElementById ( "lastRunDuration" ) . textContent = formatDuration ( last . duration_ms ) ;
1422+ document . getElementById ( "lastRunErrorRate" ) . textContent = total > 0 ? `${ Math . round ( ( failed / total ) * 100 ) } %` : "--" ;
13711423}
13721424
13731425// ========== Screenshot Drag&Drop ==========
0 commit comments