test-reporting 3.1.1__tar.gz → 3.2.0__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 (24) hide show
  1. {test_reporting-3.1.1 → test_reporting-3.2.0}/PKG-INFO +64 -5
  2. {test_reporting-3.1.1 → test_reporting-3.2.0}/README.md +63 -4
  3. {test_reporting-3.1.1 → test_reporting-3.2.0}/reporting/cli.py +104 -1
  4. {test_reporting-3.1.1 → test_reporting-3.2.0}/reporting/config.py +8 -0
  5. {test_reporting-3.1.1 → test_reporting-3.2.0}/reporting/publisher.py +2 -6
  6. test_reporting-3.2.0/reporting/templates/index.html +507 -0
  7. test_reporting-3.2.0/reporting/templates/project.html +1080 -0
  8. test_reporting-3.2.0/reporting/templates/run.html +627 -0
  9. {test_reporting-3.1.1 → test_reporting-3.2.0}/setup.py +1 -1
  10. {test_reporting-3.1.1 → test_reporting-3.2.0}/test_reporting.egg-info/PKG-INFO +64 -5
  11. test_reporting-3.1.1/reporting/templates/index.html +0 -208
  12. test_reporting-3.1.1/reporting/templates/project.html +0 -641
  13. test_reporting-3.1.1/reporting/templates/run.html +0 -398
  14. {test_reporting-3.1.1 → test_reporting-3.2.0}/LICENSE +0 -0
  15. {test_reporting-3.1.1 → test_reporting-3.2.0}/reporting/__init__.py +0 -0
  16. {test_reporting-3.1.1 → test_reporting-3.2.0}/reporting/classifier.py +0 -0
  17. {test_reporting-3.1.1 → test_reporting-3.2.0}/reporting/plugin.py +0 -0
  18. {test_reporting-3.1.1 → test_reporting-3.2.0}/reporting/storage.py +0 -0
  19. {test_reporting-3.1.1 → test_reporting-3.2.0}/setup.cfg +0 -0
  20. {test_reporting-3.1.1 → test_reporting-3.2.0}/test_reporting.egg-info/SOURCES.txt +0 -0
  21. {test_reporting-3.1.1 → test_reporting-3.2.0}/test_reporting.egg-info/dependency_links.txt +0 -0
  22. {test_reporting-3.1.1 → test_reporting-3.2.0}/test_reporting.egg-info/entry_points.txt +0 -0
  23. {test_reporting-3.1.1 → test_reporting-3.2.0}/test_reporting.egg-info/requires.txt +0 -0
  24. {test_reporting-3.1.1 → test_reporting-3.2.0}/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.1.1
3
+ Version: 3.2.0
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
@@ -43,12 +43,22 @@ Multi-project test reporting dashboard. Collects pytest results, publishes to a
43
43
  ## How it works
44
44
 
45
45
  ```
46
- Project A tests run ──┐
47
- Project B tests run ──┼──► Central store (local or S3) ──► Dashboard (index.html)
48
- Project C tests run ──┘
46
+ Project A: pytest → local SQLite → publish → ┐
47
+ Project B: pytest local SQLite → publish → ┼─► Central store (local/S3) ─► Dashboard
48
+ Project C: pytest → local SQLite → publish → ┘
49
49
  ```
50
50
 
51
- Each project publishes its results to a shared location. The dashboard reads from that location and shows all projects together. The HTML files are static — they're deployed once and the data file (`data.json`) is updated after every run.
51
+ **Local (per project):**
52
+ - Tests run and results are saved to a local SQLite database (`test_results.db`)
53
+ - SQLite is a scratchpad for local commands (`stats`, `suites`, `cleanup`)
54
+ - SQLite is **never** pushed to the store
55
+
56
+ **Central store (shared):**
57
+ - `test-report publish` reads from SQLite and publishes JSON files to the store
58
+ - Store contains: run files, artifacts (screenshots/traces), and `data.json`
59
+ - Dashboard reads from the store and aggregates all projects
60
+
61
+ The HTML files are static — deployed once. The `data.json` is rebuilt after every publish.
52
62
 
53
63
  ---
54
64
 
@@ -224,8 +234,14 @@ auto_publish = false
224
234
  # Local SQLite — used by stats/suites/cleanup commands
225
235
  db_path = test_results.db
226
236
  retention_days = 365
