# Script

Узел, выполняющий сценарии, написанные на JavaScript. Script будет полезен для решения разных задач:

* Реализация сложных тестов над результатами одного или нескольких других узлов
* Генерация тестовых данных
* Преобразование переменных других узлов
* Выполнение операций для приведения тестируемой системы в заданное состояние (set\_up, tear\_down)
* Отладка и доступ к состоянию всех узлов проекта

## Редактирование

Окно редактирования узла разделено на две области - окно редактирования кода и окно консольного вывода. Для скрытия консоли нажмите на кнопку <img src="/files/-Ll_h1PL8l6PT7lhrK1H" alt="" data-size="original"> .

Над окном консольного вывода расположена панель инструментов для управления поведением консоли:

* <img src="/files/-Ll_hOSBQvP4qe3Riv4E" alt="" data-size="original">/ <img src="/files/-Ll_hTTunohBFKEZy1Wc" alt="" data-size="original"> - переключение между режимами сохранения результатов выполнения в консоли. <img src="/files/-Ll_hOSBQvP4qe3Riv4E" alt="" data-size="original"> - очистка консоли перед каждым запуском скрипта, <img src="/files/-Ll_hTTunohBFKEZy1Wc" alt="" data-size="original"> - накопление результатов запуска.
* <img src="/files/-Ll_hnFKHfCrfBxWPORk" alt="" data-size="original"> - автоматическая прокрутка к последней строке вывода консоли
* <img src="/files/-Ll_iGoJ2glWwnW_bhTr" alt="" data-size="original"> - очистить текущий вывод консоли

## Запуск

Скрипт начинает выполнение при нажатии на кнопку `RUN`. Узел заканчивает свое выполнение после исполнения всех строки кода и после завершения всех асинхронных задач (например, `setTimeout`). Скрипт считается выполненным успешно при выполнении следующих условий:

* В коде не выявлено синтаксических ошибок
* При выполнении все выброшенные исключения обработаны
* Выполнение заняло не более 30 секунд (по истечении этого времени скрипт будет прерван)

{% hint style="success" %}
Вызов скрипта обернут в функцию, поэтому для того, чтобы прервать выполнение без ошибок воспользуйтесь инструкцией возврата: `return;`
{% endhint %}

{% hint style="danger" %}
Чтобы прервать выполнения скрипта с ошибкой, воспользуйтесь выбросом любого исключения: `throw new Error('Something went wrong');`
{% endhint %}

## Библиотеки

Запуск осуществляется в виртуальном окружении node.js. Пользователю доступно некоторые модули из node.js, а также все встроенные возможности JavaScript, поддерживаемые движком V8.&#x20;

{% hint style="info" %}
Осуществляется поддержка стандарта ECMAScript 6
{% endhint %}

### Доступные модули из node.js

