Selenium cannot log in to ZTE ZXHN H298A modem admin panel despite correct credentials

13 views
Skip to first unread message

Raysefo

unread,
Aug 19, 2025, 11:33:53 PMAug 19
to Selenium Users

I am trying to automate the login and PPP credential update process for a ZTE ZXHN H298A modem (firmware TTN.26_240416) using Selenium and Python.

The exact manual steps are:

Open the modem’s web interface at http://192.168.5.1. Enter username/password (admin / admin) and log in. Click the WAN tab at the top of the interface. Select WAN Connections from the left-hand menu. In the center panel, expand the INTERNET entry. Update PPP Username and PPP Password fields. Click Apply.

My goal is to perform these steps programmatically and reliably with Selenium, without manual interaction.

I am running the script like this:

py "Modem Refurbishment App/scripts/zte_ppp_selenium.py" \ --ip 192.168.5.1 \ --user admin \ --pass admin \ --headful \ --prelogin-delay-sec 10

Problem:

Even though I provide the correct credentials, Selenium fails to complete the login step. The modem’s admin panel remains at the login page.

I have checked:

Credentials are correct (I can log in manually). The login form fields are being filled by Selenium. The modem’s UI uses dynamic content loading (AJAX) after login. The script tries both JS-based and click-based form submission

Additional requirement: When running in headful mode, I pause after Selenium fills in the username and password so I can see the login screen with the credentials filled in inside the browser. In addition to this, I also want to be able to see the interface after a successful login — including the admin menu and subsequent navigation steps — inside the browser while the script continues executing.

Questions:

Are there any known quirks or anti-automation measures in ZTE modem firmware that would block Selenium login? Is there a reliable way in Selenium to handle this kind of dynamic login (possibly involving hidden tokens or delayed AJAX form handling)? Could the modem require additional request headers or cookies that Selenium is not sending by default?

Environment:

Python 3.x Selenium 4.x Chrome + matching ChromeDriver ZTE ZXHN H298A modem, firmware TTN.26_240416

Sample script:
#!/usr/bin/env python3
"""
ZTE ZXHN H298A – PPP dump/update using Selenium (UI flow mirroring the C# service)

Usage examples:
  Dump PPP only:
    py "Modem Refurbishment App/scripts/zte_ppp_selenium.py" --ip 192.168.5.1 --user admin --pass admin

  Update PPP and verify:
    py "Modem Refurbishment App/scripts/zte_ppp_selenium.py" --ip 192.168.5.1 --user admin --pass admin --ppp-user NEW --ppp-pass NEWPASS

Notes:
- Uses Chrome; requires a compatible chromedriver on PATH
- Follows the modem UI: navigate → login → WAN → expand INTERNET → read/update PPP → apply
"""
from __future__ import annotations

import argparse
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Optional, Tuple

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException, NoSuchWindowException


def save_debug(prefix: str, html: str) -> None:
    try:
        p = Path(f"debug_{prefix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html")
        p.write_text(html or "", encoding="utf-8", errors="ignore")
        print(f"Saved {p}")
    except Exception:
        pass


def wait_find(driver, by, selector, timeout=8):
    return WebDriverWait(driver, timeout).until(EC.presence_of_element_located((by, selector)))


def any_present(driver, selectors, timeout=6) -> Optional[Tuple[By, str]]:
    end = time.time() + timeout
    while time.time() < end:
        for by, sel in selectors:
            try:
                els = driver.find_elements(by, sel)
                if els:
                    return by, sel
            except Exception:
                continue
        time.sleep(0.2)
    return None