237
+
238
+ # Dashboard data retention (affects data.json size and loading speed)
239
+ dashboard_max_days = 90 # Days of history in dashboard (default: 90)
240
+ dashboard_max_runs_per_suite = 50 # Run history per suite (default: 50)
227
241
  ```
228
242
 
243
+ **Note:** `retention_days` controls manual cleanup commands, while `dashboard_max_days` and `dashboard_max_runs_per_suite` control what appears in the dashboard automatically. Individual run files remain accessible regardless of these settings.
244
+
229
245
  ---
230
246
 
231
247
  ## CLI commands
@@ -238,6 +254,49 @@ retention_days = 365
238
254
  | `test-report stats` | Show stats from latest run (reads local SQLite) |
239
255
  | `test-report suites` | Show suite statistics (reads local SQLite) |
240
256
  | `test-report cleanup` | Delete old runs from local SQLite |
257
+ | `test-report cleanup-artifacts` | Delete old screenshots and traces (preserves data) |
258
+
259
+ ### Cleaning up old data
260
+
261
+ Both cleanup commands use the `retention_days` setting from `reporting.ini` (default: 365 days).
262
+
263
+ #### `test-report cleanup`
264
+ Removes **all test data** from the **local SQLite database only**:
265
+ - Deletes test runs, results, steps, logs, and analytics older than `retention_days`
266
+ - Use this to free up local database space
267
+ - **Does not affect the published store** (JSON files, artifacts, or dashboard data remain intact)
268
+ - Only impacts local commands like `stats` and `suites`
269
+
270
+ #### `test-report cleanup-artifacts`
271
+ Removes **only screenshots and traces** while preserving all test data:
272
+
273
+ ```bash
274
+ test-report cleanup-artifacts
275
+ ```
276
+
277
+ This will:
278
+ - Remove screenshots (`.png`) and traces (`.zip`) older than `retention_days`
279
+ - Clean **local** directories (`screenshots/`, `traces/`)
280
+ - Clean **published store** artifacts (`store_path/artifacts/` for local stores)
281
+ - Show file count and total size before deletion
282
+ - Ask for confirmation
283
+ - **Preserve all local SQLite data and published JSON files** (logs, error messages, analytics remain intact)
284
+
285
+ **Why use this?** Traces can be 10-50 MB each. This frees disk space on both local machines and the published store while keeping all test data for trend analysis.
286
+
287
+ **What happens in the UI?** The dashboard gracefully handles missing artifacts:
288
+ - Screenshots show: "Screenshot cleaned up" (dashed border placeholder)
289
+ - Trace links show: "Trace file cleaned up" (yellow badge)
290
+ - All test data, logs, and error messages remain visible
291
+
292
+ **Note:** Only failed tests have screenshots and traces saved. Passed tests don't generate these artifacts.
293
+
294
+ **S3 limitation:** Currently only cleans local artifacts. S3 artifact cleanup will be added in a future version.
295
+
296
+ Configure retention in `reporting.ini`:
297
+ ```ini
298
+ retention_days = 90 # Keep 90 days of artifacts and data
299
+ ```
241
300
 
242
301
  ---
243
302
 
@@ -7,12 +7,22 @@ Multi-project test reporting dashboard. Collects pytest results, publishes to a
7
7
  ## How it works
8
8
 
9
9
  ```
10
- Project A tests run ──┐
11
- Project B tests run ──┼──► Central store (local or S3) ──► Dashboard (index.html)
12
- Project C tests run ──┘
10
+ Project A: pytest → local SQLite → publish → ┐
11
+ Project B: pytest local SQLite → publish → ┼─► Central store (local/S3) ─► Dashboard
12
+ Project C: pytest → local SQLite → publish → ┘
13
13
  ```
14
14
 
15
- Each project publishes its results to a shared location. The dashboard reads from that location and shows all projects together. The HTML files are static — they're deployed once and the data file (`data.json`) is updated after every run.
15
+ **Local (per project):**
16
+ - Tests run and results are saved to a local SQLite database (`test_results.db`)
17
+ - SQLite is a scratchpad for local commands (`stats`, `suites`, `cleanup`)
18
+ - SQLite is **never** pushed to the store
19
+
20
+ **Central store (shared):**
21
+ - `test-report publish` reads from SQLite and publishes JSON files to the store
22
+ - Store contains: run files, artifacts (screenshots/traces), and `data.json`
23
+ - Dashboard reads from the store and aggregates all projects
24
+
25
+ The HTML files are static — deployed once. The `data.json` is rebuilt after every publish.
16
26
 
17
27
  ---
18
28
 
@@ -188,8 +198,14 @@ auto_publish = false
188
198
  # Local SQLite — used by stats/suites/cleanup commands
189
199
  db_path = test_results.db
190
200
  retention_days = 365
201
+
202
+ # Dashboard data retention (affects data.json size and loading speed)
203
+ dashboard_max_days = 90 # Days of history in dashboard (default: 90)
204
+ dashboard_max_runs_per_suite = 50 # Run history per suite (default: 50)
191
205
  ```
