Setting up Wallaby with Docker Compose, Chrome and Selenium

I am trying to get Wallaby setup with Chrome and Selenium (these are hosted as docker compose), here is my docker compose file:

...
  chrome:
    image: selenium/node-chrome:3.14.0-gallium
    volumes:
      - /dev/shm:/dev/shm
    depends_on:
      - hub
    environment:
      HUB_HOST: hub

  hub:
    image: selenium/hub:3.14.0-gallium
    ports:
      - "4444:4444"

When I run the tests I get the following error:

** (RuntimeError) Wallaby had an internal issue with HTTPoison:
     %HTTPoison.Error{id: nil, reason: :econnrefused}
     stacktrace:
       (wallaby 0.25.0) lib/wallaby/httpclient.ex:50: Wallaby.HTTPClient.make_request/5
       (wallaby 0.25.0) lib/wallaby/selenium.ex:92: Wallaby.Selenium.start_session/1
       (wallaby 0.25.0) lib/wallaby.ex:90: Wallaby.start_session/1
       (wallaby 0.25.0) lib/wallaby/feature.ex:70: Wallaby.Feature.start_session/2
       (elixir 1.10.3) lib/enum.ex:1400: anonymous fn/3 in Enum.map/2
       (elixir 1.10.3) lib/enum.ex:2116: Enum.map/2
       test/hinesh_blogs_web/feature/admin_test.exs:3: HineshBlogsWeb.AdminTest.__ex_unit_setup_0/1
       test/hinesh_blogs_web/feature/admin_test.exs:1: HineshBlogsWeb.AdminTest.__ex_unit__/2

Here is my config/test.exs:

# Chrome
config :wallaby, driver: Wallaby.Chrome

# Selenium
config :wallaby, driver: Wallaby.Selenium

config :wallaby,
  hackney_options: [timeout: 5_000]

And here is the sample test I am trying to run:

defmodule HineshBlogsWeb.AdminTest do
  use ExUnit.Case, async: true
  use Wallaby.Feature

  setup do
    Wallaby.start_session(
      remote_url: "http://localhost:4444/wd/hub/",
      capabilities: %{browserName: "firefox"}
    )
  end

  feature "user can login", %{session: session} do
    session
    |> visit("/admin/login")
    |> assert(false)
  end
end

I am banging my head against the wall trying to solve this, any help would be appreciated.

I don’t have any experience with Wallaby, chrome or selenium, but I have some with Docker.

Can you try:

 "http://hub:4444/wd/hub/",

So you are replacing localhost by the name of the service in docker compose, because that’s how containers communicate between them. Using localhost inside a container will reach the container internal network, not the your host network or the other container network.

Nope didn’t work :frowning:

There has to be a way for it to work with containers but can’t figure it out.

Are you trying to use the Chromedriver Wallaby backend or the Selenium Wallaby backend? If you have both of the above lines in your config, you’ll end up using Selenium. I am not familiar with how the selenium/hub and selenium/node-chrome docker images work but it seems that they are exposing Selenium.

Looking at the Feature module documentation, won’t that result in the test trying to create two browser instances? I think the feature macro will call start_session for you already, but I may be wrong.

I think to set capabilities when using the feature macro, the @sessions module attribute should be used. But you should double check with the documentation.

Are you sure that Firefox is available to be started by your selenium hub? You can check that by navigating to http://localhost:4444/wd/hub/ in your browser, clicking Create Browser and selecting Firefox in the dropdown.

Hi there,

I have been setting up Wallaby in docker with my new Phoenix app and today I finished setting up. It works very well and I have no problems, but I am using the default Wallaby.Chrome driver.

Out of curiousity, I added this one line to my config/test.exs:

config :wallaby, driver: Wallaby.Selenium

and ran the tests again. I immediately got this error:

** (RuntimeError) Wallaby had an internal issue with HTTPoison:
     %HTTPoison.Error{id: nil, reason: :checkout_timeout}
     %HTTPoison.Error{id: nil, reason: :econnrefused}
     stacktrace:
       (wallaby 0.26.2) lib/wallaby/httpclient.ex:50: Wallaby.HTTPClient.make_request/5
       (wallaby 0.26.2) lib/wallaby/selenium.ex:92: Wallaby.Selenium.start_session/1

which seems to be the same problem as you.

Then I saw this:

which describes the same problem over two years ago.

I strongly recommend not using Wallaby.Selenium and sticking with the default Wallaby.Chrome driver.

The Wallaby.Chrome driver will start chromedriver for you, whereas the Wallaby.Selenium driver does not start the selenium server automatically. I believe you are getting that error because you have not started the selenium server.

This issue you linked is regarding the old phantom js driver, so I don’t think it is relevant.

For the top-level post, to get selenium to run using chrome and Wallaby.Feature, you can set the default capabilities in your application config to use chrome

