Skip to main content
Glama
run-e2e-tests.js9.29 kB
#!/usr/bin/env node /** * E2E Test Runner with Environment Validation * * Validates the environment, provides actionable guidance for missing * configuration, and runs Vitest in the desired mode. */ import { spawn } from 'child_process'; import { existsSync } from 'fs'; import { join } from 'path'; import { collectEnvironmentStatus, DEFAULT_ENV_FILES, loadEnvironmentFiles, logSecretPresence, } from './utils/environment.js'; import { createE2ELogger } from './utils/logger.js'; import { resolveTestPattern, listAvailablePatterns, } from './config/test-patterns.js'; const CONFIG = { requiredEnvVars: ['ATTIO_API_KEY'], optionalEnvVars: [ 'E2E_TEST_PREFIX', 'E2E_TEST_EMAIL_DOMAIN', 'E2E_TEST_COMPANY_DOMAIN', ], configFiles: ['test/e2e/config.local.json', 'test/e2e/config.template.json'], }; const logger = createE2ELogger('E2E Runner'); function ensureEnvironmentFilesLoaded() { const loaderLogger = createE2ELogger('E2E Env Loader'); loadEnvironmentFiles({ files: DEFAULT_ENV_FILES, logger: loaderLogger }); } function parseCommandLineArgs(argv) { const options = { help: false, check: false, limited: false, verbose: false, reporter: null, pattern: 'all', }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; switch (arg) { case '--help': case '-h': options.help = true; break; case '--check': options.check = true; break; case '--limited': options.limited = true; break; case '--verbose': case '-v': options.verbose = true; break; case '--reporter': options.reporter = argv[index + 1] ?? null; index += 1; break; case '--pattern': options.pattern = argv[index + 1] ?? 'all'; index += 1; break; default: if (!options.pattern || options.pattern === 'all') { options.pattern = arg; } break; } } return options; } function findConfigFile(configFiles) { return configFiles .map((file) => ({ file, absolute: join(process.cwd(), file) })) .find(({ absolute }) => existsSync(absolute)); } function gatherEnvironmentDetails() { const envStatus = collectEnvironmentStatus( CONFIG.requiredEnvVars, CONFIG.optionalEnvVars ); const info = []; const warnings = []; const issues = []; envStatus.presentRequired.forEach((envVar) => { info.push(`${envVar} is set`); }); envStatus.presentOptional.forEach((envVar) => { info.push(`${envVar} is set`); }); envStatus.missingRequired.forEach((envVar) => { issues.push(`Missing required environment variable: ${envVar}`); }); envStatus.missingOptional.forEach((envVar) => { warnings.push( `Optional environment variable not set: ${envVar} (defaults will be used)` ); }); const configFile = findConfigFile(CONFIG.configFiles); if (configFile) { info.push(`Configuration file found: ${configFile.file}`); } else { issues.push( 'No configuration file found. Create test/e2e/config.local.json from test/e2e/config.template.json' ); } const nodeVersion = process.version; const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10); if (Number.isNaN(majorVersion) || majorVersion < 18) { warnings.push( `Node.js version ${nodeVersion} detected. Recommended: Node.js 18+` ); } else { info.push(`Node.js version: ${nodeVersion}`); } return { envStatus, configFile, info, warnings, issues }; } function reportEnvironmentStatus(details) { if (details.info.length > 0) { logger.success('Environment status:'); details.info.forEach((entry) => logger.info(` • ${entry}`)); } if (details.warnings.length > 0) { logger.warn('Warnings:'); details.warnings.forEach((warning) => logger.warn(` • ${warning}`)); } if (details.issues.length > 0) { logger.error('Issues detected:'); details.issues.forEach((issue) => logger.error(` • ${issue}`)); } } function printUsageHelp() { logger.info('E2E Test Runner Usage:'); logger.log(' npm run test:e2e - Run all E2E tests'); logger.log(' npm run test:e2e -- --help - Show this help'); logger.log(' npm run test:e2e -- --check - Environment check only'); logger.log( ' npm run test:e2e -- --limited - Run limited tests (no API calls)' ); logger.log( ' npm run test:e2e -- --pattern <name> - Run specific test pattern' ); logger.log(''); logger.log('Available patterns:'); listAvailablePatterns().forEach((pattern) => { logger.log(` - ${pattern}`); }); logger.log(''); logger.log('Environment setup:'); logger.log( ' 1. Copy test/e2e/config.template.json to test/e2e/config.local.json' ); logger.log(' 2. Set ATTIO_API_KEY environment variable'); logger.log(' 3. Optionally set additional E2E_* environment variables'); } function printSolutionGuidance(details) { if (details.issues.length === 0) { return; } const guidance = []; if (details.issues.some((issue) => issue.includes('ATTIO_API_KEY'))) { guidance.push( '1. Acquire an Attio API key and set ATTIO_API_KEY in your environment or .env file.' ); } if (details.issues.some((issue) => issue.includes('configuration file'))) { guidance.push( '2. Copy test/e2e/config.template.json to test/e2e/config.local.json and update it with your workspace settings.' ); } if (guidance.length > 0) { logger.info('How to resolve the issues:'); guidance.forEach((item) => logger.info(` • ${item}`)); } } function buildVitestArguments(patternKey, options) { const resolvedPattern = resolveTestPattern(patternKey); const args = ['run', '--config', 'vitest.config.e2e.ts']; if (patternKey && patternKey !== 'all') { args.push(resolvedPattern); } if (options.reporter) { args.push('--reporter', options.reporter); } if (options.verbose) { args.push('--reporter', 'verbose'); } return { args, resolvedPattern }; } function executeVitestProcess(args) { return new Promise((resolve, reject) => { const vitest = spawn('npx', ['vitest', ...args], { stdio: 'inherit', env: { ...process.env }, }); vitest.on('close', (code) => { resolve(code ?? 1); }); vitest.on('error', (error) => { reject(error); }); }); } async function runVitest(patternKey, options) { const { args, resolvedPattern } = buildVitestArguments(patternKey, options); if (options.limited) { process.env.SKIP_E2E_TESTS = 'true'; logger.warn('Running in limited mode - API tests will be skipped'); } logger.info(`Running E2E tests: ${resolvedPattern}`); logger.info(`Command: npx vitest ${args.join(' ')}`); if (process.env.ATTIO_API_KEY) { logSecretPresence({ key: 'ATTIO_API_KEY', logger }); } else { logger.warn('ATTIO_API_KEY not found in environment'); } const exitCode = await executeVitestProcess(args); if (exitCode === 0) { logger.success('E2E tests completed successfully'); } else { logger.warn(`E2E tests finished with exit code ${exitCode}`); } return exitCode; } async function main() { ensureEnvironmentFilesLoaded(); const options = parseCommandLineArgs(process.argv.slice(2)); logger.info('Attio MCP Server - E2E Test Runner'); if (options.help) { printUsageHelp(); return process.exit(0); } const details = gatherEnvironmentDetails(); reportEnvironmentStatus(details); if (options.check) { const exitCode = details.issues.length > 0 ? 1 : 0; if (exitCode === 0) { logger.success('Environment check completed successfully'); } else { logger.warn('Environment check detected issues'); } return process.exit(exitCode); } if (!details.configFile) { logger.error('Cannot run tests without configuration file'); printSolutionGuidance(details); return process.exit(1); } const missingApiKey = details.envStatus.missingRequired.includes('ATTIO_API_KEY'); if (missingApiKey && !options.limited) { logger.warn('No API key detected. Use --limited to run without API calls.'); printSolutionGuidance(details); return process.exit(1); } if (missingApiKey && options.limited) { logger.warn('Running without API key - limited tests only'); } try { const exitCode = await runVitest(options.pattern, options); if (exitCode === 0) { logger.success('All E2E tests passed!'); } else { logger.warn('Some E2E tests failed. Check the output above for details.'); if (missingApiKey) { logger.warn('Failures may be due to running without ATTIO_API_KEY.'); } } process.exit(exitCode); } catch (error) { logger.error( `E2E test execution failed: ${(error && error.message) || error}` ); process.exit(1); } } process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled Rejection detected'); logger.error(String(reason), promise); process.exit(1); }); process.on('uncaughtException', (error) => { logger.error(`Uncaught Exception: ${(error && error.message) || error}`); process.exit(1); }); main();

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/kesslerio/attio-mcp-server'

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