" 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: optionsName: port: server: sessionId: 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 [ | 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 ]