-3

I have written a webscraping script that automatically logs into my Email account and sends a message.

Code:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

myPassword = 'XXXXXXXXXXXXXX!'

browser = webdriver.Firefox() # Opens Firefox webbrowser
browser.get('https://protonmail.com/') # Go to protonmail website
loginButton = WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, "a.btn-ghost:nth-child(1)")))
loginButton.click()
usernameElem = WebDriverWait(browser, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#username")))
usernameElem.send_keys("first.last@protonmail.com")
passwordElem = browser.find_element_by_css_selector("#password")
passwordElem.send_keys(myPassword)
anmeldenButton = browser.find_element_by_css_selector(".button")
anmeldenButton.click()
newMessage = WebDriverWait(browser, 10).until(EC.element_to_be_clickable((By.XPATH, "/html/body/div[1]/div[3]/div/div/div[1]/div[2]/button")))
newMessage.click()
addressElem = WebDriverWait(browser, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "input[id^='to-composer']")))
addressElem.send_keys('first.last@mail.com')
subjectElem = WebDriverWait(browser, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "input[id^='subject-composer']")))
subjectElem.send_keys('anySubject')
WebDriverWait(browser, 20).until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR, "iframe[title='Editor']")))
WebDriverWait(browser, 20).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div#squire"))).send_keys('Test message')
sendButton = WebDriverWait(browser, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "body > div.app-root > div.composer-container > div > div > div > footer > div > div.button-group.button-group-solid-norm.button-group-medium > button > span")))
sendButton.click()
browser.switch_to.default_content()

I wanted the browser to click the submit button after input of the message.

enter image description here

I have copied the CSS selector of the span element and tried this:

sendButton = WebDriverWait(browser, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "body > div.app-root > div.composer-container > div > div > div > footer > div > div.button-group.button-group-solid-norm.button-group-medium > button > span")))
sendButton.click()

I get the following error:

selenium.common.exceptions.NoSuchWindowException: Message: Browsing context has been discarded

