Commit Graph
9 Commits
Author SHA1 Message Date
Ben YounesandOra Studio 023caf9ce5 fix(cdp): return descriptive error for Page.printToPDF instead of generic Unknown Page method (#53) (#112)
Playwright's `page.pdf()` issues a `Page.printToPDF` CDP request which
Obscura was not handling, so the dispatcher fell through to the generic
catch-all and clients saw:

    Page.pdf: Protocol error (Page.printToPDF): Unknown Page method: printToPDF

This reads like an oversight rather than a deliberate limitation.
Obscura intentionally has no layout engine — option (2) from the issue —
so make the limitation explicit by handling the method and returning a
clear error that names the method, says why it isn't supported, and
points users at a working alternative (extract HTML via
Runtime.evaluate, then render to PDF in their client).

The behavior change is small but meaningful for DX: the JSON-RPC error
code stays -32601 (the dispatcher's mapping for `Err(_)`), but the
human-readable message becomes actionable instead of looking like a
forgotten method.

Adds a unit test that pins the regression: printToPDF must NOT fall
through to the "Unknown Page method" catch-all and must mention both
"not supported" and a workaround.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-09 17:36:11 +02:00
Ben YounesandOra Studio 7dfc2a9c01 fix(cli): strip nav/header/footer/aside from --dump text output (#55) (#82)
`obscura fetch --dump text` walked the entire <body> and emitted the
text of navigation menus, sidebars, and footers alongside the article
body. The skip list inside extract_readable_text already excluded
<script>/<style>; extend it with the four standard semantic-boilerplate
tags so the dump is closer to the article content users actually want
to scrape.

Behaviour change for users who relied on the noise being included is
acknowledged but unlikely — these tags are the canonical wrappers
boilerplate goes in. Anyone needing the full body should use --dump
html and post-process.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-09 17:32:59 +02:00
Ben YounesandOra Studio 84115ca06e fix(cli): make --quiet suppress JS console errors on stderr (#56) (#74)
* fix(cli): make --quiet suppress JS console errors on stderr (#56)

The --quiet flag was only gating banner prints in run_fetch but not
the global tracing_subscriber, so ERROR-level JS console output kept
leaking to stderr.

Detect quiet from the parsed Fetch subcommand before initializing
the subscriber and switch the filter to "off" in that case. Verbose
still wins over quiet to keep diagnostic mode usable.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-Authored-By: Ora Studio <[email protected]>

* test(cli): add integration tests for --quiet matcher wiring

Address review feedback on PR #74: previous tests only covered the
pure helper select_log_filter, not the matches!() wiring in main()
that maps Command::Fetch { quiet: true, .. } → quiet flag.

Extract the matcher into is_quiet_command(&Option<Command>) and add
six new tests that parse real CLI args via Args::try_parse_from and
assert the full chain:

- parsed_fetch_with_quiet_flag_is_detected: --quiet on fetch → true
- parsed_fetch_without_quiet_is_not_detected: bare fetch → false
- parsed_serve_command_is_not_quiet: serve subcommand → false
- no_subcommand_is_not_quiet: None → false
- parsed_fetch_quiet_resolves_to_off_filter: end-to-end clap → "off"
- matcher_still_uses_fetch_variant: regression guard if Command::Fetch
  variant is renamed/restructured

cargo test -p obscura-cli: 10 passed; 0 failed.

Co-Authored-By: Ora Studio <[email protected]>

---------

Co-authored-by: Ora Studio <[email protected]>
2026-05-09 17:27:03 +02:00
Ben YounesandOra Studio 536072b323 fix(serve): plumb --user-agent through CDP server (#71) (#76)
obscura serve --user-agent <ua> only logged the value via tracing::info!
and never passed it to the CDP server, so every navigation kept using
the default Chrome UA string and httpbin.org/user-agent reported the
default rather than the override.

Plumb the option end-to-end:

- BrowserContext::with_full_options(id, proxy, stealth, user_agent)
  initializes both BrowserContext::user_agent and the underlying
  ObscuraHttpClient::user_agent RwLock at construction time so the
  first navigation already sees the override.
- CdpContext::new_with_full_options forwards the UA to the default
  BrowserContext.
- obscura_cdp::start_with_full_options accepts the UA and passes it
  through cdp_processor.
- main.rs Serve arm calls start_with_full_options with the parsed UA.
- run_multi_worker_serve forwards --user-agent <ua> when spawning
  worker subprocesses so multi-worker mode honors the flag too.

Existing two-arg start_with_options and three-arg with_options stay
as thin shims (call the full variant with None) so older callers and
the public crate API remain source-compatible.

Three new unit tests in obscura-browser cover the propagation, the
Chrome default fallback, and the legacy with_options path.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:32:38 +02:00
Ben YounesandOra Studio a7b1acfa18 fix(cdp): accept Audits domain as no-op for Puppeteer compatibility (#57) (#80)
Puppeteer's FrameManager.initialize calls Audits.enable on connect.
Without it puppeteer.connect() fails with "Unknown domain: Audits"
before any user code runs. Playwright doesn't enable Audits, which is
why the workaround in #57 sidesteps the issue.

Add Audits to the existing accepted-but-no-op domain list, alongside
Log/Performance/Security/etc. that already swallow Puppeteer's connect
chatter. No new module needed.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:22:24 +02:00
Ben YounesandOra Studio 111150a4f8 fix(js): add elementFromPoint / elementsFromPoint stubs (#63) (#75)
document.elementFromPoint(x, y) was undefined, which caused Google
Publisher Tag, viewability libs and any code doing hit-testing to
throw on bootstrap and trigger retry loops in the surrounding
framework. This is one of the throwing APIs feeding into the wedge
described in #62.

Without a layout engine we can't return the actual topmost element,
but a non-throwing stub is strictly better than "undefined" for the
headless-scraping use case:

- coords inside the viewport return document.body (or
  document.documentElement as a fallback) so callers that just want
  a hit target stop throwing
- coords outside the viewport (negative, > innerWidth/innerHeight,
  NaN/Infinity) return null per spec
- elementsFromPoint mirrors the same shape but returns a one- or
  zero-length array
- ShadowRoot.prototype gets the same treatment for completeness

Five new unit tests in obscura-js cover function presence, in-viewport
hit, out-of-viewport coords, non-numeric input, and the array shape.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:19:17 +02:00
Ben YounesandOra Studio d50dd0f553 fix(js): make createEvent type-aware and add initCustomEvent (#41) (#77)
document.createEvent('CustomEvent') was returning a generic Event,
so the legacy DOM Level 2 pattern

  var e = document.createEvent('CustomEvent');
  e.initCustomEvent('foo', true, false, payload);

threw "TypeError: e.initCustomEvent is not a function" because the
returned object was an Event, not a CustomEvent — and CustomEvent
didn't define initCustomEvent anyway. The Starbucks China bundle
(and a long tail of other CDN bundles using the same shim) depended
on this and aborted bootstrap.

Two changes in crates/obscura-js/js/bootstrap.js:

1. Add CustomEvent.prototype.initCustomEvent(type, bubbles, cancelable,
   detail) matching the spec.
2. Rewrite Document.prototype.createEvent so it dispatches to the
   right class based on the type string ('CustomEvent', 'MouseEvent',
   'MouseEvents', 'KeyboardEvent', 'FocusEvent', 'InputEvent',
   'UIEvent', 'WheelEvent', 'PointerEvent', 'ErrorEvent',
   'PopStateEvent', 'AnimationEvent', 'TransitionEvent', and
   case-insensitive 'Events' aliases). The HTMLDocument variant on
   the second Document class delegates to the canonical impl.

Spec note: createEvent returns the event with an empty type until
init*Event() is called — matched exactly.

Four new unit tests cover initCustomEvent presence and field setters,
class dispatch for CustomEvent / MouseEvent / MouseEvents / KeyboardEvent,
and the unknown-type fallback to Event.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:18:30 +02:00
Ben YounesandOra Studio 23753405e5 fix(cdp): implement Target.attachToBrowserTarget for Playwright (#64) (#81)
Playwright (and recent Puppeteer) call Target.attachToBrowserTarget on
connect to obtain a session for the implicit "browser" target. Without
the handler the dispatcher returns "Unknown Target method:
attachToBrowserTarget" and the connect handshake aborts before any
user code runs.

Add a handler that mirrors Target.attachToTarget but for the browser
target: register a "browser-session" → "browser" mapping in
ctx.sessions, emit Target.attachedToTarget with type=browser, and
return the new session id.

Note: this addresses item (1) from #64. The Rocket Loader / Cloudflare
JS execution gap and Page.getLayoutMetrics are tracked separately
(getLayoutMetrics is fixed in PR #79).

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:10:07 +02:00
Ben YounesandOra Studio 3bb628eaf0 fix(cdp): implement Page.getLayoutMetrics for screenshot compatibility (#52) (#79)
Playwright calls Page.getLayoutMetrics before every page.screenshot() to
size the capture. Without it the call fails with "Unknown Page method:
getLayoutMetrics" and screenshot() throws.

Obscura has no visual layout engine, so we return a fixed 1280x720
viewport (Chrome's default) and try to derive the content height from
document.documentElement.scrollHeight via the JS runtime, falling back
to the viewport height when no live page is bound.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:09:44 +02:00