UNPKG

@servicenow/sdk

Version:
335 lines (245 loc) 13.4 kB
--- 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 fluent 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" }); }); ```