Test Management (system testing - automated)

Tools

 

Gui Testing (on Test Management)

Sources can be found here : https://scm.gforge.inria.fr/anonscm/svn/gazelle/Maven/gazelle-gui-testing/Automated-Test-testNG/trunk (website link)

This project, based on Automated Testlink scenarios project do the same job : automates scenarios written in Testlink for Gazelle-TM. All the test classes have been updated, the XPaths library has been also updated to be compliant with new Gazelle Test Management interface.

The Development environment and Maintenance sections are only about the Gui Testing project. 

Pages Testing (on Test Management)

Sources can be found here : https://scm.gforge.inria.fr/anonscm/svn/gazelle/Maven/gazelle-gui-testing/PagesTesting/branches/selenide_testing (website link)

This project, using Page Object like in Gui Testing project automates scenarios written in Testlink for Gazelle-TM. It is used to test the access rights on all pages for each existing user profile within Gazelle TM

Old Tools (no updated since 2014)

Menu checker

Sources can be found here : https://scm.gforge.inria.fr/svn/gazelle/Maven/gazelle-gui-testing/topmenu (website link)

This tool browses every menu item with every account and with every Gazelle mod (tm, gmm, etc.). The concerned menu is the main menu on the top of the page. The tool just visit the page and checks that it does not redirect to error.seam. Checks are performed on the title, the url of the page, or on the text contained in the page. Errors can be defined either directly in the class ErrorPages or in an external file errorPages.xml. Logs are put in the /log folder. They contains the list of errors found, a screenshot of before/after encoutering the error, and the pages error.seam themselves (debug info can be stored here). The tool can be run by calling mvn -test (which is useful for jenkins) or by building a jar. See the specific section for more details.

EJB dependency : an EJB is used by the menu checker to get the list of the menu items. This dependency is set in the pom.xml. It should be updated if the one used in the tested gazelle installation differs from the one set in pom.xml.

Monkey testing

Sources can be found here : https://scm.gforge.inria.fr/svn/gazelle/Maven/gazelle-gui-testing/monkey-testing (website link)

This tool works the same way as the menu checker, except it clicks on a random link in the page. A time is set for a given session in the config.properties file and the tool travels through gazelle for that given time, then switches to another profile, etc. Logs work the same way as menu checker. The latest version is still a bit old and can only be run with mvn -test.

Automated Testlink scenarios

Sources can be found here : https://scm.gforge.inria.fr/svn/gazelle/Maven/gazelle-gui-testing/Automated-Test-testNG (website link)

This project automates scenarios written in Testlink for Gazelle-TM. The purpose of this tool is to be run with Jenkins at a given rythme, to give a constant view of the state of Gazelle-TM. The can be run with mvn -test. It is possible to configure Jenkins in such a way that it logs all test results in Testlink. See the specific section for more details. It is possible to add more test pretty easily, this is explained here

Development environment

Dependencies

Several dependency are required for the project to work. They are all included in the pom.xml :

  • TestNG
  • Selenium (only version 2.X)
  • Gazelle-TM

TestNG eclipse plugin must be installed for test development. It can be found in the eclipse marketplace (TestNG for eclipse) or installed manually : Help > Install new software > fill in with "http://beust.com/eclipse". Further instruction on TestNG here.

Selenium and browsers configuration

Selenium requires a Mozilla Firefox installed in the developper's computer. To be able to run tests on Mozilla Firefox, you need 46.0.1 version or older, after 47, Selenium is not able to driver the browser.

NEVER UPDATE MOZILLA FIREFOX VERSION, WEBDRIVER IS NO MORE MANAGED BY SELENIUM BY UPDATE 47

The project allow you to choose if you want to use Mozilla Firefox browser or Google Chrome. To do this, you will need to install chromedriver binary on the developper's computer.

You can find what you want on this page. If you choose to use Google Chrome instead of Mozilla Firefox, just edit Maven's pom.xml browser property, on concerned profile. (firefox, chrome are accepted).

Test Development

When developping a test, it is possible to run it by pressing Shift + Alt + X then N (or by clicking Run > Run as > TestNG test) when you're using Eclipse. If you're using Intellij IDEA you can run the current test by pressing Shift + F10 (or by clicking Run > Run 'CLASS_NAME').

Make sure that the "headless" property in pom.xml current used profile is set to false, otherwise the test will be run with xvfb (which can be useful, though). It can run in debug mode, like any other program.

Maintenance

The issue that is most likely to happen is xPath deprecation. Some changes won't affect the ability of xpaths to select the right object, but some will. The most vulnerable xpaths are those with a "nth" constraint, as "//table/tr/td[2]". This one for example select the second cell of a line (on the second column). If a column is added at the begining of this table, the xpath will stop selecting what it should. There are two kind of xpath deprecation :

  1. The xpath selects nothing
  2. The xpath selects another object

The first case is not that hard to fix. Because all xpath are named and all their call encapsulated in "getWebElement()", all exception will be caught. If, for example, I use the xpath "Button.Login", that has deprecated because the webpage changed, the method getWebElement() will display a console message of this kind : "[Button.Login] can't be found. Called by [ThisPageObject]". In this case my xpath stopped working but I know exactly which one and in which page. Half of the job is already done, I just now have to find a new xpath for Button.Login.

The second case can be tricky. Suppose the xpath "Td.Name" select the first column of a table with two columns : name and age. Suppose now that a new column is added at the first position : Surname. The xpath will still select something without error, but it won't be what we want to select. No direct error message will be displayed. The most likely outcome is that an assertion will fail. It is possible that this problem will require some more debugging just to find the erroneous xpath.

Another kind of problem is a webpage whose behavior changes. In this case, not only the xpaths, but the PageObject and the test classes are deprecated and have to be remake.

The most dangerous change (by far) is an update of seam. The way seam converts java component into html/css/javascript ones is decisive. PageObject class relies a lot on how the page is implemented : if one xpath says "I want the link inside this cell of this table", it won't work if the link is placed inside a div. But that change could happen, seam wraps its components inside a lot of invisible containers (div, span). It is completely possible that, for example, seam changes the way it renders its table in html, and that all cells of tables (td) become wrapped inside a div. When this happen, a lot of tests will suffer. It is a risk and will maybe never happen (given the last update of the seam website, it is indeed no very likely) but it is important to know this weakness.

Build and use the menu checker (deprecated)

The purpose of this page is to explain how to run the menu checker. It also contains information about how to create a standalone version, and how to configure it.

Sources

SVN link : https://scm.gforge.inria.fr/svn/gazelle/Maven/gazelle-gui-testing/topmenu/trunk

GForge link : https://gforge.inria.fr/scm/viewvc.php/Maven/gazelle-gui-testing/topmenu/?root=gazelle

Configuration

config.properties

This (internal) file contains several configuration properties and also some xpath.

base.url=http://ovh3.ihe-europe.net:8080/gazelle/ Url of the gazelle to test
base.page.url=home.seam Filename of the home page
admin.login=sballuais Username of an admin account, for mode switching
admin.password=azerty Password of this admin account
gazelle.version.url=rest/version Relative url of the version xml (rest)
administration.preference.url=administration/preferences.seam Relative url of the preference page (administration)
crowdin.default.language=en Default language
war.base.path=/home/sbs/workspace/Gazelle/gazelle-tm-war/ Local absolute path of a version-equivalent Gazelle
headless=false If true, tries to run with an xvfb session
run.span.check=true Turns on or off the check of span (inner text). It is possible to turn if off because it can be slow (it waits for a timeout at each check)
logfile.url=log/ Relative url that will be used to store log
firefox.download.directory=log/download/ Relative url that will be used to store downloads
login.attempts.max=10 The tool tries to log-in several times before giving up. This is this amout of time.
language.change.attempt.max=10 The tool tries several times to change the language before giving up. This is this amount of time
slash.replacement.in.filename=__ The character used to replace the slash when taking a screenshot or saving an error.seam page. If the page where an error is detected is /foo/bar.seam and this properties is "__", the filename of the screenshot will be "foo__bar.seam.png"

errorPages.xml

This file can be used to override the errors that are defined in the class ErrorPages. Is the program find this file in its root, it will use it instead of the class ErrorPages. If not it will use the default values of ErrorPages.

Its functionning is pretty simple. There a three groups of error : pageName, pageTitles and pageSpans. The first one defines what page file-name are considered as errorneous (exactly), the second what title are considered erroneous (exactly) and the third is the list of text samples that make a page erroneous : if a page contains at least one occurence of this test, it is considered as erroneous.

LoginProfiles.xml

Contains all profiles that are used in the test. Note that all profiles must be valid and created in Gazelle.

Use with maven test

Use the command 'mvn test' when you are in the project root. This is how the project should be run by jenkins.

Build a jar

It is possible to build a fat jar (~80MB) that can be run anywhere. To do this, place yourself in the project root with a terminal. use the command 'mvn clean compile assembly:single'. The jar is created in the /target folder. It is advised to add configuration files next to it to allow it to run properly. Those are :

  • config.properties : works the same way as when inside the jar. Overrides the internal config.properties.
  • errorPages.xml : see above

Usage of the jar

To run the tool, just use the command 'java -jar the-jar-name.jar'. Logs and screenshots are created in the /log folder, where the jar is located.

Create a java automated test

The purpose of this document is to explain how to create a new test class in an existing test project. It contains technical explanation about how automated tests work.

Project in the forge : https://scm.gforge.inria.fr/svn/gazelle/Maven/gazelle-gui-testing/Automated-Test-testNG/branches/idr_testing

The project has three important parts :

  • Test classes
  • PageObject classes
  • XPath catalog

Tests and PageObject classes

The project uses the design pattern page object. Test classes do not interact with webpage directly. All page interactions are factored in PageObject classes. This way if an action is required by several tests, it is written only once in one PageOblect class. PageObject classes are "interfaces" between webpages and test classes.

Test classes overview

All test classes extends BaseTest. Two methods must be defined : run() and testScenario(Object dataSet). The skeleton of a test class looks like this :

public class TM123 extends BaseTest {	
	@Override
	@Test
	public void run() {
		// Dataset initialization.
		// Usually the 1st one is a LoginProfile
		Object[][] dataSets = new Object[][] {
			{ new LoginProfile("login", "password",
			           Languages.ENGLISH, "Some TestingSession"),
			  "Some Text" },
			{ /* Other dataSet */ }
		};
	withDataSets(dataSets);
	super.run();
	}
	
	@Override
	public void testScenario(Object[] ds) {
		LoginProfile loginProfile = (LoginProfile)ds[0];
		String someText = (String)ds[1];

		// Most of the tests start like this :
		// Open the main page, log-in and set the default testing session
		MainPage mp = new MainPage(getDriver(), loginProfile);
		mp.go();
		// Page command.
		// Notice that there should be only PageObject requests
		MiscPageObject mpo = mp.clickOnSomeLink();
		mpo.fillInSomeText(someText);
		// Assertions. Again, they are about PageObject method,
		// not about the page directly
mp.logout(); }

 run() is the entry point for TestNG. It must always be annoted @Test. The class must not be annoted @Test.

Why are there 2 methods for one single test ?

There was initially only one method, run(). After some work it appeared that the test sometime failed for reasons that could not be considered as a "tested functionnality malfunction" (e.g. internet connection problem, etc.). The solution to this was to run the test method several times until it finished without encountering "external" failures. There was another constraints : having the entry point of a test in each class. It is important to have the entry point in the test class, without it we would have to write an other file whose function would be to reference all test class. We would then have to maintain this file, etc. The design pattern "template" was used.

BaseTest does one big thing : its run() method does some initialization (required by every test) and calls testScenario() until it ends successfully or with a decisive assertion. The method testScenario is the one which is implemented in every test class. Also, every test class' run() has to call super.run(). The overall functionning works like this :

  • TestNG find a class with a method annoted @Test. In our case this method is always (and only) run()
  • The run() of a concrete test class is called
  • Some data sets initializations are made
  • super.run() is called, the same for every test class : the one of BaseTest
  • BaseTest.run() does some 'universal' initialization
  • BaseTest.run() calls testScenario() until it returns properly and the testScenario() of the concrete test class is called

XPath catalog and PageObject functionning

All webpage query are made using xPath and not component Ids. This is because, the way GazelleTM is developped, Ids are redefined randomly everytime jboss restarts (and are thus uselss for identification). XPaths can be viewed as a description of a web component. If the component change in a non-essential way, the xPath still selects the same component, which is good; but the component can change too much, or a new component can be added to the page, making the xPath ambigous between the old and the new component (the xPath selects them both). To (partly) solve this problem, all xPath are stored in a single file and most of them have a part that is dynamically loaded.

The file xpath.properties contains all xPaths that are used in PageObject classes. All used xPath should be placed in this file. If possible, all "essential" text of a component used in an xPath should not be written directly in this file and should be loaded dynamically in the PageObject (e.g. the name of a button). Before an xPath can be used by a PageObject class, it should be loaded in its xPath hasmap. BasePage, whose every PageObject inherits, does one important things : it encapsulates all xPath queries. BasePage has a hasmap <String, String> of xPath. The key is the name of the xPath, close to the key of the xPath in the xpath.properties. The value is the xPath found xpath.properties filled with data. All xPath should be loaded and stored in the hasmap at the construction of the PageObject. Then, all xPath query should be done by calling the getWebElement() method of BasePage. Here is a skeleton shared by all PageObject class :

public class MiscPageObject extends BasePage{
	public PreConnectathonResults(WebDriver driver, LoginProfile lp) {
		super(driver, Loader.instance().config("base.url")
			Pages.CAT_PRECAT_VALIDATION_RESULT.getLink()
			.replace(".xhtml", ".seam"), lp);
	}
	
	@Override
	protected void loadXPaths() {
		// BasePage.loadXPaths() loads some xpath used by everything
		super.loadXPaths();

		// xPaths is the hashMap of all used xPath in this class
		xPaths.put("TextBox.Status",
			MessageFormat.format(
				Loader.instance().xpath("MiscPageObject.TextBox.Status.XPath"),
				Loader.instance().crowdin("gazelle.tf.table.Status")));
		xPaths.put("Span.Name",
				Loader.instance().xpath("MiscPageObject.Span.Name.XPath"));

	}

	/**
	 * Enters a status in the textbox. Presses enter
	 */
	public void fillInTextbox(String text) {
		// Notice how we get the WebElement only through getWebElement
		getWebElement("TextBox.Status").clear();
		sleep(1);
		getWebElement("TextBox.Status").sendKeys(text);
		getWebElement("TextBox.Status").sendKeys(Keys.RETURN);
	}
	
	/**
	 * Gets the content of the textbox
	 */
	public String getTestStatus(String testNumber, String keyword) {
		return getWebElement("Span.Name", keyword, testNumber).getText();
	}
}

 All WebElement query is fetched only getWebElement(). This allows one thing : if the xPaths is unable to select an element, we can track immediatly the error and display the name of the xPath that failed. It is important to have the name instead of the value of the xPath, because the value of the xPath can be long and tough to read.

 Step-by-step tutorial

We will take a simple example : the test we want to write checks a login feature. We login and we check that the displayed name is properly our.

The test to automate

Our two pages have this html code :

Login page :

<html>
 <h1>Login</h1>
 <table>
  <tr>
   <td>Username</td>
   <td><input type="textbox" /></td>
  </tr>
  <tr>
   <td>Password</td>
   <td><input type="textbox" /></td>
  </tr>
  <tr>
   <td><input type="button" value="Login" /></td>
  </tr>
 </table>
 
 <!-- some other content ... -->

</html>

Profile page :

<html>
 <h1>User Page</h1>
 <table>
  <tr>
   <td>Username</td>
   <td><span>Teemo</span></td>
  </tr>
  <tr>
   <td>Address</td>
   <td><span>blablabla</span></td>
  </tr>
  <tr>
   <td>City</td>
   <td><span>bla</span></td>
  </tr>
 </table>
 
 <!-- some other content ... -->

</html>

Let's write down exactly what our test has to do :

  • Fill in the "Username" textbox
  • Fill in the "Password" textbox
  • Click the "Login" button
  • Check that the displayed username is "Teemo"

The first step is to list every component that will be used. Here :

  • The Username textbox
  • The Password textbox
  • The login button
  • AND the username span

XPath catalog

The second step is to create the xPaths. This part is kind of tricky. The goal is to have an xPath flexible enough to handle some changes but rigid enough to select only the one component we want. Here is one solution :

For the Username textbox : //table//tr[td[text()='Username']]/td/input[@type='textbox']

This xPath means : "The first textbox you find (input[@type='textbox']) in the row (tr) that contains a cell (td) that has the text "Username ([text()='Username']).

For the Password textbox we can use the same, given we change "Username" into "Password" : //table//tr[td[text()='Password']]/td/input[@type='textbox']

Notice the "//" before table and tr. It means that the table can be anywhere in the page and that the line (tr) can be anyone. This is a change resistance.

For the login button, we can take this simple one : //input[@type='button' and @value='Login']

This one selects the button whose text is "Login". It is very unlikely that another button labelled "Login" will be put in the page in the future, so we can keep this wide xPath : the button can now move anywhere in the page, we will still get it.

For the Name span we can use pretty much the same as for the previous texboxes : //table//tr[td[text()='Name']]/td[2]/span

It means "The span of the second cell (/td[2]/span) of the row that contains a cell whose text is "Name" (//tr[td[text()='Name']])

We now have our xPath, but we have important text in them ("Username", "Login"). This is something we can extract and load dynamically, this way if the text is changed by Gazelle's developper, it will be instantly changed in our xPaths (change resistance).

So, let's replace every text by a {x} so we can fill them in later in our PageObject classes :

