ServiceNow ATF Testing Guide: Automated Test Framework Deep Dive

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:

  1. UI tests — browser automation of the ServiceNow interface
  2. Server-side script tests — direct Script Include and Business Rule testing
  3. 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 = true
  • ATF test runner client = enabled (for UI tests)

Install the ATF Test Runner

UI tests require the Test Runner, a browser extension or standalone client:

  1. Navigate to Automated Test Framework > Run > Client Test Runner
  2. Click "Start Test Runner" — this opens a dedicated browser tab
  3. 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 Runner

Writing 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 Desk

Each 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_id

Chaining 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 > 0

Test 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">fi

GitHub 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.json

ATF 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.

Read more