-
Notifications
You must be signed in to change notification settings - Fork 29
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
Add Wallaby driver #74
Conversation
Changed to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice initiative! Just a few early comments:
f2163a9
to
a5f6d7a
Compare
lib/phoenix_test/wallaby.ex
Outdated
defp map_errors(fun) do | ||
fun.() | ||
rescue | ||
e -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer:
e in Wallaby.QueryError ->
raise ArgumentError, e.message
But that fails to match. Why? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. I would expect that to work. Is it that a different error was being raised when you tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's what happens if I add in Wallaby.QueryError
:
1) test click_link/2 raises error when there are multiple links with same text (JS) (PhoenixTest.StaticTest)
test/phoenix_test/static_test.exs:121
Expected exception ArgumentError but got Wallaby.QueryError (Expected to find 1 visible element that matched the css 'a' and contained the text 'Multiple links', but 2 visible elements were found.
)
code: assert_raise ArgumentError, fn ->
Updated:
|
Hi @ftes, thanks so much for this work! I really appreciate it. I know you've already put a lot of time and effort into this. Sorry for not responding sooner. I was hoping to get a good chunk of time to start reviewing this, since it's such a large change. But it doesn't look like I'll have the time anytime soon. Given that, I would suggest we put this on pause. I know having something that can handle Another potential idea I had was to expose the driver protocol so that other libraries could implement their own handlers -- and that would open the possibility for a Wallaby or Playwright or whatever else. So, just to share where my mind is:
I just need to carve out time to review this PR and have an idea in my head of what I'd like the JS handling to be like. Does that make sense? Of course, I'm open to other suggestions -- maybe I'm missing something here, and we should just go forward with this path. |
Thanks @germsvel :) This work is mostly done. Pretty much only documentation + I appreciate that it is a sizeable PR which demands significant time for review though. The only potential blocker I see is if you found any changes to existing code problematic.
Apart from that, I don't see why we shouldn't move forward with this for now:
|
@ftes I like what you're saying,
And
As for your two potential blockers (assertions and endpoint changes), I'll have to give those a look. Since this is so close to being done, I'll try to carve out some time to review this before going on my tour of alternatives 😄 |
@germsvel Would it help you if I broke this PR into a series/stack of smaller PRs? |
Thanks for the offer @ftes, but don't spend your time on it just yet! I've been doing a little bit of research on playwright to see if that's a realistic alternative. I know you said we could introduce this and then swap it out if we need to, but once something is in the codebase, it'll be hard to remove it. Don't want to ship breaking changes too often (even in pre 1.0 version). Just hang in there for me if you can. I would really like to handle JS somehow -- just want to do due diligence before we fully commit to Wallaby. |
Hey @germsvel how's it going? And getting the preliminary changes in would also allow us to implement an external Wallaby Driver in the current form until playwright is usable. Without having to fork |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ftes thanks again for the PR!
I haven't finished reviewing the full thing, but I figured I should at least do it in chunks when I can. There's a lot of work done here. Really appreciate it. 👍
I left a few comments and questions that I hope are helpful.
I'll continue reviewing the PR when I have more time. Let me know if you have questions in the meantime.
lib/phoenix_test/wallaby.ex
Outdated
# Set via JS to avoid locale format issues | ||
type when type in ~w(date datetime-local time week) -> | ||
js = """ | ||
el = document.querySelector('#{field.selector}'); | ||
el.value = '#{value}'; | ||
""" | ||
|
||
session.session | ||
|> Wallaby.Browser.execute_script(js) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I'm following here. Why do we do this via JS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<input type="date"
has different formats depending on the user locale.
E.g. DD.MM.YYYY
(german), MM.DD.YYYY
(US).
While the browser is usually smart enough to cope with different separates (-
, .
) it will get mixed up if the month and day are swapped.
Basically giving an incorrect input.
I've had to work around this issue before, this is the best we came up with back then.
Maybe there's a better solution.
E.g. forcing the Chrome locale to a known good value and applying a correctly formatted input string via the usual Wallaby fill_in
. But we got stuck on that route (CI was still behaving differently than local machines).
lib/phoenix_test/wallaby.ex
Outdated
|
||
defp trigger_phx_change_validations(session, query) do | ||
if has?(session.session, query) do | ||
Wallaby.Browser.send_keys(session.session, query, [:tab]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a clever way to handle this! Do you know if it works reliably? I haven't tested this myself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been using it for a while in tests.
"Works for me", but I can't speak to how stable this is.
lib/phoenix_test/wallaby.ex
Outdated
defp retry(fun, timeout_ms \\ 300, interval_ms \\ 10) do | ||
now = DateTime.to_unix(DateTime.utc_now(), :millisecond) | ||
timeout_at = DateTime.utc_now() |> DateTime.add(timeout_ms, :millisecond) |> DateTime.to_unix(:millisecond) | ||
retry(fun, now, timeout_at, interval_ms) | ||
end | ||
|
||
defp retry(fun, now, timeout_at, _interval_ms) when now >= timeout_at do | ||
fun.() | ||
end | ||
|
||
defp retry(fun, _now, timeout_at, interval_ms) do | ||
fun.() | ||
rescue | ||
AssertionError -> | ||
Process.sleep(interval_ms) | ||
now = DateTime.to_unix(DateTime.utc_now(), :millisecond) | ||
retry(fun, now, timeout_at, interval_ms) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not necessarily against this, but I wonder if this is something we need to introduce now. I think we should rely as much as possible on Wallaby's built-in waiting mechanisms that come from find
. Is there a reason why we're including this here and wrapping our assertions with retry
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two routes we can go down to implement phoenix_test
assertions for Wallaby:
- Wrap Wallaby's built-in assertions
a. (+) built-in retry/waiting - Use
PhoenixTest.Assertions
a. (+) behaviour automatically the same as other Drivers
I opted for (2.) to ensure the Wallaby Driver behaves as similarly as possible as the other Drivers.
But if we warp PhoenixTest.Assertions
we have to add our own retry around them.
Similar case:
For the mutations (fill_in
) I also used PhoenixTest.Query
instead of Wallaby.Query
to find the correct field.
Wallaby doesn't support exact-match label semantics (e.g. field label Race
vs Race 2
).
Does that make sense to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ftes makes total sense. I finally had a chance to give the implementation a good look. It's really interesting how far we can get if we just render HTML with Wallaby. That being said, I'd like to explore relying on Wallaby's built-in finders as much as possible.
I'm not asking you to do that here (though, of course, feel free to do so too), but I'm gonna create a branch based on this one and test out a few alternatives to see how that would look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One hard limitation you'll run into is that Wallaby doesn't provide "Exact match" finders for the input labels.
That will definitely break some tests (Race
vs Race 2
). Maybe worth a PR for Wallaby?
But I agree. Consistency between Drivers is nice.
But users would likely rather care more about having a feature test that simulates real user behaviour as well as possible. And using Wallaby's built-in functions where possible is probably the best way to simulate that. Instead of doing to much "HTML interpretation" ourselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But users would likely rather care more about having a feature test that simulates real user behaviour as well as possible. And using Wallaby's built-in functions where possible is probably the best way to simulate that. Instead of doing to much "HTML interpretation" ourselves.
Yeah, I agree. I also imagine Wallaby's waiting mechanism logic has a few iterations on it (so it's probably ironed out any kinks), and it's also lower level (so might be able to do things we can't). So I'd rather rely on that instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to give this a shot and hit a stumbling block:
CSS selectors vs XPath
PhoenixTest
exposes CSS selectors in the API and uses Elixir code to implement text match semantics (exact: true/false
).Wallaby
usually uses XPath to encode text match semantics
A little background on Wallaby.Query
Wallaby mostly tries to push element searching to the browser using xpath
and css
queries.
Most of the Query
functions generate xpath
queries.
Example: Query.select/2
generates an xpath query with a non-exact match on the label (also supports ID filter etc.).
Nearly all Query
functions generate non-exact xpath queries. Query.option
is the only exception. This behaviour is not configurable. However, either wallaby could be extended to support customisable exact-match semantics or we could hand-craft the xpath queries in phoenix_test.
Wallaby supports additional query options (e.g. text
) via Elixir post-processing.
This is less efficient, but flexible:
- The browser executes the base query (either
css
orxpath
). - The resulting element list is then filtered by inner text in Elixir.
The dilemma
CSS selectors and text matching don't play nicely.
CSS selectors and XPath can't be combined (although there are some attempts to transpile CSS selectors to XPath, I don't know if CSS selectors are a subset of XPath though).
It's difficult to get the best of both worlds: Expose CSS selectors to PhoenixTest users and use as much vanilla Wallaby as possible. Vanilla Wallaby relies mostly on XPath.
Where to go from here
Currently, the Wallaby driver always fetches the full HTML from the browser to do anything. That's slow.
Instead, we could at least push CSS queries to the browser where possible and filter the resulting element list via our own Elixir code (customisable exact-match semantics).
That could be a sweet spot of performance, consistency with the other drivers, and little need for changes to Wallaby itself.
I think this question might also have to be answered for Playwright.
Also, the discussion might inform what the PhoenixTest API should expose:
- generic
locator
s that can be "transpiled" to whatever works best for the driver - CSS selectors (status quo)
From a quick glance, Playwright mostly exposes generic locator
s but does offer full control via css or xpath if need be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not an issue for Playwright after all. After playing with it a bit (#136) I've seen that Playwright:
- allows mixing different selector "engines" in locators (e.g. CSS + XPath)
- has in-built selectors for
label
andtext
that support exact and non-exact matching, very similar to what PhoenixTest does - so we can get the best of both worlds: expose CSS selectors to users and combine them with efficient text selectors, executed with retry right in the browser
lib/phoenix_test/wallaby.ex
Outdated
defp map_errors(fun) do | ||
fun.() | ||
rescue | ||
e -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. I would expect that to work. Is it that a different error was being raised when you tested?
@ftes just saw your comment after I reviewed the PR 😆 You mentioned:
Having reviewed the parts that I reviewed, I do think there could be some nice splits (if you're interested in doing them). Moving the assertions into the We could also add the I don't want you to do more work if you don't feel like it, but if you want to do that, that'd be a great way to introduce changes in pieces. (always easier to review too 😄 ) |
e3f3557
to
404ba2f
Compare
216294c
to
dc6f75f
Compare
lib/phoenix_test/wallaby.ex
Outdated
@@ -0,0 +1,259 @@ | |||
defmodule PhoenixTest.Wallaby do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want to wrap most of this module in Code.ensure_loaded? to check if Wallaby is included.
We shouldn't make Wallaby a required dependency in case some people don't want to use it. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! We should definitely not force Wallaby on users.
Did you gain any further insights by looking at Playwright, @germsvel ? |
@ftes yes! Great question. Here are my thoughts so far. No particular order, and take them with a grain of salt. They're all subject to change as we discover more:
I know your PR seemed really close to getting Wallaby in here. And I'm sorry we haven't merged it in yet. Thanks for the patience! I just want to land at some good API before we fully introduce it. |
That seems sensible.
I'd say: It depends on what the goal of offering alternative drivers is.
Personally, I'd vote for plug&play. For me this would be the most common requirement.
I think this only adds support for |
That wouldn't be an issue if we use the approach in this PR: implement auto awaiting in |
otp_app: :phoenix_test, | ||
js_logger: nil, | ||
chromedriver: [ | ||
headless: System.get_env("WALLABY_HEADLESS", "t") in ["t", "true"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should go in runtime.exs
@@ -17,7 +18,7 @@ defmodule PhoenixTest.Link do | |||
raw: link_html, | |||
parsed: link, | |||
id: id, | |||
selector: selector, | |||
selector: Element.build_selector(link), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a required change. Just for consistency with the other modules.
Closing in favour of #145 |
Fixes #73
Done
Endpoint
to proper/regular endpoint)Assertions
intoDriver
protocol (wrap with retry for Wallaby)test_also_with_js
macroTo do
Wallaby
, use via@tag :js
conn
passed tovisit/2
is mostly ignored (not supported:conn.assigns
, supported:conn.req_cookies
)NoRouteError
tests (broken due to changes toEndpoint
)open_browser
value/selected/checked
attributes)conn.req_cookies
viaWallaby.Browser.set_cookie/4