1

I am writing a webscraping script that automatically logs into my Email account and sends a message.

I have written the code to the point where the browser has to input the message. I don't know how to access the input field correctly. I have seen that it is an iframe element. Do I have to use the switch_to_frame() method and how can I do that? How can I switch to the iframe if there is no name attribute? Do I need the switch_to_frame() method or can I just use the find_element_by_css_selector() method?

This is the source code of the iframe:

enter image description here

Here is my 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 = 'xxxxxxxxxxxxxxxx'

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')
messageElem = WebDriverWait(browser, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#squire > div > div:nth-child(1)")))
messageElem.send_keys('message')
undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
aurumpurum
  • 932
  • 3
  • 11
  • 27

3 Answers3

1

To access the <input> field within the iframe so you have to:

  • Induce WebDriverWait for the desired frame to be available and switch to it.

  • Induce WebDriverWait for the desired element to be clickable.

  • You can use either of the following Locator Strategies:

    • Using CSS_SELECTOR:

      WebDriverWait(driver, 20).until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR,"iframe[title='Editor']")))
      WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div#squire"))).send_keys('message')
      
    • Using XPATH:

      WebDriverWait(driver, 20).until(EC.frame_to_be_available_and_switch_to_it((By.XPATH,"//iframe[@title='Editor']")))
      WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "//div[@id='squire']"))).send_keys('message')
      
  • Note : You have to add the following imports :

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

PS: As the <div> tag is having the attribute contenteditable="true" you can still send text to the element.


Reference

You can find a couple of relevant discussions in:

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
  • Thanks. In Firefox and following your CSS_SELECTOR strategy, this did not work for me. Isn't the .send_keys() missing somewhere in your code? There is just the click. The browser should type a message. I tried to replace the .click() method in your second line with .send_keys('Test message') but then it throws an error: Element
    is not reachable by keyboard. The same error is thrown when I copy your second line and add it as third line just using the .send_click() method.
    – aurumpurum Jan 23 '22 at 15:34
  • What I don't understand is how can I input a message when there is no input element, only a div element? – aurumpurum Jan 23 '22 at 15:40
  • @aurumpurum Checkout the updated answer and let me know the status. – undetected Selenium Jan 23 '22 at 17:25
  • Hey, your code works in my usecase in Firefox, thanks! I used the CSS_SELECTOR code. Just as a "lesson learned": How did you figure out the css_selector "div#squire"? Using Firefox, inspect, Firefox gives me the following css_selector for that element: "#squire > div:nth-child(1)". My everyday-browser at the moment is Brave Browser. This one gives me back the following css_selector: #squire > div:nth-child(1) > div:nth-child(1) . So a different one! Why is that? Second: in your css_selector, div and #squire have switched position as opposed to the css_selector the browser tells me...why? – aurumpurum Jan 23 '22 at 18:11
  • @aurumpurum 1) _How did you figure out the css_selector_: Long [story](https://stackoverflow.com/questions/46700764/how-to-inspect-element-for-selenium-v3-6-as-firebug-is-not-an-option-any-more-fo/46702281#46702281) 2) _Firefox gives me the following css_selector_: Generally targetting the element with `contenteditable="true"` is enough, but at times you need to go deeper as per the type of browser you are using. 3) _switched places_: Locator was as per the snapshot, in real time scenarios you may require to tweak a bit as different browsers renders the DOM in a different way. – undetected Selenium Jan 23 '22 at 18:18
  • Ok, thanks. I just checked again the HTML. Obviously, we need to target the grand-parent element of the element marked. The contenteditable="true" was a hint...this makes sense! I assume, this is also a little bit trial-and-error, depending on the browser in use...Anyway...problem solved, this was my task of the day! :-) Thanks, also to @cruisepandey and @Prophet! – aurumpurum Jan 23 '22 at 18:28
1

You have to switch to iframe with driver.switch_to.frame method.
Like any other web element iframe element can be located by ID, CLASS, XPATH, CSS_SELECTOR etc.
Looks like here you can use this method:

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

Or

WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR,"iframe[data-testid='squire-iframe']")))

When finished working within the iframe you will have to switch back to the default content with

driver.switch_to.default_content()
Prophet
  • 32,350
  • 22
  • 54
  • 79
  • Thanks. You would insert this line (CSS-SELECTOR strategy) as the 3rd last line in my code? Would this be your solution? – aurumpurum Jan 23 '22 at 15:54
  • I'm not sure I understand this your question. You asked how to switch to iframe you shown in the picture in your question. My solution is how to locate iframe element, switch to iframe according to the proper locator, do what you want with elements inside that iframe and finally switch to the default content. You can locate the iframe by XPATH or with CSS_SELECTOR, as I mentioned. – Prophet Jan 23 '22 at 16:18
  • Ok, yes locating the iFrame works but I also have problems with the following steps: I need to find the correct element in the iFrame and input some message. – aurumpurum Jan 23 '22 at 16:39
  • 1
    Well, you didn't ask for that in your initial question, so what you are asking for now is additional question. The 2 other fellows are already answered your for this too recently. – Prophet Jan 23 '22 at 17:29
1

you first need to switch to iframe

wait = WebDriverWait(driver, 30)
wait.until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR, "iframe[title='Editor']")))

and now here write the code to send the message body. something like this:

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"))).click()

email = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "div#squire div:nth-child(2)")))
email.send_keys('write the email here')

also once you are done with iframe interaction, you should switch to default content:

driver.switch_to.default_content()
cruisepandey
  • 28,520
  • 6
  • 20
  • 38
  • Thanks, but I am still not there, where I wanted to be...I pasted your code but still get an error: selenium.common.exceptions.ElementNotInteractableException: Message: Element
    is not reachable by keyboard
    – aurumpurum Jan 23 '22 at 13:19
  • based on the HTML that we have, I do not see the input field and that is the reason I have given `div#squire div>div` CSS and send_keys.. however, I would also like to try with the `execute_script`, also updated above, Please verify – cruisepandey Jan 23 '22 at 13:27
  • Thanks. I used your code, including the execute_script, but in Firefox it does not seem to work for me. At least I don't get an error, but nothing is written as a message. My problem is, I don't know exactly which element I have to target for the message input. By checking the source code of the area where I would type in my message, it is a div element (I updated the screenshot to present the HTML). In some other attempts I got the error: div element not reachable by keyboard... – aurumpurum Jan 23 '22 at 15:45
  • @aurumpurum: I could do it, check the updated code above ! – cruisepandey Jan 23 '22 at 16:56
  • Thanks. In my usecase and in Firefox, your code did not work for me. I have figured out the following problems: 1) I removed the .click() method from your second 3rd line and assign it to the variable email as a web element. 2) The 4th line is redundant in my opinion (visibility_of_element_located), also the css_selector did not work. Thanks anyway, your answer was also helpful! – aurumpurum Jan 23 '22 at 18:20