Automating ATF - My Journey Building a Test Recorder
var allUIActionsQuery = getFormUIAction(tableName) + "^ORactive=false";
Set Field Values
Sets field values on the current form.
Inputs
- Table
- Field Values (String, Encoded Query)
Field Values
There isn't any documented Client Script API to get all populated fields on a form.
With a little digging in Xplore, I found that GlideForm had a property called 'modifiedFields' which tracked all fields that had changed value by means of manual changes:
var modifiedFields = g_form.modifiedFields;
Changing the Short Description field on an Incident would result in the contents of modifiedFields as follows:
{incident.short_description: true}
The values weren't there, but I could get that just as easily:
g_form.getValue('incident.short_description')
Field Values Validation
Validates field values on the current form.
Inputs
- Table
- Conditions (String, Encoded Query)
If Set Field Values throws a fail if the field is not populated as defined in the Test Step, then why do I need to validate field values?
Take changing the value of Impact or Urgency as an example.
Changing these values may change the value of Priority - but the Priority field will not show up in g_form.modifiedFields - nor would you want it to.
The Priority field is read-only, and if your Test tried to set that field value, it would fail.
We need a way to find all field values that have changed as a result of changing other fields.
Conditions
Back in my System Administrator days, I created hundreds of Email Notifications via Notification records.
A great feature of Email Notifications was that you could adjust the record and click 'Preview Notification' to see what effect your changes would have, without committing to saving those changes.
With this in mind, I knew there had to be an existing way to get all fields that had changed value. Reverse engineering is your friend ![]()
The UI Action code revealed the following:
function showSimulator() {
var generationType = g_form.getValue('generation_type');
if (!g_form.getValue('collection') && generationType == 'engine')
g_form.addErrorMessage(new GwtMessage().getMessage("Table is required to preview this notification"));
else {
if (typeof rowSysId == 'undefined')
sysId = gel('sys_uniqueValue').value;
else
sysId = rowSysId;
var dialogClass = window.GlideModal ? GlideModal : GlideDialogWindow;
var dd = new dialogClass("notification_preview");
dd.setTitle("Notification Preview");
dd.setWidth(800);
dd.setPreference('sysparm_notification_id', sysId);
dd.setPreference('sysparm_changed_fields',g_form.serializeChangedAll());
dd.render();
}
}
The golden line:
dd.setPreference('sysparm_changed_fields',g_form.serializeChangedAll());
The output from serializeChangedAll() looks something like this:
&sys_target=incident&sys_uniqueValue=c8ef1aaedb76c8109e101461399619ea&sys_row=-1&sysparm_encoded_record=SBSvOy%2BGbxRnTUw297uYJlfHsT4CchYhL8qsQvVS4oYRqJf5GoVaR2BXDzla2tU8q3ug3aoDHV9t1DUxeof9%2BC1O6nUWGM99c5XsbmeFryD%2FqmZLEMKuybxG2rt59gLaa%2FL0qnX8xqsFbcVp2V8RIjM5P5v23ZrzyULjWCB18QEkKCv4xTRl7wxpg0NnJkLmjeLYPSlECZnCV3mhSUGxXpyyKC635YQFYbDz5N5yYSXjIc8yObiI2GdU3yzJwWEkDX3bpLCTIJJrIB%2FOmfg5YI0f%2F1iRgdGywLd%2F%2F1LVEV6dUdT33sgd0z82ASeObP%2FOeXZ6O1%2Fe5iXWrd9I7zw20SAk2JKEzBYQBgPBBDA42X%2BUZYlzv4dv4nlYITwcKdYXi1PYzruNthKzwKUWq4kdR6KJp7kErQ7xVzy5W5tTfqp500GkCke57BCUxrZZCFqJSwSTh2tB7lf7HJSArNOqeD19niysdPM4W%2BREKFGCwQdmrQtGvvFLq9oMWIT3Pjrz%2FjA8I9WF6s%2F%2B8OpMI7SlZRbYPbACZEhL6qVn5T8XdrMlhPFWxe3rPuIf%2BmYxminAf1uJmof%2BorxB9gPaNQfdZi2Uwj%2FCBMiK%2FeSKEwsZNz%2B3eFwnu3aMTg%3D%3D&incident.impact=1&incident.urgency=1&incident.priority=1
It's ugly, but we can work with it.
Using some dirty Regular Expression, I was able to strip out everything except the changed fields then turn it into an encoded query:
impact=1^urgency=1^priority=1
Field State Validation
Validates states of the desired fields.
The field states can be one (or more) of mandatory, not mandatory, read only, not read only, visible and not visible.
Inputs
- Table
- Visible (GlideList, Field names)
- Not visible (GlideList, Field names)
- Read only (GlideList, Field names)
- Not read only (GlideList, Field names)
- Mandatory (GlideList, Field names)
- Not mandatory (GlideList, Field names)
All field names are contained in g_form.elements fieldName property. You can get all the field names if you like by looping through them:
var fieldElements = g_form.elements;
var fieldNames = [];
for (var f in fieldElements) {
if (fieldElements.hasOwnProperty(f)) {
var fieldElement = fieldElements[f];
fieldNames.push(fieldElement.fieldName);
}
}
Visible
The isVisible function takes an element (like those in g_form.elements) and does not work with a field name alone.
var uiElement= g_form.getGlideUIElement(fieldName);
var visible = g_form.isVisible(uiElement);
Not Visible
var uiElement= g_form.getGlideUIElement(fieldName);
var notVisible = !g_form.isVisible(uiElement);
Read Only
The isReadOnly() function is more complicated still:
var element = g_form.getElement(fieldName);
var control = g_form.getControl(fieldName);
var isReadOnly = g_form.isReadOnly(element,control);
Not Read Only
var element = g_form.getElement(fieldName);
var control = g_form.getControl(fieldName);
var isNotReadOnly = !g_form.isReadOnly(element,control);
Mandatory
Why is the last one the easiest?
var isMandatory = g_form.isMandatory(fieldName);
Not Mandatory
var isNotMandatory = !g_form.isMandatory(fieldName);
Click a UI Action
Clicks a UI Action on the current form.
Inputs
- Table
- UI action (Reference UI Action)
- Assert type
UI Action
I thought this one was going to be really simple.
It turned out this was the hardest one to do and has many limitations that I didn't realize when I was first doing this proof of concept.
To get the UI Action just clicked, you can pop the following code into an onSubmit Client Script:
var actionName = g_form.getActionName();
If you click the OOTB Save button, you get an actionName of the following:
sysverb_update_and_stay
The Test Step requires a sys_id, so we need to work out how we can use an action name to get the sys_id.
I ended up reverse-engineering the code for getActionName() to get the sys_id:
function getUIActionSysID() {
var uiActionSysID='';
var form = g_form.getFormElement();
if (form) {
var theButton = form.sys_action;
if (theButton) {
var buttonName = theButton.value;
//Try get UI Action Sys ID
var actionElement = g_form.getElement(buttonName);
if (actionElement) {
uiActionSysID = actionElement.getAttribute("gsft_id");
jslog('Clicked UI action SYS_ID is ' + uiActionSysID);
}
}
}
return uiActionSysID;
}
I soon discovered that g_form.getActionName() is not a reliable way to track the UI Action the was clicked.
Client-Side UI Actions that use g_form.save() or g_form.submit() without an action name as a parameter just result in the OOTB action names for save and submit being captured.
I'll dig into this further at a later time. You can see how I worked around this for yourself.
Assert Type
We can really only test for success here, as onSubmit Client Scripts are never called if the UI Action result was not successful.
Conclusion
So, I had proof to myself that I could automate the recording of ATF Tests for forms.
I thought it would only take a few hours to build a simple framework to automate all the Test Steps above.
I wanted to make a simple ATF Recorder that could be used by a Process Coordinator, a non-technical role who knew how to use ServiceNow but not how to use ATF.
Tests needed to almost always pass after recording, with little to no modification required to the generated Test.
The following Use Cases were decided for my MVP, using an OOTB system:
- Record the following Form Actions
- * Impersonate
- Open a New Form
- UI Action Visibility
- Set Field Values
- Field Values Validation
- Field State Validation
- Click UI Action
- Open a New Form
- Record the following use Cases End to End with a Pass on replay
- * New Incident to Resolution
- Request Item Workflow
- New Normal Change to Closure including Approvals
- Request Item Workflow
Like a typical Developer, I grossly underestimated how much effort this would take.
Like a typical Engineer, my proof of concepts was not exhaustive enough to detect some limitation that almost made this impossible. I'll address the Limitations in further blogs. Watch this space!
Weeks / Months later, I present to the Community my Application
️ Regress - ATF Recorder for UI16 Forms, now available on Share (v0.99).
Thanks for reading and please bookmark and comment below.
If you enjoyed this, please check out my Technical Blog series Service-KnowHow.
Labels:
https://www.servicenow.com/community/developer-blog/automating-atf-my-journey-building-a-test-recorder/ba-p/2288344