test-reporting 3.6.1__tar.gz → 3.6.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. {test_reporting-3.6.1/test_reporting.egg-info → test_reporting-3.6.3}/PKG-INFO +1 -1
  2. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/plugin.py +3 -0
  3. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/publisher.py +15 -5
  4. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/storage.py +43 -3
  5. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/templates/project.html +14 -2
  6. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/templates/run.html +3 -1
  7. {test_reporting-3.6.1 → test_reporting-3.6.3}/setup.py +1 -1
  8. {test_reporting-3.6.1 → test_reporting-3.6.3/test_reporting.egg-info}/PKG-INFO +1 -1
  9. {test_reporting-3.6.1 → test_reporting-3.6.3}/LICENSE +0 -0
  10. {test_reporting-3.6.1 → test_reporting-3.6.3}/README.md +0 -0
  11. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/__init__.py +0 -0
  12. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/classifier.py +0 -0
  13. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/cli.py +0 -0
  14. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/config.py +0 -0
  15. {test_reporting-3.6.1 → test_reporting-3.6.3}/reporting/templates/index.html +0 -0
  16. {test_reporting-3.6.1 → test_reporting-3.6.3}/setup.cfg +0 -0
  17. {test_reporting-3.6.1 → test_reporting-3.6.3}/test_reporting.egg-info/SOURCES.txt +0 -0
  18. {test_reporting-3.6.1 → test_reporting-3.6.3}/test_reporting.egg-info/dependency_links.txt +0 -0
  19. {test_reporting-3.6.1 → test_reporting-3.6.3}/test_reporting.egg-info/entry_points.txt +0 -0
  20. {test_reporting-3.6.1 → test_reporting-3.6.3}/test_reporting.egg-info/requires.txt +0 -0
  21. {test_reporting-3.6.1 → test_reporting-3.6.3}/test_reporting.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: test-reporting
3
- Version: 3.6.1
3
+ Version: 3.6.3
4
4
  Summary: Multi-project test reporting dashboard — collect results locally or push to S3
5
5
  Home-page: https://github.com/amahdy77/test-reporting.git
6
6
  Author: Ashfaqur Mahdy
@@ -248,6 +248,9 @@ class TestReportingPlugin:
248
248
  logging.info(f"[Reporting] Published run: {run_id}")
249
249
  except Exception as e:
250
250
  logging.warning(f"[Reporting] Auto-publish failed: {e}")
251
+
252
+ # Close database connection to prevent ResourceWarning
253
+ self.storage.close()
251
254
 
252
255
  def _save_latest_json(self):
253
256
  """Save latest run data as JSON for dashboard."""
@@ -493,8 +493,12 @@ class Publisher:
493
493
  # Regression grouping
494
494
  if tag not in regressions:
495
495
  regressions[tag] = {'tag': tag, 'date': tag[:10], 'suites': [],
496
- 'total_tests': 0, 'passed': 0, 'failed': 0, 'pass_rate': 0}
496
+ 'total_tests': 0, 'passed': 0, 'failed': 0, 'pass_rate': 0,
497
+ 'timestamp': ''}
497
498
  reg = regressions[tag]
499
+ # Track the latest timestamp for this regression
500
+ if ts > reg.get('timestamp', ''):
501
+ reg['timestamp'] = ts
498
502
  existing = next((s for s in reg['suites'] if s['suite'] == suite), None)
499
503
  if existing:
500
504
  if ts > existing.get('timestamp', ''):
@@ -525,8 +529,10 @@ class Publisher:
525
529
  s['runs'] = sorted(s['runs'], key=lambda r: r['timestamp'], reverse=True)
526
530
  s['runs'] = s['runs'][:self.config.DASHBOARD_MAX_RUNS_PER_SUITE]
527
531
 
528
- # Sort regressions newest first
529
- regressions = dict(sorted(regressions.items(), key=lambda x: x[0], reverse=True))
532
+ # Sort regressions by timestamp (newest first), fallback to tag name
533
+ regressions = dict(sorted(regressions.items(),
534
+ key=lambda x: (x[1].get('timestamp', ''), x[0]),
535
+ reverse=True))
530
536
 
531
537
  # Overall health from latest regression
532
538
  last_reg = next(iter(regressions.values()), None) if regressions else None
@@ -745,7 +751,9 @@ class Publisher:
745
751
  s3.put_object(Bucket=bucket, Key=dest_key, Body=f.read(),
746
752
  ContentType='image/png')
747
753
  print(f"[Reporting] ✓ Uploaded screenshot: {src.name}")
748
- test['screenshot_path'] = f"artifacts/{src.name}"
754
+ # Use full S3 URL so it works when dashboard is served from S3
755
+ s3_url = f"https://{bucket}.s3.{self.config.S3_REGION}.amazonaws.com/{dest_key}"
756
+ test['screenshot_path'] = s3_url
749
757
  artifact_count += 1
750
758
  else:
751
759
  print(f"[Reporting] ✗ Screenshot not found: {src}")
@@ -761,7 +769,9 @@ class Publisher:
761
769
  s3.put_object(Bucket=bucket, Key=dest_key, Body=f.read(),
762
770
  ContentType='application/zip')
763
771
  print(f"[Reporting] ✓ Uploaded trace: {src.name}")
764
- test['trace_path'] = f"artifacts/{src.name}"
772
+ # Use full S3 URL so it works when dashboard is served from S3
773
+ s3_url = f"https://{bucket}.s3.{self.config.S3_REGION}.amazonaws.com/{dest_key}"
774
+ test['trace_path'] = s3_url
765
775
  artifact_count += 1
766
776
  else:
767
777
  print(f"[Reporting] ✗ Trace not found: {src}")
@@ -18,15 +18,49 @@ class TestResultStorage:
18
18
  self.config = config or ReportingConfig()
19
19
  self.db_path = db_path or self.config.DB_PATH
20
20
  self.db_path.parent.mkdir(parents=True, exist_ok=True)
21
+ self._connection = None
21
22
  self._init_database()
22
23
 
24
+ def __enter__(self):
25
+ """Context manager entry."""
26
+ return self
27
+
28
+ def __exit__(self, exc_type, exc_val, exc_tb):
29
+ """Context manager exit - ensure connection is closed."""
30
+ self.close()
31
+ return False
32
+
33
+ def close(self):
34
+ """Close any open database connections."""
35
+ if self._connection is not None:
36
+ try:
37
+ self._connection.close()
38
+ except Exception:
39
+ pass
40
+ finally:
41
+ self._connection = None
42
+
43
+ def _execute_with_connection(self, func):
44
+ """Execute a function with a database connection, ensuring it's closed."""
45
+ conn = sqlite3.connect(self.db_path)
46
+ try:
47
+ result = func(conn)
48
+ conn.commit()
49
+ return result
50
+ finally:
51
+ conn.close()
52
+
23
53
  def _init_database(self):
24
54
  """Initialize database with WAL mode and create tables."""
25
- with sqlite3.connect(self.db_path) as conn:
55
+ conn = sqlite3.connect(self.db_path)
56
+ try:
26
57
  # Enable WAL mode for concurrent access
27
58
  conn.execute('PRAGMA journal_mode=WAL')
28
59
  conn.execute('PRAGMA busy_timeout=5000') # Wait 5s if locked
29
60
  self._create_tables(conn)
61
+ conn.commit()
62
+ finally:
63
+ conn.close()
30
64
 
31
65
  def _create_tables(self, conn: sqlite3.Connection):
32
66
  """Create database tables if they don't exist."""
@@ -156,7 +190,8 @@ class TestResultStorage:
156
190
 
157
191
  def save_test_run(self, run_data: Dict[str, Any]) -> int:
158
192
  """Save a test run and return the run ID."""
159
- with sqlite3.connect(self.db_path) as conn:
193
+ conn = sqlite3.connect(self.db_path)
194
+ try:
160
195
  cursor = conn.execute('''
161
196
  INSERT INTO test_runs (
162
197
  project_name, timestamp, suite_name, build_number, build_url, run_tag,
@@ -180,10 +215,13 @@ class TestResultStorage:
180
215
  ))
181
216
  conn.commit()
182
217
  return cursor.lastrowid
218
+ finally:
219
+ conn.close()
183
220
 
184
221
  def save_test_result(self, run_id: int, result_data: Dict[str, Any]) -> int:
185
222
  """Save a test result and return the result ID."""
186
- with sqlite3.connect(self.db_path) as conn:
223
+ conn = sqlite3.connect(self.db_path)
224
+ try:
187
225
  cursor = conn.execute('''
188
226
  INSERT INTO test_results (
189
227
  run_id, project_name, file_name, test_name, full_name, status,
@@ -211,6 +249,8 @@ class TestResultStorage:
211
249
  ))