* [fs](https://nodejs.org/docs/latest-v10.x/api/fs.html) - работа с файловой системой

### Доступные сторонние модули

* [lodash](https://lodash.com/) - библиотека со множеством утилитарных алгоритмов
* [moment.js](https://momentjs.com/) - библиотека для работы с датами
* [CryptoJS](https://cryptojs.gitbook.io/docs/) - библиотека реализующая множество криптографических алгоритмов&#x20;
* [random-js](https://github.com/ckknight/random-js) - библиотека для генерации математически корректных случайных чисел
* [faker.js](https://github.com/marak/Faker.js/) - библиотека для генерации случайных данных для свойств различных сущностей
* [chai.js](https://www.chaijs.com/) - библиотека предоставляющая комфортные интерфейсы для проверки логических утверждений. &#x20;
* [request](https://github.com/request/request) - библиотека предоставляющая мощный HTTP-клиент.
* [axios](https://github.com/axios/axios) - библиотека для отправки HTTP-запросов

## Контекст выполнения

Ниже приведены объекты и функции глобальной области видимости скрипта.

### Доступ к сторонним модулям

Все описанные модули автоматически подключаются к контексту выполнения и доступны в глобальной области видимости.&#x20;

#### lodash

```javascript
_.sum([1, 2, 3, 4]) // 10
```

![](/files/-MD0EAHlkmZtuluuck_W)

#### moment.js

```javascript
const now = moment(new Date()).format();
```

![](/files/-MD0ETQnY_XPrga2xRPK)

#### CryptoJS

```javascript
const hash = crypto.MD5('Message');
```

![](/files/-MD0Eak46akTPZogvz11)

#### random-js

```javascript
const randomEngine = new random.Random();
const shuffledArray = randomEngine.shuffle([1,2,3,4,5]);
```

![](/files/-MD0ExVPoMTldJQ5YdE5)

#### faker.js

```javascript
const person = { 
    'name': faker.name.findName(),
    'email': faker.internet.email()
};
```

![](/files/-MD0F9FhT-nl0baS5UYx)

#### chai.js

```javascript
const foo = 'bar';

// success
assert.equal(foo, 'bar');
expect(foo).to.equal('bar');

// failure
assert.equal(1, 0);
```

![](/files/-MD0FGgjs00FJqqXQtjO)

#### request

Библиотека `request` не предоставляет интерфейса для работы с async/await. Одно из возможных решений это обернуть вызов `request` в объект [`Promise`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise)

```javascript
await new Promise((resolve, reject) => {
  request('https://docs-ru.testmace.com', (error, response, body) => {
    try {
      assert.equal(error, null);
      assert.equal(response.statusCode, 206);
      assert.notEqual(body, null);
      resolve();
    } catch(e) {
      reject(e);
    }
  });
})
```

![](/files/-MD0Ge5Hy1HEuhKHDJ1X)

#### axios

```javascript
await axios.get('https://api.ipify.org?format=json')
```

![](/files/-MD0IIXrpj4NjgXWp9Jl)

### console.\*

Методы для вывода данных в консоль: log, info, warn, error, debug, exception

Сигнатура методов совпадает с их стандартными версиями. Каждый тип события в консоли окрашивается в свой цвет. Каждая строка сопровождается указателем на строку и столбец, из которой произошел вызов функции вывода. События типа exception отображаются вместе со стеком вызовов внутри скрипта.

![](/files/-MD0Gynf0rNTjhcb0kV3)

### Асинхронный код

{% hint style="warning" %}
В предыдущих версиях TestMace пытался автоматически определить, когда скрипт завершал свою работу. В новых версиях эта возможность (вкупе с функцией finish()) удалена.
{% endhint %}

Скрипт может содержать асинхронные вызовы (например, Promise, setTimeout, addEventListener, колбэки при вызове встроенных модулей и пр.). Для работы с асинхронными операции JavaSсript предоставляет удобный механизм, называемый [async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await). TestMace полностью поддерживает работу с async/await и полагается на него при выполнении асинхронных операций, чтобы определить, завершился ли скрипт или нет. К примеру, рассмотрим следующий код:

```javascript
console.log('1');
setTimeout(() => console.log('2'), 100);
console.log('3');
```

В консоли будет выведено

```javascript
1
3
```

Для того, чтобы исправить данное поведение, нужно явно указать, что данная операция асинхронная, используя ключевое слово `await`. Для удобства мы добавили функцию `delay`, которая позволяет поставить выполнение скрипта "на паузу" и совместима с async/await:

```javascript
console.log('1');
await delay(100);
console.log('2');
console.log('3');
```

В данном случае вывод будет:

```javascript
1
2
3
```

Для получения дальнейшей информации о механизме async/await рекомендуем обратиться к [документации](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await). Работу с async/await поддерживают следующие модули:

* `fs.promises`
* `axios`

В заключении отметим, что на работу скрипта отводится максимум 30 секунд, по истечению которых скрипт завершится с ошибкой.

### Навигация по проекту

В глобальной области видимости доступен объект для доступа к проекту и текущему Script узлу - `tm`.&#x20;

#### tm

* `currentNode: nodeAPI` - интерфейс текущего Script узла&#x20;
* `project: nodeAPI` - интерфейс узла проекта
* `env: envAPI` - интерфейс для доступа к переменным окружения проекта
* `cookies: cookie[]` - список установленных в проекте cookies
* `systemVars: object` - объект, содержащий переменные окружения системы

#### nodeAPI

* `parent: nodeAPI` - возвращает интерфейс для родительского узла. Для узла проекта значение будет null
* `name: string` - имя данного узла
* `type: string` - тип данного узла.&#x20;
* `path: string` - путь до данного узла, относительно корня проекта.
* `children: nodeAPI[]` - список интерфейсов дочерних узлов
* `findChild(name: string): nodeAPI` - поиск дочернего узла по его \`name\`. Если узел с таким именем не найдет, вернется null
* `next: nodeAPI` - интерфейс следующего по порядку узла в группе. Если текущий узел является последним, то вернется null
* `prev: nodeAPI` - интерфейс предыдущего по порядку узла в группе. Если текущий узел является первым в группе, то вернется null
* `nextNodes: nodeAPI[]`  - список всех узлов в группе следующих за текущим. Если текущий узел является последним, то вернется пустой список
* `prevNodes: nodeAPI[]` - список всех узлов в группе предшествующих текущему. Если текущий узел является первым по порядку, то вернется пустой список
* `vars: object` - объект, содержащий все статические переменные данного узла
* `dynamicVars: object` - объект, содержащий все динамические переменные данного узла.
* `setDynamicVar(name: string, value: any): void` - метод устанавливает динамическую переменную \`name\` cо значением \`value\` для данного узла.

#### requestNodeAPI

Узел типа `RequestStep` обладает расширенным интерфейсом.

* `request: object` - объект содержит настройки запроса узла.
* `response: object` - объект содержит результаты последнего выполнения запроса

#### envAPI

* `active: string` - имя активного окружения
* `vars: object` - объект содержит переменные текущего окружения

## Примеры

### Рекурсивный обход потомков узла

Данный пример демонстрирует как пробросить данные всем потомкам текущего узла.

```javascript
const current = tm.currentNode;
const parent = current.parent;
if (!parent) {
  console.warn(`Parent of ${current.path} not found`);
  return;
}

const value = parent.vars['ID'];
if (!value) {
  console.warn(`Node ${parent.path} hasn't have value for ID`);
  return;
}
console.log(`Parent ID = ${value}`);

const setIDToNode = (node) => {
  node.setDynamicVar('ID', value);
};

const traverseDescendants = (node, func, depth) => {
  node.children.forEach((child) => {
    func(node);
    
    indent = '\t'.repeat(depth);
    console.debug(
      `${indent}${child.path}`,
      `${indent}Value: ${child.dynamicVars['ID']}`
    );
    
    traverseDescendants(child, func, depth+1);
  });
};

traverseDescendants(parent, setIDToNode, 0);
```

### Поиск узлов по имени

В данном примере демонстрируется как можно найти дочерний узел по его имени при помощи метода `findChild`

```javascript
const current = tm.currentNode;
const scriptNode = current.parent.findChild(current.name);
assert.equal(current, scriptNode);
```

### Генерация и сохранение данных в переменные

В данном примере мы генерируем случайную строку-идентификатор при помощи библиотеки `faker` и устанавливаем ее следующему узлу как динамическую переменную `UUID`. После выполнения скрипта из следующего узла можно сослаться на данную переменную в URL, теле запроса и т.п.&#x20;

```javascript
const current = tm.currentNode;
const uuid = faker.random.uuid();
const anotherNode = current.next;
anotherNode.setDynamicVar('UUID', uuid);
```

## Файловое представление

```javascript
{
  "type": "object",
  "properties": {
    "type": {
      "description": "Type of Script node",
      "const": "Script",
      "type": "string"
    },
    "script": {
      "description": "Javascript code",
      "type": "string"
    },
    "children": {
      "description": "List of children names",
      "type": "array",
      "items": {
        "type": "string"
      },
      "default": []
    },
    "variables": {
      "$ref": "#/definitions/NodeVariables",
      "description": "Node variables dictionary"
    },
    "name": {
      "description": "Node name",
      "type": "string"
    }
  },
  "required": [
    "children",
    "name",
    "script",
    "type",
    "variables"
  ],
  "definitions": {
    "NodeVariables": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs-ru.testmace.com/node-types/script.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
