Three views are used to enter the contact information:
bp_cont/ContactDetails
bp_cont/SalesEmployee
bp_addr/StandardAddress
The query API can retrieve all the input fields in the SalesEmployee
view.
Searching for Web Controls
The following script shows how to create and execute a query:
The VB Script accesses the HTML document by calling the CrmController
method, and creates a query using theCreateQuery
method. The query is empty. Specify the criteria to identify the controls we are looking for, using filters and conditions. In our example, a single filter with two conditions is specified. The filter is created using the SetFilter
method, and the two conditions are added using the AddCondition
method.
Once the criteria are set, the query can be executed with the following methods:
Select
: returns a collection of CRM Web controls which match the criteria.
SelectSingle
: returns the first CRM Web control which matches the criteria, not a collection
The next exemplary sections describe how to use the results of the Select
and SelectSingle
methods.
Setting the Input Field Value
Our first example explains how to create and execute a query. Our second example will show how to use the result, and manipulate the retrieved CRM Web control object.
Syntax
Dim controller, query, filter, crmControl, controlUri
Set controller = CrmController()
Set query = controller.CreateQuery()
Set filter = query.SetFilter()
filter.AddCondition "crm.view", "=", "SalesEmployee"
filter.AddCondition "crm.type", "=", "inputField"
Set crmControl = query.SelectSingle()
If Not crmControl Is Nothing Then
controlUri = crmControl.GetControlUri()
controller.SetElementValue controlUri, "1234"
End If
This script invokes the SelectSingle
method to retrieve a single CRM Web control (so there is no need to iterate through a collection). It checks whether the crmControl
variable is set, and asks for the URI identifying the CRM Web control, by calling the GetControlUri
method. This call returns a URI which complies with the ones that the runtime library expects when you use default components.
In this example, this is the URI that identifies the CRM Web control:
crm.area=WorkArea; tag=INPUT;
crm.id=A_bp_cont:V_SalesEmployee:T_inputField:C_salesemployee:I_struct.salesemployee
With this URI, the script can invoke one of the methods exposed by the CRM Web controller API to perform actions that are normally performed with default components. In our example, the script sets the value of the input field to 1234, by calling the SetElementValue
method.
The scenarios that we have seen so far are not dynamic, so the query API is not necessary, because the default components are normally sufficient to automate them. The next example explains how to select CRM Web controls by filtering on the values of the INPUT
HTML element that the browser displays.
Searching for Web Controls in a Table
We must automate a dynamic scenario in which the script selects the first row in which the partner function is Sales employee and Partner ID is 244.
The difficulty is that the conditions are to be checked against the CELL
contents in the same row but a different column, not against the CRM Web control that is being searched. This kind of query can be very complex when it searches the internal hierarchy of HTML elements. To make it simple, the filters and the conditions have been specified to hide the complexity and handle common use cases like this one.
This is what the VB Script coding for row selection looks like:
Syntax
Dim controller, query, filter, selector, controlUri
Set controller = CrmController()
Set query = controller.CreateQuery()
Set filter = query.SetFilter()
filter.AddCondition "crm.application", "=", "btpartner"
filter.AddCondition "crm.view", "=", "Partner"
filter.AddCondition "crm.context", "=", "btpartner"
filter.AddCondition "crm.type", "=", "ROW_SELECTOR"
filter.AddCondition "crm.column:btpartner.partner_no", "=", "244"
filter.AddCondition "crm.column~Partner Function", "=", "Sales employee"
Set selector = query.SelectSingle()
If Not selector Is Nothing Then
If Not selector Is Nothing Then
If Not selector Is Nothing Then
End If
This script shows the types of conditions that the query supports. The conditions here check the attributes of the CRM Web controls. The API lets you check the regular HTML attributes and properties, and some CRM-specific attributes to check the fragments of the CRM IDs.
The object spy (or the developer tool from MS Internet Explorer) shows the CRM ID of the first row selector:
A_btpartner:V_Partner:T_ROW_SELECTOR:C_btpartner:I_:R_1
The fragments provide meta-information about the CRM Web control:
A_
: application
V_
: view name
T_
: type
C_
: context node
I_
: interface; for input fields, the technical name
R_
: row number
The condition can check the value of these fragments using CRM attributes like the following:
crm.application
: checks the application
crm.application
: checks the view name
crm.application
: checks for the UI element type
...
The following filtering guarantees that the query will only return the row selectors:
Syntax
filter.AddCondition "crm.application", "=", "btpartner"
filter.AddCondition "crm.view", "=", "Partner"
filter.AddCondition "crm.context", "=", "btpartner"
filter.AddCondition "crm.context", "=", "btpartner"
We now need to select the second one only (the second visible line). If our scenario was not dynamic, we could select the second row directly, using a default component (for example SelectRow
) and specifying the URI:
A_btpartner:V_Partner:T_ROW_SELECTOR:C_btpartner:I_:R_2
Because our scenario is dynamic, the row we want to select may not always be in the second position. So we need to search for it by checking the value of the other columns. Two syntaxes are possible when you filter for column values, depending on whether you pass the column title or the column technical name.
The syntax can be crm.colum:<context>.<interface>
or crm.colum~<Column Title>
, so the following two ways of filtering are equivalent:
Syntax
filter.AddCondition "crm.column:btpartner.partner_no", "=", "244"
filter.AddCondition "crm.column:btpartner.partner_fct ", "=", "Sales employee"
...
Syntax
filter.AddCondition "crm.column~Partner ID", "=", "244"
filter.AddCondition "crm.column~Partner Function", "=", "Sales employee"
In our example, both syntaxes are used:
Syntax
filter.AddCondition "crm.column:btpartner.partner_no", "=", "244"
filter.AddCondition "crm.column~Partner Function", "=", "Sales employee"
Iterating Through a Set of Web Controls
The following code shows how to iterate though the set of CRM Web controls that are retrieved by the query. The collection implements the Web Control Collection Interface described in Technical Information for the Query API.
Syntax
Dim collection
Set collection = query.Select()
If collection Is Nothing Then
MsgBox "Nothing has been found"
Else
MsgBox "Element Found: " & collection.Count
Dim childControl
for i=0 To collection.Count-1
Set childControl = collection.ControlAt(i)
MsgBox "Uri: " & childControl.GetControlUri()
Next
End If
Note
The VB Script For Each
statement is not supported.
Making Information Available to Subsequent Components
Usually, only a part of the tested scenario is dynamic and requires custom coding and queries. Then it can be useful to make the URI of the retrieved CRM Web controls available to subsequent components, and continue the scenario using default components. This is typically done with the SetToken
method, as shown in the following example:
Syntax
Dim crmControl, crmControlUri
Set crmControl = query.SelectSingle()
If crmControl Is Nothing Then
CBTA.Log "Unexpected Situation - Control not found"
Else
crmControlUri = crmControl.GetControlUri()
CBTA.Log "Uri: " & crmControlUri
ExecutionContext.SetToken "rowSelector", crmControlUri
End If
Subsequent components can reuse the value that is stored in the execution context, using the %token%
syntax. In this example, the subsequent components can use %rowSelector%
to perform operations on the ROW_SELECTOR
that was selected by the query.
Writing Information to the Execution Report
The CBTA object provides the following methods:
CBTA.Log
: troubleshoots complex scenarios
Public Sub Log(message)
CBTA.Report
: provides feedback to the tester by the Execution Report
Public Sub Report(Severity, Topic, Message, Options)
Reducing the Scope of a Query
By default, the CRM queries automatically search for CRM Web controls in the FRAME
work area of the main browser window. The query analyzes the content of the body of the HTML document, and checks the filters and conditions for all CRM Web controls that were found.
For performance reasons, you can reduce the scope of the query and specify where to start searching. For instance, when searching for CRM Web controls of type ROW_SELECTOR
, first specify the parent container HTML table element, using the ParentControlUri
property of the query. For example:
Syntax
'--------------------------------------------
' Query Example with Restricted Scope
'--------------------------------------------
Dim query, filter, subFilter
Set query = CrmController().CreateQuery()
query.ParentControlUri = "tag=DIV; crm.id=A_btpartner:V_Partner:T_cellerator:C_btpartner:I_"
Note
The URI should be determined by the object spy, but the HTML element table cannot always be selected, depending on the style.
The alternative to the object spy is to search for the CRM ID of the cellerator
UI element with the developer tool in MS Internet Explorer.
Searching for Web Controls in a Popup Window
The queries search for CRM Web controls in the FRAME
work area of the main browser window, by default. You can change this default behavior with the ParentControlUri
property.
Example VB Script to search for a control in a modal popup window:
Syntax
Dim controller, query, filter
Set controller = CrmController()
Set query = controller.CreateQuery()
query.ParentControlUri = "popupId=1; crm.area=WorkArea; tag=body"
Set filter = query.SetFilter()
filter.AddCondition "crm.type", "=", "ROW_SELECTOR"
filter.AddCondition "crm.context", "=", "proctype"
filter.AddCondition "crm.context", "=", "proctype"
filter.AddCondition "crm.column:proctype.proc_type_descr_20", "=", "Meeting"
Dim selectorInPopup, selectorUri
Set selectorInPopup = query.SelectSingle()
selectorInPopup.HighLight "red"
selectorUri = selectorInPopup.GetControlUri()
controller.SearchResult_SelectRow selectorUri
This example contains nothing new regarding the selection of the ROW_SELECTOR
. The script uses some conditions to select the transaction with type 1001 and type description Meeting. The difference is that it now specifies searching in the FRAME
work area of the popup by setting the ParentControlUri
property:
query.ParentControlUri = "popupId=1; crm.area=WorkArea; tag=body"
In this example, it would have been possible to restrict the scope of the query even more, by specifying the URI of the TABLE
HTML element like this:
query.ParentControlUri = "popupId=1; crm.id=A_btfollowup:V_ProcType:T_cellerator:C_proctype:I_"
Combining Filters for Complex Queries
All examples so far use only one filter with several conditions. Conditions are evaluated one by one, and the CRM Web UI elements are excluded as soon as a condition is not met. The conditions are connected by a logical AND
operator.
Our previous example selected the first row with the following conditions: Partner Function equals "Sales employee" AND Partner ID equals "244".
Instead of searching for the ROW_SELECTOR
, we could retrieve controls that are either checkboxes or input fields that are associated with an F4 help, by combining several filters, as in the following example:
Syntax
Dim query, filter, subFilter, collection, childControl
Set query = CrmController().CreateQuery()
query.ParentControlUri = "tag=DIV; crm.id=A_btpartner:V_Partner:T_cellerator:C_btpartner:I_"
Set filter = query.SetFilter()
filter.AddCondition "crm.application", "=", "btpartner"
filter.AddCondition "crm.view", "=", "Partner"
filter.AddCondition "crm.context", "=", "btpartner"
filter.AddCondition "crm.column:btpartner.partner_no", "=", "244"
filter.AddCondition "crm.column~Partner Function", "=", "Sales employee"
Set subFilter = filter.SetFilter()
subfilter.AddCondition "crm.tagType", "=", "valueHelp"
Set subFilter = filter.AddFilter()
subfilter.AddCondition "crm.tagType", "=", "checkbox"
Set collection = query.Select()
If collection Is Nothing Then
CBTA.Report CBTA.FAILED, "Demo Query", "No controls have been found", ""
Else
CBTA.Report CBTA.INFO, "Demo Query", "Nb. Controls: " & collection.Count, ""
for i=0 To collection.Count-1
Set childControl = collection.ControlAt(i)
CBTA.Report CBTA.INFO, "Demo Query", "Uri: " & childControl.GetControlUri(), ""
Next
End If
In this example, the main filter no longer checks for the ROW_SELECTOR
type, so all cells in the row match its criteria. The difference is that we have now defined two sub-filters that specify the type of the UI elements for which we are looking.
The first sub-filter checks for elements that are associated with an F4 help, which use the valueHelp tagType
.
Syntax
Set subFilter = filter.SetFilter()
subfilter.AddCondition "crm.tagType", "=", "valueHelp"
The second sub-filter checks for checkbox UI elements:
Syntax
Set subFilter = filter.AddFilter()
subfilter.AddCondition "crm.tagType", "=", "checkbox"
There are two sub-filters here. Both have a single condition which checks for the crm.tagType
attribute.
The first sub-filter is created by calling the SetFilter
method on the main filter object.
The second sub-filter is added by the AddFilter
method-
By defining several filters we specify that we are looking for cells which match both CRM tag types. This is possible because, unlike conditions, the filters are connected by the logical OR
operator.
Reusing Default Component Implementations
The controller interfaces also expose methods to interact with the application UI. There is at least one method per default component that CBTA delivers.
The two examples below are equivalent:
Syntax
CRM_Table_SelectRow crmControl.GetControlUri()
If ReportStatus = "FAILED" The
' Nothing to do
End If
...
Syntax
Set operationResult = CrmController().Table_SelectRow crmControl.GetControlUri()
If operationResult.Status = "FAILED" Then
CBTA.Report CBTA.FAILED, "Demo Query", "Row selection failed", ""
Else
CBTA.Report CBTA.INFO, "Demo Query", "Row has been selected", ""
End If
The first example invokes the component implementation, while the second one invokes the corresponding method by the controller interface. The advantage of the first approach is that it benefits from default exception handling (writing information in the execution report when an error occurs). In the second example, the script is responsible for checking whether the operation succeeds.
Note
In general, the public methods of the controller interfaces return an Operation Result Object
that implement the Operation Result Interface
(see Technical Information for the Query API). The information that is available there can provide human-readable feedback in the execution report.
See Writing Information to the Execution Report
, earlier in this document.
Mapping Between Public Methods and Component Implementations
The following list shows examples of methods, which are exposed by the controller interfaces, and their component implementations:
Controller | Method Name | Component Implementation (VB Script Function) |
---|---|---|
|
|
|
|
| |
| N/A | |
|
| |
|
| |
|
|
|
|
| |
|
|
CBTA contains default components that search tables for a row, by the content of one of the cells. As of CBTA 3.0 SP02, the following components are available:
CBTA_LS_T_FindRow
: searches a Light Speed table for a row, by checking the value of a single cell.
Input parameters:
URI
: specifies the URI of the table container
ColumnTitle
: title of the column (the visible text)
Operator
: boolean operator used to compare the actual value with the expected one
Content
: expected value of the cell
Options
: Some options can be used to convert the values before comparing them.
Output parameters: The number of the row matching the criteria.
CBTA_CRM_T_FindRow
: similar to CBTA_LS_T_FindRow
but for SAP CRM Web applications
Both components were implemented using the query API, so a test engineer can copy and paste the source code of the component implementation, and adapt it to his needs.
Note
The code of the CBTA_LS_T_FindRow
is in the runtime library, it is visible in theLS_Functions.vbs
file.
Use the runtime library manager to customize the runtime library and write custom functions. When you open the runtime library for editing, the LS_Functions.vbs
file is made available, locally in the file system, at the location that has been specified by the test engineer.
VB Script Function – LsTable_FindRowByContent
Syntax
Public Function LsTable_FindRowByContent ( Uri, ColumnTitle, Operator, Content, Options )
On Error Resume Next
EventWebComponentBegin()
LsTable_FindRowByContent = LsTable_FindRowByContent_Impl( Uri, ColumnTitle, Operator, Content, Options )
EventWebComponentEnd()
End Function
Public Function LsTable_FindRowByContent_Impl ( Uri, ColumnTitle, Operator, Content, Options )
LsTable_FindRowByContent_Impl = ""
If IsNull(Uri) Or IsNull(ColumnTitle) Or IsNull(Content) Then
Exit Function
End If
If IsNull(Operator) Then
Operator = "="
End If
If IsNull(Options) Then
Options = ""
End I
ReportDebugLog "LsTable_FindRowByContent" & _
vbCrLf & "Uri: " & Uri & _
vbCrLf & "ColumnTitle: " & ColumnTitle & _
vbCrLf & "Operator: " & Operator & _
vbCrLf & "Content: " & Content & _
vbCrLf & "Options: " & Option
Dim controller, query, filter
Set controller = LsController()
Set query = controller.CreateLsQuery()
query.ParentControlUri = uri
Set filter = query.SetFilter()
filter.AddCondition "tag", "=", "TD"
filter.AddCondition "ls.subtype", "=", "SC"
filter.AddCondition "ls.column~" & ColumnTitle, Operator, Content, Options
Set control = query.SelectSingle()
If control Is Nothing Then
ReportLog "FAILED", "LsTable_FindRowByContent", "Operation Failed - Row could not be found"
Else
LsTable_FindRowByContent_Impl = control.GetRelevantControl().GetLsRow()
If InStr(Options, "/Select" ) Then
ReportLog "INFO", "LsTable_FindRowByContent", _
"Operation succeeded - Row has been selected" & vbCRLf & _
"Row is: " & LsTable_FindRowByContent
ReportDebugLog "LsTable_FindRowByContent - Row Selector Uri: " & control.GetControlUri()
WEB_WebControl_Table_SelectRow control.GetControlUri()
Else
ReportLog "INFO", "LsTable_FindRowByContent", _
"Operation succeeded - Row is: " & LsTable_FindRowByContent
End If
End If
End Function
The following sections explain in detail how this component has been implemented.
Exception handling: The implementation consists of two functions. The first function is required only because the VB Script syntax is not good for exception handling and the On Error Resume Next
keyword used here is the only available option.
All default components are built according to the same concept; the first surrounds the invocation of the actual implementation to intercept any errors.
Syntax
Public Function LsTable_FindRowByContent ( Uri, ColumnTitle, Operator, Content, Options )
On Error Resume Next
EventWebComponentBegin()
LsTable_FindRowByContent = LsTable_FindRowByContent_Impl( Uri, ColumnTitle, Operator, Content, Options )
EventWebComponentEnd()
End Function
Validating input parameters: The first statements of the implementation validate the input parameters, to avoid common issues.
Syntax
Public Function LsTable_FindRowByContent_Impl ( Uri, ColumnTitle, Operator, Content, Options )
LsTable_FindRowByContent_Impl = "
If IsNull(Uri) Or IsNull(ColumnTitle) Or IsNull(Content) Then
Exit Function
End If
If IsNull(Operator) Then
Operator = "="
End If
If IsNull(Options) Then
Options = ""
End If
...
End If
In this example, the URI and some parameters are mandatory, some have a default value. The script checks them all and reacts accordingly.
Writing information to the execution report: The following code just provides feedback to the test engineer. The value of each input parameter is written to the execution report.
Syntax
ReportDebugLog "LsTable_FindRowByContent" & _
vbCrLf & "Uri: " & Uri & _
vbCrLf & "ColumnTitle: " & ColumnTitle & _
vbCrLf & "Operator: " & Operator & _
vbCrLf & "Content: " & Content &
vbCrLf & "Options: " & Option
Creation of the Query Restricted to a Table Container
If the input parameters are consistent, a query is created using the Light Speed Controller, and it’s scope is restricted to the table by setting the ParentControlUri
property:
Syntax
Dim controller, query, filter
Set controller = LsController()
Set query = controller.CreateLsQuery()
query.ParentControlUri = uri
Definition of the Criteria
To find a row by checking the content of one of the cells, we use a single filter with 3 conditions:
Syntax
Set filter = query.SetFilter()
filter.AddCondition "tag", "=", "TD"
filter.AddCondition "ls.subtype", "=", "SC"
filter.AddCondition "ls.column~" & ColumnTitle, Operator, Content, Options
The first condition specifies the tag used by the Light Speed Framework to generate a cell. The regular <TD>
HTML tag is used.
Note
This condition is not mandatory, but filtering out the other tags has a significant impact on performance, since the number of UI elements matching this condition might be small.
The second condition relies on Light Speed data to determine the sub-type of control. The Light Speed Framework marks all cells with the SC
sub-type, regardless of the actual type (such as input field or checkbox). For more information on data specific to the Light Speed Framework, see Technical Information for the Query API.
The third condition is the most important one; it uses the ColumnTitle
parameter specified to search for the cell displayed in the column we are interested in. This condition also uses the Operator
, Content
, and Options
parameters to specify the value we expect in the cells. For more information on boolean operators and options for conditions, see Technical Information for the Query API.
Resolving the Query
The criteria have been defined; the query is now ready to be executed. The following code triggers the query resolution:
Syntax
Set control = query.SelectSingle()
If control Is Nothing Then
ReportLog "FAILED", "LsTable_FindRowByContent", "Operation Failed - Row could not be found"
Else
LsTable_FindRowByContent_Impl = control.GetRelevantControl().GetLsRow()
...
End If
By calling the SelectSingle
method, the CBTA_LS_T_FindRow
component only searches for a single row, that is, the first row that matches the criteria. It is also possible to retrieve a set of cells that match the criteria, by calling the Select
method instead.
Returning the Row Number
The following code gets the row number by using Light Speed Data that is made available by interfaces that are specific to the underlying UI technology.
For more details on Light Speed-relevant control interfaces, see Technical Information for the Query API.
Syntax
LsTable_FindRowByContent_Impl = control.GetRelevantControl().GetLsRow()
...
Note
The CBTA_LS_T_FindRow
component returns the row number in its output parameter. The internal method used to set the output parameter is not in scope of this document.
Selecting the Row
The CBTA_LS_T_FindRow
component can select the row as well as search for it. The script has to ask for the URI of the cell that matches the criteria, and then trigger the row selection.
The URI is retrieved by calling the following GetControlUri
method, and the selection is made by calling the WEB_WebControl_Table_SelectRow
function, which corresponds to theCBTA_WEB_SelectRow
default component. In the following example, the selection is only made when the Options
parameter contains /Select:
Syntax
If InStr(Options, "/Select" ) Then
ReportLog "INFO", "LsTable_FindRowByContent", _
"Operation succeeded - Row has been selected" & vbCRLf & _
"Row is: " & LsTable_FindRowByContent
ReportDebugLog "LsTable_FindRowByContent - Row Selector Uri: " & control.GetControlUri()
WEB_WebControl_Table_SelectRow control.GetControlUri()
Else
ReportLog "INFO", "LsTable_FindRowByContent",
"Operation succeeded - Row is: " & LsTable_FindRowByContent
End If