3

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.

Tijmen
  • 542
  • 1
  • 6
  • 29
  • Ok so apparently this does work in Chrome, which I reluctantly installed after staring at this for days. That's a bit of an anticlimax, as it's not so much something wrong with my code as much as it is (apparently) a bug in either Firefox or Geckodriver. Nonetheless I'm going to leave this question open at least until the bounty expires to see if anyone has a solution for Firefox specifically. – Tijmen Apr 19 '21 at 19:31
  • I am not able to access the webpage that you shared on my laptop due to some firewalls yet on my end but, you can refer to https://stackoverflow.com/questions/56380091/how-to-interact-with-the-elements-within-shadow-root-open-while-clearing-brow/56381495#56381495 to understand the shadow-root and it's implementation along with the options to work on them. – supputuri Apr 21 '21 at 01:07
  • Thanks, but that answer, although informative, ends up referring to the solution I'm already implementing in my code. As noted in my comment above, the solution works in Chrome with Chromedriver, but not in Firefox with Geckodriver. – Tijmen Apr 21 '21 at 19:24

2 Answers2

5

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()
bmcculley
  • 2,048
  • 1
  • 14
  • 17
2

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/