MEMBLOG

Exploring Browser Automation

- 2025-01-17

While we're all together in Miami for our team onsite, I wanted to explore getting browser automation working in Membrane. Browserless offers some REST APIs for browser automation, and I was particularly interested in their function endpoint, which lets you send a Puppeteer script to be executed in their hosted browser environment.

What's nice about their function endpoint is that you don't need to install or manage any browser instances, you just send your code and they run it in their browser for you. I used it to create iamseeley/browserless-function, a driver that any program can add as a connection to run browser automations.

The driver exposes this functionality through a simple interface:

await nodes.browserless_function.runInBrowser({
 code,    // Your Puppeteer script as a string
 context  // Optional data to pass to your script
});

To test this out, I put together a web crawler - iamseeley/web-crawler. You feed it a URL, and it uses the Browserless function endpoint (through the driver) to spin up a browser, navigate to the page, and extract its content. All the content gets stored right in Membrane's durable state object, which means we didn't need to set up any external database.

I ended up crawling docs.membrane.io.

A neat thing is that we can expose the crawled documents to Membrane's graph. Using Membrane's collection pattern, we define three types - Document, DocumentCollection, and DocumentPage. The Document type represents a single crawled page and has a gref that makes it uniquely referenceable in the graph. The DocumentCollection provides ways to query these documents, either getting one by URL or a paginated list of all documents. This means other Membrane programs can add the crawler as a connection and use these types to access the crawled content. :)

So, with all of our docs content now easily accessible I thought I'd make a iamseeley/chat-with-docs program to help users explore our documentation. The program connects to both the web crawler and our membrane/anthropic driver - it uses the crawler's collection of documents to provide context, and Claude to generate relevant responses to user questions.

The program follows the same pattern as the crawler, exposing Conversation and Message types that can be referenced in Membrane's graph. Each conversation is stored in state with a unique ID, allowing users to return to their conversations even after refreshing the page.

I took advantage of Membrane's built-in HTTP endpoint feature to create the web interface. Every Membrane program gets its own URL, and by exporting an endpoint function, you can handle HTTP requests. GET requests serve the chat interface with message history. POST requests to /chat handle new messages.

And because we're using Membrane's graph, you can actually explore these conversations right in the Navigator:

For those interested here's the full Puppeteer script I used to crawl our docs:

export default async function ({ page, context }) {
await page.goto(context.url);

const data = await page.evaluate(() => {
  return JSON.stringify({
    title: document.title,
    content: Array.from(document.querySelectorAll('article, main, .content, #content, .main-content'))
      .map(el => el.textContent)
      .join('\\n')
      .trim() || document.body.innerText,
    links: [...new Set(
      Array.from(document.querySelectorAll('a'))
        .map(a => a.href)
        .filter(href => {
          try {
            const url = new URL(href);
            if (url.origin === window.location.origin) {
              url.hash = '';
              return url.href;
            }
            return false;
          } catch {
            return false;
          }
        })
    )]
  });
});

return {
  data: JSON.parse(data),
  type: 'application/json'
};
}

This was a fun project to speedrun during our time in Miami. Between state, endpoint, and drivers that just work together, Membrane made it easy to focus on the interesting parts.

Would love to see what others might build with browser automation in Membrane. Give it a try and let us know what you come up with!


- Thomas Seeley (thomas@membrane.io)

© 2024 Programmability, Inc.