Code Monkey home page Code Monkey logo

phoenix_integration's People

Contributors

adz avatar andreapavoni avatar arnodirlam avatar boydm avatar dlederle avatar gmodarelli avatar hzeus avatar jamescheuk91 avatar jonasschmidt avatar marick avatar martinos avatar mbuckley0 avatar roelandvanbatenburg avatar stanislove avatar zombieharvester 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

phoenix_integration's Issues

Assert custom function taking a Plug.Conn

Hi!

I find this library very elegant and handy to make integration test readable without compromising simplicity and speed. Congratulations!

Today I came across a not covered scenario that maybe could be worthwhile to add to it. The idea is to allow asserting custom functions that take Plug.Conn as first parameter.

I describe my scenario: I'm testing that a user signs in. With assert_response there is no problem to assert that the name of the user is printed on screen, but I would like to really be sure that the user has been added to the session (or refute it, in an invalid attempt). As I'm using Guardian for that, now I have something like the following:

#...
  defp assert_user_in_session(conn) do
    assert Guardian.Plug.current_resource(conn)
    conn
  end

  defp refute_user_in_session(conn) do
    refute Guardian.Plug.current_resource(conn)
    conn
  end

  describe "when credentials are valid" do
    test "signs the user in", %{conn: conn} do
      get(conn, session_path(conn, :new))
      # sign_in is just a helper function that uses follow_form
      |> sign_in("[email protected]", "secure_password")
      |> assert_user_in_session
      |> assert_response(
        status: 200,
        html: "[email protected]"
      )
    end
  end
#...

Adding the functionality to this library, we could write something like:

      get(conn, session_path(conn, :new))
      |> sign_in("[email protected]", "secure_password")
      |> assert_response(
        status: 200,
        html: "[email protected]",
        # Ideally, accepting several `function` keys
        function: fn(conn) -> Guardian.Plug.current_resource(conn) end
      )

or

      get(conn, session_path(conn, :new))
      |> sign_in("[email protected]", "secure_password")
      |> assert_response(
        status: 200,
        html: "[email protected]",
      )
      |> assert_function(
        fn(conn) -> Guardian.Plug.current_resource(conn) end
      )

What do you think? Maybe another noun other than function could be used for the key/function name.

Failed to use "phoenix" (version 1.3.0-rc.0) because

I'm not able to use this library with phoenix 1.3.0-rc.0. I've get the following error when running mix deps.get:

Failed to use "phoenix" (version 1.3.0-rc.0) because
  phoenix_integration (version 0.2.0) requires ~> 1.1 *
  phoenix_live_reload (version 1.0.8) requires ~> 1.0 or ~> 1.2-rc
  mix.lock specifies 1.3.0-rc.0

* This requirement does not match pre-releases. To match pre-releases include a pre-release in the requirement, such as: "~> 2.0-beta".

** (Mix) Hex dependency resolution failed, relax the version requirements of your dependencies or unlock them (by using mix deps.update or mix deps.unlock). If you are unable to resolve the conflicts you can try overriding with {:dependency, "~> 1.0", override: true}

v0.5.0 release tag

Hi, thanks for this project, I wanted to use browser-less acceptance framework for a long time and I didn't know it was there all along!

I was trying to get to code from docs but it 404s because there's no v0.5.0 tag on GitHub. It would be great if you could add that in case anyone else runs into this problem in the future.

shameless plug

I've built a tool https://github.com/wojtekmach/shipit that is supposed to automate common steps in publishing a package. If this is something you'd consider using I'd appreciate any feedback!

** (RuntimeError) no @endpoint set in test case

I'm trying to use it in Phoenix 1.3.0-rc.0. I've added integration_case.ex to my test/support folder:

defmodule MyApp.Web.IntegrationCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      use MyApp.Web.ConnCase
      use PhoenixIntegration
    end
  end
end

and I wrote simple spec which look like this:

defmodule MyApp.AboutIntegrationTest do
  use MyApp.Web.IntegrationCase, async: true

  setup do
    conn = build_conn()
    {:ok, conn: conn}
  end

  test "Basic page flow", %{conn: conn} do
    get( conn, project_path(conn, :index) )
    |> follow_link( "MyApp )
    |> assert_response( status: 200, path: project_path(conn, :index) )
  end
end

When I try to run specs it returns me following error:

  1) test Basic page flow (MyApp.AboutIntegrationTest)
     test/integrations/first_test.exs:9
     ** (RuntimeError) no @endpoint set in test case
     stacktrace:
       (phoenix) lib/phoenix/test/conn_test.ex:214: Phoenix.ConnTest.dispatch/5
       (phoenix_integration) lib/phoenix_integration/requests.ex:233: PhoenixIntegration.Requests.follow_link/3
       test/integrations/first_test.exs:11: (test)

What I'm doing wrong?

Click to button method

Hi! I have html like this:

<button 
 class="btn btn-info btn-block" 
 data-csrf="csrf=="
 data-method="post"
 data-to="/test?level=test"
 role="button">Test</button>

As I understand follow_link and click_link maethods works only with <a> tags. (Floki.find(html, "a"))

Is there any way to follow the buttons?

