ServiceNow ATF Testing Guide: Automated Test Framework Deep Dive
ServiceNow's Automated Test Framework (ATF) is a built-in, no-license-required testing tool that supports UI tests, server-side script tests, and API tests. This guide covers ATF setup, test writing patterns, and CI/CD integration using the ServiceNow REST API.
What Is ServiceNow ATF?
The Automated Test Framework (ATF) is ServiceNow's native testing platform, available in every instance since the Istanbul release. Unlike third-party tools, ATF:
- Runs inside your ServiceNow instance — no external infrastructure
- Understands ServiceNow's data model, roles, and impersonation
- Handles ServiceNow UI components natively (forms, lists, UI actions)
- Is free — included with your ServiceNow license
ATF supports three test types:
- UI tests — browser automation of the ServiceNow interface
- Server-side script tests — direct Script Include and Business Rule testing
- REST API tests — testing ServiceNow Table API and custom Scripted REST APIs
Setting Up ATF
Enable ATF in Your Instance
Navigate to Automated Test Framework > Administration > Properties and ensure:
ATF enabled= trueATF test runner client= enabled (for UI tests)
Install the ATF Test Runner
UI tests require the Test Runner, a browser extension or standalone client:
- Navigate to
Automated Test Framework > Run > Client Test Runner - Click "Start Test Runner" — this opens a dedicated browser tab
- Keep the test runner tab open while executing UI tests
Alternatively, use the Scheduled Test Runner for headless CI execution:
Automated Test Framework > Administration > Scheduled Test RunnerWriting ATF Tests
Test Structure
Every ATF test has:
- Test steps — individual actions or assertions
- Test data — optional parameterized inputs
- User impersonation — run as specific role/user
UI Test: Create an Incident
Test: Create Incident and Verify Assignment
Step 1: Impersonate User
- User: ITIL User (itil role)
Step 2: Open Form
- Navigate to: incident.do?sys_id=-1
Step 3: Set Field
- Field: Short description
- Value: Test incident from ATF
Step 4: Set Field
- Field: Urgency
- Value: 2 - Medium
Step 5: Submit Form
- Click: Submit button
Step 6: Assert Field Value
- Field: State
- Expected: New
Step 7: Assert Field Value
- Field: Assignment group
- Expected: Service DeskEach step uses ATF's built-in step configurations — no custom code for standard UI interactions.
Server-Side Script Test
For testing Script Includes directly:
// ATF Server-Side Script test step
(function(outputs, steps, params, stepResult) {
// Instantiate the Script Include
var calc = new IncidentPriorityCalculator();
// Test the method
var priority = calc.calculatePriority('1', '1'); // High urgency, High impact
// Assert
if (priority !== '1') {
stepResult.setFailed('Expected priority 1, got: ' + priority);
return;
}
stepResult.setSuccess('Priority calculation correct: ' + priority);
})(outputs, steps, params, stepResult);Testing Business Rules
Business Rules fire on table operations. Test them by triggering the DML:
// Test step: Server-side script
(function(outputs, steps, params, stepResult) {
// Insert a record that should trigger the Business Rule
var gr = new GlideRecord('incident');
gr.initialize();
gr.setValue('short_description', 'ATF Test Incident');
gr.setValue('urgency', '1');
gr.setValue('impact', '1');
var sysId = gr.insert();
// Re-query to see the Business Rule's effects
var updated = new GlideRecord('incident');
updated.get(sysId);
// Assert Business Rule set priority to 1
if (updated.getValue('priority') !== '1') {
stepResult.setFailed('Business Rule did not set priority. Got: ' + updated.getValue('priority'));
return;
}
// Clean up
updated.deleteRecord();
stepResult.setSuccess('Business Rule correctly set priority to 1');
})(outputs, steps, params, stepResult);Testing Custom APIs (Scripted REST)
ServiceNow Scripted REST APIs need both authentication and response assertion testing.
REST API Test Step
ATF has a built-in REST step:
Step: REST Step
Method: POST
URL: /api/now/table/incident
Headers:
Content-Type: application/json
Accept: application/json
Authentication: Basic (use ATF credential alias)
Request Body:
{
"short_description": "API test incident",
"urgency": "2",
"impact": "2"
}
Expected Response Code: 201
Outputs:
incident_sys_id: response.result.sys_idChaining REST Steps
Pass outputs between steps for complex scenarios:
Step 1: Create Incident → Output: incident_sys_id
Step 2: Add Work Note
URL: /api/now/table/incident/${incident_sys_id}
Method: PATCH
Body: {"work_notes": "Test note added by ATF"}
Expected: 200
Step 3: Assert Work Note Exists
URL: /api/now/table/sys_journal_field?sysparm_query=element_id=${incident_sys_id}^element=work_notes
Method: GET
Assert: response.result.length > 0Test Suites
Group related tests into suites for efficient execution:
Suite: Incident Management Regression
Tests:
- Create Incident (UI)
- Assign Incident to Group (UI)
- Resolve Incident (UI)
- Create Incident via API (REST)
- Verify Priority Calculation (Script)
- Verify SLA Assignment (Script)
Suite Properties:
- Rollback test data: Yes
- Run in order: Yes
- Stop on failure: No (collect all failures)Rollback and Data Cleanup
ATF supports automatic rollback of test data after suite execution. Enable it in suite settings:
Rollback test transactions= true
This reverts all DML operations from the test, keeping your QA instance clean. Note: rollback doesn't work for Business Rules that call external systems.
CI/CD Integration
Triggering ATF from CI via REST API
#!/bin/bash
<span class="hljs-comment"># Run ATF test suite and wait for results
INSTANCE=<span class="hljs-string">"your-instance.service-now.com"
SUITE_ID=<span class="hljs-string">"your-suite-sys-id"
AUTH=<span class="hljs-string">"Basic $(echo -n "$SN_USER:<span class="hljs-variable">$SN_PASSWORD" <span class="hljs-pipe">| base64)"
<span class="hljs-comment"># Trigger the suite
RESPONSE=$(curl -s -X POST \
<span class="hljs-string">"https://$INSTANCE/api/sn_atf/suite_runner/run_suite" \
-H <span class="hljs-string">"Authorization: $AUTH" \
-H <span class="hljs-string">"Content-Type: application/json" \
-d <span class="hljs-string">"{\"test_suite_sys_id\": \"$SUITE_ID\"}")
RUN_ID=$(<span class="hljs-built_in">echo <span class="hljs-variable">$RESPONSE <span class="hljs-pipe">| jq -r <span class="hljs-string">'.result.sys_id')
<span class="hljs-built_in">echo <span class="hljs-string">"Suite run started: $RUN_ID"
<span class="hljs-comment"># Poll for completion
<span class="hljs-keyword">while <span class="hljs-literal">true; <span class="hljs-keyword">do
STATUS=$(curl -s \
<span class="hljs-string">"https://$INSTANCE/api/now/table/sys_atf_test_suite_run/<span class="hljs-variable">$RUN_ID?sysparm_fields=status,tests_run,tests_passed,tests_failed" \
-H <span class="hljs-string">"Authorization: $AUTH" <span class="hljs-pipe">| jq -r <span class="hljs-string">'.result.status')
<span class="hljs-built_in">echo <span class="hljs-string">"Status: $STATUS"
<span class="hljs-keyword">if [ <span class="hljs-string">"$STATUS" = <span class="hljs-string">"complete" ] <span class="hljs-pipe">|| [ <span class="hljs-string">"$STATUS" = <span class="hljs-string">"failed" ]; <span class="hljs-keyword">then
<span class="hljs-built_in">break
<span class="hljs-keyword">fi
<span class="hljs-built_in">sleep 15
<span class="hljs-keyword">done
<span class="hljs-comment"># Get results
RESULTS=$(curl -s \
<span class="hljs-string">"https://$INSTANCE/api/now/table/sys_atf_test_suite_run/<span class="hljs-variable">$RUN_ID" \
-H <span class="hljs-string">"Authorization: $AUTH")
PASSED=$(<span class="hljs-built_in">echo <span class="hljs-variable">$RESULTS <span class="hljs-pipe">| jq -r <span class="hljs-string">'.result.tests_passed')
FAILED=$(<span class="hljs-built_in">echo <span class="hljs-variable">$RESULTS <span class="hljs-pipe">| jq -r <span class="hljs-string">'.result.tests_failed')
<span class="hljs-built_in">echo <span class="hljs-string">"Results: $PASSED passed, <span class="hljs-variable">$FAILED failed"
<span class="hljs-keyword">if [ <span class="hljs-string">"$FAILED" -gt <span class="hljs-string">"0" ]; <span class="hljs-keyword">then
<span class="hljs-built_in">echo <span class="hljs-string">"ATF tests failed — blocking deployment"
<span class="hljs-built_in">exit 1
<span class="hljs-keyword">fiGitHub Actions Integration
name: ServiceNow ATF Tests
on:
push:
branches: [main, staging]
jobs:
atf-regression:
runs-on: ubuntu-latest
steps:
- name: Run ATF Suite
env:
SN_INSTANCE: ${{ secrets.SN_INSTANCE }}
SN_USER: ${{ secrets.SN_USER }}
SN_PASSWORD: ${{ secrets.SN_PASSWORD }}
ATF_SUITE_ID: ${{ secrets.ATF_REGRESSION_SUITE_ID }}
run: |
chmod +x ./scripts/run-atf.sh
./scripts/run-atf.sh
- name: Upload ATF Results
if: always()
uses: actions/upload-artifact@v4
with:
name: atf-results
path: atf-results.jsonATF Test Patterns and Anti-Patterns
Do: Use Impersonation for Role Testing
Step: Set Impersonation
User: abel.tuter (end-user, no ITIL role)
Step: Navigate to incident.list
Step: Assert Field Not Visible
Field: Resolution notes
(End users shouldn't see resolution notes)Do: Parameterize with Test Data
Create a test data record instead of hardcoding values:
Test Data: Incident Scenarios
Row 1: urgency=1, impact=1, expected_priority=1
Row 2: urgency=1, impact=2, expected_priority=2
Row 3: urgency=2, impact=2, expected_priority=3
Test: Priority Matrix (data-driven, runs 3 times)
Step 1: Create incident with ${urgency}, ${impact}
Step 2: Assert priority = ${expected_priority}Don't: Depend on Existing Data
Tests that query [SELECT FROM incident WHERE state=1 LIMIT 1] break when someone changes that incident. Always create your own test data.
Don't: Leave Test Data Behind
Always clean up or use rollback mode. Stale test incidents inflate reports and confuse operations teams.
Monitoring ServiceNow in Production
After ATF validates your configuration, add continuous monitoring for critical workflows:
# Monitor the ServiceNow instance availability
helpmetest health servicenow-instance 5m
<span class="hljs-comment"># Monitor a specific API endpoint
helpmetest health sn-incident-api 5m \
--check-url <span class="hljs-string">"https://yourinstance.service-now.com/api/now/table/incident?sysparm_limit=1"This catches configuration drift between releases — when an admin changes a Business Rule or assignment rule that breaks a workflow your ATF caught in QA.
Summary
ServiceNow ATF is underutilized in most organizations despite being included with every license. The key patterns:
- Use server-side script steps for Business Rule and Script Include unit testing
- Chain REST steps to test complex API scenarios end-to-end
- Enable rollback to keep test data clean
- Trigger via REST API from CI to gate deployments on test results
- Impersonate users to test role-based access controls
ATF won't replace full browser automation for complex multi-system workflows, but for ServiceNow-internal logic, it's the most reliable and lowest-overhead option available.