192
206
 
207
+ **Note:** `retention_days` controls manual cleanup commands, while `dashboard_max_days` and `dashboard_max_runs_per_suite` control what appears in the dashboard automatically. Individual run files remain accessible regardless of these settings.
208
+
193
209
  ---
194
210
 
195
211
  ## CLI commands
@@ -202,6 +218,49 @@ retention_days = 365
202
218
  | `test-report stats` | Show stats from latest run (reads local SQLite) |
203
219
  | `test-report suites` | Show suite statistics (reads local SQLite) |
204
220
  | `test-report cleanup` | Delete old runs from local SQLite |
221
+ | `test-report cleanup-artifacts` | Delete old screenshots and traces (preserves data) |
222
+
223
+ ### Cleaning up old data
224
+
225
+ Both cleanup commands use the `retention_days` setting from `reporting.ini` (default: 365 days).
226
+
227
+ #### `test-report cleanup`
228
+ Removes **all test data** from the **local SQLite database only**:
229
+ - Deletes test runs, results, steps, logs, and analytics older than `retention_days`
230
+ - Use this to free up local database space
231
+ - **Does not affect the published store** (JSON files, artifacts, or dashboard data remain intact)
232
+ - Only impacts local commands like `stats` and `suites`
233
+
234
+ #### `test-report cleanup-artifacts`
235
+ Removes **only screenshots and traces** while preserving all test data:
236
+
237
+ ```bash
238
+ test-report cleanup-artifacts
239
+ ```
240
+
241
+ This will:
242
+ - Remove screenshots (`.png`) and traces (`.zip`) older than `retention_days`
243
+ - Clean **local** directories (`screenshots/`, `traces/`)
244
+ - Clean **published store** artifacts (`store_path/artifacts/` for local stores)
245
+ - Show file count and total size before deletion
246
+ - Ask for confirmation
247
+ - **Preserve all local SQLite data and published JSON files** (logs, error messages, analytics remain intact)
248
+
249
+ **Why use this?** Traces can be 10-50 MB each. This frees disk space on both local machines and the published store while keeping all test data for trend analysis.
250
+
251
+ **What happens in the UI?** The dashboard gracefully handles missing artifacts:
252
+ - Screenshots show: "Screenshot cleaned up" (dashed border placeholder)
253
+ - Trace links show: "Trace file cleaned up" (yellow badge)
254
+ - All test data, logs, and error messages remain visible
255
+
256
+ **Note:** Only failed tests have screenshots and traces saved. Passed tests don't generate these artifacts.
257
+
258
+ **S3 limitation:** Currently only cleans local artifacts. S3 artifact cleanup will be added in a future version.
259
+
260
+ Configure retention in `reporting.ini`:
261
+ ```ini
262
+ retention_days = 90 # Keep 90 days of artifacts and data
263
+ ```
205
264
 
206
265
  ---
207
266
 
@@ -197,6 +197,28 @@ def show_suites():
197
197
  print("\n" + "="*80)
198
198
 
199
199
 
200
+ def rebuild_dashboard(config_file='reporting.ini'):
201
+ """Rebuild data.json from existing run files in the store."""
202
+ config = ReportingConfig(config_file=config_file)
203
+ publisher = Publisher(config=config)
204
+
205
+ store_type = config.STORE_TYPE
206
+ store_location = config.S3_BUCKET if store_type == 's3' else str(config.STORE_PATH)
207
+ print(f"[Reporting] Rebuilding dashboard from {store_type}: {store_location}")
208
+
209
+ if store_type == 's3':
210
+ s3 = publisher._get_s3_client()
211
+ publisher._rebuild_data_json_s3(s3, config.S3_BUCKET)
212
+ else:
213
+ store = config.STORE_PATH
214
+ if not store.exists():
215
+ print(f"[Reporting] Store path does not exist: {store}")
216
+ sys.exit(1)
217
+ publisher._rebuild_data_json_local(store)
218
+
219
+ print(f"[Reporting] Dashboard rebuilt successfully!")
220
+
221
+
200
222
  def cleanup_old_data():
