Handbook
Prompt: Teams live read tests (1 message vs 50) — Edge CDP
Use this prompt in a new Cursor chat to implement and run governed LCDL + Cockpit tests for Microsoft Teams (chats, channels, thread structure), mirroring the Outlook inbox chunk work.
Updated
Goal
Add read-only Playwright coverage for Teams on a pre-authorized Edge session (CDP), with scenario tests that:
- Read exactly 1 message/post (newest or first visible in the target list).
- Read up to 50 messages/posts in one chunk (scroll-harvest), with the same field parity as the 1-message case for the top row.
- Map results through Cockpit using the same bridge pattern as
cockpit_server/lcdl_outlook_bridge.py→harvest_inbox_chunk_via_lcdl/run_outlook_sync(Teams equivalent).
Do not use Graph APIs; use OWA/Teams web UI only.
Environment (operator)
- Edge with remote debugging, signed into Teams and Outlook if needed.
- Default CDP:
http://127.0.0.1:9223(adjust if different). forge-lcdl:pip install -e ".[dev,playwright]"in.venv.forge-cockpit-web:pip install -e .withforge-lcdlon path.
export FORGE_LCDL_PLAYWRIGHT_LIVE=1
export FORGE_LCDL_PLAYWRIGHT_CDP_URL=http://127.0.0.1:9223
export COCKPIT_OUTLOOK_LIVE=1 # reuse live flag pattern or add COCKPIT_TEAMS_LIVE=1
export COCKPIT_CDP_URL=http://127.0.0.1:9223
Existing code to extend (do not reinvent)
| Area | Location |
|---|---|
| Teams connector (HTML snapshot parsers) | forge-lcdl/src/forge_lcdl/playwright/connectors/teams.py |
| Outlook chunk + merge pattern | outlook_chunk.py, outlook_list_row.py, outlook_readonly.py |
| Governed task pattern | tasks/outlook_read_v1.py, tasks/packs/install.py |
| Cockpit bridge | forge-cockpit-web/cockpit_server/lcdl_outlook_bridge.py |
| Live CDP fixture | tests/integration/ms_playwright.py, test_outlook_inbox_chunk_cdp_live.py |
| Field compare script | scripts/compare-outlook-chunk-message-fields.py |
Deliverables
1. LCDL Teams read tasks (v1)
Implement governed tasks (contracts + run_*_v1) analogous to Outlook:
| Task | Purpose |
|---|---|
teams_probe_health |
Teams app loaded, signed in, list surface visible |
teams_list_chats |
1:1/group chat list inventory |
teams_list_channels |
Team/channel tree or channel list for a team |
teams_list_chat_messages_chunk |
Chunked harvest in a chat (chunk_size 1 / 50) |
teams_list_channel_posts_chunk |
Chunked harvest in a channel |
teams_read_thread_replies |
Replies under a post (structure for threading) |
Use evaluate_readonly scripts (new teams_readonly.py) where DOM is stable; keep HTML snapshot parsers as fallback.
2. UnifiedItem fields (minimum per message/post)
Ensure wire + Cockpit mapping includes consistently:
item_id,thread_id/reply_to_id(if visible)title(subject or channel post title)body_preview(list row); optionalbody_textafter thread openactor(author display name)container(e.g.Chat/{name}orChannel/{team}/{channel})url(deep link when extractable from DOM)created_at(ISO only) +extra.received_hintfor non-ISO UI datessource_kinddistinguishing chat vs channel vs reply
Apply merge_*_list_row semantics so chunk_size=1 and chunk_size=50 return the same top-row fields (copy outlook_list_row.py pattern).
3. Tests
| Test file | Mode |
|---|---|
tests/playwright/test_teams_chunk_scenarios.py |
Offline fakes: 1 + 50, task + connector |
tests/integration/test_teams_chunk_cdp_live.py |
Live CDP; skip without FORGE_LCDL_PLAYWRIGHT_LIVE=1 |
forge-cockpit-web/tests/test_teams_inbox_chunk_scenarios.py |
Bridge offline |
forge-cockpit-web/tests/e2e/test_lcdl_teams_bridge_guided.py |
Live guided (COCKPIT_TEAMS_LIVE=1) |
Parity assertion: items[0] from chunk_size=1 equals items[0] from chunk_size=50 for the same session/URL.
4. Cockpit sync
- Add
lcdl_teams_bridge.py+ wireconnectors/teams.py(or messaging sync) to callteams_list_*_chunkin a loop with checkpoint file undervar/data/teams_chunk_checkpoints/. - Reuse
wire_item_to_cockpit_fieldsor a Teams-specific mapper that shares datetime coercion (outlook_coerce_received_at→ rename to sharedcoerce_ui_datetimeif needed).
5. Scripts
scripts/run-teams-chunk-scenarios.sh(offline + live,.venvpython, CDP 9223 default)scripts/compare-teams-chunk-message-fields.py(dump JSON report like Outlook)
Acceptance criteria
- [ ] Offline pytest green for 1 + 50 scenarios and parity test.
- [ ] Live: 1-message test passes on signed-in Teams; 50-message test passes or skips with clear message if fewer rows visible.
- [ ] Compare script reports no
first_message.*discrepancies between chunk 1 and chunk 50 for the same topitem_id. - [ ] Cockpit bridge parity test passes offline; live chunk e2e passes for
chunk_size=1. - [ ] Document entry URLs in test docstrings (e.g.
https://teams.microsoft.com/v2/+ chat deep link envFORGE_LCDL_TEAMS_CHAT_URL).
Run loop (agent)
Fix failures up to 10 attempts per test; after code changes run:
cd forge-lcdl
.venv/bin/python -m pytest tests/playwright/test_teams_chunk_scenarios.py -q
FORGE_LCDL_PLAYWRIGHT_LIVE=1 FORGE_LCDL_PLAYWRIGHT_CDP_URL=http://127.0.0.1:9223 \
.venv/bin/python -m pytest tests/integration/test_teams_chunk_cdp_live.py -v --tb=short
Reference: one-message field dump (Outlook, live)
After Outlook fixes, a single inbox row exposes (LCDL wire → Cockpit):
| LCDL | Example / notes |
|---|---|
item_id |
OWA data-convid stable id |
thread_id |
Usually same as item_id for mail |
title |
Subject line |
actor.display_name |
Sender |
body_preview |
List preview (not full MIME body) |
container |
Mail/Inbox |
url |
Often mailbox URL until item deep link is parsed |
created_at |
ISO from <time datetime> when present |
extra.received_hint |
e.g. 5/28/2026 when not ISO |
Cockpit title_guess, sender_guess, body, owa_item_id_raw, received_at, received_hint |
Via wire_item_to_cockpit_fields |
Full body requires a follow-up task (outlook_read_conversation_thread); list chunk is intentionally preview-only.
Policy (mandatory)
- Read-only only: allowed operations are
navigate,snapshot,evaluate_readonly, andscroll_readonly. - Forbidden:
click(except future allowlisted read navigation),type,fill,send,reply,call, upload, delete, reactions, or any compose/send UI automation. - Tasks and contracts must set
"read_only": truein capabilities. - Live tests must skip (not fail) when zero rows are visible — operator opens a chat with history first (
FORGE_LCDL_TEAMS_CHAT_URL). - No secrets in logs or committed JSON reports.
- pytest
--import-mode=importlibis already inpyproject.tomlto avoidtests/playwrightvs pipplaywrightclash; keep that for new Teams tests undertests/playwright/.