cursorflow 2.6.3__py3-none-any.whl → 2.7.2__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.
@@ -0,0 +1,518 @@
1
+ """
2
+ Data Presenter - AI-Optimized Data Organization
3
+
4
+ Presents test data in structured markdown format optimized for AI consumption.
5
+ Pure data organization without analysis or recommendations.
6
+ """
7
+
8
+ from pathlib import Path
9
+ from typing import Dict, Any
10
+ import json
11
+ from datetime import datetime
12
+
13
+
14
+ class DataPresenter:
15
+ """
16
+ Generates AI-optimized markdown presentation of test data.
17
+
18
+ Philosophy: Organize and present raw data clearly for AI analysis.
19
+ No subjective recommendations, no analysis - just structured information.
20
+ """
21
+
22
+ def generate_data_digest(
23
+ self,
24
+ session_dir: Path,
25
+ results: Dict[str, Any]
26
+ ) -> str:
27
+ """
28
+ Generate AI-optimized data digest from test results.
29
+
30
+ Args:
31
+ session_dir: Path to session directory with split data files
32
+ results: Original complete results dictionary
33
+
34
+ Returns:
35
+ Markdown string with organized data presentation
36
+ """
37
+ # Load split data files
38
+ summary = self._load_json(session_dir / "summary.json")
39
+ errors = self._load_json(session_dir / "errors.json")
40
+ network = self._load_json(session_dir / "network.json")
41
+ console = self._load_json(session_dir / "console.json")
42
+ performance = self._load_json(session_dir / "performance.json")
43
+ server_logs = self._load_json(session_dir / "server_logs.json")
44
+ screenshots = self._load_json(session_dir / "screenshots.json")
45
+
46
+ # Optional data files
47
+ mockup = self._load_json(session_dir / "mockup_comparison.json")
48
+ responsive = self._load_json(session_dir / "responsive_results.json")
49
+ css_iterations = self._load_json(session_dir / "css_iterations.json")
50
+
51
+ # Build markdown digest
52
+ digest = self._build_header(summary)
53
+ digest += self._build_quick_stats(summary, errors, network, console, server_logs)
54
+ digest += self._build_errors_section(errors, session_dir)
55
+ digest += self._build_network_section(network, session_dir)
56
+ digest += self._build_console_section(console, session_dir)
57
+ digest += self._build_server_logs_section(server_logs, session_dir)
58
+ digest += self._build_screenshots_section(screenshots, session_dir)
59
+
60
+ # Optional sections
61
+ if mockup:
62
+ digest += self._build_mockup_section(mockup, session_dir)
63
+ if responsive:
64
+ digest += self._build_responsive_section(responsive, session_dir)
65
+ if css_iterations and css_iterations.get('total_iterations', 0) > 0:
66
+ digest += self._build_css_iterations_section(css_iterations, session_dir)
67
+
68
+ digest += self._build_performance_section(performance, session_dir)
69
+ digest += self._build_data_references(session_dir)
70
+ digest += self._build_metadata(summary)
71
+
72
+ return digest
73
+
74
+ def _build_header(self, summary: Dict) -> str:
75
+ """Build document header with basic info"""
76
+ session_id = summary.get('session_id', 'unknown')
77
+ timestamp = summary.get('timestamp', '')
78
+ success = summary.get('success', False)
79
+
80
+ status_emoji = "✅" if success else "⚠️"
81
+ status_text = "Completed" if success else "Needs Attention"
82
+
83
+ return f"""# CursorFlow Test Data Digest
84
+
85
+ **Session**: `{session_id}`
86
+ **Timestamp**: {timestamp}
87
+ **Status**: {status_emoji} {status_text}
88
+ **Execution Time**: {summary.get('execution_time', 0):.2f}s
89
+
90
+ ---
91
+
92
+ """
93
+
94
+ def _build_quick_stats(
95
+ self,
96
+ summary: Dict,
97
+ errors: Dict,
98
+ network: Dict,
99
+ console: Dict,
100
+ server_logs: Dict
101
+ ) -> str:
102
+ """Build quick statistics table"""
103
+ metrics = summary.get('metrics', {})
104
+
105
+ return f"""## Quick Statistics
106
+
107
+ | Metric | Value | Status |
108
+ |--------|-------|--------|
109
+ | DOM Elements | {metrics.get('total_dom_elements', 0)} | ℹ️ Data |
110
+ | Network Requests | {metrics.get('total_network_requests', 0)} | ℹ️ Data |
111
+ | Failed Requests | {metrics.get('failed_network_requests', 0)} | {"⚠️ Review" if metrics.get('failed_network_requests', 0) > 0 else "✅ OK"} |
112
+ | Console Errors | {metrics.get('total_errors', 0)} | {"🚨 Review" if metrics.get('total_errors', 0) > 0 else "✅ OK"} |
113
+ | Console Warnings | {metrics.get('total_warnings', 0)} | {"⚠️ Review" if metrics.get('total_warnings', 0) > 0 else "✅ OK"} |
114
+ | Server Logs | {server_logs.get('total_logs', 0)} | ℹ️ Data |
115
+ | Server Errors | {len(server_logs.get('logs_by_severity', {}).get('error', []))} | {"🚨 Review" if len(server_logs.get('logs_by_severity', {}).get('error', [])) > 0 else "✅ OK"} |
116
+ | Total Messages | {console.get('total_messages', 0)} | ℹ️ Data |
117
+ | Screenshots | {metrics.get('total_screenshots', 0)} | ℹ️ Data |
118
+ | Timeline Events | {metrics.get('total_timeline_events', 0)} | ℹ️ Data |
119
+
120
+ ---
121
+
122
+ """
123
+
124
+ def _build_errors_section(self, errors: Dict, session_dir: Path) -> str:
125
+ """Build errors section with categorization"""
126
+ total_errors = errors.get('total_errors', 0)
127
+
128
+ if total_errors == 0:
129
+ return """## Console Errors
130
+
131
+ ✅ **No console errors detected**
132
+
133
+ ---
134
+
135
+ """
136
+
137
+ section = f"""## Console Errors
138
+
139
+ **Total Errors**: {total_errors}
140
+ **Unique Error Types**: {errors.get('summary', {}).get('unique_error_types', 0)}
141
+
142
+ ### Errors by Type
143
+
144
+ """
145
+
146
+ # Organize by error type
147
+ errors_by_type = errors.get('errors_by_type', {})
148
+ for error_type, error_list in errors_by_type.items():
149
+ section += f"""#### {error_type.replace('_', ' ').title()} ({len(error_list)})\n\n"""
150
+
151
+ # Show first 3 errors of each type
152
+ for i, error in enumerate(error_list[:3], 1):
153
+ section += f"""**Error #{i}**
154
+ - **Message**: `{error.get('message', 'Unknown')[:200]}`
155
+ - **Source**: `{error.get('source', 'Unknown')}`
156
+ - **Location**: Line {error.get('line', '?')}, Column {error.get('column', '?')}
157
+ - **Screenshot**: `{error.get('screenshot_name', 'unknown')}`
158
+ - **URL**: `{error.get('url', '')[:100]}`
159
+
160
+ """
161
+
162
+ if len(error_list) > 3:
163
+ section += f"*...and {len(error_list) - 3} more errors of this type*\n\n"
164
+
165
+ section += f"""**Full Error Data**: See `{session_dir.name}/errors.json` for complete error details and stack traces.
166
+
167
+ ---
168
+
169
+ """
170
+ return section
171
+
172
+ def _build_network_section(self, network: Dict, session_dir: Path) -> str:
173
+ """Build network requests section"""
174
+ total_requests = network.get('total_requests', 0)
175
+ failed_requests = network.get('failed_requests', [])
176
+
177
+ section = f"""## Network Activity
178
+
179
+ **Total Requests**: {total_requests}
180
+ **Failed Requests**: {len(failed_requests)}
181
+ **Success Rate**: {network.get('summary', {}).get('success_rate', 100):.1f}%
182
+
183
+ """
184
+
185
+ if failed_requests:
186
+ section += f"""### Failed Requests ({len(failed_requests)})
187
+
188
+ """
189
+ # Group by status code
190
+ by_status = {}
191
+ for req in failed_requests:
192
+ status = req.get('status_code', 0)
193
+ if status not in by_status:
194
+ by_status[status] = []
195
+ by_status[status].append(req)
196
+
197
+ for status_code in sorted(by_status.keys()):
198
+ requests = by_status[status_code]
199
+ section += f"""#### HTTP {status_code} ({len(requests)} requests)
200
+
201
+ """
202
+ for i, req in enumerate(requests[:5], 1):
203
+ section += f"""**Request #{i}**
204
+ - **URL**: `{req.get('url', 'Unknown')[:100]}`
205
+ - **Method**: {req.get('method', 'GET')}
206
+ - **Status**: {req.get('status_code', 0)}
207
+ - **Screenshot**: `{req.get('screenshot_name', 'unknown')}`
208
+
209
+ """
210
+
211
+ if len(requests) > 5:
212
+ section += f"*...and {len(requests) - 5} more {status_code} errors*\n\n"
213
+ else:
214
+ section += "✅ **All network requests successful**\n\n"
215
+
216
+ section += f"""**Full Network Data**: See `{session_dir.name}/network.json` for complete request/response details.
217
+
218
+ ---
219
+
220
+ """
221
+ return section
222
+
223
+ def _build_console_section(self, console: Dict, session_dir: Path) -> str:
224
+ """Build console messages section"""
225
+ total_messages = console.get('total_messages', 0)
226
+ messages_by_type = console.get('messages_by_type', {})
227
+
228
+ section = f"""## Console Messages
229
+
230
+ **Total Messages**: {total_messages}
231
+
232
+ """
233
+
234
+ if total_messages == 0:
235
+ return section + "No console messages captured.\n\n---\n\n"
236
+
237
+ # Show counts by type
238
+ section += "### Message Breakdown\n\n"
239
+ for msg_type, messages in messages_by_type.items():
240
+ emoji = {
241
+ 'errors': '🚨',
242
+ 'warnings': '⚠️',
243
+ 'logs': '📝',
244
+ 'info': 'ℹ️'
245
+ }.get(msg_type, '📋')
246
+
247
+ section += f"- {emoji} **{msg_type.title()}**: {len(messages)}\n"
248
+
249
+ section += f"""\n**Full Console Data**: See `{session_dir.name}/console.json` for all console messages.
250
+
251
+ ---
252
+
253
+ """
254
+ return section
255
+
256
+ def _build_performance_section(self, performance: Dict, session_dir: Path) -> str:
257
+ """Build performance metrics section"""
258
+ summary = performance.get('summary', {})
259
+ execution_time = performance.get('execution_time', 0)
260
+
261
+ section = f"""## Performance Metrics
262
+
263
+ **Test Execution Time**: {execution_time:.2f}s
264
+
265
+ """
266
+
267
+ if summary:
268
+ avg_load = summary.get('average_page_load_time', 0)
269
+ max_memory = summary.get('max_memory_usage', 0)
270
+
271
+ section += f"""### Page Performance
272
+
273
+ - **Average Load Time**: {avg_load:.1f}ms
274
+ - **Max Memory Usage**: {max_memory:.1f}MB
275
+ - **Min Memory Usage**: {summary.get('min_memory_usage', 0):.1f}MB
276
+
277
+ """
278
+
279
+ section += f"""**Full Performance Data**: See `{session_dir.name}/performance.json` for detailed metrics.
280
+
281
+ ---
282
+
283
+ """
284
+ return section
285
+
286
+ def _build_data_references(self, session_dir: Path) -> str:
287
+ """Build section with references to all data files"""
288
+ return f"""## Complete Data Files
289
+
290
+ All comprehensive data available in: `{session_dir.name}/`
291
+
292
+ ### Structured Data Files
293
+
294
+ | File | Description | Use Case |
295
+ |------|-------------|----------|
296
+ | `summary.json` | High-level metrics and counts | Quick overview, status checking |
297
+ | `errors.json` | All console errors with context | Error analysis, debugging |
298
+ | `network.json` | Complete network request/response data | API debugging, performance analysis |
299
+ | `console.json` | All console messages (errors, warnings, logs) | Application flow analysis |
300
+ | `server_logs.json` | Server-side logs (SSH/local/Docker) | Backend correlation, server debugging |
301
+ | `dom_analysis.json` | Complete DOM structure and elements | UI analysis, element inspection |
302
+ | `performance.json` | Performance metrics and timing | Performance optimization |
303
+ | `timeline.json` | Chronological event timeline | Understanding test flow, correlation |
304
+ | `screenshots.json` | Screenshot metadata and index | Screenshot navigation, filtering |
305
+
306
+ ### Optional Data Files
307
+
308
+ | File | Description | When Present |
309
+ |------|-------------|--------------|
310
+ | `mockup_comparison.json` | Mockup vs implementation comparison | When using `compare-mockup` |
311
+ | `responsive_results.json` | Multi-viewport testing results | When using `--responsive` flag |
312
+ | `css_iterations.json` | CSS iteration history | When using `css_iteration_session()` |
313
+
314
+ ### Artifact Directories
315
+
316
+ | Directory | Contents |
317
+ |-----------|----------|
318
+ | `screenshots/` | Visual captures at key moments |
319
+ | `traces/` | Playwright trace files (open with: `playwright show-trace`) |
320
+
321
+ ---
322
+
323
+ """
324
+
325
+ def _build_metadata(self, summary: Dict) -> str:
326
+ """Build metadata section"""
327
+ return f"""## Metadata
328
+
329
+ ```json
330
+ {{
331
+ "session_id": "{summary.get('session_id', 'unknown')}",
332
+ "timestamp": "{summary.get('timestamp', '')}",
333
+ "success": {str(summary.get('success', False)).lower()},
334
+ "execution_time": {summary.get('execution_time', 0)},
335
+ "has_errors": {str(summary.get('status', {}).get('has_errors', False)).lower()},
336
+ "has_network_failures": {str(summary.get('status', {}).get('has_network_failures', False)).lower()},
337
+ "has_warnings": {str(summary.get('status', {}).get('has_warnings', False)).lower()}
338
+ }}
339
+ ```
340
+
341
+ ---
342
+
343
+ **Generated by CursorFlow v2.7.0** - AI-Optimized Data Collection
344
+ **Format**: Multi-file structured output for AI consumption
345
+ **Philosophy**: Pure data organization, no analysis - AI does the thinking
346
+ """
347
+
348
+ def _build_server_logs_section(self, server_logs: Dict, session_dir: Path) -> str:
349
+ """Build server logs section"""
350
+ total_logs = server_logs.get('total_logs', 0)
351
+
352
+ if total_logs == 0:
353
+ return """## Server Logs
354
+
355
+ ✅ **No server logs captured** (log monitoring may not be configured)
356
+
357
+ ---
358
+
359
+ """
360
+
361
+ section = f"""## Server Logs
362
+
363
+ **Total Server Logs**: {total_logs}
364
+
365
+ ### Server Logs by Severity
366
+
367
+ """
368
+
369
+ logs_by_severity = server_logs.get('logs_by_severity', {})
370
+ for severity, logs in logs_by_severity.items():
371
+ emoji = {
372
+ 'error': '🚨',
373
+ 'warning': '⚠️',
374
+ 'info': 'ℹ️',
375
+ 'debug': '🔍'
376
+ }.get(severity.lower(), '📝')
377
+
378
+ section += f"- {emoji} **{severity.title()}**: {len(logs)}\n"
379
+
380
+ # Show error logs if present
381
+ error_logs = logs_by_severity.get('error', [])
382
+ if error_logs:
383
+ section += f"""\n### Server Error Logs ({len(error_logs)})
384
+
385
+ """
386
+ for i, log in enumerate(error_logs[:5], 1):
387
+ section += f"""**Log #{i}**
388
+ - **Content**: `{log.get('content', 'Unknown')[:150]}`
389
+ - **Source**: {log.get('source', 'unknown')}
390
+ - **File**: `{log.get('file', 'unknown')}`
391
+ - **Timestamp**: {log.get('timestamp', 0)}
392
+
393
+ """
394
+
395
+ if len(error_logs) > 5:
396
+ section += f"*...and {len(error_logs) - 5} more server errors*\n\n"
397
+
398
+ section += f"""**Full Server Log Data**: See `{session_dir.name}/server_logs.json` for all server logs.
399
+
400
+ ---
401
+
402
+ """
403
+ return section
404
+
405
+ def _build_screenshots_section(self, screenshots: Dict, session_dir: Path) -> str:
406
+ """Build screenshots section"""
407
+ total = screenshots.get('total_screenshots', 0)
408
+
409
+ if total == 0:
410
+ return ""
411
+
412
+ section = f"""## Screenshots
413
+
414
+ **Total Screenshots**: {total}
415
+
416
+ """
417
+
418
+ screenshot_list = screenshots.get('screenshots', [])
419
+ screenshots_with_errors = [s for s in screenshot_list if s.get('has_errors')]
420
+ screenshots_with_network_failures = [s for s in screenshot_list if s.get('has_network_failures')]
421
+
422
+ if screenshots_with_errors:
423
+ section += f"- 🚨 **With Console Errors**: {len(screenshots_with_errors)}\n"
424
+ if screenshots_with_network_failures:
425
+ section += f"- ⚠️ **With Network Failures**: {len(screenshots_with_network_failures)}\n"
426
+
427
+ section += f"""\n**Screenshot Index**: See `{session_dir.name}/screenshots.json` for complete metadata.
428
+ **Screenshot Files**: See `{session_dir.name}/screenshots/` directory.
429
+
430
+ ---
431
+
432
+ """
433
+ return section
434
+
435
+ def _build_mockup_section(self, mockup: Dict, session_dir: Path) -> str:
436
+ """Build mockup comparison section"""
437
+ similarity = mockup.get('similarity_score', 0)
438
+
439
+ section = f"""## Mockup Comparison
440
+
441
+ **Mockup URL**: `{mockup.get('mockup_url', 'N/A')}`
442
+ **Implementation URL**: `{mockup.get('implementation_url', 'N/A')}`
443
+ **Similarity Score**: {similarity:.1f}%
444
+
445
+ """
446
+
447
+ differences = mockup.get('differences', [])
448
+ if differences:
449
+ section += f"**Differences Detected**: {len(differences)}\n\n"
450
+
451
+ section += f"""**Full Mockup Data**: See `{session_dir.name}/mockup_comparison.json` for detailed comparison.
452
+
453
+ ---
454
+
455
+ """
456
+ return section
457
+
458
+ def _build_responsive_section(self, responsive: Dict, session_dir: Path) -> str:
459
+ """Build responsive testing section"""
460
+ viewports = responsive.get('viewports', {})
461
+
462
+ section = f"""## Responsive Testing
463
+
464
+ **Viewports Tested**: {len(viewports)}
465
+
466
+ """
467
+
468
+ for viewport_name, viewport_data in viewports.items():
469
+ errors = viewport_data.get('errors', 0)
470
+ network_failures = viewport_data.get('network_failures', 0)
471
+
472
+ section += f"- **{viewport_name.title()}**: "
473
+ if errors > 0 or network_failures > 0:
474
+ section += f"{errors} errors, {network_failures} network failures\n"
475
+ else:
476
+ section += "✅ OK\n"
477
+
478
+ section += f"""\n**Full Responsive Data**: See `{session_dir.name}/responsive_results.json` for all viewport results.
479
+
480
+ ---
481
+
482
+ """
483
+ return section
484
+
485
+ def _build_css_iterations_section(self, css_iterations: Dict, session_dir: Path) -> str:
486
+ """Build CSS iterations section"""
487
+ total = css_iterations.get('total_iterations', 0)
488
+
489
+ section = f"""## CSS Iterations
490
+
491
+ **Total Iterations**: {total}
492
+
493
+ """
494
+
495
+ iterations = css_iterations.get('iterations', [])
496
+ for i, iteration in enumerate(iterations[:5], 1):
497
+ section += f"- **Iteration {i}**: {iteration.get('name', 'unnamed')}\n"
498
+
499
+ if len(iterations) > 5:
500
+ section += f"*...and {len(iterations) - 5} more iterations*\n"
501
+
502
+ section += f"""\n**Full CSS Iteration Data**: See `{session_dir.name}/css_iterations.json` for all iterations.
503
+
504
+ ---
505
+
506
+ """
507
+ return section
508
+
509
+ def _load_json(self, path: Path) -> Dict:
510
+ """Load JSON file, return empty dict if not found"""
511
+ try:
512
+ with open(path, 'r', encoding='utf-8') as f:
513
+ return json.load(f)
514
+ except FileNotFoundError:
515
+ return {}
516
+ except json.JSONDecodeError:
517
+ return {}
518
+