chrobot

Welcome to Chrobot! 🤖 This module exposes high level functions for browser automation.

Some basic concepts:

The functions in this module just make calls to protocol/ modules, if you would like to customize the behaviour, take a look at them to see how to make direct protocol calls and pass different defaults.

Something to consider:
A lot of the functions in this module are interpolating their parameters into
JavaScript expressions that are evaluated in the page context.
No attempt is made to escape the passed parameters or prevent script injection through them, you should not use the functions in this module with arbitrary strings if you want to treat the pages you are operating on as a secure context.

Types

Type wrapper to let you pass in custom arguments of different types to a JavaScript function as a list of the same type

pub type CallArgument {
  StringArg(value: String)
  IntArg(value: Int)
  FloatArg(value: Float)
  BoolArg(value: Bool)
  ArrayArg(value: List(CallArgument))
}

Constructors

  • StringArg(value: String)
  • IntArg(value: Int)
  • FloatArg(value: Float)
  • BoolArg(value: Bool)
  • ArrayArg(value: List(CallArgument))

Holds a base64 encoded file and its extension

pub type EncodedFile {
  EncodedFile(data: String, extension: String)
}

Constructors

  • EncodedFile(data: String, extension: String)

Holds information about the current page, as well as the desired timeout in milliseconds to use when waiting for browser responses.

pub type Page {
  Page(
    browser: Subject(chrome.Message),
    time_out: Int,
    target_id: target.TargetID,
    session_id: target.SessionID,
  )
}

Constructors

  • Page(
      browser: Subject(chrome.Message),
      time_out: Int,
      target_id: target.TargetID,
      session_id: target.SessionID,
    )

Functions

pub fn as_value(
  result: Result(RemoteObject, RequestError),
  decoder: fn(Dynamic) -> Result(a, b),
) -> Result(a, RequestError)

Cast a RemoteObject into a value by passing a dynamic decoder.
This is a convenience for when you know a RemoteObject is returned by value and not ID, and you want to extract the value from it.
Because it accepts a Result, you can chain this to eval or eval_async like so:

eval(page, "window.document.documentElement.outerHTML")
  |> as_value(dynamic.string)
pub fn await_load_event(
  browser: Subject(Message),
  page: Page,
) -> Result(Dynamic, RequestError)

Block until the page load event has fired. Note that with local pages, the load event can often fire before the handler is attached.
It’s best to use await_selector instead of this

pub fn await_selector(
  on page: Page,
  select selector: String,
) -> Result(RemoteObjectId, RequestError)

Continously attempt to run a selector, until it succeeds.
You can use this after opening a page, to wait for the moment it has initialized enough sufficiently for you to run your automation on it.
The final result will be single runtime.RemoteObjectId

pub fn call_custom_function_on(
  callback: fn(String, Option(Json)) ->
    Result(Dynamic, RequestError),
  function_declaration function_declaration: String,
  object_id object_id: RemoteObjectId,
  args arguments: List(CallArgument),
  value_decoder value_decoder: fn(Dynamic) -> Result(a, b),
) -> Result(a, RequestError)

This is a version of runtime.call_function_on which allows passing in arguments, and always returns the result as a value, which will be decoded by the decoder you pass in

You would use it with a JavaScript function declaration like this:

function my_function(my_arg) {
  // You can access the passed RemoteObject with `this`
  const wibble = this.getAttribute('href')
  // You have access to the arguments you passed in
  const wobble = 'hello ' + my_arg
  // You receive this return value, you should pass in a string decoder
  // in this case
  return wibble + wobble;
}
pub fn call_custom_function_on_raw(
  callback: fn(String, Option(Json)) ->
    Result(Dynamic, RequestError),
  function_declaration function_declaration: String,
  object_id object_id: RemoteObjectId,
  args arguments: List(CallArgument),
) -> Result(CallFunctionOnResponse, RequestError)

This is a version of call_custom_function_on which does not attempt to decode the result as a value and just returns it directly instead.
Useful when the return value should be discarded or handled in a custom way.

pub fn click(
  on page: Page,
  target item: RemoteObjectId,
) -> Result(Nil, RequestError)

Simulate a click on an element.
Calls HTMLElement.click() via JavaScript.

pub fn click_selector(
  on page: Page,
  target selector: String,
) -> Result(Nil, RequestError)

Convencience function to simulate a click on an element by selector.
See click for more info.

pub fn close(page: Page) -> Result(Dynamic, RequestError)

Close the passed page

pub fn create_page(
  with browser: Subject(Message),
  from html: String,
  time_out time_out: Int,
) -> Result(Page, RequestError)

Similar to open, but creates a new page from HTML that you pass to it. The page will be created under the about:blank URL.

pub fn defer_quit(
  browser: Subject(Message),
  body: fn() -> a,
) -> Result(Nil, CallError(Nil))

Convenience function that lets you defer quitting the browser after you are done with it, it’s meant for a use expression like this:

let assert Ok(browser_subject) = browser.launch()
use <- browser.defer_quit(browser_subject)
// do stuff with the browser
pub fn down_key(
  on page: Page,
  key key: String,
  modifiers modifiers: Int,
) -> Result(Nil, RequestError)

Simulate a keydown event for a given key.

You can pass in latin characters, digits and some DOM key names, The keymap is based on the US keyboard layout.

⌨️ You can see the supported key values here

pub fn eval(
  on page: Page,
  js expression: String,
) -> Result(RemoteObject, RequestError)

Evaluate some JavaScript on the page and return the result, which will be a runtime.RemoteObject reference.

pub fn eval_async(
  on page: Page,
  js expression: String,
) -> Result(RemoteObject, RequestError)

Like eval, but awaits for the result of the evaluation and returns once promise has been resolved

pub fn eval_to_value(
  on page: Page,
  js expression: String,
) -> Result(RemoteObject, RequestError)
pub fn focus(
  on page: Page,
  target item: RemoteObjectId,
) -> Result(Nil, RequestError)

Focus an element.
Calls HTMLElement.focus() via JavaScript.

pub fn focus_selector(
  on page: Page,
  target selector: String,
) -> Result(Nil, RequestError)

Convenience function to focus an element by selector.
See focus for more info.

pub fn get_all_html(
  on page: Page,
) -> Result(String, RequestError)

Return the entire HTML of the page as a string.
Returns document.documentElement.outerHTML via JavaScript.

pub fn get_attribute(
  on page: Page,
  from item: RemoteObjectId,
  name attribute_name: String,
) -> Result(String, RequestError)

Assuming the passed runtime.RemoteObjectId reference is an Element, return an attribute of that element.
Attributes are always returned as a string.
If the attribute is not found, or the item is not an Element, an error will be returned.

Example

let assert Ok(foo_data) = get_attribute(page, item, "data-foo")
pub fn get_inner_html(
  on page: Page,
  from item: RemoteObjectId,
) -> Result(String, RequestError)

Get the inner HTML of an element. Returns the Element.innerHTML JavaScript property.

pub fn get_outer_html(
  on page: Page,
  from item: RemoteObjectId,
) -> Result(String, RequestError)

Get the outer HTML of an element.
Returns the Element.outerHTML JavaScript property.

pub fn get_property(
  on page: Page,
  from item: RemoteObjectId,
  name property_name: String,
  property_decoder property_decoder: fn(Dynamic) -> Result(a, b),
) -> Result(a, RequestError)

Get a property of a runtime.RemoteObjectId and decode it with the provided decoder

Example

import gleam/dynamic
let assert Ok(link_target) = get_property(page, item, "href", dynamic.string)
pub fn get_text(
  on page: Page,
  from item: RemoteObjectId,
) -> Result(String, RequestError)

Get the text content of an element.
Returns the HTMLElement.innerText property via JavaScript, NOT Node.textContent.
Learn about the differences here.

pub fn insert_char(
  on page: Page,
  key key: String,
) -> Result(Nil, RequestError)

Insert the given character into a focused input field by sending a char keyboard event.
Note that this does not trigger a keydown or keyup event, see press_key for that.
If you want to insert a whole string into an input field, use type_text instead.

pub fn launch() -> Result(Subject(Message), LaunchError)

✨Cleverly✨ try to find a chrome installation and launch it with reasonable defaults.

  1. If CHROBOT_BROWSER_PATH is set, use that
  2. If a local chrome installation is found, use that
  3. If a system chrome installation is found, use that
  4. If none of the above, return an error

If you want to always use a specific chrome installation, take a look at launch_with_config or launch_with_env to set the path explicitly.

This function will validate that the browser launched successfully, and the protocol version matches the one supported by this library.

pub fn launch_window() -> Result(Subject(Message), LaunchError)

Like launch, but launches the browser with a visible window, not in headless mode, which is useful for debugging and development.

pub fn launch_with_config(
  config: BrowserConfig,
) -> Result(Subject(Message), LaunchError)

Launch a browser with the given configuration, to populate the arguments, use chrome.get_default_chrome_args. This function will validate that the browser launched successfully, and the protocol version matches the one supported by this library.

Example

let config =
browser.BrowserConfig(
  path: "chrome/linux-116.0.5793.0/chrome-linux64/chrome",
  args: chrome.get_default_chrome_args(),
  start_timeout: 5000,
)
let assert Ok(browser_subject) = launch_with_config(config)
pub fn launch_with_env() -> Result(Subject(Message), LaunchError)