I can make PR, if this functionality is missing.
Thanks all!

Confused about endpoint config

I really like this library -- it's so much nicer with |>. I have just got started with phoenix, so excuse me while still learning basics!

So my confusion: in setup, Step 3 you say:

Alternately you could place the call to use PhoenixIntegration in your conn_case.ex file. 
Just make sure it is after the definition of @endpoint.

Ignoring the use PhoenixIntegration point, given that there is an @endpoint definition in conn_case.ex, why is it necessary to do it again under config/test.exs for :phonix_integration (as stated in "Step 1"?)

I tried without that config, and everything seems to work ok...

follow_path for POST with json body

I have an API endopoint expecting a JSON payload in the body. We are trying to use the follow_path/3 but cannot seem to find a way of adding the request body without treating it as url params.
We are getting the following error if we include the request as params

 Postgrex expected a binary, got 2. Please make sure the value you are passing matches the definition in your table or in your query or convert the value accordingly.

We feel if we are able to send the parameters as the request body it will be handled correctly by phoenix.

Despite the failure above, when we test the endpoints manually it works okay

Current test

      conn
      |> follow_path(Routes.api_resource_path(conn, :create, valid_attrs), method: :post)
      |> assert_response(json: valid_response())

click_link wipes current user in assigns

Not sure if I'm doing something wrong or that this is a bug, but I am passing an authenticated conn struct into a test pipeline (conn.assigns.current_user is set). This works fine on the first assertion, but when I try to continue the path to the next page it wipes the current_user...

Here is the test:

test "Basic page flow", %{conn: conn, student: student} do
    conn
    |> get(Routes.student_path(conn, :index))
    |> follow_link(student.first_name)
    |> IO.inspect # => at this point conn.assigns.current_user is nil 
    |> assert_response(status: 200, path: Routes.student_path(conn, :show, student))
  end

I have tracked it down to here:

request_path(conn, href, opts.method)

Basically that return does not keep the current_user intact. I am not sure what is happening, but it seems it might have something to do with the macro implementation in ConnTest.

Hope you are able to understand and track it down. If you have pointers for me on how to fix it, I'd be happy to contribute!

Thanks!

Tjaco

Error following get form

Hi!

I'm trying to follow a search form. It is a form with :get method pointing to index action.

If I do:

conn
|> get(event_path(conn, :index))
|> follow_form(%{search: %{     
  source_eq: "web"     
}}, identifier: event_path(conn, :index))

It gets confused with the create action:

** (Phoenix.ActionClauseError) could not find a matching Eventbox.Web.EventController.create clause       
     to process request. This typically happens when there is a                                                
     parameter mismatch but may also happen when any of the other
     action arguments do not match. The request parameters are:
                               
       %{"_utf8" => "✓", "search" => %{"category_id_eq" => nil, "client_type_id_eq" => nil, "lounge_id_eq" => nil, "people_eq" => nil, "source_eq" =>
 "web", "start_datetime_gte" => nil, "start_datetime_lte" => nil, "status_eq" => nil, "subcategory_id_eq" => nil}}

While if I specify the method:

conn
|> sign_in_user(user)
|> get(event_path(conn, :index))
|> follow_form(%{search: %{
  source_eq: "web"
}}, identifier: event_path(conn, :index), method: :get)

It doesn't find the form:

** (RuntimeError) Failed to find form "/events", :get in the response
Expected to find a form with action="/events"

Thanks!

Consider buttons with a name and value as possible input for a form on follow_form

I have a use case where form has multiple buttons each of them pointing at the same controller action but each with a separate name and value:

<button class="btn btn-sm btn-danger" id="mark-released" name="action[is_released]" type="submit" value="true">Release</button>
<button class="btn btn-sm btn-danger" id="mark-released" name="action[is_deployed]" type="submit" value="true">Deployed</button>

In my tests, with the current implementation of follow_form I can never get the value of the button to be set as:

