Skip to content

Commit 2b979d6

Browse files
author
SIN-Agent
committed
feat: heypiggy-login dual-flow detection (Flow A email + Flow B konto)
Dual-Flow Consent (E2E-verifiziert 2026-05-03): - FLOW A: Frischer Browser → Email eingeben → Fortfahren → Consent → Dashboard - FLOW B: Bereits eingeloggt → Konto klicken → Consent → Dashboard detect_screen1_type() auto-erkennung: - "E-Mail" im tree → Flow A - "Jeremy Schulze" / "zukunftsorientierte" → Flow B NEU: click_by_label() helper mit multi-label fallback + generic search DOCS: - commands.md: dual-flow dokumentiert - brain.md: dual-flow pattern + detect_screen1_type() - fix.md: dual-flow als neuen Fix erklärt - learn.md: Zwei-Flow-Mapping + Robustness-Regeln erweitert WICHTIG: Google kann Flows KÜRZEN wenn Cookies gecached sind! Flow B ist 2 Schritte kürzer als Flow A!
1 parent ad7eb2e commit 2b979d6

5 files changed

Lines changed: 234 additions & 128 deletions

File tree

brain.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,23 @@ cua-driver call set_value '{"pid":PID,"window_id":WID,"element_index":N,"value":
1818

1919
Siehe `docs/cua-driver-popup-pattern.md` für vollständige Doku.
2020

21-
## 🔑 Google Login: Consent-Flow (E2E-verifiziert 2026-05-03, PID 89465)
21+
## 🔑 Google Login: Dual-Flow Consent (E2E-verifiziert 2026-05-03)
2222

2323
**Kein Passwort, kein Passkey — nur Consent mit bestehenden Google-Cookies!**
2424

25-
⚠️ **KEINE FIXEN INDICES!** Google ändert UI ständig. Script nutzt `find_element_by_label()`:
26-
- Sucht nach Label im `tree_markdown` via regex
27-
- Multi-Language: "Weiter" + "Continue", "Fortfahren" + "Continue"
28-
- Retry mit exponential backoff
25+
Google ist SMART und kürzt den Flow wenn Cookies gecached sind!
2926

30-
Flow: Google(43) → Email(Label) → Weiter(Label) → Fortfahren(Label) → Weiter(Label) → Dashboard ✅
27+
**FLOW A — Frischer Browser (keine Google-Cookies):**
28+
`Google(43) → Email(Label) → Weiter → Fortfahren(Label) → Weiter(Label) → Dashboard`
29+
30+
**FLOW B — Bereits eingeloggt (Google-Cookies im Browser):**
31+
`Google(43) → Konto klicken(Label) → Weiter(Label) → Dashboard`
32+
33+
**detect_screen1_type() in heypiggy-login:**
34+
- Sucht "E-Mail" im tree → FLOW A (Email-Eingabe)
35+
- Sucht "Jeremy Schulze" / "zukunftsorientierte" im tree → FLOW B (Konto-auswählen)
36+
37+
⚠️ **KEINE FIXEN INDICES!** Immer `find_element_by_label()` + `detect_screen1_type()`
3138

3239
## 🔑 ROBUSTNESS-PRINZIP (2026-05-03): Immer dynamisch, nie fix!
3340

cli/heypiggy-login

