automated-testing
About automated-testing
A skill for ServiceNow developers, QA engineers, and administrators using the Automated Test Framework (ATF). It covers test suites, steps, assertions, and result analysis for development checks, upgrades, and regression testing.
Key features
- 01Create suites and tests
- 02Compose server, client, and UI steps
- 03Write assertions and manage test data
- 04Parameterize and schedule tests
- 05Analyze runs and failed steps
- 06Integrate tests with CI/CD
Use cases
- 01Validate platform changes before deployment
- 02Run test-driven checks during development
- 03Perform regression testing after upgrades
MCP support
The skill describes using MCP tools to create and query ServiceNow ATF records, and background scripts to run tests.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ happy-technologies-llc/ happy-platform-skills --skill automated-testingAfter installing, type/automated-testingin Claude Code to confirm it loaded.
Details
From the project READMEAutomated Test Framework (ATF)
Overview
The Automated Test Framework (ATF) is ServiceNow's native testing solution for validating platform functionality. This skill covers:
- Creating test suites and organizing tests
- Building tests with various step types (Server, Client, UI)
- Writing assertions and validations
- Managing test data setup and cleanup
- Implementing parameterized tests
- Running tests manually and on schedule
- Analyzing test results and failures
- Integrating ATF with CI/CD pipelines
- Best practices for maintainable, reliable tests
When to use: Before deploying changes to production, during development (TDD), after upgrades, and as part of regression testing.
Who should use this: Developers, QA engineers, and administrators who need to ensure platform reliability.
Prerequisites
- Roles:
atf_test_admin(full access) oratf_test_designer(create/edit tests) - Plugins: Automated Test Framework (com.snc.automated_testing)
- Access: sys_atf_test, sys_atf_test_suite, sys_atf_step tables
- Knowledge: Basic understanding of ServiceNow scripting (GlideRecord, client scripts)
- Related Skills:
admin/script-execution,admin/update-set-management
ATF Architecture
┌─────────────────────────────────────────────────────────────┐
│ Test Suite │
│ (Groups related tests, runs in sequence or parallel) │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Test 1 │ │ Test 2 │ │ Test 3 │ │
│ │ (Scenario) │ │ (Scenario) │ │ (Scenario) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
│ │ Steps │ │ Steps │ │ Steps │ │
│ │ 1. Setup │ │ 1. Setup │ │ 1. Setup │ │
│ │ 2. Action │ │ 2. Action │ │ 2. Action │ │
│ │ 3. Assert │ │ 3. Assert │ │ 3. Assert │ │
│ │ 4. Cleanup │ │ 4. Cleanup │ │ 4. Cleanup │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
Key Tables
| Table | Purpose |
|---|---|
sys_atf_test_suite | Test suite containers |
sys_atf_test | Individual test definitions |
sys_atf_step | Test steps within tests |
sys_atf_step_config | Step type configurations |
sys_atf_test_result | Test execution results |
sys_atf_step_result | Individual step results |
sys_atf_parameter | Test parameters |
sys_atf_variable | Test variables (runtime data) |
sys_atf_test_suite_test | Suite-to-test relationships |
Procedure
Phase 1: Create Test Suite
Step 1.1: Create the Test Suite
Organize related tests into a suite for easier management and execution.
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_test_suite
data:
name: "Incident Management Tests"
description: "Comprehensive tests for incident creation, assignment, resolution, and closure workflows"
active: true
run_parallel: false
application: [app_sys_id] # Optional: for scoped apps
Using REST API:
POST /api/now/table/sys_atf_test_suite
Content-Type: application/json
{
"name": "Incident Management Tests",
"description": "Comprehensive tests for incident creation, assignment, resolution, and closure workflows",
"active": "true",
"run_parallel": "false"
}
Step 1.2: Query Existing Test Suites
Using MCP:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_test_suite
query: active=true
fields: sys_id,name,description,run_parallel,sys_updated_on
limit: 50
Phase 2: Create Tests
Step 2.1: Create a Basic Test
Each test represents a specific scenario to validate.
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_test
data:
name: "Create P1 Incident and Verify Auto-Assignment"
description: "Tests that P1 incidents are automatically assigned to the Critical Incidents team"
active: true
type: test_script # test_script, browser, quick_start
Test Types:
| Type | Value | Description |
|---|---|---|
| Server Side | test_script | Server-side JavaScript tests |
| Browser | browser | Client-side UI tests |
| Quick Start | quick_start | Guided test creation |
Step 2.2: Add Test to Suite
Link the test to your test suite.
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_test_suite_test
data:
test_suite: [suite_sys_id]
test: [test_sys_id]
order: 100 # Execution order (100, 200, 300...)
Phase 3: Create Test Steps
Step 3.1: Server-Side Test Steps
Server-side steps execute GlideRecord operations and server-side JavaScript.
Step: Create Record
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id] # "Record - Insert"
inputs:
table: incident
fields:
short_description: "ATF Test - Server Outage P1"
description: "Automated test incident for validation"
priority: 1
category: hardware
subcategory: server
outputs:
record: inserted_incident # Variable name for reference
Step: Query Records
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 200
active: true
step_config: [step_config_sys_id] # "Record - Query"
inputs:
table: incident
query: number=${inserted_incident.number}
outputs:
record: queried_incident
Step: Update Record
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 300
active: true
step_config: [step_config_sys_id] # "Record - Update"
inputs:
record: ${inserted_incident}
fields:
state: 2 # In Progress
assigned_to: [user_sys_id]
work_notes: "ATF: Assigning for testing"
Step: Run Server-Side Script
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 400
active: true
step_config: [step_config_sys_id] # "Run Server Side Script"
inputs:
script: |
// Custom server-side validation
var gr = new GlideRecord('incident');
gr.get('${inserted_incident.sys_id}');
// Store result for assertion
outputs.actual_state = gr.state.toString();
outputs.assigned_group = gr.assignment_group.getDisplayValue();
outputs.is_valid = (gr.state == 2);
outputs:
actual_state: actual_state
assigned_group: assigned_group
is_valid: is_valid
Step 3.2: Client-Side Test Steps
Client-side steps test UI behavior and client scripts.
Step: Open Form
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id] # "Open a new form"
inputs:
table: incident
Step: Set Field Value
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 200
active: true
step_config: [step_config_sys_id] # "Set field value"
inputs:
field: priority
value: 1 - Critical
Step: Click Button
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 300
active: true
step_config: [step_config_sys_id] # "Click a button"
inputs:
button_name: Submit
Step: Validate Field State
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 400
active: true
step_config: [step_config_sys_id] # "Field state validation"
inputs:
field: caller_id
is_mandatory: true
is_visible: true
is_readonly: false
Step 3.3: UI Test Steps
UI steps interact with the ServiceNow interface.
Step: Open Record
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id] # "Open an existing record"
inputs:
table: incident
sys_id: ${inserted_incident.sys_id}
Step: Navigate to Module
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id] # "Navigate to a module"
inputs:
module: Incident > Create New
Phase 4: Assertions and Validations
Step 4.1: Basic Assertions
Assert Field Value
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id] # "Verify field value"
inputs:
record: ${inserted_incident}
field: state
expected_value: 2
operator: =
Assert Record Exists
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id] # "Verify record exists"
inputs:
table: incident
query: number=${inserted_incident.number}^active=true
Assert Record Count
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id] # "Verify record count"
inputs:
table: task
query: parent=${inserted_incident.sys_id}
expected_count: 3
operator: >=
Step 4.2: Custom Script Assertions
For complex validations, use script assertions.
Run Server-Side Script with Assertions
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id] # "Run Server Side Script"
inputs:
script: |
// Complex assertion logic
var testPassed = true;
var messages = [];
// Get the incident
var gr = new GlideRecord('incident');
gr.get('${inserted_incident.sys_id}');
// Assertion 1: State validation
if (gr.state != 2) {
testPassed = false;
messages.push('Expected state 2, got ' + gr.state);
}
// Assertion 2: Assignment validation
if (gr.assignment_group.nil()) {
testPassed = false;
messages.push('Assignment group should not be empty for P1');
}
// Assertion 3: SLA attached
var sla = new GlideRecord('task_sla');
sla.addQuery('task', gr.sys_id);
sla.query();
if (!sla.hasNext()) {
testPassed = false;
messages.push('Expected SLA to be attached to P1 incident');
}
// Set outputs
outputs.test_passed = testPassed;
outputs.validation_messages = messages.join('; ');
// This will fail the step if assertions fail
if (!testPassed) {
throw new Error('Assertions failed: ' + outputs.validation_messages);
}
Step 4.3: Assertion Operators
| Operator | Description | Example |
|---|---|---|
= | Equals | state = 2 |
!= | Not equals | state != 7 |
< | Less than | priority < 3 |
<= | Less or equal | priority <= 2 |
> | Greater than | age > 0 |
>= | Greater or equal | count >= 1 |
contains | String contains | description contains "error" |
starts with | String prefix | number starts with "INC" |
ends with | String suffix | email ends with "@company.com" |
is empty | Null or empty | assigned_to is empty |
is not empty | Has value | caller_id is not empty |
Phase 5: Test Data Management
Step 5.1: Setup Test Data
Create test data at the beginning of each test.
Using Data Setup Step
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 10
active: true
step_config: [step_config_sys_id] # "Run Server Side Script"
description: "Setup: Create test data"
inputs:
script: |
// Create test user
var user = new GlideRecord('sys_user');
user.initialize();
user.user_name = 'atf_test_user_' + gs.generateGUID().substring(0, 8);
user.first_name = 'ATF';
user.last_name = 'Test User';
user.email = user.user_name + '@test.example.com';
user.active = true;
outputs.test_user_sys_id = user.insert();
outputs.test_user_name = user.user_name;
// Create test group
var group = new GlideRecord('sys_user_group');
group.initialize();
group.name = 'ATF Test Group ' + gs.generateGUID().substring(0, 8);
group.active = true;
outputs.test_group_sys_id = group.insert();
outputs.test_group_name = group.name;
gs.info('ATF: Created test user ' + outputs.test_user_name);
gs.info('ATF: Created test group ' + outputs.test_group_name);
Step 5.2: Cleanup Test Data
Always clean up test data to prevent accumulation.
Using Cleanup Step (End of Test)
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 9999
active: true
step_config: [step_config_sys_id] # "Run Server Side Script"
description: "Cleanup: Remove test data"
inputs:
script: |
// Cleanup test incident
if ('${inserted_incident.sys_id}') {
var inc = new GlideRecord('incident');
if (inc.get('${inserted_incident.sys_id}')) {
inc.deleteRecord();
gs.info('ATF: Cleaned up test incident');
}
}
// Cleanup test user
if ('${test_user_sys_id}') {
var user = new GlideRecord('sys_user');
if (user.get('${test_user_sys_id}')) {
user.deleteRecord();
gs.info('ATF: Cleaned up test user');
}
}
// Cleanup test group
if ('${test_group_sys_id}') {
var group = new GlideRecord('sys_user_group');
if (group.get('${test_group_sys_id}')) {
group.deleteRecord();
gs.info('ATF: Cleaned up test group');
}
}
Step 5.3: Reusable Data Setup (Data Broker)
For tests that need consistent data across multiple scenarios:
Query Step Config for Data Broker
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_step_config
query: nameLIKEData Broker
fields: sys_id,name,description,category
Phase 6: Parameterized Tests
Step 6.1: Create Test Parameters
Parameters allow running the same test with different inputs.
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_parameter
data:
test: [test_sys_id]
name: priority_value
label: "Priority Value"
default_value: 3
type: integer
hint: "Incident priority (1-5)"
Create Multiple Parameters:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_parameter
data:
test: [test_sys_id]
name: category
label: "Category"
default_value: software
type: string
hint: "Incident category"
# Additional parameter
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_parameter
data:
test: [test_sys_id]
name: expected_sla_minutes
label: "Expected SLA (minutes)"
default_value: 60
type: integer
hint: "Expected SLA resolution time"
Step 6.2: Use Parameters in Steps
Reference parameters using the ${} syntax.
Using Parameters in Record Insert
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id]
description: "Create incident with parameterized values"
inputs:
table: incident
fields:
short_description: "ATF Parameterized Test"
priority: ${priority_value}
category: ${category}
Using Parameters in Assertions
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id]
description: "Verify SLA meets expected time"
inputs:
script: |
var sla = new GlideRecord('task_sla');
sla.addQuery('task', '${inserted_incident.sys_id}');
sla.query();
if (sla.next()) {
var plannedMinutes = sla.planned_end_time.dateNumericValue() -
sla.start_time.dateNumericValue();
plannedMinutes = plannedMinutes / (1000 * 60);
outputs.actual_sla_minutes = Math.round(plannedMinutes);
outputs.expected_sla_minutes = ${expected_sla_minutes};
if (plannedMinutes > ${expected_sla_minutes}) {
throw new Error('SLA ' + plannedMinutes + ' mins exceeds expected ' +
${expected_sla_minutes} + ' mins');
}
}
Phase 7: Running Tests
Step 7.1: Manual Execution
Run Single Test:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var runner = new sn_atf.ATFTestRunner();
runner.setTest('[test_sys_id]');
var resultId = runner.run();
gs.info('ATF: Test execution started. Result ID: ' + resultId);
description: Run ATF test manually
Run Test Suite:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var suiteRunner = new sn_atf.ATFTestSuiteRunner();
suiteRunner.setSuite('[suite_sys_id]');
var resultId = suiteRunner.run();
gs.info('ATF: Suite execution started. Result ID: ' + resultId);
description: Run ATF test suite
Step 7.2: Scheduled Execution
Create a scheduled job to run tests regularly.
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Nightly ATF Regression Suite"
active: true
run_type: daily
time: "02:00:00"
script: |
// Run regression test suite nightly
var suiteRunner = new sn_atf.ATFTestSuiteRunner();
suiteRunner.setSuite('[regression_suite_sys_id]');
// Optional: Set parameters
suiteRunner.setParameter('environment', 'nightly');
var resultId = suiteRunner.run();
// Log result
gs.info('Nightly regression started. Result: ' + resultId);
// Optional: Send notification on completion
// (handled by ATF result business rules)
Step 7.3: Run with Impersonation
Test as different users to validate role-based access.
Using MCP:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var runner = new sn_atf.ATFTestRunner();
runner.setTest('[test_sys_id]');
runner.setImpersonateUser('[user_sys_id]'); // Run as this user
var resultId = runner.run();
gs.info('ATF: Test running as impersonated user. Result: ' + resultId);
description: Run ATF test with user impersonation
Phase 8: Test Results Analysis
Step 8.1: Query Test Results
Get Latest Test Results:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_test_result
query: test=[test_sys_id]^ORDERBYDESCsys_created_on
fields: sys_id,status,start_time,end_time,duration,message
limit: 10
Get Suite Execution Results:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_test_suite_result
query: test_suite=[suite_sys_id]^ORDERBYDESCsys_created_on
fields: sys_id,status,start_time,end_time,test_count,pass_count,fail_count,skip_count
limit: 5
Step 8.2: Analyze Failed Steps
Get Step-Level Results for Failed Test:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_step_result
query: test_result=[test_result_sys_id]^status=failure
fields: sys_id,step_config,status,message,output
limit: 50
Step 8.3: Aggregate Test Metrics
Using MCP:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Aggregate test metrics for the last 7 days
var startDate = gs.daysAgo(7);
var metrics = {
total: 0,
passed: 0,
failed: 0,
skipped: 0,
error: 0,
passRate: 0,
avgDuration: 0
};
var ga = new GlideAggregate('sys_atf_test_result');
ga.addQuery('sys_created_on', '>', startDate);
ga.addAggregate('COUNT');
ga.addAggregate('COUNT', 'status');
ga.addAggregate('AVG', 'duration');
ga.groupBy('status');
ga.query();
while (ga.next()) {
var status = ga.status.toString();
var count = parseInt(ga.getAggregate('COUNT'));
metrics.total += count;
if (status === 'success') metrics.passed = count;
else if (status === 'failure') metrics.failed = count;
else if (status === 'skipped') metrics.skipped = count;
else metrics.error += count;
}
metrics.passRate = metrics.total > 0 ?
Math.round((metrics.passed / metrics.total) * 100) : 0;
gs.info('ATF Metrics (Last 7 Days): ' + JSON.stringify(metrics, null, 2));
description: Generate ATF test metrics
Phase 9: CI/CD Integration
Step 9.1: REST API for CI/CD
ServiceNow provides REST APIs for ATF integration.
Start Test Suite via REST:
POST /api/sn_cicd/testsuite/run
Content-Type: application/json
{
"test_suite_sys_id": "[suite_sys_id]",
"browser_name": "Chrome",
"browser_version": "latest"
}
Check Test Progress:
GET /api/sn_cicd/progress/[result_id]
Get Test Results:
GET /api/sn_cicd/testsuite/results/[result_id]
Step 9.2: Integration with Jenkins
Jenkins Pipeline Example:
pipeline {
agent any
environment {
SN_INSTANCE = 'https://dev123.service-now.com'
SN_CREDENTIALS = credentials('servicenow-api')
}
stages {
stage('Run ATF Tests') {
steps {
script {
// Start test suite
def response = httpRequest(
url: "${SN_INSTANCE}/api/sn_cicd/testsuite/run",
httpMode: 'POST',
authentication: 'servicenow-api',
contentType: 'APPLICATION_JSON',
requestBody: """
{
"test_suite_sys_id": "${params.TEST_SUITE_ID}",
"browser_name": "Chrome"
}
"""
)
def result = readJSON text: response.content
env.RESULT_ID = result.result.sys_id
}
}
}
stage('Wait for Results') {
steps {
script {
def status = 'running'
while (status == 'running') {
sleep 30
def response = httpRequest(
url: "${SN_INSTANCE}/api/sn_cicd/progress/${env.RESULT_ID}",
authentication: 'servicenow-api'
)
def progress = readJSON text: response.content
status = progress.result.status
}
}
}
}
stage('Evaluate Results') {
steps {
script {
def response = httpRequest(
url: "${SN_INSTANCE}/api/sn_cicd/testsuite/results/${env.RESULT_ID}",
authentication: 'servicenow-api'
)
def results = readJSON text: response.content
if (results.result.status != 'success') {
error "ATF tests failed: ${results.result.fail_count} failures"
}
}
}
}
}
}
Step 9.3: Integration with GitHub Actions
GitHub Actions Workflow:
name: ServiceNow ATF Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
atf-tests:
runs-on: ubuntu-latest
steps:
- name: Run ATF Test Suite
id: run-tests
run: |
RESPONSE=$(curl -s -X POST \
-u "${{ secrets.SN_USERNAME }}:${{ secrets.SN_PASSWORD }}" \
-H "Content-Type: application/json" \
-d '{"test_suite_sys_id":"${{ vars.ATF_SUITE_ID }}"}' \
"${{ vars.SN_INSTANCE }}/api/sn_cicd/testsuite/run")
RESULT_ID=$(echo $RESPONSE | jq -r '.result.sys_id')
echo "result_id=$RESULT_ID" >> $GITHUB_OUTPUT
- name: Wait for Completion
run: |
STATUS="running"
while [ "$STATUS" = "running" ]; do
sleep 30
RESPONSE=$(curl -s \
-u "${{ secrets.SN_USERNAME }}:${{ secrets.SN_PASSWORD }}" \
"${{ vars.SN_INSTANCE }}/api/sn_cicd/progress/${{ steps.run-tests.outputs.result_id }}")
STATUS=$(echo $RESPONSE | jq -r '.result.status')
echo "Test status: $STATUS"
done
- name: Get Results
run: |
RESPONSE=$(curl -s \
-u "${{ secrets.SN_USERNAME }}:${{ secrets.SN_PASSWORD }}" \
"${{ vars.SN_INSTANCE }}/api/sn_cicd/testsuite/results/${{ steps.run-tests.outputs.result_id }}")
STATUS=$(echo $RESPONSE | jq -r '.result.status')
PASS=$(echo $RESPONSE | jq -r '.result.pass_count')
FAIL=$(echo $RESPONSE | jq -r '.result.fail_count')
echo "Results: $PASS passed, $FAIL failed"
if [ "$STATUS" != "success" ]; then
echo "::error::ATF tests failed"
exit 1
fi
Phase 10: Advanced Patterns
Step 10.1: Page Object Pattern for UI Tests
Create reusable step configurations for common UI operations.
Query Available Step Configs:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_step_config
query: active=true
fields: sys_id,name,category,description
limit: 100
Common Step Configs:
| Category | Step Config | Purpose |
|---|---|---|
| Record | Record - Insert | Create new record |
| Record | Record - Query | Find existing record |
| Record | Record - Update | Modify record |
| Record | Record - Delete | Remove record |
| Form | Open a new form | Navigate to new form |
| Form | Open an existing record | Navigate to record |
| Form | Set field value | Populate field |
| Form | Click a button | Trigger button action |
| Assertion | Verify field value | Assert field content |
| Script | Run Server Side Script | Execute custom logic |
Step 10.2: Test Data Factory Pattern
Create a script include for generating test data.
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_include
data:
name: ATFTestDataFactory
api_name: global.ATFTestDataFactory
active: true
client_callable: false
description: "Factory for creating consistent ATF test data"
script: |
var ATFTestDataFactory = Class.create();
ATFTestDataFactory.prototype = {
initialize: function() {
this.createdRecords = [];
},
createTestIncident: function(options) {
options = options || {};
var gr = new GlideRecord('incident');
gr.initialize();
gr.short_description = options.short_description ||
'ATF Test Incident ' + gs.generateGUID().substring(0, 8);
gr.description = options.description || 'Created by ATF Test';
gr.priority = options.priority || 3;
gr.category = options.category || 'software';
gr.caller_id = options.caller_id || gs.getUserID();
var sysId = gr.insert();
this.createdRecords.push({ table: 'incident', sys_id: sysId });
return sysId;
},
createTestUser: function(options) {
options = options || {};
var username = 'atf_' + gs.generateGUID().substring(0, 8);
var gr = new GlideRecord('sys_user');
gr.initialize();
gr.user_name = options.user_name || username;
gr.first_name = options.first_name || 'ATF';
gr.last_name = options.last_name || 'User';
gr.email = options.email || username + '@test.example.com';
gr.active = true;
var sysId = gr.insert();
this.createdRecords.push({ table: 'sys_user', sys_id: sysId });
return sysId;
},
createTestGroup: function(options) {
options = options || {};
var gr = new GlideRecord('sys_user_group');
gr.initialize();
gr.name = options.name || 'ATF Group ' + gs.generateGUID().substring(0, 8);
gr.active = true;
var sysId = gr.insert();
this.createdRecords.push({ table: 'sys_user_group', sys_id: sysId });
return sysId;
},
cleanup: function() {
// Delete in reverse order to handle dependencies
for (var i = this.createdRecords.length - 1; i >= 0; i--) {
var record = this.createdRecords[i];
var gr = new GlideRecord(record.table);
if (gr.get(record.sys_id)) {
gr.deleteRecord();
}
}
this.createdRecords = [];
},
type: 'ATFTestDataFactory'
};
Using Factory in Test:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 10
step_config: [run_script_config_sys_id]
description: "Setup: Create test data using factory"
inputs:
script: |
var factory = new ATFTestDataFactory();
// Create test data
outputs.incident_sys_id = factory.createTestIncident({
priority: 1,
short_description: 'Critical Server Issue'
});
outputs.user_sys_id = factory.createTestUser({
first_name: 'Test',
last_name: 'Admin'
});
// Store factory for cleanup
outputs.factory = factory;
Step 10.3: Conditional Test Execution
Skip tests based on conditions.
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 5
step_config: [run_script_config_sys_id]
description: "Pre-check: Verify test prerequisites"
inputs:
script: |
// Check if required plugin is active
var plugin = new GlideRecord('v_plugin');
plugin.addQuery('id', 'com.snc.change_management');
plugin.addQuery('state', 'active');
plugin.query();
if (!plugin.hasNext()) {
// Skip this test if plugin not installed
outputs.skip_test = true;
outputs.skip_reason = 'Change Management plugin not active';
// This will mark test as skipped, not failed
stepResult.setStatus('skipped');
stepResult.setMessage('Prerequisites not met: ' + outputs.skip_reason);
}
Common Step Configurations
Query All Available Step Types:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_step_config
query: active=true
fields: sys_id,name,category,description,inputs
limit: 200
Essential Step Configs:
| sys_id (varies) | Name | Category |
|---|---|---|
| Record - Insert | Create a new record | Record |
| Record - Query | Query for records | Record |
| Record - Update | Update a record | Record |
| Record - Delete | Delete a record | Record |
| Open a new form | Open empty form | Form |
| Open an existing record | Open by sys_id | Form |
| Set field value | Set form field | Form |
| Click a button | Button interaction | Form |
| Run Server Side Script | Custom server code | Script |
| Impersonate | Change user context | Security |
| Verify field value | Assert field equals | Assertion |
| Log Message | Write to output | Debug |
| Wait | Pause execution | Flow |
Tool Usage Summary
| Operation | MCP Tool | Purpose |
|---|---|---|
| Create Test Suite | SN-Create-Record | Create sys_atf_test_suite |
| Create Test | SN-Create-Record | Create sys_atf_test |
| Create Step | SN-Create-Record | Create sys_atf_step |
| Add Parameters | SN-Create-Record | Create sys_atf_parameter |
| Query Results | SN-Query-Table | Read sys_atf_test_result |
| Run Test | SN-Execute-Background-Script | Trigger test execution |
| Get Step Configs | SN-Query-Table | List sys_atf_step_config |
| Update Test | SN-Update-Record | Modify test properties |
Best Practices
Test Design
- Single Responsibility: Each test should validate one specific scenario
- Independence: Tests should not depend on other tests' execution order
- Isolation: Use unique test data to prevent conflicts with other tests
- Cleanup: Always clean up test data, even on test failure
- Naming: Use descriptive names:
[Feature]_[Scenario]_[ExpectedResult]
Test Data
- Generate Unique Data: Use GUIDs or timestamps in test data names
- Avoid Production Data: Never reference real user data in tests
- Minimal Data: Create only the data needed for the test
- Data Factory Pattern: Use script includes for consistent data creation
Assertions
- Specific Assertions: Assert specific values, not just "not empty"
- Multiple Checks: Validate all critical aspects of the outcome
- Meaningful Messages: Include context in assertion failure messages
- Negative Testing: Test that invalid operations are correctly rejected
Performance
- Limit Queries: Use setLimit() and efficient queries
- Batch Operations: Group similar operations together
- Avoid UI Tests When Possible: Server-side tests are faster and more reliable
- Parallel Suites: Run independent test suites in parallel
Maintenance
- Version Control: Include tests in update sets
- Documentation: Add descriptions to tests and steps
- Refactoring: Update tests when requirements change
- Regular Execution: Run tests frequently to catch issues early
Troubleshooting
Test Fails to Start
Symptom: Test shows "Pending" status indefinitely Causes:
- ATF Runner not configured
- Missing test client for UI tests
- Test scheduler disabled Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_schedule
query: active=true
fields: sys_id,name,state,last_run
UI Tests Fail Randomly
Symptom: Same UI test passes and fails intermittently Causes:
- Timing issues with page load
- DOM changes between runs
- Browser version differences Solution:
- Add Wait steps before UI interactions
- Use stable element selectors
- Specify browser version in test configuration
Step Variables Not Resolved
Symptom: ${variable} appears literally in output
Causes:
- Variable not set by previous step
- Typo in variable name
- Step order incorrect Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_variable
query: test=[test_sys_id]
fields: name,step,value
Test Data Not Cleaned Up
Symptom: Test data accumulates in instance Causes:
- Cleanup step not added
- Cleanup step failed before completing
- Test halted before cleanup Solution:
- Use try-finally pattern in cleanup scripts
- Create scheduled cleanup job for orphaned data:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Clean up orphaned ATF test data
var tables = ['incident', 'sys_user', 'sys_user_group'];
var cutoff = gs.daysAgo(1); // Older than 1 day
tables.forEach(function(table) {
var gr = new GlideRecord(table);
gr.addQuery('short_description', 'STARTSWITH', 'ATF');
gr.addQuery('sys_created_on', '<', cutoff);
gr.query();
var count = 0;
while (gr.next()) {
gr.deleteRecord();
count++;
}
if (count > 0) {
gs.info('Cleaned up ' + count + ' orphaned records from ' + table);
}
});
description: Clean orphaned ATF test data
CI/CD Pipeline Timeout
Symptom: Pipeline fails waiting for test completion Causes:
- Test suite takes too long
- ServiceNow instance performance issues
- Network connectivity problems Solution:
- Set appropriate timeout in pipeline
- Split large suites into smaller ones
- Use parallel test execution
Examples
Example 1: Complete Incident Lifecycle Test
Tool: SN-Create-Record (Test)
Parameters:
table_name: sys_atf_test
data:
name: "Incident_Lifecycle_CreateToClose"
description: "Validates complete incident workflow from creation to closure"
active: true
# Step 1: Create Incident
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
step_config: [record_insert_config]
inputs:
table: incident
fields:
short_description: ATF Lifecycle Test
priority: 3
category: software
outputs:
record: test_incident
# Step 2: Assert Created
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 200
step_config: [verify_field_config]
inputs:
record: ${test_incident}
field: state
expected_value: 1
operator: =
# Step 3: Assign Incident
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 300
step_config: [record_update_config]
inputs:
record: ${test_incident}
fields:
state: 2
assigned_to: [admin_sys_id]
# Step 4: Resolve Incident
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 400
step_config: [record_update_config]
inputs:
record: ${test_incident}
fields:
state: 6
resolution_code: Solved (Permanently)
resolution_notes: ATF Test Resolution
# Step 5: Assert Resolved
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
step_config: [verify_field_config]
inputs:
record: ${test_incident}
field: state
expected_value: 6
# Step 6: Cleanup
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 9999
step_config: [record_delete_config]
inputs:
record: ${test_incident}
Example 2: Role-Based Access Test
# Test that ITIL users can update incidents but not delete them
# Step 1: Create test incident as admin
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
step_config: [record_insert_config]
inputs:
table: incident
fields:
short_description: ATF RBAC Test
outputs:
record: test_incident
# Step 2: Impersonate ITIL user
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 200
step_config: [impersonate_config]
inputs:
user: [itil_user_sys_id]
# Step 3: Update should succeed
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 300
step_config: [run_script_config]
inputs:
script: |
var gr = new GlideRecord('incident');
gr.get('${test_incident.sys_id}');
gr.work_notes = 'ITIL user update test';
var result = gr.update();
if (!result) {
throw new Error('ITIL user should be able to update incidents');
}
outputs.update_succeeded = true;
# Step 4: Delete should fail
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 400
step_config: [run_script_config]
inputs:
script: |
var gr = new GlideRecord('incident');
gr.get('${test_incident.sys_id}');
try {
var deleted = gr.deleteRecord();
if (deleted) {
throw new Error('ITIL user should NOT be able to delete incidents');
}
} catch (e) {
// Expected - delete should fail
outputs.delete_blocked = true;
}
# Step 5: Stop impersonation
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
step_config: [impersonate_config]
inputs:
stop_impersonation: true
# Step 6: Cleanup as admin
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 9999
step_config: [record_delete_config]
inputs:
record: ${test_incident}
Related Skills
admin/script-execution- Background script patterns for ATFadmin/update-set-management- Version control for testsadmin/deployment-workflow- Deploy tests between instancessecurity/acl-management- Test ACL configurations