config :wallaby,
  driver: Wallaby.Selenium,
  selenium: [
    capabilities: %{
      javascriptEnabled: true,
      browserName: "chrome",
      "chromeOptions": %{
         args: [
           "--no-sandbox",
           "window-size=1280,800",
           "--disable-gpu",
           "--headless",
           "--fullscreen",
           "--user-agent=Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
        ]
      }
    }
  ]

If you are running selenium and your tests using docker-compose, you need to make sure to set the remote_url to the appropriate value, using the docker-compose created network http://hub:4444/wd/hub/. This currently isn’t supported by app config, so if using with the Wallaby.Feature module, you’ll need to pass it in as an option to start_session.

defmodule MyAppWeb.AFeatureTest do
  use ExUnit.Case
  use Wallaby.Feature
  
  @sessions [[remote_url: "http://hub:4444/wd/hub"]]
  feature "my test", %{session: session} do
    # ...
  end
end

Or, you can write your own ExUnit.CaseTemplate to start this in a setup, while using import Wallaby.Feature instead of use Wallaby.Feature. You can find an example of this at the bottom of this page: https://hexdocs.pm/wallaby/Wallaby.Feature.html?#feature/3.

I apologize for just seeing this, but I recently set an alert on the #wallaby tag. I should be notified of any further questions :smile:

2 Likes

Thanks for the clarification.

Can you advise please - is there any reason why Selenium driver might be preferred over Chrome driver?

You would want to use the selenium driver in order to test with Firefox, edge, or safari. Or if you’re using a hosted selenium service.

2 Likes

There is a crucial problem in this that you should change. The remote_url has to end in a trailing slash / or else it won’t work.

You can see this in webdriver_client.ex in the wallaby lib:

    request(:post, "#{base_url}session", params)

It just does bare string interpolation.

Good point to mention. That should be fixed. Thanks!

1 Like

While I have your attention, I am getting a problem while trying to use Selenium (default driver works fine for me). I am using

    {:ok, session} = Wallaby.start_session(metadata: metadata, capabilities: chrome_capabilities, remote_url: "http://selenium-hub:4444/wd/hub/")

and I get this error:

* (RuntimeError) Content-Type header does not indicate utf-8 encoded json: application/json
     stacktrace:
       (wallaby 0.26.2) lib/wallaby/httpclient.ex:136: Wallaby.HTTPClient.check_for_response_errors/1
       (wallaby 0.26.2) lib/wallaby/httpclient.ex:56: Wallaby.HTTPClient.make_request/5
       (wallaby 0.26.2) lib/wallaby/selenium.ex:92: Wallaby.Selenium.start_session/1
       (wallaby 0.26.2) lib/wallaby.ex:83: Wallaby.start_session/1

Any ideas? No need to spend much time on it, just if you have any “hunch” of what might be happening.

What version of selenium are you using? I’d make sure you’re using the latest version.

Using docker hub selenium tag 4.0.0 (released 6 days ago)

Oh, I didn’t know v4 was released! I’ll have to rerun the tests to see if the suite fails.

Can you open an issue for making sure v4 works? I’d appreciate it. Thanks!

OK I opened a github issue to remind to test against v4.

I am pulling 3.141 images now. Hopefully it works (or not, for you I guess, since that means work needs to be done to work with v4).

I can confirm that when I downgrade to v3.141 of selenium-hub then I get a different error:

