-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add services for managing the driver processes (#2)
Rather than requiring users of the library to start their own driver processes (like chromedriver), provide the ability to start them in the library. This is largely a simplified copy of the ruby selenium library's implementation: https://github.com/SeleniumHQ/selenium/blob/e675f9bf66537930de9ebf2be2de44635821db22/rb/lib/selenium/webdriver/common/service.rb#L27 The only part that I know that I wasn't able to implement is the exit hook piece https://github.com/SeleniumHQ/selenium/blob/e675f9bf66537930de9ebf2be2de44635821db22/rb/lib/selenium/webdriver/common/service_manager.rb#L52 This is because the way the `at_exit` hook works causes problems with Spec. Spec uses an `at_exit` hook to run the tests so if this library had one that means it would always run before any of the tests ran. It will need to be very clear that the user _MUST_ call `driver.stop` in order to end the process.
- Loading branch information
Matthew McGarvey
authored
May 9, 2020
1 parent
120b240
commit 4f9c100
Showing
8 changed files
with
234 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
class Selenium::TestDriverFactory | ||
def self.build(browser) : Tuple(Driver, Capabilities) | ||
case browser | ||
when "chrome" | ||
build_chrome_driver | ||
when "firefox" | ||
build_firefox_driver | ||
when "chrome-no-service" | ||
build_chrome_driver_no_service | ||
else | ||
raise ArgumentError.new("unknown browser for running tests: #{browser}") | ||
end | ||
end | ||
|
||
def self.build_chrome_driver : Tuple(Driver, Capabilities) | ||
capabilities = Chrome::Capabilities.new | ||
capabilities.args(["no-sandbox", "headless", "disable-gpu"]) | ||
driver = Driver.for(:chrome, service: Service.chrome(driver_path: Webdrivers::Chromedriver.install)) | ||
{driver, capabilities} | ||
end | ||
|
||
def self.build_firefox_driver : Tuple(Driver, Capabilities) | ||
capabilities = Firefox::Capabilities.new | ||
capabilities.args(["-headless"]) | ||
driver = Driver.for(:firefox, service: Service.firefox(driver_path: Webdrivers::Geckodriver.install)) | ||
{driver, capabilities} | ||
end | ||
|
||
def self.build_chrome_driver_no_service : Tuple(Driver, Capabilities) | ||
capabilities = Chrome::Capabilities.new | ||
capabilities.args(["no-sandbox", "headless", "disable-gpu"]) | ||
driver = Driver.for(:chrome, base_url: "http://localhost:9515") | ||
{driver, capabilities} | ||
end | ||
|
||
private def self.chrome?(browser) | ||
browser == "chrome" | ||
end | ||
|
||
private def self.firefox?(browser) | ||
browser == "firefox" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
class Selenium::Chrome::Service < Selenium::Service | ||
def default_port : Int32 | ||
9515 | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class Selenium::Firefox::Service < Selenium::Service | ||
def default_port : Int32 | ||
4444 | ||
end | ||
|
||
def stop | ||
stop_process(process) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
abstract class Selenium::Service | ||
CONNECTION_INTERVAL = 1.seconds | ||
CONNECTION_TIMEOUT = 5.seconds | ||
|
||
def self.chrome(**opts) | ||
Chrome::Service.new(**opts) | ||
end | ||
|
||
def self.firefox(**opts) | ||
Firefox::Service.new(**opts) | ||
end | ||
|
||
private property process : Process? | ||
|
||
def initialize(@driver_path : String, @port : Int32 = default_port, @args = [] of String) | ||
end | ||
|
||
def start | ||
start_process | ||
verify_running | ||
end | ||
|
||
def stop | ||
send_shutdown_command | ||
process.try &.wait | ||
ensure | ||
stop_process(process) | ||
end | ||
|
||
def base_url : String | ||
"http://localhost:#{@port}" | ||
end | ||
|
||
abstract def default_port : Int32 | ||
|
||
private def start_process | ||
@process = Process.new( | ||
@driver_path, | ||
["--port=#{@port}"] | @args, | ||
shell: spawn_in_shell?, | ||
output: {% if flag?(:DEBUG) %} STDOUT {% else %} Process::Redirect::Close {% end %}, | ||
error: {% if flag?(:DEBUG) %} STDERR {% else %} Process::Redirect::Close {% end %} | ||
) | ||
end | ||
|
||
private def verify_running | ||
result = with_timeout { listening? } | ||
raise "Unable to connect to driver process. Try running in DEBUG mode to find more information." unless result | ||
end | ||
|
||
private def with_timeout | ||
max_time = Time.utc + CONNECTION_TIMEOUT | ||
|
||
until Time.utc > max_time | ||
return true if yield | ||
|
||
sleep CONNECTION_INTERVAL | ||
end | ||
|
||
false | ||
end | ||
|
||
private def listening? | ||
TCPSocket.new("localhost", @port).close | ||
true | ||
rescue | ||
false | ||
end | ||
|
||
private def send_shutdown_command | ||
return if @process.nil? || @process.try &.terminated? | ||
|
||
HTTP::Client.new(host: "localhost", port: @port) do |client| | ||
client.connect_timeout = 10 | ||
client.read_timeout = 10 | ||
headers = HTTP::Headers{ | ||
"Accept" => "application/json", | ||
"Content-Type" => "application/json; charset=UTF-8", | ||
"User-Agent" => "selenium/#{Selenium::VERSION} (crystal #{os})", | ||
} | ||
client.get("/shutdown", headers) | ||
end | ||
end | ||
|
||
private def stop_process(process) | ||
return if process.nil? || process.terminated? | ||
|
||
process.kill | ||
end | ||
|
||
private def spawn_in_shell? | ||
os != "linux" | ||
end | ||
|
||
private def os | ||
{% if flag?(:linux) %} | ||
"linux" | ||
{% else %} | ||
"macos" | ||
{% end %} | ||
end | ||
end |