  • Username textbox : //table//tr[td[text()=''{0}'']]/td/input[@type=''textbox'']
  • Password textbox : //table//tr[td[text()=''{0}'']]/td/input[@type=''textbox'']
  • Login button : //input[@type=''button'' and @value=''{0}'']
  • Name span : //table//tr[td[text()=''{0}'']]/td[2]/span

Note how all quotes became quoted. This is because every curly bracket is used by MessageFormat. To display a bracket we have to escape it. We, here, don't need to display any bracket .. but the escape character is the quote ' so we have to escape every quote.

We can also notice that the Username and Password textbox are exactly the same once we have extracted their particular text. We can, in the xpath.properties, use only one xpath for the two (here it would be named simply "textbox" or something) but we can keep the two like this. It create a duplication, but if we chose to merge the two into one, maybe one day only one element will change. We will then need to split the xpath into two (for example if "Username" is displayed in bold and not "Login"), which is as anoying as keeping a code duplication. There is no one answer here, sometimes it is good to keep two separate xpath, sometimes it is good to have only one. In this case we will merge the two.

What we have to write in our xpath.properties is thus :

LoginPage.TextBox.XPath=//table//tr[td[text()=''{0}'']]/td/input[@type=''textbox'']
LoginPage.Button.Login.XPath=//input[@type=''button'' and @value=''{0}'']
UserPage.Span.Username.XPath=//table//tr[td[text()=''{0}'']]/td[2]/span

 Note how we name our properties : [PageConcerned].[TypeOfComponent].[DetailAboutTheComponent].XPath

At this point we are done with the xpath catalog. Let's work on the PageObject classes.

PageObject classes

We have two different page. We will thus make two PageObject classes. Note that it is arbitraty : it can be adequate to make one class for just a part of a page. The goal is to have a functionnal separation between files.

Let's create our file Login.java. It will contain the skeleton shown above :

public class MiscPageObject extends BasePage{
	public LoginPage(WebDriver driver, LoginProfile lp) {
		super(driver, Loader.instance().config("base.url")
			Pages.LOGIN_FOR_OUR_EXAMPLE.getLink()
			.replace(".xhtml", ".seam"), lp);
	}

	@Override
	protected void loadXPaths() {
		// BasePage.loadXPaths() loads some xpath used by everything
		super.loadXPaths();
	}
}

The first thing to do is to set the default url for the page. We usually want to use a value found in the Page.class enum of the Gazelle project.

Then, we have to load the xPaths we defined above :

@Override
protected void loadXPaths() {
	super.loadXPaths();

	xPaths.put("TextBox.Username",
		MessageFormat.format(
			Loader.instance().xpath("LoginPage.TextBox.XPath"),
			Loader.instance().crowdin("properties.for.username"));
	xPaths.put("TextBox.Password",
		MessageFormat.format(
			Loader.instance().xpath("LoginPage.TextBox.XPath")
			Loader.instance().crowdin("properties.for.password"));
	xPaths.put("Button.Login",
		MessageFormat.format(
			Loader.instance().xpath("LoginPage.Button.Login.XPath")
			Loader.instance().crowdin("properties.for.login"));
}

Note how we fill the xPath with data found with the Gazelle crowdin properties.

Once this is done, we have to create the methods that will be used in our test. We will need :

  • A method to fill in Username
  • A method to fill in Password
  • A method to click on Login

There is no a priori good level of granularity. We can either have on single method for the three action or one for each. In this case one big method is fine : it is not currently likely that we will need to fill in the Username and not the password. But this could be the case, for other tests. Anyway, the goal is to anticipate which solution will last longer. In our case, we groupe the three tasks into one.

It gives us :

public UserPage login(String username, String password) {
	getWebElement("TextBox.Login").clear();
	getWebElement("TextBox.Password").clear();
	getWebElement("TextBox.Login").sendKeys(username);
	getWebElement("TextBox.Password").sendKeys(password);
	getWebElement("Button.Login").click();

	return new UserPage(getDriver(), getLoginProfile());
}

Note that we return a UserPage. This is because we know that we are supposed to be redirected to a user page : it allows method chaining in the test classes.

We won't need anything more with that page for now (fot this test). We are done with this page, but if we need, for another test, new things in this page, we will add the new methods here.

The class UserPage.class, meanwhile, will look like this :

public class UserPage extends BasePage{
	public PreConnectathonResults(WebDriver driver, LoginProfile lp) {
		super(driver, Loader.instance().config("base.url")
			Pages.USERPAGE_FOR_OUR_EXAMPLE.getLink()
			.replace(".xhtml", ".seam"), lp);
	}
	
	@Override
	protected void loadXPaths() {
		super.loadXPaths();

		xPaths.put("Span.Username",
			MessageFormat.format(
				Loader.instance().xpath("UserPage.Span.Username.XPath"),
				Loader.instance().crowdin("properties.for.username")));
	}

	public String getDisplayedName() {
		return getWebElement("Span.Username").getText();
	}
}

We are now done with PageObject, our pages should be able to initialize themselves and to query webpages.

Test classes

The shortest part remains : writing the tests. The result is the skeleton shown above plus methods call corresponding to the steps we identified above :

public class TMEXAMPLE extends BaseTest {	
	@Override
	@Test
	public void run() {
		Object[][] dataSets = new Object[][] {
			{ "Teemo", "ThePassword" }
		};
	withDataSets(dataSets);
	super.run();
	}
	
	@Override
	public void testScenario(Object[] ds) {
		String username = (String)ds[0];
		String password = (String)ds[1];

		UserPage up = new LoginPage(getDriver())
		  .login(username, password);
		
		Assert.assertequals(up.getDisplayedName(), username,
			"The displayed name should be : " + username);
	}
}

Note how we use the dataset to center the data at the same place. At the begining of testScenario() we extract the data from the array, for more readability, then we call the righ methods. At the end we perform an assert.

Our test is now ready. For debugging, it can be run with Alt+Shit+X then N (or in Run > Run as > TestNG test) when you're using Eclipse. If you're using Intellij IDEA you can run the current test by pressing Shift + F10 (or by clicking Run > Run 'CLASS_NAME'). Just commit it and it will automatically be run at the next test session. For automatic result logging, see this section.

Create a pipeline job

The purpose of this page is to explain how to create a Jenkins pipeline job to execute every job by launch only one build.

Prerequisites

  • A jenkins server
  • Gazelle-TM-GuiTesting project job (configuration link)
  • Gazelle-TM-EU-CAT project job to be able to build EU-CAT application

Step 1 : Check Jenkins version

To be able to use pipeline jobs, you need to have a Jenkins server which is running on version 2 at least. It's because Pipeline plugin's suite is natively installed on this version.

Step 2 : Create pipeline job

First go to Jenkins's homepage. Now click on "New job" and choose "Pipeline" and don't forget to fill-in the name ("Gazelle-TM-Pipeline").

In "Pipeline" section, choose "Pipeline script" in Definition.

In Script textbox, you now need to script everything to create a pipeline. You need to specify every step of the pipeline : Building Gazelle-TM-EU-CAT application, launch Gazelle-TM-Deployment to deploy application on Gazelle's server, launch GuiTesting job, launch PagesTesting job.

To do this, we used stage() groovy syntax. Stage is used to separate every step of the pipeline. In each stage, you have to use build() method to trigger build on each job concerned. You can pass parameters if you have make some modifications on GuiTesting or PagesTesting projects configurations to pass some parameters.

 

//Job parameters to be used in other jobs builded
def browser = BROWSER; def xml_file = XML_FILE; //First step stage("Build new Gazelle-TM V7 application"){ build 'gazelle-tm-eucat-v7-SNAPSHOT' } //Second step : Deploy new application and restore last database on Gazelle's server stage("Kujira environnment preparation"){ build 'Kujira-Gazelle-Deployment' } //Third step stage("Running interface tests job : Kujira-GuiTesting"){ //Launch GuiTesting job build with passed parameters build job: 'Kujira-GuiTesting', propagate: false, parameters: [string(name: 'selenide.browser', value: browser), string(name: 'XML_FILE', value: xml_file)] } stage("Running interface tests job : Kujira-PagesTesting"){ //Launch PagesTesting job build with passed parameters build job: 'Kujira-PagesTesting', propagate: false, parameters: [string(name: 'selenide.browser', value: browser)] }

Gui Testing (Automated system testing)

Known errors and problems : How to resolve them

Known errors and problems : How to resolve them

 

Fatal server error:
(EE) Cannot establish any listening sockets - Make sure an X server isn't already running(EE) 

To correct this Fatal server error, connect to the server who run job, then type following command : sudo htop

If htop is not installed, just type : sudo apt-get install htop.

 

Then, press F3 key and type : "xvfb". If Search find something press F9 and select SIGTERM.

Check another time if there's no more xvfb phantom process.

Problem should now be solved.

Fatal server error:
(EE) Server is already active for display 1
	If this server is no longer running, remove /tmp/.X1-lock
	and start again.

To correct this Fatal server error, connect to sthe erver who run job, then type following command : sudo rm /tmp/.X1-lock

Problem should now be solved.

Use TestLink and Jenkins to automate tests results logging

The purpose of this page is to explain how to create a Jenkins job that will run an automated test project an save test results in Testlink.

Prerequisite

Three things are required :

  • A Jenkins server
  • A TestLink server
  • A maven java project with automated tests

Step 1 : Configure TestLink

We start by configuring Testlink. The goal is to add some informations and configuration that will be used by Jenkins. We consider that we starts with an already existing Testlink project that contains the tests that are automated in the java project.

Add custom fields

We add a custom field to every test scenario. Those fields will be used to bind one test to one java class.

Select the right Project ("Test Management") and Test Plan ("System testing (Automated)"). From the main page, go to the "Define custom fields" section at the left of the screen". Click on "Create". Fill in informations :

  • Name : "java_class"
  • Label : "TestNG class name"
  • Available for : "Test Case"
  • Type : "string"
  • Enable on : "Test Spec Design
  • Display on test execution : "No"

Fill in custom fields and configuring test scenarios

Go to the "Test Specifications" section. The list of every test case is displayed. For every test case for whom you have an automated test :

  • Click on this test in the list
  • On the right part of the screen, click on "Add to test plan".
  • Tick "System testing (Automated)" and click on "Add"
  • For every step of the scenario, click on the line on the table, then select "Automated" in the "Execution" column then click on "Save"
  • Click on "Save & exit"
  • On the right part of the screen, click on the Edit button
  • Fill in the field "java_class" with the complete class name, including packages. For example : "net.ihe.gazelle.test.test_case.TM321".
  • Check that the Execution Type select box is at "Automated". Chose Automated if it's not.
  • Click on the Save button.

Create a developper key

Teslink has a special user which corresponds to the automated tester. He is called "Jenkins tester". To identify as this user, Jenkins uses its developper key. Currently it is "3f216bbb9926913a3373274d33c04242". If the key changed, it is accessible in the "My setting" menu when logged-in as the Jenkins tester user in Testlink.

At the home page of testlink, with an admin account, click on "Test project management". Clik on our project ("Test Management") and make sure that "Enable Test Automation (API key)" and "Enable inventory" are properly checked.

We are now done with TestLink. Every test case is considered as automated and is able to tell which java class should run it.

Step 2 : Configure Jenkins

Install the TestLink plugin

Click on "Administrate Jenkins" > "Plugins Management" > "Available" tab > at the "TestLink Plugin" line, check the box. At the bottom of the page, click on "Download now and install after restart". Wait a few seconds.

Go back to jenkins' homepage. Click on "Administrate Jenkins" > "Configure System".

At the TestLink section, fill in informations :

  • Name : the name of the installation
  • URL : the url of the xmlrpc.php page of the TestLink installation. In our case "http://gazelle.ihe.net/testlink/lib/api/xmlrpc/v1/xmlrpc.php"
  • Developper key : the key generated at the end of the TestLink section above
  • Click on "Save" at the bottom of the page

Installing the Extensible Choice plugin

Click on "Administrate Jenkins" > "Plugins Management" > "Available" tab > at the "Extensible Choice" line, check the box. At the bottom of the page, click on "Download now and install after restart". Wait a few seconds.

Installing the Xvfb plugin

Click on "Administrate Jenkins" > "Plugins Management" > "Available" tab > at the "Xvfb" line, check the box. At the bottom of the page, click on "Download now and install after restart". Wait a few seconds.

This plugin is used to create a temporaty virtual frame buffer for the job.

Installing the SSH plugin

Click on "Administrate Jenkins" > "Plugins Management" > "Available" tab > at the "SSH Plugin" line, check the box. At the bottom of the page, click on "Download now and install after restart". Wait a few seconds.

Go back to jenkins' homepage. Click on "Administrate Jenkins" > "Configure System".

At the "SSH Remote Hosts" section, Fill in information for the ssh access to the server that host the tested Gazelle-TM.

  • Hostname : GAZELLE_APPLICATION_SERVER_IP_ADDRESS
  • Port : 22
  • User name : gazelle
  • Keyfile : /home/jenkins/.ssh/id_rsa  (A pair of key must be generated for the jenkins user and the server must be configured to accept him)

This way jenkins can perform some initialization commands in the Gazelle-TM server.

Configure global choice parameters

Go to "Manage Jenkins" menu then click on "Configure Jenkins".

Then scroll to :

Extensible Choice: Available Choice Providers

 

In this section, make sure that Global Choice Parameter is ticked. Then click on "Add New Choice List" button.

Fill-in the new container like this :

  • Name : BROWSERS
  • Choices : List every browser you want to use. (firefox, chrome, ie choices only are supported)

Then click on "Add New Choice List" button.Fill-in the new container like this :

  • Name : XML_FILES
  • Choices :
    • testng
    • institutionmanagement
    • invoicemanagement
    • testingsessionmanagement
    • systemmanagement
    • useraccountmanagement
    • preconnectathonworkflow
    • testng-failed

Then click on "Add New Choice List" button.Fill-in the new container like this :

  • Name : SERVERS
  • Choices :
    • Ip adress or domain name of Gazelle-TM server

Create the job

Go back to Jenkins' home page. Click on "New Job", select "Free Style Project" and fill-in the name ("Gazelle-TM-GuiTesting").

In "source code management" select how your test project will be fetched by Jenkins. If it is on a SVN repository, select "Subversion" and fill-in repository informations. If the project is locally stored select "none".

The currend location is : "https://scm.gforge.inria.fr/anonscm/svn/gazelle/Maven/gazelle-gui-testing/Automated-Test-testNG/branches/idr_testing"

In the "General" section, tick "This build has parameters" then select "Add parameter" and chose "Extensible Choice". Fill-in informations :

  • Name : selenide.browser (Don't worry about selenide word, it's for future updates)
  • Choice Provider : Global Choice Parameter
    • Name : BROWSERS (Global choice parameter defined previously)
    • Default choice : As your convenience

Click another time on "Add parameter" and chose "Extensible Choice" again. Fill-in like this :

  • Name : XML_FILE
  • Choice Provider : Global Choice Parameter
    • Name : XML_FILES (Global choice parameter defined previously)
    • Default choice : testng.xml or as your convenience

Click another time on "Add parameter" and chose "Extensible Choice" again. Fill-in like this :

  • Name : server.ip
  • Choice Provider : Global Choice Parameter
    • Name : SERVERS (Global choice parameter defined previously)
    • Default choice : As your convenience

In the "build" section, select "Add a step" and chose "Invoke TestLink". Fill-in informations :

  • TestLink Version : select the one that has the name defined above
  • Test Project Name : the TestLink project name
  • Test Plan Name : the TestLink Plan name
  • Build name : the name of every test execution in testlink. You can use variables to have dynamic naming. Here :"build-$BUILD_NUMBER"
  • Custom field : the custome field we defined above : "java_class"

In the "Test execution subsection", for "single build test", select "Add action". Then fill-in :

  • Maven Target : "-P jenkins clean test"

In the "Result seeking strategy" select Add Strategy > TestNG class name. Fill-in :

  • Include pattern : "target/surefire-reports/testng-results.xml". It is the path of the xml containing the test results.
  • Key custom field : "java_class", same as above
  • Select "Attach TestNG xml"

Click on save at the bottom of the page

 

Create restore database and application job

Go back to Jenkins' home page. Click on "New Job", select "Free Style Project" and fill-in the name ("Gazelle-Deployment").

In the "build" section, select "Add a step" and choose "Execute shell script on remote host using ssh". Fill-in informations :

  • SSH Site : select the one that has been defined in Administrate Jenkins above
  • Command : 

cd /home/gazelle/unit_tests_selenium/
./restore_database_J7.sh

In the "Actions after build" section, select "Add a step" and choose "Trigger parametrized build on other projects".

Fiil-in informations : 

  • Project to build : Gazelle-TM-GuiTesting
  • Trigger when build is : Stable

Click on save at the bottom of the page

Step 3 : Execute the tests

The jenkins jobs are now completely configured and should work properly.

A sum up of an execution would be :

  • Run a new build of Gazelle-Deployment
  • He execute restore_database.sh to reset the database
  • After build Success, trigger build on Gazelle-TM-GuiTesting
  • Jenkins gather the sources
  • He starts the xvfb instance
  • He asks Testlink to know which test are automated
  • He build the project and start the maven target test
  • TestNG checks the testng.xml file in the java project
  • This file indicates him to run every class in the net.ihe.gazelle.test.test_case package
  • The run() method of all class are executed
  • An xml file cointaining test results is generated by TestNG
  • Jenkins parses this file and logs the result in TestLink. He knows which class corresponds to which test with the java_class field.