Lines changed: 146 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,22 @@
88
#
99
# ROBUSTNESS-PRINZIP (2026-05-03):
1010
# - Google kann UI jederzeit ändern → fixe Indices brechen!
11+
# - Google kann Flows KÜRZEN (Cookies cached)!
1112
# - IMMER nach Label/Title suchen, nie nach hartcodiertem Index
1213
# - Nach jedem Klick: NEU CACHEN (neuer Screen = neue Indices!)
1314
#
14-
# FLOW (dynamisch, E2E-verifiziert 2026-05-03, PID 89465):
15-
# 1. skylight klickt Google-Button (Hauptfenster, element 43)
16-
# 2. cua-driver findet Popup "Anmelden – Google Konten"
17-
# 3. Email eingeben (suche "E-Mail oder Telefonnummer" label)
18-
# 4. "Weiter" klicken (suche AXButton mit Label "Weiter")
19-
# 5. Account-Seite → "Fortfahren" suchen
20-
# 6. Consent-Seite → "Weiter" suchen
21-
# 7. Popup weg → HeyPiggy Dashboard!
15+
# ZWEI MOEGLICHE FLOWS (E2E-verifiziert 2026-05-03):
16+
# FLOW A — "Frischer Browser" (keine Google-Cookies):
17+
# 1. Google klicken (43) → Email-Screen → Email eingeben → Weiter
18+
# → Account-Bestätigung → Fortfahren → Consent → Weiter → Dashboard
19+
#
20+
# FLOW B — "Bereits eingeloggt" (Google-Cookies im Browser):
21+
# 1. Google klicken (43) → Konto-auswählen-Screen → Konto klicken
22+
# → Consent → Weiter → Dashboard
23+
#
24+
# E2E VERIFIZIERT:
25+
# FLOW A: PID 89465 (erster Login, Email eingegeben)
26+
# FLOW B: PID 89465 (zweiter Login, Account direkt gewählt)
2227
#
2328
# NUTZUNG:
2429
# playstealth launch --url 'https://heypiggy.com/?page=dashboard'
@@ -29,7 +34,11 @@
2934
# 1 = Kein PID
3035
# 2 = skylight Klick fehlgeschlagen
3136
# 3 = kein Google-Popup gefunden
32-
# 4-8 = spezifische Steps fehlgeschlagen
37+
# 4 = Screen 1 konnte nicht erkannt werden
38+
# 5 = Email-Eingabe fehlgeschlagen (Flow A)
39+
# 6 = Konto-Auswahl fehlgeschlagen (Flow B)
40+
# 7 = Flow A: Account/B Fortfahren-Step fehlgeschlagen
41+
# 8 = Flow B: Consent-Weiter fehlgeschlagen
3342
# 10 = Login fehlgeschlagen nach allen Retries
3443
# ============================================================
3544
set -euo pipefail
@@ -49,22 +58,78 @@ log() { echo "{\"step\":\"$1\",\"$2\"}" >&2; }
4958
# ════════════════════════════════════════════════════════════
5059
# Helper: Find element index by LABEL in tree_markdown
5160
# Robust gegen UI-Änderungen — sucht Label, nicht Index!
61+
# Supports: "Weiter" + "Continue", "Fortfahren" + "Continue"
5262
# ════════════════════════════════════════════════════════════
5363
find_element_by_label() {
5464
local label="$1"
55-
local role_pattern="${2:-AXButton|AXLink|AXTextField}" # default: alle interaktiven
65+
local role_pattern="${2:-AXButton|AXLink|AXTextField}"
5666
local tree="$3"
5767
echo "$tree" | python3 -c "
5868
import sys, re
5969
tree = sys.stdin.read()
60-
# Match: [NNN] role (label) oder [NNN] AXStaticText = \"label\"
6170
pattern = r'\[(\d+)\]\s+(' + role_pattern + r')[\s\"]+[\"(]*(' + re.escape(label) + r')[\")]*'
6271
matches = re.findall(pattern, tree)
6372
if matches:
6473
print(matches[0][0])
6574
" 2>/dev/null || echo ""
6675
}
6776

