Handbook
Playwright extension points
This document lists stable seams for implementing and integrating the Playwright extension without broad refactors of forge-lcdl core. Details of module layout are in ARCHITECTURE.md; v1 rules are in READONLY_POLICY.md.
Updated
1. PlaywrightSession protocol (primary seam)
Location (planned): src/forge_lcdl/playwright/session.py
The runtime unknowns are isolated behind a typing.Protocol so core ships before playwright productizes its attach API.
| Method | Purpose | v1 constraint |
|---|---|---|
attach(handle) |
Bind to host-provided session | No login/MFA inside LCDL |
is_attached() |
Introspection | — |
navigate(url) |
Go to allowlisted URL | Policy-checked |
snapshot() |
Return opaque snapshot bytes/structure | Read-only |
download_readonly(ref) |
Local file bytes when permitted | No upload |
evaluate_readonly(script_id, arg=…) |
Vendored read-only JS (SharePoint modern) | No form submit / upload |
scroll_readonly(aggressive=…) |
Virtualized grid scroll step | No click automation |
Extension: Hosts implement the Protocol in forge-lcdl-runtime or application code. Core provides FakePlaywrightSession and EvaluatingFakePlaywrightSession for offline tests. For a live sync Page, use PlaywrightPageSession and register_playwright_page (pip install -e '.[playwright]').
Pattern to mirror: injectable fakes in tests/mcp_client/test_playwright_adapter.py (_FakeHub call log).
2. Policy hooks
Location (planned): src/forge_lcdl/playwright/policy.py
| Hook | Use |
|---|---|
validate_navigation_url(url, source_system) |
Domain allowlists per enterprise source |
assert_readonly_operation(op) |
Block disallowed facade operations |
filter_tool_name(qualified_name) |
When delegating to MCP tools |
PlaywrightPolicy.from_preset(name) |
Operator-facing presets (strict_readonly, per-source packs) |
Reuse (optional): wrap or copy patterns from validate_navigation_url and McpPolicy — do not widen MCP defaults globally.
3. Normalizer plugins (per source)
Location (planned): src/forge_lcdl/playwright/sources/<source>.py
Each source module exports pure functions:
def normalize_snapshot(snapshot: SnapshotPayload, *, options: NormalizeOptions) -> list[UnifiedItem]:
...
| Extension | Contract |
|---|---|
New source_system value |
Add sources/<name>.py + fixtures + tests; extend UnifiedItem source_system literal carefully |
| Tenant variants | Sub-functions or source_kind discriminators; keep main normalizer ≤ tight LoC band |
Normalizers must not call session.navigate — callers orchestrate I/O.
4. UnifiedItem and errors
| Type | Location (planned) | Extension |
|---|---|---|
UnifiedItem |
playwright/types.py |
Bump schema_version for breaking wire changes |
PlaywrightError |
playwright/errors.py |
New kind strings; mirror McpError shape |
Result |
forge_lcdl.result |
All public APIs return Result[T, PlaywrightError] |
Optional JSON Schema: contracts/playwright_<task>/v1/ if governed tasks wrap reads (later PDCA).
5. MCP bridge adapter (optional backend)
Location (planned): src/forge_lcdl/playwright/adapters/mcp_bridge.py
When playwright exposes Playwright MCP-compatible tools:
PlaywrightSession implementation
→ McpHub + PlaywrightAdapter (existing)
→ policy.filter_tool_name before each tool call
→ snapshot bytes fed to normalizers
| Flag / config | Behavior |
|---|---|
| Default | Not imported — optional extra or explicit factory |
FORGE_LCDL_PLAYWRIGHT_MCP=1 (illustrative) |
Enable bridge at runtime |
Extension point: PlaywrightSessionFactory.from_mcp_hub(hub: McpHub, *, policy: PlaywrightPolicy) without adding hard dependency in core pyproject.toml base install (follow [mcp] extra pattern).
Reference: PlaywrightAdapter.fetch_snapshot, MCP client docs.
6. Runtime sibling (forge-lcdl-runtime)
Per runtime boundary:
| Belongs in runtime | Belongs in core playwright |
|---|---|
| Disk session paths, bridge stubs | Protocol, types, policy, normalizers |
| Host-specific attach handles | FakePlaywrightSession |
| Playwright process lifecycle | Optional MCP bridge hook only |
Core documents the hook; runtime repo implements attachment when API is known.
7. Governed LLM tasks (optional, later)
Not required for v1 reads. If product needs LLM-assisted normalization:
| Extension | Touch points |
|---|---|
| New task pack | tasks/packs/install.py — extend playwright_task_pack() or add a separate pack id |
| Registration | tasks/registry.py — register_task |
| Catalog | tasks/catalog_v1.py only if ids join TASK_REGISTRY_V1 |
| Contracts | src/forge_lcdl/contracts/playwright_*/v1/contract.md + JSON sidecar |
Keep pw_* Playwright LLM tasks separate — different inputs/outputs and threat model.
8. File format extraction
Location (planned): src/forge_lcdl/playwright/sources/files.py
| Extension | Mechanism |
|---|---|
| New mime/type | Register extractor extract_<format>(bytes) -> Result[str, PlaywrightError] |
| Heavy deps | pyproject.toml optional extra playwright-files |
Extractors receive bytes only — no network fetch inside core.
9. Pytest and CI extension
| Mechanism | Planned detail |
|---|---|
Marker playwright |
Unit tests with fake session only |
Marker playwright_live |
Skipped unless FORGE_LCDL_PLAYWRIGHT_LIVE=1 |
| Fixtures | tests/playwright/fixtures/<source>/ HTML/JSON |
| Env | FORGE_LCDL_PLAYWRIGHT_SESSION_ID (documented in future TESTING.md) |
Default pyproject.toml addopts must remain free of live playwright requirements.
10. Documentation and handbook
| Extension | Location |
|---|---|
| Architecture | ARCHITECTURE.md |
| Policy | READONLY_POLICY.md |
| PDCA map | PDCA_IMPLEMENTATION_MAP.md |
| Handbook nav | docs/index.md — add when content stabilizes |
| Cursor guardrails | .cursor/rules/lcdl-playwright-pdca.mdc |
11. SharePoint modern bridge (KA-aligned, read-only)
| Module | Role |
|---|---|
connectors/sharepoint_urls.py |
normalize_sharepoint_library_url, allows_read_only_sharepoint_navigation |
connectors/sharepoint_modern.py |
SharePointPageHealth, HTML / viewport row parsers |
connectors/sharepoint_scroll.py |
harvest_sharepoint_links_readonly |
scripts/sharepoint_readonly.py |
EvaluateScriptId, vendored evaluate JS |
Governed tasks (canonical): sharepoint_normalize_library_url, sharepoint_probe_library_health, sharepoint_list_library_files_viewport | scroll | columns | rest. Deprecated aliases: playwright_normalize_sharepoint_url, playwright_sharepoint_page_health, playwright_read_items + source_kind. See SHAREPOINT-LIBRARY-READ-STRATEGIES.md.
Tests: tests/playwright/test_sharepoint_urls.py, test_sharepoint_modern.py, test_sharepoint_scroll.py, test_sharepoint_reader_modern.py, test_playwright_sharepoint_tasks.py.
12. What not to extend (v1)
Avoid using these as playwright extension points:
| Area | Reason |
|---|---|
runner.py dispatch |
Keep read APIs separate from LLM task dispatch |
transport.py |
Unrelated to browser session reads |
| Cookie/storage APIs on MCP | Denied by policy; not wrapped for playwright |
Top-level forge_lcdl.__all__ |
Wait until API stable |
Implementation order (PDCA)
- Types + errors +
FakePlaywrightSession - Policy module
- One source normalizer per step
- Optional MCP bridge
- Optional governed tasks + catalog entries
See PDCA_IMPLEMENTATION_MAP.md for the full step list.