# Testare UI VFP headless cu harness + screenshots

Harness reutilizabil pentru teste care afiseaza un formular REAL (cu dependinte dummy, fara
Oracle), executa actiuni si valideaza vizual prin capturi. Valabil pentru toate proiectele VFP
din suita ROA. Alternativa la `testare-vfp-mcp.md` (care porneste programul principal intreg);
capcanele generale de rulare headless (`-A -T`, SAFETY, script agatat) sunt in
`depanare_testare_vfp.md`.

Fisiere in `COMUN\utile\Teste\`: `vfp_ui_harness.ps1` (orchestrator generic, parametrizat),
`_precompile.ps1` (precompilare izolata), `ui_harness.prg` (proceduri de handshake),
`mock_amessagebox.prg` (mock dialog) — infrastructura sta in radacina `Teste\`; suitele stau in
subfoldere pe subiect. Exemplu complet: `achizitie_import\test_import_nota_ui.prg` + `.ps1`.

**REGULA (Marius, 17/07/2026): testele pe aplicatii frontend exerseaza fluxul REAL al
utilizatorului** — formularul/butonul/metoda pe care o apeleaza aplicatia, NU introducerea
directa a randurilor in cursoare/tabele. Popularea directa sare peste validari/filtre/efecte
laterale si da PASS fals (caz real: un articol "adaugat" direct in cursor trecea testul, dar
prin fluxul real nu se adauga deloc). Mock-urile raman permise doar pe INFRASTRUCTURA (Oracle,
dialoguri modale), nu pe pasii fluxului testat.

## Test UI nou in 5 pasi

1. `.prg` de test: `PUBLIC gcUILog, gcSyncDir`; `SET SAFETY OFF` + `SET TALK OFF` ca PRIMA
   linie (vezi capcana a); `SET PATH/CLASSLIB/PROCEDURE` ca in aplicatie; incarca ULTIMELE
   `ui_harness.prg` si (daca ai dialoguri) `mock_amessagebox.prg`, cu `ADDITIVE`.
2. Creeaza dummy-urile minime (`goExecutor` cu metode reale, `goApp` pt. `ReadIni`, `poAct`
   pt. curs valutar) + cursoarele cerute de Init (RecordSource-urile grilelor).
3. Instantiaza clasa, `WindowType=0` (modeless - `Show()` pe modal nu returneaza), `Show()`,
   `WindowState=2`.
4. Pe fiecare pas: fa actiunea, verifica efectul in date (PASS/FAIL cu helper local), apoi
   `DO HarnessStep WITH <n>, '<mesaj>'` (scrie ready_n, asteapta cont_n de la orchestrator sau
   auto-continua dupa ~30s). La final `DO HarnessDone WITH 'done'`.
5. `powershell -File vfp_ui_harness.ps1 -TestPrg <test.prg> -Steps @('pas0',...)`.
   PNG-uri in `screenshots\step_<n>_<eticheta>.png`; log in `<test>_log.txt`.

## Capcane / deblocari (toate confirmate in sesiune)

a. **`SET SAFETY OFF` + `SET TALK OFF` inaintea primului `STRTOFILE`.** Cu SAFETY ON, scrierea
   log-ului existent scoate un dialog modal care blocheaza rularea (proces viu, "instanta
   moarta"). Cu TALK ON, `SUM`/`CALCULATE` isi echo-eaza rezultatele peste formular in
   screenshots.
b. **Lansarea orchestratorului**: prin tool cu timeout implicit (~120s) e omorat inainte de
   final. Ruleaza-l in background + asteapta `done.txt`, sau timeout 300-600s. NU porni VFP
   separat de orchestrator - si-l lanseaza singur.
c. **Asteptarea `ready_0`**: cu `.fxp` cald START apare in ~2s, la rece mult mai mult (zeci de
   classlib-uri). `-ReadyTimeoutSec` generos (implicit 180s); retry NUMAI la "start ratat",
   nu la "start lent". Detectia "a pornit" se face pe mtime-ul log-ului > momentul lansarii
   (un log stale tinut de un vfp9 zombi pacaleste verificarea "log ne-gol") - omoara zombii
   PROPRII (filtrati pe linia de comanda, vezi punctul e) inainte de fiecare lansare.
d. **`vfp9 -A test.prg` deschide INTERMITENT editorul** in loc sa ruleze (log gol). Lanseaza
   `.fxp`-ul PRECOMPILAT. Precompilarea (`COMPILE`+`QUIT`) atarna dupa COMPILE si, rulata in
   ACEEASI sesiune powershell, otraveste lansarile ulterioare - ruleaza-o intr-un proces copil
   izolat (`Start-Process -Wait _precompile.ps1`).
e. **Suitele NU se ruleaza in paralel** intre ele: harness-ul isi omoara instantele `vfp9`
   ramase inainte de fiecare lansare, deci un al doilea test pornit peste primul il ucide.
   Omorarea e INSA filtrata pe linia de comanda (`Win32_Process.CommandLine` care contine
   folderul de teste): instantele `vfp9` straine — sesiunea IDE a utilizatorului sau teste
   dintr-un ALT proiect VFP — nu sunt atinse si nu blocheaza rularea. Nu reintroduce
   `Get-Process vfp9 | Stop-Process -Force` fara filtru.
f. **FARA FURT DE FOCUS (17/07/2026)**: `Graphics.CopyFromScreen` fura focus si se corupe daca
   utilizatorul lucreaza in paralel - nu se mai foloseste. Capturile se fac cu `PrintWindow`
   (user32, P/Invoke) pe `Process.MainWindowHandle`, flag `2` = `PW_RENDERFULLCONTENT`
   (fallback `0` daca iese goala); merge cu fereastra acoperita, dar NU minimizata. Fereastra
   se muta OFF-SCREEN (`SetWindowPos`, `HWND_BOTTOM`, x=-4000, `SWP_NOACTIVATE`) imediat ce
   apare handle-ul si SE RE-IMPINGE la fiecare pas (VFP se reactiveaza singur la
   `Show()`/dialoguri). Consolele powershell copil sunt `-WindowStyle Hidden` (sigur pentru
   consola, NU pentru GUI VFP - ascunsa, `PrintWindow` poate reda gol). Implementat in
   `vfp_ui_harness.ps1` (Take-Screenshot, Push-Offscreen, Wait-MainWindowHandle) si
   `_precompile.ps1`.
g. **Mock de messagebox**: `FUNCTION amessagebox` care returneaza direct valoarea butonului
   (6=Da), incarcat cu `SET PROCEDURE ... ADDITIVE` **PRIMUL**, inaintea fisierelor aplicatiei:
   la nume duplicat de procedura VFP foloseste fisierul cautat PRIMUL (verificat empiric).
   Astfel umbreste `amessagebox` si in apelurile din metodele de clasa.
h. **Cursorii ceruti de Init**: pre-populeaza-i INAINTE de instantiere (Init-ul recreeaza din
   Oracle cursoarele goale, iar recrearea lasa grid-ul fara coloane, eroare 1925). Pune 1 rand
   placeholder inainte, apoi **`ZAP`** dupa `Show()` — NU `DELETE ALL`, care lasa un rand fizic
   sters si pointerul la EOF (cod care face `GO <recno>` da eroare 5, vezi
   `conventie_go_recno.md`).
i. **Cursorii trebuie sa aiba EXACT indecsii creati de fluxul real** inainte de instantiere
   (fluxul real ii creeaza in metoda apelanta, ex. un `INDEX ON ... TAG ord_doc` +
   `SET ORDER TO` inainte de `CREATEOBJECT`). Fara ei cursorul ramane pe ordinea FIZICA, iar
   logica de tip `GO TOP` + `LOCATE FOR ...` alege alt rand decat in productie - rezultat fals,
   uneori in cascada la apeluri repetate. Verifica in codul apelant ce indecsi se creeaza si
   replica-i identic.
j. **Mock-urile de date trebuie sa reproduca structura si semantica REALA a sursei** (view
   Oracle: toate coloanele; relatii intre randuri: toate randurile implicate, nu unul singur cu
   valori "compuse" manual in test). Un mock incomplet lasa campuri mereu NULL sau ascunde
   pasul care se testeaza. Cand un flag global activeaza cod suplimentar (ex. `gl406`), acel
   cod cere mock-uri in plus - verifica ce apeleaza si adauga metodele lipsa din dummy.
   Cu `gl406=.T.` (SAFT), `GetTaxCodeIdPart`/`GetTaxCode` cer: `goApp.ReadIni`/`WriteIni`
   (proprietate simpla nu ajunge, clasa `dummyapp` are nevoie de METODE), `goExecutor.oReset`
   (no-op e suficient), si un cursor `saft_taxtable` real (nu doar mock pe `goExecutor` -
   `update_jtva_coloane` face `USE saft_taxtable` direct pe alias). Fara ele, simptomul e un
   dialog nativ Windows "Open" (cauta `saft_taxtable.dbf`) care blocheaza headless la nesfarsit,
   fara nicio linie noua in log si CPU 0% - vezi `depanare_testare_vfp.md` pentru diagnosticul
   cu `EnumWindows`/`PrintWindow` pe fereastra ascunsa cand simptomul e "ecran gol, fara eroare".
k. **`LOCATE FOR camp == 'literal'` pe camp `C(n)` padded nu gaseste nimic** (`==` e exact) -
   foloseste `ALLTRIM(camp) == 'literal'`.
l. **`CREATEOBJECT`/`ADDPROPERTY` se comporta gresit apelate DINTR-O PROCEDURA in acest runtime
   headless** (obiect returnat ca string de 30 spatii; `ADDPROPERTY` da eroare 11). Din
   programul PRINCIPAL merg. Construieste obiectele-parametru INLINE in main, nu intr-un helper.
   Cauza confirmata intr-un caz concret: linie de cod prea lunga in ACEEASI metoda de clasa -
   vezi `depanare_testare_vfp.md` sectiunea 6. Remediul (constructie inline) ramane valabil
   oricum, indiferent de cauza exacta dintr-un caz punctual.
m. **Fara `SELECT-SQL` pe cursorul legat de grid**: `SELECT ... FROM <cursor> INTO CURSOR` cat
   timp cursorul e RecordSource-ul unui grid viu poate omori procesul vfp9 silentios (fara
   eroare catchabila, fara semafor). Foloseste xBase nativ (`COUNT FOR ... TO`, `CALCULATE`,
   `LOCATE`), care nu comuta zona de lucru a grid-ului.
n. **Wrapper-ul `.ps1` trebuie sa paseze `-SyncDir` daca `.prg`-ul isi seteaza propriul
   `gcSyncDir`** - altfel testul scrie semafoarele intr-un folder si `vfp_ui_harness.ps1` le
   asteapta in cel implicit (`uisync\`), deadlock pana la timeout. Simptom identic cu "test
   agatat", dar FARA `.ERR` si cu CPU ~0 la procesul vfp9 (nu un dialog modal - procesul chiar
   asteapta un fisier care nu vine). Timeout minim: ~30s x numarul de pasi din `-Steps`
   (auto-continue per checkpoint din `HarnessWaitContinue`), plus marja de pornire.
o. **Formularul resincronizeaza singur dupa o alegere din grid** (`lSyncPending` + `tmrSync`,
   ex. `do_modifica_explicatie_tva` din `ointroduceri.vcx`): starea de imediat dupa actiune NU e
   observabila - `DOEVENTS FORCE` lasa timer-ul sa porneasca, iar resincronizarea rescrie ce
   tocmai s-a aplicat (la explicatia TVA: randul S revine la familia documentului si randurile T
   se realiniaza dupa el). Scrie assert-urile pe starea de DUPA resincronizare si alege un efect
   pe care resincronizarea NU il repara (acolo: `scc`-ul randului T, atins doar cand se schimba
   explicatia). Altfel testul pica fara sa fie ceva gresit in cod.
