Selenium
| Repository | https://github.com/dyalog/Selenium |
| Copyright | Made with Material for MkDocs. Contents copyright ©2015-2024 Dyalog, LTD |
Driving Selenium from Dyalog APL
Selenium is a widely used open-source tool for automating browsers, with growing support from browser vendors. The GitHub repository Dyalog/Selenium contains code which allows Dyalog applications to drive browsers via Selenium.
The namespace Selenium contained in this repository contains cover-functions for some of the most frequently used features of Selenium. In contrast to earlier versions it is not necessary to search for keep up with browsers versions etc. - using the latest Selenium features it is now possible to detect the locally installed browser (Chrome, Edge and Firefox are now supported!) and its version and get the appropriate driver automatically!
Usage
Installing Selenium
Selenium is available as a Tatin package.
when using Tatin
First, use ]TATIN.ListPackages [tatin] -tags=selenium to find out which versions are available.
to be continued
when not using Tatin (and still using it)
Even when not using Tatin (in the code you write and want to deploy) you probably have Tatin installed
and can use it. If you feel comfortable doing so, ]Tatin.LoadPackages dyalog-selenium,dyalog-nuget can be used to bring in the Selenium namespace (and the NuGet package that it depends on) into #.
without Tatin
You'll need the Selenium namespace as well as NuGet. There are infinite ways to get them, such as
- cloning the repositories
- downloading the required files from the home repositories
- and more.
We'll trust you'll have your own methods to get the files.
Settings browser options
Selenium provide several layers to control its behavior (and that of the browser it controls):
Environment variables / Configuration settings
This refers to variables that are set up in the environment that the interpreter's executable runs in or in a CONFIGFILE or USERCONFIGFILE that it uses.
SELENIUM_HOME
The config variable SELENIUM_HOME is the first candidate that Selenium looks at when it searches its home directory.
SELENIUM_CONFIGFILE
This optional variable can be defined to point to a .json5 file with the configuration to use. This file will then complement the settings found in Selenium's settings.json5.
Setting options in the Selenium namespace
DEFAULTRETRYLIMIT
The number of seconds that the Retry operator and its derivatives should retry an operation.
SELENIUM_HOME
The variable SELENIUM_HOME is supposed to contain the path to Selenium's settings file. If is not defined, we determine the path and store it in this variable for further references.
PORT
The address of the port that the browser uses to retrieve content.
The configuration file settings.json5
In earlier versions of our implementation, this file needed frequent updates in order to adjust to the latest browser versions. With the release 2.0 we are using new features of Selenium that help to identify browsers version and load suitable drivers. Consequently this file now has a more static nature and should not need updates by the user.
When new versions of Selenium and its drivers are released, we may update the file - it is therefore advised to no longer keep local versions of it, but instead treat it as part of the code.
Inspecting the Contents of the Page
The Selenium namespace contains a number of functions which manipulate the browser in various ways (see the next section for details).
R←IsVisible id
The boolean result R indicated whether or not the control with the given id is visible or not.
Note
It might be interesting to combine IsVisible and Retry to wait until a dynamic element becomes visible.
html←PageSource
You can verify whether the page has been correctly loaded by inspecting the PageSource, for example:
∨/'<link href="/Styles/tryapl.css"'⍷Selenium.BROWSER.PageSource
{ok}←(fn Retry) arg
Following any action, an unknown amount of time may pass before the server responds and a response is detectable in the browser. The Retry operator is provided to allow waiting for an expected effect. The time to wait is controlled by the variable Selenium.DEFAULTRETRYLIMIT . Retry invokes fn on arg, and retries until the result is true, or DEFAULTRETRYLIMIT seconds have passed.
For example:
{(Find 'result').Text=⍵} Retry 'You pressed the button!'
Retry returns the result of the final application of the function.
SaveScreenshot filename
see separate page
R←Text id
Returns the text displayed in control id. In contrast to Selenium's internal functions this works for all types of controls.
{msg}←element WaitFor text [message]
The left argument can be the id or a reference to an element to be tested or a two element vector containing the left and right arguments of the Find function. WaitFor will use Retry to wait until the element in question contains the specified text anywhere within its Text property. If the element is of type input, (element.GetAttribute⊂'value') is tested instead.
For example:
'result' WaitFor 'Welcome!'
Browser Automation Functions
The following functions manipulate the browser in various ways. Note that, wherever a {selector} is used, a reference to an IWebElement (returned by Find or the argument(s) of Find can be used instead.
id Add2InputValue (t1 t2)
While SetInputValue replaces the complete text of the control, Add2InputValue can be used to prepend or append text to the current value of the control. t1 will be prepended, t2 appended. Use '' to avoid the related change.
ClearInput
Clears the content of input-fields or textarea.
{selector} Click id
Clicks on the selected element. Both arguments are passed directly to the Find function, and the Click method is invoked on the result. For example:
Click 'btn1'
{R}←{args}ExecuteScript sc
Executes the Javascript code passed in sc. The optional left argument can be used to pass arguments to it.
A good use case for this is this line of the SetInputValue function:
(obj text)ExecuteScript'arguments[0].value = arguments[1];'
fromid DragAndDrop toid
Drag the element 'draggable'#.S.DragAndDrop'droppable'
fromid DragAndDropToOffset xy
Drag the element 'draggable'#.S.DragAndDropToOffset 10 10
R←{level}GetLogs type
This function retrieves the logs. Valid values for type are browser, webdriver and performance, the optional level must be one of All, Debug, Info, Off, Severe or Warning.
Info
By default, only browser and webdriver logs are enabled.
If you want to access performance logs, they needs to be enabled in settings.json5 first.
{action} MoveToElement toid [x y]
Move the mouse to the middle of an element, or optionally to the (x y) coordinates.
If the optional left argument is provided and is a character vector containing one of the strings Click | ClickAndHold | ContextClick | DoubleClick, that mouse action will be performed after the move.
For example:
'DoubleClick' MoveToElement 'btn1' 10 10
{ok}←id Select text
Select the item with a given text in a dropdown. For example:
'fruits' Select 'apples'
The right argument text can also be a two-element vector containing (srch crit)where
srchspecifies what you're searching for- and
critdescribes the criteria:CssSelector: find the element using a CSS SelectorIndex:srchgives the index of the element you want to selectPartialText:srchis a partial textText:srchis the full text that needs to matchValue:srchmust match thevalueattribute of the element you're looking for- a boolean value in
critindicates whether partial matches are accepted (=1) or not (=0, default)
The boolean result ok indicates if the selection was successful (=1) or not (=0).
{ok}←id SelectItemText text
Much like the previous function, this function can be used to select elements in a select control based on the item text. While it does not provide the flexibility regarding selections, it also enables you to de-select elements by providing a prefix - and to de-select all element by passing the value ~.
id SendKeys text
see separate doc
id SetInputValue text
This method is an alternative to SendKeys. While SendKeys focusses on sending actual keystrokes, this function provides an easy way to send text to an input control. It is recommended when sending "complex" text that may require key-combinations to enter it, for example APL-Symbols or special characters like "@".
Other WebDriver Functionality
Instances of the Selenium WebDriver classes support significant functionality which is not covered by the existing Selenium namespace. The full set of methods and properties exposed by WebDriver.dll and WebDriver.Support.dll, which are documented here on the Selenium web site: https://www.selenium.dev/documentation/webdriver/
After InitBrowser has been called, the variable BROWSER is a reference to the current instance. It exposes several methods which are not covered by the Selenium namespace, but can be called from APL after consulting the Selenium documentation. It also exposes a number of interesting properties.
The ACTIONS variable is a reference to an instance of an Actions object, which also exposes functions not currently supported by any of the functions in the Selenium namespace:
- DragAndDropToOffset
- KeyDown
- KeyUp
- Release
It is also worth mentioning Selenium.BROWSER.Navigate.Refresh which can be useful when experimenting in the session to refresh the current page.
There is scope for future extensions to the tool, suggestions and pull requests are most welcome!
End
As you have noticed, running a test opens a console-window showing the output of the webdriver that was launched – which will in turn launch the browser. In order to close the windows at the end of a test, it is recommended to call the End function which will close those windows and dispose all objects it created.
As the code uses NuGet, InitBrowser will create a temporary project folder for NuGet to work with which End will also delete. (If you do not call End in your tests, you may want to check the variable Selenium.myNuGetFolder and make sure the folder is deleted.)
Examples
Simple example
A basic example is provided in the Samples\ folder of the GitHub repository where the code in TestUppercase.aplf controls the website coded in text-uppercase.html that accepts text input and shows its representation in uppercase upon clicking the "Transform" button.
Use LINK to bring it into the workspace:
]LINK.Create Sample /git/Selenium/Samples
r←TestUppercase nul;S;path;demoText;expect;errMsg;file
path←1⊃1 ⎕NPARTS{6::50 ⎕ATX ⍵ ⋄ SALT_Data.SourceFile}1⊃⎕SI ⍝ where does this file come from?
11 ⎕SIGNAL(0=≢path)/'Unable to determine source path'
file←path,'text-uppercase.html'
11 ⎕SIGNAL(≡⎕NEXISTS file)/'File "',file,'" does not exist!'
demoText←'Hello World!'
expect←1 ⎕C demoText
errMsg←'Did not find expected output!'
S←##.Selenium
S.InitBrowser''
S.GoTo'file:///',file
'inputText'S.SendKeys demoText
(S.Find'transformButton').Click
r←'outputText'S.WaitFor expect errMsg
S.End
There is some initialisation work to do by assigning path as the directory where the code was loaded from. (This approach is compatible with SALT's ]Load as well as with Link´s ]Import or ⎕FIX.) It then assigns demoText as the text that we want to operate on and uses ⎕C to determine the expected value, which we will compare against the result that the web page delivers.
The above code assumes that the Selenium namespace has been loaded into the same namespace as the TestUppercase function itself and creates a reference to it using the variable S.
After this setup work, the test is executed:
| Expression | Discussion |
|---|---|
S.InitBrowser'' |
Launch the browser (looks for one of Chrome, Firefox, Edge) |
S.GoTo'file:///',file |
Open the local version of the uppercase application |
'inputText'S.SendKeys demoText |
Enter the sample text into the input field |
(S.Find'transformButton').Click |
Click the "Transform" button |
r←'outputText'S.WaitFor expect errMsg |
Wait until the expected text appears in the output field, otherwise print an error message |
S.End |
Close Browser and clean up |
Note
This test is written to be executed as a standalone test. If multiple tests are executed, it makes sense to separate out the instructions for initialisation of Selenium and the browser as well as the "Teardown" instructions like S.End.
Testing RIDE with Selenium
The namespace Selenium.ride contains functions that support testing RIDE (standalone or zero footprint). The folder Samples/RIDE contains several tests that illustrate its usage.
Usage
InitRIDE (browser port)
The function InitRIDE launches a new interpreter instance.
browser
Selects how and where you want to run Ride:
-
to run zero footprint Ride in a browser, pick an entry from Selenium's settings.json5 file (ie.
Chrome,Firefoxor evenHtmlRenderer). -
to test the native Ride client, pass the fully qualified name of its executable.
In any case, InitRIDE will also launch an interpreter (a 2nd instance of the one in which you called it) and connect Ride to it. (Or the Ride client will start it.)
Port
It is always an "interesting question" which port to use. Fortunately v20 significantly improves this process by allowing to assign port 0 and have the interpreter use a free one. For versions < 20, this is a manual process: the system will attempt to use the default port and the user has to observe the launch and see if Dyalog started w/o an error message about the port being used or not. Two different statements to continue processing depending on the result will be printed into the session and then InitRIDE will temporary interrupt with an intended error.
Special considerations
When testing against CEF we are dealing with reduced functionality in Selenium. In addition, Ride internally uses the Monaco editor
Command Reference
Commands by category
Initialisation and teardown
- Initialise the browser: InitBrowser
- End a session: End
Navigation
- Open URL: GoTo
Dealing with websites
- Clear content of an input control: ClearInput
- Click a control: Click
- Compare Screenshots: CompareScreenshots
- Drag and Drop elements: DragAndDrop and DragAndDropToOffset
- Execute Javascript code: ExecuteScript
- Find elements on page: Find
- Input value: SetInputValue
- Mouse movements: MoveToElement
- Page source: PageSource
- Query item text: Text
- Save a Screenshot: SaveScreenshot
- Selections: Select, SelectItemText
- Send keystrokes: SendKeys
- Wait: Retry, Wait, WaitFor
r←{selector}Find id
Allows searching for DOM elements by Id (the default if no left argument is provided) or one of the other selectors: ClassName, CssSelector, Id, LinkText, Name, PartialLinkText, TagName or XPath.
In order to find multiple elements (when using options such as ClassName, CssSelector etc.) simply append a plural-s to the criteria (i.e. 'ClassNames'Find'...')
The result r will either be an instance of IWebElement or the scalar 0 if the element was not found.
Find will retry the search for an element until RETRYLIMIT has elapsed, so if it takes time for a page to render completely, or an element is created by an earlier action, it will wait for a while.
While it is possible to find hidden elements, it is not possible to interact with them (click or enter text into input controls). The IWebElementinstances provide a boolean Displayed that can be used to check if an element is visible or not.
Info
Our own test Tests/test_find.aplf illustrates practical uses of Find with diverse criteria.
Goto url
Calls the Navigate.GoToUrl method and verifies that the Url property subsequently has the desired value. Signals an error if navigation fails. While Navigate.GoToUrl requires a complete URL, you may also specify relative URLs here (omitting protocol, domain & port).
If the first 4 characters of the URL match either 'http' or 'file', we assume it is a complete URL, otherwise it wil be treated as being relative to the current URL.
We hope to be helpful
There is an interesting challenge when writing cross-platform tests that run on local .html files: you typically have to specify a fully qualified path, such as c:/documents/foo.html on Windows or /Users/Dyalog/Documents/foo.html on macOS.
The URL for these files uses the file:// protocol. The standard URI syntax requires a hostname after the protocol, which is represented by two slashes, //. Since local files have no hostname, a third slash is used to indicate the start of the file path. This leads to the standard prefix file:///.
The correct URLs are therefore:
- On Windows: file:///c:/documents/foo.html
- On macOS: file:///Users/Dyalog/Documents/foo.html
The challenge arises if you try to build this URL dynamically. If you have a variable containing the platform-specific path and then prepend file://, you will be missing a slash on both Windows and macOS, resulting in an incorrect URI.
To mitigate this common cross-platform issue, the GoTo command has a built-in tolerance. It will detect occurrences of file:// followed by a path that begins with / (on macOS) or a drive letter (on Windows) and will intelligently add the missing third slash to form the correct file:/// prefix, ensuring the command works as expected on both platforms.
{⍺}InitBrowser ⍵
The function InitBrowser takes a right argument which is a character vector indicating the name of the browser. If the element is empty, the system will look for an environment variable SELENIUM_DRIVER or used the Browser element from settings.json5 will be used and if both are also not available, check the DEFAULTBROWSER variable in the Selenium namespace.
The name should either be one of the browsers currently supported by Selenium (Chrome, Firefox, Edge) or one of the browsers defined in settings.json5.
Note
The browser names are case-senstive!
The optional left argument is a boolean indicating if we're re-initialising the browser - it saves the time required to setup of the NuGet components and simply re-initializes the browser driver. This can be useful when the browser was closed.
InitBrowser creates an object BROWSER in the Selenium namespace which is used for further interaction with the browser. BROWSER is an instance of the Selenium WebDriver linked to your specific browser's .NET driver. It will also create a variable CURRENTBROWSER with the name of the driver instantiated in BROWSER and ACTIONS which is a reference to an instance of Selenium.Interactions.Actions, used to automate mouse movements.
{r}←obj SendKeys keys
The SendKeys function sends they keystrokes given in the right argument keys to the object obj.
Argument obj
The target object can either be passed as a previously found object or it can be the right argument of the Find function.
Argument keys
The right argument specifies the keystrokes that are to be sent as a vector.
To send special keys like Escape, Shift or others, use the BROWSER.Keys enumeration and pass it as a nested element optionally joined by the basic key.
Example: Send the keystrokes Ctrl+A
SendKeys ⊂BROWSER.Keys.Ctrl,'A'
Example: Send the keystrokes Shift+h followed by "ello" to enter "Hello"
SendKeys (BROWSER.Keys.Ctrl,'h'),'ello'
Note
- If you want to test input controls, it is recommended to use SetInputValue to set their values instead, as that function will also pass unicode values reliably without requiring keystroke translation.
- It is also worth noting that the underlying Selenium function
SendKeyssupports extended features such as "unicode keystrokes" in varying degress on different browsers: while sending the keystroke'≢'to Chrome will cause the symbol≢to appear in the control, CEF (as used in Dyalog's HtmlRenderer object) does not support that. For this reason SetInputValue or the Monaco tools are recommended when you want to simulate complex input. - Another point to consider is that the drivers may implement specific keyboard layouts, so while you think you were sending a 'Y', the browser may receive a 'Z' on a QWERTZ layout.
- Please be careful when writing cross-platform (and/or cross-browser) tests: we noticed that
<select>controls respond differently to keystrokes on windows or MacOS. To manipulate these, it is recommended to use Select or SelectItemText.
Complete list of key names
Add
Alt
ArrowDown
ArrowLeft
ArrowRight
ArrowUp
Backspace
Cancel
Clear
Command
Control
Decimal
Delete
Divide
Down
End
Enter
Equal
Escape
F1
F10
F11
F12
F2
F3
F4
F5
F6
F7
F8
F9
Help
Home
Insert
Left
LeftAlt
LeftControl
LeftShift
Meta
Multiply
Null
NumberPad0
NumberPad1
NumberPad2
NumberPad3
NumberPad4
NumberPad5
NumberPad6
NumberPad7
NumberPad8
NumberPad9
PageDown
PageUp
Pause
Return
Right
Semicolon
Separator
Shift
Space
Subtract
Tab
Up
ZenkakuHankaku
Wait msec
This function waits for msec miliseconds.
Additional Information
Terminology
Note
It is very likely that this page contains words that would benefit from a deeper discussion. If you find any, please feel free to submit a pull request or let us know about it by creating an issue on our GitHub site.
CEF
CEF (Chromium Embedded Framework) is an open-source framework that allows developers to embed a Chromium-based web browser within desktop applications. It provides a simple API for integrating modern web technologies and rendering web content inside native applications.It is used inside Dyalog to create the HtmlRenderer component and also inside the Ride client.
Monaco Editor
Monaco Editor is the code editor that powers Visual Studio Code.
It is a web-based, extensible editor written in TypeScript, designed to provide a rich editing experience in browsers and desktop applications. Monaco offers features such as syntax highlighting, IntelliSense, code folding, and multi-language support.
It can be embedded into web applications or frameworks like Electron or CEF to provide a VS Code-like editing environment.
FAQ
Where do we live?
Determining a function's home folder (that it was loaded from) is a popular question in times of text-based code. Selenium needs to answer it so that it knows where to look for its settings file (settings.json5).
Because Selenium can be brought into the WS in a variety of ways (recommended: using Tatin) it tries to handle this using several alternative techniques:
- look for an environment variable
SELENIUM_HOME - use
50[⎕ATX](https://docs.dyalog.com/20.0/language-reference-guide/system-functions/atx/) - look for a
SALT_Datanamespace in the Selenium ns (as]Loadwould create)
If one of these approaches gives us a valid path, it is assigned to the variable SELENIUM_HOME which is used to answer the question the next time it is asked.
Therefore, depending on the setup in which you use Selenium, you can also initialise Selenium.SELENIUM_HOME with the path to settings.json5 or rely on Tatin or Link and not worry about it all.
Hint
Please note that SELENIUM_HOME points to Selenium's base folder (the folder that contains the APLSource directory with the individual functions.)
Release notes
2025 10 29 MBaas, V3.0.0 - major update
- in addition to Chrome, we now also support Firefox and Edge browsers
- Selenium should work with the browser version that's installed
- without any tweaking of settings...
- uses Cider & Tatin
- converted doc to be MkDocs based, updated it.
- converted from scripted ns to individual files for the functions
- GoTo now also supports file:// URLs
- "WaitFor" was not working as expected with textarea-elements
- remove ⍺ from InitBrowser
- added "End" method to dispose .NET artifacts from memory and disc
- ExecuteScript now supports passing arguments to the js code (in ⍺) - see SetInputValue for an example
- SetInputValue passes values with JS which means that even APL characters can be set
- SendKeys simplified internally thanks to recent changes in Selenium and WebDriver4
- MoveToElement did no work as expected when only ⍵[1] was given - fixed
- new functions Savescreenshot and CompareScreenshots to deal with screenshots
- function GetLogs can be used to access the browser logs (though it wasn't working reliably in our tests - it sometimes does not get logs though are some to be gotten.)
- Select has been significantly improved and offers more options to identify the target element
- updated Ride tests and implemented tools to support testing against its Monaco editor
- changed major version to 3.0.0 (there were previous releases with a version number mentioning > 2.0)
BREAKING CHANGES:
- Dyalog no longer distributes the Syncfusion controls, so the utilities dealing with them (
ejAccordionTabandListMgrSelect) were removed.
2024 05 08 MBaas
- optional ⍺ to ReInitBrowser to support re-initialization of the browser (useful to reconnect when HtmlRenderer was closed and re-opened)
- *SetInputValue did not work as expected - fixed. (NB: it now clears the control before setting text)
2024 04 15 MBaas
- replaced BHC's NugetConsum with the Dyalog's "officiat" NuGet package (this should really be a Tatin dependency and will soon be changed)
- enhanced WaitFor to not only WaitFor a text to appear but also to wait for the element itself to appear
- No other outstanding items atm...(more testing needed)
- Not yet cross-platform (BHC is on it...)
NB: if you previously did ApplySettings 'foo' followed by InitBrowser'', you should now combine these calls with InitBrowser 'foo'
2021 02 22 MBaas: MAJOR UPDATE (branch WebDriver4)
- now using WebDriver4 and NuGet. This removes the need to distribute DLLs. (started development on branch "WebDriver4")
- removed all paths from settings.json
-
- we're aiming to keep all changes "under the cover". Selenium should behave as before.
2021 11 15 MBaas:
- settings.json may now use "SELENIUM_DRIVERPATH" to point to the folder with the downloaded drivers -this way it becomes more generally usable and less platform-dependent.
- Changed to semantic versioning.
- Settings file now uses JSON5.
2020 07 11 MBaas
- lots of changes to make it working on ALL platforms
- revised structure of settings (AND names of parameter DRIVERS → DRIVER)
2020 05 08 MBaas
- preparing for cross-platformness;
- new folder-structure for drivers
2020 02 12 MBaas
- updated to use a config (.json)-file to facilitate testing with various browsers (incl. HTMLRenderer)
2017 05 23 Adam
- now gives helpful messages for DLL problems, harmonised ADOC utils
2017 05 09 Adam
- Version info added