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.pyregister_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)

  1. Types + errors + FakePlaywrightSession
  2. Policy module
  3. One source normalizer per step
  4. Optional MCP bridge
  5. Optional governed tasks + catalog entries

See PDCA_IMPLEMENTATION_MAP.md for the full step list.

References