====== Sahi ======
===== Introduction =====
Sahi is a mature, business-ready tool for automation of web application testing.
\\ Sahi is available as an Open Source free product and as Sahi Pro, the commercial version.
\\ Sahi is especially suited for cross-browser/multi-browser testing of complex web 2.0 applications with lots of AJAX
and dynamic content. Sahi works well in Agile development environments, enabling rapid automation and maintenance
and easily integrating with build systems. Sahi saves time and effort with faster development, less maintenance
and fast distributed playback. Sahi runs on any modern browser which supports javascript.
\\
\\ Current version **Sahi Pro v6.0.1**
\\ https://sahipro.com/docs/using-sahi/index.html
==== Features ====
* Recorder and object spy which works on Internet Explorer, Firefox, Chrome, Safari, Opera (and any modern browser).
* Playback on any desktop browser and even on mobile browsers.
* Robust object identification mechanism which works across browsers.
* Sahi automatically waits for page loads and ajax activity. There is no need for adding wait statements in 95% cases.
* Sahi does not need the browser to be in focus.
* Sahi can playback multiple scripts simultaneously reducing playback time.
* Sahi automatically builds rich reports without adding any extra code.
* ...
===== Architecture =====
{{:power-agent:project-1:sahi_architecture_1_.gif}}
The injected javascript
* adds event handlers for recording
* adds Sahi's APIs for event simulation
* mocks some window dialogs like alert, prompt, confirm, print so that playback is not affected
===== Sahi script =====
Example:
_setValue(_textbox("user"), "test");
_setValue(_password("password"), "secret");
_click(_submit("Login"));
_setValue(_textbox("q"), "2");
_setValue(_textbox("q[1]"), "1");
_setValue(_textbox("q[2]"), "1");
_click(_button("Add"));
_click(_cell("Rs.200[1]"));
_assertExists(_textbox("total"));
_assert(_isVisible(_textbox("total")));
_assertEqual("1150", _textbox("total").value);
_click(_button("Logout"));
create Functions:
_include("library.sah");
login("test", "secret");
addBooks(2, 1, 1);
verifyTotal(1150);
_click(_button("Logout"));
function login($username, $password){
_setValue(_textbox("user"), $username);
_setValue(_password("password"), $password);
_click(_submit("Login"));
}
function addBooks($numJava, $numRuby, $numPython){
_setValue(_textbox("q"), $numJava);
_setValue(_textbox("q[1]"), $numRuby);
_setValue(_textbox("q[2]"), $numPython);
_click(_button("Add"));
}
function verifyTotal($total){
_assertEqual($total, _textbox("total").value);
}
Sahi uses **Rhino engine** so Sahi script can call any java code in Sahi's classpath.
\\ Rhino seamlessly translates variables between Java and Javascript.
\\
\\ The scripts are stored on the location where sahi is installed.
\\ The folder Sahi_pro contains all the files to use sahi.
\\ Under this folder you find userdata where all the scripts are stored
===== Using =====
==== Install ====
Double click on install_sahi_pro_xxx.jar to start the installer. If Java is configured properly, it will launch the installer.
\\ https://sahipro.com/docs/using-sahi/quick-tutorial.html
\\
\\ on **MAC**
\\ **start** sahi with \sahi_pro\userdata\bin\start_dashboard.sh
\\ Add **license** to \sahi_pro\userdata\config folder
==== Dashboard ====
{{:power-agent:project-1:sahi_dashboard.png?direct&100|}} The Sahi Dashboard automatically starts the Sahi proxy and lets you start your configured browsers.
\\ If you don't see your browser click configure and change the default location to your browser location.
==== Recording ====
Click on a browser. Now you should see a browser window like this
\\ Press ALT and double click on the browser window. Sahi's Controller window will pop up.
\\ {{:power-agent:project-1:sahi_browser.png?direct&320|}} {{:power-agent:project-1:sahi_controller_record.png?direct&200|}}{{:power-agent:project-1:sahi_controller_playback.png?direct&200|}}
\\ Click record and sahi records all your actions into the script.
\\ When testing an Extjs application sahi sometimes takes the dynamic id of an element.
\\ By using CTRL and hovering over an element the element will show in the sahi controller under Accessor.
\\ You can choose alternatives and actions to use.
\\ Using the arrow you can go to the parent node and you can anchor to this element by clicking the anchor button.
\\ If you now select an other element sahi will use _in, _near API to find your element.
\\ https://sahipro.com/docs/using-sahi/sahi-controller.html
==== Editor ====
You can use the sahi editor or notepadd ++ with autocomplete api
\\ http://sahipro.com/alldocs/v5.1.0.0/faq/sahi-with-notepad++.html
\\
\\ The sahi editor allows you to edit scripts, make functions, add documentation, make suites, data driven test ...
\\ Run tests on multiple browsers using playback.
\\ By rightclicking a test you can see all the logs from a test or suite.
\\ https://sahipro.com/docs/using-sahi/sahi-script-editor.html
==== API ====
=== Accessor API Basics ===
| _link(12)|Using index in the page; assuming it is the 13th link on the page. |
| _link("sahi_link")|Using id as string |
| _link(/.*_link/)|Using id as regular expression |
| _link("Link to Sahi website")|Using visible text as string |
| _link(/Link to .* website/)|Using visible text as regular expression |
| _link("/Link to .* website/")|Using visible text as regular expression |
| _link({id:"sahi_link"})|Using an associative array with id |
| _link({sahiText:"/Link to .*/"})|Using an associative array with visible text as regular expression |
| _link({className:"low",sahiText:"/Link.*/"})|Using an associative array with multiple attributes like className and sahiText |
| | |
| _link("delete")|points to the first delete link. This is the same as _link("delete[0]") |
| _link("delete[1]")|points to the second delete link; Note that indexing starts at 0 |
| _link("/del/[1]")|points to the second delete link; Note that indexing starts at 0 |
| | |
|**_near** is a DOM relation marker which specifies that the element should be searched near another element.||
|**_in** is a DOM relation marker which specifies that the element should be searched within another element.||
| _link(0, _near(_cell("User Two")))|points to the 0th link near cell with text "User Two". Note that the index is 0 here since it is the nearest link. |
| _link("delete", _near(_cell("User Two")))|points to the nearest link with text "delete" near cell with text "User Two". \\ Note that we do not need to specify "delete[1]" since it is the delete. link nearest to User Two. |
| _link(/del/, _near(_cell("User Two")))|points to the nearest link with text which matches regular expression /del/ near cell with text "User Two". |
| _link("delete[2]", _near(_cell("User Two")))|points to the 3rd nearest link with text "delete" near cell with text "User Two". |
| _link("/del/[2]", _near(_cell("User Two")))|points to the 3rd nearest link with text matching /del/ near cell with text "User Two". \\ Note how the regular expression is appended with the index in square brackets and quoted to make it a string |
| _link(0, _in(_cell("del2")))|points to the 0th link in cell with id "del2" |
| _link("delete", _in(_cell("del2")))|points to the link with text "delete" within cell with id "del2" |
| | |
| _cell(0, _near(_cell("User One")), _under(_cell("Status")))|Finds first cell near User One and under Status |
| _cell(0, _rightOf(_cell("User One")), _under(_cell("Status")))|Finds first cell to the right of User One and under Status |
| _cell("Inactive", _under(_cell("Status")))|Finds first Inactive cell under Status |
https://sahipro.com/docs/sahi-apis/accessor-api-basics.html
=== Accessor APIs ===
**Form Input Elements**
* _password
* _textbox
* _hidden
* _datebox
* _datetimebox
* _datetimelocalbox
* _emailbox
* _monthbox
* _numberbox
* _rangebox
* _searchbox
* _telephonebox
* _timebox
* _urlbox
* _weekbox
* _textarea
https://sahipro.com/docs/sahi-apis/accessor-apis.html
=== Action APIs ===
**Mouse events**
* _xy
* _click
* _doubleClick
* _rightClick
* _mouseDown
* _mouseUp
* _mouseOver
* _check
* _uncheck
* _setSelected
* _dragDrop
* _dragDropXY
**Touch events**
* _tap
* _touch
* _touchStart
* _touchEnd
* _touchCancel
* _touchMove
* _swipe
**Keyboard events**
* _setValue
* _keyDown
* _keyUp
* _keyPress
* _type
**More Actions:** https://sahipro.com/docs/sahi-apis/action-apis.html
\\ Focus Events
\\ Text Selection
\\ Page Navigation
\\ Waits
\\ Native Events
\\ File Upload
\\ File Download
\\ Window open/close APIs
\\ Calling Generic Code
\\
\\ **More APIs:** https://sahipro.com/docs/sahi-apis/index.html
\\ Relation APIs
\\ Screenshot APIs
\\ Include APIs
\\ Fetch APIs
\\ Assertion APIs
\\ Logging APIs
\\ Debug Helper APIs
\\ File APIs
\\ Excel APIs
\\ Database APIs
\\ Data Drive APIs
\\ URL APIs
\\ Script/Suite Info APIs
\\ Script Execution Control APIs
\\ HTTP Header Manipulation APIs
\\ URL Mock APIs
\\ Javascript Dialogs
\\ Rich Text Editors
\\ Popup Windows
\\ Multiple Domains
\\ Frames and Domains
\\ Multiple Browsers
\\ Execute External Programs
\\ Utility APIs
\\ Rest APIs
\\
\\ Applet APIs
\\ Flex APIs
\\ Layout APIs
\\ Documentation APIs
==== Script ====
PageObject model
if(!_isDataPassed()){
// entered if executed from Controller
// NOT entered if run from dd.csv
$username = "jcpadmin";
$password = "test";
}
function LoginPage() {
this.$url = "login.html";
this.go = function() {
_navigateTo(this.$url);
}
this.loginAs = function($username, $password){
_startLookInside(_div("loginWindow"));
_setValue(_textbox(0), $username); //username
_setValue(_password(0), $password); //password
//_click(_button(0)); //remember me
_click(_link(0)); // login
_stopLookInside();
}
this.logout = function(){
//_click(_span($username, _in(_div("/.* viewportToolbar .*/"))));
//_link("/x-btn.*x-btn-toolbar x-box-item.*/[2]");
_click(_link(0,_rightOf(_link("/Feedback/"))));
_click(_span("Abmelden"));
}
this.verifyPage = function($page){
_assertEqual("/.*"+$page+".*/",document.location.pathname);
//_assertEqual($page,window.document.documentURI.split("/")[4]);
}
this.verifyError = function(){
_assertExists(_div("errors"));
_assertVisible(_div("errors"));
}
this.verifyErrorMessage = function($message){
this.verifyError()
_assertContainsText($message, _div("errors"));
}
}
function logout(){
$loginPage.logout();
$loginPage.verifyPage("login.html");
}
function loginfail($username,$password,$errorMessage){
$loginPage.go();
$loginPage.verifyPage("login.html");
$loginPage.loginAs($username,$password);
$loginPage.verifyPage("login.html");
$loginPage.verifyErrorMessage($errorMessage);
}
function login($username,$password){
$loginPage.go();
$loginPage.verifyPage("login.html");
$loginPage.loginAs($username,$password);
$loginPage.verifyPage("analyse.html");
}
/* --Functions Above-- */
_include("../pages/loginPage.sah");
$loginPage = new LoginPage();
_setStrictVisibilityCheck(true);
function AnalysePage() {
this.$url = "analyse.html";
this.go = function(){ _navigateTo(this.$url);}
this.goMainMenu = function(){ this.clickTab("Hauptmenü");}
this.clickTab = function($name){ _click(_span($name)); }
this.closeTab = function($name){ _click(_span("/x-tab-close-btn/", _near(_span($name))));}
this.clickNewClient = function(){ _click(_link("neueMandantButton"));}
this.clickEditClient = function(){ _click(_link("ladeMandantButton")); }
this.clientFilterByName = function($fname, $lname) {
_setValue(_textbox("vorname"), $fname);
_setValue(_textbox("nachname"), $lname);
_click(_span("Suche"));
_wait(2000,!_isVisible(_div("Lade Daten...")) );
}
this.clientFilterByBerater = function($filter,$item) {
_setValue(_textbox("beraterSearchField"), $filter);
_click(_div({className: "/x-boundlist-item/", sahiText : $item}));
_click(_span("Suche"));
_wait(2000,!_isVisible(_div("Lade Daten...")) );
}
this.clickClient = function($fname,$lname) {
this.clientFilterByName($fname,$lname);
this.clickClientByName($fname,$lname);
}
this.clickClientByName = function($fname,$lname) {
$rows = _count("_row","/x-grid-row.*x-grid-data-row/"); // get all rows with clients visible
if($rows <= 0) _fail("no mandanten found");
_log($rows + " rows found");
for(var $i=0; $i<$rows; $i++) {
$row = _row("/x-grid-row.*x-grid-data-row/["+$i+"]");
if($fname == _getCellText(_cell(0, _in($row))) && $lname == _getCellText(_cell(1, _in($row))) ){ //check firstname and lastname
_doubleClick(_cell(0, _in($row)));
return true;
}
}
_fail("Mandant not found");
}
this.clickTabPersönlicheAngaben = function() {this.clickTab("Persönliche Angaben")}
this.clickTabVerträgeAngebote = function() {this.clickTab("Verträge & Angebote")}
this.clickTabBeratungHandlungsempfehlung = function() {this.clickTab("Beratung - Handlungsempfehlung")}
this.clickTabAngebotsrechner = function() {this.clickTab("Angebotsrechner")}
this.clickTabDokumente = function() {this.clickTab("Dokumente")}
this.clickTabWiedervorlagen = function() {this.clickTab("Wiedervorlagen")}
this.clickTabKommunikation = function() {this.clickTab("Kommunikation")}
this.deleteMandant = function($fname,$lname){ //start from main menu
this.clientFilterByName($fname,$lname);
$rows = _count("_row","/x-grid-row.*x-grid-data-row/");
_log($rows + " rows found");
for(var $i=0; $i<$rows; $i++) {
$row = _row("/x-grid-row.*x-grid-data-row/["+$i+"]");
if($fname == _getCellText(_cell(0, _in($row))) && $lname == _getCellText(_cell(1, _in($row))) ){ //check firstname and lastname
_click(_image("user_delete.png", _in($row))); //delete row
_assertVisible(_span("Achtung"));
_click(_link("Ja"));
}
}
_click(_image("x-tool-img x-tool-close")); // close window
}
}
function clickFormTrigger($inputElName){ // dropdown or calender icon
_click(_div("/ x-form-trigger /", _near(_textbox($inputElName))));
}
function setDropdownItem($inputElName, $item){
clickFormTrigger($inputElName);
_click(_listItem($item));
}
function checkFieldRequired($inputElName) {
_assertEqual("/x-form-required-field/", _textbox($inputElName).className);
}
function checkFieldDisabled($inputElName) {
_assertEqual(true, _textbox($inputElName).disabled);
}
_include("../../login/login_lib.sah");
login("jcpadmin","test");
_include("../../pages/analysePage.sah");
$ap = new AnalysePage();
$ap.go();
//------------------------------------------
// test create Mandant
//------------------------------------------
//---delete all "Test Tester" if exists ----
$ap.clickEditClient();
$ap.deleteMandant("Test Tester","Sahi");
//-----------create new (required)-----
$ap.clickNewClient();
checkFieldRequired("vorname");
_setValue(_textbox("vorname"), "Test Tester");
checkFieldRequired("nachname");
_setValue(_textbox("nachname"), "Sahi");
_setValue(_textbox("geburtsdatum"), "01.01.1990");
checkFieldRequired("geburtsdatum");
_click(_span("Speichern"));
//-------------Haushalt----------------
//------Kunde------------------------
_click(_div("Kunde"));
_setValue(_textbox("strasse"), "Sahistraße");
_setValue(_textbox("hausnummer"), "1");
_click(_span("Änderungen speichern"));
//------Partner------------------------
_click(_div("Partner"));
_setValue(_textbox("vorname"), "Test Partner");
_setValue(_textbox("nachname"), "Sahi");
_setValue(_textbox("geburtsdatum"), "02.01.1990");
_click(_span("Änderungen speichern"));
//-----Kinder-----------
_click(_div("Kinder"));
_click(_span("Neue"));
_setValue(_textbox("vorname"), "Test Junior");
_setValue(_textbox("nachname"), "Sahi");
_setValue(_textbox("geburtsdatum"), "01.01.2015");
_click(_span("Änderungen speichern"));
_click(_span("Neue"));
clickFormTrigger("anrede");
_click(_listItem("weiblich"));
_setValue(_textbox("vorname"), "Test Kinder");
_setValue(_textbox("nachname"), "Sahi");
_setValue(_textbox("geburtsdatum"), "02.01.2015");
_click(_span("Änderungen speichern"));
_include("../../pages/analysePage.sah");
$ap = new AnalysePage();
$ap.go();
$ap.clickEditClient();
$ap.clickClient("Test Tester","Sahi");
$ap.clickTabPersönlicheAngaben();
//------------------------------------------
// test Kunde
//------------------------------------------
//-------------Person----------------------
_click(_div("Kunde"));
_assertEqual("Torsten B", _getValue(_textbox("beraterId")));
_assertEqual("Herr", _getValue(_textbox("anrede")));
checkFieldRequired("anrede");
_assertEqual("Test Tester", _getValue(_textbox("vorname")));
checkFieldRequired("vorname");
_assertEqual("Sahi", _getValue(_textbox("nachname")));
checkFieldRequired("nachname");
_assertEqual("01.01.1990", _getValue(_textbox("geburtsdatum")));
checkFieldRequired("geburtsdatum");
_assertEqual("Sahistraße", _getValue(_textbox("strasse")));
//-------------Ausbildung/Beruf------------
_click(_span("Ausbildung/Beruf"));
//_assertEqual("Angestellte/r", _getValue(_textbox("beschaeftigungsverhaeltnis")));
//-------------Arbeitgeber-------------------
_click(_span("Arbeitgeber"));
//-------------Kommunikation---------------
_click(_span("Kommunikation", _near(_span("Kunde"))));
//_assertEqual("VersicherungsAG", _getValue(_textbox("contactVersicherungen")));
//_assertEqual("BankAG", _getValue(_textbox("contactBank")));
//_assertEqual("SteuerAG", _getValue(_textbox("contactSteuern")));
//_assertEqual("RechtsAG", _getValue(_textbox("contactRecht")));
//-------------Legitimation------------------
_click(_span("Legitimation"));
//-------------Bank--------------------------
_click(_span("Bank"));
//_assertEqual("BankAG", _getValue(_textbox("bank0")));
//-------------Gesundheit--------------------
_click(_span("Gesundheit"));
//_assertEqual("193", _getValue(_textbox("grosse")));
//_assertEqual("89", _getValue(_textbox("gewicht")));
//_assertEqual("Nein", _getValue(_textbox("motorradfahrer")));
//-------------Erweiterte Berufsfragen------
_click(_span("Erweiterte Berufsfragen"));
_assertEqual("Sonstige Berufsausbildung", _getValue(_textbox("eb_abgeschlosseneAusbildung")));
//-------------Steuern------------------------
_click(_span("Steuern"));
//_assertEqual("1", _getValue(_textbox("steuerklasse")));
**TestSuite**
test/login/testlogin.s.csv
test/login/testlogin.dd.csv
test/analyse/TestTabs.sah
test/analyse/TestNewMandant.sah
test/analyse/TestFormHundehalter.sah
Data driven test:
\\ {{:power-agent:project-1:sahi_script_dd.csv.png|}}
\\ You can set the start url, add tags and pass parameters.
\\ If you want to run only tests with a specific tag, you can configure that at playback.
==== Reports ====
{{:power-agent:project-1:sahi_reports_root.png?direct&200|}}{{:power-agent:project-1:sahi_reports_compare.png?direct&250|}}{{:power-agent:project-1:sahi_reports_script.png?direct&200|}}
\\ You can see all the test that you ran, filter, compare test and screenshots, see details.
\\ You can also make custom reports.
==== Mobile ====
configure sahi proxy https://sahipro.com/docs/using-sahi/playback-mobile.html
\\
\\ Use the safari debug tools.
\\ Because the app uses a local file for the app the proxy is not used.
\\
\\ We will need to change the location to your webserver and run the tests.
\\ change the location to your proxy ex.: ''location.href = "http://192.168.4.6:9999";''
\\ Now you see the sahi start page where you can insert the test you want to run and the url of you webserver.
\\ you can also use the command line to start the test.
$("filebox").value = "mobile/test3.sah";
$("starturl").value = "http://192.168.4.6:8080/Finanzanalyse.app/www";
launch();
OR
_sahi.sendToServer("_s_/dyn/Player_setScriptFile?dir=C%3A%2FUsers%2Fmathias%2Fsahi_pro%2Fuserdata%2Fscripts%2F&file="+"mobile/test3.sah"+"&starturl="+"http://192.168.4.6:8080/Finanzanalyse.app/www"+"&manual=0"+"&browserType=Netscape");
_sahi.setServerVar("sahi_paused", "0");