Files
comun/docs/testare-ui-vfp.md
Marius Mutu 53d9f1c64a Achizitie import unificata (ROAGEST): ointroduceri, teste UI, documentatie
- import_nota / import_adauga_factura: flux unificat note contabile + articole,
  sincronizare automata, total factura automat, TVA impartit pe conturile
  creditoare ale notelor (rundele 1-31)
- runda 32: totalurile import_nota pe _label/_textbox din _baza.vcx (Arial 10),
  fara containerele clb_tx_simplu si fara bifa "Recalculeaza TVA" (recalcul
  mereu automat); "Sincronizeaza facturile secundare" mutata sub butoane
- utile/Teste: harness UI VFP headless (vfp_ui_harness.ps1, ui_harness.prg,
  precompilare, mock-uri) in radacina; suitele e2e pentru achizitia din import
  grupate in utile/Teste/achizitie_import/
- docs: flux editare text vcx/scx, testare-ui-vfp si testare-vfp-mcp (mutate
  din ROAGEST, general valabile), inventar comun, orchestrare subagenti,
  conventie GO recno, depanare testare VFP

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DGibQKa5uK4P2TNSFFQNh
2026-07-20 00:50:18 +03:00

11 KiB

Testare UI VFP headless cu harness + screenshots

Harness reutilizabil pentru teste care afiseaza un formular REAL (fara Oracle/UI complet), executa actiuni si valideaza vizual prin capturi de ecran. Valabil pentru toate proiectele VFP din suita ROA; exemplele sunt pe ROAGEST. Alternativa la testare-vfp-mcp.md (care ruleaza programul principal intreg + windows-mcp): aici instantiem o singura clasa cu dependinte dummy, deci pornire rapida si izolata.

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

