347 lines
8.3 KiB
Smalltalk
347 lines
8.3 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',
|
|
'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 : #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 : #simulating }
|
|
WebDriver >> executeAsync: script with: args [
|
|
^ self sendWithSession: { #script -> script . #args -> args }
|
|
to: 'execute/async'
|
|
using: #POST.
|
|
]
|
|
|
|
{ #category : #simulating }
|
|
WebDriver >> executeSync: script with: args [
|
|
^ self sendWithSession: { #script -> script . #args -> args }
|
|
to: 'execute/sync'
|
|
using: #POST.
|
|
]
|
|
|
|
{ #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: 'Session Capabilities')
|
|
expand: false
|
|
fill: false
|
|
padding: 5;
|
|
add: capabilities asStringTreeTablePresenter
|
|
expand: true
|
|
fill: true
|
|
padding: 5 yourself.
|
|
presenter := SpPresenter new
|
|
layout: presenter;
|
|
yourself.
|
|
^ presenter
|
|
]
|
|
|
|
{ #category : #'private - utilities' }
|
|
WebDriver >> postprocessResult: result [
|
|
^ result.
|
|
]
|
|
|
|
{ #category : #utilities }
|
|
WebDriver >> print [
|
|
^ self sendWithSession: { } to: 'print' using: #POST.
|
|
]
|
|
|
|
{ #category : #navigation }
|
|
WebDriver >> refresh [
|
|
|
|
self send: { } to: 'session/' , sessionId , '/refresh' using: #POST
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
WebDriver >> screenshot [
|
|
^ (self sendWithSession: '' to: 'screenshot' using: #GET) base64Decoded.
|
|
]
|
|
|
|
{ #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
|
|
]
|