@ihyeon/flow-qa 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +3 -1
- package/README.md +3 -1
- package/dist/src/ui/public/app.js +60 -11
- package/dist/src/ui/public/i18n.js +12 -0
- package/dist/src/ui/public/index.html +1 -1
- package/dist/src/ui/public/style.css +7 -0
- package/docs/mobile.md +1 -1
- package/docs/releases.md +20 -0
- package/docs/ui.md +13 -1
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -16,7 +16,7 @@ CLI와 `127.0.0.1` 전용 웹 UI를 제공합니다. UI는 영어로 시작하
|
|
|
16
16
|
Node.js 24.x(24.22.1 이상) 또는 26.12.1 이상이 필요합니다. 브라우저는 첫 웹 실행 전에 설치합니다.
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
npm install -g @ihyeon/flow-qa@0.
|
|
19
|
+
npm install -g @ihyeon/flow-qa@0.8.1
|
|
20
20
|
flow-qa browser install
|
|
21
21
|
flow-qa ui --demo
|
|
22
22
|
```
|
|
@@ -70,6 +70,8 @@ CLI에서는 `flow-qa run cases/task-flow.json --target demo --case TC-001 --cas
|
|
|
70
70
|
|
|
71
71
|
`0.5.0`부터 상세 보고서에서 **실패·오류 케이스 준비**를 누르면 실행 당시와 같은 저장된 케이스를 불러와 `실패`와 `실행 오류`만 선택합니다. 준비 후 케이스와 대상을 다시 검토하고 직접 실행하세요. 케이스·원본 파일이 없거나 바뀌었으면 준비를 중단하며, 실행 대상이 없거나 바뀌었으면 다시 선택해야 합니다. 자세한 동작은 [실패 케이스 준비](docs/ui.md#prepare-failed-cases)를 참고하세요.
|
|
72
72
|
|
|
73
|
+
`0.8.0`부터 **상세 보고서**에서 케이스·오류 내용을 검색하고 상태별로 좁히거나 **실패·오류만** 볼 수 있습니다. 표시 건수와 보고서 전체 케이스 수를 구분하며, 다운로드와 실패·오류 재실행 준비는 보고서 전체 범위를 유지합니다. 자세한 동작은 [보고서 케이스 탐색](docs/ui.md#explore-cases-in-a-report)을 참고하세요.
|
|
74
|
+
|
|
73
75
|
## 실행 결과 비교
|
|
74
76
|
|
|
75
77
|
`0.6.0`부터 **실행 기록**에서 저장된 전체 보고서를 스위트 ID·실행 ID·대상 이름으로 검색하고 실행 상태로 함께 좁힐 수 있습니다. 결과는 50건씩 표시하며 **더 보기**로 이전 기록을 불러옵니다. **실행 기록 새로고침**은 검색·상태 조건을 유지한 채 목록을 갱신합니다. 오래된 보고서도 다운로드와 실패 케이스 준비를 사용할 수 있습니다. `0.7.0`부터 각 기록의 **기준으로 선택**·**현재로 선택**으로 이전 실행끼리 비교할 수 있으며, 검색이나 새로고침 후에도 선택을 유지합니다. 자세한 동작은 [기록 검색·페이지 조회](docs/ui.md#search-run-history)를 참고하세요.
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Flow QA is available as a CLI and a web UI bound to `127.0.0.1`. Provide a targe
|
|
|
16
16
|
Node.js 24.x (24.22.1 or later) or 26.12.1 or later is required. Install Chromium before the first web run:
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
npm install -g @ihyeon/flow-qa@0.
|
|
19
|
+
npm install -g @ihyeon/flow-qa@0.8.1
|
|
20
20
|
flow-qa browser install
|
|
21
21
|
flow-qa ui --demo
|
|
22
22
|
```
|
|
@@ -70,6 +70,8 @@ The CLI supports repeated IDs: `flow-qa run cases/task-flow.json --target demo -
|
|
|
70
70
|
|
|
71
71
|
From `0.5.0`, open a detailed report and choose **Prepare failed/error cases** to load its unchanged saved suite with only `failed` and `error` cases selected. Preparation never starts a run: review the cases and target, then run explicitly. Changed or missing case/source files block preparation; a changed or missing target must be selected again. See [preparing failed cases](docs/ui.md#prepare-failed-cases).
|
|
72
72
|
|
|
73
|
+
From `0.8.0`, **Detailed report** supports case/error-text search, status filters, and **Failed/error only**. Counts distinguish displayed matches from all recorded cases. Report downloads and failed/error preparation retain the full report scope. See [report case exploration](docs/ui.md#explore-cases-in-a-report).
|
|
74
|
+
|
|
73
75
|
## Compare recorded runs
|
|
74
76
|
|
|
75
77
|
From `0.6.0`, **Run history** searches all valid saved reports by suite ID, run ID, or target name, combined with the run-status filter. Results load 50 at a time; use **Load more** for older results and **Refresh history** to update the list while keeping your filters. Older reports support the same downloads and failed-case preparation. From `0.7.0`, choose **Use as baseline** and **Use as current** on any history rows to compare older runs, retaining the pair through searches and refreshes. See [history search and paging](docs/ui.md#search-run-history).
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
const i18n = window.FlowQAI18n;
|
|
5
5
|
const t = i18n.t;
|
|
6
6
|
const names = { scenario: 'nav.scenario', library: 'nav.library', history: 'nav.history', settings: 'nav.settings' };
|
|
7
|
-
const app = { state: null, connected: false, displayTargets: [], draft: null, selectedCaseIds: new Set(), script: '', savedFile: '', savedHash: '', origin: 'none', sourceFlow: '', boundSource: null, stale: false, dirty: false, busy: false, planning: false, planRequest: 0, stopping: false, report: null, reportMarkdown: '', selectedRun: '', rerunOrigin: null, rerunTargetMismatch: false, navigationVersion: 0, comparison: null, comparisonRequest: 0, comparisonBusy: false, comparisonStatus: 'comparison.selectBoth', comparisonPins: { baseline: null, current: null }, comparisonSelectionGeneration: 0, comparisonValidation: null, history: { runs: [], nextCursor: null, pagesLoaded: 0, loaded: false, loading: false, request: 0, debounce: null, kind: null, error: null, updatesAvailable: false, pendingRecentUpdate: false }, view: 'scenario' };
|
|
7
|
+
const app = { state: null, connected: false, displayTargets: [], draft: null, selectedCaseIds: new Set(), script: '', savedFile: '', savedHash: '', origin: 'none', sourceFlow: '', boundSource: null, stale: false, dirty: false, busy: false, planning: false, planRequest: 0, stopping: false, report: null, reportMarkdown: '', selectedRun: '', reportFilter: { search: '', status: 'all' }, rerunOrigin: null, rerunTargetMismatch: false, navigationVersion: 0, comparison: null, comparisonRequest: 0, comparisonBusy: false, comparisonStatus: 'comparison.selectBoth', comparisonPins: { baseline: null, current: null }, comparisonSelectionGeneration: 0, comparisonValidation: null, history: { runs: [], nextCursor: null, pagesLoaded: 0, loaded: false, loading: false, request: 0, debounce: null, kind: null, error: null, updatesAvailable: false, pendingRecentUpdate: false }, view: 'scenario' };
|
|
8
8
|
|
|
9
9
|
function el(tag, className, value) {
|
|
10
10
|
const node = document.createElement(tag);
|
|
@@ -636,7 +636,7 @@
|
|
|
636
636
|
catch (error) { showError(error); }
|
|
637
637
|
finally { app.stopping = false; readiness(); }
|
|
638
638
|
}
|
|
639
|
-
async function loadReport(runId) { await work(async () => { const result = await api(`/api/report?runId=${encodeURIComponent(runId)}`); app.report = result.report; app.reportMarkdown = result.markdown; app.selectedRun = runId; renderReport(); readiness(); $('report-panel').hidden = false; $('report-panel').scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }
|
|
639
|
+
async function loadReport(runId) { await work(async () => { const result = await api(`/api/report?runId=${encodeURIComponent(runId)}`); if (app.selectedRun !== runId) app.reportFilter = { search: '', status: 'all' }; app.report = result.report; app.reportMarkdown = result.markdown; app.selectedRun = runId; renderReport(); readiness(); $('report-panel').hidden = false; $('report-panel').scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }
|
|
640
640
|
async function prepareFailedCases() { await work(async () => {
|
|
641
641
|
const runId = app.selectedRun;
|
|
642
642
|
const report = app.report;
|
|
@@ -671,15 +671,51 @@
|
|
|
671
671
|
download(`${runId}.md`, result.markdown, 'text/markdown');
|
|
672
672
|
} catch (error) { if (runId === app.selectedRun && locale === i18n.locale()) showError(error); }
|
|
673
673
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
674
|
+
const reportStatuses = ['all', 'failed-error', 'passed', 'failed', 'error', 'blocked', 'flaky', 'skipped'];
|
|
675
|
+
function reportCaseMatchesSearch(item, query) {
|
|
676
|
+
if (!query) return true;
|
|
677
|
+
const values = [item.id, item.title, item.reason, ...(item.expected || [])];
|
|
678
|
+
for (const attempt of item.attempts || []) {
|
|
679
|
+
if (attempt.error) values.push(attempt.error.code, attempt.error.message);
|
|
680
|
+
for (const error of attempt.secondaryErrors || []) values.push(error.code, error.message);
|
|
681
|
+
for (const step of attempt.steps || []) {
|
|
682
|
+
values.push(step.label);
|
|
683
|
+
if (step.error) values.push(step.error.code, step.error.message);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return values.some((value) => String(value ?? '').toLowerCase().includes(query));
|
|
687
|
+
}
|
|
688
|
+
function renderReportFilters() {
|
|
689
|
+
const cases = app.report?.cases || [];
|
|
690
|
+
const counts = Object.fromEntries(reportStatuses.map((status) => [status, 0]));
|
|
691
|
+
counts.all = cases.length;
|
|
692
|
+
for (const item of cases) {
|
|
693
|
+
if (Object.hasOwn(counts, item.status)) counts[item.status] += 1;
|
|
694
|
+
if (item.status === 'failed' || item.status === 'error') counts['failed-error'] += 1;
|
|
695
|
+
}
|
|
696
|
+
for (const option of $('report-case-status').options) {
|
|
697
|
+
const label = option.value === 'all' ? t('report.filterAll') : option.value === 'failed-error' ? t('report.filterFailedError') : i18n.status(option.value);
|
|
698
|
+
option.textContent = `${label} (${counts[option.value]})`;
|
|
699
|
+
}
|
|
700
|
+
if ($('report-case-search').value !== app.reportFilter.search) $('report-case-search').value = app.reportFilter.search;
|
|
701
|
+
$('report-case-status').value = app.reportFilter.status;
|
|
702
|
+
$('report-case-reset').disabled = !app.reportFilter.search && app.reportFilter.status === 'all';
|
|
703
|
+
return cases;
|
|
704
|
+
}
|
|
705
|
+
function renderReportCases() {
|
|
706
|
+
if (!app.report) return;
|
|
707
|
+
const cases = renderReportFilters();
|
|
708
|
+
const query = app.reportFilter.search.trim().toLowerCase();
|
|
709
|
+
const status = app.reportFilter.status;
|
|
710
|
+
const shown = cases.filter((item) => (status === 'all' || (status === 'failed-error' ? item.status === 'failed' || item.status === 'error' : item.status === status)) && reportCaseMatchesSearch(item, query));
|
|
711
|
+
$('report-case-count').textContent = t('report.caseCount', { shown: shown.length, total: cases.length });
|
|
712
|
+
const host = $('report-case-list'); replace(host);
|
|
713
|
+
if (!cases.length) { host.append(el('p', 'empty', t('report.empty'))); return; }
|
|
714
|
+
if (!shown.length) { host.append(el('p', 'empty', t('report.noMatches'))); return; }
|
|
715
|
+
for (const item of shown) {
|
|
716
|
+
const attemptCount = (item.attempts || []).length;
|
|
717
|
+
const attemptCountLabel = t(attemptCount === 1 ? 'report.attemptCountSingular' : 'report.attemptCountPlural', { count: attemptCount });
|
|
718
|
+
const section = el('section', 'case-report'); section.append(el('h3', '', `${item.id} · ${item.title} · ${i18n.status(item.status)} · ${attemptCountLabel}`));
|
|
683
719
|
if (item.reason) section.append(el('p', '', t('app.reason', { reason: item.reason })));
|
|
684
720
|
section.append(el('p', '', t('app.expected'))); const expected = el('ul', 'compact-list'); for (const check of item.expected || []) expected.append(el('li', '', check)); section.append(expected);
|
|
685
721
|
for (const [attemptIndex, attempt] of (item.attempts || []).entries()) {
|
|
@@ -702,6 +738,16 @@
|
|
|
702
738
|
host.append(section);
|
|
703
739
|
}
|
|
704
740
|
}
|
|
741
|
+
function renderReport() {
|
|
742
|
+
const report = app.report; if (!report) return;
|
|
743
|
+
$('report-heading').textContent = `${report.runId} · ${i18n.status(report.status)} · ${targetDisplay(report.target)}`;
|
|
744
|
+
const host = $('report-content'); replace(host);
|
|
745
|
+
const meta = el('div', 'report-meta'); for (const value of [t('app.reportStatus', { status: i18n.status(report.status) }), t('app.reportTarget', { target: targetDisplay(report.target) }), t('app.reportStarted', { date: formatDate(report.startedAt) }), t('app.reportFinished', { date: formatDate(report.finishedAt) })]) meta.append(el('span', '', value)); const scope = selectionScope(report.selection); if (scope) meta.append(el('span', 'selection-scope', scope)); host.append(meta);
|
|
746
|
+
if (report.usage) host.append(el('p', 'hint', t('app.usage', { tokens: report.usage.modelTokens ?? t('app.unavailable'), cost: typeof report.usage.estimatedCostUsd === 'number' ? `$${report.usage.estimatedCostUsd.toFixed(4)}` : t('app.unavailable') })));
|
|
747
|
+
if (report.errors?.length) host.append(el('p', 'report-errors', report.errors.join('\n')));
|
|
748
|
+
const caseList = el('div'); caseList.id = 'report-case-list'; host.append(caseList);
|
|
749
|
+
renderReportCases();
|
|
750
|
+
}
|
|
705
751
|
async function saveModel(event) { event.preventDefault(); const form = event.currentTarget; await work(async () => { const values = new FormData(form); const body = { role: form.dataset.role, baseURL: String(values.get('baseURL') || '').trim(), model: String(values.get('model') || '').trim() }; const apiKeyEnv = String(values.get('apiKeyEnv') || '').trim(); if (apiKeyEnv) body.apiKeyEnv = apiKeyEnv; await api('/api/models', body); await refresh(true); noticeKey('app.modelSaved'); }); }
|
|
706
752
|
async function clearModel(role) { await work(async () => { await api('/api/models', { role, clear: true }); await refresh(true); noticeKey('app.modelCleared'); }); }
|
|
707
753
|
function targetFields() { const mobile = $('target-kind').value !== 'web'; for (const [id, required] of [['target-url-field', !mobile], ['target-bundle-field', mobile], ['target-device-field', mobile]]) { const field = $(id); field.hidden = !required; field.querySelector('input').required = required; } }
|
|
@@ -789,6 +835,9 @@
|
|
|
789
835
|
$('report-md').addEventListener('click', downloadReportMarkdown);
|
|
790
836
|
$('report-json').addEventListener('click', () => { if (app.report) download(`${app.selectedRun}.json`, JSON.stringify(app.report, null, 2), 'application/json'); });
|
|
791
837
|
$('report-rerun').addEventListener('click', prepareFailedCases);
|
|
838
|
+
$('report-case-search').addEventListener('input', (event) => { app.reportFilter.search = event.currentTarget.value; renderReportCases(); });
|
|
839
|
+
$('report-case-status').addEventListener('change', (event) => { app.reportFilter.status = event.currentTarget.value; renderReportCases(); });
|
|
840
|
+
$('report-case-reset').addEventListener('click', () => { app.reportFilter = { search: '', status: 'all' }; renderReportCases(); });
|
|
792
841
|
$('target-form').addEventListener('submit', saveTarget);
|
|
793
842
|
$('target-kind').addEventListener('change', targetFields); targetFields();
|
|
794
843
|
$('language-select').addEventListener('change', (event) => i18n.setLocale(event.currentTarget.value));
|
|
@@ -143,6 +143,18 @@
|
|
|
143
143
|
'comparison.error': ['Comparison failed: {message}', '비교 실패: {message}'],
|
|
144
144
|
'comparison.runLabel': ['{date} · {suite} · {shortId} · {target} · {status} · {id}', '{date} · {suite} · {shortId} · {target} · {status} · {id}'],
|
|
145
145
|
'history.report': ['Detailed report', '상세 보고서'],
|
|
146
|
+
'report.searchLabel': ['Search cases', '케이스 검색'],
|
|
147
|
+
'report.searchPlaceholder': ['Search ID, title, expected result, or errors', 'ID, 제목, 기대 결과 또는 오류 검색'],
|
|
148
|
+
'report.statusLabel': ['Case status', '케이스 상태'],
|
|
149
|
+
'report.filterAll': ['All statuses', '전체 상태'],
|
|
150
|
+
'report.filterFailedError': ['Failed/error only', '실패·오류만'],
|
|
151
|
+
'report.reset': ['Reset filters', '필터 초기화'],
|
|
152
|
+
'report.caseCount': ['Showing {shown} of {total} report cases', '보고서 케이스 {total}개 중 {shown}개 표시'],
|
|
153
|
+
'report.attemptCountSingular': ['{count} attempt', '시도 {count}회'],
|
|
154
|
+
'report.attemptCountPlural': ['{count} attempts', '시도 {count}회'],
|
|
155
|
+
'report.empty': ['This report has no cases.', '이 보고서에는 케이스가 없습니다.'],
|
|
156
|
+
'report.noMatches': ['No cases match these filters.', '필터에 맞는 케이스가 없습니다.'],
|
|
157
|
+
'report.filterHint': ['Filters only change the displayed cases. Downloads include all cases, and failed/error preparation uses all recorded failed/error cases regardless of visibility.', '필터는 표시되는 케이스에만 적용됩니다. 다운로드에는 모든 케이스가 포함되고, 실패·오류 준비에는 표시 여부와 관계없이 기록된 모든 실패·오류 케이스가 사용됩니다.'],
|
|
146
158
|
'history.prepareFailed': ['Prepare failed/error cases', '실패·오류 케이스 준비'],
|
|
147
159
|
'history.prepareHint': ['Only failed and error cases are selected. Passed, flaky, blocked, and skipped cases are excluded. Preparation never starts a run.', '실패·오류 케이스만 선택합니다. 통과·간헐 실패·차단·건너뜀 케이스는 제외됩니다. 준비만으로 실행은 시작되지 않습니다.'],
|
|
148
160
|
'history.downloadMarkdown': ['Download Markdown', 'Markdown 다운로드'],
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
<div id="comparison-results" hidden><div id="comparison-warnings"></div><div id="comparison-summary"></div><div class="comparison-filter-row"><label for="comparison-filter" data-i18n="comparison.filter">Show cases</label><select id="comparison-filter" aria-controls="comparison-content"><option value="" data-i18n="comparison.all">All categories</option><option value="newly-failed" data-i18n="comparison.newlyFailed">Newly failed</option><option value="now-passed" data-i18n="comparison.nowPassed">Now passed</option><option value="changed" data-i18n="comparison.changed">Changed status</option><option value="unchanged" data-i18n="comparison.unchanged">Same status</option><option value="added" data-i18n="comparison.added">Added to current</option><option value="missing" data-i18n="comparison.missing">Missing from current</option></select></div><div class="actions comparison-actions"><button type="button" id="comparison-md" class="button" data-i18n="comparison.downloadMarkdown" disabled>Download comparison Markdown</button><button type="button" id="comparison-json" class="button quiet" data-i18n="comparison.downloadJson" disabled>Download comparison JSON</button><button type="button" id="compare-open-baseline" class="button quiet" data-i18n="comparison.openBaseline" disabled>View baseline report</button><button type="button" id="compare-open-current" class="button quiet" data-i18n="comparison.openCurrent" disabled>View current report</button></div><div id="comparison-content"></div></div>
|
|
105
105
|
</div>
|
|
106
106
|
</section>
|
|
107
|
-
<section id="report-panel" class="report-panel" hidden><div class="section-head"><span class="section-number">03</span><div><h2 data-i18n="history.report">Detailed report</h2><p id="report-heading"></p></div></div><div class="section-body"><div class="actions"><button type="button" id="report-md" class="button" data-i18n="history.downloadMarkdown">Download Markdown</button><button type="button" id="report-json" class="button quiet" data-i18n="history.downloadJson">Download JSON</button><button type="button" id="report-rerun" class="button primary" disabled data-i18n="history.prepareFailed">Prepare failed/error cases</button></div><p class="hint" data-i18n="history.prepareHint">Passed, flaky, blocked, and skipped cases are excluded. Preparation does not start a run.</p><p class="hint" data-i18n="history.evidenceHint">The report summarizes results. Open or download screenshots and recordings from each attempt. Evidence files are not included in report downloads.</p><div id="report-content"></div></div></section>
|
|
107
|
+
<section id="report-panel" class="report-panel" hidden><div class="section-head"><span class="section-number">03</span><div><h2 data-i18n="history.report">Detailed report</h2><p id="report-heading"></p></div></div><div class="section-body"><div class="actions"><button type="button" id="report-md" class="button" data-i18n="history.downloadMarkdown">Download Markdown</button><button type="button" id="report-json" class="button quiet" data-i18n="history.downloadJson">Download JSON</button><button type="button" id="report-rerun" class="button primary" disabled data-i18n="history.prepareFailed">Prepare failed/error cases</button></div><p class="hint" data-i18n="history.prepareHint">Passed, flaky, blocked, and skipped cases are excluded. Preparation does not start a run.</p><p class="hint" data-i18n="history.evidenceHint">The report summarizes results. Open or download screenshots and recordings from each attempt. Evidence files are not included in report downloads.</p><div class="report-case-filters"><div><label for="report-case-search" data-i18n="report.searchLabel">Search cases</label><input id="report-case-search" type="search" aria-controls="report-case-list" placeholder="Search ID, title, expected result, or errors" data-i18n-placeholder="report.searchPlaceholder"></div><div><label for="report-case-status" data-i18n="report.statusLabel">Case status</label><select id="report-case-status" aria-controls="report-case-list"><option value="all">All</option><option value="failed-error">Failed/error only</option><option value="passed">passed</option><option value="failed">failed</option><option value="error">error</option><option value="blocked">blocked</option><option value="flaky">flaky</option><option value="skipped">skipped</option></select></div><button type="button" id="report-case-reset" class="button quiet" data-i18n="report.reset">Reset filters</button></div><p id="report-case-count" class="hint" role="status" aria-live="polite"></p><p class="hint" data-i18n="report.filterHint">Filters only change the displayed cases. Downloads include all cases, and failed/error preparation uses all recorded failed/error cases regardless of visibility.</p><div id="report-content"></div></div></section>
|
|
108
108
|
</section>
|
|
109
109
|
<section id="view-settings" class="view" hidden>
|
|
110
110
|
<div class="page-heading"><p class="eyebrow" data-i18n="settings.eyebrow">04 / CONNECTIONS</p><h1 data-i18n="nav.settings">Connections</h1><p data-i18n="settings.intro">Configure run targets and models separately. Key values are read only from server environment variables.</p></div>
|
|
@@ -105,6 +105,11 @@ button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-
|
|
|
105
105
|
.runtime-panel > p { margin: 0; overflow-wrap: anywhere; }
|
|
106
106
|
#case-readiness { margin: 0 0 12px; }
|
|
107
107
|
#report-content { margin-top: 20px; }
|
|
108
|
+
.report-case-filters { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) auto; align-items: end; gap: 12px; margin-top: 24px; }
|
|
109
|
+
.report-case-filters > div { min-width: 0; }
|
|
110
|
+
.report-case-filters label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 750; }
|
|
111
|
+
.report-case-filters input, .report-case-filters select { display: block; width: 100%; min-width: 0; min-height: 44px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-control); background: var(--paper); color: var(--ink); }
|
|
112
|
+
#report-case-count { margin: 12px 0 4px; }
|
|
108
113
|
.blocked-title { margin-top: 21px !important; color: var(--accent-text); }
|
|
109
114
|
.draft-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; margin: calc(var(--space-factor) * 16px) 0; }
|
|
110
115
|
.draft-actions span { color: var(--muted); font-size: 12px; }
|
|
@@ -213,6 +218,8 @@ button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-
|
|
|
213
218
|
.settings-grid { grid-template-columns: 1fr; }
|
|
214
219
|
}
|
|
215
220
|
@media (max-width: 550px) {
|
|
221
|
+
.report-case-filters { grid-template-columns: 1fr; }
|
|
222
|
+
.report-case-filters .button { justify-self: start; }
|
|
216
223
|
.page-heading h1 { font-size: clamp(36px, 10vw, 46px); }
|
|
217
224
|
.page-heading > p:last-child { font-size: 14px; }
|
|
218
225
|
.topline-right { display: none; }
|
package/docs/mobile.md
CHANGED
|
@@ -9,7 +9,7 @@ Choose **Mobile** on the scenario screen, describe the flow, and generate or dow
|
|
|
9
9
|
With the installed CLI:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install -g @ihyeon/flow-qa@0.
|
|
12
|
+
npm install -g @ihyeon/flow-qa@0.8.1
|
|
13
13
|
flow-qa init ./my-qa
|
|
14
14
|
# Write your mobile flow in my-qa/flows/my-flow.md first.
|
|
15
15
|
flow-qa plan flows/my-flow.md --platform mobile --prompt-only --workspace ./my-qa
|
package/docs/releases.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
The alpha entries below are historical release records. They document earlier package versions and checks.
|
|
4
4
|
|
|
5
|
+
## 0.8.1: show recorded attempt counts in report headings
|
|
6
|
+
|
|
7
|
+
Detailed report case headings now show their recorded attempt count, including zero, with English singular/plural and Korean labels. This makes repeated or absent attempts visible before reading the attempt details. The count uses only the saved attempt list; case status, search/filter behavior, evidence links, report exports, and execution remain unchanged.
|
|
8
|
+
|
|
9
|
+
Locally verified on macOS with Node.js 24.22.1 on 2026-11-07 (Asia/Tokyo): 118 tests, type checking, UI syntax/design-system audit, the report-explorer browser smoke, and package checks passed. The existing report smoke now covers zero, one, and two recorded attempts, English singular/plural, Korean labels, and 390px layout alongside report filtering/export/evidence checks. A fresh tarball installation ran seven passing browser cases and verified the attempt count in the installed UI. No model calls, external logins, or native-device runs were used.
|
|
10
|
+
|
|
11
|
+
- Package: [`@ihyeon/flow-qa@0.8.1`](https://www.npmjs.com/package/@ihyeon/flow-qa/v/0.8.1)
|
|
12
|
+
- Release: [GitHub 0.8.1](https://github.com/hyeonse-swing/flow-qa/releases/tag/v0.8.1)
|
|
13
|
+
|
|
14
|
+
## 0.8.0: explore cases and errors within a report
|
|
15
|
+
|
|
16
|
+
Detailed reports now support case-content search and status filters, including **Failed/error only**. Search covers IDs, titles, reasons, expectations, attempt/secondary errors, and step labels/errors. Status choices show counts from the complete open report, while the match count describes displayed cases. Partial-run scope and report-level warnings remain visible; empty reports are distinguished from filters with no matches.
|
|
17
|
+
|
|
18
|
+
Filters persist through navigation, language changes, polling, and reopening the same report. Opening a different report successfully resets them; a failed load preserves the existing view. Downloads and failed/error preparation continue to use every recorded case, and evidence URLs retain original attempt/artifact indices. No report, execution, or API schema changes are introduced. Installation examples now use this release.
|
|
19
|
+
|
|
20
|
+
Locally verified on macOS with Node.js 24.22.1 on 2026-11-06: 118 tests, type checking, UI syntax/design-system audit, all existing UI checks, and the real Chromium demo passed. A dedicated browser smoke covered all six case statuses, case/expectation/error/step search, intersecting filters, complete Markdown/JSON exports, original evidence indices, full failed/error preparation scope, same/new/unreadable report transitions, partial and empty reports, draft/review preservation, English/Korean, and a 390px layout. A fresh tarball installation ran seven passing browser cases and exercised the installed report filter UI and complete JSON download. No model calls, external logins, or native-device runs were used.
|
|
21
|
+
|
|
22
|
+
- Package: [`@ihyeon/flow-qa@0.8.0`](https://www.npmjs.com/package/@ihyeon/flow-qa/v/0.8.0)
|
|
23
|
+
- Release: [GitHub 0.8.0](https://github.com/hyeonse-swing/flow-qa/releases/tag/v0.8.0)
|
|
24
|
+
|
|
5
25
|
## 0.7.0: compare archived runs directly from history
|
|
6
26
|
|
|
7
27
|
History rows now offer **Use as baseline** and **Use as current**, so older runs found by search or paging can be compared. The chosen pair stays available through query/status changes, history refresh, polling, navigation, and language changes. Selected row actions expose their pressed state, and changing a selection or swapping roles clears the old comparison and exports until compared again.
|
package/docs/ui.md
CHANGED
|
@@ -7,7 +7,7 @@ Describe a user flow, review cases, and configure a target when ready to run. We
|
|
|
7
7
|
Node.js 24.x (24.22.1 or later) or 26.12.1 or later is required. Node 25 is unsupported. Upgrade the Node runtime and restart any running UI process; updating Flow QA alone does not patch Node. Install Flow QA and start the web demo:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install -g @ihyeon/flow-qa@0.
|
|
10
|
+
npm install -g @ihyeon/flow-qa@0.8.1
|
|
11
11
|
flow-qa browser install
|
|
12
12
|
flow-qa ui --demo
|
|
13
13
|
```
|
|
@@ -78,6 +78,18 @@ Changing search or status restarts at the first page. The query, status, and loa
|
|
|
78
78
|
|
|
79
79
|
When recent run records change, a single loaded page refreshes automatically. If several pages are loaded, the UI keeps them and shows an update hint. **Refresh history** restarts at the first page with the current filters and also picks up external edits or deletions of older records. Pagination is a live view, not a frozen snapshot: newly added records appear when refreshed, and reports changed externally while paging may move. Invalid, incomplete, or unsafe report files are excluded. Use the row actions to select any listed report for comparison, including older results.
|
|
80
80
|
|
|
81
|
+
### Explore cases in a report
|
|
82
|
+
|
|
83
|
+
From `0.8.1`, each case heading also shows the number of attempts recorded in that report. Zero attempts is shown explicitly. This count does not infer a retry policy or change the recorded case status.
|
|
84
|
+
|
|
85
|
+
From `0.8.0`, open **Run history → Detailed report** and use **Search cases** with **Case status** to narrow the displayed results. Search ignores case and surrounding whitespace, and matches case IDs, titles, reasons, expected results, attempt errors, secondary errors, step labels, and step errors. User-written Korean text is supported. Artifact paths and translated status labels are not search fields.
|
|
86
|
+
|
|
87
|
+
**Failed/error only** shows those two recorded statuses; the other status choices distinguish passed, failed, error, blocked, flaky, and skipped. Status-option counts describe the entire open report, while the displayed count shows matches out of that report's recorded cases. A partial run's omitted cases are not included in those counts; its selected/full-suite scope remains visible separately. Report-level warnings remain visible even when no case matches. **Reset filters** clears both search and status.
|
|
88
|
+
|
|
89
|
+
Filters stay while navigating, changing language, refreshing history/settings, or reopening the same report. Successfully opening another report resets them; a failed report load preserves the current report and filters. Filters reset on page reload. Evidence links still refer to each case's original attempt and artifact.
|
|
90
|
+
|
|
91
|
+
These controls change only displayed cases. Markdown and JSON downloads contain every recorded case, and **Prepare failed/error cases** still prepares every recorded failed/error case from the report, even if hidden by the current filters. Filtering does not alter a saved report, the scenario draft, review confirmation, or execution selection.
|
|
92
|
+
|
|
81
93
|
### Prepare failed cases
|
|
82
94
|
|
|
83
95
|
From `0.5.0`, open **Run history → Detailed report → Prepare failed/error cases**. Only cases whose recorded status is `failed` or `error` are selected; `passed`, `flaky`, `blocked`, and `skipped` are excluded. The button is unavailable when no eligible cases exist or an operation is active. Preparation does not execute tests, call a model, or change files.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ihyeon/flow-qa",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Hyeonse Im",
|
|
6
6
|
"repository": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"check:ui": "node --check src/ui/public/app.js && node --check src/ui/public/display.js && node --check src/ui/public/i18n.js && hds-audit .",
|
|
58
58
|
"demo:serve": "node examples/demo/server.ts",
|
|
59
59
|
"demo:smoke": "node scripts/demo-smoke.ts",
|
|
60
|
-
"smoke:ui": "node scripts/ui-i18n-smoke.mjs && node scripts/ui-comparison-smoke.mjs && node scripts/ui-selection-smoke.mjs && node scripts/ui-rerun-smoke.mjs && node scripts/ui-history-smoke.mjs && node scripts/ui-archive-comparison-smoke.mjs"
|
|
60
|
+
"smoke:ui": "node scripts/ui-i18n-smoke.mjs && node scripts/ui-comparison-smoke.mjs && node scripts/ui-selection-smoke.mjs && node scripts/ui-rerun-smoke.mjs && node scripts/ui-history-smoke.mjs && node scripts/ui-archive-comparison-smoke.mjs && node scripts/ui-report-explorer-smoke.mjs"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@ai-sdk/openai-compatible": "3.0.53",
|