- TVA-ul de pe DVI poate avea valuta si curs proprii, diferite de ale facturii (rand "protejat": rand_dvi/valuta_proprie pe introdc, sparge_tva_protejat). - Discount financiar pe factura: rand tip_rand='G' (401 = 767), TVA calculat pe baza diminuata, marfa si preturile articolelor pe valoarea integrala. - Factura multi-cota: randurile S, G si T se sparg pe cotele articolelor, cu explicatia TVA din familia coloanei si alinierea T -> S. - Teste noi in utile/Teste/achizitie_import (discount, DVI, cote, e2e). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9uDgDfUQXbh2Neib36CN8
9.3 KiB
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
.prgde test:PUBLIC gcUILog, gcSyncDir;SET SAFETY OFF+SET TALK OFFca PRIMA linie (vezi capcana a);SET PATH/CLASSLIB/PROCEDUREca in aplicatie; incarca ULTIMELEui_harness.prgsi (daca ai dialoguri)mock_amessagebox.prg, cuADDITIVE.- Creeaza dummy-urile minime (
goExecutorcu metode reale,goApppt.ReadIni,poActpt. curs valutar) + cursoarele cerute de Init (RecordSource-urile grilelor). - Instantiaza clasa,
WindowType=0(modeless -Show()pe modal nu returneaza),Show(),WindowState=2. - 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 finalDO HarnessDone WITH 'done'. powershell -File vfp_ui_harness.ps1 -TestPrg <test.prg> -Steps @('pas0',...). PNG-uri inscreenshots\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.