这是我的botium.json文件(网址已修改):
{
"botium": {
"Capabilities": {
"PROJECTNAME": "Production Test",
"CONTAINERMODE": "webdriverio",
"WEBDRIVERIO_OPTIONS": {
"desiredCapabilities": {
"browserName": "chrome",
"chromeOptions": {
"args": [ "--headless", "--no-sandbox", "--disable--dev-shm-usage" ]
}
}
},
"WEBDRIVERIO_URL": "https://example.com",
"WEBDRIVERIO_OPENBOT": "./actions/open_test",
"WEBDRIVERIO_IGNOREWELCOMEMESSAGES": 2,
"WEBDRIVERIO_SENDTOBOT": "./actions/send",
"WEBDRIVERIO_GETBOTMESSAGE": "./actions/parse_response",
"WEBDRIVERIO_INPUT_ELEMENT": "#vc-input",
"WEBDRIVERIO_INPUT_ELEMENT_SENDBUTTON": "#vc-btn-send",
"WEBDRIVERIO_OUTPUT_ELEMENT": ".vcw-message-container",
"WEBDRIVERIO_START_SELENIUM": true,
"WEBDRIVERIO_START_SELENIUM_OPTS": {
"drivers": {
"chrome": {
"version": "2.36"
}
}
},
"ASSERTERS": [
{
"ref": "GALLERY",
"src": "./asserters/gallery",
"global": true
}
]
}
}
}
下面是我用来获取漫游器消息的文件(parse_response.js)
let debug = (thing) => {
console.log(thing)
}
module.exports = (container, browser, elementId) => {
console.log('CHECKING ELEMENT', elementId)
const botMsg = { sender: 'bot', buttons: [], cards: [], media: [] }
Promise.resolve(1)
.then(() => browser.elementIdAttribute(elementId, 'class'))
.then(elemClass => elemClass.value.indexOf('from-me') > -1 ? Promise.reject(new Error('from-me')) : Promise.resolve(1))
.then(() => console.log('FROM ME DECIDED', elementId))
// get the images
.then(() => browser.elementIdElement(elementId, '.vcw-message-bubble')
// .then(elements => Promise.resolve(console.log('getting images', elementId)).then(() => elements))
.then(elements => elements.value.ELEMENT))
.then(buble => browser.elementIdElement(buble, 'img'))
.then(img => img.value ? browser.elementIdAttribute(img.value.ELEMENT, 'src').then(src => botMsg.media.push({mediaUri: src.value})) : Promise.resolve())
.catch(e => e.message === 'from-me' ? Promise.reject(e) : Promise.resolve(debug('no images: ' + elementId)))
// get the audio
.then(() => browser.elementIdElement(elementId, '.vcw-message-bubble').then(elements => elements.value.ELEMENT))
.then(buble => browser.elementIdElement(buble, 'source'))
.then(img => img.value ? browser.elementIdAttribute(img.value.ELEMENT, 'src').then(src => botMsg.media.push({mediaUri: src.value})) : Promise.resolve())
.catch(e => e.message === 'from-me' ? Promise.reject(e) : Promise.resolve(debug('no audio: ' + elementId)))
// get the video
.then(() => browser.elementIdElement(elementId, '.vcw-message-bubble').then(elements => elements.value.ELEMENT))
.then(buble => browser.elementIdElement(buble, 'source'))
.then(img => img.value ? browser.elementIdAttribute(img.value.ELEMENT, 'src').then(src => botMsg.media.push({mediaUri: src.value})) : Promise.resolve())
.catch(e => e.message === 'from-me' ? Promise.reject(e) : Promise.resolve(debug('no video: ' + elementId)))
// get the buttons postback & quick replies
.then(() => browser.elementIdElements(elementId, '.quick-reply').then(elements => elements.value))
.then(elements =>
Promise.all(
elements.map(element =>
browser.elementIdText(element.ELEMENT).then(text => Object.assign({text: text.value}))
)
).then(replies => {
botMsg.buttons = botMsg.buttons.concat(replies)
})
)
.catch(e => e.message === 'from-me' ? Promise.reject(e) : Promise.resolve(debug('no qr: ' + elementId)))
// get url buttons
.then(() => browser.elementIdElements(elementId, '.vcw-message-button').then(elements => elements.value))
.then(elements =>
Promise.all(
elements.map(element =>
browser.elementIdText(element.ELEMENT).then(text => Object.assign({text: text.value}))
)
).then(replies => {
botMsg.buttons = botMsg.buttons.concat(replies)
})
)
.catch(e => e.message === 'from-me' ? Promise.reject(e) : Promise.resolve(debug('no btn: ' + elementId)))
// get the gallery cards
.then(() =>
browser.elementIdElements(elementId, '.vcw-card')
// .then(xx => Promise.resolve(console.log('GALLERY', xx)).then(() => xx))
.then(elements => elements.value)
)
.then(elements =>
Promise.all(
elements.map(
element => {
let card = { buttons: [] }
return browser.elementIdElement(element.ELEMENT, 'img')
.then(elem => elem.value ? browser.elementIdAttribute(elem.value.ELEMENT, 'src') : Object.assign({ }))
// .then(imgElem => )
.then(src => card.image = src.value)
.then(() => browser.elementIdElement(element.ELEMENT, '.vcw-card-text-title'))
.then(elem => elem.value ? browser.elementIdText(elem.value.ELEMENT) : Object.assign({}))
// .then(titleElem => )
.then(title => card.title = title.value)
.then(() => browser.elementIdElement(element.ELEMENT, '.vcw-card-text-subtitle'))
.then(elem => elem.value ? browser.elementIdText(elem.value.ELEMENT) : Object.assign({}))
// .then(subtitleElem => )
.then(subtitle => card.subtitle = subtitle.value)
.then(() => browser.elementIdElement(element.ELEMENT, '.vcw-card-button-wrapper'))
.then(elem => elem.value.ELEMENT)
.then(wrapperElement => browser.elementIdElements(wrapperElement, '.vcw-card-button'))
.then(elem => elem.value)
.then(buttons =>
Promise.all(
buttons.map(button => browser.elementIdText(button.ELEMENT)
.then(text => text.value))
)
.then(buttons => card.buttons = buttons)
)
.then(() => card)
.catch(e => {
console.log(e)
})
}
)
).then(cards => botMsg.cards = cards)
)
.catch(e => e.message === 'from-me' ? Promise.reject(e) : Promise.resolve(debug('no gallery: ' + elementId)))
// get the message
.then(() => browser.elementIdElements(elementId, '.vcw-message-bubble').then(elements => elements.value)
)
.then(elements =>
Promise.all(
elements.map(element =>
browser.elementIdText(element.ELEMENT).then(text => text.value)
)
).then(messages => {
if (messages.length > 0) botMsg.messageText = messages[0]
})
)
.catch(e => e.message === 'from-me' ? Promise.reject(e) : Promise.resolve(debug('no text: ' + elementId + ' | ' + e.message)))
.then(() => {
if (botMsg.buttons.length > 0 && !botMsg.messageText) { // qr only
setTimeout(() => {
container.BotSays(botMsg)
}, 1000)
} else {
container.BotSays(botMsg)
}
})
.catch(err => {
if (err.message === 'from-me') {
return Promise.resolve(1)
}
console.log('error', elementId, err)
return Promise.reject(err)
})
}
用于图库断言的文件:
const ParseGalleryString = (galleryString) => {
let tokens = galleryString.match(reg)
if (!tokens) return {}
/** @type {Card} */
let card = {}
tokens.forEach(token => {
let ctoken = token.replace(/\[/g, '').replace(/\]/g, '')
let vals = ctoken.split(':')
let key = vals[0]
let val = vals[1]
if (val) {
vals.splice(0, 1)
val = vals.join(':')
}
if (!key) return
if (key === 'buttons') val = val.split(',').map(v => v.trim())
else val = val.trim()
card[key] = val
})
if (!card.buttons) card.buttons = []
return card
}
module.exports = class GalleryAsserter {
/**
*
* @param {GalleryAssertStepParam} param
*/
assertConvoStep(param) {
let args = param.args
let botMsg = param.botMsg
if (args[0] === 'skip') return Promise.resolve()
if (!args.concat) return Promise.reject(new Error('args for GALLERY is not an array'))
if (args.length > botMsg.cards.length) return Promise.reject(new Error('number of gallery cards doesnt match. expecting ' + args.length +'. Got ' + botMsg.cards.length))
for (var i = 0; i < args.length; i++) {
let card = ParseGalleryString(args[i])
let testcard = botMsg.cards[i]
if (card.image !== testcard.image) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting image to be ${ card.image }, got ${ testcard.image }`))
if (card.title !== testcard.title) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting title to be ${ card.title }, got ${ testcard.title }`))
if (card.subtitle !== testcard.subtitle) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting subtitle to be ${ card.subtitle }, got ${ testcard.subtitle }`))
if (card.buttons.length !== testcard.buttons.length) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting ${ card.buttons.length }(${card.buttons.join(', ')}) buttons, got ${ testcard.buttons.length }(${testcard.buttons.join(', ')})`))
if (card.buttons.join('_') !== testcard.buttons.join('_')) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting buttons to be ${ card.buttons.join(', ') }, got ${ testcard.buttons.join(', ') }`))
}
return Promise.resolve()
}
}
还有我的convo文件:
#me
Find events
#bot
Please wait as I retrieve our list of events. Alternatively, you may find out more
BUTTONS More
#bot
Showing all events at all libraries
#bot
GALLERY skip
#bot
Would you like to add to or change your search filters?
#bot
BUTTONS Category | Language | Library | Clear All Filters | Cancel
-verbose给我看了什么
2019-03-06T13:13:41.964Z botium-Convo Mobile Part 1/Line 68: user says {
"sender": "me",
"channel": null,
"messageText": "Find events",
"stepTag": "Line 68",
"not": false,
"asserters": [],
"logicHooks": []
}
2019-03-06T13:13:41.965Z botium-connector-webdriverio UserSays called BotiumMockMessage {
sender: 'me',
channel: null,
messageText: 'Find events',
media: null,
buttons: null,
cards: null,
sourceData: undefined,
sourceAction: undefined,
attachments: null }
2019-03-06T13:13:43.649Z botium-Convo Mobile Part 1 wait for bot null
2019-03-06T13:13:43.710Z botium-connector-webdriverio Found new bot response element .vcw-message-container, id 0.4377955000683098-61
CHECKING ELEMENT 0.4377955000683098-61
2019-03-06T13:13:43.711Z botium-connector-webdriverio polling for bot output (.vcw-message-container)
2019-03-06T13:13:44.255Z botium-connector-webdriverio Found new bot response element .vcw-message-container, id 0.4377955000683098-62
CHECKING ELEMENT 0.4377955000683098-62
2019-03-06T13:13:44.255Z botium-connector-webdriverio polling for bot output (.vcw-message-container)
FROM ME DECIDED 0.4377955000683098-62
2019-03-06T13:13:44.531Z botium-connector-webdriverio BotSays called { sender: 'bot',
buttons: [ { text: 'More' } ],
cards: [],
media: [],
messageText: 'Please wait as I retrieve our list of events. Alternatively, you may find out more' }
2019-03-06T13:13:44.531Z botium-Convo Mobile Part 1: bot says {
"sender": "bot",
"buttons": [
{
"text": "More"
}
],
"cards": [],
"media": [],
"messageText": "Please wait as I retrieve our list of events. Alternatively, you may find out more",
"channel": "default"
}
2019-03-06T13:13:44.532Z botium-ScriptingProvider assertBotResponse Mobile Part 1/Line 71 (Line 68: #me - Find events ) BOT: Please wait as I retrieve our list of events. Alternatively, you may find out more = Please wait as I retrieve our list of events. Alternatively, you may find out more ...
2019-03-06T13:13:44.532Z botium-Convo Mobile Part 1 wait for bot null
2019-03-06T13:13:45.842Z botium-connector-webdriverio Found new bot response element .vcw-message-container, id 0.4377955000683098-65
CHECKING ELEMENT 0.4377955000683098-65
2019-03-06T13:13:45.843Z botium-connector-webdriverio polling for bot output (.vcw-message-container)
FROM ME DECIDED 0.4377955000683098-65
2019-03-06T13:13:46.126Z botium-connector-webdriverio BotSays called { sender: 'bot',
buttons: [],
cards: [],
media: [],
messageText: 'Showing all events at all libraries' }
2019-03-06T13:13:46.126Z botium-Convo Mobile Part 1: bot says {
"sender": "bot",
"buttons": [],
"cards": [],
"media": [],
"messageText": "Showing all events at all libraries",
"channel": "default"
}
2019-03-06T13:13:46.126Z botium-ScriptingProvider assertBotResponse Mobile Part 1/Line 75 (Line 68: #me - Find events ) BOT: Showing all events at all libraries = Showing all events at all libraries ...
2019-03-06T13:13:46.127Z botium-Convo Mobile Part 1 wait for bot null
2019-03-06T13:13:46.376Z botium-connector-webdriverio Found new bot response element .vcw-message-container, id 0.4377955000683098-67
CHECKING ELEMENT 0.4377955000683098-67
2019-03-06T13:13:46.377Z botium-connector-webdriverio polling for bot output (.vcw-message-container)
FROM ME DECIDED 0.4377955000683098-67
no images: 'image_url',
no audio: 0.4377955000683098-67
no video: 0.4377955000683098-67
2019-03-06T13:13:47.124Z botium-connector-webdriverio Found new bot response element .vcw-message-container, id 0.4377955000683098-82
CHECKING ELEMENT 0.4377955000683098-82
2019-03-06T13:13:47.124Z botium-connector-webdriverio polling for bot output (.vcw-message-container)
FROM ME DECIDED 0.4377955000683098-82
2019-03-06T13:13:47.500Z botium-connector-webdriverio Found new bot response element .vcw-message-container, id 0.4377955000683098-89
CHECKING ELEMENT 0.4377955000683098-89
2019-03-06T13:13:47.501Z botium-connector-webdriverio polling for bot output (.vcw-message-container)
FROM ME DECIDED 0.4377955000683098-89
no images: 'image_url',
no audio: 0.4377955000683098-89
no video: 0.4377955000683098-89
2019-03-06T13:13:49.335Z botium-connector-webdriverio BotSays called { sender: 'bot',
buttons: [],
cards: [],
media: [],
messageText: 'Would you like to add to or change your search filters?' }
2019-03-06T13:13:49.335Z botium-Convo Mobile Part 1: bot says {
"sender": "bot",
"buttons": [],
"cards": [],
"media": [],
"messageText": "Would you like to add to or change your search filters?",
"channel": "default"
}
2019-03-06T13:13:49.335Z botium-Convo Mobile Part 1 wait for bot null
2019-03-06T13:13:49.352Z botium-connector-webdriverio BotSays called { sender: 'bot',
buttons: [],
cards:
[ { buttons: [Array],
image: 'image_url',
title: 'title',
subtitle: 'subtitle'
{ buttons: [Array],
image: 'image_url',
title: 'title',
subtitle: 'subtitle'
{ buttons: [Array],
image: 'image_url',
title: 'title',
subtitle: 'subtitle'
{ buttons: [Array],
image: 'image_url',
title: 'title',
subtitle: 'subtitle'
{ buttons: [Array],
image: 'image_url',
title: 'title',
subtitle: 'subtitle'
{ buttons: [Array],
image: 'image_url',
title: 'title',
subtitle: 'subtitle'
{ buttons: [Array],
image: 'image_url',
title: 'title',
subtitle: 'subtitle'
{ buttons: [Array],
image: 'image_url',
title: 'title',
subtitle: 'subtitle'
media: [] }
2019-03-06T13:13:49.352Z botium-Convo Mobile Part 1: bot says {
"sender": "bot",
"buttons": [],
"cards": [
{
"buttons": [
"Find Out More",
"Description"
],
"image": 'image_url',
"title":'title',
"subtitle":'subtitle'
},
{
"buttons": [
"Find Out More",
"Description"
],
"image": 'image_url',
"title":'title',
"subtitle":'subtitle'
},
{
"buttons": [
"Find Out More",
"Description"
],
"image": 'image_url',
"title":'title',
"subtitle":'subtitle'
},
{
"buttons": [
"Find Out More",
"Description"
],
"image": 'image_url',
"title":'title',
"subtitle":'subtitle'
},
{
"buttons": [
"Find Out More",
"Description"
],
"image": 'image_url',
"title":'title',
"subtitle":'subtitle'
},
{
"buttons": [
"Find Out More",
"Description"
],
"image": 'image_url',
"title":'title',
"subtitle":'subtitle'
},
{
"buttons": [
"Find Out More",
"Description"
],
"image": 'image_url',
"title":'title',
"subtitle":'subtitle'
},
{
"buttons": [
"Find Out More",
"Description"
],
"image": 'image_url',
"title":'title',
"subtitle":'subtitle'
}
],
"media": [],
"channel": "default"
}
2019-03-06T13:13:49.352Z botium-ScriptingProvider assertBotResponse Mobile Part 1/Line 81 (Line 68: #me - Find events ) BOT: undefined = Would you like to add to or change your search filters? ...
2019-03-06T13:13:49.353Z botium-cli-run Mobile Part 1 failed: { TranscriptError: Error: Mobile Part 1/Line 81: Expected bot response (on Line 68: #me - Find events ) "undefined" to match one of "Would you like to add to or change your search filters?"
我的convo文件测试命令正确,机器人会根据该命令进行手动测试进行回复。
但是,基于--verbose的日志,在最后一行中,出现以下错误:“未定义=您要添加还是更改搜索过滤器?” 而且从日志来看,似乎bot回复了以下命令:
如果bot最后回答的是图片库,则未定义的错误是正确的。问题是,如果我使用botium运行,为什么bot不能以正确的顺序答复(就像在手动测试中一样)?
答案 0 :(得分:0)
Botium实际上确实通过链接处理承诺(here是代码)来确保已识别的元素连续处理。
问题似乎出在您的parse_response.js中-它实际上未返回任何内容,从而使处理链无法等待。在启动Promise链时,尝试添加 return 语句:
...
module.exports = (container, browser, elementId) => {
console.log('CHECKING ELEMENT', elementId)
const botMsg = { sender: 'bot', buttons: [], cards: [], media: [] }
return Promise.resolve(1)
...