# Automated Testing

> Comprehensive Automated Test Framework (ATF) guide for creating, managing, and executing automated tests in ServiceNow

- Skill: `happy-technologies-llc/automated-testing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add happy-technologies-llc/automated-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/happy-technologies-llc/automated-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Happy-Technologies-LLC (https://skillmd.com/u/happy-technologies-llc)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/happy-technologies-llc/automated-testing

---


# 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

| 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:**
```bash
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:**
```bash
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:**
```bash
GET /api/sn_cicd/progress/[result_id]
```

**Get Test Results:**
```bash
GET /api/sn_cicd/testsuite/results/[result_id]
```

#### Step 9.2: Integration with Jenkins

**Jenkins Pipeline Example:**
```groovy
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:**
```yaml
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.dele

…(truncated)
