Skip to main content
Glama
upload-latest-local-test-runs.ts10.3 kB
/** * Upload latest local test runs tool */ import { execSync } from "child_process"; import { existsSync } from "fs"; import { resolve, dirname, join } from "path"; import { getApiKey } from "../../lib/env.js"; /** * Find the git repository root by walking up from the given path */ function findGitRoot(startPath: string): string | null { let currentPath = resolve(startPath); const maxDepth = 20; let depth = 0; while (depth < maxDepth) { const gitPath = join(currentPath, ".git"); if (existsSync(gitPath)) { return currentPath; } const parentPath = dirname(currentPath); if (parentPath === currentPath) { break; // Reached filesystem root } currentPath = parentPath; depth++; } return null; } export const uploadLatestLocalTestRunsTool = { name: "upload_latest_local_test_runs", description: "Upload your local Playwright test results to TestDino. After running tests locally, upload the report to track and analyze results. Automatically detects git info (branch, commit, author). Use absolute paths for best results.", inputSchema: { type: "object", properties: { reportDir: { type: "string", description: "Path to Playwright report directory. Use absolute path (e.g., '/Users/jon/project/playwright-report' or 'C:\\Users\\jon\\project\\playwright-report'). Default: './playwright-report'.", default: "./playwright-report", }, uploadHtml: { type: "boolean", description: "Upload HTML reports with all data (JSON, images, videos). Recommended. Default: true.", default: true, }, uploadImages: { type: "boolean", description: "Upload screenshot images. Default: false (included in uploadHtml).", default: false, }, uploadVideos: { type: "boolean", description: "Upload test execution videos. Default: false (included in uploadHtml).", default: false, }, uploadTraces: { type: "boolean", description: "Upload Playwright trace files for debugging. Default: false.", default: false, }, uploadFiles: { type: "boolean", description: "Upload file attachments (.md, .pdf, .txt, .log). Default: false.", default: false, }, uploadFullJson: { type: "boolean", description: "Upload complete JSON bundle with all artifacts (alternative to uploadHtml). Default: false.", default: false, }, jsonReport: { type: "string", description: "Specific JSON report file path (overrides reportDir).", }, htmlReport: { type: "string", description: "Specific HTML report path (overrides reportDir).", }, traceDir: { type: "string", description: "Specific trace directory path (overrides reportDir).", }, verbose: { type: "boolean", description: "Show detailed logging. Default: false.", default: false, }, }, required: [], }, }; export async function handleUploadLatestLocalTestRuns(args: any) { const reportDir = args?.reportDir || "./playwright-report"; const uploadHtml = args?.uploadHtml === undefined ? true : Boolean(args?.uploadHtml); const uploadImages = Boolean(args?.uploadImages); const uploadVideos = Boolean(args?.uploadVideos); const uploadTraces = Boolean(args?.uploadTraces); const uploadFiles = Boolean(args?.uploadFiles); const uploadFullJson = Boolean(args?.uploadFullJson); const jsonReport = args?.jsonReport; const htmlReport = args?.htmlReport; const traceDir = args?.traceDir; const verbose = Boolean(args?.verbose); const token = getApiKey(args); if (!token) { throw new Error( "Missing TESTDINO_API_KEY environment variable. " + "Please configure it in your .cursor/mcp.json file under the 'env' section." ); } // Determine runtime dynamically based on API key using regex let runtime = "development"; // default const tokenMatch = String(token).match(/^trx_([a-z]+)_/i); if (tokenMatch && tokenMatch[1]) { const env = tokenMatch[1].toLowerCase(); if (["production", "staging", "development"].includes(env)) { runtime = env; } } try { // Resolve report directory path (only if jsonReport/htmlReport/traceDir are not provided) let resolvedReportDir: string | undefined; let projectRoot: string = process.cwd(); if (!jsonReport && !htmlReport && !traceDir) { const reportDirStr = String(reportDir); // Check if reportDir is an absolute path if (reportDirStr.startsWith("/") || /^[A-Za-z]:/.test(reportDirStr)) { // Absolute path - use as-is resolvedReportDir = reportDirStr; } else { // Relative path - find git root (project directory) first // Git root gives us the actual project directory like /Users/ashish/Desktop/demostore-testdino const gitRoot = findGitRoot(process.cwd()); if (gitRoot) { // Found git root - this is our project directory resolvedReportDir = resolve(gitRoot, reportDirStr); projectRoot = gitRoot; } else { // No git root found - use current working directory as fallback resolvedReportDir = resolve(process.cwd(), reportDirStr); projectRoot = process.cwd(); } } // Check if the directory exists if (!existsSync(resolvedReportDir)) { throw new Error( `Report directory does not exist: ${resolvedReportDir}\n` + `Please ensure the Playwright report has been generated at: ${reportDir}\n` + `Searched from: ${projectRoot || process.cwd()}\n` + `Tip: Use an absolute path if the report is in a different location.` ); } // Find git repository root from the resolved report directory (if not already found) if (!projectRoot || projectRoot === process.cwd()) { const gitRoot = findGitRoot(resolvedReportDir); if (gitRoot) { projectRoot = gitRoot; } else { // If no git root found, use the directory containing the report projectRoot = dirname(resolvedReportDir); } } } else { // If specific paths are provided, try to find git root from them const pathToCheck = jsonReport || htmlReport || traceDir; if (pathToCheck) { const pathStr = String(pathToCheck); let resolvedPath: string; // Check if it's an absolute path if (pathStr.startsWith("/") || /^[A-Za-z]:/.test(pathStr)) { resolvedPath = pathStr; } else { // Try to find git root first const gitRootFromCwd = findGitRoot(process.cwd()); const basePath = gitRootFromCwd || process.cwd(); resolvedPath = resolve(basePath, pathStr); } const gitRoot = findGitRoot(resolvedPath); if (gitRoot) { projectRoot = gitRoot; } } } // Build the command: npx tdpw upload [reportDir] --token=<token> [options] // Note: TESTDINO_RUNTIME is set via the env option in execSync (cross-platform compatible) const cmdParts = [ "npx", "tdpw", "upload", ]; // Add report directory or specific paths if (resolvedReportDir) { cmdParts.push(resolvedReportDir); } // Add token (ensure it's a string and wrapped in double quotes) cmdParts.push(`--token="${token}"`); // Add upload options (order matters for some flags) if (uploadFullJson) { cmdParts.push("--upload-full-json"); } else { // Individual upload options (only if uploadFullJson is not set) if (uploadHtml) { cmdParts.push("--upload-html"); } if (uploadImages) { cmdParts.push("--upload-images"); } if (uploadVideos) { cmdParts.push("--upload-videos"); } if (uploadTraces) { cmdParts.push("--upload-traces"); } if (uploadFiles) { cmdParts.push("--upload-files"); } } // Add specific report paths if (jsonReport) { cmdParts.push(`--json-report=${jsonReport}`); } if (htmlReport) { cmdParts.push(`--html-report=${htmlReport}`); } if (traceDir) { cmdParts.push(`--trace-dir=${traceDir}`); } // Add verbose flag if (verbose) { cmdParts.push("--verbose"); } const cmd = cmdParts.join(" "); // Execute command with TESTDINO_RUNTIME environment variable const env = { ...process.env, TESTDINO_RUNTIME: runtime, }; let output: string; let stderr: string = ""; try { // Execute from the git repository root so git metadata can be detected output = execSync(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], cwd: projectRoot, env: env, }) as string; } catch (execError: any) { output = execError.stdout || ""; stderr = execError.stderr || ""; throw execError; } const successMessage = output.trim() ? `✅ Report uploaded successfully!\n\nOutput:\n${output}` : `✅ Report uploaded successfully!`; return { content: [ { type: "text", text: successMessage, }, ], }; } catch (error: any) { const stderr = error?.stderr || ""; const stdout = error?.stdout || ""; const errorMessage = `❌ Upload failed.\n\nError: ${error?.message || "Unknown error"}\n\n${ stderr ? `Stderr:\n${stderr}\n\n` : "" }${stdout ? `Stdout:\n${stdout}\n\n` : ""}Please check:\n1. The report directory exists: ${reportDir}\n2. Your TestDino token is valid\n3. You have internet connectivity\n4. The tdpw package is available (try: npx tdpw --help)`; return { content: [ { type: "text", text: errorMessage, }, ], }; } }

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/testdino-inc/testdino-mcp'

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