pythonselenium-webdrivernosuchelementexception

How to access message field inside iframe on protonmail.com?


I am trying to send an email with Python Selenium on Protonmail server. Everything works fine until until I get to the email message.

To get to the email message field I use the following xpath: "//div[@id='rooster-editor']/div[1]". It gives a unique element when searching manually through the DOM (Chromium gives the same xpath as well).

But within a script this XPath throws a 'no such element' exception. I first thought the error was caused by the fact the page was not completely loaded as explained in this post: Selenium Webdriver - NoSuchElementExceptions. To avoid this issue, I inserted a 20 seconds implicitly_wait, but the issue remains.

I do not think this feature would solve the issue either: EC.presence_of_element_located(By.XPATH,"//div[@id='rooster-editor']/div[1]" because my snippet is slower than sending an email manually.

To summarize, here is where I introduced some 'wait' or 'time.sleep' in the snippet: From click on 'New Message' button -> implicit wait 20 sec -> fill recipient email -> 2 sec sleep -> email subject -> 3 sec sleep -> then email message. I therefore dont think the problem could be caused by a not completely loaded page.

So my question is: What could bring such an exception?

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

class FindProton():
        def test(self, recipient, subject, msg):
            self.recipient = recipient
            self.subject = subject
            self.msg = msg
            baseUrl = 'https://www.protonmail.com'
            driver = webdriver.Firefox()
            driver.get(baseUrl)
            driver.maximize_window()
            driver.implicitly_wait(5)
            time.sleep(2)
    
            #SIGN IN 
            elementXpath = driver.find_element(By.XPATH,"//span[text()='Sign in']") 
            elementXpath.click()
    
            #USERNAME 
            time.sleep(3)
            #elementId_0 = driver.find_element(By.ID,'username').click()
            elementId_0 = driver.find_element(By.ID,'username')
            #elementId_0.send_keys(self.user)
            elementId_0.send_keys('xxxxxxx')
    
            #PASSWORD
            time.sleep(1)
            elementId_1 = driver.find_element(By.ID,'password')
            #elementId_1.send_keys(self.passwd)
            elementId_1.send_keys('xxxxxx')
    
            time.sleep(2)
            elementId_2 = driver.find_element(By.XPATH,"//button[text()='Sign in']")
            elementId_2.click()
    
            #my router is the hell slow so I introduce a long implicit wait...
            driver.implicitly_wait(20)
            #time.sleep(1)
            elementId_3 = driver.find_element(By.XPATH,"//button[text()='New message']")
            elementId_3.click()
    
            driver.implicitly_wait(15)
            #recipient ; part of id=dynamic, so use id contains 
            element_4 = driver.find_element(By.XPATH,"//input[contains(@id,'to-composer-')]")
            element_4.send_keys(self.recipient)
    
            time.sleep(2)
            #subject, part of id = dynamic, so use id contains 
            element_5 = driver.find_element(By.XPATH,"//input[contains(@id,'subject-composer-')]")
            element_5.send_keys(self.subject)
               
            time.sleep(3)
            #email message: BUG HERE !
            element_6 = driver.find_element(By.XPATH,"//div[@id='rooster-editor']/div[1]").click()

    ff=FindProton()
    ff.test('unknown0j0@gmail.com','no subject email','this is a short message')

Solution

  • Message area is placed in iframe with selector [data-testid=rooster-iframe].

    So to access the element you need to switch to iframe content first.

    Also be aware, that message is not input field, to type you should click on it and emulate keyboard actions first.

    After typing / any other actions in iframe, to access previous context you should switch to default context.

    from selenium import webdriver
    from selenium.webdriver import ActionChains
    from selenium.webdriver.common.by import By
    import time
    
       #your code
       action_chains = ActionChains(driver)
       message_frame = driver.find_element(By.CSS_SELECTOR, '[data-testid=rooster-iframe]')
       driver.switch_to.frame(message_frame)
    
       element_6 = driver.find_element(By.XPATH, "//div[@id='rooster-editor']/div[1]")
       element_6.click()
       action_chains.send_keys(msg).perform()
       # if you finished with actions inside iframe - add line below
       driver.switch_to.default_content()