Overview
ConductorQA provides extensive integration capabilities to connect with your existing development workflow, CI/CD pipelines, and third-party tools. This guide covers all available integrations and how to implement them effectively.
API Integration
REST API Overview
API Architecture
ConductorQA API Structure:
├── Authentication: Secure API key-based access
├── Projects: Project management and configuration
├── Test Suites: Test organization and management
├── Test Cases: Individual test management
├── Test Runs: Execution tracking and results
├── Results: Test result reporting and retrieval
└── Analytics: Metrics and reporting data
Base API Configuration
// API Configuration
const CONDUCTORQA_CONFIG = {
baseURL: 'https://api.conductorqa.com/v1',
headers: {
'Authorization': `Bearer ${process.env.CONDUCTORQA_API_KEY}`,
'Content-Type': 'application/json',
'User-Agent': 'MyApp/1.0'
},
timeout: 30000,
retries: 3
};
Authentication and Security
API Key Management
// Secure API Key Usage
class ConductorQAClient {
constructor(apiKey, options = {}) {
this.apiKey = apiKey;
this.baseURL = options.baseURL || 'https://api.conductorqa.com/v1';
this.timeout = options.timeout || 30000;
}
async authenticate() {
try {
const response = await this.request('GET', '/auth/verify');
return response.data;
} catch (error) {
throw new Error(`Authentication failed: ${error.message}`);
}
}
async request(method, endpoint, data = null) {
const config = {
method,
url: `${this.baseURL}${endpoint}`,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
timeout: this.timeout
};
if (data) {
config.data = data;
}
try {
const response = await axios(config);
return response.data;
} catch (error) {
this.handleApiError(error);
}
}
}
Security Best Practices
- Environment Variables: Store API keys securely
- Key Rotation: Regular API key updates
- Scope Limitation: Restrict API key permissions
- Request Validation: Validate all API requests and responses
- Error Handling: Secure error message handling
Core API Operations
Project Management
// Project Operations
class ProjectAPI {
constructor(client) {
this.client = client;
}
async listProjects() {
return await this.client.request('GET', '/projects');
}
async getProject(projectId) {
return await this.client.request('GET', `/projects/${projectId}`);
}
async createProject(projectData) {
return await this.client.request('POST', '/projects', projectData);
}
async updateProject(projectId, updates) {
return await this.client.request('PUT', `/projects/${projectId}`, updates);
}
}
Test Execution Integration
// Test Run Management
class TestExecutionAPI {
constructor(client) {
this.client = client;
}
async createTestRun(projectId, testRunData) {
return await this.client.request(
'POST',
`/projects/${projectId}/test-runs`,
testRunData
);
}
async reportResults(testRunId, results) {
return await this.client.request(
'POST',
`/test-runs/${testRunId}/results`,
{ results }
);
}
async bulkReportResults(projectId, bulkData) {
return await this.client.request(
'POST',
`/projects/${projectId}/bulk-results`,
bulkData
);
}
}
CI/CD Pipeline Integration
GitHub Actions Integration
Workflow Configuration
# .github/workflows/test-integration.yml
name: Test Execution with ConductorQA
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test-and-report:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Run Tests
run: npm run test:ci
env:
NODE_ENV: test
- name: Report Results to ConductorQA
uses: ./.github/actions/conductorqa-report
with:
api-key: ${{ secrets.CONDUCTORQA_API_KEY }}
project-id: ${{ secrets.CONDUCTORQA_PROJECT_ID }}
test-results-path: './test-results.json'
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
build-number: ${{ github.run_number }}
if: always()
- name: Create Test Summary
run: |
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
echo "Build: ${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
echo "Environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}" >> $GITHUB_STEP_SUMMARY
node scripts/create-test-summary.js >> $GITHUB_STEP_SUMMARY
if: always()
Custom Action for ConductorQA
// .github/actions/conductorqa-report/index.js
const core = require('@actions/core');
const axios = require('axios');
const fs = require('fs');
async function reportToConductorQA() {
try {
const apiKey = core.getInput('api-key', { required: true });
const projectId = core.getInput('project-id', { required: true });
const testResultsPath = core.getInput('test-results-path', { required: true });
const environment = core.getInput('environment') || 'ci';
const buildNumber = core.getInput('build-number');
// Read test results
const testResults = JSON.parse(fs.readFileSync(testResultsPath, 'utf8'));
// Prepare payload
const payload = {
name: `CI Build #${buildNumber} - ${environment}`,
environment,
build_number: buildNumber,
executed_by: 'github-actions',
started_at: new Date().toISOString(),
results: testResults.tests.map(test => ({
test_case_name: test.fullName,
status: test.status === 'passed' ? 'passed' : 'failed',
duration: test.duration / 1000,
error_message: test.failureMessages?.join('\n'),
file_path: test.ancestorTitles.join(' > ')
}))
};
// Send to ConductorQA
const response = await axios.post(
`https://api.conductorqa.com/v1/projects/${projectId}/test-runs`,
payload,
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
}
);
core.setOutput('test-run-id', response.data.id);
core.setOutput('results-url', response.data.url);
console.log(`✅ Test results reported to ConductorQA: ${response.data.url}`);
} catch (error) {
core.setFailed(`Failed to report to ConductorQA: ${error.message}`);
}
}
reportToConductorQA();
GitLab CI Integration
Pipeline Configuration
# .gitlab-ci.yml
stages:
- test
- report
variables:
CONDUCTORQA_BASE_URL: "https://api.conductorqa.com/v1"
test:
stage: test
image: node:20
script:
- npm ci
- npm run test:ci
artifacts:
reports:
junit: junit.xml
paths:
- test-results.json
- coverage/
expire_in: 1 week
when: always
report-to-conductorqa:
stage: report
image: node:20
dependencies:
- test
script:
- node scripts/report-to-conductorqa.js
variables:
ENVIRONMENT: $CI_COMMIT_REF_NAME
BUILD_NUMBER: $CI_PIPELINE_ID
COMMIT_SHA: $CI_COMMIT_SHA
only:
- main
- develop
when: always
GitLab Reporting Script
// scripts/report-to-conductorqa.js
const axios = require('axios');
const fs = require('fs');
async function reportResults() {
const testResults = JSON.parse(fs.readFileSync('test-results.json', 'utf8'));
const payload = {
name: `GitLab Pipeline #${process.env.BUILD_NUMBER} - ${process.env.ENVIRONMENT}`,
environment: process.env.ENVIRONMENT,
build_number: process.env.BUILD_NUMBER,
commit_sha: process.env.COMMIT_SHA,
executed_by: 'gitlab-ci',
results: testResults.tests.map(test => ({
test_case_name: test.title,
status: test.status,
duration: test.duration,
error_message: test.error?.message,
suite: test.suite
}))
};
try {
const response = await axios.post(
`${process.env.CONDUCTORQA_BASE_URL}/projects/${process.env.CONDUCTORQA_PROJECT_ID}/test-runs`,
payload,
{
headers: {
'Authorization': `Bearer ${process.env.CONDUCTORQA_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
console.log('✅ Results reported to ConductorQA:', response.data.url);
} catch (error) {
console.error('❌ Failed to report to ConductorQA:', error.response?.data || error.message);
process.exit(1);
}
}
reportResults();
Jenkins Integration
Pipeline Script
pipeline {
agent any
environment {
CONDUCTORQA_API_KEY = credentials('conductorqa-api-key')
CONDUCTORQA_PROJECT_ID = credentials('conductorqa-project-id')
CONDUCTORQA_BASE_URL = 'https://api.conductorqa.com/v1'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install Dependencies') {
steps {
sh 'npm ci'
}
}
stage('Run Tests') {
steps {
sh 'npm run test:ci'
}
post {
always {
publishTestResults testResultsPattern: 'junit.xml'
archiveArtifacts artifacts: 'test-results.json', fingerprint: true
}
}
}
stage('Report to ConductorQA') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
script {
def testResults = readJSON file: 'test-results.json'
def payload = [
name: "Jenkins Build #${BUILD_NUMBER} - ${BRANCH_NAME}",
environment: BRANCH_NAME == 'main' ? 'production' : 'staging',
build_number: BUILD_NUMBER,
executed_by: 'jenkins',
results: testResults.tests.collect { test ->
[
test_case_name: test.fullName,
status: test.status,
duration: test.duration / 1000,
error_message: test.failureMessages?.join('\n')
]
}
]
def response = httpRequest(
httpMode: 'POST',
url: "${CONDUCTORQA_BASE_URL}/projects/${CONDUCTORQA_PROJECT_ID}/test-runs",
requestBody: groovy.json.JsonOutput.toJson(payload),
customHeaders: [
[name: 'Authorization', value: "Bearer ${CONDUCTORQA_API_KEY}"],
[name: 'Content-Type', value: 'application/json']
]
)
def responseData = readJSON text: response.content
echo "✅ Test results reported to ConductorQA: ${responseData.url}"
}
}
}
}
}
Test Framework Integration
Jest Integration
Jest Reporter
// jest-conductorqa-reporter.js
class ConductorQAReporter {
constructor(globalConfig, options) {
this.globalConfig = globalConfig;
this.options = options;
this.results = [];
}
onTestResult(test, testResult, aggregatedResult) {
testResult.testResults.forEach(result => {
this.results.push({
test_case_name: result.fullName,
status: result.status === 'passed' ? 'passed' : 'failed',
duration: result.duration / 1000,
error_message: result.failureMessages.join('\n'),
file_path: test.path,
suite: result.ancestorTitles.join(' > ')
});
});
}
async onRunComplete(contexts, aggregatedResult) {
if (process.env.CONDUCTORQA_API_KEY) {
await this.reportToConductorQA(this.results);
}
}
async reportToConductorQA(results) {
const axios = require('axios');
const payload = {
name: `Jest Test Run - ${new Date().toISOString()}`,
environment: process.env.NODE_ENV || 'test',
executed_by: 'jest',
results: results
};
try {
const response = await axios.post(
`${process.env.CONDUCTORQA_BASE_URL}/projects/${process.env.CONDUCTORQA_PROJECT_ID}/test-runs`,
payload,
{
headers: {
'Authorization': `Bearer ${process.env.CONDUCTORQA_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
console.log(`✅ Results reported to ConductorQA: ${response.data.url}`);
} catch (error) {
console.error('❌ Failed to report to ConductorQA:', error.message);
}
}
}
module.exports = ConductorQAReporter;
Jest Configuration
// jest.config.js
module.exports = {
reporters: [
'default',
['./jest-conductorqa-reporter.js', {
projectId: process.env.CONDUCTORQA_PROJECT_ID,
environment: process.env.NODE_ENV || 'test'
}]
],
testResultsProcessor: './scripts/process-test-results.js'
};
Cypress Integration
Cypress Plugin
// cypress/plugins/conductorqa.js
const axios = require('axios');
function reportToConductorQA(results) {
return new Promise(async (resolve, reject) => {
try {
const payload = {
name: `Cypress E2E Tests - ${new Date().toISOString()}`,
environment: Cypress.env('ENVIRONMENT') || 'test',
executed_by: 'cypress',
results: results.runs[0].tests.map(test => ({
test_case_name: test.title.join(' > '),
status: test.state === 'passed' ? 'passed' : 'failed',
duration: test.duration / 1000,
error_message: test.err?.message,
suite: test.title.slice(0, -1).join(' > ')
}))
};
const response = await axios.post(
`${Cypress.env('CONDUCTORQA_BASE_URL')}/projects/${Cypress.env('CONDUCTORQA_PROJECT_ID')}/test-runs`,
payload,
{
headers: {
'Authorization': `Bearer ${Cypress.env('CONDUCTORQA_API_KEY')}`,
'Content-Type': 'application/json'
}
}
);
console.log('✅ Results reported to ConductorQA:', response.data.url);
resolve(response.data);
} catch (error) {
console.error('❌ Failed to report to ConductorQA:', error.message);
reject(error);
}
});
}
module.exports = { reportToConductorQA };
Cypress Configuration
// cypress.config.js
const { defineConfig } = require('cypress');
const { reportToConductorQA } = require('./cypress/plugins/conductorqa');
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('after:run', async (results) => {
if (config.env.CONDUCTORQA_API_KEY) {
await reportToConductorQA(results);
}
});
}
}
});
Third-Party Tool Integration
Slack Integration
Notification Setup
// slack-integration.js
class SlackNotifier {
constructor(webhookUrl) {
this.webhookUrl = webhookUrl;
}
async sendTestResults(testRunData) {
const { name, results, pass_rate, environment } = testRunData;
const passed = results.filter(r => r.status === 'passed').length;
const failed = results.filter(r => r.status === 'failed').length;
const color = pass_rate >= 95 ? 'good' : pass_rate >= 80 ? 'warning' : 'danger';
const emoji = pass_rate >= 95 ? '✅' : pass_rate >= 80 ? '⚠️' : '❌';
const payload = {
username: 'ConductorQA',
icon_emoji: ':test_tube:',
attachments: [{
color: color,
title: `${emoji} Test Run Completed: ${name}`,
fields: [
{ title: 'Environment', value: environment, short: true },
{ title: 'Pass Rate', value: `${pass_rate.toFixed(1)}%`, short: true },
{ title: 'Passed', value: passed.toString(), short: true },
{ title: 'Failed', value: failed.toString(), short: true }
],
footer: 'ConductorQA',
ts: Math.floor(Date.now() / 1000)
}]
};
await axios.post(this.webhookUrl, payload);
}
}
Jira Integration
Issue Creation
// jira-integration.js
class JiraIntegration {
constructor(config) {
this.baseURL = config.baseURL;
this.username = config.username;
this.apiToken = config.apiToken;
this.projectKey = config.projectKey;
}
async createBugFromFailure(testResult) {
const payload = {
fields: {
project: { key: this.projectKey },
summary: `Test Failure: ${testResult.test_case_name}`,
description: this.formatDescription(testResult),
issuetype: { name: 'Bug' },
priority: this.mapPriority(testResult.priority),
labels: ['automated-test', 'qa-found'],
customfield_10001: testResult.environment // Environment field
}
};
const response = await axios.post(
`${this.baseURL}/rest/api/3/issue`,
payload,
{
auth: {
username: this.username,
password: this.apiToken
},
headers: {
'Content-Type': 'application/json'
}
}
);
return response.data;
}
formatDescription(testResult) {
return `
h3. Test Case
${testResult.test_case_name}
h3. Environment
${testResult.environment}
h3. Error Message
{code}
${testResult.error_message}
{code}
h3. Test Run Details
* Duration: ${testResult.duration}s
* Executed By: ${testResult.executed_by}
* Build: ${testResult.build_number}
h3. ConductorQA Link
[View Test Result|${testResult.conductorqa_url}]
`;
}
}
Webhook Integration
Webhook Configuration
Setting Up Webhooks
// webhook-handler.js
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Webhook verification middleware
function verifyWebhookSignature(req, res, next) {
const signature = req.headers['x-conductorqa-signature'];
const payload = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET);
const expectedSignature = 'sha256=' + hmac.update(payload).digest('hex');
if (signature === expectedSignature) {
next();
} else {
res.status(401).send('Unauthorized');
}
}
// Test run completion webhook
app.post('/webhooks/test-run-completed', verifyWebhookSignature, async (req, res) => {
const { event, data } = req.body;
if (event === 'test_run_completed') {
await handleTestRunCompletion(data);
}
res.status(200).send('OK');
});
async function handleTestRunCompletion(testRunData) {
// Send Slack notification
if (process.env.SLACK_WEBHOOK_URL) {
await sendSlackNotification(testRunData);
}
// Create Jira issues for failures
if (testRunData.failed_tests.length > 0) {
await createJiraIssues(testRunData.failed_tests);
}
// Update external dashboard
await updateExternalDashboard(testRunData);
}
Webhook Event Types
Supported Webhook Events:
{
"test_run_started": {
"description": "Test run execution begins",
"payload": "test_run_id, project_id, name, started_at"
},
"test_run_completed": {
"description": "Test run execution completes",
"payload": "test_run_id, results, metrics, duration"
},
"test_case_failed": {
"description": "Individual test case fails",
"payload": "test_case_id, failure_reason, environment"
},
"flaky_test_detected": {
"description": "Flaky test pattern identified",
"payload": "test_case_id, failure_rate, recommendations"
}
}
Error Handling and Monitoring
Integration Health Monitoring
Health Check Implementation
// integration-health.js
class IntegrationHealthChecker {
constructor(integrations) {
this.integrations = integrations;
}
async checkAllIntegrations() {
const results = {};
for (const [name, integration] of Object.entries(this.integrations)) {
try {
await integration.healthCheck();
results[name] = { status: 'healthy', timestamp: new Date() };
} catch (error) {
results[name] = {
status: 'unhealthy',
error: error.message,
timestamp: new Date()
};
}
}
return results;
}
async validateApiConnectivity() {
try {
const response = await axios.get(
`${process.env.CONDUCTORQA_BASE_URL}/health`,
{
headers: {
'Authorization': `Bearer ${process.env.CONDUCTORQA_API_KEY}`
},
timeout: 10000
}
);
return response.status === 200;
} catch (error) {
console.error('API connectivity check failed:', error.message);
return false;
}
}
}
Error Recovery Strategies
Retry Logic Implementation
// retry-handler.js
class RetryHandler {
constructor(maxRetries = 3, baseDelay = 1000) {
this.maxRetries = maxRetries;
this.baseDelay = baseDelay;
}
async executeWithRetry(operation, context = {}) {
let lastError;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (attempt === this.maxRetries) {
break;
}
if (!this.isRetryableError(error)) {
break;
}
const delay = this.calculateDelay(attempt);
console.warn(`Operation failed (attempt ${attempt + 1}), retrying in ${delay}ms:`, error.message);
await this.sleep(delay);
}
}
throw new Error(`Operation failed after ${this.maxRetries + 1} attempts: ${lastError.message}`);
}
isRetryableError(error) {
// Retry on network errors and 5xx status codes
return error.code === 'ECONNRESET' ||
error.code === 'ETIMEDOUT' ||
(error.response && error.response.status >= 500);
}
calculateDelay(attempt) {
// Exponential backoff with jitter
const delay = this.baseDelay * Math.pow(2, attempt);
const jitter = delay * 0.1 * Math.random();
return delay + jitter;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Integration Best Practices
Security Best Practices
Secure Configuration
// secure-config.js
class SecureIntegrationConfig {
constructor() {
this.validateRequiredEnvVars();
this.setupRateLimiting();
}
validateRequiredEnvVars() {
const required = [
'CONDUCTORQA_API_KEY',
'CONDUCTORQA_PROJECT_ID',
'CONDUCTORQA_BASE_URL'
];
const missing = required.filter(env => !process.env[env]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}
setupRateLimiting() {
// Implement rate limiting to avoid API throttling
this.rateLimiter = new Map();
this.maxRequestsPerMinute = 100;
}
async enforceRateLimit(key = 'default') {
const now = Date.now();
const minute = Math.floor(now / 60000);
const limiterKey = `${key}-${minute}`;
const count = this.rateLimiter.get(limiterKey) || 0;
if (count >= this.maxRequestsPerMinute) {
throw new Error('Rate limit exceeded. Please wait before making more requests.');
}
this.rateLimiter.set(limiterKey, count + 1);
}
}
Performance Optimization
Batch Processing
// batch-processor.js
class BatchProcessor {
constructor(batchSize = 100, delayBetweenBatches = 1000) {
this.batchSize = batchSize;
this.delayBetweenBatches = delayBetweenBatches;
}
async processBatches(items, processor) {
const batches = this.createBatches(items);
const results = [];
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
console.log(`Processing batch ${i + 1} of ${batches.length} (${batch.length} items)`);
try {
const batchResults = await processor(batch);
results.push(...batchResults);
} catch (error) {
console.error(`Batch ${i + 1} failed:`, error.message);
// Continue with next batch or implement retry logic
}
// Delay between batches to avoid overwhelming the API
if (i < batches.length - 1) {
await this.sleep(this.delayBetweenBatches);
}
}
return results;
}
createBatches(items) {
const batches = [];
for (let i = 0; i < items.length; i += this.batchSize) {
batches.push(items.slice(i, i + this.batchSize));
}
return batches;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Next Steps
To maximize your integration capabilities:
- Set Up API Authentication - Configure secure API access
- Configure CI/CD Integration - Automate test result reporting
- Implement Webhook Handlers - Set up real-time notifications
- Monitor Integration Health - Track integration performance and reliability
Ready to integrate ConductorQA with your workflow? Start with a simple CI/CD integration, then gradually add more advanced features like webhooks and third-party tool connections based on your team’s needs.