pythonpython-3.xseleniumcpythonviewbox

How can I automate a Chess board with Selenium?


There's a chess website I found (https://www.chess.com/play/computer), it's very confusing to work with on Selenium. The problem I'm encountering is that most squares are not actually treated as an element, it's only treated as an element if there's a piece on it or it's highlighted. This makes it very, very hard to work within Selenium as I cannot highlight a square without clicking it and to click it with Selenium I need the element.

Perhaps I could use a coordinates system as suggested here: Selenium click trouble (Python) but their code was not functional as the post was five years old.

The board itself is a view box and each square that is an element has "square-" followed by a two-digit number in its class where the first digit is the numbers on the board and the second digit is the letters on the board converted to a digit. In the image I provided the exact class it has is "highlight square-45"

Thanks for any help! Demostrating the class ordering system


Solution

  • You can use a Javascript snippet via driver.execute_script to get the coordinates for each square on the board:

    from selenium import webdriver
    d = webdriver.Chrome('/path/to/chromedriver')
    d.get('https://www.chess.com/play/computer')
    board = d.execute_script('''
      function coords(elem){
          var n = elem.getBoundingClientRect()
          return {top:n.top, left:n.left, width:n.width, height:n.height}
      }
      var pieces = []
      for (var i = 1; i < 9; i++){
         if (i > 6 || i < 3){
            pieces.push(Array.from((new Array(8)).keys()).map(function(x){
               var square = document.querySelector(`.piece.square-${x+1}${i}`)
               return {...coords(square), piece:square.getAttribute('class').split(' ')[1]}
            }));
         }
         else{
            pieces.push(Array.from((new Array(8)).keys()).map(function(x){
               var arr = pieces[pieces.length-1]
               return {left:arr[x].left, top:arr[x].top - arr[x].height, 
                 width:arr[x].width, height:arr[x].height, piece:null}
            }));
         }
      }
      return pieces
    ''')[::-1]
    

    Output:

    [[{'height': 62, 'left': 187, 'piece': 'br', 'top': 66, 'width': 62}, {'height': 62, 'left': 249, 'piece': 'bn', 'top': 66, 'width': 62}, {'height': 62, 'left': 311, 'piece': 'bb', 'top': 66, 'width': 62}, {'height': 62, 'left': 373, 'piece': 'bq', 'top': 66, 'width': 62}, {'height': 62, 'left': 435, 'piece': 'bk', 'top': 66, 'width': 62}, {'height': 62, 'left': 497, 'piece': 'bb', 'top': 66, 'width': 62}, {'height': 62, 'left': 559, 'piece': 'bn', 'top': 66, 'width': 62}, {'height': 62, 'left': 621, 'piece': 'br', 'top': 66, 'width': 62}], [{'height': 62, 'left': 187, 'piece': 'bp', 'top': 128, 'width': 62}, {'height': 62, 'left': 249, 'piece': 'bp', 'top': 128, 'width': 62}, {'height': 62, 'left': 311, 'piece': 'bp', 'top': 128, 'width': 62}, {'height': 62, 'left': 373, 'piece': 'bp', 'top': 128, 'width': 62}, {'height': 62, 'left': 435, 'piece': 'bp', 'top': 128, 'width': 62}, {'height': 62, 'left': 497, 'piece': 'bp', 'top': 128, 'width': 62}, {'height': 62, 'left': 559, 'piece': 'bp', 'top': 128, 'width': 62}, {'height': 62, 'left': 621, 'piece': 'bp', 'top': 128, 'width': 62}], [{'height': 62, 'left': 187, 'piece': None, 'top': 190, 'width': 62}, {'height': 62, 'left': 249, 'piece': None, 'top': 190, 'width': 62}, {'height': 62, 'left': 311, 'piece': None, 'top': 190, 'width': 62}, {'height': 62, 'left': 373, 'piece': None, 'top': 190, 'width': 62}, {'height': 62, 'left': 435, 'piece': None, 'top': 190, 'width': 62}, {'height': 62, 'left': 497, 'piece': None, 'top': 190, 'width': 62}, {'height': 62, 'left': 559, 'piece': None, 'top': 190, 'width': 62}, {'height': 62, 'left': 621, 'piece': None, 'top': 190, 'width': 62}], [{'height': 62, 'left': 187, 'piece': None, 'top': 252, 'width': 62}, {'height': 62, 'left': 249, 'piece': None, 'top': 252, 'width': 62}, {'height': 62, 'left': 311, 'piece': None, 'top': 252, 'width': 62}, {'height': 62, 'left': 373, 'piece': None, 'top': 252, 'width': 62}, {'height': 62, 'left': 435, 'piece': None, 'top': 252, 'width': 62}, {'height': 62, 'left': 497, 'piece': None, 'top': 252, 'width': 62}, {'height': 62, 'left': 559, 'piece': None, 'top': 252, 'width': 62}, {'height': 62, 'left': 621, 'piece': None, 'top': 252, 'width': 62}], [{'height': 62, 'left': 187, 'piece': None, 'top': 314, 'width': 62}, {'height': 62, 'left': 249, 'piece': None, 'top': 314, 'width': 62}, {'height': 62, 'left': 311, 'piece': None, 'top': 314, 'width': 62}, {'height': 62, 'left': 373, 'piece': None, 'top': 314, 'width': 62}, {'height': 62, 'left': 435, 'piece': None, 'top': 314, 'width': 62}, {'height': 62, 'left': 497, 'piece': None, 'top': 314, 'width': 62}, {'height': 62, 'left': 559, 'piece': None, 'top': 314, 'width': 62}, {'height': 62, 'left': 621, 'piece': None, 'top': 314, 'width': 62}], [{'height': 62, 'left': 187, 'piece': None, 'top': 376, 'width': 62}, {'height': 62, 'left': 249, 'piece': None, 'top': 376, 'width': 62}, {'height': 62, 'left': 311, 'piece': None, 'top': 376, 'width': 62}, {'height': 62, 'left': 373, 'piece': None, 'top': 376, 'width': 62}, {'height': 62, 'left': 435, 'piece': None, 'top': 376, 'width': 62}, {'height': 62, 'left': 497, 'piece': None, 'top': 376, 'width': 62}, {'height': 62, 'left': 559, 'piece': None, 'top': 376, 'width': 62}, {'height': 62, 'left': 621, 'piece': None, 'top': 376, 'width': 62}], [{'height': 62, 'left': 187, 'piece': 'wp', 'top': 438, 'width': 62}, {'height': 62, 'left': 249, 'piece': 'wp', 'top': 438, 'width': 62}, {'height': 62, 'left': 311, 'piece': 'wp', 'top': 438, 'width': 62}, {'height': 62, 'left': 373, 'piece': 'wp', 'top': 438, 'width': 62}, {'height': 62, 'left': 435, 'piece': 'wp', 'top': 438, 'width': 62}, {'height': 62, 'left': 497, 'piece': 'wp', 'top': 438, 'width': 62}, {'height': 62, 'left': 559, 'piece': 'wp', 'top': 438, 'width': 62}, {'height': 62, 'left': 621, 'piece': 'wp', 'top': 438, 'width': 62}], [{'height': 62, 'left': 187, 'piece': 'wr', 'top': 500, 'width': 62}, {'height': 62, 'left': 249, 'piece': 'wn', 'top': 500, 'width': 62}, {'height': 62, 'left': 311, 'piece': 'wb', 'top': 500, 'width': 62}, {'height': 62, 'left': 373, 'piece': 'wq', 'top': 500, 'width': 62}, {'height': 62, 'left': 435, 'piece': 'wk', 'top': 500, 'width': 62}, {'height': 62, 'left': 497, 'piece': 'wb', 'top': 500, 'width': 62}, {'height': 62, 'left': 559, 'piece': 'wn', 'top': 500, 'width': 62}, {'height': 62, 'left': 621, 'piece': 'wr', 'top': 500, 'width': 62}]]
    

    Edit: clicking a square:

    from selenium.webdriver.common.action_chains import ActionChains
    def click_square(square):
       elem = d.execute_script('''return document.querySelector('body')''')
       ac = ActionChains(d)
       ac.move_to_element(elem).move_by_offset(square['left']+int(square['width']/2), square['top']+int(square['width']/2)).click().perform()
    
    click_square(board[0][0])