Skip to main content

Built and signed on GitHub Actions

Astral is the browser automation library for Deno

This package works with Deno
This package works with Deno
JSR Score
100%
Published
2 months ago (0.5.2)
Package root>README.md
# Astral <img src="./docs/static/icon.png" height="200" width="200" align="right"/> Astral is a high-level puppeteer/playwright-like library that allows for control over a web browser (primarily for automation and testing). It is written from scratch with Deno in mind. ## Usage Take a screenshot of a website. ```ts // 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(); ``` You can use the evaluate function to run code in the context of the browser. ```ts // 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"); // Run code in the context of the browser const value = await page.evaluate(() => { return document.body.innerHTML; }); console.log(value); // Run code with args const result = await page.evaluate((x, y) => { return `The result of adding ${x}+${y} = ${x + y}`; }, { args: [10, 15], }); console.log(result); // Close the browser await browser.close(); ``` You can navigate to a page and interact with it. ```ts // 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(); ``` TODO: Document the locator API. ## Advanced Usage If you already have a browser process running somewhere else or you're using a service that provides remote browsers for automation (such as [browserless.io](https://www.browserless.io/)), it is possible to directly connect to its endpoint rather than spawning a new process. ```ts // Import Astral import { connect } from "jsr:@astral/astral"; // Connect to remote endpoint const browser = await connect({ wsEndpoint: "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(); ``` If you'd like to instead re-use a browser that you already launched, astral exposes the WebSocket endpoint through `browser.wsEndpoint()`. ```ts // Spawn a browser process const browser = await launch(); // Connect to first browser instead const anotherBrowser = await connect({ wsEndpoint: browser.wsEndpoint() }); ``` ### Page authenticate [authenticate example code](https://github.com/lino-levan/astral/blob/main/examples/authenticate.ts): ```ts // Open a new page const page = await browser.newPage(); // Provide credentials for HTTP authentication. const url = "https://postman-echo.com/basic-auth"; await page.authenticate({ username: "postman", password: "password" }); await page.goto(url, { waitUntil: "networkidle2" }); ``` ## BYOB - Bring Your Own Browser Essentially the process is as simple as running a chromium-like binary with the following flags: ``` chromium --remote-debugging-port=1337 \ --headless=new \ --no-first-run \ --password-store=basic \ --use-mock-keychain \ --hide-scrollbars ``` Technically, only the first flag is necessary, though I've found that these flags generally get the best result. Once your browser process is running, connecting to it is as simple as ```typescript // Import Astral import { connect } from "jsr:@astral/astral"; // Connect to remote endpoint const browser = await connect({ wsEndpoint: "<WS-ENDPOINT>", headless: false, }); console.log(browser.wsEndpoint()); // Do stuff const page = await browser.newPage("http://example.com"); console.log(await page.evaluate(() => document.title)); // Close connection await browser.close(); ``` ## FAQ ### Launch FAQ #### "No usable sandbox!" with user namespace cloning enabled > Ubuntu 23.10+ (or possibly other Linux distros in the future) ship an AppArmor > profile that applies to Chrome stable binaries installed at > /opt/google/chrome/chrome (the default installation path). This policy is > stored at /etc/apparmor.d/chrome. This AppArmor policy prevents Chrome for > Testing binaries downloaded by Puppeteer from using user namespaces resulting > in the No usable sandbox! error when trying to launch the browser. For > workarounds, see > https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md The following command removes AppArmor restrictions on user namespaces, allowing Puppeteer to launch Chrome without the "No usable sandbox!" error (see [puppeteer#13196](https://github.com/puppeteer/puppeteer/pull/13196)): echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns