external-site-profile-learning
Use this skill when investigating, adding, validating, or debugging external website profiles for the 99idea Playwright browser demo. It teaches how to probe selectors, classify failure modes, add config-driven profiles, and validate both heuristic and Gemini flows.
External site profile learning skill
Use this skill when working on the target Playwright browser project (for example, the 99idea demo project).
This project is a Playwright-based browser demo that supports config-driven external site profiles through:
scenarios\site-profiles.jsonsrc\site-profiles.jssrc\planner.jssrc\llm-planner.jssrc\run-demo.js
What this skill is for
Use this skill when the task involves any of the following:
- adding a new external site profile
- debugging why an external site passes heuristics but fails with Gemini
- turning a random website experiment into a stable config-driven profile
- identifying whether a site is suitable for the default regression matrix
- distinguishing selector problems from login, anti-bot, or hidden-UI problems
Core rules
- Prefer URL or title verification for external sites whenever possible.
- Prefer stable, unique selectors over generic selectors like
buttonor[name="q"]. - If a site profile exists, the final plan should use the profile's exact
searchInputSelectorandsubmitSelector. - Keep GitHub out of the default regression matrix. It works, but repeated probing can trigger rate limits.
- Classify the site's execution tier before writing a browser profile:
- If opencli already supports the site, prefer an opencli-based workflow over a selector-based profile
- If the site has a public API, prefer fetch over browser automation
- Only write a browser profile when no higher-tier execution path exists
- Treat these as different failure classes:
- selector ambiguity
- hidden or disabled controls
- opener-required search UI
- SPA shell (no usable DOM without full JS rendering)
- login gates
- anti-bot / rate-limit behavior
- heavy pages that stall on
load
Dual-engine awareness (inspired by opencli)
When stabilizing a site, consider whether the profile should be:
- Data-only (YAML-like): selector + verification config in JSON — for traditional DOM-based sites
- Runtime-aware: includes execution path recommendation (opencli / fetch / browser) — for modern platforms
A good profile should record not just how to interact with the UI, but also what the best execution path is.
Standard workflow
1. Probe the site
Open the homepage in Playwright and inspect:
- search inputs
- submit buttons
- search opener buttons or icons
- whether inputs are visible, enabled, and editable
- whether there are duplicate controls in headers, footers, drawers, or overlays
Record:
- selector candidates
- whether search is immediately usable
- whether search is hidden/disabled until another action happens
- whether the site supports a deterministic URL check after search
2. Classify the site
Use this decision process:
- If a visible input and a stable submit control exist, the site is a good normal profile candidate.
- If the site requires an opener interaction but still fits the current action model, prefer a more specific selector strategy or a special-case scenario.
- If the site requires behavior beyond the current action model, mark it blocked or incomplete rather than forcing a brittle profile.
- If the site is gated by login or aggressively rate-limited, do not add it to the default regression matrix.
3. Add the profile
When the site is a good fit, add a data-only profile to scenarios\site-profiles.json with:
namehostnamessearchInputSelectorsubmitSelectorverificationModeexpectedUrlTemplatewhen URL verification is availableexecutionTier— recommended execution path:opencli,fetch, orbrowseropencliCommand— if opencli supports it, the exact command templateauthLevel—PUBLIC,COOKIE,HEADER, orLOGIN-GATED
Keep the behavior in code and the site data in JSON.
Recommended profile-writing rules:
- use exact selectors, not broad selectors that only happen to work once
- qualify selectors with a container when header and footer controls are duplicated
- prefer
verificationMode: "url"when the site has a deterministic query string - prefer
verificationMode: "title"when the search result page has a stable title change - do not force opener-only or login-gated flows into a normal default profile
4. Validate both planning modes
Validate in this order:
- Heuristic run
- Gemini / LLM run
Use commands like:
npm run validate:external:core
npm run validate:external:popup
npm run validate:external:heavy
npm run validate:external
npm run validate:external:llm:core
npm run validate:external:llm:popup
npm run validate:external:llm:heavy
npm run validate:external:llm
node src\run-demo.js --allow-external --url "https://example.com/" --goal "Search Example for AI and verify that the URL includes q=AI."
node src\run-demo.js --scenario <temp-scenario.json> --planner llm
Before Gemini validation, ensure:
GEMINI_API_KEYis availableBROWSER_USE_LLM_PROVIDER=gemini
If the site hangs on full load, use a temporary scenario with:
waitUntil: "domcontentloaded"postNavigationLoadState: "domcontentloaded"
5. Record the failure mode if it does not fit
Use these interpretations:
-
strict mode violation / multiple matches
The selector is too broad. Add a stronger container-qualified selector. -
timeout waiting for visible or editable
The control is hidden, disabled, off-screen, or not opened yet. -
type succeeds but Enter does not navigate
The site likely needs a specific submit control instead of Enter. -
works only with a special opener path
Consider a special scenario instead of a default profile. -
rate limit / anti-bot / login gate
Do not force it into the stable regression matrix.
Known good profiles
These are already proven patterns in this project:
Wikipedia
- input:
#searchInput - submit:
#search-form button[type="submit"] - verification: title
Python.org
- input:
#id-search-field - submit:
#submit - verification: URL contains
q={searchTerm}
arXiv
- input:
form.mini-search input[name="query"] - submit:
form.mini-search button - verification: URL contains
query={searchTerm}
YouTube
- input:
input[name="search_query"] - submit:
button.ytSearchboxComponentSearchButton - verification: URL contains
search_query={searchTerm}
CNN
- input:
#pageFooter input[name="q"] - submit:
#pageFooter button.search-bar__submit - verification: URL contains
q={searchTerm} - runtime hint: prefer
domcontentloadedbecause the homepage may stall on fullload
Bilibili
- input:
#nav-searchform input.nav-search-input - submit:
#nav-searchform .nav-search-btn - verification: URL contains
keyword={searchTerm} - runtime hint: the search flow opens a popup / new tab, so popup following must remain enabled in the runner
Special case
GitHub
GitHub works through the search palette, but repeated probing can trigger secondary rate limits.
Recommended pattern:
- click
button[aria-label="Search or jump to…"] - type into
#query-builder-test - press
Enter - verify URL contains
q={searchTerm}
Do not treat GitHub as a normal default regression target.
Blocked or incomplete sites
BBC
- current state: blocked
- reason: the discovered input is hidden and disabled until additional UI state changes occur
Tencent News
- current state: blocked
- reason: the homepage shows a search button but no directly usable search input in the default state
Zhihu
- current state: blocked
- reason: homepage flow goes through sign-in, so anonymous search is not a clean default regression target
Implementation notes for future agents
- When a profile exists, normalize LLM-generated
typeandclicksteps back to the exact profile selectors. - If a site has duplicate header/footer search controls, prefer container-qualified selectors.
- If an external site is flaky under full page load, fix the runtime first before assuming the selectors are wrong.
- Keep default validation conservative. A smaller stable matrix is better than a noisy one.
- Use grouped validation runs to isolate profile regressions by class:
core,popup, andheavy. - For each validation report, include concrete evidence (URL/title signal, selector used, and whether popup switching occurred).