follow_form(conn, %{
  action: %{is_deployed: "true"},
  metadata: %{"#{version0.id}": "true", "#{version1.id}": "true"},
},

We currently use v0.7.0 but if this is of interest I'd be willing to submit a patch to 0.7.0 and higher versions.
From a quick look at it in 0.7.0, it requires adding some lines in request.ex

  defp get_form_data(form) do
    %{}
    |> build_form_by_type(form, "input")
    |> build_form_by_type(form, "textarea")
    |> build_form_by_type(form, "select")
    |> build_form_by_type(form, "button")
  end

...

  defp get_input_value(input, "button") do
    Floki.attribute(input, "value")
  end

I can see this would be very different for current HEAD, so I wanted to check before jumping into the code currently in master.

Another case of a broken array of checkboxes

Hi,

First of all, thanks for this awesome library, we use it on a few projects and it helps a lot, also thanks @marick for the amazing work refactoring the way it parses the form tree.

I have all tests green on 0.7.0 and the application works as intended, but when updating to 0.8.2 I started getting the following error:

Error: You are combining list and scalar values.
The value you want to use is a list:
    ["ci"]
But the name of the tag doesn't end in `[]`:
    "ab_test[environments]"
Form action: "/manage/config/games/VGIIQQZCKNSNVRYN/ab_tests"

The form itself has this rendered HTML:

<div>
  <input id="ab_test_environments" name="ab_test[environments]" type="hidden" value="" />
  <label class="checkbox-inline">
    <input data-persisted="false" name="ab_test[environments][]" type="checkbox" value="ci" checked="" />
    Ci
  </label>
  <label class="checkbox-inline">
    <input data-persisted="false" name="ab_test[environments][]" type="checkbox" value="staging" checked="" />
    Staging
  </label>
  <label class="checkbox-inline">
    <input data-persisted="false" name="ab_test[environments][]" type="checkbox" value="production" />
    Production
  </label>
</div>

What this code accomplishes is when we have at least one selected checkbox the Phoenix controller receives an array like ["ci", "staging"] that Ecto does understand, and when there is none selected it receives an empty string that Ecto does transform automatically into an empty array when creating the Changeset.

I've IO.inspect'ed a bit into the library code and it seams like the multiple tags are not merged, probably because the name differ(with and without []), and the checkbox tags are simply ignored.

If we change the name of the hidden field to match the other inputs this error is fixed, but the functionality is broken since it will actually always send the empty string as the first element of the array(["", "ci", "staging"]) and Ecto does not understand that.

I could remove the first element of the array, but that would require also changing a lot of validations around it, and it feels wrong to change so much battle tested production code to fix a test.

Any other ideas on how I could fix this while keeping the same behaviour?
Or could it be possible to backport #42 to 0.7.x in order to have it compatible with phoenix 1.5?

Github Actions Workflow

Sorry for ask this here, I'm not sure if I need anything else in my github action workflow, the whole integration tests fails.

I really like phoenix_integration for integration tests, I really would appreciate If anyone can give me a good approach to run it using github actions.

  1) test Try to login with invalid company (Web.CompanyLoginTest)
     apps/web/test/web/integration/company_login_test.exs:7
     ** (RuntimeError) Failed to find link "Mis ofertas", :get in the response
     Expected to find an anchor with text containing "Mis ofertas"
     code: |> follow_link("Mis ofertas")
     stacktrace:
       (phoenix_integration) lib/phoenix_integration/requests.ex:773: PhoenixIntegration.Requests.find_html_link/3
       (phoenix_integration) lib/phoenix_integration/requests.ex:193: PhoenixIntegration.Requests.click_link/3
       (phoenix_integration) lib/phoenix_integration/requests.ex:255: PhoenixIntegration.Requests.follow_link/3
       test/web/integration/company_login_test.exs:9: (test)
name: Continuous Integration
on: [push]

# This workflow will build and test a Phoenix application that uses
# PostgreSQL as a database. Will also cache dependencies and build
# artifacts to speed up the build.
jobs:
  test:
    env:
      MIX_ENV: test
      TEST_DATABASE_NAME: postgres
      TEST_DATABASE_USERNAME: postgres
      TEST_DATABASE_PASSWORD: postgres
      TEST_DATABASE_HOSTNAME: localhost
      TEST_DATABASE_PORT: 5432
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [10.x]
        otp-version: [22.2]
        elixir-version: [1.9.4]

    services:
      db:
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        image: postgres:11
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/[email protected]
      - name: Recover dependency cache
        uses: actions/cache@v1
        id: deps_cache
        with:
          path: deps
          key: ${{ runner.OS }}-deps-${{ hashFiles('**/mix.lock') }}
          restore-keys: |
            ${{ runner.OS }}-deps-${{ env.cache-name }}-
            ${{ runner.OS }}-deps-
            ${{ runner.OS }}-

      - name: Copy config file
        run: cp config/env.local.exs.example config/env.local.exs

      - name: Recover build cache
        uses: actions/cache@v1
        with:
          path: _build
          key: ${{ runner.OS }}-build-${{ env.cache-name }}
          restore-keys: |
            ${{ runner.OS }}-build-
            ${{ runner.OS }}-

      - uses: actions/[email protected]
        with:
          otp-version: ${{ matrix.otp-version }}
          elixir-version: ${{ matrix.elixir-version }}

      - uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install Dependencies
        run: mix local.rebar --force
      - run: mix local.hex --force
      - run: mix deps.get
      - run: mix compile
      - run: mix ecto.migrate
      - run: npm install --prefix ./apps/web/assets
      - run: npm run deploy --prefix ./apps/web/assets

      - name: Run tests
        run: mix test --trace

Floki v0.24.0 emits deprecations

https://github.com/philss/floki version 0.24.0 emits deprecations on PhoenixIntegration v0.6

warning: deprecation: parse the HTML with parse_document or parse_fragment before using find/2
  lib/floki.ex:170: Floki.find/2
  (phoenix_integration) lib/phoenix_integration/requests.ex:675: PhoenixIntegration.Requests.find_html_link/3
  (phoenix_integration) lib/phoenix_integration/requests.ex:189: PhoenixIntegration.Requests.click_link/3
  (phoenix_integration) lib/phoenix_integration/requests.ex:251: PhoenixIntegration.Requests.follow_link/3
...

Elixir v1.10 compilation error, Kernel.is_struct/1 conflicts with local function

Elixir 1.10 added Kernel.is_struct/1

==> phoenix_integration
Compiling 3 files (.ex)
warning: function is_struct/1 is unused
  lib/phoenix_integration/requests.ex:1159


== Compilation error in file lib/phoenix_integration/requests.ex ==
** (CompileError) lib/phoenix_integration/requests.ex:1159: imported Kernel.is_struct/1 conflicts with local function
    (elixir 1.10.0) src/elixir_locals.erl:94: :elixir_locals."-ensure_no_import_conflict/3-lc$^0/1-0-"/2
    (elixir 1.10.0) src/elixir_locals.erl:95: anonymous fn/3 in :elixir_locals.ensure_no_import_conflict/3
    (stdlib 3.11.1) erl_eval.erl:680: :erl_eval.do_apply/6

follow_form/3 fails when using email_input/HTML input of type "email" in v0.8

Given the following form and test:

# Form ----
<%= form_for @changeset, @action, fn f -> %>

  <%= label f, :email %>
  <%= email_input f, :email %>
  <%= error_tag f, :email %>

  <div>
    <%= submit "Save" %>
  </div>
<% end %>

# Test ----
test "create user", %{conn: conn} do
    get(conn, Routes.user_path(conn, :new))
    |> follow_form(%{user: %{email: "[email protected]"}})
end

Will fail with this error:

Error: You tried to set the value of a tag that isn't in the form.
Path tried: [:user, :email]
Is this a typo?: :user
Your value: "[email protected]"
Form action: "/users"

Changing the email_input in the form to a normal text_input and running the test again works.

email_input is just a normal Phoenix tag that sets the type of the HTML input to "email" instead of "text".

An array of checkboxes where none are checked isn't shown with `fetch_form`

Minor bug; I'll try to have a look at it soonish

Consider the following and note that the name of the second and third line ends in [].

<input name="procedures[0][name]"          type="text"     value="">
<input name="procedures[0][species_ids][]" type="checkbox" value="1">
<input name="procedures[0][species_ids][]" type="checkbox" value="2">

That means a value is submitted with something like this:

    |> follow_form(%{procedures:                           
                    %{0 => %{name: "procedure #1",
                             species_ids: [1]}
                    }})

That works fine. However, I would expect the result for fetch_form to look something like this:

  inputs: %{
    _csrf_token: "XCgQR0wwBREzPykwC2Y3egtGIhp0HioOegs4xQORPuldT9s6l7O-CvsZ",
    procedures: %{
      "0": %{name: "", 
             species_ids: []}, ...     # <<<<<<

However, it looks like this:

  inputs: %{
    _csrf_token: "XCgQR0wwBREzPykwC2Y3egtGIhp0HioOegs4xQORPuldT9s6l7O-CvsZ",
    procedures: %{
      "0": %{name: ""}, ...     # <<<<<<

Trouble with follow_form

I am having issues getting the follow_form function to work for my tests. Any help with getting it working would be greatly appreciated. I am getting the following error.

** (RuntimeError) Attempted to set missing input in form
     Setting key: name
     And value: CSP
     Into fields: %{}
     stacktrace:
       lib/phoenix_integration/requests.ex:630: PhoenixIntegration.Requests.put_if_available!/3
       (stdlib) lists.erl:1263: :lists.foldl/3
       lib/phoenix_integration/requests.ex:612: anonymous fn/2 in PhoenixIntegration.Requests.merge_grouped_fields/2
       (stdlib) lists.erl:1263: :lists.foldl/3
       lib/phoenix_integration/requests.ex:290: PhoenixIntegration.Requests.submit_form/3
       lib/phoenix_integration/requests.ex:340: PhoenixIntegration.Requests.follow_form/3
       test/features/user_edits_a_project_feature_test.exs:14: (test)

This is my test

defmodule KitetimerElixir.EditProjectTest do
 use KitetimerElixir.IntegrationCase, async: true

  alias KitetimerElixir.User
  alias KitetimerElixir.Project

  test "edits a project", %{conn: conn} do
    Repo.insert!(%Project{id: 1, name: "2RK"}

  login(conn)
  |> follow_link(project_path(conn, :index))
  |> follow_link(project_path(conn, :show, 1))
  |> follow_link(project_path(conn, :edit, 1))
  |> IO.inspect
  |> follow_form(%{ project: %{
                   name: "CSP"
                 }})
  |> assert_response(
    status: 200,
    path: project_path(conn, :show, 1),
    html: "CSP" )
 end

  def login(conn) do
    Repo.insert!(%User{id: 1, name: "Test User", email: "[email protected]", encrypted_password: Comeonin.Bcrypt.hashpwsalt("password")})

    get(conn, session_path(conn, :new))
    |> follow_form(%{ session: %{
                        email: "[email protected]",
                        password: "password"
                      }
                    })
   end
end

Here is the result of an IO.inspect just before this I try to run follow_form

%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...},
 assigns: %{changeset: #Ecto.Changeset<action: nil, changes: %{},
    errors: [uid: {"can't be blank", [validation: :required]}],
    data: #KitetimerElixir.Project<>, valid?: false>,
   layout: {KitetimerElixir.LayoutView, "app.html"},
   project: %KitetimerElixir.Project{__meta__: #Ecto.Schema.Metadata<:loaded, "projects">,
    id: 1, inserted_at: ~N[2017-02-01 10:52:05.763467], internal: false,
    minimum_duration: nil, name: "2RK",
    tickets: #Ecto.Association.NotLoaded<association :tickets is not loaded>,
    uid: nil, updated_at: ~N[2017-02-01 10:52:05.769452]}},
 before_send: [#Function<0.101282891/1 in Plug.CSRFProtection.call/2>,
  #Function<4.45310566/1 in Phoenix.Controller.fetch_flash/2>,
  #Function<0.61377594/1 in Plug.Session.before_send/2>,
  #Function<1.70936004/1 in Plug.Logger.call/2>], body_params: %{},
 cookies: %{"_kitetimer_elixir_key" => "SFMyNTY.g3QAAAACbQAAAAtfY3NyZl90b2tlbm0AAAAYbGtrL1FuakJqWlRQemw2ZW15QUR4dz09bQAAAAxjdXJyZW50X3VzZXJhAQ.d1bWLgR79pEuLgoLjFKgUDfsFcKaAnXJ3d6tqQ6fYAw"},
 halted: false, host: "www.example.com", method: "GET", owner: #PID<0.450.0>,
 params: %{"id" => "1"}, path_info: ["projects", "1", "edit"], path_params: %{},
 peer: {{127, 0, 0, 1}, 111317}, port: 80,
 private: %{KitetimerElixir.Router => {[], %{}}, :phoenix_action => :edit,
   :phoenix_controller => KitetimerElixir.ProjectController,
   :phoenix_endpoint => KitetimerElixir.Endpoint, :phoenix_flash => %{},
   :phoenix_format => "html",
   :phoenix_layout => {KitetimerElixir.LayoutView, :app},
   :phoenix_pipelines => [:browser], :phoenix_recycled => false,
   :phoenix_route => #Function<63.71185309/1 in KitetimerElixir.Router.match_route/4>,
   :phoenix_router => KitetimerElixir.Router, :phoenix_template => "edit.html",
   :phoenix_view => KitetimerElixir.ProjectView,
   :plug_session => %{"_csrf_token" => "lkk/QnjBjZTPzl6emyADxw==",
     "current_user" => 1}, :plug_session_fetch => :done,
   :plug_skip_csrf_protection => true}, query_params: %{}, query_string: "",
 remote_ip: {127, 0, 0, 1},
 req_cookies: %{"_kitetimer_elixir_key" => "SFMyNTY.g3QAAAACbQAAAAtfY3NyZl90b2tlbm0AAAAYbGtrL1FuakJqWlRQemw2ZW15QUR4dz09bQAAAAxjdXJyZW50X3VzZXJhAQ.d1bWLgR79pEuLgoLjFKgUDfsFcKaAnXJ3d6tqQ6fYAw"},
 req_headers: [{"cookie",
   "_kitetimer_elixir_key=SFMyNTY.g3QAAAACbQAAAAtfY3NyZl90b2tlbm0AAAAYbGtrL1FuakJqWlRQemw2ZW15QUR4dz09bQAAAAxjdXJyZW50X3VzZXJhAQ.d1bWLgR79pEuLgoLjFKgUDfsFcKaAnXJ3d6tqQ6fYAw"},
  {"content-type", "multipart/mixed; charset: utf-8"}],
 request_path: "/projects/1/edit",
 resp_body: "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <meta name=\"description\" content=\"\">\n  <meta name=\"author\" content=\"\">\n\n  <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/semantic-ui/2.2.6/semantic.min.css\">\n\n  <title>Kitetimer</title>\n  <link rel=\"stylesheet\" href=\"/css/app.css\">\n</head>\n\n<body>\n<div class=\"ui small menu\">\n<a class=\"header item\" href=\"/\">KiteTimer</a>  \n<a class=\"header item\" href=\"/\">Team Dashboard</a>    <a class=\"item\" href=\"/users/1/personal_dashboard\">Test User</a>\n    <div class=\"right menu\">\n<form action=\"/logout\" class=\"link\" method=\"post\"><input name=\"_method\" type=\"hidden\" value=\"delete\"><input name=\"_csrf_token\" type=\"hidden\" value=\"NjMaGBgjKHsSHD0WLgNMSlo2FCUCAAAAZXq7IMB9xFiFToz/7OUazw==\"><a class=\"item\" data-submit=\"parent\" href=\"#\" rel=\"nofollow\">Logout</a></form>    </div>\n</div>\n\n<div class=\"ui fluid container\">\n  <div class=\"ui two column stackable grid\">\n    <div class=\"three wide column\">\n          <div class=\"activating element\">\n    <h3 id=\"running_timer\">\n      Timer:\n        Stopped\n    </h3>\n</div>\n\n\n<div class=\"ui fluid vertical menu\">\n  <div class=\"item\">\n    <div class=\"header\" id=\"username\">\nTest User    </div>\n    <div class=\"menu\">\n<a class=\"item\" href=\"/users/1/personal_dashboard\">My Dashboard</a>      <a class=\"item\" href=\"/users/1\">Test User</a>\n<a class=\"item\" href=\"/users/1/tickets\">Tickets</a>      <a class=\"item\" href=\"/users/1/intervals\">Intervals</a>\n<a class=\"item\" href=\"/users/1/favorites\">Favourites</a>    </div>\n  </div>\n  <div class=\"item\">\n    <div class=\"menu\">\n    </div>\n  </div>\n  <div class=\"item\">\n    <div class=\"header\">Global Nav</div>\n    <div class=\"menu\">\n<a class=\"item\" href=\"/users\">Users</a>      <a class=\"item\" href=\"/tickets\">Tickets</a>\n<a class=\"item\" href=\"/favorites\">Favourites</a>      <a class=\"item\" href=\"/intervals\">Intervals</a>\n<a class=\"item\" href=\"/projects\">Projects</a>      <a class=\"item\" href=\"/tickets/new\">Import Tickets</a>\n    </div>\n  </div>\n</div>\n      \n    </div>\n    <div class=\"thirteen wide column\">\n      <p class=\"alert alert-info\" role=\"alert\"></p>\n      <p class=\"alert alert-danger\" role=\"alert\"></p>\n\n        \n\n<h2>Edit project</h2>\n\n<form accept-charset=\"UTF-8\" action=\"/projects/1\" class=\"ui form\" method=\"post\"><input name=\"_method\" type=\"hidden\" value=\"put\"><input name=\"_csrf_token\" type=\"hidden\" value=\"NjMaGBgjKHsSHD0WLgNMSlo2FCUCAAAAZXq7IMB9xFiFToz/7OUazw==\"><input name=\"_utf8\" type=\"hidden\" value=\"✓\">  \n  <div class=\"field\">\n<label for=\"project_uid\">Uid</label>    <input id=\"project_uid\" name=\"project[uid]\" type=\"text\">\n  </div>\n\n  <div class=\"field\">\n<label for=\"project_name\">Name</label>    <input id=\"project_name\" name=\"project[name]\" type=\"text\" value=\"2RK\">\n  </div>\n\n  <div class=\"field\">\n<label for=\"project_internal\">Internal</label>    <input name=\"project[internal]\" type=\"hidden\" value=\"false\"><input id=\"project_internal\" name=\"project[internal]\" type=\"checkbox\" value=\"true\">\n  </div>\n\n  <div class=\"field\">\n<label for=\"project_minimum_duration\">Minimum duration</label>    <input id=\"project_minimum_duration\" name=\"project[minimum_duration]\" type=\"number\">\n  </div>\n\n<button class=\"ui right floated positive primary button\" type=\"submit\">Submit</button></form>\n\n<a href=\"/projects\">Back</a>    </div>\n  </div>\n</div>\n<script src=\"/js/app.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/semantic-ui/2.2.6/semantic.min.js\"></script>\n</body>\n</html>\n\n",
 resp_cookies: %{},
 resp_headers: [{"content-type", "text/html; charset=utf-8"},
  {"cache-control", "max-age=0, private, must-revalidate"},
  {"x-request-id", "3vfbo56q3nnevc59upg8fbbbk8g8qlgg"},
  {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"},
  {"x-content-type-options", "nosniff"}], scheme: :http, script_name: [],
 secret_key_base: "OxKW6JNpNggIVPsHXNbwu/PzgwPJ72ZNqBYwyiQP5/Sivgx3bR1UHV39FRCqapQW",
 state: :sent, status: 200}

Missing Poison on phoenix 1.4

Compilation time

When compiling the dependencies I got the following error

==> phoenix_integration
Compiling 3 files (.ex)
warning: function Poison.decode!/1 is undefined (module Poison is not available)
Found at 2 locations:
  lib/phoenix_integration/assertions.ex:388
  lib/phoenix_integration/assertions.ex:411

running tests

Later on when doing assertions for json response I got the error shown below

** (UndefinedFunctionError) function Poison.decode!/1 is undefined (module Poison is not available)

Will this switch to using Jason or can Poison be added as a dependency of this project. Let me know of your preferred solution so I can help 😄

proposed solution that I used

Added the poison library to my mix file for use on test environment only and now things are working

Phoenix 1.5.1 deprecates `use Phoenix.ConnTest`

Just upgraded to Phoenix 1.5.1. Getting this warning

==> phoenix_integration
Compiling 10 files (.ex)
warning: Using Phoenix.ConnTest is deprecated, instead of:

    use Phoenix.ConnTest

do:

    import Plug.Conn
    import Phoenix.ConnTest

  lib/phoenix_integration/requests.ex:2: PhoenixIntegration.Requests (module)

Generated phoenix_integration app

How to handle checkboxes?

I have checkboxes that I am perhaps creating wrongly - if so, what would be the right way?

    <%= for a <- @animals do %>
       <div class="field">
          <div class="ui checkbox">
            <%= checkbox(f, :animal_ids,
                  name: "#{input_name(:animals, :chosen_animal_ids)}[#{a.id}]") %>

In manual testing, the checkboxes work as expected. In particular, the checkboxes are unchecked when the form first shows.

However, fetch_form shows both values starting as "true":

  "chosen_animal_ids" => %{"237" => "true", "238" => "true"},
  "transaction_key" => "0f10e52f-abc4-49b5-9e05-14658295742f"
}

(Perhaps this is because the "real" checkbox input comes after the hidden one?)

Further, I can't seem to change the value the form delivers. For example, suppose I do this:

       follow_form(has_animal_form,
         %{ animals:
            %{chosen_animal_ids: %{:"#{bossie.id}" => "true" }}})

What's delivered to the controller is this:

%{
  "chosen_animal_ids" => %{"239" => "true", "240" => "true"},
  "transaction_key" => "a5743aa5-f1ce-42fe-ad12-1d44e97d86e1"
}

Is there a correct way to do this?

Feature request: accept a list of strings in assert_response(html: ...)

Hello!

I want to test a blog that paginates posts, showing 5 per page. I'd like to get the post list, and check that I can find the post title of the first 10 posts, something like this:

test "Blog post pagination", %{conn: conn} do
  get(conn, "/blog")
  |> assert_response(status, 200, path: "/blog", html: "Listing recent posts")
  |> assert_response(html: "Post title 1")
  |> assert_response(html: "Post title 2")
  |> assert_response(html: "Post title 3")
  |> assert_response(html: "Post title 4")
  |> assert_response(html: "Post title 5")
end

Instead of this, it would be great if we could pass a list of strings to :html, so we could do this:

test "Blog post pagination", %{conn: conn, posts: posts} do
  get(conn, "/blog")
  |> assert_response(status, 200, path: "/blog", html: "Listing recent posts")
  |> assert_response(html: Enum.map(posts, & &1.title))
end

Or this, check for different strings explicitly:

test "Pricing plans", %{conn: conn} do
  get(conn, "/pricing")
  |> assert_response(status, 200,
    path: "/pricing",
    html: [
      "Pricing",
      "Starter plan",
      "Pro plan",
      "Enterprise plan"
    ]
  )
end

Same for refute_response.

What do you think?

Proposal: work with any plug-based app

First of all, I love this! Thanks for making it. It fills a Phoenix need that's been there for awhile.

I'd like to propose perhaps extracting this out to another package that would work with any plug-based application, similar to how capybara in Ruby land works with any Rack-based app.

A prerequisite to this would involve making a phoenix-agnostic alternative to Phoenix.ConnTest, which I've been considering creating. Perhaps this could also eventually replace Phoenix.ConnTest within Phoenix.

Do you have any thoughts on this?

Regex bug

Hello,

I'm testing against some html with a regex using Regex.compile! and there seems to be a bug where a valid regex will start to throw an error with the following error log:

** (Protocol.UndefinedError) protocol String.Chars not implemented for ~r/Team-oriented ReducedInc\s*blah/ of type Regex (a struct). This protocol is implemented for the following type(s): Phoenix.LiveComponent.CID, Postgrex.Copy, Postgrex.Query, Floki.Selector.AttributeSelector, Floki.Selector, Floki.Selector.Functional, Floki.Selector.Combinator, Floki.Selector.PseudoClass, Decimal, Float, DateTime, Time, List, Version.Requirement, Atom, Integer, Version, Date, BitString, NaiveDateTime, URI
     code: |> assert_response(
     stacktrace:
       (elixir 1.11.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
       (elixir 1.11.3) lib/string/chars.ex:22: String.Chars.to_string/1
       (phoenix_integration 0.8.2) lib/phoenix_integration/assertions.ex:437: PhoenixIntegration.Assertions.assert_body/3
       (elixir 1.11.3) lib/enum.ex:798: Enum."-each/2-lists^foreach/1-0-"/2
       (phoenix_integration 0.8.2) lib/phoenix_integration/assertions.ex:81: PhoenixIntegration.Assertions.assert_response/2

When I look at line 437 of assertions.ex I don't understand what's happening, or how it got there, or why it's in the stack trace at all. It should be going into assert_body_html, and presumably it does when the very similar regex's pass the same test. To that point, a bunch of other versions of the Regex.compile! will work, in particular: Regex.compile!("#{company_name}\\s*") will work, but the minute I add anything after the *, e.g., Regex.compile!("#{company_name}\\s*x") it breaks.

The code which works is essentially:

conn |> follow_path("/") |> assert_response(html: Regex.compile!("#{company_name}\\s*"))

The code which will result in a stacktrace (instead of a pass/fail) is essentially:

conn |> follow_path("/") |> assert_response(html: Regex.compile!("#{company_name}\\s*x"))

The one difference is the x after the *. This is valid regex, of course, and will work in iex.

Thanks :D

Radio buttons not being found as acceptable inputs

Hey, thanks for this! I like having nice fast server side integration tests.

I was trying this out for the first time today, and I ran into an issue where I'm trying to use the follow_form/3 function, but it's not seeing any of my radio buttons as acceptable inputs. The
select and text inputs work just fine - it's just the radios that are weird.

Here's what I have in my test:

    |> submit_form(%{
      user: %{
        num_students: "200",
        num_schools: "12",
        usage: "moderate",
        locale: "urban",
        contract_length: "5",
        cost_of_hardware: "5000.0",
        circuit_of_choice: "1000.0",
        installation_method: "aerial"
      }
    })

and with that I'm getting the following error:

** (RuntimeError) Attempted to set missing input in form
     Form action: /defaults
     Setting key: usage
     And value: moderate
     Into fields: %{circuit_of_choice: "1000.0", contract_length: "5", cost_of_hardware: "5000.0", num_schools: "12", num_students: "200", result_type: "Calculate Defaults"}

Here's the HTML that I have for my form:

  <form action="/ia/lease_build/defaults">
    <span class="uppercase-title">About Your School District:</span>
    <br>
    <input class="text-input" type="text" name="user[num_students]" placeholder="Number of Students" required="">
    <br>
    <input class="text-input" type="text" name="user[num_schools]" placeholder="Number of Schools" required="">
    <br>

    <span class="form-title">Broadband Usage:</span>
  <input type="radio" name="user[usage]" value="media_rich" required="">  Media Rich<br>
  <input type="radio" name="user[usage]" value="moderate" required="">  Moderate<br>
  <input type="radio" name="user[usage]" value="one_to_one" required="">  One to One<br>

    <span class="form-title">School Locale:</span>
  <input type="radio" name="user[locale]" value="rural" required="">  Rural - Distant/Remote<br>
  <input type="radio" name="user[locale]" value="town" required="">  Town<br>
  <input type="radio" name="user[locale]" value="urban" required="">  Urban/Suburban<br>

    <span class="uppercase-title">ABOUT YOUR DESIRED CIRCUIT:</span>
    <br>
    <input class="text-input" type="number" name="user[contract_length]" placeholder="Contract Length (years)" required="">
    <br>
    <input class="text-input" type="text" name="user[cost_of_hardware]" placeholder="Cost of Hardware (per Circuit)" required="">
    <br>
    <span class="form-title">Circuit of Choice (Mbps):</span>
<select id="circut_of_choice_bandwidth" name="user[circuit_of_choice]" required="required"></select>    <br>
    
    <span class="form-title">Installation Method:</span>
    <input type="radio" name="user[installation_method]" id="installation_method_underground_bored" value="underground_bored" required=""> Underground Bored<br>
    <input type="radio" name="user[installation_method]" id="installation_method_aerial" value="aerial" required=""> Aerial / Existing Conduit<br>
    <input type="radio" name="user[installation_method]" id="installation_method_underground_plowed" value="underground_plowed" required=""> Underground Plowed<br>
    <br>

    <input class="submit-button" name="user[result_type]" type="submit" value="Calculate Defaults">
    <input class="clear-form-button" type="reset" value="Clear Form">
  </form>

Hopefully this is just something I'm doing weird.

Double Follow_form raise error

login_conn = get( conn, page_path(conn, :index) ) |> follow_link("Login", %{max_redirects: 10000}) |> follow_form(%{ login: %{ login: "abc", password: "test.password"}}, %{identifier: "#login_new_user", max_redirects: 10000}) |> follow_form(%{ tweet: %{ text: "hello"}}, %{identifier: "#send_tweets", max_redirects: 10000}) assert login_conn.status == 200

It gives me the following error
** (RuntimeError) expected action/2 to return a Plug.Conn, all plugs must receive a connection (conn) and return a connection

@boydm Do you have any idea on how to solve this problem? Thank you very much.

Host lost during follow_redirect

I'm setting a host in the setup block like this:

setup do
  conn = %{build_conn() | host: "foo.bar.dev"}
  %{conn: conn}
end

However, this seems to not be respected when I call follow_redirect. Is this a known issue?

Note: in a previous version of this lib there was a method called preserve_host, which now seems gone.

File upload integration

I am currently trying to test a form that has a file upload field. Is there a way to test these form using the follow_form function ?

I tried the following code:

    upload = %Plug.Upload{content_type: "text", path: "mix.exs", filename: "mix.exs"}

    get(conn, expense_path(conn, :index) )
    |> follow_form(%{ expense: %{receipt: upload, description: "Cell"}})
    |> assert_response(status: 200, path: expense_path(conn, :index), html: "Cell")

And I got the following message:

  1) test Expense page flow (CieExpenses.ExpenseIntegrationTest)
     test/integrations/expense_integration_test.exs:4
     ** (Protocol.UndefinedError) protocol Enumerable not implemented for %Plug.Upload{content_type: "text", filename: "mix.exs", path: "mix.exs"}
     stacktrace:
       (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
       (elixir) lib/enum.ex:116: Enumerable.reduce/3
       (elixir) lib/enum.ex:1627: Enum.reduce/3
       lib/phoenix_integration/requests.ex:612: anonymous fn/2 in PhoenixIntegration.Requests.merge_grouped_fields/2
       (stdlib) lists.erl:1263: :lists.foldl/3
       lib/phoenix_integration/requests.ex:612: anonymous fn/2 in PhoenixIntegration.Requests.merge_grouped_fields/2
       (stdlib) lists.erl:1263: :lists.foldl/3
       lib/phoenix_integration/requests.ex:290: PhoenixIntegration.Requests.submit_form/3
       lib/phoenix_integration/requests.ex:340: PhoenixIntegration.Requests.follow_form/3
       test/integrations/expense_integration_test.exs:15: (test)

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.