Giter Site home page Giter Site logo

elixir-wallaby / wallaby Goto Github PK

View Code? Open in Web Editor NEW
1.6K 31.0 188.0 1.29 MB

Concurrent browser tests for your Elixir web apps.

Home Page: https://twitter.com/elixir_wallaby

License: MIT License

Elixir 95.51% Shell 0.23% HTML 4.27%
testing elixir ecto wallaby phoenix hacktoberfest

wallaby's Introduction

Wallaby

Actions Status Module Version Hex Docs License

Wallaby helps you test your web applications by simulating realistic user interactions. By default it runs each test case concurrently and manages browsers for you. Here's an example test for a simple Todo application:

defmodule MyApp.Features.TodoTest do
  use ExUnit.Case, async: true
  use Wallaby.Feature

  import Wallaby.Query, only: [css: 2, text_field: 1, button: 1]

  feature "users can create todos", %{session: session} do
    session
    |> visit("/todos")
    |> fill_in(text_field("New Todo"), with: "Write my first Wallaby test")
    |> click(button("Save"))
    |> assert_has(css(".alert", text: "You created a todo"))
    |> assert_has(css(".todo-list > .todo", text: "Write my first Wallaby test"))
  end
end

Because Wallaby manages multiple browsers for you, its possible to test several users interacting with a page simultaneously.

defmodule MyApp.Features.MultipleUsersTest do
  use ExUnit.Case, async: true
  use Wallaby.Feature

  import Wallaby.Query, only: [text_field: 1, button: 1, css: 2]

  @page message_path(Endpoint, :index)
  @message_field text_field("Share Message")
  @share_button button("Share")

  def message(msg), do: css(".messages > .message", text: msg)

  @sessions 2
  feature "That users can send messages to each other", %{sessions: [user1, user2]} do
    user1
    |> visit(@page)
    |> fill_in(@message_field, with: "Hello there!")
    |> click(@share_button)

    user2
    |> visit(@page)
    |> fill_in(@message_field, with: "Hello yourself")
    |> click(@share_button)

    user1
    |> assert_has(message("Hello yourself"))

    user2
    |> assert_has(message("Hello there!"))
  end
end

Read on to see what else Wallaby can do or check out the Official Documentation.

Sponsors

Your company's name and logo could be here!

Setup

Requirements

Wallaby requires Elixir 1.12+ and OTP 22+.

Wallaby also requires bash to be installed. Generally bash is widely available, but it does not come pre-installed on Alpine Linux.

Installation

Add Wallaby to your list of dependencies in mix.exs:

def deps do
  [
    {:wallaby, "~> 0.30", runtime: false, only: :test}
  ]
end

Configure the driver.

# Chrome
config :wallaby, driver: Wallaby.Chrome # default

# Selenium
config :wallaby, driver: Wallaby.Selenium

You'll need to install the actual drivers as well.

When calling use Wallaby.Feature and using Ecto, please configure your otp_app.

config :wallaby, otp_app: :your_app

Then ensure that Wallaby is started in your test_helper.exs:

{:ok, _} = Application.ensure_all_started(:wallaby)

Phoenix

If you're testing a Phoenix application with Ecto and a database that supports sandbox mode, you can enable concurrent testing by adding the Phoenix.Ecto.SQL.Sandbox plug to your Endpoint. It's important that this is at the top of endpoint.ex before any other plugs.

# lib/your_app_web/endpoint.ex

defmodule YourAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :your_app

  if sandbox = Application.compile_env(:your_app, :sandbox, false) do
    plug Phoenix.Ecto.SQL.Sandbox, sandbox: sandbox
  end

  # ...

  socket("/live", Phoenix.LiveView.Socket,
    websocket: [connect_info: [:user_agent, session: @session_options]]
  )

It's also important to make sure the user_agent is passed in the connect_info in order to allow the database and browser session to be wired up correctly.

Then make sure Phoenix is set up to serve endpoints in tests and that the sandbox is enabled:

# config/test.exs

config :your_app, YourAppWeb.Endpoint,
  server: true

config :your_app, :sandbox, Ecto.Adapters.SQL.Sandbox

This enables the database connection to be owned by the process that is running your test, but the connection is shared to the process receiving the HTTP requests from the browser, so that the same data is visible in both processes.

If you have other resources that should be shared by both processes (e.g. expectations or stubs if using Mox), then you can define a custom sandbox module:

# test/support/sandbox.ex

defmodule YourApp.Sandbox do
  def allow(repo, owner_pid, child_pid) do
    # Delegate to the Ecto sandbox
    Ecto.Adapters.SQL.Sandbox.allow(repo, owner_pid, child_pid)

    # Add custom process-sharing configuration
    Mox.allow(MyMock, owner_pid, child_pid)
  end
end

And update the test config to use your custom sandbox:

# config/test.exs

config :your_app, :sandbox, YourApp.Sandbox

Finally, in your test_helper.exs you can provide some configuration to Wallaby. At minimum, you need to specify a :base_url, so Wallaby knows how to resolve relative paths.

# test/test_helper.exs

Application.put_env(:wallaby, :base_url, YourAppWeb.Endpoint.url)

Assets

Assets are not re-compiled when you run mix test. This can lead to confusion if you've made changes in JavaScript or CSS but tests are still failing. There are two common ways to avoid this confusion.

esbuild

If you're using esbuild you can add esbuild default to the test alias in the mix config file.

  defp aliases do
    [
      "test": [
        "esbuild default",
        "ecto.create --quiet",
        "ecto.migrate",
        "test",
      ]
    ]
  end
Webpack

The first solution is to run webpack --mode development --watch from the assets directory. This will ensure that assets get recompiled after any changes.

The second solution is to add a new alias to your mix config that recompiles assets for you:

  def project do
    [
      app: :my_app,
      version: "1.0.0",
      aliases: aliases()
    ]
  end

  defp aliases, do: [
    "test": [
      "assets.compile --quiet",
      "ecto.create --quiet",
      "ecto.migrate",
      "test",
    ],
    "assets.compile": &compile_assets/1
  ]

  defp compile_assets(_) do
    Mix.shell().cmd("cd assets && ./node_modules/.bin/webpack --mode development",
      quiet: true
    )
  end

This method is less error prone but it will cause a delay when starting your test suite.

LiveView

In order to test Phoenix LiveView (as of version 0.17.7) with Wallaby you'll also need to add a function to each mount/3 function in your LiveViews, or use the on_mount live_session lifecycle hook in the router:

defmodule MyApp.Hooks.AllowEctoSandbox do
  import Phoenix.LiveView
  import Phoenix.Component

  def on_mount(:default, _params, _session, socket) do
    allow_ecto_sandbox(socket)
    {:cont, socket}
  end

  defp allow_ecto_sandbox(socket) do
    %{assigns: %{phoenix_ecto_sandbox: metadata}} =
      assign_new(socket, :phoenix_ecto_sandbox, fn ->
        if connected?(socket), do: get_connect_info(socket, :user_agent)
      end)

    Phoenix.Ecto.SQL.Sandbox.allow(metadata, Application.get_env(:your_app, :sandbox))
  end
end

and then including the function usage in the router:

live_session :default, on_mount: MyApp.Hooks.AllowEctoSandbox do
  # ...
end

Umbrella Apps

If you're testing an umbrella application containing a Phoenix application for the web interface (MyWebApp) and a separate persistence application (MyPersistenceApp) using Ecto 2 or 3 with a database that supports sandbox mode, then you can use the same setup as above, with a few tweaks.

# my_web_app/lib/endpoint.ex

defmodule MyWebApp.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_web_app

  if Application.get_env(:my_persistence_app, :sql_sandbox) do
    plug Phoenix.Ecto.SQL.Sandbox
  end

Make sure MyWebApp is set up to serve endpoints in tests and that the SQL sandbox is enabled:

# my_web_app/config/test.exs

config :my_web_app, MyWebApp.Endpoint,
  server: true

config :my_persistence_app, :sql_sandbox, true

Then in MyWebApp's test_helper.exs you can provide some configuration to Wallaby. At minimum, you need to specify a :base_url, so Wallaby knows how to resolve relative paths.

# my_web_app/test/test_helper.exs

Application.put_env(:wallaby, :base_url, MyWebApp.Endpoint.url)

You will also want to add phoenix_ecto as a dependency to MyWebApp:

# my_web_app/mix.exs

def deps do
  [
    {:phoenix_ecto, "~> 3.0", only: :test}
  ]
end

Writing tests

It's easiest to add Wallaby to your test suite by using the Wallaby.Feature module.

defmodule YourApp.UserListTest do
  use ExUnit.Case, async: true
  use Wallaby.Feature

  feature "users have names", %{session: session} do
    session
    |> visit("/users")
    |> find(Query.css(".user", count: 3))
    |> List.first()
    |> assert_has(Query.css(".user-name", text: "Chris"))
  end
end

API

The full documentation for the DSL is in the official documentation.

Queries and Actions

Wallaby's API is broken into 2 concepts: Queries and Actions.

Queries allow us to declaratively describe the elements that we would like to interact with and Actions allow us to use those queries to interact with the DOM.

Lets say that our html looks like this:

<ul class="users">
  <li class="user">
    <span class="user-name">Ada</span>
  </li>
  <li class="user">
    <span class="user-name">Grace</span>
  </li>
  <li class="user">
    <span class="user-name">Alan</span>
  </li>
</ul>

If we wanted to interact with all of the users then we could write a query like so css(".user", count: 3).

If we only wanted to interact with a specific user then we could write a query like this css(".user-name", count: 1, text: "Ada"). Now we can use those queries with some actions:

session
|> find(css(".user", count: 3))
|> List.first
|> assert_has(css(".user-name", count: 1, text: "Ada"))

There are several queries for common html elements defined in the Query module. All actions accept a query. This makes it easy to use queries we've already defined. Actions will block until the query is either satisfied or the action times out. Blocking reduces race conditions when elements are added or removed dynamically.

Navigation

We can navigate directly to pages with visit:

visit(session, "/page.html")
visit(session, user_path(Endpoint, :index, 17))

It's also possible to click links directly:

click(session, link("Page 1"))

Finding

We can find a specific element or list of elements with find:

@user_form   css(".user-form")
@name_field  text_field("Name")
@email_field text_field("Email")
@save_button button("Save")

find(page, @user_form, fn(form) ->
  form
  |> fill_in(@name_field, with: "Chris")
  |> fill_in(@email_field, with: "[email protected]")
  |> click(@save_button)
end)

Passing a callback to find will return the parent which makes it easy to chain find with other actions:

page
|> find(css(".users"), & assert has?(&1, css(".user", count: 3)))
|> click(link("Next Page"))

Without the callback find returns the element. This provides a way to scope all future actions within an element.

page
|> find(css(".user-form"))
|> fill_in(text_field("Name"), with: "Chris")
|> fill_in(text_field("Email"), with: "[email protected]")
|> click(button("Save"))

Interacting with forms

There are a few ways to interact with form elements on a page:

fill_in(session, text_field("First Name"), with: "Chris")
clear(session, text_field("last_name"))
click(session, option("Some option"))
click(session, radio_button("My Fancy Radio Button"))
click(session, button("Some Button"))

If you need to send specific keys to an element, you can do that with send_keys:

send_keys(session, ["Example", "Text", :enter])

Assertions

Wallaby provides custom assertions to make writing tests easier:

assert_has(session, css(".signup-form"))
refute_has(session, css(".alert"))
has?(session, css(".user-edit-modal", visible: false))

assert_has and refute_has both take a parent element as their first argument. They return that parent, making it easy to chain them together with other actions.

session
|> assert_has(css(".signup-form"))
|> fill_in(text_field("Email", with: "[email protected]"))
|> click(button("Sign up"))
|> refute_has(css(".error"))
|> assert_has(css(".alert", text: "Welcome!"))

Window Size

You can set the default window size by passing in the window_size option into Wallaby.start_session\1.

Wallaby.start_session(window_size: [width: 1280, height: 720])

You can also resize the window and get the current window size during the test.

resize_window(session, 100, 100)
window_size(session)

Screenshots

It's possible take screenshots:

take_screenshot(session)

All screenshots are saved to a screenshots directory in the directory that the tests were run in.

If you want to customize the screenshot directory you can pass it as a config value:

# config/test.exs
config :wallaby, screenshot_dir: "/file/path"

# test_helper.exs
Application.put_env(:wallaby, :screenshot_dir, "/file/path")

Automatic screenshots

You can automatically take screenshots on an error when using the Wallaby.Feature.feature/3 macro.

# config/test.exs
config :wallaby, screenshot_on_failure: true

# test_helper.exs
Application.put_env(:wallaby, :screenshot_on_failure, true)

JavaScript

Asynchronous code

Testing asynchronous JavaScript code can expose timing issues and race conditions. We might try to interact with an element that hasn't yet appeared on the page. Elements can become stale while we're trying to interact with them.

Wallaby helps solve this by blocking. Instead of manually setting timeouts we can use assert_has and some declarative queries to block until the DOM is in a good state.

session
|> click(button("Some Async Button"))
|> assert_has(css(".async-result"))
|> click(button("Next Action"))

Interacting with dialogs

Wallaby provides several ways to interact with JavaScript dialogs such as window.alert, window.confirm and window.prompt.

You can use one of the following functions:

  • For window.alert use accept_alert/2
  • For window.confirm use accept_confirm/2 or dismiss_confirm/2
  • For window.prompt use accept_prompt/2-3 or dismiss_prompt/2

All of these take a function as last parameter, which must include the necessary interactions to trigger the dialog. For example:

alert_message = accept_alert session, fn(session) ->
  click(session, link("Trigger alert"))
end

To emulate user input for a prompt, accept_prompt takes an optional parameter:

prompt_message = accept_prompt session, [with: "User input"], fn(session) ->
  click(session, link("Trigger prompt"))
end

JavaScript logging and errors

Wallaby captures both JavaScript logs and errors. Any uncaught exceptions in JavaScript will be re-thrown in Elixir. This can be disabled by specifying js_errors: false in your Wallaby config.

JavaScript logs are written to :stdio by default. This can be changed to any IO device by setting the :js_logger option in your Wallaby config. For instance if you want to write all JavaScript console logs to a file you could do something like this:

{:ok, file} = File.open("browser_logs.log", [:write])
Application.put_env(:wallaby, :js_logger, file)

Logging can be disabled by setting :js_logger to nil.

Configuration

Adjusting timeouts

Wallaby uses hackney under the hood, so we offer a hook that allows you to control any hackney options you'd like to have sent along on every request. This can be controlled with the :hackney_options setting in config.exs.

config :wallaby,
  hackney_options: [timeout: :infinity, recv_timeout: :infinity]

# Overriding a value
config :wallaby,
  hackney_options: [timeout: 5_000]

Contributing

Wallaby is a community project. Pull Requests (PRs) and reporting issues are greatly welcome.

To get started and setup the project, make sure you've got Elixir 1.7+ installed and then:

Development Dependencies

Wallaby requires the following tools.

  • ChromeDriver
  • Google Chrome
  • GeckoDriver
  • Mozilla Firefox
  • selenium-server-standalone
# Unit tests
$ mix test

# Integration tests for all drivers
$ mix test.drivers

# Integration tests for a specific driver
$ WALLABY_DRIVER=chrome mix test
$ WALLABY_DRIVER=selenium mix test

# All tests
$ mix test.all

wallaby's People

Contributors

aaronrenner avatar bobwaycott avatar britton-jb avatar drumusician avatar dvic avatar follmann avatar germsvel avatar graeme-defty avatar jaimeiniesta avatar jangromko avatar jonleighton avatar keathley avatar kianmeng avatar knewter avatar marcandre avatar marcdel avatar mhanberg avatar michallepicki avatar nathanl avatar optikfluffel avatar pragtob avatar praveenperera avatar sevenseacat avatar steve0hh avatar stevegraham avatar stratigos avatar tmepple avatar tokafish avatar vanderhoop avatar xtian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wallaby's Issues

Ability to set user agent.

I have a frontend which depends on various options, like screen resolution or user agent.

I haven't found ability to mock user agent unfortunately.

Waiting for async code without narrowing find scope

The advice for async code is to find something on the page that will be visible once the async action has completed (find will block for up to 3 secs by default). This works great but there's a problem in that find is chainable and narrows the scope of any finds further on in the chain.

    session
    |> visit("http://localhost:28080/?authAdapter=test&apiBasePath=%2F%2Flocalhost%3A4001%2F")
    |> fill_in("Add new feed", with: "Lobby")
    |> send_keys([:enter])
    |> find(".side-nav__list-item a", text: "Lobby") # wait for async action to complete
    |> fill_in("What's got you thinking?", with: "hello")

The last line fails because in this scenario the textarea that it's attempting to fill in is not inside the scope specified by the previous find, ".side-nav__list-item a", text: "Lobby".

There's a few options here:

1) Introduce a command that waits for something to appear

|> waitFor(".side-nav__list-item a", text: "Lobby") # wait for async action to complete

2) Allow the find scope to be reset

    session
    |> visit("http://localhost:28080/?authAdapter=test&apiBasePath=%2F%2Flocalhost%3A4001%2F")
    |> fill_in("Add new feed", with: "Lobby")
    |> send_keys([:enter])
    |> find(".side-nav__list-item a", text: "Lobby") # wait for async action to complete
    |> resetScope
    |> fill_in("What's got you thinking?", with: "hello")

3) Don't make find chainable
...seeing as you can always express chained finds with a single find

4) Introduce something like tap

session
|> visit("/page.html")
|> tap(fn session -> 
  session
  |> users
  |> find(".user", count: 3)
  |> List.first
  |> find(".user-name")
end)
|> continue with original scope

Methods for not-finding nodes or content

In order to help write specs where nodes are removed from the DOM, we could use a method where it resolves when the pattern is not found. This would kinda be the opposite of find(pattern, count: x)

Wishful use case:

session
  |> click("remove-button")
  |> find(".new_object", count: 0)

#or
  |> wait_until_gone(".new_object")

Where a failing spec would be like:

Expected to find 'new-object' with count 0 but found 1 node

fill_in appends content instead of replacing it

When interacting with a form and filling in a form field which already contains pre-existing text, fill_in appends to the content.

Given a form field with an input containing a value "Foo"

session |> fill_in("Name", with: "Bar")

The input field will have "FooBar" as its value

Update README

The README needs to be updated to include quick start information, info on the DSL, test examples, etc.

Wallaby cannot find a button

Hi there! First time using Wallaby on my first Phoenix app. I've written my first acceptance test but have run in to an issue I can't resolve on my own

It's very simple.. filling out a form to register a user.

The form

<%= form_for @changeset, organisation_path(@conn, :create), fn f -> %>
  <%= if @changeset.action do %>
    <p>Oh noes! Please check the errors below.</p>
  <% end %>

  <%= inputs_for f, :users, fn u -> %>
    <%= error_tag u, :first_name %>
    <%= text_input u, :first_name, placeholder: "First name" %>

    <%= error_tag u, :first_name %>
    <%= text_input u, :last_name, placeholder: "Last name" %>

    <%= error_tag u, :email %>
    <%= text_input u, :email, placeholder: "Email" %>
  <% end %>

  <%= error_tag f, :name %>
  <%= text_input f, :name, placeholder: "Organisation name" %>

  <div class="actions">
    <input type="submit" value="Submit" />
    <a href="#" class="forgot-password">Already signed up?</a>
  </div>
