v3.23.0: the start-with-reset suite option

A suite may carry `start_reset` (store schema v2, default false; the editor's
"Start with card reset" checkbox): before the run, the server physically
resets the card and runs the full re-initialization - `_card_reset_reinit`
(extracted from the eSIM profile-switch reinit) does reset_card() + equip +
_apply_equipped_card + the shell registration probe.

The reset runs in _test_suite_run_request before the preset/ADM checks:
- the run's preset must still match the post-reset ICCID (a mismatch refuses
  the start with a re-select hint),
- a require_adm suite re-verifies the ADM for the new card session (the
  latch is per session).

The suite report carries start_reset + reset {ok, iccid, ms}; the PWA shows
"started with a card reset" in the report header and a RESET badge in the
suite list.  Mid-run resets stay fatal.

Documented caveat: the init drain consumes the card's initial proactive
commands, so a script cannot catch the first command after the reset (they
stay in the proactive log).

Docs: test-scripts.md, api.md, help EN/RU, repo AGENTS.
Tests: 843 frontend + 827 Python; sw cache simple-v413.
This commit is contained in:
2026-10-07 09:22:24 +03:00
parent eee2888a2b
commit 6bea57599c
12 changed files with 277 additions and 48 deletions
+31 -1
View File
@@ -1390,6 +1390,7 @@
<input id="test-suite-name" oninput="testSuiteNameChanged()" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
</div>
<label class="flex items-center gap-1 text-sm text-gray-700 dark:text-slate-300"><input id="test-suite-adm" type="checkbox" onchange="testSuiteAdmChanged()"> <span data-l10n="ADM required">ADM required</span></label>
<label class="flex items-center gap-1 text-sm text-gray-700 dark:text-slate-300" data-l10n-title="Reset and fully re-initialize the card (ICCID, network state, TERMINAL PROFILE, drain) before the suite starts. The preset must still match the card's ICCID after the reset; the ADM is verified again for the new card session. The card's initial proactive commands are consumed by the initialization, so a script cannot catch the first command after the reset (they stay in the proactive log). A reset later in the run still stops the suite."><input id="test-suite-reset" type="checkbox" onchange="testSuiteResetChanged()"> <span data-l10n="Start with card reset">Start with card reset</span></label>
<button id="test-suite-run-btn" data-needs="card" onclick="testSuiteRunCurrent()" class="px-4 py-1.5 text-sm rounded bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Run suite">Run suite</button>
<button onclick="testSuiteExportCurrent()" class="px-3 py-1.5 text-sm rounded bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-l10n="Export">Export</button>
<button onclick="testSuiteDeleteCurrent()" class="px-3 py-1.5 text-sm rounded bg-gray-200 dark:bg-slate-700 text-red-600 dark:text-red-400 hover:bg-gray-300 dark:hover:bg-slate-600" data-l10n="Delete">Delete</button>
@@ -1903,7 +1904,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.22.8';
const SIMPLE_VERSION = '3.23.0';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== view history (the browser Back/Forward buttons) =====
@@ -7467,6 +7468,7 @@ function testRenderSuites() {
+ '<span class="flex-1 truncate">' + esc(s.name || t('Suite')) + '</span>'
+ '<span class="text-xs text-gray-400">' + n + ' ' + esc(t('scripts')) + '</span>'
+ (s.require_adm ? '<span class="text-xs px-1.5 py-0.5 rounded bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300">ADM</span>' : '')
+ (s.start_reset ? '<span class="text-xs px-1.5 py-0.5 rounded bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300">RESET</span>' : '')
+ '<button data-needs="card" onclick="testSuiteRun(' + i + ')" class="px-2 py-0.5 text-xs rounded bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-40 disabled:cursor-not-allowed">' + esc(t('Run')) + '</button>'
+ '<button onclick="testSuiteOpen(' + i + ')" class="' + btn + '">' + esc(t('Edit')) + '</button>'
+ '<button onclick="testSuiteExport(' + i + ')" class="' + btn + '">' + esc(t('Export')) + '</button>'
@@ -7534,6 +7536,8 @@ function testRenderSuiteHeader() {
if (nameEl && s) nameEl.value = s.name || '';
const adm = document.getElementById('test-suite-adm');
if (adm && s) adm.checked = !!s.require_adm;
const reset = document.getElementById('test-suite-reset');
if (reset && s) reset.checked = !!s.start_reset;
}
function testSuiteNameChanged() {
@@ -7552,6 +7556,14 @@ function testSuiteAdmChanged() {
testSuiteSave(true);
}
function testSuiteResetChanged() {
const s = testCurrentSuite();
const el = document.getElementById('test-suite-reset');
if (!s || !el) return;
s.start_reset = !!el.checked;
testSuiteSave(true);
}
function testSuiteSave(immediate) {
const s = testCurrentSuite();
if (!s || !s.id) return;
@@ -7560,6 +7572,7 @@ function testSuiteSave(immediate) {
try {
const resp = await pysimFetch('/api/test/suites/update', {
id: s.id, suite: {name: s.name || '', require_adm: !!s.require_adm,
start_reset: !!s.start_reset,
scripts: s.scripts || []}});
if (resp.error) throw new Error(resp.error);
// Adopt the stored suite: the server normalises the order (setup
@@ -8569,6 +8582,13 @@ function testRenderReport() {
if (su.wall_ms !== undefined && su.wall_ms !== null) parts.push((su.wall_ms / 1000).toFixed(1) + ' s');
}
if (snap.preset) parts.push(t('Preset') + ': ' + snap.preset);
if (s.start_reset) {
const r = s.reset || {};
let line = t('started with a card reset');
if (r.iccid) line += ' (ICCID ' + r.iccid + ')';
if (r.ms !== undefined && r.ms !== null) line += ' \u00b7 ' + (r.ms / 1000).toFixed(1) + ' s';
parts.push(line);
}
if (s.counters_before && Object.keys(s.counters_before).length) {
const after = s.counters_after || {};
parts.push('SCP80: ' + Object.keys(s.counters_before).map(k =>
@@ -8644,6 +8664,13 @@ function testReportMarkdown(snap) {
const lines = ['# ' + t('Test suite report') + ': ' + (s.name || '')];
if (snap && snap.status) lines.push('', '- ' + t('status') + ': ' + snap.status);
if (snap && snap.preset) lines.push('- ' + t('Preset') + ': ' + snap.preset);
if (s.start_reset) {
const r = s.reset || {};
let line = t('started with a card reset');
if (r.iccid) line += ' (ICCID ' + r.iccid + ')';
if (r.ms !== undefined && r.ms !== null) line += ' \u00b7 ' + (r.ms / 1000).toFixed(1) + ' s';
lines.push('- ' + line);
}
const su = s.summary || {};
lines.push('- ' + t('passed') + ': ' + (su.ok || 0) + ', ' + t('warnings') + ': ' + (su.warning || 0)
+ ', ' + t('failed') + ': ' + (su.error || 0) + ', ' + t('skipped') + ': ' + (su.skipped || 0));
@@ -22285,6 +22312,9 @@ const LANG_RU = {
'Export JSON': 'Экспорт JSON',
'Export Markdown': 'Экспорт Markdown',
'ADM required': 'Требуется ADM',
'Start with card reset': 'Начать со сброса карты',
'started with a card reset': 'запущен со сбросом карты',
'Reset and fully re-initialize the card (ICCID, network state, TERMINAL PROFILE, drain) before the suite starts. The preset must still match the card\'s ICCID after the reset; the ADM is verified again for the new card session. The card\'s initial proactive commands are consumed by the initialization, so a script cannot catch the first command after the reset (they stay in the proactive log). A reset later in the run still stops the suite.': 'Сбросить и полностью переинициализировать карту (ICCID, сетевое состояние, TERMINAL PROFILE, очистка очереди) перед запуском набора. Пресет должен по-прежнему совпадать с ICCID карты после сброса; ADM проверяется заново для новой сессии карты. Начальные proactive-команды карты поглощаются инициализацией, поэтому скрипт не поймает первую команду после сброса (они остаются в журнале proactive). Сброс в середине запуска по-прежнему останавливает набор.',
'A suite runs its scripts in order: setup first, teardown last.': 'Набор выполняет свои скрипты по порядку: сначала setup, затем teardown.',
'No test suites yet - add one.': 'Наборов тестов пока нет — добавьте.',
'No scripts yet - add one.': 'Скриптов пока нет — добавьте.',