crystal-loot / selenium.cr

Selenium library for Crystal
https://crystal-loot.github.io/selenium.cr/
MIT License
23 stars 7 forks source link

Selenium::Status return wrong ready status when use with Firefox. #47

Open zw963 opened 3 months ago

zw963 commented 3 months ago

Following is reproduce:

firefox.cr ```crystal require "selenium" def ready?(driver) pp! driver.status driver.status.ready? rescue Socket::ConnectError false end driver = Selenium::Driver.for(:firefox, base_url: "http://localhost:4444") if !ready?(driver) service = Selenium::Service.firefox(driver_path: "/usr/bin/geckodriver") driver = Selenium::Driver.for(:firefox, service: service) end options = Selenium::Firefox::Capabilities::FirefoxOptions.new options.args = ["--headless"] capabilities = Selenium::Firefox::Capabilities.new capabilities.firefox_options = options session = driver.create_session(capabilities) session.navigate_to("https://bing.com") ```
 ╰─ $ cr build firefox.cr

# When run the first time
 ╰─ $ ./firefox
driver.status # =>

# When run the second time
 ╰─ $ ./firefox
driver.status # => #<Selenium::Status:0x7f0304cb2ae0
 @message="Session already started",
 @ready=false>

When can saw the geckodriver process start twice on different port. (this is not expected)

 ╰─ $ ps aux |grep gecko
zw963      90790  0.0  0.0 145188  7424 pts/2    Sl   11:59   0:00 /usr/bin/geckodriver --port=4444
zw963      91280  0.0  0.0 145188  7328 pts/2    Sl   11:59   0:00 /usr/bin/geckodriver --port=4445

but, when use with chromedriver, it work different.

chrome.cr ```crystal require "selenium" def ready?(driver) pp! driver.status driver.status.ready? rescue Socket::ConnectError false end driver = Selenium::Driver.for(:chrome, base_url: "http://localhost:9515") if !ready?(driver) service = Selenium::Service.chrome(driver_path: "/usr/bin/chromedriver") driver = Selenium::Driver.for(:chrome, service: service) end options = Selenium::Chrome::Capabilities::ChromeOptions.new options.args = ["--headless"] capabilities = Selenium::Chrome::Capabilities.new capabilities.chrome_options = options session = driver.create_session(capabilities) session.navigate_to("https://bing.com") ```
 ╰─ $ cr build chrome.cr

# When run the first time
 ╰─ $ ./chrome
driver.status # =>

# When run the second time
 ╰─ $ ./chrome
driver.status # => #<Selenium::Status:0x7fe03231bae0
 @message="ChromeDriver ready for new sessions.",
 @ready=true>

chromedriver process only started once, it reuse exists port, this is expected, right?

 ╰─ $ 130  ps aux |grep chromedriver
zw963      96442  0.0  0.0 33649984 15716 pts/2  Sl   12:07   0:00 /usr/bin/chromedriver --port=9515

The above different is:

╰─ $ ./firefox driver.status # => #<Selenium::Status:0x7f0304cb2ae0 @message="Session already started", @ready=false>

╰─ $ ./chrome driver.status # => #<Selenium::Status:0x7fe03231bae0 @message="ChromeDriver ready for new sessions.", @ready=true>

Which one is correct?

Thanks

zw963 commented 3 months ago

This brings another side effect.

# Use code above

session1 = driver.create_session(capabilities)

session1.navigate_to("https://bing.com")

session2 = driver.create_session(capabilities)

session1.navigate_to("https://bing.com")

Create two session use same chromedriver work as expected, no error.

But, it raise exception when use with gecodriver.

 ╰─ $ ./firefox
driver.status # => #<Selenium::Status:0x7f0213773ac0
 @message="Session already started",
 @ready=false>
Unhandled exception: session not created: Session is already started (Selenium::Error)
  from lib/selenium/src/selenium/http_client.cr:13:7 in 'post'
  from lib/selenium/src/selenium/command_handler.cr:22:7 in 'execute'
  from lib/selenium/src/selenium/command_handler.cr:12:5 in 'execute:parameters'
  from lib/selenium/src/selenium/driver.cr:43:12 in 'create_session'
  from lib/selenium/src/selenium/driver.cr:53:7 in 'create_session'
  from lib/selenium/src/selenium/driver.cr:41:3 in 'create_session'
  from lib/selenium/src/selenium/firefox/driver.cr:3:5 in 'create_session'
  from firefox.cr:26:12 in '__crystal_main'
  from /home/zw963/Crystal/share/crystal/src/crystal/main.cr:129:5 in 'main_user_code'
  from /home/zw963/Crystal/share/crystal/src/crystal/main.cr:115:7 in 'main'
  from /home/zw963/Crystal/share/crystal/src/crystal/main.cr:141:3 in 'main'
  from /usr/lib/libc.so.6 in '??'
  from /usr/lib/libc.so.6 in '__libc_start_main'
  from ./firefox in '_start'
  from ???
zw963 commented 3 months ago

Updated:

I found firefox driver probably return correct status occasionally, but with a empty message.

driver.status # => #<Selenium::Status:0x7f34c55b3b80 @message="", @ready=true>

So, define a method like this seem like enough:

def self.firefox_session_ready?(driver)
    driver.status.message == "Session already started" || driver.status.ready?
  rescue Socket::ConnectError
    false
  end
zw963 commented 3 months ago

Hi, i create a PR #48 for serialize/unserialize Selenium::Session, which utilize the behavior of firefox driver which described on this issue.

Anyone, i consider one of them is incorrect, just leave this issue open for now.