Accessing ShadowRoot via Selenium in Firefox returns JavascriptException: Cyclic object value

Question:

I’m trying to automate the www.freeinvaders.org version of Space Invaders using Python and Selenium. The actual game works through a HTML5 canvas element, which is wrapped in a shadow-root.

Using the answer to this question, I’m attempting to expand the shadow-root, so I can click the canvas and ‘play’ the game.

My current code:

  def expand_shadow_element(element):
     shadowRoot = browser.execute_script('return arguments[0].shadowRoot', element)
     return shadowRoot
  

  browser = webdriver.Firefox()
  browser.get("http://www.freeinvaders.org/")

  #wait for element to load

  el = WebDriverWait(browser, timeout=20).until(lambda d:d.find_element_by_tag_name("ruffle-player"))
  time.sleep(5)

  #expand the shadowroot and click the canvas
  host = browser.find_element_by_tag_name("ruffle-player")
  shadowRoot = expand_shadow_element(host)
  canvas = shadowRoot.find_element_by_tag_name("canvas")
  canvas.click()

The HTML structure of the page is like this:
(abridged for legibility)

<!DOCTYPE html> 
<html lang="en-US">
<head>

<title>Free Invaders</title>

</head>
<body>
<div id='full-page-container'>  
<main>
  <div id="game-container">
       
    <ruffle-player>
  #shadow-root (open)
  <div id="container" style="visibility: visible;">
      
    <canvas width="1600" height="760" style="touch-action: none; cursor: auto;"></canvas>
    </div><!--container-->
  </ruffle-player>
  </div><!--game-container-->

</div><!--fullpagecontainer-->
</body>

When running the above Pyhton script, it fails on this line:

shadowRoot = browser.execute_script('return arguments[0].shadowRoot', element)

with a Javascript error:

selenium.common.exceptions.JavascriptException: Message: Cyclic object value

I know that error is supposed to mean there is a self-referencing item in the returned JSON string, but that shouldn’t be the case here.

Can anyone help me by explaining why this error occurs and what course of action might alleviate the issue?

I’m using Python 3.8.5, Selenium 3.141.0 and Firefox 86.0. All of this runs on Linux Mint 20.1.

Edit
I have also attempted the alternative Javascript:

shadowRoot = browser.execute_script('document.querySelector("ruffle-player").shadowRoot')

But that just returns another error:

AttributeError: 'NoneType' object has no attribute 'find_element_by_tag_name'

Which indicates it doesn’t even find any objects.

Asked By: Tijmen

||

Answers:

It seems that this is a known issue filed on bugzilla.

The geckodriver devs also say that the WebDriver spec needs to be updated. See: w3c/webdriver#350.

However, CAVAh has found and posted a workaround for this issue here.

Following the advice to return the children of shadowroot return arguments[0].shadowRoot.children it finds 4 elements:

[<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1dd0fedf-1a8c-42f2-a4de-0ed7df478212", element="252a6352-4fe0-409d-a626-18456a973da5")>, 
<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1dd0fedf-1a8c-42f2-a4de-0ed7df478212", element="47d76aed-5f44-4933-9718-53267a6417bf")>, 
<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1dd0fedf-1a8c-42f2-a4de-0ed7df478212", element="d3fcad48-7cbb-4de3-a247-49d7d227e982")>, 
<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1dd0fedf-1a8c-42f2-a4de-0ed7df478212", element="abb5012e-79bf-494b-b5f2-ff28dfecab0f")>]

After inspecting those, it looks like the third element contains the canvas and can be clicked on.

The following code seems to work for me:

def expand_shadow_element(element):
    # return a list of elements
    shadowRoot = browser.execute_script('return arguments[0].shadowRoot.children', element)
    return shadowRoot


browser = webdriver.Firefox()
browser.get("http://www.freeinvaders.org/")

#wait for element to load

el = WebDriverWait(browser, timeout=20).until(lambda d:d.find_element_by_tag_name("ruffle-player"))
time.sleep(5)

#expand the shadowroot and click the canvas
host = browser.find_element_by_tag_name("ruffle-player")
shadowRoot = expand_shadow_element(host)

canvas = shadowRoot[2].find_element_by_tag_name("canvas")
canvas.click()
Answered By: bmcculley

Should now be fixed in firefox 111

see https://bugzilla.mozilla.org/show_bug.cgi?id=1764594

You can download firefox version 111 here:

https://download-installer.cdn.mozilla.net/pub/firefox/releases/111.0/

Answered By: bobbybobbobbed
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.