Rescrie complet fluxul de descarcare tranzactii pentru noul UI BT George

Modificari principale:
- Noul flux download: expand -> tranzactii -> CSV -> Genereaza -> download
- Detectie inteligenta buton Tranzactii (evita butonul Delete)
- Verificare daca primul cont e deja expandat inainte de click
- Selectie conturi cu 6 strategii fallback + debug logging
- Handler pentru cookie consent "Accept toate" si "Am inteles"
- Screenshot automat la erori de selectie cont

Documentatie:
- README: sectiuni noi pentru inregistrare Playwright si testare manuala
- CLAUDE.md: selectori actualizati pentru noul UI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-09 15:18:44 +02:00
parent e49e653e12
commit 0fff07c55b
3 changed files with 513 additions and 187 deletions

View File

@@ -4,11 +4,11 @@ This file provides guidance to Claude Code when working with code in this reposi
## Project Overview
BTGO Scraper - Playwright automation for extracting account balances and transaction CSVs from Banca Transilvania George (btgo.ro).
BTGO Scraper - Playwright automation for extracting account balances and transaction CSVs from Banca Transilvania George (go.bancatransilvania.ro).
**Security Context**: Authorized personal banking automation tool for educational purposes.
**⚠️ CRITICAL**: Docker/headless mode is **BLOCKED by WAF**. ONLY works locally with `HEADLESS=false`.
**CRITICAL**: Docker/headless mode is **BLOCKED by WAF**. ONLY works locally with `HEADLESS=false`.
## Coding Guidelines
@@ -54,30 +54,54 @@ login() → handle_2fa_wait() → read_accounts() → download_transactions()
- Each card: `h4` (name), `span.text-grayscale-label` (IBAN), `strong.sold` (balance)
- Balance format: `"7,223.26 RON"` → parse to float + currency
#### 4. Transaction Download Modal (lines ~312-420)
#### 4. Transaction Download (lines ~529-732)
**State Machine:**
**Flow (2024+ version):**
```
Account 1: Expand card Click "Tranzacții" → Download → Back → Modal
Account 2+: Select from modal → [ALREADY on page] → Download → Back
Account 1: Expand card -> Click tranzactii icon -> Select "CSV" -> "Genereaza" -> Download from fba-document-item
Account 2+: Click #selectAccountBtn -> Select by heading name -> "Genereaza" -> Download
```
**Critical**: After modal selection, you're ALREADY on transactions page. Don't expand/click again.
**Key methods:**
- `_download_first_account()`: Handles first account (expand + select CSV format)
- `_download_subsequent_account()`: Handles accounts 2+ (dropdown selection)
- `_wait_and_download()`: Waits for fba-document-item and downloads
**Modal selectors:**
- Modal: `.modal-content`
- Account buttons: `#accountC14RONCRT{last_10_iban_digits}`
- Example: IBAN `...0637236701``#accountC14RONCRT0637236701`
**Account selection strategies (in order):**
1. `get_by_role("heading", name=account_name)`
2. `locator("fba-account-details").filter(has_text=account_name)`
3. `get_by_text(account_name, exact=True)`
### Key Selectors
- Login: `get_by_placeholder("ID de logare")`, `get_by_placeholder("Parola")`
- Post-login: `#accountsBtn`, `goapp.bancatransilvania.ro` domain
- Accounts: `fba-account-details-card`, `.collapse-account-btn`, `.account-transactions-btn`
- Modal: `.modal-content`, `#accountC14RONCRT{iban_digits}`
- CSV: `get_by_role("button", name="CSV")`
**Cookie consent:**
- New (2024+): `get_by_role("button", name="Accept toate")`
- One-time consent: `get_by_text("Am inteles")`
**Update selectors:** `playwright codegen https://btgo.ro --target python`
**Login page:**
- URL: `https://go.bancatransilvania.ro/`
- Login link: `get_by_role("link", name="Login")`
- Username: `get_by_placeholder("ID logare")` (intelligent fallback in `_find_username_field`)
- Password: `get_by_placeholder("Parola")` or `input[type='password']`
- Submit: `get_by_role("button", name="Autentifica-te")` (intelligent fallback in `_find_submit_button`)
**Post-login:**
- 2FA success indicator: `#accountsBtn` visible and enabled
- Domain: `goapp.bancatransilvania.ro`
**Accounts:**
- Cards: `fba-account-details-card`
- Expand icon: `.mx-auto .mat-icon svg`, `.collapse-account-btn`
- Transactions button: `fba-account-buttons svg`, `.account-transactions-btn`
**Transaction download:**
- Account selector: `#selectAccountBtn svg`
- Account in dropdown: `get_by_role("heading", name=account_name)`
- CSV format: `get_by_text("CSV", exact=True)`
- Generate button: `get_by_role("button", name="Genereaza")`
- Download item: `fba-document-item svg`, `fba-document-item path`
**Update selectors:** `playwright codegen https://go.bancatransilvania.ro --target python`
## Docker Limitation
@@ -97,17 +121,19 @@ Account 2+: Select from modal → [ALREADY on page] → Download → Back
- **Fix**: Run locally with `HEADLESS=false`
### Transaction Download Timeout
- Modal not detected: Check `.modal-content` selector
- Account ID mismatch: Verify IBAN mapping `#accountC14RONCRT{last_10_digits}`
- For idx > 1: Already on page, don't expand/click
- Check `fba-document-item` selector (wait for document generation)
- Verify `#selectAccountBtn` for account dropdown
- Account selection: verify heading name matches exactly
### 2FA Timeout
- Increase `TIMEOUT_2FA_SECONDS` in `.env`
- Verify URL redirect to `goapp.bancatransilvania.ro`
- Check for "Am inteles" consent dialog blocking
### "Nu exista card la pozitia X"
- Trying to access cards in modal context
- First account needs expand, subsequent accounts don't
### Account Selection Failed
- Account name might have changed - verify exact match
- Try running `playwright codegen` to see current UI structure
- Check if dropdown opened (`#selectAccountBtn`)
## Exit Codes

120
README.md
View File

@@ -325,12 +325,8 @@ deployment\windows\scripts\menu.ps1
- Mărește timeout: `TIMEOUT_2FA_SECONDS=180` în `.env`
- Verifică notificări activate pe telefon
### Selectors nu funcționează
Site-ul s-a schimbat. Re-generează selectors:
```bash
.venv\Scripts\activate
playwright codegen https://btgo.ro --target python
```
### Selectors nu functioneaza
Site-ul s-a schimbat. Urmeaza pasii din sectiunea **Inregistrare Manuala cu Playwright** de mai jos.
### Notificări Email nu funcționează
- Pentru Gmail: folosește App Password, nu parola normală
@@ -343,12 +339,116 @@ playwright codegen https://btgo.ro --target python
- Chat ID pentru grupuri trebuie să fie **negativ** (ex: `-1001234567890`)
- Asigură-te că botul este în grup
## Inregistrare Manuala cu Playwright (Codegen)
Cand site-ul BT George isi schimba interfata, trebuie sa reinregistrezi fluxul manual.
### 1. Porneste Playwright Codegen
```powershell
# Activeaza venv-ul
.venv\Scripts\Activate.ps1
# Porneste codegen
playwright codegen https://go.bancatransilvania.ro --target python
```
Se deschid 2 ferestre:
- **Browser** - aici faci actiunile manual
- **Playwright Inspector** - aici vezi codul Python generat
### 2. Inregistreaza Fluxul
1. Accept cookies ("Accept toate")
2. Click pe "Login" - se deschide popup
3. Completeaza username si parola
4. Click "Autentifica-te"
5. Asteapta 2FA pe telefon
6. Dupa login, click pe "Conturi"
7. **Pentru primul cont:**
- Expand card (click pe sageata)
- Click pe butonul "Tranzactii" (iconita cu grafic)
- Click pe "CSV" pentru format
- Click pe "Genereaza"
- Click pe documentul generat pentru download
8. **Pentru conturile urmatoare:**
- Click pe `#selectAccountBtn` (dropdown conturi)
- Selecteaza contul din lista
- Click pe "Genereaza"
- Download fisierul
### 3. Salveaza si Analizeaza Scriptul
- In Inspector: **Copy** sau **File > Save**
- Compara cu `btgo_scraper.py` si actualizeaza selectorii modificati
- Selectori cheie de verificat:
- Cookie consent: `get_by_role("button", name="...")`
- Username field: `get_by_placeholder("...")`
- Submit button: `get_by_role("button", name="...")`
- Account selector: `#selectAccountBtn`
- Download item: `fba-document-item`
## Testare Manuala a Scraperului
### Testare Pas cu Pas
```powershell
# 1. Activeaza venv
.venv\Scripts\Activate.ps1
# 2. Ruleaza scraper-ul
python btgo_scraper.py
```
### Verificare Output
Dupa rulare, verifica:
```powershell
# Fisiere generate
dir data\
# Trebuie sa vezi:
# - solduri_*.csv (solduri toate conturile)
# - solduri_*.json (metadata + solduri)
# - tranzactii_*.csv (cate un fisier per cont)
# - dashboard_*.png (screenshot final)
# Verifica log-ul pentru erori
type logs\scraper_*.log | Select-String -Pattern "EROARE|ERROR|Exception"
```
### Testare Doar Solduri (fara download tranzactii)
```powershell
# Seteaza variabila temporar
$env:BALANCES_ONLY = "true"
python btgo_scraper.py
```
### Debug - Screenshot-uri
Daca ceva nu merge, verifica screenshot-urile din `data/`:
- `debug_login_popup_*.png` - starea paginii de login
- `debug_dropdown_*.png` - dropdown-ul de selectare conturi (daca esueaza)
- `error_*.png` - screenshot la eroare
### Testare Notificari (fara scraping)
```powershell
# Test Telegram
python test_telegram.py
# Trimite ultimele fisiere manual
python send_notifications.py
```
## Securitate
**⚠️ IMPORTANT:**
- NU comite `.env` în git (deja în `.gitignore`)
- NU partaja screenshots/logs - conțin date sensibile
- Șterge fișierele vechi periodic:
**IMPORTANT:**
- NU comite `.env` in git (deja in `.gitignore`)
- NU partaja screenshots/logs - contin date sensibile
- Sterge fisierele vechi periodic:
```bash
# Windows

View File

@@ -98,37 +98,75 @@ class BTGoScraper:
page: Pagina Playwright pe care sa verifice
"""
try:
# Verifica daca exista gdprcookie-wrapper
gdpr_wrapper = page.locator(".gdprcookie-wrapper")
if gdpr_wrapper.is_visible(timeout=3000):
logging.info(" GDPR cookie banner detectat")
# Strategii pentru cookie consent (in ordinea probabilitatii)
cookie_strategies = [
# 1. Noul buton BT (2024+)
("role", "button", "Accept toate"),
("role", "button", "Accepta toate"),
# Incearca diverse butoane de accept (in ordinea probabilitatii)
accept_selectors = [
".gdprcookie-wrapper button:has-text('Accept')",
".gdprcookie-wrapper button:has-text('Accepta')",
".gdprcookie-wrapper button:has-text('Sunt de acord')",
".gdprcookie-wrapper button:has-text('OK')",
".gdprcookie-wrapper .gdprcookie-buttons button:first-child",
".gdprcookie-wrapper button",
]
# 2. Vechiul GDPR wrapper
("css", ".gdprcookie-wrapper button:has-text('Accept')"),
("css", ".gdprcookie-wrapper button:has-text('Sunt de acord')"),
("css", ".gdprcookie-wrapper button"),
for selector in accept_selectors:
try:
accept_btn = page.locator(selector).first
if accept_btn.is_visible(timeout=1000):
accept_btn.click()
logging.info(f" [OK] Cookies acceptate (selector: {selector})")
time.sleep(1) # Asteapta sa dispara banner-ul
return True
except:
continue
# 3. Fallback generic
("role", "button", "Accept"),
("role", "button", "Accepta"),
("role", "button", "OK"),
]
logging.warning(" Nu am gasit buton de accept in GDPR wrapper")
return False
except:
logging.info(" Nu exista GDPR cookie banner (sau deja inchis)")
for strategy in cookie_strategies:
try:
if strategy[0] == "role":
btn = page.get_by_role(strategy[1], name=strategy[2])
else:
btn = page.locator(strategy[2]).first
if btn.is_visible(timeout=2000):
btn.click()
logging.info(f" [OK] Cookies acceptate ({strategy})")
time.sleep(1)
return True
except:
continue
logging.info(" Nu exista cookie banner (sau deja acceptat)")
return False
except:
logging.info(" Nu exista cookie banner (sau deja acceptat)")
return False
def _dismiss_one_time_consent(self, page):
"""
Inchide dialoguri one-time (ex: 'Am inteles') daca apar
Args:
page: Pagina Playwright pe care sa verifice
"""
consent_buttons = [
("text", "Am înțeles"),
("text", "Am inteles"),
("role", "button", "Am înțeles"),
("role", "button", "OK"),
("role", "button", "Continua"),
]
for strategy in consent_buttons:
try:
if strategy[0] == "text":
btn = page.get_by_text(strategy[1], exact=True)
elif strategy[0] == "role":
btn = page.get_by_role(strategy[1], name=strategy[2])
if btn.is_visible(timeout=2000):
btn.click()
logging.info(f" [OK] Consent inchis ({strategy})")
time.sleep(1)
return True
except:
continue
return False
def _find_username_field(self, page):
"""
@@ -466,10 +504,14 @@ class BTGoScraper:
if accounts_btn.is_visible(timeout=1000):
# Verifică că este și clickable (enabled)
if accounts_btn.is_enabled():
logging.info(" Autentificare 2FA reusita! (Buton conturi activ)")
logging.info("[OK] Autentificare 2FA reusita! (Buton conturi activ)")
time.sleep(2) # Asteapta ca pagina sa se stabilizeze complet
# Update page reference la login_page pentru restul operatiilor
self.page = self.login_page
# Inchide dialoguri one-time (ex: "Am inteles") daca apar
self._dismiss_one_time_consent(self.page)
return True
except:
pass
@@ -485,30 +527,19 @@ class BTGoScraper:
raise TimeoutError(f"Timeout 2FA dupa {timeout} secunde. Verifica ca ai aprobat pe telefon!")
def download_transactions(self, accounts):
"""Descarca CSV-uri cu tranzactiile pentru fiecare cont"""
"""
Descarca CSV-uri cu tranzactiile pentru fiecare cont.
Flux nou (2024+):
1. Primul cont: expand card -> click tranzactii -> select CSV -> Genereaza -> download
2. Conturile urmatoare: #selectAccountBtn -> select cont by heading -> Genereaza -> download
"""
logging.info("=" * 60)
logging.info("Descarcare tranzactii pentru toate conturile...")
logging.info("=" * 60)
downloaded_files = []
# IMPORTANT: Collapse toate conturile mai intai
logging.info("Collapse toate conturile...")
all_expanded = self.page.locator(".mat-icon.rotate-90").all()
for expanded_icon in all_expanded:
try:
expanded_icon.click()
time.sleep(0.3)
except:
pass
time.sleep(1)
logging.info("✓ Toate conturile sunt collapse")
# Re-gaseste toate cardurile de conturi
all_cards = self.page.locator("fba-account-details-card").all()
logging.info(f"Gasit {len(all_cards)} carduri de conturi")
for idx, account in enumerate(accounts, 1):
try:
nume_cont = account['nume_cont']
@@ -517,118 +548,19 @@ class BTGoScraper:
self._update_progress(f"Descarc tranzactii ({idx}/{len(accounts)})...")
logging.info(f"[{idx}/{len(accounts)}] Descarcare tranzactii pentru: {nume_cont}")
# Doar pentru PRIMUL cont trebuie expand + click Tranzacții
# Pentru restul, suntem deja pe pagina de tranzacții (din selectarea din modal)
if idx == 1:
# Primul cont - expand și click Tranzacții
if idx - 1 >= len(all_cards):
logging.error(f" ✗ Nu exista card la pozitia {idx-1}")
continue
card = all_cards[idx - 1]
# Expand contul (click pe săgeată)
expand_button = card.locator(".collapse-account-btn").first
expand_button.click()
time.sleep(2) # Așteaptă expandare
logging.info(f" Contul expandat")
# Click pe butonul Tranzacții
try:
transactions_button = card.locator(".account-transactions-btn").first
transactions_button.click()
time.sleep(3) # Așteaptă încărcarea paginii cu tranzacții
logging.info(f" Click pe buton Tranzactii - pagina se incarca...")
except Exception as e:
logging.error(f" ✗ Nu am gasit butonul Tranzactii: {e}")
try:
expand_button.click()
time.sleep(0.5)
except:
pass
continue
# PRIMUL CONT: expand -> click tranzactii -> select CSV
downloaded = self._download_first_account(account)
else:
# Conturile 2-5: suntem deja pe pagina de tranzacții (din modal)
logging.info(f" Deja pe pagina tranzactii (selectat din modal)")
time.sleep(2) # Așteaptă stabilizare pagină
# CONTURILE URMATOARE: selecteaza din dropdown -> Genereaza -> download
downloaded = self._download_subsequent_account(account)
# Așteaptă să apară butonul CSV (indica că pagina s-a încărcat)
try:
self.page.wait_for_selector('button:has-text("CSV")', timeout=5000)
logging.info(f" Buton CSV detectat")
except:
logging.warning(f" Timeout asteptand butonul CSV")
# Click pe butonul CSV și așteaptă download
try:
with self.page.expect_download(timeout=15000) as download_info:
csv_button = self.page.get_by_role("button", name="CSV")
csv_button.click()
logging.info(f" Click pe butonul CSV - astept download...")
download = download_info.value
# Salvează fișierul cu un nume descriptiv
timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
nume_safe = nume_cont.replace(' ', '_').replace('/', '_')
filename = f"tranzactii_{nume_safe}_{timestamp}.csv"
save_path = Path(self.config.OUTPUT_DIR) / filename
download.save_as(save_path)
logging.info(f" ✓ Salvat: {save_path}")
downloaded_files.append({
'cont': nume_cont,
'iban': iban,
'fisier': str(save_path)
})
except Exception as e:
logging.error(f" ✗ Eroare la descarcarea CSV: {e}")
# Navighează înapoi la lista de conturi
try:
# Click pe butonul back/close (săgeată stânga sau X)
back_button = self.page.locator('button[aria-label="Back"], .back-button, #selectAccountBtn').first
back_button.click()
time.sleep(1.5)
logging.info(f" Navigat inapoi - verific modal...")
except Exception as e:
logging.warning(f" Nu am putut naviga inapoi: {e}")
time.sleep(1)
# Verifică dacă a apărut modal de selectare cont
try:
modal_visible = self.page.locator('.modal-content').is_visible(timeout=2000)
if modal_visible and idx < len(accounts):
logging.info(f" Modal detectat - selectez contul urmator...")
# Calculează ID-ul contului următor
next_account = accounts[idx] # idx este 0-indexed pentru next
next_iban = next_account['iban']
next_iban_digits = ''.join(filter(str.isdigit, next_iban))[-10:]
next_account_id = f"accountC14RONCRT{next_iban_digits}"
# Click pe contul următor din modal
modal_account = self.page.locator(f'#{next_account_id}').first
modal_account.click()
time.sleep(2)
logging.info(f" ✓ Selectat cont din modal: {next_account['nume_cont']}")
else:
# Nu e modal - e ultima iteratie sau nu a aparut modal
logging.info(f" Nu e modal - continuam normal")
except Exception as e:
logging.warning(f" Eroare verificare modal: {e}")
# Re-găsește cardurile (pentru flow normal fără modal)
try:
all_cards = self.page.locator("fba-account-details-card").all()
except:
pass
if downloaded:
downloaded_files.append(downloaded)
except Exception as e:
logging.error(f" Eroare la descarcarea tranzactiilor pentru {nume_cont}: {e}")
# Încearcă să navighezi înapoi
logging.error(f" [EROARE] Eroare la descarcarea tranzactiilor pentru {nume_cont}: {e}")
# Incearca sa revii la o stare stabila
try:
self.page.keyboard.press("Escape")
time.sleep(1)
@@ -637,11 +569,279 @@ class BTGoScraper:
continue
logging.info("=" * 60)
logging.info(f" Descarcate {len(downloaded_files)}/{len(accounts)} fisiere CSV cu tranzactii")
logging.info(f"[OK] Descarcate {len(downloaded_files)}/{len(accounts)} fisiere CSV cu tranzactii")
logging.info("=" * 60)
return downloaded_files
def _download_first_account(self, account):
"""
Descarca tranzactii pentru primul cont.
Flow: expand card (daca nu e deja) -> click buton tranzactii -> select CSV -> Genereaza -> download
"""
nume_cont = account['nume_cont']
iban = account['iban']
try:
# Gaseste primul card
first_card = self.page.locator("fba-account-details-card").first
time.sleep(1)
# Verifica daca butonul de tranzactii e DEJA vizibil (cont expandat)
transactions_btn = first_card.locator("fba-account-buttons svg, fba-account-buttons, .account-transactions-btn").first
is_already_expanded = False
try:
is_already_expanded = transactions_btn.is_visible(timeout=2000)
except:
pass
if is_already_expanded:
logging.info(" Contul deja expandat - skip expand click")
else:
# Click pe expand icon (sageata din card)
logging.info(" Contul collapsed - expandez...")
expand_icon = first_card.locator(".mx-auto .mat-icon svg, .collapse-account-btn").first
expand_icon.click()
time.sleep(2)
logging.info(" Contul expandat")
# Click pe butonul de tranzactii (NU pe delete/inchide cont!)
# Butonul corect are: clasa .account-transactions-btn, SVG cu documentChartList, <p> cu "Tranzactii"
transactions_btn = None
# Strategie 1 (PRINCIPALA): container cu clasa account-transactions-btn
try:
btn = first_card.locator(".account-transactions-btn").first
if btn.is_visible(timeout=2000):
transactions_btn = btn
logging.info(" Buton tranzactii gasit prin .account-transactions-btn")
except:
pass
# Strategie 2: SVG cu data-mat-icon-name="documentChartList"
if not transactions_btn:
try:
btn = first_card.locator("mat-icon[data-mat-icon-name='documentChartList']").first
if btn.is_visible(timeout=1000):
transactions_btn = btn
logging.info(" Buton tranzactii gasit prin documentChartList icon")
except:
pass
# Strategie 3: element care contine <p> cu text "Tranzactii"
if not transactions_btn:
try:
btn = first_card.locator("div:has(p:text('Tranzacții')), div:has(p:text('Tranzactii'))").first
if btn.is_visible(timeout=1000):
transactions_btn = btn
logging.info(" Buton tranzactii gasit prin <p> text")
except:
pass
# Strategie 4: click direct pe textul "Tranzactii" din card
if not transactions_btn:
try:
btn = first_card.get_by_text("Tranzacții", exact=True)
if btn.is_visible(timeout=1000):
transactions_btn = btn
logging.info(" Buton tranzactii gasit prin get_by_text")
except:
pass
if not transactions_btn:
raise Exception("Nu am gasit butonul de tranzactii!")
transactions_btn.click()
time.sleep(3)
logging.info(" Pagina tranzactii se incarca...")
# Selecteaza format CSV (click pe text "CSV")
csv_option = self.page.get_by_text("CSV", exact=True)
csv_option.click()
time.sleep(1)
logging.info(" Format CSV selectat")
# Click pe butonul Genereaza
generate_btn = self.page.get_by_role("button", name="Generează")
generate_btn.click()
time.sleep(2)
logging.info(" Click Genereaza - astept generare...")
# Asteapta si descarca fisierul
return self._wait_and_download(nume_cont, iban)
except Exception as e:
logging.error(f" [EROARE] Download primul cont: {e}")
return None
def _download_subsequent_account(self, account):
"""
Descarca tranzactii pentru conturile 2+.
Flow: click #selectAccountBtn -> select cont by heading -> Genereaza -> download
"""
nume_cont = account['nume_cont']
iban = account['iban']
try:
# Click pe butonul de selectare cont (#selectAccountBtn)
select_btn = self.page.locator("#selectAccountBtn svg, #selectAccountBtn").first
select_btn.click()
time.sleep(2)
logging.info(" Dropdown conturi deschis")
# Debug: listeaza toate heading-urile vizibile din dropdown
try:
headings = self.page.locator("fba-account-details h4, .account-name, h4").all()
visible_names = []
for h in headings[:10]: # Max 10
try:
if h.is_visible(timeout=500):
visible_names.append(h.inner_text().strip())
except:
pass
if visible_names:
logging.info(f" Conturi in dropdown: {visible_names}")
except:
pass
# Selecteaza contul dupa nume - strategii multiple
account_selected = False
# Strategie 1: heading cu numele exact
try:
heading = self.page.get_by_role("heading", name=nume_cont, exact=True)
if heading.is_visible(timeout=2000):
heading.click()
account_selected = True
logging.info(f" Cont selectat prin heading exact: {nume_cont}")
except Exception as e:
logging.debug(f" Heading exact failed: {e}")
# Strategie 2: heading cu numele partial (fara exact match)
if not account_selected:
try:
heading = self.page.get_by_role("heading", name=nume_cont)
if heading.is_visible(timeout=2000):
heading.click()
account_selected = True
logging.info(f" Cont selectat prin heading partial: {nume_cont}")
except Exception as e:
logging.debug(f" Heading partial failed: {e}")
# Strategie 3: fba-account-details cu has_text
if not account_selected:
try:
account_item = self.page.locator("fba-account-details").filter(has_text=nume_cont).first
if account_item.is_visible(timeout=2000):
account_item.click()
account_selected = True
logging.info(f" Cont selectat prin fba-account-details: {nume_cont}")
except Exception as e:
logging.debug(f" fba-account-details failed: {e}")
# Strategie 4: locator h4 care contine textul
if not account_selected:
try:
h4_elem = self.page.locator(f"h4:has-text('{nume_cont}')").first
if h4_elem.is_visible(timeout=2000):
h4_elem.click()
account_selected = True
logging.info(f" Cont selectat prin h4:has-text: {nume_cont}")
except Exception as e:
logging.debug(f" h4:has-text failed: {e}")
# Strategie 5: orice element cu textul contului
if not account_selected:
try:
text_elem = self.page.get_by_text(nume_cont, exact=True)
if text_elem.is_visible(timeout=2000):
text_elem.click()
account_selected = True
logging.info(f" Cont selectat prin text exact: {nume_cont}")
except Exception as e:
logging.debug(f" text exact failed: {e}")
# Strategie 6: text partial match
if not account_selected:
try:
text_elem = self.page.get_by_text(nume_cont)
if text_elem.is_visible(timeout=2000):
text_elem.click()
account_selected = True
logging.info(f" Cont selectat prin text partial: {nume_cont}")
except Exception as e:
logging.debug(f" text partial failed: {e}")
if not account_selected:
logging.error(f" [EROARE] Nu am putut selecta contul: {nume_cont}")
# Screenshot pentru debug
try:
debug_path = Path(self.config.OUTPUT_DIR) / f"debug_dropdown_{nume_cont.replace(' ', '_')}_{datetime.now().strftime('%H%M%S')}.png"
self.page.screenshot(path=str(debug_path))
logging.error(f" Screenshot debug salvat: {debug_path}")
except:
pass
return None
time.sleep(2)
# Click pe butonul Genereaza (CSV deja selectat de la primul cont)
generate_btn = self.page.get_by_role("button", name="Generează")
generate_btn.click()
time.sleep(2)
logging.info(" Click Genereaza - astept generare...")
# Asteapta si descarca fisierul
return self._wait_and_download(nume_cont, iban)
except Exception as e:
logging.error(f" [EROARE] Download cont ulterior {nume_cont}: {e}")
return None
def _wait_and_download(self, nume_cont, iban, timeout=20000):
"""
Asteapta generarea fisierului si il descarca.
Args:
nume_cont: Numele contului (pentru filename)
iban: IBAN-ul contului
timeout: Timeout pentru download (ms)
Returns:
Dict cu informatii despre fisierul descarcat sau None
"""
try:
# Asteapta sa apara fba-document-item (indica ca fisierul e gata)
self.page.wait_for_selector("fba-document-item", timeout=timeout)
logging.info(" Document generat - descarc...")
# Click pe document item pentru a descarca
with self.page.expect_download(timeout=timeout) as download_info:
download_btn = self.page.locator("fba-document-item svg, fba-document-item path").first
download_btn.click()
download = download_info.value
# Salveaza fisierul cu nume descriptiv
timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
nume_safe = nume_cont.replace(' ', '_').replace('/', '_').replace('\\', '_')
filename = f"tranzactii_{nume_safe}_{timestamp}.csv"
save_path = Path(self.config.OUTPUT_DIR) / filename
download.save_as(save_path)
logging.info(f" [OK] Salvat: {save_path}")
return {
'cont': nume_cont,
'iban': iban,
'fisier': str(save_path)
}
except Exception as e:
logging.error(f" [EROARE] Download fisier: {e}")
return None
def read_accounts(self):
"""Extrage soldurile tuturor conturilor"""
logging.info("Citire conturi si solduri...")