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)
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)