agentic-qe 2.0.0 → 2.1.0
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 +48 -0
- package/README.md +23 -16
- package/dist/agents/QXPartnerAgent.d.ts +8 -1
- package/dist/agents/QXPartnerAgent.d.ts.map +1 -1
- package/dist/agents/QXPartnerAgent.js +1174 -112
- 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/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/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 +39 -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 +13 -55
|
@@ -20,6 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
exports.QXPartnerAgent = void 0;
|
|
21
21
|
const BaseAgent_1 = require("./BaseAgent");
|
|
22
22
|
const types_1 = require("../types");
|
|
23
|
+
const playwright_1 = require("playwright");
|
|
23
24
|
const qx_1 = require("../types/qx");
|
|
24
25
|
class ConsoleLogger {
|
|
25
26
|
info(message, ...args) {
|
|
@@ -47,6 +48,8 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
47
48
|
};
|
|
48
49
|
super(baseConfig);
|
|
49
50
|
this.logger = new ConsoleLogger();
|
|
51
|
+
this.browser = null;
|
|
52
|
+
this.page = null;
|
|
50
53
|
this.config = {
|
|
51
54
|
analysisMode: config.analysisMode || 'full',
|
|
52
55
|
heuristics: config.heuristics || {
|
|
@@ -192,6 +195,15 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
192
195
|
async cleanup() {
|
|
193
196
|
try {
|
|
194
197
|
this.logger.info(`QXPartnerAgent ${this.agentId.id} cleaning up resources`);
|
|
198
|
+
// Close browser if open
|
|
199
|
+
if (this.page) {
|
|
200
|
+
await this.page.close();
|
|
201
|
+
this.page = null;
|
|
202
|
+
}
|
|
203
|
+
if (this.browser) {
|
|
204
|
+
await this.browser.close();
|
|
205
|
+
this.browser = null;
|
|
206
|
+
}
|
|
195
207
|
// Save current QX analysis state
|
|
196
208
|
await this.saveQXState();
|
|
197
209
|
// Store learned patterns
|
|
@@ -291,63 +303,577 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
291
303
|
return analysis;
|
|
292
304
|
}
|
|
293
305
|
/**
|
|
294
|
-
* Collect QX context from target
|
|
306
|
+
* Collect QX context from target using Playwright
|
|
295
307
|
*/
|
|
296
308
|
async collectQXContext(target, additionalContext) {
|
|
297
309
|
this.logger.debug(`Collecting QX context for: ${target}`);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
310
|
+
try {
|
|
311
|
+
// Launch browser if not already running
|
|
312
|
+
if (!this.browser) {
|
|
313
|
+
this.logger.debug('Launching browser...');
|
|
314
|
+
this.browser = await playwright_1.chromium.launch({
|
|
315
|
+
headless: true,
|
|
316
|
+
timeout: 30000, // 30 second timeout for launch
|
|
317
|
+
args: [
|
|
318
|
+
'--no-sandbox',
|
|
319
|
+
'--disable-setuid-sandbox',
|
|
320
|
+
'--disable-dev-shm-usage',
|
|
321
|
+
'--disable-accelerated-2d-canvas',
|
|
322
|
+
'--no-first-run',
|
|
323
|
+
'--no-zygote',
|
|
324
|
+
'--single-process', // Important for containers
|
|
325
|
+
'--disable-gpu'
|
|
326
|
+
]
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
// Create new page
|
|
330
|
+
this.page = await this.browser.newPage();
|
|
331
|
+
this.logger.debug(`Navigating to ${target}...`);
|
|
332
|
+
// Navigate to target with timeout - try quick load first
|
|
333
|
+
try {
|
|
334
|
+
await this.page.goto(target, { waitUntil: 'commit', timeout: 15000 });
|
|
335
|
+
}
|
|
336
|
+
catch (navError) {
|
|
337
|
+
this.logger.warn(`Quick navigation failed, trying basic load: ${navError}`);
|
|
338
|
+
// Fallback: just navigate without waiting
|
|
339
|
+
await this.page.goto(target, { waitUntil: 'commit', timeout: 10000 });
|
|
340
|
+
}
|
|
341
|
+
// Wait a bit for some content to load
|
|
342
|
+
await this.page.waitForTimeout(1000);
|
|
343
|
+
this.logger.debug('Extracting page context...');
|
|
344
|
+
// Extract page context WITH ACTUAL CONTENT for contextual analysis
|
|
345
|
+
const pageContext = await this.page.evaluate(() => {
|
|
346
|
+
const countElements = (selector) => document.querySelectorAll(selector).length;
|
|
347
|
+
const getText = (selector, limit = 5) => Array.from(document.querySelectorAll(selector)).slice(0, limit).map(el => el.textContent?.trim() || '').filter(t => t.length > 0);
|
|
348
|
+
// Extract navigation items for context understanding
|
|
349
|
+
const navItems = getText('nav a, nav button, [role="navigation"] a');
|
|
350
|
+
const headings = {
|
|
351
|
+
h1: getText('h1', 3),
|
|
352
|
+
h2: getText('h2', 5),
|
|
353
|
+
h3: getText('h3', 5)
|
|
354
|
+
};
|
|
355
|
+
// Extract form purposes from labels/placeholders
|
|
356
|
+
const formPurposes = Array.from(document.querySelectorAll('form')).map(form => {
|
|
357
|
+
const labels = Array.from(form.querySelectorAll('label, input[placeholder]')).slice(0, 3);
|
|
358
|
+
return labels.map(el => el.placeholder || el.textContent?.trim() || '').filter(t => t.length > 0).join(', ');
|
|
359
|
+
});
|
|
360
|
+
// Extract button purposes
|
|
361
|
+
const buttonPurposes = getText('button, [role="button"], input[type="submit"]', 10);
|
|
362
|
+
// Extract link context (first 20 meaningful links)
|
|
363
|
+
const linkTexts = getText('a[href]:not([href="#"]):not([href=""])', 20);
|
|
364
|
+
// Extract main content snippets for purpose understanding
|
|
365
|
+
const mainContent = document.querySelector('main, article, [role="main"]');
|
|
366
|
+
const contentSnippet = mainContent?.textContent?.trim().substring(0, 300) || '';
|
|
367
|
+
return {
|
|
368
|
+
title: document.title,
|
|
369
|
+
url: window.location.href,
|
|
370
|
+
viewport: {
|
|
371
|
+
width: window.innerWidth,
|
|
372
|
+
height: window.innerHeight
|
|
373
|
+
},
|
|
374
|
+
content: {
|
|
375
|
+
headings,
|
|
376
|
+
navigationItems: navItems,
|
|
377
|
+
buttonPurposes,
|
|
378
|
+
formPurposes,
|
|
379
|
+
linkTexts,
|
|
380
|
+
mainContentSnippet: contentSnippet
|
|
381
|
+
},
|
|
382
|
+
elements: {
|
|
383
|
+
total: document.querySelectorAll('*').length,
|
|
384
|
+
buttons: countElements('button, [role="button"], input[type="button"], input[type="submit"]'),
|
|
385
|
+
forms: countElements('form'),
|
|
386
|
+
inputs: countElements('input, textarea, select'),
|
|
387
|
+
links: countElements('a'),
|
|
388
|
+
headings: {
|
|
389
|
+
h1: countElements('h1'),
|
|
390
|
+
h2: countElements('h2'),
|
|
391
|
+
h3: countElements('h3')
|
|
392
|
+
},
|
|
393
|
+
images: countElements('img'),
|
|
394
|
+
videos: countElements('video'),
|
|
395
|
+
iframes: countElements('iframe')
|
|
396
|
+
},
|
|
397
|
+
semantic: {
|
|
398
|
+
hasNav: countElements('nav') > 0,
|
|
399
|
+
hasHeader: countElements('header') > 0,
|
|
400
|
+
hasFooter: countElements('footer') > 0,
|
|
401
|
+
hasMain: countElements('main') > 0,
|
|
402
|
+
hasAside: countElements('aside') > 0,
|
|
403
|
+
hasArticle: countElements('article') > 0,
|
|
404
|
+
hasSection: countElements('section') > 0
|
|
405
|
+
},
|
|
406
|
+
accessibility: {
|
|
407
|
+
ariaLabels: countElements('[aria-label]'),
|
|
408
|
+
ariaDescriptions: countElements('[aria-describedby]'),
|
|
409
|
+
altTexts: Array.from(document.querySelectorAll('img')).filter(img => img.hasAttribute('alt')).length,
|
|
410
|
+
totalImages: countElements('img'),
|
|
411
|
+
landmarkRoles: countElements('[role="banner"], [role="navigation"], [role="main"], [role="complementary"], [role="contentinfo"]'),
|
|
412
|
+
focusableElements: countElements('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])')
|
|
413
|
+
},
|
|
414
|
+
errors: {
|
|
415
|
+
consoleErrors: window.__errors || [],
|
|
416
|
+
hasErrorMessages: countElements('.error, [role="alert"], .alert-danger, .text-danger') > 0
|
|
417
|
+
},
|
|
418
|
+
meta: {
|
|
419
|
+
description: document.querySelector('meta[name="description"]')?.content || '',
|
|
420
|
+
keywords: document.querySelector('meta[name="keywords"]')?.content || '',
|
|
421
|
+
viewport: document.querySelector('meta[name="viewport"]')?.content || ''
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
// Capture performance metrics
|
|
426
|
+
const performanceMetrics = await this.page.evaluate(() => {
|
|
427
|
+
const perf = performance.getEntriesByType('navigation')[0];
|
|
428
|
+
return {
|
|
429
|
+
loadTime: perf?.loadEventEnd - perf?.fetchStart || 0,
|
|
430
|
+
domReady: perf?.domContentLoadedEventEnd - perf?.fetchStart || 0,
|
|
431
|
+
firstPaint: performance.getEntriesByType('paint').find(e => e.name === 'first-paint')?.startTime || 0
|
|
432
|
+
};
|
|
433
|
+
});
|
|
434
|
+
const context = {
|
|
435
|
+
url: target,
|
|
436
|
+
title: pageContext.title,
|
|
437
|
+
domMetrics: {
|
|
438
|
+
totalElements: pageContext.elements.total,
|
|
439
|
+
interactiveElements: pageContext.elements.buttons + pageContext.elements.inputs + pageContext.elements.links,
|
|
440
|
+
forms: pageContext.elements.forms,
|
|
441
|
+
inputs: pageContext.elements.inputs,
|
|
442
|
+
buttons: pageContext.elements.buttons,
|
|
443
|
+
semanticStructure: pageContext.semantic
|
|
444
|
+
},
|
|
445
|
+
accessibility: {
|
|
446
|
+
ariaLabelsCount: pageContext.accessibility.ariaLabels,
|
|
447
|
+
altTextsCoverage: pageContext.accessibility.totalImages > 0
|
|
448
|
+
? (pageContext.accessibility.altTexts / pageContext.accessibility.totalImages) * 100
|
|
449
|
+
: 100,
|
|
450
|
+
focusableElementsCount: pageContext.accessibility.focusableElements,
|
|
451
|
+
landmarkRoles: pageContext.accessibility.landmarkRoles
|
|
452
|
+
},
|
|
453
|
+
performance: performanceMetrics,
|
|
454
|
+
errorIndicators: pageContext.errors,
|
|
455
|
+
metadata: pageContext.meta,
|
|
456
|
+
custom: additionalContext || {}
|
|
457
|
+
};
|
|
458
|
+
// Close page but keep browser for potential reuse
|
|
459
|
+
await this.page.close();
|
|
460
|
+
this.page = null;
|
|
461
|
+
// Store context for later retrieval
|
|
462
|
+
await this.storeMemory(`qx-context:${target}`, context);
|
|
463
|
+
this.logger.debug('Context collection completed successfully');
|
|
464
|
+
return context;
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
this.logger.error(`Failed to collect QX context: ${error}`);
|
|
468
|
+
// Clean up on error
|
|
469
|
+
if (this.page) {
|
|
470
|
+
try {
|
|
471
|
+
await this.page.close();
|
|
472
|
+
}
|
|
473
|
+
catch (e) {
|
|
474
|
+
// Ignore close errors
|
|
475
|
+
}
|
|
476
|
+
this.page = null;
|
|
477
|
+
}
|
|
478
|
+
// Return minimal context on error
|
|
479
|
+
return {
|
|
480
|
+
url: target,
|
|
481
|
+
custom: additionalContext || {},
|
|
482
|
+
error: error instanceof Error ? error.message : String(error)
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Detect domain-specific failure modes based on site type
|
|
488
|
+
*/
|
|
489
|
+
detectDomainSpecificFailures(context, title, description, complexity) {
|
|
490
|
+
const failures = [];
|
|
491
|
+
const titleLower = title.toLowerCase();
|
|
492
|
+
const descLower = description.toLowerCase();
|
|
493
|
+
const forms = context.domMetrics?.forms || 0;
|
|
494
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
495
|
+
// E-commerce / Travel Booking sites
|
|
496
|
+
if (titleLower.includes('hotel') || titleLower.includes('booking') || titleLower.includes('travel') ||
|
|
497
|
+
titleLower.includes('shop') || titleLower.includes('store') || titleLower.includes('buy') ||
|
|
498
|
+
descLower.includes('book') || descLower.includes('reservation') || descLower.includes('hotel')) {
|
|
499
|
+
failures.push({
|
|
500
|
+
description: 'Search and filter complexity may overwhelm users with too many options',
|
|
501
|
+
severity: 'medium',
|
|
502
|
+
likelihood: 'likely'
|
|
503
|
+
});
|
|
504
|
+
failures.push({
|
|
505
|
+
description: 'Booking/checkout flow friction points may cause cart abandonment',
|
|
506
|
+
severity: 'high',
|
|
507
|
+
likelihood: 'likely'
|
|
508
|
+
});
|
|
509
|
+
failures.push({
|
|
510
|
+
description: 'Price transparency issues or hidden fees may erode user trust',
|
|
511
|
+
severity: 'high',
|
|
512
|
+
likelihood: 'possible'
|
|
513
|
+
});
|
|
514
|
+
if (complexity === 'complex') {
|
|
515
|
+
failures.push({
|
|
516
|
+
description: 'Multi-step booking process may lose users if progress is not clearly indicated',
|
|
517
|
+
severity: 'medium',
|
|
518
|
+
likelihood: 'likely'
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// Content/Blog/Magazine sites
|
|
523
|
+
else if (titleLower.includes('blog') || titleLower.includes('article') || titleLower.includes('news') ||
|
|
524
|
+
titleLower.includes('magazine') || titleLower.includes('testers') || titleLower.includes('testing')) {
|
|
525
|
+
failures.push({
|
|
526
|
+
description: 'Content discoverability - users may struggle to find relevant articles without robust search',
|
|
527
|
+
severity: 'medium',
|
|
528
|
+
likelihood: 'likely'
|
|
529
|
+
});
|
|
530
|
+
failures.push({
|
|
531
|
+
description: 'Reading experience on mobile devices may not be optimized for long-form content',
|
|
532
|
+
severity: 'medium',
|
|
533
|
+
likelihood: 'possible'
|
|
534
|
+
});
|
|
535
|
+
failures.push({
|
|
536
|
+
description: 'Archive navigation complexity may overwhelm readers looking for specific topics',
|
|
537
|
+
severity: 'low',
|
|
538
|
+
likelihood: 'possible'
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
// SaaS / Web Applications
|
|
542
|
+
else if (titleLower.includes('dashboard') || titleLower.includes('app') || titleLower.includes('platform') ||
|
|
543
|
+
interactiveElements > 50) {
|
|
544
|
+
failures.push({
|
|
545
|
+
description: 'Complex workflows may confuse new users without proper onboarding',
|
|
546
|
+
severity: 'medium',
|
|
547
|
+
likelihood: 'likely'
|
|
548
|
+
});
|
|
549
|
+
failures.push({
|
|
550
|
+
description: 'Data visualization and information density may cause cognitive overload',
|
|
551
|
+
severity: 'medium',
|
|
552
|
+
likelihood: 'possible'
|
|
553
|
+
});
|
|
554
|
+
failures.push({
|
|
555
|
+
description: 'Error messages may not provide actionable recovery steps',
|
|
556
|
+
severity: 'medium',
|
|
557
|
+
likelihood: 'likely'
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
// Form-heavy sites
|
|
561
|
+
else if (forms > 0) {
|
|
562
|
+
failures.push({
|
|
563
|
+
description: 'Form validation errors may not be clearly communicated to users',
|
|
564
|
+
severity: 'medium',
|
|
565
|
+
likelihood: 'likely'
|
|
566
|
+
});
|
|
567
|
+
failures.push({
|
|
568
|
+
description: 'Required field indicators may not be consistently applied',
|
|
569
|
+
severity: 'low',
|
|
570
|
+
likelihood: 'possible'
|
|
571
|
+
});
|
|
572
|
+
failures.push({
|
|
573
|
+
description: 'Form submission failure recovery path may not be clear',
|
|
574
|
+
severity: 'medium',
|
|
575
|
+
likelihood: 'possible'
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
// Return only the most relevant failures (max 5)
|
|
579
|
+
return failures.slice(0, 5);
|
|
307
580
|
}
|
|
308
581
|
/**
|
|
309
582
|
* Analyze problem using Rule of Three and complexity assessment
|
|
310
583
|
*/
|
|
311
|
-
async analyzeProblem(
|
|
584
|
+
async analyzeProblem(context) {
|
|
312
585
|
this.logger.debug('Analyzing problem');
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
586
|
+
const title = context.title || 'Untitled page';
|
|
587
|
+
const description = context.metadata?.description || '';
|
|
588
|
+
const hasError = context.errorIndicators?.hasErrorMessages || false;
|
|
589
|
+
let problemStatement = `Evaluate quality experience of "${title}"`;
|
|
590
|
+
if (description) {
|
|
591
|
+
problemStatement += ` - ${description.substring(0, 100)}`;
|
|
592
|
+
}
|
|
593
|
+
const totalElements = context.domMetrics?.totalElements || 0;
|
|
594
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
595
|
+
const forms = context.domMetrics?.forms || 0;
|
|
596
|
+
let complexity;
|
|
597
|
+
if (totalElements > 500 || interactiveElements > 50 || forms > 3) {
|
|
598
|
+
complexity = 'complex';
|
|
599
|
+
}
|
|
600
|
+
else if (totalElements > 200 || interactiveElements > 20 || forms > 1) {
|
|
601
|
+
complexity = 'moderate';
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
complexity = 'simple';
|
|
605
|
+
}
|
|
606
|
+
const breakdown = [];
|
|
607
|
+
if (context.domMetrics?.semanticStructure?.hasNav)
|
|
608
|
+
breakdown.push('Navigation structure');
|
|
609
|
+
if (forms > 0)
|
|
610
|
+
breakdown.push(`Form interactions (${forms} forms)`);
|
|
611
|
+
if (interactiveElements > 0)
|
|
612
|
+
breakdown.push(`User interactions (${interactiveElements} elements)`);
|
|
613
|
+
if (context.accessibility)
|
|
614
|
+
breakdown.push('Accessibility compliance');
|
|
615
|
+
if (context.performance)
|
|
616
|
+
breakdown.push('Performance metrics');
|
|
617
|
+
const potentialFailures = [];
|
|
618
|
+
if (!context.domMetrics?.semanticStructure?.hasMain) {
|
|
619
|
+
potentialFailures.push({
|
|
620
|
+
description: 'Missing main content landmark - users may struggle to find primary content',
|
|
621
|
+
severity: 'medium',
|
|
622
|
+
likelihood: 'likely'
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
if (context.accessibility && (context.accessibility.altTextsCoverage || 0) < 80) {
|
|
626
|
+
potentialFailures.push({
|
|
627
|
+
description: 'Poor image alt text coverage - screen reader users affected',
|
|
628
|
+
severity: 'high',
|
|
629
|
+
likelihood: 'very-likely'
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
if (hasError) {
|
|
633
|
+
potentialFailures.push({
|
|
634
|
+
description: 'Visible error messages detected - potential usability issues',
|
|
635
|
+
severity: 'medium',
|
|
636
|
+
likelihood: 'likely'
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
if (context.performance && (context.performance.loadTime || 0) > 3000) {
|
|
640
|
+
potentialFailures.push({
|
|
641
|
+
description: 'Slow load time - user frustration and abandonment risk',
|
|
642
|
+
severity: 'high',
|
|
643
|
+
likelihood: 'very-likely'
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (!context.metadata?.viewport) {
|
|
647
|
+
potentialFailures.push({
|
|
648
|
+
description: 'Missing viewport meta tag - mobile responsiveness issues',
|
|
649
|
+
severity: 'medium',
|
|
650
|
+
likelihood: 'possible'
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
// ENHANCED: Add domain-specific failure modes based on site type and context
|
|
654
|
+
const domainFailures = this.detectDomainSpecificFailures(context, title, description, complexity);
|
|
655
|
+
potentialFailures.push(...domainFailures);
|
|
656
|
+
// Rule of Three: Ensure at least 3 failure modes are identified
|
|
657
|
+
if (potentialFailures.length < 3) {
|
|
658
|
+
// Add generic contextual failures for complex sites
|
|
659
|
+
if (complexity === 'complex') {
|
|
660
|
+
if (potentialFailures.length < 3) {
|
|
661
|
+
potentialFailures.push({
|
|
662
|
+
description: 'Complex interaction flows may confuse first-time users',
|
|
663
|
+
severity: 'medium',
|
|
664
|
+
likelihood: 'possible'
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
if (potentialFailures.length < 3) {
|
|
668
|
+
potentialFailures.push({
|
|
669
|
+
description: 'Multiple interactive elements increase cognitive load',
|
|
670
|
+
severity: 'low',
|
|
671
|
+
likelihood: 'possible'
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
if (potentialFailures.length < 3) {
|
|
675
|
+
potentialFailures.push({
|
|
676
|
+
description: 'Error recovery paths may not be clear in complex workflows',
|
|
677
|
+
severity: 'medium',
|
|
678
|
+
likelihood: 'possible'
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
let clarityScore = 50;
|
|
684
|
+
if (title && title !== 'Untitled page')
|
|
685
|
+
clarityScore += 15;
|
|
686
|
+
if (description)
|
|
687
|
+
clarityScore += 15;
|
|
688
|
+
if (breakdown.length >= 3)
|
|
689
|
+
clarityScore += 10;
|
|
690
|
+
if (context.domMetrics?.semanticStructure?.hasMain)
|
|
691
|
+
clarityScore += 10;
|
|
692
|
+
clarityScore = Math.min(100, clarityScore);
|
|
693
|
+
return {
|
|
694
|
+
problemStatement,
|
|
695
|
+
complexity,
|
|
696
|
+
breakdown,
|
|
697
|
+
potentialFailures,
|
|
698
|
+
clarityScore
|
|
321
699
|
};
|
|
322
|
-
return analysis;
|
|
323
700
|
}
|
|
324
701
|
/**
|
|
325
702
|
* Analyze user needs
|
|
326
703
|
*/
|
|
327
|
-
async analyzeUserNeeds(
|
|
704
|
+
async analyzeUserNeeds(context, problemAnalysis) {
|
|
328
705
|
this.logger.debug('Analyzing user needs');
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
706
|
+
const needs = [];
|
|
707
|
+
const challenges = [];
|
|
708
|
+
const semantic = context.domMetrics?.semanticStructure;
|
|
709
|
+
const accessibility = context.accessibility;
|
|
710
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
711
|
+
const forms = context.domMetrics?.forms || 0;
|
|
712
|
+
// Must-have features (critical for basic functionality)
|
|
713
|
+
if (semantic?.hasNav) {
|
|
714
|
+
needs.push({ description: 'Clear navigation to find content', priority: 'must-have', addressed: true });
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
challenges.push('Missing navigation structure - users cannot easily explore site');
|
|
718
|
+
needs.push({ description: 'Clear navigation to find content', priority: 'must-have', addressed: false });
|
|
719
|
+
}
|
|
720
|
+
if (interactiveElements > 0) {
|
|
721
|
+
needs.push({ description: 'Interactive elements for engagement', priority: 'must-have', addressed: true });
|
|
722
|
+
}
|
|
723
|
+
if (accessibility && (accessibility.focusableElementsCount || 0) > 0) {
|
|
724
|
+
needs.push({ description: 'Keyboard navigation support', priority: 'must-have', addressed: true });
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
challenges.push('Limited keyboard navigation - inaccessible to some users');
|
|
728
|
+
needs.push({ description: 'Keyboard navigation support', priority: 'must-have', addressed: false });
|
|
729
|
+
}
|
|
730
|
+
// Should-have features (important for good UX)
|
|
731
|
+
if (semantic?.hasHeader) {
|
|
732
|
+
needs.push({ description: 'Consistent page header for orientation', priority: 'should-have', addressed: true });
|
|
733
|
+
}
|
|
734
|
+
if (semantic?.hasFooter) {
|
|
735
|
+
needs.push({ description: 'Footer with supporting information', priority: 'should-have', addressed: true });
|
|
736
|
+
}
|
|
737
|
+
if (accessibility && (accessibility.altTextsCoverage || 0) > 50) {
|
|
738
|
+
needs.push({ description: 'Image descriptions for screen readers', priority: 'should-have', addressed: true });
|
|
739
|
+
}
|
|
740
|
+
else if (accessibility && (accessibility.altTextsCoverage || 0) < 50) {
|
|
741
|
+
challenges.push('Poor alt text coverage - images not accessible');
|
|
742
|
+
needs.push({ description: 'Image descriptions for screen readers', priority: 'should-have', addressed: false });
|
|
743
|
+
}
|
|
744
|
+
if (context.performance && (context.performance.loadTime || 0) < 3000) {
|
|
745
|
+
needs.push({ description: 'Fast page load time', priority: 'should-have', addressed: true });
|
|
746
|
+
}
|
|
747
|
+
else if (context.performance && (context.performance.loadTime || 0) >= 3000) {
|
|
748
|
+
challenges.push('Slow load time - user frustration risk');
|
|
749
|
+
needs.push({ description: 'Fast page load time', priority: 'should-have', addressed: false });
|
|
750
|
+
}
|
|
751
|
+
// Nice-to-have features (enhancements)
|
|
752
|
+
if (semantic?.hasAside) {
|
|
753
|
+
needs.push({ description: 'Supplementary content sections', priority: 'nice-to-have', addressed: true });
|
|
754
|
+
}
|
|
755
|
+
if (accessibility && (accessibility.landmarkRoles || 0) > 3) {
|
|
756
|
+
needs.push({ description: 'Rich ARIA landmarks for navigation', priority: 'nice-to-have', addressed: true });
|
|
757
|
+
}
|
|
758
|
+
if (forms > 0) {
|
|
759
|
+
needs.push({ description: 'Form interactions for user input', priority: 'nice-to-have', addressed: true });
|
|
760
|
+
}
|
|
761
|
+
// Determine suitability
|
|
762
|
+
const addressedMustHaves = needs.filter(n => n.priority === 'must-have' && n.addressed).length;
|
|
763
|
+
const totalMustHaves = needs.filter(n => n.priority === 'must-have').length;
|
|
764
|
+
let suitability;
|
|
765
|
+
if (challenges.length === 0 && addressedMustHaves >= 3) {
|
|
766
|
+
suitability = 'excellent';
|
|
767
|
+
}
|
|
768
|
+
else if (challenges.length <= 1 && addressedMustHaves >= 2) {
|
|
769
|
+
suitability = 'good';
|
|
770
|
+
}
|
|
771
|
+
else if (challenges.length <= 2 && addressedMustHaves >= 2) {
|
|
772
|
+
suitability = 'adequate';
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
suitability = 'poor';
|
|
776
|
+
}
|
|
777
|
+
// Calculate alignment score
|
|
778
|
+
let alignmentScore = 40;
|
|
779
|
+
alignmentScore += addressedMustHaves * 10;
|
|
780
|
+
alignmentScore += needs.filter(n => n.priority === 'should-have' && n.addressed).length * 5;
|
|
781
|
+
alignmentScore += needs.filter(n => n.priority === 'nice-to-have' && n.addressed).length * 2;
|
|
782
|
+
alignmentScore -= challenges.length * 8;
|
|
783
|
+
alignmentScore = Math.max(0, Math.min(100, alignmentScore));
|
|
784
|
+
return {
|
|
785
|
+
needs,
|
|
786
|
+
suitability,
|
|
787
|
+
challenges,
|
|
788
|
+
alignmentScore
|
|
334
789
|
};
|
|
335
|
-
return analysis;
|
|
336
790
|
}
|
|
337
791
|
/**
|
|
338
792
|
* Analyze business needs
|
|
339
793
|
*/
|
|
340
|
-
async analyzeBusinessNeeds(
|
|
794
|
+
async analyzeBusinessNeeds(context, problemAnalysis) {
|
|
341
795
|
this.logger.debug('Analyzing business needs');
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
796
|
+
const forms = context.domMetrics?.forms || 0;
|
|
797
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
798
|
+
const performance = context.performance;
|
|
799
|
+
const hasErrors = context.errorIndicators?.hasErrorMessages || false;
|
|
800
|
+
let primaryGoal;
|
|
801
|
+
let kpisAffected = [];
|
|
802
|
+
if (forms > 2) {
|
|
803
|
+
primaryGoal = 'business-ease'; // Conversion focus leans business
|
|
804
|
+
kpisAffected = ['Form completion rate', 'Lead generation', 'User sign-ups'];
|
|
805
|
+
}
|
|
806
|
+
else if (interactiveElements > 30) {
|
|
807
|
+
primaryGoal = 'user-experience'; // Engagement focus leans UX
|
|
808
|
+
kpisAffected = ['Time on site', 'Click-through rate', 'User engagement'];
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
primaryGoal = 'balanced';
|
|
812
|
+
kpisAffected = ['Content consumption', 'Bounce rate', 'Page views'];
|
|
813
|
+
}
|
|
814
|
+
const crossTeamImpact = [];
|
|
815
|
+
if (forms > 0) {
|
|
816
|
+
crossTeamImpact.push({
|
|
817
|
+
team: 'Marketing',
|
|
818
|
+
impactType: 'positive',
|
|
819
|
+
description: 'Form conversion optimization needed'
|
|
820
|
+
});
|
|
821
|
+
crossTeamImpact.push({
|
|
822
|
+
team: 'Development',
|
|
823
|
+
impactType: 'neutral',
|
|
824
|
+
description: 'Form validation and submission handling'
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
if (context.accessibility && (context.accessibility.altTextsCoverage || 0) < 100) {
|
|
828
|
+
crossTeamImpact.push({
|
|
829
|
+
team: 'Content',
|
|
830
|
+
impactType: 'negative',
|
|
831
|
+
description: 'Image alt text creation required'
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (performance && (performance.loadTime || 0) > 2000) {
|
|
835
|
+
crossTeamImpact.push({
|
|
836
|
+
team: 'Engineering',
|
|
837
|
+
impactType: 'negative',
|
|
838
|
+
description: 'Performance optimization needed'
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
if (problemAnalysis.complexity === 'complex') {
|
|
842
|
+
crossTeamImpact.push({
|
|
843
|
+
team: 'QA',
|
|
844
|
+
impactType: 'neutral',
|
|
845
|
+
description: 'Comprehensive testing strategy required'
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
let compromisesUX = false;
|
|
849
|
+
if (hasErrors) {
|
|
850
|
+
compromisesUX = true;
|
|
851
|
+
}
|
|
852
|
+
if (context.accessibility && (context.accessibility.altTextsCoverage || 0) < 50) {
|
|
853
|
+
compromisesUX = true;
|
|
854
|
+
}
|
|
855
|
+
if (performance && (performance.loadTime || 0) > 4000) {
|
|
856
|
+
compromisesUX = true;
|
|
857
|
+
}
|
|
858
|
+
const impactsKPIs = kpisAffected.length > 0;
|
|
859
|
+
let alignmentScore = 50;
|
|
860
|
+
if (kpisAffected.length > 0)
|
|
861
|
+
alignmentScore += 15;
|
|
862
|
+
if (crossTeamImpact.length > 0)
|
|
863
|
+
alignmentScore += 10;
|
|
864
|
+
if (!compromisesUX)
|
|
865
|
+
alignmentScore += 20;
|
|
866
|
+
if (impactsKPIs)
|
|
867
|
+
alignmentScore += 5;
|
|
868
|
+
alignmentScore = Math.min(100, alignmentScore);
|
|
869
|
+
return {
|
|
870
|
+
primaryGoal,
|
|
871
|
+
kpisAffected,
|
|
872
|
+
crossTeamImpact,
|
|
873
|
+
compromisesUX,
|
|
874
|
+
impactsKPIs,
|
|
875
|
+
alignmentScore
|
|
349
876
|
};
|
|
350
|
-
return analysis;
|
|
351
877
|
}
|
|
352
878
|
/**
|
|
353
879
|
* Detect oracle problems from analysis context
|
|
@@ -381,79 +907,148 @@ class QXPartnerAgent extends BaseAgent_1.BaseAgent {
|
|
|
381
907
|
*/
|
|
382
908
|
async generateRecommendations(problemAnalysis, userNeeds, businessNeeds, oracleProblems, impactAnalysis, heuristics, _testabilityIntegration) {
|
|
383
909
|
const recommendations = [];
|
|
384
|
-
|
|
385
|
-
|
|
910
|
+
let priorityCounter = 1;
|
|
911
|
+
// Oracle problems (highest priority - match manual report structure)
|
|
912
|
+
for (const problem of oracleProblems) {
|
|
913
|
+
const impactScore = problem.severity === 'critical' ? 90 : problem.severity === 'high' ? 80 : problem.severity === 'medium' ? 60 : 40;
|
|
914
|
+
const impactPct = Math.round((impactScore / 100) * 30); // Up to 30% impact
|
|
386
915
|
recommendations.push({
|
|
387
|
-
principle: 'Problem
|
|
388
|
-
recommendation:
|
|
389
|
-
severity:
|
|
390
|
-
impact:
|
|
391
|
-
effort: 'medium',
|
|
392
|
-
priority:
|
|
393
|
-
category: '
|
|
916
|
+
principle: 'Oracle Problem',
|
|
917
|
+
recommendation: `Resolve: ${problem.description}`,
|
|
918
|
+
severity: problem.severity,
|
|
919
|
+
impact: impactScore,
|
|
920
|
+
effort: problem.severity === 'critical' || problem.severity === 'high' ? 'high' : 'medium',
|
|
921
|
+
priority: priorityCounter++,
|
|
922
|
+
category: 'qa',
|
|
923
|
+
impactPercentage: impactPct,
|
|
924
|
+
estimatedEffort: problem.severity === 'critical' ? 'High - Critical issue' : problem.severity === 'high' ? 'High' : 'Medium'
|
|
394
925
|
});
|
|
395
926
|
}
|
|
396
|
-
//
|
|
397
|
-
if (
|
|
927
|
+
// Problem clarity
|
|
928
|
+
if (problemAnalysis.clarityScore < (this.config.thresholds?.minProblemClarity || 70)) {
|
|
929
|
+
const gap = 70 - problemAnalysis.clarityScore;
|
|
398
930
|
recommendations.push({
|
|
399
|
-
principle: '
|
|
400
|
-
recommendation: '
|
|
401
|
-
severity: 'high',
|
|
402
|
-
impact:
|
|
403
|
-
effort: '
|
|
404
|
-
priority:
|
|
405
|
-
category: '
|
|
931
|
+
principle: 'Problem Understanding',
|
|
932
|
+
recommendation: 'Improve problem statement clarity with detailed breakdown of failure modes and user scenarios',
|
|
933
|
+
severity: gap > 25 ? 'high' : 'medium',
|
|
934
|
+
impact: Math.round(gap * 1.2),
|
|
935
|
+
effort: 'medium',
|
|
936
|
+
priority: priorityCounter++,
|
|
937
|
+
category: 'qx',
|
|
938
|
+
impactPercentage: Math.round((gap / 70) * 20),
|
|
939
|
+
estimatedEffort: 'Medium - Requires stakeholder workshops'
|
|
406
940
|
});
|
|
407
941
|
}
|
|
408
|
-
//
|
|
409
|
-
if (
|
|
942
|
+
// User needs alignment (match manual report priority)
|
|
943
|
+
if (userNeeds.alignmentScore < (this.config.thresholds?.minUserNeedsAlignment || 75)) {
|
|
944
|
+
const gap = 75 - userNeeds.alignmentScore;
|
|
945
|
+
const impactPct = Math.min(35, Math.round((gap / 75) * 100));
|
|
410
946
|
recommendations.push({
|
|
411
|
-
principle: '
|
|
412
|
-
recommendation:
|
|
413
|
-
severity: 'medium',
|
|
414
|
-
impact:
|
|
415
|
-
effort: 'medium',
|
|
416
|
-
priority:
|
|
417
|
-
category: '
|
|
947
|
+
principle: 'User Needs Alignment',
|
|
948
|
+
recommendation: `Improve user needs coverage from ${userNeeds.alignmentScore}/100 to at least 75/100`,
|
|
949
|
+
severity: gap > 20 ? 'high' : 'medium',
|
|
950
|
+
impact: Math.round(gap * 0.9),
|
|
951
|
+
effort: gap > 25 ? 'high' : 'medium',
|
|
952
|
+
priority: priorityCounter++,
|
|
953
|
+
category: 'ux',
|
|
954
|
+
impactPercentage: impactPct,
|
|
955
|
+
estimatedEffort: gap > 25 ? 'High - Major UX redesign' : 'Medium - UX improvements'
|
|
418
956
|
});
|
|
419
957
|
}
|
|
420
|
-
//
|
|
421
|
-
|
|
422
|
-
|
|
958
|
+
// Heuristic-specific high-impact recommendations
|
|
959
|
+
const lowScoringHeuristics = heuristics
|
|
960
|
+
.filter(h => h.score < 70)
|
|
961
|
+
.sort((a, b) => a.score - b.score)
|
|
962
|
+
.slice(0, 5); // Top 5 worst
|
|
963
|
+
lowScoringHeuristics.forEach(heuristic => {
|
|
964
|
+
const impactScore = 75 - heuristic.score;
|
|
965
|
+
const impactPct = Math.round((impactScore / 75) * 25);
|
|
966
|
+
if (heuristic.recommendations.length > 0 && heuristic.heuristicType) {
|
|
423
967
|
recommendations.push({
|
|
424
|
-
principle:
|
|
425
|
-
recommendation:
|
|
426
|
-
severity:
|
|
427
|
-
impact:
|
|
428
|
-
effort: 'high',
|
|
429
|
-
priority:
|
|
430
|
-
category:
|
|
968
|
+
principle: this.formatHeuristicName(heuristic.heuristicType),
|
|
969
|
+
recommendation: heuristic.recommendations[0],
|
|
970
|
+
severity: heuristic.score < 50 ? 'high' : 'medium',
|
|
971
|
+
impact: impactScore,
|
|
972
|
+
effort: heuristic.score < 40 ? 'high' : 'medium',
|
|
973
|
+
priority: priorityCounter++,
|
|
974
|
+
category: heuristic.category,
|
|
975
|
+
impactPercentage: impactPct,
|
|
976
|
+
estimatedEffort: heuristic.score < 40 ? 'High - Significant work required' : 'Medium'
|
|
431
977
|
});
|
|
432
978
|
}
|
|
979
|
+
});
|
|
980
|
+
// High-impact issues from heuristics
|
|
981
|
+
heuristics.forEach(heuristic => {
|
|
982
|
+
if (!heuristic.heuristicType)
|
|
983
|
+
return;
|
|
984
|
+
heuristic.issues
|
|
985
|
+
.filter(issue => issue.severity === 'critical' || issue.severity === 'high')
|
|
986
|
+
.slice(0, 1) // One per heuristic
|
|
987
|
+
.forEach(issue => {
|
|
988
|
+
const impactScore = Math.min(85, 100 - heuristic.score);
|
|
989
|
+
const impactPct = Math.round((impactScore / 100) * 22);
|
|
990
|
+
recommendations.push({
|
|
991
|
+
principle: this.formatHeuristicName(heuristic.heuristicType),
|
|
992
|
+
recommendation: issue.description,
|
|
993
|
+
severity: issue.severity,
|
|
994
|
+
impact: impactScore,
|
|
995
|
+
effort: issue.severity === 'critical' ? 'high' : 'medium',
|
|
996
|
+
priority: priorityCounter++,
|
|
997
|
+
category: heuristic.category,
|
|
998
|
+
impactPercentage: impactPct,
|
|
999
|
+
estimatedEffort: issue.severity === 'critical' ? 'High - Critical fix' : 'Medium'
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
// Business-user balance
|
|
1004
|
+
const balanceDiff = Math.abs(userNeeds.alignmentScore - businessNeeds.alignmentScore);
|
|
1005
|
+
if (balanceDiff > 15) {
|
|
1006
|
+
const impactPct = Math.round((balanceDiff / 100) * 20);
|
|
1007
|
+
const favorsUser = userNeeds.alignmentScore > businessNeeds.alignmentScore;
|
|
1008
|
+
recommendations.push({
|
|
1009
|
+
principle: 'User-Business Balance',
|
|
1010
|
+
recommendation: favorsUser
|
|
1011
|
+
? 'Strengthen business value metrics while maintaining user experience quality'
|
|
1012
|
+
: 'Enhance user experience focus to balance business-centric approach',
|
|
1013
|
+
severity: balanceDiff > 30 ? 'high' : 'medium',
|
|
1014
|
+
impact: Math.round(balanceDiff * 0.75),
|
|
1015
|
+
effort: 'medium',
|
|
1016
|
+
priority: priorityCounter++,
|
|
1017
|
+
category: 'qx',
|
|
1018
|
+
impactPercentage: impactPct,
|
|
1019
|
+
estimatedEffort: 'Medium - Requires stakeholder alignment'
|
|
1020
|
+
});
|
|
433
1021
|
}
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
1022
|
+
// Business needs (if misaligned)
|
|
1023
|
+
if (businessNeeds.alignmentScore < (this.config.thresholds?.minBusinessAlignment || 70)) {
|
|
1024
|
+
const gap = 70 - businessNeeds.alignmentScore;
|
|
1025
|
+
recommendations.push({
|
|
1026
|
+
principle: 'Business Alignment',
|
|
1027
|
+
recommendation: 'Improve alignment with business KPIs and objectives',
|
|
1028
|
+
severity: gap > 25 ? 'high' : 'medium',
|
|
1029
|
+
impact: Math.round(gap * 0.7),
|
|
1030
|
+
effort: 'medium',
|
|
1031
|
+
priority: priorityCounter++,
|
|
1032
|
+
category: 'qx',
|
|
1033
|
+
impactPercentage: Math.round((gap / 70) * 18),
|
|
1034
|
+
estimatedEffort: 'Medium - Business stakeholder review'
|
|
1035
|
+
});
|
|
449
1036
|
}
|
|
450
|
-
// Sort by
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1037
|
+
// Sort by impact percentage and limit to top 10
|
|
1038
|
+
const sorted = recommendations
|
|
1039
|
+
.sort((a, b) => (b.impactPercentage || 0) - (a.impactPercentage || 0))
|
|
1040
|
+
.slice(0, 10);
|
|
1041
|
+
// Reassign priorities
|
|
1042
|
+
sorted.forEach((rec, idx) => {
|
|
1043
|
+
rec.priority = idx + 1;
|
|
455
1044
|
});
|
|
456
|
-
return
|
|
1045
|
+
return sorted;
|
|
1046
|
+
}
|
|
1047
|
+
formatHeuristicName(heuristic) {
|
|
1048
|
+
return heuristic
|
|
1049
|
+
.split('-')
|
|
1050
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
1051
|
+
.join(' ');
|
|
457
1052
|
}
|
|
458
1053
|
/**
|
|
459
1054
|
* Calculate overall QX score
|
|
@@ -668,17 +1263,357 @@ class QXHeuristicsEngine {
|
|
|
668
1263
|
}
|
|
669
1264
|
return results;
|
|
670
1265
|
}
|
|
671
|
-
async apply(heuristic,
|
|
672
|
-
|
|
673
|
-
|
|
1266
|
+
async apply(heuristic, context, problemAnalysis, userNeeds, businessNeeds) {
|
|
1267
|
+
const category = this.getHeuristicCategory(heuristic);
|
|
1268
|
+
const findings = [];
|
|
1269
|
+
const issues = [];
|
|
1270
|
+
const recommendations = [];
|
|
1271
|
+
let score = 75; // Base score
|
|
1272
|
+
// Apply specific heuristic logic based on type
|
|
1273
|
+
switch (heuristic) {
|
|
1274
|
+
case qx_1.QXHeuristic.CONSISTENCY_ANALYSIS:
|
|
1275
|
+
if (context.domMetrics?.semanticStructure?.hasHeader && context.domMetrics?.semanticStructure?.hasFooter) {
|
|
1276
|
+
score = 85;
|
|
1277
|
+
findings.push('Consistent page structure with header and footer');
|
|
1278
|
+
}
|
|
1279
|
+
else {
|
|
1280
|
+
score = 60;
|
|
1281
|
+
recommendations.push('Add consistent header/footer structure');
|
|
1282
|
+
}
|
|
1283
|
+
break;
|
|
1284
|
+
case qx_1.QXHeuristic.INTUITIVE_DESIGN:
|
|
1285
|
+
const hasNav = context.domMetrics?.semanticStructure?.hasNav;
|
|
1286
|
+
const focusable = context.accessibility?.focusableElementsCount || 0;
|
|
1287
|
+
if (hasNav && focusable > 10) {
|
|
1288
|
+
score = 82;
|
|
1289
|
+
findings.push('Intuitive navigation and interaction design');
|
|
1290
|
+
}
|
|
1291
|
+
else {
|
|
1292
|
+
score = 55;
|
|
1293
|
+
issues.push({ description: 'Navigation or interaction patterns unclear', severity: 'medium' });
|
|
1294
|
+
}
|
|
1295
|
+
break;
|
|
1296
|
+
case qx_1.QXHeuristic.EXACTNESS_AND_CLARITY:
|
|
1297
|
+
// Visual hierarchy and clarity (Design category)
|
|
1298
|
+
score = 70;
|
|
1299
|
+
const hasSemanticStructure = context.domMetrics?.semanticStructure;
|
|
1300
|
+
const structureScore = [
|
|
1301
|
+
hasSemanticStructure?.hasHeader,
|
|
1302
|
+
hasSemanticStructure?.hasMain,
|
|
1303
|
+
hasSemanticStructure?.hasNav,
|
|
1304
|
+
hasSemanticStructure?.hasFooter
|
|
1305
|
+
].filter(Boolean).length;
|
|
1306
|
+
score = 50 + (structureScore * 10);
|
|
1307
|
+
if (structureScore >= 3) {
|
|
1308
|
+
findings.push('Strong visual hierarchy with semantic HTML elements');
|
|
1309
|
+
}
|
|
1310
|
+
else if (structureScore >= 2) {
|
|
1311
|
+
findings.push('Moderate visual hierarchy - some semantic elements present');
|
|
1312
|
+
recommendations.push('Add more semantic HTML5 elements for clarity');
|
|
1313
|
+
}
|
|
1314
|
+
else {
|
|
1315
|
+
issues.push({ description: 'Weak visual hierarchy - missing semantic structure', severity: 'high' });
|
|
1316
|
+
recommendations.push('Implement semantic HTML5: header, nav, main, footer');
|
|
1317
|
+
}
|
|
1318
|
+
// Title and description clarity
|
|
1319
|
+
if (context.metadata?.description && context.metadata.description.length > 20) {
|
|
1320
|
+
score += 10;
|
|
1321
|
+
findings.push('Page has descriptive metadata');
|
|
1322
|
+
}
|
|
1323
|
+
break;
|
|
1324
|
+
case qx_1.QXHeuristic.USER_FEELINGS_IMPACT:
|
|
1325
|
+
// Comprehensive user feelings analysis (Interaction + Accessibility)
|
|
1326
|
+
const altCoverage = context.accessibility?.altTextsCoverage || 0;
|
|
1327
|
+
const loadTime = context.performance?.loadTime || 0;
|
|
1328
|
+
const ariaLabels = context.accessibility?.ariaLabelsCount || 0;
|
|
1329
|
+
const focusableElements = context.accessibility?.focusableElementsCount || 0;
|
|
1330
|
+
score = 60; // Base
|
|
1331
|
+
// Accessibility impact on feelings (35% weight)
|
|
1332
|
+
if (altCoverage >= 90) {
|
|
1333
|
+
score += 20;
|
|
1334
|
+
findings.push('Excellent accessibility (90%+ alt coverage) creates inclusive, positive experience');
|
|
1335
|
+
}
|
|
1336
|
+
else if (altCoverage >= 70) {
|
|
1337
|
+
score += 12;
|
|
1338
|
+
findings.push('Good accessibility creates generally positive user feelings');
|
|
1339
|
+
}
|
|
1340
|
+
else if (altCoverage < 50) {
|
|
1341
|
+
score -= 15;
|
|
1342
|
+
issues.push({ description: 'Poor accessibility (<50% alt coverage) frustrates users with disabilities', severity: 'high' });
|
|
1343
|
+
recommendations.push('Improve alt text coverage to at least 80% for better accessibility');
|
|
1344
|
+
}
|
|
1345
|
+
// ARIA support impact
|
|
1346
|
+
if (ariaLabels > 5) {
|
|
1347
|
+
score += 8;
|
|
1348
|
+
findings.push('Strong ARIA labeling enhances screen reader experience');
|
|
1349
|
+
}
|
|
1350
|
+
// Performance impact on feelings (35% weight)
|
|
1351
|
+
if (loadTime < 1500) {
|
|
1352
|
+
score += 15;
|
|
1353
|
+
findings.push('Very fast load time (<1.5s) delights users');
|
|
1354
|
+
}
|
|
1355
|
+
else if (loadTime < 2500) {
|
|
1356
|
+
score += 8;
|
|
1357
|
+
findings.push('Fast load time enhances user satisfaction');
|
|
1358
|
+
}
|
|
1359
|
+
else if (loadTime > 4000) {
|
|
1360
|
+
score -= 20;
|
|
1361
|
+
issues.push({ description: 'Very slow load time (>4s) causes significant frustration', severity: 'critical' });
|
|
1362
|
+
recommendations.push('Optimize page load time - target under 2.5 seconds');
|
|
1363
|
+
}
|
|
1364
|
+
else if (loadTime > 3000) {
|
|
1365
|
+
score -= 12;
|
|
1366
|
+
issues.push({ description: 'Slow load time causes user frustration', severity: 'high' });
|
|
1367
|
+
}
|
|
1368
|
+
// Error visibility impact (15% weight)
|
|
1369
|
+
if (context.errorIndicators?.hasErrorMessages) {
|
|
1370
|
+
score -= 12;
|
|
1371
|
+
issues.push({ description: 'Visible errors reduce user confidence and satisfaction', severity: 'high' });
|
|
1372
|
+
recommendations.push('Review and fix visible error messages');
|
|
1373
|
+
}
|
|
1374
|
+
// Interaction capability (15% weight)
|
|
1375
|
+
if (focusableElements > 20) {
|
|
1376
|
+
score += 5;
|
|
1377
|
+
findings.push('Rich interactive elements provide user control and engagement');
|
|
1378
|
+
}
|
|
1379
|
+
else if (focusableElements < 5) {
|
|
1380
|
+
score -= 8;
|
|
1381
|
+
issues.push({ description: 'Limited interactivity may feel restrictive', severity: 'medium' });
|
|
1382
|
+
}
|
|
1383
|
+
score = Math.max(20, Math.min(100, score));
|
|
1384
|
+
break;
|
|
1385
|
+
case qx_1.QXHeuristic.GUI_FLOW_IMPACT:
|
|
1386
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
1387
|
+
const forms = context.domMetrics?.forms || 0;
|
|
1388
|
+
if (interactiveElements > 20) {
|
|
1389
|
+
score = 75;
|
|
1390
|
+
findings.push(`${interactiveElements} interactive elements provide user control`);
|
|
1391
|
+
}
|
|
1392
|
+
if (forms > 0) {
|
|
1393
|
+
findings.push(`${forms} forms impact user input flows`);
|
|
1394
|
+
score = Math.min(100, score + 10);
|
|
1395
|
+
}
|
|
1396
|
+
if (interactiveElements === 0) {
|
|
1397
|
+
score = 30;
|
|
1398
|
+
issues.push({ description: 'Limited user interaction capability', severity: 'high' });
|
|
1399
|
+
}
|
|
1400
|
+
break;
|
|
1401
|
+
case qx_1.QXHeuristic.CROSS_FUNCTIONAL_IMPACT:
|
|
1402
|
+
score = 70;
|
|
1403
|
+
if (context.accessibility && (context.accessibility.altTextsCoverage || 0) < 100) {
|
|
1404
|
+
findings.push('Content team needed for alt text creation');
|
|
1405
|
+
}
|
|
1406
|
+
if (context.performance && (context.performance.loadTime || 0) > 2000) {
|
|
1407
|
+
findings.push('Engineering team needed for performance optimization');
|
|
1408
|
+
}
|
|
1409
|
+
if (problemAnalysis.complexity === 'complex') {
|
|
1410
|
+
findings.push('QA team needed for comprehensive testing');
|
|
1411
|
+
}
|
|
1412
|
+
score = 70 + (findings.length * 5);
|
|
1413
|
+
break;
|
|
1414
|
+
case qx_1.QXHeuristic.DATA_DEPENDENT_IMPACT:
|
|
1415
|
+
if (context.domMetrics?.forms && context.domMetrics.forms > 0) {
|
|
1416
|
+
score = 75;
|
|
1417
|
+
findings.push(`${context.domMetrics.forms} forms depend on backend data processing`);
|
|
1418
|
+
}
|
|
1419
|
+
else {
|
|
1420
|
+
score = 50;
|
|
1421
|
+
findings.push('Limited data-dependent features');
|
|
1422
|
+
}
|
|
1423
|
+
break;
|
|
1424
|
+
case qx_1.QXHeuristic.PROBLEM_UNDERSTANDING:
|
|
1425
|
+
score = problemAnalysis.clarityScore;
|
|
1426
|
+
if (problemAnalysis.clarityScore > 80) {
|
|
1427
|
+
findings.push('Problem is well-defined');
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
issues.push({ description: 'Problem clarity needs improvement', severity: 'medium' });
|
|
1431
|
+
}
|
|
1432
|
+
findings.push(...problemAnalysis.breakdown);
|
|
1433
|
+
break;
|
|
1434
|
+
case qx_1.QXHeuristic.RULE_OF_THREE:
|
|
1435
|
+
score = problemAnalysis.potentialFailures.length >= 3 ? 85 : 60;
|
|
1436
|
+
findings.push(`${problemAnalysis.potentialFailures.length} potential failure modes identified`);
|
|
1437
|
+
if (problemAnalysis.potentialFailures.length < 3) {
|
|
1438
|
+
recommendations.push('Identify at least 3 potential failure modes');
|
|
1439
|
+
}
|
|
1440
|
+
break;
|
|
1441
|
+
case qx_1.QXHeuristic.PROBLEM_COMPLEXITY:
|
|
1442
|
+
score = problemAnalysis.complexity === 'simple' ? 90 :
|
|
1443
|
+
problemAnalysis.complexity === 'moderate' ? 75 : 60;
|
|
1444
|
+
findings.push(`Problem complexity: ${problemAnalysis.complexity}`);
|
|
1445
|
+
break;
|
|
1446
|
+
case qx_1.QXHeuristic.USER_NEEDS_IDENTIFICATION:
|
|
1447
|
+
score = userNeeds.alignmentScore;
|
|
1448
|
+
findings.push(`${userNeeds.needs.length} user needs identified`);
|
|
1449
|
+
const mustHave = userNeeds.needs.filter(n => n.priority === 'must-have').length;
|
|
1450
|
+
findings.push(`${mustHave} must-have features`);
|
|
1451
|
+
if (userNeeds.challenges.length > 0) {
|
|
1452
|
+
issues.push({ description: `${userNeeds.challenges.length} user need challenges found`, severity: 'medium' });
|
|
1453
|
+
}
|
|
1454
|
+
break;
|
|
1455
|
+
case qx_1.QXHeuristic.USER_NEEDS_SUITABILITY:
|
|
1456
|
+
score = userNeeds.suitability === 'excellent' ? 95 :
|
|
1457
|
+
userNeeds.suitability === 'good' ? 80 :
|
|
1458
|
+
userNeeds.suitability === 'adequate' ? 65 : 45;
|
|
1459
|
+
findings.push(`User needs suitability: ${userNeeds.suitability}`);
|
|
1460
|
+
break;
|
|
1461
|
+
case qx_1.QXHeuristic.USER_NEEDS_VALIDATION:
|
|
1462
|
+
const addressedNeeds = userNeeds.needs.filter(n => n.addressed).length;
|
|
1463
|
+
score = userNeeds.needs.length > 0 ? (addressedNeeds / userNeeds.needs.length) * 100 : 50;
|
|
1464
|
+
findings.push(`${addressedNeeds}/${userNeeds.needs.length} needs validated and addressed`);
|
|
1465
|
+
break;
|
|
1466
|
+
case qx_1.QXHeuristic.BUSINESS_NEEDS_IDENTIFICATION:
|
|
1467
|
+
score = businessNeeds.alignmentScore;
|
|
1468
|
+
findings.push(`Primary goal: ${businessNeeds.primaryGoal}`);
|
|
1469
|
+
findings.push(`${businessNeeds.kpisAffected.length} KPIs affected`);
|
|
1470
|
+
findings.push(`${businessNeeds.crossTeamImpact.length} cross-team impacts`);
|
|
1471
|
+
break;
|
|
1472
|
+
case qx_1.QXHeuristic.USER_VS_BUSINESS_BALANCE:
|
|
1473
|
+
const balanceScore = 100 - Math.abs(userNeeds.alignmentScore - businessNeeds.alignmentScore);
|
|
1474
|
+
score = balanceScore;
|
|
1475
|
+
if (balanceScore > 80) {
|
|
1476
|
+
findings.push('Good balance between user and business needs');
|
|
1477
|
+
}
|
|
1478
|
+
else {
|
|
1479
|
+
issues.push({ description: 'Imbalance between user and business priorities', severity: 'medium' });
|
|
1480
|
+
recommendations.push('Align user and business objectives more closely');
|
|
1481
|
+
}
|
|
1482
|
+
break;
|
|
1483
|
+
case qx_1.QXHeuristic.KPI_IMPACT_ANALYSIS:
|
|
1484
|
+
score = businessNeeds.impactsKPIs ? 85 : 50;
|
|
1485
|
+
findings.push(`KPIs impacted: ${businessNeeds.kpisAffected.join(', ')}`);
|
|
1486
|
+
if (businessNeeds.compromisesUX) {
|
|
1487
|
+
issues.push({ description: 'Business ease compromises user experience', severity: 'high' });
|
|
1488
|
+
score -= 20;
|
|
1489
|
+
}
|
|
1490
|
+
break;
|
|
1491
|
+
case qx_1.QXHeuristic.ORACLE_PROBLEM_DETECTION:
|
|
1492
|
+
// This is handled separately, score based on whether we can detect issues
|
|
1493
|
+
score = 75;
|
|
1494
|
+
findings.push('Oracle problem detection capability active');
|
|
1495
|
+
break;
|
|
1496
|
+
case qx_1.QXHeuristic.WHAT_MUST_NOT_CHANGE:
|
|
1497
|
+
score = 80;
|
|
1498
|
+
if (context.domMetrics?.semanticStructure?.hasMain) {
|
|
1499
|
+
findings.push('Main content structure is immutable');
|
|
1500
|
+
}
|
|
1501
|
+
if (context.accessibility && (context.accessibility.focusableElementsCount || 0) > 0) {
|
|
1502
|
+
findings.push('Keyboard navigation support must be maintained');
|
|
1503
|
+
}
|
|
1504
|
+
break;
|
|
1505
|
+
case qx_1.QXHeuristic.SUPPORTING_DATA_ANALYSIS:
|
|
1506
|
+
score = 75;
|
|
1507
|
+
const hasData = (context.domMetrics?.forms || 0) > 0 || (context.domMetrics?.interactiveElements || 0) > 20;
|
|
1508
|
+
if (hasData) {
|
|
1509
|
+
score = 82;
|
|
1510
|
+
findings.push('Sufficient data points for informed decision-making');
|
|
1511
|
+
}
|
|
1512
|
+
else {
|
|
1513
|
+
score = 60;
|
|
1514
|
+
issues.push({ description: 'Limited data for comprehensive analysis', severity: 'medium' });
|
|
1515
|
+
recommendations.push('Collect more user interaction data');
|
|
1516
|
+
}
|
|
1517
|
+
break;
|
|
1518
|
+
case qx_1.QXHeuristic.COMPETITIVE_ANALYSIS:
|
|
1519
|
+
score = 70; // Baseline - actual comparison would need competitor data
|
|
1520
|
+
findings.push('Competitive analysis capability available');
|
|
1521
|
+
if (context.domMetrics?.semanticStructure?.hasNav && context.domMetrics?.interactiveElements && context.domMetrics.interactiveElements > 15) {
|
|
1522
|
+
score = 78;
|
|
1523
|
+
findings.push('Navigation and interaction patterns follow industry standards');
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
recommendations.push('Compare interaction patterns with leading competitors');
|
|
1527
|
+
}
|
|
1528
|
+
break;
|
|
1529
|
+
case qx_1.QXHeuristic.DOMAIN_INSPIRATION:
|
|
1530
|
+
score = 72;
|
|
1531
|
+
const hasModernElements = context.accessibility && (context.accessibility.ariaLabelsCount || 0) > 0;
|
|
1532
|
+
if (hasModernElements) {
|
|
1533
|
+
score = 80;
|
|
1534
|
+
findings.push('Modern accessibility patterns show domain inspiration');
|
|
1535
|
+
}
|
|
1536
|
+
else {
|
|
1537
|
+
recommendations.push('Research domain-specific design patterns and best practices');
|
|
1538
|
+
}
|
|
1539
|
+
break;
|
|
1540
|
+
case qx_1.QXHeuristic.INNOVATIVE_SOLUTIONS:
|
|
1541
|
+
score = 65; // Most sites are conventional
|
|
1542
|
+
const hasAdvancedFeatures = (context.accessibility?.landmarkRoles || 0) > 3;
|
|
1543
|
+
if (hasAdvancedFeatures) {
|
|
1544
|
+
score = 75;
|
|
1545
|
+
findings.push('Advanced accessibility features show innovative thinking');
|
|
1546
|
+
}
|
|
1547
|
+
else {
|
|
1548
|
+
recommendations.push('Explore innovative UX patterns to differentiate experience');
|
|
1549
|
+
}
|
|
1550
|
+
break;
|
|
1551
|
+
case qx_1.QXHeuristic.COUNTER_INTUITIVE_DESIGN:
|
|
1552
|
+
score = 85; // High score means few counter-intuitive elements (good)
|
|
1553
|
+
const confusingNav = !context.domMetrics?.semanticStructure?.hasNav && (context.domMetrics?.interactiveElements || 0) > 10;
|
|
1554
|
+
const poorStructure = !context.domMetrics?.semanticStructure?.hasHeader && !context.domMetrics?.semanticStructure?.hasFooter;
|
|
1555
|
+
if (confusingNav) {
|
|
1556
|
+
score = 45;
|
|
1557
|
+
issues.push({ description: 'Navigation structure may be counter-intuitive', severity: 'high' });
|
|
1558
|
+
recommendations.push('Add semantic navigation elements');
|
|
1559
|
+
}
|
|
1560
|
+
if (poorStructure) {
|
|
1561
|
+
score -= 15;
|
|
1562
|
+
issues.push({ description: 'Page structure lacks expected header/footer', severity: 'medium' });
|
|
1563
|
+
}
|
|
1564
|
+
if (score > 75) {
|
|
1565
|
+
findings.push('No counter-intuitive design patterns detected');
|
|
1566
|
+
}
|
|
1567
|
+
break;
|
|
1568
|
+
case qx_1.QXHeuristic.SUPPORTING_DATA_ANALYSIS:
|
|
1569
|
+
score = 70;
|
|
1570
|
+
if (context.performance)
|
|
1571
|
+
findings.push('Performance data available');
|
|
1572
|
+
if (context.accessibility)
|
|
1573
|
+
findings.push('Accessibility metrics available');
|
|
1574
|
+
if (context.domMetrics)
|
|
1575
|
+
findings.push('DOM structure data available');
|
|
1576
|
+
score = 60 + (findings.length * 10);
|
|
1577
|
+
break;
|
|
1578
|
+
case qx_1.QXHeuristic.COMPETITIVE_ANALYSIS:
|
|
1579
|
+
score = 65;
|
|
1580
|
+
findings.push('Competitive analysis capability available');
|
|
1581
|
+
recommendations.push('Compare with competitor sites for benchmarking');
|
|
1582
|
+
break;
|
|
1583
|
+
case qx_1.QXHeuristic.DOMAIN_INSPIRATION:
|
|
1584
|
+
score = 70;
|
|
1585
|
+
findings.push('Consider best practices from similar domains');
|
|
1586
|
+
break;
|
|
1587
|
+
case qx_1.QXHeuristic.INNOVATIVE_SOLUTIONS:
|
|
1588
|
+
score = 68;
|
|
1589
|
+
findings.push('Opportunity for innovative UX solutions');
|
|
1590
|
+
break;
|
|
1591
|
+
case qx_1.QXHeuristic.COUNTER_INTUITIVE_DESIGN:
|
|
1592
|
+
score = 75;
|
|
1593
|
+
findings.push('No counter-intuitive design patterns detected');
|
|
1594
|
+
break;
|
|
1595
|
+
default:
|
|
1596
|
+
// Generic heuristic evaluation based on category
|
|
1597
|
+
if (category === 'user-needs') {
|
|
1598
|
+
score = userNeeds.alignmentScore;
|
|
1599
|
+
}
|
|
1600
|
+
else if (category === 'business-needs') {
|
|
1601
|
+
score = businessNeeds.alignmentScore;
|
|
1602
|
+
}
|
|
1603
|
+
else if (category === 'problem') {
|
|
1604
|
+
score = problemAnalysis.clarityScore;
|
|
1605
|
+
}
|
|
1606
|
+
break;
|
|
1607
|
+
}
|
|
674
1608
|
return {
|
|
675
1609
|
name: heuristic,
|
|
676
|
-
|
|
1610
|
+
heuristicType: heuristic,
|
|
1611
|
+
category,
|
|
677
1612
|
applied: true,
|
|
678
|
-
score:
|
|
679
|
-
findings
|
|
680
|
-
issues
|
|
681
|
-
recommendations
|
|
1613
|
+
score: Math.min(100, Math.max(0, score)),
|
|
1614
|
+
findings,
|
|
1615
|
+
issues,
|
|
1616
|
+
recommendations
|
|
682
1617
|
};
|
|
683
1618
|
}
|
|
684
1619
|
getHeuristicCategory(heuristic) {
|
|
@@ -733,6 +1668,67 @@ class OracleDetector {
|
|
|
733
1668
|
]
|
|
734
1669
|
});
|
|
735
1670
|
}
|
|
1671
|
+
// ENHANCED: Detect contextual oracle problems even for well-built sites
|
|
1672
|
+
const titleLower = (context.title || '').toLowerCase();
|
|
1673
|
+
const descLower = (context.metadata?.description || '').toLowerCase();
|
|
1674
|
+
// E-commerce/Travel booking: Conversion vs UX quality
|
|
1675
|
+
if (titleLower.includes('hotel') || titleLower.includes('booking') || titleLower.includes('travel') ||
|
|
1676
|
+
titleLower.includes('shop') || titleLower.includes('store') || descLower.includes('book')) {
|
|
1677
|
+
if (businessNeeds.kpisAffected.some(k => k.toLowerCase().includes('conversion') || k.toLowerCase().includes('engagement'))) {
|
|
1678
|
+
problems.push({
|
|
1679
|
+
type: 'user-vs-business',
|
|
1680
|
+
description: 'Potential conflict between conversion optimization (business) and user experience quality (user trust)',
|
|
1681
|
+
severity: 'medium',
|
|
1682
|
+
stakeholders: ['Marketing', 'Product', 'Users'],
|
|
1683
|
+
resolutionApproach: [
|
|
1684
|
+
'A/B test aggressive vs. subtle conversion tactics',
|
|
1685
|
+
'Measure both conversion rate and user satisfaction metrics',
|
|
1686
|
+
'Balance urgency messaging with transparent communication'
|
|
1687
|
+
]
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
// Price transparency oracle
|
|
1691
|
+
problems.push({
|
|
1692
|
+
type: 'unclear-criteria',
|
|
1693
|
+
description: 'Unclear criteria for price display timing - when to show fees, taxes, and final price',
|
|
1694
|
+
severity: 'medium',
|
|
1695
|
+
stakeholders: ['Users', 'Legal', 'Business'],
|
|
1696
|
+
resolutionApproach: [
|
|
1697
|
+
'Define regulatory compliance requirements for price display',
|
|
1698
|
+
'Balance business desire for competitive base pricing vs user need for full price transparency',
|
|
1699
|
+
'Establish clear standards for fee disclosure timing'
|
|
1700
|
+
]
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
// Content sites: Quality vs. Quantity
|
|
1704
|
+
if (titleLower.includes('blog') || titleLower.includes('article') || titleLower.includes('news') ||
|
|
1705
|
+
titleLower.includes('magazine') || titleLower.includes('testing')) {
|
|
1706
|
+
problems.push({
|
|
1707
|
+
type: 'user-vs-business',
|
|
1708
|
+
description: 'Content depth (user need) vs. publication frequency (business engagement goals) trade-off',
|
|
1709
|
+
severity: 'low',
|
|
1710
|
+
stakeholders: ['Readers', 'Content Team', 'Editorial'],
|
|
1711
|
+
resolutionApproach: [
|
|
1712
|
+
'Define content quality standards and acceptance criteria',
|
|
1713
|
+
'Balance editorial calendar with quality thresholds',
|
|
1714
|
+
'Consider mix of in-depth and quick-read content formats'
|
|
1715
|
+
]
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
// Complex sites: Technical constraints
|
|
1719
|
+
if ((context.domMetrics?.totalElements || 0) > 500 || (context.domMetrics?.interactiveElements || 0) > 50) {
|
|
1720
|
+
problems.push({
|
|
1721
|
+
type: 'technical-constraint',
|
|
1722
|
+
description: 'Platform technical limitations may restrict advanced UX features or accessibility enhancements',
|
|
1723
|
+
severity: 'low',
|
|
1724
|
+
stakeholders: ['Development', 'Product', 'Users'],
|
|
1725
|
+
resolutionApproach: [
|
|
1726
|
+
'Evaluate platform capabilities and constraints',
|
|
1727
|
+
'Prioritize features based on user impact vs. implementation complexity',
|
|
1728
|
+
'Consider gradual enhancement approach'
|
|
1729
|
+
]
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
736
1732
|
return problems.filter(p => this.meetsMinimumSeverity(p.severity));
|
|
737
1733
|
}
|
|
738
1734
|
meetsMinimumSeverity(severity) {
|
|
@@ -746,23 +1742,89 @@ class OracleDetector {
|
|
|
746
1742
|
* Impact Analyzer
|
|
747
1743
|
*/
|
|
748
1744
|
class ImpactAnalyzer {
|
|
749
|
-
async analyze(
|
|
750
|
-
|
|
751
|
-
|
|
1745
|
+
async analyze(context, problemAnalysis) {
|
|
1746
|
+
const guiFlowEndUser = [];
|
|
1747
|
+
const guiFlowInternal = [];
|
|
1748
|
+
const userFeelings = [];
|
|
1749
|
+
const performance = [];
|
|
1750
|
+
const security = [];
|
|
1751
|
+
const immutableRequirements = [];
|
|
1752
|
+
// Analyze visible impacts
|
|
1753
|
+
const interactiveElements = context.domMetrics?.interactiveElements || 0;
|
|
1754
|
+
const forms = context.domMetrics?.forms || 0;
|
|
1755
|
+
if (interactiveElements > 0) {
|
|
1756
|
+
guiFlowEndUser.push(`${interactiveElements} interactive elements affect user journey`);
|
|
1757
|
+
}
|
|
1758
|
+
if (forms > 0) {
|
|
1759
|
+
guiFlowEndUser.push(`${forms} forms impact user input flows`);
|
|
1760
|
+
}
|
|
1761
|
+
// User feelings based on quality metrics
|
|
1762
|
+
const altCoverage = context.accessibility?.altTextsCoverage || 0;
|
|
1763
|
+
if (altCoverage > 80) {
|
|
1764
|
+
userFeelings.push('Positive - Good accessibility creates inclusive experience');
|
|
1765
|
+
}
|
|
1766
|
+
else if (altCoverage < 50) {
|
|
1767
|
+
userFeelings.push('Frustrated - Poor accessibility excludes some users');
|
|
1768
|
+
}
|
|
1769
|
+
const loadTime = context.performance?.loadTime || 0;
|
|
1770
|
+
if (loadTime > 3000) {
|
|
1771
|
+
userFeelings.push('Impatient - Slow load time causes frustration');
|
|
1772
|
+
}
|
|
1773
|
+
else if (loadTime < 2000) {
|
|
1774
|
+
userFeelings.push('Satisfied - Fast load time enhances experience');
|
|
1775
|
+
}
|
|
1776
|
+
if (context.errorIndicators?.hasErrorMessages) {
|
|
1777
|
+
userFeelings.push('Confused - Visible errors reduce confidence');
|
|
1778
|
+
}
|
|
1779
|
+
// Analyze invisible impacts
|
|
1780
|
+
if (loadTime > 2000) {
|
|
1781
|
+
performance.push(`Load time ${loadTime}ms impacts user retention`);
|
|
1782
|
+
}
|
|
1783
|
+
if (!context.metadata?.viewport) {
|
|
1784
|
+
performance.push('Missing viewport tag affects mobile performance');
|
|
1785
|
+
}
|
|
1786
|
+
// Immutable requirements
|
|
1787
|
+
if (context.domMetrics?.semanticStructure?.hasMain) {
|
|
1788
|
+
immutableRequirements.push('Must maintain main content accessibility');
|
|
1789
|
+
}
|
|
1790
|
+
if (context.accessibility && (context.accessibility.focusableElementsCount || 0) > 0) {
|
|
1791
|
+
immutableRequirements.push('Must support keyboard navigation');
|
|
1792
|
+
}
|
|
1793
|
+
if (problemAnalysis.complexity === 'complex') {
|
|
1794
|
+
immutableRequirements.push('Must maintain system stability with complex interactions');
|
|
1795
|
+
}
|
|
1796
|
+
// Calculate impact scores
|
|
1797
|
+
let visibleScore = 50;
|
|
1798
|
+
if (guiFlowEndUser.length > 0)
|
|
1799
|
+
visibleScore += 15;
|
|
1800
|
+
if (userFeelings.some(f => f.includes('Positive') || f.includes('Satisfied')))
|
|
1801
|
+
visibleScore += 20;
|
|
1802
|
+
if (userFeelings.some(f => f.includes('Frustrated') || f.includes('Confused')))
|
|
1803
|
+
visibleScore -= 15;
|
|
1804
|
+
visibleScore = Math.max(0, Math.min(100, visibleScore));
|
|
1805
|
+
let invisibleScore = 50;
|
|
1806
|
+
if (performance.length === 0)
|
|
1807
|
+
invisibleScore += 20;
|
|
1808
|
+
if (security.length === 0)
|
|
1809
|
+
invisibleScore += 10;
|
|
1810
|
+
invisibleScore = Math.max(0, Math.min(100, invisibleScore));
|
|
1811
|
+
const overallImpactScore = Math.round((visibleScore + invisibleScore) / 2);
|
|
752
1812
|
return {
|
|
753
1813
|
visible: {
|
|
754
1814
|
guiFlow: {
|
|
755
|
-
forEndUser:
|
|
756
|
-
forInternalUser:
|
|
1815
|
+
forEndUser: guiFlowEndUser,
|
|
1816
|
+
forInternalUser: guiFlowInternal
|
|
757
1817
|
},
|
|
758
|
-
userFeelings
|
|
1818
|
+
userFeelings,
|
|
1819
|
+
score: visibleScore
|
|
759
1820
|
},
|
|
760
1821
|
invisible: {
|
|
761
|
-
performance
|
|
762
|
-
security
|
|
1822
|
+
performance,
|
|
1823
|
+
security,
|
|
1824
|
+
score: invisibleScore
|
|
763
1825
|
},
|
|
764
|
-
immutableRequirements
|
|
765
|
-
overallImpactScore
|
|
1826
|
+
immutableRequirements,
|
|
1827
|
+
overallImpactScore
|
|
766
1828
|
};
|
|
767
1829
|
}
|
|
768
1830
|
}
|