Error forwarding the new session Empty pool of VM for setup Capabilities {browserName: chrome, chromeOptions: {args: [--
no-sandbox, window-size=1280,800, --disable-gpu, --headless, --fullscreen, --user-agent=Mozilla/5.0 (W...]}, javascriptEnabled: true}

so I think there is some issue with v4.

I’m pretty sure this new problem of mine is a selenium-hub docker issue, not a wallaby issue.

I get the same error, any luck solving it?

I cannot remember. I have kind of given up on Wallaby. I get random failures when I do any tests that involve more than 10 interactions (which I really really need - I want to test entire workflows that can include up to 100 interactions in a single test). Here is my setup anyway (I stopped getting that error so maybe it will help you). The sanity tests pass reliably for me (since they do a small number of interactions):

sanity_test.exs

defmodule MyApp.WallabyTest do
  use MyAppWeb.WallabyCase, async: false

  feature "login allows good user", %{session: session} do
    session
    |> auth_user(session, "use@myapp.comr", "wallaby-user-password")
  end

  feature "login denies non-existent user", %{session: session} do
    session
    |> visit("#{@backend_url}session/new")
    |> find(@login_form, fn form ->
      form
      |> fill_in(@login_email_field, with: "a")
      |> fill_in(@login_password_field, with: "a")
      |> click(@login_submit_button)
    end)
    |> assert_has(css(".bg-red-100", text: "Wrong email or password"))
  end

  feature "frontend can be reached", %{session: session} do
    session
    |> visit("#{@frontend_url}")
    |> assert_has(css("#home-wrapper"))
  end
end

wallaby_case.ex

defmodule MyAppWeb.WallabyCase do
  @moduledoc """
  """

  use ExUnit.CaseTemplate

  using do
    quote do
      use Wallaby.Feature
      import Wallaby.{Browser, Query}

      alias MyAppWeb.Router.Helpers, as: Routes

      # absolute urls for backend and frontend
      @backend_url "http://localhost:4002/"
      @frontend_url "http://frontend:3000/"

      # wallaby elements for logging in
      @login_form css("form")
      @login_email_field text_field("Email")
      @login_password_field text_field("Password")
      @login_submit_button button("Sign In")

      defp auth_user(session, user, password) do
        session
        |> visit("#{@backend_url}session/new")
        |> find(@login_form, fn form ->
          form
          |> fill_in(@login_email_field, with: user)
          |> fill_in(@login_password_field, with: password)
          |> click(@login_submit_button)
        end)
        |> assert_has(css("#orders-table"))
      end

    end
  end

  setup tags do
    alias Ecto.Adapters.SQL.Sandbox
    alias MyApp.Accounts.User
    alias MyApp.{Repo, Seeds}

    unless tags[:async] do
      :ok = Sandbox.checkout(MyApp.Repo)
      Sandbox.mode(MyApp.Repo, {:shared, self()})
    end

    Seeds.run()

    %User{}
    |> User.changeset(%{
      email: "user@myapp.com",
      name: "user",
      password: "wallaby-user-password"
    })
    |> Repo.insert!()

    :ok
  end
end

docker-compose.override.yml

version: "3.8"
services:

  db:
    image: postgres:12.3
    env_file:
      - ./my-app-backend/docker/dev.env
    restart: always
    ports:
      - "5432:5432"
    volumes:
      - db-data:/var/lib/postgresql/data

  backend:
    image: my-app-backend:local
    build:
      context: ./my-app-backend
      dockerfile: ./docker/Dockerfile.dev
    depends_on:
      - db
      - frontend # so the wallaby tests pass
    env_file:
      - ./my-app-backend/docker/dev.env
    ports:
      - "4000:4000"
    volumes:
      - /opt/my-app/assets/node_modules
      - ./my-app-backend/assets:/opt/my-app/assets:ro
      - ./my-app-backend/config:/opt/my-app/config:ro
      - ./my-app-backend/lib:/opt/my-app/lib:ro
      - ./my-app-backend/priv:/opt/my-app/priv
      - ./my-app-backend/test:/opt/my-app/test:ro
      - ./my-app-backend/seeds:/opt/my-app/seeds:ro
      - ./my-app-backend/mix.exs:/opt/my-app/mix.exs:ro
      - ./my-app-backend/mix.lock:/opt/my-app/mix.lock:ro
      - ./my-app-backend/docker/.iex.exs:/opt/my-app/.iex.exs:ro
      - ./my-app-backend/entrypoint.sh:/opt/my-app/entrypoint.sh:ro
      - ./my-app-backend/dev/support:/opt/my-app/dev/support:ro
      - ./.wallaby_screenshots:/opt/my-app/screenshots
    stdin_open: true
    tty: true

  frontend:
    image: my-app-frontend:local
    build:
      context: ./my-app-frontend
      dockerfile: ./docker/Dockerfile.dev
    env_file:
      - ./my-app-frontend/docker/dev.env
    ports:
      - "3000:3000"
    volumes:
      - ./my-app-frontend:/opt/my-app:ro
      - /opt/my-app/node_modules
    stdin_open: true
    tty: true


volumes:
  db-data:

my-app-backend/docker/Dockerfile.dev

# https://github.com/c0b/docker-elixir/wiki/use-observer
FROM elixir:latest

RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt update
RUN apt install -y git nodejs inotify-tools
RUN apt install -y chromium-driver

RUN mkdir -p /opt/my-app
WORKDIR /opt/my-app

RUN mix local.hex --force && \
  mix local.rebar --force && \
  mix archive.install --force hex phx_new

COPY mix.exs .
COPY mix.lock .

# copy the deps in dev environment for faster builds
COPY deps ./deps
RUN ["mix", "deps.get"]
RUN ["mix", "deps.compile"]

COPY assets ./assets
WORKDIR /opt/my-app/assets
RUN ["npm", "install"]

WORKDIR /opt/my-app

COPY config ./config
COPY lib ./lib
COPY seeds ./seeds
COPY priv ./priv
COPY test ./test
COPY dev/support ./dev/support

RUN ["mix", "compile"]

# compile deps in test environment for faster test runs when built
RUN export MIX_ENV=test && mix deps.compile

COPY ./entrypoint.sh ./entrypoint.sh
COPY docker/.iex.exs .iex.exs
COPY .iex.exs .
CMD ["/bin/bash", "entrypoint.sh"]

Note this is a setup for :dev environment only. I hope it helps you and I hope someone else can spot some problem with it.

Are you using Chrome or Selenium hub?

Chrome (by default)