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'
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# ============================================================
3544set -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# ════════════════════════════════════════════════════════════
5363find_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 "
5868import sys, re
5969tree = sys.stdin.read()
60- # Match: [NNN] role (label) oder [NNN] AXStaticText = \" label\"
6170pattern = r'\[(\d+)\]\s+(' + role_pattern + r')[\s\" ]+[\" (]*(' + re.escape(label) + r')[\" )]*'
6271matches = re.findall(pattern, tree)
6372if 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)
86151for 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
97162log " 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 " " "
103168STATE=$( cua get_window_state " {\" pid\" :$PID ,\" window_id\" :$POPUP }" 2> /dev/null)
104169sleep 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
143230fi
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 "
153236STATE=$( cua get_window_state " {\" pid\" :$PID ,\" window_id\" :$POPUP }" 2> /dev/null)
154237sleep 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
162245fi
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
166248sleep 5
167249
168250# ════════════════════════════════════════════════════════════
169- # SCHRITT 6 : Verifikation (3 Retries)
251+ # SCHRITT 5 : Verifikation (3 Retries)
170252# ════════════════════════════════════════════════════════════
171- log " 9_verify " " "
253+ log " 6_verify " " "
172254for 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)
177259for 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 "
191273done
192274
193275echo " {\" status\" :\" error\" ,\" reason\" :\" popup_still_open\" ,\" pid\" :$PID }"
0 commit comments