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.
- nextog/__init__.py +4 -0
- nextog/cli.py +545 -0
- nextog/config/__init__.py +1 -0
- nextog/config/settings.py +132 -0
- nextog/core/__init__.py +1 -0
- nextog/core/engine.py +193 -0
- nextog/core/permissions.py +129 -0
- nextog/core/privacy.py +130 -0
- nextog/core/reporter.py +204 -0
- nextog/core/runner.py +236 -0
- nextog/data/__init__.py +1 -0
- nextog/data/local_db.py +367 -0
- nextog/data/models.py +72 -0
- nextog/data/sync.py +65 -0
- nextog/engines/__init__.py +1 -0
- nextog/engines/api/__init__.py +1 -0
- nextog/engines/api/graphql.py +54 -0
- nextog/engines/api/rest.py +346 -0
- nextog/engines/api/websocket.py +59 -0
- nextog/engines/embedded/__init__.py +1 -0
- nextog/engines/embedded/firmware.py +53 -0
- nextog/engines/embedded/hardware.py +330 -0
- nextog/engines/mobile/__init__.py +1 -0
- nextog/engines/mobile/android.py +333 -0
- nextog/engines/mobile/cross.py +48 -0
- nextog/engines/mobile/ios.py +46 -0
- nextog/engines/system/__init__.py +1 -0
- nextog/engines/system/load.py +121 -0
- nextog/engines/system/performance.py +128 -0
- nextog/engines/system/security.py +170 -0
- nextog/engines/web/__init__.py +1 -0
- nextog/engines/web/accessibility.py +191 -0
- nextog/engines/web/browser.py +387 -0
- nextog/engines/web/elements.py +285 -0
- nextog/engines/web/responsive.py +79 -0
- nextog/live/__init__.py +1 -0
- nextog/live/dashboard.py +30 -0
- nextog/live/panel.py +325 -0
- nextog/reports/__init__.py +1359 -0
- nextog/training/__init__.py +1 -0
- nextog/training/learner.py +269 -0
- nextog/training/patterns.py +102 -0
- nextog/utils/__init__.py +1 -0
- nextog/utils/helpers.py +91 -0
- nextog/utils/logger.py +37 -0
- nextog/utils/validators.py +98 -0
- nextog_cli-1.0.0.dist-info/METADATA +344 -0
- nextog_cli-1.0.0.dist-info/RECORD +51 -0
- nextog_cli-1.0.0.dist-info/WHEEL +5 -0
- nextog_cli-1.0.0.dist-info/entry_points.txt +2 -0
- nextog_cli-1.0.0.dist-info/top_level.txt +1 -0
nextog/__init__.py
ADDED
nextog/cli.py
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
"""
|
|
2
|
+
nextOG CLI - Main Entry Point
|
|
3
|
+
Full QA Testing Automation CLI Tool
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from nextog.core.engine import TestEngine
|
|
15
|
+
from nextog.core.permissions import PermissionManager
|
|
16
|
+
from nextog.core.privacy import PrivacyEngine
|
|
17
|
+
from nextog.config.settings import Settings
|
|
18
|
+
from nextog.data.local_db import LocalDatabase
|
|
19
|
+
from nextog.reports import IndustrialReportGenerator
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(
|
|
22
|
+
name="nextog",
|
|
23
|
+
help="🚀 nextOG CLI - Full QA Testing Automation Tool",
|
|
24
|
+
add_completion=True,
|
|
25
|
+
rich_markup_mode="rich",
|
|
26
|
+
)
|
|
27
|
+
console = Console()
|
|
28
|
+
settings = Settings()
|
|
29
|
+
db = LocalDatabase()
|
|
30
|
+
permissions = PermissionManager(db)
|
|
31
|
+
privacy = PrivacyEngine(db)
|
|
32
|
+
report_generator = IndustrialReportGenerator(db, settings, privacy)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ──────────────────────────────────────────────────────────────
|
|
36
|
+
# CLI Callback - Version & Config
|
|
37
|
+
# ──────────────────────────────────────────────────────────────
|
|
38
|
+
def version_callback(value: bool):
|
|
39
|
+
if value:
|
|
40
|
+
console.print(f"[bold green]nextOG CLI[/bold green] v{__import__('nextog').__version__}")
|
|
41
|
+
raise typer.Exit()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.callback()
|
|
45
|
+
def main(
|
|
46
|
+
version: Optional[bool] = typer.Option(
|
|
47
|
+
None, "--version", "-v", callback=version_callback, is_eager=True
|
|
48
|
+
),
|
|
49
|
+
config: Optional[Path] = typer.Option(
|
|
50
|
+
None, "--config", "-c", help="Path to config file"
|
|
51
|
+
),
|
|
52
|
+
verbose: bool = typer.Option(False, "--verbose", help="Enable verbose output"),
|
|
53
|
+
):
|
|
54
|
+
"""nextOG CLI - Full QA Testing Automation Tool"""
|
|
55
|
+
if config:
|
|
56
|
+
settings.load_from_file(config)
|
|
57
|
+
if verbose:
|
|
58
|
+
settings.set_verbose(True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ──────────────────────────────────────────────────────────────
|
|
62
|
+
# INIT Command - Initialize Project
|
|
63
|
+
# ──────────────────────────────────────────────────────────────
|
|
64
|
+
@app.command()
|
|
65
|
+
def init(
|
|
66
|
+
project_name: str = typer.Argument(..., help="Project name"),
|
|
67
|
+
template: str = typer.Option("full", help="Template: full, web, mobile, api, minimal"),
|
|
68
|
+
):
|
|
69
|
+
"""Initialize a new nextOG testing project"""
|
|
70
|
+
console.print(Panel(f"[bold]Initializing project: {project_name}[/bold]"))
|
|
71
|
+
|
|
72
|
+
from nextog.core.runner import ProjectInitializer
|
|
73
|
+
initializer = ProjectInitializer(settings, db)
|
|
74
|
+
initializer.create_project(project_name, template)
|
|
75
|
+
|
|
76
|
+
console.print("[green]✓[/green] Project initialized successfully!")
|
|
77
|
+
console.print(f" → Config: .nextog/config.yaml")
|
|
78
|
+
console.print(f" → Tests: .nextog/tests/")
|
|
79
|
+
console.print(f" → Run: [bold]nextog test web --url <url>[/bold]")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ──────────────────────────────────────────────────────────────
|
|
83
|
+
# TEST Command - Run Tests
|
|
84
|
+
# ──────────────────────────────────────────────────────────────
|
|
85
|
+
test_app = typer.Typer(help="Run various types of tests")
|
|
86
|
+
app.add_typer(test_app, name="test")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@test_app.command("web")
|
|
90
|
+
def test_web(
|
|
91
|
+
url: str = typer.Option(..., "--url", "-u", help="URL to test"),
|
|
92
|
+
browser: str = typer.Option("chromium", help="Browser: chromium, firefox, webkit"),
|
|
93
|
+
responsive: bool = typer.Option(True, help="Test responsive design"),
|
|
94
|
+
accessibility: bool = typer.Option(True, help="Test accessibility"),
|
|
95
|
+
performance: bool = typer.Option(False, help="Run performance tests"),
|
|
96
|
+
coverage_target: int = typer.Option(90, help="Target coverage percentage"),
|
|
97
|
+
headless: bool = typer.Option(True, help="Run in headless mode"),
|
|
98
|
+
):
|
|
99
|
+
"""Run web application tests"""
|
|
100
|
+
console.print(Panel(f"[bold blue]Web Testing:[/bold blue] {url}"))
|
|
101
|
+
|
|
102
|
+
from nextog.engines.web.browser import WebTestEngine
|
|
103
|
+
engine = WebTestEngine(settings, db, privacy)
|
|
104
|
+
|
|
105
|
+
with Progress(
|
|
106
|
+
SpinnerColumn(),
|
|
107
|
+
TextColumn("[progress.description]{task.description}"),
|
|
108
|
+
console=console,
|
|
109
|
+
) as progress:
|
|
110
|
+
task = progress.add_task("Running web tests...", total=None)
|
|
111
|
+
results = engine.run_tests(
|
|
112
|
+
url=url,
|
|
113
|
+
browser=browser,
|
|
114
|
+
responsive=responsive,
|
|
115
|
+
accessibility=accessibility,
|
|
116
|
+
performance=performance,
|
|
117
|
+
coverage_target=coverage_target,
|
|
118
|
+
headless=headless,
|
|
119
|
+
)
|
|
120
|
+
progress.update(task, completed=True)
|
|
121
|
+
|
|
122
|
+
_display_results(results, "Web Tests")
|
|
123
|
+
|
|
124
|
+
# Auto-generate industrial Excel report
|
|
125
|
+
_auto_generate_report(results, "web")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@test_app.command("mobile")
|
|
129
|
+
def test_mobile(
|
|
130
|
+
app_path: str = typer.Option(..., "--app", "-a", help="Path to app (.apk/.ipa)"),
|
|
131
|
+
platform: str = typer.Option("android", help="Platform: android, ios"),
|
|
132
|
+
device: str = typer.Option("emulator", help="Device or emulator"),
|
|
133
|
+
coverage_target: int = typer.Option(90, help="Target coverage percentage"),
|
|
134
|
+
):
|
|
135
|
+
"""Run mobile application tests"""
|
|
136
|
+
console.print(Panel(f"[bold magenta]Mobile Testing:[/bold magenta] {app_path}"))
|
|
137
|
+
|
|
138
|
+
from nextog.engines.mobile.android import MobileTestEngine
|
|
139
|
+
engine = MobileTestEngine(settings, db, privacy)
|
|
140
|
+
|
|
141
|
+
results = engine.run_tests(
|
|
142
|
+
app_path=app_path,
|
|
143
|
+
platform=platform,
|
|
144
|
+
device=device,
|
|
145
|
+
coverage_target=coverage_target,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
_display_results(results, "Mobile Tests")
|
|
149
|
+
|
|
150
|
+
# Auto-generate industrial Excel report
|
|
151
|
+
_auto_generate_report(results, "mobile")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@test_app.command("api")
|
|
155
|
+
def test_api(
|
|
156
|
+
spec: str = typer.Option(..., "--spec", "-s", help="API spec file (OpenAPI/GraphQL)"),
|
|
157
|
+
base_url: str = typer.Option(..., "--base-url", "-b", help="Base URL for API"),
|
|
158
|
+
auth: Optional[str] = typer.Option(None, help="Auth token or credentials"),
|
|
159
|
+
coverage_target: int = typer.Option(90, help="Target coverage percentage"),
|
|
160
|
+
):
|
|
161
|
+
"""Run API tests"""
|
|
162
|
+
console.print(Panel(f"[bold yellow]API Testing:[/bold yellow] {base_url}"))
|
|
163
|
+
|
|
164
|
+
from nextog.engines.api.rest import APITestEngine
|
|
165
|
+
engine = APITestEngine(settings, db, privacy)
|
|
166
|
+
|
|
167
|
+
results = engine.run_tests(
|
|
168
|
+
spec_file=spec,
|
|
169
|
+
base_url=base_url,
|
|
170
|
+
auth=auth,
|
|
171
|
+
coverage_target=coverage_target,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
_display_results(results, "API Tests")
|
|
175
|
+
|
|
176
|
+
# Auto-generate industrial Excel report
|
|
177
|
+
_auto_generate_report(results, "api")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@test_app.command("embedded")
|
|
181
|
+
def test_embedded(
|
|
182
|
+
target: str = typer.Option(..., "--target", "-t", help="Target device/IP"),
|
|
183
|
+
protocol: str = typer.Option("mqtt", help="Protocol: mqtt, coap, http, serial"),
|
|
184
|
+
firmware: Optional[str] = typer.Option(None, help="Firmware version"),
|
|
185
|
+
coverage_target: int = typer.Option(90, help="Target coverage percentage"),
|
|
186
|
+
):
|
|
187
|
+
"""Run embedded systems tests"""
|
|
188
|
+
console.print(Panel(f"[bold red]Embedded Systems Testing:[/bold red] {target}"))
|
|
189
|
+
|
|
190
|
+
from nextog.engines.embedded.hardware import EmbeddedTestEngine
|
|
191
|
+
engine = EmbeddedTestEngine(settings, db, privacy)
|
|
192
|
+
|
|
193
|
+
results = engine.run_tests(
|
|
194
|
+
target=target,
|
|
195
|
+
protocol=protocol,
|
|
196
|
+
firmware=firmware,
|
|
197
|
+
coverage_target=coverage_target,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
_display_results(results, "Embedded Tests")
|
|
201
|
+
|
|
202
|
+
# Auto-generate industrial Excel report
|
|
203
|
+
_auto_generate_report(results, "embedded")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@test_app.command("all")
|
|
207
|
+
def test_all(
|
|
208
|
+
config: str = typer.Option(".nextog/config.yaml", help="Project config file"),
|
|
209
|
+
coverage_target: int = typer.Option(90, help="Target coverage percentage"),
|
|
210
|
+
parallel: bool = typer.Option(True, help="Run tests in parallel"),
|
|
211
|
+
):
|
|
212
|
+
"""Run all configured tests"""
|
|
213
|
+
console.print(Panel("[bold green]Running All Tests[/bold green]"))
|
|
214
|
+
|
|
215
|
+
from nextog.core.runner import TestRunner
|
|
216
|
+
runner = TestRunner(settings, db, privacy, permissions)
|
|
217
|
+
|
|
218
|
+
results = runner.run_all(
|
|
219
|
+
config_path=config,
|
|
220
|
+
coverage_target=coverage_target,
|
|
221
|
+
parallel=parallel,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
_display_results(results, "All Tests")
|
|
225
|
+
|
|
226
|
+
# Auto-generate industrial Excel report
|
|
227
|
+
_auto_generate_report(results, "full")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# ──────────────────────────────────────────────────────────────
|
|
231
|
+
# COVERAGE Command - Coverage Tracking
|
|
232
|
+
# ──────────────────────────────────────────────────────────────
|
|
233
|
+
@app.command()
|
|
234
|
+
def coverage(
|
|
235
|
+
report: bool = typer.Option(False, help="Generate coverage report"),
|
|
236
|
+
format: str = typer.Option("table", help="Format: table, html, json, pdf"),
|
|
237
|
+
project: Optional[str] = typer.Option(None, help="Project name filter"),
|
|
238
|
+
):
|
|
239
|
+
"""View and manage test coverage"""
|
|
240
|
+
from nextog.core.reporter import CoverageReporter
|
|
241
|
+
|
|
242
|
+
reporter = CoverageReporter(db, settings)
|
|
243
|
+
data = reporter.get_coverage(project)
|
|
244
|
+
|
|
245
|
+
if report:
|
|
246
|
+
output = reporter.generate_report(data, format)
|
|
247
|
+
console.print(f"[green]✓[/green] Report generated: {output}")
|
|
248
|
+
else:
|
|
249
|
+
reporter.display_coverage(data)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ──────────────────────────────────────────────────────────────
|
|
253
|
+
# LIVE Command - Live Testing Panel
|
|
254
|
+
# ──────────────────────────────────────────────────────────────
|
|
255
|
+
@app.command()
|
|
256
|
+
def live(
|
|
257
|
+
port: int = typer.Option(8080, help="Dashboard port"),
|
|
258
|
+
host: str = typer.Option("127.0.0.1", help="Dashboard host"),
|
|
259
|
+
open_browser: bool = typer.Option(True, help="Open browser automatically"),
|
|
260
|
+
):
|
|
261
|
+
"""Start live testing dashboard"""
|
|
262
|
+
console.print(Panel(f"[bold]Live Dashboard starting on http://{host}:{port}[/bold]"))
|
|
263
|
+
|
|
264
|
+
from nextog.live.panel import LivePanel
|
|
265
|
+
panel = LivePanel(settings, db, privacy)
|
|
266
|
+
panel.start(host=host, port=port, open_browser=open_browser)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ──────────────────────────────────────────────────────────────
|
|
270
|
+
# USER Command - User & Permission Management
|
|
271
|
+
# ──────────────────────────────────────────────────────────────
|
|
272
|
+
user_app = typer.Typer(help="Manage users and permissions")
|
|
273
|
+
app.add_typer(user_app, name="user")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@user_app.command("create")
|
|
277
|
+
def user_create(
|
|
278
|
+
username: str = typer.Argument(..., help="Username"),
|
|
279
|
+
role: str = typer.Option("tester", help="Role: admin, tester, viewer, ci"),
|
|
280
|
+
email: Optional[str] = typer.Option(None, help="User email"),
|
|
281
|
+
):
|
|
282
|
+
"""Create a new user"""
|
|
283
|
+
permissions.create_user(username, role, email)
|
|
284
|
+
console.print(f"[green]✓[/green] User '{username}' created with role '{role}'")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@user_app.command("list")
|
|
288
|
+
def user_list():
|
|
289
|
+
"""List all users"""
|
|
290
|
+
users = permissions.list_users()
|
|
291
|
+
table = Table(title="Users")
|
|
292
|
+
table.add_column("Username", style="cyan")
|
|
293
|
+
table.add_column("Role", style="green")
|
|
294
|
+
table.add_column("Email", style="white")
|
|
295
|
+
table.add_column("Status", style="yellow")
|
|
296
|
+
|
|
297
|
+
for user in users:
|
|
298
|
+
table.add_row(user["username"], user["role"], user["email"] or "-", user["status"])
|
|
299
|
+
|
|
300
|
+
console.print(table)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@user_app.command("role")
|
|
304
|
+
def user_role(
|
|
305
|
+
username: str = typer.Argument(..., help="Username"),
|
|
306
|
+
role: str = typer.Argument(..., help="New role"),
|
|
307
|
+
):
|
|
308
|
+
"""Change user role"""
|
|
309
|
+
permissions.update_role(username, role)
|
|
310
|
+
console.print(f"[green]✓[/green] Role updated for '{username}' → '{role}'")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# ──────────────────────────────────────────────────────────────
|
|
314
|
+
# TRAIN Command - Auto Training
|
|
315
|
+
# ──────────────────────────────────────────────────────────────
|
|
316
|
+
@app.command()
|
|
317
|
+
def train(
|
|
318
|
+
action: str = typer.Option("status", help="Action: status, start, stop, optimize"),
|
|
319
|
+
iterations: int = typer.Option(100, help="Training iterations"),
|
|
320
|
+
):
|
|
321
|
+
"""Manage auto-training from user patterns"""
|
|
322
|
+
from nextog.training.learner import TrainingEngine
|
|
323
|
+
|
|
324
|
+
trainer = TrainingEngine(db, settings)
|
|
325
|
+
|
|
326
|
+
if action == "status":
|
|
327
|
+
status = trainer.get_status()
|
|
328
|
+
console.print(Panel(f"[bold]Training Status[/bold]"))
|
|
329
|
+
console.print(f" Patterns learned: {status['patterns']}")
|
|
330
|
+
console.print(f" Test cases optimized: {status['optimized']}")
|
|
331
|
+
console.print(f" Last trained: {status['last_trained']}")
|
|
332
|
+
console.print(f" Model accuracy: {status['accuracy']}%")
|
|
333
|
+
|
|
334
|
+
elif action == "start":
|
|
335
|
+
console.print("[bold]Starting training...[/bold]")
|
|
336
|
+
trainer.train(iterations=iterations)
|
|
337
|
+
console.print("[green]✓[/green] Training complete!")
|
|
338
|
+
|
|
339
|
+
elif action == "optimize":
|
|
340
|
+
console.print("[bold]Optimizing test cases...[/bold]")
|
|
341
|
+
trainer.optimize_tests()
|
|
342
|
+
console.print("[green]✓[/green] Optimization complete!")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# ──────────────────────────────────────────────────────────────
|
|
346
|
+
# DATA Command - Privacy & Data Management
|
|
347
|
+
# ──────────────────────────────────────────────────────────────
|
|
348
|
+
data_app = typer.Typer(help="Manage local data & privacy")
|
|
349
|
+
app.add_typer(data_app, name="data")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@data_app.command("export")
|
|
353
|
+
def data_export(
|
|
354
|
+
output: str = typer.Option("nextog-export.json", help="Output file"),
|
|
355
|
+
encrypt: bool = typer.Option(True, help="Encrypt exported data"),
|
|
356
|
+
):
|
|
357
|
+
"""Export all local data (privacy-first)"""
|
|
358
|
+
privacy.export_data(output, encrypt)
|
|
359
|
+
console.print(f"[green]✓[/green] Data exported to: {output}")
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@data_app.command("purge")
|
|
363
|
+
def data_purge(
|
|
364
|
+
confirm: bool = typer.Option(False, "--yes", help="Skip confirmation"),
|
|
365
|
+
):
|
|
366
|
+
"""Delete ALL local data permanently"""
|
|
367
|
+
if not confirm:
|
|
368
|
+
typer.confirm("⚠️ This will delete ALL local data. Continue?", abort=True)
|
|
369
|
+
|
|
370
|
+
privacy.purge_all_data()
|
|
371
|
+
console.print("[red]✓[/red] All data purged.")
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@data_app.command("stats")
|
|
375
|
+
def data_stats():
|
|
376
|
+
"""Show data storage statistics"""
|
|
377
|
+
stats = privacy.get_storage_stats()
|
|
378
|
+
console.print(Panel("[bold]Data Storage Stats[/bold]"))
|
|
379
|
+
console.print(f" Database size: {stats['db_size']}")
|
|
380
|
+
console.print(f" Test results: {stats['test_count']}")
|
|
381
|
+
console.print(f" Patterns: {stats['pattern_count']}")
|
|
382
|
+
console.print(f" Encryption: {'✓ AES-256' if stats['encrypted'] else '✗ None'}")
|
|
383
|
+
console.print(f" External sync: {'DISABLED' if not stats['external_sync'] else 'ENABLED'}")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# ──────────────────────────────────────────────────────────────
|
|
387
|
+
# ELEMENTS Command - System Element Detection
|
|
388
|
+
# ──────────────────────────────────────────────────────────────
|
|
389
|
+
@app.command()
|
|
390
|
+
def elements(
|
|
391
|
+
target: str = typer.Argument(..., help="Target URL or app path"),
|
|
392
|
+
explain: bool = typer.Option(True, help="Explain each element's use"),
|
|
393
|
+
output: Optional[str] = typer.Option(None, help="Save to file"),
|
|
394
|
+
):
|
|
395
|
+
"""Detect and explain system elements for testing"""
|
|
396
|
+
from nextog.engines.web.elements import ElementDetector
|
|
397
|
+
|
|
398
|
+
detector = ElementDetector(settings, db)
|
|
399
|
+
found = detector.detect(target, explain=explain)
|
|
400
|
+
|
|
401
|
+
table = Table(title=f"Detected Elements: {target}")
|
|
402
|
+
table.add_column("Element", style="cyan")
|
|
403
|
+
table.add_column("Type", style="green")
|
|
404
|
+
table.add_column("Description", style="white")
|
|
405
|
+
table.add_column("Test Priority", style="yellow")
|
|
406
|
+
|
|
407
|
+
for elem in found:
|
|
408
|
+
table.add_row(elem["name"], elem["type"], elem["description"], elem["priority"])
|
|
409
|
+
|
|
410
|
+
console.print(table)
|
|
411
|
+
|
|
412
|
+
if output:
|
|
413
|
+
detector.save_elements(found, output)
|
|
414
|
+
console.print(f"[green]✓[/green] Elements saved to: {output}")
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
# ──────────────────────────────────────────────────────────────
|
|
418
|
+
# REPORT Command - Generate Excel Reports
|
|
419
|
+
# ──────────────────────────────────────────────────────────────
|
|
420
|
+
@app.command()
|
|
421
|
+
def report(
|
|
422
|
+
test_type: str = typer.Option("auto", "--type", "-t", help="Test type: web, mobile, api, embedded, full, auto"),
|
|
423
|
+
last: bool = typer.Option(True, "--last", help="Generate report for last test run"),
|
|
424
|
+
output: Optional[str] = typer.Option(None, "--output", "-o", help="Custom output path"),
|
|
425
|
+
open_file: bool = typer.Option(True, "--open", help="Open report after generation"),
|
|
426
|
+
):
|
|
427
|
+
"""Generate industrial Excel report from test results"""
|
|
428
|
+
console.print(Panel("[bold blue]📊 Generating Industrial Excel Report...[/bold blue]"))
|
|
429
|
+
|
|
430
|
+
# Get latest results from DB
|
|
431
|
+
results = db.get_latest_results()
|
|
432
|
+
|
|
433
|
+
if not results:
|
|
434
|
+
console.print("[yellow]⚠ No test results found. Run tests first.[/yellow]")
|
|
435
|
+
console.print(" → nextog test web --url <url>")
|
|
436
|
+
console.print(" → nextog test mobile --app <app>")
|
|
437
|
+
console.print(" → nextog test api --spec <spec>")
|
|
438
|
+
raise typer.Exit(1)
|
|
439
|
+
|
|
440
|
+
with Progress(
|
|
441
|
+
SpinnerColumn(),
|
|
442
|
+
TextColumn("[progress.description]{task.description}"),
|
|
443
|
+
console=console,
|
|
444
|
+
) as progress:
|
|
445
|
+
task = progress.add_task("Building Excel report...", total=None)
|
|
446
|
+
|
|
447
|
+
filepath = report_generator.generate_after_test(
|
|
448
|
+
test_results=results.get("results", results),
|
|
449
|
+
test_type=test_type if test_type != "auto" else results.get("engine", "auto"),
|
|
450
|
+
)
|
|
451
|
+
progress.update(task, completed=True)
|
|
452
|
+
|
|
453
|
+
console.print(f"\n[green]✅ Report Generated Successfully![/green]")
|
|
454
|
+
console.print(f" 📄 File: [bold]{filepath}[/bold]")
|
|
455
|
+
console.print(f" 📊 Sheets: 10 (Summary, Details, Coverage, Elements, Defects, Performance, Charts, Recommendations, Environment, Sign-Off)")
|
|
456
|
+
console.print(f" 🔐 Privacy: [green]LOCAL ONLY — No data transferred[/green]")
|
|
457
|
+
|
|
458
|
+
if open_file:
|
|
459
|
+
try:
|
|
460
|
+
import subprocess, platform
|
|
461
|
+
if platform.system() == "Darwin":
|
|
462
|
+
subprocess.run(["open", filepath])
|
|
463
|
+
elif platform.system() == "Windows":
|
|
464
|
+
subprocess.run(["start", filepath], shell=True)
|
|
465
|
+
else:
|
|
466
|
+
subprocess.run(["xdg-open", filepath])
|
|
467
|
+
except Exception:
|
|
468
|
+
pass
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# ──────────────────────────────────────────────────────────────
|
|
472
|
+
# Helper Functions
|
|
473
|
+
# ──────────────────────────────────────────────────────────────
|
|
474
|
+
def _auto_generate_report(results: dict, test_type: str):
|
|
475
|
+
"""Auto-generate industrial Excel report after any test"""
|
|
476
|
+
try:
|
|
477
|
+
with Progress(
|
|
478
|
+
SpinnerColumn(),
|
|
479
|
+
TextColumn("[progress.description]{task.description}"),
|
|
480
|
+
console=console,
|
|
481
|
+
) as progress:
|
|
482
|
+
task = progress.add_task("📊 Generating Excel report...", total=None)
|
|
483
|
+
|
|
484
|
+
filepath = report_generator.generate_after_test(
|
|
485
|
+
test_results=results,
|
|
486
|
+
test_type=test_type,
|
|
487
|
+
)
|
|
488
|
+
progress.update(task, completed=True)
|
|
489
|
+
|
|
490
|
+
console.print(f"\n[green]✅ Excel Report Auto-Generated![/green]")
|
|
491
|
+
console.print(f" 📄 [bold]{filepath}[/bold]")
|
|
492
|
+
console.print(f" 📊 10 Sheets: Summary | Details | Coverage | Elements | Defects | Performance | Charts | Recommendations | Environment | Sign-Off")
|
|
493
|
+
console.print(f" 🔐 All data stored locally — Zero external transfer")
|
|
494
|
+
|
|
495
|
+
# Try to auto-open the report
|
|
496
|
+
try:
|
|
497
|
+
import subprocess, platform as plat
|
|
498
|
+
if plat.system() == "Darwin":
|
|
499
|
+
subprocess.run(["open", filepath], timeout=3)
|
|
500
|
+
elif plat.system() == "Windows":
|
|
501
|
+
subprocess.run(["start", filepath], shell=True, timeout=3)
|
|
502
|
+
else:
|
|
503
|
+
subprocess.run(["xdg-open", filepath], timeout=3)
|
|
504
|
+
except Exception:
|
|
505
|
+
pass
|
|
506
|
+
|
|
507
|
+
except Exception as e:
|
|
508
|
+
console.print(f"[yellow]⚠ Could not generate Excel report: {e}[/yellow]")
|
|
509
|
+
console.print("[dim]Install openpyxl: pip install openpyxl[/dim]")
|
|
510
|
+
def _display_results(results: dict, title: str):
|
|
511
|
+
"""Display test results in a formatted table"""
|
|
512
|
+
table = Table(title=f"📊 {title} Results")
|
|
513
|
+
table.add_column("Category", style="cyan")
|
|
514
|
+
table.add_column("Total", style="white")
|
|
515
|
+
table.add_column("Passed", style="green")
|
|
516
|
+
table.add_column("Failed", style="red")
|
|
517
|
+
table.add_column("Skipped", style="yellow")
|
|
518
|
+
table.add_column("Coverage", style="bold blue")
|
|
519
|
+
|
|
520
|
+
for category, data in results.get("categories", {}).items():
|
|
521
|
+
table.add_row(
|
|
522
|
+
category,
|
|
523
|
+
str(data["total"]),
|
|
524
|
+
str(data["passed"]),
|
|
525
|
+
str(data["failed"]),
|
|
526
|
+
str(data["skipped"]),
|
|
527
|
+
f"{data['coverage']}%",
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
console.print(table)
|
|
531
|
+
|
|
532
|
+
# Summary
|
|
533
|
+
total_coverage = results.get("total_coverage", 0)
|
|
534
|
+
console.print(f"\n[bold]Total Coverage: {total_coverage}%[/bold]")
|
|
535
|
+
|
|
536
|
+
if total_coverage >= 80:
|
|
537
|
+
console.print("[green]✅ Excellent coverage![/green]")
|
|
538
|
+
elif total_coverage >= 60:
|
|
539
|
+
console.print("[yellow]⚠️ Good coverage, room for improvement[/yellow]")
|
|
540
|
+
else:
|
|
541
|
+
console.print("[red]❌ Low coverage - add more tests[/red]")
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
if __name__ == "__main__":
|
|
545
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Configuration modules"""
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings Manager - Configuration management for nextOG CLI
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Settings:
|
|
11
|
+
"""Application settings management"""
|
|
12
|
+
|
|
13
|
+
DEFAULTS = {
|
|
14
|
+
"project": "default",
|
|
15
|
+
"version": "1.0",
|
|
16
|
+
"verbose": False,
|
|
17
|
+
"parallel": True,
|
|
18
|
+
"coverage_target": 90,
|
|
19
|
+
"headless": True,
|
|
20
|
+
"default_browser": "chromium",
|
|
21
|
+
"default_platform": "android",
|
|
22
|
+
"default_protocol": "mqtt",
|
|
23
|
+
"live_panel": {
|
|
24
|
+
"host": "127.0.0.1",
|
|
25
|
+
"port": 8080,
|
|
26
|
+
},
|
|
27
|
+
"privacy": {
|
|
28
|
+
"encrypt_local": True,
|
|
29
|
+
"external_sync": False, # NEVER enable
|
|
30
|
+
"auto_backup": True,
|
|
31
|
+
"backup_count": 5,
|
|
32
|
+
},
|
|
33
|
+
"training": {
|
|
34
|
+
"auto_train": True,
|
|
35
|
+
"iterations": 100,
|
|
36
|
+
"pattern_threshold": 3,
|
|
37
|
+
},
|
|
38
|
+
"reporting": {
|
|
39
|
+
"format": "html",
|
|
40
|
+
"include_screenshots": True,
|
|
41
|
+
"include_logs": True,
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"web": {
|
|
45
|
+
"browser": "chromium",
|
|
46
|
+
"responsive": True,
|
|
47
|
+
"accessibility": True,
|
|
48
|
+
"performance": False,
|
|
49
|
+
"viewports": ["mobile", "tablet", "desktop"],
|
|
50
|
+
},
|
|
51
|
+
"mobile": {
|
|
52
|
+
"platform": "android",
|
|
53
|
+
"device": "emulator",
|
|
54
|
+
"appium_url": "http://127.0.0.1:4723",
|
|
55
|
+
},
|
|
56
|
+
"api": {
|
|
57
|
+
"timeout": 30,
|
|
58
|
+
"retry_count": 3,
|
|
59
|
+
"verify_ssl": True,
|
|
60
|
+
},
|
|
61
|
+
"embedded": {
|
|
62
|
+
"protocol": "mqtt",
|
|
63
|
+
"timeout": 10,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self._config: Dict[str, Any] = dict(self.DEFAULTS)
|
|
70
|
+
self._load_global_config()
|
|
71
|
+
|
|
72
|
+
def _load_global_config(self):
|
|
73
|
+
"""Load global config from ~/.nextog/config.yaml"""
|
|
74
|
+
global_config = Path.home() / ".nextog" / "config.yaml"
|
|
75
|
+
if global_config.exists():
|
|
76
|
+
with open(global_config) as f:
|
|
77
|
+
user_config = yaml.safe_load(f) or {}
|
|
78
|
+
self._deep_merge(self._config, user_config)
|
|
79
|
+
|
|
80
|
+
def load_from_file(self, path: Path):
|
|
81
|
+
"""Load config from file"""
|
|
82
|
+
if path.exists():
|
|
83
|
+
with open(path) as f:
|
|
84
|
+
file_config = yaml.safe_load(f) or {}
|
|
85
|
+
self._deep_merge(self._config, file_config)
|
|
86
|
+
|
|
87
|
+
def save_to_file(self, path: Path):
|
|
88
|
+
"""Save config to file"""
|
|
89
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
with open(path, "w") as f:
|
|
91
|
+
yaml.dump(self._config, f, default_flow_style=False)
|
|
92
|
+
|
|
93
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
94
|
+
"""Get a config value by dot-separated key"""
|
|
95
|
+
keys = key.split(".")
|
|
96
|
+
value = self._config
|
|
97
|
+
|
|
98
|
+
for k in keys:
|
|
99
|
+
if isinstance(value, dict) and k in value:
|
|
100
|
+
value = value[k]
|
|
101
|
+
else:
|
|
102
|
+
return default
|
|
103
|
+
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
def set(self, key: str, value: Any):
|
|
107
|
+
"""Set a config value"""
|
|
108
|
+
keys = key.split(".")
|
|
109
|
+
config = self._config
|
|
110
|
+
|
|
111
|
+
for k in keys[:-1]:
|
|
112
|
+
if k not in config:
|
|
113
|
+
config[k] = {}
|
|
114
|
+
config = config[k]
|
|
115
|
+
|
|
116
|
+
config[keys[-1]] = value
|
|
117
|
+
|
|
118
|
+
def set_verbose(self, verbose: bool):
|
|
119
|
+
"""Set verbose mode"""
|
|
120
|
+
self._config["verbose"] = verbose
|
|
121
|
+
|
|
122
|
+
def to_dict(self) -> Dict:
|
|
123
|
+
"""Get all config as dict"""
|
|
124
|
+
return dict(self._config)
|
|
125
|
+
|
|
126
|
+
def _deep_merge(self, base: Dict, override: Dict):
|
|
127
|
+
"""Deep merge override into base"""
|
|
128
|
+
for key, value in override.items():
|
|
129
|
+
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
|
130
|
+
self._deep_merge(base[key], value)
|
|
131
|
+
else:
|
|
132
|
+
base[key] = value
|
nextog/core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core modules for nextOG CLI"""
|