Home > Mobile >  Selenium - How can I click the next item in a list with a For loop?
Selenium - How can I click the next item in a list with a For loop?

Time:06-10

I'm very new to programming so apologies in advance if I'm not communicating my issue clearly.

Essentially, using Selenium I have created a list of elements on a webpage by finding all the elements with the same class name I'm looking for.

In this case, I'm finding songs, which have the html class 'item-song' on this website.

On the website, there are lots of clickable options for each listed song . I just want to click the title of the song, which opens a popup modal window in which I edit the note attached to the song, then click save, which closes the popup.

I have successfully been able to do that by using what I guess would be called the title’s XPATH 'relative' to the song class.

songs = driver.find_elements(By.CLASS_NAME, "item-song")

songs[0].find_element(By.XPATH, "div[5]/a").click()
# other code that ends by closing popup

This works, hooray! It also works for any other list index that I put in that line of code.

However, it does not work sequentially, or in a for loop.

i.e.

songs[0].find_element(By.XPATH, "div[5]/a").click()
# other code
time.sleep(5) # to ensure the popup has finished closing

songs[1].find_element(By.XPATH, "div[5]/a").click()

Does not work.

for song in songs:
    song.find_element(By.XPATH, "div[5]/a").click()
    # other code
    time.sleep(5)
    continue

Also does not work.

I get a traceback error:

StaleElementReferenceException: Message: stale element reference: element is not attached to the page document

After going back to the original page, the song does now say note(1) so I suppose the site has changed slightly. But as far as I can tell, the 'songs' list object and the xpath for the title of the next song should be exactly the same. To verify this, I even tried:

for song in songs:
    print(song)
    print(songs)
    print()
    song.find_element(By.XPATH, "div[5]/a").click()
    # other code

Sure enough, on the first iteration, print(song) matched the first index of print(songs) and on the second iteration, print(song) matches the second index of print(songs). And print(songs) is identical both times. (Only prints twice as the error happens halfway through the second iteration)

Any help is greatly appreciated, I'm stumped!

---------------------------------

Edit: Of course, it would be easier if my songs list could be all the song titles instead of the class ‘item-song’, that was what I was trying first. However I couldn’t find anything common between the titles in the HTML that would let me use find_elements to just get the song title element, as each song has a different title, and there are also other items like videos listed in between each song.

CodePudding user response:

Through the comments, the solution is to use an iterative loop and an xpath.

songs = driver.find_elements(By.CLASS_NAME, "item-song")
for i in range(songs.count):
   driver.find_element(By.XPATH, "(//*[@class='item-song']["   i   "])/div[5]/a").click()

Breaking this down:

  • this: By.XPATH, "//*[@class='item-song']" is the same as this: By.CLASS_NAME, "item-song". The former is the xpath equivalent of the latter. I did this so we can build a single identification string to the link instead of trying to find elements within elements.
  • The [" i "] is the iteration for the the loop. If you were to print this you'd see (//*[@class='item-song'][1])") then (//*[@class='item-song'][2])"). That [x] is the ordinal identifier - it means the xth instance of the element in the DOM. The brackets around it ensure the entire thing is matched for the next part - you can sometimes get unexpected matches without it.
  • The last part /div[5]/a is just the original solution. Doing div[5] isn't great. Your link must ALWAYS be inside the 5th div else it will fail - but as i can't see your application I can't comment on another way.

The original approach throws a StaleElementReferenceException because of the way Selenium stores identified elements.

Once you've identified an element by doing driver.find_elements(By.CLASS_NAME, "item-song") Selenium essentially captures a reference to it - it doesn't store the identifier you used. Stick a break point and after you identify an element and you'll see something like this: quickwatchdotnet

That image is from visual studio as I have it hand but you can see it's a GUID on the ID.

Once you change the page that reference is lost.

Repeat the same steps, identify the same object and the ID is unique every time. This is same break point, same element on a second test run: dotnet2

Page has changed == Selenium can no longer find it == Stale element.

The solution in this answer works because we're not storing an element. Every action in the loop freshly identifies the element.

..Then add some clever pun about fresh vs stale... ;-)

  • Related