API-тестирование
tapstep не заменяет Postman — он делает API первоклассным участником внутри
флоу. Шаги request: служат для подготовки, очистки и проверок бэкенда и
свободно чередуются с UI-шагами в одном файле: что создали запросом — то
проверили на экране, с общими ${переменными}.
Чисто-API флоу
Заголовок раздела «Чисто-API флоу»Флоу с device: none не резолвит и не бутит устройство — запускается где
угодно и мгновенно:
device: noneenv: { API: "https://api.example.dev" }commands: - request: method: POST url: ${API}/staff auth: { bearer: "${STAFF_TOKEN}" } json: { email: "qa@test.dev", role: staff } extract: { staffId: "$.id" } # JSONPath → ${staffId} assert: status: 201 # код или класс: "2xx" json: { "$.role": staff } - request: url: ${API}/staff/${staffId} retryUntil: { status: "2xx" } # поллинг раз в секунду до успеха timeout: 15000Под device: none разрешены только request, sql, скрипты (evalScript,
runScript), value-ассерты (assertTrue) и control flow (runFlow, retry,
repeat). Инструмент validate_flow MCP-сервера (и write_test чата) ловит
UI-команду заранее; у tapstep test такого линта нет, там шаг упадёт в
рантайме с this flow runs with device: none — UI commands have no device. В
десктопе такие флоу запускаются без выбора устройства; в CLI устройство не
ищется и не бутится — если только не передать --driver или --browser: они
перекрывают device: none и гоняют флоу на этой цели.
Чейнинг API и UI
Заголовок раздела «Чейнинг API и UI»В обычном (девайсном) флоу request: свободно смешивается с UI-шагами:
appId: https://staging.example.devcommands: - request: # arrange: создать через API method: POST url: ${API}/staff auth: { bearer: "${STAFF_TOKEN}" } json: { email: "qa@test.dev" } extract: { staffId: "$.id" } - openLink: "${APP}/staff/${staffId}" # act + assert: проверить в UI - assertVisible: "qa@test.dev"Что умеет request:
Заголовок раздела «Что умеет request:»http: — алиас request:, поля те же.
| Поле | Смысл |
|---|---|
method, url |
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS; GET по умолчанию. - request: <url> — шортхенд для GET. |
headers, query |
Строковые map-ы, с ${}-подстановкой. |
json / body |
Структурный JSON или сырая строка — одно из двух, никогда оба. |
auth |
{ bearer: "${TOKEN}" } или { basic: { user, pass } }. |
extract |
var: $.json.path — значения ответа в ${var} для следующих шагов. Шаг падает, если тело не JSON или путь ничего не нашёл. |
assert |
status (код или "2xx"), равенства json или { contains: "…" }, headers, schema (файл JSON Schema рядом с флоу или инлайн). |
retryUntil |
Той же формы, что assert, но поллит раз в секунду до успеха или timeout — ожидание состояния бэкенда. assert и retryUntil взаимоисключающие. |
timeout |
мс на запрос (по умолчанию 30000); с retryUntil — общий дедлайн поллинга. |
${} подставляется в url, query, headers, auth, json и body — но не
в ожидаемые значения под assert / retryUntil, они сравниваются буквально.
Внутри { … } берите в кавычки каждый JSONPath и ${…} —
extract: { code: "$.items[0].code" }, headers: { X-Id: "${ID}" }. Скобка
без кавычек закрывает мапу, и флоу не разбирается.
После каждого запроса ${response.status} и ${response.body} доступны
следующим шагам. При упавшей проверке в отчёт попадают метод, URL, статус и
срез тела ответа — одинаково в JUnit, Allure и HTML.
Общие настройки запросов (http:)
Заголовок раздела «Общие настройки запросов (http:)»Флоу, который ходит в один API, держит базовый URL, заголовки и сессию в
одном месте — в блоке http: шапки или в таком же блоке config.yaml
проекта — ближайшего вверх от файла флоу, того же, откуда берутся env: и
хуки (блок флоу лежит поверх, ключ за ключом):
device: nonehttp: baseUrl: ${API} # к нему дописывается относительный url headers: X-Sandbox: "${SANDBOX}" Authorization: "Bearer ${token}" # не уходит, пока ${token} не задан cookies: true # по умолчанию: Set-Cookie переноситсяcommands: - request: method: POST url: /login json: { email: "${EMAIL}", password: "${PASSWORD}" } extract: { token: "$.token" } - request: /me # уходят Bearer <token> и cookie сессии - request: url: /issues headers: { X-Sandbox: other } # свой заголовок шага главнееbaseUrl—urlзапроса без схемы (/me,issues?status=done) дописывается к нему; абсолютный url его не использует.headers— уходят с каждым запросом. В значениях работает${}, и подстановка делается на каждом шаге, так что токен, извлечённый посреди флоу, есть во всех запросах после него; заголовок, чьи переменные ещё не заданы, не отправляется. Заголовок шага с тем же именем (регистр не важен) главнее, аauth:шага заменяетAuthorizationпо умолчанию.cookies— cookie-jar флоу: каждыйSet-Cookie, полученный запросом, уходит в следующих запросах так же, как его отправил бы браузер: на тот же хост (Domain=покрывает поддомены; чужой для хоста домен отклоняется), только под своимPath, никогда по голому http, если cookieSecure(кроме localhost), и не после того, как истёкExpires/Max-Age(Max-Age=0,Expiresв прошлом или пустое значение удаляет cookie).falseвыключает.
У хелпера, подключённого через runFlow: login.helper.yaml, может быть свой
блок http:. Он лежит поверх блока флоу только на шагах хелпера — хелпер
входа может ходить на сервер авторизации, пока флоу ходит в API, — а со
следующего шага снова действуют настройки флоу. Хелпер без своего блока
берёт блок флоу.
Из curl и обратно
Заголовок раздела «Из curl и обратно»Запрос, который у вас уже есть строкой curl, перенабирать не нужно:
pbpaste | tapstep convert --from curl # напечатать шаг requesttapstep convert --from curl req.txt -o flows/api.flow.yaml --envtapstep convert --to curl flows/api.flow.yaml # все шаги request обратно в curltapstep convert --to curl flows/api.flow.yaml --step 2 # только второйШаг возвращается с assert: { status: 2xx }, если не передан --no-assert, а
то, что похоже на секрет — bearer-токен, пароль, — выносится в ${VAR};
--env дописывает имена и значения в .env рядом со сценарием (имя, которое
там уже есть, остаётся нетронутым), так что сам сценарий можно коммитить. -o
пишет не шаг, а целый флоу с device: none. URL под http.baseUrl проекта
записывается относительным, а --to curl дописывает относительный url к базе
обратно и выводит заголовки по умолчанию. Флаги, которые есть у curl и
которых нет у шага request, попадают в отчёт, а не теряются молча. Те же две
конверсии — это MCP-инструменты convert_curl и request_to_curl, а в
десктопе импорт есть в редакторе теста.
Проверки базы данных
Заголовок раздела «Проверки базы данных»Шаги sql: выполняют один запрос к Postgres, MySQL или SQLite — та же схема
arrange / act / assert, но слоем ниже. Подготовьте фикстуру до того, как UI её
тронет, проверьте, что UI на самом деле записал, и уберите за собой:
appId: https://staging.example.devdb: ${DATABASE_URL} # значение по умолчанию для всех sql-шаговbefore: - sql: # arrange: фикстура прямо в БД exec: "insert into coupons (code, percent) values ($1, $2)" params: ["QA10", 10] # $1, $2 в Postgres; ? в MySQL/SQLite assert: { affected: 1 }commands: - launchApp: https://staging.example.dev/checkout - inputText: "QA10" - tapOn: "Apply" - sql: # assert: что записал UI query: "select status, total from orders where coupon = $1" params: ["QA10"] readOnly: true # откатывается — не может изменить данные extract: { total: "$[0].total" } assert: rows: 1 first: { status: paid }after: - sql: { exec: "delete from coupons where code = $1", params: ["QA10"] }before: и after: — подготовка и уборка флоу: after выполняется после
последнего шага что бы ни случилось, поэтому уборка выше удалит купон и после
упавшей проверки (onFlowStart / onFlowComplete — старые имена этих двух
ключей, они всё ещё работают). См.
Файл флоу.
assert принимает rows (точное число), minRows, none: true, first
(колонки первой строки), contains (какая-то строка совпала по всем колонкам)
и affected (только для exec). extract выполняет JSONPath по результату —
массив строк для query, { "affected": N } для exec; после шага следующим
доступны ${sql.rows} / ${sql.affected}.
Значения всегда идут через params и никогда не попадают в текст SQL, так что
кавычки и инъекции — забота драйвера, а не ваша. Параметр ${var} несёт
извлечённое значение и связывается как текст, поэтому в Postgres приводите
плейсхолдер к типу колонки (where id = $1::int). Запись разрешена сознательно:
фикстурам она нужна, а ограничителем служит учётная запись подключения — в
сюитах, которые только проверяют, укажите в db: пользователя базы только на
чтение. DSN считается секретом: в отчётах, событиях и логах он выглядит как
scheme://user:***@host/db — и когда написан прямо во флоу, и когда пришёл из
${DATABASE_URL}.
Попробовать шаг, не запуская флоу
Заголовок раздела «Попробовать шаг, не запуская флоу»В десктопе у шага request: и sql: есть кнопка Попробовать — и в строке
шага, и в его форме. Она выполняет ровно этот шаг: переменные разрешаются так
же, как их разрешил бы прогон, — включая env: и значения params: по
умолчанию из шапки самого флоу, даже если шапку правили и ещё не сохранили, —
для окружения, выбранного в шапке, а сам файл флоу не меняется. ${var},
который извлёк бы предыдущий шаг, здесь ещё не существует: попытка знает
переменные проекта, а не прогона.
Попытка по-настоящему отправляет запрос и по-настоящему выполняет запрос к базе:
POST или sql: exec: меняет ту систему, на которую указывает окружение, ровно
как в прогоне.
Результат открывается под шагом: статус и время, заголовки ответа, тело и
каждый ключ assert:/extract: с тем, что он увидел. У шага sql: строки
показаны таблицей, а над ней — их число. Одновременно идёт одна попытка на шаг;
закрытие панели её останавливает.
Из ответа же пишутся и проверки. Нажмите на значение в теле — панель предложит два действия:
- В переменную — добавит
extract: { <имя>: "$.path" }; имя предлагается по ключу, на который вы нажали. Путь — тот, который вычисляет движок, в кавычках, если ключ не обычное слово ($.user['user-name']). - Проверять, что равно — добавит
assert: { json: { "$.path": <значение> } }, причём тип значения тот же, что в ответе: число остаётся числом, строка — строкой.
Нажатие на статус добавляет assert: { status: 200 }. В результате sql:
нажатие на ячейку первой строки добавляет assert: { first: { <колонка>: <значение> } }
или extract от $[0].<колонка>, а нажатие на число строк — assert: { rows: N }
(после exec — assert: { affected: N }). Каждое нажатие пишет один ключ этого
шага; больше в файле ничто не сдвигается.
db у шага sql: — выпадающий список переменных текущего окружения, значение
которых похоже на строку подключения; они показаны как ${NAME}, сама строка
подключения не появляется на экране и не попадает в YAML. Рядом кнопка
Проверить: подключается, спрашивает у сервера версию и закрывает соединение,
а если не может — называет стадию, на которой не вышло: DNS, connect, auth. В
терминале та же проверка — tapstep sql --ping "${DATABASE_URL}".
Сознательно за рамками: нагрузка, моки/стабы, GUI-коллекции. Для E2E-работы привычные ходы Postman — вызвать, извлечь, проверить, подождать — доступны прямо во флоу.