chrobot
Welcome to Chrobot! 🤖 This module exposes high level functions for browser automation.
Some basic concepts:
- You’ll first want to
launch
an instance of the browser and receive aSubject
which allows you to send messages to the browser (actor) - You can
open
aPage
, which makes the browser browse to a website - Use
await_selector
to wait for an element to appear on the page before you interact with it! - You can interact with the page by calling functions in this module with the
Page
you received fromopen
- For extracting information from the page, select elements with
select
orselect_all
, then useget_text
,get_attribute
,get_property
orget_inner_html
- To simulate user input, use
press_key
,type_text
,click
andfocus
- If you want to make raw protocol calls, you can use
page_caller
, to create a callback to pass to protocol commands from yourPage
- When you are done with the browser, you should call
quit
to shut it down gracefully
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_object(
callback: fn(String, Option(Json)) ->
Result(Dynamic, RequestError),
function_declaration function_declaration: String,
object_id object_id: RemoteObjectId,
args arguments: List(CallArgument),
) -> Result(RemoteObject, RequestError)
This is a version of call_custom_function_on
which returns remote objects instead of values.
Useful when you want to pass the result to another function that expects a remote object.
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 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.
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)
pub fn launch() -> Result(Subject(Message), LaunchError)
✨Cleverly✨ try to find a chrome installation and launch it with reasonable defaults.
- If
CHROBOT_BROWSER_PATH
is set, use that - If a local chrome installation is found, use that
- If a system chrome installation is found, use that
- 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 executableCHROBOT_BROWSER_ARGS
- The arguments to pass to the browser, separated by spacesCHROBOT_BROWSER_TIMEOUT
- The timeout in milliseconds to wait for the browser to start, must be an integerCHROBOT_LOG_LEVEL
- The log level to use, one ofsilent
,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 select_all_from(
on page: Page,
from item: RemoteObjectId,
matching selector: String,
) -> Result(List(RemoteObjectId), RequestError)
Run Element.querySelectorAll
on the given
element and return a list of runtime.RemoteObjectId
items
for all matching child elements.
pub fn select_from(
on page: Page,
from item: RemoteObjectId,
matching selector: String,
) -> Result(RemoteObjectId, RequestError)
Run Element.querySelector
on the given
element and return a single runtime.RemoteObjectId
for the first matching child element.
pub fn to_file(
input input: EncodedFile,
path path: String,
) -> Result(Nil, FileError)
Write a 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.
pub fn with_timeout(page: Page, time_out: Int) -> Page
Return an updated Page
with the desired timeout to apply, in milliseconds