UNPKG

testbeats

Version:

Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB

440 lines (395 loc) 14.4 kB
const prompts = require('prompts'); const fs = require('fs/promises'); const logger = require('../utils/logger'); const pkg = require('../../package.json'); class GenerateConfigCommand { /** * TODO: [BETA / Experimental Mode] * Generates initial TestBests configuration file */ constructor(opts) { this.opts = opts; this.configPath = '.testbeats.json'; this.config = {}; } async execute() { logger.setLevel(this.opts.logLevel); this.#printBanner(); logger.info(`🚧 Config generation is still in BETA mode, please report any issues at ${pkg.bugs.url}\n`); await this.#buildConfigFilePath(); await this.#buildTestResultsConfig(); await this.#buildTargetsConfig(); await this.#buildGobalExtensionConfig(); await this.#buildTestBeatsPortalConfig(); await this.#saveConfigFile(); } #printBanner() { const banner = ` _____ _ ___ _ (_ _) ( )_ ( _'\\ ( )_ | | __ ___ | ,_)| (_) ) __ _ _ | ,_) ___ | | /'__'\\/',__)| | | _ <' /'__'\\ /'_' )| | /',__) | |( ___/\\__, \\| |_ | (_) )( ___/( (_| || |_ \\__, \\ (_)'\\____)(____/'\\__)(____/''\\____)'\\__,_)'\\__)(____/ v${pkg.version} Config Generation [BETA] `; console.log(banner); } async #buildConfigFilePath() { const { configPath } = await prompts({ type: 'text', name: 'configPath', message: 'Enter path for configuration file :', initial: '.testbeats.json' }); this.configPath = configPath; } async #buildTestResultsConfig() { const runnerChoices = [ { title: 'Mocha', value: 'mocha', selected: true }, { title: 'JUnit', value: 'junit' }, { title: 'TestNG', value: 'testng' }, { title: 'Cucumber', value: 'cucumber' }, { title: 'NUnit', value: 'nunit' }, { title: 'xUnit', value: 'xunit' }, { title: 'MSTest', value: 'mstest' } ] // Get test results details const { testResults } = await prompts([{ type: 'toggle', name: 'includeResults', message: 'Do you want to configure test results?', initial: true, active: 'Yes', inactive: 'No' }, { type: (prev) => (prev ? "multiselect" : null), name: 'testResults', message: 'Select test result types to include:', choices: runnerChoices, min: 1 }]); if (!testResults) { return }; // Handle result paths this.config.results = [] for (const resultType of testResults) { const { path } = await prompts({ type: 'text', name: 'path', message: `Enter file path for ${resultType} results (.json, .xml etc):`, initial: "" }); this.config.results.push({ files: path, type: resultType }); } } async #buildTargetsConfig() { const targetChoices = [ { title: 'Slack', value: 'slack' }, { title: 'Microsoft Teams', value: 'teams' }, { title: 'Google Chat', value: 'chat' }, ]; const { titleInput, targets } = await prompts([{ type: 'toggle', name: 'includeTargets', message: 'Do you want to configure notification targets (slack, teams, chat etc)?', initial: true, active: 'Yes', inactive: 'No' }, { type: (prev) => (prev ? "text" : null), name: 'titleInput', message: 'Enter notification title (optional):', initial: 'TestBeats Report' }, { type: (prev, values) => (values.includeTargets ? "multiselect" : null), name: 'targets', message: 'Select notification targets:', choices: targetChoices, min: 1 }]); if (!targets) { return } this.config.targets = [] // For each target, ask about target-specific extensions for (const target of targets) { const { webhookEnvVar, selectedExtensions } = await prompts([{ type: 'text', name: 'webhookEnvVar', message: `Enter environment variable name for ${target} webhook URL:`, initial: `${target.toUpperCase()}_WEBHOOK_URL` }, { type: 'toggle', name: 'useExtensions', message: `Do you want to configure extensions for ${target}?`, initial: true, active: 'Yes', inactive: 'No' }, { type: (prev, values) => (values.useExtensions ? 'multiselect' : null), name: 'selectedExtensions', message: `Select extensions for ${target}:`, choices: this.#getExtensionsList(), min: 1 }]); const targetConfig = { name: target, inputs: { title: titleInput, url: `{${webhookEnvVar}}`, publish: 'test-summary' } }; if (selectedExtensions) { targetConfig.extensions = []; // Configure extension-specific inputs for (const ext of selectedExtensions) { const extConfig = await this.#buildExtensionConfig(ext, target); targetConfig.extensions.push(extConfig); } } this.config.targets.push(targetConfig); } } async #buildGobalExtensionConfig() { const { globalExtensionsSelected } = await prompts([{ type: 'toggle', name: 'includeGlobalExtensions', message: 'Do you want to configure global extensions?', initial: false, active: 'Yes', inactive: 'No' }, { type: (prev) => (prev ? 'multiselect' : null), name: 'globalExtensionsSelected', message: 'Select global extensions to enable:', choices: this.#getExtensionsList() }]); if (!globalExtensionsSelected) { return }; this.config.extensions = []; // Configure extension-specific inputs for (const ext of globalExtensionsSelected) { const extDetails = await this.#buildExtensionConfig(ext, null); this.config.extensions.push(extDetails); } } async #buildExtHyperlinks() { const links = []; const { addLink } = await prompts({ type: 'toggle', name: 'addLink', message: 'Do you want to add a hyperlink?', initial: true, active: 'Yes', inactive: 'No' }); while (addLink) { const { text, url } = await prompts([ { type: 'text', name: 'text', message: 'Enter link text:' }, { type: 'text', name: 'url', message: 'Enter link URL:' } ]); links.push({ text, url }); const { addAnother } = await prompts({ type: 'toggle', name: 'addAnother', message: 'Add another link?', initial: false, active: 'Yes', inactive: 'No' }); if (!addAnother) break; } return { links }; } async #buildExtMentions(targetName) { const users = []; const { addUser } = await prompts({ type: 'toggle', name: 'addUser', message: 'Do you want to add user mentions?', initial: true, active: 'Yes', inactive: 'No' }); while (addUser) { const user = {}; const { name } = await prompts({ type: 'text', name: 'name', message: 'Enter user name:' }); user.name = name; if (targetName === 'teams') { const { teams_upn } = await prompts({ type: 'text', name: 'teams_upn', message: 'Enter Teams UPN (user principal name):' }); user.teams_upn = teams_upn; } else if (targetName === 'slack') { const { slack_uid } = await prompts({ type: 'text', name: 'slack_uid', message: 'Enter Slack user ID:' }); user.slack_uid = slack_uid; } else if (targetName === 'chat') { const { chat_uid } = await prompts({ type: 'text', name: 'chat_uid', message: 'Enter Google Chat user ID:' }); user.chat_uid = chat_uid; } users.push(user); const { addAnother } = await prompts({ type: 'toggle', name: 'addAnother', message: 'Add another user?', initial: false, active: 'Yes', inactive: 'No' }); if (!addAnother) break; } return { users }; } async #buildExtMetadata() { const data = []; const { addMetadata } = await prompts({ type: 'toggle', name: 'addMetadata', message: 'Do you want to add metadata?', initial: true, active: 'Yes', inactive: 'No' }); while (addMetadata) { const { key, value } = await prompts([ { type: 'text', name: 'key', message: 'Enter metadata key:' }, { type: 'text', name: 'value', message: 'Enter metadata value:' } ]); data.push({ key, value }); const { addAnother } = await prompts({ type: 'toggle', name: 'addAnother', message: 'Add another metadata item?', initial: false, active: 'Yes', inactive: 'No' }); if (!addAnother) break; } return { data }; } async #buildExtensionConfig(extension, target) { const extConfig = { name: extension }; switch (extension) { case 'hyperlinks': extConfig.inputs = await this.#buildExtHyperlinks() break; case 'mentions': extConfig.inputs = await this.#buildExtMentions(target); break; case 'metadata': extConfig.inputs = await this.#buildExtMetadata(); break; default: // Add default configuration for other extensions extConfig.inputs = { title: '', separator: target === 'slack' ? false : true }; } return extConfig; } async #buildTestBeatsPortalConfig() { // TestBeats configuration const { apiKey, project } = await prompts([{ type: 'toggle', name: 'includeTestBeats', message: 'Do you want to configure TestBeats API key (optional)?', initial: false, active: 'Yes', inactive: 'No' }, { type: (prev) => (prev ? 'text' : null), name: 'apiKey', message: 'Enter environment variable name for API key (optional):', initial: '{TEST_RESULTS_API_KEY}' }, { type: (prev, values) => (values.includeTestBeats ? 'text' : null), name: 'project', message: 'Enter project name (optional):' }]); if (apiKey) { this.config.api_key = apiKey; // Add optional fields only if they have values if (project?.trim()) { this.config.project = project.trim(); } } } #sortConfig() { // Sort keys alphabetically this.config = Object.keys(this.config).sort() .reduce((sortedConfig, key) => { sortedConfig[key] = this.config[key]; return sortedConfig; }, {}); } async #saveConfigFile() { // Write config to file try { await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2)); logger.info(`✅ Configuration file successfully generated: ${this.configPath}`); } catch (error) { throw new Error(`Error: ${error.message}`) } } #getExtensionsList() { // List of Extensions supported return [ { title: 'Quick Chart Test Summary', value: 'quick-chart-test-summary' }, { title: 'CI Information', value: 'ci-info' }, { title: 'Hyperlinks', value: 'hyperlinks' }, { title: 'Mentions', value: 'mentions' }, { title: 'Report Portal Analysis', value: 'report-portal-analysis' }, { title: 'Report Portal History', value: 'report-portal-history' }, { title: 'Percy Analysis', value: 'percy-analysis' }, { title: 'Metadata', value: 'metadata' }, { title: 'AI Failure Summary', value: 'ai-failure-summary' }, { title: 'Smart Analysis', value: 'smart-analysis' }, { title: 'Error Clusters', value: 'error-clusters' } ]; } } module.exports = { GenerateConfigCommand };