Merge lp:autopilot into lp:autopilot/1.5

Proposed by Christopher Lee
Status: Merged
Merged at revision: 505
Proposed branch: lp:autopilot
Merge into: lp:autopilot/1.5
Diff against target: 2559 lines (+1468/-250) (has conflicts)
33 files modified
README (+61/-3)
autopilot/_config.py (+8/-8)
autopilot/input/_X11.py (+4/-1)
autopilot/input/__init__.py (+52/-12)
autopilot/input/_uinput.py (+11/-4)
autopilot/process/__init__.py (+5/-5)
autopilot/testresult.py (+37/-22)
autopilot/tests/functional/test_input_stack.py (+78/-17)
autopilot/tests/functional/test_introspection_features.py (+2/-1)
autopilot/tests/functional/test_process_emulator.py (+21/-2)
autopilot/tests/unit/fixtures.py (+41/-0)
autopilot/tests/unit/test_config.py (+11/-1)
autopilot/tests/unit/test_input.py (+18/-5)
autopilot/tests/unit/test_testresults.py (+56/-2)
autopilot/tests/unit/test_utilities.py (+132/-0)
autopilot/utilities.py (+127/-0)
debian/changelog (+109/-48)
debian/python3-autopilot.docs (+1/-0)
debian/rules (+1/-0)
docs/_templates/indexcontent.html (+79/-43)
docs/contents.rst (+5/-0)
docs/faq/faq.rst (+17/-22)
docs/faq/troubleshooting.rst (+38/-7)
docs/guides/good_tests.rst (+6/-0)
docs/guides/installation.rst (+42/-0)
docs/guides/page_object.rst (+226/-0)
docs/guides/running_ap.rst (+36/-8)
docs/index.rst (+10/-6)
docs/man.rst (+7/-7)
docs/porting/porting.rst (+3/-0)
docs/tutorial/advanced_autopilot.rst (+215/-17)
docs/tutorial/getting_started.rst (+9/-6)
docs/tutorial/tutorial.rst (+0/-3)
Text conflict in debian/changelog
To merge this branch: bzr merge lp:autopilot
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Autopilot Hackers Pending
Review via email: mp+250402@code.launchpad.net

Commit message

Autopilot Release February 20th 2015.

Description of the change

Autopilot Release February 20th 2015.

- Fix for extension base classe (lp:1376996)
- Fix for desktop file name change (lp:1411096)
- Fix for config value containing '=' char (lp:1408317)
- Fix for skipped tests not appearing in log (lp:1414632)
- Add json docs build (for web publish) (lp:1409778)
- Documentation improvements for API, Tutorial, FAQ, and Guidelines

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:542
http://jenkins.qa.ubuntu.com/job/autopilot-1.5-ci/15/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-amd64-ci/1
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-amd64-ci/1/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-armhf-ci/1
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-armhf-ci/1/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-i386-ci/1
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-i386-ci/1/artifact/work/output/*zip*/output.zip
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/1462
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-vivid-autopilot/114
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-vivid-mako/1296
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1460
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1460/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/18225
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-vivid-autopilot/110
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/751
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/751/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/autopilot-1.5-ci/15/rebuild

review: Approve (continuous-integration)
lp:autopilot updated
543. By Christopher Lee

Revert previous fix to unblock landing of other fixes and documentation updates.

Approved by PS Jenkins bot, Thomi Richards.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:543
http://jenkins.qa.ubuntu.com/job/autopilot-1.5-ci/16/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-amd64-ci/2
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-amd64-ci/2/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-armhf-ci/2
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-armhf-ci/2/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-i386-ci/2
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-i386-ci/2/artifact/work/output/*zip*/output.zip
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/1544
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-vivid-autopilot/116
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-vivid-mako/1374
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1542
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1542/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/18363
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-vivid-autopilot/112
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/770
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/770/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/autopilot-1.5-ci/16/rebuild

review: Approve (continuous-integration)
lp:autopilot updated
544. By Christopher Lee

Update debian/changelog for a better log for release.

Approved by PS Jenkins bot, Max Brustkern.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:544
http://jenkins.qa.ubuntu.com/job/autopilot-1.5-ci/17/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-amd64-ci/3
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-amd64-ci/3/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-armhf-ci/3
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-armhf-ci/3/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-i386-ci/3
        deb: http://jenkins.qa.ubuntu.com/job/autopilot-1.5-vivid-i386-ci/3/artifact/work/output/*zip*/output.zip
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/1569
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-vivid-autopilot/117
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-vivid-mako/1397
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1567
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1567/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/18408
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-vivid-autopilot/113
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/779
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/779/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/autopilot-1.5-ci/17/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README'
2--- README 2014-04-16 05:39:33 +0000
3+++ README 2015-02-26 21:47:00 +0000
4@@ -5,15 +5,73 @@
5
6 Autopilot is free software, licensed under GNU General Public License (GPLv3+).
7
8+Links
9+=====
10+
11 - Project Home (Source Code, Version Control, Bug Tracking, etc):
12 https://launchpad.net/autopilot
13
14 - Documentation (Tutorial, FAQ, API Reference, etc):
15- http://unity.ubuntu.com/autopilot/
16+ https://developer.ubuntu.com/api/devel/ubuntu-14.10/python/autopilot/
17
18 - IRC channel is #ubuntu-autopilot on irc.freenode.net
19
20-happy hacking!
21+
22+Build Instructions
23+==================
24+
25+Autopilot is not buildable within a python virtualenv, as it requires several packages that are not available on ``pypi``. Instead, either use autopilot from the source tree, or build debian packages instead. Instructions for building debian packages are below:
26+
27+Assuming a current ubuntu installation, make sure you have the build tools that are required installed::
28+
29+ $ sudo apt-get install devscripts equivs bzr-builddeb
30+
31+Then install the build-dependencies for the autopilot packages::
32+
33+ $ sudo mk-build-deps -i
34+
35+Then build the debian packages::
36+
37+ $ bzr bd
38+
39+bzr-builddeb will build binary packages into the parent directory, or into '../build-area/' if you do not own the correct gpg key to sign the package. The resulting ``.deb`` files can be installed as normal with ``sudo dpkg -i package.deb``.
40+
41+The documentation can be built separately from the debian packages::
42+
43+ $ python3 setup.py build_sphinx
44+
45+The docs are built into 'build/sphinx/html' and can be opened in the default browser with::
46+
47+ $ xdg-open build/sphinx/html/index.html
48+
49+
50+Running and Listing tests
51+=========================
52+
53+Normally running autopilot tests is as easy as::
54+
55+ $ autopilot3 run <test id>
56+
57+There are some complexities when attempting to run the autopilot tests from
58+within a development branch (this related to how autopilot modules are loaded
59+and then used to attempt to collect the required tests).
60+
61+For that reason when running autopilot's tests while hacking on it it is
62+advised to run autopilot in this manner::
63+
64+ $ python3 -m autopilot.run run autopilot.tests.unit
65+
66+Listing is similar::
67+
68+ $ python3 -m autopilot.run list autopilot.tests.unit
69+
70+For a more complete explanation for running or listing tests please see the
71+full documentation found here:
72+https://developer.ubuntu.com/api/devel/ubuntu-14.10/python/autopilot/tutorial/running_ap.html
73+
74+If you are in the root of the autopilot source tree this will run/list the tests from
75+within that local module. Otherwise autopilot will look in the system python path.
76+
77
78 Release Manual Tests
79 ====================
80@@ -21,7 +79,7 @@
81 Not all our tests are automated at the moment. Specifically, the vis tool is lacking some automated tests due to deficiancies in other packages. Until we remedy this situation, the following things need to be manually tested upon an autopilot release:
82
83 - Run the following tests by running both: ``autopilot vis`` and ``autopilot3 vis``.
84- - Run 'window-mocker -testability' and the vis tool.
85+ - Run 'window-mocker -testability' and the vis tool.
86 - Make sure you can select window-mocker from the connection list.
87 - Make sure the top-level tree node is 'window-mocker'
88 - Run the vis tool with the '-testability' flag enabled. Run a second vis tool, and make sure that the second vis tool can introspect the first.
89
90=== modified file 'autopilot/_config.py'
91--- autopilot/_config.py 2014-05-21 07:42:24 +0000
92+++ autopilot/_config.py 2015-02-26 21:47:00 +0000
93@@ -71,14 +71,14 @@
94
95 def __init__(self, config_string):
96 self._data = {}
97- for item in config_string.split(','):
98- if not item:
99- continue
100- parts = item.split('=')
101- if len(parts) == 1:
102- self._data[parts[0].lstrip()] = '1'
103- elif len(parts) == 2:
104- self._data[parts[0].lstrip()] = parts[1]
105+ config_items = (item for item in config_string.split(',') if item)
106+ for item in config_items:
107+ parts = item.split('=', 1)
108+ safe_key = parts[0].lstrip()
109+ if len(parts) == 1 and safe_key != '':
110+ self._data[safe_key] = '1'
111+ elif len(parts) == 2 and safe_key != '':
112+ self._data[safe_key] = parts[1]
113 else:
114 raise ValueError(
115 "Invalid configuration string '{}'".format(config_string)
116
117=== modified file 'autopilot/input/_X11.py'
118--- autopilot/input/_X11.py 2014-10-06 17:05:53 +0000
119+++ autopilot/input/_X11.py 2015-02-26 21:47:00 +0000
120@@ -28,6 +28,7 @@
121
122 from autopilot.display import is_point_on_any_screen, move_mouse_to_screen
123 from autopilot.utilities import (
124+ EventDelay,
125 Silence,
126 sleep,
127 StagnantStateDetector,
128@@ -286,6 +287,7 @@
129 super(Mouse, self).__init__()
130 # Try to access the screen to see if X11 mouse is supported
131 get_display()
132+ self.event_delayer = EventDelay()
133
134 @property
135 def x(self):
136@@ -316,8 +318,9 @@
137 fake_input(get_display(), X.ButtonRelease, button)
138 get_display().sync()
139
140- def click(self, button=1, press_duration=0.10):
141+ def click(self, button=1, press_duration=0.10, time_between_events=0.1):
142 """Click mouse at current location."""
143+ self.event_delayer.delay(time_between_events)
144 self.press(button)
145 sleep(press_duration)
146 self.release(button)
147
148=== modified file 'autopilot/input/__init__.py'
149--- autopilot/input/__init__.py 2014-08-21 06:46:22 +0000
150+++ autopilot/input/__init__.py 2015-02-26 21:47:00 +0000
151@@ -315,11 +315,22 @@
152 """Releases mouse button at current mouse location."""
153 raise NotImplementedError("You cannot use this class directly.")
154
155- def click(self, button=1, press_duration=0.10):
156- """Click mouse at current location."""
157+ def click(self, button=1, press_duration=0.10, time_between_events=0.1):
158+ """Click mouse at current location.
159+
160+ :param time_between_events: takes floating point to represent the
161+ delay time between subsequent clicks. Default value 0.1 represents
162+ tenth of a second.
163+
164+ """
165 raise NotImplementedError("You cannot use this class directly.")
166
167- def click_object(self, object_proxy, button=1, press_duration=0.10):
168+ def click_object(
169+ self,
170+ object_proxy,
171+ button=1,
172+ press_duration=0.10,
173+ time_between_events=0.1):
174 """Click the center point of a given object.
175
176 It does this by looking for several attributes, in order. The first
177@@ -329,12 +340,15 @@
178 * center_x, center_y
179 * x, y, w, h
180
181+ :param time_between_events: takes floating point to represent the
182+ delay time between subsequent clicks. Default value 0.1 represents
183+ tenth of a second.
184 :raises: **ValueError** if none of these attributes are found, or if an
185 attribute is of an incorrect type.
186
187 """
188 self.move_to_object(object_proxy)
189- self.click(button, press_duration)
190+ self.click(button, press_duration, time_between_events)
191
192 def move(self, x, y, animate=True, rate=10, time_between_events=0.01):
193 """Moves mouse to location (x,y).
194@@ -440,11 +454,17 @@
195 """
196 raise NotImplementedError("You cannot use this class directly.")
197
198- def tap(self, x, y, press_duration=0.1):
199- """Click (or 'tap') at given x,y coordinates."""
200+ def tap(self, x, y, press_duration=0.1, time_between_events=0.1):
201+ """Click (or 'tap') at given x,y coordinates.
202+
203+ :param time_between_events: takes floating point to represent the
204+ delay time between subsequent taps. Default value 0.1 represents
205+ tenth of a second.
206+
207+ """
208 raise NotImplementedError("You cannot use this class directly.")
209
210- def tap_object(self, object, press_duration=0.1):
211+ def tap_object(self, object, press_duration=0.1, time_between_events=0.1):
212 """Tap the center point of a given object.
213
214 It does this by looking for several attributes, in order. The first
215@@ -454,6 +474,9 @@
216 * center_x, center_y
217 * x, y, w, h
218
219+ :param time_between_events: takes floating point to represent the
220+ delay time between subsequent taps. Default value 0.1 represents
221+ tenth of a second.
222 :raises: **ValueError** if none of these attributes are found, or if an
223 attribute is of an incorrect type.
224
225@@ -596,21 +619,29 @@
226 "Touch devices do not have button %d" % (button))
227 self._device.release()
228
229- def click(self, button=1, press_duration=0.10):
230+ def click(self, button=1, press_duration=0.10, time_between_events=0.1):
231 """Press and release at the current pointer location.
232
233 If the wrapped device is a mouse, the button specification is used. If
234 it is a touch device, passing anything other than 1 will raise a
235 ValueError exception.
236
237+ :param time_between_events: takes floating point to represent the
238+ delay time between subsequent clicks/taps. Default value 0.1
239+ represents tenth of a second.
240 """
241 if isinstance(self._device, Mouse):
242- self._device.click(button, press_duration)
243+ self._device.click(button, press_duration, time_between_events)
244 else:
245 if button != 1:
246 raise ValueError(
247 "Touch devices do not have button %d" % (button))
248- self._device.tap(self._x, self._y, press_duration=press_duration)
249+ self._device.tap(
250+ self._x,
251+ self._y,
252+ press_duration=press_duration,
253+ time_between_events=time_between_events
254+ )
255
256 def move(self, x, y):
257 """Moves the pointer to the specified coordinates.
258@@ -627,7 +658,12 @@
259 self._x = x
260 self._y = y
261
262- def click_object(self, object_proxy, button=1, press_duration=0.10):
263+ def click_object(
264+ self,
265+ object_proxy,
266+ button=1,
267+ press_duration=0.10,
268+ time_between_events=0.1):
269 """
270 Attempts to move the pointer to 'object_proxy's centre point.
271 and click a button
272@@ -643,10 +679,14 @@
273 it is a touch device, passing anything other than 1 will raise a
274 ValueError exception.
275
276+ :param time_between_events: takes floating point to represent the
277+ delay time between subsequent clicks/taps. Default value 0.1
278+ represents tenth of a second.
279+
280 """
281
282 self.move_to_object(object_proxy)
283- self.click(button, press_duration)
284+ self.click(button, press_duration, time_between_events)
285
286 def move_to_object(self, object_proxy):
287 """Attempts to move the pointer to 'object_proxy's centre point.
288
289=== modified file 'autopilot/input/_uinput.py'
290--- autopilot/input/_uinput.py 2014-10-22 14:39:21 +0000
291+++ autopilot/input/_uinput.py 2015-02-26 21:47:00 +0000
292@@ -27,7 +27,7 @@
293 from autopilot.input import Keyboard as KeyboardBase
294 from autopilot.input import Touch as TouchBase
295 from autopilot.input._common import get_center_point
296-from autopilot.utilities import deprecated, sleep
297+from autopilot.utilities import deprecated, EventDelay, sleep
298
299
300 _logger = logging.getLogger(__name__)
301@@ -457,12 +457,13 @@
302 def __init__(self, device_class=_UInputTouchDevice):
303 super(Touch, self).__init__()
304 self._device = device_class()
305+ self.event_delayer = EventDelay()
306
307 @property
308 def pressed(self):
309 return self._device.pressed
310
311- def tap(self, x, y, press_duration=0.1):
312+ def tap(self, x, y, press_duration=0.1, time_between_events=0.1):
313 """Click (or 'tap') at given x and y coordinates.
314
315 :raises RuntimeError: if the finger is already pressed.
316@@ -470,11 +471,12 @@
317
318 """
319 _logger.debug("Tapping at: %d,%d", x, y)
320+ self.event_delayer.delay(time_between_events)
321 self._device.finger_down(x, y)
322 sleep(press_duration)
323 self._device.finger_up()
324
325- def tap_object(self, object_, press_duration=0.1):
326+ def tap_object(self, object_, press_duration=0.1, time_between_events=0.1):
327 """Click (or 'tap') a given object.
328
329 :raises RuntimeError: if the finger is already pressed.
330@@ -485,7 +487,12 @@
331 """
332 _logger.debug("Tapping object: %r", object)
333 x, y = get_center_point(object_)
334- self.tap(x, y, press_duration=press_duration)
335+ self.tap(
336+ x,
337+ y,
338+ press_duration=press_duration,
339+ time_between_events=time_between_events
340+ )
341
342 def press(self, x, y):
343 """Press and hold a given object or at the given coordinates.
344
345=== modified file 'autopilot/process/__init__.py'
346--- autopilot/process/__init__.py 2013-07-14 08:23:30 +0000
347+++ autopilot/process/__init__.py 2015-02-26 21:47:00 +0000
348@@ -1,7 +1,7 @@
349 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
350 #
351 # Autopilot Functional Test Tool
352-# Copyright (C) 2012-2013 Canonical
353+# Copyright (C) 2012, 2013, 2015 Canonical
354 #
355 # This program is free software: you can redistribute it and/or modify
356 # it under the terms of the GNU General Public License as published by
357@@ -39,11 +39,11 @@
358 'process-name': 'gucharmap',
359 },
360 'Calculator': {
361- 'desktop-file': 'gcalctool.desktop',
362+ 'desktop-file': 'gnome-calculator.desktop',
363 'process-name': 'gnome-calculator',
364 },
365 'Mahjongg': {
366- 'desktop-file': 'mahjongg.desktop',
367+ 'desktop-file': 'gnome-mahjongg.desktop',
368 'process-name': 'gnome-mahjongg',
369 },
370 'Remmina': {
371@@ -51,8 +51,8 @@
372 'process-name': 'remmina',
373 },
374 'System Settings': {
375- 'desktop-file': 'gnome-control-center.desktop',
376- 'process-name': 'gnome-control-center',
377+ 'desktop-file': 'unity-control-center.desktop',
378+ 'process-name': 'unity-control-center',
379 },
380 'Text Editor': {
381 'desktop-file': 'gedit.desktop',
382
383=== modified file 'autopilot/testresult.py'
384--- autopilot/testresult.py 2014-07-22 02:30:19 +0000
385+++ autopilot/testresult.py 2015-02-26 21:47:00 +0000
386@@ -43,39 +43,54 @@
387 if get_log_verbose():
388 logging.getLogger().log(level, message)
389
390- def _log_details(self, level, details):
391+ def _log_details(self, level, test):
392 """Log the relavent test details."""
393-
394- for detail in details:
395- # Skip the test-log as it was logged while the test executed
396- if detail == "test-log":
397- continue
398- detail_content = details[detail]
399- if detail_content.content_type.type == "text":
400- text = "%s: {{{\n%s}}}" % (detail, detail_content.as_text())
401- else:
402- text = "Binary attachment: \"%s\" (%s)" % (
403- detail,
404- detail_content.content_type
405- )
406- self._log(level, text)
407+ if hasattr(test, "getDetails"):
408+ details = test.getDetails()
409+ for detail in details:
410+ # Skip the test-log as it was logged while the test executed
411+ if detail == "test-log":
412+ continue
413+ detail_content = details[detail]
414+ if detail_content.content_type.type == "text":
415+ text = "%s: {{{\n%s}}}" % (
416+ detail,
417+ detail_content.as_text()
418+ )
419+ else:
420+ text = "Binary attachment: \"%s\" (%s)" % (
421+ detail,
422+ detail_content.content_type
423+ )
424+ self._log(level, text)
425
426 def addSuccess(self, test, details=None):
427 self._log(logging.INFO, "OK: %s" % (test.id()))
428- return super(LoggedTestResultDecorator, self).addSuccess(test, details)
429+ return super().addSuccess(test, details)
430
431 def addError(self, test, err=None, details=None):
432 self._log(logging.ERROR, "ERROR: %s" % (test.id()))
433- if hasattr(test, "getDetails"):
434- self._log_details(logging.ERROR, test.getDetails())
435- return super(type(self), self).addError(test, err, details)
436+ self._log_details(logging.ERROR, test)
437+ return super().addError(test, err, details)
438
439 def addFailure(self, test, err=None, details=None):
440 """Called for a test which failed an assert."""
441 self._log(logging.ERROR, "FAIL: %s" % (test.id()))
442- if hasattr(test, "getDetails"):
443- self._log_details(logging.ERROR, test.getDetails())
444- return super(type(self), self).addFailure(test, err, details)
445+ self._log_details(logging.ERROR, test)
446+ return super().addFailure(test, err, details)
447+
448+ def addSkip(self, test, reason=None, details=None):
449+ self._log(logging.INFO, "SKIP: %s" % test.id())
450+ return super().addSkip(test, reason, details)
451+
452+ def addUnexpectedSuccess(self, test, details=None):
453+ self._log(logging.ERROR, "UNEXPECTED SUCCESS: %s" % test.id())
454+ self._log_details(logging.ERROR, test)
455+ return super().addUnexpectedSuccess(test, details)
456+
457+ def addExpectedFailure(self, test, err=None, details=None):
458+ self._log(logging.INFO, "EXPECTED FAILURE: %s" % test.id())
459+ return super().addExpectedFailure(test, err, details)
460
461
462 def get_output_formats():
463
464=== modified file 'autopilot/tests/functional/test_input_stack.py'
465--- autopilot/tests/functional/test_input_stack.py 2014-10-06 23:23:42 +0000
466+++ autopilot/tests/functional/test_input_stack.py 2015-02-26 21:47:00 +0000
467@@ -23,9 +23,16 @@
468 import os
469 from tempfile import mktemp
470 from testtools import TestCase, skipIf
471-from testtools.matchers import IsInstance, Equals, raises
472+from testtools.matchers import (
473+ IsInstance,
474+ Equals,
475+ raises,
476+ GreaterThan
477+)
478+from testscenarios import TestWithScenarios
479 from textwrap import dedent
480 from time import sleep
481+import timeit
482 from unittest import SkipTest
483 from unittest.mock import patch
484
485@@ -43,6 +50,22 @@
486 from autopilot.utilities import on_test_started
487
488
489+class ElapsedTimeCounter(object):
490+
491+ """A simple utility to count the amount of real time that passes."""
492+
493+ def __enter__(self):
494+ self._start_time = timeit.default_timer()
495+ return self
496+
497+ def __exit__(self, *args):
498+ pass
499+
500+ @property
501+ def elapsed_time(self):
502+ return timeit.default_timer() - self._start_time
503+
504+
505 class InputStackKeyboardBase(AutopilotTestCase):
506
507 scenarios = [
508@@ -328,8 +351,27 @@
509 )
510
511
512+class MockAppMouseTestBase(AutopilotTestCase):
513+
514+ def start_mock_app(self):
515+ window_spec_file = mktemp(suffix='.json')
516+ window_spec = {"Contents": "MouseTest"}
517+ json.dump(
518+ window_spec,
519+ open(window_spec_file, 'w')
520+ )
521+ self.addCleanup(os.remove, window_spec_file)
522+
523+ return self.launch_test_application(
524+ 'window-mocker', window_spec_file, app_type='qt')
525+
526+
527 class MouseTestCase(AutopilotTestCase, tests.LogHandlerTestCase):
528
529+ def setUp(self):
530+ super(MouseTestCase, self).setUp()
531+ self.device = Mouse.create()
532+
533 @skipIf(platform.model() != "Desktop", "Only suitable on Desktop (Mouse)")
534 def test_move_to_nonint_point(self):
535 """Test mouse does not get stuck when we move to a non-integer point.
536@@ -338,11 +380,10 @@
537
538 """
539 screen_geometry = Display.create().get_screen_geometry(0)
540- device = Mouse.create()
541 target_x = screen_geometry[0] + 10
542 target_y = screen_geometry[1] + 10.6
543- device.move(target_x, target_y)
544- self.assertEqual(device.position(), (target_x, int(target_y)))
545+ self.device.move(target_x, target_y)
546+ self.assertEqual(self.device.position(), (target_x, int(target_y)))
547
548 @patch('autopilot.platform.model', new=lambda *args: "Not Desktop", )
549 def test_mouse_creation_on_device_raises_useful_error(self):
550@@ -368,7 +409,7 @@
551
552
553 @skipIf(platform.model() != "Desktop", "Only suitable on Desktop (WinMocker)")
554-class TouchTests(AutopilotTestCase):
555+class TouchTests(MockAppMouseTestBase):
556
557 def setUp(self):
558 super(TouchTests, self).setUp()
559@@ -379,18 +420,6 @@
560 self.button_status = self.app.select_single(
561 'QLabel', objectName='button_status')
562
563- def start_mock_app(self):
564- window_spec_file = mktemp(suffix='.json')
565- window_spec = {"Contents": "MouseTest"}
566- json.dump(
567- window_spec,
568- open(window_spec_file, 'w')
569- )
570- self.addCleanup(os.remove, window_spec_file)
571-
572- return self.launch_test_application(
573- 'window-mocker', window_spec_file, app_type='qt')
574-
575 def test_tap(self):
576 x, y = get_center_point(self.widget)
577 self.device.tap(x, y)
578@@ -477,6 +506,38 @@
579 self.assertThat(p.y, Equals(123))
580
581
582+@skipIf(platform.model() != "Desktop", "Window mocker only available on X11")
583+class InputEventDelayTests(MockAppMouseTestBase, TestWithScenarios):
584+
585+ scenarios = [
586+ ('Touch', dict(input_class=Touch)),
587+ ('Mouse', dict(input_class=Mouse)),
588+ ]
589+
590+ def setUp(self):
591+ super(InputEventDelayTests, self).setUp()
592+ self.device = Pointer(self.input_class.create())
593+ self.widget = self.get_mock_app_main_widget()
594+
595+ def get_mock_app_main_widget(self):
596+ self.app = self.start_mock_app()
597+ return self.app.select_single('MouseTestWidget')
598+
599+ def test_subsequent_events_delay(self):
600+ with ElapsedTimeCounter() as time_counter:
601+ for i in range(3):
602+ self.device.click_object(self.widget, time_between_events=0.6)
603+
604+ self.assertThat(time_counter.elapsed_time, GreaterThan(1.0))
605+
606+ def test_subsequent_events_default_delay(self):
607+ with ElapsedTimeCounter() as time_counter:
608+ for i in range(10):
609+ self.device.click_object(self.widget)
610+
611+ self.assertThat(time_counter.elapsed_time, GreaterThan(0.9))
612+
613+
614 class InputStackCleanupTests(TestCase):
615
616 def test_cleanup_called(self):
617
618=== modified file 'autopilot/tests/functional/test_introspection_features.py'
619--- autopilot/tests/functional/test_introspection_features.py 2014-07-31 04:46:16 +0000
620+++ autopilot/tests/functional/test_introspection_features.py 2015-02-26 21:47:00 +0000
621@@ -24,7 +24,7 @@
622 import subprocess
623 import tempfile
624 from tempfile import mktemp
625-from testtools import skipIf
626+from testtools import skip, skipIf
627 from testtools.matchers import (
628 Contains,
629 Equals,
630@@ -99,6 +99,7 @@
631 Equals(WindowMockerApp)
632 )
633
634+ @skip("Currently fails due to lp:1425721 (and lp:1376996)")
635 def test_customised_proxy_classes_have_extension_classes(self):
636 class WindowMockerApp(EmulatorBase):
637 @classmethod
638
639=== modified file 'autopilot/tests/functional/test_process_emulator.py'
640--- autopilot/tests/functional/test_process_emulator.py 2014-10-22 17:23:02 +0000
641+++ autopilot/tests/functional/test_process_emulator.py 2015-02-26 21:47:00 +0000
642@@ -1,7 +1,7 @@
643 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
644 #
645 # Autopilot Functional Test Tool
646-# Copyright (C) 2012-2014 Canonical
647+# Copyright (C) 2012, 2013, 2014, 2015 Canonical
648 #
649 # This program is free software: you can redistribute it and/or modify
650 # it under the terms of the GNU General Public License as published by
651@@ -87,7 +87,7 @@
652 # locale='C' does not work here as this goes through bamf, so we can't
653 # assert the precise name
654 self.assertThat(app.name, NotEquals(''))
655- self.assertThat(app.desktop_file, Equals('gcalctool.desktop'))
656+ self.assertThat(app.desktop_file, Equals('gnome-calculator.desktop'))
657
658 def test_start_app_window(self):
659 """Ensure we can start an Application Window."""
660@@ -119,6 +119,25 @@
661 self.assertThat(list(window.geometry), Equals(proxy_window.geometry))
662
663
664+@skipIf(model() != "Desktop", "Not suitable for device (ProcManager)")
665+class StartKnowAppsTests(AutopilotTestCase):
666+
667+ scenarios = [
668+ (app_name, {
669+ 'app_name': app_name,
670+ 'desktop_file': (
671+ ProcessManager.KNOWN_APPS[app_name]['desktop-file'])
672+ })
673+ for app_name in ProcessManager.KNOWN_APPS
674+ ]
675+
676+ def test_start_app_window(self):
677+ """Ensure we can start all the known applications."""
678+ app = self.process_manager.start_app_window(self.app_name, locale='C')
679+
680+ self.assertThat(app, NotEquals(None))
681+
682+
683 class ProcessManagerApplicationNoCleanupTests(AutopilotTestCase):
684 """Testing the process manager without the automated cleanup that running
685 within as an AutopilotTestCase provides.
686
687=== added file 'autopilot/tests/unit/fixtures.py'
688--- autopilot/tests/unit/fixtures.py 1970-01-01 00:00:00 +0000
689+++ autopilot/tests/unit/fixtures.py 2015-02-26 21:47:00 +0000
690@@ -0,0 +1,41 @@
691+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
692+#
693+# Autopilot Functional Test Tool
694+# Copyright (C) 2015 Canonical
695+#
696+# This program is free software: you can redistribute it and/or modify
697+# it under the terms of the GNU General Public License as published by
698+# the Free Software Foundation, either version 3 of the License, or
699+# (at your option) any later version.
700+#
701+# This program is distributed in the hope that it will be useful,
702+# but WITHOUT ANY WARRANTY; without even the implied warranty of
703+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
704+# GNU General Public License for more details.
705+#
706+# You should have received a copy of the GNU General Public License
707+# along with this program. If not, see <http://www.gnu.org/licenses/>.
708+#
709+
710+"""Fixtures to be used in autopilot's unit test suite."""
711+
712+from fixtures import Fixture
713+
714+from autopilot import globals as _g
715+
716+
717+class AutopilotVerboseLogging(Fixture):
718+ """Set the autopilot verbose log flag."""
719+
720+ def __init__(self, verbose_logging=True):
721+ super().__init__()
722+ self._desired_state = verbose_logging
723+
724+ def setUp(self):
725+ super().setUp()
726+ if _g.get_log_verbose() != self._desired_state:
727+ self.addCleanup(
728+ _g.set_log_verbose,
729+ _g.get_log_verbose()
730+ )
731+ _g.set_log_verbose(self._desired_state)
732
733=== modified file 'autopilot/tests/unit/test_config.py'
734--- autopilot/tests/unit/test_config.py 2014-05-23 11:28:21 +0000
735+++ autopilot/tests/unit/test_config.py 2015-02-26 21:47:00 +0000
736@@ -51,6 +51,10 @@
737 d = config.ConfigDict('foo')
738 self.assertEqual(d['foo'], '1')
739
740+ def test_single_value_containing_equals_symbol(self):
741+ d = config.ConfigDict('foo=b=a')
742+ self.assertEqual(d['foo'], 'b=a')
743+
744 def test_multiple_simple_keys(self):
745 d = config.ConfigDict('foo,bar')
746 self.assertTrue('foo' in d)
747@@ -87,8 +91,14 @@
748 d = config.ConfigDict(' foo=bar, bar=baz')
749 self.assertEqual(set(d.keys()), {'foo', 'bar'})
750
751+ def test_raises_ValueError_on_blank_key(self):
752+ self.assertRaises(ValueError, lambda: config.ConfigDict('=,'))
753+
754+ def test_raises_ValueError_on_space_key(self):
755+ self.assertRaises(ValueError, lambda: config.ConfigDict(' =,'))
756+
757 def test_raises_ValueError_on_invalid_string(self):
758- self.assertRaises(ValueError, lambda: config.ConfigDict('f=b=c'))
759+ self.assertRaises(ValueError, lambda: config.ConfigDict('f,='))
760
761 def test_iter(self):
762 k = config.ConfigDict('foo').keys()
763
764=== modified file 'autopilot/tests/unit/test_input.py'
765--- autopilot/tests/unit/test_input.py 2014-10-22 14:39:21 +0000
766+++ autopilot/tests/unit/test_input.py 2015-02-26 21:47:00 +0000
767@@ -790,17 +790,28 @@
768 touch.tap_object(object_)
769
770 mock_tap.assert_called_once_with(
771- object_.center_x, object_.center_y, press_duration=0.1)
772+ object_.center_x,
773+ object_.center_y,
774+ press_duration=0.1,
775+ time_between_events=0.1
776+ )
777
778 def test_tap_object_with_duration_must_call_tap_with_specified_time(self):
779 object_ = make_fake_object(center=True)
780 touch = self.get_touch_with_mocked_backend()
781
782 with patch.object(touch, 'tap') as mock_tap:
783- touch.tap_object(object_, press_duration=10)
784+ touch.tap_object(
785+ object_,
786+ press_duration=10
787+ )
788
789 mock_tap.assert_called_once_with(
790- object_.center_x, object_.center_y, press_duration=10)
791+ object_.center_x,
792+ object_.center_y,
793+ press_duration=10,
794+ time_between_events=0.1
795+ )
796
797
798 class MultipleUInputTouchBackend(_uinput._UInputTouchDevice):
799@@ -947,11 +958,13 @@
800 with patch.object(pointer._device, 'tap') as mock_tap:
801 pointer.click(1)
802
803- mock_tap.assert_called_once_with(0, 0, press_duration=0.1)
804+ mock_tap.assert_called_once_with(
805+ 0, 0, press_duration=0.1, time_between_events=0.1)
806
807 def test_press_with_specified_press_duration(self):
808 pointer = self.get_pointer_with_touch_backend_with_mock_device()
809 with patch.object(pointer._device, 'tap') as mock_tap:
810 pointer.click(1, press_duration=10)
811
812- mock_tap.assert_called_once_with(0, 0, press_duration=10)
813+ mock_tap.assert_called_once_with(
814+ 0, 0, press_duration=10, time_between_events=0.1)
815
816=== modified file 'autopilot/tests/unit/test_testresults.py'
817--- autopilot/tests/unit/test_testresults.py 2014-07-24 00:24:41 +0000
818+++ autopilot/tests/unit/test_testresults.py 2015-02-26 21:47:00 +0000
819@@ -21,6 +21,8 @@
820 from unittest.mock import Mock, patch
821 import os
822 import tempfile
823+
824+from fixtures import FakeLogger
825 from testtools import TestCase, PlaceHolder
826 from testtools.content import Content, ContentType, text_content
827 from testtools.matchers import Contains, raises, NotEquals
828@@ -29,6 +31,8 @@
829
830 from autopilot import testresult
831 from autopilot import run
832+from autopilot.testcase import multiply_scenarios
833+from autopilot.tests.unit.fixtures import AutopilotVerboseLogging
834
835
836 class LoggedTestResultDecoratorTests(TestCase):
837@@ -91,13 +95,14 @@
838 result._log_details(0, fake_details)
839
840 def test_log_details_logs_binary_attachment_details(self):
841- fake_details = dict(
842+ fake_test = Mock()
843+ fake_test.getDetails = lambda: dict(
844 TestBinary=Content(ContentType('image', 'png'), lambda: b'')
845 )
846
847 result = testresult.LoggedTestResultDecorator(None)
848 with patch.object(result, '_log') as p_log:
849- result._log_details(0, fake_details)
850+ result._log_details(0, fake_test)
851
852 p_log.assert_called_once_with(
853 0,
854@@ -108,6 +113,55 @@
855 )
856
857
858+class TestResultLogMessageTests(WithScenarios, TestCase):
859+
860+ scenarios = multiply_scenarios(
861+ # Scenarios for each format we support:
862+ [(f, dict(format=f)) for f in testresult.get_output_formats().keys()],
863+ # Scenarios for each test outcome:
864+ [
865+ ('success', dict(outcome='addSuccess', log='OK: %s')),
866+ ('error', dict(outcome='addError', log='ERROR: %s')),
867+ ('fail', dict(outcome='addFailure', log='FAIL: %s')),
868+ (
869+ 'unexpected success',
870+ dict(
871+ outcome='addUnexpectedSuccess',
872+ log='UNEXPECTED SUCCESS: %s',
873+ )
874+ ),
875+ ('skip', dict(outcome='addSkip', log='SKIP: %s')),
876+ (
877+ 'expected failure',
878+ dict(
879+ outcome='addExpectedFailure',
880+ log='EXPECTED FAILURE: %s',
881+ )
882+ ),
883+
884+ ]
885+ )
886+
887+ def make_result_object(self):
888+ output_path = tempfile.mktemp()
889+ self.addCleanup(remove_if_exists, output_path)
890+ result_constructor = testresult.get_output_formats()[self.format]
891+ return result_constructor(
892+ stream=run.get_output_stream(self.format, output_path),
893+ failfast=False,
894+ )
895+
896+ def test_outcome_logs(self):
897+ test_id = self.getUniqueString()
898+ test = PlaceHolder(test_id, outcome=self.outcome)
899+ result = self.make_result_object()
900+ result.startTestRun()
901+ self.useFixture(AutopilotVerboseLogging())
902+ with FakeLogger() as log:
903+ test.run(result)
904+ self.assertThat(log.output, Contains(self.log % test_id))
905+
906+
907 class OutputFormatFactoryTests(TestCase):
908
909 def test_has_text_format(self):
910
911=== modified file 'autopilot/tests/unit/test_utilities.py'
912--- autopilot/tests/unit/test_utilities.py 2014-05-29 19:44:44 +0000
913+++ autopilot/tests/unit/test_utilities.py 2015-02-26 21:47:00 +0000
914@@ -32,10 +32,13 @@
915 import timeit
916
917 from autopilot.utilities import (
918+ _raise_if_time_delta_not_sane,
919 _raise_on_unknown_kwargs,
920+ _sleep_for_calculated_delta,
921 cached_result,
922 compatible_repr,
923 deprecated,
924+ EventDelay,
925 sleep,
926 )
927
928@@ -92,6 +95,135 @@
929 patched_time.sleep.assert_called_once_with(1.0)
930
931
932+class EventDelayTests(TestCase):
933+
934+ def test_mocked_event_delayer_contextmanager(self):
935+ event_delayer = EventDelay()
936+ with event_delayer.mocked():
937+ # The first call of delay() only stores the last time
938+ # stamp, it is only the second call where the delay
939+ # actually happens. So we call delay() twice here to
940+ # ensure mocking is working as expected.
941+ event_delayer.delay(duration=0)
942+ event_delayer.delay(duration=3)
943+ self.assertAlmostEqual(sleep.total_time_slept(), 3, places=1)
944+
945+ def test_last_event_start_at_zero(self):
946+ event_delayer = EventDelay()
947+ self.assertThat(event_delayer.last_event_time(), Equals(0.0))
948+
949+ def test_last_event_delay_counter_updates_on_first_call(self):
950+ event_delayer = EventDelay()
951+ event_delayer.delay(duration=1.0, current_time=lambda: 10)
952+
953+ self.assertThat(event_delayer._last_event, Equals(10.0))
954+
955+ def test_first_call_to_delay_causes_no_sleep(self):
956+ event_delayer = EventDelay()
957+ with sleep.mocked() as mocked_sleep:
958+ event_delayer.delay(duration=0.0)
959+ self.assertThat(mocked_sleep.total_time_slept(), Equals(0.0))
960+
961+ def test_second_call_to_delay_causes_sleep(self):
962+ event_delayer = EventDelay()
963+ with sleep.mocked() as mocked_sleep:
964+ event_delayer.delay(duration=0, current_time=lambda: 100)
965+ event_delayer.delay(duration=10, current_time=lambda: 105)
966+ self.assertThat(mocked_sleep.total_time_slept(), Equals(5.0))
967+
968+ def test_no_delay_if_time_jumps_since_last_event(self):
969+ event_delayer = EventDelay()
970+ with sleep.mocked() as mocked_sleep:
971+ event_delayer.delay(duration=2, current_time=lambda: 100)
972+ event_delayer.delay(duration=2, current_time=lambda: 110)
973+ self.assertThat(mocked_sleep.total_time_slept(), Equals(0.0))
974+
975+ def test_no_delay_if_given_delay_time_negative(self):
976+ event_delayer = EventDelay()
977+ with sleep.mocked() as mocked_sleep:
978+ event_delayer.delay(duration=-2, current_time=lambda: 100)
979+ event_delayer.delay(duration=-2, current_time=lambda: 101)
980+ self.assertThat(mocked_sleep.total_time_slept(), Equals(0.0))
981+
982+ def test_sleep_delta_calculator_returns_zero_if_time_delta_negative(self):
983+ result = _sleep_for_calculated_delta(100, 97, 2)
984+ self.assertThat(result, Equals(0.0))
985+
986+ def test_sleep_delta_calculator_doesnt_sleep_if_time_delta_negative(self):
987+ with sleep.mocked() as mocked_sleep:
988+ _sleep_for_calculated_delta(100, 97, 2)
989+ self.assertThat(mocked_sleep.total_time_slept(), Equals(0.0))
990+
991+ def test_sleep_delta_calculator_returns_zero_if_time_delta_zero(self):
992+ result = _sleep_for_calculated_delta(100, 98, 2)
993+ self.assertThat(result, Equals(0.0))
994+
995+ def test_sleep_delta_calculator_doesnt_sleep_if_time_delta_zero(self):
996+ with sleep.mocked() as mocked_sleep:
997+ _sleep_for_calculated_delta(100, 98, 2)
998+ self.assertThat(mocked_sleep.total_time_slept(), Equals(0.0))
999+
1000+ def test_sleep_delta_calculator_returns_non_zero_if_delta_not_zero(self):
1001+ with sleep.mocked():
1002+ result = _sleep_for_calculated_delta(101, 100, 2)
1003+ self.assertThat(result, Equals(1.0))
1004+
1005+ def test_sleep_delta_calc_returns_zero_if_gap_duration_negative(self):
1006+ result = _sleep_for_calculated_delta(100, 99, -2)
1007+ self.assertEquals(result, 0.0)
1008+
1009+ def test_sleep_delta_calc_raises_if_last_event_ahead_current_time(self):
1010+ self.assertRaises(
1011+ ValueError,
1012+ _sleep_for_calculated_delta,
1013+ current_time=100,
1014+ last_event_time=110,
1015+ gap_duration=2
1016+ )
1017+
1018+ def test_sleep_delta_calc_raises_if_last_event_equals_current_time(self):
1019+ self.assertRaises(
1020+ ValueError,
1021+ _sleep_for_calculated_delta,
1022+ current_time=100,
1023+ last_event_time=100,
1024+ gap_duration=2
1025+ )
1026+
1027+ def test_sleep_delta_calc_raises_if_current_time_negative(self):
1028+ self.assertRaises(
1029+ ValueError,
1030+ _sleep_for_calculated_delta,
1031+ current_time=-100,
1032+ last_event_time=10,
1033+ gap_duration=10
1034+ )
1035+
1036+ def test_time_sanity_checker_raises_if_time_smaller_than_last_event(self):
1037+ self.assertRaises(
1038+ ValueError,
1039+ _raise_if_time_delta_not_sane,
1040+ current_time=90,
1041+ last_event_time=100
1042+ )
1043+
1044+ def test_time_sanity_checker_raises_if_time_equal_last_event_time(self):
1045+ self.assertRaises(
1046+ ValueError,
1047+ _raise_if_time_delta_not_sane,
1048+ current_time=100,
1049+ last_event_time=100
1050+ )
1051+
1052+ def test_time_sanity_checker_raises_if_time_negative_last_event_not(self):
1053+ self.assertRaises(
1054+ ValueError,
1055+ _raise_if_time_delta_not_sane,
1056+ current_time=-100,
1057+ last_event_time=100
1058+ )
1059+
1060+
1061 class CompatibleReprTests(TestCase):
1062
1063 def test_py3_unicode_is_untouched(self):
1064
1065=== modified file 'autopilot/utilities.py'
1066--- autopilot/utilities.py 2014-05-23 14:01:05 +0000
1067+++ autopilot/utilities.py 2015-02-26 21:47:00 +0000
1068@@ -474,3 +474,130 @@
1069
1070 def reset_cache(self):
1071 self._cache.clear()
1072+
1073+
1074+class EventDelay(object):
1075+
1076+ """Delay execution of a subsequent event for a certain period
1077+ of time.
1078+
1079+ To delay the execution of a subsequent event for two seconds
1080+ use it like this::
1081+
1082+ from autopilot.utilities import EventDelay
1083+
1084+ event_delayer = EventDelay()
1085+
1086+ def print_something():
1087+ event_delayer.delay(2)
1088+ print("Hi! I am an event.")
1089+
1090+ print_something()
1091+ # It will take 2 seconds for second print()
1092+ # to happen.
1093+ print_something()
1094+
1095+ """
1096+
1097+ def __init__(self):
1098+ self._last_event = 0.0
1099+
1100+ @contextmanager
1101+ def mocked(self):
1102+ """Enable mocking for the EventDelay class
1103+
1104+ Also mocks all calls to autopilot.utilities.sleep.
1105+ One my use it like::
1106+
1107+ from autopilot.utilities import EventDelay
1108+
1109+ event_delayer = EventDelay()
1110+ with event_delayer.mocked() as mocked_delay:
1111+ event_delayer.delay(3)
1112+ # This call will return instantly as the sleep
1113+ # is mocked, just updating the _last_event variable.
1114+ event_delayer.delay(10)
1115+ self.assertThat(mocked_delay._last_event, GreaterThan(0.0))
1116+
1117+ """
1118+ sleep.enable_mock()
1119+ try:
1120+ yield self
1121+ finally:
1122+ sleep.disable_mock()
1123+
1124+ def last_event_time(self):
1125+ """return the time when delay() was last called."""
1126+ return self._last_event
1127+
1128+ def delay(self, duration, current_time=time.monotonic):
1129+ """Delay the next event for a given amount of time.
1130+
1131+ To humanize events, so that if a certain action is repeated
1132+ continuously, there is a delay between each subsequent action.
1133+
1134+ :param duration: Time interval between events.
1135+ :param current_time: Specify the block of time to use as relative
1136+ time. It is a float, representing time with precision of
1137+ microseconds. Only for testing purpose. Default value is the
1138+ monotonic time. 0.1 is the tenth part of a second.
1139+ :raises ValueError: If the time stopped or went back since last
1140+ event.
1141+
1142+ """
1143+ monotime = current_time()
1144+ _raise_if_time_delta_not_sane(monotime, self._last_event)
1145+ time_slept = 0.0
1146+ if monotime < (self._last_event + duration):
1147+ time_slept = _sleep_for_calculated_delta(
1148+ monotime,
1149+ self._last_event,
1150+ duration
1151+ )
1152+
1153+ self._last_event = monotime + time_slept
1154+
1155+
1156+def _raise_if_time_delta_not_sane(current_time, last_event_time):
1157+ """Will raise a ValueError exception if current_time is before
1158+ the last event or equal to it.
1159+
1160+ """
1161+ if current_time == last_event_time:
1162+ raise ValueError(
1163+ 'current_time must be more than the last event time.'
1164+ )
1165+ elif current_time < last_event_time:
1166+ raise ValueError(
1167+ 'current_time must not be behind the last event time.'
1168+ )
1169+
1170+
1171+def _sleep_for_calculated_delta(current_time, last_event_time, gap_duration):
1172+ """Sleep for the remaining time between the last event time
1173+ and duration.
1174+
1175+ Given a duration in fractional seconds, ensure that at least
1176+ that given amount of time occurs since the last event time.
1177+ e.g. If 4 seconds have elapsed since the last event and the
1178+ requested gap duration was 10 seconds, sleep for 6 seconds.
1179+
1180+ :param float current_timestamp: Current monotonic time,
1181+ in fractional seconds, used to calculate the time delta
1182+ since last event.
1183+ :param float last_event_timestamp: The last timestamp that
1184+ the previous delay occured.
1185+ :param float gap_duration: Maximum time, in fractional seconds,
1186+ to be slept between two events.
1187+ :return: Time, in fractional seconds, for which sleep happened.
1188+ :raises ValueError: If last_event_time equals current_time or
1189+ is ahead of current_time.
1190+
1191+ """
1192+ _raise_if_time_delta_not_sane(current_time, last_event_time)
1193+ time_delta = (last_event_time + gap_duration) - current_time
1194+ if time_delta > 0.0:
1195+ sleep(time_delta)
1196+ return time_delta
1197+ else:
1198+ return 0.0
1199
1200=== modified file 'debian/changelog'
1201--- debian/changelog 2014-10-31 21:50:09 +0000
1202+++ debian/changelog 2015-02-26 21:47:00 +0000
1203@@ -1,51 +1,112 @@
1204-autopilot (1.5.0+15.04.20141031-0ubuntu1) vivid; urgency=low
1205-
1206- [ Thomi Richards ]
1207- * Autopilot release: - Add support for large timestamps - Add per-test
1208- timeouts - Add press duration to touch clicks - Improved logging -
1209- Make own autopilot functional test runnable with testtools runner.
1210-
1211- [ Leo Arias ]
1212- * Autopilot release: - Add support for large timestamps - Add per-test
1213- timeouts - Add press duration to touch clicks - Improved logging -
1214- Make own autopilot functional test runnable with testtools runner.
1215-
1216- [ Leonardo Arias Fonseca ]
1217- * Autopilot release: - Add support for large timestamps - Add per-test
1218- timeouts - Add press duration to touch clicks - Improved logging -
1219- Make own autopilot functional test runnable with testtools runner.
1220-
1221- [ Christopher Lee ]
1222- * Autopilot release: - Add support for large timestamps - Add per-test
1223- timeouts - Add press duration to touch clicks - Improved logging -
1224- Make own autopilot functional test runnable with testtools runner.
1225-
1226- [ nskaggs ]
1227- * Autopilot release: - Add support for large timestamps - Add per-test
1228- timeouts - Add press duration to touch clicks - Improved logging -
1229- Make own autopilot functional test runnable with testtools runner.
1230-
1231- -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 31 Oct 2014 21:50:09 +0000
1232-
1233-autopilot (1.5.0+14.10.20141022~rtm-0ubuntu1) 14.09; urgency=low
1234-
1235- [ Christopher Lee ]
1236- * Make autopilot-touch a meta package by removing the apparmor file
1237- install rules
1238-
1239- -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 22 Oct 2014 21:11:30 +0000
1240-
1241-autopilot (1.5.0+14.10.20140812-0ubuntu1) utopic; urgency=low
1242-
1243- [ Thomi Richards ]
1244- * Release of Autopilot. Bugfix for lp:1306330 & lp:1348399 (LP:
1245- #1306330, #1078732, #1348399)
1246-
1247- -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 12 Aug 2014 09:53:34 +0000
1248-
1249-autopilot (1.5.0+14.10.20140806.1-0ubuntu1) utopic; urgency=medium
1250-
1251- [ Christopher Lee ]
1252+<<<<<<< TREE
1253+autopilot (1.5.0+15.04.20141031-0ubuntu1) vivid; urgency=low
1254+
1255+ [ Thomi Richards ]
1256+ * Autopilot release: - Add support for large timestamps - Add per-test
1257+ timeouts - Add press duration to touch clicks - Improved logging -
1258+ Make own autopilot functional test runnable with testtools runner.
1259+
1260+ [ Leo Arias ]
1261+ * Autopilot release: - Add support for large timestamps - Add per-test
1262+ timeouts - Add press duration to touch clicks - Improved logging -
1263+ Make own autopilot functional test runnable with testtools runner.
1264+
1265+ [ Leonardo Arias Fonseca ]
1266+ * Autopilot release: - Add support for large timestamps - Add per-test
1267+ timeouts - Add press duration to touch clicks - Improved logging -
1268+ Make own autopilot functional test runnable with testtools runner.
1269+
1270+ [ Christopher Lee ]
1271+ * Autopilot release: - Add support for large timestamps - Add per-test
1272+ timeouts - Add press duration to touch clicks - Improved logging -
1273+ Make own autopilot functional test runnable with testtools runner.
1274+
1275+ [ nskaggs ]
1276+ * Autopilot release: - Add support for large timestamps - Add per-test
1277+ timeouts - Add press duration to touch clicks - Improved logging -
1278+ Make own autopilot functional test runnable with testtools runner.
1279+
1280+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 31 Oct 2014 21:50:09 +0000
1281+
1282+autopilot (1.5.0+14.10.20141022~rtm-0ubuntu1) 14.09; urgency=low
1283+
1284+ [ Christopher Lee ]
1285+ * Make autopilot-touch a meta package by removing the apparmor file
1286+ install rules
1287+
1288+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 22 Oct 2014 21:11:30 +0000
1289+
1290+autopilot (1.5.0+14.10.20140812-0ubuntu1) utopic; urgency=low
1291+
1292+ [ Thomi Richards ]
1293+ * Release of Autopilot. Bugfix for lp:1306330 & lp:1348399 (LP:
1294+ #1306330, #1078732, #1348399)
1295+
1296+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 12 Aug 2014 09:53:34 +0000
1297+
1298+autopilot (1.5.0+14.10.20140806.1-0ubuntu1) utopic; urgency=medium
1299+
1300+ [ Christopher Lee ]
1301+=======
1302+autopilot (1.5.0) UNRELEASED; urgency=medium
1303+
1304+ * Fix for desktop file name change (lp:1411096)
1305+ Fix for config value containing '=' char (lp:1408317)
1306+ Fix for skipped tests not appearing in log (lp:1414632)
1307+ Add json docs build (for web publish) (lp:1409778)
1308+ Documentation improvements for API, Tutorial, FAQ, and Guidelines
1309+
1310+ -- Christopher Lee <chris.lee@canonical.com> Fri, 27 Feb 2015 10:16:42 +1300
1311+
1312+autopilot (1.5.0+15.04.20141031-0ubuntu1) vivid; urgency=low
1313+
1314+ [ Thomi Richards ]
1315+ * Autopilot release: - Add support for large timestamps - Add per-test
1316+ timeouts - Add press duration to touch clicks - Improved logging -
1317+ Make own autopilot functional test runnable with testtools runner.
1318+
1319+ [ Leo Arias ]
1320+ * Autopilot release: - Add support for large timestamps - Add per-test
1321+ timeouts - Add press duration to touch clicks - Improved logging -
1322+ Make own autopilot functional test runnable with testtools runner.
1323+
1324+ [ Leonardo Arias Fonseca ]
1325+ * Autopilot release: - Add support for large timestamps - Add per-test
1326+ timeouts - Add press duration to touch clicks - Improved logging -
1327+ Make own autopilot functional test runnable with testtools runner.
1328+
1329+ [ Christopher Lee ]
1330+ * Autopilot release: - Add support for large timestamps - Add per-test
1331+ timeouts - Add press duration to touch clicks - Improved logging -
1332+ Make own autopilot functional test runnable with testtools runner.
1333+
1334+ [ nskaggs ]
1335+ * Autopilot release: - Add support for large timestamps - Add per-test
1336+ timeouts - Add press duration to touch clicks - Improved logging -
1337+ Make own autopilot functional test runnable with testtools runner.
1338+
1339+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 31 Oct 2014 21:50:09 +0000
1340+
1341+autopilot (1.5.0+14.10.20141022~rtm-0ubuntu1) 14.09; urgency=low
1342+
1343+ [ Christopher Lee ]
1344+ * Make autopilot-touch a meta package by removing the apparmor file
1345+ install rules
1346+
1347+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 22 Oct 2014 21:11:30 +0000
1348+
1349+autopilot (1.5.0+14.10.20140812-0ubuntu1) utopic; urgency=low
1350+
1351+ [ Thomi Richards ]
1352+ * Release of Autopilot. Bugfix for lp:1306330 & lp:1348399 (LP:
1353+ #1306330, #1078732, #1348399)
1354+
1355+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 12 Aug 2014 09:53:34 +0000
1356+
1357+autopilot (1.5.0+14.10.20140806.1-0ubuntu1) utopic; urgency=medium
1358+
1359+ [ Christopher Lee ]
1360+>>>>>>> MERGE-SOURCE
1361 [Thomi Richards]
1362 * Fix the development make_coverage.py script.
1363 * Remove the python3-six dependency.
1364
1365=== modified file 'debian/python3-autopilot.docs'
1366--- debian/python3-autopilot.docs 2014-05-01 23:41:32 +0000
1367+++ debian/python3-autopilot.docs 2015-02-26 21:47:00 +0000
1368@@ -1,1 +1,2 @@
1369 build/sphinx/html/
1370+build/sphinx/json/
1371
1372=== modified file 'debian/rules'
1373--- debian/rules 2014-07-23 03:37:24 +0000
1374+++ debian/rules 2015-02-26 21:47:00 +0000
1375@@ -15,6 +15,7 @@
1376 python3 -m flake8.run .
1377 dh_auto_build
1378 python3 setup.py build_sphinx -b html
1379+ python3 setup.py build_sphinx -b json
1380 python3 setup.py build_sphinx -b man
1381
1382 override_dh_auto_test:
1383
1384=== modified file 'docs/_templates/indexcontent.html'
1385--- docs/_templates/indexcontent.html 2014-05-20 22:19:53 +0000
1386+++ docs/_templates/indexcontent.html 2015-02-26 21:47:00 +0000
1387@@ -7,54 +7,90 @@
1388 <tr>
1389 <td width="50%">
1390 <p class="biglink">
1391- <a class="biglink" href="{{ pathto("tutorial/tutorial") }}">Autopilot Tutorial</a><br/>
1392- <span class="linkdescr">Grok Autopilot!</span>
1393- </p>
1394- </td>
1395- <td width="50%">
1396- <p class="biglink">
1397- <a class="biglink" href="{{ pathto("api/index") }}">API Reference</a><br/>
1398- <span class="linkdescr">API reference documentation for Autopilot.</span>
1399- </p>
1400- </td>
1401- </tr>
1402- <tr>
1403- <td>
1404- <p class="biglink">
1405- <a class="biglink" href="{{ pathto("faq/faq") }}">Frequently Asked Questions</a><br/>
1406- <span class="linkdescr">...with answers!</span>
1407- </p>
1408- </td>
1409- <td>
1410- <p class="biglink">
1411- <a class="biglink" href="{{ pathto("faq/troubleshooting") }}">How To Troubleshoot My Failing Test</a><br/>
1412- <span class="linkdescr">My test works sometimes</span>
1413- </p>
1414- </td>
1415- </tr>
1416- <tr>
1417- <td>
1418- <p class="biglink">
1419- <a class="biglink" href="{{ pathto("porting/porting") }}">Porting Autopilot Tests</a><br/>
1420- <span class="linkdescr">How to port your tests from earlier versions of Autopilot.</span>
1421- </p>
1422- </td>
1423- <td>
1424- <p class="biglink">
1425- <a class="biglink" href="{{ pathto("appendix/appendix") }}">Appendices</a><br/>
1426- <span class="linkdescr">Technical documentation that doesn't fit anywhere else.</span>
1427+ <a class="biglink" href="{{ pathto("tutorial/what_is_autopilot") }}">What is Autopilot?</a><br/>
1428+ <span class="linkdescr">... and who is Otto anyway?</span>
1429+ </p>
1430+ </td>
1431+ <td width="50%">
1432+ <p class="biglink">
1433+ <a class="biglink" href="{{ pathto("faq/troubleshooting") }}">How To Troubleshoot My Failing Test</a><br/>
1434+ <span class="linkdescr">My test works . . . sometimes . . . </span>
1435+ </p>
1436+ </td>
1437+ </tr>
1438+ <tr>
1439+ <td width="50%">
1440+ <p class="biglink">
1441+ <a class="biglink" href="{{ pathto("guides/installation") }}">Installing Autopilot</a><br/>
1442+ <span class="linkdescr">I need this on my machine!</span>
1443+ </p>
1444+ </td>
1445+ <td width="50%">
1446+ <p class="biglink">
1447+ <a class="biglink" href="{{ pathto("faq/faq") }}">Frequently Asked Questions</a><br/>
1448+ <span class="linkdescr">... with answers!</span>
1449+ </p>
1450+ </td>
1451+ </tr>
1452+ <tr>
1453+ <td width="50%">
1454+ <p class="biglink">
1455+ <a class="biglink" href="{{ pathto("tutorial/tutorial") }}">Autopilot Tutorial</a><br/>
1456+ <span class="linkdescr">Grok Autopilot!</span>
1457+ </p>
1458+ </td>
1459+ <td width="50%">
1460+ <p class="biglink">
1461+ <a class="biglink" href="{{ pathto("porting/porting") }}">Porting Autopilot Tests</a><br/>
1462+ <span class="linkdescr">How to port your tests from earlier versions of Autopilot.</span>
1463+ </p>
1464+ </td>
1465+ </tr>
1466+ <tr>
1467+ <td width="50%">
1468+ <p class="biglink">
1469+ <a class="biglink" href="{{ pathto("guides/running_ap") }}">Running Tests</a><br/>
1470+ <span class="linkdescr">How do I start this thing?</span>
1471+ </p>
1472+ </td>
1473+ <td width="50%">
1474+ <p class="biglink">
1475+ <a class="biglink" href="{{ pathto("api/index") }}">API Reference</a><br/>
1476+ <span class="linkdescr">API reference documentation for Autopilot.</span>
1477+ </p>
1478+ </td>
1479+ </tr>
1480+ <tr>
1481+ <td width="50%">
1482+ <p class="biglink">
1483+ <a class="biglink" href="{{ pathto("guides/good_tests") }}">Writing Good Tests</a><br/>
1484+ <span class="linkdescr">Tips to bring out the test writer in you.</span>
1485+ </p>
1486+ </td>
1487+ <td width="50%">
1488+ <p class="biglink">
1489+ <a class="biglink" href="{{ pathto("appendix/appendix") }}">Appendices</a><br/>
1490+ <span class="linkdescr">Technical documentation that doesn't fit anywhere else.</span>
1491 </p>
1492 </td>
1493 </tr>
1494 </table>
1495
1496 <h2>Indices and tables:</h2>
1497- <table class="contentstable" align="center"><tr>
1498- <td width="50%">
1499- <p class="biglink"><a class="biglink" href="{{ pathto("py-modindex") }}">Module Index</a><br/>
1500- <span class="linkdescr">quick access to all modules</span></p>
1501- <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">Complete Table of Contents</a><br/>
1502- <span class="linkdescr">lists all sections and subsections</span></p>
1503- </td></tr>
1504+ <table class="contentstable" align="center">
1505+ <tr>
1506+ <td width="50%">
1507+ <p class="biglink">
1508+ <a class="biglink" href="{{ pathto("py-modindex") }}">Module Index</a><br/>
1509+ <span class="linkdescr">quick access to all modules.</span>
1510+ </p>
1511+ </td>
1512+ <td width="50%">
1513+ <p class="biglink">
1514+ <a class="biglink" href="{{ pathto("contents") }}">Complete Table of Contents</a><br/>
1515+ <span class="linkdescr">lists all sections and subsections.</span>
1516+ </p>
1517+ </td>
1518+ </tr>
1519 </table>
1520 {% endblock %}
1521
1522=== modified file 'docs/contents.rst'
1523--- docs/contents.rst 2014-05-20 14:02:18 +0000
1524+++ docs/contents.rst 2015-02-26 21:47:00 +0000
1525@@ -6,9 +6,14 @@
1526 .. toctree::
1527 :maxdepth: 5
1528
1529+ tutorial/what_is_autopilot
1530 tutorial/tutorial
1531+ guides/good_tests
1532+ guides/running_ap
1533+ guides/installation
1534 api/index
1535 porting/porting
1536+ guides/page_object
1537 faq/faq
1538 faq/contribute
1539 faq/troubleshooting
1540
1541=== modified file 'docs/faq/faq.rst'
1542--- docs/faq/faq.rst 2013-12-09 00:55:42 +0000
1543+++ docs/faq/faq.rst 2015-02-26 21:47:00 +0000
1544@@ -13,26 +13,21 @@
1545
1546 The developers hang out in the #ubuntu-autopilot IRC channel on irc.freenode.net.
1547
1548-Q. How do I install Autopilot?
1549-==============================
1550-
1551-Autopilot is in continuous development, and the best way to get the latest version of autopilot is to be running the latest Ubuntu development image. The autopilot developers traditionally support the Ubuntu release immediately prior to the development release via the autopilot PPA.
1552-
1553-**I am running the latest development image!**
1554-
1555-In that case you can install autopilot directly - either by installing the ``autopilot-desktop`` or ``autopilot-touch`` packages, depending on whether you are installing to a desktop or an Ubuntu Touch device.
1556-
1557-**I am running the Ubuntu release previous to the development release!**
1558-
1559-You may find that there are packages available for your Ubuntu release in the autopilot PPA. To add the PPA to your system, run the following command::
1560-
1561- sudo add-apt-repository ppa:autopilot/ppa && sudo apt-get update
1562-
1563-Once the PPA has been added to your system, you should be able to install the same autopilot packages as if you were running the latest development release (see above).
1564-
1565-**I am running some other Linux system!**
1566-
1567-You may have to download the source code, and either run from source, or build the packages locally. Your best bet is to ask in the autopilot IRC channel ( :ref:`help_and_support`).
1568+Q. Which version of autopilot should I install?
1569+===============================================
1570+
1571+Ideally you should adopt and utilize the latest version of autopilot. If your testcase requires you to utilize an
1572+older version of autopilot for reasons other than :ref:`porting`, please `file a bug <https://bugs.launchpad.net/autopilot/+filebug>`_ and let the development team know about your issue.
1573+
1574+Q. Should I write my tests in python2 or python3?
1575+=================================================
1576+
1577+As Autopilot fully supports python3 (see :ref:`python3_support`), you should seek to use python3 for new tests. Before making a decision, you should also ensure any 3rd party modules your test may depend on also support python3.
1578+
1579+Q: Should I convert my existing tests to python3?
1580+=================================================
1581+
1582+See above. In a word, yes. Converting python2 to python3 (see :ref:`python3_support`) is generally straightforward and converting a testcase is likely much easier than a full python application. You can also consider retaining python2 compatibility upon conversion.
1583
1584 Q. Where can I report a bug?
1585 ============================
1586@@ -187,7 +182,7 @@
1587
1588 For instance launching gedit is as easy as::
1589
1590- $ autopilot launch gedit
1591+ $ autopilot3 launch gedit
1592
1593 *Autopilot launch* attempts to detect if you are launching either a Gtk or Qt
1594 application so that it can enable the correct libraries. If is is unable to
1595@@ -203,7 +198,7 @@
1596 inform autopilot that it is a Qt application so that it can enable the correct
1597 support::
1598
1599- $ autopilot launch -i Qt testapp.py
1600+ $ autopilot3 launch -i Qt testapp.py
1601
1602 Now that it has been launched with Autopilot support we can introspect and
1603 explore out application using the :ref:`vis tool <visualise_introspection_tree>`.
1604
1605=== modified file 'docs/faq/troubleshooting.rst'
1606--- docs/faq/troubleshooting.rst 2014-05-21 09:31:12 +0000
1607+++ docs/faq/troubleshooting.rst 2015-02-26 21:47:00 +0000
1608@@ -4,9 +4,40 @@
1609
1610 .. contents::
1611
1612--------------
1613-Failing Tests
1614--------------
1615+.. _troubleshooting_general_techniques:
1616+
1617+------------------
1618+General Techniques
1619+------------------
1620+
1621+The single hardest thing to do while writing autopilot tests is to understand the state of the application's object tree. This is especially important for applications that change their object tree during the lifetime of the test. There are three techniques you can use to discover the state of the object tree:
1622+
1623+**Using Autopilot Vis**
1624+
1625+The :ref:`Autopilot vis tool <visualise_introspection_tree>` is a useful tool for exploring the entire structure of an application, and allows you to search for a particular node in the object tree. If you want to find out what parts of the application to select to gain access to certain information, the vis tool is probably the best way to do that.
1626+
1627+**Using print_tree**
1628+
1629+The :meth:`~autopilot.introspection.ProxyBase.print_tree` method is available on every proxy class. This method will print every child of the proxy object recursively, either to ``stdout`` or a file on disk. This technique can be useful when:
1630+
1631+* The application cannot easily be put into the state required before launching autopilot vis, so the vis tool is no longer an option.
1632+* The application state that has to be captured only exists for a short amount of time.
1633+* The application only runs on platforms where the vis tool isn't available.
1634+
1635+The :meth:`~autopilot.introspection.ProxyBase.print_tree` method often produces a lot of output. There are two ways this information overload can be handled:
1636+
1637+#. Specify a file path to write to, so the console log doesn't get flooded. This log file can then be searched with tools such as ``grep``.
1638+#. Specify a ``maxdepth`` limit. This controls how many levels deep the recursive search will go.
1639+
1640+Of course, these techniques can be used in combination.
1641+
1642+**Using get_properties**
1643+
1644+The :meth:`~autopilot.introspection.ProxyBase.get_properties` method can be used on any proxy object, and will return a python dictionary containing all the properties of that proxy object. This is useful when you want to explore what information is provided by a single proxy object. The information returned by this method is exactly the same as is shown in the right-hand pane of ``autopilot vis``.
1645+
1646+----------------------------------------
1647+Common Questions regarding Failing Tests
1648+----------------------------------------
1649
1650 .. _failing_tests:
1651
1652@@ -30,7 +61,7 @@
1653
1654 * solution::
1655
1656- page.animationRunning.wait_for(False)
1657+ page.animationRunning.wait_for(False)
1658 self.main_view.select_single('Button', text='click_this')
1659
1660 2. Not waiting for an object to become visible before trying to select it. Is your app slower than it used to be for some reason? Does its properties have null values? Do you see errors in stdout/stderr while using your app, if you run it from the commandline?
1661@@ -76,8 +107,8 @@
1662 * solution::
1663
1664 def _get_named_photo_element(self, photo_name):
1665- """Return the ShapeItem container object for the named photo
1666- This object can be clicked to enable the photo to be selected.
1667+ """Return the ShapeItem container object for the named photo
1668+ This object can be clicked to enable the photo to be selected.
1669 """
1670 photo_element = self.grid_view().wait_select_single(
1671 'QQuickImage',
1672@@ -87,5 +118,5 @@
1673
1674 def select_named_photo(self, photo_name):
1675 """Select the named photo from the picker view."""
1676- photo_element = self._get_named_photo_element(photo_name)
1677+ photo_element = self._get_named_photo_element(photo_name)
1678 self.pointing_device.click_object(photo_element)
1679
1680=== added directory 'docs/guides'
1681=== renamed file 'docs/tutorial/good_tests.rst' => 'docs/guides/good_tests.rst'
1682--- docs/tutorial/good_tests.rst 2014-05-16 02:09:23 +0000
1683+++ docs/guides/good_tests.rst 2015-02-26 21:47:00 +0000
1684@@ -98,6 +98,10 @@
1685 5. Commit. Push. Superseed original merge proposal with your branch.
1686 6. Celebrate!
1687
1688+Think about design
1689+++++++++++++++++++
1690+Much in the same way you might choose a functional or objective-oriented paradigm for a piece of code, a testsuite can benefit from choosing a good design pattern. One such design pattern is the page object model. The page object model can reduce testcase complexity and allow the testcase to grow and easily adapt to changes within the underlying application. Check out :ref:`page_object_guide`.
1691+
1692 Test Length
1693 +++++++++++
1694
1695@@ -339,6 +343,8 @@
1696
1697 Note that you can pass any object that follows the testtools matcher protocol (so you can write your own matchers, if you like).
1698
1699+.. _wait_for:
1700+
1701 In Proxy Classes
1702 ----------------
1703
1704
1705=== added file 'docs/guides/installation.rst'
1706--- docs/guides/installation.rst 1970-01-01 00:00:00 +0000
1707+++ docs/guides/installation.rst 2015-02-26 21:47:00 +0000
1708@@ -0,0 +1,42 @@
1709+.. _installing_autopilot:
1710+
1711+Installing Autopilot
1712+####################
1713+
1714+.. contents::
1715+
1716+
1717+Autopilot is in continuous development, and the best way to get the latest version of autopilot is to run the latest Ubuntu development image. The autopilot developers traditionally support the Ubuntu release immediately prior to the development release via the autopilot PPA.
1718+
1719+Ubuntu
1720+======
1721+**I am running the latest development image!**
1722+
1723+In that case you can install autopilot directly from the repository and know you are getting the latest release. Check out the packages below.
1724+
1725+**I am running a stable version of Ubuntu!**
1726+
1727+You may install the version of autopilot in the archive directly, however it will not be up to date. Instead, you should add the latest autopilot ppa to your system (as of this writing, that is autopilot 1.5).
1728+
1729+To add the PPA to your system, run the following command::
1730+
1731+ sudo add-apt-repository ppa:autopilot/1.5 && sudo apt-get update
1732+
1733+Once the PPA has been added to your system, you should be able to install the autopilot packages below.
1734+
1735+**Which packages should I install?**
1736+
1737+Are you working on ubuntu touch applications? The ``autopilot-touch`` metapackage is for you::
1738+
1739+ sudo apt-get install autopilot-touch
1740+
1741+If you are sticking with gtk desktop applications, install the ``autopilot-desktop`` metapackage instead::
1742+
1743+ sudo apt-get install autopilot-desktop
1744+
1745+Feel free to install both metapackages to ensure you have support for all autopilot tests.
1746+
1747+Other Linux's
1748+=============
1749+
1750+You may have to download the source code, and either run from source, or build the packages locally. Your best bet is to ask in the autopilot IRC channel ( :ref:`help_and_support`).
1751
1752=== added file 'docs/guides/page_object.rst'
1753--- docs/guides/page_object.rst 1970-01-01 00:00:00 +0000
1754+++ docs/guides/page_object.rst 2015-02-26 21:47:00 +0000
1755@@ -0,0 +1,226 @@
1756+.. _page_object_guide:
1757+
1758+Page Object Pattern
1759+####################
1760+
1761+.. contents::
1762+
1763+Introducing the Page Object Pattern
1764+-----------------------------------
1765+Automated testing of an application through the Graphical User Interface (GUI) is inherently fragile.
1766+These tests require regular review and attention during the development cycle. This is known as Interface Sensitivity (`"even minor changes to the interface can cause tests to fail" <https://books.google.com/books?isbn=0132797461>`_).
1767+Utilizing the page-object pattern, alleviates some of the problems stemming from this fragility, allowing us to do automated user acceptance testing (UAT) in a sustainable manner.
1768+
1769+The Page Object Pattern comes from the `Selenium community <https://code.google.com/p/selenium/wiki/PageObjects>`_ and is the best way to turn a flaky and unmaintainable user acceptance test into a stable and useful
1770+part of your release process. A page is what's visible on the screen at a single moment.
1771+A user story consists of a user jumping from page to page until they achieve their goal.
1772+Thus pages are modeled as objects following these guidelines:
1773+
1774+#. The public methods represent the services that the page offers.
1775+#. Try not to expose the internals of the page.
1776+#. Methods return other PageObjects.
1777+#. Assertions should exist only in tests
1778+#. Objects need not represent the entire page.
1779+#. Actions which produce multiple results should have a test for each result
1780+
1781+Lets take the page objects of the `Ubuntu Clock App <http://bazaar.launchpad.net/~ubuntu-clock-dev/ubuntu-clock-app/trunk/view/399/tests/autopilot/ubuntu_clock_app/emulators.py>`__ as an example, with some simplifications. This application is written in
1782+QML and Javascript using the `Ubuntu SDK <http://developer.ubuntu.com/apps/sdk/>`__.
1783+
1784+1. The public methods represent the services that the page offers.
1785+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1786+
1787+This application has a stopwatch page that lets users measure elapsed
1788+time. It offers services to start, stop and reset the watch, so we start
1789+by defining the stop watch page object as follows:
1790+
1791+.. code-block:: python
1792+
1793+ class Stopwatch(object):
1794+
1795+ def start(self):
1796+ raise NotImplementedError()
1797+
1798+ def stop(self):
1799+ raise NotImplementedError()
1800+
1801+ def reset(self):
1802+ raise NotImplementedError()
1803+
1804+2. Try not to expose the internals of the page.
1805+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1806+
1807+The internals of the page are more likely to change than the services it
1808+offers. A stopwatch will keep the same three services we defined above
1809+even if the whole design changes. In this case, we reset the stop watch
1810+by clicking a button on the bottom-left of the window, but we hide that
1811+as an implementation detail behind the public methods. In Python, we can
1812+indicate that a method is for internal use only by adding a single
1813+leading underscore to its name. So, lets implement the reset\_stopwatch
1814+method:
1815+
1816+.. code-block:: python
1817+
1818+ def reset(self):
1819+ self._click_reset_button()
1820+
1821+ def _click_reset_button(self):
1822+ reset_button = self.wait_select_single(
1823+ 'ImageButton', objectName='resetButton')
1824+ self.pointing_device.click_object(reset_button)
1825+
1826+Now if the designers go crazy and decide that it's better to reset the
1827+stop watch in a different way, we will have to make the change only in
1828+one place to keep all the tests working. Remember that this type of
1829+tests has Interface Sensitivity, that's unavoidable; but we can reduce
1830+the impact of interface changes with proper encapsulation and turn these
1831+tests into a useful way to verify that a change in the GUI didn't
1832+introduce any regressions.
1833+
1834+.. _page_object_guide_guideline_3:
1835+
1836+3. Methods return other PageObjects
1837+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1838+
1839+An UAT checks a user story. It will involve the journey of the user
1840+through the system, so he will move from one page to another. Lets take
1841+a look at how a journey to reset the stop watch will look like:
1842+
1843+.. code-block:: python
1844+
1845+ stopwatch = clock_page.open_stopwatch()
1846+ stopwatch.start()
1847+ stopwatch.reset()
1848+
1849+In our sample application, the first page that the user will encounter
1850+is the Clock. One of the things the user can do from this page is to
1851+open the stopwatch page, so we model that as a service that the Clock
1852+page provides. Then return the new page object that will be visible to
1853+the user after completing that step.
1854+
1855+.. code-block:: python
1856+
1857+ class Clock(object):
1858+
1859+ ...
1860+
1861+ def open_stopwatch(self):
1862+ self._switch_to_tab('StopwatchTab')
1863+ return self.wait_select_single(Stopwatch)
1864+
1865+Now the return value of open\_stopwatch will make available to the
1866+caller all the available services that the stopwatch exposes to the
1867+user. Thus it can be chained as a user journey from one page to the
1868+other.
1869+
1870+4. Assertions should exist only in tests
1871+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1872+
1873+A well written UAT consists of a sequence of
1874+steps or user actions and ends with one single assertion that verifies
1875+that the user achieved its goal. The page objects are the helpers for
1876+the user actions part of the test, so it's better to leave the check for
1877+success out of them. With that in mind, a test for the reset of the
1878+stopwatch would look like this:
1879+
1880+.. code-block:: python
1881+
1882+ def test_restart_button_must_restart_stopwatch_time(self):
1883+ # Set up.
1884+ stopwatch = self.clock_page.open_stopwatch()
1885+
1886+ stopwatch.start()
1887+ stopwatch.reset_stopwatch()
1888+
1889+ # Check that the stopwatch has been reset.
1890+ self.assertThat(
1891+ stopwatch.get_time,
1892+ Eventually(Equals('00:00.0')))
1893+
1894+We have to add a new method to the stopwatch page object: get\_time. But
1895+it only returns the state of the GUI as the user sees it. We leave in
1896+the test method the assertion that checks it's the expected value.
1897+
1898+.. code-block:: python
1899+
1900+ class Stopwatch(object):
1901+
1902+ ...
1903+
1904+ def get_time(self):
1905+ return self.wait_select_single(
1906+ 'Label', objectName='time').text
1907+
1908+5. Need not represent an entire page
1909+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1910+
1911+The objects we are modeling here can just represent a part of the page.
1912+Then we build the entire page that the user is seeing by composition of
1913+page parts. This way we can reuse test code for parts of the GUI that
1914+are reused in the application or between different applications. As an
1915+example, take the \_switch\_to\_tab('StopwatchTab') method that we are
1916+using to open the stopwatch page. The Clock application is using the
1917+Header component provided by the Ubuntu SDK, as all the other Ubuntu
1918+applications are doing too. So, the Ubuntu SDK also provides helpers to
1919+make it easier the user acceptance testing of the applications, and you
1920+will find an object like this:
1921+
1922+.. code-block:: python
1923+
1924+ class Header(object):
1925+
1926+ def switch_to_tab(tab_object_name):
1927+ """Open a tab.
1928+
1929+ :parameter tab_object_name: The QML objectName property of the tab.
1930+ :return: The newly opened tab.
1931+ :raise ToolkitException: If there is no tab with that object
1932+ name.
1933+
1934+ """
1935+ ...
1936+
1937+This object just represents the header of the page, and inside the
1938+object we define the services that the header provides to the users. If
1939+you dig into the full implementation of the Clock test class you will
1940+find that in order to open the stopwatch page we end up calling Header
1941+methods.
1942+
1943+6. Actions which produce multiple results should have a test for each result
1944+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1945+
1946+According to the guideline :ref:`page_object_guide_guideline_3`, we are returning page objects every time
1947+that a user action opens the option for new actions to execute.
1948+Sometimes the same action has different results depending on the context
1949+or the values used for the action. For example, the Clock app has an
1950+Alarm page. In this page you can add new alarms, but if you try to add
1951+an alarm for sometime in the past, it will result in an error. So, we
1952+will have two different tests that will look something like this:
1953+
1954+.. code-block:: python
1955+
1956+ def test_add_alarm_for_tomorrow_must_add_to_alarm_list(self):
1957+ tomorrow = ...
1958+ test_alarm_name = 'Test alarm for tomorrow'
1959+ alarm_page = self.alarm_page.add_alarm(
1960+ test_alarm_name, tomorrow)
1961+
1962+ saved_alarms = alarm_page.get_saved_alarms()
1963+ self.assertIn(
1964+ (test_alarm_name, tomorrow),
1965+ saved_alarms)
1966+
1967+ def test_add_alarm_for_earlier_today_must_display_error(self):
1968+ earlier_today = ...
1969+ test_alarm_name = 'Test alarm for earlier_today'
1970+ error_dialog = self.alarm_page.add_alarm_with_error(
1971+ test_alarm_name, earlier_today)
1972+
1973+ self.assertEqual(
1974+ error_dialog.text,
1975+ 'Please select a time in the future.')
1976+
1977+Take a look at the methods add\_alarm and add\_alarm\_with\_error. The
1978+first one returns the Alarm page again, where the user can continue his
1979+journey or finish the test checking the result. The second one returns
1980+the error dialog that's expected when you try to add an alarm with the
1981+wrong values.
1982
1983=== renamed file 'docs/tutorial/running_ap.rst' => 'docs/guides/running_ap.rst'
1984--- docs/tutorial/running_ap.rst 2013-06-24 04:02:17 +0000
1985+++ docs/guides/running_ap.rst 2015-02-26 21:47:00 +0000
1986@@ -8,11 +8,11 @@
1987
1988 Autopilot can list all tests found within a particular module::
1989
1990- $ autopilot list <modulename>
1991+ $ autopilot3 list <modulename>
1992
1993 where *<modulename>* is the base name of the module you want to look at. The module must either be in the current working directory, or be importable by python. For example, to list the tests inside autopilot itself, you can run::
1994
1995- $ autopilot list autopilot
1996+ $ autopilot3 list autopilot
1997 autopilot.tests.test_ap_apps.GtkTests.test_can_launch_qt_app
1998 autopilot.tests.test_ap_apps.QtTests.test_can_launch_qt_app
1999 autopilot.tests.test_application_mixin.ApplicationSupportTests.test_can_create
2000@@ -39,7 +39,7 @@
2001
2002 Running autopilot tests is very similar to listing tests::
2003
2004- $ autopilot run <modulename>
2005+ $ autopilot3 run <modulename>
2006
2007 However, the run command has many more options to customize the run behavior:
2008
2009@@ -70,30 +70,57 @@
2010
2011 1. **Run autopilot and save the test log**::
2012
2013- $ autopilot run -o . <modulename>
2014+ $ autopilot3 run -o . <modulename>
2015
2016 Autopilot will create a text log file named <hostname>_<dd.mm.yyy_HHMMSS>.log with the contents of the test log.
2017
2018 2. **Run autopilot and record failing tests**::
2019
2020- $ autopilot run -r --rd . <modulename>
2021+ $ autopilot3 run -r --rd . <modulename>
2022
2023 Videos are recorded as *ogg-vorbis* files, with an .ogv extension. They will be named with the test id that failed. All videos will be placed in the directory specified by the ``-rd`` option - in this case the currect directory. If this option is omitted, videos will be placed in ``/tmp/autopilot/``.
2024
2025 3. **Save the test log as jUnitXml format**::
2026
2027- $ autopilot run -o results.xml -f xml <modulename>
2028+ $ autopilot3 run -o results.xml -f xml <modulename>
2029
2030 The file 'results.xml' will be created when all the tests have completed, and will be in the jUnitXml file format. This is useful when running the autopilot tests within a jenkins environment.
2031
2032+.. _launching_application_to_introspect:
2033+
2034+Launching an Application to Introspect
2035+--------------------------------------
2036+
2037+In order to be able to introspect an application, it must first be launched with introspection enabled. Autopilot provides the **launch** command to enable this: ::
2038+
2039+ $ autopilot3 launch <application> <app_parameters>
2040+
2041+The *<application>* parameter could be the full path to the application, or the name of an application located somewhere on ``$PATH``. *<app_parameter>* is passed on to the application being launched.
2042+
2043+A simple Gtk example to launch gedit::
2044+
2045+ $ autopilot3 launch gedit
2046+
2047+A Qt example which passes on parameters to the application being launched::
2048+
2049+ $ autopilot3 launch qmlscene my_app.qml
2050+
2051+Autopilot launch attempts to detect if you are launching either a Gtk or Qt application so that it can enable the correct libraries. If it is unable to determine this you will need to specify the type of application it is by using the -i argument. This allows "Gtk" or "Qt" frameworks to be specified when launching the application. The default value ("Auto") will try to detect which interface to load automatically.
2052+
2053+A typical error in this situation will be "Error: Could not determine introspection type to use for application". In which case the -i option should be specified with the correct application framework type to fix the problem::
2054+
2055+ $ autopilot3 launch -i Qt address-book-app
2056+
2057+Once an application has launched with introspection enabled, it will be possible to launch autopilot vis and view the introspection tree, see: :ref:`visualise_introspection_tree`.
2058+
2059 .. _visualise_introspection_tree:
2060
2061 Visualise Introspection Tree
2062 ----------------------------
2063
2064-A very common thing to want to do while writing autopilot tests is see the structure of the application being tested. To support this, autopilot includes a simple application to help visualize the introspection tree. To start it, make sure the application you wish to test is running, and then run::
2065+A very common thing to want to do while writing autopilot tests is see the structure of the application being tested. To support this, autopilot includes a simple application to help visualize the introspection tree. To start it, make sure the application you wish to test is running (see: :ref:`launching_application_to_introspect`), and then run::
2066
2067- $ autopilot vis
2068+ $ autopilot3 vis
2069
2070 The result should be a window similar to below:
2071
2072@@ -103,3 +130,4 @@
2073
2074 .. image:: /images/ap_vis_object.png
2075
2076+Autopilot vis also has the ability to search the object tree for nodes that match a given name (such as "LauncherController", for example), and draw a transparent overlay over a widget if it contains position information. These tools, when combined can make finding certain parts of an application introspection tree much easier.
2077
2078=== modified file 'docs/images/ap_vis_front_page.png'
2079Binary files docs/images/ap_vis_front_page.png 2012-10-14 22:33:09 +0000 and docs/images/ap_vis_front_page.png 2015-02-26 21:47:00 +0000 differ
2080=== modified file 'docs/images/ap_vis_object.png'
2081Binary files docs/images/ap_vis_object.png 2012-10-14 22:33:09 +0000 and docs/images/ap_vis_object.png 2015-02-26 21:47:00 +0000 differ
2082=== modified file 'docs/index.rst'
2083--- docs/index.rst 2014-04-29 19:41:07 +0000
2084+++ docs/index.rst 2015-02-26 21:47:00 +0000
2085@@ -2,9 +2,13 @@
2086 ##################################
2087
2088 .. toctree::
2089- tutorial/tutorial
2090- api/index
2091- faq/faq
2092- porting/porting
2093- appendix/appendix
2094- man
2095+ tutorial/what_is_autopilot
2096+ tutorial/tutorial
2097+ guides/good_tests
2098+ guides/running_ap
2099+ guides/installation
2100+ api/index
2101+ faq/faq
2102+ porting/porting
2103+ appendix/appendix
2104+ man
2105
2106=== modified file 'docs/man.rst'
2107--- docs/man.rst 2014-04-30 21:37:55 +0000
2108+++ docs/man.rst 2015-02-26 21:47:00 +0000
2109@@ -72,18 +72,18 @@
2110 by the -r option).
2111
2112 -ro, --random-order
2113- Run the tests in random order
2114+ Run the tests in random order
2115
2116 -v, --verbose
2117 Causes autopilot to print the test log to stdout while the test is
2118 running.
2119
2120 --debug-profile
2121- Select a profile for what additional debugging information should
2122+ Select a profile for what additional debugging information should
2123 be attached to failed test results.
2124
2125 --timeout-profile
2126- Alter the timeout values Autopilot uses. Selecting 'long' will
2127+ Alter the timeout values Autopilot uses. Selecting 'long' will
2128 make autopilot use longer timeouts for various polling loops. This
2129 can be useful if autopilot is running on very slow hardware
2130
2131@@ -92,19 +92,19 @@
2132
2133 -v, --verbose
2134
2135- Show autopilot log messages. Set twice to also log data useful
2136+ Show autopilot log messages. Set twice to also log data useful
2137 for debugging autopilot itself.
2138
2139 -i INTERFACE, --interface INTERFACE
2140- Specify which introspection interace to load. The default
2141- ('Auto') uses ldd to try and detect which interface to load.
2142+ Specify which introspection interace to load. The default
2143+ ('Auto') uses ldd to try and detect which interface to load.
2144 Options are Gtk and Qt.
2145
2146 vis [options]
2147 Open the autopilot visualizer tool.
2148
2149 -v, --verbose
2150- Show autopilot log messages. Set twice to also log data useful
2151+ Show autopilot log messages. Set twice to also log data useful
2152 for debugging autopilot itself.
2153
2154 -testability
2155
2156=== modified file 'docs/porting/porting.rst'
2157--- docs/porting/porting.rst 2014-04-15 02:32:16 +0000
2158+++ docs/porting/porting.rst 2015-02-26 21:47:00 +0000
2159@@ -1,3 +1,5 @@
2160+.. _porting:
2161+
2162 Porting Autopilot Tests
2163 #######################
2164
2165@@ -74,6 +76,7 @@
2166
2167 all_keys = app_proxy.select_many(KeyCustomProxy)
2168
2169+.. _python3_support:
2170
2171 Python 3
2172 ++++++++
2173
2174=== modified file 'docs/tutorial/advanced_autopilot.rst'
2175--- docs/tutorial/advanced_autopilot.rst 2014-05-23 09:57:21 +0000
2176+++ docs/tutorial/advanced_autopilot.rst 2015-02-26 21:47:00 +0000
2177@@ -159,10 +159,64 @@
2178
2179 The :class:`fixtures.EnvironmentVariable` fixture will revert the value of the environment variable to it's initial value, or will delete it altogether if the environment variable did not exist when :class:`fixtures.EnvironmentVariable` was instantiated. This happens in the cleanup phase of the test execution.
2180
2181+.. _custom_assertions:
2182+
2183 Custom Assertions
2184 =================
2185
2186-.. Document the custom assertion methods present in AutopilotTestCase
2187+Autopilot provides additional custom assertion methods within the :class:`~autopilot.testcase.AutopilotTestCase` base class. These assertion methods can be used for validating the visible window stack and also properties on objects whose attributes do not have the ``wait_for`` method, such as :class:`~autopilot.process.Window` objects (See :ref:`wait_for` for more information about ``wait_for``).
2188+
2189+:py:mod:`autopilot.testcase.AutopilotTestCase.assertVisibleWindowStack`
2190+
2191+This assertion allows the test to check the start of the visible window stack by passing an iterable item of :class:`~autopilot.process.Window` instances. Minimised windows will be ignored::
2192+
2193+ from autopilot.process import ProcessManager
2194+ from autopilot.testcase import AutopilotTestCase
2195+
2196+
2197+ class WindowTests(AutopilotTestCase):
2198+
2199+ def test_window_stack(self):
2200+ self.launch_some_test_apps()
2201+ pm = ProcessManager.create()
2202+ test_app_windows = []
2203+ for window in pm.get_open_windows():
2204+ if self.is_test_app(window.name):
2205+ test_app_windows.append(window)
2206+ self.assertVisibleWindowStack(test_app_windows)
2207+
2208+.. note:: The process manager is only available on environments that use bamf, i.e. desktop running Unity 7. There is currently no process manager for any other platform.
2209+
2210+.. _custom_assertions_assertProperty:
2211+
2212+:py:mod:`autopilot.testcase.AutopilotTestCase.assertProperty`
2213+
2214+This assertion allows the test to check properties of an object that does not have a **wait_for** method (i.e.- objects that do not come from the autopilot DBus interface). For example the :py:mod:`~autopilot.process.Window` object::
2215+
2216+ from autopilot.process import ProcessManager
2217+ from autopilot.testcase import AutopilotTestCase
2218+
2219+
2220+ class WindowTests(AutopilotTestCase):
2221+
2222+ def test_window_stack(self):
2223+ self.launch_some_test_apps()
2224+ pm = ProcessManager.create()
2225+ for window in pm.get_open_windows():
2226+ if self.is_test_app(window.name):
2227+ self.assertProperty(window, is_maximized=True)
2228+
2229+.. note:: :py:mod:`~autopilot.testcase.AutopilotTestCase.assertProperties` is a synonym for this method.
2230+
2231+.. note:: The process manager is only available on environments that use bamf, i.e. desktop running Unity 7. There is currently no process manager for any other platform.
2232+
2233+:py:mod:`autopilot.testcase.AutopilotTestCase.assertProperties`
2234+
2235+See :ref:`autopilot.testcase.AutopilotTestCase.assertProperty <custom_assertions_assertProperty>`.
2236+
2237+.. note:: :py:mod:`~autopilot.testcase.AutopilotTestCase.assertProperty` is a synonym for this method.
2238+
2239+.. _platform_selection:
2240
2241 Platform Selection
2242 ==================
2243@@ -175,10 +229,67 @@
2244
2245 For examples and API documentaion please see :py:mod:`autopilot.platform`.
2246
2247-Gestures and Multitouch
2248-=======================
2249-
2250-.. How do we do multi-touch & gestures?
2251+.. _gestures_multitouch:
2252+
2253+Gestures and Multi-touch
2254+========================
2255+
2256+Autopilot provides API support for both :ref:`single-touch <single_touch>` and :ref:`multi-touch <multi_touch>` gestures which can be used to simulate user input required to drive an application or system under test. These APIs should be used in conjunction with :ref:`platform_selection` to detect platform capabilities and ensure the correct input API is being used.
2257+
2258+.. _single_touch:
2259+
2260+Single-Touch
2261+++++++++++++
2262+
2263+:class:`autopilot.input.Touch` provides single-touch input gestures, which includes:
2264+
2265+* :meth:`~autopilot.input.Touch.tap` which can be used to tap a specified [x,y] point on the screen
2266+
2267+* :meth:`~autopilot.input.Touch.drag` which will drag between 2 [x,y] points and can be customised by altering the speed of the action
2268+
2269+* :meth:`~autopilot.input.Touch.press`, :meth:`~autopilot.input.Touch.release` and :meth:`~autopilot.input.Touch.move` operations which can be combined to create custom gestures
2270+
2271+* :meth:`~autopilot.input.Touch.tap_object` can be used to tap the center point of a given introspection object, where the screen co-ordinates are taken from one of several properties of the object
2272+
2273+Autopilot additionally provides the class :class:`autopilot.input.Pointer` as a means to provide a single unified API that can be used with both :class:`~autopilot.input.Mouse` input and :class:`~autopilot.input.Touch` input . See the :class:`documentation <autopilot.input.Pointer>` for this class for further details of this, as not all operations can be performed on both of these input types.
2274+
2275+This example demonstrates swiping from the center of the screen to the left edge, which could for example be used in `Ubuntu Touch <http://www.ubuntu.com/phone/features>`_ to swipe a new scope into view.
2276+
2277+1. First calculate the center point of the screen (see: :ref:`display_information`): ::
2278+
2279+ >>> from autopilot.display import Display
2280+ >>> display = Display.create()
2281+ >>> center_x = display.get_screen_width() // 2
2282+ >>> center_y = display.get_screen_height() // 2
2283+
2284+2. Then perform the swipe operation from the center of the screen to the left edge, using :meth:`autopilot.input.Pointer.drag`: ::
2285+
2286+ >>> from autopilot.input import Touch, Pointer
2287+ >>> pointer = Pointer(Touch.create())
2288+ >>> pointer.drag(center_x, center_y, 0, center_y)
2289+
2290+.. _multi_touch:
2291+
2292+Multi-Touch
2293++++++++++++
2294+
2295+:class:`autopilot.gestures` provides support for multi-touch input which includes:
2296+
2297+* :meth:`autopilot.gestures.pinch` provides a 2-finger pinch gesture centered around an [x,y] point on the screen
2298+
2299+This example demonstrates how to use the pinch gesture, which for example could be used on `Ubuntu Touch <http://www.ubuntu.com/phone/features>`_ web-browser, or gallery application to zoom in or out of currently displayed content.
2300+
2301+1. To zoom in, pinch vertically outwards from the center point by 100 pixels: ::
2302+
2303+ >>> from autopilot import gestures
2304+ >>> gestures.pinch([center_x, center_y], [0, 0], [0, 100])
2305+
2306+2. To zoom back out, pinch vertically 100 pixels back towards the center point: ::
2307+
2308+ >>> gestures.pinch([center_x, center_y], [0, 100], [0, 0])
2309+
2310+
2311+.. note:: The multi-touch :meth:`~autopilot.gestures.pinch` method is intended for use on a touch enabled device. However, if run on a desktop environment it will behave as if the mouse select button is pressed whilst moving the mouse pointer. For example to select some text in a document.
2312
2313 .. _tut-picking-backends:
2314
2315@@ -297,7 +408,7 @@
2316 there can be some technical limitations for some backends.
2317
2318 Some of these limitations are hidden when using the "create" method and won't
2319-cause any concern (i.e. X11 backend on desktop, UInput on an Ubuntu Touch device.)
2320+cause any concern (e.g. X11 backend on desktop, UInput on an Ubuntu Touch device.)
2321 while others will raise exceptions (that are fully documented in the API docs).
2322
2323 Here is a list of known limitations:
2324@@ -328,7 +439,7 @@
2325 supported/available on the Ubuntu Touch devices. It is possible that it
2326 will be available on the desktop in the near future.
2327
2328-* Unable to type 'special' keys i.e. Alt
2329+* Unable to type 'special' keys e.g. Alt
2330
2331 - This shouldn't be an issue as applications running on Ubuntu Touch devices
2332 will be using the expected patterns of use on these platforms.
2333@@ -347,15 +458,54 @@
2334 supported by the OSK backend (or the current language layout).
2335
2336
2337+.. _process_control:
2338+
2339 Process Control
2340 ===============
2341
2342-.. Document the process stack.
2343+The :mod:`autopilot.process` module provides the :class:`~autopilot.process.ProcessManager` class to provide a high-level interface for managing applications and windows during testing. Features of the :class:`~autopilot.process.ProcessManager` allow the user to start and stop applications easily and to query the current state of an application and its windows. It also provides automatic cleanup for apps that have been launched during testing.
2344+
2345+.. note:: :class:`~autopilot.process.ProcessManager` is not intended for introspecting an application's object tree, for this see :ref:`launching_applications`. Also it does not provide a method for interacting with an application's UI or specific features.
2346+
2347+Properties of an application and its windows can be accessed using the classes :class:`~autopilot.process.Application` and :class:`~autopilot.process.Window`, which also allows the window instance to be focused and closed.
2348+
2349+A list of known applications is defined in :meth:`~autopilot.process.ProcessManager.KNOWN_APPS` and these can easily be referenced by name. This list can also be updated using :meth:`~autopilot.process.ProcessManager.register_known_application` and :meth:`~autopilot.process.ProcessManager.unregister_known_application` for easier use during the test.
2350+
2351+To use the :class:`~autopilot.process.ProcessManager` the static :meth:`~autopilot.process.ProcessManager.create` method should be called, which returns an initialised object instance.
2352+
2353+A simple example to launch the gedit text editor and check it is in focus: ::
2354+
2355+ from autopilot.process import ProcessManager
2356+ from autopilot.testcase import AutopilotTestCase
2357+
2358+ class ProcessManagerTestCase(AutopilotTestCase):
2359+
2360+ def test_launch_app(self):
2361+ pm = ProcessManager.create()
2362+ app_window = pm.start_app_window('Text Editor')
2363+ app_window.set_focus()
2364+ self.assertTrue(app_window.is_focused)
2365+
2366+.. note:: :class:`~autopilot.process.ProcessManager` is only available on environments that use bamf, i.e. desktop running Unity 7. There is currently no process manager for any other platform.
2367+
2368+.. _display_information:
2369
2370 Display Information
2371 ===================
2372
2373-.. Document the display stack.
2374+Autopilot provides the :mod:`autopilot.display` module to get information about the displays currently being used. This information can be used in tests to implement gestures or input events that are specific to the current test environment. For example a test could be run on a desktop environment with multiple screens, or on a variety of touch devices that have different screen sizes.
2375+
2376+The user must call the static :meth:`~autopilot.display.Display.create` method to get an instance of the :class:`~autopilot.display.Display` class.
2377+
2378+This example shows how to get the size of each available screen, which could be used to calculate coordinates for a swipe or input event (See the :mod:`autopilot.input` module for more details about generating input events).::
2379+
2380+ from autopilot.display import Display
2381+
2382+ display = Display.create()
2383+ for screen in range(0, display.get_num_screens()):
2384+ width = display.get_screen_width(screen)
2385+ height = display.get_screen_height(screen)
2386+ print('screen {0}: {1}x{2}'.format(screen, width, height))
2387
2388 .. _custom_proxy_classes:
2389
2390@@ -391,7 +541,7 @@
2391 return True
2392 return False
2393
2394-This method should return True if the object matches this custom proxy class, and False otherwise. If more than one custom proxy class matches an object, a :exc:`ValueError` will be raised at runtime.
2395+This method should return True if the object matches this custom proxy class, and False otherwise. If more than one custom proxy class matches an object, a :exc:`ValueError` will be raised at runtime.
2396
2397 3. Pass the custom proxy class as an argument to the launch_test_application method on your test class. Something like this::
2398
2399@@ -409,31 +559,79 @@
2400
2401 # Get all QLabels in the applicaton:
2402 labels = self.app.select_many(QLabel)
2403-
2404-.. launching_applications:
2405+
2406+If you are introspecting an application that already has a custom proxy base class defined, then this class can simply be imported and passed to the appropriate application launcher method. See :ref:`launching applications <launching_applications>` for more details on launching an application for introspection. This will allow you to call all of the public methods of the application's proxy base class directly in your test.
2407+
2408+This example will run on desktop and uses the webbrowser application to navigate to a url using the base class go_to_url() method::
2409+
2410+ from autopilot.testcase import AutopilotTestCase
2411+ from webbrowser_app.emulators import browser
2412+
2413+ class ClickAppTestCase(AutopilotTestCase):
2414+
2415+ def test_go_to_url(self):
2416+ app = self.launch_test_application(
2417+ 'webbrowser-app',
2418+ emulator_base=browser.Webbrowser)
2419+ # main_window is a property of the Webbrowser class
2420+ app.main_window.go_to_url('http://www.ubuntu.com')
2421+
2422+.. _launching_applications:
2423
2424 Launching Applications
2425 ======================
2426
2427-Applications can be launched inside of a testcase using :meth:`~autopilot.testcase.AutopilotTestCase.launch_test_application`, :meth:`~autopilot.testcase.AutopilotTestCase.launch_upstart_application`, and :meth:`~autopilot.testcase.AutopilotTestCase.launch_click_application`.
2428-
2429-Outside of testcase classes, the :class:`~autopilot.application.NormalApplicationLauncher`, :class:`~autopilot.application.UpstartApplicationLauncher`, and :class:`~autopilot.application.ClickApplicationLauncher` fixtures can be used, i.e.::
2430+Applications can be launched inside of a testcase using the application launcher methods from the :class:`~autopilot.testcase.AutopilotTestCase` class. The exact method required will depend upon the type of application being launched:
2431+
2432+* :meth:`~autopilot.testcase.AutopilotTestCase.launch_test_application` is used to launch regular executables
2433+* :meth:`~autopilot.testcase.AutopilotTestCase.launch_upstart_application` is used to launch upstart-based applications
2434+* :meth:`~autopilot.testcase.AutopilotTestCase.launch_click_package` is used to launch applications inside a `click package <https://click.readthedocs.org/en/latest/>`_
2435+
2436+This example shows how to launch an installed click application from within a test case::
2437+
2438+ from autopilot.testcase import AutopilotTestCase
2439+
2440+ class ClickAppTestCase(AutopilotTestCase):
2441+
2442+ def test_something(self):
2443+ app_proxy = self.launch_click_package('com.ubuntu.calculator')
2444+
2445+Outside of testcase classes, the :class:`~autopilot.application.NormalApplicationLauncher`, :class:`~autopilot.application.UpstartApplicationLauncher`, and :class:`~autopilot.application.ClickApplicationLauncher` fixtures can be used, e.g.::
2446
2447 from autopilot.application import NormalApplicationLauncher
2448
2449 with NormalApplicationLauncher() as launcher:
2450 launcher.launch('gedit')
2451
2452-Within a fixture or a testcase, ``self.useFixture``can be used::
2453+or a similar example for an installed click package::
2454+
2455+ from autopilot.application import ClickApplicationLauncher
2456+
2457+ with ClickApplicationLauncher() as launcher:
2458+ app_proxy = launcher.launch('com.ubuntu.calculator')
2459+
2460+Within a fixture or a testcase, ``self.useFixture`` can be used::
2461
2462 launcher = self.useFixture(NormalApplicationLauncher())
2463 launcher.launch('gedit', ['--new-window', '/path/to/file'])
2464
2465+or for an installed click package::
2466+
2467+ launcher = self.useFixture(ClickApplicationLauncher())
2468+ app_proxy = launcher.launch('com.ubuntu.calculator')
2469+
2470 Additional options can also be specified to set a custom addDetail method, a custom proxy base, or a custom dbus bus with which to patch the environment::
2471
2472-
2473 launcher = self.useFixture(NormalApplicationLauncher(
2474 case_addDetail=self.addDetail,
2475 dbus_bus='some_other_bus',
2476 proxy_base=my_proxy_class,
2477 ))
2478+
2479+.. note:: You must pass the test case's 'addDetail' method to these application launch fixtures if you want application logs to be attached to the test result. This is due to the way fixtures are cleaned up, and is unavoidable.
2480+
2481+The main qml file of some click applications can also be launched directly from source. This can be done using the `qmlscene <https://developer.ubuntu.com/api/qml/sdk-1.0/QtQuick.qtquick-qmlscene/>`_ application directly on the target application's main qml file. This example uses :meth:`~autopilot.testcase.AutopilotTestCase.launch_test_application` method from within a test case::
2482+
2483+ app_proxy = self.launch_test_application('qmlscene', 'application.qml', app_type='qt')
2484+
2485+However, using this method it will not be possible to return an application specific custom proxy object, see :ref:`custom_proxy_classes`.
2486
2487=== modified file 'docs/tutorial/getting_started.rst'
2488--- docs/tutorial/getting_started.rst 2014-05-16 02:09:23 +0000
2489+++ docs/tutorial/getting_started.rst 2015-02-26 21:47:00 +0000
2490@@ -106,7 +106,11 @@
2491 if __name__ == '__main__':
2492 main()
2493
2494-As you can see, this is a trivial application, but it serves our purpose. We will write a single autopilot test that asserts that the title of the main window is equal to the string "Hello World". Our test file is named "test_window.py", and contains the following code::
2495+As you can see, this is a trivial application, but it serves our purpose. For the upcoming tests to run this file must be executable::
2496+
2497+ $ chmod u+x testapp.py
2498+
2499+We will write a single autopilot test that asserts that the title of the main window is equal to the string "Hello World". Our test file is named "test_window.py", and contains the following code::
2500
2501 from autopilot.testcase import AutopilotTestCase
2502 from os.path import abspath, dirname, join
2503@@ -149,7 +153,7 @@
2504
2505 From the root of this directory structure, we can ask autopilot to list all the tests it can find::
2506
2507- $ autopilot list example
2508+ $ autopilot3 list example
2509 Loading tests from: /home/thomi/code/canonical/autopilot/example_test
2510
2511 example.tests.test_window.MainWindowTitleTests.test_main_window_title_string
2512@@ -161,7 +165,7 @@
2513
2514 To run our test, we use the autopilot 'run' command::
2515
2516- $ autopilot run example
2517+ $ autopilot3 run example
2518 Loading tests from: /home/thomi/code/canonical/autopilot/example_test
2519
2520 Tests running...
2521@@ -171,7 +175,7 @@
2522
2523 You will notice that the test application launches, and then dissapears shortly afterwards. Since this test doesn't manipulate the application in any way, this is a rather boring test to look at. If you ever want more output from the run command, you may specify the '-v' flag::
2524
2525- $ autopilot run -v example
2526+ $ autopilot3 run -v example
2527 Loading tests from: /home/thomi/code/canonical/autopilot/example_test
2528
2529 Tests running...
2530@@ -196,7 +200,7 @@
2531
2532 Both the 'list' and 'run' commands take a test id as an argument. You may be as generic, or as specific as you like. In the examples above, we will list and run all tests in the 'example' package (i.e.- all tests), but we could specify a more specific run criteria if we only wanted to run some of the tests. For example, to only run the single test we've written, we can execute::
2533
2534- $ autopilot run example.tests.test_window.MainWindowTitleTests.test_main_window_title_string
2535+ $ autopilot3 run example.tests.test_window.MainWindowTitleTests.test_main_window_title_string
2536
2537 .. _tut_test_with_interaction:
2538
2539@@ -256,7 +260,6 @@
2540 from os.path import abspath, dirname, join
2541 from testtools.matchers import Equals
2542
2543- from autopilot.input import Mouse
2544 from autopilot.matchers import Eventually
2545
2546 class HelloWorldTestBase(AutopilotTestCase):
2547
2548=== modified file 'docs/tutorial/tutorial.rst'
2549--- docs/tutorial/tutorial.rst 2013-04-16 22:51:59 +0000
2550+++ docs/tutorial/tutorial.rst 2015-02-26 21:47:00 +0000
2551@@ -6,10 +6,7 @@
2552 .. toctree::
2553 :maxdepth: 3
2554
2555- what_is_autopilot
2556 getting_started
2557 advanced_autopilot
2558- good_tests
2559- running_ap
2560
2561

Subscribers

People subscribed via source and target branches