Merge "Add integration tests section to testing docs"
This commit is contained in:
commit
00ccb87b93
@ -292,3 +292,168 @@ result of an error in the conditions of the test. Using the
|
|||||||
:meth:`~horizon.test.helpers.TestCase.assertMessageCount` will make it readily
|
:meth:`~horizon.test.helpers.TestCase.assertMessageCount` will make it readily
|
||||||
apparent what the problem is in the majority of cases. If not, then use ``pdb``
|
apparent what the problem is in the majority of cases. If not, then use ``pdb``
|
||||||
and start interrupting the code flow to see where things are getting off track.
|
and start interrupting the code flow to see where things are getting off track.
|
||||||
|
|
||||||
|
Integration tests in Horizon
|
||||||
|
============================
|
||||||
|
|
||||||
|
The integration tests currently live in the Horizon repository, see `here`_,
|
||||||
|
which also contains instructions on how to run the tests. To make integration
|
||||||
|
tests more understandable and maintainable, the Page Object pattern is used
|
||||||
|
throughout them.
|
||||||
|
|
||||||
|
.. _here: https://github.com/openstack/horizon/tree/master/openstack_dashboard/test/integration_tests
|
||||||
|
|
||||||
|
Page Object pattern
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Within any web application's user interface (UI) there are areas that the tests
|
||||||
|
interact with. A Page Object simply models these as objects within the test
|
||||||
|
code. This reduces the amount of duplicated code; if the UI changes, the fix
|
||||||
|
needs only be applied in one place.
|
||||||
|
|
||||||
|
Page Objects can be thought of as facing in two directions simultaneously.
|
||||||
|
Facing towards the developer of a test, they represent the services offered by
|
||||||
|
a particular page. Facing away from the developer, they should be the only
|
||||||
|
thing that has a deep knowledge of the structure of the HTML of a page (or
|
||||||
|
part of a page). It is simplest to think of the methods on a Page Object as
|
||||||
|
offering the "services" that a page offers rather than exposing the details
|
||||||
|
and mechanics of the page. As an example, think of the inbox of any web-based
|
||||||
|
email system. Amongst the services that it offers are typically the ability to
|
||||||
|
compose a new email, to choose to read a single email, and to list the subject
|
||||||
|
lines of the emails in the inbox. How these are implemented should not matter
|
||||||
|
to the test.
|
||||||
|
|
||||||
|
Writing reusable and maintainable Page Objects
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
Because the main idea is to encourage the developer of a test to try and think
|
||||||
|
about the services that they are interacting with rather than the
|
||||||
|
implementation, Page Objects should seldom expose the underlying WebDriver
|
||||||
|
instance. To facilitate this, methods on the Page Object should return other
|
||||||
|
Page Objects. This means that we can effectively model the user's journey
|
||||||
|
through the application.
|
||||||
|
|
||||||
|
Another important thing to mention is that a Page Object need not represent an
|
||||||
|
entire page. It may represent a section that appears many times within a site
|
||||||
|
or page, such as site navigation. The essential principle is that there is
|
||||||
|
only one place in your test suite with knowledge of the structure of the HTML
|
||||||
|
of a particular (part of a) page. With this in mind, a test developer builds
|
||||||
|
up regions that become reusable components (`example of a base form`_). These
|
||||||
|
properties can then be redefined or overridden (e.g. selectors) in the actual
|
||||||
|
pages (subclasses) (`example of a tabbed form`_).
|
||||||
|
|
||||||
|
The page objects are read-only and define the read-only and clickable elements
|
||||||
|
of a page, which work to shield the tests. For instance, from the test
|
||||||
|
perspective, if "Logout" used to be a link but suddenly becomes an option in a
|
||||||
|
drop-down menu, there are no changes (in the test itself) because it still simply
|
||||||
|
calls the "click_on_logout" action method.
|
||||||
|
|
||||||
|
This approach has two main aspects:
|
||||||
|
|
||||||
|
* The classes with the actual tests should be as readable as possible
|
||||||
|
* The other parts of the testing framework should be as much about data as
|
||||||
|
possible, so that if the CSS etc. changes you only need to change that one
|
||||||
|
property. If the flow changes, only the action method should need to change.
|
||||||
|
|
||||||
|
There is little that is Selenium-specific in the Pages, except for the
|
||||||
|
properties. There is little coupling between the tests and the pages. Writing
|
||||||
|
the tests becomes like writing out a list of steps (by using the previously
|
||||||
|
mentioned action methods). One of the key points, particularly important for
|
||||||
|
this kind of UI driven testing is to isolate the tests from what is behind
|
||||||
|
them.
|
||||||
|
|
||||||
|
.. _example of a base form: https://github.com/openstack/horizon/blob/8.0.0/openstack_dashboard/test/integration_tests/regions/forms.py#L250
|
||||||
|
.. _example of a tabbed form: https://github.com/openstack/horizon/blob/8.0.0/openstack_dashboard/test/integration_tests/regions/forms.py#L322
|
||||||
|
|
||||||
|
List of references
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* https://wiki.openstack.org/wiki/Horizon/Testing/UI#Page_Object_Pattern_.28Selected_Approach.29
|
||||||
|
* https://wiki.mozilla.org/QA/Execution/Web_Testing/Docs/Automation/StyleGuide#Page_Objects
|
||||||
|
* https://code.google.com/p/selenium/wiki/PageObjects
|
||||||
|
|
||||||
|
Debugging integration tests
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Even perfectly designed Page Objects are not a guarantee that your integration
|
||||||
|
test will not ever fail. This can happen due to different causes:
|
||||||
|
|
||||||
|
The first and most anticipated kind of failure is the inability to perform a
|
||||||
|
testing scenario by a living person simply because some OpenStack service or
|
||||||
|
Horizon itself prevents them from doing so. This is exactly the kind that
|
||||||
|
integration tests are designed to catch. Let us call them "good" failures.
|
||||||
|
|
||||||
|
All other kinds of failures are unwanted and could be roughly split into the
|
||||||
|
two following categories:
|
||||||
|
|
||||||
|
#. The failures that occur due to changes in application's DOM. some CSS/ Xpath selectors no longer matching
|
||||||
|
Horizon app's DOM. The usual signature for that kind of failures is having
|
||||||
|
a DOM changing patch for which the test job fails with a message like
|
||||||
|
this `selenium.common.exceptions.NoSuchElementException: Message: Unable to
|
||||||
|
locate element: {"method":"css selector","selector":"div.modal-dialog"}`.
|
||||||
|
If you find yourself in such a situation, you should fix the Page Object
|
||||||
|
selectors according to the DOM changes you made.
|
||||||
|
|
||||||
|
#. Unfortunately it is still quite possible to get the above error for a patch
|
||||||
|
which didn't implement any DOM changes. Among the reasons of such behavior
|
||||||
|
observed in past were:
|
||||||
|
|
||||||
|
* Integration tests relying on relative ordering of form fields and table
|
||||||
|
actions that broke with the addition of a new field. This issue should
|
||||||
|
be fixed by now, but may reappear in future for different entities.
|
||||||
|
|
||||||
|
* Integration tests relying on popups disappearing by the time a specific
|
||||||
|
action needs to be taken (or not existing at all). This expectation
|
||||||
|
turned out to be very fragile, since the speed of tests execution by
|
||||||
|
Jenkins workers may change independently of integration test code (hence,
|
||||||
|
popups disappear too late to free the way for the next action). The
|
||||||
|
unexpected (both too long and too short) timeouts aren't limited to just
|
||||||
|
popups, but apply to every situation when the element state transition
|
||||||
|
is not instant (like opening an external link, going to another page in
|
||||||
|
Horizon, waiting for button to become active, waiting for a table row to
|
||||||
|
change its state). Luckily, most transitions of "element becomes visible/
|
||||||
|
emerge to existence from non-existence" kind are already bulletproofed
|
||||||
|
using `implicit_wait` parameter in `integration_tests/horizon.conf` file.
|
||||||
|
Selenium just waits for specified amount of seconds for an element to
|
||||||
|
become visible (if it's not already visible) giving up when it exceeds
|
||||||
|
(with the above error). Also it's worth mentioning `explicit_wait` parameter
|
||||||
|
which is considered when the selenium `wait_until` method is involved (and
|
||||||
|
it is used, e.g. in waiting for spinner and messages popups to disappear).
|
||||||
|
|
||||||
|
An inconvenient thing about reading test results in the `console.html` file
|
||||||
|
attached to every `gate-horizon-dsvm-integration` finished job is that the test
|
||||||
|
failure may appear either as failure (assertion failed), or as error (expected
|
||||||
|
element didn't show up). In both cases an inquirer should suspect a legitimate
|
||||||
|
failure first (i.e., treat errors as failures). Unfortunately, no clear method
|
||||||
|
exists for the separation of "good" from from "bad" failures. Each case is
|
||||||
|
unique and full of mysteries.
|
||||||
|
|
||||||
|
The Horizon testing mechanism tries to alleviate this ambiguity by providing
|
||||||
|
several facilities to aid in failure investigation:
|
||||||
|
|
||||||
|
* First there comes a screenshot made for every failed test (in a separate
|
||||||
|
folder, on a same level as `console.html`) - almost instant snapshot of a
|
||||||
|
screen on the moment of failure (*almost* sometimes matters, especially in
|
||||||
|
a case of popups that hang on a screen for a limited time);
|
||||||
|
* Then the patient inquirer may skim through the vast innards of
|
||||||
|
`console.html`, looking at browser log first (all javascript and css errors
|
||||||
|
should come there),
|
||||||
|
* Then looking at a full textual snapshot of a page for which test failed
|
||||||
|
(sometimes it gives a more precise picture than a screenshot),
|
||||||
|
* And finally looking at test error stacktrace (most useful) and a lengthy
|
||||||
|
output of requests/ responses with a selenium server. The last log sometimes
|
||||||
|
might tell us how long a specific web element was polled before failing (in
|
||||||
|
case of `implicit_wait` there should be a series of requests to the same
|
||||||
|
element).
|
||||||
|
|
||||||
|
The best way to solve the cause of test failure is running and debugging the
|
||||||
|
troublesome test locally. You could use `pdb` or Python IDE of your choice to
|
||||||
|
stop test execution in arbitrary points and examining various Page Objects
|
||||||
|
attributes to understand what they missed. Looking at the real page structure
|
||||||
|
in browser developer tools also could explain why the test fails. Sometimes it
|
||||||
|
may be worth to place breakpoints in JavaScript code (provided that static is
|
||||||
|
served uncompressed) to examine the objects of interest. If it takes long, you
|
||||||
|
may also want to increase the webdriver's timeout so it will not close browser
|
||||||
|
windows forcefully. Finally, sometimes it may make sense to examine the
|
||||||
|
contents of `logs` directory, especially apache logs - but that is mostly the
|
||||||
|
case for the "good" failures.
|
Loading…
Reference in New Issue
Block a user