test-reporting 3.2.8__tar.gz → 3.5.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 (21) hide show
  1. {test_reporting-3.2.8 → test_reporting-3.5.0}/PKG-INFO +1 -1
  2. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/plugin.py +17 -83
  3. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/storage.py +5 -8
  4. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/templates/project.html +7 -0
  5. {test_reporting-3.2.8 → test_reporting-3.5.0}/setup.py +1 -1
  6. {test_reporting-3.2.8 → test_reporting-3.5.0}/test_reporting.egg-info/PKG-INFO +1 -1
  7. {test_reporting-3.2.8 → test_reporting-3.5.0}/LICENSE +0 -0
  8. {test_reporting-3.2.8 → test_reporting-3.5.0}/README.md +0 -0
  9. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/__init__.py +0 -0
  10. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/classifier.py +0 -0
  11. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/cli.py +0 -0
  12. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/config.py +0 -0
  13. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/publisher.py +0 -0
  14. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/templates/index.html +0 -0
  15. {test_reporting-3.2.8 → test_reporting-3.5.0}/reporting/templates/run.html +0 -0
  16. {test_reporting-3.2.8 → test_reporting-3.5.0}/setup.cfg +0 -0
  17. {test_reporting-3.2.8 → test_reporting-3.5.0}/test_reporting.egg-info/SOURCES.txt +0 -0
  18. {test_reporting-3.2.8 → test_reporting-3.5.0}/test_reporting.egg-info/dependency_links.txt +0 -0
  19. {test_reporting-3.2.8 → test_reporting-3.5.0}/test_reporting.egg-info/entry_points.txt +0 -0
  20. {test_reporting-3.2.8 → test_reporting-3.5.0}/test_reporting.egg-info/requires.txt +0 -0
  21. {test_reporting-3.2.8 → test_reporting-3.5.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.2.8
3
+ Version: 3.5.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
@@ -23,13 +23,8 @@ class TestReportingPlugin:
23
23
  self.config = ReportingConfig()
24
24
  self.storage = TestResultStorage(config=self.config)
25
25
 
26
- # Session ID for multi-run support
27
- self.session_id = os.getenv('TEST_SESSION_ID', None)
28
- self.append_mode = os.getenv('TEST_APPEND_MODE', 'false').lower() == 'true'
29
- self.attempt_number = int(os.getenv('TEST_ATTEMPT_NUMBER', '1'))
30
-
31
- # Debug logging
32
- logging.info(f"[Reporting] Plugin initialized - Session ID: {self.session_id}, Append Mode: {self.append_mode}, Attempt: {self.attempt_number}")
26
+ # Check for run tag (e.g., 'RERUN')
27
+ self.run_tag = os.getenv('TEST_RUN_TAG', None)
33
28
 
34
29
  self.run_data = {
35
30
  'project_name': self.config.PROJECT_NAME,
@@ -37,46 +32,35 @@ class TestReportingPlugin:
37
32
  'suite_name': None,
38
33
  'build_number': os.getenv('BUILD_NUMBER', 'local'),
39
34
  'build_url': os.getenv('BUILD_URL', ''),
35
+ 'run_tag': self.run_tag, # Add tag to run data
40
36
  'tests': [],
41
37
  'total_tests': 0,
42
38
  'passed': 0,
43
39
  'failed': 0,
44
40
  'skipped': 0,
45
41
  'duration_seconds': 0,
46
- 'session_id': self.session_id,
47
- 'is_append_mode': self.append_mode,
48
42
  }
49
43
  self.run_id = None
50
44
  self.test_start_times = {}
51
45
  self.test_logs = {} # Store logs per test
52
- self.existing_results = {} # Store existing test results in append mode
53
46
 
54
47
  @pytest.hookimpl(tryfirst=True)
55
48
  def pytest_sessionstart(self, session):
56
49
  """Called at the start of the test session."""
57
50
  self.config.ensure_directories()
58
51
 
59
- # Determine suite name from the first pytest argument (the file/dir that was run)
52
+ # Determine suite name from pytest argument
60
53
  if hasattr(session.config, 'args') and session.config.args:
61
54
  first_arg = session.config.args[0]
62
- self.run_data['suite_name'] = Path(first_arg).stem
63
-
64
- # Handle append mode - find existing run or create session ID
65
- if self.append_mode and self.session_id:
66
- existing_run_id = self.storage.find_run_by_session_id(self.session_id)
67
- if existing_run_id:
68
- self.run_id = existing_run_id
69
- self.existing_results = self.storage.get_existing_test_results(self.run_id)
70
- logging.info(f"[Reporting] Appending to existing run ID: {self.run_id} (session: {self.session_id}, attempt: {self.attempt_number})")
55
+ suite_name = Path(first_arg).stem
56
+
57
+ # Add tag to suite name if present (e.g., "test_payments [RERUN]")
58
+ if self.run_tag:
59
+ self.run_data['suite_name'] = f"{suite_name} [{self.run_tag}]"
60
+ logging.info(f"[Reporting] Starting test run: {suite_name} (tagged as {self.run_tag})")
71
61
  else:
72
- logging.info(f"[Reporting] Starting new multi-run session: {self.session_id} (attempt: {self.attempt_number})")
73
- elif not self.session_id:
74
- # Auto-generate session ID if not provided
75
- self.session_id = str(uuid.uuid4())
76
- self.run_data['session_id'] = self.session_id
77
- logging.info(f"[Reporting] Starting test run: {self.run_data['suite_name']} (session: {self.session_id})")
78
- else:
79
- logging.info(f"[Reporting] Starting test run: {self.run_data['suite_name']}")
62
+ self.run_data['suite_name'] = suite_name
63
+ logging.info(f"[Reporting] Starting test run: {suite_name}")
80
64
 
81
65
  @pytest.hookimpl(hookwrapper=True)
82
66
  def pytest_runtest_protocol(self, item, nextitem):
@@ -214,10 +198,6 @@ class TestReportingPlugin:
214
198
  @pytest.hookimpl(trylast=True)
215
199
  def pytest_sessionfinish(self, session, exitstatus):
216
200
  """Called at the end of the test session."""
217
- # In append mode, merge with existing results
218
- if self.append_mode and self.run_id and self.existing_results:
219
- self._merge_with_existing_results()
220
-
221
201
  # Calculate final metrics
222
202
  total = self.run_data['total_tests']
223
203
 
@@ -246,22 +226,11 @@ class TestReportingPlugin:
246
226
  self.run_data['pass_rate'] = 0
247
227
  self.run_data['functional_pass_rate'] = 0
248
228
 
249
- # Save to database
250
- if self.append_mode and self.run_id:
251
- # Update existing run
252
- self.storage.update_run_metrics(self.run_id, self.run_data)
253
- else:
254
- # Create new run
255
- self.run_id = self.storage.save_test_run(self.run_data)
229
+ # Save to database - always create a new run
230
+ self.run_id = self.storage.save_test_run(self.run_data)
256
231
 
257
- # Save individual test results (only new ones in append mode)
232
+ # Save individual test results
258
233
  for test_result in self.run_data['tests']:
259
- # Skip if this test already exists and hasn't changed
260
- if self.append_mode and test_result['full_name'] in self.existing_results:
261
- existing = self.existing_results[test_result['full_name']]
262
- # Only save if it's a new attempt
263
- if test_result.get('attempt_number', 1) <= existing.get('attempt_number', 1):
264
- continue
265
234
 
266
235
  result_id = self.storage.save_test_result(self.run_id, test_result)
267
236
 
@@ -271,8 +240,8 @@ class TestReportingPlugin:
271
240
  self._save_latest_json()
272
241
 
273
242
  logging.info(f"[Reporting] Test run complete. Run ID: {self.run_id}")
274
- if self.append_mode:
275
- logging.info(f"[Reporting] Append mode: attempt {self.attempt_number}")
243
+ if self.run_tag:
244
+ logging.info(f"[Reporting] Run tagged as: {self.run_tag}")
276
245
  logging.info(f"[Reporting] Pass Rate: {self.run_data['pass_rate']:.1f}%")
277
246
  logging.info(f"[Reporting] Functional Pass Rate: {self.run_data['functional_pass_rate']:.1f}%")
278
247
 
@@ -285,41 +254,6 @@ class TestReportingPlugin:
285
254
  except Exception as e:
286
255
  logging.warning(f"[Reporting] Auto-publish failed: {e}")
287
256
 
288
- def _merge_with_existing_results(self):
289
- """Merge current test results with existing results from previous attempts."""
290
- # Create a map of current test results by full_name
291
- current_tests = {t['full_name']: t for t in self.run_data['tests']}
292
-
293
- # Add existing tests that weren't run in this attempt
294
- for full_name, existing_test in self.existing_results.items():
295
- if full_name not in current_tests:
296
- # Convert DB result to test result format
297
- test_result = {
298
- 'project_name': existing_test['project_name'],
299
- 'file_name': existing_test['file_name'],
300
- 'test_name': existing_test['test_name'],
301
- 'full_name': existing_test['full_name'],
302
- 'status': existing_test['status'],
303
- 'duration_seconds': existing_test['duration_seconds'],
304
- 'error_message': existing_test.get('error_message'),
305
- 'error_type': existing_test.get('error_type'),
306
- 'screenshot_path': existing_test.get('screenshot_path'),
307
- 'trace_path': existing_test.get('trace_path'),
308
- 'logs': existing_test.get('logs'),
309
- 'attempt_number': existing_test.get('attempt_number', 1),
310
- 'is_retry': existing_test.get('is_retry', False),
311
- 'failure_type': existing_test.get('failure_type'),
312
- 'failure_category': existing_test.get('failure_category'),
313
- 'is_soft_fail': existing_test.get('is_soft_fail', False),
314
- }
315
- self.run_data['tests'].append(test_result)
316
-
317
- # Recalculate totals
318
- self.run_data['total_tests'] = len(self.run_data['tests'])
319
- self.run_data['passed'] = sum(1 for t in self.run_data['tests'] if t['status'] == 'passed')
320
- self.run_data['failed'] = sum(1 for t in self.run_data['tests'] if t['status'] == 'failed')
321
- self.run_data['skipped'] = sum(1 for t in self.run_data['tests'] if t['status'] == 'skipped')
322
-
323
257
  def _save_latest_json(self):
324
258
  """Save latest run data as JSON for dashboard."""
325
259
  output = {
@@ -39,6 +39,7 @@ class TestResultStorage:
39
39
  suite_name TEXT,
40
40
  build_number TEXT,
41
41
  build_url TEXT,
42
+ run_tag TEXT,
42
43
  total_tests INTEGER,
43
44
  passed INTEGER,
44
45
  failed INTEGER,
@@ -46,8 +47,6 @@ class TestResultStorage:
46
47
  duration_seconds REAL,
47
48
  pass_rate REAL,
48
49
  functional_pass_rate REAL,
49
- session_id TEXT,
50
- is_append_mode BOOLEAN DEFAULT 0,
51
50
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
52
51
  )
53
52
  ''')
@@ -160,17 +159,17 @@ class TestResultStorage:
160
159
  with sqlite3.connect(self.db_path) as conn:
161
160
  cursor = conn.execute('''
162
161
  INSERT INTO test_runs (
163
- project_name, timestamp, suite_name, build_number, build_url,
162
+ project_name, timestamp, suite_name, build_number, build_url, run_tag,
164
163
  total_tests, passed, failed, skipped,
165
- duration_seconds, pass_rate, functional_pass_rate,
166
- session_id, is_append_mode
167
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
164
+ duration_seconds, pass_rate, functional_pass_rate
165
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
168
166
  ''', (
169
167
  run_data.get('project_name', self.config.PROJECT_NAME),
170
168
  run_data.get('timestamp', datetime.now().isoformat()),
171
169
  run_data.get('suite_name'),
172
170
  run_data.get('build_number'),
173
171
  run_data.get('build_url'),
172
+ run_data.get('run_tag'),
174
173
  run_data.get('total_tests', 0),
175
174
  run_data.get('passed', 0),
176
175
  run_data.get('failed', 0),
@@ -178,8 +177,6 @@ class TestResultStorage:
178
177
  run_data.get('duration_seconds', 0),
179
178
  run_data.get('pass_rate', 0),
180
179
  run_data.get('functional_pass_rate', 0),
181
- run_data.get('session_id'),
182
- run_data.get('is_append_mode', False),
183
180
  ))
184
181
  conn.commit()
185
182
  return cursor.lastrowid
@@ -380,6 +380,11 @@
380
380
  return new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
381
381
  }
382
382
 
383
+ function fmtTime(iso) {
384
+ if (!iso) return '-';
385
+ return new Date(iso).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false });
386
+ }
387
+
383
388
  function fmtDur(s) {
384
389
  if (!s || s === 0) return '-';
385
390
  if (s >= 60) return Math.floor(s / 60) + 'm ' + Math.floor(s % 60) + 's';
@@ -919,6 +924,7 @@
919
924
  sparklineIds[sparkId] = fileTrends[run.file_name] || [];
920
925
  return '<tr>' +
921
926
  '<td style="font-size:12px">' + fmtDate(run.timestamp) + '</td>' +
927
+ '<td style="font-size:12px;color:var(--muted)">' + fmtTime(run.timestamp) + '</td>' +
922
928
  '<td><div style="font-weight:600;font-size:13px;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + run.file_name + '">' + run.file_name + '</div></td>' +
923
929
  '<td style="font-size:12px">' + (run.regression_tag || '-') + '</td>' +
924
930
  '<td><span class="pill ' + pc + '">' + run.pass_rate.toFixed(0) + '%</span></td>' +
@@ -960,6 +966,7 @@
960
966
  '<table class="hist-table">' +
961
967
  '<thead><tr>' +
962
968
  '<th style="width:90px">Date</th>' +
969
+ '<th style="width:60px">Time</th>' +
963
970
  '<th>Test File</th>' +
964
971
  '<th style="width:100px">Tag</th>' +
965
972
  '<th style="width:80px">Pass Rate</th>' +
@@ -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.2.8',
12
+ version='3.5.0',
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.2.8
3
+ Version: 3.5.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
File without changes
File without changes
File without changes