Robot Framework Getting Keyword failure reason - robotframework

Trying to implement a listener interface for robot framework in order to collect information about keyword executions like time taken for execution, Pass/Fail status, failure message in case if status is fail. Sample code is given below
import os.path
import tempfile
class PythonListener:
ROBOT_LISTENER_API_VERSION = 2
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
def __init__(self, filename='listen.txt'):
outpath = os.path.join(tempfile.gettempdir(), filename)
self.outfile = open(outpath, 'w')
def end_keyword(self, name, attrs):
self.outfile.write(name + "\n")
self.outfile.write(str(attrs) + "\n")
def close(self):
self.outfile.close()
All the information apart from keyword failure message is available in the attributes which is passed to end_test method from robot framework.
Documentation can be found here. https://github.com/robotframework/robotframework/blob/master/doc/userguide/src/ExtendingRobotFramework/ListenerInterface.rst#id36
The failure message is available in the attributes for end_test() method. But this will not have information if a keyword is run using RunKeywordAndIgnoreError.
I could see that there is a special variable ${KEYWORD MESSAGE} in robot framework, which contains the possible error message of the current keyword Is it possible to access this variable in the listener class.?
https://github.com/robotframework/robotframework/blob/master/doc/userguide/src/CreatingTestData/Variables.rst#automatic-variables
Are there any other ways to collect the failure message information at the end of every keyword?

That's an interesting approach, indeed, end_test will ensure an attributes.message field containing the failure. (so it goes for end_suite if it fails during the suite setup/teardown)
With end_keyword you don't have such message, but at least you can filter for the FAIL status and detect which one failed. Then the message returned by Run Keyword And Ignore Error has to be logged explicitly by you so you can capture such triggering logs with the log_message hook. Otherwise nobody is aware of the message of the exception handled by the wrapper keyword which returns a tuple of (status, message).
There's also the message hook but couldn't manage to get it called from a normal breaking robot:
Called when the framework itself writes a syslog message.
message is a dictionary with the same contents as with log_message method.
Side note: To not expose these hooks as keywords, you can precede the method names with _. Examples:
def _end_test(self, name, attributes): ...
def _log_message(self, message): ...

Related

Can I share an object between telegram commands of a bot?

I want to create an object when the user press /start in a Telegram bot, and then share this object among all the commands of the bot. Is this possible? As far as I understand, there's only one thread of your bot running in your server. However, I see that there is a context in the command functions. Can I pass this object as a kind of context? For example:
'''
This is a class object that I created to store data from the user and configure the texts I'll display depending on
the user language but maybe I fill it also with info about something it will buy in the bot
'''
import configuration
from telegram import Update, ForceReply
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext
# Commands of the bot
def start(update: Update, context: CallbackContext) -> None:
"""Send a message when the command /start is issued."""
s = configuration.conf(update) #Create the object I'm saying
update.message.reply_markdown_v2(s.text[s.lang_active],
reply_markup=ForceReply(selective=True),
)
def check(update: Update, context: CallbackContext) -> None:
"""Send a message when the command /start is issued."""
s = configuration.conf(update) # I want to avoid this!
update.message.reply_markdown_v2(s.text[s.lang_active],
reply_markup=ForceReply(selective=True),
)
... REST OF THE BOT
python-telegram-bot already comes with a built-in mechanism for storing data. You can do something like
try:
s = context.user_data['config']
except KeyError:
s = configuration.confi(update)
context.user_data['config'] = s
This doesn't have to be repeated in every callback - you can e.g.
use a TypeHandler in a low group to create the config if needed. then in all handlers in higher groups, you don't need to worry about it
use a custom implementation of CallbackContext that adds a property context.user_config
Disclaimer: I'm currently the maintainer of python-telegram-bot.

pyTelegramBotAPI message handlers can't see photo

I need my telegram bot to forward messages from the private chat to the customer care staff group.
I run this code:
#bot.message_handler(func=lambda message: message.chat.type=='private')
def forwarder(message):
bot.forward_message(group, message.chat.id, message.id)
bot.send_message(group, '#id'+str(message.chat.id))
It works smoothly with text messages, but does nothing with photos.
Even if I remove all previous message handlers, so there is no conflict with them to handle photos, it still keeps doing nothing.
If I use the get.updates() method I can check "manually" if the photo has arrived and I find it.
Edit: Even if i just run this code only
import telebot
bot = telebot.TeleBot("MY TOKEN")
#bot.message_handler(func=lambda message: True)
def trivial(message):
print('yes')
bot.polling()
I get 'yes' for text messages and absolutely nothing, not even raised exceptions for photos.
If you add content_types as a parameter you will receive whatever is specified in the argument (which takes an array of strings). Probably the default value for that parameter is set to ['text']. Which will only listen for 'text' messages.
To get the results you're looking for
your test code will look like:
import telebot
bot = telebot.TeleBot("MY TOKEN")
#bot.message_handler(func=lambda, message: True, content_types=['photo','text'])
def trivial(message):
print('yes')
bot.polling()
And your working code:
#bot.message_handler(func=lambda, message: message.chat.type=='private', content_types=['photo','text'])
def forwarder(message):
bot.forward_message(group, message.chat.id, message.id)
bot.send_message(group, '#id'+str(message.chat.id))
In Telegram Bot API there is a resource /sendphoto
See:
Sending message in telegram bot with images
Try to find the related method in the PyTelegramBotApi.
Or implement the function to send a photo your own (e.g. using requests library in Python). Then you can use it in the message-handlers of your bot, to forward images.

context.job_queue.run_once not working in Python Telegram BOT API

I'm trying to setup a bot which:
Receives the keywords in /search_msgs userkey command from a TG group
Search in DB for that userkey and send back appropriate text back
I'm getting two errors
None type object has no attribute args, in callback_search_msgs(context), see code snippet
AttributeError: 'int' object has no attribute 'job_queue', in search_msgs(update, context), see code snippet.
Telegram's official documents is way too difficult for me to read and understand. Couldn't find even one place where Updater, update, Commandhandler, context are all explained together with examples.
How to fix this code?
import telegram
from telegram.ext import Updater,CommandHandler, JobQueue
token = "Token"
bot = telegram.Bot(token=token)
# Search specific msgs on user request
def search_msgs(update, context):
context.job_queue.run_once(callback_search_msgs, context=update.message.chat_id)
def callback_search_msgs(context):
print('In TG, args', context.args)
chat_id = context.job.context
search_msgs(context, chat_id)
def main():
updater = Updater(token, use_context=True)
dp = updater.dispatcher
dp.add_handler(CommandHandler("search_msgs",search_msgs, pass_job_queue=True,
pass_user_data=True))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
Let me first try & clear something up:
Telegram's official documents is way too difficult for me to read and understand. Couldn't find even one place where Updater, update, Commandhandler, context are all explained together with examples.
I'm guessing that by "Telegram's official documents" you mean the docs at https://core.telegram.org/bots/api. However, Updater, CommandHandler and context are concepts of python-telegram-bot, which is one (of many) python libraries that provides a wrapper for the bot api. python-telegram-bot provides a tutorial, examples, a wiki where a lots of the features are explained and documentation.
Now to your code:
In context.job_queue.run_once(callback_search_msgs, context=update.message.chat_id) you're not telling job_queue when to run the the job. You must pass an integer or a datetime.(date)time object as second argument.
in
def callback_search_msgs(context):
print('In TG, args', context.args)
chat_id = context.job.context
search_msgs(context, chat_id)
you are passing context and chat_id to search_msgs. However, that function treats context as an instance of telegram.ext.CallbackContext, while you pass an integer instead. Also, even if that worked, this would just schedule another job in an infinite loop.
Finally, I don't understand what scheduling jobs has to do with looking up a key in a database. All you have to do for that is something like
def search_msgs(update, context):
userkey = context.args[0]
result = look_up_key_in_db(userkey)
# this assumes that result is a string:
update.effective_message.reply_text(result)
To understand context.args better, have a look at this wiki page.
Disclaimer: I'm currently the maintainer of python-telegram-bot.

How to fix "NameError name 'changePlaying' is not defined

I'm using cogs to shorten and organise my discord bot. However upon attempting a "Events" cog I'm faced with a NameError of changePlaying not being defined despite it literally being about the on_ready command
A: I forgot to import discord.ext and hence imported that.
B: I've tried changing the location of the list of possible statuses in and out of the changePlaying event
PlayingList = [Maximus.py.","!help"]
async def changePlaying(self):
while True:
await self.bot.change_presence(game=Game(name=random.choice(PlayingList)))
await asyncio.sleep(120)
async def on_ready(self):
print('Logged in as')
print(self.bot.user.name)
print(self.bot.user.id)
print('-----------------------------------------')
print('Log in complete')
for x in range(5):
print("")
self.bot.loop.create_task(changePlaying(self))
Well I think it's clear what the expected results are but to clarify the bot is supposed to boot. It does come online and does listen to commands but the status bar does not change
In view of the self argument of the methods, I see they are in the cog. You should use PlayingList as an attribute of the cog, that is, in its __init__ add instead self.PlayingList = ["Maximus.py.","!help"] and then access it through self. in the methods.
And so the answer is : you aren't using methods correctly. You have to do self.changePlaying() not changePlaying(self).
By the way, use a tuple instead of a list if you do not plan to modify it through the execution. And a variable name shouldn't start with a capital letter, as it is commonly reserved to classes. See PEP 8.

How to write python function to test the matched strings (to use for Robot framework keyword)?

I am writing a custom library for robot framework in python. I don't want to use builtin library for some reasons.
My python code :
import os
import re
output = "IP address is 1.1.1.1"
def find_ip():
cmd = 'ipconfig'
output = os.popen(cmd).read()
match1 = re.findall('.* (1.1.1.1).*',output)
mat1 = ['1.1.1.1']
if match1 == mat1:
print "PASS"
In the above program I have written python function to :
Execute a windows command "ipconfig"
Written regular expression to match 1.1.1.1
create a list variable, mat1 = ['1.1.1.1']
Now I want to put condition like, if "match1" and "mat1" are equal my TEST should PASS. else it should fail in Robot framework.
Any one please give idea on how to write python function for this purpose?
Please note I dont want to use "Should Match Regexp" keyword in Robot Framework. Because I know it will do the same whatever I am asking.
To make a keyword pass, you don't need to do anything except return normally to the caller. To fail, you need to raise an exception:
def find_ip():
...
if match1 != mat1:
raise Exception('expected the matches to be similar; they are not")
This is documented in the robot user guide in the section Returning Keyword Status:
Reporting keyword status is done simply using exceptions. If an
executed method raises an exception, the keyword status is FAIL, and
if it returns normally, the status is PASS.
The error message shown in logs, reports and the console is created
from the exception type and its message. With generic exceptions (for
example, AssertionError, Exception, and RuntimeError), only the
exception message is used, and with others, the message is created in
the format ExceptionType: Actual message.

Resources