# Script

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

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

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

Окно редактирования узла разделено на две области - окно редактирования кода и окно консольного вывода. Для скрытия консоли нажмите на кнопку <img src="https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-Ll_fYDd2sqAfI_PYFd1%2F-Ll_h1PL8l6PT7lhrK1H%2FTestMace%202019-07-19%2015.42.04.png?alt=media&#x26;token=0b3a10fc-2c8a-40fa-a68c-5a8850f777e9" alt="" data-size="original"> .

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

* <img src="https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-Ll_fYDd2sqAfI_PYFd1%2F-Ll_hOSBQvP4qe3Riv4E%2FTestMace%202019-07-19%2015.43.23%20(1).png?alt=media&#x26;token=68cd8189-e348-46b5-91a8-14eac49912b2" alt="" data-size="original">/ <img src="https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-Ll_fYDd2sqAfI_PYFd1%2F-Ll_hTTunohBFKEZy1Wc%2FTestMace%202019-07-19%2015.44.34%20(1).png?alt=media&#x26;token=75ba2757-b83a-4008-9e31-24722a1a4701" alt="" data-size="original"> - переключение между режимами сохранения результатов выполнения в консоли. <img src="https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-Ll_fYDd2sqAfI_PYFd1%2F-Ll_hOSBQvP4qe3Riv4E%2FTestMace%202019-07-19%2015.43.23%20(1).png?alt=media&#x26;token=68cd8189-e348-46b5-91a8-14eac49912b2" alt="" data-size="original"> - очистка консоли перед каждым запуском скрипта, <img src="https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-Ll_fYDd2sqAfI_PYFd1%2F-Ll_hTTunohBFKEZy1Wc%2FTestMace%202019-07-19%2015.44.34%20(1).png?alt=media&#x26;token=75ba2757-b83a-4008-9e31-24722a1a4701" alt="" data-size="original"> - накопление результатов запуска.
* <img src="https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-Ll_fYDd2sqAfI_PYFd1%2F-Ll_hnFKHfCrfBxWPORk%2FTestMace%202019-07-19%2015.44.14.png?alt=media&#x26;token=67103e9a-79e3-448e-99d6-463f5b9d2a9a" alt="" data-size="original"> - автоматическая прокрутка к последней строке вывода консоли
* <img src="https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-Ll_fYDd2sqAfI_PYFd1%2F-Ll_iGoJ2glWwnW_bhTr%2FTestMace%202019-07-19%2015.43.48.png?alt=media&#x26;token=0d6f2572-c6cb-4092-a6c5-194da486bd1f" 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
```

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0EAHlkmZtuluuck_W%2FScreenshot_4.png?alt=media\&token=b6ea0bfa-a801-4242-8c82-624172f483db)

#### moment.js

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

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0ETQnY_XPrga2xRPK%2FScreenshot_5.png?alt=media\&token=f544c991-dde6-468a-b9a4-f44ae0d1b925)

#### CryptoJS

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

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0Eak46akTPZogvz11%2FScreenshot_6.png?alt=media\&token=00aa3337-d369-46e0-9640-2d7d9b551647)

#### random-js

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

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0ExVPoMTldJQ5YdE5%2FScreenshot_7.png?alt=media\&token=78cc6b64-fc7b-4d8a-9356-d31ee891380b)

#### faker.js

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

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0F9FhT-nl0baS5UYx%2FScreenshot_8.png?alt=media\&token=1ca76381-0486-45c4-8e4b-b67d1083fea9)

#### chai.js

```javascript
const foo = 'bar';

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

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

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0FGgjs00FJqqXQtjO%2FScreenshot_9.png?alt=media\&token=4670ea94-7a63-4c2e-ac7c-984440eecd79)

#### 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);
    }
  });
})
```

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0Ge5Hy1HEuhKHDJ1X%2FScreenshot_10.png?alt=media\&token=29019855-830e-4fce-9cf5-f89168467554)

#### axios

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

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0IIXrpj4NjgXWp9Jl%2FScreenshot_12.png?alt=media\&token=fb4ca8c9-0198-4937-ad17-3ed9bc6ee1a2)

### console.\*

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

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

![](https://1540441421-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lh_FaVh9XfQJ0p1KqZ1%2F-MD08neqs5aFh8TZkEs7%2F-MD0Gynf0rNTjhcb0kV3%2FScreenshot_11.png?alt=media\&token=0d2ec781-7b5c-4573-996d-6967efc4d75c)

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

{% 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#"
}
```