77+
# Fallback: generische Suche
78+
find_element_generic() {
79+
local label="$1"
80+
local tree="$2"
81+
echo "$tree" | python3 -c "
82+
import sys, re
83+
tree = sys.stdin.read()
84+
# Partial match
85+
pattern = r'\[(\d+)\]\s+\w+\s+[\"(]*([^\\\"\)]*' + re.escape(label) + r'[^\"\)]*)[\")]*'
86+
matches = re.findall(pattern, tree)
87+
if matches:
88+
print(matches[0][0])
89+
" 2>/dev/null || echo ""
90+
}
91+
92+
# ════════════════════════════════════════════════════════════
93+
# Helper: Detect Screen-1 type (Email vs Konto-auswählen)
94+
# Returns: "email" oder "konto" oder "unknown"
95+
# ════════════════════════════════════════════════════════════
96+
detect_screen1_type() {
97+
local tree="$1"
98+
# Check for email text field
99+
if echo "$tree" | python3 -c "import sys,re; t=sys.stdin.read(); print('yes' if re.search(r'AXTextField.*?E-Mail|E-Mail.*?AXTextField',t,re.I) else 'no')" | grep -q "yes"; then
100+
echo "email"
101+
# Check for account selection (has Google account names like "Jeremy Schulze")
102+
elif echo "$tree" | python3 -c "import sys,re; t=sys.stdin.read(); print('yes' if re.search(r'Jeremy Schulze|zukunftsorientierte|Anderes Konto',t) else 'no')" | grep -q "yes"; then
103+
echo "konto"
104+
else
105+
echo "unknown"
106+
fi
107+
}
108+
109+
# ════════════════════════════════════════════════════════════
110+
# Helper: Click element by label with retry
111+
# ════════════════════════════════════════════════════════════
112+
click_by_label() {
113+
local label="$1"
114+
local role="$2"
115+
local state="$3"
116+
local pid="$4"
117+
local wid="$5"
118+
local name="$6"
119+
120+
IDX=$(find_element_by_label "$label" "$role" "$state")
121+
[ -z "$IDX" ] && IDX=$(find_element_by_label "$label" "AXButton|AXLink" "$state")
122+
[ -z "$IDX" ] && IDX=$(find_element_by_label "$label" "AXLink" "$state")
123+
124+
if [ -z "$IDX" ]; then
125+
echo "{\"status\":\"error\",\"reason\":\"${name}_not_found\",\"label\":\"$label\"}"
126+
return 1
127+
fi
128+
129+
cua click "{\"pid\":$pid,\"window_id\":$wid,\"element_index\":$IDX,\"action\":\"press\"}" >/dev/null 2>&1
130+
echo "$IDX"
131+
}
132+
68133
# ════════════════════════════════════════════════════════════
69134
# SCHRITT 1: Google Login Button (Hauptfenster)
70135
# ════════════════════════════════════════════════════════════
@@ -86,7 +151,7 @@ d=json.load(sys.stdin)
86151
for w in d.get('windows',[]):
87152
if w.get('pid')==$PID and w.get('is_on_screen') and w.get('on_current_space'):
88153
if w.get('bounds',{}).get('width',0)>300:
89-
if 'anmelden' in w.get('title','').lower():
154+
if 'anmelden' in w.get('title','').lower() or 'accounts.google' in w.get('title','').lower():
90155
print(w['window_id']); break
91156
" || echo "")
92157

@@ -97,78 +162,95 @@ fi
97162
log "2_done" "window_id=$POPUP"
98163

99164
# ════════════════════════════════════════════════════════════
100-
# SCHRITT 3: Screen 1 — Email + "Weiter" (dynamisch)
165+
# SCHRITT 3: Screen 1 cache + detect flow type
101166
# ════════════════════════════════════════════════════════════
102-
log "3_cache" ""
167+
log "3_cache_screen1" ""
103168
STATE=$(cua get_window_state "{\"pid\":$PID,\"window_id\":$POPUP}" 2>/dev/null)
104169
sleep 1
105170

106-
# Email-TextField: suche "E-Mail" label
107-
IDX=$(find_element_by_label "E-Mail" "AXTextField" "$STATE")
108-
[ -z "$IDX" ] && IDX=$(find_element_by_label "Telefonnummer" "AXTextField" "$STATE")
171+
SCREEN_TYPE=$(detect_screen1_type "$STATE")
172+
log "3_screen_type" "type=$SCREEN_TYPE"
109173