def switch_to_admin_frame(driver) -> bool:
    """Ensure Selenium context is inside the admin UI frame that hosts the ZTE shell.

    Strategy:
    - Always reset to top-level document first
    - Look for admin markers in the top document
    - If not found, scan all top-level iframes; if still not found, scan one nested level
    """
    try:
        driver.switch_to.default_content()
    except Exception:
        pass

    def has_admin_markers() -> bool:
        try:
            return bool(driver.find_elements(By.CSS_SELECTOR, "#page_container, #class2MenuItem, #mainNavigator"))
        except Exception:
            return False

    # Top-level check
    if has_admin_markers():
        return True

    # BFS over frames up to depth 2
    try:
        top_frames = driver.find_elements(By.TAG_NAME, 'iframe')
    except Exception:
        top_frames = []

    for f in top_frames:
        try:
            driver.switch_to.frame(f)
            if has_admin_markers():
                return True
            # search one nested level
            try:
                inner_frames = driver.find_elements(By.TAG_NAME, 'iframe')
            except Exception:
                inner_frames = []
            for inner in inner_frames:
                try:
                    driver.switch_to.frame(inner)
                    if has_admin_markers():
                        return True
                finally:
                    try:
                        driver.switch_to.parent_frame()
                    except Exception:
                        pass
        finally:
            try:
                driver.switch_to.default_content()
            except Exception:
                pass
    return False


def is_internet_ui_present(driver) -> bool:
    """Detect markers of the INTERNET WAN template being present in the DOM."""
    try:
        if driver.find_elements(By.CSS_SELECTOR, "div[id^='template_Internet'], [id^='instName_Internet'], div[id^='changeArea_Internet']"):
            return True
        # Fallback: quick page_source scan to catch server-side rendered variants
        html = driver.page_source or ""
        return ('instName_Internet' in html) or ('template_Internet' in html) or ('Internet_Eth_t.lp' in html)
    except Exception:
        return False


def click_login_button(driver) -> bool:
    """Click the login button, preferring labels like 'Oturum Aç' and common variants.

    Returns True if a click was attempted, else False.
    """
    candidates: list[Tuple[By, str]] = [
        (By.CSS_SELECTOR, "#LoginId"),
        # Turkish labels
        (By.XPATH, "//input[(translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='submit' or translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='button') and (contains(@value,'Oturum Aç') or contains(@value,'Oturum') or contains(@value,'Giriş'))]"),
        (By.XPATH, "//button[contains(.,'Oturum Aç') or contains(.,'Oturum') or contains(.,'Giriş')]") ,
        # English fallbacks
        (By.XPATH, "//input[(translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='submit' or translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='button') and (contains(@value,'Login') or contains(@value,'Sign In') or contains(@value,'Log In'))]"),
        (By.XPATH, "//button[contains(.,'Login') or contains(.,'Sign In') or contains(.,'Log In')]") ,
        # Generic
        (By.CSS_SELECTOR, "input[type='submit'], input[type='button'], button[type='submit']"),
    ]
    for by, sel in candidates:
        try:
            btns = driver.find_elements(by, sel)
            if not btns:
                continue
            btn = btns[0]
            try:
                driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn)
            except Exception:
                pass
            try:
                btn.click()
            except Exception:
                try:
                    driver.execute_script("arguments[0].click();", btn)
                except Exception:
                    continue
            # brief settle
            time.sleep(0.2)
            return True
        except Exception:
            continue
    return False


def _select_main_content_window(driver, main_handle: Optional[str], base_url: Optional[str]) -> None:
    """Switch to the main content window (not DevTools), preferring main_handle, else matching base_url.

    DevTools windows use the devtools:// scheme. We avoid those.
    """
    try:
        handles = driver.window_handles
    except Exception:
        return
    # Prefer known main handle if still present
    if main_handle and main_handle in handles:
        try:
            driver.switch_to.window(main_handle)
            return
        except Exception:
            pass
    # Else, pick a handle whose URL matches base_url and is not devtools
    for h in handles:
        try:
            driver.switch_to.window(h)
            url = ""
            try:
                url = driver.current_url or ""
            except Exception:
                url = ""
            if url.startswith("devtools://"):
                continue
            if base_url and url.startswith(base_url):
                return
        except Exception:
            continue
    # Fallback: first non-devtools window
    for h in handles:
        try:
            driver.switch_to.window(h)
            url = driver.current_url or ""
            if not url.startswith("devtools://"):
                return
        except Exception:
            continue


def assert_window_alive(driver, context: str = "", main_handle: Optional[str] = None, base_url: Optional[str] = None) -> None:
    """Ensure the browser window/session is still alive; exit cleanly if closed/crashed."""
    try:
        handles = driver.window_handles
        if not handles:
            save_debug("window_closed_" + (context or "unknown"), "")
            sys.exit("Browser window is closed; aborting")
        _select_main_content_window(driver, main_handle, base_url)
    except (NoSuchWindowException, WebDriverException):
        save_debug("window_closed_" + (context or "unknown"), "")
        sys.exit("Browser window is closed; aborting")


def enforce_wan_page(driver, base_url: str, wait_seconds: float = 6.0) -> bool:
    """Force-load the WAN INTERNET page using modem JS handlers, then fallback to direct URL.

    Returns True if INTERNET UI markers appear, else False.
    """
    # Ensure we are in the admin frame first
    switch_to_admin_frame(driver)

    # Ensure cookie support in this context
    try:
        driver.execute_script("document.cookie='_TESTCOOKIESUPPORT=1; path=/'")
    except Exception:
        pass

    # Read token from current context
    try:
        tok = driver.execute_script("return window._sessionTmpToken || window._sessionTOKEN || window.sessionToken || window.g_loginToken || '';") or ''
    except Exception:
        tok = ''

    # Attempt via firmware AJAX handler(s) with token if available (avoid template_csrf to prevent 404s)
    ajax_scripts = [
        """
        try{
          var tok = arguments[0] || (window._sessionTmpToken || window._sessionTOKEN || window.sessionToken || '');
          try{ if (tok){ window._sessionTmpToken = tok; } }catch(e){}
          if (typeof AjaxPageGet==='function'){
            var url = 'getpage.lua?pid=123&nextpage=Internet_Eth_t.lp&Menu3Location=0' + (tok?('&_sessionTOKEN='+tok):'') + '&_=' + Date.now();
            AjaxPageGet(url, '2');
          }
        }catch(e){}
        """,
        """
        try{
          var tok = arguments[0] || (window._sessionTmpToken || window._sessionTOKEN || window.sessionToken || '');
          if (typeof AjaxPageGet==='function'){
            var url = 'getpage.lua?pid=1002&nextpage=Internet_Eth_t.lp' + (tok?('&_sessionTOKEN='+tok):'') + '&_=' + Date.now();
            AjaxPageGet(url, '2');
          }
        }catch(e){}
        """,
    ]
    for js in ajax_scripts:
        try:
            driver.execute_script(js, tok)
        except Exception:
            pass
        # brief wait to allow AJAX render
        end = time.time() + 2.0
        while time.time() < end:
            if is_internet_ui_present(driver):
                return True
            time.sleep(0.2)
        switch_to_admin_frame(driver)

    # Fallback: direct navigation within current frame
    try:
        driver.execute_script("var tok=arguments[1]||''; window.location.href = arguments[0] + '/getpage.lua?pid=123&nextpage=Internet_Eth_t.lp&Menu3Location=0' + (tok?('&_sessionTOKEN='+tok):'') + '&_=' + Date.now();", base_url, tok)
    except Exception:
        try:
            url = base_url + "/getpage.lua?pid=123&nextpage=Internet_Eth_t.lp&Menu3Location=0"
            if tok:
                url += "&_sessionTOKEN=" + tok
            url += "&_=" + str(int(time.time()*1000))
            driver.get(url)
        except Exception:
            pass

    # Wait for presence
    deadline = time.time() + wait_seconds
    while time.time() < deadline:
        switch_to_admin_frame(driver)
        if is_internet_ui_present(driver):
            return True
        time.sleep(0.3)
    return False


