Real-user Telegram bot E2E testing over MTProto.
This tool signs in as a normal Telegram user, sends messages and media to a bot, clicks inline buttons, and records the chat the way a user sees it. It exists for cases where bot-side tests are not enough and you need the real pinned dashboard, transient drafts, service messages, and visible history.
- MTProto user session, not Bot API shortcuts
- one JSONL command format for interactive mode and saved scenarios
- visible
ChatStatesnapshots, diffs, pinned summary, and transcript artifacts - built-in Shelfy suite, text-matrix runner, and rate-sweep tooling
- one runtime lock per Telegram session to prevent parallel-account corruption
cp .env.example .env
make setup
make doctor
make login
make interactiveThe CLI auto-loads .env from the current working directory and falls back to the repo root. You do not need to source .env manually.
For the built-in Shelfy suite:
make fixtures
make run-suite CHAT=@your_bot_usernamemake run-suite assumes a live local Shelfy dev stack with the non-production control API reachable at http://127.0.0.1:8081.
Useful next reads:
make help
make setup
make test
make doctor
make login
make chats CHAT_GROUPS=1 CHAT_FILTER=TrackMate CHAT_TOPICS=1 CHAT_ADMINS=1
make interactive
make run-scenario SCENARIO=examples/suite/03-text-fast-path-complete.jsonl CHAT=@your_bot_username
make run-text-matrix CHAT=@your_bot_username CASES=/absolute/path/to/cases.txt
make fixtures
make run-suite CHAT=@your_bot_username
make rate-sweep CHAT=@your_bot_username
make cleanmake clean removes generated content under artifacts/ and the default .sessions/runtime.lock, but keeps the saved MTProto session at .sessions/user.json.
Supported actions:
select_chatsend_textsend_bot_noteedit_textsend_photosend_photo_groupsend_voicesend_audiosend_documentclick_buttondelete_visible_messageswaitsleepdump_stateassert_visible_textassert_not_visible_text
Interactive mode and saved scenarios use the same command contract and the same execution path.
For Telegram forum groups, commands can target a topic by passing the numeric topic starter ID as either message_thread_id or its short alias topic. A select_chat with a topic keeps that topic selected for following commands until another chat or topic is selected. Scenarios without a topic continue to target the normal chat history.
Use tg-e2e-tool chats --groups --filter TrackMate --topics --admins to find the MTProto target id, forum topic ids, and bot admin topic-management rights for scenario files. The target= value is the chat value accepted by select_chat; topic_id= is the value accepted by message_thread_id/topic.
For noisy chats with repeated inline buttons, click_button can be scoped with message_text. The button is clicked only on a visible message whose text contains that fragment.
edit_text edits a visible outgoing user message. Scope it with message_text
when several recent outgoing messages are present; message_offset selects the
newest matching message by default.
send_bot_note sends a visible explanatory note through the Bot API instead of
the MTProto user session. This keeps demo annotations from being processed as
user input by the bot under test. It requires TG_E2E_BOT_TOKEN and
TG_E2E_BOT_CHAT_ID.
sleep pauses for timeout_ms without waiting for a chat change.
assert_visible_text and assert_not_visible_text check the current visible
snapshot and fail the scenario when the expected text state is not true.
For reusable test forum groups, delete_visible_messages removes the currently visible non-service messages from the selected chat/topic. It keeps the topic starter, pinned message, and Telegram service messages, so cleanup scenarios can preserve topic structure and pinned intro/control messages. Use max_batches to keep walking older history windows; the default is 8 batches. If a product intentionally leaves its intro unpinned, pass keep_oldest: true to keep the earliest ordinary visible message too. Pass message_text to delete only visible messages containing that text; this is useful for temporary demo notes that must disappear after the relevant step.
{"id":"select","action":"select_chat","chat":"@your_forum_group","message_thread_id":12345}
{"id":"start","action":"send_text","text":"/start"}
{"id":"fix_typo","action":"edit_text","message_text":"/start","text":"/start now"}
{"id":"open_report","action":"click_button","button_text":"🏁 Report","message_text":"Task title"}
{"id":"wait","action":"wait","timeout_ms":15000}
{"id":"assert_updated","action":"assert_visible_text","text":"Updated result"}
{"id":"cleanup","action":"delete_visible_messages","max_batches":8}
{"id":"cleanup_note","action":"delete_visible_messages","message_text":"1/6 Demo note","max_batches":2}The built-in suite in examples/suite/ is kept in sync with current Shelfy product flows. It currently covers:
/startidempotence and/dashboardrecovery- dashboard navigation and settings actions
- text fast-path draft creation and confirm
- incomplete draft name editing
- repeated date editing with invalid and valid input
- unsupported document upload
- unsupported photo fail-closed handling
- product close, discard, and delete flows
- voice upload and cleanup back to home
- audio upload and cleanup back to home
- blocked save on incomplete drafts and draft cancellation
- timed digest setup, observation, close, and reconciliation cleanup
- dashboard pagination and back-to-origin navigation
- rapid same-user interaction ordering around settings and draft editing
Run the full suite with:
make fixtures
make run-suite CHAT=@your_bot_usernameRun one focused scenario with:
make run-scenario SCENARIO=examples/suite/05-edit-date-valid-invalid.jsonl CHAT=@your_bot_usernameIf you want a fresh home dashboard before a targeted scenario, prepend the helper fragment:
CHAT=@your_bot_username \
go run ./cmd/tg-e2e-tool run-scenario \
examples/helpers/00-home-ready.jsonl \
examples/suite/05-edit-date-valid-invalid.jsonlmake fixtures generates:
artifacts/fixtures/e2e-photo.pngartifacts/fixtures/e2e-receipt.pngartifacts/fixtures/e2e-blank-photo.pngartifacts/fixtures/e2e-voice.oggartifacts/fixtures/e2e-audio.mp3artifacts/fixtures/e2e-document.txt
After every run-scenario or run-suite, the tool refreshes rolling transcript artifacts under artifacts/transcripts/:
last-run-artifacts.jsonlast-run-summary.jsonlast-run-summary.txtlast-failure.jsonlast-failure.txt
Recommended triage order:
- open
artifacts/transcripts/last-run-summary.txt - if failed, open
artifacts/transcripts/last-failure.txt - only then open per-scenario compact or raw transcripts
cmd/tg-e2e-tool— main CLIcmd/fixturegen— standalone PNG fixture generatorexamples/suite— current bundled Shelfy suiteexamples/helpers— reusable helper fragmentsexamples/bench— benchmark and rate-sweep scenariosinternal/protocol— JSONL command/event contractinternal/engine— action execution pathinternal/state— visible chat snapshot/diff modelinternal/mtproto— Telegram transport and sync behavior
- The tool is intentionally user-like: no Bot API shortcuts and no test-only bot backdoors.
- The bundled scenarios keep an explicit
select_chatstep and use the placeholder@your_bot_username;run-scenario,run-suite, andrate-sweepmaterialize it fromCHAT=@your_bot_username. - Relative media paths resolve relative to the scenario file. Paths starting with
@fixtures/resolve to the tool-ownedartifacts/fixtures/directory. send_photo_groupsends one Telegram album/media group with 2-10 photo paths and one caption applied to every item:{"id":"album","action":"send_photo_group","paths":["@fixtures/e2e-photo.png","@fixtures/e2e-receipt.png"],"caption":"общая подпись"}delete_visible_messagesuses the MTProto user account. In supergroups, that account needs enough rights to delete bot messages if the cleanup should remove both test-user messages and bot replies.- Do not run
login,interactive,run-scenario,run-block,run-text-matrix, orrate-sweepin parallel with the same Telegram account.