201
223
  """Clean up old test data based on retention policy."""
202
224
  import sqlite3
@@ -233,15 +255,96 @@ def cleanup_old_data():
233
255
  print(f"[Reporting] Remaining: {after_count} test runs")
234
256
 
235
257
 
258
+ def cleanup_artifacts():
259
+ """Clean up old screenshots and traces while keeping database records."""
260
+ import os
261
+ from datetime import datetime, timedelta
262
+
263
+ config = ReportingConfig()
264
+
265
+ print(f"\n[Reporting] Artifact Cleanup")
266
+ print("=" * 60)
267
+ print(f"This will delete screenshots and traces older than {config.RETENTION_DAYS} days.")
268
+ print("Database records and logs will be preserved.")
269
+ print(f"\nLocal directories:")
270
+ print(f" Screenshots: {config.SCREENSHOTS_DIR}")
271
+ print(f" Traces: {config.TRACES_DIR}")
272
+
273
+ # Count files to delete
274
+ cutoff_date = datetime.now() - timedelta(days=config.RETENTION_DAYS)
275
+
276
+ screenshots_to_delete = []
277
+ traces_to_delete = []
278
+
279
+ if config.SCREENSHOTS_DIR.exists():
280
+ for file in config.SCREENSHOTS_DIR.glob('*.png'):
281
+ if datetime.fromtimestamp(file.stat().st_mtime) < cutoff_date:
282
+ screenshots_to_delete.append(file)
283
+
284
+ if config.TRACES_DIR.exists():
285
+ for file in config.TRACES_DIR.glob('*.zip'):
286
+ if datetime.fromtimestamp(file.stat().st_mtime) < cutoff_date:
287
+ traces_to_delete.append(file)
288
+
289
+ # Also clean up published artifacts in store
290
+ store_artifacts = []
291
+ if config.STORE_TYPE == 'local' and config.STORE_PATH.exists():
292
+ artifacts_dir = config.STORE_PATH / 'artifacts'
293
+ if artifacts_dir.exists():
294
+ print(f" Store: {artifacts_dir}")
295
+ for file in artifacts_dir.glob('*'):
296
+ if file.suffix in ['.png', '.zip']:
297
+ if datetime.fromtimestamp(file.stat().st_mtime) < cutoff_date:
298
+ store_artifacts.append(file)
299
+
300
+ total_files = len(screenshots_to_delete) + len(traces_to_delete) + len(store_artifacts)
301
+
302
+ if total_files == 0:
303
+ print(f"\n[Reporting] No artifacts older than {config.RETENTION_DAYS} days found.")
304
+ return
305
+
306
+ # Calculate total size
307
+ total_size = sum(f.stat().st_size for f in screenshots_to_delete + traces_to_delete + store_artifacts)
308
+ size_mb = total_size / (1024 * 1024)
309
+
310
+ print(f"\nFound artifacts to delete:")
311
+ print(f" Local screenshots: {len(screenshots_to_delete)}")
312
+ print(f" Local traces: {len(traces_to_delete)}")
313
+ if store_artifacts:
314
+ print(f" Store artifacts: {len(store_artifacts)}")
315
+ print(f" Total size: {size_mb:.2f} MB")
316
+ print(f"\nOlder than: {cutoff_date.strftime('%Y-%m-%d %H:%M:%S')}")
317
+
318
+ response = input(f"\n[Reporting] Delete {total_files} artifact files? (yes/no): ")
319
+ if response.lower() not in ['yes', 'y']:
320
+ print("[Reporting] Cleanup cancelled.")
321
+ return
322
+
323
+ # Delete files
324
+ deleted_count = 0
325
+ for file in screenshots_to_delete + traces_to_delete + store_artifacts:
326
+ try:
327
+ file.unlink()
328
+ deleted_count += 1
329
+ except Exception as e:
330
+ print(f"[Warning] Could not delete {file.name}: {e}")
331
+
332
+ print(f"\n[Reporting] Cleanup complete!")
333
+ print(f"Deleted {deleted_count} artifact files ({size_mb:.2f} MB freed)")
334
+ print(f"Database records preserved for historical analysis.")
335
+
336
+
236
337
  def main():
237
338
  """Main CLI entry point."""
238
339
  commands = {
239
340
  'publish': (publish, 'Push latest test run to the configured store'),
240
341
  'deploy': (deploy, 'Deploy static dashboard HTML to the store (run once)'),
342
+ 'rebuild': (rebuild_dashboard, 'Rebuild data.json from existing run files'),
241
343
  'open': (open_dashboard, 'Open local dashboard in browser'),
242
344
  'stats': (show_stats, 'Show quick stats from latest run'),
243
345
  'suites': (show_suites, 'Show statistics for all test suites'),
244
346
  'cleanup': (cleanup_old_data, 'Clean up old data from local SQLite'),
347
+ 'cleanup-artifacts': (cleanup_artifacts, 'Clean up old screenshots and traces'),
245
348
  }
246
349
 
247
350
  if len(sys.argv) < 2 or sys.argv[1] not in commands:
@@ -271,7 +374,7 @@ def main():
271
374
  command_func = commands[command_name][0]
272
375
 
273
376
  # Only pass config_file to commands that support it
274
- if command_name in ['publish', 'deploy']:
377
+ if command_name in ['publish', 'deploy', 'rebuild']:
275
378
  command_func(config_file=config_file)
276
379
  else:
277
380
  command_func()
@@ -33,6 +33,10 @@ class ReportingConfig:
33
33
  self.LATEST_JSON_PATH = self.REPORTS_DIR / 'latest.json'
34
34
  self.DASHBOARD_DIR = Path(s.get('dashboard_dir', 'dashboard'))
35
35
  self.RETENTION_DAYS = int(s.get('retention_days', 365))
36
+
37
+ # Dashboard data retention (affects data.json size and performance)
38
+ self.DASHBOARD_MAX_DAYS = int(s.get('dashboard_max_days', 90))
39
+ self.DASHBOARD_MAX_RUNS_PER_SUITE = int(s.get('dashboard_max_runs_per_suite', 50))
36
40
 
37
41
  # Store config
38
42
  self.STORE_TYPE = s.get('store_type', 'local') # local | s3
@@ -62,6 +66,10 @@ class ReportingConfig:
62
66
  self.DB_PATH = Path('test_results.db')
63
67
  self.LATEST_JSON_PATH = self.REPORTS_DIR / 'latest.json'
64
68
  self.RETENTION_DAYS = 365
69
+
70
+ # Dashboard data retention
71
+ self.DASHBOARD_MAX_DAYS = 90
72
+ self.DASHBOARD_MAX_RUNS_PER_SUITE = 50
65
73
 
66
74
  self.STORE_TYPE = os.getenv('STORE_TYPE', 'local')
67
75
  self.STORE_PATH = Path(os.getenv('STORE_PATH', 'dashboard'))
@@ -12,10 +12,6 @@ from collections import defaultdict
12
12
  from .config import ReportingConfig
13
13
 
14
14
 
15
- # How many run summaries to keep per suite in data.json
16
- MAX_RUNS_PER_SUITE = 50
17
- # How many days of run history to keep in data.json
18
- MAX_DAYS = 90
19
15
  # Minimum runs before flagging a test as flaky
20
16
  MIN_RUNS_FOR_FLAKY = 3
21
17
  # Minimum avg duration (seconds) to appear in slowest tests
@@ -514,7 +510,7 @@ class Publisher:
514
510
  # Trim suite run lists
515
511
  for s in suites.values():
516
512
  s['runs'] = sorted(s['runs'], key=lambda r: r['timestamp'], reverse=True)
517
- s['runs'] = s['runs'][:MAX_RUNS_PER_SUITE]
513
+ s['runs'] = s['runs'][:self.config.DASHBOARD_MAX_RUNS_PER_SUITE]
518
514
 
519
515
  # Sort regressions newest first
520
516
  regressions = dict(sorted(regressions.items(), key=lambda x: x[0], reverse=True))
@@ -772,5 +768,5 @@ class Publisher:
772
768
  # ------------------------------------------------------------------
773
769
 
774
770
  def _cutoff_date(self) -> str:
775
- cutoff = datetime.now(timezone.utc) - timedelta(days=MAX_DAYS)
771
+ cutoff = datetime.now(timezone.utc) - timedelta(days=self.config.DASHBOARD_MAX_DAYS)
776
772
  return cutoff.isoformat()