<% end %>

Acceptance test

defmodule Esteem.UserSignsUpTest do
  use Esteem.AcceptanceCase, async: true

  test "user signs up a new organisation", %{session: session} do
    welcome_message =
      session
      |> visit("/")
      |> find("form")
      |> fill_in("First name", with: "Luke")
      |> fill_in("Last name", with: "Rollans")
      |> fill_in("Email", with: "[email protected]")
      |> fill_in("Organisation name", with: "Org name")
      |> click("Submit")
      |> find("p")
      |> text

    assert welcome_message =~ "Whelp"
  end
end

Error

  1) test user signs up a new organisation (Esteem.UserSignsUpTest)
     test/acceptance/user_signs_up_test.exs:4
     ** (Wallaby.ElementNotFound) Could not find element
     stacktrace:
       (wallaby) lib/wallaby/node.ex:342: Wallaby.Node.retry/2
       (wallaby) lib/wallaby/node.ex:192: Wallaby.Node.click/2
       test/acceptance/user_signs_up_test.exs:15

Seems that Wallaby cannot find the "Submit" button (line 15). I've tried using an ID, scoping a find to the actions wrapper, and other methods of detection to no vail.

Am I doing something glaringly wrong? I've read through the docs a few times and looked at test source code.. from what I can tell, it should work.

fill_in doesn't find elements by ID or label

Hello, I have a signup form:

<form accept-charset="UTF-8" action="/signup" method="post">
  <input name="_csrf_token" type="hidden" value="...">
  <input name="_utf8" type="hidden" value="โœ“">

  <div class="form-group">
    <label>Name</label>
    <input class="form-control" id="user_name" name="user[name]" type="text">
  </div>

  <div class="form-group">
    <label>Email</label>
    <input class="form-control" id="user_email" name="user[email]" type="text">
  </div>

  <div class="form-group">
    <label>Password</label>
    <input class="form-control" id="user_password" name="user[password]" type="password">
  </div>

  <div class="form-group">
    <input class="btn" type="submit" value="Submit">
  </div>
</form>

Then, on the Wallaby test, I try to fill the inputs (Name, Email, Password). I've tried this:

fill_in(session, "Name", with: "Jaime")

and also this:

fill_in(session, "#user_name", with: "Jaime")

But both will fail with:

  1) test signs up a user (Rocket.SignupTest)
     test/acceptance/signup_test.exs:4
     ** (Wallaby.ElementNotFound) Could not find element
     stacktrace:
       (wallaby) lib/wallaby/node.ex:342: Wallaby.Node.retry/2
       (wallaby) lib/wallaby/node.ex:81: Wallaby.Node.fill_in/3
       test/acceptance/signup_test.exs:8

I could make it work with the name attribute:

fill_in(session, "user[name]", with: "Jaime")

I'm on Elixir 1.2.5, Phoenix 1.1.4, Ecto 1.1.7, Phantomjs 2.1.1.

Should take_screenshot return a session?

I tried this while debugging

visit("/path")
|> take_screenshot
|> find(".my-css")

But it failed because take_screenshot did not return a session. I think it would be pretty cool if it returned a session so debugging with screenshots would be a bit simpler

Ambiguous matches

We noticed some ambiguous match problems when using #find for an element - it will timeout instead of returning an error:

session |> find(".project")

# If there are two projects, this will hang

Wallaby.Node.find can return invisible elements

This test will fail based on existing elements in page_1.html

test "not finding an invisible element", %{session: session, server: server} do
  session
  |> visit(server.base_url <> "page_1.html")

  assert_raise Wallaby.ElementNotFound, fn ->
    find(session, "#invisible", count: :any)
  end

  assert find(session, "#visible", count: :any) |> length == 1
end

Support for getting page session source

Was wondering if you guys are ok with adding an addition API to get the page source.

This would aid developers that would like to stick with their preferred DSL like floki.

I already have the function working in my fork of this project but would need some guidance in writing the tests. (where to place them etc..)

Example of the proposed API:

iex(4)> session |> Wallaby.Driver.visit("http://www.example.com") |> Wallaby.Driver.source()

"<!DOCTYPE html><html><head>\n    <title>Example Domain</title>\n\n 
the page source of the session currently
</html>"

Attach files via wallaby

There is a lot of ways to interact with the forms, but I haven't found a way to interact with website by attach some files to the file fields. I think this should be implemented

@keathley WDYT?

Allow "text" to receive more than one node

it would be nice to have some way to have a collection of wallaby nodes be able to return a blob (or array!) of text:

project_text = 
  session
  |> find(".project", count: 2)
  |> text

# project_text would be "project 1 project 2" or ["project 1", "project 2"]

Include what failed in failure messages

So if you call click("Submit") and it fails:

Could not click "Submit" because it was not found on the page

Would be extra cool if it found all the link text on the page and used String.jaro_distance to find potential mispellings.

Could not click "Submit" because it was not found on the page

Did you mean "submit"?

It could potentiall work the same for CSS

Could not find ".my-css"

If you spelled it wrong

Could not find ".my-css"

Did you mean ".my_css"?

These problems come up a lot so these error messages would be insanely useful!

REST API testing

Hello. I'm wondering if it makes sense to use Wallaby for REST API testing where browser testing is also needed. Doing QA (end-to-end acceptance) testing in a standing environment with a single tool has many advantages, particularly if that tool features parallelism. Thanks!

Visit should throw an error when loading fails

I forgot to add server: true to my endpoint so when I attempted to visit the page nothing was rendered. The visit did not fail though, it continued on as if it loaded.

I think that if loading fails it should raise. It would be extra cool if the error message said something like this:

could not load #{the_full_url}. If you are using Phoenix you may need to enable the server

# config/test.exs
config :your_app, YourApplication.Endpoint,
  server: true

`Wallaby.DSL.Navigation` does not exist

Mentioned in README:

Wallaby.DSL.Navigation.visit(session, Phoenix.Ecto.SQL.Sandbox.path_for(YourApp.Repo, self()))