I am not sure, if I targeted the right element for what I want to achieve. I often struggle to find the right strategy to target an element. What is the best way if there is no id element oder a nice and short css selector? I checked the Selenium docs (https://selenium-python.readthedocs.io/locating-elements.html) but hesitated choosing one of the suggested methods.

This is an HTML snipped fo the submit button:

<footer data-testid="composer:footer" class="composer-actions flex-item-noshrink flex max-w100">
  <div class="flex flex-row-reverse flex-align-self-center w100 ml0-5 mr1-5 pl1-25 pr0-25 mb1">
    <div class="button-group button-group-solid-norm button-group-medium" data-testid="composer:send-actions"><button class="button button-group-item button-ghost-weak composer-send-button" aria-busy="false" type="button" data-testid="composer:send-button" aria-describedby="tooltip-19621"><svg viewBox="0 0 16 16" class="icon-16p no-desktop no-tablet on-mobile-flex" role="img" focusable="false"><use xlink:href="#ic-paper-plane"></use></svg><span class="pl1 pr1 no-mobile">Senden</span></button></div>
    <div
      class="flex flex-item-fluid">
      <div class="flex"><button class="button button-for-icon button-ghost-weak mr0-5" aria-busy="false" type="button" data-testid="composer:delete-draft-button" aria-describedby="tooltip-19622"><svg viewBox="0 0 16 16" class="icon-16p" role="img" focusable="false"><use xlink:href="#ic-trash"></use></svg><span class="sr-only">Entwurf löschen</span></button>
        <button
          class="button button-for-icon button-ghost-weak mr0-5" aria-busy="false" type="button" data-testid="composer:password-button" aria-pressed="false" aria-describedby="tooltip-19623"><svg viewBox="0 0 16 16" class="icon-16p" role="img" focusable="false"><use xlink:href="#ic-lock"></use></svg><span class="sr-only">Verschlüsselung</span></button><button aria-expanded="false" aria-busy="false" data-testid="dropdown-button" class="editor-toolbar-button interactive composer-toolbar-fontDropDown max-w100 flex flex-align-items-center flex-nowrap button button-for-icon composer-more-dropdown"
            type="button" tabindex="-1" title="Weitere Optionen" aria-describedby="tooltip-19625"><svg viewBox="0 0 16 16" class="icon-16p" role="img" focusable="false"><use xlink:href="#ic-ellipsis"></use></svg><span class="sr-only">Weitere Optionen</span></button></div>
      <div
        class="flex-item-fluid flex pr1"><span class="mr0-5 mauto no-mobile color-weak">Nicht gespeichert</span>
        <div class="composer-attachments-button-wrapper flex flex-flex-align-items-center"><label class="button button-for-icon button-ghost-weak inline-block text-center inline-flex" aria-busy="false" role="button" data-testid="composer:attachment-button" aria-describedby="tooltip-19626"><svg viewBox="0 0 16 16" class="icon-16p" role="img" focusable="false"><use xlink:href="#ic-paperclip"></use></svg><input type="file" multiple="" class="composer-attachments-button" data-testid="composer-attachments-button"></label></div>
  </div>
  </div>
  </div>
</footer>
aurumpurum
  • 932
  • 3
  • 11
  • 27
  • Screenshots of the UI are great, screenshots of code or HTML are not. Please read why [a screenshot of code/HTML is a bad idea](https://meta.stackoverflow.com/questions/303812/discourage-screenshots-of-code-and-or-errors). Paste the code/HTML and properly format it instead. – JeffC Jan 24 '22 at 18:53
  • 1
    The `NoSuchWindowException` is telling you that you are trying to interact with a window that no longer exists. We can't see that part of the code because you didn't include it in your question. My guess is that you or the site opened a new tab/window, did something in it, and then the window was closed. You then interacted (clicked) an element in that window that no longer exists. If you want more accurate help, you need to provide a [mcve] that we can see from start to finish but make sure it's fully reduced to the minimum code needed so we can focus on the issue and potential causes. – JeffC Jan 24 '22 at 18:56
  • @JeffC: I updated my post and added the code of my program. – aurumpurum Jan 24 '22 at 19:17
  • One quick comment... you don't need to keep using `WebDriverWait(browser, 10)`. Instead use `wait = WebDriverWait(browser, 10)` and then use the variable everywhere, e.g. `loginButton = WebDriverWait(browser, 10).until()` would become `loginButton = wait.until()`. It removes unnecessary instantiations and makes the code easier to read. – JeffC Jan 24 '22 at 19:35
  • Another comment... unless you are going to reuse the variable, just one-line the `WebDriverWait()` and action, e.g. `loginButton = wait.until() \n loginButton.click()` becomes `wait.until().click()`. It reduces the number of local variables you have, especially when you aren't even using them and also makes the code easier to read. – JeffC Jan 24 '22 at 19:37
  • Ok, yes I see... – aurumpurum Jan 24 '22 at 19:42
  • You still need to replace the HTML screenshot with HTML text, properly formatted. See my first comment. – JeffC Jan 24 '22 at 19:55
  • @JeffC: how can I copy HTML from DevTools? I am working with Brave Browser, which works like Chrome I think. How would you do it? – aurumpurum Jan 25 '22 at 06:14

4 Answers4

1

Some initial comments...

  1. You don't need to keep using WebDriverWait(browser, 10). Instead declare a variable to store the WebDriverWait instance, wait = WebDriverWait(browser, 10), and then use the variable everywhere, e.g.

    loginButton = WebDriverWait(browser, 10).until()
    

    would become

    loginButton = wait.until()
    

    It removes unnecessary instantiations and makes the code easier to read.

  2. Unless you are going to reuse the variable, just one-line the WebDriverWait() and action, e.g.

    loginButton = wait.until()
    loginButton.click()
    

    becomes

    wait.until().click()
    

    It reduces the number of local variables you have, especially when you aren't using them and also makes the code easier to read.


The main answer...

I don't have a protonmail account but my guess is that your last few lines of code are the issue. It looks like after switching to an IFRAME, the IFRAME is no longer available... maybe due to the .send_keys()? Try moving the browser.switch_to.default_content() before the final .click() line or you may even have to switch back to default and then switch into a new IFRAME. That should at least get you pointed in the right direction. Updated code below.

wait.until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR, "iframe[title='Editor']")))
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div#squire"))).send_keys('Test message')
browser.switch_to.default_content()
# may need to switch into the correct IFRAME again here
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "body > div.app-root > div.composer-container > div > div > div > footer > div > div.button-group.button-group-solid-norm.button-group-medium > button > span"))).click()
JeffC
  • 22,180
  • 5
  • 32
  • 55
  • Thanks. I followed your instructions (2nd comment), but the line wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "a.btn-ghost:nth-child(1)"))).click gets me the following: > – aurumpurum Jan 25 '22 at 18:55
  • Also, I would like to share the HTML, properly formatted. I would like to learn, how I can do that, also for future posts. If you could give me some instructions on that, it would be helpful, thanks. – aurumpurum Jan 25 '22 at 18:58
  • Last but not least, you were right: browser.switch_to.default_content() before the final .click(). That was the trick. Thanks. – aurumpurum Jan 25 '22 at 19:17
0

The input field for the email address and the subject is in an iframe. After switching to the iframe

WebDriverWait(browser, 20).until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR, "iframe[title='Editor']")))

I need to switch back to default content

 browser.switch_to.default_content()

before telling the browser to click on the submit button.

aurumpurum
  • 932
  • 3
  • 11
  • 27
-1

As per the screenshot:

screenshot

to identify the element you need to induce WebDriverWait for the element_to_be_clickable() and you can use either of the following Locator Strategies:

  • Using CSS_SELECTOR:

    WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div.button-group.button-group-solid-norm.button-group-medium > button span"))).click()
    
undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
  • Why would a bad locator throw `NoSuchWindowException`? This isn't going to fix the issue. See my comments and answer for more info. – JeffC Jan 24 '22 at 19:52
  • I was not able to target the button element with that CSS_SELECTOR – aurumpurum Jan 25 '22 at 06:15
-2

I would try to select the button by xpath. I think its way more accurate. If your on chrome or fire fox

right click on the an element on the browser

right click and copy and copy by full xpath

//*[@id="hireme"]/div/ul/li[4]/div/a
  • This is not a locator issue if you look at the exception message. Switching a CSS selector to an XPath won't help here. – JeffC Jan 24 '22 at 18:57