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 |
Related bugs: |
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
PS Jenkins bot (ps-jenkins) wrote : | # |
- 543. By Christopher Lee
-
Revert previous fix to unblock landing of other fixes and documentation updates.
Approved by PS Jenkins bot, Thomi Richards.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:543
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 544. By Christopher Lee
-
Update debian/changelog for a better log for release.
Approved by PS Jenkins bot, Max Brustkern.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:544
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Preview Diff
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' |
2079 | Binary 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' |
2081 | Binary 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 |
PASSED: Continuous integration, rev:542 jenkins. qa.ubuntu. com/job/ autopilot- 1.5-ci/ 15/ jenkins. qa.ubuntu. com/job/ autopilot- 1.5-vivid- amd64-ci/ 1 jenkins. qa.ubuntu. com/job/ autopilot- 1.5-vivid- amd64-ci/ 1/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ autopilot- 1.5-vivid- armhf-ci/ 1 jenkins. qa.ubuntu. com/job/ autopilot- 1.5-vivid- armhf-ci/ 1/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ autopilot- 1.5-vivid- i386-ci/ 1 jenkins. qa.ubuntu. com/job/ autopilot- 1.5-vivid- i386-ci/ 1/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 1462 jenkins. qa.ubuntu. com/job/ generic- mediumtests- vivid-autopilot /114 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- vivid-mako/ 1296 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1460 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1460/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 18225 jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-vivid- autopilot/ 110 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 751 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 751/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/autopilot- 1.5-ci/ 15/rebuild
http://