In part - I, I have explained about core framework architecture and keywords design.
In this post I am planning to explain the design by taking Mercury Tours.
Test Case Generator - It is an excel file containing macros and two work sheets "KeyList" and "KeyWord".
"KeyList" sheet is the place where "KeyWords" are created.
How to design KeyWords ?
Attaching the "KeyList" screen shot.
I use following naming convention by using Camel Case format for all the Keywords to increase the readability.
Let me explain by taking a KeyWords from the above screen shot.
MtWelcomeMercuryToursPUserNamePasswordEnter
Mt - Mercury Tours (All the Keywords for mercury site will use this prefix)
WelcomeMercuryToursP - Welcome Mercury Tours Page (Page Title displayed on the browser)
UserNamePasswordEnter - To fill User name(Parameter 1) and Password(Parameter 2) fields.
MtWelcomeMercuryToursPSignInBtnSelect
Mt - Mercury Tours (All the Keywords for mercury site will use this prefix)
WelcomeMercuryToursP - Welcome Mercury Tours Page (Page Title displayed on the browser)
SignInBtnSelect - Select Sign In ButtonMtBookAFlightPPassengerDetailsEnter
Mt - Mercury Tours (All the Keywords for mercury site will use this prefix)
BookAFlightP - Book A Flight Page (Page Title displayed on the browser)
PassengerDetailsEnter - To fill First name (P1), Last Name(P2), Meal Type (P3)
Following points to be considered while designing the Key words
1. Each key word should be self explanatory. Don't not bother about the length, you are not typing it, just selecting from the drop-down.
2. Granularity of the Key word.
3. No two keywords should perform the same task or having similar methods for an object.
Major issues with Key Word Driven Framework
1. If user type incorrect key word, your test may fail as it can't resolve the Key word.
2. It would be difficult to remember the input parameters for each key word. Some have one or more and others don't have.
To overcome the above mentioned issues this excel file was created.
Each key word can have none or one or more number of parameters. Each parameter is classified as Mandate(m) or Optional(o) and its name is provided in the corresponding cell as comments (as "User name" in the above screen shot.
All these keywords are listed as drop down in the "Keyword" sheet. Attaching the screen shot.
While creating the test case, user need to select the key words as per the test steps mentioned in the manual test case. Any one can create automation test case as each key word is self-explanatory. Attaching the screen shot.
You can also create key words to capture content from the screen. Like flight conformation details in the mercury tours, attaching the screen shot.
Core framework libraries will fetch each key word from the above sheet and pass it to the "Driver Script Case Structure" function. Attaching screen shots.
(fun - All functions start with this prefix)
Each keyword is calling corresponding page functions. Page function consist of a single page objects methods which are divided into groups as per the KeyWord granularity.
As the function is calling another function to actually execute methods on the web page (In the above screen shot Function 1 calling Function2), it is named as "Dual Function Hybrid Framework"
Page function logic for processing the keyWords.
1. Check for page synchronization. (It doesn't make sense to perform any method if the page is not downloaded successfully)
2. Declare the objects using DP.
3. Execute methods based on the keyword selected by using iKeyWordMethodsSelect. Core framework libraries will automatically pass the excel sheet input parameters into this function.
It is designed in such a way that QTP engineers need to concentrate on the web page objects, methods, they need not worry about the background logic. Core framework libraries automatically fetch and update data from corresponding sheets, this complex process is completely encapsulated. This will reduce the errors and increase the automation project efficiency. This design will not through any runtime errors even if browser not loaded successfully or objects missing from the screen, this error handling process is written separately. Never complicate the QTP code by inserting multiple validations, keep in mid you are testing the application, not the QTP framework or VB code.
Attaching few page function screen shots along with the corresponding web pages.
Re-usability(100%), Maintainability, Modularity, Ability to configure, Ease of use, Stability, Flexibility, Ease of code design are the important factors considered during design.
I have explained the main design, actual framework is more complex (Implementation of Response time calculation, Register methods, Object highlighting, beep on click...)
It also calculate average page response time of all the pages and generate a report in an excel format. Attaching the screen shot of average time and page count.
Evolution of this framework took 3 years, nearly spent 3 months just for optimizing the code.
Please let me know, If I had missed any functionality that has been implemented in any of your frameworks.
I think, this can help you in designing your own framework as per your project requirements.
Good Luck !
I have received many comments regarding the function "garrBroPag((fungarrBroPagFindRowIndex(1,1,))+1,6))" . I have provided explanations below (read my comments). For more clarity, I am attaching the excel screen shot.
----
Hi Bharath, Thanks for such a valuable and Important information about Automation Framework. This is the best automation blog I ever read. Thanks a lot.
ReplyDeleteI have been waiting for this. Please update "QTP - Hybrid FrameWork - Part II by taking Mercury Tours as AUT. Thanks in Advance.
ReplyDeleteHello Sir,
ReplyDeleteWaiting for your article...
hi,bharath,best automation blog ever read,hope we all get to read part 2 asap.thanks
ReplyDeleteHi Bharat,
ReplyDeletebest automation information.if it is in low level easy to understand for me.can u provide.
thanks
raghu
Hello Raghu,
ReplyDeleteI tried to explain it in a detailed way at high level, I hope this can help you in designing your own framework as per your requirements.
But actual code and techniques used are complicated and not possible to explain here. If you require any professional help, you can contact me on my email provided in "View My Profile" link.
-Bharath.
Hi Bharath,
ReplyDeleteThanks lot for creating the good framework. sir i would like requestto you.Plz provide the this framework with low level easy to understand and provide the source code in details for all levels of the framework. I never seen this type framework.
Many Thanks,
Ramamurthy
Hello Ramamurthy,
ReplyDeleteI am typing the same comments.
I tried to explain it in a detailed way at high level, I hope this can help you in designing your own framework as per your requirements.
But actual code and techniques used are complicated and not possible to explain here. If you require any professional help, you can contact me on my email provided in "View My Profile" link.
Hi Bharath,
ReplyDeleteI understood most of the part. One thing is unclear - How do you determine Pass/Fail for each case.
Good question Harpreet.
ReplyDeleteFramework have built in mechanism that automatically FAIL the STEP if page not downloaded successfully or any of the object missing or properties of the object changed(I mean not able to identify on the screen).
If a step get failed it will stop processing that particular test case only, it will automatically continue with the next valid test case.
Apart from this you can implement checkpoints for conformation messages, labels, objects that can decide whether step is PASS/FAIL.
Harpreet,
ReplyDeleteUser defined methods doesn't slow the execution, with dual and quad core CPUs this extra effort is hardly noticeable. During test execution, make sure CPU never go beyond 80%, also monitor the memory used during the test.
Thanks Bharath for providing us good insight and implementation details of your framework.I will try to implement for mercury tours as you have explained.
ReplyDeleteHi Bharath,
ReplyDeleteThanks a lot for responsing for one of my mail regarding silverlight automation!!
Execute ("Set oBroPag="&garrBroPag((fungarrBroPagFindRowIndex(1,1,))+1,6))
In the above line could you please expain what exactly the below functions are doing?
garrBroPag((fungarrBroPagFindRowIndex(1,1,))+1,6))
I know how to take browser and page properties from spreadsheet and created objects but it seems above is more effective.
Thanks in Advances for this and my future questions too :-)
Khasim
Hi Bharath,
ReplyDeleteThanks a lot for responsing for one of my mail regarding silverlight automation!!
Execute ("Set oBroPag="&garrBroPag((fungarrBroPagFindRowIndex(1,1,))+1,6))
In the above line could you please expain what exactly the below functions are doing?
garrBroPag((fungarrBroPagFindRowIndex(1,1,))+1,6))
I know how to take browser and page properties from spreadsheet and created objects but it seems above is more effective.
Thanks in Advances!!
Khasim
Hello Khasim,
ReplyDeleteAs per my client requirement, I was told to calculate every page response time (Time taken to load the page). How to perform this? by using .Sync. But there is a problem, you don't know what would be the next page, it depends on the test case that is being executed.
Then I though make it dynamic, lets load the page properties based on the requirement. Then I have moved all the pages titles into an excel sheet and gave a standard notation that is comfortable to me, so that I will not confuse while creating the script. 1 1 Login Page, 2 1 Home Page, 3 1 Update page. Reason for having two numbers? I have three different login pages, I need to some how distinguish between these three page, so I use 1 1, 1 2, 1 3. I load all the values in an array for searching items. I use above function to retrieve "Page title" property and create a page object.
Advantages
1. If there is any change in the page title, I need to change at one place only.
2. While calculating response time, based on the next loading page, I will create a dynamic page object use .Sync to calculate the exact page download time.
I hope my explanation is clear to you.
-Bharath,
Hello Khasim,
ReplyDeleteI am also updating the post with the excel screen shot for more clarity.
-Bharath
Hi Bharath,
ReplyDeleteThank you for sharing this information.
One question about the functions which you mentioned in this post, it seems to me that these functions are specificly writen for one application. Does it mean that whenever we want to automate new application, we have to rewrite the function library?! If yes, then the framwork is not reusable, am I right?
and one more thing, if we make such an effort to bring all the objects and all the functionalities to the function library (coding them), what is the use of purchesing such an expensive tool? to just execute our codes? Wouldnt it be more reasonable to use some good features of QTP, for example object repository and chekpoints with parametrizing the objects?
Best Regards,
Mohsen
Hello Mohsen,
ReplyDeleteGood questions.
Before providing answers to your questions, I would like to present the ideas behind this framework.
1. QTP test cases should resemble normal test cases.
2. Project Manager or Business Analyst should be able to compose the test cases (People who don't have any knowledge on QTP.
3. By looking at the test report we need to easily understand what things went wrong.
4. Need to execute only the failed test cases in the next run.
5. Test reports need to be emailed to all the stake holders automatically.
Many more...
Q1.Function Libraries
Framework can be designed in two ways. (1) Creating reusable functions and pass object properties and test data (2) Create application specific functions and call those when ever it is required, advantage over here is user need not require to enter object properties, only the test data. I use combination of above two methods.
Yes, you need to rewrite only the function library, but all the core libraries remain the same.
I want to bifurcate the functionality so that it is manageable(small changes in the page) and can be easily discarded if some pages are removed from the application.
It is 100% reusable. What do you mean by re-usability? Let me explain you with an example.
Lets consider there are 3 test cases.
A1.Login
A2.Update data
A3. Log Out
B1.Login
B2.Add data
B3.Log Out
C1.Login
C2.Delete data
C3.Log Out
What is common here ? Login and Log Out is common. I use the same code for all the above three test cases. If there are changes in the login screen I change at only one place, lets assume user name changed User ID. I will open the Login page function just change the object property, it is done. Any change should happen at one place only is my understanding of re usability.
Q2. Purchasing expensive tool?
I consider QTP as a tool that can identify objects on the screen, nothing more than that. That is the reason I started using Selenium these days especially for web applications.
Q3. Object repository and check points
Managing object repository is very difficult and the file size is big. My framework would automatically execute 4 different geographical locations and all the QTP engineers start updating there are lot of issues. So I prefer DP where each engineer handle his own object properties by specifying one unique property, else max two properties.
Q4. Checkpoints
QTP report will be of no use to me, I need status for each step whether it is pass/Fail along with screen shot.
Hi Bharath,
ReplyDeleteYour framework is amazing. All the features incorporated under one. Request you to kindly post the fungarrBroPagFindRowIndex function.It would be very helpful to understand your idea even better.. thanks for the help.
Hello revaraghavan,
ReplyDeleteI have already explained about this function in my comments and also in my post. Basically I want to take away the page titles and put it in an excel file. This will help me to modify easily if there is change in page title and help me to calculate the page response time for each and every click (this is one of the specialty of this framework and my requirement).
Hello revaraghavan,
ReplyDeleteFunction fungarrBroPagFindRowIndex(iPageIndex,iGUITypeIndex, iSubPageIndex)
Dim iCounter
For iCounter = 2 to garrBroPagMaxRow
If garrBroPag(iCounter,2) = iPageIndex and garrBroPag(iCounter,3) =clng( iGUITypeIndex) and garrBroPag(iCounter,4) =clng( iSubPageIndex) Then
fungarrBroPagFindRowIndex = iCounter
Exit For
End If
Next
If fungarrBroPagFindRowIndex = Empty Then
gsExecutionStatusTestCase = conF
gsStatusMessageTestCase = gsStatusMessageTestCase & "Browser Page Index not found PageIndex-GUI Type Index-Sub Page Index : " & iPageIndex & " - " & iGUITypeIndex & " - " & iSubPageIndex & " # Assigning default index as 2 # "
fungarrBroPagFindRowIndex = 2
End If
End F
Hi Bharath,
ReplyDeleteI'm so impressed with your framework and the way you explained in your blog it makes cherry on the cake. Keep up the good work...I would be in touch with you shortly.
Thanks,
Manish
Hi Bharatha,
ReplyDeleteCould you please eloborate more on pass or fail captured in the xls.
which function you have used and how it is implemented.
Nitin
Hi Bharath,
ReplyDeleteIs this framework available for download and experiment with?
Thanks,
Shadi
Salute Bharath, amazing design and great explanation in such a professional manner. I especially love fungarrBroPagFindRowIndex, system resources usage,and the real time monitoring pop-up is the diamond!
ReplyDeleteI am following your idea to do a quick demo(I am 100% sure this framework will shine but have to show it to the boss). One question is: in the "Keyword" sheet, once we select the keyword from dropdown list in "KeyList" sheet,P1...P15 will be populated automatically. You probably wrote your own macro as I know vlookup doesn't copy the comments, not to mention the conditional formatting. Can you please share, if it's not too much to ask?
Thanks in advance.
Download from the following link
Deletehttps://docs.google.com/open?id=0B7URf1xnE2o2b3hCLTFISl80cVE
Thanks Bharath for sharing this wonderful piece of code, easy to read and follow the logic. I bet it is only a small part of your marvelous art. The ideas behind this framework are so advanced that I think it is THE best out there.
DeleteWords can't express how much I enjoy reading your blog. You are not only technically talented, but also an inspiring leader.
Bharath,
ReplyDeleteThe response time feature is absolutely one of the shining stars in your framework. Can you please elaborate more on funMercuryToursSync(311)?
Right now, there is barely such tool that measures front-end response time. LR Tru-Client does it's almost impossible to reuse existing QA assets with Tru-Client.
After a test run, we can look at high-level response times and run those slow ones with either dynatrace or httpwatch for detailed elapsed time.
Best regards,
Magellan
Magellan,
DeleteQTP use .Sync to check whether page loaded successfully.
I have stored all the page titles in an excel file as shown above (at the bottom of the post). There is a function which read all the values in an multidimensional array. When QTP click any button, immediately I call funMercuryToursSync(311), based on the values 311, it will pick the corresponding titles and check for synchronization.
How to calculate the time?
I have override the QTP .Syc function in the following way
If gsExecutionStatusTestCase = EMPTY Then
On Error Resume Next
oObject.Sync
Print err.number
If err.number = 0 Then
Else
gsExecutionStatusTestCase = conF
gsStatusMessageTestCase = gsStatusMessageTestCase & "Page " & oObject.GetTOProperty("title") & " don't exist OR Using Incorrect Keyword " & " # "
End If
oTimer.Stop
' Call funBeep(2)
' Print "Sync Complete: " & oObject.GetTOProperty("title")
' Print oObject.GetTOProperty("title") & "---Bro---" & oTimer.ElapsedTime/1000
If oTimer.ElapsedTime <> 0 Then
Call funBeep(2)
gsResponceTimeTestCase = gsResponceTimeTestCase + oTimer.ElapsedTime/1000
End If
oTimer.reset
End If
oTimer.Start is in the click function.
Hi Bharath,
DeleteSorry for not showing appreciation for your last reply earlier. I found it bit hard to follow here, when you "call funMercuryToursSync(311), based on the values 311, it will pick the corresponding titles and check for synchronization."
Does it mean it picks up iPageIndex=3,iGUITypeIndex=1, iSubPageIndex=1(from the spreadsheet) similarly like fungarrBroPagFindRowIndex?
Regards,
Magellan
Yes, Initially it was designed to retrieve from the excel. But excel object consume around 0.75 seconds(Approx) to create and search the cell value, this created bottleneck when recording response time <0.75 seconds. In order to over come this limitation, during framework start-up, I move the entire excel values into multidimensional array, so that retrieval is quick and hardly take <0.1 seconds.
DeleteLet me know if you are clear with my explanation.
Thanks for your quick reply, Bharath. I understand the multidimensional array part(it is garrBroPag right?) and I was doing the same experimenting with your framework. I still can't connect funMercuryToursSync(311) with garrBroPag(iCounter,2) though, the number 311 you pass into the function, how is it converted to something in a multidimentional array?
DeleteOr if 311 is the identifier in the array, could Function fungarrBroPagFindRowIndex(iPageIndex,iGUITypeIndex, iSubPageIndex) be defined as fungarrBroPagFindRowIndex(iIDentifier) for simplicity and consistency?
Thanks in advance for your patience,
Magellan
ok. I have understood your confusion.
DeleteBrief description about the design architecture.
I treat each page as a separate object, as VB script doesn't support OOPs and sub functions (Function inside a function)I have taken Dual function approach (Function calling another function.
Page object design
Page sync (It is static) - Before executing any method on the page(Click,Set, Select..)
Case1 - Type user name password - No page sync, as page is not moving any where
Case2 - Click Update - Check Sync for update page
Case3 - Click Delete - Check sync for delete page
Logic for page sync and case page sync is same, but case page sync is called from different page objects(Pages), delete page can be called from update, search pages. So I have used function to abstract the actual sync function for 100% re-usability.
As per my definition of re-usability - Any change should be performed at one place only, if it is happening at more than one place it is not a reusable code.
Now I will explain the above concepts with the actual code for the website http://newtours.demoaut.com/
Page Object
'Page logic relating to Mercury tours
'50-1-1-1-X
Function funMtWelcomeMercuryToursPage(iKeyWordMethodsSelect)
' Browser, page object Declaration and syncronization check
Execute ("Set oBroPag = " & garrBroPag((fungarrBroPagFindRowIndex(1,1,1))+1,6))
oBroPag.sync
'Wait(3)
'Start of page logic - Object identification and data insert process
Select Case iKeyWordMethodsSelect
Case 1:
' Keyword id 50-1-1-1-1
'Function funMtWelcomeMercuryToursPUserNamePasswordEnter()
Set oTxtUserName = oBroPag.WebEdit("name:=userName")
Set oTxtPassword = oBroPag.WebEdit("name:=password")
oTxtUserName.Set gsP(1)
oTxtPassword.Set gsP(2)
Case 2:
'Keyword id 50-1-1-1-2
'Function funMtWelcomeMercuryToursPSignInBtnSelect()
Set oImgSignIn = oBroPag.Image("name:=login")
oImgSignIn.Click
Call funMercuryToursSync(211) ' Used to calculcate page response time
Case Else :
Call funKeyWordMethodsSelectCaseStructureElseCase("funMtWelcomeMercuryToursPage",iKeyWordMethodsSelect)
End Select
' End of function to update the flags
Call funExitPageFunction()
End Function
Case page sync logic
Function funMercuryToursSync(iSyncSelect)
If gsExecutionStatusTestCase = EMPTY Then
Wait 0,500
Select Case iSyncSelect
Case 111:
Execute ("Set oBroPag = " & garrBroPag(fungarrBroPagFindRowIndex(1,1,1)+1,6))
oBroPag.sync
Case 121:
Execute ("Set oBroPag = " & garrBroPag(fungarrBroPagFindRowIndex(1,2,1)+1,6))
oBroPag.sync
Case 211:
Execute ("Set oBroPag = " & garrBroPag(fungarrBroPagFindRowIndex(2,1,1)+1,6))
oBroPag.sync
Case 311:
Execute ("Set oBroPag = " & garrBroPag(fungarrBroPagFindRowIndex(3,1,1)+1,6))
oBroPag.sync
Case 411:
Execute ("Set oBroPag = " & garrBroPag(fungarrBroPagFindRowIndex(4,1,1)+1,6))
oBroPag.sync
Case 511:
Execute ("Set oBroPag = " & garrBroPag(fungarrBroPagFindRowIndex(5,1,1)+1,6))
oBroPag.sync
Case Else :
gsExecutionStatusTestCase = conB
gsStatusMessageTestCase = "funMercuryToursSync - iSyncSelect ID: " & iSyncSelect & " Not defined in the case structure"
End Select
End If
End Function
I hope you can understand the concept now.
Bharath,
ReplyDeleteThanks for your detailed explanation. It is crystal clear now. I simply wish I had found such intelligent design 2 years back. But some say it's better late than never.
The other important part in your framework , Error logic shows experience and consideration too. I completely agree with you that QTP result means nothing to me. If you don't mind, can you please post a small example of the error logic, for example funMtWelcomeMercuryToursPSignInBtnSelect(), what if the object doesn't exist, how do we wrap out QTP so test will move on to the next test case. I've been reading your blog religiously in the last 2 weeks. I hope I didn't bother you too much.
Sincerely,
Magellan
Generally critical problem for any framework are run-time errors, QTP stop executing the test. I have seen many framework designs where "On Error Resume Next" is used to over come run-time errors. You can't predict the error, based on the application there are hundreds of objects, any object properties can change, so using "On Error Resume Next" is not good.
DeleteRead following links
http://bharath-marrivada.blogspot.in/2010/03/qtp-always-use-registered-methods-for.html
http://bharath-marrivada.blogspot.in/2010/03/qtp-when-to-use-on-error-resume-next.html
Page object design (I have added one more new function now)
Page sync (It is static) - Before executing any method on the page(Click,Set, Select..)
Case1 - Type user name password - No page sync, as page is not moving any where
Case2 - Click Update - Check Sync for update page
Case3 - Click Delete - Check sync for delete page
...
CaseN -
Call funExitPageFunction()
I have used registered methods to override all the QTP methods that I use.
What is the difference between my own method and QTP method?
Both are same, but I used certain checks before executing the actual method.
As mentioned above funMtWelcomeMercuryToursPSignInBtnSelect() is used to select SignIn image. Before I click the Image need to check whether (1)Image Exist (2) Visible (3) Enabled . If all my checks are successful then only I will try to click the Image, so I am eliminating the run time errors.
For example, following case statements
Set oImgSignIn = oBroPag.Image("name:=login")
oImgSignIn.Click
Call this function to click the image
Public Function ImageClickUserDefined (oObject)
If gsExecutionStatusTestCase = EMPTY Then
If oObject.Exist(10) Then
If gsObjectHighlight = "ON" Then
oObject.Highlight
End If
If oObject.GetROProperty("disabled") = 0 Then
oObject.Click
oTimer.Start
Call funBeep(1)
' Print "WebButton - Timer Start"
' Print "oTimer.ElapsedTime/1000 :" & oTimer.ElapsedTime/1000
Else
gsExecutionStatusTestCase = conF
gsStatusMessageTestCase = gsStatusMessageTestCase & "Object Image: " & oObject.GetTOProperty("html id") & " in disable state " & " # "
End If
Else
gsExecutionStatusTestCase = conF
gsStatusMessageTestCase = gsStatusMessageTestCase & "Object Image: " & oObject.GetTOProperty("html id") & " don't exist on the page " & " # "
End If
End If
End Function
All the methods that I used are defined in this manner.
Great thinking. The more I dig into the details, the more it gets complicated. I might not be able to digest all in such short time frame. But I enjoy reading it, seriously. And I might come back with more questions.
ReplyDeleteRegards,
Magellan
Hi Bharath,
ReplyDeleteYour Articles are really good....
Thanks,
Praveen Sunku (Bob)
Hi Bharath,
ReplyDeletethis is a great article on the framework. thanks for posting it. i may be replying late. i have few clarification as i am defining a new framework using few attributes from your framework.
for the error logic we are creating user defined functions to avoid the run time error. for a test case say i have 4 statements
Browser().page().userdefinedwebbutton().fnclick
Browser().page().webedit().fnset "text1"
Browser().page().webedit().fnset "text2"
Browser().page().webbutton().fnclick
if a test fails in the second statment, we need not process the following statements. we will have the status as fail from the second stmt, how do we skip in executing the consequtive stmts and update the results. ur help is highly appreciated
Read following links
Deletehttp://bharath-marrivada.blogspot.in/2010/03/qtp-always-use-registered-methods-for.html
http://bharath-marrivada.blogspot.in/2010/04/qtp-how-to-handle-different-messages.html
http://bharath-marrivada.blogspot.in/2010/04/qtp-how-to-handle-different-messages.html
Hi Bharat,
ReplyDeleteCould you please share the script as well? This will help me understand and implement your framework in our project.
Thanks,
Ritesh
How to open safari browser using webdriver?
ReplyDeleteThis post belong to QTP...any way read the following link
Deletehttps://code.google.com/p/selenium/wiki/SafariDriver
hi bharath, i need qtp framework trainig, could u provide class room training in hyderbad? is it possible?
ReplyDeleteemail me at mbr.reddy@gmail.com
DeleteHi Bharath,
ReplyDeleteCould you please share QTP framework code with me. I will enhance this further as per my requirement
If you can say me those enhancements I will share the framework.
DeleteEmail id?