212
250
  conn.commit()
213
251
  return cursor.lastrowid
252
+ finally:
253
+ conn.close()
214
254
 
215
255
  def save_test_step(self, result_id: int, step_data: Dict[str, Any]):
216
256
  """Save a test step."""
@@ -392,6 +392,14 @@
392
392
  return new Date(iso).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false });
393
393
  }
394
394
 
395
+ function fmtDateTime(iso) {
396
+ if (!iso) return '-';
397
+ const date = new Date(iso);
398
+ const dateStr = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
399
+ const timeStr = date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false });
400
+ return dateStr + ' ' + timeStr;
401
+ }
402
+
395
403
  function fmtDur(s) {
396
404
  if (!s || s === 0) return '-';
397
405
  if (s >= 60) return Math.floor(s / 60) + 'm ' + Math.floor(s % 60) + 's';
@@ -553,10 +561,12 @@
553
561
  }).join('');
554
562
 
555
563
  const isOpen = i === 0;
564
+ const regDateTime = reg.timestamp ? fmtDateTime(reg.timestamp) : '';
565
+ const regTitle = regDateTime ? reg.tag + ' · ' + regDateTime : reg.tag;
556
566
  return '<div class="regression">' +
557
567
  '<div class="reg-head" onclick="toggleReg(this)">' +
558
568
  '<div>' +
559
- '<div class="reg-title">' + reg.tag + '</div>' +
569
+ '<div class="reg-title">' + regTitle + '</div>' +
560
570
  '<div class="reg-sub">' + reg.suites.length + ' test ' + plural(reg.suites.length, 'file') + ' · ' + reg.total_tests + ' tests</div>' +
561
571
  '</div>' +
562
572
  '<div class="reg-right">' +
@@ -1207,7 +1217,9 @@
1207
1217
  }
1208
1218
 
1209
1219
  document.getElementById('pageTitle').textContent = proj.name;
1210
- document.getElementById('lastRun').textContent = 'Last run ' + relTime(proj.last_run);
1220
+ const lastRunEl = document.getElementById('lastRun');
1221
+ lastRunEl.textContent = 'Last run ' + relTime(proj.last_run);
1222
+ lastRunEl.title = 'All times shown in your local timezone';
1211
1223
  document.title = proj.name + ' - Test Dashboard';
1212
1224
 
1213
1225
  const analytics = proj.analytics || {};
@@ -413,7 +413,9 @@
413
413
  document.title = (run.suite_name || 'Run') + ' - Test Dashboard';
414
414
  document.getElementById('pageTitle').textContent =
415
415
  (run.suite_name || 'Run') + ' · ' + (run.project_name || '');
416
- document.getElementById('runTime').textContent = fmtDate(run.timestamp);
416
+ var runTimeEl = document.getElementById('runTime');
417
+ runTimeEl.textContent = fmtDate(run.timestamp);
418
+ runTimeEl.title = 'Time shown in your local timezone';
417
419
 
418
420
  var backHref = projectName
419
421
  ? 'project.html?p=' + encodeURIComponent(projectName)
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
9
9
 
10
10
  setup(
11
11
  name='test-reporting',
12
- version='3.6.1',
12
+ version='3.6.3',
13
13
  description='Multi-project test reporting dashboard — collect results locally or push to S3',
14
14
  long_description=long_description,
15
15
  long_description_content_type="text/markdown",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: test-reporting
3
- Version: 3.6.1
3
+ Version: 3.6.3
4
4
  Summary: Multi-project test reporting dashboard — collect results locally or push to S3
5
5
  Home-page: https://github.com/amahdy77/test-reporting.git
6
6
  Author: Ashfaqur Mahdy
File without changes
File without changes
File without changes