Skip to main content
Glama

test_report

Generate automatic test reports for mobile app testing. Start report collection automatically on first interaction and produce a markdown report when testing ends.

Instructions

Gère le rapport de test automatique. Le rapport démarre AUTOMATIQUEMENT dès la première interaction (tap, type_text, etc.) — tu n'as PAS besoin d'appeler 'start'. Appelle UNIQUEMENT action='end' quand tu as fini de tester pour générer le rapport markdown. Si tu veux nommer le rapport, appelle action='start' avec un nom AVANT de commencer.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYes'start' pour commencer le suivi automatique, 'end' pour générer le rapport
nameNoNom du test (requis pour 'start')

Implementation Reference

  • The registerTestReport function registers the 'test_report' MCP tool. The handler supports two actions: 'start' (begins a named test report session) and 'end' (generates and returns the markdown report). It uses resolveDevice, startAutoReport, and endAutoReport from the auto-report utility.
    export function registerTestReport(server: McpServer): void {
      server.tool(
        "test_report",
        "Gère le rapport de test automatique. Le rapport démarre AUTOMATIQUEMENT dès la première interaction (tap, type_text, etc.) — tu n'as PAS besoin d'appeler 'start'. Appelle UNIQUEMENT action='end' quand tu as fini de tester pour générer le rapport markdown. Si tu veux nommer le rapport, appelle action='start' avec un nom AVANT de commencer.",
        {
          action: z.enum(["start", "end"]).describe("'start' pour commencer le suivi automatique, 'end' pour générer le rapport"),
          name: z.string().optional().describe("Nom du test (requis pour 'start')"),
        },
        async ({ action, name }) => {
          if (action === "start") {
            if (isAutoReportActive()) {
              return { content: [{ type: "text", text: "Rapport déjà en cours. Termine-le avec action='end' d'abord." }], isError: true };
            }
            if (!name) {
              return { content: [{ type: "text", text: "Le paramètre 'name' est requis pour démarrer un rapport." }], isError: true };
            }
    
            const result = await resolveDevice();
            if ("error" in result) return { content: [{ type: "text", text: result.error }], isError: true };
            const dev = result.device;
    
            const reportDir = startAutoReport(name, dev.name, dev.platform, dev.id);
    
            return {
              content: [{ type: "text", text: `Rapport "${name}" démarré sur ${dev.name} (${dev.platform}).\n\nToutes les actions suivantes seront enregistrées automatiquement avec un screenshot à chaque étape.\n\nQuand tu as terminé, appelle test_report(action='end') pour générer le rapport.\n\nDossier : ${reportDir}` }],
            };
          }
    
          // --- END ---
          const report = await endAutoReport();
          if (!report) {
            return { content: [{ type: "text", text: "Aucun rapport en cours. Lance action='start' d'abord." }], isError: true };
          }
    
          return {
            content: [{ type: "text", text: report.markdown }],
          };
        }
      );
    }
  • src/index.ts:68-68 (registration)
    Registration call: registerTestReport(server) adds the 'test_report' tool to the MCP server.
    registerTestReport(server);
  • Zod schema for 'test_report' tool: action is a required enum ('start'|'end'), name is an optional string (required for 'start').
    {
      action: z.enum(["start", "end"]).describe("'start' pour commencer le suivi automatique, 'end' pour générer le rapport"),
      name: z.string().optional().describe("Nom du test (requis pour 'start')"),
    },
  • Auto-report utility managing ReportSession lifecycle: startAutoReport, autoStartIfNeeded, autoLogStep, endAutoReport, getAutoReportSession. endAutoReport generates the markdown report with step-by-step screenshots and pass/fail summary.
    import { mkdir, writeFile } from "fs/promises";
    import { takeScreenshot } from "./screenshot.js";
    
    interface ReportStep {
      index: number;
      tool: string;
      description: string;
      status: "pass" | "fail";
      screenshotPath: string;
      timestamp: number;
    }
    
    interface ReportSession {
      name: string;
      startedAt: number;
      deviceName: string;
      platform: "ios" | "android";
      deviceId: string;
      steps: ReportStep[];
      reportDir: string;
    }
    
    const MAX_AUTO_STEPS = 50;
    let session: ReportSession | null = null;
    
    export function isAutoReportActive(): boolean {
      return session !== null;
    }
    
    export function startAutoReport(name: string, deviceName: string, platform: "ios" | "android", deviceId: string): string {
      const timestamp = Date.now();
      const reportDir = `/tmp/phantom-report-${timestamp}`;
      session = { name, startedAt: timestamp, deviceName, platform, deviceId, steps: [], reportDir };
      return reportDir;
    }
    
    /**
     * Auto-start a report if none is active. Called by logAction on first interaction.
     * This makes the report 100% automatic — no need to call test_report(start).
     */
    export function autoStartIfNeeded(deviceName: string, platform: "ios" | "android", deviceId: string): void {
      if (session) return; // Already active
      const timestamp = Date.now();
      const reportDir = `/tmp/phantom-report-${timestamp}`;
      session = {
        name: "Test automatique",
        startedAt: timestamp,
        deviceName,
        platform,
        deviceId,
        steps: [],
        reportDir,
      };
      console.error(`[phantom] Rapport de test démarré automatiquement → ${reportDir}`);
    }
    
    /**
     * Auto-log a step from any tool. Fire-and-forget — never throws, never blocks the caller on failure.
     * Auto-starts a report if none is active.
     */
    // Screenshot every N steps to avoid bloating disk + context
    const SCREENSHOT_INTERVAL = 3;
    
    // Tools that are important enough to always get a screenshot
    const ALWAYS_SCREENSHOT_TOOLS = new Set(["assert_visible", "assert_not_visible", "accessibility_audit", "launch_app"]);
    
    export async function autoLogStep(tool: string, description: string, isError: boolean, platform: "ios" | "android", deviceId: string): Promise<void> {
      if (session && session.steps.length >= MAX_AUTO_STEPS) return;
      if (!session) return;
    
      const stepIndex = session.steps.length + 1;
    
      try {
        await mkdir(session.reportDir, { recursive: true });
        let screenshotPath = "";
    
        // Take screenshot only on errors, important tools, or every N steps
        const shouldScreenshot = isError
          || ALWAYS_SCREENSHOT_TOOLS.has(tool)
          || stepIndex % SCREENSHOT_INTERVAL === 0
          || stepIndex === 1; // Always screenshot first step
    
        if (shouldScreenshot) {
          screenshotPath = `${session.reportDir}/step-${stepIndex}.png`;
          try {
            const buffer = await takeScreenshot(platform, deviceId);
            await writeFile(screenshotPath, buffer);
          } catch (err) {
            console.error(`[phantom] auto-report: screenshot failed for step ${stepIndex}: ${err instanceof Error ? err.message : err}`);
            screenshotPath = "";
          }
        }
    
        session.steps.push({
          index: stepIndex,
          tool,
          description,
          status: isError ? "fail" : "pass",
          screenshotPath,
          timestamp: Date.now(),
        });
      } catch (err) {
        console.error(`[phantom] auto-report: failed to log step: ${err instanceof Error ? err.message : err}`);
      }
    }
    
    export async function endAutoReport(): Promise<{ reportPath: string; markdown: string } | null> {
      if (!session) return null;
    
      const s = session;
      const duration = ((Date.now() - s.startedAt) / 1000).toFixed(1);
      const passed = s.steps.filter((st) => st.status === "pass").length;
      const failed = s.steps.filter((st) => st.status === "fail").length;
      const overall = failed === 0 ? "PASS" : "FAIL";
    
      const md: string[] = [
        `# Test Report: ${s.name}`,
        "",
        `| | |`,
        `|---|---|`,
        `| **Device** | ${s.deviceName} (${s.platform}) |`,
        `| **Date** | ${new Date(s.startedAt).toISOString()} |`,
        `| **Durée** | ${duration}s |`,
        `| **Résultat** | ${overall} |`,
        "",
        `## Résumé`,
        "",
        `- Total : ${s.steps.length} étape(s)`,
        `- Pass : ${passed}`,
        `- Fail : ${failed}`,
        "",
        `## Étapes`,
        "",
      ];
    
      for (const step of s.steps) {
        const icon = step.status === "pass" ? "PASS" : "FAIL";
        md.push(`### Étape ${step.index} — [${icon}] ${step.tool}: ${step.description}`);
        md.push("");
        if (step.screenshotPath) {
          md.push(`![Step ${step.index}](step-${step.index}.png)`);
          md.push("");
        }
      }
    
      md.push("---");
      md.push("*Généré automatiquement par Phantom MCP*");
    
      const markdown = md.join("\n");
      await mkdir(s.reportDir, { recursive: true });
      const reportPath = `${s.reportDir}/report.md`;
      await writeFile(reportPath, markdown);
    
      session = null;
    
      return { reportPath, markdown: `${overall} — ${passed} pass, ${failed} fail en ${duration}s\n\nRapport : ${reportPath}\nOuvre le dossier : open "${s.reportDir}"` };
    }
    
    export function getAutoReportSession(): ReportSession | null {
      return session;
    }
  • logAction helper auto-starts a report on first tool interaction and logs each step. getReportSuffix appends a reminder to tool responses to call test_report(end) when done.
    import { autoLogStep, isAutoReportActive, autoStartIfNeeded, getAutoReportSession } from "./auto-report.js";
    
    /**
     * Auto-log a tool action to the active test report.
     * Auto-starts a report on the first interaction tool call.
     * Fire-and-forget — never blocks, never throws.
     */
    export function logAction(tool: string, description: string, isError: boolean, platform: "ios" | "android", deviceId: string, deviceName?: string): void {
      if (!isAutoReportActive()) {
        autoStartIfNeeded(deviceName ?? deviceId, platform, deviceId);
      }
    
      autoLogStep(tool, description, isError, platform, deviceId).catch((err) => {
        console.error(`[phantom] auto-report log failed: ${err instanceof Error ? err.message : err}`);
      });
    }
    
    /**
     * Returns a suffix to append to tool responses when a report is active.
     * This reminds Claude to call test_report(end) when testing is done.
     */
    export function getReportSuffix(): string {
      const session = getAutoReportSession();
      if (!session) return "";
      return `\n\n📋 Rapport "${session.name}" en cours (${session.steps.length} étape(s)). Appelle test_report(end) quand tu as fini de tester.`;
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses automatic start behavior and the effect of calling start with a name. With no annotations, the description covers the key behavioral traits, though could mention error handling or multiple start calls.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three concise sentences in French, front-loaded with the automatic start key point. No redundant information; every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with two parameters and no output schema, the description fully explains usage. It mentions markdown report generation and when to use each action, leaving no ambiguity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and description adds context: clarifies that name is required for start despite schema not marking it required, and explains the purpose of each action.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it manages the automatic test report, specifies the automatic start on first interaction, and distinguishes the start and end actions. It is specific about when to use each action and the report generation behavior.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs not to call start unless naming the report, and to only call end to generate the report. Provides clear context for when each action is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nthImpulse/phantom-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server