nextog-cli 1.0.0__py3-none-any.whl

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 (51) hide show
  1. nextog/__init__.py +4 -0
  2. nextog/cli.py +545 -0
  3. nextog/config/__init__.py +1 -0
  4. nextog/config/settings.py +132 -0
  5. nextog/core/__init__.py +1 -0
  6. nextog/core/engine.py +193 -0
  7. nextog/core/permissions.py +129 -0
  8. nextog/core/privacy.py +130 -0
  9. nextog/core/reporter.py +204 -0
  10. nextog/core/runner.py +236 -0
  11. nextog/data/__init__.py +1 -0
  12. nextog/data/local_db.py +367 -0
  13. nextog/data/models.py +72 -0
  14. nextog/data/sync.py +65 -0
  15. nextog/engines/__init__.py +1 -0
  16. nextog/engines/api/__init__.py +1 -0
  17. nextog/engines/api/graphql.py +54 -0
  18. nextog/engines/api/rest.py +346 -0
  19. nextog/engines/api/websocket.py +59 -0
  20. nextog/engines/embedded/__init__.py +1 -0
  21. nextog/engines/embedded/firmware.py +53 -0
  22. nextog/engines/embedded/hardware.py +330 -0
  23. nextog/engines/mobile/__init__.py +1 -0
  24. nextog/engines/mobile/android.py +333 -0
  25. nextog/engines/mobile/cross.py +48 -0
  26. nextog/engines/mobile/ios.py +46 -0
  27. nextog/engines/system/__init__.py +1 -0
  28. nextog/engines/system/load.py +121 -0
  29. nextog/engines/system/performance.py +128 -0
  30. nextog/engines/system/security.py +170 -0
  31. nextog/engines/web/__init__.py +1 -0
  32. nextog/engines/web/accessibility.py +191 -0
  33. nextog/engines/web/browser.py +387 -0
  34. nextog/engines/web/elements.py +285 -0
  35. nextog/engines/web/responsive.py +79 -0
  36. nextog/live/__init__.py +1 -0
  37. nextog/live/dashboard.py +30 -0
  38. nextog/live/panel.py +325 -0
  39. nextog/reports/__init__.py +1359 -0
  40. nextog/training/__init__.py +1 -0
  41. nextog/training/learner.py +269 -0
  42. nextog/training/patterns.py +102 -0
  43. nextog/utils/__init__.py +1 -0
  44. nextog/utils/helpers.py +91 -0
  45. nextog/utils/logger.py +37 -0
  46. nextog/utils/validators.py +98 -0
  47. nextog_cli-1.0.0.dist-info/METADATA +344 -0
  48. nextog_cli-1.0.0.dist-info/RECORD +51 -0
  49. nextog_cli-1.0.0.dist-info/WHEEL +5 -0
  50. nextog_cli-1.0.0.dist-info/entry_points.txt +2 -0
  51. nextog_cli-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1359 @@
1
+ """
2
+ Industrial Excel Report Generator for nextOG CLI
3
+ Auto-generates professional Excel reports after ANY test run.
4
+ All reports are built automatically after testing completes.
5
+ """
6
+
7
+ import os
8
+ import json
9
+ from typing import Dict, List, Optional, Any
10
+ from pathlib import Path
11
+ from datetime import datetime
12
+ from rich.console import Console
13
+
14
+ console = Console()
15
+
16
+
17
+ class IndustrialReportGenerator:
18
+ """
19
+ Generates industrial-grade Excel reports after testing.
20
+ Supports: Web, Mobile, API, Embedded, System tests.
21
+ All data stays local - privacy first.
22
+ """
23
+
24
+ def __init__(self, db, settings, privacy):
25
+ self.db = db
26
+ self.settings = settings
27
+ self.privacy = privacy
28
+ self.output_dir = Path.home() / ".nextog" / "reports"
29
+ self.output_dir.mkdir(parents=True, exist_ok=True)
30
+
31
+ def generate_after_test(self, test_results: Dict, test_type: str = "auto") -> str:
32
+ """
33
+ Auto-generate Excel report after any test run.
34
+ Called automatically by all test engines.
35
+
36
+ Args:
37
+ test_results: Results from any test engine
38
+ test_type: Type of test (web, mobile, api, embedded, system, auto)
39
+
40
+ Returns:
41
+ Path to generated Excel file
42
+ """
43
+ try:
44
+ import openpyxl
45
+ from openpyxl.styles import (
46
+ Font, PatternFill, Alignment, Border, Side,
47
+ numbers, NamedStyle
48
+ )
49
+ from openpyxl.chart import BarChart, PieChart, Reference, LineChart
50
+ from openpyxl.chart.label import DataLabelList
51
+ from openpyxl.chart.series import DataPoint
52
+ from openpyxl.utils import get_column_letter
53
+ from openpyxl.formatting.rule import CellIsRule, DataBarRule
54
+ from openpyxl.worksheet.table import Table, TableStyleInfo
55
+ except ImportError:
56
+ console.print("[yellow]⚠ Installing openpyxl for Excel reports...[/yellow]")
57
+ os.system("pip install openpyxl")
58
+ import openpyxl
59
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
60
+ from openpyxl.chart import BarChart, PieChart, Reference
61
+ from openpyxl.chart.label import DataLabelList
62
+ from openpyxl.utils import get_column_letter
63
+
64
+ # Create workbook
65
+ wb = openpyxl.Workbook()
66
+
67
+ # Detect test type if auto
68
+ if test_type == "auto":
69
+ test_type = self._detect_test_type(test_results)
70
+
71
+ # Generate timestamp
72
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
73
+
74
+ # ─── SHEET 1: Executive Summary ───────────────────────────
75
+ ws_summary = wb.active
76
+ ws_summary.title = "Executive Summary"
77
+ self._create_executive_summary(ws_summary, test_results, test_type, timestamp)
78
+
79
+ # ─── SHEET 2: Test Details ────────────────────────────────
80
+ ws_details = wb.create_sheet("Test Details")
81
+ self._create_test_details(ws_details, test_results, test_type)
82
+
83
+ # ─── SHEET 3: Coverage Analysis ───────────────────────────
84
+ ws_coverage = wb.create_sheet("Coverage Analysis")
85
+ self._create_coverage_analysis(ws_coverage, test_results, test_type)
86
+
87
+ # ─── SHEET 4: Element Report ──────────────────────────────
88
+ ws_elements = wb.create_sheet("Elements & Components")
89
+ self._create_element_report(ws_elements, test_results, test_type)
90
+
91
+ # ─── SHEET 5: Defect Log ──────────────────────────────────
92
+ ws_defects = wb.create_sheet("Defect Log")
93
+ self._create_defect_log(ws_defects, test_results, test_type)
94
+
95
+ # ─── SHEET 6: Performance Metrics ─────────────────────────
96
+ ws_perf = wb.create_sheet("Performance Metrics")
97
+ self._create_performance_metrics(ws_perf, test_results, test_type)
98
+
99
+ # ─── SHEET 7: Charts & Graphs ─────────────────────────────
100
+ ws_charts = wb.create_sheet("Charts & Graphs")
101
+ self._create_charts(ws_charts, test_results, test_type)
102
+
103
+ # ─── SHEET 8: Recommendations ─────────────────────────────
104
+ ws_rec = wb.create_sheet("Recommendations")
105
+ self._create_recommendations(ws_rec, test_results, test_type)
106
+
107
+ # ─── SHEET 9: Test Environment ────────────────────────────
108
+ ws_env = wb.create_sheet("Test Environment")
109
+ self._create_test_environment(ws_env, test_type)
110
+
111
+ # ─── SHEET 10: Sign-off Sheet ─────────────────────────────
112
+ ws_sign = wb.create_sheet("Sign-Off")
113
+ self._create_signoff_sheet(ws_sign, test_results, test_type, timestamp)
114
+
115
+ # Auto-fit columns for all sheets
116
+ for ws in wb.worksheets:
117
+ self._auto_fit_columns(ws)
118
+
119
+ # Save workbook
120
+ filename = f"nextOG_{test_type}_Report_{timestamp}.xlsx"
121
+ filepath = self.output_dir / filename
122
+ wb.save(str(filepath))
123
+
124
+ # Record in privacy engine (local only)
125
+ self.privacy.record_activity("report_generated", {
126
+ "file": str(filepath),
127
+ "type": test_type,
128
+ "timestamp": timestamp,
129
+ })
130
+
131
+ # Save to DB
132
+ self.db.save_metadata(f"last_report_{test_type}", str(filepath))
133
+
134
+ return str(filepath)
135
+
136
+ # ═══════════════════════════════════════════════════════════════
137
+ # SHEET 1: Executive Summary
138
+ # ═══════════════════════════════════════════════════════════════
139
+
140
+ def _create_executive_summary(self, ws, results: Dict, test_type: str, timestamp: str):
141
+ """Create executive summary sheet"""
142
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
143
+ from openpyxl.utils import get_column_letter
144
+
145
+ # Styles
146
+ title_font = Font(name="Calibri", size=20, bold=True, color="FFFFFF")
147
+ title_fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
148
+ header_font = Font(name="Calibri", size=12, bold=True, color="FFFFFF")
149
+ header_fill = PatternFill(start_color="2E86C1", end_color="2E86C1", fill_type="solid")
150
+ label_font = Font(name="Calibri", size=11, bold=True)
151
+ value_font = Font(name="Calibri", size=11)
152
+ green_fill = PatternFill(start_color="27AE60", end_color="27AE60", fill_type="solid")
153
+ red_fill = PatternFill(start_color="E74C3C", end_color="E74C3C", fill_type="solid")
154
+ yellow_fill = PatternFill(start_color="F39C12", end_color="F39C12", fill_type="solid")
155
+ white_font = Font(name="Calibri", size=14, bold=True, color="FFFFFF")
156
+ thin_border = Border(
157
+ left=Side(style='thin'), right=Side(style='thin'),
158
+ top=Side(style='thin'), bottom=Side(style='thin')
159
+ )
160
+
161
+ # Title Row
162
+ ws.merge_cells("A1:H1")
163
+ ws["A1"] = f"🚀 nextOG CLI — {test_type.upper()} Test Report"
164
+ ws["A1"].font = title_font
165
+ ws["A1"].fill = title_fill
166
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
167
+ ws.row_dimensions[1].height = 40
168
+
169
+ # Subtitle
170
+ ws.merge_cells("A2:H2")
171
+ ws["A2"] = f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | Privacy: LOCAL ONLY | Tool: nextOG CLI v1.0"
172
+ ws["A2"].font = Font(name="Calibri", size=9, italic=True, color="666666")
173
+ ws["A2"].alignment = Alignment(horizontal="center")
174
+ ws.row_dimensions[2].height = 20
175
+
176
+ # ─── Key Metrics Box ─────────────────────────────────────
177
+ ws.merge_cells("A4:B4")
178
+ ws["A4"] = "KEY METRICS"
179
+ ws["A4"].font = header_font
180
+ ws["A4"].fill = header_fill
181
+ ws["B4"].fill = header_fill
182
+
183
+ total = results.get("total", 0)
184
+ passed = results.get("passed", 0)
185
+ failed = results.get("failed", 0)
186
+ skipped = results.get("skipped", 0)
187
+ coverage = results.get("coverage", 0)
188
+ duration = results.get("duration", 0)
189
+
190
+ metrics = [
191
+ ("Total Tests", total),
192
+ ("Passed ✅", passed),
193
+ ("Failed ❌", failed),
194
+ ("Skipped ⚠️", skipped),
195
+ ("Coverage", f"{coverage}%"),
196
+ ("Duration", f"{duration:.1f}s" if duration else "N/A"),
197
+ ("Pass Rate", f"{(passed/total*100):.1f}%" if total > 0 else "0%"),
198
+ ("Test Type", test_type.upper()),
199
+ ]
200
+
201
+ for i, (label, value) in enumerate(metrics, start=5):
202
+ ws[f"A{i}"] = label
203
+ ws[f"A{i}"].font = label_font
204
+ ws[f"A{i}"].border = thin_border
205
+
206
+ ws[f"B{i}"] = value
207
+ ws[f"B{i}"].font = value_font
208
+ ws[f"B{i}"].border = thin_border
209
+ ws[f"B{i}"].alignment = Alignment(horizontal="center")
210
+
211
+ # Color coding
212
+ if "Passed" in label:
213
+ ws[f"B{i}"].fill = PatternFill(start_color="D5F5E3", end_color="D5F5E3", fill_type="solid")
214
+ elif "Failed" in label:
215
+ ws[f"B{i}"].fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
216
+ elif "Coverage" in label:
217
+ if coverage >= 80:
218
+ ws[f"B{i}"].fill = PatternFill(start_color="D5F5E3", end_color="D5F5E3", fill_type="solid")
219
+ elif coverage >= 60:
220
+ ws[f"B{i}"].fill = PatternFill(start_color="FEF9E7", end_color="FEF9E7", fill_type="solid")
221
+ else:
222
+ ws[f"B{i}"].fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
223
+
224
+ # ─── Status Summary ──────────────────────────────────────
225
+ ws.merge_cells("D4:E4")
226
+ ws["D4"] = "OVERALL STATUS"
227
+ ws["D4"].font = header_font
228
+ ws["D4"].fill = header_fill
229
+ ws["E4"].fill = header_fill
230
+
231
+ if coverage >= 80 and failed == 0:
232
+ status = "✅ PASS — Release Ready"
233
+ status_fill = green_fill
234
+ elif coverage >= 60:
235
+ status = "⚠️ CONDITIONAL — Review Required"
236
+ status_fill = yellow_fill
237
+ else:
238
+ status = "❌ FAIL — Critical Issues Found"
239
+ status_fill = red_fill
240
+
241
+ ws.merge_cells("D5:E5")
242
+ ws["D5"] = status
243
+ ws["D5"].font = white_font
244
+ ws["D5"].fill = status_fill
245
+ ws["D5"].alignment = Alignment(horizontal="center", vertical="center")
246
+ ws["E5"].fill = status_fill
247
+ ws.row_dimensions[5].height = 35
248
+
249
+ # Risk Level
250
+ ws["D6"] = "Risk Level:"
251
+ ws["D6"].font = label_font
252
+ if failed > 5:
253
+ risk = "🔴 HIGH"
254
+ elif failed > 0:
255
+ risk = "🟡 MEDIUM"
256
+ else:
257
+ risk = "🟢 LOW"
258
+ ws["E6"] = risk
259
+ ws["E6"].font = value_font
260
+
261
+ # Recommendation
262
+ ws["D7"] = "Recommendation:"
263
+ ws["D7"].font = label_font
264
+ if coverage >= 80 and failed == 0:
265
+ rec = "Approve for production release"
266
+ elif coverage >= 60:
267
+ rec = "Fix critical defects before release"
268
+ else:
269
+ rec = "Major rework required - do not release"
270
+ ws.merge_cells("E7:F7")
271
+ ws["E7"] = rec
272
+ ws["E7"].font = value_font
273
+
274
+ # ─── Test Breakdown by Category ──────────────────────────
275
+ ws.merge_cells("A14:H14")
276
+ ws["A14"] = "TEST BREAKDOWN BY CATEGORY"
277
+ ws["A14"].font = header_font
278
+ ws["A14"].fill = header_fill
279
+ for col in range(1, 9):
280
+ ws.cell(row=14, column=col).fill = header_fill
281
+
282
+ headers = ["Category", "Total", "Passed", "Failed", "Skipped", "Pass Rate", "Coverage", "Status"]
283
+ for col, header in enumerate(headers, 1):
284
+ cell = ws.cell(row=15, column=col)
285
+ cell.value = header
286
+ cell.font = Font(bold=True, color="FFFFFF")
287
+ cell.fill = PatternFill(start_color="34495E", end_color="34495E", fill_type="solid")
288
+ cell.border = thin_border
289
+ cell.alignment = Alignment(horizontal="center")
290
+
291
+ categories = results.get("categories", {})
292
+ row = 16
293
+ for cat_name, cat_data in categories.items():
294
+ if isinstance(cat_data, dict):
295
+ cat_total = cat_data.get("total", 0)
296
+ cat_passed = cat_data.get("passed", 0)
297
+ cat_failed = cat_data.get("failed", 0)
298
+ cat_skipped = cat_data.get("skipped", 0)
299
+ cat_pass_rate = f"{(cat_passed/cat_total*100):.1f}%" if cat_total > 0 else "0%"
300
+ cat_coverage = f"{cat_data.get('coverage', 0)}%"
301
+
302
+ ws.cell(row=row, column=1, value=cat_name.replace("_", " ").title()).border = thin_border
303
+ ws.cell(row=row, column=2, value=cat_total).border = thin_border
304
+ ws.cell(row=row, column=2).alignment = Alignment(horizontal="center")
305
+ ws.cell(row=row, column=3, value=cat_passed).border = thin_border
306
+ ws.cell(row=row, column=3).fill = PatternFill(start_color="D5F5E3", end_color="D5F5E3", fill_type="solid")
307
+ ws.cell(row=row, column=3).alignment = Alignment(horizontal="center")
308
+ ws.cell(row=row, column=4, value=cat_failed).border = thin_border
309
+ if cat_failed > 0:
310
+ ws.cell(row=row, column=4).fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
311
+ ws.cell(row=row, column=4).alignment = Alignment(horizontal="center")
312
+ ws.cell(row=row, column=5, value=cat_skipped).border = thin_border
313
+ ws.cell(row=row, column=5).alignment = Alignment(horizontal="center")
314
+ ws.cell(row=row, column=6, value=cat_pass_rate).border = thin_border
315
+ ws.cell(row=row, column=6).alignment = Alignment(horizontal="center")
316
+ ws.cell(row=row, column=7, value=cat_coverage).border = thin_border
317
+ ws.cell(row=row, column=7).alignment = Alignment(horizontal="center")
318
+
319
+ status = "✅ Pass" if cat_failed == 0 else "❌ Fail"
320
+ ws.cell(row=row, column=8, value=status).border = thin_border
321
+ ws.cell(row=row, column=8).alignment = Alignment(horizontal="center")
322
+
323
+ row += 1
324
+
325
+ # ─── Target vs Actual ────────────────────────────────────
326
+ row += 2
327
+ ws.merge_cells(f"A{row}:H{row}")
328
+ ws[f"A{row}"] = "COVERAGE TARGET vs ACTUAL"
329
+ ws[f"A{row}"].font = header_font
330
+ ws[f"A{row}"].fill = header_fill
331
+ for col in range(1, 9):
332
+ ws.cell(row=row, column=col).fill = header_fill
333
+
334
+ row += 1
335
+ target_headers = ["Phase", "Target Min", "Target Max", "Actual", "Status", "Gap"]
336
+ for col, h in enumerate(target_headers, 1):
337
+ cell = ws.cell(row=row, column=col)
338
+ cell.value = h
339
+ cell.font = Font(bold=True, color="FFFFFF")
340
+ cell.fill = PatternFill(start_color="34495E", end_color="34495E", fill_type="solid")
341
+ cell.border = thin_border
342
+ cell.alignment = Alignment(horizontal="center")
343
+
344
+ phases = [
345
+ ("Smoke Tests", 0, 20),
346
+ ("Functional Tests", 20, 40),
347
+ ("Integration Tests", 40, 60),
348
+ ("Performance & Security", 60, 80),
349
+ ("Advanced & Regression", 80, 90),
350
+ ]
351
+
352
+ row += 1
353
+ phase_keys = ["smoke", "functional", "integration", "performance", "advanced"]
354
+ for i, (phase_name, target_min, target_max) in enumerate(phases):
355
+ phase_data = categories.get(phase_keys[i], {}) if i < len(phase_keys) else {}
356
+ actual = phase_data.get("coverage", 0) if isinstance(phase_data, dict) else 0
357
+
358
+ ws.cell(row=row, column=1, value=phase_name).border = thin_border
359
+ ws.cell(row=row, column=2, value=f"{target_min}%").border = thin_border
360
+ ws.cell(row=row, column=2).alignment = Alignment(horizontal="center")
361
+ ws.cell(row=row, column=3, value=f"{target_max}%").border = thin_border
362
+ ws.cell(row=row, column=3).alignment = Alignment(horizontal="center")
363
+ ws.cell(row=row, column=4, value=f"{actual}%").border = thin_border
364
+ ws.cell(row=row, column=4).alignment = Alignment(horizontal="center")
365
+
366
+ if actual >= target_max:
367
+ status = "✅ Achieved"
368
+ gap = "—"
369
+ elif actual >= target_min:
370
+ status = "🔄 In Progress"
371
+ gap = f"+{target_max - actual}% needed"
372
+ else:
373
+ status = "⬜ Pending"
374
+ gap = f"+{target_min - actual}% needed"
375
+
376
+ ws.cell(row=row, column=5, value=status).border = thin_border
377
+ ws.cell(row=row, column=5).alignment = Alignment(horizontal="center")
378
+ ws.cell(row=row, column=6, value=gap).border = thin_border
379
+ ws.cell(row=row, column=6).alignment = Alignment(horizontal="center")
380
+
381
+ row += 1
382
+
383
+ # ═══════════════════════════════════════════════════════════════
384
+ # SHEET 2: Test Details
385
+ # ═══════════════════════════════════════════════════════════════
386
+
387
+ def _create_test_details(self, ws, results: Dict, test_type: str):
388
+ """Create detailed test results sheet"""
389
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
390
+
391
+ thin_border = Border(
392
+ left=Side(style='thin'), right=Side(style='thin'),
393
+ top=Side(style='thin'), bottom=Side(style='thin')
394
+ )
395
+ header_fill = PatternFill(start_color="2E86C1", end_color="2E86C1", fill_type="solid")
396
+ header_font = Font(name="Calibri", size=11, bold=True, color="FFFFFF")
397
+
398
+ # Title
399
+ ws.merge_cells("A1:J1")
400
+ ws["A1"] = f"DETAILED TEST RESULTS — {test_type.upper()}"
401
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
402
+ ws["A1"].fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
403
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
404
+ ws.row_dimensions[1].height = 35
405
+
406
+ # Headers
407
+ headers = [
408
+ "#", "Test ID", "Test Name", "Category", "Type",
409
+ "Element/Endpoint", "Status", "Duration (ms)",
410
+ "Expected", "Actual", "Notes"
411
+ ]
412
+
413
+ for col, header in enumerate(headers, 1):
414
+ cell = ws.cell(row=3, column=col)
415
+ cell.value = header
416
+ cell.font = header_font
417
+ cell.fill = header_fill
418
+ cell.border = thin_border
419
+ cell.alignment = Alignment(horizontal="center", wrap_text=True)
420
+
421
+ # Data rows
422
+ details = results.get("details", [])
423
+ categories = results.get("categories", {})
424
+
425
+ row = 4
426
+ test_num = 1
427
+
428
+ # If we have detailed test cases
429
+ if details:
430
+ for detail in details:
431
+ if isinstance(detail, dict):
432
+ ws.cell(row=row, column=1, value=test_num).border = thin_border
433
+ ws.cell(row=row, column=1).alignment = Alignment(horizontal="center")
434
+ ws.cell(row=row, column=2, value=detail.get("id", f"TC-{test_num:04d}")).border = thin_border
435
+ ws.cell(row=row, column=3, value=detail.get("name", "Unnamed Test")).border = thin_border
436
+ ws.cell(row=row, column=4, value=detail.get("category", test_type)).border = thin_border
437
+ ws.cell(row=row, column=5, value=detail.get("type", "functional")).border = thin_border
438
+ ws.cell(row=row, column=6, value=detail.get("element", detail.get("endpoint", "N/A"))).border = thin_border
439
+
440
+ status = detail.get("status", "unknown")
441
+ ws.cell(row=row, column=7, value=status.upper()).border = thin_border
442
+ ws.cell(row=row, column=7).alignment = Alignment(horizontal="center")
443
+
444
+ if status == "passed":
445
+ ws.cell(row=row, column=7).fill = PatternFill(start_color="D5F5E3", end_color="D5F5E3", fill_type="solid")
446
+ elif status == "failed":
447
+ ws.cell(row=row, column=7).fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
448
+ elif status == "skipped":
449
+ ws.cell(row=row, column=7).fill = PatternFill(start_color="FEF9E7", end_color="FEF9E7", fill_type="solid")
450
+
451
+ ws.cell(row=row, column=8, value=detail.get("duration", 0)).border = thin_border
452
+ ws.cell(row=row, column=9, value=detail.get("expected", "")).border = thin_border
453
+ ws.cell(row=row, column=10, value=detail.get("actual", detail.get("error", ""))).border = thin_border
454
+ ws.cell(row=row, column=11, value=detail.get("notes", "")).border = thin_border
455
+
456
+ test_num += 1
457
+ row += 1
458
+ else:
459
+ # Generate from category data
460
+ for cat_name, cat_data in categories.items():
461
+ if isinstance(cat_data, dict):
462
+ for i in range(cat_data.get("passed", 0)):
463
+ ws.cell(row=row, column=1, value=test_num).border = thin_border
464
+ ws.cell(row=row, column=2, value=f"TC-{test_num:04d}").border = thin_border
465
+ ws.cell(row=row, column=3, value=f"{cat_name} test {i+1}").border = thin_border
466
+ ws.cell(row=row, column=4, value=cat_name).border = thin_border
467
+ ws.cell(row=row, column=7, value="PASSED").border = thin_border
468
+ ws.cell(row=row, column=7).fill = PatternFill(start_color="D5F5E3", end_color="D5F5E3", fill_type="solid")
469
+ ws.cell(row=row, column=7).alignment = Alignment(horizontal="center")
470
+ test_num += 1
471
+ row += 1
472
+
473
+ for i in range(cat_data.get("failed", 0)):
474
+ ws.cell(row=row, column=1, value=test_num).border = thin_border
475
+ ws.cell(row=row, column=2, value=f"TC-{test_num:04d}").border = thin_border
476
+ ws.cell(row=row, column=3, value=f"{cat_name} test (failed) {i+1}").border = thin_border
477
+ ws.cell(row=row, column=4, value=cat_name).border = thin_border
478
+ ws.cell(row=row, column=7, value="FAILED").border = thin_border
479
+ ws.cell(row=row, column=7).fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
480
+ ws.cell(row=row, column=7).alignment = Alignment(horizontal="center")
481
+ test_num += 1
482
+ row += 1
483
+
484
+ # Summary at bottom
485
+ row += 2
486
+ ws.merge_cells(f"A{row}:C{row}")
487
+ ws[f"A{row}"] = "SUMMARY"
488
+ ws[f"A{row}"].font = Font(bold=True, size=12)
489
+ row += 1
490
+ ws[f"A{row}"] = "Total Test Cases:"
491
+ ws[f"B{row}"] = results.get("total", 0)
492
+ ws[f"B{row}"].font = Font(bold=True)
493
+ row += 1
494
+ ws[f"A{row}"] = "Pass Rate:"
495
+ total = results.get("total", 1)
496
+ ws[f"B{row}"] = f"{(results.get('passed', 0) / total * 100):.1f}%"
497
+ row += 1
498
+ ws[f"A{row}"] = "Overall Coverage:"
499
+ ws[f"B{row}"] = f"{results.get('coverage', 0)}%"
500
+
501
+ # ═══════════════════════════════════════════════════════════════
502
+ # SHEET 3: Coverage Analysis
503
+ # ═══════════════════════════════════════════════════════════════
504
+
505
+ def _create_coverage_analysis(self, ws, results: Dict, test_type: str):
506
+ """Create coverage analysis sheet"""
507
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
508
+
509
+ thin_border = Border(
510
+ left=Side(style='thin'), right=Side(style='thin'),
511
+ top=Side(style='thin'), bottom=Side(style='thin')
512
+ )
513
+ header_fill = PatternFill(start_color="2E86C1", end_color="2E86C1", fill_type="solid")
514
+ header_font = Font(name="Calibri", size=11, bold=True, color="FFFFFF")
515
+
516
+ # Title
517
+ ws.merge_cells("A1:G1")
518
+ ws["A1"] = "COVERAGE ANALYSIS — 0% → 90% PROGRESS TRACKER"
519
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
520
+ ws["A1"].fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
521
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
522
+ ws.row_dimensions[1].height = 35
523
+
524
+ # Overall Coverage
525
+ ws.merge_cells("A3:B3")
526
+ ws["A3"] = "Overall Coverage:"
527
+ ws["A3"].font = Font(bold=True, size=14)
528
+ coverage = results.get("coverage", 0)
529
+ ws["C3"] = f"{coverage}%"
530
+ ws["C3"].font = Font(bold=True, size=14,
531
+ color="27AE60" if coverage >= 80 else "F39C12" if coverage >= 60 else "E74C3C")
532
+
533
+ # Phase coverage table
534
+ ws.merge_cells("A5:G5")
535
+ ws["A5"] = "PHASE-WISE COVERAGE BREAKDOWN"
536
+ ws["A5"].font = header_font
537
+ ws["A5"].fill = header_fill
538
+ for col in range(1, 8):
539
+ ws.cell(row=5, column=col).fill = header_fill
540
+
541
+ headers = ["Phase", "Target Range", "Actual Coverage", "Status", "Tests Run", "Tests Passed", "Gap to Next"]
542
+ for col, h in enumerate(headers, 1):
543
+ cell = ws.cell(row=6, column=col)
544
+ cell.value = h
545
+ cell.font = Font(bold=True, color="FFFFFF")
546
+ cell.fill = PatternFill(start_color="34495E", end_color="34495E", fill_type="solid")
547
+ cell.border = thin_border
548
+ cell.alignment = Alignment(horizontal="center")
549
+
550
+ phases_data = [
551
+ ("Phase 1: Smoke Tests", "0% - 20%", "smoke"),
552
+ ("Phase 2: Functional Tests", "20% - 40%", "functional"),
553
+ ("Phase 3: Integration Tests", "40% - 60%", "integration"),
554
+ ("Phase 4: Performance & Security", "60% - 80%", "performance"),
555
+ ("Phase 5: Advanced & Regression", "80% - 90%", "advanced"),
556
+ ]
557
+
558
+ categories = results.get("categories", {})
559
+ row = 7
560
+
561
+ for phase_name, target_range, phase_key in phases_data:
562
+ cat_data = categories.get(phase_key, {})
563
+ actual = cat_data.get("coverage", 0) if isinstance(cat_data, dict) else 0
564
+ total = cat_data.get("total", 0) if isinstance(cat_data, dict) else 0
565
+ passed = cat_data.get("passed", 0) if isinstance(cat_data, dict) else 0
566
+
567
+ ws.cell(row=row, column=1, value=phase_name).border = thin_border
568
+ ws.cell(row=row, column=2, value=target_range).border = thin_border
569
+ ws.cell(row=row, column=2).alignment = Alignment(horizontal="center")
570
+ ws.cell(row=row, column=3, value=f"{actual}%").border = thin_border
571
+ ws.cell(row=row, column=3).alignment = Alignment(horizontal="center")
572
+
573
+ # Color code actual coverage
574
+ if actual >= 80:
575
+ ws.cell(row=row, column=3).fill = PatternFill(start_color="D5F5E3", end_color="D5F5E3", fill_type="solid")
576
+ elif actual >= 40:
577
+ ws.cell(row=row, column=3).fill = PatternFill(start_color="FEF9E7", end_color="FEF9E7", fill_type="solid")
578
+ elif actual > 0:
579
+ ws.cell(row=row, column=3).fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
580
+
581
+ # Status
582
+ if actual >= 80:
583
+ status = "✅ Complete"
584
+ elif actual > 0:
585
+ status = "🔄 In Progress"
586
+ else:
587
+ status = "⬜ Not Started"
588
+ ws.cell(row=row, column=4, value=status).border = thin_border
589
+ ws.cell(row=row, column=4).alignment = Alignment(horizontal="center")
590
+
591
+ ws.cell(row=row, column=5, value=total).border = thin_border
592
+ ws.cell(row=row, column=5).alignment = Alignment(horizontal="center")
593
+ ws.cell(row=row, column=6, value=passed).border = thin_border
594
+ ws.cell(row=row, column=6).alignment = Alignment(horizontal="center")
595
+
596
+ gap = max(0, 20 - actual) if actual < 20 else max(0, 40 - actual) if actual < 40 else max(0, 60 - actual) if actual < 60 else max(0, 80 - actual) if actual < 80 else max(0, 90 - actual)
597
+ ws.cell(row=row, column=7, value=f"+{gap}%" if gap > 0 else "✅").border = thin_border
598
+ ws.cell(row=row, column=7).alignment = Alignment(horizontal="center")
599
+
600
+ row += 1
601
+
602
+ # Coverage by Element Type
603
+ row += 2
604
+ ws.merge_cells(f"A{row}:G{row}")
605
+ ws[f"A{row}"] = "COVERAGE BY ELEMENT TYPE"
606
+ ws[f"A{row}"].font = header_font
607
+ ws[f"A{row}"].fill = header_fill
608
+ for col in range(1, 8):
609
+ ws.cell(row=row, column=col).fill = header_fill
610
+
611
+ row += 1
612
+ elem_headers = ["Element Type", "Total Found", "Tested", "Coverage %", "Priority", "Status", "Notes"]
613
+ for col, h in enumerate(elem_headers, 1):
614
+ cell = ws.cell(row=row, column=col)
615
+ cell.value = h
616
+ cell.font = Font(bold=True, color="FFFFFF")
617
+ cell.fill = PatternFill(start_color="34495E", end_color="34495E", fill_type="solid")
618
+ cell.border = thin_border
619
+ cell.alignment = Alignment(horizontal="center")
620
+
621
+ row += 1
622
+ element_types = [
623
+ ("Buttons/CTAs", "critical"), ("Input Fields", "critical"),
624
+ ("Forms", "critical"), ("Navigation Links", "high"),
625
+ ("Images/Media", "medium"), ("Tables/Data", "medium"),
626
+ ("Modals/Dialogs", "high"), ("Search Components", "high"),
627
+ ("Footer Elements", "low"), ("Third-party Widgets", "medium"),
628
+ ]
629
+
630
+ for elem_name, priority in element_types:
631
+ ws.cell(row=row, column=1, value=elem_name).border = thin_border
632
+ ws.cell(row=row, column=5, value=priority).border = thin_border
633
+ ws.cell(row=row, column=5).alignment = Alignment(horizontal="center")
634
+
635
+ if priority == "critical":
636
+ ws.cell(row=row, column=5).fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
637
+ elif priority == "high":
638
+ ws.cell(row=row, column=5).fill = PatternFill(start_color="FEF9E7", end_color="FEF9E7", fill_type="solid")
639
+
640
+ row += 1
641
+
642
+ # ═══════════════════════════════════════════════════════════════
643
+ # SHEET 4: Element Report
644
+ # ═══════════════════════════════════════════════════════════════
645
+
646
+ def _create_element_report(self, ws, results: Dict, test_type: str):
647
+ """Create element/component report"""
648
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
649
+
650
+ thin_border = Border(
651
+ left=Side(style='thin'), right=Side(style='thin'),
652
+ top=Side(style='thin'), bottom=Side(style='thin')
653
+ )
654
+
655
+ ws.merge_cells("A1:I1")
656
+ ws["A1"] = "ELEMENTS & COMPONENTS DETECTION REPORT"
657
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
658
+ ws["A1"].fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
659
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
660
+ ws.row_dimensions[1].height = 35
661
+
662
+ ws.merge_cells("A2:I2")
663
+ ws["A2"] = "All system elements detected with their purpose and test coverage status"
664
+ ws["A2"].font = Font(italic=True, color="666666")
665
+ ws["A2"].alignment = Alignment(horizontal="center")
666
+
667
+ headers = [
668
+ "#", "Element Name", "Type", "Selector/ID",
669
+ "Description/Purpose", "Test Priority",
670
+ "Tests Applied", "Result", "Recommendation"
671
+ ]
672
+
673
+ header_fill = PatternFill(start_color="2E86C1", end_color="2E86C1", fill_type="solid")
674
+ for col, h in enumerate(headers, 1):
675
+ cell = ws.cell(row=4, column=col)
676
+ cell.value = h
677
+ cell.font = Font(bold=True, color="FFFFFF")
678
+ cell.fill = header_fill
679
+ cell.border = thin_border
680
+ cell.alignment = Alignment(horizontal="center", wrap_text=True)
681
+
682
+ # Element data based on test type
683
+ elements = results.get("elements", [])
684
+
685
+ if not elements:
686
+ # Generate standard elements based on test type
687
+ elements = self._get_standard_elements(test_type)
688
+
689
+ row = 5
690
+ for i, elem in enumerate(elements, 1):
691
+ ws.cell(row=row, column=1, value=i).border = thin_border
692
+ ws.cell(row=row, column=2, value=elem.get("name", "")).border = thin_border
693
+ ws.cell(row=row, column=3, value=elem.get("type", "")).border = thin_border
694
+ ws.cell(row=row, column=4, value=elem.get("selector", elem.get("id", ""))).border = thin_border
695
+ ws.cell(row=row, column=5, value=elem.get("description", "")).border = thin_border
696
+ ws.cell(row=row, column=6, value=elem.get("priority", "medium")).border = thin_border
697
+ ws.cell(row=row, column=7, value=elem.get("tests_applied", "")).border = thin_border
698
+ ws.cell(row=row, column=8, value=elem.get("result", "pending")).border = thin_border
699
+ ws.cell(row=row, column=9, value=elem.get("recommendation", "")).border = thin_border
700
+
701
+ # Wrap text for description
702
+ ws.cell(row=row, column=5).alignment = Alignment(wrap_text=True)
703
+ ws.cell(row=row, column=9).alignment = Alignment(wrap_text=True)
704
+
705
+ row += 1
706
+
707
+ def _get_standard_elements(self, test_type: str) -> List[Dict]:
708
+ """Get standard elements based on test type"""
709
+ web_elements = [
710
+ {"name": "Header/Logo", "type": "header", "selector": "header > .logo", "description": "Company branding and navigation home link", "priority": "high", "tests_applied": "Load, Click, Responsive", "result": "pending", "recommendation": "Verify logo links to homepage"},
711
+ {"name": "Navigation Menu", "type": "nav", "selector": "nav, .navbar", "description": "Main navigation links for site sections", "priority": "critical", "tests_applied": "Links, Hover, Mobile menu", "result": "pending", "recommendation": "Test all nav links, mobile hamburger menu"},
712
+ {"name": "Hero Section", "type": "section", "selector": ".hero, #hero", "description": "Primary landing content with CTA", "priority": "high", "tests_applied": "Visibility, CTA click, Responsive", "result": "pending", "recommendation": "Verify CTA button functionality"},
713
+ {"name": "Login Form", "type": "form", "selector": "form[action*='login']", "description": "User authentication form", "priority": "critical", "tests_applied": "Input validation, Submit, Error states", "result": "pending", "recommendation": "Test empty fields, invalid credentials, CSRF"},
714
+ {"name": "Search Bar", "type": "input", "selector": "input[type='search']", "description": "Site search functionality", "priority": "high", "tests_applied": "Input, Submit, Results, Empty state", "result": "pending", "recommendation": "Test autocomplete, no results, special chars"},
715
+ {"name": "Footer Links", "type": "footer", "selector": "footer a", "description": "Footer navigation and legal links", "priority": "medium", "tests_applied": "Link validation", "result": "pending", "recommendation": "Verify all footer links resolve correctly"},
716
+ {"name": "Contact Form", "type": "form", "selector": "form[action*='contact']", "description": "Contact/submission form", "priority": "critical", "tests_applied": "Validation, Submission, Confirmation", "result": "pending", "recommendation": "Test all field validations and submission"},
717
+ {"name": "Images", "type": "image", "selector": "img", "description": "All page images with alt text", "priority": "medium", "tests_applied": "Load, Alt text, Responsive, Lazy load", "result": "pending", "recommendation": "Verify alt text for accessibility"},
718
+ ]
719
+
720
+ mobile_elements = [
721
+ {"name": "App Launcher", "type": "activity", "selector": "MainActivity", "description": "App main activity/launch point", "priority": "critical", "tests_applied": "Launch time, Crash check", "result": "pending", "recommendation": "Verify cold start under 3 seconds"},
722
+ {"name": "Login Screen", "type": "screen", "selector": "LoginActivity", "description": "User authentication screen", "priority": "critical", "tests_applied": "Input, Submit, Error handling", "result": "pending", "recommendation": "Test all error states and keyboard types"},
723
+ {"name": "Navigation Drawer", "type": "component", "selector": "NavigationView", "description": "Side navigation menu", "priority": "high", "tests_applied": "Open/Close, Item click, Back", "result": "pending", "recommendation": "Test swipe gesture and keyboard nav"},
724
+ {"name": "List/RecyclerView", "type": "component", "selector": "RecyclerView", "description": "Scrollable data list", "priority": "high", "tests_applied": "Scroll, Click item, Pull-to-refresh", "result": "pending", "recommendation": "Test with empty, loading, and error states"},
725
+ {"name": "Bottom Navigation", "type": "component", "selector": "BottomNavigationView", "description": "Bottom tab navigation bar", "priority": "critical", "tests_applied": "Tab switch, Badge, Active state", "result": "pending", "recommendation": "Verify all tabs switch correctly"},
726
+ {"name": "Floating Action Button", "type": "button", "selector": "FloatingActionButton", "description": "Primary action button", "priority": "high", "tests_applied": "Click, Animation, Accessibility", "result": "pending", "recommendation": "Verify content description for a11y"},
727
+ ]
728
+
729
+ api_elements = [
730
+ {"name": "Health Endpoint", "type": "GET", "selector": "/api/health", "description": "API health check endpoint", "priority": "critical", "tests_applied": "Status 200, Response time, Format", "result": "pending", "recommendation": "Must return 200 with version info"},
731
+ {"name": "Auth Endpoint", "type": "POST", "selector": "/api/auth/login", "description": "Authentication endpoint", "priority": "critical", "tests_applied": "Valid/invalid creds, Token generation", "result": "pending", "recommendation": "Test brute force protection, token expiry"},
732
+ {"name": "CRUD Endpoints", "type": "ALL", "selector": "/api/resources/*", "description": "Create, Read, Update, Delete operations", "priority": "critical", "tests_applied": "All HTTP methods, Schema validation", "result": "pending", "recommendation": "Test all CRUD operations with edge cases"},
733
+ {"name": "Pagination", "type": "GET", "selector": "/api/resources?page=", "description": "Paginated list endpoints", "priority": "high", "tests_applied": "Page navigation, Limits, Sorting", "result": "pending", "recommendation": "Test boundary pages and empty results"},
734
+ {"name": "File Upload", "type": "POST", "selector": "/api/upload", "description": "File upload endpoint", "priority": "high", "tests_applied": "Size limits, Type validation, Storage", "result": "pending", "recommendation": "Test max file size and invalid types"},
735
+ {"name": "WebSocket", "type": "WS", "selector": "ws://api/ws", "description": "Real-time WebSocket connection", "priority": "medium", "tests_applied": "Connect, Send/Receive, Reconnect", "result": "pending", "recommendation": "Test connection timeout and message format"},
736
+ ]
737
+
738
+ embedded_elements = [
739
+ {"name": "MQTT Broker", "type": "protocol", "selector": "mqtt://target:1883", "description": "Message broker connection", "priority": "critical", "tests_applied": "Connect, Subscribe, Publish, QoS", "result": "pending", "recommendation": "Verify QoS levels and retain flag"},
740
+ {"name": "Sensor Data", "type": "telemetry", "selector": "topic/sensors/*", "description": "Sensor data topics", "priority": "critical", "tests_applied": "Data format, Range, Frequency", "result": "pending", "recommendation": "Verify data within expected ranges"},
741
+ {"name": "Command Interface", "type": "control", "selector": "topic/commands/*", "description": "Device command topics", "priority": "high", "tests_applied": "Send/Receive, Ack, Timeout", "result": "pending", "recommendation": "Test command acknowledgment and retries"},
742
+ {"name": "OTA Updates", "type": "firmware", "selector": "topic/ota/*", "description": "Over-the-air firmware updates", "priority": "high", "tests_applied": "Version check, Download, Verify, Install", "result": "pending", "recommendation": "Test update rollback on failure"},
743
+ {"name": "Watchdog Timer", "type": "system", "selector": "device/watchdog", "description": "Device health monitoring", "priority": "critical", "tests_applied": "Heartbeat, Timeout, Recovery", "result": "pending", "recommendation": "Verify automatic recovery after timeout"},
744
+ ]
745
+
746
+ type_map = {
747
+ "web": web_elements,
748
+ "mobile": mobile_elements,
749
+ "api": api_elements,
750
+ "embedded": embedded_elements,
751
+ }
752
+
753
+ return type_map.get(test_type, web_elements)
754
+
755
+ # ═══════════════════════════════════════════════════════════════
756
+ # SHEET 5: Defect Log
757
+ # ═══════════════════════════════════════════════════════════════
758
+
759
+ def _create_defect_log(self, ws, results: Dict, test_type: str):
760
+ """Create defect/bug tracking sheet"""
761
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
762
+
763
+ thin_border = Border(
764
+ left=Side(style='thin'), right=Side(style='thin'),
765
+ top=Side(style='thin'), bottom=Side(style='thin')
766
+ )
767
+
768
+ ws.merge_cells("A1:K1")
769
+ ws["A1"] = "DEFECT LOG — Issues Found During Testing"
770
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
771
+ ws["A1"].fill = PatternFill(start_color="C0392B", end_color="C0392B", fill_type="solid")
772
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
773
+ ws.row_dimensions[1].height = 35
774
+
775
+ headers = [
776
+ "Defect ID", "Severity", "Priority", "Category",
777
+ "Title", "Description", "Steps to Reproduce",
778
+ "Expected Result", "Actual Result", "Environment",
779
+ "Status"
780
+ ]
781
+
782
+ header_fill = PatternFill(start_color="E74C3C", end_color="E74C3C", fill_type="solid")
783
+ for col, h in enumerate(headers, 1):
784
+ cell = ws.cell(row=3, column=col)
785
+ cell.value = h
786
+ cell.font = Font(bold=True, color="FFFFFF")
787
+ cell.fill = header_fill
788
+ cell.border = thin_border
789
+ cell.alignment = Alignment(horizontal="center", wrap_text=True)
790
+
791
+ # Generate defects from failed tests
792
+ details = results.get("details", [])
793
+ defects = [d for d in details if isinstance(d, dict) and d.get("status") == "failed"]
794
+
795
+ row = 4
796
+ for i, defect in enumerate(defects, 1):
797
+ ws.cell(row=row, column=1, value=f"DEF-{i:04d}").border = thin_border
798
+ ws.cell(row=row, column=2, value="High").border = thin_border
799
+ ws.cell(row=row, column=3, value="P1").border = thin_border
800
+ ws.cell(row=row, column=4, value=defect.get("category", test_type)).border = thin_border
801
+ ws.cell(row=row, column=5, value=defect.get("name", "Test Failure")).border = thin_border
802
+ ws.cell(row=row, column=6, value=defect.get("error", defect.get("actual", "Test failed"))).border = thin_border
803
+ ws.cell(row=row, column=7, value=defect.get("steps", "Run automated test suite")).border = thin_border
804
+ ws.cell(row=row, column=8, value=defect.get("expected", "Test should pass")).border = thin_border
805
+ ws.cell(row=row, column=9, value=defect.get("actual", "Test failed")).border = thin_border
806
+ ws.cell(row=row, column=10, value=test_type).border = thin_border
807
+ ws.cell(row=row, column=11, value="Open").border = thin_border
808
+
809
+ # Color severity
810
+ ws.cell(row=row, column=2).fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
811
+ ws.cell(row=row, column=11).fill = PatternFill(start_color="FEF9E7", end_color="FEF9E7", fill_type="solid")
812
+
813
+ row += 1
814
+
815
+ if not defects:
816
+ ws.merge_cells(f"A4:K4")
817
+ ws["A4"] = "✅ No defects found — All tests passed!"
818
+ ws["A4"].font = Font(size=14, bold=True, color="27AE60")
819
+ ws["A4"].alignment = Alignment(horizontal="center")
820
+ ws.row_dimensions[4].height = 30
821
+
822
+ # Summary
823
+ row += 2
824
+ ws[f"A{row}"] = "Defect Summary:"
825
+ ws[f"A{row}"].font = Font(bold=True, size=12)
826
+ row += 1
827
+ ws[f"A{row}"] = "Total Defects:"
828
+ ws[f"B{row}"] = len(defects)
829
+ row += 1
830
+ ws[f"A{row}"] = "Critical:"
831
+ ws[f"B{row}"] = sum(1 for d in defects if isinstance(d, dict) and d.get("severity") == "critical")
832
+ row += 1
833
+ ws[f"A{row}"] = "High:"
834
+ ws[f"B{row}"] = len(defects) # All assumed high for now
835
+ row += 1
836
+ ws[f"A{row}"] = "Open:"
837
+ ws[f"B{row}"] = len(defects)
838
+
839
+ # ═══════════════════════════════════════════════════════════════
840
+ # SHEET 6: Performance Metrics
841
+ # ═══════════════════════════════════════════════════════════════
842
+
843
+ def _create_performance_metrics(self, ws, results: Dict, test_type: str):
844
+ """Create performance metrics sheet"""
845
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
846
+
847
+ thin_border = Border(
848
+ left=Side(style='thin'), right=Side(style='thin'),
849
+ top=Side(style='thin'), bottom=Side(style='thin')
850
+ )
851
+
852
+ ws.merge_cells("A1:F1")
853
+ ws["A1"] = "PERFORMANCE METRICS"
854
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
855
+ ws["A1"].fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
856
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
857
+ ws.row_dimensions[1].height = 35
858
+
859
+ # Metrics table
860
+ headers = ["Metric", "Value", "Benchmark", "Status", "Trend", "Notes"]
861
+ header_fill = PatternFill(start_color="2E86C1", end_color="2E86C1", fill_type="solid")
862
+
863
+ for col, h in enumerate(headers, 1):
864
+ cell = ws.cell(row=3, column=col)
865
+ cell.value = h
866
+ cell.font = Font(bold=True, color="FFFFFF")
867
+ cell.fill = header_fill
868
+ cell.border = thin_border
869
+ cell.alignment = Alignment(horizontal="center")
870
+
871
+ metrics = self._get_performance_metrics(results, test_type)
872
+ row = 4
873
+ for metric in metrics:
874
+ ws.cell(row=row, column=1, value=metric["name"]).border = thin_border
875
+ ws.cell(row=row, column=2, value=metric["value"]).border = thin_border
876
+ ws.cell(row=row, column=2).alignment = Alignment(horizontal="center")
877
+ ws.cell(row=row, column=3, value=metric["benchmark"]).border = thin_border
878
+ ws.cell(row=row, column=3).alignment = Alignment(horizontal="center")
879
+ ws.cell(row=row, column=4, value=metric["status"]).border = thin_border
880
+ ws.cell(row=row, column=4).alignment = Alignment(horizontal="center")
881
+
882
+ if metric["status"] == "✅ Pass":
883
+ ws.cell(row=row, column=4).fill = PatternFill(start_color="D5F5E3", end_color="D5F5E3", fill_type="solid")
884
+ elif metric["status"] == "❌ Fail":
885
+ ws.cell(row=row, column=4).fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
886
+
887
+ ws.cell(row=row, column=5, value=metric.get("trend", "—")).border = thin_border
888
+ ws.cell(row=row, column=5).alignment = Alignment(horizontal="center")
889
+ ws.cell(row=row, column=6, value=metric.get("notes", "")).border = thin_border
890
+
891
+ row += 1
892
+
893
+ def _get_performance_metrics(self, results: Dict, test_type: str) -> List[Dict]:
894
+ """Get performance metrics based on test type"""
895
+ duration = results.get("duration", 0)
896
+
897
+ base_metrics = [
898
+ {"name": "Total Test Duration", "value": f"{duration:.1f}s", "benchmark": "< 300s",
899
+ "status": "✅ Pass" if duration < 300 else "❌ Fail", "notes": "End-to-end test time"},
900
+ {"name": "Tests Per Second", "value": f"{results.get('total', 0) / max(duration, 1):.1f}",
901
+ "benchmark": "> 1 tps", "status": "✅ Pass", "notes": "Test execution throughput"},
902
+ {"name": "Pass Rate", "value": f"{(results.get('passed', 0) / max(results.get('total', 1), 1) * 100):.1f}%",
903
+ "benchmark": "> 95%", "status": "✅ Pass" if results.get("passed", 0) / max(results.get("total", 1), 1) > 0.95 else "❌ Fail",
904
+ "notes": "Percentage of tests passing"},
905
+ {"name": "Defect Density", "value": f"{results.get('failed', 0)} defects",
906
+ "benchmark": "< 5 defects", "status": "✅ Pass" if results.get("failed", 0) < 5 else "❌ Fail",
907
+ "notes": "Number of defects found"},
908
+ {"name": "Coverage Efficiency", "value": f"{results.get('coverage', 0)}%",
909
+ "benchmark": "> 80%", "status": "✅ Pass" if results.get("coverage", 0) >= 80 else "⚠️ Warning",
910
+ "notes": "Test coverage vs target"},
911
+ ]
912
+
913
+ if test_type == "web":
914
+ base_metrics.extend([
915
+ {"name": "Page Load Time", "value": "—", "benchmark": "< 3s", "status": "Pending", "notes": "Run with --performance flag"},
916
+ {"name": "Lighthouse Score", "value": "—", "benchmark": "> 90", "status": "Pending", "notes": "Run with --performance flag"},
917
+ ])
918
+ elif test_type == "api":
919
+ base_metrics.extend([
920
+ {"name": "Avg Response Time", "value": "—", "benchmark": "< 500ms", "status": "Pending", "notes": "API response time"},
921
+ {"name": "Throughput", "value": "—", "benchmark": "> 100 req/s", "status": "Pending", "notes": "Requests per second"},
922
+ ])
923
+
924
+ return base_metrics
925
+
926
+ # ═══════════════════════════════════════════════════════════════
927
+ # SHEET 7: Charts & Graphs
928
+ # ═══════════════════════════════════════════════════════════════
929
+
930
+ def _create_charts(self, ws, results: Dict, test_type: str):
931
+ """Create charts and graphs sheet"""
932
+ from openpyxl.chart import BarChart, PieChart, Reference
933
+ from openpyxl.chart.label import DataLabelList
934
+ from openpyxl.chart.series import DataPoint
935
+ from openpyxl.styles import Font, PatternFill, Alignment
936
+
937
+ ws["A1"] = "CHARTS & VISUAL ANALYTICS"
938
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
939
+ ws["A1"].fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
940
+
941
+ # ─── Pie Chart: Pass/Fail Distribution ───────────────────
942
+ ws["A3"] = "Status"
943
+ ws["B3"] = "Count"
944
+ ws["A4"] = "Passed"
945
+ ws["B4"] = results.get("passed", 0)
946
+ ws["A5"] = "Failed"
947
+ ws["B5"] = results.get("failed", 0)
948
+ ws["A6"] = "Skipped"
949
+ ws["B6"] = results.get("skipped", 0)
950
+
951
+ pie = PieChart()
952
+ pie.title = "Test Results Distribution"
953
+ pie.style = 10
954
+ data = Reference(ws, min_col=2, min_row=3, max_row=6)
955
+ cats = Reference(ws, min_col=1, min_row=4, max_row=6)
956
+ pie.add_data(data, titles_from_data=True)
957
+ pie.set_categories(cats)
958
+ pie.width = 15
959
+ pie.height = 10
960
+
961
+ # Color the slices
962
+ from openpyxl.chart.series import DataPoint
963
+ from openpyxl.drawing.fill import PatternFillProperties, ColorChoice
964
+ passed_pt = DataPoint(idx=0)
965
+ passed_pt.graphicalProperties.solidFill = "27AE60"
966
+ failed_pt = DataPoint(idx=1)
967
+ failed_pt.graphicalProperties.solidFill = "E74C3C"
968
+ skip_pt = DataPoint(idx=2)
969
+ skip_pt.graphicalProperties.solidFill = "F39C12"
970
+ pie.series[0].data_points = [passed_pt, failed_pt, skip_pt]
971
+
972
+ ws.add_chart(pie, "D3")
973
+
974
+ # ─── Bar Chart: Coverage by Phase ────────────────────────
975
+ ws["A9"] = "Phase"
976
+ ws["B9"] = "Coverage %"
977
+ categories = results.get("categories", {})
978
+ phase_names = ["Smoke", "Functional", "Integration", "Performance", "Advanced"]
979
+ phase_keys = ["smoke", "functional", "integration", "performance", "advanced"]
980
+
981
+ row = 10
982
+ for i, (name, key) in enumerate(zip(phase_names, phase_keys)):
983
+ ws.cell(row=row, column=1, value=name)
984
+ cat_data = categories.get(key, {})
985
+ coverage = cat_data.get("coverage", 0) if isinstance(cat_data, dict) else 0
986
+ ws.cell(row=row, column=2, value=coverage)
987
+ row += 1
988
+
989
+ bar = BarChart()
990
+ bar.type = "col"
991
+ bar.title = "Coverage by Phase (Target: 90%)"
992
+ bar.style = 10
993
+ bar.y_axis.title = "Coverage %"
994
+ bar.x_axis.title = "Test Phase"
995
+
996
+ data = Reference(ws, min_col=2, min_row=9, max_row=14)
997
+ cats = Reference(ws, min_col=1, min_row=10, max_row=14)
998
+ bar.add_data(data, titles_from_data=True)
999
+ bar.set_categories(cats)
1000
+ bar.width = 15
1001
+ bar.height = 10
1002
+
1003
+ ws.add_chart(bar, "D19")
1004
+
1005
+ # ═══════════════════════════════════════════════════════════════
1006
+ # SHEET 8: Recommendations
1007
+ # ═══════════════════════════════════════════════════════════════
1008
+
1009
+ def _create_recommendations(self, ws, results: Dict, test_type: str):
1010
+ """Create recommendations sheet"""
1011
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
1012
+
1013
+ thin_border = Border(
1014
+ left=Side(style='thin'), right=Side(style='thin'),
1015
+ top=Side(style='thin'), bottom=Side(style='thin')
1016
+ )
1017
+
1018
+ ws.merge_cells("A1:F1")
1019
+ ws["A1"] = "RECOMMENDATIONS & ACTION ITEMS"
1020
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
1021
+ ws["A1"].fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
1022
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
1023
+ ws.row_dimensions[1].height = 35
1024
+
1025
+ headers = ["#", "Category", "Recommendation", "Priority", "Effort", "Impact"]
1026
+ header_fill = PatternFill(start_color="2E86C1", end_color="2E86C1", fill_type="solid")
1027
+
1028
+ for col, h in enumerate(headers, 1):
1029
+ cell = ws.cell(row=3, column=col)
1030
+ cell.value = h
1031
+ cell.font = Font(bold=True, color="FFFFFF")
1032
+ cell.fill = header_fill
1033
+ cell.border = thin_border
1034
+ cell.alignment = Alignment(horizontal="center")
1035
+
1036
+ # Generate recommendations based on results
1037
+ recommendations = self._generate_recommendations(results, test_type)
1038
+
1039
+ row = 4
1040
+ for i, rec in enumerate(recommendations, 1):
1041
+ ws.cell(row=row, column=1, value=i).border = thin_border
1042
+ ws.cell(row=row, column=2, value=rec["category"]).border = thin_border
1043
+ ws.cell(row=row, column=3, value=rec["recommendation"]).border = thin_border
1044
+ ws.cell(row=row, column=3).alignment = Alignment(wrap_text=True)
1045
+ ws.cell(row=row, column=4, value=rec["priority"]).border = thin_border
1046
+ ws.cell(row=row, column=4).alignment = Alignment(horizontal="center")
1047
+ ws.cell(row=row, column=5, value=rec["effort"]).border = thin_border
1048
+ ws.cell(row=row, column=5).alignment = Alignment(horizontal="center")
1049
+ ws.cell(row=row, column=6, value=rec["impact"]).border = thin_border
1050
+ ws.cell(row=row, column=6).alignment = Alignment(horizontal="center")
1051
+
1052
+ # Color code priority
1053
+ if rec["priority"] == "Critical":
1054
+ ws.cell(row=row, column=4).fill = PatternFill(start_color="FADBD8", end_color="FADBD8", fill_type="solid")
1055
+ elif rec["priority"] == "High":
1056
+ ws.cell(row=row, column=4).fill = PatternFill(start_color="FEF9E7", end_color="FEF9E7", fill_type="solid")
1057
+
1058
+ row += 1
1059
+
1060
+ def _generate_recommendations(self, results: Dict, test_type: str) -> List[Dict]:
1061
+ """Generate recommendations based on test results"""
1062
+ recs = []
1063
+ coverage = results.get("coverage", 0)
1064
+ failed = results.get("failed", 0)
1065
+ total = results.get("total", 1)
1066
+
1067
+ if coverage < 60:
1068
+ recs.append({
1069
+ "category": "Coverage",
1070
+ "recommendation": f"Coverage is at {coverage}%. Add more test cases to reach at least 80% coverage before release.",
1071
+ "priority": "Critical",
1072
+ "effort": "High",
1073
+ "impact": "High",
1074
+ })
1075
+
1076
+ if failed > 0:
1077
+ recs.append({
1078
+ "category": "Defects",
1079
+ "recommendation": f"Fix {failed} failing test(s) before release. Prioritize critical and high severity defects.",
1080
+ "priority": "Critical",
1081
+ "effort": "Medium",
1082
+ "impact": "High",
1083
+ })
1084
+
1085
+ if test_type == "web":
1086
+ recs.append({
1087
+ "category": "Accessibility",
1088
+ "recommendation": "Run WCAG 2.1 AA compliance audit. Ensure all interactive elements have ARIA labels.",
1089
+ "priority": "High",
1090
+ "effort": "Medium",
1091
+ "impact": "High",
1092
+ })
1093
+ recs.append({
1094
+ "category": "Performance",
1095
+ "recommendation": "Run Lighthouse audit for Core Web Vitals. Target LCP < 2.5s, FID < 100ms, CLS < 0.1.",
1096
+ "priority": "Medium",
1097
+ "effort": "Low",
1098
+ "impact": "Medium",
1099
+ })
1100
+
1101
+ if test_type == "api":
1102
+ recs.append({
1103
+ "category": "Security",
1104
+ "recommendation": "Implement rate limiting on all API endpoints. Add request validation middleware.",
1105
+ "priority": "High",
1106
+ "effort": "Medium",
1107
+ "impact": "High",
1108
+ })
1109
+ recs.append({
1110
+ "category": "Documentation",
1111
+ "recommendation": "Ensure OpenAPI spec is up-to-date. All endpoints should have request/response examples.",
1112
+ "priority": "Medium",
1113
+ "effort": "Low",
1114
+ "impact": "Medium",
1115
+ })
1116
+
1117
+ if test_type == "mobile":
1118
+ recs.append({
1119
+ "category": "UX",
1120
+ "recommendation": "Test on physical devices (not just emulators). Verify on low-end devices with limited RAM.",
1121
+ "priority": "High",
1122
+ "effort": "High",
1123
+ "impact": "High",
1124
+ })
1125
+ recs.append({
1126
+ "category": "Performance",
1127
+ "recommendation": "Monitor memory leaks during extended usage. Check battery consumption impact.",
1128
+ "priority": "Medium",
1129
+ "effort": "Medium",
1130
+ "impact": "Medium",
1131
+ })
1132
+
1133
+ if test_type == "embedded":
1134
+ recs.append({
1135
+ "category": "Reliability",
1136
+ "recommendation": "Run 72-hour soak test. Verify watchdog timer recovery and power cycling.",
1137
+ "priority": "Critical",
1138
+ "effort": "High",
1139
+ "impact": "Critical",
1140
+ })
1141
+ recs.append({
1142
+ "category": "Security",
1143
+ "recommendation": "Verify firmware encryption and secure boot. Test OTA update rollback mechanism.",
1144
+ "priority": "High",
1145
+ "effort": "High",
1146
+ "impact": "Critical",
1147
+ })
1148
+
1149
+ # Always add general recommendations
1150
+ recs.append({
1151
+ "category": "CI/CD",
1152
+ "recommendation": "Integrate nextOG tests into CI/CD pipeline. Set coverage gate at 80% minimum.",
1153
+ "priority": "High",
1154
+ "effort": "Medium",
1155
+ "impact": "High",
1156
+ })
1157
+ recs.append({
1158
+ "category": "Regression",
1159
+ "recommendation": "Add failed tests to regression suite. Schedule weekly full regression runs.",
1160
+ "priority": "Medium",
1161
+ "effort": "Low",
1162
+ "impact": "Medium",
1163
+ })
1164
+ recs.append({
1165
+ "category": "Monitoring",
1166
+ "recommendation": "Set up production monitoring with alerts matching test coverage areas.",
1167
+ "priority": "Medium",
1168
+ "effort": "Medium",
1169
+ "impact": "Medium",
1170
+ })
1171
+
1172
+ return recs
1173
+
1174
+ # ═══════════════════════════════════════════════════════════════
1175
+ # SHEET 9: Test Environment
1176
+ # ═══════════════════════════════════════════════════════════════
1177
+
1178
+ def _create_test_environment(self, ws, test_type: str):
1179
+ """Create test environment documentation sheet"""
1180
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
1181
+ import platform as plat
1182
+
1183
+ try:
1184
+ import psutil
1185
+ except ImportError:
1186
+ psutil = None
1187
+
1188
+ thin_border = Border(
1189
+ left=Side(style='thin'), right=Side(style='thin'),
1190
+ top=Side(style='thin'), bottom=Side(style='thin')
1191
+ )
1192
+
1193
+ ws.merge_cells("A1:D1")
1194
+ ws["A1"] = "TEST ENVIRONMENT DETAILS"
1195
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
1196
+ ws["A1"].fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
1197
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
1198
+ ws.row_dimensions[1].height = 35
1199
+
1200
+ headers = ["Property", "Value", "Category", "Notes"]
1201
+ header_fill = PatternFill(start_color="2E86C1", end_color="2E86C1", fill_type="solid")
1202
+
1203
+ for col, h in enumerate(headers, 1):
1204
+ cell = ws.cell(row=3, column=col)
1205
+ cell.value = h
1206
+ cell.font = Font(bold=True, color="FFFFFF")
1207
+ cell.fill = header_fill
1208
+ cell.border = thin_border
1209
+
1210
+ env_data = [
1211
+ ("Tool", "nextOG CLI v1.0.0", "Software", "QA Automation Tool"),
1212
+ ("Python Version", plat.python_version(), "Runtime", "Test execution engine"),
1213
+ ("OS", f"{plat.system()} {plat.release()}", "Platform", "Test host OS"),
1214
+ ("Machine", plat.machine(), "Hardware", "CPU architecture"),
1215
+ ("Processor", plat.processor() or "N/A", "Hardware", "CPU details"),
1216
+ ("Total RAM", f"{psutil.virtual_memory().total / (1024**3):.1f} GB" if psutil else "N/A", "Hardware", "System memory"),
1217
+ ("Available RAM", f"{psutil.virtual_memory().available / (1024**3):.1f} GB" if psutil else "N/A", "Hardware", "Free memory"),
1218
+ ("CPU Cores", str(psutil.cpu_count()) if psutil else str(os.cpu_count() or "N/A"), "Hardware", "Logical processors"),
1219
+ ("Test Type", test_type.upper(), "Configuration", "Test suite type"),
1220
+ ("Test Date", datetime.now().strftime("%Y-%m-%d"), "Schedule", "Execution date"),
1221
+ ("Test Time", datetime.now().strftime("%H:%M:%S"), "Schedule", "Execution time"),
1222
+ ("Timezone", datetime.now().astimezone().tzname(), "Schedule", "Local timezone"),
1223
+ ("Privacy Mode", "LOCAL ONLY - No External Data Transfer", "Security", "All data stays on device"),
1224
+ ("Data Encryption", "AES-256 (Fernet)", "Security", "Local data encryption"),
1225
+ ("Report Location", f"~/.nextog/reports/", "Storage", "Local report storage"),
1226
+ ]
1227
+
1228
+ row = 4
1229
+ for prop, value, category, notes in env_data:
1230
+ ws.cell(row=row, column=1, value=prop).border = thin_border
1231
+ ws.cell(row=row, column=2, value=value).border = thin_border
1232
+ ws.cell(row=row, column=3, value=category).border = thin_border
1233
+ ws.cell(row=row, column=4, value=notes).border = thin_border
1234
+ row += 1
1235
+
1236
+ # ═══════════════════════════════════════════════════════════════
1237
+ # SHEET 10: Sign-Off Sheet
1238
+ # ═══════════════════════════════════════════════════════════════
1239
+
1240
+ def _create_signoff_sheet(self, ws, results: Dict, test_type: str, timestamp: str):
1241
+ """Create sign-off/approval sheet"""
1242
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
1243
+
1244
+ thin_border = Border(
1245
+ left=Side(style='thin'), right=Side(style='thin'),
1246
+ top=Side(style='thin'), bottom=Side(style='thin')
1247
+ )
1248
+
1249
+ ws.merge_cells("A1:F1")
1250
+ ws["A1"] = "TEST SIGN-OFF & APPROVAL"
1251
+ ws["A1"].font = Font(name="Calibri", size=16, bold=True, color="FFFFFF")
1252
+ ws["A1"].fill = PatternFill(start_color="1B4F72", end_color="1B4F72", fill_type="solid")
1253
+ ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
1254
+ ws.row_dimensions[1].height = 35
1255
+
1256
+ # Report info
1257
+ ws["A3"] = "Report ID:"
1258
+ ws["A3"].font = Font(bold=True)
1259
+ ws["B3"] = f"nextOG-{test_type.upper()}-{timestamp}"
1260
+
1261
+ ws["A4"] = "Generated By:"
1262
+ ws["A4"].font = Font(bold=True)
1263
+ ws["B4"] = "nextOG CLI v1.0.0 (Automated)"
1264
+
1265
+ ws["A5"] = "Test Type:"
1266
+ ws["A5"].font = Font(bold=True)
1267
+ ws["B5"] = test_type.upper()
1268
+
1269
+ ws["A6"] = "Coverage:"
1270
+ ws["A6"].font = Font(bold=True)
1271
+ ws["B6"] = f"{results.get('coverage', 0)}%"
1272
+
1273
+ ws["A7"] = "Status:"
1274
+ ws["A7"].font = Font(bold=True)
1275
+ coverage = results.get("coverage", 0)
1276
+ failed = results.get("failed", 0)
1277
+ if coverage >= 80 and failed == 0:
1278
+ ws["B7"] = "✅ PASS — Recommended for Release"
1279
+ elif coverage >= 60:
1280
+ ws["B7"] = "⚠️ CONDITIONAL — Requires Review"
1281
+ else:
1282
+ ws["B7"] = "❌ FAIL — Not Recommended for Release"
1283
+
1284
+ # Approval section
1285
+ ws.merge_cells("A9:F9")
1286
+ ws["A9"] = "APPROVAL SIGNATURES"
1287
+ ws["A9"].font = Font(size=14, bold=True, color="FFFFFF")
1288
+ ws["A9"].fill = PatternFill(start_color="2E86C1", end_color="2E86C1", fill_type="solid")
1289
+ ws["A9"].alignment = Alignment(horizontal="center")
1290
+
1291
+ sign_headers = ["Role", "Name", "Signature", "Date", "Comments", "Approved"]
1292
+ for col, h in enumerate(sign_headers, 1):
1293
+ cell = ws.cell(row=10, column=col)
1294
+ cell.value = h
1295
+ cell.font = Font(bold=True, color="FFFFFF")
1296
+ cell.fill = PatternFill(start_color="34495E", end_color="34495E", fill_type="solid")
1297
+ cell.border = thin_border
1298
+ cell.alignment = Alignment(horizontal="center")
1299
+
1300
+ roles = ["QA Lead", "Dev Lead", "Product Manager", "Release Manager"]
1301
+ row = 11
1302
+ for role in roles:
1303
+ ws.cell(row=row, column=1, value=role).border = thin_border
1304
+ ws.cell(row=row, column=2).border = thin_border
1305
+ ws.cell(row=row, column=3).border = thin_border
1306
+ ws.cell(row=row, column=4).border = thin_border
1307
+ ws.cell(row=row, column=5).border = thin_border
1308
+ ws.cell(row=row, column=6).border = thin_border
1309
+ ws.row_dimensions[row].height = 30
1310
+ row += 1
1311
+
1312
+ # Footer
1313
+ row += 2
1314
+ ws.merge_cells(f"A{row}:F{row}")
1315
+ ws[f"A{row}"] = "This report was generated automatically by nextOG CLI. All data is stored locally with AES-256 encryption. No external data transfer occurred."
1316
+ ws[f"A{row}"].font = Font(italic=True, size=9, color="666666")
1317
+ ws[f"A{row}"].alignment = Alignment(horizontal="center", wrap_text=True)
1318
+
1319
+ # ═══════════════════════════════════════════════════════════════
1320
+ # UTILITY METHODS
1321
+ # ═══════════════════════════════════════════════════════════════
1322
+
1323
+ def _detect_test_type(self, results: Dict) -> str:
1324
+ """Auto-detect test type from results"""
1325
+ engine = results.get("engine", "")
1326
+ if engine:
1327
+ return engine
1328
+
1329
+ # Try to detect from structure
1330
+ if "url" in results:
1331
+ return "web"
1332
+ elif "app" in results or "platform" in results:
1333
+ return "mobile"
1334
+ elif "base_url" in results or "spec" in results:
1335
+ return "api"
1336
+ elif "target" in results or "protocol" in results:
1337
+ return "embedded"
1338
+
1339
+ return "full"
1340
+
1341
+ def _auto_fit_columns(self, ws):
1342
+ """Auto-fit column widths"""
1343
+ from openpyxl.utils import get_column_letter
1344
+
1345
+ for column in ws.columns:
1346
+ max_length = 0
1347
+ column_letter = get_column_letter(column[0].column)
1348
+
1349
+ for cell in column:
1350
+ try:
1351
+ if cell.value:
1352
+ cell_len = len(str(cell.value))
1353
+ if cell_len > max_length:
1354
+ max_length = cell_len
1355
+ except Exception:
1356
+ pass
1357
+
1358
+ adjusted_width = min(max_length + 4, 50)
1359
+ ws.column_dimensions[column_letter].width = max(adjusted_width, 12)