WebDriver/src/WebDriver/WebDriver.class.st

324 lines
8.2 KiB
Smalltalk

"
I am a cleanroom implementation of the [W3C WebDriver protocol](https://w3c.github.io/webdriver/).
I am responsible for dispatching the correct messages to start remotely controlled browsers and send commands to them.
My subclasses implement browser-specific behaviour.
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: <Object>
optionsName: <Object>
port: <Object>
server: <Object>
sessionId: <Object>
Implementation Points
"
Class {
#name : #WebDriver,
#superclass : #Object,
#instVars : [
'browser',
'server',
'port',
'sessionId',
'prefs',
'capabilities'
],
#category : #'WebDriver-Base'
}
{ #category : #testing }
WebDriver class >> matchesBrowserType: browserType [
^ self subclassResponsibility.
]
{ #category : #accessing }
WebDriver class >> start: browserType [
| emptyOpts |
emptyOpts := Dictionary new.
^ self start: browserType with: emptyOpts.
]
{ #category : #'instance creation' }
WebDriver class >> start: browserType with: options [
"comment stating purpose of class-side method"
"scope: class-variables & class-instance-variables"
| matchingClass |
matchingClass := self subclasses
detect: [ :operationClass | operationClass matchesBrowserType: browserType ]
ifNone: [ self ].
^ matchingClass startWithOptions: options.
]
{ #category : #'instance creation' }
WebDriver class >> startWithOptions: options [
^ self subclassResponsibility.
]
{ #category : #accessing }
WebDriver >> atPref: prefKey put: prefVal [
prefs at: prefKey put: prefVal.
]
{ #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.
]
{ #category : #'private - utilities' }
WebDriver >> browserDetails [
^ self subclassResponsibility
]
{ #category : #'private - utilities' }
WebDriver >> constructCapabilities: caps [
self subclassResponsibility
]
{ #category : #finalization }
WebDriver >> deleteSession [
"Deletes the session."
self send: { } to: 'session/' , sessionId using: #DELETE
]
{ #category : #finalization }
WebDriver >> finalize [
browser terminate.
super finalize
]
{ #category : #accessing }
WebDriver >> findElement: elemSelector using: locStrategy [
| reply |
reply := self
send: {
(#using -> locStrategy).
(#value -> elemSelector) }
to: 'session/' , sessionId , '/element'
using: #POST.
^ WDElement new
driver: self;
element: reply values first.
]
{ #category : #accessing }
WebDriver >> findElements: elemSelector using: locStrategy [
| reply |
reply := self
send: {
(#using -> locStrategy).
(#value -> elemSelector) }
to: 'session/' , sessionId , '/elements'
using: #POST.
^ reply collect: [ :elem | WDElement new driver: self; element: elem values first. ]
]
{ #category : #navigation }
WebDriver >> forward [
self send: { } to: 'session/' , sessionId , '/forward' using: #POST
]
{ #category : #'private - utilities' }
WebDriver >> inspectionWebDriver [
<inspectorPresentationOrder: 10 title: 'Browser'>
| presenter browserInfo |
browserInfo := self browserDetails.
presenter := SpBoxLayout newTopToBottom
add: (SpLabelPresenter new label: 'Connection Infos'; yourself) expand: false fill: false padding: 5;
add: (SpTablePresenter new
addColumn: (SpStringTableColumn title: 'Key' evaluated: [ :anObject | anObject at: #title ] );
addColumn: (SpStringTableColumn title: 'Value' evaluated: [ :anObject | anObject at: #text ] );
items: {
Dictionary new at: #title put: 'Browser'; at: #text put: (browserInfo at: #name); yourself.
Dictionary new at: #title put: 'Driver'; at: #text put: (browserInfo at: #driver); yourself.
Dictionary new at: #title put: 'Address'; at: #text put: ('http://{1}:{2}/' format: {server.port}); yourself.
})
expand: true
fill: true
padding: 5;
add: (SpLabelPresenter new label: 'Preferences'; yourself) expand: false fill: false padding: 5;
add: (SpTreeTablePresenter new
addColumn: (SpStringTableColumn title: 'Key' evaluated: [ :anObject | anObject key ]);
addColumn: (SpStringTableColumn title: 'Value' evaluated: [ :anObject | anObject value ]);
roots: prefs;
children: [ :anItem | anItem value isDictionary ifTrue: [ anItem value ] ifFalse: [ false ] ])
expand: true
fill: true
padding: 5;
add: (SpLabelPresenter new label: 'Session Capabilities'; yourself) expand: false fill: false padding: 5;
add: (SpTreeTablePresenter new
addColumn: (SpStringTableColumn title: 'Key' evaluated: [ :anObject |
anObject isDictionary
ifTrue: [ anObject isEmpty ifTrue: [ '' ] ifFalse: [ anObject keys at: 1 ] ]
ifFalse: [ anObject key ] ]);
addColumn: (SpStringTableColumn title: 'Value' evaluated: [ :anObject |
anObject isDictionary ifTrue: [ '' ] ifFalse: [ anObject value ] ]);
roots: (capabilities ifNil: [ { } ] ifNotNil: [ capabilities ] );
children: [ :anItem |
anItem value isDictionary
ifTrue: [ anItem value isEmpty ifTrue: [ {} ] ifFalse: [ anItem value ] ]
ifFalse: [ {} ] ] )
expand: true
fill: true
padding: 5
yourself.
presenter := SpPresenter new layout: presenter; yourself.
^ presenter.
]
{ #category : #'private - utilities' }
WebDriver >> postprocessResult: result [
^ result.
]
{ #category : #navigation }
WebDriver >> refresh [
self send: { } to: 'session/' , sessionId , '/refresh' using: #POST
]
{ #category : #'private - utilities' }
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.
result := self postprocessResult: result.
result isDictionary ifTrue:
[ (result includesKey: #error) ifTrue: [ WDException raise: result ]. ]
];
execute.
^result.
]
{ #category : #'private - utilities' }
WebDriver >> sendWithSession: dict to: url using: method [
^ self send: dict to: '/session/' , sessionId , '/' , url using: method
]
{ #category : #accessing }
WebDriver >> session [
"Initializes a new WebDriver session."
capabilities := self constructCapabilities: { } asDictionary.
sessionId ifNil: [
sessionId := (self
send: capabilities
to: 'session'
using: #POST) at: #sessionId ].
^ sessionId
]
{ #category : #accessing }
WebDriver >> session: caps [
sessionId ifNotNil: [ ^ self session ] ifNil: [
capabilities := self constructCapabilities: caps.
sessionId := (self
send: capabilities
to: 'session'
using: #POST) at: #sessionId.
^ sessionId ]
]
{ #category : #'private - utilities' }
WebDriver >> sessionId [
^ sessionId
]
{ #category : #accessing }
WebDriver >> source [
^ (self
send: { }
to:
'session/' , sessionId , '/source'
using: #GET)
]
{ #category : #accessing }
WebDriver >> status [
^ self send: { } to: 'status' using: #GET
]
{ #category : #accessing }
WebDriver >> timeouts [
| toutdata |
toutdata := self send: { } to: 'session/',sessionId,'/timeouts' using: #GET.
^ WDTimeouts new
script: (toutdata at: #script);
pageLoad: (toutdata at: #pageLoad);
implicit: (toutdata at: #implicit).
]
{ #category : #accessing }
WebDriver >> timeouts: timeouts [
self
send: timeouts extract
to: 'session/' , sessionId , 'timeouts'
using: #POST
]
{ #category : #accessing }
WebDriver >> title [
^ self send: { } to: 'session/' , sessionId , '/title' using: #GET
]
{ #category : #accessing }
WebDriver >> url [
^ 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
]