botium-core 1.14.3 → 1.14.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botium-core",
3
- "version": "1.14.3",
3
+ "version": "1.14.5",
4
4
  "description": "The Selenium for Chatbots",
5
5
  "main": "index.js",
6
6
  "module": "dist/botium-es.js",
@@ -89,7 +89,8 @@ module.exports = class CompilerMarkdown extends CompilerBase {
89
89
  stepTag: 'Line ' + (step.map[0] + 1)
90
90
  },
91
91
  linesToConvoStep(step.children.map(child => child.content +
92
- (child.children ? ' ' + child.children.map(child => child.content).join('|') : '')), sender, this.context, this.eol)
92
+ (child.children && child.children.length > 0 ? ' ' + child.children.map(child => child.content).join('|') : '')
93
+ ), sender, this.context, this.eol)
93
94
  ))
94
95
  } else {
95
96
  debug(`Expected sender ${validSenders.map(s => `'${s}'`).join(' or ')} but found ${sender}`)
@@ -1,11 +1,9 @@
1
- const _ = require('lodash')
2
-
3
1
  const Capabilities = require('../Capabilities')
4
2
  const Constants = require('./Constants')
5
3
  const CompilerBase = require('./CompilerBase')
6
4
  const Utterance = require('./Utterance')
7
5
  const { ConvoHeader, Convo } = require('./Convo')
8
- const { linesToConvoStep, convoStepToLines, validateConvo, validSenders, linesToScriptingMemories } = require('./helper')
6
+ const { linesToConvoStep, convoStepToLines, validateConvo, validSenders, linesToScriptingMemories, trimExceptSpaceEnd } = require('./helper')
9
7
 
10
8
  module.exports = class CompilerTxt extends CompilerBase {
11
9
  constructor (context, caps = {}) {
@@ -37,7 +35,7 @@ module.exports = class CompilerTxt extends CompilerBase {
37
35
  let scriptData = scriptBuffer
38
36
  if (Buffer.isBuffer(scriptBuffer)) scriptData = scriptData.toString()
39
37
 
40
- const lines = _.map(scriptData.split(this.eol), (line) => line.trim())
38
+ const lines = scriptData.split(this.eol)
41
39
 
42
40
  if (scriptType === Constants.SCRIPTING_TYPE_CONVO) {
43
41
  return this._compileConvo(lines, false)
@@ -94,7 +92,6 @@ module.exports = class CompilerTxt extends CompilerBase {
94
92
 
95
93
  lines.forEach((line) => {
96
94
  currentLineIndex++
97
- line = line.trim()
98
95
  if (isValidTagLine(line)) {
99
96
  pushPrev()
100
97
 
@@ -123,7 +120,7 @@ module.exports = class CompilerTxt extends CompilerBase {
123
120
 
124
121
  _compileUtterances (lines) {
125
122
  if (lines && lines.length > 0) {
126
- const result = [new Utterance({ name: lines[0], utterances: lines.length > 1 ? lines.slice(1) : [] })]
123
+ const result = [new Utterance({ name: lines[0].trim(), utterances: lines.length > 1 ? lines.slice(1).map(line => trimExceptSpaceEnd(line)) : [] })]
127
124
  this.context.AddUtterances(result)
128
125
  return result
129
126
  }
@@ -1,8 +1,12 @@
1
1
  const _ = require('lodash')
2
+ const debug = require('debug')('botium-core-MatchFunctions')
2
3
 
3
4
  const { toString, quoteRegexpString, calculateWer } = require('./helper')
4
5
 
5
- const _normalize = (botresponse) => {
6
+ const _normalize = (botresponse, args, convoStepParameters) => {
7
+ if (!convoStepParameters) {
8
+ debug('Convo step parameters might be missing!')
9
+ }
6
10
  if (_.isUndefined(botresponse) || _.isNil(botresponse)) return ''
7
11
  if (_.isObject(botresponse) && _.has(botresponse, 'messageText')) {
8
12
  return toString(botresponse.messageText) || ''
@@ -10,7 +14,10 @@ const _normalize = (botresponse) => {
10
14
  return toString(botresponse)
11
15
  }
12
16
 
13
- const regexp = (ignoreCase) => (botresponse, utterance) => {
17
+ const regexp = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
18
+ if (!convoStepParameters) {
19
+ debug('Convo step parameters might be missing!')
20
+ }
14
21
  if (_.isUndefined(botresponse)) return false
15
22
  utterance = toString(utterance)
16
23
  botresponse = _normalize(botresponse)
@@ -19,7 +26,10 @@ const regexp = (ignoreCase) => (botresponse, utterance) => {
19
26
  return regexp.test(botresponse)
20
27
  }
21
28
 
22
- const wildcard = (ignoreCase) => (botresponse, utterance) => {
29
+ const wildcard = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
30
+ if (!convoStepParameters) {
31
+ debug('Convo step parameters might be missing!')
32
+ }
23
33
  if (_.isUndefined(botresponse)) {
24
34
  if (utterance.trim() === '*') return true
25
35
  else return false
@@ -37,7 +47,10 @@ const wildcard = (ignoreCase) => (botresponse, utterance) => {
37
47
  return regexp.test(botresponse)
38
48
  }
39
49
 
40
- const wildcardExact = (ignoreCase) => (botresponse, utterance) => {
50
+ const wildcardExact = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
51
+ if (!convoStepParameters) {
52
+ debug('Convo step parameters might be missing!')
53
+ }
41
54
  if (_.isUndefined(botresponse)) {
42
55
  if (utterance.trim() === '*') return true
43
56
  else return false
@@ -55,7 +68,10 @@ const wildcardExact = (ignoreCase) => (botresponse, utterance) => {
55
68
  return regexp.test(botresponse)
56
69
  }
57
70
 
58
- const include = (ignoreCase) => (botresponse, utterance) => {
71
+ const include = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
72
+ if (!convoStepParameters) {
73
+ debug('Convo step parameters might be missing!')
74
+ }
59
75
  if (_.isUndefined(botresponse)) return false
60
76
  utterance = toString(utterance)
61
77
  botresponse = _normalize(botresponse)
@@ -67,7 +83,10 @@ const include = (ignoreCase) => (botresponse, utterance) => {
67
83
  return botresponse.indexOf(utterance) >= 0
68
84
  }
69
85
 
70
- const equals = (ignoreCase) => (botresponse, utterance) => {
86
+ const equals = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
87
+ if (!convoStepParameters) {
88
+ debug('Convo step parameters might be missing!')
89
+ }
71
90
  if (_.isUndefined(botresponse)) return false
72
91
  utterance = toString(utterance)
73
92
  botresponse = _normalize(botresponse)
@@ -79,11 +98,14 @@ const equals = (ignoreCase) => (botresponse, utterance) => {
79
98
  return botresponse === utterance
80
99
  }
81
100
 
82
- const wer = () => (botresponse, utterance, args) => {
101
+ const wer = () => (botresponse, utterance, args, convoStepParameters) => {
102
+ if (!convoStepParameters) {
103
+ debug('Convo step parameters might be missing!')
104
+ }
83
105
  botresponse = _normalize(botresponse || '')
84
106
  utterance = toString(utterance || '')
85
107
 
86
- const threshold = ([',', '.'].find(p => `${args[0]}`.includes(p)) ? parseFloat(args[0]) : parseInt(args[0]) / 100)
108
+ const threshold = !_.isNil(convoStepParameters?.matchingModeWer) ? (convoStepParameters?.matchingModeWer / 100) : ([',', '.'].find(p => `${args[0]}`.includes(p)) ? parseFloat(args[0]) : parseInt(args[0]) / 100)
87
109
  return calculateWer(botresponse, utterance) <= threshold
88
110
  }
89
111
 
@@ -130,19 +130,20 @@ module.exports = class ScriptingProvider {
130
130
  resolveUtterance: ({ utterance, resolveEmptyIfUnknown }) => {
131
131
  return this._resolveUtterance({ utterance, resolveEmptyIfUnknown })
132
132
  },
133
- assertBotResponse: (botresponse, tomatch, stepTag, meMsg, convoStepParameters) => {
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
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
+ const found = _.find(tomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS], convoStepParameters))
140
140
  const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
141
141
  if (_.isNil(found)) {
142
- if (this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer') {
142
+ const matchingMode = convoStepParameters.matchingMode || this.caps[Capabilities.SCRIPTING_MATCHING_MODE]
143
+ if (matchingMode === 'wer') {
143
144
  const wer = calculateWer(botresponse, tomatch[0])
144
145
  const werArgs = this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]
145
- const threshold = ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
146
+ const threshold = !_.isNil(convoStepParameters.matchingModeWer) ? (convoStepParameters.matchingModeWer / 100) : ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
146
147
  const message = `${stepTag}: Word Error Rate (${toPercent(wer)}) higher than accepted (${toPercent(threshold)})`
147
148
  throw new BotiumError(
148
149
  message,
@@ -150,7 +151,7 @@ module.exports = class ScriptingProvider {
150
151
  type: 'asserter',
151
152
  source: asserterType,
152
153
  params: {
153
- matchingMode: this.caps[Capabilities.SCRIPTING_MATCHING_MODE],
154
+ matchingMode,
154
155
  args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
155
156
  },
156
157
  context: {
@@ -192,19 +193,20 @@ module.exports = class ScriptingProvider {
192
193
  }
193
194
  }
194
195
  },
195
- assertBotNotResponse: (botresponse, nottomatch, stepTag, meMsg, convoStepParameters) => {
196
+ assertBotNotResponse: (botresponse, nottomatch, stepTag, meMsg, convoStepParameters = {}) => {
196
197
  if (!_.isArray(nottomatch)) {
197
198
  nottomatch = [nottomatch]
198
199
  }
199
200
  debug(`assertBotNotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} != ${nottomatch} ...`)
200
201
  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]))
202
+ const found = _.find(nottomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS], convoStepParameters))
202
203
  const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
203
204
  if (!_.isNil(found)) {
204
- if (this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer') {
205
+ const matchingMode = convoStepParameters.matchingMode || this.caps[Capabilities.SCRIPTING_MATCHING_MODE]
206
+ if (matchingMode === 'wer') {
205
207
  const wer = calculateWer(botresponse, nottomatch[0])
206
208
  const werArgs = this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]
207
- const threshold = ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
209
+ const threshold = !_.isNil(convoStepParameters.matchingModeWer) ? (convoStepParameters.matchingModeWer / 100) : ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
208
210
  const message = `${stepTag}: Word Error Rate (${toPercent(wer)}) lower than accepted (${toPercent(threshold)})`
209
211
  throw new BotiumError(
210
212
  message,
@@ -212,7 +214,7 @@ module.exports = class ScriptingProvider {
212
214
  type: 'asserter',
213
215
  source: asserterType,
214
216
  params: {
215
- matchingMode: this.caps[Capabilities.SCRIPTING_MATCHING_MODE],
217
+ matchingMode,
216
218
  args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
217
219
  },
218
220
  context: {
@@ -1305,17 +1307,18 @@ module.exports = class ScriptingProvider {
1305
1307
  }
1306
1308
  if (utterances) {
1307
1309
  _.forEach(utterances, (utt) => {
1308
- const eu = this.utterances[utt.name]
1310
+ const uttName = utt.name?.trim()
1311
+ const eu = this.utterances[uttName]
1309
1312
  if (eu) {
1310
1313
  eu.utterances = _.uniq(_.concat(eu.utterances, utt.utterances))
1311
1314
  } else {
1312
- this.utterances[utt.name] = utt
1315
+ this.utterances[uttName] = utt
1313
1316
  }
1314
1317
 
1315
- const { ambiguous, expected } = findAmbiguous(this.utterances[utt.name].utterances)
1318
+ const { ambiguous, expected } = findAmbiguous(this.utterances[uttName].utterances)
1316
1319
 
1317
1320
  if (ambiguous && ambiguous.length > 0) {
1318
- debug(`Ambigous utterance "${utt.name}", expecting exact ${expected.length ? ('"' + expected.join(', ') + '"') : '<none>'} scripting memory variables in following user examples: ${ambiguous.map(d => `"${d}"`).join(', ')}`)
1321
+ debug(`Ambigous utterance "${uttName}", expecting exact ${expected.length ? ('"' + expected.join(', ') + '"') : '<none>'} scripting memory variables in following user examples: ${ambiguous.map(d => `"${d}"`).join(', ')}`)
1319
1322
  }
1320
1323
  })
1321
1324
  }
@@ -4,6 +4,7 @@ const speechScorer = require('word-error-rate')
4
4
  const debug = require('debug')('botium-core-scripting-helper')
5
5
 
6
6
  const { E_SCRIPTING_MEMORY_COLUMN_MODE } = require('../Enums')
7
+ const WHITE_SPACES_EXCEPT_SPACE_CHAR_AT_THE_END = /[\n\t\r]+$/
7
8
 
8
9
  const normalizeText = (str, doCleanup) => {
9
10
  if (str && _.isArray(str)) {
@@ -247,7 +248,7 @@ const linesToConvoStep = (lines, sender, context, eol, singleLineMode = false) =
247
248
  if (eol === null) {
248
249
  throw new Error('eol cant be null')
249
250
  }
250
- convoStep.messageText = textLines.join(eol).trim()
251
+ convoStep.messageText = trimExceptSpaceEnd(textLines.join(eol))
251
252
  }
252
253
  }
253
254
  } else {
@@ -270,6 +271,10 @@ const linesToConvoStep = (lines, sender, context, eol, singleLineMode = false) =
270
271
  return convoStep
271
272
  }
272
273
 
274
+ const trimExceptSpaceEnd = (string) => {
275
+ return string?.replace(WHITE_SPACES_EXCEPT_SPACE_CHAR_AT_THE_END, '')
276
+ }
277
+
273
278
  const convoStepToObject = (step) => {
274
279
  const result = []
275
280
  if (step.sender === 'me') {
@@ -473,7 +478,7 @@ const convoStepToLines = (step) => {
473
478
  lines.push(logicHook.name + _formatAppendArgs(logicHook.args))
474
479
  })
475
480
  }
476
- return lines.map(l => l.trim())
481
+ return lines
477
482
  }
478
483
 
479
484
  const linesToScriptingMemories = (lines, columnMode = null) => {
@@ -607,5 +612,6 @@ module.exports = {
607
612
  validateConvo,
608
613
  linesToScriptingMemories,
609
614
  calculateWer,
610
- toPercent
615
+ toPercent,
616
+ trimExceptSpaceEnd
611
617
  }
@@ -161,7 +161,7 @@ describe('compiler.compilermarkdown', function () {
161
161
  const compiler = new Compiler(context, Object.assign({}, DefaultCapabilities, caps))
162
162
 
163
163
  compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
164
- assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag \n!hello2')
164
+ assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag\n!hello2')
165
165
  assert.equal(context.convos[0].conversation[1].not, true)
166
166
  })
167
167
  })
@@ -191,7 +191,7 @@ describe('compiler.compilermarkdown', function () {
191
191
  compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
192
192
  assert.fail('expected error')
193
193
  } catch (err) {
194
- assert.equal(err.message, 'Failed to parse conversation. All element in convo step has to be optional or not optional: ["?hello meat bag ","BUTTONS checkbutton|checkbutton2 "]')
194
+ assert.equal(err.message, 'Failed to parse conversation. All element in convo step has to be optional or not optional: ["?hello meat bag","BUTTONS checkbutton|checkbutton2"]')
195
195
  }
196
196
  })
197
197
  it('should read ?? as ?', async function () {
@@ -225,7 +225,7 @@ describe('compiler.compilermarkdown', function () {
225
225
  const compiler = new Compiler(context, Object.assign({}, DefaultCapabilities, caps))
226
226
 
227
227
  compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
228
- assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag \n?hello2')
228
+ assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag\n?hello2')
229
229
  assert.equal(context.convos[0].conversation[1].optional, true)
230
230
  })
231
231
  })
@@ -236,7 +236,7 @@ describe('compiler.compilertxt', function () {
236
236
  assert.equal(convo.conversation.length, 2)
237
237
  assert.equal(convo.conversation[0].messageText, '')
238
238
  assert.equal(convo.conversation[0].logicHooks.length, 0)
239
- assert.equal(convo.conversation[1].messageText, 'Hi')
239
+ assert.equal(convo.conversation[1].messageText, 'Hi ')
240
240
  })
241
241
  it('should read nothing if there is nothing (even no separator)', async function () {
242
242
  const scriptBuffer = fs.readFileSync(path.resolve(__dirname, CONVOS_DIR, 'convos_emptyrow_no_separator_row.convo.txt'))
@@ -64,6 +64,17 @@ describe('scripting.asserters.convoStepParametersForAssert', function () {
64
64
  assert.isTrue(err.message.indexOf('"You said Hello" expected to match "Hello"') >= 0)
65
65
  }
66
66
  })
67
+ it('should not accept bad chatbot response on exact match defined on step even with WER asserter', async function () {
68
+ this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_matchmode_failed_wer.convo.txt')
69
+ assert.equal(this.compiler.convos.length, 1)
70
+
71
+ try {
72
+ await this.compiler.convos[0].Run(this.container)
73
+ assert.fail('should have failed')
74
+ } catch (err) {
75
+ assert.isTrue(err.message.indexOf('Word Error Rate (50%) higher than accepted (25%)') >= 0)
76
+ }
77
+ })
67
78
  })
68
79
 
69
80
  describe('scripting.asserters.convoStepParametersForAssert.retry', function () {
@@ -0,0 +1,9 @@
1
+ convo_step_parameter_matchmode_failed_wer
2
+
3
+ #me
4
+ Hello1
5
+
6
+ #bot
7
+ You said Hello1 Hello2 Hello3 Hello4
8
+ CONVO_STEP_PARAMETERS {"matchingMode":"wer","matchingModeWer":25}
9
+
@@ -7,7 +7,7 @@ this is a variable: VARVALUE1
7
7
  this is a variable: $myvar
8
8
 
9
9
  #me
10
- this is a variable: VARVALUE2
10
+ this is a variable: VARVALUE2
11
11
 
12
12
  #bot
13
- this is a variable: $myvar
13
+ this is a variable: $myvar