110-
if [ -z "$IDX" ]; then
111-
echo "{\"status\":\"error\",\"reason\":\"email_field_not_found\",\"step\":\"3\"}"
112-
exit 4
113-
fi
114-
log "3_email" "index=$IDX"
115-
cua click "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX,\"action\":\"press\"}" >/dev/null 2>&1
116-
cua set_value "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX,\"value\":\"$EMAIL\"}" >/dev/null 2>&1
117-
118-
# "Weiter"-Button
119-
IDX=$(find_element_by_label "Weiter" "AXButton" "$STATE")
120-
if [ -z "$IDX" ]; then
121-
echo '{"status":"error","reason":"weiter_not_found","step":"3"}'
122-
exit 5
123-
fi
124-
log "4_weiter" "index=$IDX"
125-
cua click "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX,\"action\":\"press\"}" >/dev/null 2>&1
126-
log "4_done" ""
127-
sleep 5
174+
# ════════════════════════════════════════════════════════════
175+
# FLOW A: Email-Eingabe (Browser hat keine Google-Cookies)
176+
# ════════════════════════════════════════════════════════════
177+
if [ "$SCREEN_TYPE" = "email" ]; then
178+
log "3a_flow_email" "mode=flow_a"
179+
180+
# Email-TextField finden und füllen
181+
IDX=$(find_element_by_label "E-Mail" "AXTextField" "$STATE")
182+
[ -z "$IDX" ] && IDX=$(find_element_by_label "Telefonnummer" "AXTextField" "$STATE")
183+
[ -z "$IDX" ] && IDX=$(find_element_generic "E-Mail" "$STATE")
184+
185+
if [ -z "$IDX" ]; then
186+
echo '{"status":"error","reason":"email_field_not_found","step":"3a"}'
187+
exit 5
188+
fi
189+
log "3a_email_index" "index=$IDX"
190+
cua click "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX,\"action\":\"press\"}" >/dev/null 2>&1
191+
cua set_value "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX,\"value\":\"$EMAIL\"}" >/dev/null 2>&1
192+
193+
# "Weiter" klicken
194+
IDX_WEITER=$(click_by_label "Weiter" "AXButton" "$STATE" "$PID" "$POPUP" "weiter_s1")
195+
[ -z "$IDX_WEITER" ] && exit 5
196+
log "3a_weiter" "index=$IDX_WEITER"
197+
sleep 5
198+
199+
# ═══ FLOW A Step 2: Account-Bestätigung → "Fortfahren"
200+
STATE=$(cua get_window_state "{\"pid\":$PID,\"window_id\":$POPUP}" 2>/dev/null)
201+
sleep 1
202+
IDX_FORT=$(click_by_label "Fortfahren" "AXButton" "$STATE" "$PID" "$POPUP" "fortfahren")
203+
[ -z "$IDX_FORT" ] && IDX_FORT=$(click_by_label "Continue" "AXButton" "$STATE" "$PID" "$POPUP" "fortfahren")
204+
[ -z "$IDX_FORT" ] && exit 7
205+
log "4a_fortfahren" "index=$IDX_FORT"
206+
sleep 5
128207

129208
# ════════════════════════════════════════════════════════════
130-
# SCHRITT 4: Screen 2 — Account-Seite → "Fortfahren" (dynamisch)
209+
# FLOW B: Konto-auswählen (Browser hat Google-Cookies!)
131210
# ════════════════════════════════════════════════════════════
132-
log "5_cache" ""
133-
STATE=$(cua get_window_state "{\"pid\":$PID,\"window_id\":$POPUP}" 2>/dev/null)
134-
sleep 1
211+
elif [ "$SCREEN_TYPE" = "konto" ]; then
212+
log "3b_flow_konto" "mode=flow_b"
135213

136-
IDX=$(find_element_by_label "Fortfahren" "AXButton" "$STATE")
137-
[ -z "$IDX" ] && IDX=$(find_element_by_label "Continue" "AXButton" "$STATE")
138-
[ -z "$IDX" ] && IDX=$(find_element_by_label "Fortfahren" "AXLink" "$STATE")
214+
# Konto "Jeremy Schulze" finden und klicken
215+
IDX_KONTO=$(find_element_by_label "Jeremy Schulze" "AXLink" "$STATE")
216+
[ -z "$IDX_KONTO" ] && IDX_KONTO=$(find_element_by_label "zukunftsorientierte" "AXLink" "$STATE")
217+
[ -z "$IDX_KONTO" ] && IDX_KONTO=$(find_element_generic "Jeremy Schulze" "$STATE")
139218

