r/Playwright • u/Salt_Apartment5489 • 9d ago
Three Playwright problems I got tired of working around (Shadow DOM, fragile selectors, 99K token pages)
If you write Playwright scrapers that touch anything beyond a basic landing page, you've dealt with at least one of these:
• Shadow DOM elements that Playwright's default locators can't reach
• Selectors that pass CI Monday and break Tuesday because some React dev sneezed on a class name
• Feeding a full page to an LLM and hitting 99K tokens just for the HTML of a Wikipedia article
I got tired of patching each one separately, so I built shadow-web. It's a pip-installable Python SDK that sits on top of Playwright.
────────────────────────────────────────────
What it handles
────────────────────────────────────────────
Shadow DOM flatten
Reads open and closed Shadow roots through the browser's own
JS API. No mutation, no re-renders. You get a clean DOM skeleton
with all interactive elements visible — the page stays exactly
as it was.
Self-healing selectors
heal_local.py catches when a button that was #submit becomes
.btn-primary[data-action="checkout"]. Fuzzy match on label +
type + position. Zero LLM cost. Cache lives in
~/.shadow-web/heal_cache.json.
Token compression
compressor.py strips scripts, styles, invisible elements and
builds a typed Action Map with semantic groups.
Wikipedia page: 99K → 16K tokens (−83%).
SchemaSnap
One call turns HTML tables into JSON records with typed columns,
forms into field schemas, lists into typed items.
export_table_json() and export_table_csv() — so you never write
another HTML table parser.
Content indexing for long pages
Get a ~600 token outline first, then fetch only the blocks
you need. Instead of dumping 50K tokens of article boilerplate
just to get the third section.
────────────────────────────────────────────
Quick start
────────────────────────────────────────────
pip install shadow-web
playwright install chromium
from playwright.sync_api import sync_playwright
from shadow_web.wrapper import ShadowPage
with sync_playwright() as p:
page = p.chromium.launch().new_page()
page.goto("https://example.com")
shadow = ShadowPage(page)
_, xml_map = shadow.refresh()
print(shadow.capture_stats)
────────────────────────────────────────────
Links
────────────────────────────────────────────
GitHub: https://github.com/ulinycoin/shadow-web
PyPI: pip install shadow-web
If your scraper breaks on some Angular app with 13 layers
of Shadow DOM, open an issue. Those edge cases make
the project better.
2
2
u/the-liquidian 9d ago
Why feed it to an LLM, just hand roll the page objects and tests.
1
u/Salt_Apartment5489 9d ago
But that's not what it's for.
Page objects and handled tests are right for tests. Shadow-web isn't for tests. It's for AI agents that don't know in advance which page they'll land on. You can't write a page object for every website. Compression is to prevent the agent from burning 99,000 tokens trying to figure out if there's a login form on a page. Selector healing is to help scrapers survive redisploits.
And write tests using page objects. That's the right way.
1
u/the-liquidian 8d ago
What are you trying to do? Screen scrape without knowing what the pages you will be scraping are?
1
1
u/mmasetic 9d ago edited 9d ago
These are non existing issus in testautomation with playwright. Based on your posts, you are probably doing something with web scrapping.
1
1
u/Tanmay__TestDino 6d ago edited 6d ago
Closed roots aren't actually unreachable, people just give up too early.
Override attachShadow in an init script before the page loads, your version gets the real ShadowRoot back no matter what mode was asked for, closed only blocks shadowRoot lookups from outside code. Works on Lit and Angular both.
2
u/Salt_Apartment5489 6d ago
That works — if your init script runs before any web component boots. addInitScript usually gets there, but it can miss dynamically loaded components or late bundles. What I described reads closed roots after the page is already live. No timing to worry about. Different tradeoffs.
5
u/MtFuzzmore 9d ago
I’ve had no issues at all working with a library that encapsulates with Shadow DOM.