botium-core 1.14.1 → 1.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/botium-cjs.js +268 -77
- package/dist/botium-cjs.js.map +1 -1
- package/dist/botium-es.js +268 -77
- package/dist/botium-es.js.map +1 -1
- package/package.json +11 -11
- package/src/Capabilities.js +2 -0
- package/src/scripting/BotiumError.js +40 -3
- package/src/scripting/Convo.js +113 -19
- package/src/scripting/ScriptingMemory.js +7 -0
- package/src/scripting/ScriptingProvider.js +79 -30
- package/src/scripting/logichook/LogicHookConsts.js +5 -2
- package/src/scripting/logichook/LogicHookUtils.js +8 -6
- package/src/scripting/logichook/logichooks/ConvoStepParametersLogicHook.js +6 -0
- package/src/scripting/logichook/logichooks/OrderedListToButtonLogicHook.js +37 -0
- package/test/convo/fillAndApplyScriptingMemory.spec.js +11 -0
- package/test/logichooks/orderedListToButton.spec.js +35 -0
- package/test/scripting/asserters/convoStepParameters.spec.js +140 -0
- package/test/scripting/asserters/convos/TEXT_GOOD.convo.txt +6 -0
- package/test/scripting/asserters/convos/convo_step_parameter_matchmode_failed.convo.txt +8 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_asserters_all_good.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_asserters_botium_timeout.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_asserters_good.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_asserters_good_global.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_and_asserter.convo.txt +10 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_botium_timeout.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_but_no_button.convo.txt +10 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_good.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_good_begin.convo.txt +11 -0
- package/test/scripting/matching/matchingmode.spec.js +4 -1
- package/test/scripting/scriptingProvider.spec.js +38 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botium-core",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.3",
|
|
4
4
|
"description": "The Selenium for Chatbots",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "dist/botium-es.js",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
},
|
|
32
32
|
"homepage": "https://www.botium.ai",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@babel/runtime": "^7.23.
|
|
34
|
+
"@babel/runtime": "^7.23.5",
|
|
35
35
|
"async": "^3.2.5",
|
|
36
36
|
"body-parser": "^1.20.2",
|
|
37
37
|
"boolean": "^3.2.0",
|
|
38
38
|
"bottleneck": "^2.19.5",
|
|
39
|
-
"csv-parse": "^5.5.
|
|
39
|
+
"csv-parse": "^5.5.3",
|
|
40
40
|
"debug": "^4.3.4",
|
|
41
41
|
"express": "^4.18.2",
|
|
42
42
|
"globby": "11.0.4",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"is-json": "^2.0.1",
|
|
46
46
|
"jsonpath": "^1.1.1",
|
|
47
47
|
"lodash": "^4.17.21",
|
|
48
|
-
"markdown-it": "^
|
|
48
|
+
"markdown-it": "^14.0.0",
|
|
49
49
|
"mime-types": "^2.1.35",
|
|
50
50
|
"mkdirp": "^3.0.1",
|
|
51
51
|
"moment": "^2.29.4",
|
|
@@ -71,23 +71,23 @@
|
|
|
71
71
|
"yaml": "^2.3.4"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@babel/core": "^7.23.
|
|
74
|
+
"@babel/core": "^7.23.5",
|
|
75
75
|
"@babel/node": "^7.22.19",
|
|
76
|
-
"@babel/plugin-transform-runtime": "^7.23.
|
|
77
|
-
"@babel/preset-env": "^7.23.
|
|
76
|
+
"@babel/plugin-transform-runtime": "^7.23.4",
|
|
77
|
+
"@babel/preset-env": "^7.23.5",
|
|
78
78
|
"chai": "^4.3.10",
|
|
79
79
|
"chai-as-promised": "^7.1.1",
|
|
80
80
|
"cross-env": "^7.0.3",
|
|
81
|
-
"eslint": "^8.
|
|
81
|
+
"eslint": "^8.55.0",
|
|
82
82
|
"eslint-config-standard": "^17.1.0",
|
|
83
83
|
"eslint-plugin-import": "^2.29.0",
|
|
84
84
|
"eslint-plugin-mocha": "^10.2.0",
|
|
85
|
-
"eslint-plugin-n": "^16.
|
|
85
|
+
"eslint-plugin-n": "^16.4.0",
|
|
86
86
|
"eslint-plugin-promise": "^6.1.1",
|
|
87
87
|
"eslint-plugin-standard": "^4.1.0",
|
|
88
88
|
"mocha": "^10.2.0",
|
|
89
|
-
"nock": "^13.
|
|
90
|
-
"npm-check-updates": "^16.14.
|
|
89
|
+
"nock": "^13.4.0",
|
|
90
|
+
"npm-check-updates": "^16.14.11",
|
|
91
91
|
"nyc": "^15.1.0",
|
|
92
92
|
"rollup": "2.79.1",
|
|
93
93
|
"rollup-plugin-babel": "^4.4.0",
|
package/src/Capabilities.js
CHANGED
|
@@ -157,6 +157,8 @@ module.exports = {
|
|
|
157
157
|
// varnames, testcasenames
|
|
158
158
|
SCRIPTING_MEMORY_COLUMN_MODE: 'SCRIPTING_MEMORY_COLUMN_MODE',
|
|
159
159
|
// Botium Lifecycle Hooks
|
|
160
|
+
SCRIPTING_CONVO_STEP_PARAMETERS: 'SCRIPTING_CONVO_STEP_PARAMETERS',
|
|
161
|
+
// Botium Lifecycle Hooks
|
|
160
162
|
CUSTOMHOOK_ONBUILD: 'CUSTOMHOOK_ONBUILD',
|
|
161
163
|
CUSTOMHOOK_ONSTART: 'CUSTOMHOOK_ONSTART',
|
|
162
164
|
CUSTOMHOOK_ONUSERSAYS: 'CUSTOMHOOK_ONUSERSAYS',
|
|
@@ -90,6 +90,43 @@ const BotiumError = class BotiumError extends Error {
|
|
|
90
90
|
return null
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
hasError ({ type, source }) {
|
|
95
|
+
if (this.context) {
|
|
96
|
+
const errArr = _.isArray(this.context) ? this.context : [this.context]
|
|
97
|
+
for (const err of errArr) {
|
|
98
|
+
if (err.type === 'list') {
|
|
99
|
+
for (const internal of err.errors) {
|
|
100
|
+
if ((!type || internal.type === type) && (!source || internal.source === source)) {
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if ((!type || err.type === type) && (!source || err.source === source)) {
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
toArray () {
|
|
115
|
+
if (this.context) {
|
|
116
|
+
let result = []
|
|
117
|
+
const errArr = _.isArray(this.context) ? this.context : [this.context]
|
|
118
|
+
for (const err of errArr) {
|
|
119
|
+
if (err.type === 'list') {
|
|
120
|
+
result = result.concat(err.errors)
|
|
121
|
+
} else {
|
|
122
|
+
result.push(err)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return result
|
|
126
|
+
} else {
|
|
127
|
+
return []
|
|
128
|
+
}
|
|
129
|
+
}
|
|
93
130
|
}
|
|
94
131
|
|
|
95
132
|
const _getChildErrorsFromContext = (context) => {
|
|
@@ -99,11 +136,11 @@ const _getChildErrorsFromContext = (context) => {
|
|
|
99
136
|
return false
|
|
100
137
|
}
|
|
101
138
|
|
|
102
|
-
const botiumErrorFromErr = (message, err) => {
|
|
139
|
+
const botiumErrorFromErr = (message, err, context = {}) => {
|
|
103
140
|
if (err instanceof BotiumError) {
|
|
104
|
-
return new BotiumError(message, err.context, true)
|
|
141
|
+
return new BotiumError(message, { ...err.context, ...context }, true)
|
|
105
142
|
} else {
|
|
106
|
-
return new BotiumError(message, { err }, true)
|
|
143
|
+
return new BotiumError(message, { err, ...context }, true)
|
|
107
144
|
}
|
|
108
145
|
}
|
|
109
146
|
|
package/src/scripting/Convo.js
CHANGED
|
@@ -277,8 +277,55 @@ class Convo {
|
|
|
277
277
|
let skipTranscriptStep = false
|
|
278
278
|
let conditionalGroupId = null
|
|
279
279
|
let conditionMetInGroup = false
|
|
280
|
-
|
|
280
|
+
let globalConvoStepParameters = container.caps[Capabilities.SCRIPTING_CONVO_STEP_PARAMETERS] || {}
|
|
281
|
+
let retryBotMessageTimeoutEnd = null
|
|
282
|
+
let retryBotMessageConvoId = null
|
|
283
|
+
let retryBotMessageDropBotResponse = false
|
|
284
|
+
for (let i = 0; i < this.conversation.length; i = (retryBotMessageDropBotResponse ? i : i + 1)) {
|
|
285
|
+
retryBotMessageDropBotResponse = false
|
|
281
286
|
const convoStep = this.conversation[i]
|
|
287
|
+
const rawConvoStepParameters = convoStep.logicHooks.find(lh => lh.name === 'CONVO_STEP_PARAMETERS')?.args
|
|
288
|
+
let convoStepParameters = {}
|
|
289
|
+
if (rawConvoStepParameters && rawConvoStepParameters.length) {
|
|
290
|
+
let params
|
|
291
|
+
if (rawConvoStepParameters[0].trim().startsWith('{')) {
|
|
292
|
+
try {
|
|
293
|
+
params = JSON.parse(rawConvoStepParameters[0])
|
|
294
|
+
} catch (e) {
|
|
295
|
+
debug(`${this.header.name}/${convoStep.stepTag}: Failed to parse convo step parameters from JSON ${rawConvoStepParameters[0]}`)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (!params || !Object.keys(params).length) {
|
|
299
|
+
params = {}
|
|
300
|
+
for (const param of rawConvoStepParameters) {
|
|
301
|
+
const semicolon = param.indexOf(':')
|
|
302
|
+
if (semicolon) {
|
|
303
|
+
try {
|
|
304
|
+
const name = param.substring(0, semicolon)
|
|
305
|
+
const value = param.substring(semicolon + 1)
|
|
306
|
+
params[name] = value
|
|
307
|
+
} catch (e) {
|
|
308
|
+
debug(`${this.header.name}/${convoStep.stepTag}: Failed to parse convo step parameter from arg ${param}`)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (convoStep.sender === 'begin') {
|
|
315
|
+
globalConvoStepParameters = Object.assign({}, globalConvoStepParameters || {}, params)
|
|
316
|
+
} else {
|
|
317
|
+
convoStepParameters = Object.assign({}, globalConvoStepParameters || {}, params)
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
if (convoStep.sender !== 'begin') {
|
|
321
|
+
convoStepParameters = globalConvoStepParameters
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (Object.keys(convoStepParameters).length) {
|
|
326
|
+
debug(`${this.header.name}: using convo step parameters ${JSON.stringify(convoStepParameters)}`)
|
|
327
|
+
}
|
|
328
|
+
|
|
282
329
|
const currentStepIndex = i
|
|
283
330
|
container.eventEmitter.emit(Events.CONVO_STEP_NEXT, container, convoStep, i)
|
|
284
331
|
skipTranscriptStep = false
|
|
@@ -319,8 +366,8 @@ class Convo {
|
|
|
319
366
|
const coreMsg = _.omit(removeBuffers(meMsg), ['sourceData'])
|
|
320
367
|
debug(`${this.header.name}/${convoStep.stepTag}: user says (cleaned by binary and base64 data and sourceData) ${JSON.stringify(coreMsg, null, 2)}`)
|
|
321
368
|
await new Promise(resolve => {
|
|
322
|
-
if (container.caps.SIMULATE_WRITING_SPEED && meMsg.messageText && meMsg.messageText.length) {
|
|
323
|
-
setTimeout(() => resolve(), container.caps.SIMULATE_WRITING_SPEED * meMsg.messageText.length)
|
|
369
|
+
if (container.caps[Capabilities.SIMULATE_WRITING_SPEED] && meMsg.messageText && meMsg.messageText.length) {
|
|
370
|
+
setTimeout(() => resolve(), container.caps[Capabilities.SIMULATE_WRITING_SPEED] * meMsg.messageText.length)
|
|
324
371
|
} else {
|
|
325
372
|
resolve()
|
|
326
373
|
}
|
|
@@ -453,11 +500,32 @@ class Convo {
|
|
|
453
500
|
}
|
|
454
501
|
const isErrorHandledWithOptionConvoStep = (err) => {
|
|
455
502
|
const nextConvoStep = this.conversation[i + 1]
|
|
503
|
+
const retryConfig = convoStepParameters?.ignoreNotMatchedBotResponses
|
|
504
|
+
const retryOn = convoStep.sender === 'bot' && retryConfig && retryConfig.timeout && retryConfig.mainAsserter
|
|
456
505
|
if (convoStep.optional && nextConvoStep && nextConvoStep.sender === 'bot') {
|
|
506
|
+
if (retryOn) {
|
|
507
|
+
debug(`${this.header.name}/${convoStep.stepTag}: Retry failed asserter is ignored on optional convo`)
|
|
508
|
+
}
|
|
457
509
|
waitForBotSays = false
|
|
458
510
|
skipTranscriptStep = true
|
|
459
511
|
return true
|
|
512
|
+
} else if (retryOn) {
|
|
513
|
+
if (!retryBotMessageTimeoutEnd || retryBotMessageConvoId !== convoStep.stepTag) {
|
|
514
|
+
retryBotMessageTimeoutEnd = transcriptStep.stepBegin.getTime() + +retryConfig.timeout
|
|
515
|
+
retryBotMessageConvoId = convoStep.stepTag
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const now = new Date().getTime()
|
|
519
|
+
const timeoutRemaining = retryBotMessageTimeoutEnd - now
|
|
520
|
+
if (timeoutRemaining > 0) {
|
|
521
|
+
debug(`${this.header.name}/${convoStep.stepTag}: Convo step retry on, timeout remaining: ${timeoutRemaining}, error: "${err.message}"`)
|
|
522
|
+
retryBotMessageDropBotResponse = true
|
|
523
|
+
return false
|
|
524
|
+
} else {
|
|
525
|
+
debug(`${this.header.name}/${convoStep.stepTag}: Convo step retry on, but timeout is over. error: "${err.message}"`)
|
|
526
|
+
}
|
|
460
527
|
}
|
|
528
|
+
|
|
461
529
|
if (container.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]) {
|
|
462
530
|
assertErrors.push(err)
|
|
463
531
|
return false
|
|
@@ -474,7 +542,7 @@ class Convo {
|
|
|
474
542
|
const tomatch = this._resolveUtterancesToMatch(container, Object.assign({}, scriptingMemoryUpdate, scriptingMemory), messageText, botMsg)
|
|
475
543
|
if (convoStep.not) {
|
|
476
544
|
try {
|
|
477
|
-
this.scriptingEvents.assertBotNotResponse(response, tomatch, `${this.header.name}/${convoStep.stepTag}`, lastMeConvoStep)
|
|
545
|
+
this.scriptingEvents.assertBotNotResponse(response, tomatch, `${this.header.name}/${convoStep.stepTag}`, lastMeConvoStep, convoStepParameters)
|
|
478
546
|
} catch (err) {
|
|
479
547
|
if (isErrorHandledWithOptionConvoStep(err)) {
|
|
480
548
|
continue
|
|
@@ -482,7 +550,7 @@ class Convo {
|
|
|
482
550
|
}
|
|
483
551
|
} else {
|
|
484
552
|
try {
|
|
485
|
-
this.scriptingEvents.assertBotResponse(response, tomatch, `${this.header.name}/${convoStep.stepTag}`, lastMeConvoStep)
|
|
553
|
+
this.scriptingEvents.assertBotResponse(response, tomatch, `${this.header.name}/${convoStep.stepTag}`, lastMeConvoStep, convoStepParameters)
|
|
486
554
|
} catch (err) {
|
|
487
555
|
if (isErrorHandledWithOptionConvoStep(err)) {
|
|
488
556
|
continue
|
|
@@ -491,7 +559,7 @@ class Convo {
|
|
|
491
559
|
}
|
|
492
560
|
} else if (convoStep.sourceData) {
|
|
493
561
|
try {
|
|
494
|
-
this._compareObject(container, scriptingMemory, convoStep, botMsg.sourceData, convoStep.sourceData, botMsg)
|
|
562
|
+
this._compareObject(container, scriptingMemory, convoStep, botMsg.sourceData, convoStep.sourceData, botMsg, convoStepParameters)
|
|
495
563
|
} catch (err) {
|
|
496
564
|
if (isErrorHandledWithOptionConvoStep(err)) {
|
|
497
565
|
continue
|
|
@@ -509,20 +577,46 @@ class Convo {
|
|
|
509
577
|
skipTranscriptStep = true
|
|
510
578
|
continue
|
|
511
579
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
580
|
+
|
|
581
|
+
const errors = err.toArray ? err.toArray() : []
|
|
582
|
+
const retryConfig = convoStepParameters?.ignoreNotMatchedBotResponses
|
|
583
|
+
const retryOn =
|
|
584
|
+
convoStep.sender === 'bot' &&
|
|
585
|
+
retryConfig &&
|
|
586
|
+
retryConfig.timeout &&
|
|
587
|
+
errors.length &&
|
|
588
|
+
errors.filter(({ type, source, asserter }) => type === 'asserter' && (retryConfig.allAsserters || (retryConfig.asserters && retryConfig.asserters.includes(asserter)))).length
|
|
589
|
+
if (retryOn && (!retryBotMessageTimeoutEnd || retryBotMessageConvoId !== convoStep.stepTag)) {
|
|
590
|
+
retryBotMessageTimeoutEnd = transcriptStep.stepBegin.getTime() + +retryConfig.timeout
|
|
591
|
+
retryBotMessageConvoId = convoStep.stepTag
|
|
517
592
|
}
|
|
518
|
-
|
|
519
|
-
|
|
593
|
+
|
|
594
|
+
const now = new Date().getTime()
|
|
595
|
+
const timeoutRemaining = retryOn && (retryBotMessageTimeoutEnd - now)
|
|
596
|
+
if (retryOn && timeoutRemaining > 0) {
|
|
597
|
+
debug(`${this.header.name}/${convoStep.stepTag}: Convo step retry on, timeout remaining: ${timeoutRemaining}, error: "${err.message}"`)
|
|
598
|
+
retryBotMessageDropBotResponse = true
|
|
520
599
|
} else {
|
|
521
|
-
|
|
600
|
+
if (retryOn && timeoutRemaining <= 0) {
|
|
601
|
+
debug(`${this.header.name}/${convoStep.stepTag}: Convo step retry on, but timeout is over. error: "${err.message}"`)
|
|
602
|
+
}
|
|
603
|
+
const failErr = botiumErrorFromErr(`${this.header.name}/${convoStep.stepTag}: assertion error - ${err.message || err}`, err)
|
|
604
|
+
debug(failErr)
|
|
605
|
+
try {
|
|
606
|
+
this.scriptingEvents.fail && this.scriptingEvents.fail(failErr, lastMeConvoStep)
|
|
607
|
+
} catch (failErr) {
|
|
608
|
+
}
|
|
609
|
+
if (container.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS] && err instanceof BotiumError) {
|
|
610
|
+
assertErrors.push(err)
|
|
611
|
+
} else {
|
|
612
|
+
throw failErr
|
|
613
|
+
}
|
|
522
614
|
}
|
|
523
615
|
}
|
|
524
616
|
if (container.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]) {
|
|
525
617
|
if (assertErrors.length > 0) {
|
|
618
|
+
// this has no effect, but logically it has to be false
|
|
619
|
+
retryBotMessageDropBotResponse = false
|
|
526
620
|
throw botiumErrorFromList(assertErrors, {})
|
|
527
621
|
}
|
|
528
622
|
} else {
|
|
@@ -580,7 +674,7 @@ class Convo {
|
|
|
580
674
|
}
|
|
581
675
|
}
|
|
582
676
|
|
|
583
|
-
_compareObject (container, scriptingMemory, convoStep, result, expected, botMsg) {
|
|
677
|
+
_compareObject (container, scriptingMemory, convoStep, result, expected, botMsg, convoStepParameters) {
|
|
584
678
|
if (expected === null || expected === undefined) return
|
|
585
679
|
|
|
586
680
|
if (_.isArray(expected)) {
|
|
@@ -591,12 +685,12 @@ class Convo {
|
|
|
591
685
|
throw new BotiumError(`${this.header.name}/${convoStep.stepTag}: bot response expected array length ${expected.length}, got ${result.length}`)
|
|
592
686
|
}
|
|
593
687
|
for (let i = 0; i < expected.length; i++) {
|
|
594
|
-
this._compareObject(container, scriptingMemory, convoStep, result[i], expected[i])
|
|
688
|
+
this._compareObject(container, scriptingMemory, convoStep, result[i], expected[i], null, convoStepParameters)
|
|
595
689
|
}
|
|
596
690
|
} else if (_.isObject(expected)) {
|
|
597
691
|
_.forOwn(expected, (value, key) => {
|
|
598
692
|
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
599
|
-
this._compareObject(container, scriptingMemory, convoStep, result[key], expected[key])
|
|
693
|
+
this._compareObject(container, scriptingMemory, convoStep, result[key], expected[key], null, convoStepParameters)
|
|
600
694
|
} else {
|
|
601
695
|
throw new BotiumError(`${this.header.name}/${convoStep.stepTag}: bot response "${result}" missing expected property: ${key}`)
|
|
602
696
|
}
|
|
@@ -605,7 +699,7 @@ class Convo {
|
|
|
605
699
|
ScriptingMemory.fill(container, scriptingMemory, result, expected, this.scriptingEvents)
|
|
606
700
|
const response = this._checkNormalizeText(container, result)
|
|
607
701
|
const tomatch = this._resolveUtterancesToMatch(container, scriptingMemory, expected, botMsg)
|
|
608
|
-
this.scriptingEvents.assertBotResponse(response, tomatch, `${this.header.name}/${convoStep.stepTag}
|
|
702
|
+
this.scriptingEvents.assertBotResponse(response, tomatch, `${this.header.name}/${convoStep.stepTag}`, null, convoStepParameters)
|
|
609
703
|
}
|
|
610
704
|
}
|
|
611
705
|
|
|
@@ -673,7 +767,7 @@ class Convo {
|
|
|
673
767
|
}
|
|
674
768
|
|
|
675
769
|
_checkBotRepliesConsumed (container) {
|
|
676
|
-
if (container.caps.SCRIPTING_FORCE_BOT_CONSUMED) {
|
|
770
|
+
if (container.caps[Capabilities.SCRIPTING_FORCE_BOT_CONSUMED]) {
|
|
677
771
|
const queueLength = container._QueueLength()
|
|
678
772
|
if (queueLength === 1) {
|
|
679
773
|
throw new Error('There is an unread bot reply in queue')
|
|
@@ -261,6 +261,13 @@ const _apply = (scriptingMemory, str, caps, mockMsg) => {
|
|
|
261
261
|
return arg
|
|
262
262
|
}
|
|
263
263
|
})
|
|
264
|
+
args = args.map(arg => {
|
|
265
|
+
const argStr = `${arg}`
|
|
266
|
+
if (argStr.startsWith('$')) {
|
|
267
|
+
return scriptingMemory[argStr.substring(1)] || arg
|
|
268
|
+
}
|
|
269
|
+
return arg
|
|
270
|
+
})
|
|
264
271
|
str = str.replace(match, SCRIPTING_FUNCTIONS[key].handler(caps, ...args, mockMsg))
|
|
265
272
|
} else {
|
|
266
273
|
str = str.replace(match, SCRIPTING_FUNCTIONS[key].handler(caps))
|
|
@@ -92,37 +92,37 @@ module.exports = class ScriptingProvider {
|
|
|
92
92
|
|
|
93
93
|
this.scriptingEvents = {
|
|
94
94
|
onConvoBegin: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
95
|
-
return this._createLogicHookPromises({ hookType: 'onConvoBegin', logicHooks: (convo
|
|
95
|
+
return this._createLogicHookPromises({ hookType: 'onConvoBegin', logicHooks: (convo?.beginLogicHook || []), convo, convoStep, scriptingMemory, ...rest })
|
|
96
96
|
},
|
|
97
97
|
onConvoEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
98
|
-
return this._createLogicHookPromises({ hookType: 'onConvoEnd', logicHooks: (convo
|
|
98
|
+
return this._createLogicHookPromises({ hookType: 'onConvoEnd', logicHooks: (convo?.endLogicHook || []), convo, convoStep, scriptingMemory, ...rest })
|
|
99
99
|
},
|
|
100
100
|
onMeStart: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
101
|
-
return this._createLogicHookPromises({ hookType: 'onMeStart', logicHooks: (convoStep
|
|
101
|
+
return this._createLogicHookPromises({ hookType: 'onMeStart', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
102
102
|
},
|
|
103
103
|
onMePrepare: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
104
|
-
return this._createLogicHookPromises({ hookType: 'onMePrepare', logicHooks: (convoStep
|
|
104
|
+
return this._createLogicHookPromises({ hookType: 'onMePrepare', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
105
105
|
},
|
|
106
106
|
onMeEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
107
|
-
return this._createLogicHookPromises({ hookType: 'onMeEnd', logicHooks: (convoStep
|
|
107
|
+
return this._createLogicHookPromises({ hookType: 'onMeEnd', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
108
108
|
},
|
|
109
109
|
onBotStart: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
110
|
-
return this._createLogicHookPromises({ hookType: 'onBotStart', logicHooks: (convoStep
|
|
110
|
+
return this._createLogicHookPromises({ hookType: 'onBotStart', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
111
111
|
},
|
|
112
112
|
onBotPrepare: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
113
|
-
return this._createLogicHookPromises({ hookType: 'onBotPrepare', logicHooks: (convoStep
|
|
113
|
+
return this._createLogicHookPromises({ hookType: 'onBotPrepare', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
114
114
|
},
|
|
115
115
|
onBotEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
116
|
-
return this._createLogicHookPromises({ hookType: 'onBotEnd', logicHooks: (convoStep
|
|
116
|
+
return this._createLogicHookPromises({ hookType: 'onBotEnd', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
117
117
|
},
|
|
118
118
|
assertConvoBegin: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
119
|
-
return this._createAsserterPromises({ asserterType: 'assertConvoBegin', asserters: (convo
|
|
119
|
+
return this._createAsserterPromises({ asserterType: 'assertConvoBegin', asserters: (convo?.beginAsserter || []), convo, convoStep, scriptingMemory, ...rest })
|
|
120
120
|
},
|
|
121
121
|
assertConvoStep: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
122
|
-
return this._createAsserterPromises({ asserterType: 'assertConvoStep', asserters: (convoStep
|
|
122
|
+
return this._createAsserterPromises({ asserterType: 'assertConvoStep', asserters: (convoStep?.asserters || []), convo, convoStep, scriptingMemory, ...rest })
|
|
123
123
|
},
|
|
124
124
|
assertConvoEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
125
|
-
return this._createAsserterPromises({ asserterType: 'assertConvoEnd', asserters: (convo
|
|
125
|
+
return this._createAsserterPromises({ asserterType: 'assertConvoEnd', asserters: (convo?.endAsserter || []), convo, convoStep, scriptingMemory, ...rest })
|
|
126
126
|
},
|
|
127
127
|
setUserInput: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
128
128
|
return this._createUserInputPromises({ convo, convoStep, scriptingMemory, ...rest })
|
|
@@ -130,12 +130,13 @@ module.exports = class ScriptingProvider {
|
|
|
130
130
|
resolveUtterance: ({ utterance, resolveEmptyIfUnknown }) => {
|
|
131
131
|
return this._resolveUtterance({ utterance, resolveEmptyIfUnknown })
|
|
132
132
|
},
|
|
133
|
-
assertBotResponse: (botresponse, tomatch, stepTag, meMsg) => {
|
|
133
|
+
assertBotResponse: (botresponse, tomatch, stepTag, meMsg, convoStepParameters) => {
|
|
134
134
|
if (!_.isArray(tomatch)) {
|
|
135
135
|
tomatch = [tomatch]
|
|
136
136
|
}
|
|
137
137
|
debug(`assertBotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} = ${tomatch} ...`)
|
|
138
|
-
const
|
|
138
|
+
const matchFn = convoStepParameters.matchingMode ? (getMatchFunction(convoStepParameters.matchingMode) || this.matchFn) : this.matchFn
|
|
139
|
+
const found = _.find(tomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]))
|
|
139
140
|
const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
|
|
140
141
|
if (_.isNil(found)) {
|
|
141
142
|
if (this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer') {
|
|
@@ -191,12 +192,13 @@ module.exports = class ScriptingProvider {
|
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
},
|
|
194
|
-
assertBotNotResponse: (botresponse, nottomatch, stepTag, meMsg) => {
|
|
195
|
+
assertBotNotResponse: (botresponse, nottomatch, stepTag, meMsg, convoStepParameters) => {
|
|
195
196
|
if (!_.isArray(nottomatch)) {
|
|
196
197
|
nottomatch = [nottomatch]
|
|
197
198
|
}
|
|
198
199
|
debug(`assertBotNotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} != ${nottomatch} ...`)
|
|
199
|
-
const
|
|
200
|
+
const matchFn = convoStepParameters.matchingMode ? (getMatchFunction(convoStepParameters.matchingMode) || this.matchFn) : this.matchFn
|
|
201
|
+
const found = _.find(nottomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]))
|
|
200
202
|
const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
|
|
201
203
|
if (!_.isNil(found)) {
|
|
202
204
|
if (this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer') {
|
|
@@ -270,6 +272,22 @@ module.exports = class ScriptingProvider {
|
|
|
270
272
|
assertConvoStep: 'assertNotConvoStep',
|
|
271
273
|
assertConvoEnd: 'assertNotConvoEnd'
|
|
272
274
|
}
|
|
275
|
+
const updateExceptionContext = (promise, asserter) => {
|
|
276
|
+
const updateError = (err) => {
|
|
277
|
+
if (err instanceof BotiumError) {
|
|
278
|
+
if (!err.context) {
|
|
279
|
+
err.context = {}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
err.context.asserter = asserter.name
|
|
283
|
+
|
|
284
|
+
throw err
|
|
285
|
+
} else {
|
|
286
|
+
throw botiumErrorFromErr(_.isString(err) ? err : err.message, err, { asserter: asserter.name })
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return promise.catch(err => updateError(err))
|
|
290
|
+
}
|
|
273
291
|
const callAsserter = (asserterSpec, asserter, params) => {
|
|
274
292
|
if (asserterSpec.not) {
|
|
275
293
|
const notAsserterType = mapNot[asserterType]
|
|
@@ -301,18 +319,35 @@ module.exports = class ScriptingProvider {
|
|
|
301
319
|
|
|
302
320
|
const convoAsserter = asserters
|
|
303
321
|
.filter(a => this.asserters[a.name][asserterType])
|
|
304
|
-
.map(a =>
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
322
|
+
.map(a => ({
|
|
323
|
+
asserter: a,
|
|
324
|
+
promise: callAsserter(a, this.asserters[a.name], {
|
|
325
|
+
convo,
|
|
326
|
+
convoStep,
|
|
327
|
+
scriptingMemory,
|
|
328
|
+
container,
|
|
329
|
+
args: ScriptingMemory.applyToArgs(a.args, scriptingMemory, container.caps, rest.botMsg),
|
|
330
|
+
isGlobal: false,
|
|
331
|
+
...rest
|
|
332
|
+
})
|
|
312
333
|
}))
|
|
334
|
+
.map(({ promise, asserter }) => updateExceptionContext(promise, asserter))
|
|
335
|
+
|
|
313
336
|
const globalAsserter = Object.values(this.globalAsserter)
|
|
314
337
|
.filter(a => a[asserterType])
|
|
315
|
-
.map(a =>
|
|
338
|
+
.map(a => ({
|
|
339
|
+
asserter: a,
|
|
340
|
+
promise: p(this.retryHelperAsserter, () => a[asserterType]({
|
|
341
|
+
convo,
|
|
342
|
+
convoStep,
|
|
343
|
+
scriptingMemory,
|
|
344
|
+
container,
|
|
345
|
+
args: [],
|
|
346
|
+
isGlobal: true,
|
|
347
|
+
...rest
|
|
348
|
+
}))
|
|
349
|
+
}))
|
|
350
|
+
.map(({ promise, asserter }) => updateExceptionContext(promise, asserter))
|
|
316
351
|
|
|
317
352
|
const allPromises = [...convoAsserter, ...globalAsserter]
|
|
318
353
|
if (this.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]) {
|
|
@@ -335,8 +370,10 @@ module.exports = class ScriptingProvider {
|
|
|
335
370
|
throw Error(`Unknown hookType ${hookType}`)
|
|
336
371
|
}
|
|
337
372
|
|
|
338
|
-
const
|
|
373
|
+
const localHooks = (logicHooks || [])
|
|
339
374
|
.filter(l => this.logicHooks[l.name][hookType])
|
|
375
|
+
|
|
376
|
+
const convoStepPromises = localHooks
|
|
340
377
|
.map(l => p(this.retryHelperLogicHook, () => this.logicHooks[l.name][hookType]({
|
|
341
378
|
convo,
|
|
342
379
|
convoStep,
|
|
@@ -347,17 +384,24 @@ module.exports = class ScriptingProvider {
|
|
|
347
384
|
...rest
|
|
348
385
|
})))
|
|
349
386
|
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
.map(l => p(this.retryHelperLogicHook, () => l[hookType]({ convo, convoStep, scriptingMemory, container, args: [], isGlobal: true, ...rest })))
|
|
387
|
+
const globalHooks = Object.values(this.globalLogicHook).filter(l => l[hookType])
|
|
388
|
+
const globalPromises = globalHooks.map(l => p(this.retryHelperLogicHook, () => l[hookType]({ convo, convoStep, scriptingMemory, container, args: [], isGlobal: true, ...rest })))
|
|
353
389
|
|
|
354
390
|
const allPromises = [...convoStepPromises, ...globalPromises]
|
|
355
|
-
|
|
391
|
+
|
|
392
|
+
if (allPromises.length > 0) {
|
|
393
|
+
return Promise.all(allPromises).then(() => {
|
|
394
|
+
return {
|
|
395
|
+
// just returning some humanreadable
|
|
396
|
+
hooks: [...localHooks, ...globalHooks].map(h => h.name || h.context?.ref || JSON.stringify(h))
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
}
|
|
356
400
|
return Promise.resolve(false)
|
|
357
401
|
}
|
|
358
402
|
|
|
359
403
|
_createUserInputPromises ({ convo, convoStep, scriptingMemory, container, ...rest }) {
|
|
360
|
-
const convoStepPromises = (convoStep
|
|
404
|
+
const convoStepPromises = (convoStep?.userInputs || [])
|
|
361
405
|
.filter(ui => this.userInputs[ui.name])
|
|
362
406
|
.map(ui => p(this.retryHelperUserInput, () => this.userInputs[ui.name].setUserInput({
|
|
363
407
|
convo,
|
|
@@ -424,6 +468,11 @@ module.exports = class ScriptingProvider {
|
|
|
424
468
|
}
|
|
425
469
|
}
|
|
426
470
|
|
|
471
|
+
// Livechat, and crawler using logichooks too. So they need script context
|
|
472
|
+
BuildScriptContext () {
|
|
473
|
+
return this._buildScriptContext()
|
|
474
|
+
}
|
|
475
|
+
|
|
427
476
|
Build () {
|
|
428
477
|
const CompilerXlsx = require('./CompilerXlsx')
|
|
429
478
|
this.compilers[Constants.SCRIPTING_FORMAT_XSLX] = new CompilerXlsx(this._buildScriptContext(), this.caps)
|
|
@@ -61,11 +61,14 @@ module.exports = {
|
|
|
61
61
|
{ name: 'CONDITIONAL_STEP_TIME_BASED', className: 'ConditionalTimeBasedLogicHook' },
|
|
62
62
|
{ name: 'CONDITIONAL_STEP_BUSINESS_HOURS', className: 'ConditionalBusinessHoursLogicHook' },
|
|
63
63
|
{ name: 'CONDITIONAL_STEP_CAPABILITY_VALUE_BASED', className: 'ConditionalCapabilityValueBasedLogicHook' },
|
|
64
|
-
{ name: 'CONDITIONAL_STEP_JSON_PATH_BASED', className: 'ConditionalJsonPathBasedLogicHook.js' }
|
|
64
|
+
{ name: 'CONDITIONAL_STEP_JSON_PATH_BASED', className: 'ConditionalJsonPathBasedLogicHook.js' },
|
|
65
|
+
{ name: 'CONVO_STEP_PARAMETERS', className: 'ConvoStepParametersLogicHook.js' },
|
|
66
|
+
{ name: 'ORDERED_LIST_TO_BUTTON', className: 'OrderedListToButtonLogicHook' }
|
|
65
67
|
],
|
|
66
68
|
DEFAULT_USER_INPUTS: [
|
|
67
69
|
{ name: 'BUTTON', className: 'ButtonInput' },
|
|
68
70
|
{ name: 'MEDIA', className: 'MediaInput' },
|
|
69
71
|
{ name: 'FORM', className: 'FormInput' }
|
|
70
|
-
]
|
|
72
|
+
],
|
|
73
|
+
LOGIC_HOOK_EVENTS: ['onConvoBegin', 'onMeStart', 'onMePrepare', 'onMeEnd', 'onBotStart', 'onBotEnd', 'onBotPrepare', 'onConvoEnd']
|
|
71
74
|
}
|
|
@@ -147,19 +147,21 @@ module.exports = class LogicHookUtils {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
const typeAsText = hookType === 'asserter' ? 'Asserter' : hookType === 'logichook' ? 'Logic Hook' : hookType === 'userinput' ? 'User Input' : 'Unknown'
|
|
151
|
+
|
|
150
152
|
if (isClass(src)) {
|
|
151
153
|
try {
|
|
152
154
|
const CheckClass = src
|
|
153
155
|
return new CheckClass({ ref, ...this.buildScriptContext }, this.caps, args)
|
|
154
156
|
} catch (err) {
|
|
155
|
-
throw new Error(
|
|
157
|
+
throw new Error(`${typeAsText} specification ${ref} from class invalid: ${err.message}`)
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
160
|
if (_.isFunction(src)) {
|
|
159
161
|
try {
|
|
160
162
|
return src({ ref, ...this.buildScriptContext }, this.caps, args)
|
|
161
163
|
} catch (err) {
|
|
162
|
-
throw new Error(
|
|
164
|
+
throw new Error(`${typeAsText} specification ${ref} from function invalid: ${err.message}`)
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
if (_.isObject(src) && !_.isString(src)) {
|
|
@@ -177,7 +179,7 @@ module.exports = class LogicHookUtils {
|
|
|
177
179
|
}, {})
|
|
178
180
|
return hookObject
|
|
179
181
|
} catch (err) {
|
|
180
|
-
throw new Error(
|
|
182
|
+
throw new Error(`${typeAsText} specification ${ref} ${hookType} from provided src (${util.inspect(src)}) invalid: ${err.message}`)
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
|
|
@@ -230,7 +232,7 @@ module.exports = class LogicHookUtils {
|
|
|
230
232
|
try {
|
|
231
233
|
return tryLoadFromSource(tryLoadFile, tryLoad.tryLoadAsserterByName)
|
|
232
234
|
} catch (err) {
|
|
233
|
-
loadErr.push(
|
|
235
|
+
loadErr.push(`${typeAsText} specification ${ref} ${hookType} from "${src}" invalid: ${err.message} `)
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
238
|
}
|
|
@@ -239,13 +241,13 @@ module.exports = class LogicHookUtils {
|
|
|
239
241
|
try {
|
|
240
242
|
return tryLoadFromSource(tryLoad.tryLoadPackageName, tryLoad.tryLoadAsserterByName)
|
|
241
243
|
} catch (err) {
|
|
242
|
-
loadErr.push(
|
|
244
|
+
loadErr.push(`${typeAsText} specification ${ref} ${hookType} from "${src}" invalid: ${err.message} `)
|
|
243
245
|
}
|
|
244
246
|
}
|
|
245
247
|
}
|
|
246
248
|
|
|
247
249
|
loadErr.forEach(debug)
|
|
248
250
|
}
|
|
249
|
-
throw new Error(
|
|
251
|
+
throw new Error(`${typeAsText} specification ${ref} ${hookType} from "${util.inspect(src)}" invalid : no loader available`)
|
|
250
252
|
}
|
|
251
253
|
}
|