andrewvy / chrome-remote-interface

Elixir Client for the Chrome Debugger Protocol
https://hexdocs.pm/chrome_remote_interface
66 stars 31 forks source link

Document common use-case examples #9

Open andrewvy opened 7 years ago

andrewvy commented 7 years ago

Should benefit by providing some common use-case examples.

These examples are built on unstable version and is not guaranteed to be working in the near future!


Taking a screenshot, quick and dirty, error-prone

defmodule CriExample do
  require Logger

  def init() do
    server = ChromeRemoteInterface.Session.new()
    {:ok, pages} = ChromeRemoteInterface.Session.list_pages(server)
    List.first(pages)
  end

  def take_screenshot(page, url, file_path) do
    start_time = System.monotonic_time()
    {:ok, page_pid} = ChromeRemoteInterface.PageSession.start_link(page)

    ChromeRemoteInterface.PageSession.subscribe(page_pid, "Page.frameStoppedLoading")
    ChromeRemoteInterface.RPC.Page.enable(page_pid)
    ChromeRemoteInterface.RPC.Page.navigate(page_pid, %{url: url})

    receive do
      {:chrome_remote_interface, "Page.frameStoppedLoading", _payload} ->
        {:ok, %{"result" => %{"data" => data}}} = ChromeRemoteInterface.RPC.Page.captureScreenshot(page_pid)
        binary_data = Base.decode64!(data)
        {:ok, file} = File.open(file_path, [:write])
        IO.binwrite(file, binary_data)
        File.close(file)

        stop_time = System.monotonic_time()
        diff = System.convert_time_unit(stop_time - start_time, :native, :micro_seconds)

        Logger.info("url=#{url} file_path=#{file_path} total_time=#{formatted_diff(diff)}")
        ChromeRemoteInterface.PageSession.stop(page_pid)
    after
      10_000 ->
        :error
    end
  end

  defp formatted_diff(diff) when diff > 1000, do: [diff |> div(1000) |> Integer.to_string, "ms"]
  defp formatted_diff(diff), do: [Integer.to_string(diff), "µs"]
end
CriExample.init()
|> CriExample.take_screenshot("https://google.com", "test.png")
johns10 commented 2 years ago

Here's another one I came up with, opening chrome and adding a script to evaluate on new pages (took me a while to figure it out):


  def open_chrome(css) do
    task = Task.async(fn ->
      System.cmd(Paths.chromium_executable_path, Constants.chrome_startup_args())
    end)
    server = ChromeRemoteInterface.Session.new()
    {:ok, pages} = ChromeRemoteInterface.Session.list_pages(server)
    pages
    |> Enum.each(fn(page) ->
      {:ok, page_pid} = ChromeRemoteInterface.PageSession.start_link(page)
      Page.enable(page_pid)
      script = script(css)
      Page.addScriptToEvaluateOnNewDocument(page_pid, %{source: script})
    end)
    {task, server}
  end

  def script(css_text) do
  """
  var style = document.createElement('style');
  style.type = 'text/css';
  style.innerHTML = '#{css_text}'
  var script = document.createElement('script');
  script.type = 'text/javascript'
  script.src = 'http://localhost:4001/assets/annotations.js'
  document.addEventListener('DOMContentLoaded', () => {
    var head = document.getElementsByTagName('head')[0]
    head.appendChild(style);
    head.appendChild(script);
  }, false);
  """
  end
johns10 commented 2 years ago

Here's an example of using evaluate to fetch a remote object and using callfunctionon to operate on the object:


  def get_remote_object("css", selector, page_pid) do
    case Runtime.evaluate(page_pid, %{expression: "document.querySelector('#{selector}');"}) do
      {:ok, %{"result" => %{"result" => %{"subtype" => "null", "type" => "object", "value" => nil}}}} ->
        {:warning, "Element Not Found in Browser"}
      {:ok, %{"result" => %{"result" => remote_object}}} -> {:ok, cast_remote_object(remote_object)}
    end
  end
  def get_remote_object("xpath", selector, page_pid) do
    opts = %{
      includeCommandLineAPI: true,
      expression: "elements = $x('#{selector}'); elements[0];"
    }
    case Runtime.evaluate(page_pid, opts) do
      {:ok, %{"result" => %{"result" => %{"type" => "undefined"}}}} -> {:warning, "Element Not Found"}
      {:ok, %{"result" => %{"result" => remote_object}}} -> {:ok, cast_remote_object(remote_object)}
    end
  end

  def cast_remote_object(%{
    "className" => class_name,
    "description" => description,
    "objectId" => object_id,
    "subtype" => subtype,
    "type" => type
  }) do
    %{
      class_name: class_name,
      description: description,
      object_id: object_id,
      subtype: subtype,
      type: type
    }
  end
  def execute_command({:fill_field, %{strategy: strategy, selector: selector, text: text}}, page_pid) do
    with {:ok, remote_object} <-
        Utilities.get_remote_object(strategy, selector, page_pid),
      {:ok, %{"result" => _focus_result}} <-
        DOM.focus(page_pid, %{objectId: remote_object.object_id}),
      :ok <- type_text(page_pid, text),
      {:ok, %{"result" => result}} <-
        Runtime.callFunctionOn(page_pid, %{
          arguments: [%{objectId: remote_object.object_id}],
          functionDeclaration: "(element) => {element.blur()}",
          objectId: remote_object.object_id
        })
    do
      {:ok, result}
    else
      {:error, error_object} -> cast_error(error_object)
      {:warning, message} -> {:warning, message}
    end
  end