Astral Browser Automation
repository·main·Indexed 43 days ago
https://github.com/lino-levan/astralA high-level browser automation library for Deno providing a Puppeteer/Playwright-like API. It utilizes the Chrome DevTools Protocol (CDP) and a specialized binding system called Celestial to control Chromium-based browsers. Features include support for the RAII pattern via the `using` keyword, remote browser connection via `connect()`, HTTP request interception for mocking, and integrated code coverage for `page.evaluate()`.
What's inside Astral
- Astral provides bindings to the Chrome DevTools Protocol (CDP). Because the CDP is extremely large and changes frequently, Astral uses a specialized system called Celestial to manage these protocol bindings. Celestial is a home-grown implementation designed to ensure that the bindings remain up-to-date with the evolving CDP specification, preventing the manual overhead and staleness associated with hand-written bindings.
Astral API Design Philosophy
mainAstral's API is designed to follow the patterns established bypuppeteerandplaywright, as these are widely recognized and popular in the browser automation space. However, Astral aims to improve upon specific design choices found in those libraries, such as reducing ambiguity in top-level methods (e.g., avoiding overlappingtypeandclickmethods that both accept selectors) and simplifying the complexity of the selectors API.Understand the core components of Astral
mainAstral is composed of three primary layers that work together:
- Browser Binaries: A Chrome or Firefox binary that is executed to perform tasks.
- Bindings: Low-level bindings generated specifically for those browser binaries.
- API Layer: A high-level abstraction layer built on top of the bindings to provide a developer-friendly API.
Understanding this hierarchy is essential for understanding how Astral interacts with the browser and how the high-level commands translate into low-level browser actions.
How Astral generates CDP bindings
mainThe binding generation process is managed by a script located in
bindings/_tools/mod.ts. The generation workflow follows three main steps:- Protocol Extraction: The script fetches the protocol JSON directly from a running browser instance at
http://localhost:9222/json/protocol. - Type Extraction: It parses the retrieved JSON to extract the necessary type definitions.
- Binding Generation: It generates bindings for both methods and events using specific format strings.
After the generation is complete,
deno fmtis automatically run to ensure the generated code is properly formatted.- Protocol Extraction: The script fetches the protocol JSON directly from a running browser instance at
Astral Implementation Details and CDP
mainAstral functions as a high-level wrapper aroundcelestialbindings. Many of the APIs are implemented by mapping directly to the Chrome DevTools Protocol (CDP) API, similar to how Puppeteer operates. For APIs that do not map directly to CDP—such as thewaitFor*family of functions—the implementation involves more complex logic to bridge the gap between the high-level API and the underlying protocol.Understand Astral's CDP compatibility
mainAstral interfaces with browsers using the Chrome DevTools Protocol (CDP). This means any browser that implements the CDP protocol should be compatible with Astral out of the box.
Supported Browsers:
- Browsers implementing CDP (e.g., Chromium-based browsers).
- Firefox (implementation status: likely compatible, but untested).
- Webkit-backed browsers (currently not supported).
Communication Method: Astral currently interacts with the protocol via an exposed port and a websocket rather than a unix pipe.
Quickstart: Launch a browser and take a screenshot with Astral
mainAstral is a Puppeteer/Playwright-like library for Deno. You can use it to automate browser tasks such as generating screenshots/PDFs, crawling SPAs for pre-rendering, and automating UI testing. To get started, import
launchfromjsr:@astral/astral, usebrowser.newPage(url)to open a page, andpage.screenshot()to capture the view.Note: Astral is designed to be stealthy by default without requiring extra configuration.
// Import Astral import { launch } from "jsr:@astral/astral"; // Launch the browser const browser = await launch(); // Open a new page const page = await browser.newPage("https://deno.land"); // Take a screenshot of the page and save that to disk const screenshot = await page.screenshot(); Deno.writeFileSync("screenshot.png", screenshot); // Close the browser await browser.close();Use the HTTP interceptor to mock or fail requests
mainThe
interceptorfunction allows you to capture outgoing requests and either provide a customResponse(mocking) or throw anInterceptorError(simulating failure). This is useful for testing with fixtures or simulating network errors without making actual network calls.Control Flow
The behavior of an intercepted request is determined by the return value of the interceptor handler:
- Continue Request: Return
null,undefined, or a void value to let the request proceed normally. - Fulfill with Mock: Return a
Responseobject to fulfill the request with that specific response. - Fail Request: Throw an
InterceptorErrorto fail the request with a specific reason.
Filtering by Resource Type
The interceptor receives a
Requestobject and an info record containingresourceType: Network_ResourceType. You can use this to filter logic based on whether the request is aDocument,Stylesheet,Image, etc.Important Security Note
The HTTP interceptor executes before sandbox permission checks. This means intercepted requests are not subject to the sandbox's permission restrictions.
await using browser = await launch(); await using page = await browser.newPage("http://example.com", { interceptor(request: Request) { if (new URL(request.url).pathname === "/blocked") { throw new InterceptorError("BlockedByClient"); } return new Response( "<!DOCTYPE html><html><head><title>Intercepted request!</title></head><body></body></html>", { status: 200, headers: { "content-type": "text/html" } }, ); }, });- Continue Request: Return
Perform complex navigation in Astral
mainTo automate complex user flows like searching and navigating through links, use the
launchfunction to start a browser,newPageto open a URL, and standard selector methods ($) to interact with elements. When clicking links that trigger page loads, wrap the click action inPromise.allwithpage.waitForNavigation()to ensure the script waits for the new page to load before proceeding.// Import Astral import { launch } from "jsr:@astral/astral"; // Launch browser in headfull mode const browser = await launch({ headless: false }); // Open the webpage const page = await browser.newPage("https://deno.land"); // Click the search button const button = await page.$("button"); await button!.click(); // Type in the search input const input = await page.$("#search-input"); await input!.type("pyro", { delay: 1000 }); // Wait for the search results to come back await page.waitForNetworkIdle({ idleConnections: 0, idleTime: 1000 }); // Click the 'pyro' link const xLink = await page.$("a.justify-between:nth-child(1)"); await Promise.all([ page.waitForNavigation(), xLink!.click(), ]); // Click the link to 'pyro.deno.dev' const dLink = await page.$( ".markdown-body > p:nth-child(8) > a:nth-child(1)", ); await Promise.all([ page.waitForNavigation(), dLink!.click(), ]); // Close browser await browser.close();Regenerate CDP protocol bindings
mainAstral generates fully typed and JSDoc'd bindings for the Chrome DevTools Protocol (CDP). If you are working with a binary and need to update or regenerate these bindings, use thedeno task bindcommand.deno task bindTake screenshots of web pages with Astral
mainYou can capture screenshots of web pages by using the
launch()function to start a browser instance, navigating to a URL withbrowser.newPage(), and callingpage.screenshot(). Thescreenshot()method returns the image data as a buffer (typically aUint8Array), which you can then write to disk using your runtime's file system API (e.g.,Deno.writeFileSyncin Deno).// Import Astral import { launch } from "jsr:@astral/astral"; // Launch the browser const browser = await launch(); // Open a new page const page = await browser.newPage("https://deno.land"); // Take a screenshot of the page and save that to disk const screenshot = await page.screenshot(); Deno.writeFileSync("screenshot.png", screenshot); // Close the browser await browser.close();Connect to a remote browser endpoint
mainInstead of spawning a new local browser process, you can connect
astralto an existing browser process or a remote browser service (like browserless.io) by providing a WebSocket URL to theendpointoption in thelaunchfunction. This is useful for using remote automation services or connecting to a browser already running in a different environment.// Import Astral import { launch } from "jsr:@astral/astral"; // Connect to remote endpoint const browser = await launch({ endpoint: "wss://remote-browser-endpoint.example.com", }); // Do stuff const page = await browser.newPage("http://example.com"); console.log(await page.evaluate(() => document.title)); // Close connection await browser.close();