diff --git a/.gitignore b/.gitignore index 7f25924..6e9aaca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.crystal /.shards +/shard.lock /lib diff --git a/shard.lock b/shard.lock index 4eab3f7..5f4ae93 100644 --- a/shard.lock +++ b/shard.lock @@ -2,5 +2,5 @@ version: 1.0 shards: minitest: github: ysbaddaden/minitest.cr - version: 0.3.6 + version: 0.4.0 diff --git a/src/webdriver.cr b/src/webdriver.cr index 2436a4a..64e1ae4 100644 --- a/src/webdriver.cr +++ b/src/webdriver.cr @@ -16,7 +16,7 @@ module Selenium end def get(path) - headers = HTTP::Headers{ "Accept" => "application/json" } + headers = HTTP::Headers{"Accept" => "application/json"} {% if flag?(:DEBUG) %} puts "REQUEST: GET #{path}" @@ -27,13 +27,13 @@ module Selenium {% if flag?(:DEBUG) %} puts "RESPONSE: #{response.status_code}" - p JSON.parse(response.body).raw + p JSON.parse(response.body) puts {% end %} case response.status_code when 200 - JSON.parse(response.body).raw.as(Hash) + JSON.parse(response.body) else failure(response) end @@ -46,7 +46,7 @@ module Selenium {% end %} if body - headers = HTTP::Headers{ "Content-Type" => "application/json; charset=UTF-8" } + headers = HTTP::Headers{"Content-Type" => "application/json; charset=UTF-8"} response = @client.post("#{@path}#{path}", headers, body.to_json) else response = @client.post("#{@path}#{path}") @@ -54,13 +54,13 @@ module Selenium {% if flag?(:DEBUG) %} puts "RESPONSE: #{response.status_code}" - p JSON.parse(response.body).raw + p JSON.parse(response.body) puts {% end %} case response.status_code when 200 - JSON.parse(response.body).raw.as(Hash) + JSON.parse(response.body) else failure(response) end @@ -75,7 +75,7 @@ module Selenium {% if flag?(:DEBUG) %} puts "RESPONSE: #{response.status_code}" - p JSON.parse(response.body).raw + p JSON.parse(response.body) {% end %} raise Error.new(response.body) unless response.status_code == 200 @@ -84,10 +84,9 @@ module Selenium private def failure(response) if response.headers["Content-Type"].starts_with?("application/json") - body = JSON.parse(response.body).raw.as(Hash) - status = body["status"].as(Int) - value = body["value"].as(Hash) - raise Selenium.error_class(status).new(value["message"].as(String)) + body = JSON.parse(response.body) + status = body["status"].as_i + raise Selenium.error_class(status).new(body["value"]["message"].as_s) end raise Error.new(response.body) end diff --git a/src/webdriver/alert.cr b/src/webdriver/alert.cr index b5bb73e..d0e53f7 100644 --- a/src/webdriver/alert.cr +++ b/src/webdriver/alert.cr @@ -10,7 +10,7 @@ module Selenium end def send_keys(sequence : String) - session.post("/alert_text", { value: sequence }) + session.post("/alert_text", {value: sequence}) end def accept diff --git a/src/webdriver/errors.cr b/src/webdriver/errors.cr index e975f46..4220f22 100644 --- a/src/webdriver/errors.cr +++ b/src/webdriver/errors.cr @@ -1,37 +1,61 @@ module Selenium class Error < Exception; end + class NoSuchDriver < Error; end + class NoSuchElement < Error; end + class NoSuchFrame < Error; end + class UnknownCommand < Error; end + class StaleElementReference < Error; end + class ElementNotVisible < Error; end + class InvalidElementState < Error; end + class UnknownError < Error; end + class ElementIsNotSelectable < Error; end + class JavaScriptError < Error; end + class XPathLookupError < Error; end + class Timeout < Error; end + class NoSuchWindow < Error; end + class InvalidCookieDomain < Error; end + class UnableToSetCookie < Error; end + class UnexpectedAlertOpen < Error; end + class NoAlertOpenError < Error; end + class ScriptTimeout < Error; end + class InvalidElementCoordinates < Error; end + class IMENotAvailable < Error; end + class IMEEngineActivationFailed < Error; end + class InvalidSelector < Error; end + class SessionNotCreatedException < Error; end + class MoveTargetOutOfBounds < Error; end # :nodoc: protected def self.error_class(status) case status - when 6 then NoSuchDriver - when 7 then NoSuchElement - when 8 then NoSuchFrame - when 9 then UnknownCommand + when 6 then NoSuchDriver + when 7 then NoSuchElement + when 8 then NoSuchFrame + when 9 then UnknownCommand when 10 then StaleElementReference when 11 then ElementNotVisible when 12 then InvalidElementState diff --git a/src/webdriver/session.cr b/src/webdriver/session.cr index 6f7d344..e688acb 100644 --- a/src/webdriver/session.cr +++ b/src/webdriver/session.cr @@ -20,17 +20,17 @@ module Selenium getter driver : Webdriver getter! id : String - getter! capabilities : Hash(String, JSON::Type) + getter! capabilities : Hash(String, JSON::Any) def initialize(@driver, desired_capabilities = Webdriver::CAPABILITIES, required_capabilities = Webdriver::CAPABILITIES, url = "about:blank") body = { - "desiredCapabilities" => desired_capabilities, + "desiredCapabilities" => desired_capabilities, "requiredCapabilities" => required_capabilities, } response = driver.post("/session", body) - @id = response["sessionId"].as(String) - @capabilities = response["value"].as(Hash) + @id = response["sessionId"].as_s + @capabilities = response["value"].as_h if url self.url = url @@ -54,11 +54,11 @@ module Selenium end def url - get("/url").as(String) + get("/url").as_s end def url=(url) - post("/url", { url: url }) + post("/url", {url: url}) end def forward @@ -82,15 +82,15 @@ module Selenium end def execute(script, *args) - post("/execute", { script: script, args: args }) + post("/execute", {script: script, args: args}) end def execute_async(script, *args) - post("/execute", { script: script, args: args }) + post("/execute", {script: script, args: args}) end def frame(identifier) - post("/frame", { id: identifier }) + post("/frame", {id: identifier}) end def parent_frame @@ -103,31 +103,31 @@ module Selenium end def save_screenshot(path) - data = get("/screenshot").as(String) + data = get("/screenshot").as_s File.open(path, "w") { |file| Base64.decode(data, file) } end def find_element(by, selector, parent : WebElement? = nil) - url = parent ? "/element/#{ parent.id }/element" : "/element" + url = parent ? "/element/#{parent.id}/element" : "/element" value = post(url, { using: WebElement.locator_for(by), - value: selector + value: selector, }) - WebElement.new(self, value.as(Hash)) + WebElement.new(self, value.as_h) end def find_elements(by, selector, parent : WebElement? = nil) - url = parent ? "/element/#{ parent.id }/elements" : "/elements" + url = parent ? "/element/#{parent.id}/elements" : "/elements" value = post(url, { using: WebElement.locator_for(by), - value: selector - }).as(Array) - value.map { |item| WebElement.new(self, item.as(Hash)) } + value: selector, + }).as_a + value.map { |item| WebElement.new(self, item.as_h) } end def active_element value = post("/element/active") - WebElement.new(self, value.as(Hash)) + WebElement.new(self, value.as_h) end def orientation @@ -136,7 +136,7 @@ module Selenium def orientation=(value) raise ArgumentError.new unless %i(portrait landscape).includes?(value) - post("/orientation", { orientation: value.to_s.upcase }) + post("/orientation", {orientation: value.to_s.upcase}) end def alert @@ -152,33 +152,33 @@ module Selenium end def click(button : MouseButton = MouseButton::LEFT) - post("/click", { button: button.value }) + post("/click", {button: button.value}) end def double_click(button : MouseButton = MouseButton::Left) - post("/doubleclick", { button: button.value }) + post("/doubleclick", {button: button.value}) end def button_down(button : MouseButton = MouseButton::Left) - post("/buttondown", { button: button.value }) + post("/buttondown", {button: button.value}) end def button_up(button : MouseButton = MouseButton::Left) - post("/buttonup", { button: button.value }) + post("/buttonup", {button: button.value}) end protected def get(path = "") - response = driver.get("/session/#{ id }#{ path }") + response = driver.get("/session/#{id}#{path}") response["value"] end protected def post(path, body = nil) - response = driver.post("/session/#{ id }#{ path }", body) + response = driver.post("/session/#{id}#{path}", body) response["value"] end protected def delete(path = "") - driver.delete("/session/#{ id }#{ path }") + driver.delete("/session/#{id}#{path}") end end end diff --git a/src/webdriver/session/cookies.cr b/src/webdriver/session/cookies.cr index c19d309..358a8d6 100644 --- a/src/webdriver/session/cookies.cr +++ b/src/webdriver/session/cookies.cr @@ -1,38 +1,40 @@ module Selenium class Session struct Cookie + @json : JSON::Any + def initialize(json) - @json = json.as(Hash(String, JSON::Type)) + @json = json end def name - @json["name"].as(String) + @json["name"].as_s end def value - @json["value"].as(String) + @json["value"].as_s end def domain - @json["domain"].as(String) + @json["domain"].as_s end def path - @json["path"].as(String) + @json["path"].as_s end def expiry if timestamp = @json["expiry"]? - Time.epoch(timestamp.as(Int64)) + Time.epoch(timestamp.as_i64) end end def http_only - @json["httpOnly"].as(Bool) + @json["httpOnly"].as_bool end def secure - @json["secure"].as(Bool) + @json["secure"].as_bool end end @@ -50,7 +52,7 @@ module Selenium def each(&block) @session.get("/cookie") - .as(Array) + .as_a .each { |json| yield Cookie.new(json) } end @@ -60,19 +62,19 @@ module Selenium def set(name, value, domain = nil, path = "/", http_only = false, secure = false) cookie = { - "name" => name, - "value" => value, - "path" => path, + "name" => name, + "value" => value, + "path" => path, "httpOnly" => http_only, - "secure" => secure + "secure" => secure, } cookie["domain"] = domain if domain - @session.post("/cookie", { cookie: cookie }) + @session.post("/cookie", {cookie: cookie}) end def to_a @session.get("/cookie") - .as(Array) + .as_a .map { |json| Cookie.new(json) } end @@ -81,13 +83,13 @@ module Selenium expiry = Time.epoch(timestamp.as(Int64)) end { - name: json["name"].as(String), - value: json["value"].as(String), - domain: json["domain"].as(String), - path: json["path"].as(String), - expiry: expiry, + name: json["name"].as(String), + value: json["value"].as(String), + domain: json["domain"].as(String), + path: json["path"].as(String), + expiry: expiry, http_only: json["httpOnly"].as(Bool), - secure: json["secure"].as(Bool), + secure: json["secure"].as(Bool), } end end diff --git a/src/webdriver/web_element.cr b/src/webdriver/web_element.cr index f26ba5e..9e2e9e7 100644 --- a/src/webdriver/web_element.cr +++ b/src/webdriver/web_element.cr @@ -1,24 +1,24 @@ module Selenium class WebElement LOCATORS = { - id: "id", - name: "name", - tag_name: "tag name", - class_name: "class name", - css: "css selector", - link_text: "link text", + id: "id", + name: "name", + tag_name: "tag name", + class_name: "class name", + css: "css selector", + link_text: "link text", partial_link_test: "partial link text", - xpath: "xpath", + xpath: "xpath", } def self.locator_for(by) locator = LOCATORS[by] - raise ArgumentError.new("Unsupported locator strategy: #{ by.inspect }") unless locator + raise ArgumentError.new("Unsupported locator strategy: #{by.inspect}") unless locator locator end # TODO: special keys - #KEYS = { + # KEYS = { # NULL U+E000 # Cancel U+E001 # Help U+E002 @@ -57,7 +57,7 @@ module Selenium # Numpad 7 U+E021 # Numpad 8 U+E022 # Numpad 9 U+E023 - #} + # } getter id : String private getter session : Session @@ -65,11 +65,11 @@ module Selenium def initialize(@session, item) if id = item["ELEMENT"]? # JsonWireProtocol (obsolete) - @id = id.as(String) + @id = id.as_s else # W3C Webdriver identifier = item.keys.find(&.starts_with?("element-")) - @id = item[identifier].as(String) + @id = item[identifier].as_s end end @@ -82,15 +82,15 @@ module Selenium end def text - get("/text").as(String) + get("/text").as_s end def name - get("/name").as(String) + get("/name").as_s end def attribute(name) - get("/attribute/#{ name }").as(String) + get("/attribute/#{name}").as_s end def click @@ -106,7 +106,7 @@ module Selenium end def send_keys(sequence : Array) - post("/value", { value: sequence }) + post("/value", {value: sequence}) end def clear @@ -114,39 +114,39 @@ module Selenium end def ==(other : WebElement) - get("/equals/#{ other.id }").as(Bool) + get("/equals/#{other.id}").as_bool end def displayed? - get("/displayed").as(Bool) + get("/displayed").as_bool end def location - get("/location").as(Hash) + get("/location") end def location_in_view - get("/location_in_view").as(Hash) + get("/location_in_view") end def size - get("/size").as(Hash) + get("/size") end def css(property) - get("/css/#{ property }").as(String) + get("/css/#{property}").as_s end def to_json(io) - { "ELEMENT" => id }.to_json(io) + {"ELEMENT" => id}.to_json(io) end protected def get(path) - session.get("/element/#{ id }#{ path }") + session.get("/element/#{id}#{path}") end protected def post(path, body = nil) - session.post("/element/#{ id }#{ path }", body) + session.post("/element/#{id}#{path}", body) end end end