140-
if [ -z "$IDX" ]; then
141-
echo "{\"status\":\"error\",\"reason\":\"fortfahren_not_found\",\"step\":\"5\"}"
142-
exit 6
219+
if [ -z "$IDX_KONTO" ]; then
220+
echo '{"status":"error","reason":"konto_not_found","step":"3b"}'
221+
exit 6
222+
fi
223+
log "3b_konto" "index=$IDX_KONTO"
224+
cua click "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX_KONTO,\"action\":\"press\"}" >/dev/null 2>&1
225+
sleep 5
226+
227+
# Refresh state
228+
STATE=$(cua get_window_state "{\"pid\":$PID,\"window_id\":$POPUP}" 2>/dev/null)
229+
sleep 1
143230
fi
144-
log "6_fortfahren" "index=$IDX"
145-
cua click "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX,\"action\":\"press\"}" >/dev/null 2>&1
146-
log "6_done" ""
147-
sleep 5
148231

149232
# ════════════════════════════════════════════════════════════
150-
# SCHRITT 5: Screen 3 — Consent-Seite → "Weiter" (dynamisch)
233+
# SCHRITT 4: Consent-Seite → "Weiter" (beide Flows enden hier)
151234
# ════════════════════════════════════════════════════════════
152-
log "7_cache" ""
235+
log "4_consent" "window_id=$POPUP"
153236
STATE=$(cua get_window_state "{\"pid\":$PID,\"window_id\":$POPUP}" 2>/dev/null)
154237
sleep 1
155238

156-
IDX=$(find_element_by_label "Weiter" "AXButton" "$STATE")
157-
[ -z "$IDX" ] && IDX=$(find_element_by_label "Continue" "AXButton" "$STATE")
239+
IDX_WEITER=$(click_by_label "Weiter" "AXButton" "$STATE" "$PID" "$POPUP" "consent_weiter")
240+
[ -z "$IDX_WEITER" ] && IDX_WEITER=$(click_by_label "Continue" "AXButton" "$STATE" "$PID" "$POPUP" "consent_weiter")
158241

159-
if [ -z "$IDX" ]; then
160-
echo '{"status":"error","reason":"consent_weiter_not_found","step":"7"}'
242+
if [ -z "$IDX_WEITER" ]; then
243+
echo "{\"status\":\"error\",\"reason\":\"consent_weiter_not_found\",\"step\":\"4\"}"
161244
exit 8
162245
fi
163-
log "8_consent" "index=$IDX"
164-
cua click "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX,\"action\":\"press\"}" >/dev/null 2>&1
165-
log "8_done" ""
246+
log "5_click_consent" "index=$IDX_WEITER"
247+
cua click "{\"pid\":$PID,\"window_id\":$POPUP,\"element_index\":$IDX_WEITER,\"action\":\"press\"}" >/dev/null 2>&1
166248
sleep 5
167249

