Search code examples
rubywatir

How do you insert text/value in google doc using Watir/ruby


The google doc is embedded on a website inside an iframe.

Here is the code I use to try to insert a random text on the google doc

 sample_text = Faker::Lorem.sentence
 $browser.iframe(id: 'google_iframe').div(xpath: "//div[@class='kix-lineview']//div[contains(@class, 'kix-lineview-content')]").send_keys (sample_text)
 $advanced_text_info['google_doc_article'] = sample_text

But im getting error when I run the test

  element located, but timed out after 30 seconds, waiting for #<Watir::Div: located: true; {:id=>"google_iframe", :tag_name=>"iframe"} --> {:xpath=>"//div[@class='kix-lineview']//div[contains(@class, 'kix-lineview-content')]", :tag_name=>"div"}> to be present (Watir::Exception::UnknownObjectException)

Solution

  • Problem

    The root of the problem is how Google Docs has implemented their application. The div you are writing to does not include the contenteditable attribute:

    <div class="kix-lineview-content" style="margin-left: 0px; padding-top: 0px; top: -1px;">
    

    As a result, Selenium does not consider this element in an interactable state. A Selenium::WebDriver::Error::ElementNotInteractableError exception is raised. You can see this by bypassing Watir and calling Selenium directly:

    browser.div(class: 'kix-lineview-content').wd.send_keys('text')
    #=> Selenium::WebDriver::Error::ElementNotInteractableError (element not interactable)
    

    Watir confuses the matter by hiding the exception. As seen in the following code, the not interactable error is raised as an element not present exception - UnknownObjectException. I am not sure if this was intentional (eg backwards compatibility) or an oversight.

    rescue Selenium::WebDriver::Error::ElementNotInteractableError
      raise_present unless browser.timer.remaining_time.positive?
      raise_present unless %i[wait_for_present wait_for_enabled wait_for_writable].include?(precondition)
      retry
    

    In summary, the problem is that the element is not considered interactable rather than because it isn't present.

    Solution

    I think the best approach here is to make the div interactable before trying to set the text. You can do this by adding the contenteditable attribute yourself:

    content = browser.div(class: 'kix-lineview-content') # This was sufficient when using Google Docs directly - update this for your specific iframe
    content.execute_script('arguments[0].setAttribute("contenteditable", "true")', content)
    content.send_keys(sample_text)