@zebrunner/javascript-agent-webdriverio
Version:
Zebrunner Agent for webdriver.io
297 lines (228 loc) • 10.9 kB
text/typescript
import log from 'loglevel';
import { ArtifactReference, Label } from './types';
import { isArray, isNotBlankString, isString } from './type-utils';
const logger = log.getLogger('zebrunner');
const logsPushDelay = 5000;
const screenshotBeforeCommands: string[] = [];
const screenshotAfterCommands: string[] = ['click', 'doubleClick', 'navigateTo', 'elementClick', 'scroll', 'scrollIntoView'];
interface ServerConfig {
readonly hostname: string;
readonly accessToken: string;
}
interface LaunchConfig {
readonly context: string;
readonly displayName: string;
readonly build: string;
readonly environment: string;
readonly locale: string;
readonly treatSkipsAsFailures: boolean;
readonly labels: Label[];
readonly artifactReferences: ArtifactReference[];
}
interface MilestoneConfig {
readonly id: number;
readonly name: string;
}
export interface LogsConfig {
readonly pushDelayMillis: number;
readonly includeLoggerName: boolean;
readonly excludeLoggers: Set<string>;
}
export interface ScreenshotConfig {
readonly afterError: boolean;
readonly beforeCommands: Set<string>;
readonly afterCommands: Set<string>;
}
interface NotificationsConfig {
readonly notifyOnEachFailure: boolean;
readonly slackChannels: string;
readonly teamsChannels: string;
readonly emails: string;
}
interface Tcm {
readonly testCaseStatus: TestCaseStatus;
readonly zebrunner: ZebrunnerTcm;
readonly testRail: TestRailTcm;
readonly xray: XrayTcm;
readonly zephyr: ZephyrTcm;
}
interface TestCaseStatus {
readonly onPass: string;
readonly onFail: string;
}
interface ZebrunnerTcm {
readonly pushResults: boolean;
readonly pushInRealTime: boolean;
readonly testRunId: number;
}
interface TestRailTcm {
readonly pushResults: boolean;
readonly pushInRealTime: boolean;
readonly suiteId: number;
readonly runId: number;
readonly includeAllTestCasesInNewRun: boolean;
readonly runName: string;
readonly milestoneName: string;
readonly assignee: string;
}
interface XrayTcm {
readonly pushResults: boolean;
readonly pushInRealTime: boolean;
readonly executionKey: string;
}
interface ZephyrTcm {
readonly pushResults: boolean;
readonly pushInRealTime: boolean;
readonly jiraProjectKey: string;
readonly testCycleKey: string;
}
function getString(envVar: string, configValue: any, defaultValue: string = null): string {
const value = process.env[envVar] as string;
return isNotBlankString(value)
? value
: isNotBlankString(configValue)
? configValue
: defaultValue;
}
function getBoolean(envVar: string, configValue: any, defaultValue = false): boolean {
return process.env[envVar]?.toLowerCase?.() === 'true'
|| configValue === true
|| configValue?.toLowerCase?.() === 'true'
|| defaultValue;
}
function getNumber(envVar: string, configValue: any, defaultValue: number = null): number {
return parseInt(process.env[envVar], 10)
|| parseInt(configValue, 10)
|| defaultValue;
}
function tokenizeString(value: string, defaultTokens: string[] = []): string[] {
return isNotBlankString(value)
? value.split(',')
.map((val) => val.trim())
: defaultTokens;
}
function getSetOfStrings(envVar: string, configValue: any, defaultValue: string[] = []): Set<string> {
const envVarValue = process.env[envVar];
const values: string[] = isNotBlankString(envVarValue)
? tokenizeString(envVarValue)
: isArray(configValue)
? configValue.flatMap((value) => tokenizeString(value))
: isString(configValue)
? tokenizeString(configValue)
: defaultValue;
return new Set<string>(values);
}
function parseLabels(config: any): Label[] {
const labels = config?.launch?.labels || {};
return Object.keys(labels)
.filter((key) => {
const isNotBlank = isNotBlankString(labels[key]);
if (!isNotBlank) {
logger.warn(`Label value must be a not blank string. Configuration contains label with key '${key}' which violates this`);
}
return isNotBlank;
})
.map((key) => ({
key,
value: labels[key]
}));
}
function parseArtifactReferences(config: any): ArtifactReference[] {
const artifactReferences = config?.launch?.artifactReferences || {};
return Object.keys(artifactReferences)
.filter((name) => {
const isNotBlank = isNotBlankString(artifactReferences[name]);
if (!isNotBlank) {
logger.warn(`Artifact reference value must be a not blank string. Configuration contains artifact reference with name '${name}' which violates this`);
}
return isNotBlank;
})
.map((name) => ({
name,
value: artifactReferences[name]
}));
}
export class ReportingConfig {
readonly enabled: boolean;
readonly projectKey: string;
readonly server: ServerConfig;
readonly launch: LaunchConfig;
readonly milestone: MilestoneConfig;
readonly logs: LogsConfig;
readonly screenshot: ScreenshotConfig;
readonly notifications: NotificationsConfig;
readonly tcm: Tcm;
constructor(config: any) {
this.enabled = getBoolean('REPORTING_ENABLED', config?.enabled);
this.projectKey = getString('REPORTING_PROJECT_KEY', config?.projectKey, 'DEF');
this.server = {
hostname: getString('REPORTING_SERVER_HOSTNAME', config?.server?.hostname),
accessToken: getString('REPORTING_SERVER_ACCESS_TOKEN', config?.server?.accessToken),
};
if (this.enabled === true && !this.server.hostname && !this.server.accessToken) {
throw new Error('When reporting is enabled, you must provide Zebrunner hostname and accessToken');
}
this.launch = {
context: getString('REPORTING_RUN_CONTEXT', null),
displayName: getString('REPORTING_LAUNCH_DISPLAY_NAME', config?.launch?.displayName, process.env.npm_package_name),
build: getString('REPORTING_LAUNCH_BUILD', config?.launch?.build),
environment: getString('REPORTING_LAUNCH_ENVIRONMENT', config?.launch?.environment),
locale: getString('REPORTING_LAUNCH_LOCALE', config?.launch?.locale),
treatSkipsAsFailures: getBoolean('REPORTING_LAUNCH_TREAT_SKIPS_AS_FAILURES', config?.launch?.treatSkipsAsFailures, true),
labels: parseLabels(config),
artifactReferences: parseArtifactReferences(config),
};
this.milestone = {
id: getNumber('REPORTING_MILESTONE_ID', config?.milestone?.id),
name: getString('REPORTING_MILESTONE_NAME', config?.milestone?.name),
};
this.logs = {
pushDelayMillis: getNumber('REPORTING_LOGS_PUSH_DELAY_MILLIS', config?.logs?.pushDelayMillis, logsPushDelay),
includeLoggerName: getBoolean('REPORTING_LOGS_INCLUDE_LOGGER_NAME', config?.logs?.includeLoggerName),
excludeLoggers: getSetOfStrings('REPORTING_LOGS_EXCLUDE_LOGGERS', config?.logs?.excludeLoggers),
};
this.screenshot = {
afterError: getBoolean('REPORTING_SCREENSHOT_AFTER_ERROR', config?.screenshots?.afterError, true),
beforeCommands: getSetOfStrings('REPORTING_SCREENSHOT_BEFORE_COMMANDS', config?.screenshots?.beforeCommands, screenshotBeforeCommands),
afterCommands: getSetOfStrings('REPORTING_SCREENSHOT_AFTER_COMMANDS', config?.screenshots?.afterCommands, screenshotAfterCommands),
};
this.notifications = {
notifyOnEachFailure: getBoolean('REPORTING_NOTIFICATION_NOTIFY_ON_EACH_FAILURE', config?.notifications?.notifyOnEachFailure),
slackChannels: getString('REPORTING_NOTIFICATION_SLACK_CHANNELS', config?.notifications?.slackChannels),
teamsChannels: getString('REPORTING_NOTIFICATION_MS_TEAMS_CHANNELS', config?.notifications?.teamsChannels),
emails: getString('REPORTING_NOTIFICATION_EMAILS', config?.notifications?.emails),
};
this.tcm = {
testCaseStatus: {
onPass: getString('REPORTING_TCM_TEST_CASE_STATUS_ON_PASS', config?.tcm?.testCaseStatus?.onPass),
onFail: getString('REPORTING_TCM_TEST_CASE_STATUS_ON_FAIL', config?.tcm?.testCaseStatus?.onFail),
},
zebrunner: {
pushResults: getBoolean('REPORTING_TCM_ZEBRUNNER_PUSH_RESULTS', config?.tcm?.zebrunner?.pushResults),
pushInRealTime: getBoolean('REPORTING_TCM_ZEBRUNNER_PUSH_IN_REAL_TIME', config?.tcm?.zebrunner?.pushInRealTime),
testRunId: getNumber('REPORTING_TCM_ZEBRUNNER_TEST_RUN_ID', config?.tcm?.zebrunner?.testRunId),
},
testRail: {
pushResults: getBoolean('REPORTING_TCM_TESTRAIL_PUSH_RESULTS', config?.tcm?.testRail?.pushResults),
pushInRealTime: getBoolean('REPORTING_TCM_TESTRAIL_PUSH_IN_REAL_TIME', config?.tcm?.testRail?.pushInRealTime),
suiteId: getNumber('REPORTING_TCM_TESTRAIL_SUITE_ID', config?.tcm?.testRail?.suiteId),
runId: getNumber('REPORTING_TCM_TESTRAIL_RUN_ID', config?.tcm?.testRail?.runId),
includeAllTestCasesInNewRun: getBoolean('REPORTING_TCM_TESTRAIL_INCLUDE_ALL_IN_NEW_RUN', config?.tcm?.testRail?.includeAllTestCasesInNewRun),
runName: getString('REPORTING_TCM_TESTRAIL_RUN_NAME', config?.tcm?.testRail?.runName),
milestoneName: getString('REPORTING_TCM_TESTRAIL_MILESTONE_NAME', config?.tcm?.testRail?.milestoneName),
assignee: getString('REPORTING_TCM_TESTRAIL_ASSIGNEE', config?.tcm?.testRail?.assignee),
},
xray: {
pushResults: getBoolean('REPORTING_TCM_XRAY_PUSH_RESULTS', config?.tcm?.xray?.pushResults),
pushInRealTime: getBoolean('REPORTING_TCM_XRAY_PUSH_IN_REAL_TIME', config?.tcm?.xray?.pushInRealTime),
executionKey: getString('REPORTING_TCM_XRAY_EXECUTION_KEY', config?.tcm?.xray?.executionKey),
},
zephyr: {
pushResults: getBoolean('REPORTING_TCM_ZEPHYR_PUSH_RESULTS', config?.tcm?.zephyr?.pushResults),
pushInRealTime: getBoolean('REPORTING_TCM_ZEPHYR_PUSH_IN_REAL_TIME', config?.tcm?.zephyr?.pushInRealTime),
jiraProjectKey: getString('REPORTING_TCM_ZEPHYR_JIRA_PROJECT_KEY', config?.tcm?.zephyr?.jiraProjectKey),
testCycleKey: getString('REPORTING_TCM_ZEPHYR_TEST_CYCLE_KEY', config?.tcm?.zephyr?.testCycleKey),
}
};
}
}