@ovotech/genesys-web-messaging-tester-cli
Version:
235 lines (234 loc) • 13 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createScriptedTestCommand = void 0;
const ci_info_1 = __importDefault(require("ci-info"));
const listr2_1 = require("listr2");
const commander = __importStar(require("commander"));
const commander_1 = require("commander");
const fs_1 = require("fs");
const readableFileValidator_1 = require("../../fileSystem/readableFileValidator");
const yamlFileReader_1 = require("../../fileSystem/yamlFileReader");
const parseTestScript_1 = require("./testScript/parseTestScript");
const genesys_web_messaging_tester_1 = require("@ovotech/genesys-web-messaging-tester");
const ui_1 = require("./ui");
const validateSessionConfig_1 = require("./testScript/validateSessionConfig");
const validateTestScript_1 = require("./testScript/validateTestScript");
const validateGenesysEnvVariables_1 = require("../../genesysPlatform/validateGenesysEnvVariables");
const messageIdToConversationIdFactory_1 = require("../../genesysPlatform/messageIdToConversationIdFactory");
const CommandExpectedlyFailedError_1 = require("../CommandExpectedlyFailedError");
const taskRetry_1 = require("./taskRetry");
function parsePositiveInt(value) {
const parsedValue = parseInt(value, 10);
if (isNaN(parsedValue)) {
throw new commander.InvalidArgumentError('Not a number.');
}
if (parsedValue <= 0) {
throw new commander.InvalidArgumentError('Must be greater than 0.');
}
return parsedValue;
}
function createScriptedTestCommand({ command = new commander_1.Command(), ui = new ui_1.Ui(), reorderedMessageDelayerFactory = (delayBeforeEmittingInMs) => new genesys_web_messaging_tester_1.ReorderedMessageDelayer(delayBeforeEmittingInMs), webMessengerSessionFactory = (config, reorderedMessageDelayer) => new genesys_web_messaging_tester_1.WebMessengerGuestSession(config, { IsAutomatedTest: 'true' }, reorderedMessageDelayer), conversationFactory = (session) => new genesys_web_messaging_tester_1.Conversation(session), fsReadFileSync = fs_1.readFileSync, fsAccessSync = fs_1.accessSync, quietMode = !process.stdout.isTTY || ci_info_1.default.isCI, } = {}) {
const yamlFileReader = (0, yamlFileReader_1.createYamlFileReader)(fsReadFileSync);
if (!ui) {
throw new Error('UI must be defined');
}
return command
.command('scripted')
.description('Test a WM Deployment against a scenario')
.argument('<filePath>', 'Path of the YAML test-script file', (0, readableFileValidator_1.readableFileValidator)(fsAccessSync))
.option('-id, --deployment-id <deploymentId>', "Web Messenger Deployment's ID")
.option('-r, --region <region>', 'Region of Genesys instance that hosts the Web Messenger Deployment')
.option('-o, --origin <origin>', 'Origin domain used for restricting Web Messenger Deployment')
.option('-p, --parallel <number>', 'Maximum scenarios to run in parallel', parsePositiveInt, 1)
.option('-a, --associate-id', `Associate tests their conversation ID.
This requires the following environment variables to be set for an OAuth client
with the role conversation:webmessaging:view:
GENESYS_REGION
GENESYSCLOUD_OAUTHCLIENT_ID
GENESYSCLOUD_OAUTHCLIENT_SECRET`, false)
.option('-fo, --failures-only', 'Only output failures', false)
.option('-t, --timeout <number>', 'Seconds to wait for a response before failing the test', parsePositiveInt, 10)
.action(async (testScriptPath, options) => {
const outputConfig = command.configureOutput();
if (!outputConfig?.writeOut || !outputConfig?.writeErr) {
throw new Error('No writeOut and/or writeErr');
}
let associateId;
if (!options.associateId) {
associateId = { enabled: false };
}
else {
const genesysEnvValidationResult = (0, validateGenesysEnvVariables_1.validateGenesysEnvVariables)(process.env);
if (!genesysEnvValidationResult.genesysVariables) {
outputConfig.writeErr(ui.validatingAssociateConvoIdEnvValidationFailed(genesysEnvValidationResult.error));
}
else {
// Only load when required
// Also removes 'You are trying to `import` a file after the Jest environment has been torn down' error due to
// file-watcher it starts
const { configurePlatformClients } = await Promise.resolve().then(() => __importStar(require('../../genesysPlatform/configurePlatformClients')));
const clients = await configurePlatformClients(genesysEnvValidationResult.genesysVariables);
const messageIdToConversationIdClient = (0, messageIdToConversationIdFactory_1.messageIdToConversationIdFactory)(clients);
associateId = { enabled: true, client: messageIdToConversationIdClient };
const checkResult = await messageIdToConversationIdClient.preflightCheck();
if (!checkResult.ok) {
outputConfig.writeErr(ui.preflightCheckOfAssociateConvoIdFailed(checkResult));
throw new CommandExpectedlyFailedError_1.CommandExpectedlyFailedError();
}
}
}
// 1. Read YAML file
let testScriptFileContents;
try {
testScriptFileContents = yamlFileReader(testScriptPath);
}
catch (error) {
outputConfig.writeErr(ui.errorReadingTestScriptFile(error));
throw new CommandExpectedlyFailedError_1.CommandExpectedlyFailedError();
}
// 2. Validate Test Script
const testScriptValidationResults = (0, validateTestScript_1.validateTestScript)(testScriptFileContents);
if (testScriptValidationResults.error) {
outputConfig.writeErr(ui.validatingTestScriptFailed(testScriptValidationResults.error));
throw new CommandExpectedlyFailedError_1.CommandExpectedlyFailedError();
}
// 3. Merge session config from args and Test Script - args take priority
const { validTestScript } = testScriptValidationResults;
const mergedSessionConfig = {
deploymentId: options.deploymentId ?? validTestScript.config?.deploymentId,
region: options.region ?? validTestScript.config?.region,
origin: options.origin ?? validTestScript.config?.origin,
};
// 4. Validate session config
const sessionConfigValidationResults = (0, validateSessionConfig_1.validateSessionConfig)(mergedSessionConfig);
if (!sessionConfigValidationResults.validSessionConfig) {
outputConfig.writeErr(ui.validatingSessionConfigFailed(sessionConfigValidationResults.error));
throw new CommandExpectedlyFailedError_1.CommandExpectedlyFailedError();
}
// 5. Extract Scenarios from Test Script
const testScriptScenarios = (0, parseTestScript_1.extractScenarios)(testScriptValidationResults.validTestScript, sessionConfigValidationResults.validSessionConfig);
const hasMultipleTests = testScriptScenarios.length > 1;
const tasks = new listr2_1.Listr(testScriptScenarios.map((scenario) => ({
title: ui.titleOfTask(scenario),
task: (context, task) => (0, taskRetry_1.tryableTask)(async (isRetry) => {
const transcription = [];
const reorderedMessageDelayer = reorderedMessageDelayerFactory(isRetry ? 6000 : 1000);
const session = webMessengerSessionFactory(scenario.sessionConfig, reorderedMessageDelayer);
let conversationIdGetter = undefined;
if (associateId.enabled) {
conversationIdGetter = (0, messageIdToConversationIdFactory_1.createConversationIdGetter)(session, associateId.client);
}
new genesys_web_messaging_tester_1.SessionTranscriber(session).on('messageTranscribed', (event) => {
transcription.push(event);
if (!quietMode) {
if (hasMultipleTests) {
task.output = ui?.firstLineOfMessageTranscribed(event);
}
else {
const message = ui?.messageTranscribed(event);
if (task.output) {
task.output += message;
}
else {
task.output = message;
}
}
}
});
const convo = conversationFactory(session);
await convo.waitForConversationToStart();
let stepError;
try {
for (const step of scenario.steps) {
await step(convo, { timeoutInSeconds: options.timeout });
}
}
catch (e) {
if (!isRetry && reorderedMessageDelayer.unorderdMessageDetected) {
task.output = ui?.retryingTestDueToFailureLikelyByUnorderedMessage();
task.title = ui?.titleOfTask(scenario, true);
throw new taskRetry_1.RetryTask();
}
else {
stepError = e;
}
}
finally {
session.close();
}
if (stepError) {
context.scenarioResults.push({
scenario,
transcription,
wasRetriedDueToUnorderedMessageFailure: isRetry,
hasPassed: false,
reasonForError: stepError instanceof Error
? stepError
: new Error('Unexpected error occurred'),
conversationId: conversationIdGetter
? { associateId: true, conversationIdGetter }
: { associateId: false },
});
throw new Error(ui?.titleOfFinishedTask(scenario, false));
}
task.title = ui?.titleOfFinishedTask(scenario, true);
context.scenarioResults.push({
scenario,
transcription,
wasRetriedDueToUnorderedMessageFailure: isRetry,
hasPassed: true,
conversationId: conversationIdGetter
? { associateId: true, conversationIdGetter }
: { associateId: false },
});
}),
})), {
concurrent: options.parallel,
exitOnError: false,
collectErrors: false,
rendererFallback: () => quietMode,
});
const results = await tasks.run({ scenarioResults: [] });
const scenariosThatPassed = results.scenarioResults.filter((s) => s.hasPassed);
if (!options.failuresOnly) {
for (const s of scenariosThatPassed) {
outputConfig.writeOut(await ui.scenarioTestResult(s));
}
}
const scenariosThatFailed = results.scenarioResults.filter((s) => !s.hasPassed);
for (const s of scenariosThatFailed) {
outputConfig.writeOut(await ui.scenarioTestResult(s));
}
outputConfig.writeOut(ui.testScriptSummary([...scenariosThatPassed, ...scenariosThatFailed]));
if (results.scenarioResults.some((r) => !r.hasPassed)) {
throw new CommandExpectedlyFailedError_1.CommandExpectedlyFailedError();
}
});
}
exports.createScriptedTestCommand = createScriptedTestCommand;