Current section

Files

Jump to
chrobot src chrobot.erl
Raw

src/chrobot.erl

-module(chrobot).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([open/3, with_timeout/2, to_file/2, as_value/2, quit/1, defer_quit/2, page_caller/1, close/1, screenshot/1, pdf/1, await_load_event/2, launch/0, launch_with_config/1, eval/2, eval_to_value/2, eval_async/2, get_all_html/1, select_all/2, select/2, call_custom_function_on/5, to_value/3, get_attribute/3, get_property/4, get_text/2, get_inner_html/2, get_outer_html/2, await_selector/2, create_page/3]).
-export_type([page/0, encoded_file/0, call_argument/0]).
-type page() :: {page,
gleam@erlang@process:subject(chrome:message()),
integer(),
protocol@target:target_id(),
protocol@target:session_id()}.
-type encoded_file() :: {encoded_file, binary(), binary()}.
-type call_argument() :: {string_arg, binary()} |
{int_arg, integer()} |
{float_arg, float()} |
{bool_arg, boolean()} |
{array_arg, list(call_argument())}.
-spec open(gleam@erlang@process:subject(chrome:message()), binary(), integer()) -> {ok,
page()} |
{error, chrome:request_error()}.
open(Browser_subject, Url, Time_out) ->
gleam@result:'try'(
protocol@target:create_target(
fun(Method, Params) ->
chrome:call(Browser_subject, Method, Params, none, Time_out)
end,
Url,
{some, 1920},
{some, 1080},
none,
none
),
fun(Target_response) ->
gleam@result:'try'(
protocol@target:attach_to_target(
fun(Method@1, Params@1) ->
chrome:call(
Browser_subject,
Method@1,
Params@1,
none,
Time_out
)
end,
erlang:element(2, Target_response),
{some, true}
),
fun(Session_response) ->
{ok,
{page,
Browser_subject,
Time_out,
erlang:element(2, Target_response),
erlang:element(2, Session_response)}}
end
)
end
).
-spec with_timeout(page(), integer()) -> page().
with_timeout(Page, Time_out) ->
{page,
erlang:element(2, Page),
Time_out,
erlang:element(4, Page),
erlang:element(5, Page)}.
-spec to_file(encoded_file(), binary()) -> {ok, nil} |
{error, simplifile:file_error()}.
to_file(Input, Path) ->
Res = begin
_pipe = gleam@bit_array:base64_decode(erlang:element(2, Input)),
gleam@result:replace_error(_pipe, unknown)
end,
gleam@result:'try'(
Res,
fun(Binary) ->
simplifile:write_bits(
<<<<Path/binary, "."/utf8>>/binary,
(erlang:element(3, Input))/binary>>,
Binary
)
end
).
-spec as_value(
{ok, protocol@runtime:remote_object()} | {error, chrome:request_error()},
fun((gleam@dynamic:dynamic_()) -> {ok, XDU} | {error, any()})
) -> {ok, XDU} | {error, chrome:request_error()}.
as_value(Result, Decoder) ->
case Result of
{ok, {remote_object, _, _, _, {some, Value}, _, _, _}} ->
_pipe = Decoder(Value),
gleam@result:replace_error(_pipe, protocol_error);
{error, Something} ->
{error, Something};
_ ->
{error, not_found_error}
end.
-spec quit(gleam@erlang@process:subject(chrome:message())) -> {ok, nil} |
{error, gleam@erlang@process:call_error(nil)}.
quit(Browser) ->
chrome:quit(Browser).
-spec defer_quit(
gleam@erlang@process:subject(chrome:message()),
fun(() -> any())
) -> {ok, nil} | {error, gleam@erlang@process:call_error(nil)}.
defer_quit(Browser, Body) ->
Body(),
chrome:quit(Browser).
-spec pass_session(protocol@target:session_id()) -> gleam@option:option(binary()).
pass_session(Session_id) ->
case Session_id of
{session_id, Value} ->
{some, Value}
end.
-spec page_caller(page()) -> fun((binary(), gleam@option:option(gleam@json:json())) -> {ok,
gleam@dynamic:dynamic_()} |
{error, chrome:request_error()}).
page_caller(Page) ->
fun(Method, Params) ->
chrome:call(
erlang:element(2, Page),
Method,
Params,
pass_session(erlang:element(5, Page)),
erlang:element(3, Page)
)
end.
-spec close(page()) -> {ok, gleam@dynamic:dynamic_()} |
{error, chrome:request_error()}.
close(Page) ->
protocol@target:close_target(page_caller(Page), erlang:element(4, Page)).
-spec screenshot(page()) -> {ok, encoded_file()} |
{error, chrome:request_error()}.
screenshot(Page) ->
gleam@result:'try'(
protocol@page:capture_screenshot(
page_caller(Page),
{some, capture_screenshot_format_png},
none,
none
),
fun(Response) ->
{ok, {encoded_file, erlang:element(2, Response), <<"png"/utf8>>}}
end
).
-spec pdf(page()) -> {ok, encoded_file()} | {error, chrome:request_error()}.
pdf(Page) ->
gleam@result:'try'(
protocol@page:print_to_pdf(
page_caller(Page),
{some, false},
{some, false},
none,
none,
none,
none,
none,
none,
none,
none,
none,
none,
none,
none
),
fun(Response) ->
{ok, {encoded_file, erlang:element(2, Response), <<"pdf"/utf8>>}}
end
).
-spec await_load_event(gleam@erlang@process:subject(chrome:message()), page()) -> {ok,
gleam@dynamic:dynamic_()} |
{error, chrome:request_error()}.
await_load_event(Browser, Page) ->
gleam@result:'try'(
protocol@page:enable(page_caller(Page)),
fun(_) ->
chrome:listen_once(
Browser,
<<"Page.loadEventFired"/utf8>>,
erlang:element(3, Page)
)
end
).
-spec validate_launch(
{ok, gleam@erlang@process:subject(chrome:message())} |
{error, chrome:launch_error()}
) -> {ok, gleam@erlang@process:subject(chrome:message())} |
{error, chrome:launch_error()}.
validate_launch(Launch_result) ->
gleam@result:'try'(
Launch_result,
fun(Instance) ->
{Major, Minor} = protocol:version(),
Target_protocol_version = <<<<Major/binary, "."/utf8>>/binary,
Minor/binary>>,
Version_response = begin
_pipe = chrome:get_version(Instance),
gleam@result:replace_error(_pipe, unresponsive_after_start)
end,
gleam@result:'try'(
Version_response,
fun(Actual_version) ->
case Target_protocol_version =:= erlang:element(
2,
Actual_version
) of
true ->
{ok, Instance};
false ->
{error,
{protocol_version_mismatch,
Target_protocol_version,
erlang:element(2, Actual_version)}}
end
end
)
end
).
-spec launch() -> {ok, gleam@erlang@process:subject(chrome:message())} |
{error, chrome:launch_error()}.
launch() ->
Launch_result = validate_launch(chrome:launch()),
case Launch_result of
{error, could_not_find_executable} ->
gleam@io:println(
<<"\x{1b}[31mChrobot could not find a chrome executable to launch!\x{1b}[0m"/utf8>>
),
gleam@io:println(<<"\x{1b}[36m"/utf8>>),
gleam@io:println(
<<"ℹ️ Hint: Consider installing Chrome for Testing from puppeteer:"/utf8>>
),
gleam@io:println(<<"npx @puppeteer/browsers install chrome"/utf8>>),
gleam@io:println(<<"\x{1b}[0m "/utf8>>),
Launch_result;
Other ->
Other
end.
-spec launch_with_config(chrome:browser_config()) -> {ok,
gleam@erlang@process:subject(chrome:message())} |
{error, chrome:launch_error()}.
launch_with_config(Config) ->
validate_launch(chrome:launch_with_config(Config)).
-spec handle_eval_response(
{ok, protocol@runtime:evaluate_response()} | {error, chrome:request_error()}
) -> {ok, protocol@runtime:remote_object()} | {error, chrome:request_error()}.
handle_eval_response(Eval_response) ->
case Eval_response of
{ok, {evaluate_response, _, {some, Exception}}} ->
{error,
{runtime_exception,
erlang:element(3, Exception),
erlang:element(4, Exception),
erlang:element(5, Exception)}};
{ok, {evaluate_response, Result_data, none}} ->
{ok, Result_data};
{error, Other} ->
{error, Other}
end.
-spec eval(page(), binary()) -> {ok, protocol@runtime:remote_object()} |
{error, chrome:request_error()}.
eval(Page, Expression) ->
_pipe = protocol@runtime:evaluate(
page_caller(Page),
Expression,
none,
none,
{some, false},
none,
{some, false},
{some, true},
{some, false}
),
handle_eval_response(_pipe).
-spec eval_to_value(page(), binary()) -> {ok, protocol@runtime:remote_object()} |
{error, chrome:request_error()}.
eval_to_value(Page, Expression) ->
_pipe = protocol@runtime:evaluate(
page_caller(Page),
Expression,
none,
none,
{some, false},
none,
{some, true},
{some, true},
{some, false}
),
handle_eval_response(_pipe).
-spec eval_async(page(), binary()) -> {ok, protocol@runtime:remote_object()} |
{error, chrome:request_error()}.
eval_async(Page, Expression) ->
_pipe = protocol@runtime:evaluate(
page_caller(Page),
Expression,
none,
none,
{some, false},
none,
{some, false},
{some, true},
{some, true}
),
handle_eval_response(_pipe).
-spec get_all_html(page()) -> {ok, binary()} | {error, chrome:request_error()}.
get_all_html(Page) ->
_pipe = eval(Page, <<"window.document.documentElement.outerHTML"/utf8>>),
as_value(_pipe, fun gleam@dynamic:string/1).
-spec select_all(page(), binary()) -> {ok,
list(protocol@runtime:remote_object_id())} |
{error, chrome:request_error()}.
select_all(Page, Selector) ->
Selector_code = <<<<"window.document.querySelectorAll(\""/utf8,
Selector/binary>>/binary,
"\")"/utf8>>,
Result = eval(Page, Selector_code),
case Result of
{ok, {remote_object, _, _, _, _, _, _, {some, Remote_object_id}}} ->
gleam@result:'try'(
protocol@runtime:get_properties(
page_caller(Page),
Remote_object_id,
{some, true}
),
fun(Result_properties) -> case Result_properties of
{get_properties_response, _, _, {some, Exception}} ->
{error,
{runtime_exception,
erlang:element(3, Exception),
erlang:element(4, Exception),
erlang:element(5, Exception)}};
{get_properties_response, Property_descriptors, _, none} ->
{ok,
gleam@list:filter_map(
Property_descriptors,
fun(Prop_descriptor) ->
case Prop_descriptor of
{property_descriptor,
_,
{some,
{remote_object,
_,
_,
_,
_,
_,
_,
{some, Object_id}}},
_,
_,
_,
_,
_,
_,
_,
_} ->
{ok, Object_id};
_ ->
{error, nil}
end
end
)}
end end
);
{ok, _} ->
{ok, []};
{error, Any} ->
{error, Any}
end.
-spec handle_object_id_response(
{ok, protocol@runtime:remote_object()} | {error, chrome:request_error()}
) -> {ok, protocol@runtime:remote_object_id()} | {error, chrome:request_error()}.
handle_object_id_response(Response) ->
case Response of
{ok, {remote_object, _, _, _, _, _, _, {some, Remote_object_id}}} ->
{ok, Remote_object_id};
{ok, _} ->
{error, not_found_error};
{error, Any} ->
{error, Any}
end.
-spec select(page(), binary()) -> {ok, protocol@runtime:remote_object_id()} |
{error, chrome:request_error()}.
select(Page, Selector) ->
Selector_code = <<<<"window.document.querySelector(\""/utf8,
Selector/binary>>/binary,
"\")"/utf8>>,
_pipe = eval(Page, Selector_code),
handle_object_id_response(_pipe).
-spec encode_custom_arg(call_argument()) -> gleam@json:json().
encode_custom_arg(Arg) ->
case Arg of
{string_arg, Value} ->
gleam@json:string(Value);
{int_arg, Value@1} ->
gleam@json:int(Value@1);
{float_arg, Value@2} ->
gleam@json:float(Value@2);
{bool_arg, Value@3} ->
gleam@json:bool(Value@3);
{array_arg, Value@4} ->
gleam@json:array(Value@4, fun encode_custom_arg/1)
end.
-spec encode_custom_arguments(list(call_argument())) -> gleam@json:json().
encode_custom_arguments(Input) ->
gleam@json:array(
Input,
fun(Arg) ->
gleam@json:object([{<<"value"/utf8>>, encode_custom_arg(Arg)}])
end
).
-spec call_custom_function_on(
fun((binary(), gleam@option:option(gleam@json:json())) -> {ok,
gleam@dynamic:dynamic_()} |
{error, chrome:request_error()}),
binary(),
protocol@runtime:remote_object_id(),
list(call_argument()),
fun((gleam@dynamic:dynamic_()) -> {ok, XKU} | {error, any()})
) -> {ok, XKU} | {error, chrome:request_error()}.
call_custom_function_on(
Callback,
Function_declaration,
Object_id,
Arguments,
Value_decoder
) ->
Encoded_arguments = encode_custom_arguments(Arguments),
Payload = {some,
gleam@json:object(
[{<<"functionDeclaration"/utf8>>,
gleam@json:string(Function_declaration)},
{<<"objectId"/utf8>>,
protocol@runtime:encode__remote_object_id(Object_id)},
{<<"arguments"/utf8>>, Encoded_arguments},
{<<"returnByValue"/utf8>>, gleam@json:bool(true)}]
)},
gleam@result:'try'(
Callback(<<"Runtime.callFunctionOn"/utf8>>, Payload),
fun(Result) ->
gleam@result:'try'(
begin
_pipe = protocol@runtime:decode__call_function_on_response(
Result
),
gleam@result:replace_error(_pipe, protocol_error)
end,
fun(Decoded_response) -> case Decoded_response of
{call_function_on_response, _, {some, Exception}} ->
{error,
{runtime_exception,
erlang:element(3, Exception),
erlang:element(4, Exception),
erlang:element(5, Exception)}};
{call_function_on_response,
{remote_object, _, _, _, {some, Value}, _, _, _},
none} ->
_pipe@1 = Value_decoder(Value),
gleam@result:replace_error(_pipe@1, protocol_error);
_ ->
{error, not_found_error}
end end
)
end
).
-spec to_value(
page(),
protocol@runtime:remote_object_id(),
fun((gleam@dynamic:dynamic_()) -> {ok, XKV} | {error, any()})
) -> {ok, XKV} | {error, chrome:request_error()}.
to_value(Page, Remote_object_id, Decoder) ->
Declaration = <<"function to_value(){
return JSON.stringify(this)
}"/utf8>>,
call_custom_function_on(
page_caller(Page),
Declaration,
Remote_object_id,
[],
Decoder
).
-spec get_attribute(page(), protocol@runtime:remote_object_id(), binary()) -> {ok,
binary()} |
{error, chrome:request_error()}.
get_attribute(Page, Item, Attribute_name) ->
Declaration = <<"function get_arg(attribute_name)
{
return this.getAttribute(attribute_name)
}
"/utf8>>,
call_custom_function_on(
page_caller(Page),
Declaration,
Item,
[{string_arg, Attribute_name}],
fun gleam@dynamic:string/1
).
-spec get_property(
page(),
protocol@runtime:remote_object_id(),
binary(),
fun((gleam@dynamic:dynamic_()) -> {ok, XLB} | {error, any()})
) -> {ok, XLB} | {error, chrome:request_error()}.
get_property(Page, Item, Property_name, Property_decoder) ->
Declaration = <<"function get_prop(property_name)
{
return this[property_name]
}
"/utf8>>,
call_custom_function_on(
page_caller(Page),
Declaration,
Item,
[{string_arg, Property_name}],
Property_decoder
).
-spec get_text(page(), protocol@runtime:remote_object_id()) -> {ok, binary()} |
{error, chrome:request_error()}.
get_text(Page, Item) ->
get_property(Page, Item, <<"innerText"/utf8>>, fun gleam@dynamic:string/1).
-spec get_inner_html(page(), protocol@runtime:remote_object_id()) -> {ok,
binary()} |
{error, chrome:request_error()}.
get_inner_html(Page, Item) ->
get_property(Page, Item, <<"innerHTML"/utf8>>, fun gleam@dynamic:string/1).
-spec get_outer_html(page(), protocol@runtime:remote_object_id()) -> {ok,
binary()} |
{error, chrome:request_error()}.
get_outer_html(Page, Item) ->
get_property(Page, Item, <<"outerHTML"/utf8>>, fun gleam@dynamic:string/1).
-spec poll(fun(() -> {ok, XBE} | {error, XBF}), integer()) -> {ok, XBE} |
{error, XBF}.
poll(Callback, Remaining_time) ->
case Callback() of
{ok, A} ->
{ok, A};
{error, B} when Remaining_time =< 0 ->
{error, B};
{error, _} ->
gleam_erlang_ffi:sleep(100),
poll(Callback, Remaining_time - 100)
end.
-spec await_selector(page(), binary()) -> {ok,
protocol@runtime:remote_object_id()} |
{error, chrome:request_error()}.
await_selector(Page, Selector) ->
Polly = fun() ->
_pipe = eval(
Page,
<<<<"window.document.querySelector(\""/utf8, Selector/binary>>/binary,
"\")"/utf8>>
),
handle_object_id_response(_pipe)
end,
poll(Polly, erlang:element(3, Page)).
-spec create_page(
gleam@erlang@process:subject(chrome:message()),
binary(),
integer()
) -> {ok, page()} | {error, chrome:request_error()}.
create_page(Browser, Html, Time_out) ->
gleam@result:'try'(
open(Browser, <<"about:blank"/utf8>>, Time_out),
fun(Created_page) ->
gleam@result:'try'(
await_selector(Created_page, <<"body"/utf8>>),
fun(_) ->
Payload = <<<<"window.document.open();
window.document.write(`"/utf8,
Html/binary>>/binary,
"`);
window.document.close();
"/utf8>>,
gleam@result:'try'(
eval(Created_page, Payload),
fun(_) -> {ok, Created_page} end
)
end
)
end
).