mcpField
A
automated-testing

automated-testing

github.com/happy-technologies-llc测试与质量4143 次安装2 天前更新
代码仓库

关于 automated-testing

面向 ServiceNow 开发者、QA 工程师和管理员的 Automated Test Framework (ATF) 测试技能,涵盖测试套件、步骤、断言及运行结果分析。适用于开发验证、升级检查和回归测试。

主要功能

  1. 01创建测试套件与测试
  2. 02编排服务端、客户端和 UI 步骤
  3. 03编写断言并管理测试数据
  4. 04参数化及定时运行测试
  5. 05分析执行结果与失败步骤
  6. 06将测试接入 CI/CD

适用场景

  1. 01部署前验证平台变更
  2. 02开发期间执行测试驱动验证
  3. 03升级后开展回归测试

MCP 相关

介绍通过 MCP 工具创建、查询 ServiceNow ATF 记录,并使用后台脚本运行测试。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/happy-technologies-llc/happy-platform-skills --skill automated-testing

安装后在 Claude Code 中输入/automated-testing确认已加载。

详细介绍

自动化测试框架(ATF)

概述

自动化测试框架(ATF)是 ServiceNow 的原生测试解决方案,用于验证平台功能。此技能涵盖:

  • 创建测试套件并组织测试
  • 使用各种步骤类型(服务器端、客户端、UI)构建测试
  • 编写断言和验证
  • 管理测试数据的设置和清理
  • 实施参数化测试
  • 手动运行测试和按计划运行测试
  • 分析测试结果和失败原因
  • 将 ATF 集成到 CI/CD 流水线中
  • 制定可维护且可靠的测试最佳实践

适用场景: 在将更改部署到生产环境之前、开发期间(TDD)、升级之后,以及作为回归测试的一部分。

适用对象: 需要确保平台可靠性的开发者、QA 工程师和管理员。

前提条件

  • 角色: atf_test_admin(完全访问权限)或 atf_test_designer(创建/编辑测试)
  • 插件: Automated Test Framework (com.snc.automated_testing)
  • 访问权限: sys_atf_test、sys_atf_test_suite、sys_atf_step 表
  • 知识: 基本了解 ServiceNow 脚本(GlideRecord、客户端脚本)
  • 相关技能: admin/script-execution、admin/update-set-management

ATF 架构

┌─────────────────────────────────────────────────────────────┐
│                      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  │          │
│  └─────────────┘  └─────────────┘  └─────────────┘          │
└─────────────────────────────────────────────────────────────┘

关键表

表用途
sys_atf_test_suite测试套件容器
sys_atf_test单个测试定义
sys_atf_step测试中的测试步骤
sys_atf_step_config步骤类型配置
sys_atf_test_result测试执行结果
sys_atf_step_result单个步骤结果
sys_atf_parameter测试参数
sys_atf_variable测试变量(运行时数据)
sys_atf_test_suite_test套件与测试之间的关系

操作流程

阶段 1:创建测试套件
步骤 1.1:创建测试套件

将相关测试整理到一个套件中,以便更轻松地管理和执行。

使用 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

使用 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"
}
步骤 1.2:查询现有测试套件

使用 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
阶段 2:创建测试
步骤 2.1:创建基本测试

每个测试代表一个要验证的特定场景。

使用 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_script服务器端 JavaScript 测试
浏览器browser客户端 UI 测试
快速入门quick_start引导式测试创建
步骤 2.2:将测试添加到套件

将测试关联到测试套件。

使用 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...)
阶段 3:创建测试步骤
步骤 3.1:服务器端测试步骤

服务器端步骤执行 GlideRecord 操作和服务器端 JavaScript。

步骤:创建记录

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

步骤:查询记录

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

步骤:更新记录

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"

步骤:运行服务器端脚本

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
步骤 3.2:客户端测试步骤

客户端步骤用于测试 UI 行为和客户端脚本。

步骤:打开表单

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

步骤:设置字段值

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

步骤:点击按钮

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

步骤:验证字段状态

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
步骤 3.3:UI 测试步骤

UI 步骤用于与 ServiceNow 界面交互。

步骤:打开记录

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}

步骤:导航到模块

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
阶段 4:断言和验证
步骤 4.1:基本断言

断言字段值

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: =

断言记录存在

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

