Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2be578271 | ||
|
|
c51d51fd72 |
||
|
|
61aea333ae |
||
|
|
038b5c8c70 |
||
|
|
bc9dd909a6 | ||
|
|
ef844cd998 | ||
|
|
1400f3f1d8 |
3 changed files with 106 additions and 11 deletions
7
README.md
Normal file
7
README.md
Normal 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
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue