Botium的测试顺序不正确(?)

时间:2019-03-06 16:30:11

标签: testing chatbot

这是我的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回复了以下命令:

  1. “显示所有事件”
  2. “您想添加或更改搜索过滤器吗?”
  3. 画廊。

如果bot最后回答的是图片库,则未定义的错误是正确的。问题是,如果我使用botium运行,为什么bot不能以正确的顺序答复(就像在手动测试中一样)?

1 个答案:

答案 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)
    ...