断言记录数量

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: >=
步骤 4.2:自定义脚本断言

对于复杂验证,请使用脚本断言。

运行带断言的服务器端脚本

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);
        }
步骤 4.3:断言运算符
运算符描述示例
=等于state = 2
!=不等于state != 7
<小于priority < 3
<=小于或等于priority <= 2
>大于age > 0
>=大于或等于count >= 1
contains字符串包含description contains "error"
starts with字符串前缀number starts with "INC"
ends with字符串后缀email ends with "@company.com"
is empty为空或 nullassigned_to is empty
is not empty有值caller_id is not empty
阶段 5:测试数据管理
步骤 5.1:设置测试数据

在每项测试开始时创建测试数据。

使用数据设置步骤

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);
步骤 5.2:清理测试数据

始终清理测试数据,以防止数据累积。

使用清理步骤(测试结束时)

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');
          }
        }
步骤 5.3:可复用的数据设置(Data Broker)

对于需要在多个场景间使用一致数据的测试:

Data Broker 的查询步骤配置

Tool: SN-Query-Table
Parameters:
  table_name: sys_atf_step_config
  query: nameLIKEData Broker
  fields: sys_id,name,description,category
阶段 6:参数化测试
步骤 6.1:创建测试参数

参数可用于使用不同输入运行同一项测试。

使用 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)"

创建多个参数:

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"
步骤 6.2:在步骤中使用参数

使用 ${} 语法引用参数。

在记录插入中使用参数

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}

在断言中使用参数

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');
          }
        }
阶段 7:运行测试
步骤 7.1:手动执行

运行单项测试:

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

运行测试套件:

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
步骤 7.2:计划执行

创建计划作业以定期运行测试。

使用 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)
步骤 7.3:以模拟身份运行

以不同用户身份进行测试,以验证基于角色的访问控制。

使用 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
阶段 8:测试结果分析
步骤 8.1:查询测试结果

获取最新测试结果:

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

获取套件执行结果:

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
步骤 8.2:分析失败步骤

获取失败测试的步骤级结果:

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
步骤 8.3:汇总测试指标

使用 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
阶段 9:CI/CD 集成
步骤 9.1:用于 CI/CD 的 REST API

ServiceNow 提供用于 ATF 集成的 REST API。

通过 REST 启动测试套件:

POST /api/sn_cicd/testsuite/run
Content-Type: application/json

{
  "test_suite_sys_id": "[suite_sys_id]",
  "browser_name": "Chrome",
  "browser_version": "latest"
}

检查测试进度:

GET /api/sn_cicd/progress/[result_id]

获取测试结果:

GET /api/sn_cicd/testsuite/results/[result_id]
步骤 9.2:与 Jenkins 集成

Jenkins 流水线示例:

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"
          }
        }
      }
    }
  }
}
步骤 9.3:与 GitHub Actions 集成

GitHub Actions 工作流:

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
阶段 10:高级模式
步骤 10.1:UI 测试的页面对象模式

为常见 UI 操作创建可复用的步骤配置。

查询可用的步骤配置:

Tool: SN-Query-Table
Parameters:
  table_name: sys_atf_step_config
  query: active=true
  fields: sys_id,name,category,description
  limit: 100

常见步骤配置:

类别步骤配置用途
记录Record - Insert创建新记录
记录Record - Query查找现有记录
记录Record - Update修改记录
记录Record - Delete删除记录
表单Open a new form导航至新表单
表单Open an existing record导航至记录
表单Set field value填充字段
表单Click a button触发按钮操作
断言Verify field value断言字段内容
脚本Run Server Side Script执行自定义逻辑
步骤 10.2:测试数据工厂模式

创建一个脚本包含项,用于生成测试数据。

使用 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'
      };

在测试中使用工厂:

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;
步骤 10.3:条件测试执行

根据条件跳过测试。

使用 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);
        }

常见步骤配置

查询所有可用的步骤类型:

Tool: SN-Query-Table
Parameters:
  table_name: sys_atf_step_config
  query: active=true
  fields: sys_id,name,category,description,inputs
  limit: 200

基本步骤配置:

