Skip to content

feat: Playwright skills (screenshot, browser) + scrape backend - #53

Merged
5uck1ess merged 5 commits into
mainfrom
feat/playwright-skills
Apr 10, 2026
Merged

feat: Playwright skills (screenshot, browser) + scrape backend#53
5uck1ess merged 5 commits into
mainfrom
feat/playwright-skills

Conversation

@5uck1ess

Copy link
Copy Markdown
Owner

Summary

  • Add screenshot skill — capture web pages as PNG/PDF via Playwright (full-page, viewport, element, device emulation)
  • Add browser skill — general browser automation (click, fill, navigate, extract, codegen, E2E testing)
  • Extend scrape skill with Playwright as a new backend for JS-heavy pages (slotted between Jina Reader and Firecrawl)

Both new skills are auto-invoked via description triggers, not slash commands. Playwright is an optional dependency — users install it themselves with npx playwright install chromium. Skills detect availability and provide install instructions if missing.

Why Playwright

Researched 12 browser automation tools. Playwright wins on:

  • Real CLI (@playwright/cli) built for AI agent use cases
  • Full coverage: scrape, screenshot, PDF, form filling, codegen, multi-step automation, E2E testing
  • Multi-browser (Chromium, Firefox, WebKit)
  • Free, open-source, no API keys
  • Microsoft-maintained

Full research: homebase/docs/research/browser-automation-for-devkit.md

Test plan

  • npx playwright --version detection works in all three skills
  • scrape still falls back gracefully when Playwright isn't installed
  • screenshot captures viewport, full-page, element, PDF modes
  • browser codegen, form-fill, and data-extraction flows work
  • URL validation rejects file://, private IPs, credential-in-URL attacks
  • README updates render correctly in marketplace listing

…crape backend

Add two new auto-invoked skills that leverage Playwright as an optional dependency:

- screenshot: capture web pages as PNG/PDF (full-page, viewport, element, device emulation)
- browser: general browser automation (click, fill, navigate, extract, codegen, E2E testing)

Also add Playwright as a new backend in the existing scrape skill, slotted
between Jina Reader and Firecrawl for JS-heavy pages without paid API dependencies.

Playwright stays optional — users install it themselves via `npx playwright install chromium`.
Skills check for availability and provide install instructions when missing.

Research in homebase/docs/research/browser-automation-for-devkit.md covers the
full landscape comparison (Playwright, shot-scraper, Puppeteer, Selenium, Rod,
Crawlee, Skyvern, Browser Use, Stagehand, Steel, Browserbase, AgentQL).
Critical:
- scrape: remove fabricated `npx playwright cr -e` subcommand (not a real
  Playwright CLI command) — replace with file-based .mjs script that reads
  the URL from process.argv, invoked via shell variable. Kills shell
  injection and script injection via URL contents.
- scrape: expand URL validation to match screenshot's full blocklist
  (include 169.254.169.254 cloud metadata IP). Require reject-and-stop
  reporting in batch mode.
- browser: expand one-line URL validation to full blocklist matching
  scrape and screenshot. Add cloud metadata IP. Require reject-and-stop.

Important:
- scrape: fallback chain contradicted itself (Jina→Playwright→WebFetch vs
  Jina→Playwright→Firecrawl→WebFetch). Now single linear chain:
  Jina → Playwright → Firecrawl → WebFetch. Require reporting which
  backend actually served the result + why prior backends failed, so
  silent content substitution (paywall/cookie-wall) can't sneak through.
- scrape: remove paywall contradiction — listed Playwright as good for
  paywalled content while Rules forbade scraping paywalled content.
- screenshot: element-selector block self-contradicted (told agent not
  to interpolate into -e while showing a node -e example with
  interpolation placeholders). Rewrite as file-based script reading from
  process.argv with shell variable pass-through.
- browser: wrap browser.close() in its own try/catch inside finally so
  a close-time crash can't mask the real error via JS exception
  replacement semantics. Apply to all three script examples.
- browser: troubleshooting table for "Timeout exceeded" was training the
  agent to retry with longer waits instead of surfacing the failure.
  Now requires reporting first, investigating second.

Review sources: pr-review-toolkit code-reviewer, silent-failure-hunter,
and Codex rescue agent (gemini rescue failed to return output).
From code-simplifier and comment-analyzer findings:

browser:
- Trim "When to Use" table (redundant with description frontmatter)
- Collapse Step 1 preflight (3 lines vs 14)
- Merge Parse + Clarify into one section
- Remove mid-step restatements of the "no raw interpolation" rule
  (kept once in Rules section only)
- Remove credential-restatement after Mode D (covered in Rules)
- Remove dead "Auth state reuse / storageState" rule (no example,
  not actionable)

screenshot:
- Collapse Step 1 preflight (same as browser)
- Merge Step 2 (Parse) + Step 3 (Validate) into one "Parse and Validate"
  step — validation is part of parsing the URL arg
- Fix --viewport-size example: concrete "1280,800" instead of
  "{width},{height}" placeholder (could be left unsubstituted)
- Renumber subsequent steps (old Step 4 → 3, Step 5 → 4)
- Rewrite Rules: full URL validation block (matching other skills) +
  close-safely rule to match browser pattern. Drop vague "URL validation
  first" bullet; rule is now concrete.

scrape:
- Remove "or as auto-fallback for JS-heavy sites" parenthetical —
  contradicted the clean linear fallback chain in Error Handling
- Remove duplicate "record this in the backend-ran report" from Step 2
  (error handling section already mandates it)
- Clarify "tell the user once per session" for Playwright install hint

Net: ~65 lines removed across three files, zero functionality loss.
Final: browser 161L, screenshot 105L, scrape 156L = 422L total.
Add screenshot + browser to ROADMAP's skill enumeration so
validate-counts CI job passes.
@5uck1ess
5uck1ess merged commit 997af4e into main Apr 10, 2026
3 checks passed
@5uck1ess
5uck1ess deleted the feat/playwright-skills branch April 10, 2026 04:41
5uck1ess added a commit that referenced this pull request Apr 10, 2026
Ships the critical fixes a full mega-pr review on #52 uncovered, beyond
the binary-shipping bug already addressed in the first commit on this
branch. All findings from silent-failure-hunter and code-reviewer
marked CRITICAL or HIGH are addressed here.

CRITICAL — shell injection in command steps

runCommand interpolated {{input}} and {{step-id}} into `sh -c <cmd>`.
Any LLM-chosen input or contaminated prior-step output executed shell.
Command and gate strings are now literal YAML; values are passed via
$DEVKIT_INPUT and $DEVKIT_OUT_<step_id> env vars. The engine validator
rejects {{...}} in command/gate strings at parse time with an explicit
"shell injection mitigation" error. Both engine.runCommand and
mcp.runCommand were affected; both fixed. The 5 self-* workflows
(self-test, self-lint, self-improve, self-perf, self-migrate) were
updated to use $DEVKIT_INPUT.

Regression tests: TestRunWorkflowCommandRejectsInterpolation,
TestRunWorkflowCommandEnvInput, TestRunWorkflowCommandEnvPriorOutput.

CRITICAL — PreToolUse guard bypassable

hooks/devkit-guard.sh blocklist matched 11 hardcoded tool names and
missed Task, SlashCommand, TodoWrite, BashOutput, any mcp__* tool, and
every future tool Claude Code adds. Claude could trivially drive a
command step by calling Task (subagent) or any non-listed tool. Fixed
by inverting to an allowlist: during command steps only mcp__devkit*
and TodoWrite are permitted; everything else returns exit 2 with a
clear diagnostic including the attempted tool name.

CRITICAL — stop-guard and guard had opposite fail-closed policies

devkit-stop-guard approved on corrupt session.json; devkit-guard
blocked. A corrupt state file mid-workflow would silently end the
session. Both now fail closed with a diagnostic and handle the TOCTOU
race between the -f test and python open() by treating
FileNotFoundError as "no session" (matching pre-check intent).

HIGH — CLAUDE_PLUGIN_DATA unset silent no-op

Both guard hooks exited 0 with no output when the env var was missing.
Combined with the binary-shipping bug, enforcement was an invisible
no-op. Added stderr warnings so the degraded state is observable.

HIGH — loop until / branch when substring false-positives

strings.Contains("no failures found", "fail") returned true, silently
terminating loops or routing branches incorrectly. Same bug in
EvalBranch. Both now use word-boundary matching (grep -w semantics) via
a new containsWord helper: the sentinel must be bounded on both sides
by a non-alphanumeric/underscore character. Accepts idiomatic forms
like "TINY: short fix" and "attempt 2: ALL_PASSING" while rejecting
accidental substrings. Regression tests cover both directions.

HIGH — completeWorkflow swallowed git.CommitAll errors

A failed final commit was logged to stderr only, so the user saw
"WORKFLOW COMPLETE" with uncommitted branch work. Now collected into
a warnings list and surfaced in the MCP response body, alongside any
DB update or state-clear failures.

HIGH — runCommand discarded stdout/stderr on non-ExitError failures

When the error wasn't an *exec.ExitError (missing binary, permission
denied, ctx cancelled), the captured combined stream was thrown away.
Now returned alongside the wrapped error so users see the actual cause.
Also distinguishes context.DeadlineExceeded (returns exit 124 + "timed
out after 5m") from generic execution failure.

MEDIUM — YAML typos silently ignored

yaml.Unmarshal accepted unknown fields, so "commnd: foo" (missing e)
parsed as a no-op step. Now uses NewDecoder(bytes).KnownFields(true);
typos fail loudly at parse time.

MEDIUM — make sync-version clobbered manual bumps

Triggering any make target on a feature branch rewrote plugin.json to
the latest git tag, silently undoing manual version bumps. Now only
writes when the git tag is strictly higher than the current
plugin.json version (semver ordering via sort -V).

MEDIUM — pr-gate.sh violated project shell rules

Missing `set -euo pipefail`, used /tmp/devkit-pr-gate-done shared
across concurrent projects, and arithmetic fallback that silently
skipped the gate on any failure. Now strict mode, per-user cooldown
file under $XDG_CACHE_HOME/devkit, and explicit numeric-regex guard
on the timestamp comparison.

Stale docs

- src/cmd/status.go, README.md, CONTRIBUTING.md, CHANGELOG.md —
  replaced `devkit workflow run <name>` with the correct
  `devkit workflow <name> "<description>"`. There is no `run`
  subcommand; src/cmd/workflow.go defines `Use: "workflow [name] ..."`.
- README.md architecture block — reframed the "binary ships in bin/"
  claim to describe the wrapper + download flow introduced by this PR.
- workflows/pr-ready.yml description — updated to reflect all 8 steps
  (validate, necessity, lint, test, security, changelog, create-pr,
  monitor) rather than the stale 5-step list from before pr-monitor
  was folded in.
- hooks/hooks.json top-level description — now mentions the workflow
  guard and completion guard hooks that were silently added in 2.1.0.
- ROADMAP.md — 19 → 22 skills (stale count from before the Playwright
  skills were added in PR #53).
- CHANGELOG.md — new 2.1.2 section documenting every fix in this PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant