From 1400f3f1d867b7e8d128975cbcca7381b5969db9 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 15 Oct 2017 14:05:46 +0100 Subject: [PATCH 1/5] findElement returns Option[Element] and added close. --- src/webdriver.nim | 25 +++++++++++++++++++------ webdriver.nimble | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/webdriver.nim b/src/webdriver.nim index ea4477c..7960a52 100644 --- a/src/webdriver.nim +++ b/src/webdriver.nim @@ -1,4 +1,4 @@ -import httpclient, uri, json, tables +import httpclient, uri, json, tables, options type WebDriver* = ref object @@ -61,6 +61,12 @@ proc createSession*(self: WebDriver): Session = return Session(id: sessionObj["value"]["sessionId"].getStr(), driver: self) +proc close*(self: Session) = + let reqUrl = $(self.driver.url / "session" / self.id) + let resp = self.driver.client.request(reqUrl, HttpDelete) + + let respObj = checkResponse(resp.body) + proc navigate*(self: Session, url: string) = ## Instructs the session to navigate to the specified URL. let reqUrl = $(self.driver.url / "session" / self.id / "url") @@ -81,15 +87,20 @@ proc getPageSource*(self: Session): string = return respObj{"value"}.getStr() proc findElement*(self: Session, selector: string, - strategy = CssSelector): Element = + strategy = CssSelector): Option[Element] = let reqUrl = $(self.driver.url / "session" / self.id / "element") let reqObj = %*{"using": toKeyword(strategy), "value": selector} - let resp = self.driver.client.postContent(reqUrl, $reqObj) + let resp = self.driver.client.post(reqUrl, $reqObj) + if resp.status == Http404: + return none(Element) - let respObj = checkResponse(resp) + if resp.status != Http200: + raise newException(WebDriverException, resp.status) + + let respObj = checkResponse(resp.body) for key, value in respObj["value"].getFields().pairs(): - return Element(id: value.getStr(), session: self) + return some(Element(id: value.getStr(), session: self)) proc getText*(self: Element): string = let reqUrl = $(self.session.driver.url / "session" / self.session.id / @@ -106,4 +117,6 @@ when isMainModule: "Entertainment-System/dp/B073BVHY3F" session.navigate(amazonUrl) - echo session.findElement("#priceblock_ourprice").getText() \ No newline at end of file + echo session.findElement("#priceblock_ourprice").get().getText() + + session.close() \ No newline at end of file diff --git a/webdriver.nimble b/webdriver.nimble index 66a8e12..80c27b8 100644 --- a/webdriver.nimble +++ b/webdriver.nimble @@ -1,6 +1,6 @@ # Package -version = "0.1.0" +version = "0.2.0" author = "Dominik Picheta" description = "Implementation of the WebDriver w3c spec." license = "MIT" From ef844cd998c819620dcfb3029228209d96e69987 Mon Sep 17 00:00:00 2001 From: Thomas Johnson Date: Sun, 15 Oct 2017 15:38:04 -0700 Subject: [PATCH 2/5] Added element.click() and element.sendKeys() --- src/webdriver.nim | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/webdriver.nim b/src/webdriver.nim index 7960a52..36b6a45 100644 --- a/src/webdriver.nim +++ b/src/webdriver.nim @@ -110,6 +110,28 @@ proc getText*(self: Element): string = return respObj["value"].getStr() +proc click*(self: Element) = + let reqUrl = $(self.session.driver.url / "session" / self.session.id / + "element" / self.id / "click") + let obj = %*{} + let resp = self.session.driver.client.post(reqUrl, $obj) + if resp.status != Http200: + raise newException(WebDriverException, resp.status) + + discard checkResponse(resp.body) + +# Note: There currently is an open bug in geckodriver that causes DOM events not to fire when sending keys. +# https://github.com/mozilla/geckodriver/issues/348 +proc sendKeys*(self: Element, text: string) = + let reqUrl = $(self.session.driver.url / "session" / self.session.id / + "element" / self.id / "value") + let obj = %*{"text": text} + let resp = self.session.driver.client.post(reqUrl, $obj) + if resp.status != Http200: + raise newException(WebDriverException, resp.status) + + discard checkResponse(resp.body) + when isMainModule: let webDriver = newWebDriver() let session = webDriver.createSession() From 038b5c8c70af2b66b885e6dcc01b60d5cb68e163 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Wed, 29 Nov 2017 22:16:33 +0000 Subject: [PATCH 3/5] Create README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba163c0 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# webdriver + +A simple implementation of the pretty recent [W3C WebDriver spec](https://www.w3.org/TR/webdriver/). +Currently Firefox is just about the only browser that supports it. + +I have coded this library during a livestream, do check that out to see how the library is used: +https://www.youtube.com/watch?v=583BwZ7uSro&index=1&list=PLm-fq5xBdPkrMuVkPWuho7XzszB6kJ2My From 61aea333aeb5db481e4faff7cb3a2a12ee25616b Mon Sep 17 00:00:00 2001 From: hlaaf Date: Thu, 7 Dec 2017 11:45:52 +0300 Subject: [PATCH 4/5] Traverse object once for nil checks --- src/webdriver.nim | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/webdriver.nim b/src/webdriver.nim index 36b6a45..80725be 100644 --- a/src/webdriver.nim +++ b/src/webdriver.nim @@ -43,12 +43,13 @@ proc createSession*(self: WebDriver): Session = # Check the readiness of the Web Driver. let resp = self.client.getContent($(self.url / "status")) let obj = parseJson(resp) + let ready = obj{"value", "ready"} - if obj{"value", "ready"}.isNil(): + if ready.isNil(): let msg = "Readiness message does not follow spec" raise newException(ProtocolException, msg) - if not obj{"value", "ready"}.getBool(): + if not ready.getBool(): raise newException(WebDriverException, "WebDriver is not ready") # Create our session. @@ -56,10 +57,11 @@ proc createSession*(self: WebDriver): Session = let sessionResp = self.client.postContent($(self.url / "session"), $sessionReq) let sessionObj = parseJson(sessionResp) - if sessionObj{"value", "sessionId"}.isNil(): + let sessionId = sessionObj{"value", "sessionId"} + if sessionId.isNil(): raise newException(ProtocolException, "No sessionId in response to request") - return Session(id: sessionObj["value"]["sessionId"].getStr(), driver: self) + return Session(id: sessionId.getStr(), driver: self) proc close*(self: Session) = let reqUrl = $(self.driver.url / "session" / self.id) @@ -141,4 +143,4 @@ when isMainModule: echo session.findElement("#priceblock_ourprice").get().getText() - session.close() \ No newline at end of file + session.close() From a2be5782719c8f53b5b73b8681447e33826e2520 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 19 May 2018 13:18:15 +0100 Subject: [PATCH 5/5] Implements ability to press buttons. --- src/webdriver.nim | 57 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/webdriver.nim b/src/webdriver.nim index 80725be..b164ca2 100644 --- a/src/webdriver.nim +++ b/src/webdriver.nim @@ -1,4 +1,6 @@ -import httpclient, uri, json, tables, options +# For reference, this is brilliant: https://github.com/jlipps/simple-wd-spec + +import httpclient, uri, json, tables, options, strutils, unicode type WebDriver* = ref object @@ -121,11 +123,11 @@ proc click*(self: Element) = raise newException(WebDriverException, resp.status) discard checkResponse(resp.body) - + # Note: There currently is an open bug in geckodriver that causes DOM events not to fire when sending keys. # https://github.com/mozilla/geckodriver/issues/348 proc sendKeys*(self: Element, text: string) = - let reqUrl = $(self.session.driver.url / "session" / self.session.id / + let reqUrl = $(self.session.driver.url / "session" / self.session.id / "element" / self.id / "value") let obj = %*{"text": text} let resp = self.session.driver.client.post(reqUrl, $obj) @@ -134,6 +136,55 @@ proc sendKeys*(self: Element, text: string) = discard checkResponse(resp.body) +type + # https://w3c.github.io/webdriver/#keyboard-actions + Key* = enum + Unidentified = 0, + Cancel, + Help, + Backspace, + Tab, + Clear, + Return, + Enter, + Shift, + Control, + Alt, + Pause, + Escape + +proc toUnicode(key: Key): Rune = + Rune(0xE000 + ord(key)) + +proc press*(self: Session, keys: varargs[Key]) = + let reqUrl = $(self.driver.url / "session" / self.id / "actions") + let obj = %*{"actions": [ + { + "type": "key", + "id": "keyboard", + "actions": [] + } + ]} + for key in keys: + obj["actions"][0]["actions"].elems.add( + %*{ + "type": "keyDown", + "value": $toUnicode(key) + } + ) + obj["actions"][0]["actions"].elems.add( + %*{ + "type": "keyUp", + "value": $toUnicode(key) + } + ) + + let resp = self.driver.client.post(reqUrl, $obj) + if resp.status != Http200: + raise newException(WebDriverException, resp.status) + + discard checkResponse(resp.body) + when isMainModule: let webDriver = newWebDriver() let session = webDriver.createSession()