Closed yashaka closed 3 years ago
@wjgerritsen-0001, let's discuss it here:)
Agree, extension JS method is better. But there should be a way to get access to the IWebDriver and the ActualWebElement. Maybe provide a generic method to execute javascipt, which passes the element as argument[0]. Then build extensions using this.
I will provide a proposal
I propose to use same approach as we already implemented with @alex-popov-tech in selenidejs: The idea is in addition to classic way of calling executeScript on driver, provide the SeleneElement's own version of executeScript, that will be executed on the element itself, already passed as argument to the script, so we can build the script in a more concise way ;)
What do you think about it?
selenidejs implementation inside element class: https://github.com/KnowledgeExpert/selenidejs/blob/master/lib/element.ts#L101
example of usage for methods like "scroll intro view by js":
https://github.com/KnowledgeExpert/selenidejs/blob/master/lib/commands.ts
(here it is implemented more of S("#foo").Perform(Command.Js.Click())
style, but probably in C# with its extension methods support - the latter would be easier and more convenient, especially because this gives an option for users to use them only as examples, copy&paste to their project and tune to their personal needs...)
The thing also is that selenidejs from js world, selene from python and nselene from .net - are all similar styled group of libraries, if we stay more or less consistent with them - it will be easier to support these opensource projects, by sharing same ideas, same approaches, etc. Of course also keeping the consistency with C# common style conventions and guidelines
Ok, that would become something like below in SeleneElement
public T ExecuteScript<T>(string script, params object[] args)
{
IJavaScriptExecutor js = (IJavaScriptExecutor)this.driver.Value;
return (T)js.ExecuteScript($@"
return (function(element, args) {{
{script}
}})(arguments[0], arguments[1])", new object[] { this.ActualWebElement, args });
}
In the extensions class:
public static SeleneElement JSScrollIntoView(this SeleneElement selement)
{
selement.ExecuteScript("element.scrollIntoView({ behavior: 'smooth', block: 'center'})");
return selement;
}
Not yet complete to the S(''div'').Perform(JS.ScrollIntoView)
proposal, Also passing params and returning values is unclear to me. How to solve that.
Any suggestions?
PS the above approach is currently in the PR
Not yet complete to the S(''div'').Perform(JS.ScrollIntoView) proposal
If we go the "Extension Methods" way, then we don't need such style, at least we don't need to rush with it...
Also passing params and returning values is unclear to me
It would be possible either by using C# delegates, or the Command pattern. The Command pattern is probably the preferable way because of some limitations in using C# delegates... But I may be wrong, maybe things changed in the latest C# versions...
Nevertheless, let's keep it out of board for now, and come back to this later. In Python and Js such style was needed also to provide a proper way to extend SeleneElement with custom commands, but in C# we have extension methods... So maybe we don't even need this style, let's see...
Let's just stick to the args
, now we can just copy code from selenide ;-)
No real preference. We can do either way.
Agree, with Extension methods is is really intuitive already.
Good, good, let me finish reviewing your PR...
The #49 was reviewed, impoved and merged...
One more open point:
Should we move Js* extensions to a separate namespace for more granular extensions usage, like: using Nselene.Support.Extensions.Js;
?
Currently implemented as Extension methods in its own separate NSelene.Support.SeleneElementJsExtension
namespace.
Also can be used semi-implicitly, like this:
S("#submit").With(clickByJs: true).Click()
That is also more handy, because allows to put S("#submit").With(clickByJs: true)
into var and then call all following clicks already by js;)
P.S. Maybe one day we yet consider adding something like S("#foo").Perform(Command.Js.Click())
, as described in https://github.com/yashaka/NSelene/issues/50#issuecomment-648276248 ... But for now let's keep things simple...
the #49 PR came, and I decided to bring this topic up... while was thinking on it for a while...
In #49 the method was added to SeleneElement:
The first thing to mention: should we add a
Js
prefix to this kind of method... So it will be explicitly visible in tests that we are doing some "magical tricks"...actually we can add a lot of other similar methods, like:
.JsClick() .JsSetValue(val) .Js...
but another thing that I am thinking... Is it good to include such type of "trick-style" methods into SeleneElement?
Wouldn't it be better to move them into extensions? So people can use them if they need them, and in case they need a bit tuned version of such tricks, they can just copy&paste them into their own base of extension methods, and tune according to their needs...
Or maybe implement it in the way it was implemented in selenidejs and selene from python, something like:
In this way we can collect all "js workarounds/additions" in a separate space, and not make the "big heap to be lost inside" from SeleneElement...
From other point of view, if somebody needs something, he can faster find it in the "one big heap"...
Heh, thinking on all these options... Trying to find the best solution:)
Let's discuss:)