Launch a browser, and read the configuration from environment variables. The browser path variable must be set, all others will fall back to a default.

This function will validate that the browser launched successfully, and the protocol version matches the one supported by this library.

Configuration variables:

  • CHROBOT_BROWSER_PATH - The path to the browser executable
  • CHROBOT_BROWSER_ARGS - The arguments to pass to the browser, separated by spaces
  • CHROBOT_BROWSER_TIMEOUT - The timeout in milliseconds to wait for the browser to start, must be an integer
  • CHROBOT_LOG_LEVEL - The log level to use, one of silent, warnings, info, debug
pub fn open(
  with browser_subject: Subject(Message),
  to url: String,
  time_out time_out: Int,
) -> Result(Page, RequestError)

Open a new page in the browser. Returns a response when the protocol call succeeds, please use await_selector to determine when the page is ready.
The timeout passed to this function will be attached to the returned Page type to be reused by other functions in this module.
You can always adjust it using with_timeout.

pub fn page_caller(
  page: Page,
) -> fn(String, Option(Json)) -> Result(Dynamic, RequestError)

Create callback to pass to protocol commands from a Page
This is useful when you want to make raw protocol calls

Example

import chrobot.{open, page_caller}
import gleam/option.{None}
import protocol/page
pub fn main() {
  let assert Ok(browser) = chrobot.launch()
  let assert Ok(page) = open(browser, "https://example.com", 5000)
  let callback = page_caller(page)
  let assert Ok(_) =
    page.navigate(callback, "https://gleam.run", None, None, None)
}
pub fn pdf(page: Page) -> Result(EncodedFile, RequestError)

Export the current page as PDF and return it as a base64 encoded string.
Transferring the encoded file from the browser to the chrome agent can take a pretty long time, depending on the document size.
Consider setting a larger timeout, you can use with_timeout on your existing Page to do this. The Ok(result) of this function can be passed to to_file

If you want to customize the settings of the output document, use page.print_to_pdf directly.

pub fn poll(
  callback: fn() -> Result(a, RequestError),
  timeout: Int,
) -> Result(a, RequestError)

Utility to repeatedly call a browser function until it succeeds or times out.

pub fn press_key(
  on page: Page,
  key key: String,
) -> Result(Nil, RequestError)

Simulate a keypress for a given key.
This will trigger a keydown and keyup event in sequence.

You can pass in latin characters, digits and some DOM key names, The keymap is based on the US keyboard layout.

⌨️ You can see the supported key values here

If you want to insert a whole string into an input field, use type_text instead.

pub fn quit(
  browser: Subject(Message),
) -> Result(Nil, CallError(Nil))

Quit the browser (alias for chrome.quit)

pub fn screenshot(
  page: Page,
) -> Result(EncodedFile, RequestError)

Capture a screenshot of the current page and return it as a base64 encoded string The Ok(result) of this function can be passed to to_file

If you want to customize the settings of the output image, use page.capture_screenshot directly.

pub fn select(
  on page: Page,
  matching selector: String,
) -> Result(RemoteObjectId, RequestError)

Run document.querySelector on the page and return a single runtime.RemoteObjectId for the first matching element.

pub fn select_all(
  on page: Page,
  matching selector: String,
) -> Result(List(RemoteObjectId), RequestError)

Run document.querySelectorAll on the page and return a list of runtime.RemoteObjectId items for all matching elements.

pub fn to_file(
  input input: EncodedFile,
  path path: String,
) -> Result(Nil, FileError)

Write an file returned from screenshot or pdf to a file.
File path should not include the file extension, it will be appended automatically!
Will return a FileError from the simplifile package if not successfull

pub fn to_value(
  on page: Page,
  from remote_object_id: RemoteObjectId,
  to decoder: fn(Dynamic) -> Result(a, b),
) -> Result(a, RequestError)

Evalute a runtime.RemoteObjectId to a value, passing in the appropriate decoder function

pub fn type_text(
  on page: Page,
  text input: String,
) -> Result(Nil, RequestError)

Type text by simulating keypresses for each character in the input string.
Note: If a character is not supported by the virtual keyboard, it will be inserted using a char event, which will not produce keydown or keyup events.
⌨️ You can see the key values supported by the virtual keyboard here

If you want to type text into an input field, make sure to focus it first!

pub fn up_key(
  on page: Page,
  key key: String,
  modifiers modifiers: Int,
) -> Result(Nil, RequestError)

Simulate a keyup event for a given key.

You can pass in latin characters, digits and some DOM key names, The keymap is based on the US keyboard layout.

⌨️ You can see the supported key values here

pub fn with_timeout(page: Page, time_out: Int) -> Page

Return an updated Page with the desired timeout to apply, in milliseconds

Search Document