agentic-qe 2.0.0 → 2.1.1
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.
- package/.claude/agents/qx-partner.md +17 -4
- package/.claude/skills/accessibility-testing/SKILL.md +144 -692
- package/.claude/skills/agentic-quality-engineering/SKILL.md +176 -529
- package/.claude/skills/api-testing-patterns/SKILL.md +180 -560
- package/.claude/skills/brutal-honesty-review/SKILL.md +113 -603
- package/.claude/skills/bug-reporting-excellence/SKILL.md +116 -517
- package/.claude/skills/chaos-engineering-resilience/SKILL.md +127 -72
- package/.claude/skills/cicd-pipeline-qe-orchestrator/SKILL.md +209 -404
- package/.claude/skills/code-review-quality/SKILL.md +158 -608
- package/.claude/skills/compatibility-testing/SKILL.md +148 -38
- package/.claude/skills/compliance-testing/SKILL.md +132 -63
- package/.claude/skills/consultancy-practices/SKILL.md +114 -446
- package/.claude/skills/context-driven-testing/SKILL.md +117 -381
- package/.claude/skills/contract-testing/SKILL.md +176 -141
- package/.claude/skills/database-testing/SKILL.md +137 -130
- package/.claude/skills/exploratory-testing-advanced/SKILL.md +160 -629
- package/.claude/skills/holistic-testing-pact/SKILL.md +140 -188
- package/.claude/skills/localization-testing/SKILL.md +145 -33
- package/.claude/skills/mobile-testing/SKILL.md +132 -448
- package/.claude/skills/mutation-testing/SKILL.md +147 -41
- package/.claude/skills/performance-testing/SKILL.md +200 -546
- package/.claude/skills/quality-metrics/SKILL.md +164 -519
- package/.claude/skills/refactoring-patterns/SKILL.md +132 -699
- package/.claude/skills/regression-testing/SKILL.md +120 -926
- package/.claude/skills/risk-based-testing/SKILL.md +157 -660
- package/.claude/skills/security-testing/SKILL.md +199 -538
- package/.claude/skills/sherlock-review/SKILL.md +163 -699
- package/.claude/skills/shift-left-testing/SKILL.md +161 -465
- package/.claude/skills/shift-right-testing/SKILL.md +161 -519
- package/.claude/skills/six-thinking-hats/SKILL.md +175 -1110
- package/.claude/skills/skills-manifest.json +71 -20
- package/.claude/skills/tdd-london-chicago/SKILL.md +131 -448
- package/.claude/skills/technical-writing/SKILL.md +103 -154
- package/.claude/skills/test-automation-strategy/SKILL.md +166 -772
- package/.claude/skills/test-data-management/SKILL.md +126 -910
- package/.claude/skills/test-design-techniques/SKILL.md +179 -89
- package/.claude/skills/test-environment-management/SKILL.md +136 -91
- package/.claude/skills/test-reporting-analytics/SKILL.md +169 -92
- package/.claude/skills/testability-scoring/SKILL.md +172 -538
- package/.claude/skills/testability-scoring/scripts/generate-html-report.js +0 -0
- package/.claude/skills/visual-testing-advanced/SKILL.md +155 -78
- package/.claude/skills/xp-practices/SKILL.md +151 -587
- package/CHANGELOG.md +86 -0
- package/README.md +23 -16
- package/dist/agents/QXPartnerAgent.d.ts +47 -1
- package/dist/agents/QXPartnerAgent.d.ts.map +1 -1
- package/dist/agents/QXPartnerAgent.js +2086 -125
- package/dist/agents/QXPartnerAgent.js.map +1 -1
- package/dist/agents/lifecycle/AgentLifecycleManager.d.ts.map +1 -1
- package/dist/agents/lifecycle/AgentLifecycleManager.js +34 -31
- package/dist/agents/lifecycle/AgentLifecycleManager.js.map +1 -1
- package/dist/cli/commands/init-claude-md-template.d.ts.map +1 -1
- package/dist/cli/commands/init-claude-md-template.js +14 -0
- package/dist/cli/commands/init-claude-md-template.js.map +1 -1
- package/dist/core/SwarmCoordinator.d.ts +180 -0
- package/dist/core/SwarmCoordinator.d.ts.map +1 -0
- package/dist/core/SwarmCoordinator.js +473 -0
- package/dist/core/SwarmCoordinator.js.map +1 -0
- package/dist/core/memory/ReflexionMemoryAdapter.d.ts +109 -0
- package/dist/core/memory/ReflexionMemoryAdapter.d.ts.map +1 -0
- package/dist/core/memory/ReflexionMemoryAdapter.js +306 -0
- package/dist/core/memory/ReflexionMemoryAdapter.js.map +1 -0
- package/dist/core/memory/RuVectorPatternStore.d.ts +28 -0
- package/dist/core/memory/RuVectorPatternStore.d.ts.map +1 -1
- package/dist/core/memory/RuVectorPatternStore.js +70 -0
- package/dist/core/memory/RuVectorPatternStore.js.map +1 -1
- package/dist/core/memory/SparseVectorSearch.d.ts +55 -0
- package/dist/core/memory/SparseVectorSearch.d.ts.map +1 -0
- package/dist/core/memory/SparseVectorSearch.js +130 -0
- package/dist/core/memory/SparseVectorSearch.js.map +1 -0
- package/dist/core/memory/TieredCompression.d.ts +81 -0
- package/dist/core/memory/TieredCompression.d.ts.map +1 -0
- package/dist/core/memory/TieredCompression.js +270 -0
- package/dist/core/memory/TieredCompression.js.map +1 -0
- package/dist/core/memory/index.d.ts +6 -0
- package/dist/core/memory/index.d.ts.map +1 -1
- package/dist/core/memory/index.js +29 -1
- package/dist/core/memory/index.js.map +1 -1
- package/dist/core/metrics/MetricsAggregator.d.ts +228 -0
- package/dist/core/metrics/MetricsAggregator.d.ts.map +1 -0
- package/dist/core/metrics/MetricsAggregator.js +482 -0
- package/dist/core/metrics/MetricsAggregator.js.map +1 -0
- package/dist/core/metrics/index.d.ts +5 -0
- package/dist/core/metrics/index.d.ts.map +1 -0
- package/dist/core/metrics/index.js +11 -0
- package/dist/core/metrics/index.js.map +1 -0
- package/dist/core/optimization/SwarmOptimizer.d.ts +5 -0
- package/dist/core/optimization/SwarmOptimizer.d.ts.map +1 -1
- package/dist/core/optimization/SwarmOptimizer.js +17 -0
- package/dist/core/optimization/SwarmOptimizer.js.map +1 -1
- package/dist/core/orchestration/AdaptiveScheduler.d.ts +190 -0
- package/dist/core/orchestration/AdaptiveScheduler.d.ts.map +1 -0
- package/dist/core/orchestration/AdaptiveScheduler.js +460 -0
- package/dist/core/orchestration/AdaptiveScheduler.js.map +1 -0
- package/dist/core/orchestration/WorkflowOrchestrator.d.ts +13 -0
- package/dist/core/orchestration/WorkflowOrchestrator.d.ts.map +1 -1
- package/dist/core/orchestration/WorkflowOrchestrator.js +32 -0
- package/dist/core/orchestration/WorkflowOrchestrator.js.map +1 -1
- package/dist/core/recovery/CircuitBreaker.d.ts +176 -0
- package/dist/core/recovery/CircuitBreaker.d.ts.map +1 -0
- package/dist/core/recovery/CircuitBreaker.js +382 -0
- package/dist/core/recovery/CircuitBreaker.js.map +1 -0
- package/dist/core/recovery/RecoveryOrchestrator.d.ts +186 -0
- package/dist/core/recovery/RecoveryOrchestrator.d.ts.map +1 -0
- package/dist/core/recovery/RecoveryOrchestrator.js +476 -0
- package/dist/core/recovery/RecoveryOrchestrator.js.map +1 -0
- package/dist/core/recovery/RetryStrategy.d.ts +127 -0
- package/dist/core/recovery/RetryStrategy.d.ts.map +1 -0
- package/dist/core/recovery/RetryStrategy.js +314 -0
- package/dist/core/recovery/RetryStrategy.js.map +1 -0
- package/dist/core/recovery/index.d.ts +8 -0
- package/dist/core/recovery/index.d.ts.map +1 -0
- package/dist/core/recovery/index.js +27 -0
- package/dist/core/recovery/index.js.map +1 -0
- package/dist/core/skills/DependencyResolver.d.ts +99 -0
- package/dist/core/skills/DependencyResolver.d.ts.map +1 -0
- package/dist/core/skills/DependencyResolver.js +260 -0
- package/dist/core/skills/DependencyResolver.js.map +1 -0
- package/dist/core/skills/ManifestGenerator.d.ts +114 -0
- package/dist/core/skills/ManifestGenerator.d.ts.map +1 -0
- package/dist/core/skills/ManifestGenerator.js +449 -0
- package/dist/core/skills/ManifestGenerator.js.map +1 -0
- package/dist/core/skills/index.d.ts +9 -0
- package/dist/core/skills/index.d.ts.map +1 -0
- package/dist/core/skills/index.js +24 -0
- package/dist/core/skills/index.js.map +1 -0
- package/dist/mcp/handlers/chaos/chaos-inject-failure.d.ts +5 -0
- package/dist/mcp/handlers/chaos/chaos-inject-failure.d.ts.map +1 -1
- package/dist/mcp/handlers/chaos/chaos-inject-failure.js +36 -2
- package/dist/mcp/handlers/chaos/chaos-inject-failure.js.map +1 -1
- package/dist/mcp/handlers/chaos/chaos-inject-latency.d.ts +5 -0
- package/dist/mcp/handlers/chaos/chaos-inject-latency.d.ts.map +1 -1
- package/dist/mcp/handlers/chaos/chaos-inject-latency.js +36 -2
- package/dist/mcp/handlers/chaos/chaos-inject-latency.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -9
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +1 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/types/qx.d.ts +113 -7
- package/dist/types/qx.d.ts.map +1 -1
- package/dist/types/qx.js.map +1 -1
- package/dist/visualization/api/RestEndpoints.js +1 -1
- package/dist/visualization/api/RestEndpoints.js.map +1 -1
- package/package.json +15 -54
|
@@ -16,11 +16,50 @@
|
|
|
16
16
|
* - Integration with testability scoring
|
|
17
17
|
* - Contextual recommendations generation
|
|
18
18
|
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
19
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
53
|
exports.QXPartnerAgent = void 0;
|
|
21
54
|
const BaseAgent_1 = require("./BaseAgent");
|
|
22
55
|
const types_1 = require("../types");
|
|
56
|
+
const playwright_1 = require("playwright");
|
|
57
|
+
const fs = __importStar(require("fs"));
|
|
58
|
+
const path = __importStar(require("path"));
|
|
59
|
+
const child_process_1 = require("child_process");
|
|
60
|
+
const util_1 = require("util");
|
|
23
61
|
const qx_1 = require("../types/qx");
|
|
62
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
24
63
|
class ConsoleLogger {
|
|
25
64
|
info(message, ...args) {
|
|
26
65
|
console.log(`[INFO] ${message}`, ...args);
|
|
@@ -47,6 +86,8 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
47
86
|
};
|
|
48
87
|
super(baseConfig);
|
|
49
88
|
this.logger = new ConsoleLogger();
|
|
89
|
+
this.browser = null;
|
|
90
|
+
this.page = null;
|
|
50
91
|
this.config = {
|
|
51
92
|
analysisMode: config.analysisMode || 'full',
|
|
52
93
|
heuristics: config.heuristics || {
|
|
@@ -192,6 +233,15 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
192
233
|
async cleanup() {
|
|
193
234
|
try {
|
|
194
235
|
this.logger.info(`QXPartnerAgent ${this.agentId.id} cleaning up resources`);
|
|
236
|
+
// Close browser if open
|
|
237
|
+
if (this.page) {
|
|
238
|
+
await this.page.close();
|
|
239
|
+
this.page = null;
|
|
240
|
+
}
|
|
241
|
+
if (this.browser) {
|
|
242
|
+
await this.browser.close();
|
|
243
|
+
this.browser = null;
|
|
244
|
+
}
|
|
195
245
|
// Save current QX analysis state
|
|
196
246
|
await this.saveQXState();
|
|
197
247
|
// Store learned patterns
|
|
@@ -252,14 +302,18 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
252
302
|
const userNeeds = await this.analyzeUserNeeds(context, problemAnalysis);
|
|
253
303
|
// 4. Analyze business needs
|
|
254
304
|
const businessNeeds = await this.analyzeBusinessNeeds(context, problemAnalysis);
|
|
255
|
-
// 5.
|
|
305
|
+
// 5. Analyze creativity - drawing inspiration from diverse domains
|
|
306
|
+
const creativityAnalysis = await this.analyzeCreativity(context, problemAnalysis);
|
|
307
|
+
// 6. Analyze design - exactness, intuitive, and counter-intuitive design
|
|
308
|
+
const designAnalysis = await this.analyzeDesign(context, problemAnalysis);
|
|
309
|
+
// 7. Detect oracle problems
|
|
256
310
|
const oracleProblems = this.config.detectOracleProblems
|
|
257
311
|
? await this.detectOracleProblemsFromContext(context, userNeeds, businessNeeds)
|
|
258
312
|
: [];
|
|
259
|
-
//
|
|
313
|
+
// 8. Perform impact analysis
|
|
260
314
|
const impactAnalysis = await this.analyzeImpact(context, problemAnalysis);
|
|
261
|
-
//
|
|
262
|
-
const heuristics = await this.applyAllHeuristics(context, problemAnalysis, userNeeds, businessNeeds);
|
|
315
|
+
// 9. Apply heuristics
|
|
316
|
+
const heuristics = await this.applyAllHeuristics(context, problemAnalysis, userNeeds, businessNeeds, creativityAnalysis, designAnalysis);
|
|
263
317
|
// 8. Integrate testability (if enabled)
|
|
264
318
|
const testabilityIntegration = this.config.integrateTestability
|
|
265
319
|
? await this.integrateTestabilityScoring(params)
|
|
@@ -267,7 +321,7 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
267
321
|
// 9. Generate recommendations
|
|
268
322
|
const recommendations = await this.generateRecommendations(problemAnalysis, userNeeds, businessNeeds, oracleProblems, impactAnalysis, heuristics, testabilityIntegration);
|
|
269
323
|
// 10. Calculate overall score
|
|
270
|
-
const overallScore = this.calculateOverallQXScore(problemAnalysis, userNeeds, businessNeeds, impactAnalysis, heuristics);
|
|
324
|
+
const overallScore = this.calculateOverallQXScore(problemAnalysis, userNeeds, businessNeeds, creativityAnalysis, designAnalysis, impactAnalysis, heuristics);
|
|
271
325
|
const grade = this.scoreToGrade(overallScore);
|
|
272
326
|
const analysis = {
|
|
273
327
|
overallScore,
|
|
@@ -277,6 +331,8 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
277
331
|
problemAnalysis,
|
|
278
332
|
userNeeds,
|
|
279
333
|
businessNeeds,
|
|
334
|
+
creativityAnalysis,
|
|
335
|
+
designAnalysis,
|
|
280
336
|
oracleProblems,
|
|
281
337
|
impactAnalysis,
|
|
282
338
|
heuristics,
|
|
@@ -288,66 +344,1385 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
288
344
|
await this.storeMemory(`qx-analysis:${target}`, analysis);
|
|
289
345
|
const duration = Date.now() - startTime;
|
|
290
346
|
this.logger.info(`QX analysis completed in ${duration}ms. Score: ${overallScore}/100 (${grade})`);
|
|
347
|
+
// Generate HTML report and auto-launch
|
|
348
|
+
try {
|
|
349
|
+
const reportPath = await this.generateHTMLReport(analysis);
|
|
350
|
+
this.logger.info(`HTML report generated: ${reportPath}`);
|
|
351
|
+
// Auto-launch browser
|
|
352
|
+
await this.launchReportInBrowser(reportPath);
|
|
353
|
+
this.logger.info(`Report launched in browser`);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
this.logger.warn(`Failed to generate/launch HTML report:`, error);
|
|
357
|
+
// Don't fail the analysis if report generation fails
|
|
358
|
+
}
|
|
291
359
|
return analysis;
|
|
292
360
|
}
|
|
293
361
|
/**
|
|
294
|
-
*
|
|
362
|
+
* Generate HTML report from QX analysis
|
|
363
|
+
*/
|
|
364
|
+
async generateHTMLReport(analysis) {
|
|
365
|
+
const sanitizedTarget = analysis.target.replace(/[^a-zA-Z0-9]/g, '-').substring(0, 50);
|
|
366
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
|
|
367
|
+
const filename = `qx-report-${sanitizedTarget}-${timestamp}.html`;
|
|
368
|
+
const reportsDir = path.join(process.cwd(), 'docs', 'qx-reports');
|
|
369
|
+
// Ensure reports directory exists
|
|
370
|
+
if (!fs.existsSync(reportsDir)) {
|
|
371
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
372
|
+
}
|
|
373
|
+
const reportPath = path.join(reportsDir, filename);
|
|
374
|
+
const html = this.generateHTMLContent(analysis);
|
|
375
|
+
fs.writeFileSync(reportPath, html, 'utf8');
|
|
376
|
+
return reportPath;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Generate HTML content for the report
|
|
380
|
+
*/
|
|
381
|
+
generateHTMLContent(analysis) {
|
|
382
|
+
const date = analysis.timestamp.toLocaleDateString();
|
|
383
|
+
const time = analysis.timestamp.toLocaleTimeString();
|
|
384
|
+
return `<!DOCTYPE html>
|
|
385
|
+
<html lang="en">
|
|
386
|
+
<head>
|
|
387
|
+
<meta charset="UTF-8">
|
|
388
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
389
|
+
<title>QX Analysis: ${this.escapeHtml(analysis.target)}</title>
|
|
390
|
+
${this.getReportStyles()}
|
|
391
|
+
</head>
|
|
392
|
+
<body>
|
|
393
|
+
<div class="container">
|
|
394
|
+
<div class="header">
|
|
395
|
+
<h1>🍵 Quality Experience (QX) Analysis</h1>
|
|
396
|
+
<div class="subtitle">${this.escapeHtml(analysis.target)}</div>
|
|
397
|
+
<div class="meta">
|
|
398
|
+
<strong>URL:</strong> ${this.escapeHtml(analysis.target)}<br>
|
|
399
|
+
<strong>Analysis Date:</strong> ${date} at ${time}<br>
|
|
400
|
+
<strong>Framework:</strong> QX Partner (Quality + UX Advocacy)
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<div class="content">
|
|
405
|
+
<!-- Executive Summary -->
|
|
406
|
+
<div class="section">
|
|
407
|
+
<h2>📊 Executive Summary</h2>
|
|
408
|
+
<p>
|
|
409
|
+
This comprehensive QX analysis evaluates <strong>${this.escapeHtml(analysis.context.title || analysis.target)}</strong>
|
|
410
|
+
through the lens of Quality Experience, examining how quality is co-created for all stakeholders.
|
|
411
|
+
</p>
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<!-- Overall QX Score -->
|
|
415
|
+
<div class="section">
|
|
416
|
+
<h2>🎯 Overall QX Score</h2>
|
|
417
|
+
<div class="score-card">
|
|
418
|
+
<div class="score-item">
|
|
419
|
+
<h4>Problem Clarity</h4>
|
|
420
|
+
<div class="score-value">${analysis.problemAnalysis.clarityScore}</div>
|
|
421
|
+
<div class="score-label">/ 100</div>
|
|
422
|
+
</div>
|
|
423
|
+
<div class="score-item">
|
|
424
|
+
<h4>User Needs Alignment</h4>
|
|
425
|
+
<div class="score-value">${analysis.userNeeds.alignmentScore}</div>
|
|
426
|
+
<div class="score-label">/ 100</div>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="score-item">
|
|
429
|
+
<h4>Business Alignment</h4>
|
|
430
|
+
<div class="score-value">${analysis.businessNeeds.alignmentScore}</div>
|
|
431
|
+
<div class="score-label">/ 100</div>
|
|
432
|
+
</div>
|
|
433
|
+
<div class="score-item">
|
|
434
|
+
<h4>Impact Assessment</h4>
|
|
435
|
+
<div class="score-value">${100 - analysis.impactAnalysis.overallImpactScore}</div>
|
|
436
|
+
<div class="score-label">/ 100</div>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
<div style="text-align: center; margin-top: 30px;">
|
|
440
|
+
<div class="score-value" style="font-size: 3em; color: ${this.getScoreColor(analysis.overallScore)};">${analysis.overallScore}</div>
|
|
441
|
+
<div class="score-label" style="font-size: 1.2em;">OVERALL QX SCORE (Grade: ${analysis.grade})</div>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<!-- Problem Analysis -->
|
|
446
|
+
<div class="section">
|
|
447
|
+
<h2>🔍 Problem Analysis</h2>
|
|
448
|
+
<div class="info-box">
|
|
449
|
+
<h3>Problem Statement</h3>
|
|
450
|
+
<p>${this.escapeHtml(analysis.problemAnalysis.problemStatement)}</p>
|
|
451
|
+
<p><strong>Complexity:</strong> ${analysis.problemAnalysis.complexity}</p>
|
|
452
|
+
<p><strong>Clarity Score:</strong> ${analysis.problemAnalysis.clarityScore}/100</p>
|
|
453
|
+
</div>
|
|
454
|
+
${analysis.problemAnalysis.potentialFailures.length > 0 ? `
|
|
455
|
+
<div class="improvements">
|
|
456
|
+
<h3>⚠️ Potential Failure Modes</h3>
|
|
457
|
+
<ul>
|
|
458
|
+
${analysis.problemAnalysis.potentialFailures.map(f => `
|
|
459
|
+
<li>
|
|
460
|
+
<strong>[${f.severity.toUpperCase()}]</strong> ${this.escapeHtml(f.description)}
|
|
461
|
+
<br><small>Likelihood: ${f.likelihood}</small>
|
|
462
|
+
</li>
|
|
463
|
+
`).join('')}
|
|
464
|
+
</ul>
|
|
465
|
+
</div>
|
|
466
|
+
` : ''}
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
<!-- User Needs -->
|
|
470
|
+
<div class="section">
|
|
471
|
+
<h2>👥 User Needs Analysis</h2>
|
|
472
|
+
<p><strong>Suitability:</strong> ${analysis.userNeeds.suitability} | <strong>Score:</strong> ${analysis.userNeeds.alignmentScore}/100</p>
|
|
473
|
+
${analysis.userNeeds.needs.length > 0 ? `
|
|
474
|
+
<div class="strengths">
|
|
475
|
+
<h3>✅ User Needs</h3>
|
|
476
|
+
<ul>
|
|
477
|
+
${analysis.userNeeds.needs.map(n => `
|
|
478
|
+
<li>
|
|
479
|
+
<strong>[${n.priority}]</strong> ${this.escapeHtml(n.description)}
|
|
480
|
+
${n.addressed ? '✓ Addressed' : '✗ Not Addressed'}
|
|
481
|
+
</li>
|
|
482
|
+
`).join('')}
|
|
483
|
+
</ul>
|
|
484
|
+
</div>
|
|
485
|
+
` : ''}
|
|
486
|
+
${analysis.userNeeds.challenges.length > 0 ? `
|
|
487
|
+
<div class="improvements">
|
|
488
|
+
<h3>⚠️ User Challenges</h3>
|
|
489
|
+
<ul>
|
|
490
|
+
${analysis.userNeeds.challenges.map(c => `<li>${this.escapeHtml(c)}</li>`).join('')}
|
|
491
|
+
</ul>
|
|
492
|
+
</div>
|
|
493
|
+
` : ''}
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<!-- Business Needs -->
|
|
497
|
+
<div class="section">
|
|
498
|
+
<h2>💼 Business Needs Analysis</h2>
|
|
499
|
+
<p><strong>Primary Goal:</strong> ${analysis.businessNeeds.primaryGoal}</p>
|
|
500
|
+
<p><strong>Compromises UX:</strong> ${analysis.businessNeeds.compromisesUX ? 'Yes ⚠️' : 'No ✓'}</p>
|
|
501
|
+
${analysis.businessNeeds.kpisAffected.length > 0 ? `
|
|
502
|
+
<div class="info-box">
|
|
503
|
+
<h3>KPIs Affected</h3>
|
|
504
|
+
<ul>
|
|
505
|
+
${analysis.businessNeeds.kpisAffected.map(k => `<li>${this.escapeHtml(k)}</li>`).join('')}
|
|
506
|
+
</ul>
|
|
507
|
+
</div>
|
|
508
|
+
` : ''}
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<!-- Creativity Analysis -->
|
|
512
|
+
<div class="section">
|
|
513
|
+
<h2>🎨 Creativity & Innovation Analysis</h2>
|
|
514
|
+
<p><strong>Creativity Score:</strong> ${analysis.creativityAnalysis.creativityScore}/100</p>
|
|
515
|
+
<p><strong>Domains Explored:</strong> ${analysis.creativityAnalysis.domainsExplored.join(', ')}</p>
|
|
516
|
+
${analysis.creativityAnalysis.innovativeApproaches.length > 0 ? `
|
|
517
|
+
<div class="strengths">
|
|
518
|
+
<h3>✨ Innovative Testing Approaches</h3>
|
|
519
|
+
<ul>
|
|
520
|
+
${analysis.creativityAnalysis.innovativeApproaches.map(a => `
|
|
521
|
+
<li>
|
|
522
|
+
<strong>[${a.inspirationSource.toUpperCase()}]</strong> ${this.escapeHtml(a.description)}
|
|
523
|
+
<br><small>Applicability: ${a.applicability} | Novelty: ${a.novelty}</small>
|
|
524
|
+
</li>
|
|
525
|
+
`).join('')}
|
|
526
|
+
</ul>
|
|
527
|
+
</div>
|
|
528
|
+
` : ''}
|
|
529
|
+
${analysis.creativityAnalysis.perspectives.length > 0 ? `
|
|
530
|
+
<div class="info-box">
|
|
531
|
+
<h3>🔍 Testing Perspectives Applied</h3>
|
|
532
|
+
<ul>
|
|
533
|
+
${analysis.creativityAnalysis.perspectives.map(p => `<li>${this.escapeHtml(p)}</li>`).join('')}
|
|
534
|
+
</ul>
|
|
535
|
+
</div>
|
|
536
|
+
` : ''}
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
<!-- Design Quality Analysis -->
|
|
540
|
+
<div class="section">
|
|
541
|
+
<h2>🎯 Design Quality Analysis</h2>
|
|
542
|
+
<p><strong>Overall Design Score:</strong> ${analysis.designAnalysis.overallDesignScore}/100</p>
|
|
543
|
+
|
|
544
|
+
<!-- Exactness -->
|
|
545
|
+
<div class="info-box" style="margin-top: 20px;">
|
|
546
|
+
<h3>📏 Exactness & Clarity (${analysis.designAnalysis.exactness.score}/100)</h3>
|
|
547
|
+
<p><strong>Clarity Level:</strong> ${analysis.designAnalysis.exactness.clarity}</p>
|
|
548
|
+
${analysis.designAnalysis.exactness.clearElements.length > 0 ? `
|
|
549
|
+
<div style="margin-top: 15px;">
|
|
550
|
+
<strong>✓ Clear Elements:</strong>
|
|
551
|
+
<ul>
|
|
552
|
+
${analysis.designAnalysis.exactness.clearElements.map(e => `<li>${this.escapeHtml(e)}</li>`).join('')}
|
|
553
|
+
</ul>
|
|
554
|
+
</div>
|
|
555
|
+
` : ''}
|
|
556
|
+
${analysis.designAnalysis.exactness.unclearElements.length > 0 ? `
|
|
557
|
+
<div style="margin-top: 15px;">
|
|
558
|
+
<strong>⚠️ Unclear Elements:</strong>
|
|
559
|
+
<ul>
|
|
560
|
+
${analysis.designAnalysis.exactness.unclearElements.map(e => `<li>${this.escapeHtml(e)}</li>`).join('')}
|
|
561
|
+
</ul>
|
|
562
|
+
</div>
|
|
563
|
+
` : ''}
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<!-- Intuitive Design -->
|
|
567
|
+
<div class="strengths" style="margin-top: 20px;">
|
|
568
|
+
<h3>🧭 Intuitive Design (${analysis.designAnalysis.intuitive.score}/100)</h3>
|
|
569
|
+
<p><strong>Follows Conventions:</strong> ${analysis.designAnalysis.intuitive.followsConventions ? 'Yes ✓' : 'No ⚠️'}</p>
|
|
570
|
+
${analysis.designAnalysis.intuitive.intuitivePatterns.length > 0 ? `
|
|
571
|
+
<div style="margin-top: 15px;">
|
|
572
|
+
<strong>Intuitive Patterns Detected:</strong>
|
|
573
|
+
<ul>
|
|
574
|
+
${analysis.designAnalysis.intuitive.intuitivePatterns.map(p => `<li>${this.escapeHtml(p)}</li>`).join('')}
|
|
575
|
+
</ul>
|
|
576
|
+
</div>
|
|
577
|
+
` : ''}
|
|
578
|
+
${analysis.designAnalysis.intuitive.culturalIssues.length > 0 ? `
|
|
579
|
+
<div style="margin-top: 15px;">
|
|
580
|
+
<strong>⚠️ Cultural Considerations:</strong>
|
|
581
|
+
<ul>
|
|
582
|
+
${analysis.designAnalysis.intuitive.culturalIssues.map(i => `<li>${this.escapeHtml(i)}</li>`).join('')}
|
|
583
|
+
</ul>
|
|
584
|
+
</div>
|
|
585
|
+
` : ''}
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
<!-- Counter-intuitive Design -->
|
|
589
|
+
${analysis.designAnalysis.counterIntuitive.deviations.length > 0 ? `
|
|
590
|
+
<div class="improvements" style="margin-top: 20px;">
|
|
591
|
+
<h3>🔄 Counter-intuitive Design Patterns</h3>
|
|
592
|
+
<p><strong>Issues Found:</strong> ${analysis.designAnalysis.counterIntuitive.issuesCount}</p>
|
|
593
|
+
<p><strong>Fresh Eyes Perspective Applied:</strong> ${analysis.designAnalysis.counterIntuitive.freshEyesPerspective ? 'Yes ✓' : 'No'}</p>
|
|
594
|
+
<ul>
|
|
595
|
+
${analysis.designAnalysis.counterIntuitive.deviations.map(d => `
|
|
596
|
+
<li>
|
|
597
|
+
<strong>${this.escapeHtml(d.element)}</strong>
|
|
598
|
+
<br>Expected: ${this.escapeHtml(d.expectedBehavior)}
|
|
599
|
+
<br>Actual: ${this.escapeHtml(d.actualBehavior)}
|
|
600
|
+
<br><small>Impact: ${d.impact} ${d.justification ? '| ' + this.escapeHtml(d.justification) : ''}</small>
|
|
601
|
+
</li>
|
|
602
|
+
`).join('')}
|
|
603
|
+
</ul>
|
|
604
|
+
</div>
|
|
605
|
+
` : ''}
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<!-- Oracle Problems -->
|
|
609
|
+
${analysis.oracleProblems.length > 0 ? `
|
|
610
|
+
<div class="section">
|
|
611
|
+
<h2>🔮 Oracle Problems Detected</h2>
|
|
612
|
+
${analysis.oracleProblems.map(p => `
|
|
613
|
+
<div class="improvements">
|
|
614
|
+
<h3>[${p.severity.toUpperCase()}] ${p.type}</h3>
|
|
615
|
+
<p>${this.escapeHtml(p.description)}</p>
|
|
616
|
+
${p.stakeholders ? `<p><strong>Stakeholders:</strong> ${p.stakeholders.join(', ')}</p>` : ''}
|
|
617
|
+
${p.resolutionApproach ? `
|
|
618
|
+
<p><strong>Resolution Approach:</strong></p>
|
|
619
|
+
<ul>
|
|
620
|
+
${p.resolutionApproach.map(r => `<li>${this.escapeHtml(r)}</li>`).join('')}
|
|
621
|
+
</ul>
|
|
622
|
+
` : ''}
|
|
623
|
+
</div>
|
|
624
|
+
`).join('')}
|
|
625
|
+
</div>
|
|
626
|
+
` : ''}
|
|
627
|
+
|
|
628
|
+
<!-- Recommendations -->
|
|
629
|
+
<div class="section">
|
|
630
|
+
<h2>💡 Strategic Recommendations</h2>
|
|
631
|
+
${analysis.recommendations.slice(0, 10).map((rec, idx) => `
|
|
632
|
+
<div class="recommendations">
|
|
633
|
+
<h3>🎯 Priority ${idx + 1}: ${this.escapeHtml(rec.principle)}</h3>
|
|
634
|
+
<p>${this.escapeHtml(rec.recommendation)}</p>
|
|
635
|
+
<p>
|
|
636
|
+
<strong>Severity:</strong> ${rec.severity} |
|
|
637
|
+
<strong>Impact:</strong> ${rec.impactPercentage || rec.impact}% |
|
|
638
|
+
<strong>Effort:</strong> ${rec.estimatedEffort || rec.effort}
|
|
639
|
+
</p>
|
|
640
|
+
</div>
|
|
641
|
+
`).join('')}
|
|
642
|
+
</div>
|
|
643
|
+
|
|
644
|
+
<!-- Heuristics Results -->
|
|
645
|
+
${analysis.heuristics.length > 0 ? `
|
|
646
|
+
<div class="section">
|
|
647
|
+
<h2>📐 Heuristics Analysis</h2>
|
|
648
|
+
<div class="score-card">
|
|
649
|
+
${analysis.heuristics.slice(0, 8).map(h => `
|
|
650
|
+
<div class="score-item">
|
|
651
|
+
<h4>${this.formatHeuristicName(h.name)}</h4>
|
|
652
|
+
<div class="score-value" style="font-size: 1.8em; color: ${this.getScoreColor(h.score)};">${h.score}</div>
|
|
653
|
+
<div class="score-label">/ 100</div>
|
|
654
|
+
</div>
|
|
655
|
+
`).join('')}
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
` : ''}
|
|
659
|
+
|
|
660
|
+
<!-- Impact Analysis -->
|
|
661
|
+
<div class="section">
|
|
662
|
+
<h2>⚡ Impact Analysis</h2>
|
|
663
|
+
<div class="score-card">
|
|
664
|
+
<div class="score-item">
|
|
665
|
+
<h4>Visible Impact</h4>
|
|
666
|
+
<div class="score-value">${analysis.impactAnalysis.visible.score}</div>
|
|
667
|
+
<div class="score-label">/ 100</div>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="score-item">
|
|
670
|
+
<h4>Invisible Impact</h4>
|
|
671
|
+
<div class="score-value">${analysis.impactAnalysis.invisible.score}</div>
|
|
672
|
+
<div class="score-label">/ 100</div>
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
${analysis.impactAnalysis.visible.userFeelings && analysis.impactAnalysis.visible.userFeelings.length > 0 ? `
|
|
676
|
+
<div class="info-box">
|
|
677
|
+
<h3>User Feelings</h3>
|
|
678
|
+
<ul>
|
|
679
|
+
${analysis.impactAnalysis.visible.userFeelings.map(f => {
|
|
680
|
+
if (typeof f === 'string') {
|
|
681
|
+
return `<li>${this.escapeHtml(f)}</li>`;
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
return `<li><strong>${f.feeling}</strong> (${f.likelihood}): ${this.escapeHtml(f.context)}</li>`;
|
|
685
|
+
}
|
|
686
|
+
}).join('')}
|
|
687
|
+
</ul>
|
|
688
|
+
</div>
|
|
689
|
+
` : ''}
|
|
690
|
+
</div>
|
|
691
|
+
|
|
692
|
+
<!-- Conclusion -->
|
|
693
|
+
<div class="section">
|
|
694
|
+
<h2>🎓 Conclusion</h2>
|
|
695
|
+
<p>
|
|
696
|
+
This QX analysis reveals an overall score of <strong>${analysis.overallScore}/100 (Grade: ${analysis.grade})</strong>.
|
|
697
|
+
${this.getScoreInterpretation(analysis.overallScore)}
|
|
698
|
+
</p>
|
|
699
|
+
</div>
|
|
700
|
+
|
|
701
|
+
<!-- QX Methodology -->
|
|
702
|
+
<div class="section" style="background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%); padding: 30px; border-radius: 8px; margin-top: 40px;">
|
|
703
|
+
<h2>📚 QX Methodology: Key Concepts</h2>
|
|
704
|
+
|
|
705
|
+
<div style="margin-top: 20px;">
|
|
706
|
+
<h3 style="color: #667eea; margin-bottom: 10px;">🤝 What is Quality Experience (QX)?</h3>
|
|
707
|
+
<p style="text-align: justify;">
|
|
708
|
+
<strong>Quality Experience (QX)</strong> is the marriage between <strong>Quality Advocacy (QA)</strong> and
|
|
709
|
+
<strong>User Experience (UX)</strong>. Unlike traditional testing that focuses solely on defects, QX recognizes
|
|
710
|
+
that quality is co-created by everyone associated with the product—developers, testers, designers, users, and
|
|
711
|
+
business stakeholders. QX enables testers to collaborate meaningfully with UX professionals by understanding
|
|
712
|
+
design effectiveness beyond technical correctness.
|
|
713
|
+
</p>
|
|
714
|
+
</div>
|
|
715
|
+
|
|
716
|
+
<div style="margin-top: 25px;">
|
|
717
|
+
<h3 style="color: #667eea; margin-bottom: 10px;">🎨 Creativity in Testing</h3>
|
|
718
|
+
<p style="text-align: justify;">
|
|
719
|
+
QX encourages drawing inspiration from <strong>diverse domains</strong>—philosophy, social science, medicine,
|
|
720
|
+
e-commerce, fashion, gaming—to generate innovative test ideas when conventional approaches fall short. This
|
|
721
|
+
cross-disciplinary perspective helps testers uncover unconventional risks and approach problems from fresh angles
|
|
722
|
+
that technical testing alone might miss.
|
|
723
|
+
</p>
|
|
724
|
+
</div>
|
|
725
|
+
|
|
726
|
+
<div style="margin-top: 25px;">
|
|
727
|
+
<h3 style="color: #667eea; margin-bottom: 10px;">📏 Exactness & Clarity</h3>
|
|
728
|
+
<p style="text-align: justify;">
|
|
729
|
+
<strong>Exactness</strong> evaluates how clearly a product communicates its intent to users. Testers should assess
|
|
730
|
+
whether menu items, buttons, labels, and terminology are self-evident. Are component interactions obvious? Do users
|
|
731
|
+
understand what will happen when they click? Exactness testing identifies ambiguities that create confusion, focusing
|
|
732
|
+
on <em>clarity of communication</em> rather than just functional correctness.
|
|
733
|
+
</p>
|
|
734
|
+
</div>
|
|
735
|
+
|
|
736
|
+
<div style="margin-top: 25px;">
|
|
737
|
+
<h3 style="color: #667eea; margin-bottom: 10px;">🧭 Intuitive Design</h3>
|
|
738
|
+
<p style="text-align: justify;">
|
|
739
|
+
<strong>Intuitive design</strong> follows common conventions and user expectations. QX testing evaluates whether
|
|
740
|
+
component interactions follow familiar patterns, respect cultural sensitivities, and align with mental models users
|
|
741
|
+
bring from other products. Intuitive design reduces cognitive load and makes products immediately usable without
|
|
742
|
+
extensive training.
|
|
743
|
+
</p>
|
|
744
|
+
</div>
|
|
745
|
+
|
|
746
|
+
<div style="margin-top: 25px;">
|
|
747
|
+
<h3 style="color: #667eea; margin-bottom: 10px;">🔄 Counter-intuitive Design Detection</h3>
|
|
748
|
+
<p style="text-align: justify;">
|
|
749
|
+
QX testing deliberately <strong>looks at products like an unexperienced user</strong> to spot design elements that
|
|
750
|
+
deviate from expectations. Counter-intuitive patterns aren't always bad—they might represent innovation—but they
|
|
751
|
+
require justification. The key is distinguishing between deliberate, valuable innovations and accidental friction
|
|
752
|
+
that experienced users have normalized but newcomers will struggle with.
|
|
753
|
+
</p>
|
|
754
|
+
</div>
|
|
755
|
+
|
|
756
|
+
<div style="margin-top: 25px; padding: 20px; background: white; border-left: 4px solid #667eea; border-radius: 4px;">
|
|
757
|
+
<p style="text-align: justify; font-style: italic;">
|
|
758
|
+
<strong>The QX Advantage:</strong> By combining quality advocacy with UX design thinking, QX enables testers to
|
|
759
|
+
contribute informed insights about user-facing quality, bridge the gap between QA and UX teams, and ensure that
|
|
760
|
+
testing serves the genuine experience quality that all stakeholders care about—not just technical correctness.
|
|
761
|
+
</p>
|
|
762
|
+
</div>
|
|
763
|
+
|
|
764
|
+
<div style="margin-top: 20px; text-align: center;">
|
|
765
|
+
<p style="font-size: 0.9em;">
|
|
766
|
+
<strong>Learn more:</strong>
|
|
767
|
+
<a href="https://talesoftesting.com/quality-experienceqx-co-creating-quality-experience-for-everyone-associated-with-the-product/"
|
|
768
|
+
target="_blank" rel="noopener noreferrer" style="color: #667eea; text-decoration: none;">
|
|
769
|
+
Quality Experience (QX) - Tales of Testing
|
|
770
|
+
</a>
|
|
771
|
+
</p>
|
|
772
|
+
</div>
|
|
773
|
+
</div>
|
|
774
|
+
</div>
|
|
775
|
+
|
|
776
|
+
<div class="footer">
|
|
777
|
+
<p><strong>QX Analysis Report</strong></p>
|
|
778
|
+
<p>Generated by: Agentic QE Fleet v2.1.0 - QX Partner Agent</p>
|
|
779
|
+
<p>Framework: Quality Experience (QX) Analysis</p>
|
|
780
|
+
<p>Analysis Date: ${date} at ${time}</p>
|
|
781
|
+
<hr style="margin: 20px 0; border: none; border-top: 1px solid #dee2e6;">
|
|
782
|
+
<p style="font-size: 0.9em; line-height: 1.6;">
|
|
783
|
+
This report applies the QX framework developed to bridge Quality Advocacy (QA) and User Experience (UX).<br>
|
|
784
|
+
<em>"Quality is value to someone who matters"</em> - when multiple stakeholders matter simultaneously,<br>
|
|
785
|
+
QX helps find balance and solve oracle problems. It is one of the key concepts originally covered in
|
|
786
|
+
<a href="https://talesoftesting.com/qcsd/" target="_blank" rel="noopener noreferrer" style="color: #007bff; text-decoration: none;">
|
|
787
|
+
QCSD - Quality Conscious Software Delivery Framework</a> created by Lalitkumar Bhamare.
|
|
788
|
+
</p>
|
|
789
|
+
</div>
|
|
790
|
+
</div>
|
|
791
|
+
</body>
|
|
792
|
+
</html>`;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Get CSS styles for the report
|
|
796
|
+
*/
|
|
797
|
+
getReportStyles() {
|
|
798
|
+
return `
|
|
799
|
+
<style>
|
|
800
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
801
|
+
body {
|
|
802
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
803
|
+
line-height: 1.6;
|
|
804
|
+
color: #333;
|
|
805
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
806
|
+
padding: 20px;
|
|
807
|
+
}
|
|
808
|
+
.container {
|
|
809
|
+
max-width: 1200px;
|
|
810
|
+
margin: 0 auto;
|
|
811
|
+
background: white;
|
|
812
|
+
border-radius: 12px;
|
|
813
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
814
|
+
overflow: hidden;
|
|
815
|
+
}
|
|
816
|
+
.header {
|
|
817
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
818
|
+
color: white;
|
|
819
|
+
padding: 40px;
|
|
820
|
+
text-align: center;
|
|
821
|
+
}
|
|
822
|
+
.header h1 { font-size: 2.5em; margin-bottom: 10px; font-weight: 700; }
|
|
823
|
+
.header .subtitle { font-size: 1.2em; opacity: 0.9; }
|
|
824
|
+
.header .meta { margin-top: 20px; font-size: 0.9em; opacity: 0.8; }
|
|
825
|
+
.content { padding: 40px; }
|
|
826
|
+
.section { margin-bottom: 40px; }
|
|
827
|
+
.section h2 {
|
|
828
|
+
color: #667eea;
|
|
829
|
+
font-size: 1.8em;
|
|
830
|
+
margin-bottom: 20px;
|
|
831
|
+
padding-bottom: 10px;
|
|
832
|
+
border-bottom: 3px solid #667eea;
|
|
833
|
+
}
|
|
834
|
+
.section h3 { color: #764ba2; font-size: 1.4em; margin: 25px 0 15px 0; }
|
|
835
|
+
.section p { margin-bottom: 15px; text-align: justify; }
|
|
836
|
+
.score-card {
|
|
837
|
+
display: grid;
|
|
838
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
839
|
+
gap: 20px;
|
|
840
|
+
margin: 30px 0;
|
|
841
|
+
}
|
|
842
|
+
.score-item {
|
|
843
|
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
844
|
+
padding: 20px;
|
|
845
|
+
border-radius: 8px;
|
|
846
|
+
text-align: center;
|
|
847
|
+
transition: transform 0.3s ease;
|
|
848
|
+
}
|
|
849
|
+
.score-item:hover { transform: translateY(-5px); box-shadow: 0 5px 20px rgba(0,0,0,0.1); }
|
|
850
|
+
.score-item h4 { color: #667eea; font-size: 1.1em; margin-bottom: 10px; }
|
|
851
|
+
.score-value {
|
|
852
|
+
font-size: 2.5em;
|
|
853
|
+
font-weight: bold;
|
|
854
|
+
color: #764ba2;
|
|
855
|
+
margin: 10px 0;
|
|
856
|
+
}
|
|
857
|
+
.score-label {
|
|
858
|
+
font-size: 0.9em;
|
|
859
|
+
color: #666;
|
|
860
|
+
text-transform: uppercase;
|
|
861
|
+
letter-spacing: 1px;
|
|
862
|
+
}
|
|
863
|
+
.strengths, .improvements, .recommendations, .info-box {
|
|
864
|
+
background: #f8f9fa;
|
|
865
|
+
padding: 25px;
|
|
866
|
+
border-radius: 8px;
|
|
867
|
+
margin: 20px 0;
|
|
868
|
+
}
|
|
869
|
+
.strengths { border-left: 5px solid #28a745; }
|
|
870
|
+
.improvements { border-left: 5px solid #ffc107; }
|
|
871
|
+
.recommendations { border-left: 5px solid #17a2b8; }
|
|
872
|
+
.info-box { border-left: 5px solid #6c757d; }
|
|
873
|
+
.strengths h3 { color: #28a745; }
|
|
874
|
+
.improvements h3 { color: #ffc107; }
|
|
875
|
+
.recommendations h3 { color: #17a2b8; }
|
|
876
|
+
.info-box h3 { color: #6c757d; }
|
|
877
|
+
ul { margin-left: 20px; margin-top: 10px; }
|
|
878
|
+
li { margin-bottom: 10px; line-height: 1.8; }
|
|
879
|
+
.footer {
|
|
880
|
+
background: #f8f9fa;
|
|
881
|
+
padding: 30px;
|
|
882
|
+
text-align: center;
|
|
883
|
+
color: #666;
|
|
884
|
+
border-top: 1px solid #e0e0e0;
|
|
885
|
+
}
|
|
886
|
+
.footer p { margin-bottom: 10px; }
|
|
887
|
+
@media print {
|
|
888
|
+
body { background: white; padding: 0; }
|
|
889
|
+
.container { box-shadow: none; }
|
|
890
|
+
}
|
|
891
|
+
</style>`;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Escape HTML special characters
|
|
895
|
+
*/
|
|
896
|
+
escapeHtml(text) {
|
|
897
|
+
const map = {
|
|
898
|
+
'&': '&',
|
|
899
|
+
'<': '<',
|
|
900
|
+
'>': '>',
|
|
901
|
+
'"': '"',
|
|
902
|
+
"'": '''
|
|
903
|
+
};
|
|
904
|
+
return text.replace(/[&<>"']/g, m => map[m]);
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Format heuristic name for display
|
|
908
|
+
*/
|
|
909
|
+
formatHeuristicName(name) {
|
|
910
|
+
return name
|
|
911
|
+
.split('-')
|
|
912
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
913
|
+
.join(' ');
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Get color based on score
|
|
917
|
+
*/
|
|
918
|
+
getScoreColor(score) {
|
|
919
|
+
if (score >= 90)
|
|
920
|
+
return '#28a745'; // Green
|
|
921
|
+
if (score >= 80)
|
|
922
|
+
return '#17a2b8'; // Blue
|
|
923
|
+
if (score >= 70)
|
|
924
|
+
return '#ffc107'; // Yellow
|
|
925
|
+
if (score >= 60)
|
|
926
|
+
return '#fd7e14'; // Orange
|
|
927
|
+
return '#dc3545'; // Red
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Get score interpretation
|
|
931
|
+
*/
|
|
932
|
+
getScoreInterpretation(score) {
|
|
933
|
+
if (score >= 90) {
|
|
934
|
+
return 'Excellent quality experience with strong alignment across all dimensions.';
|
|
935
|
+
}
|
|
936
|
+
else if (score >= 80) {
|
|
937
|
+
return 'Good quality experience with minor areas for improvement.';
|
|
938
|
+
}
|
|
939
|
+
else if (score >= 70) {
|
|
940
|
+
return 'Adequate quality experience but significant improvements recommended.';
|
|
941
|
+
}
|
|
942
|
+
else if (score >= 60) {
|
|
943
|
+
return 'Below target quality experience. Priority improvements required.';
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
return 'Poor quality experience. Immediate action needed across multiple areas.';
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Launch report in default browser
|
|
951
|
+
*/
|
|
952
|
+
async launchReportInBrowser(reportPath) {
|
|
953
|
+
try {
|
|
954
|
+
const platform = process.platform;
|
|
955
|
+
let command;
|
|
956
|
+
if (platform === 'darwin') {
|
|
957
|
+
command = `open "${reportPath}"`;
|
|
958
|
+
}
|
|
959
|
+
else if (platform === 'win32') {
|
|
960
|
+
command = `start "" "${reportPath}"`;
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
// Linux and others
|
|
964
|
+
command = `xdg-open "${reportPath}"`;
|
|
965
|
+
}
|
|
966
|
+
await execAsync(command);
|
|
967
|
+
this.logger.info(`Launched report in browser: ${reportPath}`);
|
|
968
|
+
}
|
|
969
|
+
catch (error) {
|
|
970
|
+
this.logger.warn(`Failed to auto-launch browser:`, error);
|
|
971
|
+
this.logger.info(`Report available at: ${reportPath}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Collect QX context from target using Playwright
|
|
295
976
|
*/
|
|
296
977
|
async collectQXContext(target, additionalContext) {
|
|
297
978
|
this.logger.debug(`Collecting QX context for: ${target}`);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
979
|
+
try {
|
|
980
|
+
// Launch browser if not already running
|
|
981
|
+
if (!this.browser) {
|
|
982
|
+
this.logger.debug('Launching browser...');
|
|
983
|
+
this.browser = await playwright_1.chromium.launch({
|
|
984
|
+
headless: true,
|
|
985
|
+
timeout: 30000, // 30 second timeout for launch
|
|
986
|
+
args: [
|
|
987
|
+
'--no-sandbox',
|
|
988
|
+
'--disable-setuid-sandbox',
|
|
989
|
+
'--disable-dev-shm-usage',
|
|
990
|
+
'--disable-accelerated-2d-canvas',
|
|
991
|
+
'--no-first-run',
|
|
992
|
+
'--no-zygote',
|
|
993
|
+
'--single-process', // Important for containers
|
|
994
|
+
'--disable-gpu'
|
|
995
|
+
]
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
// Create new page
|
|
999
|
+
this.page = await this.browser.newPage();
|
|
1000
|
+
this.logger.debug(`Navigating to ${target}...`);
|
|
1001
|
+
// Navigate to target with timeout - try quick load first
|
|
1002
|
+
try {
|
|
1003
|
+
await this.page.goto(target, { waitUntil: 'commit', timeout: 15000 });
|
|
1004
|
+
}
|
|
1005
|
+
catch (navError) {
|
|
1006
|
+
this.logger.warn(`Quick navigation failed, trying basic load: ${navError}`);
|
|
1007
|
+
// Fallback: just navigate without waiting
|
|
1008
|
+
await this.page.goto(target, { waitUntil: 'commit', timeout: 10000 });
|
|
1009
|
+
}
|
|
1010
|
+
// Wait a bit for some content to load
|
|
1011
|
+
await this.page.waitForTimeout(1000);
|
|
1012
|
+
this.logger.debug('Extracting page context...');
|
|
1013
|
+
// Extract page context WITH ACTUAL CONTENT for contextual analysis
|
|
1014
|
+
const pageContext = await this.page.evaluate(() => {
|
|
1015
|
+
const countElements = (selector) => document.querySelectorAll(selector).length;
|
|
1016
|
+
const getText = (selector, limit = 5) => Array.from(document.querySelectorAll(selector)).slice(0, limit).map(el => el.textContent?.trim() || '').filter(t => t.length > 0);
|
|
1017
|
+
// Extract navigation items for context understanding
|
|
1018
|
+
const navItems = getText('nav a, nav button, [role="navigation"] a');
|
|
1019
|
+
const headings = {
|
|
1020
|
+
h1: getText('h1', 3),
|
|
1021
|
+
h2: getText('h2', 5),
|
|
1022
|
+
h3: getText('h3', 5)
|
|
1023
|
+
};
|
|
1024
|
+
// Extract form purposes from labels/placeholders
|
|
1025
|
+
const formPurposes = Array.from(document.querySelectorAll('form')).map(form => {
|
|
1026
|
+
const labels = Array.from(form.querySelectorAll('label, input[placeholder]')).slice(0, 3);
|
|
1027
|
+
return labels.map(el => el.placeholder || el.textContent?.trim() || '').filter(t => t.length > 0).join(', ');
|
|
1028
|
+
});
|
|
1029
|
+
// Extract button purposes
|
|
1030
|
+
const buttonPurposes = getText('button, [role="button"], input[type="submit"]', 10);
|
|
1031
|
+
// Extract link context (first 20 meaningful links)
|
|
1032
|
+
const linkTexts = getText('a[href]:not([href="#"]):not([href=""])', 20);
|
|
1033
|
+
// Extract main content snippets for purpose understanding
|
|
1034
|
+
const mainContent = document.querySelector('main, article, [role="main"]');
|
|
1035
|
+
const contentSnippet = mainContent?.textContent?.trim().substring(0, 300) || '';
|
|
1036
|
+
return {
|
|
1037
|
+
title: document.title,
|
|
1038
|
+
url: window.location.href,
|
|
1039
|
+
viewport: {
|
|
1040
|
+
width: window.innerWidth,
|
|
1041
|
+
height: window.innerHeight
|
|
1042
|
+
},
|
|
1043
|
+
content: {
|
|
1044
|
+
headings,
|
|
1045
|
+
navigationItems: navItems,
|
|
1046
|
+
buttonPurposes,
|
|
1047
|
+
formPurposes,
|
|
1048
|
+
linkTexts,
|
|
1049
|
+
mainContentSnippet: contentSnippet
|
|
1050
|
+
},
|
|
1051
|
+
elements: {
|
|
1052
|
+
total: document.querySelectorAll('*').length,
|
|
1053
|
+
buttons: countElements('button, [role="button"], input[type="button"], input[type="submit"]'),
|
|
1054
|
+
forms: countElements('form'),
|
|
1055
|
+
inputs: countElements('input, textarea, select'),
|
|
1056
|
+
links: countElements('a'),
|
|
1057
|
+
headings: {
|
|
1058
|
+
h1: countElements('h1'),
|
|
1059
|
+
h2: countElements('h2'),
|
|
1060
|
+
h3: countElements('h3')
|
|
1061
|
+
},
|
|
1062
|
+
images: countElements('img'),
|
|
1063
|
+
videos: countElements('video'),
|
|
1064
|
+
iframes: countElements('iframe')
|
|
1065
|
+
},
|
|
1066
|
+
semantic: {
|
|
1067
|
+
hasNav: countElements('nav') > 0,
|
|
1068
|
+
hasHeader: countElements('header') > 0,
|
|
1069
|
+
hasFooter: countElements('footer') > 0,
|
|
1070
|
+
hasMain: countElements('main') > 0,
|
|
1071
|
+
hasAside: countElements('aside') > 0,
|
|
1072
|
+
hasArticle: countElements('article') > 0,
|
|
1073
|
+
hasSection: countElements('section') > 0
|
|
1074
|
+
},
|
|
1075
|
+
accessibility: {
|
|
1076
|
+
ariaLabels: countElements('[aria-label]'),
|
|
1077
|
+
ariaDescriptions: countElements('[aria-describedby]'),
|
|
1078
|
+
altTexts: Array.from(document.querySelectorAll('img')).filter(img => img.hasAttribute('alt')).length,
|
|
1079
|
+
totalImages: countElements('img'),
|
|
1080
|
+
landmarkRoles: countElements('[role="banner"], [role="navigation"], [role="main"], [role="complementary"], [role="contentinfo"]'),
|
|
1081
|
+
focusableElements: countElements('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])')
|
|
1082
|
+
},
|
|
1083
|
+
errors: {
|
|
1084
|
+
consoleErrors: window.__errors || [],
|
|
1085
|
+
hasErrorMessages: countElements('.error, [role="alert"], .alert-danger, .text-danger') > 0
|
|
1086
|
+
},
|
|
1087
|
+
meta: {
|
|
1088
|
+
description: document.querySelector('meta[name="description"]')?.content || '',
|
|
1089
|
+
keywords: document.querySelector('meta[name="keywords"]')?.content || '',
|
|
1090
|
+
viewport: document.querySelector('meta[name="viewport"]')?.content || ''
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
});
|
|
1094
|
+
// Capture performance metrics
|
|
1095
|
+
const performanceMetrics = await this.page.evaluate(() => {
|
|
1096
|
+
const perf = performance.getEntriesByType('navigation')[0];
|
|
1097
|
+
return {
|
|
1098
|
+
loadTime: perf?.loadEventEnd - perf?.fetchStart || 0,
|
|
1099
|
+
domReady: perf?.domContentLoadedEventEnd - perf?.fetchStart || 0,
|
|
1100
|
+
firstPaint: performance.getEntriesByType('paint').find(e => e.name === 'first-paint')?.startTime || 0
|
|
1101
|
+
};
|
|
1102
|
+
});
|
|
1103
|
+
const context = {
|
|
1104
|
+
url: target,
|
|
1105
|
+
title: pageContext.title,
|
|
1106
|
+
domMetrics: {
|
|
1107
|
+
totalElements: pageContext.elements.total,
|
|
1108
|
+
interactiveElements: pageContext.elements.buttons + pageContext.elements.inputs + pageContext.elements.links,
|
|
1109
|
+
forms: pageContext.elements.forms,
|
|
1110
|
+
inputs: pageContext.elements.inputs,
|
|
1111
|
+
buttons: pageContext.elements.buttons,
|
|
1112
|
+
semanticStructure: pageContext.semantic
|
|
1113
|
+
},
|
|
1114
|
+
accessibility: {
|
|
1115
|
+
ariaLabelsCount: pageContext.accessibility.ariaLabels,
|
|
1116
|
+
altTextsCoverage: pageContext.accessibility.totalImages > 0
|
|
1117
|
+
? (pageContext.accessibility.altTexts / pageContext.accessibility.totalImages) * 100
|
|
1118
|
+
: 100,
|
|
1119
|
+
focusableElementsCount: pageContext.accessibility.focusableElements,
|
|
1120
|
+
landmarkRoles: pageContext.accessibility.landmarkRoles
|
|
1121
|
+
},
|
|
1122
|
+
performance: performanceMetrics,
|
|
1123
|
+
errorIndicators: pageContext.errors,
|
|
1124
|
+
metadata: pageContext.meta,
|
|
1125
|
+
custom: additionalContext || {}
|
|
1126
|
+
};
|
|
1127
|
+
// Close page but keep browser for potential reuse
|
|
1128
|
+
await this.page.close();
|
|
1129
|
+
this.page = null;
|
|
1130
|
+
// Store context for later retrieval
|
|
1131
|
+
await this.storeMemory(`qx-context:${target}`, context);
|
|
1132
|
+
this.logger.debug('Context collection completed successfully');
|
|
1133
|
+
return context;
|
|
1134
|
+
}
|
|
1135
|
+
catch (error) {
|
|
1136
|
+
this.logger.error(`Failed to collect QX context: ${error}`);
|
|
1137
|
+
// Clean up on error
|
|
1138
|
+
if (this.page) {
|
|
1139
|
+
try {
|
|
1140
|
+
await this.page.close();
|
|
1141
|
+
}
|
|
1142
|
+
catch (e) {
|
|
1143
|
+
// Ignore close errors
|
|
1144
|
+
}
|
|
1145
|
+
this.page = null;
|
|
1146
|
+
}
|
|
1147
|
+
// Return minimal context on error
|
|
1148
|
+
return {
|
|
1149
|
+
url: target,
|
|
1150
|
+
custom: additionalContext || {},
|
|
1151
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Detect domain-specific failure modes based on site type
|
|
1157
|
+
*/
|
|
1158
|
+
detectDomainSpecificFailures(context, title, description, complexity) {
|
|
1159
|
+
const failures = [];
|
|
1160
|
+
const titleLower = title.toLowerCase();
|
|
1161
|
+
const descLower = description.toLowerCase();
|
|
1162
|
+
const forms = context.domMetrics?.forms || 0;
|
|
1163
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
1164
|
+
// E-commerce / Travel Booking sites
|
|
1165
|
+
if (titleLower.includes('hotel') || titleLower.includes('booking') || titleLower.includes('travel') ||
|
|
1166
|
+
titleLower.includes('shop') || titleLower.includes('store') || titleLower.includes('buy') ||
|
|
1167
|
+
descLower.includes('book') || descLower.includes('reservation') || descLower.includes('hotel')) {
|
|
1168
|
+
failures.push({
|
|
1169
|
+
description: 'Search and filter complexity may overwhelm users with too many options',
|
|
1170
|
+
severity: 'medium',
|
|
1171
|
+
likelihood: 'likely'
|
|
1172
|
+
});
|
|
1173
|
+
failures.push({
|
|
1174
|
+
description: 'Booking/checkout flow friction points may cause cart abandonment',
|
|
1175
|
+
severity: 'high',
|
|
1176
|
+
likelihood: 'likely'
|
|
1177
|
+
});
|
|
1178
|
+
failures.push({
|
|
1179
|
+
description: 'Price transparency issues or hidden fees may erode user trust',
|
|
1180
|
+
severity: 'high',
|
|
1181
|
+
likelihood: 'possible'
|
|
1182
|
+
});
|
|
1183
|
+
if (complexity === 'complex') {
|
|
1184
|
+
failures.push({
|
|
1185
|
+
description: 'Multi-step booking process may lose users if progress is not clearly indicated',
|
|
1186
|
+
severity: 'medium',
|
|
1187
|
+
likelihood: 'likely'
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
// Content/Blog/Magazine sites
|
|
1192
|
+
else if (titleLower.includes('blog') || titleLower.includes('article') || titleLower.includes('news') ||
|
|
1193
|
+
titleLower.includes('magazine') || titleLower.includes('testers') || titleLower.includes('testing')) {
|
|
1194
|
+
failures.push({
|
|
1195
|
+
description: 'Content discoverability - users may struggle to find relevant articles without robust search',
|
|
1196
|
+
severity: 'medium',
|
|
1197
|
+
likelihood: 'likely'
|
|
1198
|
+
});
|
|
1199
|
+
failures.push({
|
|
1200
|
+
description: 'Reading experience on mobile devices may not be optimized for long-form content',
|
|
1201
|
+
severity: 'medium',
|
|
1202
|
+
likelihood: 'possible'
|
|
1203
|
+
});
|
|
1204
|
+
failures.push({
|
|
1205
|
+
description: 'Archive navigation complexity may overwhelm readers looking for specific topics',
|
|
1206
|
+
severity: 'low',
|
|
1207
|
+
likelihood: 'possible'
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
// SaaS / Web Applications
|
|
1211
|
+
else if (titleLower.includes('dashboard') || titleLower.includes('app') || titleLower.includes('platform') ||
|
|
1212
|
+
interactiveElements > 50) {
|
|
1213
|
+
failures.push({
|
|
1214
|
+
description: 'Complex workflows may confuse new users without proper onboarding',
|
|
1215
|
+
severity: 'medium',
|
|
1216
|
+
likelihood: 'likely'
|
|
1217
|
+
});
|
|
1218
|
+
failures.push({
|
|
1219
|
+
description: 'Data visualization and information density may cause cognitive overload',
|
|
1220
|
+
severity: 'medium',
|
|
1221
|
+
likelihood: 'possible'
|
|
1222
|
+
});
|
|
1223
|
+
failures.push({
|
|
1224
|
+
description: 'Error messages may not provide actionable recovery steps',
|
|
1225
|
+
severity: 'medium',
|
|
1226
|
+
likelihood: 'likely'
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
// Form-heavy sites
|
|
1230
|
+
else if (forms > 0) {
|
|
1231
|
+
failures.push({
|
|
1232
|
+
description: 'Form validation errors may not be clearly communicated to users',
|
|
1233
|
+
severity: 'medium',
|
|
1234
|
+
likelihood: 'likely'
|
|
1235
|
+
});
|
|
1236
|
+
failures.push({
|
|
1237
|
+
description: 'Required field indicators may not be consistently applied',
|
|
1238
|
+
severity: 'low',
|
|
1239
|
+
likelihood: 'possible'
|
|
1240
|
+
});
|
|
1241
|
+
failures.push({
|
|
1242
|
+
description: 'Form submission failure recovery path may not be clear',
|
|
1243
|
+
severity: 'medium',
|
|
1244
|
+
likelihood: 'possible'
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
// Return only the most relevant failures (max 5)
|
|
1248
|
+
return failures.slice(0, 5);
|
|
307
1249
|
}
|
|
308
1250
|
/**
|
|
309
1251
|
* Analyze problem using Rule of Three and complexity assessment
|
|
310
1252
|
*/
|
|
311
|
-
async analyzeProblem(
|
|
1253
|
+
async analyzeProblem(context) {
|
|
312
1254
|
this.logger.debug('Analyzing problem');
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
1255
|
+
const title = context.title || 'Untitled page';
|
|
1256
|
+
const description = context.metadata?.description || '';
|
|
1257
|
+
const hasError = context.errorIndicators?.hasErrorMessages || false;
|
|
1258
|
+
let problemStatement = `Evaluate quality experience of "${title}"`;
|
|
1259
|
+
if (description) {
|
|
1260
|
+
problemStatement += ` - ${description.substring(0, 100)}`;
|
|
1261
|
+
}
|
|
1262
|
+
const totalElements = context.domMetrics?.totalElements || 0;
|
|
1263
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
1264
|
+
const forms = context.domMetrics?.forms || 0;
|
|
1265
|
+
let complexity;
|
|
1266
|
+
if (totalElements > 500 || interactiveElements > 50 || forms > 3) {
|
|
1267
|
+
complexity = 'complex';
|
|
1268
|
+
}
|
|
1269
|
+
else if (totalElements > 200 || interactiveElements > 20 || forms > 1) {
|
|
1270
|
+
complexity = 'moderate';
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
complexity = 'simple';
|
|
1274
|
+
}
|
|
1275
|
+
const breakdown = [];
|
|
1276
|
+
if (context.domMetrics?.semanticStructure?.hasNav)
|
|
1277
|
+
breakdown.push('Navigation structure');
|
|
1278
|
+
if (forms > 0)
|
|
1279
|
+
breakdown.push(`Form interactions (${forms} forms)`);
|
|
1280
|
+
if (interactiveElements > 0)
|
|
1281
|
+
breakdown.push(`User interactions (${interactiveElements} elements)`);
|
|
1282
|
+
if (context.accessibility)
|
|
1283
|
+
breakdown.push('Accessibility compliance');
|
|
1284
|
+
if (context.performance)
|
|
1285
|
+
breakdown.push('Performance metrics');
|
|
1286
|
+
const potentialFailures = [];
|
|
1287
|
+
if (!context.domMetrics?.semanticStructure?.hasMain) {
|
|
1288
|
+
potentialFailures.push({
|
|
1289
|
+
description: 'Missing main content landmark - users may struggle to find primary content',
|
|
1290
|
+
severity: 'medium',
|
|
1291
|
+
likelihood: 'likely'
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
if (context.accessibility && (context.accessibility.altTextsCoverage || 0) < 80) {
|
|
1295
|
+
potentialFailures.push({
|
|
1296
|
+
description: 'Poor image alt text coverage - screen reader users affected',
|
|
1297
|
+
severity: 'high',
|
|
1298
|
+
likelihood: 'very-likely'
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
if (hasError) {
|
|
1302
|
+
potentialFailures.push({
|
|
1303
|
+
description: 'Visible error messages detected - potential usability issues',
|
|
1304
|
+
severity: 'medium',
|
|
1305
|
+
likelihood: 'likely'
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
if (context.performance && (context.performance.loadTime || 0) > 3000) {
|
|
1309
|
+
potentialFailures.push({
|
|
1310
|
+
description: 'Slow load time - user frustration and abandonment risk',
|
|
1311
|
+
severity: 'high',
|
|
1312
|
+
likelihood: 'very-likely'
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
if (!context.metadata?.viewport) {
|
|
1316
|
+
potentialFailures.push({
|
|
1317
|
+
description: 'Missing viewport meta tag - mobile responsiveness issues',
|
|
1318
|
+
severity: 'medium',
|
|
1319
|
+
likelihood: 'possible'
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
// ENHANCED: Add domain-specific failure modes based on site type and context
|
|
1323
|
+
const domainFailures = this.detectDomainSpecificFailures(context, title, description, complexity);
|
|
1324
|
+
potentialFailures.push(...domainFailures);
|
|
1325
|
+
// Rule of Three: Ensure at least 3 failure modes are identified
|
|
1326
|
+
if (potentialFailures.length < 3) {
|
|
1327
|
+
// Add generic contextual failures for complex sites
|
|
1328
|
+
if (complexity === 'complex') {
|
|
1329
|
+
if (potentialFailures.length < 3) {
|
|
1330
|
+
potentialFailures.push({
|
|
1331
|
+
description: 'Complex interaction flows may confuse first-time users',
|
|
1332
|
+
severity: 'medium',
|
|
1333
|
+
likelihood: 'possible'
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
if (potentialFailures.length < 3) {
|
|
1337
|
+
potentialFailures.push({
|
|
1338
|
+
description: 'Multiple interactive elements increase cognitive load',
|
|
1339
|
+
severity: 'low',
|
|
1340
|
+
likelihood: 'possible'
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
if (potentialFailures.length < 3) {
|
|
1344
|
+
potentialFailures.push({
|
|
1345
|
+
description: 'Error recovery paths may not be clear in complex workflows',
|
|
1346
|
+
severity: 'medium',
|
|
1347
|
+
likelihood: 'possible'
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
let clarityScore = 50;
|
|
1353
|
+
if (title && title !== 'Untitled page')
|
|
1354
|
+
clarityScore += 15;
|
|
1355
|
+
if (description)
|
|
1356
|
+
clarityScore += 15;
|
|
1357
|
+
if (breakdown.length >= 3)
|
|
1358
|
+
clarityScore += 10;
|
|
1359
|
+
if (context.domMetrics?.semanticStructure?.hasMain)
|
|
1360
|
+
clarityScore += 10;
|
|
1361
|
+
clarityScore = Math.min(100, clarityScore);
|
|
1362
|
+
return {
|
|
1363
|
+
problemStatement,
|
|
1364
|
+
complexity,
|
|
1365
|
+
breakdown,
|
|
1366
|
+
potentialFailures,
|
|
1367
|
+
clarityScore
|
|
321
1368
|
};
|
|
322
|
-
return analysis;
|
|
323
1369
|
}
|
|
324
1370
|
/**
|
|
325
1371
|
* Analyze user needs
|
|
326
1372
|
*/
|
|
327
|
-
async analyzeUserNeeds(
|
|
1373
|
+
async analyzeUserNeeds(context, problemAnalysis) {
|
|
328
1374
|
this.logger.debug('Analyzing user needs');
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
1375
|
+
const needs = [];
|
|
1376
|
+
const challenges = [];
|
|
1377
|
+
const semantic = context.domMetrics?.semanticStructure;
|
|
1378
|
+
const accessibility = context.accessibility;
|
|
1379
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
1380
|
+
const forms = context.domMetrics?.forms || 0;
|
|
1381
|
+
// Must-have features (critical for basic functionality)
|
|
1382
|
+
if (semantic?.hasNav) {
|
|
1383
|
+
needs.push({ description: 'Clear navigation to find content', priority: 'must-have', addressed: true });
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
challenges.push('Missing navigation structure - users cannot easily explore site');
|
|
1387
|
+
needs.push({ description: 'Clear navigation to find content', priority: 'must-have', addressed: false });
|
|
1388
|
+
}
|
|
1389
|
+
if (interactiveElements > 0) {
|
|
1390
|
+
needs.push({ description: 'Interactive elements for engagement', priority: 'must-have', addressed: true });
|
|
1391
|
+
}
|
|
1392
|
+
if (accessibility && (accessibility.focusableElementsCount || 0) > 0) {
|
|
1393
|
+
needs.push({ description: 'Keyboard navigation support', priority: 'must-have', addressed: true });
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
challenges.push('Limited keyboard navigation - inaccessible to some users');
|
|
1397
|
+
needs.push({ description: 'Keyboard navigation support', priority: 'must-have', addressed: false });
|
|
1398
|
+
}
|
|
1399
|
+
// Should-have features (important for good UX)
|
|
1400
|
+
if (semantic?.hasHeader) {
|
|
1401
|
+
needs.push({ description: 'Consistent page header for orientation', priority: 'should-have', addressed: true });
|
|
1402
|
+
}
|
|
1403
|
+
if (semantic?.hasFooter) {
|
|
1404
|
+
needs.push({ description: 'Footer with supporting information', priority: 'should-have', addressed: true });
|
|
1405
|
+
}
|
|
1406
|
+
if (accessibility && (accessibility.altTextsCoverage || 0) > 50) {
|
|
1407
|
+
needs.push({ description: 'Image descriptions for screen readers', priority: 'should-have', addressed: true });
|
|
1408
|
+
}
|
|
1409
|
+
else if (accessibility && (accessibility.altTextsCoverage || 0) < 50) {
|
|
1410
|
+
challenges.push('Poor alt text coverage - images not accessible');
|
|
1411
|
+
needs.push({ description: 'Image descriptions for screen readers', priority: 'should-have', addressed: false });
|
|
1412
|
+
}
|
|
1413
|
+
if (context.performance && (context.performance.loadTime || 0) < 3000) {
|
|
1414
|
+
needs.push({ description: 'Fast page load time', priority: 'should-have', addressed: true });
|
|
1415
|
+
}
|
|
1416
|
+
else if (context.performance && (context.performance.loadTime || 0) >= 3000) {
|
|
1417
|
+
challenges.push('Slow load time - user frustration risk');
|
|
1418
|
+
needs.push({ description: 'Fast page load time', priority: 'should-have', addressed: false });
|
|
1419
|
+
}
|
|
1420
|
+
// Nice-to-have features (enhancements)
|
|
1421
|
+
if (semantic?.hasAside) {
|
|
1422
|
+
needs.push({ description: 'Supplementary content sections', priority: 'nice-to-have', addressed: true });
|
|
1423
|
+
}
|
|
1424
|
+
if (accessibility && (accessibility.landmarkRoles || 0) > 3) {
|
|
1425
|
+
needs.push({ description: 'Rich ARIA landmarks for navigation', priority: 'nice-to-have', addressed: true });
|
|
1426
|
+
}
|
|
1427
|
+
if (forms > 0) {
|
|
1428
|
+
needs.push({ description: 'Form interactions for user input', priority: 'nice-to-have', addressed: true });
|
|
1429
|
+
}
|
|
1430
|
+
// Determine suitability
|
|
1431
|
+
const addressedMustHaves = needs.filter(n => n.priority === 'must-have' && n.addressed).length;
|
|
1432
|
+
const totalMustHaves = needs.filter(n => n.priority === 'must-have').length;
|
|
1433
|
+
let suitability;
|
|
1434
|
+
if (challenges.length === 0 && addressedMustHaves >= 3) {
|
|
1435
|
+
suitability = 'excellent';
|
|
1436
|
+
}
|
|
1437
|
+
else if (challenges.length <= 1 && addressedMustHaves >= 2) {
|
|
1438
|
+
suitability = 'good';
|
|
1439
|
+
}
|
|
1440
|
+
else if (challenges.length <= 2 && addressedMustHaves >= 2) {
|
|
1441
|
+
suitability = 'adequate';
|
|
1442
|
+
}
|
|
1443
|
+
else {
|
|
1444
|
+
suitability = 'poor';
|
|
1445
|
+
}
|
|
1446
|
+
// Calculate alignment score
|
|
1447
|
+
let alignmentScore = 40;
|
|
1448
|
+
alignmentScore += addressedMustHaves * 10;
|
|
1449
|
+
alignmentScore += needs.filter(n => n.priority === 'should-have' && n.addressed).length * 5;
|
|
1450
|
+
alignmentScore += needs.filter(n => n.priority === 'nice-to-have' && n.addressed).length * 2;
|
|
1451
|
+
alignmentScore -= challenges.length * 8;
|
|
1452
|
+
alignmentScore = Math.max(0, Math.min(100, alignmentScore));
|
|
1453
|
+
return {
|
|
1454
|
+
needs,
|
|
1455
|
+
suitability,
|
|
1456
|
+
challenges,
|
|
1457
|
+
alignmentScore
|
|
334
1458
|
};
|
|
335
|
-
return analysis;
|
|
336
1459
|
}
|
|
337
1460
|
/**
|
|
338
1461
|
* Analyze business needs
|
|
339
1462
|
*/
|
|
340
|
-
async analyzeBusinessNeeds(
|
|
1463
|
+
async analyzeBusinessNeeds(context, problemAnalysis) {
|
|
341
1464
|
this.logger.debug('Analyzing business needs');
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
1465
|
+
const forms = context.domMetrics?.forms || 0;
|
|
1466
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
1467
|
+
const performance = context.performance;
|
|
1468
|
+
const hasErrors = context.errorIndicators?.hasErrorMessages || false;
|
|
1469
|
+
let primaryGoal;
|
|
1470
|
+
let kpisAffected = [];
|
|
1471
|
+
if (forms > 2) {
|
|
1472
|
+
primaryGoal = 'business-ease'; // Conversion focus leans business
|
|
1473
|
+
kpisAffected = ['Form completion rate', 'Lead generation', 'User sign-ups'];
|
|
1474
|
+
}
|
|
1475
|
+
else if (interactiveElements > 30) {
|
|
1476
|
+
primaryGoal = 'user-experience'; // Engagement focus leans UX
|
|
1477
|
+
kpisAffected = ['Time on site', 'Click-through rate', 'User engagement'];
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
primaryGoal = 'balanced';
|
|
1481
|
+
kpisAffected = ['Content consumption', 'Bounce rate', 'Page views'];
|
|
1482
|
+
}
|
|
1483
|
+
const crossTeamImpact = [];
|
|
1484
|
+
if (forms > 0) {
|
|
1485
|
+
crossTeamImpact.push({
|
|
1486
|
+
team: 'Marketing',
|
|
1487
|
+
impactType: 'positive',
|
|
1488
|
+
description: 'Form conversion optimization needed'
|
|
1489
|
+
});
|
|
1490
|
+
crossTeamImpact.push({
|
|
1491
|
+
team: 'Development',
|
|
1492
|
+
impactType: 'neutral',
|
|
1493
|
+
description: 'Form validation and submission handling'
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
if (context.accessibility && (context.accessibility.altTextsCoverage || 0) < 100) {
|
|
1497
|
+
crossTeamImpact.push({
|
|
1498
|
+
team: 'Content',
|
|
1499
|
+
impactType: 'negative',
|
|
1500
|
+
description: 'Image alt text creation required'
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
if (performance && (performance.loadTime || 0) > 2000) {
|
|
1504
|
+
crossTeamImpact.push({
|
|
1505
|
+
team: 'Engineering',
|
|
1506
|
+
impactType: 'negative',
|
|
1507
|
+
description: 'Performance optimization needed'
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
if (problemAnalysis.complexity === 'complex') {
|
|
1511
|
+
crossTeamImpact.push({
|
|
1512
|
+
team: 'QA',
|
|
1513
|
+
impactType: 'neutral',
|
|
1514
|
+
description: 'Comprehensive testing strategy required'
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
let compromisesUX = false;
|
|
1518
|
+
if (hasErrors) {
|
|
1519
|
+
compromisesUX = true;
|
|
1520
|
+
}
|
|
1521
|
+
if (context.accessibility && (context.accessibility.altTextsCoverage || 0) < 50) {
|
|
1522
|
+
compromisesUX = true;
|
|
1523
|
+
}
|
|
1524
|
+
if (performance && (performance.loadTime || 0) > 4000) {
|
|
1525
|
+
compromisesUX = true;
|
|
1526
|
+
}
|
|
1527
|
+
const impactsKPIs = kpisAffected.length > 0;
|
|
1528
|
+
let alignmentScore = 50;
|
|
1529
|
+
if (kpisAffected.length > 0)
|
|
1530
|
+
alignmentScore += 15;
|
|
1531
|
+
if (crossTeamImpact.length > 0)
|
|
1532
|
+
alignmentScore += 10;
|
|
1533
|
+
if (!compromisesUX)
|
|
1534
|
+
alignmentScore += 20;
|
|
1535
|
+
if (impactsKPIs)
|
|
1536
|
+
alignmentScore += 5;
|
|
1537
|
+
alignmentScore = Math.min(100, alignmentScore);
|
|
1538
|
+
return {
|
|
1539
|
+
primaryGoal,
|
|
1540
|
+
kpisAffected,
|
|
1541
|
+
crossTeamImpact,
|
|
1542
|
+
compromisesUX,
|
|
1543
|
+
impactsKPIs,
|
|
1544
|
+
alignmentScore
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Analyze Creativity - Drawing inspiration from diverse domains
|
|
1549
|
+
*/
|
|
1550
|
+
async analyzeCreativity(context, problemAnalysis) {
|
|
1551
|
+
this.logger.debug('Analyzing creativity and innovation');
|
|
1552
|
+
const innovativeApproaches = [];
|
|
1553
|
+
// Analyze based on problem complexity
|
|
1554
|
+
if (problemAnalysis.complexity === 'complex' || problemAnalysis.complexity === 'moderate') {
|
|
1555
|
+
// Philosophy-inspired: Question fundamental assumptions
|
|
1556
|
+
innovativeApproaches.push({
|
|
1557
|
+
description: 'Question fundamental assumptions about user mental models and expected workflows',
|
|
1558
|
+
inspirationSource: 'philosophy',
|
|
1559
|
+
applicability: 'high',
|
|
1560
|
+
novelty: 'moderately-novel'
|
|
1561
|
+
});
|
|
1562
|
+
// Medicine-inspired: Diagnostic approach
|
|
1563
|
+
if (context.errorIndicators?.hasErrorMessages) {
|
|
1564
|
+
innovativeApproaches.push({
|
|
1565
|
+
description: 'Apply diagnostic testing - systematically isolate error sources through controlled scenarios',
|
|
1566
|
+
inspirationSource: 'medicine',
|
|
1567
|
+
applicability: 'high',
|
|
1568
|
+
novelty: 'moderately-novel'
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
// E-commerce/Fashion-inspired: User journey and aesthetics
|
|
1573
|
+
if (context.domMetrics?.forms && context.domMetrics.forms > 0) {
|
|
1574
|
+
innovativeApproaches.push({
|
|
1575
|
+
description: 'Test checkout/form flows like fashion retail - focus on friction points, abandonment triggers',
|
|
1576
|
+
inspirationSource: 'e-commerce',
|
|
1577
|
+
applicability: 'high',
|
|
1578
|
+
novelty: 'incremental'
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
// Social Science-inspired: Cultural sensitivity and demographics
|
|
1582
|
+
innovativeApproaches.push({
|
|
1583
|
+
description: 'Analyze through diverse demographic lenses (age, gender, culture, ability) for inclusive testing',
|
|
1584
|
+
inspirationSource: 'social science',
|
|
1585
|
+
applicability: 'high',
|
|
1586
|
+
novelty: 'moderately-novel'
|
|
1587
|
+
});
|
|
1588
|
+
// Gaming-inspired: Edge cases and exploits
|
|
1589
|
+
if (context.domMetrics?.interactiveElements && context.domMetrics.interactiveElements > 20) {
|
|
1590
|
+
innovativeApproaches.push({
|
|
1591
|
+
description: 'Test for "game-breaking" exploits - unexpected interaction sequences, boundary conditions',
|
|
1592
|
+
inspirationSource: 'gaming',
|
|
1593
|
+
applicability: 'medium',
|
|
1594
|
+
novelty: 'highly-novel'
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
const domainsExplored = [...new Set(innovativeApproaches.map(a => a.inspirationSource))];
|
|
1598
|
+
const perspectives = [
|
|
1599
|
+
'Unexperienced user perspective (fresh eyes)',
|
|
1600
|
+
'Power user perspective (efficiency focus)',
|
|
1601
|
+
'Accessibility perspective (assistive tech users)',
|
|
1602
|
+
'International perspective (cultural differences)'
|
|
1603
|
+
];
|
|
1604
|
+
// Calculate creativity score
|
|
1605
|
+
let creativityScore = 50; // Base score
|
|
1606
|
+
creativityScore += innovativeApproaches.length * 8; // +8 per approach
|
|
1607
|
+
creativityScore += domainsExplored.length * 5; // +5 per domain
|
|
1608
|
+
creativityScore = Math.min(100, creativityScore);
|
|
1609
|
+
return {
|
|
1610
|
+
innovativeApproaches,
|
|
1611
|
+
domainsExplored,
|
|
1612
|
+
perspectives,
|
|
1613
|
+
creativityScore,
|
|
1614
|
+
notes: [
|
|
1615
|
+
'Creativity draws from diverse domains to uncover unconventional testing approaches',
|
|
1616
|
+
'Higher complexity problems benefit from cross-disciplinary inspiration',
|
|
1617
|
+
`Applied ${innovativeApproaches.length} creative approaches from ${domainsExplored.length} domains`
|
|
1618
|
+
]
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Analyze Design - Exactness, Intuitive, and Counter-intuitive Design
|
|
1623
|
+
*/
|
|
1624
|
+
async analyzeDesign(context, _problemAnalysis) {
|
|
1625
|
+
this.logger.debug('Analyzing design quality');
|
|
1626
|
+
// 1. Exactness Analysis - How clearly the product communicates its intent
|
|
1627
|
+
const clearElements = [];
|
|
1628
|
+
const unclearElements = [];
|
|
1629
|
+
// Check semantic structure for clarity
|
|
1630
|
+
if (context.domMetrics?.semanticStructure?.hasNav) {
|
|
1631
|
+
clearElements.push('Navigation structure clearly defined with semantic <nav> element');
|
|
1632
|
+
}
|
|
1633
|
+
if (context.domMetrics?.semanticStructure?.hasMain) {
|
|
1634
|
+
clearElements.push('Main content area clearly identified');
|
|
1635
|
+
}
|
|
1636
|
+
if (context.domMetrics?.semanticStructure?.hasHeader && context.domMetrics?.semanticStructure?.hasFooter) {
|
|
1637
|
+
clearElements.push('Header and footer provide clear page structure');
|
|
1638
|
+
}
|
|
1639
|
+
// Check for unclear elements
|
|
1640
|
+
const ariaLabels = context.semanticQuality?.ariaLabels || 0;
|
|
1641
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
1642
|
+
if (interactiveElements > 0 && ariaLabels < interactiveElements * 0.5) {
|
|
1643
|
+
unclearElements.push('Many interactive elements lack ARIA labels for clarity');
|
|
1644
|
+
}
|
|
1645
|
+
if (context.errorIndicators?.hasErrorMessages) {
|
|
1646
|
+
unclearElements.push('Error messages present - may indicate unclear user guidance');
|
|
1647
|
+
}
|
|
1648
|
+
const exactnessClarity = unclearElements.length === 0 ? 'excellent' :
|
|
1649
|
+
unclearElements.length <= 2 ? 'good' :
|
|
1650
|
+
unclearElements.length <= 4 ? 'adequate' : 'poor';
|
|
1651
|
+
let exactnessScore = 50;
|
|
1652
|
+
exactnessScore += clearElements.length * 10;
|
|
1653
|
+
exactnessScore -= unclearElements.length * 8;
|
|
1654
|
+
exactnessScore = Math.max(0, Math.min(100, exactnessScore));
|
|
1655
|
+
// 2. Intuitive Design Analysis
|
|
1656
|
+
const intuitivePatterns = [];
|
|
1657
|
+
const culturalIssues = [];
|
|
1658
|
+
if (context.domMetrics?.semanticStructure?.hasNav) {
|
|
1659
|
+
intuitivePatterns.push('Standard navigation patterns (semantic nav element)');
|
|
1660
|
+
}
|
|
1661
|
+
if (context.domMetrics?.forms && context.domMetrics.forms > 0) {
|
|
1662
|
+
intuitivePatterns.push('Standard form patterns detected');
|
|
1663
|
+
}
|
|
1664
|
+
if (context.metadata?.viewport) {
|
|
1665
|
+
intuitivePatterns.push('Responsive design viewport configured');
|
|
1666
|
+
}
|
|
1667
|
+
// Cultural sensitivity check (basic)
|
|
1668
|
+
if (context.title && /[^\x00-\x7F]/.test(context.title || '')) {
|
|
1669
|
+
// Non-ASCII characters detected - could be good (internationalization) or need review
|
|
1670
|
+
culturalIssues.push('International characters detected - ensure cultural appropriateness');
|
|
1671
|
+
}
|
|
1672
|
+
const followsConventions = intuitivePatterns.length >= 2;
|
|
1673
|
+
let intuitiveScore = followsConventions ? 70 : 50;
|
|
1674
|
+
intuitiveScore += intuitivePatterns.length * 8;
|
|
1675
|
+
intuitiveScore -= culturalIssues.length * 10;
|
|
1676
|
+
intuitiveScore = Math.max(0, Math.min(100, intuitiveScore));
|
|
1677
|
+
// 3. Counter-intuitive Design Detection
|
|
1678
|
+
const deviations = [];
|
|
1679
|
+
// Check for potential counter-intuitive patterns
|
|
1680
|
+
const buttons = context.domMetrics?.buttons || 0;
|
|
1681
|
+
const forms = context.domMetrics?.forms || 0;
|
|
1682
|
+
if (buttons > 20 && forms === 0) {
|
|
1683
|
+
deviations.push({
|
|
1684
|
+
element: 'Multiple buttons without forms',
|
|
1685
|
+
expectedBehavior: 'Forms typically accompany many buttons',
|
|
1686
|
+
actualBehavior: 'Many buttons present without traditional forms',
|
|
1687
|
+
impact: 'neutral',
|
|
1688
|
+
justification: 'May be single-page app or API-driven interface'
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
if (!context.domMetrics?.semanticStructure?.hasMain && (context.domMetrics?.totalElements || 0) > 50) {
|
|
1692
|
+
deviations.push({
|
|
1693
|
+
element: 'Complex page without main landmark',
|
|
1694
|
+
expectedBehavior: 'Complex pages typically have <main> landmark',
|
|
1695
|
+
actualBehavior: 'No main content landmark defined',
|
|
1696
|
+
impact: 'negative',
|
|
1697
|
+
justification: 'Reduces accessibility and clarity'
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
const innovativeJustification = deviations.some(d => d.impact === 'positive' || d.justification?.includes('innovation'));
|
|
1701
|
+
const freshEyesPerspective = deviations.length > 0; // We're looking from unexperienced user view
|
|
1702
|
+
const issuesCount = deviations.filter(d => d.impact === 'negative').length;
|
|
1703
|
+
// 4. Overall Design Score
|
|
1704
|
+
const overallDesignScore = Math.round((exactnessScore + intuitiveScore) / 2);
|
|
1705
|
+
return {
|
|
1706
|
+
exactness: {
|
|
1707
|
+
clarity: exactnessClarity,
|
|
1708
|
+
clearElements,
|
|
1709
|
+
unclearElements,
|
|
1710
|
+
score: exactnessScore
|
|
1711
|
+
},
|
|
1712
|
+
intuitive: {
|
|
1713
|
+
followsConventions,
|
|
1714
|
+
intuitivePatterns,
|
|
1715
|
+
culturalIssues,
|
|
1716
|
+
score: intuitiveScore
|
|
1717
|
+
},
|
|
1718
|
+
counterIntuitive: {
|
|
1719
|
+
deviations,
|
|
1720
|
+
innovativeJustification,
|
|
1721
|
+
freshEyesPerspective,
|
|
1722
|
+
issuesCount
|
|
1723
|
+
},
|
|
1724
|
+
overallDesignScore
|
|
349
1725
|
};
|
|
350
|
-
return analysis;
|
|
351
1726
|
}
|
|
352
1727
|
/**
|
|
353
1728
|
* Detect oracle problems from analysis context
|
|
@@ -370,102 +1745,219 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
370
1745
|
/**
|
|
371
1746
|
* Apply all enabled heuristics
|
|
372
1747
|
*/
|
|
373
|
-
async applyAllHeuristics(context, problemAnalysis, userNeeds, businessNeeds) {
|
|
1748
|
+
async applyAllHeuristics(context, problemAnalysis, userNeeds, businessNeeds, creativityAnalysis, designAnalysis) {
|
|
374
1749
|
if (!this.heuristicsEngine) {
|
|
375
1750
|
throw new Error('Heuristics engine not initialized');
|
|
376
1751
|
}
|
|
377
|
-
|
|
1752
|
+
// Get standard heuristics
|
|
1753
|
+
const standardHeuristics = await this.heuristicsEngine.applyAll(context, problemAnalysis, userNeeds, businessNeeds);
|
|
1754
|
+
// Add creativity and design heuristics
|
|
1755
|
+
const creativityHeuristic = {
|
|
1756
|
+
name: 'creativity-innovation',
|
|
1757
|
+
category: 'creativity',
|
|
1758
|
+
applied: true,
|
|
1759
|
+
score: creativityAnalysis.creativityScore,
|
|
1760
|
+
findings: [
|
|
1761
|
+
`Applied ${creativityAnalysis.innovativeApproaches.length} creative approaches`,
|
|
1762
|
+
`Explored ${creativityAnalysis.domainsExplored.length} diverse domains: ${creativityAnalysis.domainsExplored.join(', ')}`,
|
|
1763
|
+
...creativityAnalysis.notes
|
|
1764
|
+
],
|
|
1765
|
+
issues: creativityAnalysis.innovativeApproaches
|
|
1766
|
+
.filter(a => a.applicability === 'low')
|
|
1767
|
+
.map(a => ({
|
|
1768
|
+
description: `Low applicability approach: ${a.description}`,
|
|
1769
|
+
severity: 'low'
|
|
1770
|
+
})),
|
|
1771
|
+
recommendations: creativityAnalysis.innovativeApproaches
|
|
1772
|
+
.filter(a => a.applicability === 'high')
|
|
1773
|
+
.map(a => `Consider: ${a.description} (${a.inspirationSource})`)
|
|
1774
|
+
};
|
|
1775
|
+
const designHeuristic = {
|
|
1776
|
+
name: 'design-quality',
|
|
1777
|
+
category: 'design',
|
|
1778
|
+
applied: true,
|
|
1779
|
+
score: designAnalysis.overallDesignScore,
|
|
1780
|
+
findings: [
|
|
1781
|
+
`Exactness: ${designAnalysis.exactness.clarity} (${designAnalysis.exactness.score}/100)`,
|
|
1782
|
+
`Intuitive Design: ${designAnalysis.intuitive.followsConventions ? 'Follows' : 'Deviates from'} conventions (${designAnalysis.intuitive.score}/100)`,
|
|
1783
|
+
`Counter-intuitive issues: ${designAnalysis.counterIntuitive.issuesCount}`,
|
|
1784
|
+
...designAnalysis.exactness.clearElements.map(e => `✓ ${e}`),
|
|
1785
|
+
...designAnalysis.intuitive.intuitivePatterns.map(p => `✓ ${p}`)
|
|
1786
|
+
],
|
|
1787
|
+
issues: [
|
|
1788
|
+
...designAnalysis.exactness.unclearElements.map(e => ({
|
|
1789
|
+
description: e,
|
|
1790
|
+
severity: 'medium'
|
|
1791
|
+
})),
|
|
1792
|
+
...designAnalysis.counterIntuitive.deviations
|
|
1793
|
+
.filter(d => d.impact === 'negative')
|
|
1794
|
+
.map(d => ({
|
|
1795
|
+
description: `${d.element}: ${d.expectedBehavior} vs ${d.actualBehavior}`,
|
|
1796
|
+
severity: 'medium'
|
|
1797
|
+
}))
|
|
1798
|
+
],
|
|
1799
|
+
recommendations: [
|
|
1800
|
+
...designAnalysis.exactness.unclearElements.map(e => `Improve clarity: ${e}`),
|
|
1801
|
+
...designAnalysis.intuitive.culturalIssues.map(i => `Address: ${i}`)
|
|
1802
|
+
]
|
|
1803
|
+
};
|
|
1804
|
+
return [...standardHeuristics, creativityHeuristic, designHeuristic];
|
|
378
1805
|
}
|
|
379
1806
|
/**
|
|
380
1807
|
* Generate QX recommendations
|
|
381
1808
|
*/
|
|
382
1809
|
async generateRecommendations(problemAnalysis, userNeeds, businessNeeds, oracleProblems, impactAnalysis, heuristics, _testabilityIntegration) {
|
|
383
1810
|
const recommendations = [];
|
|
384
|
-
|
|
385
|
-
|
|
1811
|
+
let priorityCounter = 1;
|
|
1812
|
+
// Oracle problems (highest priority - match manual report structure)
|
|
1813
|
+
for (const problem of oracleProblems) {
|
|
1814
|
+
const impactScore = problem.severity === 'critical' ? 90 : problem.severity === 'high' ? 80 : problem.severity === 'medium' ? 60 : 40;
|
|
1815
|
+
const impactPct = Math.round((impactScore / 100) * 30); // Up to 30% impact
|
|
386
1816
|
recommendations.push({
|
|
387
|
-
principle: 'Problem
|
|
388
|
-
recommendation:
|
|
389
|
-
severity:
|
|
390
|
-
impact:
|
|
391
|
-
effort: 'medium',
|
|
392
|
-
priority:
|
|
393
|
-
category: '
|
|
1817
|
+
principle: 'Oracle Problem',
|
|
1818
|
+
recommendation: `Resolve: ${problem.description}`,
|
|
1819
|
+
severity: problem.severity,
|
|
1820
|
+
impact: impactScore,
|
|
1821
|
+
effort: problem.severity === 'critical' || problem.severity === 'high' ? 'high' : 'medium',
|
|
1822
|
+
priority: priorityCounter++,
|
|
1823
|
+
category: 'qa',
|
|
1824
|
+
impactPercentage: impactPct,
|
|
1825
|
+
estimatedEffort: problem.severity === 'critical' ? 'High - Critical issue' : problem.severity === 'high' ? 'High' : 'Medium'
|
|
394
1826
|
});
|
|
395
1827
|
}
|
|
396
|
-
//
|
|
397
|
-
if (
|
|
1828
|
+
// Problem clarity
|
|
1829
|
+
if (problemAnalysis.clarityScore < (this.config.thresholds?.minProblemClarity || 70)) {
|
|
1830
|
+
const gap = 70 - problemAnalysis.clarityScore;
|
|
398
1831
|
recommendations.push({
|
|
399
|
-
principle: '
|
|
400
|
-
recommendation: '
|
|
401
|
-
severity: 'high',
|
|
402
|
-
impact:
|
|
403
|
-
effort: '
|
|
404
|
-
priority:
|
|
405
|
-
category: '
|
|
1832
|
+
principle: 'Problem Understanding',
|
|
1833
|
+
recommendation: 'Improve problem statement clarity with detailed breakdown of failure modes and user scenarios',
|
|
1834
|
+
severity: gap > 25 ? 'high' : 'medium',
|
|
1835
|
+
impact: Math.round(gap * 1.2),
|
|
1836
|
+
effort: 'medium',
|
|
1837
|
+
priority: priorityCounter++,
|
|
1838
|
+
category: 'qx',
|
|
1839
|
+
impactPercentage: Math.round((gap / 70) * 20),
|
|
1840
|
+
estimatedEffort: 'Medium - Requires stakeholder workshops'
|
|
406
1841
|
});
|
|
407
1842
|
}
|
|
408
|
-
//
|
|
409
|
-
if (
|
|
1843
|
+
// User needs alignment (match manual report priority)
|
|
1844
|
+
if (userNeeds.alignmentScore < (this.config.thresholds?.minUserNeedsAlignment || 75)) {
|
|
1845
|
+
const gap = 75 - userNeeds.alignmentScore;
|
|
1846
|
+
const impactPct = Math.min(35, Math.round((gap / 75) * 100));
|
|
410
1847
|
recommendations.push({
|
|
411
|
-
principle: '
|
|
412
|
-
recommendation:
|
|
413
|
-
severity: 'medium',
|
|
414
|
-
impact:
|
|
415
|
-
effort: 'medium',
|
|
416
|
-
priority:
|
|
417
|
-
category: '
|
|
1848
|
+
principle: 'User Needs Alignment',
|
|
1849
|
+
recommendation: `Improve user needs coverage from ${userNeeds.alignmentScore}/100 to at least 75/100`,
|
|
1850
|
+
severity: gap > 20 ? 'high' : 'medium',
|
|
1851
|
+
impact: Math.round(gap * 0.9),
|
|
1852
|
+
effort: gap > 25 ? 'high' : 'medium',
|
|
1853
|
+
priority: priorityCounter++,
|
|
1854
|
+
category: 'ux',
|
|
1855
|
+
impactPercentage: impactPct,
|
|
1856
|
+
estimatedEffort: gap > 25 ? 'High - Major UX redesign' : 'Medium - UX improvements'
|
|
418
1857
|
});
|
|
419
1858
|
}
|
|
420
|
-
//
|
|
421
|
-
|
|
422
|
-
|
|
1859
|
+
// Heuristic-specific high-impact recommendations
|
|
1860
|
+
const lowScoringHeuristics = heuristics
|
|
1861
|
+
.filter(h => h.score < 70)
|
|
1862
|
+
.sort((a, b) => a.score - b.score)
|
|
1863
|
+
.slice(0, 5); // Top 5 worst
|
|
1864
|
+
lowScoringHeuristics.forEach(heuristic => {
|
|
1865
|
+
const impactScore = 75 - heuristic.score;
|
|
1866
|
+
const impactPct = Math.round((impactScore / 75) * 25);
|
|
1867
|
+
if (heuristic.recommendations.length > 0 && heuristic.heuristicType) {
|
|
423
1868
|
recommendations.push({
|
|
424
|
-
principle:
|
|
425
|
-
recommendation:
|
|
426
|
-
severity:
|
|
427
|
-
impact:
|
|
428
|
-
effort: 'high',
|
|
429
|
-
priority:
|
|
430
|
-
category:
|
|
1869
|
+
principle: this.formatHeuristicName(heuristic.heuristicType),
|
|
1870
|
+
recommendation: heuristic.recommendations[0],
|
|
1871
|
+
severity: heuristic.score < 50 ? 'high' : 'medium',
|
|
1872
|
+
impact: impactScore,
|
|
1873
|
+
effort: heuristic.score < 40 ? 'high' : 'medium',
|
|
1874
|
+
priority: priorityCounter++,
|
|
1875
|
+
category: heuristic.category,
|
|
1876
|
+
impactPercentage: impactPct,
|
|
1877
|
+
estimatedEffort: heuristic.score < 40 ? 'High - Significant work required' : 'Medium'
|
|
431
1878
|
});
|
|
432
1879
|
}
|
|
1880
|
+
});
|
|
1881
|
+
// High-impact issues from heuristics
|
|
1882
|
+
heuristics.forEach(heuristic => {
|
|
1883
|
+
if (!heuristic.heuristicType)
|
|
1884
|
+
return;
|
|
1885
|
+
heuristic.issues
|
|
1886
|
+
.filter(issue => issue.severity === 'critical' || issue.severity === 'high')
|
|
1887
|
+
.slice(0, 1) // One per heuristic
|
|
1888
|
+
.forEach(issue => {
|
|
1889
|
+
const impactScore = Math.min(85, 100 - heuristic.score);
|
|
1890
|
+
const impactPct = Math.round((impactScore / 100) * 22);
|
|
1891
|
+
recommendations.push({
|
|
1892
|
+
principle: this.formatHeuristicName(heuristic.heuristicType),
|
|
1893
|
+
recommendation: issue.description,
|
|
1894
|
+
severity: issue.severity,
|
|
1895
|
+
impact: impactScore,
|
|
1896
|
+
effort: issue.severity === 'critical' ? 'high' : 'medium',
|
|
1897
|
+
priority: priorityCounter++,
|
|
1898
|
+
category: heuristic.category,
|
|
1899
|
+
impactPercentage: impactPct,
|
|
1900
|
+
estimatedEffort: issue.severity === 'critical' ? 'High - Critical fix' : 'Medium'
|
|
1901
|
+
});
|
|
1902
|
+
});
|
|
1903
|
+
});
|
|
1904
|
+
// Business-user balance
|
|
1905
|
+
const balanceDiff = Math.abs(userNeeds.alignmentScore - businessNeeds.alignmentScore);
|
|
1906
|
+
if (balanceDiff > 15) {
|
|
1907
|
+
const impactPct = Math.round((balanceDiff / 100) * 20);
|
|
1908
|
+
const favorsUser = userNeeds.alignmentScore > businessNeeds.alignmentScore;
|
|
1909
|
+
recommendations.push({
|
|
1910
|
+
principle: 'User-Business Balance',
|
|
1911
|
+
recommendation: favorsUser
|
|
1912
|
+
? 'Strengthen business value metrics while maintaining user experience quality'
|
|
1913
|
+
: 'Enhance user experience focus to balance business-centric approach',
|
|
1914
|
+
severity: balanceDiff > 30 ? 'high' : 'medium',
|
|
1915
|
+
impact: Math.round(balanceDiff * 0.75),
|
|
1916
|
+
effort: 'medium',
|
|
1917
|
+
priority: priorityCounter++,
|
|
1918
|
+
category: 'qx',
|
|
1919
|
+
impactPercentage: impactPct,
|
|
1920
|
+
estimatedEffort: 'Medium - Requires stakeholder alignment'
|
|
1921
|
+
});
|
|
433
1922
|
}
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
1923
|
+
// Business needs (if misaligned)
|
|
1924
|
+
if (businessNeeds.alignmentScore < (this.config.thresholds?.minBusinessAlignment || 70)) {
|
|
1925
|
+
const gap = 70 - businessNeeds.alignmentScore;
|
|
1926
|
+
recommendations.push({
|
|
1927
|
+
principle: 'Business Alignment',
|
|
1928
|
+
recommendation: 'Improve alignment with business KPIs and objectives',
|
|
1929
|
+
severity: gap > 25 ? 'high' : 'medium',
|
|
1930
|
+
impact: Math.round(gap * 0.7),
|
|
1931
|
+
effort: 'medium',
|
|
1932
|
+
priority: priorityCounter++,
|
|
1933
|
+
category: 'qx',
|
|
1934
|
+
impactPercentage: Math.round((gap / 70) * 18),
|
|
1935
|
+
estimatedEffort: 'Medium - Business stakeholder review'
|
|
1936
|
+
});
|
|
449
1937
|
}
|
|
450
|
-
// Sort by
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1938
|
+
// Sort by impact percentage and limit to top 10
|
|
1939
|
+
const sorted = recommendations
|
|
1940
|
+
.sort((a, b) => (b.impactPercentage || 0) - (a.impactPercentage || 0))
|
|
1941
|
+
.slice(0, 10);
|
|
1942
|
+
// Reassign priorities
|
|
1943
|
+
sorted.forEach((rec, idx) => {
|
|
1944
|
+
rec.priority = idx + 1;
|
|
455
1945
|
});
|
|
456
|
-
return
|
|
1946
|
+
return sorted;
|
|
457
1947
|
}
|
|
458
1948
|
/**
|
|
459
1949
|
* Calculate overall QX score
|
|
460
1950
|
*/
|
|
461
|
-
calculateOverallQXScore(problemAnalysis, userNeeds, businessNeeds, impactAnalysis, heuristics) {
|
|
1951
|
+
calculateOverallQXScore(problemAnalysis, userNeeds, businessNeeds, creativityAnalysis, designAnalysis, impactAnalysis, heuristics) {
|
|
462
1952
|
// Weighted average of all components
|
|
463
1953
|
const weights = {
|
|
464
|
-
problem: 0.20
|
|
465
|
-
userNeeds: 0.25
|
|
466
|
-
businessNeeds: 0.20
|
|
467
|
-
|
|
468
|
-
|
|
1954
|
+
problem: 0.15, // Reduced from 0.20
|
|
1955
|
+
userNeeds: 0.20, // Reduced from 0.25
|
|
1956
|
+
businessNeeds: 0.15, // Reduced from 0.20
|
|
1957
|
+
creativity: 0.15, // NEW
|
|
1958
|
+
design: 0.15, // NEW
|
|
1959
|
+
impact: 0.10, // Reduced from 0.15
|
|
1960
|
+
heuristics: 0.10 // Reduced from 0.20
|
|
469
1961
|
};
|
|
470
1962
|
const heuristicsAvg = heuristics.length > 0
|
|
471
1963
|
? heuristics.reduce((sum, h) => sum + h.score, 0) / heuristics.length
|
|
@@ -474,6 +1966,8 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
474
1966
|
const score = problemAnalysis.clarityScore * weights.problem +
|
|
475
1967
|
userNeeds.alignmentScore * weights.userNeeds +
|
|
476
1968
|
businessNeeds.alignmentScore * weights.businessNeeds +
|
|
1969
|
+
creativityAnalysis.creativityScore * weights.creativity +
|
|
1970
|
+
designAnalysis.overallDesignScore * weights.design +
|
|
477
1971
|
impactScore * weights.impact +
|
|
478
1972
|
heuristicsAvg * weights.heuristics;
|
|
479
1973
|
return Math.round(score);
|
|
@@ -668,17 +2162,357 @@ class QXHeuristicsEngine {
|
|
|
668
2162
|
}
|
|
669
2163
|
return results;
|
|
670
2164
|
}
|
|
671
|
-
async apply(heuristic,
|
|
672
|
-
|
|
673
|
-
|
|
2165
|
+
async apply(heuristic, context, problemAnalysis, userNeeds, businessNeeds) {
|
|
2166
|
+
const category = this.getHeuristicCategory(heuristic);
|
|
2167
|
+
const findings = [];
|
|
2168
|
+
const issues = [];
|
|
2169
|
+
const recommendations = [];
|
|
2170
|
+
let score = 75; // Base score
|
|
2171
|
+
// Apply specific heuristic logic based on type
|
|
2172
|
+
switch (heuristic) {
|
|
2173
|
+
case qx_1.QXHeuristic.CONSISTENCY_ANALYSIS:
|
|
2174
|
+
if (context.domMetrics?.semanticStructure?.hasHeader && context.domMetrics?.semanticStructure?.hasFooter) {
|
|
2175
|
+
score = 85;
|
|
2176
|
+
findings.push('Consistent page structure with header and footer');
|
|
2177
|
+
}
|
|
2178
|
+
else {
|
|
2179
|
+
score = 60;
|
|
2180
|
+
recommendations.push('Add consistent header/footer structure');
|
|
2181
|
+
}
|
|
2182
|
+
break;
|
|
2183
|
+
case qx_1.QXHeuristic.INTUITIVE_DESIGN:
|
|
2184
|
+
const hasNav = context.domMetrics?.semanticStructure?.hasNav;
|
|
2185
|
+
const focusable = context.accessibility?.focusableElementsCount || 0;
|
|
2186
|
+
if (hasNav && focusable > 10) {
|
|
2187
|
+
score = 82;
|
|
2188
|
+
findings.push('Intuitive navigation and interaction design');
|
|
2189
|
+
}
|
|
2190
|
+
else {
|
|
2191
|
+
score = 55;
|
|
2192
|
+
issues.push({ description: 'Navigation or interaction patterns unclear', severity: 'medium' });
|
|
2193
|
+
}
|
|
2194
|
+
break;
|
|
2195
|
+
case qx_1.QXHeuristic.EXACTNESS_AND_CLARITY:
|
|
2196
|
+
// Visual hierarchy and clarity (Design category)
|
|
2197
|
+
score = 70;
|
|
2198
|
+
const hasSemanticStructure = context.domMetrics?.semanticStructure;
|
|
2199
|
+
const structureScore = [
|
|
2200
|
+
hasSemanticStructure?.hasHeader,
|
|
2201
|
+
hasSemanticStructure?.hasMain,
|
|
2202
|
+
hasSemanticStructure?.hasNav,
|
|
2203
|
+
hasSemanticStructure?.hasFooter
|
|
2204
|
+
].filter(Boolean).length;
|
|
2205
|
+
score = 50 + (structureScore * 10);
|
|
2206
|
+
if (structureScore >= 3) {
|
|
2207
|
+
findings.push('Strong visual hierarchy with semantic HTML elements');
|
|
2208
|
+
}
|
|
2209
|
+
else if (structureScore >= 2) {
|
|
2210
|
+
findings.push('Moderate visual hierarchy - some semantic elements present');
|
|
2211
|
+
recommendations.push('Add more semantic HTML5 elements for clarity');
|
|
2212
|
+
}
|
|
2213
|
+
else {
|
|
2214
|
+
issues.push({ description: 'Weak visual hierarchy - missing semantic structure', severity: 'high' });
|
|
2215
|
+
recommendations.push('Implement semantic HTML5: header, nav, main, footer');
|
|
2216
|
+
}
|
|
2217
|
+
// Title and description clarity
|
|
2218
|
+
if (context.metadata?.description && context.metadata.description.length > 20) {
|
|
2219
|
+
score += 10;
|
|
2220
|
+
findings.push('Page has descriptive metadata');
|
|
2221
|
+
}
|
|
2222
|
+
break;
|
|
2223
|
+
case qx_1.QXHeuristic.USER_FEELINGS_IMPACT:
|
|
2224
|
+
// Comprehensive user feelings analysis (Interaction + Accessibility)
|
|
2225
|
+
const altCoverage = context.accessibility?.altTextsCoverage || 0;
|
|
2226
|
+
const loadTime = context.performance?.loadTime || 0;
|
|
2227
|
+
const ariaLabels = context.accessibility?.ariaLabelsCount || 0;
|
|
2228
|
+
const focusableElements = context.accessibility?.focusableElementsCount || 0;
|
|
2229
|
+
score = 60; // Base
|
|
2230
|
+
// Accessibility impact on feelings (35% weight)
|
|
2231
|
+
if (altCoverage >= 90) {
|
|
2232
|
+
score += 20;
|
|
2233
|
+
findings.push('Excellent accessibility (90%+ alt coverage) creates inclusive, positive experience');
|
|
2234
|
+
}
|
|
2235
|
+
else if (altCoverage >= 70) {
|
|
2236
|
+
score += 12;
|
|
2237
|
+
findings.push('Good accessibility creates generally positive user feelings');
|
|
2238
|
+
}
|
|
2239
|
+
else if (altCoverage < 50) {
|
|
2240
|
+
score -= 15;
|
|
2241
|
+
issues.push({ description: 'Poor accessibility (<50% alt coverage) frustrates users with disabilities', severity: 'high' });
|
|
2242
|
+
recommendations.push('Improve alt text coverage to at least 80% for better accessibility');
|
|
2243
|
+
}
|
|
2244
|
+
// ARIA support impact
|
|
2245
|
+
if (ariaLabels > 5) {
|
|
2246
|
+
score += 8;
|
|
2247
|
+
findings.push('Strong ARIA labeling enhances screen reader experience');
|
|
2248
|
+
}
|
|
2249
|
+
// Performance impact on feelings (35% weight)
|
|
2250
|
+
if (loadTime < 1500) {
|
|
2251
|
+
score += 15;
|
|
2252
|
+
findings.push('Very fast load time (<1.5s) delights users');
|
|
2253
|
+
}
|
|
2254
|
+
else if (loadTime < 2500) {
|
|
2255
|
+
score += 8;
|
|
2256
|
+
findings.push('Fast load time enhances user satisfaction');
|
|
2257
|
+
}
|
|
2258
|
+
else if (loadTime > 4000) {
|
|
2259
|
+
score -= 20;
|
|
2260
|
+
issues.push({ description: 'Very slow load time (>4s) causes significant frustration', severity: 'critical' });
|
|
2261
|
+
recommendations.push('Optimize page load time - target under 2.5 seconds');
|
|
2262
|
+
}
|
|
2263
|
+
else if (loadTime > 3000) {
|
|
2264
|
+
score -= 12;
|
|
2265
|
+
issues.push({ description: 'Slow load time causes user frustration', severity: 'high' });
|
|
2266
|
+
}
|
|
2267
|
+
// Error visibility impact (15% weight)
|
|
2268
|
+
if (context.errorIndicators?.hasErrorMessages) {
|
|
2269
|
+
score -= 12;
|
|
2270
|
+
issues.push({ description: 'Visible errors reduce user confidence and satisfaction', severity: 'high' });
|
|
2271
|
+
recommendations.push('Review and fix visible error messages');
|
|
2272
|
+
}
|
|
2273
|
+
// Interaction capability (15% weight)
|
|
2274
|
+
if (focusableElements > 20) {
|
|
2275
|
+
score += 5;
|
|
2276
|
+
findings.push('Rich interactive elements provide user control and engagement');
|
|
2277
|
+
}
|
|
2278
|
+
else if (focusableElements < 5) {
|
|
2279
|
+
score -= 8;
|
|
2280
|
+
issues.push({ description: 'Limited interactivity may feel restrictive', severity: 'medium' });
|
|
2281
|
+
}
|
|
2282
|
+
score = Math.max(20, Math.min(100, score));
|
|
2283
|
+
break;
|
|
2284
|
+
case qx_1.QXHeuristic.GUI_FLOW_IMPACT:
|
|
2285
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
2286
|
+
const forms = context.domMetrics?.forms || 0;
|
|
2287
|
+
if (interactiveElements > 20) {
|
|
2288
|
+
score = 75;
|
|
2289
|
+
findings.push(`${interactiveElements} interactive elements provide user control`);
|
|
2290
|
+
}
|
|
2291
|
+
if (forms > 0) {
|
|
2292
|
+
findings.push(`${forms} forms impact user input flows`);
|
|
2293
|
+
score = Math.min(100, score + 10);
|
|
2294
|
+
}
|
|
2295
|
+
if (interactiveElements === 0) {
|
|
2296
|
+
score = 30;
|
|
2297
|
+
issues.push({ description: 'Limited user interaction capability', severity: 'high' });
|
|
2298
|
+
}
|
|
2299
|
+
break;
|
|
2300
|
+
case qx_1.QXHeuristic.CROSS_FUNCTIONAL_IMPACT:
|
|
2301
|
+
score = 70;
|
|
2302
|
+
if (context.accessibility && (context.accessibility.altTextsCoverage || 0) < 100) {
|
|
2303
|
+
findings.push('Content team needed for alt text creation');
|
|
2304
|
+
}
|
|
2305
|
+
if (context.performance && (context.performance.loadTime || 0) > 2000) {
|
|
2306
|
+
findings.push('Engineering team needed for performance optimization');
|
|
2307
|
+
}
|
|
2308
|
+
if (problemAnalysis.complexity === 'complex') {
|
|
2309
|
+
findings.push('QA team needed for comprehensive testing');
|
|
2310
|
+
}
|
|
2311
|
+
score = 70 + (findings.length * 5);
|
|
2312
|
+
break;
|
|
2313
|
+
case qx_1.QXHeuristic.DATA_DEPENDENT_IMPACT:
|
|
2314
|
+
if (context.domMetrics?.forms && context.domMetrics.forms > 0) {
|
|
2315
|
+
score = 75;
|
|
2316
|
+
findings.push(`${context.domMetrics.forms} forms depend on backend data processing`);
|
|
2317
|
+
}
|
|
2318
|
+
else {
|
|
2319
|
+
score = 50;
|
|
2320
|
+
findings.push('Limited data-dependent features');
|
|
2321
|
+
}
|
|
2322
|
+
break;
|
|
2323
|
+
case qx_1.QXHeuristic.PROBLEM_UNDERSTANDING:
|
|
2324
|
+
score = problemAnalysis.clarityScore;
|
|
2325
|
+
if (problemAnalysis.clarityScore > 80) {
|
|
2326
|
+
findings.push('Problem is well-defined');
|
|
2327
|
+
}
|
|
2328
|
+
else {
|
|
2329
|
+
issues.push({ description: 'Problem clarity needs improvement', severity: 'medium' });
|
|
2330
|
+
}
|
|
2331
|
+
findings.push(...problemAnalysis.breakdown);
|
|
2332
|
+
break;
|
|
2333
|
+
case qx_1.QXHeuristic.RULE_OF_THREE:
|
|
2334
|
+
score = problemAnalysis.potentialFailures.length >= 3 ? 85 : 60;
|
|
2335
|
+
findings.push(`${problemAnalysis.potentialFailures.length} potential failure modes identified`);
|
|
2336
|
+
if (problemAnalysis.potentialFailures.length < 3) {
|
|
2337
|
+
recommendations.push('Identify at least 3 potential failure modes');
|
|
2338
|
+
}
|
|
2339
|
+
break;
|
|
2340
|
+
case qx_1.QXHeuristic.PROBLEM_COMPLEXITY:
|
|
2341
|
+
score = problemAnalysis.complexity === 'simple' ? 90 :
|
|
2342
|
+
problemAnalysis.complexity === 'moderate' ? 75 : 60;
|
|
2343
|
+
findings.push(`Problem complexity: ${problemAnalysis.complexity}`);
|
|
2344
|
+
break;
|
|
2345
|
+
case qx_1.QXHeuristic.USER_NEEDS_IDENTIFICATION:
|
|
2346
|
+
score = userNeeds.alignmentScore;
|
|
2347
|
+
findings.push(`${userNeeds.needs.length} user needs identified`);
|
|
2348
|
+
const mustHave = userNeeds.needs.filter(n => n.priority === 'must-have').length;
|
|
2349
|
+
findings.push(`${mustHave} must-have features`);
|
|
2350
|
+
if (userNeeds.challenges.length > 0) {
|
|
2351
|
+
issues.push({ description: `${userNeeds.challenges.length} user need challenges found`, severity: 'medium' });
|
|
2352
|
+
}
|
|
2353
|
+
break;
|
|
2354
|
+
case qx_1.QXHeuristic.USER_NEEDS_SUITABILITY:
|
|
2355
|
+
score = userNeeds.suitability === 'excellent' ? 95 :
|
|
2356
|
+
userNeeds.suitability === 'good' ? 80 :
|
|
2357
|
+
userNeeds.suitability === 'adequate' ? 65 : 45;
|
|
2358
|
+
findings.push(`User needs suitability: ${userNeeds.suitability}`);
|
|
2359
|
+
break;
|
|
2360
|
+
case qx_1.QXHeuristic.USER_NEEDS_VALIDATION:
|
|
2361
|
+
const addressedNeeds = userNeeds.needs.filter(n => n.addressed).length;
|
|
2362
|
+
score = userNeeds.needs.length > 0 ? (addressedNeeds / userNeeds.needs.length) * 100 : 50;
|
|
2363
|
+
findings.push(`${addressedNeeds}/${userNeeds.needs.length} needs validated and addressed`);
|
|
2364
|
+
break;
|
|
2365
|
+
case qx_1.QXHeuristic.BUSINESS_NEEDS_IDENTIFICATION:
|
|
2366
|
+
score = businessNeeds.alignmentScore;
|
|
2367
|
+
findings.push(`Primary goal: ${businessNeeds.primaryGoal}`);
|
|
2368
|
+
findings.push(`${businessNeeds.kpisAffected.length} KPIs affected`);
|
|
2369
|
+
findings.push(`${businessNeeds.crossTeamImpact.length} cross-team impacts`);
|
|
2370
|
+
break;
|
|
2371
|
+
case qx_1.QXHeuristic.USER_VS_BUSINESS_BALANCE:
|
|
2372
|
+
const balanceScore = 100 - Math.abs(userNeeds.alignmentScore - businessNeeds.alignmentScore);
|
|
2373
|
+
score = balanceScore;
|
|
2374
|
+
if (balanceScore > 80) {
|
|
2375
|
+
findings.push('Good balance between user and business needs');
|
|
2376
|
+
}
|
|
2377
|
+
else {
|
|
2378
|
+
issues.push({ description: 'Imbalance between user and business priorities', severity: 'medium' });
|
|
2379
|
+
recommendations.push('Align user and business objectives more closely');
|
|
2380
|
+
}
|
|
2381
|
+
break;
|
|
2382
|
+
case qx_1.QXHeuristic.KPI_IMPACT_ANALYSIS:
|
|
2383
|
+
score = businessNeeds.impactsKPIs ? 85 : 50;
|
|
2384
|
+
findings.push(`KPIs impacted: ${businessNeeds.kpisAffected.join(', ')}`);
|
|
2385
|
+
if (businessNeeds.compromisesUX) {
|
|
2386
|
+
issues.push({ description: 'Business ease compromises user experience', severity: 'high' });
|
|
2387
|
+
score -= 20;
|
|
2388
|
+
}
|
|
2389
|
+
break;
|
|
2390
|
+
case qx_1.QXHeuristic.ORACLE_PROBLEM_DETECTION:
|
|
2391
|
+
// This is handled separately, score based on whether we can detect issues
|
|
2392
|
+
score = 75;
|
|
2393
|
+
findings.push('Oracle problem detection capability active');
|
|
2394
|
+
break;
|
|
2395
|
+
case qx_1.QXHeuristic.WHAT_MUST_NOT_CHANGE:
|
|
2396
|
+
score = 80;
|
|
2397
|
+
if (context.domMetrics?.semanticStructure?.hasMain) {
|
|
2398
|
+
findings.push('Main content structure is immutable');
|
|
2399
|
+
}
|
|
2400
|
+
if (context.accessibility && (context.accessibility.focusableElementsCount || 0) > 0) {
|
|
2401
|
+
findings.push('Keyboard navigation support must be maintained');
|
|
2402
|
+
}
|
|
2403
|
+
break;
|
|
2404
|
+
case qx_1.QXHeuristic.SUPPORTING_DATA_ANALYSIS:
|
|
2405
|
+
score = 75;
|
|
2406
|
+
const hasData = (context.domMetrics?.forms || 0) > 0 || (context.domMetrics?.interactiveElements || 0) > 20;
|
|
2407
|
+
if (hasData) {
|
|
2408
|
+
score = 82;
|
|
2409
|
+
findings.push('Sufficient data points for informed decision-making');
|
|
2410
|
+
}
|
|
2411
|
+
else {
|
|
2412
|
+
score = 60;
|
|
2413
|
+
issues.push({ description: 'Limited data for comprehensive analysis', severity: 'medium' });
|
|
2414
|
+
recommendations.push('Collect more user interaction data');
|
|
2415
|
+
}
|
|
2416
|
+
break;
|
|
2417
|
+
case qx_1.QXHeuristic.COMPETITIVE_ANALYSIS:
|
|
2418
|
+
score = 70; // Baseline - actual comparison would need competitor data
|
|
2419
|
+
findings.push('Competitive analysis capability available');
|
|
2420
|
+
if (context.domMetrics?.semanticStructure?.hasNav && context.domMetrics?.interactiveElements && context.domMetrics.interactiveElements > 15) {
|
|
2421
|
+
score = 78;
|
|
2422
|
+
findings.push('Navigation and interaction patterns follow industry standards');
|
|
2423
|
+
}
|
|
2424
|
+
else {
|
|
2425
|
+
recommendations.push('Compare interaction patterns with leading competitors');
|
|
2426
|
+
}
|
|
2427
|
+
break;
|
|
2428
|
+
case qx_1.QXHeuristic.DOMAIN_INSPIRATION:
|
|
2429
|
+
score = 72;
|
|
2430
|
+
const hasModernElements = context.accessibility && (context.accessibility.ariaLabelsCount || 0) > 0;
|
|
2431
|
+
if (hasModernElements) {
|
|
2432
|
+
score = 80;
|
|
2433
|
+
findings.push('Modern accessibility patterns show domain inspiration');
|
|
2434
|
+
}
|
|
2435
|
+
else {
|
|
2436
|
+
recommendations.push('Research domain-specific design patterns and best practices');
|
|
2437
|
+
}
|
|
2438
|
+
break;
|
|
2439
|
+
case qx_1.QXHeuristic.INNOVATIVE_SOLUTIONS:
|
|
2440
|
+
score = 65; // Most sites are conventional
|
|
2441
|
+
const hasAdvancedFeatures = (context.accessibility?.landmarkRoles || 0) > 3;
|
|
2442
|
+
if (hasAdvancedFeatures) {
|
|
2443
|
+
score = 75;
|
|
2444
|
+
findings.push('Advanced accessibility features show innovative thinking');
|
|
2445
|
+
}
|
|
2446
|
+
else {
|
|
2447
|
+
recommendations.push('Explore innovative UX patterns to differentiate experience');
|
|
2448
|
+
}
|
|
2449
|
+
break;
|
|
2450
|
+
case qx_1.QXHeuristic.COUNTER_INTUITIVE_DESIGN:
|
|
2451
|
+
score = 85; // High score means few counter-intuitive elements (good)
|
|
2452
|
+
const confusingNav = !context.domMetrics?.semanticStructure?.hasNav && (context.domMetrics?.interactiveElements || 0) > 10;
|
|
2453
|
+
const poorStructure = !context.domMetrics?.semanticStructure?.hasHeader && !context.domMetrics?.semanticStructure?.hasFooter;
|
|
2454
|
+
if (confusingNav) {
|
|
2455
|
+
score = 45;
|
|
2456
|
+
issues.push({ description: 'Navigation structure may be counter-intuitive', severity: 'high' });
|
|
2457
|
+
recommendations.push('Add semantic navigation elements');
|
|
2458
|
+
}
|
|
2459
|
+
if (poorStructure) {
|
|
2460
|
+
score -= 15;
|
|
2461
|
+
issues.push({ description: 'Page structure lacks expected header/footer', severity: 'medium' });
|
|
2462
|
+
}
|
|
2463
|
+
if (score > 75) {
|
|
2464
|
+
findings.push('No counter-intuitive design patterns detected');
|
|
2465
|
+
}
|
|
2466
|
+
break;
|
|
2467
|
+
case qx_1.QXHeuristic.SUPPORTING_DATA_ANALYSIS:
|
|
2468
|
+
score = 70;
|
|
2469
|
+
if (context.performance)
|
|
2470
|
+
findings.push('Performance data available');
|
|
2471
|
+
if (context.accessibility)
|
|
2472
|
+
findings.push('Accessibility metrics available');
|
|
2473
|
+
if (context.domMetrics)
|
|
2474
|
+
findings.push('DOM structure data available');
|
|
2475
|
+
score = 60 + (findings.length * 10);
|
|
2476
|
+
break;
|
|
2477
|
+
case qx_1.QXHeuristic.COMPETITIVE_ANALYSIS:
|
|
2478
|
+
score = 65;
|
|
2479
|
+
findings.push('Competitive analysis capability available');
|
|
2480
|
+
recommendations.push('Compare with competitor sites for benchmarking');
|
|
2481
|
+
break;
|
|
2482
|
+
case qx_1.QXHeuristic.DOMAIN_INSPIRATION:
|
|
2483
|
+
score = 70;
|
|
2484
|
+
findings.push('Consider best practices from similar domains');
|
|
2485
|
+
break;
|
|
2486
|
+
case qx_1.QXHeuristic.INNOVATIVE_SOLUTIONS:
|
|
2487
|
+
score = 68;
|
|
2488
|
+
findings.push('Opportunity for innovative UX solutions');
|
|
2489
|
+
break;
|
|
2490
|
+
case qx_1.QXHeuristic.COUNTER_INTUITIVE_DESIGN:
|
|
2491
|
+
score = 75;
|
|
2492
|
+
findings.push('No counter-intuitive design patterns detected');
|
|
2493
|
+
break;
|
|
2494
|
+
default:
|
|
2495
|
+
// Generic heuristic evaluation based on category
|
|
2496
|
+
if (category === 'user-needs') {
|
|
2497
|
+
score = userNeeds.alignmentScore;
|
|
2498
|
+
}
|
|
2499
|
+
else if (category === 'business-needs') {
|
|
2500
|
+
score = businessNeeds.alignmentScore;
|
|
2501
|
+
}
|
|
2502
|
+
else if (category === 'problem') {
|
|
2503
|
+
score = problemAnalysis.clarityScore;
|
|
2504
|
+
}
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
674
2507
|
return {
|
|
675
2508
|
name: heuristic,
|
|
676
|
-
|
|
2509
|
+
heuristicType: heuristic,
|
|
2510
|
+
category,
|
|
677
2511
|
applied: true,
|
|
678
|
-
score:
|
|
679
|
-
findings
|
|
680
|
-
issues
|
|
681
|
-
recommendations
|
|
2512
|
+
score: Math.min(100, Math.max(0, score)),
|
|
2513
|
+
findings,
|
|
2514
|
+
issues,
|
|
2515
|
+
recommendations
|
|
682
2516
|
};
|
|
683
2517
|
}
|
|
684
2518
|
getHeuristicCategory(heuristic) {
|
|
@@ -733,6 +2567,67 @@ class OracleDetector {
|
|
|
733
2567
|
]
|
|
734
2568
|
});
|
|
735
2569
|
}
|
|
2570
|
+
// ENHANCED: Detect contextual oracle problems even for well-built sites
|
|
2571
|
+
const titleLower = (context.title || '').toLowerCase();
|
|
2572
|
+
const descLower = (context.metadata?.description || '').toLowerCase();
|
|
2573
|
+
// E-commerce/Travel booking: Conversion vs UX quality
|
|
2574
|
+
if (titleLower.includes('hotel') || titleLower.includes('booking') || titleLower.includes('travel') ||
|
|
2575
|
+
titleLower.includes('shop') || titleLower.includes('store') || descLower.includes('book')) {
|
|
2576
|
+
if (businessNeeds.kpisAffected.some(k => k.toLowerCase().includes('conversion') || k.toLowerCase().includes('engagement'))) {
|
|
2577
|
+
problems.push({
|
|
2578
|
+
type: 'user-vs-business',
|
|
2579
|
+
description: 'Potential conflict between conversion optimization (business) and user experience quality (user trust)',
|
|
2580
|
+
severity: 'medium',
|
|
2581
|
+
stakeholders: ['Marketing', 'Product', 'Users'],
|
|
2582
|
+
resolutionApproach: [
|
|
2583
|
+
'A/B test aggressive vs. subtle conversion tactics',
|
|
2584
|
+
'Measure both conversion rate and user satisfaction metrics',
|
|
2585
|
+
'Balance urgency messaging with transparent communication'
|
|
2586
|
+
]
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
// Price transparency oracle
|
|
2590
|
+
problems.push({
|
|
2591
|
+
type: 'unclear-criteria',
|
|
2592
|
+
description: 'Unclear criteria for price display timing - when to show fees, taxes, and final price',
|
|
2593
|
+
severity: 'medium',
|
|
2594
|
+
stakeholders: ['Users', 'Legal', 'Business'],
|
|
2595
|
+
resolutionApproach: [
|
|
2596
|
+
'Define regulatory compliance requirements for price display',
|
|
2597
|
+
'Balance business desire for competitive base pricing vs user need for full price transparency',
|
|
2598
|
+
'Establish clear standards for fee disclosure timing'
|
|
2599
|
+
]
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
// Content sites: Quality vs. Quantity
|
|
2603
|
+
if (titleLower.includes('blog') || titleLower.includes('article') || titleLower.includes('news') ||
|
|
2604
|
+
titleLower.includes('magazine') || titleLower.includes('testing')) {
|
|
2605
|
+
problems.push({
|
|
2606
|
+
type: 'user-vs-business',
|
|
2607
|
+
description: 'Content depth (user need) vs. publication frequency (business engagement goals) trade-off',
|
|
2608
|
+
severity: 'low',
|
|
2609
|
+
stakeholders: ['Readers', 'Content Team', 'Editorial'],
|
|
2610
|
+
resolutionApproach: [
|
|
2611
|
+
'Define content quality standards and acceptance criteria',
|
|
2612
|
+
'Balance editorial calendar with quality thresholds',
|
|
2613
|
+
'Consider mix of in-depth and quick-read content formats'
|
|
2614
|
+
]
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
// Complex sites: Technical constraints
|
|
2618
|
+
if ((context.domMetrics?.totalElements || 0) > 500 || (context.domMetrics?.interactiveElements || 0) > 50) {
|
|
2619
|
+
problems.push({
|
|
2620
|
+
type: 'technical-constraint',
|
|
2621
|
+
description: 'Platform technical limitations may restrict advanced UX features or accessibility enhancements',
|
|
2622
|
+
severity: 'low',
|
|
2623
|
+
stakeholders: ['Development', 'Product', 'Users'],
|
|
2624
|
+
resolutionApproach: [
|
|
2625
|
+
'Evaluate platform capabilities and constraints',
|
|
2626
|
+
'Prioritize features based on user impact vs. implementation complexity',
|
|
2627
|
+
'Consider gradual enhancement approach'
|
|
2628
|
+
]
|
|
2629
|
+
});
|
|
2630
|
+
}
|
|
736
2631
|
return problems.filter(p => this.meetsMinimumSeverity(p.severity));
|
|
737
2632
|
}
|
|
738
2633
|
meetsMinimumSeverity(severity) {
|
|
@@ -746,23 +2641,89 @@ class OracleDetector {
|
|
|
746
2641
|
* Impact Analyzer
|
|
747
2642
|
*/
|
|
748
2643
|
class ImpactAnalyzer {
|
|
749
|
-
async analyze(
|
|
750
|
-
|
|
751
|
-
|
|
2644
|
+
async analyze(context, problemAnalysis) {
|
|
2645
|
+
const guiFlowEndUser = [];
|
|
2646
|
+
const guiFlowInternal = [];
|
|
2647
|
+
const userFeelings = [];
|
|
2648
|
+
const performance = [];
|
|
2649
|
+
const security = [];
|
|
2650
|
+
const immutableRequirements = [];
|
|
2651
|
+
// Analyze visible impacts
|
|
2652
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
2653
|
+
const forms = context.domMetrics?.forms || 0;
|
|
2654
|
+
if (interactiveElements > 0) {
|
|
2655
|
+
guiFlowEndUser.push(`${interactiveElements} interactive elements affect user journey`);
|
|
2656
|
+
}
|
|
2657
|
+
if (forms > 0) {
|
|
2658
|
+
guiFlowEndUser.push(`${forms} forms impact user input flows`);
|
|
2659
|
+
}
|
|
2660
|
+
// User feelings based on quality metrics
|
|
2661
|
+
const altCoverage = context.accessibility?.altTextsCoverage || 0;
|
|
2662
|
+
if (altCoverage > 80) {
|
|
2663
|
+
userFeelings.push('Positive - Good accessibility creates inclusive experience');
|
|
2664
|
+
}
|
|
2665
|
+
else if (altCoverage < 50) {
|
|
2666
|
+
userFeelings.push('Frustrated - Poor accessibility excludes some users');
|
|
2667
|
+
}
|
|
2668
|
+
const loadTime = context.performance?.loadTime || 0;
|
|
2669
|
+
if (loadTime > 3000) {
|
|
2670
|
+
userFeelings.push('Impatient - Slow load time causes frustration');
|
|
2671
|
+
}
|
|
2672
|
+
else if (loadTime < 2000) {
|
|
2673
|
+
userFeelings.push('Satisfied - Fast load time enhances experience');
|
|
2674
|
+
}
|
|
2675
|
+
if (context.errorIndicators?.hasErrorMessages) {
|
|
2676
|
+
userFeelings.push('Confused - Visible errors reduce confidence');
|
|
2677
|
+
}
|
|
2678
|
+
// Analyze invisible impacts
|
|
2679
|
+
if (loadTime > 2000) {
|
|
2680
|
+
performance.push(`Load time ${loadTime}ms impacts user retention`);
|
|
2681
|
+
}
|
|
2682
|
+
if (!context.metadata?.viewport) {
|
|
2683
|
+
performance.push('Missing viewport tag affects mobile performance');
|
|
2684
|
+
}
|
|
2685
|
+
// Immutable requirements
|
|
2686
|
+
if (context.domMetrics?.semanticStructure?.hasMain) {
|
|
2687
|
+
immutableRequirements.push('Must maintain main content accessibility');
|
|
2688
|
+
}
|
|
2689
|
+
if (context.accessibility && (context.accessibility.focusableElementsCount || 0) > 0) {
|
|
2690
|
+
immutableRequirements.push('Must support keyboard navigation');
|
|
2691
|
+
}
|
|
2692
|
+
if (problemAnalysis.complexity === 'complex') {
|
|
2693
|
+
immutableRequirements.push('Must maintain system stability with complex interactions');
|
|
2694
|
+
}
|
|
2695
|
+
// Calculate impact scores
|
|
2696
|
+
let visibleScore = 50;
|
|
2697
|
+
if (guiFlowEndUser.length > 0)
|
|
2698
|
+
visibleScore += 15;
|
|
2699
|
+
if (userFeelings.some(f => f.includes('Positive') || f.includes('Satisfied')))
|
|
2700
|
+
visibleScore += 20;
|
|
2701
|
+
if (userFeelings.some(f => f.includes('Frustrated') || f.includes('Confused')))
|
|
2702
|
+
visibleScore -= 15;
|
|
2703
|
+
visibleScore = Math.max(0, Math.min(100, visibleScore));
|
|
2704
|
+
let invisibleScore = 50;
|
|
2705
|
+
if (performance.length === 0)
|
|
2706
|
+
invisibleScore += 20;
|
|
2707
|
+
if (security.length === 0)
|
|
2708
|
+
invisibleScore += 10;
|
|
2709
|
+
invisibleScore = Math.max(0, Math.min(100, invisibleScore));
|
|
2710
|
+
const overallImpactScore = Math.round((visibleScore + invisibleScore) / 2);
|
|
752
2711
|
return {
|
|
753
2712
|
visible: {
|
|
754
2713
|
guiFlow: {
|
|
755
|
-
forEndUser:
|
|
756
|
-
forInternalUser:
|
|
2714
|
+
forEndUser: guiFlowEndUser,
|
|
2715
|
+
forInternalUser: guiFlowInternal
|
|
757
2716
|
},
|
|
758
|
-
userFeelings
|
|
2717
|
+
userFeelings,
|
|
2718
|
+
score: visibleScore
|
|
759
2719
|
},
|
|
760
2720
|
invisible: {
|
|
761
|
-
performance
|
|
762
|
-
security
|
|
2721
|
+
performance,
|
|
2722
|
+
security,
|
|
2723
|
+
score: invisibleScore
|
|
763
2724
|
},
|
|
764
|
-
immutableRequirements
|
|
765
|
-
overallImpactScore
|
|
2725
|
+
immutableRequirements,
|
|
2726
|
+
overallImpactScore
|
|
766
2727
|
};
|
|
767
2728
|
}
|
|
768
2729
|
}
|