Skip to content

An integration test and automation library for Telegram Bots based on Telethon. Test your bot in realtime scenarios!

License

Notifications You must be signed in to change notification settings

elebur/tgtestkit

Repository files navigation

TgTestKit

An integration test and automation library for Telegram Bots based on Telethon.
Test your bot in realtime scenarios!

Note

The origin of the TgTestKit is TgIntegration

The link to the commit TgTestKit was forked from.


FeaturesRequirementsInstallationQuick Start Guide

Features

  • 👤 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

Prerequisites

Same as Telethon:

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.

Installation

pip install git+https://github.com/elebur/tgtestkit.git

Quick Start Guide

Setup

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` object

Examining 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 == 2

We 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)

Error handling

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

About

An integration test and automation library for Telegram Bots based on Telethon. Test your bot in realtime scenarios!

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages