diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f586d9..ae64517 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,19 @@ every change carries a descriptive commit message.
## [Unreleased]
+## [3.38.0] — 2026-10-24
+
+### Added
+
+- **Known-file picker in the test-script file actions.** The `UPDATE file` /
+ `READ file` step editor's path field gains the selector the APDU builder's
+ SELECT rows use: the file-manager tree, the custom files and the standard
+ file list (`uicc_files.json`), grouped by root with a filter, filling the
+ path. Only EFs are offered (the action reads/writes a file; a DF/ADF is not
+ a target); the text field stays authoritative, so a name path or a file the
+ lists do not carry can still be typed, and the picker preselects the current
+ path when it is known.
+
## [3.37.0] — 2026-10-23
### Added
diff --git a/docs/test-scripts.md b/docs/test-scripts.md
index 9536e6c..c488823 100644
--- a/docs/test-scripts.md
+++ b/docs/test-scripts.md
@@ -329,7 +329,12 @@ consecutive runs continue correctly.
| `proactive-drain` | `respond`, `first`, `attempts` 1–1000 (default 3), `interval_ms` (default 200), `require` | drain the card's announced proactive commands and confirm it is idle (see below) |
`path` is `/`-separated: `MF` (or `3F00`) or an ADF name/AID first, then FIDs
-or file names - e.g. `MF/7F20/6F07`, `ADF.USIM/EF.TEST`.
+or file names - e.g. `MF/7F20/6F07`, `ADF.USIM/EF.TEST`. The step editor's
+**pick a known file** selector (the same source merge as the APDU builder's
+SELECT picker: the loaded file tree, custom files and the standard
+`uicc_files.json` list, grouped by root, with a filter) fills the path;
+only EFs are listed, and the text field stays authoritative (typing a name
+path or an unlisted file is always possible).
`file-write` sends a direct terminal-side UPDATE BINARY/RECORD - it does **not**
fire the card's EVENT_REMOTE_FILE_UPDATE (that event reports an update done by
diff --git a/frontend/help-ru.html b/frontend/help-ru.html
index b92427c..d99af3f 100644
--- a/frontend/help-ru.html
+++ b/frontend/help-ru.html
@@ -575,7 +575,7 @@
У вкладки четыре представления: список наборов (по умолчанию; Добавить набор / Редактировать / Запустить / Экспорт / Удалить), редактор набора (скрипты набора в порядке выполнения — необязательный setup первым и teardown последним, с пометками ролей; у каждого скрипта Запустить/Редактировать/Переместить/Скопировать/Удалить, политика при ошибкеостановить (по умолчанию) / продолжить и Требуется ADM), редактор скрипта (список шагов; кнопка Старт выполняет только этот скрипт и показывает результат под шагами — он не сохраняется) и отчёт набора (после запуска набора: статус каждого скрипта, таблица шагов с ожидаемым/фактическим, примечание об ошибке и выдержка из журнала запуска; сводка с успешно/предупреждения/ошибки/пропущено, время выполнения, счётчики SCP80 до → после по каждому keyset, пресет и предупреждение о смене сессии карты; экспорт в JSON или Markdown). Запуск набора выполняет его скрипты по порядку в одной сессии карты, без сбросов и без продолжения с места остановки; при ошибке решает, остановится ли набор на этом скрипте или запустится следующий, teardown выполняется всегда при остановке или ошибке (пока карта в ридере), чтобы следующий тест начался с чистого состояния, а сброс карты останавливает набор и отмечается в отчёте. Начать со сброса карты сбрасывает карту и выполняет полную переинициализацию перед запуском набора (скрипты затем работают со свежеинициализированной картой); пресет должен по-прежнему совпадать с ICCID карты, а требуемый ADM проверяется заново для новой сессии карты. Инициализация поглощает начальные proactive-команды карты, поэтому скрипт не поймает первую команду после сброса (они остаются в журнале proactive). Требуется ADM (у набора или скрипта) проверяет ключ ADM из подобранного пресета один раз перед запуском — ошибка отклоняет запуск (проверьте вручную через значок ADM; автоматических повторов нет). Скрипты хранятся в хранилище наборов (~/.pysim-simple-server/test_suites.json, --test-suites); каждый скрипт принадлежит ровно одному набору, и скрипт можно переместить или скопировать в другой набор (экспорт набора — это пакет с набором и его скриптами; старый экспорт только скриптов импортируется в автоматически созданный набор «Imported scripts»).
Прогон можно повторить: поле «Итерации» рядом с Start / Run suite выполняет скрипт или весь набор указанное число раз — это нагрузочный тест. Успешные итерации не сохраняются: в статусе видны только счётчик (итерация k / N) и время, журнал шагов остаётся пустым, а память не растёт при любом числе итераций. Первая итерация, завершившаяся ошибкой, останавливает цикл и сохраняет свой полный отчёт (шаги, неудачные проверки, фрагмент журнала, отчёт по скриптам набора); Остановить завершает работу после текущей итерации, а смена карты между итерациями считается ошибкой (следующая карта никогда не запускается со скриптом другой карты). Число итераций — параметр прогона, сохранённый скрипт/набор не меняется; счётчики SCP80 продолжаются с того значения, которое израсходовала предыдущая итерация.
-
Действия — Envelope (Event Download: сырой тип события + данные, с опциональным переопределением источника — 82 терминал / 83 сеть) или Event (семантические формы: та же форма, что и в диалоге отправки на вкладке «Телефон», для всех смоделированных событий — четыре события, которые собирает сервер (Location status, Access technology change, Network rejection, Data connection status change), передаются полями, которые он проверяет и собирает во время запуска, остальные несут собранный формой hex данных события (показывается под формой, с проверкой одного ENVELOPE); источник device identities берётся из события (например, MT call → сеть) или из его поля источника), Menu Selection (item id 1–255 или текст пункта с точным/частичным совпадением, по умолчанию без учёта регистра — выбор «Учёт регистра» в редакторе шагов включает точное совпадение; текст подбирается по меню карты, которое следует за параметрами установки апплета), UPDATE/READ файла (путь + режим; чтение проверяет данные файла), сырой APDU (без авто-обработчика), SCP80 (C-APDU или готовый защищённый пакет, опциональные переопределения номера keyset (kvn) / TAR / SPI1 / SPI2 и формат команды — компактный или обёрнутый в шаблон Command Scripting TS 102 226 §5.2.1 expanded (AA) / expanded AE 80; ключи и счётчик берутся из пресета карты, подобранного по ICCID подключённой карты — запуск называет пресет в хранилище, поэтому всегда используются его keysets, TAR и счётчик — а сервер сохраняет потреблённый счётчик в пресет по ходу запуска), STATUS (при попытках > 1 опрашивает, пока карта не объявит команду) и Очистка очереди проактивных команд (опрос STATUS и поглощение всех объявленных команд — результат TERMINAL RESPONSE для каждой, опционально другой результат для первой; граница попыток/интервала; опциональная проверка require по поглощённому. Пустой опрос проходит, финальный STATUS должен быть idle, а оставшаяся команда передаётся следующему шагу — используйте в начале, середине или конце скрипта, чтобы убедиться, что карта перестала слать команды; она же поглощает команду, оставшуюся от предыдущего шага (ответившего 91XX)). У каждого действия есть проверка ответа: ожидаемые SW и данные (точно или маска с подстановкой ? — 91??) и проверка PoR для SCP80 (любой / без PoR / por_ok или своя проверка декодированного PoR — статус / SW / данные; транспорт через SEND SHORT MESSAGE работает так же).
+
Действия — Envelope (Event Download: сырой тип события + данные, с опциональным переопределением источника — 82 терминал / 83 сеть) или Event (семантические формы: та же форма, что и в диалоге отправки на вкладке «Телефон», для всех смоделированных событий — четыре события, которые собирает сервер (Location status, Access technology change, Network rejection, Data connection status change), передаются полями, которые он проверяет и собирает во время запуска, остальные несут собранный формой hex данных события (показывается под формой, с проверкой одного ENVELOPE); источник device identities берётся из события (например, MT call → сеть) или из его поля источника), Menu Selection (item id 1–255 или текст пункта с точным/частичным совпадением, по умолчанию без учёта регистра — выбор «Учёт регистра» в редакторе шагов включает точное совпадение; текст подбирается по меню карты, которое следует за параметрами установки апплета), UPDATE/READ файла (путь + режим; путь можно ввести или выбрать из списка известных файлов — дерево файлов, пользовательские файлы и стандартный список, сгруппированные по корню, с фильтром; чтение проверяет данные файла), сырой APDU (без авто-обработчика), SCP80 (C-APDU или готовый защищённый пакет, опциональные переопределения номера keyset (kvn) / TAR / SPI1 / SPI2 и формат команды — компактный или обёрнутый в шаблон Command Scripting TS 102 226 §5.2.1 expanded (AA) / expanded AE 80; ключи и счётчик берутся из пресета карты, подобранного по ICCID подключённой карты — запуск называет пресет в хранилище, поэтому всегда используются его keysets, TAR и счётчик — а сервер сохраняет потреблённый счётчик в пресет по ходу запуска), STATUS (при попытках > 1 опрашивает, пока карта не объявит команду) и Очистка очереди проактивных команд (опрос STATUS и поглощение всех объявленных команд — результат TERMINAL RESPONSE для каждой, опционально другой результат для первой; граница попыток/интервала; опциональная проверка require по поглощённому. Пустой опрос проходит, финальный STATUS должен быть idle, а оставшаяся команда передаётся следующему шагу — используйте в начале, середине или конце скрипта, чтобы убедиться, что карта перестала слать команды; она же поглощает команду, оставшуюся от предыдущего шага (ответившего 91XX)). У каждого действия есть проверка ответа: ожидаемые SW и данные (точно или маска с подстановкой ? — 91??) и проверка PoR для SCP80 (любой / без PoR / por_ok или своя проверка декодированного PoR — статус / SW / данные; транспорт через SEND SHORT MESSAGE работает так же).
Ожидания — карта должна объявить проактивную команду на предыдущем шаге (SW 91XX, TS 102 221 §7.4.2.1 / TS 102 223 §6.3); 9000 означает «команды нет»: шаг падает на своём уровне ошибок — Ошибка останавливает скрипт, Предупреждение продолжает (терпимая форма «поглотить команду, если она есть») — а действие STATUS с попытками > 1 опрашивает, когда апплет выдаёт команду при опросе. Проверяются тип команды, квалификатор и содержимое: текст / пункт / raw, проверка Alpha (идентификатор Alpha команды, тег 05/85 — отличается от текстовой строки 8D), проверка SMS TPDU (submit-TPDU команды SEND SHORT MESSAGE: получатель / PID / DCS / UDL / данные — для собственного SMS апплета), проверка PoR (SEND SHORT MESSAGE несёт PoR, когда пакет запросил PoR-in-submit, бит SPI2 20: статус ответа, SW R-APDU и данные ответа; перед этим должен выполниться шаг SCP80 — SW R-APDU находится внутри полезной нагрузки ответа: собственный TAR апплета возвращает данные приложения, поэтому проверяйте данные, а SW оставляйте пустым; отклонённый ENVELOPE (62XX/63XX) может вовсе не нести пакет ответа — для такого шага оставляйте проверку PoR «любой» или ожидайте «без PoR») и проверка Files (File List команды REFRESH, например квалификатор 00 = NAA initialization with full file change notification). TERMINAL RESPONSE является частью инструкции: поле результата предлагает полный список TS 102 223 §8.12.0 (каждая строка подписана <hex> - <алиас или имя>, плюс ручной ввод hex; при выбранной команде список сгруппирован по таблице применимости спецификации (сначала возможные для этой команды значения, затем остальные — ничего не скрывается); алиасы соответствуют спецификации — ok 00, modified 07, cancel 10, timeout 12, refused 22, not_understood 32, …), поле additional information (дополнительная информация общего результата, по умолчанию 00; предлагаются значения §8.12.2 для проблем терминала), идентификатор пункта, текст (+DCS) и дополнительные TLV.
Уровни ошибок — несовпадение помечает шаг как Ошибка (скрипт останавливается) или Предупреждение (продолжает); уровень применяется к любой ошибке шага, включая ожидание без объявленной команды и ошибку карты, возникшую на шаге (например, 6A82 отсутствующего необязательного файла); каждая проверка показывается с ожидаемым и фактическим значением.
Шаблоны — Empty, Обзор меню STK, Апплет через SCP80; редактор шагов — формный (действие Event показывает форму события из симулятора), а набор экспортируется/импортируется пакетом JSON (набор и его скрипты).
The pill has four views: the suite list (default; Add suite / Edit / Run / Export / Delete), the suite editor (the suite's scripts in run order — the optional setup first and the teardown last, marked with role badges; per script Run/Edit/Move/Copy/Delete, the per-member on failure policy stop (default) / continue, and ADM required), the script editor (the step list; its Start runs that single script and shows the result under the steps — not preserved) and the suite report (after a suite run: every script's status, its step table with expected/actual, the failing note and a run-log excerpt; the summary with passed/warnings/failed/skipped, wall time, the SCP80 counters before → after per keyset, the preset and a card-session-changed warning; export as JSON or Markdown). A suite run executes its scripts in order in one card session, without resets and without resume; on failure decides whether the suite stops at that script or runs the next one, the teardown always runs on a stop or a failure (while the card is in the reader) so the next test starts clean, and a card reset stops the suite and is flagged in the report. Start with card reset resets the card and runs the full re-initialization before the suite starts (the scripts then run against a freshly initialized card); the preset must still match the card's ICCID afterwards and a required ADM is verified again for the new card session. The initialization consumes the card's initial proactive commands, so a script cannot catch the first command after the reset (they stay in the proactive log). ADM required (on the suite or a script) verifies the matched preset's ADM key once before the start — a failure refuses the run (verify manually with the ADM badge; nothing is retried automatically). Scripts live in the suite's store (~/.pysim-simple-server/test_suites.json, --test-suites); every script belongs to exactly one suite, and a script can be moved or copied to another suite (a bundle export carries the suite plus its scripts; the old script-only export imports into an auto-created “Imported scripts” suite).
A run can be repeated: the Iterations field next to Start / Run suite runs the script or the whole suite that many times — a load test. Successful iterations are not tracked: the status shows only the counter (iteration k / N) and the timing, the per-step log stays empty, and memory use stays flat however high the count. The first iteration that ends in an error stops the loop and keeps its full report (steps, failed checks, run-log excerpt, suite member report); a manual Stop ends it after the current iteration, and a card change between iterations is reported as an error (the next card is never run against another card’s script). The count is a run option — the saved script/suite is unchanged — and the SCP80 counters continue from what the previous iteration consumed.
-
Actions — Envelope (Event Download: raw event type + data, with an optional source override — 82 terminal / 83 network) or Event (the semantic forms: the same form the Phone tab's send dialog uses, for every modelled event — the four events the server builds (Location status, Access technology change, Network rejection, Data connection status change) travel as fields it validates and builds at run time, every other event carries the form's built event-data hex (shown live under the form, with the one-ENVELOPE check); the device-identities source comes from the event (e.g. MT call → network) or its source field), Menu Selection (item id 1–255, or the item's text with an exact/contains match, case-insensitive by default — the step editor's Match case choice switches to an exact-case match; the text is resolved against the card's menu, which follows the applet's install parameters), UPDATE/READ file (path + mode; reads verify the file data), raw APDU (no auto-handler), SCP80 (C-APDU or a pre-built secured packet, optional keyset number (kvn) / TAR / SPI1 / SPI2 overrides and a command format — compact, or the TS 102 226 §5.2.1 Command Scripting template expanded (AA) / expanded AE 80 around the C-APDU; the keys and the counter come from the card preset matched by the equipped card's ICCID — the run names the stored preset, so it always uses its keysets, TARs and counter — the server persists the consumed counter into it as the run proceeds), STATUS (with attempts > 1 it polls until the card announces a command) and Proactive drain (poll STATUS and consume every announced command — the TERMINAL RESPONSE result for each, optionally a different result for the first; the attempt/interval bound; an optional require assertion over what was drained. An empty poll passes, the final STATUS must be idle, and a still-pending command is handed to the next step — use it at the start, in the middle or at the end of a script to make sure the card has stopped sending; it also consumes a command a previous step left pending (a step that answered 91XX). Every action has a response check: expected SW and data (exact, or a mask with ? nibble wildcards — 91??) plus a PoR check for SCP80 (any / no PoR / por_ok, or a custom assertion of the decoded PoR — status / SW / data; the SEND SHORT MESSAGE transport works the same).
+
Actions — Envelope (Event Download: raw event type + data, with an optional source override — 82 terminal / 83 network) or Event (the semantic forms: the same form the Phone tab's send dialog uses, for every modelled event — the four events the server builds (Location status, Access technology change, Network rejection, Data connection status change) travel as fields it validates and builds at run time, every other event carries the form's built event-data hex (shown live under the form, with the one-ENVELOPE check); the device-identities source comes from the event (e.g. MT call → network) or its source field), Menu Selection (item id 1–255, or the item's text with an exact/contains match, case-insensitive by default — the step editor's Match case choice switches to an exact-case match; the text is resolved against the card's menu, which follows the applet's install parameters), UPDATE/READ file (path + mode; the path can be typed or picked from the known-file list — the file tree, custom files and the standard list, grouped by root with a filter; reads verify the file data), raw APDU (no auto-handler), SCP80 (C-APDU or a pre-built secured packet, optional keyset number (kvn) / TAR / SPI1 / SPI2 overrides and a command format — compact, or the TS 102 226 §5.2.1 Command Scripting template expanded (AA) / expanded AE 80 around the C-APDU; the keys and the counter come from the card preset matched by the equipped card's ICCID — the run names the stored preset, so it always uses its keysets, TARs and counter — the server persists the consumed counter into it as the run proceeds), STATUS (with attempts > 1 it polls until the card announces a command) and Proactive drain (poll STATUS and consume every announced command — the TERMINAL RESPONSE result for each, optionally a different result for the first; the attempt/interval bound; an optional require assertion over what was drained. An empty poll passes, the final STATUS must be idle, and a still-pending command is handed to the next step — use it at the start, in the middle or at the end of a script to make sure the card has stopped sending; it also consumes a command a previous step left pending (a step that answered 91XX). Every action has a response check: expected SW and data (exact, or a mask with ? nibble wildcards — 91??) plus a PoR check for SCP80 (any / no PoR / por_ok, or a custom assertion of the decoded PoR — status / SW / data; the SEND SHORT MESSAGE transport works the same).
Expectations — the card must have announced a proactive command in the previous step (SW 91XX, TS 102 221 §7.4.2.1 / TS 102 223 §6.3); a 9000 means “no command”: the step fails at its fail level — Error stops the script, Warning continues (the tolerant “consume a command if there is one” form) — and a STATUS action with attempts > 1 polls when the applet delivers on poll. The command type, qualifier and contents are verified: text / item / raw checks, an Alpha check (the command's Alpha identifier, tag 05/85 — distinct from the 8D text string), an SMS TPDU check (the submit TPDU of a SEND SHORT MESSAGE: destination / PID / DCS / UDL / user data — for an applet's own SMS), a PoR check (a SEND SHORT MESSAGE carries the PoR when the packet requested PoR-in-submit, SPI2 bit 20: the response status, the R-APDU SW and the response data; make sure an SCP80 step ran first — the R-APDU SW lives inside the response payload: an applet's own TAR returns application data, so check its data and leave the SW empty; a refused ENVELOPE (62XX/63XX) may carry no response packet at all — leave the PoR check at any or expect no PoR for such a step) and a Files check (the File List of a REFRESH, e.g. qualifier 00 = NAA initialization with full file change notification). The scripted TERMINAL RESPONSE is part of the instruction: the result offers the full TS 102 223 §8.12.0 list (each entry labelled <hex> - <alias or name>, plus a manual hex entry; with a command selected the list is grouped by the spec’s applicability table (the command-relevant values first, the rest after — nothing is hidden); the aliases follow the spec — ok 00, modified 07, cancel 10, timeout 12, refused 22, not_understood 32, …), the additional information byte of the general result (default 00; the §8.12.2 terminal-problem values are suggested), the item identifier, the text (+DCS) and extra TLVs.
Fail levels — a mismatch flags the step as Error (terminates the script) or Warning (continues); every step failure honours it, including an expectation that finds no pending command and a card error raised during the step (an absent optional file's 6A82); each check is reported with expected vs actual values.
Templates — Empty, STK menu browsing, Applet via SCP80; the step editor is form-based (the event action renders the Simulator's event form), and a suite exports/imports as a JSON bundle (the suite plus its scripts).
diff --git a/frontend/index.html b/frontend/index.html
index 0f4efbc..25542e7 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1959,7 +1959,7 @@
// ===== Version =====
// Single source of truth for the PWA version: shown in the header and used
// by the server version check in pysimConnect().
-const SIMPLE_VERSION = '3.37.0';
+const SIMPLE_VERSION = '3.38.0';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== view history (the browser Back/Forward buttons) =====
@@ -8344,6 +8344,53 @@ function testFormSelect(id, value, options, onchange) {
return html + '';
}
+// The file actions' path picker: the known EFs (the loaded file-manager
+// tree, the custom files and the standard file list, merged by
+// rfmFileEntries) as full canonical paths, grouped by root. The path field
+// stays authoritative - a pick only fills it (a name path or an unlisted
+// file stays possible).
+function testPathEntries() {
+ return rfmFileEntries().filter(e => e.kind === 'ef');
+}
+
+function testPathOptionsHtml(filter, current) {
+ const f = String(filter || '').toUpperCase();
+ const cur = String(current || '').toUpperCase();
+ const groups = new Map();
+ for (const e of testPathEntries()) {
+ if (f && (e.name + ' ' + e.path).toUpperCase().indexOf(f) < 0) continue;
+ if (!groups.has(e.root)) groups.set(e.root, []);
+ groups.get(e.root).push(e);
+ }
+ let html = '';
+ for (const [root, items] of groups) {
+ html += '';
+ }
+ if (!groups.size) {
+ html += '';
+ }
+ return html;
+}
+
+function testPathPick(sel) {
+ const input = document.getElementById('test-f-path');
+ if (input && sel && sel.value) input.value = sel.value;
+}
+
+function testPathFilterInput(input) {
+ const sel = document.getElementById('test-f-pathpick');
+ const path = document.getElementById('test-f-path');
+ if (sel) sel.innerHTML = testPathOptionsHtml(input ? input.value : '',
+ path ? path.value : '');
+}
+
function testStepRender() {
const modal = document.getElementById('test-step-modal');
const title = document.getElementById('test-step-title');
@@ -8412,6 +8459,13 @@ function testStepRender() {
{v: 'true', l: t('Case-sensitive')}]));
} else if (step.kind === 'file-write' || step.kind === 'file-read') {
html += testFormRow('File path', testFormInput('test-f-path', p.path, 'ADF.USIM/EF.EST or MF/2FE2'));
+ html += testFormRow('\u2026 or pick a known file',
+ '
' +
+ '' +
+ '
');
html += testFormRow('Mode', testFormSelect('test-f-mode', p.mode || 'auto', [
{v: 'auto', l: t('Auto (by file type)')}, {v: 'binary', l: t('Transparent (binary)')}, {v: 'record', l: t('Record')}]));
html += testFormRow('Record number (record mode)', testFormInput('test-f-record', p.record, '1..255'));
@@ -26584,6 +26638,9 @@ const LANG_RU = {
'item id or text is required': 'нужен item id или текст пункта',
'give the item id or the text, not both': 'укажите item id или текст, но не оба',
'… or item text': '… или текст пункта',
+ '… or pick a known file': '… или выберите известный файл',
+ '— pick a known file —': '— выберите известный файл —',
+ 'no matching files': 'нет подходящих файлов',
'menu item text': 'текст пункта меню',
'Match case': 'Учёт регистра',
'Case-insensitive (default)': 'Без учёта регистра (по умолчанию)',
diff --git a/frontend/sw.js b/frontend/sw.js
index 99a5af5..a14376e 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'simple-v477';
+const CACHE = 'simple-v478';
const URLS = [
'index.html',
'help.html',
diff --git a/frontend/tests/testscripts.test.js b/frontend/tests/testscripts.test.js
index fded3ca..b5c3fd2 100644
--- a/frontend/tests/testscripts.test.js
+++ b/frontend/tests/testscripts.test.js
@@ -58,11 +58,25 @@ eval(extractFunc(html, 'testIterationsValue'));
eval(extractFunc(html, 'testCommandOptions'));
globalThis.t = s => s;
globalThis.esc = s => String(s);
+// The file-action picker merges the tree/custom/standard sources in
+// rfmFileEntries(); the editor tests stub a small list (an EF per root plus
+// a DF and an ADF, which the picker must not offer).
+globalThis.rfmFileEntries = () => [
+ {path: 'MF/2FE2', name: 'EF.ICCID', fid: '2FE2', kind: 'ef', root: 'MF', source: 'standard'},
+ {path: 'ADF.USIM/6F7E', name: 'EF.LOCI', fid: '6F7E', kind: 'ef', root: 'ADF.USIM', source: 'standard'},
+ {path: 'ADF.USIM/5F3A', name: 'DF.PHONEBOOK', fid: '5F3A', kind: 'df', root: 'ADF.USIM', source: 'standard'},
+ {path: 'ADF.USIM', name: 'ADF.USIM', fid: '', kind: 'adf', root: 'ADF.USIM', source: 'standard'},
+];
globalThis.testScripts = () => _testScripts || [];
globalThis.testSuites = () => _testSuites || [];
eval(extractFunc(html, 'testFormRow'));
eval(extractFunc(html, 'testFormInput'));
eval(extractFunc(html, 'testFormSelect'));
+// the file actions' known-file picker (v3.38.0)
+eval(extractFunc(html, 'testPathEntries'));
+eval(extractFunc(html, 'testPathOptionsHtml'));
+eval(extractFunc(html, 'testPathPick'));
+eval(extractFunc(html, 'testPathFilterInput'));
// the TERMINAL RESPONSE result tables + the shared result-row helpers
eval(extractBlock('const TR_RESULTS = [', 'const TR_RESULT_ADD_INFO = [').replace(/^const /gm, 'var '));
eval(extractBlock('const TR_RESULT_ADD_INFO = [', '// TS 102 223 v18.3.0 Tables 6.1/6.2').replace(/^const /gm, 'var '));
@@ -461,6 +475,55 @@ test('the menu-select step form renders the case select', () => {
assert.match(els['test-step-body'].innerHTML, /