324 lines
8.2 KiB
Smalltalk
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
|
|
]
|