Chat Beta

Showing posts with label Dev Stuff. Show all posts
Showing posts with label Dev Stuff. Show all posts

12/21/2016

Unit Tests With Telerik- A Simple End-To-End UI Test Automation Solution

Thinking about automating functional tests (UI) for the web application that I work with, and having the idea to make it an "End-to-end" solution where tests are written and pushed to the test runner, are then picked up and run against SUT automatically, and finally a result of the test status is sent to user in the form of a mail, following was an attempt that I made during last couple of days which I guess works fine at the moment. Sharing the experience hoping it will shed some light to others

If you have a working CI environment like Jenkins or TFS, it may be handy as it  will do the most of the things out of the box, but in my case I had to come up with my own way of achieving it.

The entire project can be found here .

Following technology/ tools stack was used to achieve this
 1. Telerik Testing Framework - This is the tool used for writing UI tests. You might probably be using Selenium webdriver over Telerik as your preference  
 2. Visual Studio 2013/15- 'VSUnit' unit-test framework with C#  
 3. Python scripting language - To automate some background tasks like sending mail, reading log files, achieving files etc.  
 4. DOS batch files- To initiate the test building and running  
 5. SMTP Server - This is to send out the status mail that is composed at the end of the test run. I use a free version of an easy to setup mail server called MailEnable for this purpose.  
 6 . Windows Task Scheduler -To schedule test runs  
 7. Command line tools - MSBuild.exe, MSTest.exe - This is to build the visual studio project and run the tests in 'Test Runner' machine  
 8. Dedicated VM for running the tests- Which I call it the 'Test Runner'. Test Runner will run the UI tests at scheduled time without user-intervention  
 9. Github repository for version controlling and pushing tests to 'Test Runner' after they are being developed and tested locally.  
 10. Git command "git pull" to pull new changes back to the test runner  

Telerik Test Studio is a commercial grade UI Test automation platform that helps QA professionals to automate their functional test in Web , Silverlight and WPF applications. 

Telerik Test Framework is their .net library that the Test Studio is built upon which can be freely downloaded from there site. Its free to use under certain conditions. It contain automation infrastructure library + various other libraries to support browsers, proxy , Silverlight etc. We will have to write code to accomplish what is expected from our tests.

We adopt the 'Page object model" when handling locators which could reduce potential maintenance overhead in the long run. If you want to know what it is, click here and here

Once the Telerik Framework is installed in the PC, we can start using it via Visual studio.

We start with a new project in VS, and select "Unit Test Project" from "Test" under "Templates"




 We will need to add references to the solution to make it work




Then use "VsUnit" test under "Telerik TestingFramework" . VsUnit is the Visual studio team test with unit testing framework. Since we develop our tests using Visual studio its a better option to use VsUnit rather than other unit test frameworks lik MBUnit or NUnit




In VsUnit test template, you get several methods and you can write your tests under [TestMethod] section




Once tests are built, they will appear in "Test explorer" in VS



we can manually execute tests from the "Test explorer"




The project  structure looks like following



I have developed some utility methods that can be reused in tests which ease the development efforts. You will find them under 'common methods' folder

Following methods are available.
 1. A separate class has been written to handle database interactions. It has methods to connect, execute and retrieve result from the database.  
 2. A method is implemented to capture screenshots and error logs, which can be used for error handling purposes.  
 3. An email sending method has been added which can be called in a test when you need to send a mail to a recipient.  
 4. A method to provide keyboard inputs to a text field in your application. It has been overloaded so that it can handle text fields in both documents and iframes.  
 5. A random data generating method which can be used as an input.  

Tests can be scheduled to run automatically against a SUT in the following manner.



We can create a batch scripts to initiate the process, first by pulling the new changes from the remote repository to the test runner, and then run MSBuild.exe to rebuild the project and run the test using MSTest.exe. Here the result output is directed to a log file instead of them being printed in the console. This log file later be fed to the email body.
 @echo off 
 "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe" "C:\GIT\Telerik\CS.sln" /p:configuration=debug  
 call pull.bat
 "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\MSTest.exe" /testcontainer:"C:\GIT\Telerik\CS\bin\Debug\CS.dll" /testsettings:"C:\GIT\Telerik\Settings.testsettings" >>"C:\GIT\Telerik\CS\TestResults\Summary.log"  
 exit  

Also add a testsettings file to the project where we can invoke some actions before and after the test execution


For example, here I call a script to delete files in the result log before a new test run begins and call a batch script to send out the mail after the test run is finished. 

How the mail is composed (by reading log files and attaching error-dumps), is done using a python script which can be found in the git repo.
 https://github.com/tharinda2012/Telerik/blob/master/CS/SupportingScripts/SendSummaryMail.py  
 https://github.com/tharinda2012/Telerik/blob/master/CS/SupportingScripts/utils.py  

The parameters that must be fed to the tests can be given in a config like following


The Status of each test run will be notified to the user via an email as below




In case you use mouse or key board actions inside the test scripts to achieve certain tasks, you might encounter issues when running tests if the running machines are locked (no active session). In this case following approach can be used to remedy it.





6/30/2014

Selective File copying using Python

Last few days I had a chance to learn a new scripting language- Python. 

I installed python 3.4.1 on windows, as well as a free IDE called "Eric" from http://eric-ide.python-projects.org/

After learning some basics of Python for couple of hours, I started working on an assignment which is to do a selective file copy from a source and save it to a desired destination.

Problem is something like following:

We have certain files in a remote machine, that we need to download daily. These files are build files created every night by the build server. Files are saved in the disk based on the development branch that is specified in build definition.A main directory is created with a build number, then inside the directory, several other sub directories are created for different products

We are tasked with getting the latest build from the server each following morning. A DOS based batch script has been used for this purpose, and  I thought of re-writing same with this new scripting language Python

The process that I constructed to achieve this as follows:

1. Sort main level directories based on the date they are created. This way we can find out the latest added directory- which also carries the latest build number
2. Select the latest directory
3. Select what sub directories to be copied (products)
4. Copy the required files and folders to the destination
5. Exception handling
6. Logging
7. Notify member by e-mail

Several different Python commands, modules are required for this tasks to accomplish

First I had to import following libraries
import os,smtplib,subprocess


With library "os", I could use listdir() method  which returns a list containing the names of the entries in the directory given by path. So this command created me a list having main directory names("50041","50042","50043" etc) as list items

dir_namelist.sort(key=lambda x: os.stat(os.path.join(source, x)).st_ctime)

dir_namelist=os.listdir(source)

source is the path to the main directories. Ex: source=r'\\tfs-file-01\\Builds\\BranchXXX'

Next. the list is in arbitrary order. So I had to sort the list based on the date/time. Following can be used for the purpose
Or we can sort the list by using sorted()command

dir_namelist=sorted(dir_namelist, reverse=True)
This will sort the list in descending order.

"os.stat". "os.path.join() are methods from "os" library

To get the latest, I used following command
index=len(dir_namelist) gets the length of the list
mydir=dir_namelist[index-1]

or simply  mydir=dir_namelist[0]


"mydir" is actually the main directory name that we are targeting(ex:50041)

To copy the files, I used copytree() method in shutil library.
shutil.copytree(CS, dest_cs, symlinks=False, ignore=None, ignore_dangling_symlinks=False)

or robocopy in windows:

subprocess.call("robocopy /S %s %s" %source, destination)

CopyTree() recursively copy an entire directory tree rooted at source.

To log certain activity, I write to a txt file using file.write() method
logfile=open(basedir+"\\"+"log.txt", "a") . file is created with mode=a which is to "append" content to the file


To send email, smtplib library is used
sender = 'builds@test.com'
receiver = 'user@test.com'
message="""From: Auto Builder <builds@test.com>
To: Build downloader <user@test.com>
Subject:your subject 
This is an automatically generated e-mail. Do not reply

"""
E-mail can be sent using following methods
smtpObj = smtplib.SMTP('mal.test.com')
           smtpObj.sendmail(sender, receiver, message)

The complete script looks like following 

 import os , smtplib, subprocess  
   
 #define variables for source and destination flder paths  
   
 #source='E:\\Python\\excercises\\test'  
 #destination='E:\\Python\\excercises\\dest'  
   
 #Create a Pythn list to hold all the folder names avalable under the source  
 dir_namelist=os.listdir(source)  
   
 #Sort the list descending order  
 dir_namelist=sorted(dir_namelist, reverse=True)  
   
 #Getting the first list item which actualy represents the latest folder name available  
 mydir=dir_namelist[0]  
     
   
 #Constructing Path to the latest build available in the build server.  
 final_source=os.path.join(source, mydir)  
   
 installerfolder=final_source+"\\_Installers"  
 CS=installerfolder+"\\cs"  
 SMweb=installerfolder+"\\SM.web"  
 SMwin=installerfolder+"\\SM.win"  
   
 basedir=os.path.join(destination, mydir)  
   
 class utilities:   
   i=1   
   j=1   
   #Define variables to hold email parameters  
   SMTPServer='mail.xxx.com'  
   sender = 'builds@downloadSL.com'  
   receiver = 'xxx@xx.lk'  
   def logging(self, logstr, b_dir, myd):   
     basedir=b_dir     
     mydir=myd  
     if os.path.exists(basedir):  
       logfile=open(basedir+"\\"+"log.txt", "a+")        
       if self.i==1:  
         logfile.write("Latest available successful build is " + mydir + "\n\n")   
         logfile.write("Files are copied to: " +basedir +"\n\n")  
         logfile.write("Following files copied:\n")  
         logfile.write("------------------------------\n")  
         self.i+=1  
       logfile.write(logstr)  
     else:  
       print("basedir is not available")  
         
     
   def sendmail(self, message):   
     try:  
       smtpObj = smtplib.SMTP(self.SMTPServer)  
       smtpObj.sendmail(self.sender, self.receiver, message)    
     except Exception as e:  
        print("e-mail sending error occured: " + e)  
         
   def readlog(self, b_dir):  
     basedir=b_dir  
     if os.path.exists(basedir):  
       log=open(basedir+"\\"+"log.txt", "r")  
       if os.stat(basedir+"\\"+"log.txt").st_size!=0:  
         logstr=log.read()  
         return str(logstr)  
         log.close()  
     else:  
      return "empty log file"   
      log.close()   
     
   def recursive(self):  
              
         myd=dir_namelist[self.j]  
         final_source=os.path.join(source, myd)  
         instfolder=final_source+"\\_Installers"   
         b_dir=os.path.join(destination, myd)  
         pCS=instfolder+"\\cs"  
         pSMweb=instfolder+"\\SM.web"  
         pSMwin=instfolder+"\\SM.win"   
         self.j+=1         
         main(myd, instfolder, b_dir, pCS, pSMweb, pSMwin)  
           
 #initiate an object from "helper" class  
 help=utilities()  
   
 #e-mail messages  
 def m_success(b_dir, myd):  
   mydir=myd  
   msgsuccess = "From: Auto Builder " + "\n" + \  
       "To: Build downloader " + "\n" + \  
       "Subject: " +mydir + " downloaded successfully." + "\n\n" + \  
        ""+help.readlog(b_dir)  
     
     
   return (msgsuccess)  
   
 def m_notavail(myd):  
   mydir=myd  
   msgnotavail="From: Auto Builder " + "\n" + \  
       "To: Build downloader &lt;xxx@x.lk&gt;" + "\n" + \  
       "Subject:" +mydir + " _Installer folder is not available. Retrying previous..." + "\n\n" + \  
       "This is a system generated e-mail. Do not reply"  
   return (msgnotavail)  
   
 def m_exists(myd):   
   mydir=myd  
   msgexists="From: Auto Builder " + "\n" + \  
         "To: Build downloader &lt;xxx@x.lk&gt;" + "\n" + \  
         "Subject:" +mydir + "  build already exists." + "\n\n" + \  
         "This is a system generated e-mail. Do not reply"  
   return (msgexists)      
     
 def m_initiated(myd):    
   mydir=myd   
   msginitiated="From: Auto Builder " + "\n" + \  
       "To: Build downloader &lt;xxx@x.lk&gt;" + "\n" + \  
       "Subject:" +mydir + " build download started..." + "\n\n" + \  
       "This is a system generated e-mail. Do not reply"  
   return (msginitiated)   
   
 def m_retry(myd):  
     
   msgretry="From: Auto Builder " + "\n" + \  
         "To: Build downloader &lt;xxx@x.lk&gt;" + "\n" + \  
         "Subject:It seems that latest build folders are not available. retrying one before...\n\n" + \  
         "This is a system generated e-mail. Do not reply"  
   return (msgretry)   
   
 def main(myd, instfolder, b_dir, pCS, pSMweb, pSMwin):  
   try:  
     if myd is not None and instfolder is not None and b_dir is not None :  
       mydir=myd  
       installerfolder=instfolder  
       basedir=b_dir  
     if pCS is not None and pSMweb is not None and pSMwin is not None:  
       CS=pCS  
       SMweb=pSMweb  
       SMwin=pSMwin  
         
     if not os.path.exists(installerfolder):  
       print("installer folder is not available for build "+ mydir)   
            
       help.sendmail (m_notavail(myd))       
       print ("Successfully sent email")  
       help.recursive()  
         
     elif os.path.exists(basedir) :    
       print( mydir + " build already exists")    
         
       help.sendmail(m_exists(myd))       
       print ("Successfully sent email")  
         
         
     else:   
       print("Latest available successful build is " + mydir)    
       flag=0  
       #create the base directory  
       os.mkdir(basedir)  
       #create robocopy log file  
       open(basedir+"\\"+"robolog.txt", "w+")   
       #download CS  
       dest_cs=basedir + "\\cs"   
       if os.path.exists(installerfolder+"\\cs"):  
         help.sendmail(m_initiated(myd))  
         print("CS build copy initiated...")        
         subprocess.call("robocopy /S "+ CS+ " " + dest_cs + " /LOG+:" + basedir + "\\robolog.txt")        
         help.logging("- CS\n", b_dir, myd)      
         print("CS build copy completed")  
       else:  
         #os.mkdir(basedir)  
         help.logging("- CS files NOT found \n", b_dir, myd)  
         print("CS files NOT found")  
         flag+=1  
          
       #download Web  
       dest_web=basedir + "\\SM.web"   
       if os.path.exists(installerfolder+"\\SM.web"):  
         print("SM Web build copy initiated...")       
           
         subprocess.call("robocopy /S "+ SMweb+ " " + dest_web + " /LOG+:" + basedir + "\\robolog.txt")  
         help.logging("- SM Web\n", b_dir, myd)  
         print("SM Web build copy competed")  
       else:  
         help.logging("- SM.Web files NOT found\n", b_dir, myd)  
         print("SM.Web files NOT found")  
         flag+=1  
       #download win  
       dest_win=basedir + "\\SM.win"    
       if os.path.exists(installerfolder+"\\SM.win"):      
         print("SM Win build copy initiated...")     
           
         subprocess.call("robocopy /S "+ SMwin+ " " + dest_win + " /LOG+:" + basedir + "\\robolog.txt")  
         help.logging("- SM Win\n", b_dir, myd)  
         print("SM Win build copy completed")  
       else:  
         help.logging("- SM.Win files NOT found \n\n", b_dir, myd)  
         print("SM.Win files NOT found")  
         flag+=1  
       if flag==3:  
         print('It seems that latest build folders are not available. retrying one before...\n\n')  
         help.logging("It seems that latest build folders are not available. retrying one before...\n", b_dir, myd)  
         help.sendmail(m_retry(myd))  
         help.recursive()  
         flag=0  
       else:  
         #sending mail      
         print ("Successfully sent email")      
         help.sendmail(m_success(b_dir,myd ))   
         
       
   except Exception as e:  
       #exception handling  
       print ("Error occured:" , e)        
       msgerr = "From: Auto Builder " + "\n" + \  
             "To: Build downloader &lt;xxx@x.lk&gt;" + "\n" + \  
             "Subject: " +str(e) + " : An error has occured." + "\n\n" + \  
              "This is a system generated e-mail. Do not reply"  
       help.sendmail(msgerr)  
       help.logging(str(e), b_dir, myd)  
       help.recursive()  
       #input("Press to exit")  
         
 if __name__=='__main__':  
   
   main(mydir, installerfolder, basedir, CS, SMweb, SMwin)  

10/11/2012

Classic ASP, Global.asa, IIS 7.5

When you deploy a classic asp application having variables (application, session) declared in a Global.asa file in IIS7 or above, you might come across an error something like this 

ADODB.Recordset error '800a0bb9'
Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another.

This is probably due to the fact that  global.asa file has not been initialized at start up. The easiest solution for this is to use "convert to application" option in IIS. 

In IIS, select the folder containing Global.asa file and then right click and select "convert to application"



10/06/2009

Display records/results across multiple pages in ASP

There is a simple way to show results across multiple pages if you use ADO. That is basically to divide the ADO record set in to multiple pages and show results in each page in the browser. A number of articles can be found regarding this in Web.

Few findings are worth noting

A recordset object has number of properties, methods and events.
Properties like absolutepage, cursorlocation, cursortype, pagecount, pagesize are required to do this job.

Say you have a recordset with 100 records
And you set recordset.pagesize = 20
That means your pagecount = 5(100/20)
Absolutepage indicates which page the cursor is locating in the recordset.

So set the recordset with following properties

with myrecordset

.cursorlocation=3

.cursortype=3

.pagesize=6

end with

then instantiate the recordset like following:

myrecordset.open sql datapovidername , , , adcmdtext

set the absolutepage property like this:

myrecordset.absolutepage=pg

'pg' is a variable used to store the page number retrieve at the time of user click on the page number.

Then its time to execute the recordset in a loop to retrieve the records

10/03/2009

Developing a basic ASP application

Recent 2-3 weeks I 've been involved in developing a small data entry application together with our group members as one of our internal projects. We had this urge to do it by our selves rather than to get it done by some dev guys, mainly because to get a respite from the monotonous life style of being a tester. Luckily I had the chance to learn some stuff of coding thanks to Google,of course the resource donors.

Application was decided to be done in classic ASP( which was the only language that I knew to be frank ;) I used to do some coding with ASP when I was a student). So started with ASP/VB Script/ADO at the middle and HTML/JavaScript at the front and with MS Access at the back end.

here is a sample code to counter it. Its simple. first line of defense. Just sanitize the input. Check for dangerous characters and replace them with harmless. .I f you have a query string like:

"select username,password from table_login where username='"&userval&"' AND password='"&passval&"'",
then this can be easily be manipulated and gain access to internal resources by using a string like " ' or 1= 1 " (there are variations of this type) as your inputs.
use the following piece of code.
username=Replace(Request.Form("username"), "'", "''")
The potentially dangerous character " ' " is replaced here. Use of stored procedures is not allowed with Access. Its a better method of countering the hazard

Another issue with log in
What if the user types the URL of an internal resource other than the login page in the address bar and gain access, bypassing the login? How do you redirect the user back to the login page if user is not authenticated?
Use a session variable (session("Authenticated") as a flag.
If user is validated, set to session("Authenticated") =1 else to 0.And use this code in pages where necessary.
Declare session("Authenticated") =0 variable in global.asa file under sub Session_OnStart event.
global.asa--
sub Session_OnStart
Session("Authenticated") = 0
end sub
Writing error message to the request page after an unsuccessful login attempt
Say you have login.asp page where you accept the user inputs and pass it to validate.asp page to validate. There it finds the credentials given are incorrect and user is redirected to login.asp again with a error message.
if login unsuccessful then
response.redirect(login.asp)- this works
response.write("wrong username/password")- this won't work
end if
so how to write this error message in the login.asp?

response.Redirect("login.asp?status=unsuccess"). pass a parameter 'status' together with the redirection. and capture it at the login.asp page like this:
if request.querystring("status")="unsuccess" then
response.write ("Wrong username or password")
end if
Some client side java script validations used
1. checking the inputs are not blank
var input1= form1.input1.value;
var
input2= form1.input2.value;
if (
input1.length == 0 || input2.length == 0)
{
alert ("Fields cannot be empty")
}

2. Number validation
verify that the input only accepts numbers
var val=document.form1.id.value==parseInt(document.form1.id.value);
if (!val)
{
alert("Numbers only")
}