Compare commits

...

7 commits

Author SHA1 Message Date
Dominik Picheta
a2be578271 Implements ability to press buttons. 2018-05-19 13:18:15 +01:00
Dominik Picheta
c51d51fd72
Merge pull request #3 from hlaaftana/patch-1
Traverse object once for nil checks
2017-12-07 10:38:36 +00:00
hlaaf
61aea333ae
Traverse object once for nil checks 2017-12-07 11:45:52 +03:00
Dominik Picheta
038b5c8c70
Create README.md 2017-11-29 22:16:33 +00:00
Dominik Picheta
bc9dd909a6 Merge pull request #2 from darkmusic/master
Added element.click() and element.sendKeys()
2017-10-23 14:20:04 +01:00
Thomas Johnson
ef844cd998 Added element.click() and element.sendKeys() 2017-10-15 15:38:04 -07:00
Dominik Picheta
1400f3f1d8 findElement returns Option[Element] and added close. 2017-10-15 14:05:46 +01:00
3 changed files with 106 additions and 11 deletions

7
README.md Normal file
View file

@ -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

View file

@ -1,4 +1,6 @@
import httpclient, uri, json, tables # For reference, this is brilliant: https://github.com/jlipps/simple-wd-spec
import httpclient, uri, json, tables, options, strutils, unicode
type type
WebDriver* = ref object WebDriver* = ref object
@ -43,12 +45,13 @@ proc createSession*(self: WebDriver): Session =
# Check the readiness of the Web Driver. # Check the readiness of the Web Driver.
let resp = self.client.getContent($(self.url / "status")) let resp = self.client.getContent($(self.url / "status"))
let obj = parseJson(resp) 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" let msg = "Readiness message does not follow spec"
raise newException(ProtocolException, msg) raise newException(ProtocolException, msg)
if not obj{"value", "ready"}.getBool(): if not ready.getBool():
raise newException(WebDriverException, "WebDriver is not ready") raise newException(WebDriverException, "WebDriver is not ready")
# Create our session. # Create our session.
@ -56,10 +59,17 @@ proc createSession*(self: WebDriver): Session =
let sessionResp = self.client.postContent($(self.url / "session"), let sessionResp = self.client.postContent($(self.url / "session"),
$sessionReq) $sessionReq)
let sessionObj = parseJson(sessionResp) 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") 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)
let resp = self.driver.client.request(reqUrl, HttpDelete)
let respObj = checkResponse(resp.body)
proc navigate*(self: Session, url: string) = proc navigate*(self: Session, url: string) =
## Instructs the session to navigate to the specified URL. ## Instructs the session to navigate to the specified URL.
@ -81,15 +91,20 @@ proc getPageSource*(self: Session): string =
return respObj{"value"}.getStr() return respObj{"value"}.getStr()
proc findElement*(self: Session, selector: string, proc findElement*(self: Session, selector: string,
strategy = CssSelector): Element = strategy = CssSelector): Option[Element] =
let reqUrl = $(self.driver.url / "session" / self.id / "element") let reqUrl = $(self.driver.url / "session" / self.id / "element")
let reqObj = %*{"using": toKeyword(strategy), "value": selector} 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(): 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 = proc getText*(self: Element): string =
let reqUrl = $(self.session.driver.url / "session" / self.session.id / let reqUrl = $(self.session.driver.url / "session" / self.session.id /
@ -99,6 +114,77 @@ proc getText*(self: Element): string =
return respObj["value"].getStr() 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)
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: when isMainModule:
let webDriver = newWebDriver() let webDriver = newWebDriver()
let session = webDriver.createSession() let session = webDriver.createSession()
@ -106,4 +192,6 @@ when isMainModule:
"Entertainment-System/dp/B073BVHY3F" "Entertainment-System/dp/B073BVHY3F"
session.navigate(amazonUrl) session.navigate(amazonUrl)
echo session.findElement("#priceblock_ourprice").getText() echo session.findElement("#priceblock_ourprice").get().getText()
session.close()

View file

@ -1,6 +1,6 @@
# Package # Package
version = "0.1.0" version = "0.2.0"
author = "Dominik Picheta" author = "Dominik Picheta"
description = "Implementation of the WebDriver w3c spec." description = "Implementation of the WebDriver w3c spec."
license = "MIT" license = "MIT"