Skip to content

Quick Start

losenine edited this page Oct 31, 2026 · 1 revision

English · 中文

Quick Start

Each Context is its own process with a persistent JavaScript world. Use it as a context manager so the process is always cleaned up.

1. Run JavaScript

from ruyijs import Context

with Context() as context:
    context.eval("let count = 40")
    print(context.eval("count += 2"))        # 42
    print(context.eval("navigator.userAgent"))

Variables and functions persist across eval calls within the same context. Objects and arrays come back as Python dict and list; JavaScript null becomes None and undefined becomes ruyijs.UNDEFINED.

2. Pass data in, get functions out

with Context() as context:
    context.set("config", {"offset": 2})
    add = context.eval("(value) => value + config.offset", to_py=False)
    print(add(40))  # 42

set() copies a Python value onto the page global. eval(..., to_py=False) keeps functions, DOM nodes and class instances as live JSFunction / JSObject handles that you can call or read from Python.

3. Expose a Python function

with Context() as context:
    context.expose(lambda a, b: a + b, "add")
    print(context.eval("add(20, 22)"))  # 42

The callback runs while the script waits for it. It must return within the context's timeout, and it must not call back into the same context (that raises ReentrantError).

4. Handle JavaScript errors

from ruyijs import Context, JSError

with Context(timeout=10) as context:
    try:
        context.eval("throw new TypeError('bad input')", filename="demo.js")
    except JSError as error:
        print(error.name, error.message)  # TypeError bad input
        print(error.stack)

A JSError leaves the context usable.

5. Load a page with offline resources

with Context(timeout=60) as context:
    context.put_resource("https://smoke.test/app.js",
                         "window.loaded = document.title + ':' + location.host;")
    context.load_page("https://smoke.test/",
                      "<!doctype html><title>smoke</title><script src='/app.js'></script>")
    print(context.eval("window.loaded"))   # smoke:smoke.test

load_page(url, html) makes url the document address, so relative URLs resolve against it. Parser-inserted classic scripts run in order, then DOMContentLoaded and load fire. Scripts with src are read from the resource directory (put_resource) or from the network. If a page script throws, load_page raises the first error after the load finishes.

6. Timers and the event loop

with Context() as context:
    context.configure_clock("logical", epoch_ms=1700000000000)
    context.eval("globalThis.result = 0; setTimeout(() => { result = 42 }, 10)")
    context.advance(10)
    print(context.eval("result"))  # 42

The host drives time. Timers, network callbacks and navigations make progress when you call advance(ms) (or tick() / drain_timers()). With the logical clock time moves forward instantly and runs are reproducible; with the system clock advance() waits in real time.

7. Navigate to a live site

with Context(proxy="http://127.0.0.1:7890", timeout=60) as context:
    context.configure_clock("system")
    context.navigate("https://example.com/")
    for _ in range(40):                                   # up to ~10 s
        context.advance(250, max_tasks=500)
        if context.eval("location.host === 'example.com' && document.readyState === 'complete'"):
            break
    print(context.eval("document.title"))

navigate() makes the top-level navigation the browser itself would make (no Referer, Sec-Fetch-Site: none, Sec-Fetch-User: ?1); advance() lets it fetch and commit. Leave out proxy= for a direct connection. The CreepJS and Akamai examples use the same pattern: system clock, navigate(), then a loop of advance() calls.

In-page requests work the same way — start them with eval, then advance:

with Context(proxy="http://127.0.0.1:7890", timeout=30, tls="120") as context:
    context.load_page("https://example.com/", "")
    context.eval("globalThis.r = null; fetch('/api').then(x => x.json()).then(v => r = v)")
    context.advance(25)               # drive the event loop until it settles
    print(context.eval("r"))

8. Configure the environment first

with Context() as context:
    context.configure_fingerprint({
        "navigator": {"language": "zh-CN", "hardwareConcurrency": 8},
        "screen": {"width": 1920, "height": 1080},
        "viewport": {"width": 1280, "height": 720},
        "locale": {"timezone": "Asia/Shanghai"},
    })
    print(context.eval("[navigator.language, screen.width]"))  # ['zh-CN', 1920]

Apply the profile before the first page operation so every script observes the same values. See Fingerprint and Environment.

9. Read the console and network logs

records = context.console_log()   # [{"realm", "method", "level", "text", "indent", "stack"}, ...]
requests = context.net_log()      # [{"id", "method", "url", "requestBody", "status", "matched"}, ...]

10. Closing

  • Prefer with Context() as context:. Outside a with block, call context.close(); repeated calls have no effect.
  • A timeout or a broken pipe closes the context and kills its process; any later call raises RuntimeError: ruyijs Context is closed. Create a new Context.
  • Contexts share nothing: each has its own process, JavaScript world, cookies, resource directory and proxy.

Next: API Reference · Examples

Clone this wiki locally