Файл флоу и селекторы
Флоу — один YAML-файл. Ключи верхнего уровня:
| Ключ | Кратко |
|---|---|
appId |
Приложение по умолчанию — bundle / package id, или URL для web |
commands |
Упорядоченный список шагов (единственный обязательный ключ) |
tags |
Теги для фильтрации --tags / --exclude-tags |
before |
Шаги подготовки или clean; ошибка здесь пропускает commands (onFlowStart — старое имя) |
after |
Шаги уборки — выполняются всегда, что бы ни случилось (onFlowComplete — старое имя) |
inheritHooks |
false отключает before/after, унаследованные от проекта и testspace |
env |
Переменные флоу, используются как ${VAR}; config env:, .env, -e их перекрывают |
params |
Объявленные входы: description, required, default, options; вызывающие передают их через env: в runFlow или -e |
device |
none = чисто API-флоу: устройство не ищется и не поднимается |
db |
Строка подключения по умолчанию для шагов sql — обычно ${DATABASE_URL} |
http |
Общие настройки шагов request: baseUrl, headers (${} на каждом шаге), cookies; http: из config.yaml лежит под ним |
vscode |
Цель — расширение VS Code: testspace, vsix, workspace, settings |
appId: https://staging.example.comtags: [smoke]env: USER: qa@example.combefore: - launchApp: https://staging.example.comcommands: - tapOn: "Log in" - inputText: "${USER}" - assertVisible: "Welcome"after: - takeScreenshot: endСелекторы
Заголовок раздела «Селекторы»Команды с целью-элементом принимают голую строку (точный видимый текст) или
объект-селектор. Матчеры, фильтры и отношения комбинируются; не больше одного
реляционного ключа на селектор, а css отрабатывает первым — остальные
ключи селектора фильтруют то, что он нашёл.
Значения — строки: числа берите в кавычки (text: "2025").
above, below, leftOf и rightOf смотрят на собственную линию якоря:
строку для leftOf/rightOf, колонку для above/below. Поэтому
leftOf подписи — это контрол рядом с этой подписью, а не всё, что левее её
на странице, и подпись следующей строки не отвечает за эту. Совпадения
упорядочены по близости к якорю — в этом же порядке считает index, — так что
{ role: switch, leftOf: "Draft" } — ближайший к Draft переключатель. Если
на линии якоря не стоит вообще ничего, работает прежнее, более широкое правило
(только центр якоря, в порядке обхода дерева), поэтому селектор, написанный
против подписи сбоку, продолжает разрешаться.
В браузере contains читает и сквозь строчные теги: <p>Signed in as <strong>ann@x.test</strong></p> — одно предложение, и contains: "as ann@x"
находит <p>, а фраза, которую целиком держит ребёнок, по-прежнему находит
этого ребёнка. text по-прежнему означает собственный текст элемента.
text и contains читаются дважды. Сначала точно так, как написано во флоу;
и только если на экране так не отвечает ничего, экран читается второй раз со
схлопнутыми пробелами (включая неразрывный) и без учёта регистра — поэтому
"log in" находит LOG IN, а подпись, в которой появился перенос строки,
продолжает совпадать. Точное совпадение всегда важнее, index за пределами
списка не повод ослаблять текст, а шаг, совпавший только вторым чтением, несёт
пометку matched ignoring case/spacing. Второе чтение — для шагов, которые
ищут элемент: assertNotVisible, waitForNotVisible и условие notVisible
читают текст только так, как он написан, потому что экран, который пишет его
иначе, этого текста на себе уже не держит.
of — это то, что делает index проверяемым: он записывает, сколько
элементов совпало на момент написания шага, и ничего не фильтрует.
{ text: "Log In", index: 1, of: 2 } на странице, где теперь совпадает три,
даст пометку 2 matched when this was recorded, 3 match now — the index may
point at the wrong one, а пока число совпадает — промолчит. Индексированный
селектор без of отмечается один раз за прогон. Шаг с css считает так же
и, как и обход дерева, не берёт скрытые элементы ни в счёт, ни в нумерацию, по
которой идёт index. Совпадение по css — обычный узел этого обхода:
отношения, scrollUntilVisible и centerElement читают его как любое
другое, а index считает страницу целиком — сначала главный документ, затем
каждый её фрейм, в одной нумерации.
{ css: ".row", text: "Save" } — это найденные CSS строки, из которых
осталась та, чей текст Save; если после фильтра не осталось ничего, шаг
падает как «не найдено» и называет оба ключа.
Имя, которое достаёт и до контрола, и до подписи с теми же словами, — это
одна вещь, на которую нажимают, а не две. Шаг, который действует на элемент
(tapOn, doubleTapOn, longPressOn, hover, check/uncheck,
selectOption, uploadFile, dragAndDrop, scrollUntilVisible),
разрешается только в контролы; шаг, который читает слово (copyText) или
доказывает, что оно на экране (assertVisible, waitForVisible,
assertNotVisible, условие when:), совпадает со всеми элементами, до
которых достаёт имя. Если контролов осталось несколько — это та же
неоднозначность, что и всегда: первое совпадение, index, отношение. Если
имени не отвечает ни один контрол, слово нажимается всё равно, а шаг несёт
пометку matched only text, nothing to press — её никогда не несёт селектор,
назвавший role, потому что он уже сказал, какой элемент имеет в виду.
Контрол, который носит своё имя на обёрнутом элементе
(<button><span>Save</span></button>, где у самой кнопки текста нет), —
контрол: нажимают слово внутри него, а не подпись рядом. index выключает
правило: число уже выбрало, какое совпадение имелось в виду, и считает все
элементы, включая текстовые узлы.
or даёт селектору до трёх запасных. Каждый — полноценный селектор со своими
index/of; вкладывать альтернативы нельзя. Сначала пробуется основной, и
только на устоявшейся странице — и только если основной не совпал ни с чем —
альтернативы читаются по порядку. Попадание в одну из них проводит шаг и
помечает строку drifted: в заметке будет found by alternative 2 (…) — the
primary selector no longer matches, в JSON-отчёте — поле drifted, в JUnit —
<property name="drifted">, в HTML-отчёте — чип, а итог прогона их считает.
Код возврата не меняется, пока не задан tapstep test --strict-selectors,
который валит прогон, опершийся на запасной селектор. Экспортёры пишут только
основной, а альтернативы называют в комментарии на той же строке.
or принадлежит селектору, а не одной команде: assertNotVisible зелёный,
только когда на экране нет ни основного, ни одной из альтернатив; условие
when: / while: открывается по любой из них, а scrollUntilVisible
останавливается, как только одна из них попала в видимую область.
| Ключ | Кратко |
|---|---|
text |
Точное совпадение с любым именем элемента — собственный текст, accessibility label, текущее значение или плейсхолдер (голая строка-селектор — это text) |
id |
Accessibility id / resource id / DOM id |
testId |
Тестовый хук, проставленный приложением: data-testid/data-test/data-qa/data-cy в вебе, accessibility identifier в нативе — самый стабильный матчер (только на web, vscode) |
role |
Роль в стиле ARIA, обычно вместе с name (нативные классы виджетов ложатся на тот же словарь) — движок называет: button | checkbox | radio | switch | menuitem | tab | option | link | textbox | searchbox | combobox | spinbutton | slider | list | listitem | dialog | navigation |
name |
Доступное имя — label, если он есть, иначе собственный текст; пробелы схлопываются. Используйте вместе с role |
label |
Accessibility label / текст <label> — продолжает совпадать после заполнения поля |
placeholder |
Плейсхолдер / подсказка, видимая пока поле пустое |
value |
Текущее содержимое поля |
contains |
Подстрока любого имени элемента |
regex |
Регулярное выражение по любому имени элемента (без подстановки ${}) |
index |
Взять N-е совпадение (с нуля) |
of |
Сколько элементов совпало на момент записи шага — ничего не фильтрует; прогон отметит, если страница отвечает другим числом |
enabled |
Фильтр по состоянию enabled (true/false) |
checked |
Фильтр по состоянию checked |
focused |
Фильтр по фокусу |
selected |
Фильтр по состоянию selected |
traits |
Подсказки формы: text, long-text, square (только на ios) |
width |
Ожидаемая ширина в px (с tolerance) |
height |
Ожидаемая высота в px (с tolerance) |
tolerance |
Допуск ± px для width / height |
css |
CSS-селектор; остальные ключи сужают то, что он нашёл (только на web, vscode) |
or |
До 3 запасных селекторов, которые пробуются по порядку, когда основной перестал совпадать на устоявшейся странице; шаг проходит, а отчёт помечает строку как drifted |
above |
Отношение: совпадение выше этого селектора, в его колонке |
below |
Отношение: ниже этого селектора, в его колонке |
leftOf |
Отношение: левее этого селектора, в его строке |
rightOf |
Отношение: правее этого селектора, в его строке |
childOf |
Отношение: потомок этого селектора |
containsChild |
Отношение: имеет этот селектор прямым ребёнком |
containsDescendants |
Отношение: содержит все эти селекторы (список) |
commands: - tapOn: text: "Delete" index: 1 of: 3 - assertVisible: contains: "items" below: id: cart-title - tapOn: css: "button[data-testid=pay]" - tapOn: text: "Place order" or: - testId: place-order - role: button rightOf: "Total" - check: role: switch leftOf: "Published" index: 0 - assertVisible: id: card containsDescendants: - text: "Visa" - contains: "4242"Хуки: before и after
Заголовок раздела «Хуки: before и after»В обоих можно писать те же шаги, что и в commands: — runFlow,
request, sql, launchApp, openLink, vscodeCommand, — и все
пошаговые возможности (when, retry, repeat, ${}) работают внутри
без изменений. onFlowStart: / onFlowComplete: — старые имена этих двух
ключей, они всё ещё работают.
before выполняется перед шагом 1. after выполняется после последнего шага
всегда: после упавшего шага, после before, сломавшегося на половине,
после остановки — и каждая строка многошагового after выполняется, даже если
одна из них упала. Падение в before валит флоу отдельным вердиктом —
setup failed at B2: …, — и пронумерованные шаги не выполняются; падение в
after отчитывается рядом с вердиктом как teardown failed at A1: … и не
меняет его. В отчётах и в экране прогона десктопа это блоки Setup и
Teardown со строками B1.. / A1..; пронумерованные шаги остаются
1..N.
Свои before: / after: может объявить config.yaml
проекта — они применяются ко всем флоу в нём — и testspace VS Code
(vscode.testspaces.<name>.before / .after). Они оборачивают флоу: внешние
входят первыми и выходят последними:
project.before → testspace.before → flow.before → шаги → flow.after → testspace.after → project.afterbefore: [] во флоу значит «своих нет», а не «нет вообще»; чтобы отключить
унаследованные, напишите в шапке inheritHooks: false. Именно эта собранная
цепочка попадает в test.beforeEach / test.afterEach спеки, которую пишет
tapstep export playwright.
Хук любого слоя может вызвать хелпер, как и всякий блок. Путь его runFlow:
— и исходник runScript: — разрешается относительно папки того слоя, который
его написал (корня проекта для config.yaml, папки конфига для testspace),
а затем от корня проекта — это правило для всех include (см. Хелперы).
Отсутствующий хелпер ловится до того, как что-либо начнёт выполняться, — и
командой validate, и командой test.
Пресет clean
Заголовок раздела «Пресет clean»before: clean — строка вместо списка — возвращает окно VS Code в известное
состояние перед шагами: vscodeCommand ×
workbench.action.closeAllEditors, workbench.action.closePanel,
workbench.action.closeAuxiliaryBar, notifications.clearAll,
workbench.action.terminal.killAll, workbench.action.closeSidebar. Боковая
панель закрывается последней, потому что иконка в activity bar — переключатель:
нажатие открывает вид, только если панель была скрыта, а в удерживаемом окне
панель переживает прогон, который её открыл. На любой другой платформе clean —
ошибка валидации, которая называет, что писать вместо него (web: openLink и
clearState; мобильные: launchApp с clearState: true).
vscode: { testspace: pd-full }before: cleancommands: - vscodeCommand: { title: "My Ext: Generate Config" } - assertFile: gen/config.yamlafter: - vscodeCommand: workbench.action.closePanelvscodeCommand выполняет команду VS Code напрямую: по id (голое
workbench.action.closeAllEditors или { id, args } — args только с
id) через helper-расширение, которое кладёт рядом драйвер и которое отвечает,
когда команда завершилась; по { title: … } — через палитру команд. Экспорт в
Playwright умеет только печатать заголовки, поэтому id без известного заголовка
палитры остаётся // TODO, а не превращается в команду, делающую другое.
Переменные
Заголовок раздела «Переменные»Любое строковое значение может содержать ${NAME}. Имя ищется по порядку: регистр
копирования (copiedText), переменные флоу (env:, дефолты params:, значения
из copyText, extractTextWithAI, request.extract, JS output.*), затем
окружение процесса; неизвестное имя становится пустой строкой. Значения слоятся —
полный порядок в config.yaml (дефолты params: < env: флоу <
env: конфига < environments.<текущее> < .env < .env.<текущее> <
--env-file < -e). --env-set NAME в этой цепочке не стоит: он выбирает,
каким будет <текущее> у двух слоёв окружения, а выбранное имя — это
${TAPSTEP_ENV}, переменная того же слоя окружения, так что
-e TAPSTEP_ENV=… всё равно её перебьёт. В regex: подстановки нет.
JS-выражение внутри значения env: — EMAIL: "qa+${Date.now()}@example.com"
— вычисляется один раз, до первого шага, и результат запоминается: значение
свежее на каждый прогон и одинаковое во всех шагах, которые его читают. То же
выражение, написанное прямо в команде, вычисляется на каждой команде. env: у
runFlow разрешается так же на входе в блок (один раз на область, включая
вложенные), а сломанное выражение валит прогон с именем ключа.
params: объявляет входы флоу, чтобы вызывающие и десктоп могли их проверить:
params: USER: description: Логин для входа required: true PLAN: default: free options: [free, pro] RETRIES: "3" # сокращение для { default: "3" } — голое число не принимаетсяОтсутствующий required-параметр роняет прогон до первого шага; options
ограничивает значение. Передаются через env: в runFlow, -e NAME=value или
цепочку конфига выше.
Управление потоком
Заголовок раздела «Управление потоком»Три команды задают структуру: runFlow
(вставить хелпер или вложенный блок, опционально под when),
repeat (times или while) и
retry. Блоки свободно вкладываются.
Условия: { visible: <селектор> }, { notVisible: <селектор> } или
{ platform: web | android | ios | vscode | [список] } (while принимает только
формы видимости). Окно VS Code отвечает vscode, а не web, — поэтому блок,
который должен идти и в браузере, и в редакторе, пишется как
{ platform: [web, vscode] }.
Хелперы
Заголовок раздела «Хелперы»Хелпер — файл флоу с именем *.helper.yaml. Устроен как флоу (свои params:/env:),
но в место вызова runFlow вставляются только его commands; appId, tags и
хуки игнорируются. Правила:
runFlow: <путь>принимает только.helper.yaml. Путь ищется сначала в папке вызывающего файла, затем от корня проекта (папки ближайшегоconfig.yaml);\читается как/. Пишите путь от корня —helpers/login.helper.yaml, — и та же строка работает во флоу на любой глубине: шаги, скопированные между тестами, не ломаются. Хелперы могут вызывать хелперы, циклы — ошибка.- То же правило действует для всех файлов, которые называет шаг: исходник
runScript:, схемаrequest:, файлuploadFile:, пакетinstallApp:и хелперы хуковbefore:/after:. tapstep validateзагружает каждый include оттуда, где лежит файл, и по строкам сообщает: хелпера нет по этому пути (и какой файл с таким именем нашёлся в проекте), обязательные параметры не переданы (все, по именам), цикл, а предупреждением — хелпер за пределами проекта.tapstep fix-pathsпереписывает старые пути с../от корня.tapstep testне запускает хелпер напрямую, а прогон по папке их пропускает.- Обязательные
params:хелпера проверяются при загрузке и берутся изenv:вызова, переменных вызывающего флоу или окружения процесса. - В отчёте вызов хелпера — один шаг с вложенными шагами хелпера.
Скрипты
Заголовок раздела «Скрипты»evalScript,
runScript и JS-форма assertTrue
делят один QuickJS-контекст на прогон. Внутри доступны:
- все переменные флоу с именем-идентификатором как глобалы (плюс
copiedText); output— объект; каждый ключ становится${output.<ключ>}для следующих шагов;console.log/info/warn/error— попадают в отчёт шага;json(text),maestro.copiedTextи блокирующийhttp.get/post/put/delete/request(url, { headers, body })с ответом{ status, ok, body }(таймаут 30 с).
Чисто API-флоу
Заголовок раздела «Чисто API-флоу»device: none запускает флоу без поиска и загрузки устройства: разрешены только
request,
sql, скрипты, assertTrue и
управление потоком. См. API-тестирование.
Цель — расширение VS Code
Заголовок раздела «Цель — расширение VS Code»Заголовок vscode: делает флоу тестом расширения VS Code и выбирает VS Code-драйвер:
vscode: testspace: default # именованная запись из config.yaml vscode.testspaces vsix: [dist/my-ext.vsix] # или один путь; ${} подставляется workspace: fixtures/ws settings: editor.minimap.enabled: falseЯвные поля перекрывают поля testspace. Про vscode.testspaces — в
config.yaml.
Заметки
Заголовок раздела «Заметки»- Файл флоу — один YAML-документ;
commands— единственный обязательный ключ. - Неизвестные ключи верхнего уровня молча игнорируются — опечатка вроде
platfrom:не даст ошибки. Подмапыparamsиvscodeстрогие.