sys_id(各不相同)名称类别
Record - Insert创建新记录Record
Record - Query查询记录Record
Record - Update更新记录Record
Record - Delete删除记录Record
Open a new form打开空白表单Form
Open an existing record按 sys_id 打开Form
Set field value设置表单字段Form
Click a button按钮交互Form
Run Server Side Script自定义服务端代码Script
Impersonate更改用户上下文Security
Verify field value断言字段相等Assertion
Log Message写入输出Debug
Wait暂停执行Flow

工具使用概要

操作MCP 工具用途
创建测试套件SN-Create-Record创建 sys_atf_test_suite
创建测试SN-Create-Record创建 sys_atf_test
创建步骤SN-Create-Record创建 sys_atf_step
添加参数SN-Create-Record创建 sys_atf_parameter
查询结果SN-Query-Table读取 sys_atf_test_result
运行测试SN-Execute-Background-Script触发测试执行
获取步骤配置SN-Query-Table列出 sys_atf_step_config
更新测试SN-Update-Record修改测试属性

最佳实践

测试设计
  • 单一职责: 每个测试都应验证一个特定场景
  • 独立性: 测试不应依赖其他测试的执行顺序
  • 隔离性: 使用唯一的测试数据,避免与其他测试冲突
  • 清理: 始终清理测试数据,即使测试失败也不例外
  • 命名: 使用描述性名称:[Feature]_[Scenario]_[ExpectedResult]
测试数据
  • 生成唯一数据: 在测试数据名称中使用 GUID 或时间戳
  • 避免生产数据: 测试中切勿引用真实用户数据
  • 最少数据: 只创建测试所需的数据
  • 数据工厂模式: 使用脚本包含项一致地创建数据
断言
  • 具体断言: 断言具体值,而不只是“非空”
  • 多项检查: 验证结果的所有关键方面
  • 有意义的消息: 在断言失败消息中包含上下文
  • 负向测试: 测试无效操作是否被正确拒绝
性能
  • 限制查询: 使用 setLimit() 和高效查询
  • 批量操作: 将相似操作分组执行
  • 尽可能避免 UI 测试: 服务端测试速度更快,也更可靠
  • 并行套件: 并行运行相互独立的测试套件
维护
  • 版本控制: 将测试纳入更新集
  • 文档: 为测试和步骤添加说明
  • 重构: 需求变更时更新测试
  • 定期执行: 经常运行测试,及早发现问题

故障排除

测试无法启动

症状: 测试状态一直显示为“Pending” 原因:

  • 未配置 ATF Runner
  • UI 测试缺少测试客户端
  • 测试调度器已禁用 解决方案:
Tool: SN-Query-Table
Parameters:
  table_name: sys_atf_schedule
  query: active=true
  fields: sys_id,name,state,last_run
UI 测试随机失败

症状: 同一个 UI 测试时而通过、时而失败 原因:

  • 页面加载时序问题
  • 不同运行之间 DOM 发生变化
  • 浏览器版本差异 解决方案:
  • 在 UI 交互前添加 Wait 步骤
  • 使用稳定的元素选择器
  • 在测试配置中指定浏览器版本
步骤变量未解析

症状: 输出中原样出现 ${variable} 原因:

  • 前一步未设置变量
  • 变量名称拼写错误
  • 步骤顺序不正确 解决方案:
Tool: SN-Query-Table
Parameters:
  table_name: sys_atf_variable
  query: test=[test_sys_id]
  fields: name,step,value
测试数据未清理

症状: 实例中的测试数据不断累积 原因:

  • 未添加清理步骤
  • 清理步骤未能完成
  • 测试在清理前中止 解决方案:
  • 在清理脚本中使用 try-finally 模式
  • 为孤立数据创建计划清理作业:
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 流水线超时

症状: 流水线在等待测试完成时失败 原因:

  • 测试套件耗时过长
  • ServiceNow 实例存在性能问题
  • 网络连接问题 解决方案:
  • 在流水线中设置适当的超时时间
  • 将大型测试套件拆分为较小的套件
  • 使用并行测试执行

示例

示例 1:完整事件生命周期测试
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}
示例 2:基于角色的访问测试
# 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}

相关技能

  • admin/script-execution - 用于 ATF 的后台脚本模式
  • admin/update-set-management - 测试的版本控制
  • admin/deployment-workflow - 在实例之间部署测试
  • security/acl-management - 测试 ACL 配置

参考资料

评论

支持 Markdown