An integration test and automation library for Telegram Bots based on Telethon.
Test your bot in realtime scenarios!
Features • Requirements • Installation • Quick Start Guide
- 👤 Log into a Telegram user account and interact with bots or other users
- ✅ Write realtime integration tests to ensure that your bot works as expected!
- ⚡️ Automate any interaction on Telegram!
- 🛡 Fully typed for safety and autocompletion with your favorite IDE
- 🐍 Built for modern Python (3.10+) with high test coverage
- A Telegram API key.
- A user session (seeing things happen in your own account is great for getting started)
- But: Python 3.10 or higher!
from telethon import TelegramClient
# These example values won't work. You must get your own api_id and
# api_hash from https://my.telegram.org, under API Development.
api_id = 12345
api_hash = '0123456789abcdef0123456789abcdef'
client = TelegramClient('session_name', api_id, api_hash)
client.start()A basic understanding of async/await and asynchronous context managers is assumed, as TgIntegration heavily relies on the latter to automate conversations.
pip install git+https://github.com/elebur/tgtestkit.git
To follow this guide you have to run the test bot
Note
Requirements are the same as described above and you would also need a bot token
After configuring a Telethon user client,
let's start by creating a BotController:
from telethon import TelegramClient
from tgtestkit import BotController
BOT_USERNAME = "" # A username of the bot under the test.
API_ID = 1234 # Telegram API ID.
API_HASH = "" # Telegram API hash.
client = TelegramClient(
session="test_account", # this value will be used as a file name for a session file.
api_hash=API_HASH,
api_id=API_ID,
)
controller = BotController(
client=client, # This assumes you already have a Pyrogram user client available
target_username=BOT_USERNAME, # The @username of the bot under test
max_wait=8, # Maximum timeout for responses (optional)
raise_no_response=True, # Raise `InvalidResponseError` when no response is received (defaults to True)
global_action_delay=2.5, # Choosing a rather high delay so we can observe what's happening (optional)
)
Let's clear chat history
Warning
This action will delete all chat history.
# You might need to call this method multiple times to clear the whole history,
# because it deletes only 100 hundred messages per request.
await controller.delete_messages() # Start with a blank screen (⚠️)Now, let's send /start to the bot and wait until exactly three messages
have been received by using the asynchronous collect_messages context manager:
async with controller.collect_messages(count=3, max_wait=4, raise_=False) as reply_keyboard_response:
await controller.send_command("start")
assert reply_keyboard_response.has_messages # Check that the `Response` has messages.
assert len(reply_keyboard_response.messages) == 3 # Ensure that exactly three messages were received, bundled under a `Response` objectExamining the buttons in the response...
# The response has three `ReplyKeyboard`s,
# because each of the messages has its own keyboard.
assert len(reply_keyboard_response.get_reply_keyboards()) == 3
# But only the most recent ReplyKeyboard is shown in the Telegram UI,
# and this the keyboard that will be returned by the
# `Response.reply_keyboard` property.
assert reply_keyboard_response.reply_keyboard.buttons_count == 2We can also press keyboard buttons, for example based on a regular expression:
reply_keyboard = reply_keyboard_response.reply_keyboard
async with controller.collect_messages(count=2, max_wait=4, raise_=False) as click_reply_resp:
# Based on the regular expression.
await reply_keyboard.click(pattern=r"Show the Inline Keyboard")
# Based on the index.
# await keyboard.click(index=0)Clicking inline keyboards pretty much the same:
inline_keyboard = click_reply_resp.get_inline_keyboards()[-1]
async with controller.collect_messages(count=1, max_wait=4, raise_=False) as click_inline_resp:
# Now let's click by index
await inline_keyboard.click(index=0)Let's ensure that we caught the edited message:
assert click_inline_resp.messages[0].message == "Hm, it seems you have pressed the inline 'Button 1'"Now let's click the reply keyboard button again.
async with controller.collect_messages(count=2, max_wait=4, raise_=False) as click_reply_resp:
response_reply = await reply_keyboard.click(pattern=r"Show the Inline Keyboard")When the controller clicks the reply button then click method returns Message object
from telethon.types import Message
assert isinstance(response_reply, Message)After clicking an inline button click returns BotCallbackAnswer (Telegram docs. Telethon docs)
inline_keyboard = click_reply_resp.get_inline_keyboards()[-1]
async with controller.collect_messages(count=1, max_wait=4, raise_=False) as click_inline_resp:
response_inline = await inline_keyboard.click(index=1)
from telethon.tl.types.messages import BotCallbackAnswer
assert isinstance(response_inline, BotCallbackAnswer)So what happens when the peer fails to respond?
The following instruction will raise an ExpectationError after controller.max_wait seconds.
This is because we passed raise_no_response=True during controller initialization.
from tgtestkit import ExpectationError
try:
async with controller.collect_messages():
await controller.send_command("ayylmao")
except ExpectationError:
print("No response")Let's explicitly set raise_ to False so that no exception occurs:
async with controller.collect_messages(raise_=False) as response:
# You can use the `TelegramClient` object directly.
await client.send_message(controller.target_username, "Henlo Fren")In this case, tgtestkit will simply emit a warning, but you can still assert
that no response has been received by using the has_messages property:
assert not response.has_messages