├── APLSource ├── isChar.aplf ├── Split.aplf ├── lc.aplf ├── Wait.aplf ├── V │ ├── Char.aplf │ ├── String.aplf │ ├── Numeric.aplf │ ├── readme.md │ ├── GET.aplf │ ├── SET.aplf │ ├── ∆NV.aplf │ ├── ∆NS.aplf │ └── ∆NG.aplf ├── Nested.aplf ├── NlFmt.aplf ├── PathOf.aplf ├── eis.aplf ├── ride │ ├── Gives.aplf │ ├── Ed.aplf │ ├── Tb.aplf │ ├── Session.aplf │ ├── Se.aplf │ ├── Cache │ │ └── foo.aplf │ ├── Do.aplf │ ├── Fix.aplf │ ├── LastIs.aplf │ ├── Lb.aplf │ ├── EdRef.aplf │ ├── EndsWith.aplf │ ├── Has.aplf │ ├── SeRef.aplf │ ├── MonacoInput.aplf │ ├── MonacoEnter.aplf │ └── RideScript.aplf ├── Files.aplf ├── DefaultTo.aplf ├── UrlIs.aplf ├── ClearInput.aplf ├── Folders.aplf ├── InitOptions.aplf ├── List.aplf ├── SaveScreenshot.aplf ├── Version.aplf ├── Urlify.aplf ├── IsVisible.aplf ├── GetSettings.aplf ├── DragAndDrop.aplf ├── DragAndDropToOffset.aplf ├── End.aplf ├── FindListItems.aplf ├── AddVars.aplf ├── Text.aplf ├── Retry.aplf ├── ExecuteScript.aplf ├── ExecuteASyncScript.aplf ├── FindAllFiles.aplf ├── InjectScript.aplf ├── SendKeys.aplf ├── Click.aplf ├── SetInputValue.aplf ├── SelectItemText.aplf ├── Init.aplf ├── MergeNS.aplf ├── Add2InputValue.aplf ├── MoveToElement.aplf ├── CompareScreenshots.aplf ├── _HOME.aplf ├── ApplySettings.aplf ├── Run1Test.aplf ├── Test.aplf ├── GoTo.aplf ├── RunAllTests.aplf ├── GetLogs.aplf ├── WaitFor.aplf ├── Find.aplf ├── ScreenDiffersFrom.aplf ├── Select.aplf ├── Flatten.aplf ├── Version.aplf.bak ├── FindChromeDriver.aplf └── InitBrowser.aplf ├── apl-dependencies.txt ├── deps ├── apl-dependencies.txt └── apl-buildlist.json ├── .gitignore ├── docs ├── Wait.md ├── img │ ├── sample.png │ ├── favicon-32.png │ └── dyalog-white.svg ├── FindListItems.md ├── End.md ├── screenshots.md ├── index.md ├── install-selenium.md ├── MonacoTools.md ├── faq.md ├── Find.md ├── other-webdriver-functionality.md ├── terminology.md ├── InitBrowser.md ├── commands-by-category.md ├── ride.md ├── GoTo.md ├── settings.md ├── inspecting-the-contents.md ├── simple-example.md ├── SendKeys.md ├── release-notes.md └── browser-automation.md ├── Samples ├── RIDE │ ├── multiline.dyalog │ ├── tools │ │ ├── factorial.aplf │ │ ├── ReportRidePort.aplf │ │ └── MonacoTools.js │ ├── Local.aplf │ ├── fixfns.dyalog │ ├── subfolder │ │ ├── scriptfile.ridetest │ │ ├── scriptfn.aplf │ │ └── gives.aplf │ ├── longlines.dyalog │ ├── basic.aplf │ └── InitRIDE.aplf ├── TestUppercase.aplf └── text-uppercase.html ├── .gitmodules ├── apl-buildlist.json ├── Tests ├── teardown.aplf ├── selenium-chrome.dyalogtest ├── selenium-htmlrenderer.dyalogtest ├── repro.aplf ├── test_keyboard_monitor.aplf ├── init_selenium_chrome.aplf ├── test_screenshots.aplf ├── test_logs.aplf ├── init_selenium_htmlrenderer.aplf ├── test_input_monitor.aplf ├── kbdm_test.aplf ├── test_interaction.aplf ├── test_find.aplf ├── input_monitor.html ├── keyboard-monitor.html ├── test_find.html └── test_interaction.html ├── README.md ├── apl-package.json ├── LICENSE ├── cider.config ├── settings.json5 └── mkdocs.yml /APLSource/isChar.aplf: -------------------------------------------------------------------------------- 1 | isChar←{0 2∊⍨10|⎕DR ⍵} -------------------------------------------------------------------------------- /apl-dependencies.txt: -------------------------------------------------------------------------------- 1 | dyalog-NuGet-0.2.5 2 | -------------------------------------------------------------------------------- /deps/apl-dependencies.txt: -------------------------------------------------------------------------------- 1 | dyalog-NuGet-0.2.3 2 | -------------------------------------------------------------------------------- /APLSource/Split.aplf: -------------------------------------------------------------------------------- 1 | Split←Split 2 | Split←,⊂⍨⊣=, 3 | -------------------------------------------------------------------------------- /APLSource/lc.aplf: -------------------------------------------------------------------------------- 1 | r←lc R ⍝ lowercase 2 | r←⎕C R 3 | -------------------------------------------------------------------------------- /APLSource/Wait.aplf: -------------------------------------------------------------------------------- 1 | {ok}←Wait msec 2 | ok←×⎕DL msec÷1000 3 | -------------------------------------------------------------------------------- /APLSource/V/Char.aplf: -------------------------------------------------------------------------------- 1 | Char←Char 2 | Char←80 82 160 320∊⍨⎕DR 3 | -------------------------------------------------------------------------------- /APLSource/V/String.aplf: -------------------------------------------------------------------------------- 1 | String←String 2 | String←Char∧1≥∘≢⍴ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | selenium.dws 2 | deps/*.* 3 | venv/* 4 | dyalog-NuGet-0.2.2/* 5 | -------------------------------------------------------------------------------- /docs/Wait.md: -------------------------------------------------------------------------------- 1 | # Wait msec 2 | 3 | This function waits for `msec` miliseconds. -------------------------------------------------------------------------------- /APLSource/Nested.aplf: -------------------------------------------------------------------------------- 1 | Nested←Nested 2 | Nested←{(+/∨\' '≠⌽⍵)↑¨↓⍵}⊢⍴⍨¯2↑1 1,⍴ 3 | -------------------------------------------------------------------------------- /APLSource/NlFmt.aplf: -------------------------------------------------------------------------------- 1 | NlFmt←{1↓∊'¶',¨⍵} ⍝ Convert VTV to ¶-separated simple vector -------------------------------------------------------------------------------- /APLSource/PathOf.aplf: -------------------------------------------------------------------------------- 1 | PathOf←{⍵↓⍨1-⌊/'/\'⍳⍨⌽⍵} ⍝ Extract path from path/filename.ext -------------------------------------------------------------------------------- /APLSource/eis.aplf: -------------------------------------------------------------------------------- 1 | eis←{(≡⍵)∊0 1:,⊂,⍵ ⋄ ⍵} ⍝ Enlose (even scalars) If Simple 2 | -------------------------------------------------------------------------------- /APLSource/ride/Gives.aplf: -------------------------------------------------------------------------------- 1 | Gives←{Do:r←LastIs¨##.eis ⍵⊣Se ⍺ ##.Keys.Enter ⋄ r←1} 2 | -------------------------------------------------------------------------------- /APLSource/Files.aplf: -------------------------------------------------------------------------------- 1 | Files←Files ⍝ Filter for files only 2 | Files←⊃(/⍨∘(2∘=))/ 3 | -------------------------------------------------------------------------------- /APLSource/DefaultTo.aplf: -------------------------------------------------------------------------------- 1 | DefaultTo←{0=⎕NC ⍺:⍎⍺,'←⍵' ⋄ _←⍵} ⍝ set ⍺ to ⍵ if undefined 2 | -------------------------------------------------------------------------------- /APLSource/UrlIs.aplf: -------------------------------------------------------------------------------- 1 | UrlIs←{(⊂BROWSER.Url)∊⍥⎕C⍵(⍵,'/')} ⍝ Is the browser currently at ⍵? 2 | -------------------------------------------------------------------------------- /docs/img/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Selenium/master/docs/img/sample.png -------------------------------------------------------------------------------- /APLSource/ClearInput.aplf: -------------------------------------------------------------------------------- 1 | ClearInput obj;q 2 | :If 0≢q←Find obj 3 | q.Clear 4 | :EndIf 5 | -------------------------------------------------------------------------------- /APLSource/Folders.aplf: -------------------------------------------------------------------------------- 1 | Folders←Folders ⍝ Filter for folders only 2 | Folders←⊃(/⍨∘(1∘=))/ 3 | -------------------------------------------------------------------------------- /APLSource/InitOptions.aplf: -------------------------------------------------------------------------------- 1 | options←InitOptions browser 2 | options←⎕NEW⍎browser,'Options' 3 | -------------------------------------------------------------------------------- /APLSource/List.aplf: -------------------------------------------------------------------------------- 1 | List←List ⍝ List names and types in directory ⍵ 2 | List←0 1 ⎕NINFO⍠1⊢ 3 | -------------------------------------------------------------------------------- /APLSource/V/Numeric.aplf: -------------------------------------------------------------------------------- 1 | Numeric←{(0<≢⍵)∧2|⎕DR⍵} ⍝ test if ⍵ is a numeric value (⍬ isn't!) 2 | -------------------------------------------------------------------------------- /APLSource/SaveScreenshot.aplf: -------------------------------------------------------------------------------- 1 | SaveScreenshot ToFile 2 | BROWSER.GetScreenshot.SaveAsFile⊂ToFile 3 | -------------------------------------------------------------------------------- /APLSource/Version.aplf: -------------------------------------------------------------------------------- 1 | version←Version 2 | ⍝ see docs/release-notes.md for details 3 | version←'3.0.0' -------------------------------------------------------------------------------- /docs/img/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Selenium/master/docs/img/favicon-32.png -------------------------------------------------------------------------------- /APLSource/ride/Ed.aplf: -------------------------------------------------------------------------------- 1 | R←Ed arg 2 | R←{Do:EdRef ##.SendKeys¨⊂⍣(1=≡,⍵)⊢⍵ ⋄ 1}##.Retry arg ⍝ SendKeys in Editor -------------------------------------------------------------------------------- /APLSource/ride/Tb.aplf: -------------------------------------------------------------------------------- 1 | {R}←Tb arg 2 | R←{'ClassName'##.Click'tb_',⍵}##.Retry arg ⍝ click toolbar button 3 | -------------------------------------------------------------------------------- /APLSource/ride/Session.aplf: -------------------------------------------------------------------------------- 1 | r←Session ⍝ Session text 2 | r←(⌷'CssSelectors'##.Find'.view-line > span > span').Text 3 | -------------------------------------------------------------------------------- /Samples/RIDE/multiline.dyalog: -------------------------------------------------------------------------------- 1 | msg←multiline dummy 2 | '∘.∧⍨0 1'Gives↑'0 0' '0 1' 3 | '∘.×⍨⍳10'Gives'90'(↑'81' '100') 4 | -------------------------------------------------------------------------------- /APLSource/Urlify.aplf: -------------------------------------------------------------------------------- 1 | Urlify←{0''⍬∊⍨⊂⍵:∇ PORT ⋄ ⍬≡0⍴⍵:'http://127.0.0.1:',⍕⍵ ⋄ ⍵} ⍝ Ensure URL even if given just port number -------------------------------------------------------------------------------- /Samples/RIDE/tools/factorial.aplf: -------------------------------------------------------------------------------- 1 | factorial←{ ⍝ Tail recursive factorial. 2 | ⍺←1 3 | ⍵=0:⍺ 4 | (⍺×⍵)∇ ⍵-1 5 | } 6 | -------------------------------------------------------------------------------- /APLSource/ride/Se.aplf: -------------------------------------------------------------------------------- 1 | {R}←Se arg 2 | R←{ 3 | Do:SeRef ##.SendKeys¨⊂⍣(1=≡,⍵)⊢⍵ 4 | 0 5 | }##.Retry arg ⍝ SendKeys in Session 6 | -------------------------------------------------------------------------------- /Samples/RIDE/tools/ReportRidePort.aplf: -------------------------------------------------------------------------------- 1 | ReportRidePort nul;file;port 2 | file←1⊃160⌶'rpfile' 3 | port←(497⌶⍬)[1;4] 4 | (⊂⍕port)⎕NPUT file 1 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/style/documentation-styles"] 2 | path = docs/style/documentation-styles 3 | url = https://github.com/Dyalog/documentation-styles 4 | -------------------------------------------------------------------------------- /APLSource/IsVisible.aplf: -------------------------------------------------------------------------------- 1 | IsVisible←{(Find ⍵).Displayed} ⍝ test if given element is Displayed (useful if combined with Retry to wait till control is accessible) -------------------------------------------------------------------------------- /APLSource/ride/Cache/foo.aplf: -------------------------------------------------------------------------------- 1 | foo←{ 2 | 'This is an almost useless placeholder function - but we need some code here to get the cache folder created!' 3 | } 4 | 5 | -------------------------------------------------------------------------------- /APLSource/GetSettings.aplf: -------------------------------------------------------------------------------- 1 | R←GetSettings;v;varnam 2 | {}Init 3 | R←1⊃⎕NGET _HOME,'settings.json5' 4 | R←(⎕JSON⍠'Dialect' 'JSON5')R 5 | R.Browsers←Flatten R.Browsers 6 | -------------------------------------------------------------------------------- /APLSource/ride/Do.aplf: -------------------------------------------------------------------------------- 1 | r←Do ⍝ Enables skipping to end if a problem has already occured 2 | :If 0=⎕NC'msg' 3 | msg←'' 4 | :EndIf 5 | r←''≡msg 6 | -------------------------------------------------------------------------------- /Samples/RIDE/Local.aplf: -------------------------------------------------------------------------------- 1 | path←Local file 2 | ⍝ no idea what the original fn "Local" did - just trying to emulate it in a sensible way 3 | path←S._HOME,'/Samples/RIDE/tools/',file 4 | -------------------------------------------------------------------------------- /APLSource/DragAndDrop.aplf: -------------------------------------------------------------------------------- 1 | {ok}←fromid DragAndDrop toid;from;to 2 | ⍝ Drag and Drop 3 | ok←1 4 | (from to)←Find¨fromid toid 5 | ACTIONS.Reset 6 | (ACTIONS.DragAndDrop from to).Perform 7 | -------------------------------------------------------------------------------- /apl-buildlist.json: -------------------------------------------------------------------------------- 1 | { 2 | packageID: [ 3 | "dyalog-NuGet-0.2.5", 4 | ], 5 | principal: [ 6 | 1, 7 | ], 8 | url: [ 9 | "https://tatin.dev/", 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /deps/apl-buildlist.json: -------------------------------------------------------------------------------- 1 | { 2 | packageID: [ 3 | "dyalog-NuGet-0.2.3", 4 | ], 5 | principal: [ 6 | 1, 7 | ], 8 | url: [ 9 | "https://tatin.dev/", 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /APLSource/DragAndDropToOffset.aplf: -------------------------------------------------------------------------------- 1 | {ok}←fromid DragAndDropToOffset xy;from 2 | ⍝ Drag 3 | ok←1 4 | from←Find fromid 5 | ACTIONS.Reset 6 | (ACTIONS.DragAndDropToOffset from,xy).Build.Perform 7 | -------------------------------------------------------------------------------- /APLSource/ride/Fix.aplf: -------------------------------------------------------------------------------- 1 | Fix←{⍺←2 ⍝ execute ⎕FIX in session and print out result 2 | nul←1 MonacoInput('⎕←⊃',(⍕⍺),'⎕FIX''file://',⍵,'''') 3 | ⍝sink←⎕DL 1 4 | nul←MonacoEnter 1 5 | } 6 | -------------------------------------------------------------------------------- /APLSource/ride/LastIs.aplf: -------------------------------------------------------------------------------- 1 | R←LastIs arg 2 | R←{ 3 | Do:''≡msg,←EndsWith ##.Nested ⍵ 4 | 0 ⍝ 251008, MB: don't pretend "all is ok" if it isn't 5 | }##.Retry arg ⍝ Check last non-empty line in session 6 | -------------------------------------------------------------------------------- /APLSource/End.aplf: -------------------------------------------------------------------------------- 1 | End 2 | :Trap 0 3 | BROWSER.Close 4 | :EndTrap 5 | :Trap 0 6 | BROWSER.Dispose 7 | :EndTrap 8 | :Trap 0 9 | 1 ⎕NDELETE myNuGetFolder 10 | :EndTrap 11 | ⎕EX'myNuGetFolder' 12 | -------------------------------------------------------------------------------- /APLSource/FindListItems.aplf: -------------------------------------------------------------------------------- 1 | r←id FindListItems text;li;ok 2 | ⍝ Find list items with a given text within element with id 3 | (ok li)←{li←⌷'CssSelectors'Find'#',⍵,' li' ⋄ (0≠⍴li)li}Retry id 4 | r←(li.Text∊eis text)/li 5 | -------------------------------------------------------------------------------- /APLSource/AddVars.aplf: -------------------------------------------------------------------------------- 1 | vars←vars AddVars ns;n;i 2 | ⍝ settings manipulation 3 | :For n :In ns.⎕NL-2 4 | :If (≢vars)1000×RETRYLIMIT ⍝ Try for a second 11 | -------------------------------------------------------------------------------- /APLSource/V/readme.md: -------------------------------------------------------------------------------- 1 | # v 2 | 3 | The ns folder contains functions that serve as a compatibility layer dealing with ⎕VGET and ⎕VSET from v20. 4 | v.GET and v.SET provide limited functionality (as required for use in Selenium). They are based on Adam's ∆NG and ∆NS with minor modifications. 5 | -------------------------------------------------------------------------------- /APLSource/ExecuteScript.aplf: -------------------------------------------------------------------------------- 1 | {r}←{args}ExecuteScript script;js 2 | js←(IJavaScriptExecutor)⎕CLASS BROWSER 3 | :If 0=⎕NC'args' 4 | args←⍬ 5 | :Else 6 | :If isChar args 7 | args←,⎕NEW System.String(⊂args) 8 | :EndIf 9 | :EndIf 10 | r←js.ExecuteScript script args 11 | -------------------------------------------------------------------------------- /APLSource/ExecuteASyncScript.aplf: -------------------------------------------------------------------------------- 1 | {r}←{args}ExecuteASyncScript script;js 2 | js←(IJavaScriptExecutor)⎕CLASS BROWSER 3 | :If 0=⎕NC'args' 4 | args←⍬ 5 | :Else 6 | :If isChar args 7 | args←,⎕NEW System.String(⊂args) 8 | :EndIf 9 | :EndIf 10 | r←js.ExecuteASyncScript script args 11 | -------------------------------------------------------------------------------- /APLSource/FindAllFiles.aplf: -------------------------------------------------------------------------------- 1 | r←FindAllFiles root ⍝ Recursive 2 | :If ∨/' '≠root 3 | root,←'/'/⍨~'/\'∊⍨¯1↑root ⍝ append trailing / if missing 4 | r←Files List root,'*',EXT 5 | r,←Files List root,'*',RIDEEXT 6 | r,←⊃,/FindAllFiles¨Folders List root,'*' 7 | :Else 8 | r←0⍴⊂'' 9 | :EndIf 10 | -------------------------------------------------------------------------------- /docs/FindListItems.md: -------------------------------------------------------------------------------- 1 | #ref←id FindListItems text 2 | 3 | Return refs to all list items found within a list with the id given on the left, which have Text properties that can be found in the vector of text vectors on the right. 4 | 5 | !!!note 6 | The given text must completely match the item's text, partial matches are not considered. 7 | -------------------------------------------------------------------------------- /APLSource/InjectScript.aplf: -------------------------------------------------------------------------------- 1 | rc←InjectScript code 2 | ⍝ adds code to a script section in the current page's header 3 | :If ⎕NEXISTS code 4 | code←1⊃⎕NGET code 5 | :EndIf 6 | code ExecuteScript'(function(code){var s = document.createElement("script");s.type = "text/javascript";s.text = code;document.head.appendChild(s);})(arguments[0]);' 7 | -------------------------------------------------------------------------------- /Samples/RIDE/subfolder/scriptfile.ridetest: -------------------------------------------------------------------------------- 1 | )clear: clear ws 2 | var←'Hej Morten' 3 | var: Hej Morten 4 | var←'Hello',3↓var 5 | var: Hello Morten 6 | ∇res←fn;name 7 | name←⍞ 8 | res←'Hello ',name 9 | ∇ 10 | fn 11 | Morten: Hello Morten 12 | ∇fn 13 | [2]'Hej ',name 14 | ∇ 15 | fn 16 | Morten: Hej Morten 17 | ⍝ RegEx: 18 | ?9: $\d$ 19 | ⍝ wait up to 10 seconds: 20 | ⊢⎕DL 6: 6.0: 10 -------------------------------------------------------------------------------- /APLSource/ride/MonacoInput.aplf: -------------------------------------------------------------------------------- 1 | {nul}← idx MonacoInput txt;id;sc;class 2 | ⍝ idx=1: Session 3 | ⍝ 2: Editor 4 | class←idx⊃'.ride_win' '.ride_win_me' 5 | :If 1=≡txt 6 | sc←'MonacoInput(arguments[0],arguments[1]);' 7 | :Else 8 | sc←'MonacoInput(arguments[0],arguments[1],arguments[2]);' 9 | :EndIf 10 | txt←(⊂class),⊆txt 11 | txt ##.ExecuteScript sc 12 | nul←⍬ 13 | -------------------------------------------------------------------------------- /Tests/selenium-chrome.dyalogtest: -------------------------------------------------------------------------------- 1 | DyalogTest : 1.30 2 | ID : selenium-chrome 3 | Description: run tests of Selenium against the installed version of chrome 4 | 5 | Setup : init_selenium_chrome 6 | 7 | Test : test_find 8 | Test : test_input_monitor 9 | Test : test_interaction 10 | Test : test_keyboard_monitor 11 | Test : test_logs 12 | Test : test_screenshots 13 | 14 | Teardown: teardown -------------------------------------------------------------------------------- /APLSource/SendKeys.aplf: -------------------------------------------------------------------------------- 1 | {ok}←obj SendKeys text;q;i;k 2 | ⍝ Send keystrokes - see Keys.⎕NL -2 for special keys like Keys.Enter 3 | ⍝ Note that even 'A' Control 'X' will be interpreted as Ctrl+A,X 4 | ⍝ To get A,Ctrl+X use 'A'(Control,'X') 5 | ok←1 6 | q←Find obj 7 | :If q≢0 ⍝ make sure that we found it! 8 | text←eis text 9 | q.SendKeys¨⊂¨text 10 | ⎕dl .5 ⍝ allow some time to process it... 11 | :EndIf 12 | -------------------------------------------------------------------------------- /Tests/selenium-htmlrenderer.dyalogtest: -------------------------------------------------------------------------------- 1 | DyalogTest : 1.30 2 | ID : selenium-htmlrenderer 3 | Description: run tests of Selenium against Dyalog's HtmlRenderer 4 | 5 | Setup : init_selenium_htmlrenderer 6 | 7 | Test : test_find 8 | Test : test_input_monitor 9 | Test : test_interaction 10 | Test : test_keyboard_monitor 11 | Test : test_logs 12 | Test : test_screenshots 13 | 14 | 15 | Teardown: teardown -------------------------------------------------------------------------------- /APLSource/Click.aplf: -------------------------------------------------------------------------------- 1 | {ok}←{type}Click id;b;ok;time;reTry 2 | ⍝ Click on an element, by default identified by id. See "Find" for options 3 | 'type'DefaultTo'Id' 4 | ok←reTry←0 5 | :Repeat 6 | :Trap 90 7 | b←type Find id 8 | b.Click 9 | ok←1 10 | :Else 11 | reTry+←1 12 | ⎕DL 1 13 | :EndTrap 14 | :Until ok∨reTry>3 15 | ('Control "',id,'" not found')⎕SIGNAL(0≡b)/11 16 | -------------------------------------------------------------------------------- /APLSource/SetInputValue.aplf: -------------------------------------------------------------------------------- 1 | {ok}←id SetInputValue text;s;i;r;obj;js 2 | ⍝ Set the value of an input control to text. 3 | (id obj)←id{2::(⍺)(Find ⍺) ⋄ (⍺.GetAttribute⊂⍵)⍺}'id' ⍝ if "id" is an element, get its "id" Attibute 4 | 5 | obj.Click 6 | 7 | (obj text)ExecuteScript'arguments[0].value = arguments[1];arguments[0].dispatchEvent(new Event("input", { bubbles: true }));arguments[0].dispatchEvent(new Event("change", { bubbles: true }));' 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Selenium QA Tools for Dyalog APL 2 | 3 | This repository contains code which allows Dyalog to automate browsers using [Selenium WebDriver](http://www.seleniumhq.org/). 4 | The new release 3.0.0 is usable on all Windows, macOS and Linux/Unix. 5 | 6 | This version needs at least Dyalog APL v19.0. If you're using Dyalog 18.2, please use the branch "v2.11" (which works on Windows only). 7 | 8 | Please refer to the documentation pages for more information. 9 | -------------------------------------------------------------------------------- /Samples/RIDE/subfolder/scriptfn.aplf: -------------------------------------------------------------------------------- 1 | msg←scriptfn dummy 2 | #.Selenium.ride.RideScript 3 | ⍝ )clear: clear ws 4 | ⍝ var←'Hej Morten' 5 | ⍝ var: Hej Morten 6 | ⍝ var←'Hello',3↓var 7 | ⍝ var: Hello Morten 8 | ⍝ ∇res←fn;name 9 | ⍝ name←⍞ 10 | ⍝ res←'Hello ',name 11 | ⍝ ∇ 12 | ⍝ fn 13 | ⍝ Morten: Hello Morten 14 | ⍝ ∇fn 15 | ⍝ [2]'Hej ',name 16 | ⍝ ∇ 17 | ⍝ fn 18 | ⍝ Morten: Hej Morten 19 | ⍝ ⍝ RegEx: 20 | ⍝ ?9: $\d$ 21 | ⍝ ⍝ wait up to 10 seconds: 22 | ⍝ ⊢⎕DL 6: 6.0: 10 23 | -------------------------------------------------------------------------------- /APLSource/V/GET.aplf: -------------------------------------------------------------------------------- 1 | R←{a}GET w 2 | :If 0=⎕NC'a' 3 | a←⊃⎕RSI 4 | :EndIf 5 | :If 20>2⊃⎕VFI 2↑2⊃'.'⎕WG'APLVersion' 6 | :If Numeric w 7 | R←a ∆NV w 8 | :Else 9 | R←a ∆NG w 10 | :EndIf 11 | :Else 12 | R←a ⎕VGET w 13 | :If Numeric w 14 | :If R≢a ∆NV w ⍝ temporary qa 15 | ∘∘∘ 16 | :EndIf 17 | :Else 18 | :If R≢a ∆NG w 19 | ∘∘∘ 20 | :EndIf 21 | :EndIf 22 | :EndIf 23 | -------------------------------------------------------------------------------- /APLSource/V/SET.aplf: -------------------------------------------------------------------------------- 1 | R←{a}SET w 2 | 3 | :If 20>2⊃⎕VFI 2↑2⊃'.'⎕WG'APLVersion' 4 | :If 0=⎕NC'a' 5 | a←⊃⎕RSI 6 | :EndIf 7 | R←a ∆NS w 8 | :Else 9 | :If 0=⎕NC'a' 10 | R←⎕VSET w 11 | :If (⎕JSON R)≢⎕JSON ∆NS w ⍝ temporary QA 12 | ∘∘∘ 13 | :EndIf 14 | :Else 15 | R←a ⎕VSET w 16 | :If (⎕JSON R)≢⎕JSON a ∆NS w ⍝ temporary QA 17 | ∘∘∘ 18 | :EndIf 19 | :EndIf 20 | :EndIf 21 | -------------------------------------------------------------------------------- /Tests/repro.aplf: -------------------------------------------------------------------------------- 1 | repro nul;z;dir;d 2 | ⎕SE.Tatin.LoadPackages'nuget'# ⍝ ]LOAD if you do not have Tatin 3 | :If ⎕NEXISTS dir←(739⌶0),'/repro-mb-250801/' 4 | ⎕NDELETE dir 5 | :EndIf 6 | ⎕MKDIR dir 7 | 8 | {}NuGet.Setup dir 9 | NuGet.Add dir'WebDriverManager' 10 | ⎕USING←('(includePrimary: 0)'NuGet.Using ¯1↓dir),'WebDriverManager' 'WebDriverManager.DriverConfigs.Impl' 'WebDriverManager.Helpers' 11 | d←⎕NEW DriverManager.SetUpDriver(⎕NEW ChromeConfig) 12 | 13 | ⎕NDELETE dir 14 | -------------------------------------------------------------------------------- /APLSource/SelectItemText.aplf: -------------------------------------------------------------------------------- 1 | {ok}←selectId SelectItemText itemsText;item;se 2 | ⍝ Select items in a select control 3 | ⍝ Each item can be deselected by preceding it with '-'. 4 | ⍝ A single '-' deselects all 5 | ok←1 6 | se←⎕NEW SelectElement,⊂Find selectId 7 | :For item :In eis itemsText 8 | :If item≡'~' 9 | se.DeselectAll 10 | :ElseIf '~'=1↑item 11 | se.DeselectByText⊂1↓item 12 | :Else 13 | se.SelectByText item 1 14 | :EndIf 15 | :EndFor 16 | -------------------------------------------------------------------------------- /APLSource/ride/MonacoEnter.aplf: -------------------------------------------------------------------------------- 1 | {nul}←{opts}MonacoEnter idx;sc;class 2 | ⍝ Sends Enterkey to Monaco Editor 3 | ⍝ idx=1: Session 4 | ⍝ 2: Editor 5 | ⍝ opts: 6 | ⍝ - combo: "plain" | "shift" | "ctrl" | "ctrl-shift" 7 | 8 | class←idx⊃'.ride_win' '.ride_win_me' 9 | :If 0=⎕NC'opts' 10 | sc←'EnterKey("',class,'");' 11 | :Else 12 | :If 9=⎕NC'opts' 13 | opts←⎕JSON opts 14 | :EndIf 15 | sc←'EnterKey("',class,'",',opts,');' 16 | :EndIf 17 | ##.ExecuteScript sc 18 | nul←⍬ 19 | -------------------------------------------------------------------------------- /APLSource/Init.aplf: -------------------------------------------------------------------------------- 1 | (R msg)←Init 2 | ⍝ R=0: no error initialising Selenium 3 | ⍝ otherwise error code & message 4 | ⍝ (not yet used, but might be one day...) 5 | R←0 6 | msg←'' 7 | 8 | :If 2=⎕NC'__initialised' 9 | :AndIf __initialised 10 | msg←'Selenium already initialised!' 11 | →0 12 | :EndIf 13 | ⎕WX←3 14 | 15 | RETRYLIMIT←DEFAULTRETRYLIMIT←5 ⍝ seconds 16 | HOME←'' ⍝ the home folder of this ns in the filesystem 17 | 18 | EXT←'.dyalog' 19 | RIDEEXT←'.ridetest' 20 | 21 | __initialised←1 22 | -------------------------------------------------------------------------------- /Tests/test_keyboard_monitor.aplf: -------------------------------------------------------------------------------- 1 | r←test_keyboard_monitor nul;z;ctl;allKeys;log;clb;symbols;lf 2 | ⍝ 3 | r←'' 4 | allKeys←{nl←⍵.⎕NL ¯2 ⋄ nl,[1.5]⍵⍎¨⍵.⎕NL ¯2}#.S.Keys 5 | lf←⎕UCS 10 6 | #.S.GoTo'file://',path,'Tests/keyboard-monitor.html' 7 | ctl←#.S.Find'inputField' 8 | log←#.S.Find'outputLog' 9 | clb←#.S.Find'clearLogButton' 10 | 11 | kbdm_test'ABC',⎕D,'abc' ⍝ avoid YZ! (see -md for SendKeys 12 | kbdm_test⊂#.S.Keys.(Shift,Enter) 13 | kbdm_test⊂#.S.Keys.Shift,'a' 14 | kbdm_test #.S.Keys.F2 15 | kbdm_test⊂#.S.Keys.(Shift,F3) 16 | -------------------------------------------------------------------------------- /APLSource/MergeNS.aplf: -------------------------------------------------------------------------------- 1 | Y←X MergeNS Y;nl;n1;n2;n;n3 2 | :If 19<2 1⊃'.'⎕VFI 2⊃# ⎕WG'APLVersion' 3 | Y←X ⎕NS Y 4 | →0 5 | :EndIf 6 | 7 | :If 0=≢nl←(Y.⎕NL ¯9)∪X.⎕NL ¯9 8 | {}'Y'⎕NS X 9 | →0 10 | :EndIf 11 | :For n :In nl 12 | n1←⎕JSON ⎕JSON X{6::⎕NS'' ⋄ ⍺⍎⍵}n 13 | n2←⎕JSON ⎕JSON Y{6::⎕NS'' ⋄ ⍺⍎⍵}n 14 | n3←n1 MergeNS n2 15 | ⍎'Y.',n,'←⎕JSON ⎕JSON n3' 16 | :EndFor 17 | :If 0<≢nl←(Y.⎕NL ¯2)∪X.⎕NL ¯2 18 | :For n :In nl 19 | :If 0=Y.⎕NC n 20 | ⍎'Y.',n,'←X.',n 21 | :EndIf 22 | :EndFor 23 | :EndIf 24 | -------------------------------------------------------------------------------- /Samples/RIDE/subfolder/gives.aplf: -------------------------------------------------------------------------------- 1 | msg←gives dummy 2 | ')clear'Gives'clear ws' 3 | Se'var←''Hej Morten'''Keys.Enter 4 | 'var'Gives'Hej Morten' 5 | Se'var←''Hello'',3↓var'Keys.Enter 6 | 'var'Gives'Hello John' 7 | Se'∇res←fn;name'Keys.Enter 8 | Se'name←⍞'Keys.Enter 9 | Se'res←''Hello '',name'Keys.Enter 10 | Se'∇'Keys.Enter 11 | Se'fn'Keys.Enter 12 | 'Morten'Gives'Hello Morten' 13 | Se'∇fn[2]''Hej '',name∇'Keys.Enter 14 | Se'fn'Keys.Enter 15 | 'Morten'Gives'Hej Morten' 16 | ⍝ RegEx: 17 | '?9'Gives'$\d$' 18 | ⍝ wait up to 10 seconds: 19 | '⊢⎕DL 6'Gives'6.0'⊣RETRYTIMEOUT←10 20 | -------------------------------------------------------------------------------- /APLSource/Add2InputValue.aplf: -------------------------------------------------------------------------------- 1 | {ok}←id Add2InputValue(pre post);s;i;r;obj;js 2 | ⍝ Modify the value of an input control to text by prefixing and/or appending some text (pass '' to not use relevant option) 3 | (id obj)←id{2::(⍺)(Find ⍺) ⋄ (⍺.GetAttribute⊂⍵)⍺}'id' ⍝ if "id" is an element, get its "id" Attibute 4 | 5 | obj.Click 6 | (pre post)←{0<≢⍵:⍵⋄''}¨pre post ⍝ transform ⍬ to '' 7 | (obj pre post)ExecuteScript'arguments[0].value = arguments[1] + arguments[0].value + arguments[2];arguments[0].dispatchEvent(new Event("input", { bubbles: true }));arguments[0].dispatchEvent(new Event("change", { bubbles: true }));' 8 | -------------------------------------------------------------------------------- /APLSource/MoveToElement.aplf: -------------------------------------------------------------------------------- 1 | {ok}←{action}MoveToElement args;id;target 2 | ⍝ Move to element with optional x & y offsets 3 | ⍝ And perform optional action (Click|ClickAndHold|ContextClick|DoubleClick) 4 | ok←1 5 | args←eis args 6 | (⊃args)←Find⊃args ⍝ Elements [2 3] optional x & y offsets (integers) 7 | ACTIONS.Reset 8 | (ACTIONS.MoveToElement args).Build.Perform 9 | ⎕DL 0.1 10 | :If 2=⎕NC'action' 11 | :If (⊂action)∊'Click' 'ClickAndHold' 'ContextClick' 'DoubleClick' 12 | ((ACTIONS⍎action)⍬).Build.Perform 13 | :Else 14 | ('Unsupported action: ',action)⎕SIGNAL 11 15 | :EndIf 16 | :EndIf 17 | -------------------------------------------------------------------------------- /Tests/init_selenium_chrome.aplf: -------------------------------------------------------------------------------- 1 | r←init_selenium_chrome nul;z;arg;⎕TRAP 2 | ⍝ Initialize Selenium with Chrome driver 3 | path←1⊃⎕NPARTS{(-'/\'∊⍨⊢/⍵)↓⍵}##.TESTSOURCE ⍝ needs to be global so that tests can find resources 4 | arg←⎕NS'' 5 | arg.folder←path 6 | arg.batch←1 7 | 8 | :If ~⊃r←⎕SE.Cider.OpenProject ⎕SE.Cider.CreateOpenParms arg ⍝ open project using Cider 9 | r←2⊃r 10 | →0 11 | :EndIf 12 | 13 | #.S←#.Selenium 14 | :If ⊃r←#.S.InitBrowser'Chrome' 80 ⍝ if there was an error initialising the browser 15 | r←2⊃r ⍝ report it... 16 | :Else 17 | r←'' 18 | :EndIf 19 | -------------------------------------------------------------------------------- /Samples/TestUppercase.aplf: -------------------------------------------------------------------------------- 1 | r←TestUppercase nul;S;path;demoText;expect;errMsg;file 2 | path←1⊃1 ⎕NPARTS{6::50 ⎕ATX ⍵ ⋄ SALT_Data.SourceFile}1⊃⎕SI ⍝ where does this file come from? 3 | 11 ⎕SIGNAL(0=≢path)/'Unable to determine source path' 4 | file←path,'text-uppercase.html' 5 | 11 ⎕SIGNAL(≡⎕NEXISTS file)/'File "',file,'" does not exist!' 6 | demoText←'Hello World!' 7 | expect←1 ⎕C demoText 8 | errMsg←'Did not find expected output!' 9 | S←##.Selenium 10 | S.InitBrowser'Chrome' 11 | S.GoTo'file:///',file 12 | 'inputText'S.SendKeys demoText 13 | (S.Find'transformButton').Click 14 | r←'outputText'S.WaitFor expect errMsg 15 | S.End -------------------------------------------------------------------------------- /APLSource/V/∆NV.aplf: -------------------------------------------------------------------------------- 1 | ∆NV←{ ⍝ ⎕NV ─ Name-Values 2 | ⍝ LEFT ARGUMENT: target namespace (default is calling space) 3 | ⍝ RIGHT ARGUMENT: like ⎕NL's 4 | ⍝ RESULT: 5 | ⍝ vector of name-value pairs if any element of right argument is negative 6 | ⍝ 2-element vector of names matrix and value vector otherwise 7 | 8 | ⎕ML←1 9 | ⍺←⊃⎕RSI 10 | target←⍺⍎'⎕THIS' ⍝ ensure ref 11 | nl←target.⎕NL ⍵ 12 | ∨/bad←3 4∊⍨target.⎕NC↑nl:⎕SIGNAL⊂('EN' 2)('Message'('Invalid value: ',' '~⍨⊃nl⌷⍨1⍳⍨bad)) 13 | nl≡0⍴⊂'':0⍴⊂'' 0 ⍝ no elements 14 | nl≡0 0⍴'':nl ⍬ ⍝ no rows 15 | ∨/0>⍵:target.{⍵(⍎⍵)}¨nl 16 | target.{⍵(⍎¨↓⍵)}nl 17 | } 18 | -------------------------------------------------------------------------------- /docs/End.md: -------------------------------------------------------------------------------- 1 | # End 2 | 3 | 4 | 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. 5 | 6 | As the code uses NuGet, [`InitBrowser`](InitBrowser.md) 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.) 7 | -------------------------------------------------------------------------------- /Tests/test_screenshots.aplf: -------------------------------------------------------------------------------- 1 | r←test_screenshots nul;ctl;file2;file 2 | 3 | r←'' 4 | 3 ⎕NDELETE path,'screenshot.png' 5 | #.S.GoTo'file://',path,'Tests/test_interaction.html' 6 | 7 | ⍝ 1. Take a screenshot and check it is there... 8 | #.S.SaveScreenshot file←path,'screenshot.png' 9 | 1 Assert ⎕NEXISTS file 10 | 11 | ⍝ 2. Take a 2nd and compare it with the first one 12 | #.S.SaveScreenshot file2←path,'screenshot2.png' 13 | 0 Assert file #.S.CompareScreenshots file2 14 | 15 | 'clearInput'#.S.SetInputValue'⍳3' 16 | 3 ⎕NDELETE file2 17 | #.S.SaveScreenshot file2←path,'screenshot2.png' 18 | 1 Assert file #.S.CompareScreenshots file2 19 | 20 | 3 ⎕NDELETE¨file file2 ((∊2↑⎕nparts file2),'-diff.png') 21 | -------------------------------------------------------------------------------- /docs/screenshots.md: -------------------------------------------------------------------------------- 1 | # Dealing with Screenshots 2 | 3 | Screenshots can be useful in testing as they can help to ensure proper rendering and identify regressions over time (when comparing screenshots from different runs). 4 | 5 | ## SaveScreenshot filename 6 | 7 | 8 | This function saves a screenshot to the file that you specified in the argument. The file should have a PNG extension. 9 | 10 | ## z←ref CompareScreenshots file 11 | 12 | 13 | This function compares the screenshots in the two files given in the left and right argument. If they are the same, z←0. If they differ, z←1 and a file indicating the differences will be created (named like file2 with a suffix "-diff"). -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Driving Selenium from Dyalog APL 2 | 3 | [Selenium](http://seleniumhq.org) is a widely used open-source tool for automating browsers, with growing support from browser vendors. The GitHub repository [Dyalog/Selenium](https://github.com/Dyalog/Selenium) contains code which allows Dyalog applications to drive browsers via Selenium. 4 | 5 | The namespace Selenium contained in this repository contains cover-functions for some of the most frequently used features of Selenium. 6 | 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! 7 | -------------------------------------------------------------------------------- /APLSource/CompareScreenshots.aplf: -------------------------------------------------------------------------------- 1 | z←ref CompareScreenshots file;bool;fileStreamDifferenceMask;maskImage;encodedData 2 | ⍝ z=0 Identical 3 | ⍝ z=1 different 4 | 5 | bool←Codeuctivity.ImageSharpCompare.ImageSharpCompare.ImagesAreEqual ref file #.S.Codeuctivity.ImageSharpCompare.ResizeOption.DontResize 0 6 | 7 | :If bool 8 | z←0 9 | :Else 10 | z←1 11 | fileStreamDifferenceMask←System.IO.File.Create⊂∊(2↑⎕NPARTS file),'-diff.png' 12 | :Trap 90 ⍝ catch execptions 13 | maskImage←Codeuctivity.ImageSharpCompare.ImageSharpCompare.CalcDiffMaskImage file ref #.S.Codeuctivity.ImageSharpCompare.ResizeOption.Resize 0 14 | maskImage.Save fileStreamDifferenceMask(⎕NEW SixLabors.ImageSharp.Formats.Png.PngEncoder) 15 | :EndTrap 16 | fileStreamDifferenceMask.Close 17 | :EndIf 18 | -------------------------------------------------------------------------------- /apl-package.json: -------------------------------------------------------------------------------- 1 | { 2 | api: "", 3 | assets: "", 4 | description: "Testing Web Applications with Dyalog APL using Selenium ", // better ideas anyone? 5 | documentation: "", 6 | files: "LICENSE.md,SETTINGS.json5",// should Cider files be included? 7 | group: "Dyalog", 8 | io: 1, 9 | license: "MIT", 10 | lx: "", 11 | maintainer: "support@dyalog.com", // or mbaas? 12 | minimumAplVersion: "19.0", // test use Cider to load the package so we need at least 19.0 13 | ml: 1, 14 | name: "Selenium", 15 | os_lin: 1, 16 | os_mac: 1, 17 | os_win: 1, 18 | project_url: "https://github.com/Dyalog/Selenium", 19 | source: "APLSource/", 20 | tags: "test-framework,testing,web,http,run-applications,test,tools,website",// used the list of existing tags 21 | userCommandScript: "", 22 | version: "2.0.0+0", 23 | } 24 | -------------------------------------------------------------------------------- /APLSource/_HOME.aplf: -------------------------------------------------------------------------------- 1 | home←_HOME;src 2 | :If 2=⎕NC'SELENIUM_HOME' 3 | :AndIf 0<≢SELENIUM_HOME 4 | home←SELENIUM_HOME 5 | :Else 6 | :If 0=≢src←2 ⎕NQ'.' 'GetEnvironment' 'SELENIUM_HOME' 7 | :If 0=≢src←50 ⎕ATX 1⊃⎕SI ⍝ loaded with 2⎕FIX or ]Get etc. 8 | :AndIf 0=≢src←{l←⍵[≢⍵;] ⋄ '⍝∇⍣§'≢4↑l:'' ⋄ 1↓(1=+\l='§')/l}⎕CR 1⊃⎕SI ⍝ if ]LOADed 9 | :AndIf 0=≢src←{0::'' ⋄ ⍵.SALT_Data.SourceFile}⎕THIS 10 | 'Unable to determine home directory and HOME is emppty!'⎕SIGNAL 11 11 | :EndIf 12 | :If ⎕NEXISTS src 13 | src←1⊃⎕NPARTS ¯1↓1⊃⎕NPARTS src 14 | :Else 15 | ('Unable to determine home directory as "',src,'" does not exist!')⎕SIGNAL 11 16 | :EndIf 17 | :EndIf 18 | home←SELENIUM_HOME←src 19 | :EndIf 20 | -------------------------------------------------------------------------------- /Tests/test_logs.aplf: -------------------------------------------------------------------------------- 1 | r←test_logs nul;z;txt;l 2 | r←'' 3 | ⎕←'Warning: test_logs is disabled! (The GetLogs function of WebDriver4 is said to be chronically unreliable and we saw it working and failing...)' 4 | →0 5 | 6 | ⍝ The following C# Code is supposed to use Chrome DevTools Protocol (CDP) which is more reliable. 7 | ⍝ I could not translate that to APL. 8 | ⍝ var devTools = driver as IDevTools; 9 | ⍝ var session = devTools.GetDevToolsSession(); 10 | ⍝ session.Domains.Log.Enable(); 11 | ⍝ session.Domains.Log.EntryAdded += (sender, e) => { 12 | ⍝ Console.WriteLine($"[BrowserLog] {e.Entry.Text}"); 13 | ⍝ }; 14 | txt←'Hello world!' 15 | #.S.ExecuteScript'console.log("',txt,'");' 16 | l←#.S.(GetLogs BROWSER.LogType.Browser) 17 | 2 Assert≢l ⍝ check if we have two entries 18 | 1 Assert(∨/('"',txt,'"')⍷2⊃2↑l) ⍝ and if the 2nd contains the text we logged 19 | -------------------------------------------------------------------------------- /APLSource/ApplySettings.aplf: -------------------------------------------------------------------------------- 1 | ApplySettings name;settings;ref;go;rarg;nv;s2;n;ns∆;nv_;ns1;ns2;i 2 | ⍝ ⍵ is (name of browser)[port] 3 | ⍝ or a namespace with the same content 4 | 5 | settings←GetSettings 6 | :If (0 2∊⍨10|⎕DR)⊃name ⍝ browser[port] 7 | :AndIf 0<≢name 8 | rarg←name 9 | name←⎕NS'' 10 | name.Browser←⊃⊆rarg 11 | :If 2=≢rarg 12 | :AndIf (2|⎕DR)2⊃rarg 13 | name.Port←2⊃rarg 14 | :EndIf 15 | :EndIf 16 | 17 | 18 | :If 0<≢name 19 | SETTINGS←settings MergeNS name 20 | :EndIf 21 | 22 | ref←{ 23 | 6::'' 24 | ⍵.Browsers⍎⍵.Browser 25 | }SETTINGS 26 | :If ref≡'' 27 | ('Settings "',SETTINGS.Browser,'" not found!')⎕SIGNAL 11 28 | :EndIf 29 | SETTINGS.Browser←⎕JSON ⎕JSON ref 30 | BROWSEROPTIONS←⍬ ⍝ no options found... 31 | :Select ⍬⍴ref.⎕NC'Options' 32 | :CaseList 2 9 ⋄ BROWSEROPTIONS←ref.Options 33 | :EndSelect 34 | 35 | ⎕USING←'System' 36 | -------------------------------------------------------------------------------- /APLSource/Run1Test.aplf: -------------------------------------------------------------------------------- 1 | r←stop_site_match Run1Test(path file);name;Test;stop;site;match 2 | stop site match←3↑stop_site_match 3 | site←Urlify site 4 | {~UrlIs ⍵:GoTo ⍵}site,'/',match/file↓⍨¯7×EXT≡¯7↑file 5 | :Select ⊃⌽'.'Split file 6 | :Case RIDEEXT 7 | name←⎕FX'msg←Test dummy' 'RideScript','⍝',¨⊃⎕NGET(path,file)1 ⍝ Create Test function from raw RIDE script 8 | :Case EXT 9 | name←⎕SE.SALT.Load path,file 10 | :EndSelect 11 | :If ×⎕NC'name' 12 | :AndIf 'Test'≡name 13 | :Trap 0/⍨0=stop 14 | 'Test'⎕STOP⍨1/⍨2=stop ⍝ stop on line 1 if stop=2 15 | r←Test ⍬ 16 | :If stop⌊×≢r 17 | ⎕←'test for ',file,' failed:' 18 | ⎕←r 19 | ⎕←' Test ⍬ ⍝ to Rerun' 20 | ⎕←' →⎕LC ⍝ to continue' 21 | (⎕LC[1]+1)⎕STOP 1⊃⎕SI 22 | :EndIf 23 | :Else 24 | r←'Trapped error: ',,⍕⎕DMX.EN 25 | :EndTrap 26 | :EndIf 27 | -------------------------------------------------------------------------------- /Tests/init_selenium_htmlrenderer.aplf: -------------------------------------------------------------------------------- 1 | r←init_selenium_htmlrenderer nul;z;arg;cmd 2 | ⍝ Initialize Selenium with Chrome driver to control an HtmlRenderer that we will also launch 3 | path←1⊃⎕NPARTS{(-'/\'∊⍨⊢/⍵)↓⍵}##.TESTSOURCE ⍝ needs to be global so that tests can find resources 4 | arg←⎕NS'' 5 | arg.folder←path 6 | arg.batch←1 7 | :If ~⊃r←⎕SE.Cider.OpenProject ⎕SE.Cider.CreateOpenParms arg ⍝ open project using Cider 8 | r←2⊃r 9 | →0 10 | :EndIf 11 | 12 | ⍝ kick off separate interpreter with an HtmlRenderer 13 | {}⎕SE.Link.Import #'APLProcess' 14 | cmd←'LX="''h''⎕WC''HTMLRenderer''⋄h.ShowDevTools 1" -cef --remote-debugging-port=8080' 15 | PROCESS←⎕NEW #.APLProcess(''cmd) 16 | 17 | #.S←#.Selenium 18 | :If ⊃r←#.S.InitBrowser'HtmlRenderer' 8080 ⍝ if there was an error initialising the browser 19 | ∘∘∘ 20 | r←2⊃r ⍝ report it... 21 | :Else 22 | r←'' 23 | :EndIf 24 | -------------------------------------------------------------------------------- /APLSource/Test.aplf: -------------------------------------------------------------------------------- 1 | failed←{stop_site_match_config}Test path_filter;⎕USING;stop;match;site 2 | ⍝ stop: 0 (default) ignore but report errors; 1 stop on error; 2 stop before every test 3 | ⍝ site: port number (default is PORT) or URL 4 | ⍝ match: 0 (default) run all tests on the baseURL; 1 run tests on baseURL matching dir struct 5 | ⍝ config: points to an entry of your settings.json5 6 | 'stop_site_match_config'DefaultTo 0 7 | :If isChar stop_site_match_config ⍝ handle mode where just the name of a config is given 8 | stop_site_match_config←0 0 0,⊂stop_site_match_config 9 | :EndIf 10 | :If 0<≢4⊃4↑stop_site_match_config 11 | :AndIf 0<≢4⊃stop_site_match_config 12 | ApplySettings 4⊃stop_site_match_config 13 | :EndIf 14 | InitBrowser'' 15 | (⍎,∘'←∊Keys.(',,∘')')⍕Keys.⎕NL ¯2 ⍝ Localize non-alphanumeric key names for easy access 16 | failed←({3<≢⍵:3↑⍵ ⋄ ⍵}stop_site_match_config)RunAllTests path_filter 17 | BROWSER.Quit 18 | -------------------------------------------------------------------------------- /Samples/RIDE/longlines.dyalog: -------------------------------------------------------------------------------- 1 | msg←longlines dummy 2 | :If 1 3 | Se')clear'S.Keys.Enter 4 | :AndIf LastIs'clear ws' 5 | Se'var←''Hej Morten'''S.Keys.Enter'var'S.Keys.Enter 6 | :AndIf LastIs'Hej Morten' 7 | Se'var'S.Keys.(⊂Shift,Enter) 8 | Ed S.Keys.(Home,(3/Delete)),'Hello'S.Keys.Escape 9 | Se S.Keys.Enter 10 | :AndIf LastIs'Hello Morten' 11 | Se'fn'S.Keys.(⊂Shift,Enter) 12 | Ed';name'S.Keys.Home 13 | 2 MonacoInput'res←' ⍝ APL chararacters should not be entered with SendKeys (as used in Se) 14 | Ed S.Keys.Down 15 | 2 MonacoInput 'name←⍞' 16 | Ed S.Keys.Enter 17 | 2 MonacoInput'res←''Hello '',name' 18 | Es S.Keys.Escape 19 | Se S.Keys.Enter'Morten'S.Keys.Enter 20 | :AndIf LastIs'Hello Morten' 21 | Se'fn'S.Keys.(⊂Control,Enter) 22 | Ed S.Keys.(End(Shift,Enter)Down Left,(5/⊂Shift Right),'Hej'Escape) 23 | Tb'RM' 24 | Se'Morten'S.Keys.Enter 25 | :AndIf LastIs'Hej Morten' 26 | :EndIf -------------------------------------------------------------------------------- /docs/install-selenium.md: -------------------------------------------------------------------------------- 1 | # Installing Selenium 2 | 3 | Selenium is available as a [Tatin](http://tatin.dev) package. 4 | 5 | ### when using Tatin 6 | 7 | First, use `]TATIN.ListPackages [tatin] -tags=selenium` to find out which versions are available. 8 | ***to be continued*** 9 | 10 | ### when not using Tatin (and still using it) 11 | 12 | Even when not using Tatin (in the code you write and want to deploy) you probably have Tatin installed 13 | 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 #. 14 | 15 | ### without Tatin 16 | 17 | You'll need the Selenium namespace as well as NuGet. There are infinite ways to get them, such as 18 | 19 | * cloning the repositories 20 | * downloading the required files from the home repositories 21 | * and more. 22 | 23 | We'll trust you'll have your own methods to get the files. 24 | -------------------------------------------------------------------------------- /docs/MonacoTools.md: -------------------------------------------------------------------------------- 1 | # Monaco tools 2 | 3 | When testing applications that use the [Monaco editor](terminology.md#MonacoEditor), it is not possible 4 | to use the default tools to emulate input or send keystrokes. 5 | 6 | For such cases we provide a set of alternate tools. Their usage can be seen in the [ride tests](ride.md). 7 | 8 | ## `loc MonacoInput txt` 9 | 10 | This function is used to emulate entering text. 11 | 12 | * `loc` selects the target. The value `1` picks the session, `2` is the editor. 13 | * `txt` is the text you want to enter. You can also pass a 2-element vector which will be used to edit the current content 14 | of the active line: `txt[⎕IO]` will be prefixed and `txt[⎕IO+1]` will be appended to the current content. 15 | 16 | ## `{combo}MonacoEnter loc` 17 | 18 | Sends the Enter key to submit the current line. The left argument `combo` can be used to specify they key you want to combine `Enter` with: 19 | 20 | * `shift` 21 | * `ctrl` 22 | * `ctrl-shift` -------------------------------------------------------------------------------- /APLSource/GoTo.aplf: -------------------------------------------------------------------------------- 1 | {ok}←GoTo url;z;base ⍝ Ask the browser to navigate to a URL and check that it did it 2 | ok←1 3 | :If ~(⊂lc 4↑url)∊'http' 'file' ⍝ it's probably a relative URL (does this text need be more detailed?) 4 | base←BROWSER.Url 5 | :While (≢url)>z←url⍳'/' 6 | :If z=1 7 | base←((2≥+\base='/')/base),'/' 8 | :ElseIf '../'≡z↑url 9 | base←(2⊃⌽⍸base='/')↑base 10 | :ElseIf './'≡z↑url 11 | base←(⊢/⍸base='/')↑base 12 | :Else 13 | base←base,z↑url 14 | :EndIf 15 | url←z↓url 16 | :EndWhile 17 | url←base,url 18 | :ElseIf 'file'≡lc 4↑url 19 | :AndIf 'win'≡⎕C 3↑1⊃'.'⎕WG'APLVersion' 20 | url←(7↑url),((url[8]≠'/')/'/'),7↓url 21 | :EndIf 22 | :Trap 90 23 | BROWSER.Navigate.GoToUrl⊂url 24 | ('Could not navigate from ',BROWSER.Url,' to ',url)⎕SIGNAL 11/⍨~UrlIs url 25 | :Else 26 | ⎕TRAP←0 'S' ⋄ (⎕LC[1]+1)⎕STOP 1⊃⎕SI 27 | ('Alert running "',url,'": ',⎕EXCEPTION.Message)⎕SIGNAL 11 28 | :EndTrap 29 | -------------------------------------------------------------------------------- /APLSource/RunAllTests.aplf: -------------------------------------------------------------------------------- 1 | failed←stop_site_match RunAllTests path_filter;files;maxlen;n;start;i;file;msg;time;path;filter;allfiles;hasfilter;shutUp;showMsg;prefix 2 | path filter←2↑(eis path_filter),⊂'' 3 | shutUp←0{6::⍺ ⋄ ⍎⍵}'QUIETMODE' ⍝ use QUIETMODE to suppress everything BUT error-messages 4 | showMsg←⍎(1+shutUp⊃)'{⍵}' '{}' 5 | allfiles←(≢path)↓¨FindAllFiles path 6 | hasfilter←×≢filter 7 | files←filter ⎕S'%'⍣hasfilter⊢allfiles 8 | n←≢files 9 | showMsg'Selected: ',(⍕n),(hasfilter/' of ',⍕≢allfiles),' tests.' 10 | maxlen←⌈/≢¨files 11 | failed←'' 12 | start←⎕AI[3] 13 | :For i file :InEach (⍳n)files 14 | prefix←(⎕UCS 13),maxlen↑file 15 | msg←stop_site_match Run1Test path file 16 | :If 0=⍴msg 17 | showMsg prefix,' *** PASSED ***' 18 | :Else 19 | failed,←⊂file 20 | ⎕←prefix,' *** FAILED *** #',(⍕i),' of ',(⍕n),': ',msg 21 | :EndIf 22 | :EndFor 23 | time←∊'ms',¨⍨⍕¨24 60⊤⌊0.5+0.001×⎕AI[3]-start 24 | showMsg←'Total of ',(⍕n),' samples tested in ',time,': ',(⍕≢failed),' failed.' 25 | -------------------------------------------------------------------------------- /APLSource/GetLogs.aplf: -------------------------------------------------------------------------------- 1 | r←{level}GetLogs types;lb;e;entry;tit;r∆;type 2 | ⍝ chould/should take ⍵ to select desired log(s) - once we have some data in them...;) 3 | ⍝ level ∊ 'Info' 'Severe' 4 | ⍝ types ∊ 'browser' 'driver' 5 | ⍝ These values depend on the browser that's used! 6 | ⍝ Be careful: it seems new Chrome versions will not return info-logs but only errors and warnings! 7 | r←'' 8 | 9 | types←types{0=≢⍺:⍵ ⋄ (⊆⍺)∩⍵}⊃⌷¨BROWSER.Manage.Logs.AvailableLogTypes 10 | :For type :In types 11 | lb←BROWSER.Manage.Logs.GetLog⊂type 12 | tit←'Log: ',type,' (' 13 | r∆←⍬ 14 | :If 0