feat: Add Puppeteer humanize support and fix Playwright humanize gaps#129
Conversation
1703d45 to
1b63aeb
Compare
Cloak-HQ
left a comment
There was a problem hiding this comment.
Thanks for the PR! Found a few things to address:
1. Horizontal scroll bug in smoothWheel
smoothWheel() hardcodes the Y axis — raw.wheel(0, chunk * sign). When mouse.wheel({deltaX: 200}) hits the patch, it passes dx to smoothWheel, which scrolls vertically instead of horizontally.
2. createIncognitoBrowserContext — forward compat
Works on puppeteer-core 21.x (our current dev dep), but current Puppeteer docs show createBrowserContext() as the API. Since our peer dep is >=21.0.0, worth checking for both names so it doesn't silently skip context patching on newer versions.
3. el.evaluate() in moveToElement bypasses stealth
Page-level methods correctly use StealthEval (CDP isolated world) for isInputElement. But the ElementHandle path in moveToElement() calls el.evaluate() directly — main world execution on every el.click() / el.hover() / el.type(). Should route through the existing StealthEval instance instead.
4. Dead patches (non-blocking)
Puppeteer Page doesn't have fill, check, uncheck, clear, press, or pressSequentially — those are Playwright-only. Same for several Frame methods. The (page as any) casts create new properties no standard Puppeteer caller would use. Not harmful, just dead code. Up to you whether to keep or trim.
5. Missing newline at EOF in puppeteer.ts
Playwright fixes (pressSequentially, tap, clear on page + frame) look good. Let us know if you have questions on any of these.
- Add full Puppeteer humanize implementation (page, frame, element handle patching) - Fix critical Playwright gaps: page.pressSequentially, page.tap, page.clear - Fix frame-level patching: frame.pressSequentially, frame.tap - Add comprehensive stealth tests for Puppeteer - Update SLOW test suite to use correct humanize: true API - Add 4 new tests validating fixed Playwright methods
1b63aeb to
1f5ed8a
Compare
Addressed all review feedbackAll 5 points from the review have been fixed: 1. Horizontal scroll bug in smoothWheel ✅
2. createIncognitoBrowserContext — forward compat ✅
Works on v21 ( 3. el.evaluate() in moveToElement bypasses stealth ✅New Falls back to 4. Dead patches removed ✅Removed all Playwright-only methods that don't exist on Puppeteer's API:
Only Tests updated to match — removed tests for dead APIs, replaced with tests for actual Puppeteer APIs. 5. Missing newline at EOF ✅Added. Test results77/77 passing (including 6 SLOW tests with real browser): |
|
Merged, thanks! All tests pass including integration with a real browser on our test infra. Nice work on the CDP |
Fix critical Playwright humanize gaps + Add full Puppeteer support
Overview
Found and fixed 3 critical stealth bypasses in the existing Playwright humanize implementation, then added complete Puppeteer humanize support mirroring the same architecture.
Issues Found & Fixed
Issue #1 (CRITICAL): 3 page-level methods not patched in Playwright
File:
js/src/human/index.tsProblem: Functions
humanPressSequentiallyFn,humanTapFn,humanClearFnwere implemented but never assigned topage.pressSequentially,page.tap,page.clear. They only existed as internal properties for frame patches.Impact:
page.pressSequentially('input', 'text')bypassed humanize completely, typing instantly withisTrusted=falsefor shift characterspage.tap('button')bypassed Bézier mouse movement, performing instant clickspage.clear('input')bypassed CDP Isolated World focus checksThis directly compromised stealth. Anti-bot systems could detect superhuman typing speed and instant mouse teleportation.
Fix: Added 3 missing page-level patches
Issue #2 (CRITICAL): 2 frame-level methods not patched in Playwright
File:
js/src/human/index.ts, functionpatchSingleFrame()Problem: Frame-level patching covered 11 methods but was missing
pressSequentiallyandtap.Why this matters: Users commonly write code like this:
await page.locator('#input').pressSequentially('Hello!'); await page.locator('#button').tap();Locator calls go through Frame methods. Without frame-level patches, the entire Locator chain bypasses humanize even if page-level patches exist.
Fix: Added 2 missing frame-level patches
Issue #3 (MODERATE): SLOW tests used incorrect API
File:
js/tests/stealth.test.ts(SLOW section)Problem: Browser-level SLOW tests (run with
SLOW=1) usedargs: ['--humanize']instead ofhumanize: true. This meant SLOW tests were not testing humanize at all. They ran with native Playwright behavior.Fix: Changed to correct API:
humanize: truePuppeteer Implementation
Added complete Puppeteer humanize support with full API coverage:
Coverage:
goto,click,hover,type,fill,check,uncheck,select,clear,focus,press,pressSequentially,tap(13 methods)pressSequentially,tap,focus,dragAndDropclick,hover,type,press,tap,focus,select,drop,dragAndDrop+ nested$,$$,waitForSelectornewPage,createIncognitoBrowserContext,targetcreatedFiles added:
js/src/human-puppeteer/index.ts(1,247 lines)js/src/human-puppeteer/keyboard.ts(89 lines)js/src/human-puppeteer/scroll.ts(56 lines)js/tests/stealth.puppeteer.test.ts(2,174 lines, 77 tests)Note: I have limited experience with Puppeteer's internal architecture. Would really appreciate a detailed review. Happy to fix any issues or improve the implementation based on feedback.
Test Results
All tests passing. Added 81 new tests total:
Files Changed
js/src/human/index.ts— Added 11 lines (3 page-level, 8 frame-level patches)js/tests/stealth.test.ts— Added 95 lines (4 new tests + SLOW test fixes)js/src/human-puppeteer/index.ts— Added 1,247 lines (new file)js/src/human-puppeteer/keyboard.ts— Added 89 lines (new file)js/src/human-puppeteer/scroll.ts— Added 56 lines (new file)js/src/puppeteer.ts— Added 2 lines (humanize integration)js/tests/stealth.puppeteer.test.ts— Added 2,174 lines (new file)Note on Puppeteer v21 Compatibility
This implementation uses
createIncognitoBrowserContext()instead ofcreateBrowserContext()because CloakBrowser depends on Puppeteer v21.In Puppeteer v23+ (2024),
createIncognitoBrowserContextwas renamed tocreateBrowserContextas part of a breaking change, but v21 doesn't have that method yet.Review Request
I have limited experience with Puppeteer internals, so detailed review is very welcome. Happy to fix any issues or improve the implementation based on feedback.