Overview
Implement automated feature change detection and validation reporting to identify UI changes, categorize them as intentional features or regressions, and generate actionable reports for code reviewers.
Goal: Transform visual regression testing from a simple pass/fail system into an intelligent feature validation platform that helps teams distinguish between intentional improvements and accidental breakage.
Parent: #71 (Visual Regression Testing Pipeline)
Builds on: #76 (GitHub Pages Dashboard), #73 (Screenshot Capture), #75 (GitHub Gist PR Comments)
Problem Statement
Current State:
- Visual regression tests flag all UI changes as failures
- No distinction between intentional features and bugs
- Manual review required to approve each visual change
- No historical context for why changes were approved
- Difficult to track feature evolution over time
Desired State:
- Automatic detection of feature changes vs regressions
- Categorized changes with confidence scores
- Validation workflow with approval tracking
- Historical context and change documentation
- Feature evolution timeline
Features
1. Change Detection Engine (Priority: High)
Complexity: High
Estimated Effort: 4-5 days
Detection Categories:
type ChangeCategory =
| 'new-feature' // New UI element added
| 'enhancement' // Existing element improved
| 'regression' // Unintended visual breakage
| 'refactoring' // Visual identical, code changed
| 'layout-shift' // Position/spacing changes
| 'style-update' // Color/font/theme changes
| 'content-change' // Text or image content changed
| 'removal' // UI element removed
| 'unknown'; // Unable to categorize
interface DetectedChange {
id: string;
category: ChangeCategory;
confidence: number; // 0-1
location: {
selector: string;
boundingBox: { x: number; y: number; width: number; height: number };
};
diff: {
pixelsChanged: number;
percentChanged: number;
screenshotBefore: string;
screenshotAfter: string;
diffImage: string;
};
context: {
componentName?: string;
interactionStep?: number;
relatedCommits: string[];
prNumber?: number;
};
validationStatus: 'pending' | 'approved' | 'rejected';
}
Detection Algorithm:
class ChangeDetectionEngine {
async analyzeChanges(
baseline: Screenshot,
current: Screenshot,
metadata: TestMetadata
): Promise<DetectedChange[]> {
// 1. Pixel-level diff
const diffResult = await this.pixelDiff(baseline, current);
// 2. Structural analysis
const structuralChanges = await this.detectStructuralChanges(
baseline,
current
);
// 3. Semantic analysis (using CV/ML)
const semanticChanges = await this.semanticAnalysis(
baseline,
current,
metadata
);
// 4. Categorize changes
const categorized = this.categorizeChanges(
diffResult,
structuralChanges,
semanticChanges
);
// 5. Calculate confidence scores
return this.scoreConfidence(categorized, metadata);
}
private categorizeChanges(
pixelDiff: PixelDiff,
structural: StructuralChange[],
semantic: SemanticChange[]
): DetectedChange[] {
const changes: DetectedChange[] = [];
// New elements (present in current, not in baseline)
for (const element of structural.newElements) {
changes.push({
category: 'new-feature',
confidence: 0.9,
location: element.boundingBox,
// ...
});
}
// Removed elements
for (const element of structural.removedElements) {
changes.push({
category: 'removal',
confidence: 0.85,
// ...
});
}
// Style changes (same structure, different appearance)
for (const change of semantic.styleChanges) {
changes.push({
category: this.classifyStyleChange(change),
confidence: 0.75,
// ...
});
}
return changes;
}
private classifyStyleChange(change: SemanticChange): ChangeCategory {
// Heuristics for style classification
if (change.colorDifference > 0.5) return 'style-update';
if (change.positionDifference > 10) return 'layout-shift';
if (change.sizeDifference > 0.2) return 'enhancement';
return 'unknown';
}
}
2. Validation Workflow (Priority: High)
Complexity: Medium
Estimated Effort: 3 days
Workflow States:
┌─────────────┐
│ Detected │
│ Changes │
└──────┬──────┘
│
▼
┌─────────────┐ ┌──────────────┐
│ Auto- │ Yes │ Approved │
│ Approve? ├─────▶│ (Baseline │
│ │ │ Updated) │
└──────┬──────┘ └──────────────┘
│ No
▼
┌─────────────┐
│ Manual │
│ Review │
│ Required │
└──────┬──────┘
│
▼
┌────────┴────────┐
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Approve │ │ Reject │
│ (Update │ │ (Flag as │
│ Baseline)│ │ Bug) │
└──────────┘ └──────────┘
Auto-Approval Rules:
interface AutoApprovalRule {
name: string;
condition: (change: DetectedChange) => boolean;
enabled: boolean;
}
const defaultAutoApprovalRules: AutoApprovalRule[] = [
{
name: 'Minor style updates',
condition: (change) =>
change.category === 'style-update' &&
change.diff.percentChanged < 2 &&
change.confidence > 0.8,
enabled: true
},
{
name: 'Refactoring with no visual change',
condition: (change) =>
change.category === 'refactoring' &&
change.diff.percentChanged < 0.1,
enabled: true
},
{
name: 'Known feature branches',
condition: (change) =>
change.context.relatedCommits.some(commit =>
commit.message.startsWith('feat:')
),
enabled: false // Disabled by default, opt-in
}
];
Manual Review Interface:
// Dashboard component for change review
export function ChangeReviewPanel({ changes }: Props) {
return (
<div className="change-review-panel">
{changes.map(change => (
<ChangeCard
key={change.id}
change={change}
onApprove={() => approveChange(change.id)}
onReject={(reason) => rejectChange(change.id, reason)}
/>
))}
</div>
);
}
// Individual change card
function ChangeCard({ change, onApprove, onReject }: CardProps) {
return (
<Card className={`change-card ${change.category}`}>
<Badge category={change.category} confidence={change.confidence} />
<div className="change-comparison">
<img src={change.diff.screenshotBefore} alt="Before" />
<img src={change.diff.diffImage} alt="Diff" />
<img src={change.diff.screenshotAfter} alt="After" />
</div>
<ChangeDetails change={change} />
<div className="actions">
<Button onClick={onApprove} kind="primary">
✓ Approve & Update Baseline
</Button>
<Button onClick={() => onReject('regression')} kind="danger">
✗ Reject (Bug)
</Button>
<Button onClick={() => onReject('review-later')} kind="secondary">
⏸ Review Later
</Button>
</div>
</Card>
);
}
3. Confidence Scoring (Priority: Medium)
Complexity: High
Estimated Effort: 2-3 days
Scoring Factors:
interface ConfidenceFactors {
// Visual similarity (inverse of diff)
visualSimilarity: number; // 0-1
// Structural consistency
structuralMatch: number; // 0-1
// Context signals
commitMessageSignal: number; // -1 to 1 (feat: +1, fix: -0.5)
branchNameSignal: number; // -1 to 1
prLabelsSignal: number; // -1 to 1
// Historical patterns
similarChangesApproved: number; // 0-1
developerReliability: number; // 0-1 (based on past approvals)
// Temporal factors
changeRecency: number; // 0-1 (recent = higher confidence)
testFrequency: number; // 0-1 (frequent = higher confidence)
}
function calculateConfidence(
change: DetectedChange,
factors: ConfidenceFactors
): number {
// Weighted sum
const weights = {
visual: 0.3,
structural: 0.2,
contextSignals: 0.2,
historical: 0.2,
temporal: 0.1
};
const contextSignal =
(factors.commitMessageSignal +
factors.branchNameSignal +
factors.prLabelsSignal) / 3;
const score =
factors.visualSimilarity * weights.visual +
factors.structuralMatch * weights.structural +
contextSignal * weights.contextSignals +
factors.similarChangesApproved * weights.historical +
factors.changeRecency * weights.temporal;
return Math.max(0, Math.min(1, score));
}
4. Feature Evolution Timeline (Priority: Medium)
Complexity: Medium
Estimated Effort: 2 days
Timeline Visualization:
interface FeatureEvolutionTimeline {
featureId: string; // e.g., "bio-dashboard-upload-button"
componentName: string;
changes: FeatureChange[];
}
interface FeatureChange {
timestamp: string;
commit: string;
pr: number;
changeType: ChangeCategory;
screenshot: string;
approvedBy: string;
notes: string;
}
// Visual timeline component
export function FeatureTimeline({ timeline }: Props) {
return (
<div className="feature-timeline">
<h3>{timeline.componentName} Evolution</h3>
<div className="timeline-track">
{timeline.changes.map((change, i) => (
<TimelineMarker
key={i}
change={change}
position={calculatePosition(change.timestamp)}
/>
))}
</div>
<div className="timeline-details">
{timeline.changes.map(change => (
<ChangeSnapshot key={change.commit} change={change} />
))}
</div>
</div>
);
}
5. Validation Reporting (Priority: High)
Complexity: Medium
Estimated Effort: 2-3 days
Report Types:
A. PR Comment Report:
## 🔍 Feature Change Detection Report
**Summary**: 5 visual changes detected (3 approved, 2 require review)
### ✅ Auto-Approved Changes (3)
#### 1. Button Style Update - bio-upload-button
- **Category**: style-update (confidence: 92%)
- **Change**: Primary button color updated to match new theme
- **Impact**: 1.8% of component area
- **Auto-approved by**: Rule "Minor style updates"
[Before] [Diff] [After]
---
### ⚠️ Changes Requiring Review (2)
#### 2. New Element Detected - file-upload-progress
- **Category**: new-feature (confidence: 85%)
- **Change**: Progress bar added during file upload
- **Impact**: New UI element (not in baseline)
- **Context**: Related to commit `feat: add upload progress indicator`
👉 **Action Required**: Review and approve/reject
[Screenshot]
---
#### 3. Layout Shift - bio-table-header
- **Category**: layout-shift (confidence: 68%)
- **Change**: Table header moved 15px down
- **Impact**: Possible unintended side effect
- **Context**: No related feat commits found
⚠️ **Warning**: May be unintended regression
[Before] [Diff] [After]
---
**Next Steps**:
1. Review changes requiring manual approval
2. Approve intentional changes and update baselines
3. Investigate potential regressions
B. Dashboard Report:
export function ValidationReport({ testRun }: Props) {
const changes = useDetectedChanges(testRun.id);
const stats = calculateStats(changes);
return (
<div className="validation-report">
<ReportHeader stats={stats} />
<ChangesSummary changes={changes} />
<ApprovalQueue
pending={changes.filter(c => c.validationStatus === 'pending')}
/>
<ChangeHistory
approved={changes.filter(c => c.validationStatus === 'approved')}
/>
</div>
);
}
Implementation Plan
Phase 1: Change Detection (4-5 days)
Phase 2: Validation Workflow (3 days)
Phase 3: Reporting (2-3 days)
Phase 4: Integration & Polish (2 days)
Acceptance Criteria
Technical Considerations
Machine Learning Integration
Optional Enhancement: Train ML model on historical approvals to improve categorization
# Training data: historical changes and their approvals
training_data = [
{
'diff_pixels': 1250,
'percent_changed': 1.8,
'has_feat_commit': True,
'category': 'style-update',
'approved': True
},
# ... more examples
]
# Train classifier
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()
model.fit(X_train, y_train)
# Use for predictions
prediction = model.predict(new_change_features)
Performance
- Cache diff results
- Parallelize screenshot analysis
- Use web workers for heavy computation
- Lazy-load change details
Data Storage
// Store validation history
interface ValidationHistory {
changeId: string;
approvedAt: string;
approvedBy: string;
reason: string;
relatedCommits: string[];
}
// localStorage for dashboard
// Git-tracked JSON for CI/CD
Related Issues
Success Metrics
- 50%+ reduction in manual review time
- 90%+ accuracy in change categorization
- Zero false negatives (all regressions caught)
- <10% false positives (intentional changes flagged)
- Approval workflow completes in <2 minutes per PR
This feature transforms visual regression testing from a burden into an intelligent assistant that accelerates development while maintaining quality!
🤖 Generated with Claude Code
Overview
Implement automated feature change detection and validation reporting to identify UI changes, categorize them as intentional features or regressions, and generate actionable reports for code reviewers.
Goal: Transform visual regression testing from a simple pass/fail system into an intelligent feature validation platform that helps teams distinguish between intentional improvements and accidental breakage.
Parent: #71 (Visual Regression Testing Pipeline)
Builds on: #76 (GitHub Pages Dashboard), #73 (Screenshot Capture), #75 (GitHub Gist PR Comments)
Problem Statement
Current State:
Desired State:
Features
1. Change Detection Engine (Priority: High)
Complexity: High
Estimated Effort: 4-5 days
Detection Categories:
Detection Algorithm:
2. Validation Workflow (Priority: High)
Complexity: Medium
Estimated Effort: 3 days
Workflow States:
Auto-Approval Rules:
Manual Review Interface:
3. Confidence Scoring (Priority: Medium)
Complexity: High
Estimated Effort: 2-3 days
Scoring Factors:
4. Feature Evolution Timeline (Priority: Medium)
Complexity: Medium
Estimated Effort: 2 days
Timeline Visualization:
5. Validation Reporting (Priority: High)
Complexity: Medium
Estimated Effort: 2-3 days
Report Types:
A. PR Comment Report:
B. Dashboard Report:
Implementation Plan
Phase 1: Change Detection (4-5 days)
Phase 2: Validation Workflow (3 days)
Phase 3: Reporting (2-3 days)
Phase 4: Integration & Polish (2 days)
Acceptance Criteria
Technical Considerations
Machine Learning Integration
Optional Enhancement: Train ML model on historical approvals to improve categorization
Performance
Data Storage
Related Issues
Success Metrics
This feature transforms visual regression testing from a burden into an intelligent assistant that accelerates development while maintaining quality!
🤖 Generated with Claude Code