---
tags: [test, testing, atf, automated-test-framework, form, rest, catalog, email, server, dashboard, reporting, service-portal]
---

# Implementing Tests Guide

Create ServiceNow Automated Test Framework (ATF) test cases using Fluent APIs across 11 ATF categories: server, form, REST, catalog, email, app navigator, reporting, responsive dashboard, and Service Portal variants (form_SP, catalog_SP). This guide covers test strategy, category selection, step configuration, and the full API surface for each ATF namespace.

## When to Use

- Generating automated test cases for a ServiceNow application
- Testing forms, APIs, catalog items, dashboards, or server-side logic
- Building end-to-end workflow tests combining multiple ATF categories
- Validating email notifications, report visibility, or navigation menus

## Instructions

### Strategic Approach

1. **Analyze the application context** -- examine custom tables, forms, APIs, catalog items, dashboards, and business logic.
2. **Develop a test strategy** -- propose up to 3 representative test cases reflecting critical workflows before expanding coverage.
3. **Select ATF categories** -- map each test interaction to the appropriate `atf.*` namespace (see table below).
4. **Implement test steps** using the category-specific Fluent ATF APIs.

### Category Selection

| Interaction type | ATF namespace | Use for |
|-----------------|---------------|---------|
| UI navigation | `atf.applicationNavigator` | Verify menus/modules visible, navigate to modules |
| Form interactions | `atf.form` | Open/submit forms, set/validate fields, click UI actions |
| Forms in Service Portal | `atf.form_SP` | Same as form but in Service Portal context |
| REST API validation | `atf.rest` | Send HTTP requests, assert status codes/headers/payload |
| Server-side logic | `atf.server` | Impersonation, CRUD operations, record validation, logging |
| Service Catalog | `atf.catalog` | Open/order catalog items, set/validate variables |
| Catalog in Service Portal | `atf.catalog_SP` | Same as catalog but in portal -- plus order guides and multi-row variable sets |
| Email testing | `atf.email` | Validate outbound emails, generate inbound emails |
| Reporting | `atf.reporting` | Assert report visibility |
| Dashboards | `atf.responsiveDashboard` | Assert dashboard visibility and sharing |

### Test File Structure

Every ATF test file must:

```typescript
import { Test } from "@servicenow/sdk/core";
import "@servicenow/sdk/global";

Test({
  $id: Now.ID['test_id'],
  name: 'test name',
  description: 'optional description',
  failOnServerError: true
}, (atf) => {
  // Steps execute sequentially
  atf.<category>.<method>({
    $id: Now.ID['step_id'],
    ...params
  });
});
```

- `$id` must be globally unique for both the test and each step.
- Steps execute sequentially -- capture earlier step outputs in variables to pass to later steps.

### Category Selection Guidance

- Prefer UI-based categories (`atf.form`, `atf.catalog`) over `atf.server` for interactions that users normally perform through the UI.
- Use `atf.server` only when backend assertions, data setup, or server-only operations are needed.
- When the user mentions Service Portal, use the `_SP` variants (`atf.form_SP`, `atf.catalog_SP`).
- Combine categories within a single test for end-to-end workflows.

## Key Concepts

- **Test data setup**: Use `atf.server.impersonate` and `atf.server.createUser` to establish user context. Use `atf.server.recordInsert` to create prerequisite data.
- **Assertion chaining**: After `atf.form.submitForm`, follow with `atf.server.recordValidation` to verify the record was created correctly server-side.
- **Form UI flavors**: `standard_ui`, `service_operations_workspace`, `asset_workspace`, `cmdb_workspace`.
- **Navigator styles**: `ui15`, `ui16`, `polaris`.
- **Catalog variable format**: `IO:<sys_id>=<value>` joined with `^` and ending with `^EQ`.
- **Encoded queries**: Field value conditions use ServiceNow encoded query syntax (e.g., `short_description=Test^priority=1`).

## API Reference: atf.server

### Methods

| Method | Description | Key Output |
|--------|-------------|------------|
| `impersonate` | Impersonate a user for the test | `{ user }` |
| `createUser` | Create a user with roles and groups | `{ user }` |
| `log` | Log a message to test results | void |
| `recordQuery` | Query records with encoded query | `{ table, first_record }` |
| `recordInsert` | Insert a record | `{ table, record_id }` |
| `recordValidation` | Validate record meets conditions | void |
| `recordUpdate` | Update a record's fields | void |
| `recordDelete` | Delete a record | void |
| `searchForCatalogItem` | Search catalog items | `{ catalog_item_id }` |
| `checkoutShoppingCart` | Checkout cart | `{ request_id }` |
| `replayRequestItem` | Replay a previous request item | `{ table, req_item }` |

### impersonate

| Name | Type | Mandatory | Description |
|------|------|-----------|-------------|
| `user` | `string \| Record<'sys_user'>` | Yes | User to impersonate |

### createUser

| Name | Type | Mandatory | Description |
|------|------|-----------|-------------|
| `firstName` | `string` | Yes | First name |
| `lastName` | `string` | Yes | Last name |
| `fieldValues` | `Partial<Data<'sys_user'>>` | Yes | Additional user fields (JSON) |
| `groups` | `Array<string>` | Yes | Group sys_ids |
| `roles` | `Array<string>` | Yes | Role sys_ids |
| `impersonate` | `boolean` | Yes | Whether to impersonate after creation |

### recordInsert / recordUpdate

| Name | Type | Mandatory | Description |
|------|------|-----------|-------------|
| `table` | `TableName` | Yes | Target table |
| `fieldValues` | `Partial<Data<T>>` | Yes | Field-value map (snake_case keys) |
| `assert` | `string` | No | `'record_successfully_inserted'` / `'record_not_inserted'` / `'record_successfully_updated'` / `'record_not_updated'` |
| `enforceSecurity` | `boolean` | No | Default: `true` |
| `recordId` | `string` | Yes (update only) | sys_id of record to update |

### recordValidation

| Name | Type | Mandatory | Description |
|------|------|-----------|-------------|
| `table` | `TableName` | Yes | Table to validate against |
| `recordId` | `string` | Yes | sys_id of record |
| `fieldValues` | `string` | Yes | Encoded query condition |
| `assert` | `string` | No | `'record_validated'` / `'record_not_found'` |

## API Reference: atf.form

### Methods

`openNewForm`, `openExistingRecord`, `submitForm`, `setFieldValue`, `fieldValueValidation`, `fieldStateValidation`, `uiActionVisibility`, `clickUIAction`, `clickModalButton`, `declarativeActionVisibility`, `clickDeclarativeAction`

### Key Properties

**openNewForm**: `table` (required), `view`, `formUI` (default: `"standard_ui"`)

**setFieldValue**: `table` (required), `fieldValues` (required, JSON object), `formUI`

**submitForm**: `assert` (`""`, `"form_submitted_to_server"`, `"form_submission_canceled_in_browser"`), `formUI`. Returns `{ table, record_id }`.

**fieldValueValidation**: `table`, `conditions` (encoded query), `formUI`

**fieldStateValidation**: `table`, `visible[]`, `notVisible[]`, `readOnly[]`, `notReadOnly[]`, `mandatory[]`, `notMandatory[]`, `formUI`

**clickUIAction**: `table`, `uiAction` (sys_id), `assert`, `actionType` (`"ui_action"` or `"declarative_action"`), `formUI`

## API Reference: atf.rest

### Methods

`sendRestRequest`, `assertStatusCodeName`, `assertStatusCode`, `assertResponseTime`, `assertResponseHeader`, `assertResponsePayload`, `assertResponseJSONPayloadIsValid`, `assertJsonResponsePayloadElement`, `assertResponseXMLPayloadIsWellFormed`, `assertXMLResponsePayloadElement`

### sendRestRequest

| Name | Type | Mandatory | Description |
|------|------|-----------|-------------|
| `path` | `string` | Yes | API path (e.g., `/api/now/table/incident`) |
| `body` | `string` | Yes | JSON string request body |
| `auth` | `string` | Yes | `'basic'`, `'mutual'`, or `''` |
| `method` | `string` | No | `'get'`, `'post'`, `'put'`, `'delete'`, `'patch'` |
| `queryParameters` | `object` | No | Key-value query params |
| `headers` | `object` | No | Key-value request headers |

### Assert Methods

- `assertStatusCode`: `statusCode` (number), `operation` (`'equals'`, `'not_equals'`, `'less_than'`, etc.)
- `assertResponsePayload`: `responseBody` (string), `operation` (`'contains'`, `'equals'`, etc.)
- `assertJsonResponsePayloadElement`: `elementName` (JSON path), `elementValue`, `operation`

## API Reference: atf.catalog

### Methods

`openCatalogItem`, `addItemToShoppingCart`, `setCatalogItemQuantity`, `orderCatalogItem`, `validatePriceAndRecurringPrice`, `validateVariableValue`, `variableStateValidation`, `setVariableValue`, `openRecordProducer`, `submitRecordProducer`

### Key Properties

**openCatalogItem**: `catalogItem` (sys_id, required)

**setVariableValue**: `catalogItem` (sys_id), `variableValues` (format: `IO:<sys_id>=<value>^IO:<sys_id>=<value>^EQ`)

**orderCatalogItem**: `assert` (`'form_submitted_to_server'` or `'form_submission_cancelled_in_browser'`). Returns `{ request_id, cart }`.

**Important sequencing**: `openCatalogItem` must precede `orderCatalogItem`. `openRecordProducer` must precede `submitRecordProducer`.

## API Reference: atf.email

### Methods

| Method | Description |
|--------|-------------|
| `validateOutboundEmail` | Filter sys_email table for sent emails |
| `validateOutboundEmailGeneratedByNotification` | Filter by notification source |
| `validateOutboundEmailGeneratedByFlow` | Filter by flow source |
| `generateInboundEmail` | Generate a new inbound email |
| `generateInboundReplyEmail` | Generate an inbound reply |
| `generateRandomString` | Generate test data string |

### generateInboundEmail

`from`, `to`, `subject`, `body` (all required strings). Returns `{ output_email_record }`.

## API Reference: atf.applicationNavigator

### Methods

- `moduleVisibility`: Check if modules are visible in navigation. `navigator` (`'ui15'`, `'ui16'`, `'polaris'`), `visibleModules[]`, `notVisibleModules[]`.
- `navigateToModule`: Navigate to a module. `module` (sys_id).
- `applicationMenuVisibility`: Check if app menus are visible. `visible[]`, `notVisible[]`.

## API Reference: atf.reporting

### reportVisibility

`report` (sys_id of `sys_report`), `assert` (`'can_view_report'` or `'cannot_view_report'`).

## API Reference: atf.responsiveDashboard

### responsiveDashboardVisibility

`dashboard` (sys_id of `pa_dashboards`), `assert` (`'dashboard_is_visible'` or `'dashboard_is_not_visible'`).

### responsiveDashboardSharing

`dashboard` (sys_id), `assert` (`'can_share_dashboard'` or `'cannot_share_dashboard'`).

## Service Portal Variants

### atf.form_SP

Same methods as `atf.form` with additional `portal` and `page` properties plus `openServicePortalPage` method. Uses `form_SP` namespace.

### atf.catalog_SP

Same methods as `atf.catalog` with additional `portal` and `page` properties, plus: `openOrderGuide`, `navigatewithinOrderGuide`, `validateOrderGuideItem`, `reviewOrderGuideSummary`, `saveCurrentRowOfMultiRowVariableSet`, `addRowToMultiRowVariableSet`.

## Avoidance

1. Do not overuse `atf.server` for tasks that form or catalog APIs handle directly.
2. Do not hardcode `sys_id` values -- always look them up.
3. Do not skip mandatory fields when using `setFieldValue` or `recordInsert`.
4. Do not call sequence-dependent steps out of order.
5. Do not create generic or template-based tests -- each test should reflect real usage scenarios.

## Example: End-to-End Form Test

```javascript
import "@servicenow/sdk/global";
import { Test } from "@servicenow/sdk/core";

Test({
  $id: Now.ID["validate_incident_form"],
  name: "Create and Validate Incident",
  description: "Opens a new incident form, sets fields, submits, and validates",
  failOnServerError: true
}, (atf) => {
  atf.form.openNewForm({
    $id: Now.ID["open_new_incident"],
    table: "incident",
    formUI: "standard_ui"
  });

  atf.form.setFieldValue({
    $id: Now.ID["set_fields"],
    table: "incident",
    fieldValues: {
      short_description: "Email server is down"
    },
    formUI: "standard_ui"
  });

  const result = atf.form.submitForm({
    $id: Now.ID["submit_form"],
    assert: "form_submitted_to_server",
    formUI: "standard_ui"
  });

  atf.server.recordValidation({
    $id: Now.ID["validate_record"],
    table: "incident",
    recordId: result.record_id,
    fieldValues: "short_description=Email server is down",
    assert: "record_validated"
  });
});
```

## Example: REST API Test

```javascript
import "@servicenow/sdk/global";
import { Test } from "@servicenow/sdk/core";

Test({
  $id: Now.ID["scaffold_api_test"],
  name: "Scaffold API Test",
  failOnServerError: true
}, (atf) => {
  atf.rest.sendRestRequest({
    $id: Now.ID["send_request"],
    path: "/api/now/fluent/scaffold",
    body: "",
    auth: "basic",
    method: "get",
    queryParameters: { new: "true" },
    headers: {}
  });

  atf.rest.assertStatusCode({
    $id: Now.ID["assert_status"],
    operation: "equals",
    statusCode: 200
  });

  atf.rest.assertResponseJSONPayloadIsValid({
    $id: Now.ID["assert_json_valid"]
  });

  atf.rest.assertJsonResponsePayloadElement({
    $id: Now.ID["assert_result"],
    elementName: "result",
    operation: "equals",
    elementValue: "success"
  });
});
```
