mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
Remove the nested v3/ subdirectory by moving all active source code, tests, configs, and assets directly to the project root. Archive v2/ (dead code, 847MB) to v2-archive.tar.gz. Key changes: - Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/ - Move v3/tsconfig.json, v3/vitest.config.ts to root - Merge v3/package.json into root package.json (ESM, scripts, deps, exports) - Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/ - Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes - Fix all init installer path resolution (3-level-up → 2-level-up) - Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe" - Update settings.json ADR/DDD directories to docs/implementation/ - Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks) - Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces - Update 15 guidance shards: v3/src/ → src/ path references - Fix sync interfaces: remove v3/ and v2/ path references - Fix test path calculations (process.cwd() + '..' no longer needed) - Fix flaky perf test threshold (500ms → 1000ms for CI environments) - Fix complexity-analyzer test to match ADR-051 keyword-based eligibility Build passes, 17,901 tests pass, CLI and MCP bundles verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
300 lines
9.4 KiB
TypeScript
300 lines
9.4 KiB
TypeScript
/**
|
|
* QX Analysis Tools Unit Tests
|
|
*
|
|
* Tests for the programmatic QX analysis MCP tools.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { QXAnalyzeTool } from '../../../../src/mcp/tools/qx-analysis/analyze';
|
|
import { QXHeuristicsEngine } from '../../../../src/mcp/tools/qx-analysis/heuristics-engine';
|
|
import { OracleDetector } from '../../../../src/mcp/tools/qx-analysis/oracle-detector';
|
|
import { ImpactAnalyzer } from '../../../../src/mcp/tools/qx-analysis/impact-analyzer';
|
|
import { QXHeuristic, QXContext } from '../../../../src/mcp/tools/qx-analysis/types';
|
|
|
|
describe('QXHeuristicsEngine', () => {
|
|
let engine: QXHeuristicsEngine;
|
|
|
|
beforeEach(() => {
|
|
engine = new QXHeuristicsEngine();
|
|
});
|
|
|
|
it('should apply all 23 heuristics by default', async () => {
|
|
const context: QXContext = {
|
|
url: 'https://example.com',
|
|
title: 'Test Page',
|
|
domMetrics: {
|
|
totalElements: 100,
|
|
interactiveElements: 20,
|
|
forms: 1,
|
|
inputs: 5,
|
|
buttons: 10,
|
|
semanticStructure: {
|
|
hasNav: true,
|
|
hasHeader: true,
|
|
hasFooter: true,
|
|
hasMain: true,
|
|
hasAside: false,
|
|
hasArticle: false,
|
|
hasSection: true,
|
|
},
|
|
},
|
|
accessibility: {
|
|
altTextsCoverage: 85,
|
|
focusableElementsCount: 15,
|
|
ariaLabelsCount: 8,
|
|
},
|
|
performance: {
|
|
loadTime: 1500,
|
|
},
|
|
};
|
|
|
|
const problemAnalysis = {
|
|
problemStatement: 'Evaluate quality experience of "Test Page"',
|
|
complexity: 'moderate' as const,
|
|
breakdown: ['Navigation structure', 'Form interactions'],
|
|
potentialFailures: [
|
|
{ description: 'Test failure', severity: 'medium' as const, likelihood: 'possible' as const },
|
|
{ description: 'Test failure 2', severity: 'low' as const, likelihood: 'unlikely' as const },
|
|
{ description: 'Test failure 3', severity: 'medium' as const, likelihood: 'likely' as const },
|
|
],
|
|
clarityScore: 75,
|
|
};
|
|
|
|
const userNeeds = {
|
|
needs: [
|
|
{ description: 'Clear navigation', priority: 'must-have' as const, addressed: true },
|
|
],
|
|
suitability: 'good' as const,
|
|
challenges: [],
|
|
alignmentScore: 80,
|
|
};
|
|
|
|
const businessNeeds = {
|
|
primaryGoal: 'balanced' as const,
|
|
kpisAffected: ['Page views', 'Engagement'],
|
|
crossTeamImpact: [],
|
|
compromisesUX: false,
|
|
impactsKPIs: true,
|
|
alignmentScore: 75,
|
|
};
|
|
|
|
const results = await engine.applyAll(context, problemAnalysis, userNeeds, businessNeeds);
|
|
|
|
expect(results).toHaveLength(23);
|
|
expect(results.every((r) => r.applied)).toBe(true);
|
|
expect(results.every((r) => r.score >= 0 && r.score <= 100)).toBe(true);
|
|
});
|
|
|
|
it('should apply specific heuristics when configured', async () => {
|
|
const customEngine = new QXHeuristicsEngine({
|
|
enabledHeuristics: [QXHeuristic.RULE_OF_THREE, QXHeuristic.USER_FEELINGS_IMPACT],
|
|
});
|
|
|
|
const context: QXContext = { url: 'https://example.com', title: 'Test' };
|
|
const problemAnalysis = {
|
|
problemStatement: 'Test',
|
|
complexity: 'simple' as const,
|
|
breakdown: [],
|
|
potentialFailures: [
|
|
{ description: 'F1', severity: 'low' as const, likelihood: 'unlikely' as const },
|
|
{ description: 'F2', severity: 'low' as const, likelihood: 'unlikely' as const },
|
|
{ description: 'F3', severity: 'low' as const, likelihood: 'unlikely' as const },
|
|
],
|
|
clarityScore: 50,
|
|
};
|
|
const userNeeds = { needs: [], suitability: 'adequate' as const, challenges: [], alignmentScore: 50 };
|
|
const businessNeeds = {
|
|
primaryGoal: 'balanced' as const,
|
|
kpisAffected: [],
|
|
crossTeamImpact: [],
|
|
compromisesUX: false,
|
|
impactsKPIs: false,
|
|
alignmentScore: 50,
|
|
};
|
|
|
|
const results = await customEngine.applyAll(context, problemAnalysis, userNeeds, businessNeeds);
|
|
|
|
expect(results).toHaveLength(2);
|
|
expect(results[0].id).toBe(QXHeuristic.RULE_OF_THREE);
|
|
expect(results[1].id).toBe(QXHeuristic.USER_FEELINGS_IMPACT);
|
|
});
|
|
});
|
|
|
|
describe('OracleDetector', () => {
|
|
it('should detect user vs business conflicts', () => {
|
|
const detector = new OracleDetector('low');
|
|
|
|
const context: QXContext = { url: 'https://example.com', title: 'E-commerce Shop' };
|
|
const userNeeds = {
|
|
needs: [],
|
|
suitability: 'good' as const,
|
|
challenges: [],
|
|
alignmentScore: 90, // High user alignment
|
|
};
|
|
const businessNeeds = {
|
|
primaryGoal: 'business-ease' as const,
|
|
kpisAffected: ['Conversion rate'],
|
|
crossTeamImpact: [],
|
|
compromisesUX: false,
|
|
impactsKPIs: true,
|
|
alignmentScore: 60, // Low business alignment
|
|
};
|
|
|
|
const problems = detector.detect(context, userNeeds, businessNeeds);
|
|
|
|
expect(problems.length).toBeGreaterThan(0);
|
|
expect(problems.some((p) => p.type === 'user-vs-business')).toBe(true);
|
|
});
|
|
|
|
it('should detect domain-specific oracle problems for healthcare', () => {
|
|
const detector = new OracleDetector('low');
|
|
|
|
const context: QXContext = {
|
|
url: 'https://healthcare.org',
|
|
title: 'Northern Healthcare NHS',
|
|
accessibility: { altTextsCoverage: 60 },
|
|
};
|
|
const userNeeds = {
|
|
needs: [],
|
|
suitability: 'good' as const,
|
|
challenges: [],
|
|
alignmentScore: 75,
|
|
};
|
|
const businessNeeds = {
|
|
primaryGoal: 'balanced' as const,
|
|
kpisAffected: [],
|
|
crossTeamImpact: [],
|
|
compromisesUX: false,
|
|
impactsKPIs: false,
|
|
alignmentScore: 70,
|
|
};
|
|
|
|
const problems = detector.detect(context, userNeeds, businessNeeds);
|
|
|
|
// Healthcare sites should have compliance-related oracle problems
|
|
expect(problems.some((p) => p.type === 'stakeholder-conflict')).toBe(true);
|
|
expect(problems.some((p) => p.stakeholders?.includes('Patients'))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('ImpactAnalyzer', () => {
|
|
it('should analyze visible and invisible impacts', () => {
|
|
const analyzer = new ImpactAnalyzer();
|
|
|
|
const context: QXContext = {
|
|
url: 'https://example.com',
|
|
title: 'Test Page',
|
|
domMetrics: {
|
|
totalElements: 100,
|
|
interactiveElements: 25,
|
|
forms: 2,
|
|
inputs: 8,
|
|
buttons: 10,
|
|
semanticStructure: {
|
|
hasNav: true,
|
|
hasHeader: true,
|
|
hasFooter: true,
|
|
hasMain: true,
|
|
hasAside: false,
|
|
hasArticle: false,
|
|
hasSection: false,
|
|
},
|
|
},
|
|
accessibility: {
|
|
altTextsCoverage: 75,
|
|
focusableElementsCount: 20,
|
|
},
|
|
performance: {
|
|
loadTime: 2500,
|
|
},
|
|
};
|
|
|
|
const problemAnalysis = {
|
|
problemStatement: 'Test',
|
|
complexity: 'moderate' as const,
|
|
breakdown: [],
|
|
potentialFailures: [],
|
|
clarityScore: 70,
|
|
};
|
|
|
|
const impact = analyzer.analyze(context, problemAnalysis);
|
|
|
|
expect(impact.visible.guiFlow?.forEndUser.length).toBeGreaterThan(0);
|
|
expect(impact.visible.userFeelings.length).toBeGreaterThan(0);
|
|
expect(impact.invisible.performance.length).toBeGreaterThan(0);
|
|
expect(impact.immutableRequirements.length).toBeGreaterThan(0);
|
|
expect(impact.overallImpactScore).toBeGreaterThanOrEqual(0);
|
|
expect(impact.overallImpactScore).toBeLessThanOrEqual(100);
|
|
});
|
|
});
|
|
|
|
describe('QXAnalyzeTool', () => {
|
|
let tool: QXAnalyzeTool;
|
|
|
|
beforeEach(() => {
|
|
tool = new QXAnalyzeTool();
|
|
});
|
|
|
|
it('should have correct configuration', () => {
|
|
expect(tool.config.name).toBe('qe/qx/analyze');
|
|
expect(tool.config.domain).toBe('quality-assessment');
|
|
});
|
|
|
|
it('should execute full QX analysis', async () => {
|
|
const params = {
|
|
target: 'https://example.com',
|
|
context: {
|
|
url: 'https://example.com',
|
|
title: 'Example Domain',
|
|
domMetrics: {
|
|
totalElements: 50,
|
|
interactiveElements: 10,
|
|
forms: 0,
|
|
inputs: 0,
|
|
buttons: 5,
|
|
semanticStructure: {
|
|
hasNav: true,
|
|
hasHeader: true,
|
|
hasFooter: true,
|
|
hasMain: true,
|
|
hasAside: false,
|
|
hasArticle: false,
|
|
hasSection: false,
|
|
},
|
|
},
|
|
accessibility: {
|
|
altTextsCoverage: 80,
|
|
focusableElementsCount: 12,
|
|
ariaLabelsCount: 5,
|
|
},
|
|
performance: {
|
|
loadTime: 1200,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = await tool.invoke(params);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toBeDefined();
|
|
expect(result.data?.overallScore).toBeGreaterThanOrEqual(0);
|
|
expect(result.data?.overallScore).toBeLessThanOrEqual(100);
|
|
expect(result.data?.grade).toMatch(/^[A-F]$/);
|
|
expect(result.data?.heuristics).toHaveLength(23);
|
|
expect(result.data?.problemAnalysis.potentialFailures.length).toBeGreaterThanOrEqual(3); // Rule of Three
|
|
expect(result.data?.recommendations.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should work with minimal context', async () => {
|
|
const params = {
|
|
target: 'https://example.com',
|
|
};
|
|
|
|
const result = await tool.invoke(params);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toBeDefined();
|
|
expect(result.data?.target).toBe('https://example.com');
|
|
});
|
|
});
|