@@ -368,289 +368,135 @@ def select_first_available_time(self):
368368
369369 def select_first_available_time_resilient (self , max_attempts = 3 ):
370370 """
371- Versión con debugging extremo para diagnosticar problemas en CI/CD .
372- Captura estado completo de la página en cada intento .
371+ Versión mejorada que maneja el caso cuando no hay horarios disponibles .
372+ Si no encuentra horarios, intenta con otras fechas automáticamente .
373373
374374 :param max_attempts: Número de intentos (default: 3)
375375 :return: El texto de la hora seleccionada
376376 """
377377 import os
378378 import time
379379
380- # Timeout más largo en CI/CD
381380 timeout = 30 if os .getenv ('CI' ) else 20
382381
383- logger .debug ("=" * 60 )
384- logger .debug ("🔍 INICIO DEBUG - select_first_available_time_resilient" )
385- logger .debug (f"Ambiente: { 'CI/CD' if os .getenv ('CI' ) else 'LOCAL' } " )
386- logger .debug (f"Timeout configurado: { timeout } s" )
387- logger .debug ("=" * 60 )
388-
389382 for attempt in range (max_attempts ):
390383 try :
391- logger .debug (f"\n { '=' * 60 } " )
392- logger .debug (f"[RETRY] 🔄 Intento { attempt + 1 } /{ max_attempts } " )
393- logger .debug (f"{ '=' * 60 } " )
394-
395- # ========== PASO 1: Verificar URL actual ==========
396- current_url = self .driver .current_url
397- logger .debug (f"📍 URL actual: { current_url } " )
384+ logger .debug (f"[RETRY] Intento { attempt + 1 } /{ max_attempts } de seleccionar horario" )
398385
399- # ========== PASO 2: Esperar que la página termine de cargar ==========
400- logger .debug ("⏳ Esperando document.readyState = 'complete'..." )
386+ # Esperar que la página termine de cargar
401387 WebDriverWait (self .driver , 10 ).until (
402388 lambda d : d .execute_script ('return document.readyState' ) == 'complete'
403389 )
404- logger .debug ("✅ document.readyState = 'complete'" )
405390
406- # ========== PASO 3: Verificar estado de fecha seleccionada ==========
407- try :
408- selected_date = self .driver .execute_script ("""
409- const selected = document.querySelector('button[class*="selected"], button[aria-pressed="true"]');
410- return selected ? selected.innerText : 'No date selected';
411- """ )
412- logger .debug (f"📅 Fecha seleccionada: { selected_date } " )
413- except Exception as e :
414- logger .warning (f"⚠️ No se pudo verificar fecha seleccionada: { e } " )
415-
416- # ========== PASO 4: Buscar contenedor de horarios ==========
417- logger .debug ("🔍 Buscando contenedor de horarios en el DOM..." )
418- try :
419- time_container_info = self .driver .execute_script ("""
420- const selectors = [
421- '[class*="time"]',
422- '[class*="schedule"]',
423- '[class*="hour"]',
424- 'section',
425- '[data-times]',
426- '[data-schedule]'
427- ];
428-
429- for (let selector of selectors) {
430- const container = document.querySelector(selector);
431- if (container) {
432- return {
433- selector: selector,
434- found: true,
435- innerHTML_length: container.innerHTML.length,
436- innerHTML_preview: container.innerHTML.substring(0, 300),
437- visible: container.offsetParent !== null
438- };
439- }
440- }
441- return { found: false };
442- """ )
443-
444- if time_container_info .get ('found' ):
445- logger .debug (f"✅ Contenedor encontrado con selector: { time_container_info ['selector' ]} " )
446- logger .debug (f" - Visible: { time_container_info ['visible' ]} " )
447- logger .debug (f" - Longitud HTML: { time_container_info ['innerHTML_length' ]} chars" )
448- logger .debug (f" - Preview: { time_container_info ['innerHTML_preview' ][:150 ]} ..." )
449- else :
450- logger .warning ("⚠️ NO se encontró contenedor de horarios en el DOM" )
451- except Exception as e :
452- logger .error (f"❌ Error al buscar contenedor: { e } " )
453-
454- # ========== PASO 5: Verificar loaders activos ==========
455- logger .debug ("🔍 Verificando loaders activos..." )
456- try :
457- loaders = self .driver .execute_script ("""
458- const loadingSelectors = [
459- '.loading', '.spinner', '.loader',
460- '[class*="load"]', '[class*="spinner"]',
461- '[data-loading]', '[data-loading="true"]'
462- ];
463-
464- let activeLoaders = [];
465- for (let selector of loadingSelectors) {
466- const elements = document.querySelectorAll(selector);
467- if (elements.length > 0) {
468- activeLoaders.push({
469- selector: selector,
470- count: elements.length
471- });
472- }
473- }
474- return activeLoaders;
475- """ )
476-
477- if loaders and len (loaders ) > 0 :
478- logger .warning (f"⚠️ Se encontraron { len (loaders )} tipos de loaders activos:" )
479- for loader in loaders :
480- logger .warning (f" - { loader ['selector' ]} : { loader ['count' ]} elementos" )
481- else :
482- logger .debug ("✅ No hay loaders activos" )
483- except Exception as e :
484- logger .error (f"❌ Error al verificar loaders: { e } " )
485-
486- # ========== PASO 6: Intentar eliminar loaders ==========
391+ # Scroll y espera
392+ self .driver .execute_script ("window.scrollTo(0, 500);" )
393+ time .sleep (2 )
394+
395+ # Intentar eliminar loaders
487396 try :
488- logger .debug ("🧹 Intentando eliminar loaders..." )
489397 WebDriverWait (self .driver , 5 ).until_not (
490398 EC .presence_of_element_located (
491399 (By .CSS_SELECTOR , ".loading, .spinner, [class*='load'], [class*='spinner']" ))
492400 )
493- logger .debug ("✅ Loaders eliminados o no presentes" )
494401 except :
495- logger .debug ("⏭️ Timeout esperando eliminación de loaders (continuando...)" )
496-
497- # ========== PASO 7: Scroll al área de horarios ==========
498- logger .debug ("📜 Ejecutando scroll..." )
499- self .driver .execute_script ("window.scrollTo(0, 500);" )
500- time .sleep (2 )
501- logger .debug ("✅ Scroll ejecutado" )
402+ pass
502403
503- # ========== PASO 8: Contar botones de horario ANTES del wait ==========
504- logger .debug ("🔍 Contando botones de horario ANTES del WebDriverWait..." )
505- try :
506- button_count = self .driver .execute_script ("""
507- const buttons = document.querySelectorAll('button');
508- const timeButtons = Array.from(buttons).filter(btn => {
509- const text = btn.innerText || btn.textContent;
510- return text.includes(':') && (text.includes('AM') || text.includes('PM'));
511- });
512-
513- return {
514- total_buttons: buttons.length,
515- time_buttons: timeButtons.length,
516- sample_buttons: timeButtons.slice(0, 3).map(b => ({
517- text: b.innerText,
518- visible: b.offsetParent !== null,
519- enabled: !b.disabled,
520- classes: b.className
521- }))
522- };
523- """ )
524-
525- logger .debug (f"📊 Botones en página:" )
526- logger .debug (f" - Total de botones: { button_count ['total_buttons' ]} " )
527- logger .debug (f" - Botones de horario: { button_count ['time_buttons' ]} " )
528-
529- if button_count ['time_buttons' ] > 0 :
530- logger .debug (f" - Muestra de botones encontrados:" )
531- for i , btn in enumerate (button_count ['sample_buttons' ], 1 ):
532- logger .debug (
533- f" { i } . '{ btn ['text' ]} ' - Visible: { btn ['visible' ]} , Enabled: { btn ['enabled' ]} " )
534- else :
535- logger .warning ("⚠️ NO se encontraron botones de horario en el DOM aún" )
536-
537- except Exception as e :
538- logger .error (f"❌ Error al contar botones: { e } " )
539-
540- # ========== PASO 9: WebDriverWait con logging ==========
541- logger .debug (f"⏳ Iniciando WebDriverWait ({ timeout } s) para botones de horario..." )
542- start_wait = time .time ()
543-
544- time_buttons = WebDriverWait (self .driver , timeout ).until (
545- EC .presence_of_all_elements_located ((
546- By .XPATH ,
547- "//button[contains(text(), ':') and (contains(text(), 'AM') or contains(text(), 'PM'))]"
548- ))
404+ # NUEVO: Verificar si hay botones de horario ANTES del wait largo
405+ time_buttons = self .driver .find_elements (
406+ By .XPATH ,
407+ "//button[contains(text(), ':') and (contains(text(), 'AM') or contains(text(), 'PM'))]"
549408 )
550409
551- elapsed_wait = time .time () - start_wait
552- logger .debug (f"✅ WebDriverWait completado en { elapsed_wait :.2f} s" )
410+ # Si NO hay botones, intentar con otra fecha
411+ if len (time_buttons ) == 0 :
412+ logger .warning (f"⚠️ No hay horarios para la fecha actual, probando otra fecha..." )
553413
554- # ========== PASO 10: Validar botones encontrados ==========
555- if not time_buttons :
556- logger .error ("❌ time_buttons está vacío después del wait" )
557- raise Exception ("No se encontraron horarios disponibles (lista vacía)" )
414+ # Obtener todas las fechas disponibles
415+ date_buttons = self .driver .find_elements (
416+ By .XPATH ,
417+ "//button[contains(@class, 'date') or @data-date or contains(text(), '/')]"
418+ )
558419
559- logger .debug (f"✅ Se encontraron { len (time_buttons )} botones de horario" )
420+ if len (date_buttons ) == 0 :
421+ # Si no hay botones de fecha, buscar por cualquier botón clickeable
422+ date_buttons = self .driver .find_elements (
423+ By .XPATH ,
424+ "//section//button[not(contains(text(), ':'))]"
425+ )
560426
561- # ========== PASO 11: Intentar click en primer botón ==========
562- for idx , button in enumerate (time_buttons ):
563- try :
564- is_displayed = button .is_displayed ()
565- is_enabled = button .is_enabled ()
427+ logger .debug (f" Fechas disponibles: { len (date_buttons )} " )
566428
567- logger .debug (f"🔍 Botón { idx + 1 } : displayed={ is_displayed } , enabled={ is_enabled } " )
429+ # Probar con las siguientes 3 fechas
430+ for idx , date_btn in enumerate (date_buttons [1 :4 ], 1 ):
431+ try :
432+ logger .debug (f" Probando fecha #{ idx } ..." )
568433
569- if is_displayed and is_enabled :
570- # Scroll al elemento
571- self .driver .execute_script ("arguments[0].scrollIntoView({block: 'center'});" , button )
434+ # Scroll al botón de fecha
435+ self .driver .execute_script ("arguments[0].scrollIntoView({block: 'center'});" , date_btn )
572436 time .sleep (0.5 )
573437
574- button_text = button .text .strip ()
575- logger .debug (f"🎯 Intentando click en: '{ button_text } '" )
576-
577- # Click con JavaScript como fallback
438+ # Click en la fecha
578439 try :
579- button .click ()
580- logger .debug ("✅ Click con .click() exitoso" )
440+ date_btn .click ()
581441 except :
582- self .driver .execute_script ("arguments[0].click();" , button )
583- logger .debug ("✅ Click con JavaScript exitoso" )
442+ self .driver .execute_script ("arguments[0].click();" , date_btn )
443+
444+ time .sleep (3 ) # Esperar que carguen los horarios
445+
446+ # Verificar si ahora hay horarios
447+ time_buttons = self .driver .find_elements (
448+ By .XPATH ,
449+ "//button[contains(text(), ':') and (contains(text(), 'AM') or contains(text(), 'PM'))]"
450+ )
451+
452+ if len (time_buttons ) > 0 :
453+ logger .info (f"✅ Fecha #{ idx } tiene { len (time_buttons )} horarios disponibles" )
454+ break
455+ else :
456+ logger .debug (f" Fecha #{ idx } sin horarios, siguiente..." )
457+
458+ except Exception as e :
459+ logger .debug (f" Error con fecha #{ idx } : { e } " )
460+ continue
461+
462+ # Si TODAVÍA no hay botones después de probar fechas, usar el wait original
463+ if len (time_buttons ) == 0 :
464+ logger .debug ("Esperando horarios con WebDriverWait..." )
465+ time_buttons = WebDriverWait (self .driver , timeout ).until (
466+ EC .presence_of_all_elements_located ((
467+ By .XPATH ,
468+ "//button[contains(text(), ':') and (contains(text(), 'AM') or contains(text(), 'PM'))]"
469+ ))
470+ )
584471
585- logger .info (f"✅ Horario '{ button_text } ' seleccionado en intento { attempt + 1 } " )
586- logger .debug ("=" * 60 )
587- logger .debug ("🎉 FIN DEBUG - Selección exitosa" )
588- logger .debug ("=" * 60 )
589- return button_text
472+ # Validar que hay botones
473+ if not time_buttons :
474+ raise Exception ("No se encontraron horarios disponibles." )
590475
591- except Exception as e :
592- logger .warning (f"⚠️ Error al procesar botón { idx + 1 } : { type (e ).__name__ } - { str (e )} " )
593- continue
476+ # Seleccionar primer botón habilitado
477+ for button in time_buttons :
478+ if button .is_displayed () and button .is_enabled ():
479+ self .driver .execute_script ("arguments[0].scrollIntoView({block: 'center'});" , button )
480+ time .sleep (0.5 )
594481
595- raise Exception ( "No se encontró ningún botón de hora habilitado." )
482+ button_text = button . text . strip ( )
596483
597- except Exception as e :
598- error_type = type (e ).__name__
599- error_msg = str (e )
484+ try :
485+ button .click ()
486+ except :
487+ self .driver .execute_script ("arguments[0].click();" , button )
600488
601- logger .warning (f"\n { '=' * 60 } " )
602- logger .warning (f"⚠️ INTENTO { attempt + 1 } FALLÓ" )
603- logger .warning (f"{ '=' * 60 } " )
604- logger .warning (f"Error: { error_type } " )
605- logger .warning (f"Mensaje: { error_msg [:200 ]} " )
489+ logger .info (f"✅ Horario '{ button_text } ' seleccionado en intento { attempt + 1 } " )
490+ return button_text
606491
607- # ========== CAPTURA ADICIONAL EN FALLO ==========
492+ raise Exception ("No se encontró ningún botón de hora habilitado." )
493+
494+ except Exception as e :
495+ logger .warning (f"⚠️ Intento { attempt + 1 } falló: { type (e ).__name__ } " )
608496 if attempt < max_attempts - 1 :
609- try :
610- # Capturar screenshot path (solo en CI)
611- if os .getenv ('CI' ):
612- logger .debug ("📸 Screenshot disponible en artifacts del workflow" )
613-
614- # Verificar mensajes de error en la página
615- logger .debug ("🔍 Buscando mensajes de error en la página..." )
616- error_elements = self .driver .execute_script ("""
617- const errorTexts = [];
618- const errorSelectors = ['[class*="error"]', '[class*="alert"]', '[role="alert"]'];
619-
620- for (let selector of errorSelectors) {
621- const elements = document.querySelectorAll(selector);
622- elements.forEach(el => {
623- if (el.innerText.trim()) {
624- errorTexts.push(el.innerText.trim().substring(0, 100));
625- }
626- });
627- }
628- return errorTexts;
629- """ )
630-
631- if error_elements :
632- logger .warning (f"⚠️ Mensajes de error en página:" )
633- for msg in error_elements [:3 ]:
634- logger .warning (f" - { msg } " )
635-
636- # Capturar HTML del body para análisis
637- body_html = self .driver .execute_script ("""
638- return document.body.innerHTML.substring(0, 500);
639- """ )
640- logger .debug (f"📄 HTML Body preview: { body_html [:200 ]} ..." )
641-
642- except Exception as debug_error :
643- logger .error (f"❌ Error en captura de debug: { debug_error } " )
644-
645- logger .debug (f"⏳ Esperando 3s antes del reintento..." )
646497 time .sleep (3 )
647498 else :
648- logger .error ("\n " + "=" * 60 )
649- logger .error ("❌ TODOS LOS INTENTOS FALLARON" )
650- logger .error ("=" * 60 )
651- logger .error (f"Error final: { error_type } " )
652- logger .error (f"Mensaje: { error_msg } " )
653- logger .error ("=" * 60 )
499+ logger .error ("❌ Todos los intentos fallaron" )
654500 raise
655501
656502 def is_seat_grid_displayed (self ):
0 commit comments