168250
# ════════════════════════════════════════════════════════════
169-
# SCHRITT 6: Verifikation (3 Retries)
251+
# SCHRITT 5: Verifikation (3 Retries)
170252
# ════════════════════════════════════════════════════════════
171-
log "9_verify" ""
253+
log "6_verify" ""
172254
for attempt in 1 2 3; do
173255
sleep 4
174256
POPUP_CHECK=$(cua list_windows '{}' 2>/dev/null | python3 -c "
@@ -177,17 +259,17 @@ d=json.load(sys.stdin)
177259
for w in d.get('windows',[]):
178260
if w.get('pid')==$PID and w.get('is_on_screen') and w.get('on_current_space'):
179261
t=w.get('title','')
180-
if 'anmelden' in t.lower() or 'willkommen' in t.lower():
262+
if 'anmelden' in t.lower() or 'accounts.google' in t.lower():
181263
if w.get('bounds',{}).get('width',0)>300:
182264
print(w['window_id']); break
183265
" || echo "")
184266

185267
if [ -z "$POPUP_CHECK" ]; then
186-
log "9_done" "popup_gone=true,attempt=$attempt"
187-
echo "{\"status\":\"ok\",\"verified\":true,\"pid\":$PID,\"email\":\"$EMAIL\",\"method\":\"consent-flow-dynamic\",\"steps\":8}"
268+
log "6_done" "popup_gone=true,attempt=$attempt,flow=$SCREEN_TYPE"
269+
echo "{\"status\":\"ok\",\"verified\":true,\"pid\":$PID,\"email\":\"$EMAIL\",\"method\":\"consent-flow-dynamic\",\"screen_type\":\"$SCREEN_TYPE\",\"steps\":5}"
188270
exit 0
189271
fi
190-
log "9_retry" "popup=$POPUP_CHECK,attempt=$attempt"
272+
log "6_retry" "popup=$POPUP_CHECK,attempt=$attempt"
191273
done
192274

193275
echo "{\"status\":\"error\",\"reason\":\"popup_still_open\",\"pid\":$PID}"

commands.md

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,39 @@
11
# commands.md – Alle wichtigen Befehle
22

3-
## Google Login (Consent-Flow, E2E-verifiziert 2026-05-03)
3+
## Google Login (Dual-Flow Consent, E2E-verifiziert 2026-05-03)
44

55
```bash
6-
# Automatisiert (robust, label-basiert):
6+
# Automatisiert (robust, dual-flow detection):
77
bash cli/heypiggy-login <PID>
8-
# → {"status":"ok","verified":true,"pid":..., "method":"consent-flow-dynamic"}
8+
# → {"status":"ok","verified":true,"screen_type":"email","method":"consent-flow-dynamic"}
9+
# oder:
10+
# → {"status":"ok","verified":true,"screen_type":"konto","method":"consent-flow-dynamic"}
11+
```
12+
13+
### Zwei mögliche Flows (Google entscheidet dynamisch):
914

10-
# Manuell (nur zur Illustration — Script nutzt Label-Erkennung):
11-
# 1. Chrome starten
12-
playstealth launch --url 'https://heypiggy.com/?page=dashboard'
15+
**FLOW A — Frischer Browser (keine Google-Cookies):**
16+
1. Google klicken (element 43)
17+
2. Email eingeben (Label: "E-Mail")
18+
3. "Weiter" (Label)
19+
4. "Fortfahren" (Label)
20+
5. "Weiter" (Label) → Dashboard
1321

14-
# 2. Google Login klicken (Hauptfenster)
15-
skylight-cli click --pid <PID> --element-index 43
22+
**FLOW B — Bereits eingeloggt (Google-Cookies im Browser):**
23+
1. Google klicken (element 43)
24+
2. Konto "Jeremy Schulze" klicken (Label)
25+
3. "Weiter" (Label) → Dashboard
1626

17-
# 3. Popup finden (cua-driver)
18-
# 4. Email eingeben (Label: "E-Mail", nicht Index 25!)
19-
# 5. "Weiter" klicken (Label: "Weiter", nicht Index 35!)
20-
# 6. "Fortfahren" suchen (Label: "Fortfahren" oder "Continue")
21-
# 7. "Weiter" suchen (Label: "Weiter" oder "Continue")
22-
# → Dashboard!
27+
**detect_screen1_type()** im Script:
28+
```bash
29+
if "E-Mail" in tree → Flow A (Email-Eingabe)
30+
elif "Jeremy Schulze" in tree → Flow B (Konto-auswählen)
2331
```
2432
2533
### ⚠️ Robuster Login — KEINE fixen Indices!
2634
27-
**Google ändert UI ständig!** Script sucht nach Labels in `tree_markdown`:
28-
- "E-Mail" / "E-Mail oder Telefonnummer" (AXTextField)
29-
- "Weiter" / "Continue" (AXButton)
30-
- "Fortfahren" / "Continue" (AXButton)
31-
32-
| Pattern | Sucht nach |
33-
|---------|-----------|
34-
| `find_element_by_label("E-Mail" "AXTextField" tree)` | Email-TextField |
35-
| `find_element_by_label("Weiter" "AXButton" tree)` | "Weiter" Button |
36-
| `find_element_by_label("Fortfahren" "AXButton" tree)` | "Fortfahren" Button |
37-
38-
**NIE**: `element_index=35` hardcodieren — das bricht bei jedem Google-Update!
35+
**Google kann Flows KÜRZEN wenn Cookies gecached sind!**
36+
Immer `find_element_by_label()` + `detect_screen1_type()` nutzen.
3937
4038
## Survey Automation
4139

0 commit comments

Comments
 (0)