tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

stale.rst (2827B)


      1 Dealing with Stale Elements
      2 ===========================
      3 .. py:currentmodule:: marionette_driver.marionette
      4 
      5 Marionette does not keep a live representation of the DOM saved. All it can do
      6 is send commands to the Marionette server which queries the DOM on the client's
      7 behalf. References to elements are also not passed from server to client. A
      8 unique id is generated for each element that gets referenced and a mapping of
      9 id to element object is stored on the server. When commands such as
     10 :func:`~WebElement.click` are run, the client sends the element's id along
     11 with the command. The server looks up the proper DOM element in its reference
     12 table and executes the command on it.
     13 
     14 In practice this means that the DOM can change state and Marionette will never
     15 know until it sends another query. For example, look at the following HTML::
     16 
     17    <head>
     18    <script type=text/javascript>
     19        function addDiv() {
     20           var div = document.createElement("div");
     21           document.getElementById("container").appendChild(div);
     22        }
     23    </script>
     24    </head>
     25 
     26    <body>
     27        <div id="container">
     28        </div>
     29        <input id="button" type=button onclick="addDiv();">
     30    </body>
     31 
     32 Care needs to be taken as the DOM is being modified after the page has loaded.
     33 The following code has a race condition::
     34 
     35    button = client.find_element('id', 'button')
     36    button.click()
     37    assert len(client.find_elements('css selector', '#container div')) > 0
     38 
     39 
     40 Explicit Waiting and Expected Conditions
     41 ----------------------------------------
     42 .. py:currentmodule:: marionette_driver
     43 
     44 To avoid the above scenario, manual synchronisation is needed. Waits are used
     45 to pause program execution until a given condition is true. This is a useful
     46 technique to employ when documents load new content or change after
     47 ``Document.readyState``'s value changes to "complete".
     48 
     49 The :class:`Wait` helper class provided by Marionette avoids some of the
     50 caveats of ``time.sleep(n)``. It will return immediately once the provided
     51 condition evaluates to true.
     52 
     53 To avoid the race condition in the above example, one could do::
     54 
     55    from marionette_driver import Wait
     56 
     57    button = client.find_element('id', 'button')
     58    button.click()
     59 
     60    def find_divs():
     61        return client.find_elements('css selector', '#container div')
     62 
     63    divs = Wait(client).until(find_divs)
     64    assert len(divs) > 0
     65 
     66 This avoids the race condition. Because finding elements is a common condition
     67 to wait for, it is built in to Marionette. Instead of the above, you could
     68 write::
     69 
     70    from marionette_driver import Wait
     71 
     72    button = client.find_element('id', 'button')
     73    button.click()
     74    assert len(Wait(client).until(expected.elements_present('css selector', '#container div'))) > 0
     75 
     76 For a full list of built-in conditions, see :mod:`~marionette_driver.expected`.