1

I have a site that has multiple input fields containing URLs with the same value (X) that I want to replace with (Y). Basically, I want to automate replacing URL X with URL Y for all occurrences of URL X. There is an edit and save button to the right of each of these input fields. And each time you click save an alert pops up for each field you update. This is in short, the entire workflow I need to automate.

I'm at the point in my code that I can do it successfully for one field, but when I use a for loop it just updates one element/field twice and not the rest. FYI - I limited my for loop to two iterations using itertools.islice intentionally since I want to limit my testing to a small subset before I do a full run.

Below is a screenshot of what the page looks like: HTML

New error:


C:\Users\me\OneDrive\Documents\Python\Web page entry automation\preferences_v3 copy.py:21: DeprecationWarning: use options instead of chrome_options   chrome_browser = webdriver.Chrome(service=service, chrome_options=chrome_options)

DevTools listening on ws://127.0.0.1:61787/devtools/browser/155908c1-8be2-40a1-85bf-27c44bf25506 Traceback (most recent call last):   File "C:\Users\me\OneDrive\Documents\Python\Web page entry automation\preferences_v3 copy.py", line 90, in <module>
    auto_ftp_url()   File "C:\Users\me\OneDrive\Documents\Python\Web page entry automation\preferences_v3 copy.py", line 57, in auto_ftp_url
    number = elem.get_attribute("id").split("_")[-1]
             ^^^^^^^^^^^^^^^^^^^^^^^^   File "C:\Users\me\AppData\Roaming\Python\Python311\site-packages\selenium\webdriver\remote\webelement.py", line 177, in get_attribute
    attribute_value = self.parent.execute_script(
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^   File "C:\Users\me\AppData\Roaming\Python\Python311\site-packages\selenium\webdriver\remote\webdriver.py", line 500, in execute_script
    return self.execute(command, {"script": script, "args": converted_args})["value"]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   File "C:\Users\me\AppData\Roaming\Python\Python311\site-packages\selenium\webdriver\remote\webdriver.py", line 440, in execute
    self.error_handler.check_response(response)   File "C:\Users\me\AppData\Roaming\Python\Python311\site-packages\selenium\webdriver\remote\errorhandler.py", line 245, in check_response
    raise exception_class(message, screen, stacktrace) selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document  (Session info: chrome=109.0.5414.120) Stacktrace: Backtrace:
        (No symbol) [0x00506643]
        (No symbol) [0x0049BE21]
        (No symbol) [0x0039DA9D]
        (No symbol) [0x003A09E4]
        (No symbol) [0x003A08AD]
        (No symbol) [0x003A12E5]
        (No symbol) [0x004079F7]
        (No symbol) [0x003EFD7C]
        (No symbol) [0x00406B09]
        (No symbol) [0x003EFB76]
        (No symbol) [0x003C49C1]
        (No symbol) [0x003C5E5D]
        GetHandleVerifier [0x0077A142+2497106]
        GetHandleVerifier [0x007A85D3+2686691]
        GetHandleVerifier [0x007ABB9C+2700460]
        GetHandleVerifier [0x005B3B10+635936]
        (No symbol) [0x004A4A1F]
        (No symbol) [0x004AA418]
        (No symbol) [0x004AA505]
        (No symbol) [0x004B508B]
        BaseThreadInitThunk [0x771300F9+25]
        RtlGetAppContainerNamedObjectPath [0x77847BBE+286]
        RtlGetAppContainerNamedObjectPath [0x77847B8E+238]

PS C:\Users\me\OneDrive\Documents\Python\Web page entry automation> [15148:25476:0201/123239.502:ERROR:device_event_log_impl.cc(215)] [12:32:39.502] USB: usb_service_win.cc:415 Could not read device interface GUIDs: The system cannot find the file specified. (0x2) [15148:25476:0201/123239.557:ERROR:device_event_log_impl.cc(215)] [12:32:39.557] USB: usb_device_handle_win.cc:1046 Failed to read descriptor from node connection: A device attached to the system is not functioning. (0x1F) [15148:25476:0201/123239.575:ERROR:device_event_log_impl.cc(215)] [12:32:39.575] USB: usb_device_handle_win.cc:1046 Failed to read descriptor from node connection: A device attached to the system is not functioning. (0x1F)

Below is my code:

from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.alert import Alert
import itertools


def auto_ftp_url():
    username, password = login()

    chrome_options = Options()
    chrome_options.add_experimental_option("detach", True)

    service = Service(
        r"C:\Users\me\OneDrive\Documents\chromedriver_win32\chromedriver.exe"
    )

    chrome_browser = webdriver.Chrome(service=service, chrome_options=chrome_options)
    chrome_browser.get("https://somelink")

    # send text to username field
    userElem = chrome_browser.find_element(by="id", value="Username")
    userElem.click()
    userElem.send_keys(f"{username}")

    # send password to password field
    passwordElem = chrome_browser.find_element(by="id", value="Password")
    passwordElem.click()
    passwordElem.send_keys(f"{password}")

    # click login
    loginElem = chrome_browser.find_element(by="id", value="Button1")
    loginElem.click()

    # add some delay (2 seconds) to the webdriver so that the elements will load
    chrome_browser.implicitly_wait(2)

    # click on Admin button on left navigation pane
    adminElem = chrome_browser.find_element(By.XPATH, "//*[@id='btnAdminPage']")
    adminElem.click()

    # click on Data FTP button in the top center
    data_ftp_Elem = chrome_browser.find_element(
        By.XPATH, "//*[@id='menu-outer']/div[12]/a/div"
    )
    data_ftp_Elem.click()

    # find FTP URL with a value of 'aftersoftna.com'
    ftp_url_Elem = chrome_browser.find_elements(
        By.XPATH, "//*[@value='aftersoftna.com']"
    )
    for elem in itertools.islice(ftp_url_Elem, 2):
        if elem:  # if the value is 'aftersoftna.com' then do the following
            # click edit button
            editElem = chrome_browser.find_element(
                By.XPATH, "//*[@id='ContentPlaceHolder1_gvFTP_btnSave_7']"
            )
            editElem.click()
            # use WebDriverWait to wait for the element to be clickable
            textElem = WebDriverWait(chrome_browser, 20).until(
                EC.element_to_be_clickable(
                    (By.XPATH, "//*[@id='ContentPlaceHolder1_gvFTP_txtFtpUrl_7']")
                )
            )
            textElem.click()
            textElem.clear()
            textElem.send_keys(
                "catalog.com"
            )  # after clearing the old URL, enter the new URL
            # click save
            saveElem = chrome_browser.find_element(
                By.XPATH, "//*[@id='ContentPlaceHolder1_gvFTP_btnSave_7']"
            )
            saveElem.click()
            # popup or alert message comes up confirming changes saved. click ok
            WebDriverWait(chrome_browser, 10).until(EC.alert_is_present())
            chrome_browser.switch_to.alert.accept()


def login():
    username = input("Username: ")
    password = input("Password: ")
    return username, password


auto_ftp_url()

1 Answers1

0

Without seeing the page it's impossible to know but I'm going to guess that it's because you are using the same locators for editElem, textElem, and saveElem for each URL. Each of them end in "_7" but I would assume that there are multiple elements on the page, one set of three for each URL from what you are describing, e.g. "_8", "_9", etc. If this is true then your locators need to be relative to the URL that you want to edit.

If I were to guess even further, you should be able to get your list of ftp_url_Elem elements, grab the ID from each in the loop, and then extract the final number, e.g. 7 from ContentPlaceHolder1_gvFTP_txtFtpUrl_7, and then extrapolate that to the other ID, ContentPlaceHolder1_gvFTP_btnSave_7.

To get the magic number that corresponds to the row number, we can

  1. Grab the first ID using elem.get_attribute("id"), ContentPlaceHolder1_gvFTP_txtFtpUrl_7
  2. Split that ID by "_" resulting in ['ContentPlaceHolder1','gvFTP','txtFtpUrl','7']
  3. Grab the last instance from that list which is the desired row number, '7'
  4. Now we can append this number to the "prefix" of the ID for the other elements to get the correct element on the matching row of the table, e.g. ID prefix for the Edit/Save button, ContentPlaceHolder1_gvFTP_btnSave_, and add '7' to get ContentPlaceHolder1_gvFTP_btnSave_7

Then you just repeat that last step for all the elements on that row that you need to click.

Here's all the code as an example

for elem in itertools.islice(ftp_url_Elem, 2):
    if elem:  # if the value is 'aftersoftna.com' then do the following
        # get row number from element ID
        row_number = elem.get_attribute("id").split("_")[-1]
        # click edit button
        editElem = chrome_browser.find_element(
            By.XPATH, "//*[@id='ContentPlaceHolder1_gvFTP_btnSave_" + row_number + "']"
        )
        ... and so on

If this is enough to point you in the right direction to fix it yourself, great. If not, please post a few example rows of HTML that contain the edit, URL field, and save button so we can create code and relative locators.

JeffC
  • 22,180
  • 5
  • 32
  • 55
  • Hey so I like your idea, I'm just not sure how to apply that or make my loop work in that way. If you can please elaborate that would be great! But you are correct, that the numbers go up for each of those 3 elements. If there is a way to just append those numbers through each iteration of the loop that would be great. However, not all URL fields need to be updated. Only the ones that have ftp.aftersoftna.com need to be replaced. I've updated my post to include a screenshot, please let me know your thoughts! – Kevin Agbulos Feb 01 '23 at 16:20
  • The code I posted demonstrates what I was talking about. I'll add some more explanation... one sec – JeffC Feb 01 '23 at 16:25
  • I've added some more explanation as to how I get the row number from the ID of an element and then use that to create IDs for the other elements on the row. I've added the first few lines of the code with my updates. You should be able to see the pattern from that and apply that process to any other elements you need to click on that row. – JeffC Feb 01 '23 at 16:33
  • Got it, thanks for your explanation. However, this is not working on the second iteration of the loop. I get the below error: selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document. FYI - I've edited my post so you can see my updated code according to your advice! Please let me know your thoughts. I also have a screenshot in my post so you can see how the page looks with HTML. – Kevin Agbulos Feb 01 '23 at 16:45
  • A `StaleElementReferenceException` means that an element you are interacting with is no longer on the page/has been refreshed/updated. I'm guessing that the page refreshes after you click Save or something like that. It's possible that refetching `ftp_url_Elem` inside the loop will fix it? I would remove the `ftp_url_Elem = ...` line and replace the line after that with `for elem in chrome_browser.find_elements(By.XPATH, "//*[@value='ftp.aftersoftna.com']")[:2]:` and see if that works. – JeffC Feb 01 '23 at 16:52
  • I tried that and it still throws the same error. I've tried using a chrome_browser.implicitly_wait(2) at the end of my code in the for loop to give it time before going to the second iteration, but it doesn't work. Also, am I right in doing number += 1 at the end of my loop? Each of the 3 elements go up by 1 in at the end of the ID so hopefully I'm right in that thinking. Not sure how else to address this error? Also, do I need to add any conditionals to not overwrite URL fields I don't care about? I only care about the ftp.aftersoftna.com ones that need to be updated/replaced. – Kevin Agbulos Feb 01 '23 at 17:02
  • `number += 1` should not be there. We're dynamically getting the correct number on the `row_number = elem.get_attribute("id").split("_")[-1]` line. Also, you don't need to convert the string `number` to `int`. That just means you'll have to convert it back every time you use it to build a new locator. – JeffC Feb 01 '23 at 17:04
  • What line is the `StaleElementReferenceException` being thrown? – JeffC Feb 01 '23 at 17:05
  • Ok i've removed the number += 1. I just don't understand how the loop will know to go to the next row without the ID being incremented by 1 at the end of the 3 elements/buttons? Please see my updated post, I included the full error. – Kevin Agbulos Feb 01 '23 at 17:38
  • At this point you should post a new question with the new details. I've fixed the initial problem with my answer and continuing to update the question with new code updates is really against the rules because the question keeps changing making the older answers irrelevant. Please roll back your question to the original, accept this answer, and then post a new question with the updated code, error message, and other relevant info. – JeffC Feb 01 '23 at 17:54
  • The loop isn't directly controlling the number. The loop goes through the elements on the page and as I said above, we get the new ID from that new element each time we reenter the loop so that's the way the ID gets "incremented". Imagine if row 2, 5, 6 were the rows that need updating. We don't want to +1 the number each time. We get the first element's ID (2), and then interact with the "2" elements. Next time through we get the element's ID (5), and then interact with the "5" elements, and so on. – JeffC Feb 01 '23 at 17:57
  • 1
    Jeff, thank you for all your help and your replies! I can't tell you enough how much I appreciate it. I will go ahead and log a new question with the new info we've been discussing so far and I hope to get some answers there! – Kevin Agbulos Feb 01 '23 at 18:04
  • Here's a link to my new post if you're still willing to help! https://stackoverflow.com/questions/75314463/selenium-error-using-python-stale-element-reference-element-is-not-attached-t – Kevin Agbulos Feb 01 '23 at 18:14
  • I'll take a look in a minute. Make sure you remove the updated code from your question and mark this as the answer. – JeffC Feb 01 '23 at 18:15