This is an old revision of the document!


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

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:

sample.sah
_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:

sample.sah
_include("library.sah");
 
login("test", "secret");
addBooks(2, 1, 1);
verifyTotal(1150);
 
_click(_button("Logout"));
library.sah
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.

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

Dashboard

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.

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

LoginPage.sah
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"));
	}
}
login_lib.sah
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();
analysePage.sah
_setStrictVisibilityCheck(true);
 
function AnalysePage() {
	this.$url = "analyse.html";
 
	this.go = function() {
		_navigateTo(this.$url);
	}
 
        this.goMainMenu = function() {
		this.clickTab("Hauptmenü");
      	//_span("x-tab-button")
	}
 
	this.clickTab = function($name) {
        _click(_span($name));
      	//_click(_link("/"+$name+"/"));
	}
 
	this.clickNewClient = function() {
		_click(_link("neueMandantButton"));
	}
 
	this.clickEditClient = function() {
		_click(_link("ladeMandantButton"));
	}
 
	this.clientFilterByBerater = function($filter,$item) {
        _setValue(_textbox("beraterSearchField"), $filter);
		_click(_div({className: "/x-boundlist-item/", sahiText : $item}));
        _click(_span("Suche"));
	}
 
	this.clickClientByName = function($fname,$lname) {
		_doubleClick(_div($fname, _near(_div($lname))));
	}
 
	this.clickTabPersönlicheAngaben = function() {this.clickTab("Persönliche Angaben")}
	this.clickTabVerträgeAngebote = function() {this.clickTab("Verträge & Angebote")}
	this.clickTabAngebotsrechner = function() {this.clickTab("Angebotsrechner")}
	this.clickTabVergleichLotse = function() {this.clickTab("Vergleich Lotse")}
	this.clickTabDokumente = function() {this.clickTab("Dokumente")}
	this.clickTabWiedervorlagen = function() {this.clickTab("Wiedervorlagen")}
	this.clickTabZieleWünsche = function() {this.clickTab("Ziele / Wünsche")}
	this.clickTabIhreErwartungenEmpfehlungen = function() {this.clickTab("Ihre Erwartungen / Empfehlungen")}
 
}
 
function clickFormTrigger($inputElName){
	_click(_div("/ x-form-trigger /", _near(_textbox($inputElName))));
}
 
function setDropdownItem($inputElName, $item){
	clickFormTrigger($inputElName);
	_click(_listItem($item));
}
 
testFormHundehalter.sah
_include("../login/login_lib.sah");
login("jcpadmin","test");
_include("../pages/analysePage.sah");
$analysePage = new AnalysePage();
 
$analysePage.clickEditClient();
$analysePage.clickClientByName("Tom","Tester");
 
_doubleClick(_div("Hundehalter"));
_assertEqual("Hundehalterversicherung", _getText(_div("myBausteinTitle")));
 
// start form
clickFormTrigger("angebotsType"); //click dropdown icon in dropdownmenu
_click(_listItem("Vertrag"));     // click the item in the dropdownlist
                                  // by using _setStrictVisibilityCheck(true); sahi will only check visible items
                                  // there can only be one dropdownlist shown.
//or setDropdownItem("angebotsType","Vertrag");
_click(_button("Eigenvertrag:")); //checkbox
_click(_button("Unterlagen mitgenommen:")); // checkboxes don't have a name
                                            // using the label near it to select the checkbox 
 
_setValue(_textbox("name"), "testname"); // setValue for textbox / text inputfields
_setValue(_textbox("beschreibung"), "testbeschreibung");
 
clickFormTrigger("mp");
_click(_listItem("P: tess de tester"));
 
_setValue(_textbox("gesellschaft"), "VersicherungsAG");
_setValue(_textbox("versicherungsnummer"), "002");
 
clickFormTrigger("tarifgruppe");
_click(_listItem("Innendienst"));
 
clickFormTrigger("kampfhund");
_click(_listItem("Ja"));

TestSuite

testsuite.suite
test/login/testlogin.s.csv
test/login/testlogin.dd.csv
test/analyse/TestTabs.sah
test/analyse/TestNewMandant.sah
test/analyse/TestFormHundehalter.sah

Data drive test:
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