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`.