lefthandedgoat / canopy

f# web automation and testing library, built on top of Selenium (friendly to c# also)
http://lefthandedgoat.github.io/canopy/
MIT License
505 stars 117 forks source link

(Question) How to start WebDriver using certain port #481

Closed ofk-enterprise-account closed 4 years ago

ofk-enterprise-account commented 4 years ago

You can start WebDriver (at least chromedriver) using a specified port instead of the default one by doing chromedriver --port <number>.

How can you do the same for Canopy seeing as the runner assigns a random port on starting, and I can't find any documentation to the contrary?

Thanks.

OmanF commented 4 years ago

Following (opened question using the wrong user...)

lefthandedgoat commented 4 years ago

You can start ChromeWithOptions and pass in arguments.

https://github.com/lefthandedgoat/canopy/blob/master/src/canopy/canopy.parallell.functions.fs#L836

The lines above that one show how to add arguments.

OmanF commented 4 years ago

Indeed. However, the port number to start the WebDriver isn't a Chrome option (or any specific browser for that matter), but a WebDriver option. By which I mean, adding the argument port=4444 and starting ChromeWithOptions will take the Chrome options, disregarding the port.

I think the reason is that the WebDriver is called using OpenQA.Selenium.Chrome.ChromeDriverService.CreateDefaultService("<path to the chromedriver executable on runner machine>"), which is a Selenium library which, according to its documentation, returns a WebDriverService with a random port number on purpose (to allow parallelism or what not).

I have some idea on how to go around this: let a = OpenQA.ChromeDriverService.CreateDefaultService("<path>") followed by a.Port <- 4444, but I don't know how to then use a as the driver of my tests.

I tried looking at how other testing frameworks (e.g. Nightwatch, WebdriverIO) solve this, since I know they allow you to manipulate the WebDriver port, but couldn't find.

Alternatively, you can start chromedriver with an arbitrary port by chromedriver --port 4444, but again... I don't know how to incorporate this kind of command-line call in my script.

lefthandedgoat commented 4 years ago

Does this work? it should show how to use a when starting chrome.

https://stackoverflow.com/questions/38270270/how-do-you-set-the-port-for-chromedriver-in-selenium

OmanF commented 4 years ago

The link is helpful in general on how to create customized instances of IWebDriver. The problem is all the start "branches" call new Chrome.ChromeDriver(chromeDriverService chromeDir, options) :> IWebDriver themselves... as you can see, creating their own, ad-hoc, IWebDriver instances, that, by default per Selenium's WebDriver specification starts with a random port.

The solution is, probably, to create another chromeDriverService function, that takes not only the folder of the chromedriver but also parameters for the driver itself and adds those params on top of the ones already added by the function (e.g. HostName, hideCommandPromptWindow).

Then, as a second phase, a new "branch" should be added to the start DU: ChromeWithDriverParamsAndOptions (driverParams) (chromeOptions) that calls the new chromeDriverService.

The problem, as I can see it, is that for the driver params, reflection must be used since the way to configure the driver is driver.<Param> = <value> and the params are going to be handed as a sequence of strings (no other way I see of passing the list of params...)

I'll try investigating this myself. Making no promises.

OmanF commented 4 years ago

A better approach: on the configuration create new mutable value let mutable webdriverPort: int option = None then, when calling chromeDriverService, along the default WebDriver params to customize the WebDriver instance, also add:

...
match webdriverPort with
| Some val -> service.Port <- val
| None -> ()

(As the return value of service.Port <- 4444 is unit itself).

That way, if a configuration is given for the customized webdriverPort it is merged with all the other params to customize the final WebDriver instance. If none/None (sorry, too good to pass on) is given... we do nothing (yes, we return (), but it has no effect in the given context).

In this case it is imperative that on the documentation it is made extremely clear that the value for the webdriverPort is an option type and therefore must be given as webdriverPort <- Some <val>.

lefthandedgoat commented 4 years ago

If you want, you can just write some code that starts one and use it yourself. You don't have to go to the lengths of making it work in canopy's core bits if you don't think easy enough to do.

ofk-enterprise-account commented 4 years ago

The PR was merged. Done.