mcpField
A
automated-testing

automated-testing

github.com/happy-technologies-llcTesting & quality4143 installsUpdated 2d ago
Repository

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

  1. 01Create suites and tests
  2. 02Compose server, client, and UI steps
  3. 03Write assertions and manage test data
  4. 04Parameterize and schedule tests
  5. 05Analyze runs and failed steps
  6. 06Integrate tests with CI/CD

Use cases

  1. 01Validate platform changes before deployment
  2. 02Run test-driven checks during development
  3. 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-testing

After installing, type/automated-testingin Claude Code to confirm it loaded.

Details

From the project README

Automated 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) or atf_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

TablePurpose
sys_atf_test_suiteTest suite containers
sys_atf_testIndividual test definitions
sys_atf_stepTest steps within tests
sys_atf_step_configStep type configurations
sys_atf_test_resultTest execution results
sys_atf_step_resultIndividual step results
sys_atf_parameterTest parameters
sys_atf_variableTest variables (runtime data)
sys_atf_test_suite_testSuite-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:

TypeValueDescription
Server Sidetest_scriptServer-side JavaScript tests
BrowserbrowserClient-side UI tests
Quick Startquick_startGuided 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
OperatorDescriptionExample
=Equalsstate = 2
!=Not equalsstate != 7
<Less thanpriority < 3
<=Less or equalpriority <= 2
>Greater thanage > 0
>=Greater or equalcount >= 1
containsString containsdescription contains "error"
starts withString prefixnumber starts with "INC"
ends withString suffixemail ends with "@company.com"
is emptyNull or emptyassigned_to is empty
is not emptyHas valuecaller_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:

CategoryStep ConfigPurpose
RecordRecord - InsertCreate new record
RecordRecord - QueryFind existing record
RecordRecord - UpdateModify record
RecordRecord - DeleteRemove record
FormOpen a new formNavigate to new form
FormOpen an existing recordNavigate to record
FormSet field valuePopulate field
FormClick a buttonTrigger button action
AssertionVerify field valueAssert field content
ScriptRun Server Side ScriptExecute 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)NameCategory
Record - InsertCreate a new recordRecord
Record - QueryQuery for recordsRecord
Record - UpdateUpdate a recordRecord
Record - DeleteDelete a recordRecord
Open a new formOpen empty formForm
Open an existing recordOpen by sys_idForm
Set field valueSet form fieldForm
Click a buttonButton interactionForm
Run Server Side ScriptCustom server codeScript
ImpersonateChange user contextSecurity
Verify field valueAssert field equalsAssertion
Log MessageWrite to outputDebug
WaitPause executionFlow

Tool Usage Summary

OperationMCP ToolPurpose
Create Test SuiteSN-Create-RecordCreate sys_atf_test_suite
Create TestSN-Create-RecordCreate sys_atf_test
Create StepSN-Create-RecordCreate sys_atf_step
Add ParametersSN-Create-RecordCreate sys_atf_parameter
Query ResultsSN-Query-TableRead sys_atf_test_result
Run TestSN-Execute-Background-ScriptTrigger test execution
Get Step ConfigsSN-Query-TableList sys_atf_step_config
Update TestSN-Update-RecordModify 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 ATF
  • admin/update-set-management - Version control for tests
  • admin/deployment-workflow - Deploy tests between instances
  • security/acl-management - Test ACL configurations

References

Comments

Markdown supported