From 8f1bdccae76252dc40071c43d9be0647dd2150f1 Mon Sep 17 00:00:00 2001 From: Daniel Ziltener Date: Wed, 27 Apr 2022 02:48:41 +0200 Subject: [PATCH] Subclass for Geckodriver --- src/WebDriver/WebDriver.class.st | 184 +++++++++++++------- src/WebDriver/WebDriverGeckodriver.class.st | 35 ++++ 2 files changed, 154 insertions(+), 65 deletions(-) create mode 100644 src/WebDriver/WebDriverGeckodriver.class.st diff --git a/src/WebDriver/WebDriver.class.st b/src/WebDriver/WebDriver.class.st index e0d8c07..3addcb9 100644 --- a/src/WebDriver/WebDriver.class.st +++ b/src/WebDriver/WebDriver.class.st @@ -1,3 +1,34 @@ +" +I am a cleanroom implementation of the W3C WebDriver protocol. + +Please comment me using the following template inspired by Class Responsibility Collaborator (CRC) design: + +For the Class part: State a one line summary. For example, ""I represent a paragraph of text"". + +For the Responsibility part: Three sentences about my main responsibilities - what I do, what I know. + +For the Collaborators Part: State my main collaborators and one line about how I interact with them. + +Public API and Key Messages + +- message one +- message two +- (for bonus points) how to create instances. + + One simple example is simply gorgeous. + +Internal Representation and Key Implementation Points. + + Instance Variables + browser: + optionsName: + port: + server: + sessionId: + + + Implementation Points +" Class { #name : #WebDriver, #superclass : #Object, @@ -6,7 +37,6 @@ Class { 'server', 'port', 'sessionId', - 'parameters', 'optionsName' ], #category : #WebDriver @@ -18,7 +48,7 @@ WebDriver class >> geckodriver [ subproc := OSSUnixSubprocess new command: 'geckodriver'. [ subproc run ] fork. - ^ self new + ^ WebDriverGeckodriver new browser: subproc server: '127.0.0.1' port: 4444 @@ -27,113 +57,130 @@ WebDriver class >> geckodriver [ { #category : #accessing } WebDriver >> attribute: attr from: element [ - ^ (self send: { } - to: 'session/',sessionId,'/element/',element,'/attribute/',attr - using: #GET) - at: #value. + + ^ self + send: { } + to: + 'session/' , sessionId , '/element/' , element , '/attribute/' + , attr + using: #GET ] { #category : #navigation } WebDriver >> back [ - self send: { } to: 'session/',sessionId,'/back' using: #POST. -] -{ #category : #initialization } -WebDriver >> browser: brs server: srv port: portnum [ - browser := brs. - server := srv. - port := portnum. + self send: { } to: 'session/' , sessionId , '/back' using: #POST ] { #category : #initialization } WebDriver >> browser: brs server: srv port: portnum optionsName: optName [ + browser := brs. server := srv. port := portnum. - optionsName := optName. + optionsName := optName +] + +{ #category : #'event handling' } +WebDriver >> click: elem [ + + self + send: { } + to: 'session/' , sessionId , '/element/' , elem , '/click' + using: #POST +] + +{ #category : #'private - utilities' } +WebDriver >> constructCapabilities: caps [ + + self subclassResponsibility ] { #category : #finalization } WebDriver >> deleteSession [ + "Deletes the session." - self send: {} to: 'session/',sessionId using: #DELETE. + + self send: { } to: 'session/' , sessionId using: #DELETE ] { #category : #finalization } -WebDriver >> finalize [ +WebDriver >> finalize [ + browser terminate. - super finalize. + super finalize ] { #category : #accessing } WebDriver >> findElement: elemSelector using: locStrategy [ + | reply | - reply := (self send: { #using -> locStrategy. #value -> elemSelector. } - to: 'session/',sessionId,'/element' - using: #POST) - at: #value. + reply := self + send: { + (#using -> locStrategy). + (#value -> elemSelector) } + to: 'session/' , sessionId , '/element' + using: #POST. (reply at: #error ifPresent: [ true ] ifAbsent: [ false ]) ifTrue: [ ^ WDException raise: reply ] - ifFalse: [ ^ (WDElement fromString: reply values first) ] + ifFalse: [ ^ WDElement fromString: reply values first ] ] { #category : #accessing } WebDriver >> findElements: elemSelector using: locStrategy [ + | reply | - reply := (self send: { #using -> locStrategy. #value -> elemSelector. } - to: 'session/',sessionId,'/elements' - using: #POST) - at: #value. - (reply isArray) - ifTrue: [ ^ reply collect: [ :elem | WDElement fromString: elem values first ] ] + reply := self + send: { + (#using -> locStrategy). + (#value -> elemSelector) } + to: 'session/' , sessionId , '/elements' + using: #POST. + reply isArray + ifTrue: [ + ^ reply collect: [ :elem | WDElement fromString: elem values first ] ] ifFalse: [ ^ WDException raise: reply ] ] { #category : #navigation } WebDriver >> forward [ - self send: { } to: 'session/',sessionId,'/forward' using: #POST. + + self send: { } to: 'session/' , sessionId , '/forward' using: #POST ] { #category : #accessing } WebDriver >> property: attr from: element [ - ^ (self - send: { } - to: - 'session/' , sessionId , '/element/' , element , '/property/' - , attr - using: #GET) at: #value + ^ self + send: { } + to: + 'session/' , sessionId , '/element/' , element , '/property/' + , attr + using: #GET ] { #category : #navigation } WebDriver >> refresh [ - self send: { } to: 'session/',sessionId,'/refresh' using: #POST. + + self send: { } to: 'session/' , sessionId , '/refresh' using: #POST ] { #category : #navigation } WebDriver >> send: dict to: url using: method [ - |result| - result := nil. - ZnClient new - host: server; - port: port; - path: url; - contents: (NeoJSONWriter toString: dict asDictionary); - contentType: ZnMimeType applicationJson; - method: method; - contentReader: [ :entity | - result := (NeoJSONReader on: (entity contents) readStream) propertyNamesAsSymbols: true; next. - ]; - execute. - ^result. + ^ self subclassResponsibility. ] { #category : #accessing } WebDriver >> session [ + "Initializes a new WebDriver session." - sessionId - ifNil: [ sessionId := (self send: {} to: 'session' using: #POST) at: #value at: #sessionId ]. - ^sessionId. + + sessionId ifNil: [ + sessionId := (self + send: (self constructCapabilities: { }) + to: 'session' + using: #POST) at: #sessionId ]. + ^ sessionId ] { #category : #accessing } @@ -141,9 +188,9 @@ WebDriver >> session: capabilities [ sessionId ifNotNil: [ ^ self session ] ifNil: [ sessionId := (self - send: { #desiredCapabilities -> ({ optionsName -> ({#prefs -> capabilities}) asDictionary } asDictionary) } + send: (self constructCapabilities: capabilities) to: 'session' - using: #POST) at: #value at: #sessionId. + using: #POST) at: #sessionId. ^ sessionId ] ] @@ -154,36 +201,43 @@ WebDriver >> source [ send: { } to: 'session/' , sessionId , '/source' - using: #GET) at: #value + using: #GET) ] { #category : #accessing } WebDriver >> status [ - ^ (self send: { } to: 'status' using: #GET) at: #value. + + ^ self send: { } to: 'status' using: #GET ] { #category : #accessing } WebDriver >> title [ - ^ (self send: { } to: 'session/',sessionId,'/title' using: #GET) at: #value. + + ^ self send: { } to: 'session/' , sessionId , '/title' using: #GET ] { #category : #'text input' } WebDriver >> type: text into: element [ - self send: { #text -> text } to: 'session/',sessionId,'/element/',element,'/value' using: #POST. + + self + send: { (#text -> text) } + to: 'session/' , sessionId , '/element/' , element , '/value' + using: #POST ] { #category : #accessing } WebDriver >> url [ - ^ (self - send: { } - to: - 'session/' , sessionId , '/url' - using: #GET) at: #value + ^ self send: { } to: 'session/' , sessionId , '/url' using: #GET ] { #category : #navigation } WebDriver >> url: url [ + "Navigates the browser to the given URL." - self send: { #url -> url } to: 'session/',sessionId,'/url' using: #POST. + + self + send: { (#url -> url) } + to: 'session/' , sessionId , '/url' + using: #POST ] diff --git a/src/WebDriver/WebDriverGeckodriver.class.st b/src/WebDriver/WebDriverGeckodriver.class.st new file mode 100644 index 0000000..9679768 --- /dev/null +++ b/src/WebDriver/WebDriverGeckodriver.class.st @@ -0,0 +1,35 @@ +" +I implement the specifics and quirks to remote control a Firefox instance using Geckodriver. +" +Class { + #name : #WebDriverGeckodriver, + #superclass : #WebDriver, + #category : #WebDriver +} + +{ #category : #'private - utilities' } +WebDriverGeckodriver >> constructCapabilities: caps [ + + ^ { (#desiredCapabilities + -> + { (optionsName -> { (#prefs -> caps) } asDictionary) } + asDictionary) } +] + +{ #category : #navigation } +WebDriverGeckodriver >> send: dict to: url using: method [ + |result| + result := nil. + ZnClient new + host: server; + port: port; + path: url; + contents: (NeoJSONWriter toString: dict asDictionary); + contentType: ZnMimeType applicationJson; + method: method; + contentReader: [ :entity | + result := (NeoJSONReader on: (entity contents) readStream) propertyNamesAsSymbols: true; next. + ]; + execute. + ^result at: #value. +]