Hello selenium-developers,
I am currently working on a single page web application that uses shadow DOM very extensively. To allow automated testing of this application, I am working on adding shadow DOM support to chromedriver, based on the working draft at http://www.w3.org/TR/webdriver/. I understand that the draft is a work in progress, but I have a few concerns about the draft and hoping to spark some discussion about them.
Concern #1: The proposal treats shadow DOM a lot like an iframe, and using iframes with webdriver sucks.
You must call switchToSubTree(element, index) to interact with anything in a shadow DOM, very similar to how you must call switchToFrame(frame) to interact with an iframe. I understand there are probably technical and security-related reasons this is required for interacting with an iframe. This is not necessarily true for items in a shadow DOM, which are all controlled by the domain hosting them.
Concern #2: It is extremely likely that something in the shadow DOM will affect something outside the shadow DOM. Unlike an iframe.
A classic example of an iframe would be an embedded '+1' or 'like' button. Clicking the button actually has no effect on anything outside the iframe. This is not necessarily true for something in a shadow DOM.
Here is an example from the single page web application I want to test. The application has a table component implemented with shadow DOM and a dropdown menu component (with filters for the table) also implemented with shadow DOM. Both these subcomponents are combined into a TableWithFilter component that has its own shadow DOM. Now suppose I want to test that a certain filter has 5 results.
table_with_filter_host = driver.find_element_by_id('host-of-table-with-filter')
driver.switchToSubTree(table_with_filter_host)
dropdown_filter_host = driver.find_element_by_id('host-of-dropdown-filter')
driver.switchToSubTree(dropdown_filter_host)
select = driver.find_element_by_tag_name('select')
select.click()
select.find_element_by_xpath('//[text()=="Some Filter"]').click()
driver.switch_to_default_content()
driver.switchToSubTree(table_with_filter_host)
table_host = driver.find_element_by_id('host-of-table')
driver.switchToSubTree(table_host)
rows = driver.find_elements_by_xpath("//tr")
assert(len(rows), 5)
In this simple example, 8 of 13 lines of code are just navigating the shadow DOM. Navigating the shadow DOM is over 60% of the test but provides no benefit.
Concern #3: Unlike an iframe, there could easily be tens to hundreds of shadow DOMs in a document, and they may be nested deeply.
I have never ran into nested iframes. I am having nightmares about testing my teams application, which has shadow DOM going at least 5 layers deep in some places.
Concern #4: It is impossible to switch between shadow DOMs without first reseting to the default content, and drilling down one shadow DOM at a time.
Take the example in Concern #2 and lets say in the same test I want to test another filter, I would have to add.
driver.switch_to_default_content()
driver.switchToSubTree(table_with_filter_host)
driver.switchToSubTree(dropdown_filter_host)
select.click()
select.find_element_by_xpath('//[text()=="Another Filter"]').click()
driver.switch_to_default_content()
driver.switchToSubTree(table_with_filter_host)
driver.switchToSubTree(table_host)
rows = driver.find_elements_by_xpath("//tr")
assert(len(rows), 0)
I gain almost nothing from having previously located the shadow hosts and elements I care about.
Possible solution #1 (from me): Allow interaction with ANY element, as long as the element.ownerDocument == current frame. Rename switchToSubTree() to something that indicates you are only changing the search context of driver.find_element and driver.find_elements, like switchSearchContextToSubTree().
Pros:
Once you located the element(s) you want to interact with, you don't have to worry about calling switchSearchContextToSubTree() anymore. You could call element.text or element.click() no matter what your search context was.
You could use javascript to return elements in nested shadow DOM, circumventing the need for multiple switchSearchContextToSubTree() calls in the first place.
Cons:
Blurs the lines of shadow DOM encapsulation. However I would argue that shadow DOM encapsulation is an implementation detail that makes life easier for a web developer. It is not a security feature and is completely transparent to an actual user of a web page, so I don't see the harm in blurring the lines when simulating a user with webdriver.
Possible solution #2 (from simonstewart): Provide a method like driver.switchToOwningSubTree(<element>)
Pros:
Once you located the elements you want to interact with, interacting with them is one switchToOwningSubTree() call away.
You could jump between shadow DOMs.
Cons:
Still have to change contexts a lot, (but it is only 1 line of code instead of multiple lines at least).
These are just 2 solutions, and I am sure there are other ones. The main point of this e-mail was to spark discussion about the current proposal for webdriver + shadow DOM and some possible drawbacks of it.