Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental selenium driver #198

Merged
merged 9 commits into from
May 8, 2017

Conversation

aaronrenner
Copy link
Member

This is a work in progress to add selenium support and allows the existing test suite to be run against a local instance of geckodriver or chromedriver. This is definitely a work in progress, but will show us how the existing test suite runs against different webdriver backends.

base_url = Keyword.get(opts, :remote_url, "http://localhost:4444/wd/hub/")
capabilities = Keyword.get(opts, :capabilities, %{})
create_session_fn = Keyword.get(opts, :create_session_fn,
&Wallaby.Phantom.Driver.create_session/2)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still calling Wallaby.Phantom.Driver.create_session to make the http request to selenium to start the session. Do you think all of the webdriver http requests should live in a common module like in #195, or would you prefer if each driver implements its own http requests?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally they could live in their own module but for now lets separate them. Once we have support for 3 drivers (phantomjs, selenium, chromedriver, etc.) then we can see if they share enough functionality to merge the driver implementations.

@aaronrenner aaronrenner force-pushed the ar-selenium branch 2 times, most recently from 18d83a8 to 1e67d27 Compare April 14, 2017 20:05
@aaronrenner
Copy link
Member Author

I'd also be interested in how you'd like to see the driver modules designed. Should I be writing each one of the browser interaction functions (like visit, click, etc) in each module to make them easier to customize behavior per driver? Since chromedriver is different than geckodriver and phantomjs (and Appium for that matter), should I have one driver per backend? If so, do you think each module should do something like use Wallaby.WebDriver.Driver to share common boilerplate and then override functions when we need to change something? I'm not sure if using use Wallaby.WebDriver.Driver to share boilerplate functionality would make things to confusing.

I'm looking forward to hearing your ideas so I can build it right the first time.

base_url = Keyword.get(opts, :remote_url, "http://localhost:4444/wd/hub/")
capabilities = Keyword.get(opts, :capabilities, %{})
create_session_fn = Keyword.get(opts, :create_session_fn,
&Wallaby.Phantom.Driver.create_session/2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally they could live in their own module but for now lets separate them. Once we have support for 3 drivers (phantomjs, selenium, chromedriver, etc.) then we can see if they share enough functionality to merge the driver implementations.

screenshots: list
server: pid | nil,
screenshots: list,
driver: module
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a big fan of this idea. This gives us a lot of flexibility for running different browsers against each other.

@@ -8,7 +8,19 @@ defmodule Wallaby.SessionCase do
end

setup do
{:ok, session} = Wallaby.start_session
session_opts = case System.get_env("WEBDRIVER") do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize that this is just for testing so it doesn't matter right now, but if we decide to stick with an implementation like this we should probably prefix the env variable with WALLABY_. Totally not important right now though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just changed the variable name to WALLABY_WEBDRIVER.

@keathley
Copy link
Member

I think for now we shouldn't try to share functionality between the different driver implementations. Once we have multiple implementations we can see exactly where the common code / patterns are and we can use shared code at that point. Its easier to remove common code once you can see all of it.

For now it would be a good idea to share a common api between the drivers. We should create a behaviour that each driver needs to conform to. If you want I can work on that tonight. Then we can use that as a basis for the other drivers.

@aaronrenner
Copy link
Member Author

I was thinking all of the drivers would share the following functions:

create_session/2
delete_session/1
find_elements/2
set_value/2
clear/1
click/1
text/1
page_title/1
attribute/1
visit/2
current_url/1
current_url!/1
current_path!1/1
selected/1
displayed/1
displayed!/1
size/1
rect/1
take_screenshot/1
cookies/1
set_cookies/3
set_windows_size/3
get_windows_size/1
execute_script/3
send_keys/2
log/1
accept_dialogs/1
dismiss_dialogs/1
accept_alert/2
accept_confirm/2
dismiss_confirm/2
accept_prompt/3
dismiss_prompt/2

If a driver didn't support one of these functions, we could return {:error, :unsupported_command} or raise an exception. Is that what you were thinking?

@keathley
Copy link
Member

keathley commented Apr 17, 2017

That looks about right. I think we can get rid of the ! functions (current_url!, current_path!, displayed!). Once #95 lands (which I think @krankin is working on) we can remove logs from that list as well.

@aaronrenner
Copy link
Member Author

I just checked in the Wallaby.Driver behavior and switched Wallaby.Element and Wallaby.Browser to get the current driver off the session in #201. I think this will make adding a Selenium driver much easier.

@aaronrenner aaronrenner force-pushed the ar-selenium branch 8 times, most recently from 9b8582f to bc1c5bf Compare April 27, 2017 21:13
@aaronrenner aaronrenner changed the title WIP - Experimental selenium driver Experimental selenium driver Apr 27, 2017
@aaronrenner
Copy link
Member Author

I finally got the tests passing and this is code should be ready for a review. 🎉

There's quite a bit of changes here, so it might be worth reviewing commit by commit. Here are the highlights.

  1. First and foremost I added Wallaby.Experimental.Selenium. It has most of the basic test cases working but there are a few things I haven't had time to port over from phantomjs. Those are dialogs, getting the file upload tests to work with selenium and screenshots (I don't think selenium supports them).
  2. I set up the build to run the unit tests, and then integration tests for phantom, selenium 2, and selenium 3. It took some tinkering in start_webdriver.sh but I think I have a good build environment now. Here's the build matrix on Travis.
    screen shot 2017-04-27 at 3 37 02 pm
  3. I've gone through and fixed several issues with integration tests that have come up since I started running them against Selenium. The biggest issue was selenium doesn't wait until the page loads (after visit, clicking a link, or clicking a button) before it runs the next command. That was giving us a lot of failures when asserting with the current_url function. The way I solved this was to create a couple page objects with ensure_page_loaded/1 functions that use find/2 to wait until the page has loaded successfully before continuing. I'm sure we can expand the page object paradigm if we want, but this seemed like an ok way to get the tests working.

There's definitely still more to do on this driver (like handling weird responses from BrowserStack), but I am hoping to get this code merged into master so the PR doesn't get huge as I fix more and more issues. I set it to @moduledoc false and it's under the Wallaby.Experimental namespace, so people should realize that it's not ready for primetime, yet. Please let me know if you'd like any changes and I'd love to get it merged as soon as we can.

@keathley
Copy link
Member

@aaronrenner Regarding the issue with selenium waiting for page loads we may need to look at this: https://www.w3.org/TR/webdriver/#navigation

Copy link
Member

@keathley keathley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! Just a few small comments. I'll pull down the branch and play with it tonight. I can merge it after that.

@@ -1,6 +1,8 @@
defmodule Wallaby.Integration.Browser.Actions.ClickButtonTest do
use Wallaby.Integration.SessionCase, async: true

alias Wallaby.Integration.PageObjects.IndexPage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small note but it might be misleading to call these Objects. I just call them Pages typically.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seconded! :)

|> click_link("Page 1")
|> IndexPage.visit()
|> IndexPage.click_page_1_link()
|> Page1.ensure_page_loaded()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love seeing all of these! Using best practices in our own tests is awesome!


def click_page_1_link(session) do
session
|> click_link("Page 1")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to update this to use click(link("Page 1")).


@spec start_session([start_session_opts]) :: {:ok, Session.t}
def start_session(opts \\ []) do
base_url = Keyword.get(opts, :remote_url, "http://localhost:4444/wd/hub/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this option will need to change to something like :selenium_remote_url once we add chromedriver support as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd start a session by calling:

Wallaby.start_session(driver: Wallaby.Experimental.Selenium, remote_url: "http://localhost:4444/wd/hub")

The :driver option determines which module to call start_session/1 on and the rest of the options are passed through directly to the driver. That way chromedriver could look like this:

Wallaby.start_session(driver: Wallaby.Experimental.ChromeDriver, remote_url: "http://localhost:4444/wd/hub")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense.

WebdriverClient.send_keys(parent, keys)
end

# Unsupported
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this.

@@ -44,7 +44,7 @@ defmodule Wallaby.Session do
id: String.t,
session_url: String.t,
url: String.t,
server: pid(),
server: pid | nil,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a super small nitpick but could we use an atom here like :empty or :none or maybe even :selenium? Just to signify that we don't actually use the server key for certain drivers.

@aaronrenner
Copy link
Member Author

aaronrenner commented May 2, 2017

@keathley I looked into the webdriver pageLoadStrategy setting that you mentioned in your link, but I am still getting issues with tests to check the current page using the currentUrl function. I was thinking we could add a function like IndexPage.loaded?/1that would find a unique element on the page to ensure the expected page has loaded. Maybe that's something that could be done as part of removing the deprecated Browser.click_button work.

Please let me know if there's anything else you'd like to see on this PR. I think it should be good to go after #209 is merged.

aaronrenner added a commit to aaronrenner/wallaby that referenced this pull request May 2, 2017
@keathley keathley mentioned this pull request May 3, 2017
aaronrenner added a commit to aaronrenner/wallaby that referenced this pull request May 3, 2017
I have to return %{“value” => nil} because everywhere uses 
Map.fetch(body, “value”) and if I didn’t have a value key in the map, 
this would errors. This API really needs to be changed.
I added page objects so I have somewhere to add checking to see if the
page is loaded yet. With selenium, when we send a visit command, it
doesn't wait until the page is completely loaded to run the next
command.
If the screen height was less then 1234 pixels, the test would fail.
This uses a much smaller screen height.
There were a couple issues with click_button test.

1. Selenium wasn't waiting until the page finished loading to get the
   current url, so these tests were failing
2. When the form was submitted it sent a POST request to the test
   server, which resulted in a 500 error. I updated it to send a get
   request, but then had to update the current url assertion since the
   form data was being included as url parameters.
The older version of Firefox injects the xmlns attribute in the html
tag. We could probably improve this test if it continues to be an issue.
Also fixed an instance where I was using a deprecated api
I have been having issues on travis ci where selenium times out when
starting a session. This should take care of it.
@aaronrenner
Copy link
Member Author

I think I finally took care of the flake tests and it's ready to merge. I added some retry logic to SessionCase.start_test_session and made it so Selenium.start_session can better handle error tuples. Let me know if you see anything else I missed.

@PragTob
Copy link
Collaborator

PragTob commented May 3, 2017

I was sort of thinking about giving this another look but honestly 1000 lines is a bit more than my casual evening read :D

If you're both 👍 on it then I'm in as well, otherwise I might get to look at it on the weekend but no promises. :)

@aaronrenner
Copy link
Member Author

Sorry it's such a big PR. I'll make my next one much smaller. 😄

@keathley
Copy link
Member

keathley commented May 8, 2017

Ran through it one last time. Looks good to me 👍

@aaronrenner Do you feel good about merging this now?

@keathley keathley merged commit c4a5754 into elixir-wallaby:master May 8, 2017
PragTob added a commit that referenced this pull request May 11, 2017
* Was accidentally removed as part of #198
keathley pushed a commit that referenced this pull request May 11, 2017
* Was accidentally removed as part of #198
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants