test-reporting-triage-skill

安装量: 35
排名: #19526

安装

npx skills add https://github.com/patricio0312rev/skills --skill test-reporting-triage-skill

Test Reporting & Triage Skill

Automatically triage test failures and suggest next actions.

Failure Categorization // types/test-failure.ts export type FailureCategory = | "timeout" | "assertion" | "network" | "database" | "authentication" | "permission" | "configuration" | "flaky" | "infrastructure" | "unknown";

export interface TestFailure { testName: string; category: FailureCategory; errorMessage: string; stackTrace: string; suggestedOwner: string; suggestedFixes: string[]; runId: string; timestamp: Date; }

Failure Analyzer // analyzers/failure-analyzer.ts export class FailureAnalyzer { categorize(error: Error, testName: string): TestFailure { const errorMessage = error.message.toLowerCase(); const stackTrace = error.stack || "";

// Timeout detection
if (errorMessage.includes("timeout") || errorMessage.includes("exceeded")) {
  return {
    testName,
    category: "timeout",
    errorMessage: error.message,
    stackTrace,
    suggestedOwner: "Performance Team",
    suggestedFixes: [
      "Check if API is slow",
      "Increase timeout value",
      "Optimize database query",
      "Check for network issues",
    ],
    runId: process.env.CI_RUN_ID || "local",
    timestamp: new Date(),
  };
}

// Network errors
if (
  errorMessage.includes("econnrefused") ||
  errorMessage.includes("network") ||
  errorMessage.includes("fetch failed")
) {
  return {
    testName,
    category: "network",
    errorMessage: error.message,
    stackTrace,
    suggestedOwner: "DevOps Team",
    suggestedFixes: [
      "Check if service is running",
      "Verify network connectivity",
      "Check firewall rules",
      "Verify DNS resolution",
    ],
    runId: process.env.CI_RUN_ID || "local",
    timestamp: new Date(),
  };
}

// Database errors
if (
  errorMessage.includes("database") ||
  errorMessage.includes("prisma") ||
  errorMessage.includes("unique constraint")
) {
  return {
    testName,
    category: "database",
    errorMessage: error.message,
    stackTrace,
    suggestedOwner: "Backend Team",
    suggestedFixes: [
      "Check database connection",
      "Verify test data cleanup",
      "Check for race conditions",
      "Review migration status",
    ],
    runId: process.env.CI_RUN_ID || "local",
    timestamp: new Date(),
  };
}

// Authentication errors
if (
  errorMessage.includes("unauthorized") ||
  errorMessage.includes("authentication") ||
  errorMessage.includes("401")
) {
  return {
    testName,
    category: "authentication",
    errorMessage: error.message,
    stackTrace,
    suggestedOwner: "Auth Team",
    suggestedFixes: [
      "Check auth token validity",
      "Verify test user credentials",
      "Check session expiration",
      "Review auth middleware",
    ],
    runId: process.env.CI_RUN_ID || "local",
    timestamp: new Date(),
  };
}

// Assertion failures
if (
  errorMessage.includes("expected") &&
  errorMessage.includes("received")
) {
  return {
    testName,
    category: "assertion",
    errorMessage: error.message,
    stackTrace,
    suggestedOwner: this.determineOwnerFromPath(stackTrace),
    suggestedFixes: [
      "Review recent code changes",
      "Check if test expectations are correct",
      "Verify test data setup",
      "Check for breaking changes",
    ],
    runId: process.env.CI_RUN_ID || "local",
    timestamp: new Date(),
  };
}

// Default: unknown
return {
  testName,
  category: "unknown",
  errorMessage: error.message,
  stackTrace,
  suggestedOwner: "On-Call Engineer",
  suggestedFixes: [
    "Review error message and stack trace",
    "Check recent commits",
    "Run test locally to reproduce",
    "Add more specific error handling",
  ],
  runId: process.env.CI_RUN_ID || "local",
  timestamp: new Date(),
};

}

private determineOwnerFromPath(stackTrace: string): string { if (stackTrace.includes("/frontend/")) return "Frontend Team"; if (stackTrace.includes("/backend/")) return "Backend Team"; if (stackTrace.includes("/api/")) return "API Team"; if (stackTrace.includes("/database/")) return "Database Team"; return "Development Team"; } }

Test Report Generator // reporters/test-report.ts import { FailureAnalyzer } from "../analyzers/failure-analyzer";

export class TestReporter { private analyzer = new FailureAnalyzer(); private failures: TestFailure[] = [];

recordFailure(error: Error, testName: string) { const failure = this.analyzer.categorize(error, testName); this.failures.push(failure); }

generateReport(): string { const grouped = this.groupByCategory(); const report: string[] = [];

report.push("# Test Failure Report\n");
report.push(`Generated: ${new Date().toISOString()}\n`);
report.push(`Total Failures: ${this.failures.length}\n\n`);

// Summary by category
report.push("## Summary by Category\n");
Object.entries(grouped).forEach(([category, failures]) => {
  report.push(`- ${category}: ${failures.length} failures`);
});
report.push("\n");

// Detailed failures
report.push("## Detailed Failures\n\n");
Object.entries(grouped).forEach(([category, failures]) => {
  report.push(`### ${category.toUpperCase()} (${failures.length})\n\n`);

  failures.forEach((failure, i) => {
    report.push(`#### ${i + 1}. ${failure.testName}\n`);
    report.push(`**Owner:** ${failure.suggestedOwner}\n\n`);
    report.push(`**Error:**\n\`\`\`\n${failure.errorMessage}\n\`\`\`\n\n`);
    report.push(`**Suggested Fixes:**\n`);
    failure.suggestedFixes.forEach((fix) => {
      report.push(`- ${fix}\n`);
    });
    report.push("\n");
  });
});

return report.join("");

}

generateSlackMessage(): string { const grouped = this.groupByCategory(); const messages: string[] = [];

messages.push("🔴 *Test Failures Detected*\n");
messages.push(`Total: ${this.failures.length} failures\n`);

Object.entries(grouped).forEach(([category, failures]) => {
  const icon = this.getCategoryIcon(category);
  messages.push(`${icon} ${category}: ${failures.length}`);
});

// Top 3 failures
messages.push("\n*Top Failures:*");
this.failures.slice(0, 3).forEach((failure, i) => {
  messages.push(`\n${i + 1}. \`${failure.testName}\``);
  messages.push(`   Owner: @${failure.suggestedOwner}`);
});

return messages.join("\n");

}

private groupByCategory(): Record { return this.failures.reduce((acc, failure) => { if (!acc[failure.category]) { acc[failure.category] = []; } acc[failure.category].push(failure); return acc; }, {} as Record); }

private getCategoryIcon(category: string): string { const icons: Record = { timeout: "⏱️", network: "🌐", database: "💾", authentication: "🔐", assertion: "❌", flaky: "🔄", infrastructure: "🏗️", unknown: "❓", }; return icons[category] || "❓"; } }

Common Fix Checklists // checklists/fix-checklists.ts export const fixChecklists = { timeout: { title: "Timeout Failure Checklist", steps: [ "☐ Check if the timeout is too short", "☐ Verify API response time in logs", "☐ Check database query performance", "☐ Look for network latency issues", "☐ Verify no infinite loops or deadlocks", "☐ Check if external services are slow", "☐ Consider increasing timeout temporarily", "☐ Optimize slow code path if confirmed slow", ], },

flaky: { title: "Flaky Test Checklist", steps: [ "☐ Run test 10 times locally", "☐ Check for race conditions", "☐ Verify proper test cleanup", "☐ Look for timing dependencies", "☐ Check for shared state between tests", "☐ Verify no randomness in test data", "☐ Check for network/external dependencies", "☐ Add explicit waits where needed", ], },

database: { title: "Database Error Checklist", steps: [ "☐ Verify database is running", "☐ Check connection string", "☐ Verify test database cleanup", "☐ Check for constraint violations", "☐ Look for migration issues", "☐ Verify proper transaction handling", "☐ Check for concurrent access issues", "☐ Review recent schema changes", ], },

assertion: { title: "Assertion Failure Checklist", steps: [ "☐ Review what changed in recent commits", "☐ Verify test expectations are still valid", "☐ Check if feature requirements changed", "☐ Run test locally to reproduce", "☐ Check test data setup", "☐ Verify mocks are up to date", "☐ Review API contract changes", "☐ Update test if behavior change is intentional", ], }, };

CI Integration

.github/workflows/test-report.yml

name: Test Report

on: workflow_run: workflows: ["CI"] types: [completed]

jobs: report: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'failure' }}

steps:
  - uses: actions/checkout@v4

  - name: Download test results
    uses: actions/download-artifact@v4
    with:
      name: test-results

  - name: Generate report
    run: npm run analyze-failures

  - name: Post to Slack
    uses: slackapi/slack-github-action@v1
    with:
      channel-id: "test-failures"
      payload: ${{ steps.analyze.outputs.slack_message }}
    env:
      SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

  - name: Create GitHub Issue
    uses: actions/github-script@v7
    with:
      script: |
        const report = require('./test-report.json');
        github.rest.issues.create({
          owner: context.repo.owner,
          repo: context.repo.repo,
          title: `Test Failures - ${new Date().toISOString()}`,
          body: report.markdown,
          labels: ['test-failure', 'automated'],
        });

Dashboard Metrics // dashboard/test-metrics.ts export interface TestMetrics { totalTests: number; passed: number; failed: number; skipped: number; duration: number; failureRate: number;

failuresByCategory: Record; failuresByOwner: Record; flakyTests: string[]; slowTests: Array<{ name: string; duration: number }>; }

export function generateMetrics(results: TestResult[]): TestMetrics { const failures = results.filter((r) => r.status === "failed"); const analyzer = new FailureAnalyzer();

const categorized = failures.map((f) => analyzer.categorize(f.error, f.testName) );

return { totalTests: results.length, passed: results.filter((r) => r.status === "passed").length, failed: failures.length, skipped: results.filter((r) => r.status === "skipped").length, duration: results.reduce((sum, r) => sum + r.duration, 0), failureRate: (failures.length / results.length) * 100,

failuresByCategory: categorized.reduce((acc, f) => {
  acc[f.category] = (acc[f.category] || 0) + 1;
  return acc;
}, {} as Record<FailureCategory, number>),

failuresByOwner: categorized.reduce((acc, f) => {
  acc[f.suggestedOwner] = (acc[f.suggestedOwner] || 0) + 1;
  return acc;
}, {} as Record<string, number>),

flakyTests: identifyFlakyTests(results),
slowTests: results
  .filter((r) => r.duration > 5000)
  .sort((a, b) => b.duration - a.duration)
  .slice(0, 10),

}; }

Best Practices Auto-categorize: Classify failures automatically Suggest owners: Route to right team Actionable fixes: Provide clear next steps Track trends: Monitor failure patterns Notify quickly: Slack/email on failures Create issues: Auto-file for persistent failures Dashboard: Visual metrics for team Output Checklist Failure categorization logic Owner assignment rules Fix checklists per category Report generation (Markdown/Slack) CI integration GitHub issue creation Slack notifications Dashboard metrics Trend analysis Flaky test detection

返回排行榜