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. 06CI/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 emptynull または空assigned_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 の Query ステップ設定

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 - Queryレコードを照会レコード
Record - Updateレコードを更新レコード
Record - Deleteレコードを削除レコード
Open a new form空のフォームを開くフォーム
Open an existing recordsys_id で開くフォーム
Set field valueフォームフィールドを設定フォーム
Click a buttonボタンを操作フォーム
Run Server Side Scriptカスタムサーバーコードスクリプト
Impersonateユーザーコンテキストを変更セキュリティ
Verify field valueフィールドの値が一致することを検証アサーション
Log Message出力に書き込むデバッグ
Wait実行を一時停止フロー

ツールの使用概要

操作MCP ツール目的
テストスイートを作成SN-Create-Recordsys_atf_test_suite を作成
テストを作成SN-Create-Recordsys_atf_test を作成
ステップを作成SN-Create-Recordsys_atf_step を作成
パラメーターを追加SN-Create-Recordsys_atf_parameter を作成
結果を照会SN-Query-Tablesys_atf_test_result を読み取る
テストを実行SN-Execute-Background-Scriptテスト実行をトリガー
ステップ設定を取得SN-Query-Tablesys_atf_step_config を一覧表示
テストを更新SN-Update-Recordテストのプロパティを変更

ベストプラクティス

テスト設計
  • 単一責任: 各テストでは、1 つの特定のシナリオを検証します
  • 独立性: テストは、ほかのテストの実行順序に依存しないようにします
  • 分離: ほかのテストとの競合を防ぐため、一意のテストデータを使用します
  • クリーンアップ: テストが失敗した場合も含め、必ずテストデータをクリーンアップします
  • 命名: 内容がわかる名前を使用します:[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 設定のテスト

参考資料

コメント