""" Record humanize=True cursor movements from a real build and draw them as an animated SVG: assets/humanize-cursor.svg, the figure in the README's "Page Interactions" section. Every point is a mousemove event the page received, at the time it received it, so the figure shows what a site sees: the shape of each path, the spacing of its events (close together = slow), and the pauses, replayed in real time. python3 scripts/cursor-demo.py /path/to/camoufox-bin """ import asyncio import sys from pathlib import Path from camoufox.async_api import AsyncCamoufox W, H = 900, 420 # A tour of short and long moves in several directions, like a user working # through a form. STOPS = [(80, 360), (820, 70), (460, 330), (170, 110), (640, 380), (840, 250), (80, 360)] RECORDER = """ window.moves = []; addEventListener("mousemove", e => moves.push([e.clientX, e.clientY, performance.now()])); """ OUT = Path(__file__).resolve().parent.parent / "assets" / "humanize-cursor.svg" LABEL = 36 # caption strip under the page area PAUSE_MS = 600 # between moves in the replay; the recorded gap is Playwright overhead async def record(executable): moves = [] async with AsyncCamoufox( headless=True, os="linux", humanize=True, executable_path=executable, window=(W + 100, H + 200), ) as browser: page = await browser.new_page() await page.set_content(f'') await page.mouse.move(*STOPS[0]) for stop in STOPS[1:]: await page.evaluate(RECORDER) await page.mouse.move(*stop) moves.append(await page.evaluate("moves")) return moves def render(moves): paths, frames, t0 = [], [], 0.0 for events in moves: start = events[0][2] paths.append(events) for x, y, t in events: frames.append((x, y, t0 + t - start)) t0 += events[-1][2] - start + PAUSE_MS total = t0 keyTimes = ";".join(f"{t / total:.5f}" for *_, t in frames) + ";1" values = ";".join(f"{x},{y}" for x, y, _ in frames) + f";{frames[-1][0]},{frames[-1][1]}" out = [ f'', f'', ] for events in paths: pts = " ".join(f"{x},{y}" for x, y, _ in events) out.append(f'') out += [f'' for x, y, _ in events] for x, y in STOPS[:-1]: out.append(f'') out.append( '' f'' "" ) n = sum(len(e) for e in moves) out.append( f'{n} mousemove events from' f" {len(moves)} page.mouse.move() calls, humanize=True, replayed at recorded speed" ) out.append("") return "\n".join(out) + "\n" if __name__ == "__main__": OUT.write_text(render(asyncio.run(record(sys.argv[1])))) print(OUT)