summaryrefslogtreecommitdiffstats
path: root/testing/marionette/client/docs/advanced/stale.rst
blob: 885083993c3e6c8e0498db8deee7a8eb0e1c34f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Dealing with Stale Elements
===========================
.. py:currentmodule:: marionette_driver.marionette

Marionette does not keep a live representation of the DOM saved. All it can do
is send commands to the Marionette server which queries the DOM on the client's
behalf. References to elements are also not passed from server to client. A
unique id is generated for each element that gets referenced and a mapping of
id to element object is stored on the server. When commands such as
:func:`~HTMLElement.click` are run, the client sends the element's id along
with the command. The server looks up the proper DOM element in its reference
table and executes the command on it.

In practice this means that the DOM can change state and Marionette will never
know until it sends another query. For example, look at the following HTML::

    <head>
    <script type=text/javascript>
        function addDiv() {
           var div = document.createElement("div");
           document.getElementById("container").appendChild(div);
        }
    </script>
    </head>

    <body>
        <div id="container">
        </div>
        <input id="button" type=button onclick="addDiv();">
    </body>

Care needs to be taken as the DOM is being modified after the page has loaded.
The following code has a race condition::

    button = client.find_element('id', 'button')
    button.click()
    assert len(client.find_elements('css selector', '#container div')) > 0


Explicit Waiting and Expected Conditions
----------------------------------------
.. py:currentmodule:: marionette_driver

To avoid the above scenario, manual synchronisation is needed. Waits are used
to pause program execution until a given condition is true. This is a useful
technique to employ when documents load new content or change after
``Document.readyState``'s value changes to "complete".

The :class:`Wait` helper class provided by Marionette avoids some of the
caveats of ``time.sleep(n)``. It will return immediately once the provided
condition evaluates to true.

To avoid the race condition in the above example, one could do::

    from marionette_driver import Wait

    button = client.find_element('id', 'button')
    button.click()

    def find_divs():
        return client.find_elements('css selector', '#container div')

    divs = Wait(client).until(find_divs)
    assert len(divs) > 0

This avoids the race condition. Because finding elements is a common condition
to wait for, it is built in to Marionette. Instead of the above, you could
write::

    from marionette_driver import Wait

    button = client.find_element('id', 'button')
    button.click()
    assert len(Wait(client).until(expected.elements_present('css selector', '#container div'))) > 0

For a full list of built-in conditions, see :mod:`~marionette_driver.expected`.