lib/container/selenium_container.ex

# SPDX-License-Identifier: MIT
defmodule Testcontainers.SeleniumContainer do
  @moduledoc """
  Work in progress. Not stable for use yet. Not yet documented for this very reason.
  Can use https://github.com/stuart/elixir-webdriver for client in tests
  """
  alias Testcontainers.ContainerBuilder
  alias Testcontainers.Container
  alias Testcontainers.SeleniumContainer
  alias Testcontainers.PortWaitStrategy
  alias Testcontainers.LogWaitStrategy

  import Testcontainers.Container, only: [is_valid_image: 1]

  @default_image "selenium/standalone-chrome"
  @default_tag "118.0"
  @default_image_with_tag "#{@default_image}:#{@default_tag}"
  @default_port1 7900
  @default_port2 4400
  @default_wait_timeout 120_000

  @enforce_keys [:image, :port1, :port2, :wait_timeout]
  defstruct [
    :image,
    :port1,
    :port2,
    :wait_timeout,
    check_image: @default_image,
    reuse: false
  ]

  def new,
    do: %__MODULE__{
      image: @default_image_with_tag,
      wait_timeout: @default_wait_timeout,
      port1: @default_port1,
      port2: @default_port2
    }

  def with_image(%__MODULE__{} = config, image) when is_binary(image) do
    %{config | image: image}
  end

  def with_port1(%__MODULE__{} = config, port1) when is_integer(port1) do
    %{config | port1: port1}
  end

  def with_port2(%__MODULE__{} = config, port2) when is_integer(port2) do
    %{config | port2: port2}
  end

  def with_wait_timeout(%__MODULE__{} = config, wait_timeout) when is_integer(wait_timeout) do
    %{config | wait_timeout: wait_timeout}
  end

  @doc """
  Set the regular expression to check the image validity.
  """
  def with_check_image(%__MODULE__{} = config, check_image) when is_valid_image(check_image) do
    %__MODULE__{config | check_image: check_image}
  end

  @doc """
  Set the reuse flag to reuse the container if it is already running.
  """
  def with_reuse(%__MODULE__{} = config, reuse) when is_boolean(reuse) do
    %__MODULE__{config | reuse: reuse}
  end

  def default_image, do: @default_image

  defimpl ContainerBuilder do
    import Container

    @spec build(%SeleniumContainer{}) :: %Container{}
    @impl true
    def build(%SeleniumContainer{} = config) do
      new(config.image)
      |> with_exposed_ports([config.port1, config.port2])
      |> with_waiting_strategies([
        LogWaitStrategy.new(~r/.*(RemoteWebDriver instances should connect to|Selenium Server is up and running|Started Selenium Standalone).*\n/, config.wait_timeout, 1000),
        PortWaitStrategy.new("127.0.0.1", config.port1, config.wait_timeout, 1000),
        PortWaitStrategy.new("127.0.0.1", config.port2, config.wait_timeout, 1000)
      ])
      |> with_check_image(config.check_image)
      |> with_reuse(config.reuse)
      |> valid_image!()
    end

    @impl true
    def after_start(_config, _container, _conn), do: :ok
  end
end