REGULA (Marius, 17/07/2026): testele pe aplicatii frontend exerseaza fluxul REAL al utilizatorului — formularul/butonul/metoda pe care o apeleaza aplicatia (ex. "Adauga repere", do_adauga_factura prin dialogul real), NU introducerea directa a randurilor in cursoare/tabele (ex. Append in rul_temp/introdc). Popularea directa sare peste validari/filtre/efecte laterale si da PASS fals (caz real: articol cont 212 "adaugat" direct in rul_temp 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 TALK OFF (obligatoriu, vezi cap. c); SET PATH/CLASSLIB/PROCEDURE ca in aplicatie; incarca ULTIMELE SET PROCEDURE TO ...\ui_harness.prg ADDITIVE si (daca ai dialoguri) ...\mock_amessagebox.prg ADDITIVE.
  2. Creeaza dummy-urile minime (goExecutor, goApp, etc.) + cursoarele pe care le cere Init-ul clasei (RecordSource-urile grilelor).
  3. Instantiaza clasa, WindowType=0, Show(), WindowState=2.
  4. Pe fiecare pas: fa actiunea, verifica efectul in date (loghezi PASS/FAIL cu un 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. Ruleaza: powershell -File vfp_ui_harness.ps1 -TestPrg <test.prg> -Steps @('pas0',...). PNG-urile ies in screenshots\step_<n>_<eticheta>.png; log in <test>_log.txt.

Capcane / deblocari (toate confirmate in sesiune)

a. Lansarea orchestratorului: prin tool cu timeout implicit (~120s) e omorat inainte de finalul pasilor. Ruleaza-l in background (run_in_background) + asteapta done.txt, sau timeout mare (300-600s). NU porni VFP separat de orchestrator - si-l lanseaza singur. b. Asteptarea ready_0 prea scurta omoara VFP exact inainte sa scrie semaforul. Cu .fxp cald START apare in ~2s, la rece mult mai mult (zeci de classlib-uri). Foloseste -ReadyTimeoutSec generos (implicit 180s). Retry NUMAI la "start ratat", nu la "start lent". c. SET SAFETY OFF + SET TALK OFF ca PRIMUL lucru, inainte de primul STRTOFILE. Cu SAFETY ON, prima scriere a log-ului (daca fisierul exista deja) scoate dialogul modal "...already exists, overwrite it?" care BLOCHEAZA rularea headless (VFP pare "instanta moarta"

  • proces viu, blocat pe dialog). Cu TALK ON, SUM/CALCULATE (ex. din recalculeaza) isi echo-eaza rezultatele pe desktop, ca text parazit peste formular in screenshots. Harness-ul are HarnessInit/SET SAFETY OFF defensiv in procedurile de scriere, dar testul trebuie sa faca SET SAFETY OFF oricum INAINTE de scrierea lui proprie de "START". d. vfp9 -A test.prg deschide INTERMITENT editorul in loc sa ruleze (log ramane gol). Lanseaza .fxp-ul PRECOMPILAT (vfp9 -A test.fxp) - se executa mereu. e. Precompilarea (COMPILE+QUIT) atarna dupa COMPILE; rulata in ACEEASI sesiune powershell care lanseaza apoi testul, otraveste lansarile (nu executa). Ruleaza precompilarea intr-un proces copil izolat (Start-Process -Wait _precompile.ps1). f. Detectia "a pornit" pe mtime-ul log-ului > momentul lansarii, nu pe "log ne-gol": un log stale tinut deschis de un vfp9 zombi pacaleste verificarea. Omoara zombii inainte de fiecare lansare. g. Formulare modale (WindowType) - Show() nu returneaza si blocheaza testul. Seteaza goObj.WindowType = 0 (modeless) pentru afisare non-modala automatizata. h. Dummy minimali: goExecutor (oExecute/oExecuta creeaza cursorul cerut), goApp (ReadIni pt. font din settings.ini), poAct/oact (curs valutar). Init-ul recreeaza cursoarele goale din Oracle daca nu-s pre-populate - pre-populeaza-le INAINTE de instantiere. i. LOCATE FOR camp == 'literal' pe camp C(n) padded cu spatii nu gaseste nimic (== e exact) - foloseste ALLTRIM(camp) == 'literal'. j. Messagebox-uri de confirmare blocheaza testul headless. Mock: FUNCTION amessagebox care returneaza direct valoarea butonului (6=Da). Incarca-l cu SET PROCEDURE ... ADDITIVE PRIMUL (inaintea oproceduri_comune.prg): la nume duplicat de procedura, VFP foloseste fisierul cautat PRIMUL in lista (verificat empiric: incarcat ultimul PIERDE, primul CASTIGA). Mock-ul umbreste amessagebox si din apelurile din metodele de clasa (ex. do_sterge_articol). k. Cursor bound la o grila, recreat de Init -> grila pierde coloanele (eroare 1925). Ex.: import_nota.Init recreeaza rul_temp din Oracle daca e gol la instantiere; GridArt ramane fara coloanele denumite. Fix: pune 1 rand placeholder in cursor INAINTE de instantiere (ca Init sa nu-l recreeze), apoi ZAP dupa Show() (NU DELETE ALL: acesta lasa un rand fizic sters si pointerul la EOF, iar cod gen do_calculeaza_diferente face GO recno_EOF -> eroare 5). 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 (ex. toDlg pt. do_adauga_factura) INLINE in main, nu intr-un helper. (Cauza exacta neclara; reproductibil.) m. gl406=.T. (SAFT) activeaza cod care are nevoie de mock-uri suplimentare, altfel crapa sau taxcode ramane mereu NULL: GetTaxCode/GetTaxCodeIdPart (oproceduri_comune.prg) apeleaza update_jtva_coloane() care ruleaza goExecutor.oExecute("select * from vjtva_coloane ...", <cursor>)
  • mock-ul de oExecute trebuie sa intercepteze 'VJTVA_COLOANE' $ UPPER(tcSql) si sa returneze un cursor cu structura reala a view-ului (id_jtva_coloana, denumire, jc, jv, coloana_jc, coloana_jv, cota_tva, id_tva, taxcode, taxcode_neexigibil, taxcode_n50, taxcode_n50_tvai, taxcode_n100, taxcode_regularizare, taxcode_tvai, afisat, ordine) - codul apelant face singur INDEX ON id_jtva_coloana TAG id_jtva dupa, nu trebuie indexat in mock. Cand id_partc/id_partd pe rand nu e gol, GetTaxCodeIdPart cauta si codul fiscal al partenerului prin goExecutor.oSelect2Value(sql, @toValue) (GetCodFiscalPartenerById) - metoda lipseste din dummyexecutor-ul de baza, trebuie adaugata (returneaza cod fiscal gol -> VERIFICA_RTVAI sare peste apelul Oracle real de validare CIF). n. Explicatia TVA de pe randul T ("in tandem" cu baza) NU se scrie prin cod separat - e pur si simplu o A DOUA intrare in jtva_coloane2/vjtva_coloane (coloana id_tva de pe randul bazei punctand spre id_jtva_coloana al randului TVA); aplica_explicatie_tva (ointroduceri.prg ~9807) citeste acea a doua intrare si o scrie pe rand T. Populeaza mock-ul cu ambele randuri (baza + TVA), nu doar cu unul si nu concatena manual "TVA " + denumirea bazei in test. o. FARA FURT DE FOCUS (17/07/2026): Graphics.CopyFromScreen (captura ecranului intreg) fura focus si se corupe daca utilizatorul lucreaza in paralel - NU se mai foloseste. Capturile se fac cu PrintWindow (user32, P/Invoke) pe HANDLE-ul ferestrei vfp9 (Process.MainWindowHandle), flag 2 = PW_RENDERFULLCONTENT (fallback flag 0 daca iese goala); merge cu fereastra acoperita/in fundal, dar NU minimizata. Fereastra e mutata OFF-SCREEN (SetWindowPos cu HWND_BOTTOM, x=-4000, SWP_NOACTIVATE) imediat ce apare handle-ul si RE-IMPINSA la fiecare pas (VFP isi reactiveaza singur fereastra la Show()/dialoguri modale - o singura impingere la lansare nu e suficienta). Toate consolele powershell copil (precompilare) sunt -WindowStyle Hidden (sigur pentru consola; NU si pentru GUI VFP, care trebuie doar off-screen, nu ascuns - PrintWindow pe fereastra minimizata/ascunsa poate reda gol). Implementat in vfp_ui_harness.ps1 (Take-Screenshot, Push-Offscreen, Wait-MainWindowHandle) si _precompile.ps1 (acelasi Push-Offscreen pe fereastra tranzitorie de compilare). p. Cursorul introdc FARA indexul ord_doc schimba documentul "principal" la sincronizari repetate (sincronizeaza() apelat de mai multe ori, ex. la adaugarea a 2+ articole succesiv prin fluxul real): identificarea documentului principal (Go Top + Locate For Inlist(tip_rand,'B','S') And !Empty(doc_key)) e stabila in productie doar pentru ca achizitie_import (COMUN\programe\ointroduceri.prg:1616-1618) creeaza, INAINTE de CREATEOBJECT('import_nota'), indexul ord_doc (Str(Nvl(nr_doc,0),8) + Icase(tip_rand='B','1', tip_rand='S','2', tip_rand='T','3', tip_rand='D','4', '5') + Padr(Nvl(scd,''),10)) + SET ORDER TO ord_doc, care ordoneaza dupa nr_doc (S-urile principalei mostenesc nr_doc-ul ei) - documentul cu nr_doc minim ramane mereu acelasi. Un test care creeaza introdc fara acest index ramane pe ordinea FIZICA (recno): dupa ce principala e sparta (randul B sters, randurile S adaugate la coada), urmatorul apel de sincronizeaza() gaseste gresit ca "principal" documentul cu cel mai mic recno ramas si il sparge si pe el - cascada se repeta la fiecare apel ulterior. Fix: dupa popularea introdc, INAINTE de CREATEOBJECT('import_nota', ...), adauga exact acelasi INDEX ON ... TAG ord_doc
  • SET ORDER TO ord_doc ca in achizitie_import. q. Suitele de teste NU se ruleaza in paralel: _precompile.ps1 face Get-Process vfp9 | Stop-Process -Force (omoara TOATE instantele vfp9 de pe masina), deci un test lansat in paralel ucide testul aflat in curs si corupe rezultatele. Ruleaza suitele strict secvential. r. Fara SELECT-SQL pe cursorul legat de grid: SELECT ... FROM introdc INTO CURSOR intr-un test, cat timp introdc 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.