Should be either Wallaby.Driver.visit or Wallaby.Session.visit? (they're the only two visit methods present)

`click_link` does not work with `method: :delete` links

Hi,

I am trying to use Wallaby to test the user session flow, it's a Phoenix app. So for the sign out, the EEx looks like this:

<%= link "Sign Out", to: session_path(@conn, :delete), method: :delete %>

Unfortunately this doesn't appear to work. Use take_screenshot I can see that the link is not getting clicked.

If I change the implementation to either:

<%= form_tag session_path(@conn, :delete), method: :delete %>
  <%= submit "Sign Out", class: "btn btn-primary" %>
</form>

# use `click_on` in the test

Or:

<%= link "Sign Out", to: session_path(@conn, :delete) %>

# change route from :delete to :get

Then the test works.

Also, the Phoenix JS is included correctly, and JS is being executed in the test environment.

Is this a known issue, or have I somehow done this incorrectly?

Support for multiple `Repo`s

It seems to be quite common to have multiple OTP-Apps dealing with their own Repos in in umbrella application.

According to a short talk to @keathley, this is currently not supported by wallaby.

An example of such an umbrella application is wojtekmach's acme_bank from ExConf '16. It has a Repo at least in the sub-apps bank and auth.

[BUG] Session does not load

When using boilerplate instructions & trying to find an element on the page, the page was never fully rendering (in Allocations, we captured screenshots of the "Loading" page):

session
  |> sign_in
  |> visit("/")

# at this point, the page is stuck in the loading screen and never finds the project, even after:
:timer.sleep(5000)

session
  |> find(".project", count: 1)

It seems like a setup issue, and the bug starts via this commit: f1bcc46

Return order from `find` is inconsistent

The #find method for a given selector which returns more than one result; it may not return these elements in the strictly-same order (expected to always be ordered by the DOM). This was our work-around:

projects =
  session
    |> sign_in
    |> find(".project", count: 2)

first_project = List.first(projects)

project_text =
  projects
    |> Enum.map(&text(&1))
    |> Enum.sort

assert project_text == ["Initech", "The Petty Marsh"]

Automatically convert css selectors into xpath queries

Using css selectors is a convenient and familiar construct for web developers. However, css has limitations that make it hard for wallaby to generate efficient queries. In fact most queries require multiple round trips through the webdriver before they can be resolved. Multiple queries means a larger opportunity for race conditions or stale dom nodes. These are edge cases that we have to handle explicitly because if we don't they creep into the users experience.

I propose this solution. We continue to allow people to use css selectors. But under the hood we convert those css selectors into a proper xpath query. Xpath would allow us to run all of our filters as a single query. Its well supported by webdriver; we already use it for all of the actions.

A side benefit of xpath would be that the query logic could be greatly simplified. Currently its a highly stateful and error prone process.

The largest downsides to generating xpath for users is that it could be surprising if they discover any bugs in our implementation. Its also impossible to use pseudo selectors like :hover in xpath. We will need to call this out in our documentation and make sure users have a good escape hatch from xpath if need be.

In the future I want to encourage users to create page modules; re-usable functions that can encapsulate complex interactions on the page. Having a robust query generator is a good first step towards that goal.

Unreliable, multiple acceptance tests when async is on

Hey there!

I've been experiencing this for a while, but given it isn't important I haven't opened an issue. I consistently get timeout issues (and sometimes other, seemingly unrelated error messages) when I run my acceptance tests asynchronously using the async option.

If I turn off the async option everything is green but my suite takes about an extra 3-4 seconds to run at its current size. Currently sitting at about 200 tests all up, so it's not huge, but will continue to grow pretty steadily so I'm hoping to fix this issue now rather than later.

Important: This only happens on CI.. which is an ec2 t2.small instance running in a Docker Compose container. Might a simply need to beef that up a bit?

Here's an example of two acceptance tests that reproduce the issue when run in isolation together.

Test that a user can log in

defmodule Sightseer.UserLogsInTest do
  use Sightseer.AcceptanceCase, async: true

  test "user logs in", %{session: session} do
    user = insert(:user, encrypted_password: Comeonin.Bcrypt.hashpwsalt("test"))

    session
    |> visit("/sessions/new")
    |> fill_in("Email address", with: user.email)
    |> fill_in("Password", with: "test")
    |> click_button("Submit")

    notice = session
    |> find(".alert-info")
    |> text

    assert notice == "Signed in successfully"
  end
end

Test that a user can register

defmodule Sightseer.UserSignsUpTest do
  use Sightseer.AcceptanceCase, async: true

  test "user signs up", %{session: session} do
    session
    |> visit("/signup")
    |> fill_in("First name", with: "Luke")
    |> fill_in("Email address", with: "[email protected]")
    |> fill_in("Password", with: "1234567madpasswordbro")
    |> click_button("Submit")

    notice = session
    |> find(".alert-info")
    |> text

    assert notice == "Successfully signed up!"
  end
end

Error thrown on CI in the environment described above, when async is on

Generated sightseer app

  1) test user logs in up (Sightseer.UserLogsInTest)
     test/acceptance/user_logs_in_test.exs:4
     ** (exit) exited in: :gen_server.call(Wallaby.ServerPool, {:checkout, #Reference<0.0.1.94027>, true}, 5000)
         ** (EXIT) time out
     stacktrace:
       (stdlib) gen_server.erl:212: :gen_server.call/3
       (poolboy) src/poolboy.erl:55: :poolboy.checkout/3
       (wallaby) lib/wallaby.ex:34: Wallaby.start_session/1
       (sightseer) test/support/acceptance_case.ex:21: Sightseer.AcceptanceCase.__ex_unit_setup_0/1
       (sightseer) test/support/acceptance_case.ex:1: Sightseer.AcceptanceCase.__ex_unit__/2
       test/acceptance/user_logs_in_test.exs:1: Sightseer.UserLogsInTest.__ex_unit__/2

...................................................................................................................................................................................

Finished in 5.9 seconds
180 tests, 1 failure

Any insight in to how I might be able improve reliability here would be very much appreciated :)

Better names for screenshots

I have configured wallaby to take screenshots when something went wrong.

This was fine when only a single test failed, but when there are multiple ones failing I do have problems to assign the correct screenshot to the test.

So it would be nice if either the testsname or some info from conn ("#{method}-#{path}") could be part of the name of the screenshot.

Windows

Is there any chance this can be modified to run on Windows. The shall script requires a bash environment, as I understand it. Could that not be replaced with a similar batch file?

Simulate Drag and Drop Between Elements

It would be nice to be able to test drag and drop interactions with wallaby easily by specifying a selector to drag and the selector it should be dropped upon.

File upload?

Has file uploading been implemented in Wallaby? I looked but didn't see anything that would match a file input in xpath.ex nor did I see anything in the docs to indicate support.

Issue getting up and running

Pretty sure I've followed everything in the README and got up to actually writing some tests; but running into issues. I'm seeing this error:

Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

test/acceptance/create_projects_test.exs:6: warning: variable session is unused

Forum.CreateProjectTest
  * alternate formatRequest to pry #PID<0.304.0> at test/acceptance/create_projects_test.exs:20

        visit session, "/"

        IEx.pry
        click_link session, "New Project"


Allow? [Yn] n
23:51:33.726 [error] Postgrex.Protocol (#PID<0.258.0>) disconnected: ** (DBConnection.Error) ownership timeout
23:51:34.456 [error] Postgrex.Protocol (#PID<0.257.0>) disconnected: ** (DBConnection.Error) ownership timeout

And then after 60 second the test times out and fails. The first visit is fine, but inspecting text(session) in the IEx.pry returns an empty string. Something is missing either in my configuration, or in the setup steps, and I'm not sure what it is :)

I've pushed my test app to GitHub here: https://github.com/sevenseacat/test_wallaby

Using the action fill_in takes longer than expected to complete

Seems that using

fill_in(session, "session[email]", with: "x")

Takes 300-400ms to complete.

Not sure why this is.

Here is a exprof dump (note that this is in the header, so I use the full namespace:

        profile do
            Wallaby.DSL.Actions.fill_in(session, "session[email]", with: "xxx")
        end
FUNCTION                                                      CALLS        %  TIME  [uS / CALLS]
--------                                                      -----  -------  ----  [----------]
hackney_response:stream_body/2                                    1     0.00     0  [      0.00]
hackney_response:stream_body_recv/2                               1     0.00     0  [      0.00]
'Elixir.Keyword':merge/2                                          1     0.00     0  [      0.00]
'Elixir.Keyword':'-merge/2-fun-0-'/2                              2     0.00     0  [      0.00]
'Elixir.Wallaby.Driver':clear/1                                   1     0.00     0  [      0.00]
'Elixir.Wallaby.Driver':displayed/1                               1     0.00     0  [      0.00]
'Elixir.Wallaby.Driver':request/2                                 2     0.00     0  [      0.00]
'Elixir.Wallaby.Driver':set_value/2                               1     0.00     0  [      0.00]
'Elixir.Wallaby.Driver':to_params/1                               1     0.00     0  [      0.00]
'Elixir.Wallaby.Driver':'-find_elements/1-fun-0-'/2               1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':assert_count/1                        1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':build_locator/1                       1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':default_conditions/0                  1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':fillable_field/3                      1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':find_element/3                        1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':find_field/3                          1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':retry/1                               1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':retry/2                               1     0.00     0  [      0.00]
'Elixir.Wallaby.Node.Query':'-find_element/3-fun-0-'/1            1     0.00     0  [      0.00]
'Elixir.Enum':'all?'/2                                            1     0.00     0  [      0.00]
'Elixir.Enum':'do_all?'/2                                         2     0.00     0  [      0.00]
'Elixir.Enum':map/2                                               1     0.00     0  [      0.00]
'Elixir.Enum':'-map/2-lists^map/1-0-'/2                           2     0.00     0  [      0.00]
'Elixir.Wallaby.Node':clear/1                                     1     0.00     0  [      0.00]
'Elixir.Wallaby.Node':'visible?'/1                                1     0.00     0  [      0.00]
'Elixir.Poison.Encoder.List':encode/2                             1     0.00     0  [      0.00]
'Elixir.Poison.Encoder.List':encode/3                             1     0.00     0  [      0.00]
'Elixir.Poison.Encoder.List':'-encode/3-lists^foldr/2-1-'/3       2     0.00     0  [      0.00]
'Elixir.Poison.Encoder.List':'-encode/3-fun-0-'/3                 1     0.00     0  [      0.00]
'Elixir.Map':get/2                                                1     0.00     0  [      0.00]
'Elixir.Map':get/3                                                1     0.00     0  [      0.00]
erlang:send/2                                                     1     0.00     0  [      0.00]
erlang:monotonic_time/1                                           1     0.00     0  [      0.00]
'Elixir.String.Casing':downcase/1                                 8     0.02     1  [      0.13]
'Elixir.Keyword':'-merge/2-lists^filter/1-0-'/2                   3     0.02     1  [      0.33]
hackney_manager:get_state/2                                       8     0.02     1  [      0.13]
hackney_http:ce_identity/1                                        8     0.02     1  [      0.13]
hackney_url:parse_url/2                                           8     0.02     1  [      0.13]
hackney_url:parse_addr/2                                          8     0.02     1  [      0.13]
'Elixir.Wallaby.Driver':'check_logs!'/2                           4     0.02     1  [      0.25]
'Elixir.Wallaby.Driver':find_elements/1                           1     0.02     1  [      1.00]
'Elixir.Wallaby.Driver':'-set_value/2-fun-0-'/2                   1     0.02     1  [      1.00]
'Elixir.Wallaby.Driver':'-find_elements/1-fun-1-'/3               1     0.02     1  [      1.00]
'Elixir.Wallaby.Driver':'-displayed/1-fun-0-'/1                   1     0.02     1  [      1.00]
'Elixir.Wallaby.Driver':'-clear/1-fun-0-'/1                       1     0.02     1  [      1.00]
metrics_dummy:increment_counter/1                                 8     0.02     1  [      0.13]
'Elixir.Wallaby.Node.Query':assert_text/1                         1     0.02     1  [      1.00]
'Elixir.Wallaby.Node.Query':assert_visibility/1                   1     0.02     1  [      1.00]
'Elixir.Wallaby.Node.Query':build_conditions/1                    1     0.02     1  [      1.00]
inet_parse:hex/1                                                  8     0.02     1  [      0.13]
'Elixir.Agent':get_and_update/2                                   4     0.02     1  [      0.25]
prim_inet:enc_opts/2                                              8     0.02     1  [      0.13]
'Elixir.Process':whereis/1                                        4     0.02     1  [      0.25]
'Elixir.HTTPoison':process_headers/1                              8     0.02     1  [      0.13]
'Elixir.HTTPoison':process_response_body/1                        8     0.02     1  [      0.13]
'Elixir.HTTPoison':process_status_code/1                          8     0.02     1  [      0.13]
'Elixir.Poison':'encode!'/2                                       6     0.02     1  [      0.17]
'Elixir.Enum':each/2                                              4     0.02     1  [      0.25]
'Elixir.Enum':'-each/2-lists^foreach/1-0-'/2                      4     0.02     1  [      0.25]
'Elixir.Wallaby.XPath':fillable_field/1                           1     0.02     1  [      1.00]
'Elixir.Wallaby.Node':fill_in/2                                   1     0.02     1  [      1.00]
hackney_connect:reconnect/4                                       8     0.02     1  [      0.13]
'Elixir.Poison.Parser':array_values/3                             5     0.02     1  [      0.20]
'Elixir.GenServer':call/3                                         4     0.02     1  [      0.25]
maps:get/2                                                        7     0.02     1  [      0.14]
hackney_request:perform_all/6                                     8     0.04     2  [      0.25]
hackney_headers:get_value/2                                       8     0.04     2  [      0.25]
hackney_headers:insert/3                                          8     0.04     2  [      0.25]
error_handler:undefined_function/3                                3     0.04     2  [      0.67]
hackney_http:content_decode/3                                     8     0.04     2  [      0.25]
'Elixir.Poison.Encoder.Map':encode/2                              6     0.04     2  [      0.33]
'Elixir.Poison.Encoder.Map':'-encode/3-fun-1-'/4                  7     0.04     2  [      0.29]
hackney_url:urldecode/1                                           8     0.04     2  [      0.25]
'Elixir.Poison.Decode':decode/2                                   8     0.04     2  [      0.25]
dict:fold/3                                                       8     0.04     2  [      0.25]
dict:fold_dict/3                                                  8     0.04     2  [      0.25]
'Elixir.Wallaby.Driver':cast_as_node/2                            1     0.04     2  [      2.00]
'Elixir.Wallaby.Driver':request/3                                 6     0.04     2  [      0.33]
metrics_dummy:update_histogram/2                                  8     0.04     2  [      0.25]
metrics_dummy:update_meter/2                                      8     0.04     2  [      0.25]
inet_parse:ipv4_addr/5                                            8     0.04     2  [      0.25]
inet_parse:ipv4_field/2                                           8     0.04     2  [      0.25]
inet_parse:ipv6strict_address/1                                   8     0.04     2  [      0.25]
inet_parse:ipv6_addr/1                                            8     0.04     2  [      0.25]
'Elixir.HTTPoison.Base':request/9                                 8     0.04     2  [      0.25]
'Elixir.Agent':get_and_update/3                                   4     0.04     2  [      0.50]
prim_inet:send/3                                                  8     0.04     2  [      0.25]
prim_inet:type_value_default/3                                    8     0.04     2  [      0.25]
prim_inet:enc_value/2                                             8     0.04     2  [      0.25]
prim_inet:enc_value_default/3                                     8     0.04     2  [      0.25]
prim_inet:enc_value_1/3                                           8     0.04     2  [      0.25]
prim_inet:enc_value_2/2                                           8     0.04     2  [      0.25]
prim_inet:decode_opt_val/1                                        8     0.04     2  [      0.25]
prim_inet:dec_opt_val/3                                           8     0.04     2  [      0.25]
'Elixir.Wallaby.DSL.Actions':fill_in/3                            1     0.04     2  [      2.00]
'Elixir.HTTPoison':process_request_body/1                         8     0.04     2  [      0.25]
'Elixir.Poison':'decode!'/1                                       8     0.04     2  [      0.25]
'Elixir.Poison':'encode!'/1                                       6     0.04     2  [      0.33]
hackney_connect:socket_from_pool/4                                8     0.04     2  [      0.25]
hackney_tcp_transport:send/2                                      8     0.04     2  [      0.25]
'Elixir.Poison.Parser':number_complete/2                          8     0.04     2  [      0.25]
'Elixir.Poison.Parser':number_frac/2                              8     0.04     2  [      0.25]
gen_server:do_cast/2                                              8     0.04     2  [      0.25]
'Elixir.Wallaby.Phantom.Logger':log/1                             4     0.04     2  [      0.50]
application_controller:get_key/2                                  8     0.04     2  [      0.25]
gen_tcp:send/2                                                    8     0.04     2  [      0.25]
gen_tcp:controlling_process/2                                     8     0.04     2  [      0.25]
maps:keys/1                                                       6     0.04     2  [      0.33]
maps:find/2                                                       6     0.04     2  [      0.33]
erlang:binary_to_integer/1                                        8     0.04     2  [      0.25]
lists:keymember/3                                                10     0.04     2  [      0.20]
erlang:'++'/2                                                     9     0.04     2  [      0.22]
erlang:make_ref/0                                                 8     0.04     2  [      0.25]
hackney_response:wait_headers/2                                   8     0.06     3  [      0.38]
hackney_response:end_stream_body/2                                8     0.06     3  [      0.38]
hackney_request:maybe_add_host/2                                  8     0.06     3  [      0.38]
hackney_headers:'-new/1-fun-0-'/2                                 8     0.06     3  [      0.38]
hackney_bstr:join/2                                              16     0.06     3  [      0.19]
error_handler:ensure_loaded/1                                     3     0.06     3  [      1.00]
hackney_http:transfer_decode/2                                    8     0.06     3  [      0.38]
hackney_http:'-parse_body/1-fun-3-'/1                             8     0.06     3  [      0.38]
'Elixir.Poison.Encoder.Map':encode/3                              6     0.06     3  [      0.50]
hackney_url:parse_netloc/2                                        8     0.06     3  [      0.38]
hackney_url:parse_path/1                                          8     0.06     3  [      0.38]
hackney_url:parse_fragment/1                                      8     0.06     3  [      0.38]
hackney_url:pathencode/1                                          8     0.06     3  [      0.38]
hackney:reply/2                                                   8     0.06     3  [      0.38]
hackney:'-body/1-fun-0-'/1                                        8     0.06     3  [      0.38]
code:call/1                                                      11     0.06     3  [      0.27]
dict:mk_seg/1                                                     8     0.06     3  [      0.38]
'Elixir.Wallaby.Driver':make_request/3                            8     0.06     3  [      0.38]
'Elixir.Wallaby.Driver':request/4                                 8     0.06     3  [      0.38]
inet_tcp:send/2                                                   8     0.06     3  [      0.38]
hackney_pool:find_pool/2                                          8     0.06     3  [      0.38]
'Elixir.Wallaby.Node.Query':build_query/3                         1     0.06     3  [      3.00]
unicode:characters_to_binary/1                                    8     0.06     3  [      0.38]
inet_parse:ipv4_addr/1                                            8     0.06     3  [      0.38]
inet_parse:ipv4_addr/2                                            8     0.06     3  [      0.38]
inet_parse:hex/3                                                  8     0.06     3  [      0.38]
'Elixir.HTTPoison.Base':default_process_url/1                     8     0.06     3  [      0.38]
prim_inet:setopts/2                                               8     0.06     3  [      0.38]
prim_inet:dec_opt/1                                               8     0.06     3  [      0.38]
prim_inet:type_value/2                                            8     0.06     3  [      0.38]
prim_inet:type_value_1/3                                          8     0.06     3  [      0.38]
prim_inet:type_value_2/2                                          8     0.06     3  [      0.38]
prim_inet:enc_value/3                                             8     0.06     3  [      0.38]
prim_inet:enum_name/2                                             8     0.06     3  [      0.38]
prim_inet:enc_opt_val/4                                           8     0.06     3  [      0.38]
idna_ucs:from_utf8/1                                              8     0.06     3  [      0.38]
idna_ucs:expand_utf8/1                                            8     0.06     3  [      0.38]
hackney_app:get_app_env/2                                         8     0.06     3  [      0.38]
'Elixir.HTTPoison':process_url/1                                  8     0.06     3  [      0.38]
'Elixir.Poison':'decode!'/2                                       8     0.06     3  [      0.38]
string:to_lower/1                                                 8     0.06     3  [      0.38]
inet:setopts/2                                                    8     0.06     3  [      0.38]
inet:tcp_controlling_process/2                                    8     0.06     3  [      0.38]
hackney_connect:connect/5                                         8     0.06     3  [      0.38]
'Elixir.Poison.Parser':number_exp/3                               8     0.06     3  [      0.38]
'Elixir.Poison.Parser':number_start/1                             8     0.06     3  [      0.38]
'Elixir.Poison.Parser':parse/2                                    8     0.06     3  [      0.38]
gen_server:cast/2                                                 8     0.06     3  [      0.38]
gen_server:do_send/2                                              8     0.06     3  [      0.38]
idna:utf8_to_ascii/1                                              8     0.06     3  [      0.38]
gen:'-call/4-fun-0-'/4                                           28     0.08     4  [      0.14]
application:get_key/2                                             8     0.08     4  [      0.50]
hackney_util:mod_metrics/0                                        8     0.08     4  [      0.50]
hackney_response:body/1                                           8     0.08     4  [      0.50]
hackney_request:can_perform_all/3                                 8     0.08     4  [      0.50]
hackney_request:maybe_add_cookies/2                               8     0.08     4  [      0.50]
hackney_request:default_ua/0                                      8     0.08     4  [      0.50]
hackney_headers:to_list/1                                         8     0.08     4  [      0.50]
hackney_manager:init_request/1                                    8     0.08     4  [      0.50]
hackney_manager:close_request/1                                   8     0.08     4  [      0.50]
hackney_manager:update_state/2                                   16     0.08     4  [      0.25]
hackney_http:parser/1                                             8     0.08     4  [      0.50]
hackney_http:parse_response_line/1                                8     0.08     4  [      0.50]
hackney_http:parse_reason/4                                       8     0.08     4  [      0.50]
hackney_http:'-parse_body/1-fun-2-'/2                             8     0.08     4  [      0.50]
'Elixir.Poison.Encoder.Map':encode_name/1                         7     0.08     4  [      0.57]
'Elixir.Poison.Encoder.Map':'-encode/3-lists^foldl/2-0-'/3       13     0.08     4  [      0.31]
hackney:host_header/2                                             8     0.08     4  [      0.50]
hackney:maybe_update_req/1                                        8     0.08     4  [      0.50]
dict:new/0                                                        8     0.08     4  [      0.50]
dict:fold_segs/4                                                 16     0.08     4  [      0.25]
dict:maybe_expand/2                                              38     0.08     4  [      0.11]
inet_tcp:recv/3                                                  16     0.08     4  [      0.25]
'Elixir.Fops.AcceptanceCase':'-__ex_unit_setup_0/1-fun-0-'/2      1     0.08     4  [      4.00]
'Elixir.HTTPoison.Base':build_hackney_options/2                   8     0.08     4  [      0.50]
'Elixir.HTTPoison.Base':response/6                                8     0.08     4  [      0.50]
prim_inet:recv/3                                                 16     0.08     4  [      0.25]
prim_inet:recv0/3                                                16     0.08     4  [      0.25]
prim_inet:getopt/2                                                8     0.08     4  [      0.50]
prim_inet:getopts/2                                               8     0.08     4  [      0.50]
prim_inet:type_opt_1/1                                           24     0.08     4  [      0.17]
prim_inet:type_value/3                                            8     0.08     4  [      0.50]
prim_inet:encode_opt_val/1                                        8     0.08     4  [      0.50]
prim_inet:encode_opts/1                                           8     0.08     4  [      0.50]
metrics:increment_counter/2                                       8     0.08     4  [      0.50]
'Elixir.HTTPoison':'-request/5-fun-1-'/1                          8     0.08     4  [      0.50]
'Elixir.HTTPoison':'-request/5-fun-0-'/1                          8     0.08     4  [      0.50]
string:tokens_single_1/3                                         24     0.08     4  [      0.17]
inet:tcp_sync_input/3                                             8     0.08     4  [      0.50]
hackney_connect:create_connection/5                               8     0.08     4  [      0.50]
hackney_tcp_transport:messages/1                                  8     0.08     4  [      0.50]
hackney_tcp_transport:recv/3                                     16     0.08     4  [      0.25]
'Elixir.Poison.Parser':'parse!'/2                                 8     0.08     4  [      0.50]
'Elixir.Poison.Encoder':encode/2                                 14     0.08     4  [      0.29]
'Elixir.Poison.Encoder':impl_for/1                               14     0.08     4  [      0.29]
gen_server:cast_msg/1                                             8     0.08     4  [      0.50]
'Elixir.GenServer':whereis/1                                      4     0.08     4  [      1.00]
idna:to_ascii/2                                                  32     0.08     4  [      0.13]
'Elixir.Poison.Encoder.BitString':encode/2                       14     0.11     5  [      0.36]
hackney_response:start_response/1                                 8     0.11     5  [      0.63]
hackney_request:send/2                                            8     0.11     5  [      0.63]
hackney_request:expectation/1                                     8     0.11     5  [      0.63]
hackney_request:end_stream_body/1                                 8     0.11     5  [      0.63]
'Elixir.Keyword':'has_key?'/2                                    10     0.11     5  [      0.50]
hackney_headers:new/1                                             8     0.11     5  [      0.63]
hackney_headers:to_binary/1                                      16     0.11     5  [      0.31]
hackney_manager:update_state/1                                   16     0.11     5  [      0.31]
hackney_url:parse_url/1                                           8     0.11     5  [      0.63]
hackney:send_request/2                                           16     0.11     5  [      0.31]
hackney:make_request/6                                            8     0.11     5  [      0.63]
hackney:maybe_proxy/4                                             8     0.11     5  [      0.63]
hackney:maybe_redirect/2                                          8     0.11     5  [      0.63]
dict:store/3                                                     38     0.11     5  [      0.13]
'Elixir.String':slice/3                                           8     0.11     5  [      0.63]
'Elixir.Wallaby.LogStore':append_logs/2                           4     0.11     5  [      1.25]
inet_parse:address/1                                              8     0.11     5  [      0.63]
inet_parse:ipv4_address/1                                         8     0.11     5  [      0.63]
prim_inet:enc_opt_val/2                                          16     0.11     5  [      0.31]
prim_inet:enc_opts/1                                             16     0.11     5  [      0.31]
prim_inet:dec_opt_val/1                                          16     0.11     5  [      0.31]
metrics:update_meter/3                                            8     0.11     5  [      0.63]
'Elixir.HTTPoison':process_request_headers/1                      8     0.11     5  [      0.63]
erlang:port_command/3                                             8     0.11     5  [      0.63]
erlang:port_connect/2                                             8     0.11     5  [      0.63]
hackney_connect:maybe_connect/1                                   8     0.11     5  [      0.63]
hackney_connect:check_mod_metrics/1                              16     0.11     5  [      0.31]
hackney_tcp_transport:setopts/2                                   8     0.11     5  [      0.63]
idna:label_to_ascii/1                                            16     0.11     5  [      0.31]
gen_tcp:recv/3                                                   16     0.11     5  [      0.31]
erlang:error/1                                                    8     0.11     5  [      0.63]
erlang:function_exported/3                                       11     0.11     5  [      0.45]
gen:do_for_proc/2                                                28     0.13     6  [      0.21]
hackney_response:wait_status/1                                   16     0.13     6  [      0.38]
hackney_response:stream_body1/2                                  17     0.13     6  [      0.35]
hackney_request:perform/2                                         8     0.13     6  [      0.75]
hackney_request:handle_body/4                                     7     0.13     6  [      0.86]
hackney_request:req_type/2                                        8     0.13     6  [      0.75]
hackney_headers:update/2                                         15     0.13     6  [      0.40]
hackney_headers:make_header/2                                    31     0.13     6  [      0.19]
hackney_headers:'-to_list/1-fun-0-'/3                            31     0.13     6  [      0.19]
'Elixir.Access':get/3                                            41     0.13     6  [      0.15]
hackney_bstr:'-to_upper/1-lbc$^0/2-0-'/2                         39     0.13     6  [      0.15]
hackney_manager:new_request/1                                     8     0.13     6  [      0.75]
hackney_http:parse_response_version/2                             8     0.13     6  [      0.75]
hackney_http:parse_headers/1                                     39     0.13     6  [      0.15]
hackney_http:parse_header/2                                      24     0.13     6  [      0.25]
hackney_http:parse_options/2                                     16     0.13     6  [      0.38]
hackney_url:do_partial_pathencode/2                              46     0.13     6  [      0.13]
hackney:body/1                                                    8     0.13     6  [      0.75]
code:ensure_loaded/1                                             11     0.13     6  [      0.55]
dict:get_bucket_s/2                                              39     0.13     6  [      0.15]
dict:maybe_expand_aux/2                                          38     0.13     6  [      0.16]
hackney_pool:checkout/4                                           8     0.13     6  [      0.75]
hackney_pool:checkin/2                                            8     0.13     6  [      0.75]
unicode:characters_to_list/1                                     16     0.13     6  [      0.38]
prim_inet:enc_opt/1                                              16     0.13     6  [      0.38]
metrics:init/1                                                    8     0.13     6  [      0.75]
idna_unicode:downcase/1                                          16     0.13     6  [      0.38]
'Elixir.Poison.Parser':object_name/2                             25     0.13     6  [      0.24]
'Elixir.Poison.Parser':object_pairs/3                            25     0.13     6  [      0.24]
idna:to_ascii/1                                                  16     0.13     6  [      0.38]
maps:from_list/1                                                  9     0.13     6  [      0.67]
erlang:make_fun/3                                                17     0.13     6  [      0.35]
os:timestamp/0                                                    8     0.13     6  [      0.75]
erlang:integer_to_list/1                                         23     0.13     6  [      0.26]
erlang:erase/1                                                    8     0.13     6  [      0.75]
gen:do_call/4                                                    28     0.15     7  [      0.25]
hackney_response:maybe_close/1                                    8     0.15     7  [      0.88]
hackney_request:stream_body/2                                     8     0.15     7  [      0.88]
hackney_headers:'-to_binary/1-fun-0-'/2                          31     0.15     7  [      0.23]
hackney_headers:'-update/2-fun-0-'/2                             30     0.15     7  [      0.23]
hackney_bstr:to_upper/1                                           8     0.15     7  [      0.88]
hackney:reply_response/2                                          8     0.15     7  [      0.88]
'Elixir.Wallaby.Driver':log/1                                     4     0.15     7  [      1.75]
prim_inet:dec_value/2                                             8     0.15     7  [      0.88]
metrics:update_histogram/3                                        8     0.15     7  [      0.88]
erlang:port_info/2                                                8     0.15     7  [      0.88]
re:replace/4                                                     40     0.15     7  [      0.17]
inet:'-setopts/2-lc$^0/1-0-'/1                                   16     0.15     7  [      0.44]
hackney_connect:is_pool/1                                        16     0.15     7  [      0.44]
hackney_tcp_transport:controlling_process/2                       8     0.15     7  [      0.88]
erlang:atom_to_binary/2                                          15     0.15     7  [      0.47]
lists:member/2                                                   40     0.15     7  [      0.17]
erlang:port_get_data/1                                           32     0.15     7  [      0.22]
hackney_response:read_body/3                                     16     0.17     8  [      0.50]
hackney_headers:get_value/3                                      31     0.17     8  [      0.26]
hackney_bstr:char_to_upper/1                                     31     0.17     8  [      0.26]
hackney_manager:get_state/1                                      16     0.17     8  [      0.50]
hackney_http:te_identity/2                                        8     0.17     8  [      1.00]
hackney_url:normalize/2                                           8     0.17     8  [      1.00]
dict:find_val/2                                                  47     0.17     8  [      0.17]
dict:store_bkt_val/3                                             45     0.17     8  [      0.18]
hackney_pool:sync_socket/2                                        8     0.17     8  [      1.00]
prim_inet:enum_val/2                                             16     0.17     8  [      0.50]
'Elixir.HTTPoison':request/5                                      8     0.17     8  [      1.00]
re:check_for_crlf/2                                              40     0.17     8  [      0.20]
re:do_grun/6                                                     40     0.17     8  [      0.20]
'Elixir.String.Unicode':next_extend_size/2                       64     0.17     8  [      0.13]
'Elixir.String.Unicode':split_at/2                               16     0.17     8  [      0.50]
application_controller:get_env/2                                 32     0.17     8  [      0.25]
unicode:characters_to_list/2                                     16     0.17     8  [      0.50]
erlang:list_to_binary/1                                          15     0.17     8  [      0.53]
hackney_http:parse_status/4                                      32     0.19     9  [      0.28]
dict:get_bucket/2                                                39     0.19     9  [      0.23]
prim_inet:type_opt/2                                             24     0.19     9  [      0.38]
idna_ucs:expand_utf8_1/3                                         80     0.19     9  [      0.11]
'Elixir.HTTPoison':'-request/5-fun-2-'/1                          8     0.19     9  [      1.13]
string:tokens/2                                                  24     0.19     9  [      0.38]
re:postprocess/5                                                 40     0.19     9  [      0.23]
hackney_connect:use_default_pool/0                               16     0.19     9  [      0.56]
timer:now_diff/2                                                  8     0.19     9  [      1.13]
'Elixir.Poison.Encoder':'impl_for!'/1                            14     0.19     9  [      0.64]
unicode:characters_to_binary/2                                    8     0.19     9  [      1.13]
erlang:unlink/1                                                   8     0.19     9  [      1.13]
application:get_env/2                                            32     0.21    10  [      0.31]
hackney_response:wait_headers/4                                  39     0.21    10  [      0.26]
hackney:request/5                                                16     0.21    10  [      0.63]
'Elixir.String':downcase/1                                        8     0.21    10  [      1.25]
inet_parse:ipv4_field/4                                          80     0.21    10  [      0.13]
re:grun2/3                                                       40     0.21    10  [      0.25]
'Elixir.String.Unicode':do_split_at/4                            80     0.21    10  [      0.13]
gen_server:call/3                                                24     0.21    10  [      0.42]
erlang:whereis/1                                                 15     0.21    10  [      0.67]
erlang:put/2                                                     24     0.21    10  [      0.42]
erlang:list_to_integer/1                                         32     0.21    10  [      0.31]
gen:call/4                                                       28     0.23    11  [      0.39]
'Elixir.Keyword':get/3                                           82     0.23    11  [      0.13]
hackney_bstr:join/3                                              85     0.23    11  [      0.13]
hackney_http:parse_body/1                                        25     0.23    11  [      0.44]
dict:on_bucket/3                                                 38     0.23    11  [      0.29]
prim_inet:enc_time/1                                             16     0.23    11  [      0.69]
erlang:binary_to_list/1                                          32     0.23    11  [      0.34]
'Elixir.Access':get/2                                            41     0.25    12  [      0.29]
hackney_http:execute/1                                           48     0.25    12  [      0.25]
dict:find/2                                                      39     0.25    12  [      0.31]
erlang:port_control/3                                            32     0.25    12  [      0.38]
erts_internal:port_connect/2                                      8     0.25    12  [      1.50]
erts_internal:port_info/2                                         8     0.25    12  [      1.50]
erlang:phash/2                                                   77     0.25    12  [      0.16]
'Elixir.Poison.Encoder.BitString':escape/2                       28     0.28    13  [      0.46]
hackney_response:stream_body/1                                   16     0.28    13  [      0.81]
re:grun/3                                                        40     0.28    13  [      0.33]
'Elixir.Access':fetch/2                                          41     0.30    14  [      0.34]
dict:'-store/3-fun-0-'/3                                         38     0.30    14  [      0.37]
prim_inet:ctl_cmd/3                                              32     0.30    14  [      0.44]
idna_ucs:is_ascii/1                                             144     0.30    14  [      0.10]
string:to_lower_char/1                                           72     0.30    14  [      0.19]
idna_unicode:lookup/2                                           144     0.30    14  [      0.10]
idna_unicode:'-lowercase/1-fun-0-'/2                            144     0.30    14  [      0.10]
'Elixir.Poison.Parser':value/2                                   34     0.30    14  [      0.41]
lists:foldl/3                                                   100     0.32    15  [      0.15]
hackney_bstr:trim/1                                              40     0.32    15  [      0.38]
hackney_http:parse_header/1                                      39     0.32    15  [      0.38]
string:r_pad/3                                                  144     0.32    15  [      0.10]
string:'-to_lower/1-lc$^0/1-0-'/1                                80     0.32    15  [      0.19]
'Elixir.Poison.Parser':string_continue/2                         68     0.32    15  [      0.22]
'Elixir.String.Casing':downcase/2                                72     0.34    16  [      0.22]
code_server:call/1                                               11     0.34    16  [      1.45]
hackney_http:parse_header_value/1                                48     0.34    16  [      0.33]
dict:fold_seg/4                                                 136     0.34    16  [      0.12]
erlang:list_to_integer/2                                          8     0.34    16  [      2.00]
lists:keyfind/3                                                 118     0.34    16  [      0.14]
proplists:get_value/2                                            48     0.36    17  [      0.35]
lists:reverse/2                                                 120     0.36    17  [      0.14]
'Elixir.Keyword':get/2                                           74     0.38    18  [      0.24]
dict:get_slot/2                                                  77     0.38    18  [      0.23]
string:tokens_single_2/4                                        184     0.38    18  [      0.10]
string:right/3                                                  144     0.38    18  [      0.13]
inet_db:lookup_socket/1                                          32     0.40    19  [      0.59]
re:process_parameters/6                                          80     0.40    19  [      0.24]
hackney_response:recv/1                                          16     0.42    20  [      1.25]
lists:all/2                                                     160     0.42    20  [      0.13]
hackney_url:urldecode/3                                          80     0.45    21  [      0.26]
re:do_replace/5                                                  40     0.45    21  [      0.53]
erlang:demonitor/2                                               28     0.45    21  [      0.75]
re:check_for_unicode/2                                           80     0.47    22  [      0.28]
ets:delete/2                                                      8     0.49    23  [      2.88]
erlang:monitor/2                                                 28     0.49    23  [      0.82]
hackney_trace:report_event/4                                     48     0.51    24  [      0.50]
'Elixir.String.Unicode':next_grapheme_size/1                     64     0.51    24  [      0.38]
idna_unicode_data1:l/1                                          144     0.51    24  [      0.17]
hackney_http:parse_first_line/3                                  16     0.53    25  [      1.56]
hackney_http:match_eol/2                                        144     0.55    26  [      0.18]
prim_inet:async_recv/3                                           16     0.55    26  [      1.63]
idna:'-label_to_ascii/1-fun-0-'/1                               144     0.57    27  [      0.19]
dict:fold_bucket/3                                              159     0.62    29  [      0.18]
idna_unicode:codepoint_downcase/1                               144     0.62    29  [      0.20]
idna_unicode:hex/1                                              144     0.62    29  [      0.20]
idna_unicode:'-downcase/1-lc$^0/1-0-'/1                         160     0.62    29  [      0.18]
re:loopexec/7                                                    40     0.62    29  [      0.72]
re:process_repl_params/2                                        120     0.66    31  [      0.26]
re:to_binary/2                                                  120     0.66    31  [      0.26]
erlang:integer_to_list/2                                        144     0.68    32  [      0.22]
binary:split/3                                                   32     0.68    32  [      1.00]
idna_unicode:lookup/1                                           144     0.70    33  [      0.23]
lists:reverse/1                                                 121     0.74    35  [      0.29]
hackney_bstr:to_binary/1                                        123     0.81    38  [      0.31]
idna_unicode:lowercase/1                                        144     0.85    40  [      0.28]
ets:lookup/2                                                     48     0.89    42  [      0.88]
string:chars/3                                                  432     0.93    44  [      0.10]
erlang:integer_to_list/3                                        288     0.98    46  [      0.16]
'Elixir.Poison.Parser':skip_whitespace/1                        122     1.17    55  [      0.45]
hackney_http:execute/2                                           72     1.34    63  [      0.88]
'Elixir.Poison.Parser':string_chunk_size/2                      507     1.34    63  [      0.12]
erlang:iolist_to_binary/1                                       192     1.34    63  [      0.33]
erlang:send/3                                                    36     1.40    66  [      1.83]
hackney_bstr:to_lower/1                                         141     1.47    69  [      0.49]
proplists:get_value/3                                           408     1.53    72  [      0.18]
erts_internal:port_control/3                                     32     1.68    79  [      2.47]
hackney_url:partial_pathencode/2                                496     2.04    96  [      0.19]
'Elixir.Poison.Encoder.BitString':chunk_size/3                  692     2.06    97  [      0.14]
binary:split/2                                                   87     2.31   109  [      1.25]
erts_internal:port_command/3                                      8     3.06   144  [     18.00]
hackney_bstr:'-to_lower/1-lbc$^0/2-0-'/2                       1567     3.44   162  [      0.10]
re:run/3                                                         80     3.67   173  [      2.16]
erlang:setelement/3                                             489     5.35   252  [      0.52]
hackney_bstr:char_to_lower/1                                   1426     5.84   275  [      0.19]
------------------------------------------------------------  -----  -------  ----  [----------]
Total:                                                        16645  100.00%  4709  [      0.28]

Automatic screenshots don't appear to work

I've added the config

# config/test.exs
config :wallaby, screenshot_on_failure: true

to my app but when I do assert "foo" == "bar" no screenshot is taken. Screenshots in general do work though as adding take_screenshot(session) to the same test does result in a screenshot.

Let me know if there is additional debug information needed. I've just started with wallaby so it's not impossible that I'm doing something wrong.

Unable to chain `fill_in`?

Slightly related to a previous issue, I'm trying to create a helper that steps through user sign in's.

Esteem.UserHelper.sign_in/3 is only ever invoked from another module which is using AcceptanceCase

defmodule Esteem.UserHelper do
  use Wallaby.DSL

  def sign_in(session, email, password) do
    session
    |> visit("/sessions/new")
    |> fill_in("Your email", with: email)
    |> fill_in("Password", with: password)
    |> click_button("Submit")
  end
end

When it runs, I get the following error

  1) test user signs out and is redirected to login page (Esteem.UserSessionsTest)
     test/acceptance/user_sessions_test.exs:16
     ** (FunctionClauseError) no function clause matching in Wallaby.DSL.Actions.fill_in/3
     stacktrace:
       (wallaby) lib/wallaby/dsl/actions.ex:59: Wallaby.DSL.Actions.fill_in(%Wallaby.Session{base_url: "http://localhost:65372/", id: "195cd7d0-3f74-11e6-b0d4-d76992d84dcf", screenshots: [], server: #PID<0.467.0>}, "Password", [with: 'test'])
       (esteem) test/support/user_helper.ex:8: Esteem.UserHelper.sign_in/3
       test/acceptance/user_sessions_test.exs:18

It seems that the second fill_in (in this case, "Password") is getting a clause error.. and I honestly have no idea why.

Here is another test in the suite that works just fine, so I don't know why chaining fill_in's would be an issue

defmodule Esteem.UserSignsUpTest do
  use Esteem.AcceptanceCase, async: true

  test "user signs up", %{session: session} do
    session
    |> visit("/")
    |> fill_in("First name", with: "Luke")
    |> fill_in("Last name", with: "Rollans")
    |> fill_in("Email", with: "[email protected]")
    |> fill_in("Password", with: "123456")
    |> fill_in("Organisation name", with: "Marvellous Mutants")
    |> click_button("Submit")

    notice =
      session
      |> find(".alert-info")
      |> text

    assert notice == "Signed up successfully!"
  end
end

I am really sorry to post again, but I just don't know how to proceed.. and hopefully this might help someone else in future :)

Unable to click on nodes outside of session window size

Setup

We have a page where clicking on a button will cause other buttons to appear further down the page.

Problem

Wallaby.Node.Find would be unable to find the buttons that appeared further down the page. Originally I thought it was a problem with the nodes not rendering in time, but I could see >part< of buttons inside the failure screenshots.

Workaround Solution

Increasing the window size for the session fixed the problem

This behavior seems odd though - one would think that phantom would be able to click anything on the page regardless of if it is scrolled off.

Attempting to visit without setting the base_url should raise an error

At first, I missed the instructions for setting the base_url. So when I did visit("/my_path") it did not visit my app.

I think that if you use relative paths without a base_url an error should be raised

You called visit with #{relative_path}, but did not set a base_url

Set this somewhere in your app, maybe in test/test_helper.exs:

    Application.put_env(:wallaby, :base_url, "http://localhost:4001")

If using Phoenix, you could grab the url from your endpoint:

    Application.put_env(:wallaby, :base_url, YourApplication.Endpoint.url)

Simulate Drag and Drop From Filesystem

I've split this into its own issue as from what I've read its a harder issue to solve.

It'd also be very nice to be able to test drag and drop file uploaders by allowing us to easily specify that we've dropped a file onto a specific element.

Capture JS errors and logs in the servers `handle_info` call

Blocking each webdriver call with a call to check for js errors and logs is an expensive and wasteful process. All of this same information is sent to the server process from phantom. We should be able to capture this information using the same logging capture behaviour and then print logs to the console or throw errors in our respective test. This should work correctly because each phantom is scoped to a single test at a time.

Clearing localStorage on each test run

Is there a way to clear local storage for each test run? Or to specify different local storage directories?

I see that it's possible to modify phantomjs to use a different local storage path, but I'm trying to avoid tests that modify local storage from stepping on one another.

Button presses via click("<button-name>") don't register

At least it didn't work for me, although my buttons had similar markup to the ones in test/support/pages/forms.html, then again there seems to be no test coverage for it either.

I wrote an xpath that is able to find the possible range of buttons with inspiration from Capybara.

An implementation similar to click_link (and as I use it) would be:

def press(session, button)
  button_xpath = ".//*[self::input | self::button][(./@type = 'submit' or ./@type = 'reset' or ./@type = 'button' or ./@type = 'image')][(((./@id = '#{button}' or ./@name = '#{button}' or ./@value = '#{button}' or ./@alt = '#{button}' or ./@title = '#{button}' or contains(normalize-space(string(.)), '#{button}'))))]"

  session
  |> Node.find({:xpath, button_xpath}
  |> Node.click
end

If desired, I can create a PR.

Accessing the "head title" element

I'm trying to access the <title> element in a HTML document, so that I can assert its contents, but I'm not having any luck. Is this possible with Wallaby at the moment?

I've tried the following:

Given the following html snippet from a "home-page":

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>My Title</title>
  </head>
  <body>
    This is the home-page
  </body>
</html>

And the following tests:

defmodule MyApp.HomeFeatureTest do
  use MyApp.FeatureCase

  test "home-page body is present", %{session: session} do
    text =
      session
      |> visit("/")
      |> find("body")
      |> text

    assert text == "This is the home-page"
  end

  test "home-page head title is present", %{session: session} do
    text =
      session
      |> visit("/")
      |> find("title", visible: false)
      |> text

    assert text == "My Title"
  end

  test "home-page head title is present, part 2", %{session: session} do
    text =
      session
      |> visit("/")
      |> find("head title", visible: false)
      |> text

    assert text == "My Title"
  end
end

2 out of 3 tests fail:

$ mix test test/features/homepage_feature_test.exs                                                           

  1) test home-page head title is present (MyApp.HomeFeatureTest)
     test/features/homepage_feature_test.exs:27
     Assertion with == failed
     code: text == "My Title"
     lhs:  ""
     rhs:  "My Title"
     stacktrace:
       test/features/homepage_feature_test.exs:34



  2) test home-page head title is present, part 2 (MyApp.HomeFeatureTest)
     test/features/homepage_feature_test.exs:37
     Assertion with == failed
     code: text == "My Title"
     lhs:  ""
     rhs:  "My Title"
     stacktrace:
       test/features/homepage_feature_test.exs:44



Finished in 1.1 seconds (0.1s on load, 0.9s on tests)
3 tests, 2 failures

Playing nice with authentication

I can't seem to get Wallaby playing nicely with authentication. I'm using Guardian to handle sessions and authentication, which is obviously done through a conn.

Should I ...

  1. Use a helper to step Wallaby through navigating to my login form, logging in and return the altered session
  2. Or; is there a way that Wallaby can play nice with a separate conn? If I build a conn, put the session in the state I want it to be, will the Wallaby session reflect that state? (I think this is a big no)

I'm planning on blogging this once I've figured it all out.. Seems to be a bit of a hole in the Phoenix community atm :)

PhantomJS crashing

I have a relatively simple sign up form (the body html):

<div id="root">
  <div data-reactroot="" class="view-container registrations new">
    <main>
      <header>
        <div class="logo"></div>
      </header>
      <form>
        <div class="field"><input type="text" name="first_name" value="" placeholder="First name" required=""></div>
        <div class="field"><input type="text" name="last_name" value="" placeholder="Last name" required=""></div>
        <div class="field"><input type="email" name="email" value="" placeholder="Email" required=""></div>
        <div class="field"><input type="text" name="password" value="" placeholder="Password" required=""></div>
        <div class="field"><input type="password" name="password_confirmation" value="" placeholder="Confirm password" required=""></div>
        <button type="submit">Sign up</button><a href="/signin">Sign in</a>
      </form>
    </main>
  </div>
</div>

When I attempt to run this test:

  test "redirect to sign in", %{session: session} do
    user = %{
      first_name: "Jane",
      last_name: "Smith",
      email: "[email protected]",
      password: "password",
      encrypted_password: Comeonin.Bcrypt.hashpwsalt("password"),
    }

    session
    |> visit("/signup")
    |> fill_in("first_name", with: user.first_name)
  end

I reproducibly get this output:

PhantomJS has crashed. Please read the bug reporting guide at
<http://phantomjs.org/bug-reporting.html> and file a bug report.


  1) test redirect to sign in (MyApp.UserRegistrationTest)
     test/acceptance/user_registration_test.exs:4
     ** (RuntimeError) There was an error calling: http://localhost:50589/session/216548c0-6af0-11e6-873a-d74ddc1e1078/element/:wdc:1472149027298/value -> closed
     stacktrace:
       (wallaby) lib/wallaby/driver.ex:302: Wallaby.Driver.make_request/3
       (wallaby) lib/wallaby/driver.ex:274: Wallaby.Driver.check_logs!/2
       (wallaby) lib/wallaby/node.ex:58: Wallaby.Node.fill_in/2
       (wallaby) lib/wallaby/dsl/actions.ex:63: Wallaby.DSL.Actions.fill_in/3
       test/acceptance/user_registration_test.exs:18: (test)



Finished in 2.2 seconds
1 test, 1 failure

Randomized with seed 380223

I have another, similar test for which this doesn't happen at all - is there something I'm missing?

HTTPoison timeout

I'm having some tests fail intermittently because of a HTTPPoison timeout error. Would you be open to setting the HTTPoison timeout to :infinity as ExUnit.Case already has a configurable timeout with a default?

EDIT: Adding some more information.

  • This only happens when running my Wallaby tests on their own, e.g. mix test test/features
  • It's always the first or second test.

This makes me think it's actually the first test running before everything is fully initialised (phantom?), so setting HTTPoison mike not be the best way to fix in your opinion.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.