def main() -> None:
    ap = argparse.ArgumentParser(description="Dump/Update PPP via Selenium (ZTE H298A)")
    ap.add_argument("--ip", required=True)
    ap.add_argument("--user", required=True)
    ap.add_argument("--pass", dest="password", required=True)
    ap.add_argument("--ppp-user", dest="ppp_user")
    ap.add_argument("--ppp-pass", dest="ppp_pass")
    ap.add_argument("--headful", action="store_true")
    ap.add_argument("--devtools", action="store_true", help="Open Chrome DevTools for tabs when headful")
    ap.add_argument("--debug-delays", action="store_true", help="Insert 6-7 second pauses at key steps for manual inspection")
    ap.add_argument("--debug-delay-sec", type=int, default=7, help="Pause duration in seconds when --debug-delays is set")
    ap.add_argument("--prelogin-delay-sec", type=int, default=7, help="Pause before entering credentials (seconds). Set 0 to skip")
    ap.add_argument("--no-auto-login", action="store_true", help="Do not auto-fill/submit login; hold on login page for manual testing")
    ap.add_argument("--show-typing", action="store_true", help="Use Selenium typing to show credentials being entered")
    ap.add_argument("--type-slow-ms", type=int, default=0, help="Delay in ms between keystrokes when --show-typing is used")
    ap.add_argument("--maximize", action="store_true", help="Maximize window on start (headful only)")
    ap.add_argument("--detach", action="store_true", help="Keep browser window open after script exits (headful only)")
    ap.add_argument("--hold-open-end", action="store_true", help="Keep browser open at end until manually closed")
    args = ap.parse_args()

    base = f"http://{args.ip}"

    # Chrome options
    opts = webdriver.ChromeOptions()
    if not args.headful:
        opts.add_argument("--headless=new")
    opts.add_argument("--disable-gpu")
    opts.add_argument("--no-sandbox")
    opts.add_argument("--window-size=1400,900")
    if args.headful and args.devtools:
        opts.add_argument("--auto-open-devtools-for-tabs")
    if args.headful and args.detach:
        try:
            opts.add_experimental_option("detach", True)
        except Exception:
            pass

    try:
        driver = webdriver.Chrome(options=opts)
    except WebDriverException as e:
        print(f"Failed to start ChromeDriver: {e}")
        sys.exit(1)

    driver.set_page_load_timeout(15)
    if args.headful and args.maximize:
        try:
            driver.maximize_window()
        except Exception:
            pass

    try:
        # helper for conditional debug sleep
        def dbg_sleep():
            if args.debug_delays:
                try:
                    time.sleep(max(1, int(args.debug_delay_sec)))
                except Exception:
                    time.sleep(7)

        # 1) Navigate to modem and set cookie support
        driver.get(base + "/")
        main_handle = None
        try:
            main_handle = driver.current_window_handle
        except Exception:
            main_handle = None
        assert_window_alive(driver, "after_get", main_handle, base)
        try:
            print(f"[Info] Landed URL: {driver.current_url}")
        except Exception:
            pass
        save_debug("at_landing", driver.page_source)
        dbg_sleep()
        try:
            driver.execute_script("document.cookie = '_TESTCOOKIESUPPORT=1; path=/';")
        except Exception:
            pass

        # 2) Login
        assert_window_alive(driver, "before_login_fields", main_handle, base)
        # Optional fixed pause before interacting with login form
        try:
            if args.prelogin_delay_sec and args.prelogin_delay_sec > 0:
                time.sleep(args.prelogin_delay_sec)
        except Exception:
            pass

        try:
            usr = wait_find(driver, By.CSS_SELECTOR, "#Frm_Username, input[name='Frm_Username'], input[name='username']", timeout=10)
            pwd = wait_find(driver, By.CSS_SELECTOR, "#Frm_Password, input[name='Frm_Password'], input[type='password']", timeout=10)
        except TimeoutException:
            # Ensure we're not on DevTools window while saving
            _select_main_content_window(driver, main_handle, base)
            save_debug("login_fields_missing", driver.page_source)
            sys.exit("Login fields not found")

        # Optional: hold at login page for manual inspection without auto-filling
        if args.no_auto_login:
            save_debug("at_login_hold", driver.page_source)
            # Keep the browser open for inspection, then exit
            try:
                time.sleep(max(1, int(args.debug_delay_sec)))
            except Exception:
                time.sleep(7)
            sys.exit("Held at login as requested (--no-auto-login)")

        # Helper for slow typing
        def _type_slow(el, text: str, delay_ms: int = 0):
            try:
                el.clear()
            except Exception:
                pass
            if delay_ms and delay_ms > 0:
                for ch in text:
                    el.send_keys(ch)
                    time.sleep(delay_ms / 1000.0)
            else:
                el.send_keys(text)

        # Fill via JS (ensures disabled/readonly removed), then invoke firmware login handler if present
        dbg_sleep()
        assert_window_alive(driver, "before_login_submit", main_handle, base)
        did_submit = False
        if not args.show_typing:
            try:
                driver.execute_script(
                    """
                    try{
                      var u = arguments[0], p = arguments[1], usr=arguments[2], pwd=arguments[3];
                      if(u){ u.removeAttribute('disabled'); u.value=''; if(u.type==='text'||!u.type){ u.value = usr; } else { try{ u.setAttribute('type','text'); u.value=usr; }catch(e){} } }
                      if(p){ p.removeAttribute('disabled'); p.value=''; p.setAttribute('type','password'); p.value = pwd; }
                      // prefer calling modem's login function to ensure tokenized submit
                      if (typeof doLogin === 'function'){
                        doLogin();
                      } else if (typeof LoginFormObj !== 'undefined' && LoginFormObj && typeof LoginFormObj.doLogin === 'function'){
                        try{ if (window._sessionTmpToken){ template_csrf && template_csrf(window._sessionTmpToken); } }catch(e){}
                        LoginFormObj.doLogin();
                      } else {
                        var btn = document.getElementById('LoginId') || document.querySelector("input#LoginId, input[name='LoginId'], input[type='submit'], input[type='button'], button[type='submit']");
                        if (btn) btn.click();
                        else if (p && p.form) p.form.submit();
                      }
                    }catch(e){}
                    """,
                    usr, pwd, args.user, args.password
                )
                did_submit = True
            except Exception:
                did_submit = False
        if args.show_typing or not did_submit:
            try:
                driver.execute_script("arguments[0].removeAttribute('disabled');", usr)
            except Exception:
                pass
            try:
                driver.execute_script("arguments[0].removeAttribute('disabled'); arguments[0].setAttribute('type','password');", pwd)
            except Exception:
                pass
            _type_slow(usr, args.user, args.type_slow_ms)
            _type_slow(pwd, args.password, args.type_slow_ms)
            # Click login button with robust finder
            if not click_login_button(driver):
                try:
                    if hasattr(pwd, 'submit'):
                        pwd.submit()
                except Exception:
                    pass

        # allow spinner/login processing to be visible
        dbg_sleep()
        assert_window_alive(driver, "after_login_submit", main_handle, base)

        # If no navigation or request seems to fire, force final submit paths
        try:
            driver.execute_script(
                """
                try{
                  // set cookie support again
                  try{ document.cookie='_TESTCOOKIESUPPORT=1; path=/'; }catch(e){}
                  var tok = window._sessionTmpToken || window._sessionTOKEN || window.sessionToken || '';
                  // ensure hidden token input is present if form needs it
                  try{
                    var f = (document.querySelector('#Frm_Username, input[name=Frm_Username], input[name=username]')||{}).form || (document.querySelector('#Frm_Password, input[name=Frm_Password], input[type=password]')||{}).form || document.querySelector('form');
                    if (f){
                      var hid = f.querySelector('input[name=\"_sessionTOKEN\"]');
                      if (!hid){ hid = document.createElement('input'); hid.type='hidden'; hid.name='_sessionTOKEN'; f.appendChild(hid); }
                      if (tok){ hid.value = tok; }
                      // fire typical events to satisfy validators
                      var u = document.querySelector('#Frm_Username, input[name=Frm_Username], input[name=username]');
                      var p = document.querySelector('#Frm_Password, input[name=Frm_Password], input[type=password]');
                      ['input','change','keyup','keydown','blur'].forEach(function(ev){ try{ u && u.dispatchEvent(new Event(ev,{bubbles:true})); }catch(e){} try{ p && p.dispatchEvent(new Event(ev,{bubbles:true})); }catch(e){} });
                      // set IsAllowSubmit gate
                      try{ window.IsAllowSubmit = true; }catch(e){}
                      // Firmware observed pre-login token GET: invoke via button context
                      try{
                        if (typeof $ !== 'undefined' && $.fn && $("#LoginId").length && $("#LoginId").dataTransfer){
                           $("#LoginId").dataTransfer("/function_module/login_module/login_page/logintoken_lua.lua", "GET", (window.g_loginToken||tok||''), undefined, false);
                        }
                      }catch(e){}
                      // attempt known login functions again
                      if (typeof doLogin==='function'){ doLogin(); }
                      else if (typeof Login==='function'){ Login(); }
                      else if (typeof loginSubmit==='function'){ loginSubmit(); }
                      else if (typeof doSubmit==='function'){ doSubmit(); }
                      else {
                        try{ f.dispatchEvent(new Event('submit',{bubbles:true,cancelable:true})); }catch(e){}
                        try{ f.submit(); }catch(e){}
                      }
                    }
                  }catch(e){}
                }catch(e){}
                """
            )
        except Exception:
            pass
        time.sleep(0.5)
        save_debug("after_login_trigger", driver.page_source)

        # Wait for admin UI
        logged_in = False
        for _ in range(20):
            try:
                if driver.find_elements(By.CSS_SELECTOR, "#mainNavigator, #Menu, #page_container"):
                    logged_in = True
                    break
            except Exception:
                pass
            time.sleep(0.5)
        if not logged_in:
            # Detect lockout or error messages
            html = driver.page_source or ''
            if ('300' in html and 'saniye' in html.lower()):
                save_debug("login_locked_wait_300", html)
                sys.exit("Login locked by modem: wait 300 seconds before retrying")
            save_debug("login_failed", html)
            sys.exit("Login did not complete")

        # 3) Ensure we are in admin UI context (handle frames)
        _select_main_content_window(driver, main_handle, base)
        switch_to_admin_frame(driver)
        save_debug("after_login", driver.page_source)
        dbg_sleep()

        # 4) Click top WAN/Genel Ağ
        top_candidates = [
            (By.CSS_SELECTOR, "#mmInternet"),
            (By.XPATH, "//a[contains(., 'Genel Ağ') or contains(., 'WAN')]")
        ]
        found = any_present(driver, top_candidates, timeout=6)
        assert_window_alive(driver, "before_top_wan_click", main_handle, base)
        if found:
            try:
                driver.find_element(*found).click()
            except Exception:
                try:
                    driver.execute_script("try{ var e=document.getElementById('mmInternet'); if(e&&typeof AjaxQuery_ClassMenuClick==='function'){ try{ AjaxQuery_ClassMenuClick($(e)); }catch(err){ AjaxQuery_ClassMenuClick(e); } } }catch(err){}")
                except Exception:
                    pass
        save_debug("after_top_wan", driver.page_source)
        dbg_sleep()

        # 5) Click left WAN Bağlantıları
        try:
            WebDriverWait(driver, 6).until(EC.presence_of_element_located((By.CSS_SELECTOR, "#class2MenuItem, #Class2Menu")))
        except TimeoutException:
            pass
        left_candidates = [
            (By.CSS_SELECTOR, "#smInternetCof"),
            (By.XPATH, "//a[contains(., 'WAN Bağlantıları')]")
        ]
        left = any_present(driver, left_candidates, timeout=6)
        assert_window_alive(driver, "before_left_wan_click", main_handle, base)
        if left:
            try:
                driver.find_element(*left).click()
            except Exception:
                try:
                    driver.execute_script("try{ var a=document.getElementById('smInternetCof'); if(a) a.click(); }catch(err){}")
                except Exception:
                    pass
        else:
            # Generate the submenu if missing
            try:
                driver.execute_script("try{ if (typeof Class2MenuShow==='function'){ Class2MenuShow('', 'mmInternet', 'smInternetCof'); } }catch(err){}")
            except Exception:
                pass

        # Ensure we are still in the admin frame after submenu click
        switch_to_admin_frame(driver)
        save_debug("after_left_wan", driver.page_source)
        dbg_sleep()

        # Force-load WAN page if INTERNET markers not present yet
        if not is_internet_ui_present(driver):
            dbg_sleep()
            assert_window_alive(driver, "before_enforce_wan", main_handle, base)
            enforced = enforce_wan_page(driver, base)
            save_debug("after_enforce_wan", driver.page_source)
            dbg_sleep()
            if not enforced:
                # Try a second enforcement pass after small delay
                time.sleep(0.6)
                enforced = enforce_wan_page(driver, base)
                save_debug("after_enforce_wan_retry", driver.page_source)
                dbg_sleep()

        # 6) Wait for center INTERNET template and expand INTERNET
        try:
            WebDriverWait(driver, 8).until(EC.presence_of_element_located((By.CSS_SELECTOR, "div[id^='template_Internet'], [id^='instName_Internet']")))
        except TimeoutException:
            # If still missing, attempt enforcement once more
            if not is_internet_ui_present(driver):
                enforce_wan_page(driver, base)
        # Expand INTERNET header and show ChangeArea
        try:
            driver.execute_script(
                """
                try{
                  var inst = document.querySelector("[id^='instName_Internet']");
                  if (inst){ inst.click(); inst.classList.add('instNameExp'); }
                  var t = document.querySelector("div[id^='template_Internet']") || document.querySelector("div[id^='template_Internet_']") || document.querySelector("div[id^='template_Internet:']");
                  if (t){ var ca=t.querySelector('.ChangeArea'); if (ca){ ca.style.display='block'; ca.style.visibility='visible'; } var h=t.querySelector('.collapsibleInst, .instName'); if (h){ h.classList.add('instNameExp'); } }
                }catch(e){}
                """
            )
        except Exception:
            pass

        time.sleep(0.6)

        # Derive INTERNET suffix if present (e.g., instName_Internet:3)
        suffix = None
        try:
            suffix = driver.execute_script(
                """
                try{
                  var s = document.querySelector("span[id^='instName_Internet']");
                  if (s && s.id && s.id.indexOf(':')>0) return s.id.split(':')[1];
                  var c = document.querySelector("div[id^='changeArea_Internet']");
                  if (c && c.id && c.id.indexOf(':')>0) return c.id.split(':')[1];
                }catch(e){}
                return null;
                """
            )
        except Exception:
            suffix = None

        # 7) Locate PPP inputs
        def find_ppp_fields(suf: Optional[str]):
            user = None
            pwd = None
            # Try dynamic ids first
            if suf:
                try:
                    user = driver.find_element(By.CSS_SELECTOR, f"#UserName\\:{suf}")
                except NoSuchElementException:
                    user = None
                try:
                    pwd = driver.find_element(By.CSS_SELECTOR, f"#Password\\:{suf}")
                except NoSuchElementException:
                    pwd = None
            if user is None:
                try:
                    user = driver.find_element(By.CSS_SELECTOR, "[id^='UserName:']")
                except NoSuchElementException:
                    try:
                        user = driver.find_element(By.CSS_SELECTOR, "#Frm_PPPUserName, input[name='Frm_PPPUserName']")
                    except NoSuchElementException:
                        user = None
            if pwd is None:
                try:
                    pwd = driver.find_element(By.CSS_SELECTOR, "[id^='Password:']")
                except NoSuchElementException:
                    try:
                        pwd = driver.find_element(By.CSS_SELECTOR, "#Frm_PPPPassword, input[name='Frm_PPPPassword']")
                    except NoSuchElementException:
                        pwd = None
            return user, pwd

        # Also try to identify inputs via label 'for'
        def find_via_label():
            try:
                lbl = driver.find_elements(By.CSS_SELECTOR, "label[for^='UserName:'], label[for='Frm_PPPUserName']")
                if lbl:
                    tgt = lbl[0].get_attribute('for') or ''
                    if tgt:
                        try:
                            return driver.find_element(By.ID, tgt)
                        except Exception:
                            return None
            except Exception:
                return None
            return None

        u_el, p_el = find_ppp_fields(suffix)
        if u_el is None:
            via = find_via_label()
            if via is not None:
                u_el = via
        if u_el is None and p_el is None:
            # Try clicking Modify inside template to reveal edit fields
            try:
                driver.execute_script(
                    """
                    try{
                      var el = document.querySelector("[id^='UserName:']") || document.querySelector('#Frm_PPPUserName');
                      if (el){ var t=el.closest("div[id^='template_Internet']")||el.closest("div[id^='template_Internet_']")||el.closest("div[id^='template_Internet:']");
                        if (t){ var m=t.querySelector("input[id^='Btn_modify'], .Btn_modify, input[type='button'][value*='Düzenle']"); if (m) m.click();
                          var ca=t.querySelector('.ChangeArea'); if (ca){ ca.style.display='block'; ca.style.visibility='visible'; }
                        }
                      }
                    }catch(e){}
                    """
                )
            except Exception:
                pass
            # Also try explicit modify id with suffix
            if suffix:
                try:
                    btnm = driver.find_elements(By.CSS_SELECTOR, f"#Btn_modify_internet\\:{suffix}")
                    if btnm:
                        btnm[0].click()
                except Exception:
                    pass
            time.sleep(0.4)
            u_el, p_el = find_ppp_fields(suffix)
            if u_el is None:
                via = find_via_label()
                if via is not None:
                    u_el = via

        # Dump current values
        dbg_sleep()
        cur_user = ''
        cur_pass = ''
        try:
            if u_el:
                cur_user = u_el.get_attribute('value') or driver.execute_script("return arguments[0].value || arguments[0].defaultValue || '';", u_el) or ''
            if p_el:
                cur_pass = p_el.get_attribute('value') or driver.execute_script("return arguments[0].value || arguments[0].defaultValue || '';", p_el) or ''
            if not cur_user and suffix:
                cur_user = driver.execute_script("try{ var u=document.getElementById('UserName:'+arguments[0]); return u? (u.value||u.defaultValue||'') : ''; }catch(e){return ''}", suffix) or ''
        except Exception:
            pass

        # Fallback: parse page_source for PPP user if inputs returned empty
        if not cur_user:
            try:
                html = driver.page_source or ''
                import re as _re
                # Try dynamic id UserName:<n>
                m = _re.search(r'id=["\']UserName:(\d+)["\'][^>]*?value=["\']([^"\']*)["\']', html, flags=_re.IGNORECASE)
                if m:
                    cur_user = m.group(2) or ''
                if not cur_user:
                    # Try Frm_PPPUserName
                    m2 = _re.search(r'(?:id|name)=["\']Frm_PPPUserName["\'][^>]*?value=["\']([^"\']*)["\']', html, flags=_re.IGNORECASE)
                    if m2:
                        cur_user = m2.group(1) or ''
                if not cur_user:
                    # Read-only display fields (common ZTE patterns)
                    m3 = _re.search(r'id=["\']UserNameR:(\d+)["\'][^>]*?>\s*([^<\s][^<]*)<', html, flags=_re.IGNORECASE)
                    if m3:
                        cur_user = (m3.group(2) or '').strip()
                if not cur_user:
                    # Label-near value (Turkish/English)
                    m4 = _re.search(r'(Kullanıcı\s*Adı|User\s*Name)[^<]*</[^>]+>\s*<[^>]+>\s*([^<\s][^<]*)<', html, flags=_re.IGNORECASE)
                    if m4:
                        cur_user = (m4.group(2) or '').strip()
            except Exception:
                pass
        print(f"PPP username (current): {cur_user}")

        # If update requested
        if args.ppp_user and args.ppp_pass and (u_el or p_el):
            try:
                if u_el:
                    driver.execute_script("arguments[0].value='';", u_el)
                    u_el.clear(); u_el.send_keys(args.ppp_user)
                if p_el:
                    driver.execute_script("arguments[0].value='';", p_el)
                    p_el.clear(); p_el.send_keys(args.ppp_pass)
            except Exception:
                pass
            # Attempt to click Apply within template
            applied = False
            apply_selectors = [
                (By.CSS_SELECTOR, "input[id^='Btn_apply_internet:']"),
                (By.CSS_SELECTOR, ".Btn_apply"),
                (By.XPATH, "//input[@type='button' or @type='submit'][contains(@value,'Uygula')]")
            ]
            for by, sel in apply_selectors:
                try:
                    btns = driver.find_elements(by, sel)
                    if btns:
                        try:
                            driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btns[0])
                        except Exception:
                            pass
                        btns[0].click()
                        applied = True
                        break
                except Exception:
                    continue
            if not applied:
                save_debug("wan_apply_missing", driver.page_source)
                sys.exit("Could not find Apply button")
            time.sleep(1.0)
            # Optional: accept confirm if shown
            try:
                confirm = driver.find_elements(By.CSS_SELECTOR, "#confirmOK, .confirmOK")
                if confirm:
                    confirm[0].click()
            except Exception:
                pass

            print("Apply clicked (check device UI for success)")

        save_debug("wan_ppp_final", driver.page_source)
        if args.hold_open_end and args.headful:
            print("[Info] Holding browser open at end (--hold-open-end). Close the window to exit.")
            try:
                while True:
                    time.sleep(1.0)
            except KeyboardInterrupt:
                pass

    finally:
        try:
            driver.quit()
        except Exception:
            pass


if __name__ == "__main__":
    main()





Reply all
Reply to author
Forward
0 new messages