Merge lp:~pitti/autopilot-gtk/add-tests into lp:autopilot-gtk
- add-tests
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Francis Ginther | ||||
Approved revision: | 70 | ||||
Merged at revision: | 46 | ||||
Proposed branch: | lp:~pitti/autopilot-gtk/add-tests | ||||
Merge into: | lp:autopilot-gtk | ||||
Diff against target: |
1081 lines (+946/-61) 12 files modified
CMakeLists.txt (+1/-1) debian/control (+6/-1) debian/rules (+3/-2) tests/autopilot/tests/test_actions.py (+184/-0) tests/autopilot/tests/test_gnome_system_log.py (+61/-0) tests/autopilot/tests/test_matching_properties.py (+0/-32) tests/autopilot/tests/test_properties.py (+99/-0) tests/autopilot/tests/test_widget_tree.py (+186/-0) tests/autopilot/tests/test_xpath_query.py (+110/-0) tests/hello_color.py (+53/-0) tests/hello_color.ui (+243/-0) tests/test-matching.sh (+0/-25) |
||||
To merge this branch: | bzr merge lp:~pitti/autopilot-gtk/add-tests | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francis Ginther | Approve | ||
PS Jenkins bot | continuous-integration | Approve | |
Martin Pitt (community) | Needs Resubmitting | ||
Thomi Richards (community) | Needs Fixing | ||
Autopilot Hackers | fixed license and test robustness | Pending | |
Review via email: mp+171036@code.launchpad.net |
Commit message
Add integration test suite (LP: #1083612)
Description of the change
Add some integration tests, using a tiny tests/hello_
There is also a test case that exercises gnome-system-log, which is a lot
lighter than gedit. All these supersede the old test_matching_
Please see the individual commits for details.
Martin Pitt (pitti) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:55
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Thomi Richards (thomir-deactivatedaccount) wrote : | # |
Hi,
It looks to me like your copyright header is malformed - you should have two paragraphs.
Other than that, looks good to me.
- 56. By Martin Pitt
-
add complete GPL copyright headers
Martin Pitt (pitti) wrote : | # |
Indeed, I added complete ones now.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:56
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 57. By Martin Pitt
-
Add test case for GtkMenuItem globalRect
This verifies https:/
/bugs.launchpad .net/autopilot- gtk/+bug/ 1133893
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:57
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 58. By Martin Pitt
-
tests/hello_
color.py: Implement about dialog
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:58
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 59. By Martin Pitt
-
adjust tests after previous commit
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:59
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 60. By Martin Pitt
-
Add test case for XPath queries
This also reproduces https:/
/launchpad. net/bugs/ 1179806
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:60
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 61. By Martin Pitt
-
refine test to work with the autopilot runner, too
- 62. By Martin Pitt
-
drop tests/test-
matching. sh; broken, and superseded by test_xpath_query.py - 63. By Martin Pitt
-
debian/control: Add trailing comma to build dependencies to avoid future patch noise
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:63
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Francis Ginther (fginther) wrote : | # |
I've started another CI build to get another sample to see if the same error reproduces. The failure could be caused by a timing difference.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:63
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Francis Ginther (fginther) wrote : | # |
More failures this time. I'm concerned that this is a product of running with xvfb. We may have to stop running the tests during package build.
I am working on a solution to running these types of tests during CI, but outside of the build itself. No ETA yet as I've only started.
Martin Pitt (pitti) wrote : | # |
I can reproduce this failure on my machine when I load it heavily (I have a VM with an autopkgtest running in the background). Looks like I need an Eventually matcher there, the test currently assumes that the GtkEntry has the typed text right after self.app.type(...).
BTW, I ran the tests with verbosity, and it seems it's that achingly slow not because of xvfb, but because of some timeouts due to the local D-BUS:
1: 06:44:22.605 INFO globals:49 - Starting test tests.test_
1: 06:44:23.077 INFO __init__:136 - Launching process: ['/home/
1: 06:44:23.082 INFO __init__:169 - Looking for autopilot interface for PID 11949 (and children)
1: 06:44:23.103 WARNING __init__:186 - Caught exception while searching for autopilot interface: 'DBusException(
1: 06:44:24.131 WARNING __init__:186 - Caught exception while searching for autopilot interface: 'DBusException(
1: 06:44:49.157 ERROR proxies:410 - Introspect error on :1.7:/com/
1: 06:44:49.157 DEBUG proxies:413 - Executing introspect queue due to error
1: 06:45:14.180 WARNING __init__:186 - Caught exception while searching for autopilot interface: 'DBusException('Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.',)'
1: 06:45:14.192 DEBUG dbus:354 - Selecting objects of type GtkEntry with attributes: {}
So it spends almost a minute(!) on each test case on these. I'll try to track that down, too.
- 64. By Martin Pitt
-
merge with trunk
- 65. By Martin Pitt
-
Run tests with verbosity, to see more detailled output in build logs on failures
- 66. By Martin Pitt
-
Fix d-bus startup during package build
Drop our own dbus-launch invocation in debian/rules; that instance fights with
at-spi's own D-BUS launcher, causing long timeouts in autopilot. Also, that
instance never got cleaned up.This drops the time to run the tests from 10.5 minutes to 47 seconds due to
avoiding the D-BUS timeouts. - 67. By Martin Pitt
-
Fix test_actions.
test_menu In xvfb, the menu might actually start at (0,0), cover this (literal) corner
case. - 68. By Martin Pitt
-
Work around default focus problem in Xvfb
When running under Xvfb, there is sometimes no default focus. If the entry_name
doesn't have focus, click it to work around this. - 69. By Martin Pitt
-
Use Eventually matchers for robustness
Martin Pitt (pitti) wrote : | # |
I made some progress:
* r66 fixes the abysmally long test run time due to the aforementioned D-BUS timeouts. It now runs in ~ 45 seconds instead of 10.5 minutes.
* r68 works around the test program in xvfb sometimes not having the default focus. That's what caused the above "Joe" != "" failures. Debugging this properly is a bit tricky, I wanted to find out which widget has the default focus (if at all), but I found that select_many() doesn't work for int/bool properties. I'm investigating this now, will file a bug, and add a test case. I marked these as "FIXME" for now, but the workaround is IMHO not too bad. Also, it will become more general and easier to spot after the fix for bug 1082391 lands.
* The other are various smaller improvements, see their changelogs.
Now, let's see how the CI run likes these :-) I ran this in sbuild about six times and it always passed.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:69
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:70
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Martin Pitt (pitti) wrote : | # |
OK, two passes in a row, and armhf tests only take 77.51 sec. Feel free to trigger a third build, but from my POV this is robust enough now (I ran this through sbuild another three times).
- 70. By Martin Pitt
-
Add (failing) test cases for selecting int/bool properties
This reproduces https:/
/launchpad. net/bugs/ 1194763
Francis Ginther (fginther) wrote : | # |
So I had some trouble running the tests on raring outside of the package build (i.e. "cmake .; make; make test"). But it all ran fine under saucy. The failures are in:
tests.test_
- The test attempts to open the menu where it would appear if it were embedded in the app window, but my menu is in the unity panel.
tests.test_
- This test fails because it can't find the 'GdRevealer'. Possibly this doesn't exist in the saucy version of gnome-system-log?
Given that the failures are not seen on saucy (where development is targeted). I don't see a reason not to approve.
Martin Pitt (pitti) wrote : | # |
Indeed in saucy some programs fell back to having integrated menus even under Unity, such as gtimelog or the hello_color.py test application. I'm not sure whether that's deliberate or a bug, I'll ask Didier later. But it should be possible to force a builtin menu with setting UBUNTU_MENUPROXY.
The test_search test should then just be skipped if gnome-system-log is too old.
I'll try this stuff in a raring VM (I don't currently have a raring installation), and do another MP to fix the package build on raring.
Martin Pitt (pitti) wrote : | # |
Francis, I fixed the tests for raring in https:/
Preview Diff
1 | === modified file 'CMakeLists.txt' | |||
2 | --- CMakeLists.txt 2013-06-25 14:42:17 +0000 | |||
3 | +++ CMakeLists.txt 2013-06-26 09:35:33 +0000 | |||
4 | @@ -11,4 +11,4 @@ | |||
5 | 11 | add_subdirectory(lib) | 11 | add_subdirectory(lib) |
6 | 12 | 12 | ||
7 | 13 | enable_testing() | 13 | enable_testing() |
9 | 14 | add_test(nose sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/tests/autopilot; GTK_PATH=${CMAKE_CURRENT_BINARY_DIR}/lib autopilot run tests") | 14 | add_test(nose sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/tests/autopilot; GTK_PATH=${CMAKE_CURRENT_BINARY_DIR}/lib autopilot run -v tests") |
10 | 15 | 15 | ||
11 | === modified file 'debian/control' | |||
12 | --- debian/control 2013-06-20 13:50:10 +0000 | |||
13 | +++ debian/control 2013-06-26 09:35:33 +0000 | |||
14 | @@ -11,7 +11,12 @@ | |||
15 | 11 | xvfb, | 11 | xvfb, |
16 | 12 | dbus-x11, | 12 | dbus-x11, |
17 | 13 | python-autopilot, | 13 | python-autopilot, |
19 | 14 | gedit | 14 | python-xlib, |
20 | 15 | python-evdev, | ||
21 | 16 | python-gi, | ||
22 | 17 | gir1.2-gtk-3.0, | ||
23 | 18 | gsettings-desktop-schemas, | ||
24 | 19 | gnome-system-log, | ||
25 | 15 | Standards-Version: 3.9.4 | 20 | Standards-Version: 3.9.4 |
26 | 16 | Section: libs | 21 | Section: libs |
27 | 17 | Homepage: https://launchpad.net/autopilot-gtk | 22 | Homepage: https://launchpad.net/autopilot-gtk |
28 | 18 | 23 | ||
29 | === modified file 'debian/rules' | |||
30 | --- debian/rules 2013-06-20 13:50:10 +0000 | |||
31 | +++ debian/rules 2013-06-26 09:35:33 +0000 | |||
32 | @@ -7,8 +7,9 @@ | |||
33 | 7 | dh $@ | 7 | dh $@ |
34 | 8 | 8 | ||
35 | 9 | override_dh_auto_test: | 9 | override_dh_auto_test: |
38 | 10 | mkdir -p debian/tmp/home | 10 | mkdir -p debian/tmp/home/run |
39 | 11 | env HOME=debian/tmp/home xvfb-run dbus-launch dh_auto_test | 11 | env HOME=$(CURDIR)/debian/tmp/home XDG_RUNTIME_DIR=$(CURDIR)/debian/tmp/home/run \ |
40 | 12 | xvfb-run dh_auto_test | ||
41 | 12 | 13 | ||
42 | 13 | override_dh_install: | 14 | override_dh_install: |
43 | 14 | dh_install --fail-missing | 15 | dh_install --fail-missing |
44 | 15 | 16 | ||
45 | === added file 'tests/autopilot/tests/test_actions.py' | |||
46 | --- tests/autopilot/tests/test_actions.py 1970-01-01 00:00:00 +0000 | |||
47 | +++ tests/autopilot/tests/test_actions.py 2013-06-26 09:35:33 +0000 | |||
48 | @@ -0,0 +1,184 @@ | |||
49 | 1 | # blackbox testing of autopilot API against our hello_color.py test GTK program | ||
50 | 2 | # Author: Martin Pitt <martin.pitt@ubuntu.com> | ||
51 | 3 | # Copyright (C) 2013 Canonical Ltd | ||
52 | 4 | # | ||
53 | 5 | # This program is free software: you can redistribute it and/or modify | ||
54 | 6 | # it under the terms of the GNU General Public License version 3 as | ||
55 | 7 | # published by the Free Software Foundation. | ||
56 | 8 | # | ||
57 | 9 | # This program is distributed in the hope that it will be useful, | ||
58 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
59 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
60 | 12 | # GNU General Public License for more details. | ||
61 | 13 | # | ||
62 | 14 | # You should have received a copy of the GNU General Public License | ||
63 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
64 | 16 | |||
65 | 17 | import os.path | ||
66 | 18 | |||
67 | 19 | from autopilot.testcase import AutopilotTestCase | ||
68 | 20 | from autopilot.matchers import Eventually | ||
69 | 21 | from testtools.matchers import Equals, NotEquals | ||
70 | 22 | |||
71 | 23 | tests_dir = os.path.dirname(os.path.dirname(os.path.dirname( | ||
72 | 24 | os.path.realpath(__file__)))) | ||
73 | 25 | test_app = os.path.join(tests_dir, 'hello_color.py') | ||
74 | 26 | |||
75 | 27 | |||
76 | 28 | class ActionsTest(AutopilotTestCase): | ||
77 | 29 | """Test performing actions in the UI and verify results""" | ||
78 | 30 | |||
79 | 31 | def setUp(self): | ||
80 | 32 | super(ActionsTest, self).setUp() | ||
81 | 33 | self.app = self.launch_test_application(test_app, app_type='gtk') | ||
82 | 34 | |||
83 | 35 | def test_greeting_keyboard(self): | ||
84 | 36 | """Greeting with keyboard navigation""" | ||
85 | 37 | |||
86 | 38 | entries = self.app.select_many('GtkEntry') | ||
87 | 39 | self.assertEqual(len(entries), 2) | ||
88 | 40 | # the upper entry is for the name, the lower for the color | ||
89 | 41 | # FIXME: once we have proper names (LP# 1082391), replace this with an | ||
90 | 42 | # assertion | ||
91 | 43 | if entries[0].globalRect[1] < entries[1].globalRect[1]: | ||
92 | 44 | (entry_name, entry_color) = entries | ||
93 | 45 | else: | ||
94 | 46 | (entry_color, entry_name) = entries | ||
95 | 47 | |||
96 | 48 | # FIXME: This isn't necessary for real X, but under Xvfb there is no | ||
97 | 49 | # default focus sometimes | ||
98 | 50 | if not entry_name.has_focus: | ||
99 | 51 | self.mouse.click_object(entry_name) | ||
100 | 52 | |||
101 | 53 | # type in name and color | ||
102 | 54 | self.keyboard.type('Joe') | ||
103 | 55 | self.keyboard.press_and_release('Tab') | ||
104 | 56 | self.keyboard.type('red') | ||
105 | 57 | |||
106 | 58 | # entries should now have the typed text | ||
107 | 59 | self.assertThat(entry_name.text, Eventually(Equals('Joe'))) | ||
108 | 60 | self.assertThat(entry_color.text, Eventually(Equals('red'))) | ||
109 | 61 | |||
110 | 62 | # should not have any dialogs | ||
111 | 63 | self.assertEqual(self.app.select_single('GtkMessageDialog'), None) | ||
112 | 64 | |||
113 | 65 | # focus and activate the "Greet" button | ||
114 | 66 | self.keyboard.press_and_release('Tab') | ||
115 | 67 | self.keyboard.press_and_release('Enter') | ||
116 | 68 | |||
117 | 69 | # should get the greeting dialog | ||
118 | 70 | self.assertThat(lambda: self.app.select_single('GtkMessageDialog', visible=True), | ||
119 | 71 | Eventually(NotEquals(None))) | ||
120 | 72 | md = self.app.select_single('GtkMessageDialog') | ||
121 | 73 | |||
122 | 74 | # we expect the message dialog to show the corresponding greeting | ||
123 | 75 | self.assertNotEqual(md.select_single('GtkLabel', | ||
124 | 76 | label=u'Hello Joe, you like red.'), | ||
125 | 77 | None) | ||
126 | 78 | |||
127 | 79 | # close the dialog | ||
128 | 80 | self.keyboard.press_and_release('Enter') | ||
129 | 81 | self.assertThat(lambda: self.app.select_single('GtkMessageDialog', visible=True), | ||
130 | 82 | Eventually(Equals(None))) | ||
131 | 83 | |||
132 | 84 | def test_greeting_mouse(self): | ||
133 | 85 | """Greeting with mouse navigation""" | ||
134 | 86 | |||
135 | 87 | entries = self.app.select_many('GtkEntry') | ||
136 | 88 | self.assertEqual(len(entries), 2) | ||
137 | 89 | # the upper entry is for the name, the lower for the color | ||
138 | 90 | # FIXME: once we have proper names (LP# 1082391), replace this with an | ||
139 | 91 | # assertion | ||
140 | 92 | if entries[0].globalRect[1] < entries[1].globalRect[1]: | ||
141 | 93 | (entry_name, entry_color) = entries | ||
142 | 94 | else: | ||
143 | 95 | (entry_color, entry_name) = entries | ||
144 | 96 | |||
145 | 97 | # FIXME: This isn't necessary for real X, but under Xvfb there is no | ||
146 | 98 | # default focus sometimes | ||
147 | 99 | if not entry_name.has_focus: | ||
148 | 100 | self.mouse.click_object(entry_name) | ||
149 | 101 | |||
150 | 102 | # type in name and color | ||
151 | 103 | self.keyboard.type('Joe') | ||
152 | 104 | self.mouse.click_object(entry_color) | ||
153 | 105 | self.keyboard.type('blue') | ||
154 | 106 | |||
155 | 107 | # entries should now have the typed text | ||
156 | 108 | self.assertThat(entry_name.text, Eventually(Equals('Joe'))) | ||
157 | 109 | self.assertThat(entry_color.text, Eventually(Equals('blue'))) | ||
158 | 110 | |||
159 | 111 | # should not have any dialogs | ||
160 | 112 | self.assertEqual(self.app.select_single('GtkMessageDialog'), None) | ||
161 | 113 | |||
162 | 114 | # focus and activate the "Greet" button | ||
163 | 115 | btn = self.app.select_single('GtkButton', label='Greet') | ||
164 | 116 | self.assertNotEqual(btn, None) | ||
165 | 117 | self.mouse.click_object(btn) | ||
166 | 118 | |||
167 | 119 | # should get the greeting dialog | ||
168 | 120 | self.assertThat(lambda: self.app.select_single('GtkMessageDialog', visible=True), | ||
169 | 121 | Eventually(NotEquals(None))) | ||
170 | 122 | md = self.app.select_single('GtkMessageDialog') | ||
171 | 123 | |||
172 | 124 | # we expect the message dialog to show the corresponding greeting | ||
173 | 125 | self.assertNotEqual(md.select_single('GtkLabel', | ||
174 | 126 | label=u'Hello Joe, you like blue.'), | ||
175 | 127 | None) | ||
176 | 128 | |||
177 | 129 | # close the dialog | ||
178 | 130 | btn = md.select_single('GtkButton', label='gtk-close') | ||
179 | 131 | self.mouse.click_object(btn) | ||
180 | 132 | self.assertThat(lambda: self.app.select_single('GtkMessageDialog', visible=True), | ||
181 | 133 | Eventually(Equals(None))) | ||
182 | 134 | |||
183 | 135 | def test_clear(self): | ||
184 | 136 | """Using Clear button with mouse""" | ||
185 | 137 | |||
186 | 138 | # type in name and color | ||
187 | 139 | self.keyboard.type('Joe') | ||
188 | 140 | self.keyboard.press_and_release('Tab') | ||
189 | 141 | self.keyboard.type('blue') | ||
190 | 142 | |||
191 | 143 | # clear | ||
192 | 144 | btn = self.app.select_single('GtkButton', label='gtk-delete') | ||
193 | 145 | self.mouse.click_object(btn) | ||
194 | 146 | |||
195 | 147 | # entries should be clear now | ||
196 | 148 | entries = self.app.select_many('GtkEntry') | ||
197 | 149 | self.assertEqual(len(entries), 2) | ||
198 | 150 | for e in entries: | ||
199 | 151 | self.assertThat(e.text, Eventually(Equals(''))) | ||
200 | 152 | |||
201 | 153 | def test_menu(self): | ||
202 | 154 | """Browse the menu""" | ||
203 | 155 | |||
204 | 156 | file_menu = self.app.select_single('GtkMenuItem', label='_File') | ||
205 | 157 | help_menu = self.app.select_single('GtkMenuItem', label='_Help') | ||
206 | 158 | self.assertNotEqual(file_menu, None) | ||
207 | 159 | self.assertNotEqual(help_menu, None) | ||
208 | 160 | |||
209 | 161 | # the top-level menus should be visible and thus have a rect | ||
210 | 162 | for m in (file_menu, help_menu): | ||
211 | 163 | self.assertGreaterEqual(m.globalRect[0], 0) | ||
212 | 164 | self.assertGreaterEqual(m.globalRect[1], 0) | ||
213 | 165 | self.assertGreater(m.globalRect[2], 0) | ||
214 | 166 | self.assertGreater(m.globalRect[3], 0) | ||
215 | 167 | |||
216 | 168 | # the submenus are not visible by default | ||
217 | 169 | m = self.app.select_single('GtkImageMenuItem', label='gtk-open') | ||
218 | 170 | self.assertFalse(hasattr(m, 'globalRect')) | ||
219 | 171 | |||
220 | 172 | # after opening, submenus should become visible | ||
221 | 173 | self.mouse.click_object(file_menu) | ||
222 | 174 | # FIXME: getting a reference to this object once and then just querying | ||
223 | 175 | # it doesn't work | ||
224 | 176 | self.assertThat(lambda: hasattr(self.app.select_single('GtkImageMenuItem', | ||
225 | 177 | label='gtk-open'), | ||
226 | 178 | 'globalRect'), | ||
227 | 179 | Eventually(Equals(True))) | ||
228 | 180 | m = self.app.select_single('GtkImageMenuItem', label='gtk-open') | ||
229 | 181 | self.assertGreaterEqual(m.globalRect[0], 0) | ||
230 | 182 | self.assertGreaterEqual(m.globalRect[1], 0) | ||
231 | 183 | self.assertGreater(m.globalRect[2], 0) | ||
232 | 184 | self.assertGreater(m.globalRect[3], 0) | ||
233 | 0 | 185 | ||
234 | === added file 'tests/autopilot/tests/test_gnome_system_log.py' | |||
235 | --- tests/autopilot/tests/test_gnome_system_log.py 1970-01-01 00:00:00 +0000 | |||
236 | +++ tests/autopilot/tests/test_gnome_system_log.py 2013-06-26 09:35:33 +0000 | |||
237 | @@ -0,0 +1,61 @@ | |||
238 | 1 | # blackbox testing of autopilot API against gnome-system-log | ||
239 | 2 | # Author: Martin Pitt <martin.pitt@ubuntu.com> | ||
240 | 3 | # Copyright (C) 2013 Canonical Ltd | ||
241 | 4 | # | ||
242 | 5 | # This program is free software: you can redistribute it and/or modify | ||
243 | 6 | # it under the terms of the GNU General Public License version 3 as | ||
244 | 7 | # published by the Free Software Foundation. | ||
245 | 8 | # | ||
246 | 9 | # This program is distributed in the hope that it will be useful, | ||
247 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
248 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
249 | 12 | # GNU General Public License for more details. | ||
250 | 13 | # | ||
251 | 14 | # You should have received a copy of the GNU General Public License | ||
252 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
253 | 16 | |||
254 | 17 | |||
255 | 18 | from autopilot.testcase import AutopilotTestCase | ||
256 | 19 | from autopilot.matchers import Eventually | ||
257 | 20 | from testtools.matchers import Equals, NotEquals | ||
258 | 21 | |||
259 | 22 | |||
260 | 23 | class GnomeAppTest(AutopilotTestCase): | ||
261 | 24 | """Test autopilot against an actual GNOME application""" | ||
262 | 25 | |||
263 | 26 | def setUp(self): | ||
264 | 27 | super(GnomeAppTest, self).setUp() | ||
265 | 28 | self.patch_environment('LANGUAGE', '') | ||
266 | 29 | self.patch_environment('LANG', '') | ||
267 | 30 | self.patch_environment('LC_MESSAGES', 'C') | ||
268 | 31 | self.app = self.launch_test_application('gnome-system-log', '/etc/issue') | ||
269 | 32 | |||
270 | 33 | def test_filename_label(self): | ||
271 | 34 | """Find file name label""" | ||
272 | 35 | |||
273 | 36 | l = self.app.select_single('GtkLabel', label=u'<b>issue</b>') | ||
274 | 37 | self.assertNotEqual(l, None) | ||
275 | 38 | self.assertEqual(l.visible, True) | ||
276 | 39 | |||
277 | 40 | def test_search(self): | ||
278 | 41 | """Run a search""" | ||
279 | 42 | |||
280 | 43 | revealer = self.app.select_single('GdRevealer') | ||
281 | 44 | self.assertNotEqual(revealer, None) | ||
282 | 45 | |||
283 | 46 | # search bar not visible by default | ||
284 | 47 | self.assertEqual(revealer.child_revealed, False) | ||
285 | 48 | |||
286 | 49 | search_btn = self.app.select_single('GtkToggleButton') | ||
287 | 50 | self.assertNotEqual(search_btn, None) | ||
288 | 51 | self.mouse.click_object(search_btn) | ||
289 | 52 | |||
290 | 53 | # should trigger search bar | ||
291 | 54 | self.assertThat(lambda: revealer.child_revealed, Eventually(Equals(True))) | ||
292 | 55 | search = self.app.select_single('GtkSearchEntry', visible=True) | ||
293 | 56 | self.assertTrue(search.has_focus) | ||
294 | 57 | |||
295 | 58 | # something that will not be in /etc/issue | ||
296 | 59 | self.keyboard.type('Bogus12!') | ||
297 | 60 | self.assertThat(lambda: self.app.select_single('GtkLabel', label=u'No matches found'), | ||
298 | 61 | Eventually(NotEquals(None))) | ||
299 | 0 | 62 | ||
300 | === removed file 'tests/autopilot/tests/test_matching_properties.py' | |||
301 | --- tests/autopilot/tests/test_matching_properties.py 2013-05-22 23:29:06 +0000 | |||
302 | +++ tests/autopilot/tests/test_matching_properties.py 1970-01-01 00:00:00 +0000 | |||
303 | @@ -1,32 +0,0 @@ | |||
304 | 1 | |||
305 | 2 | |||
306 | 3 | from autopilot.testcase import AutopilotTestCase | ||
307 | 4 | from testtools.matchers import NotEquals | ||
308 | 5 | |||
309 | 6 | |||
310 | 7 | |||
311 | 8 | class PropertyMatchingTest(AutopilotTestCase): | ||
312 | 9 | |||
313 | 10 | def setUp(self): | ||
314 | 11 | super(PropertyMatchingTest, self).setUp() | ||
315 | 12 | self.app = self.launch_test_application('gedit') | ||
316 | 13 | |||
317 | 14 | def test_integer_matches(self): | ||
318 | 15 | """Test property matching for integers. | ||
319 | 16 | |||
320 | 17 | Find an opaque GtkWindow in Gedit. | ||
321 | 18 | """ | ||
322 | 19 | |||
323 | 20 | opaque_window = self.app.select_many('GtkWindow', opacity=1) | ||
324 | 21 | self.assertThat(opaque_window, NotEquals(None)) | ||
325 | 22 | |||
326 | 23 | |||
327 | 24 | def test_string_matches(self): | ||
328 | 25 | """Match a string property. | ||
329 | 26 | |||
330 | 27 | Find an GtkImageMenuItem named 'BookmarkOpen' in Gedit. | ||
331 | 28 | """ | ||
332 | 29 | |||
333 | 30 | bookmark_open_item = self.app.select_single('GtkImageMenuItem', | ||
334 | 31 | name='BookmarkOpen') | ||
335 | 32 | self.assertThat(bookmark_open_item, NotEquals(None)) | ||
336 | 33 | 0 | ||
337 | === added file 'tests/autopilot/tests/test_properties.py' | |||
338 | --- tests/autopilot/tests/test_properties.py 1970-01-01 00:00:00 +0000 | |||
339 | +++ tests/autopilot/tests/test_properties.py 2013-06-26 09:35:33 +0000 | |||
340 | @@ -0,0 +1,99 @@ | |||
341 | 1 | # blackbox testing of autopilot API against our hello_color.py test GTK program | ||
342 | 2 | # Author: Martin Pitt <martin.pitt@ubuntu.com> | ||
343 | 3 | # Copyright (C) 2013 Canonical Ltd | ||
344 | 4 | # | ||
345 | 5 | # This program is free software: you can redistribute it and/or modify | ||
346 | 6 | # it under the terms of the GNU General Public License version 3 as | ||
347 | 7 | # published by the Free Software Foundation. | ||
348 | 8 | # | ||
349 | 9 | # This program is distributed in the hope that it will be useful, | ||
350 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
351 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
352 | 12 | # GNU General Public License for more details. | ||
353 | 13 | # | ||
354 | 14 | # You should have received a copy of the GNU General Public License | ||
355 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
356 | 16 | |||
357 | 17 | import os.path | ||
358 | 18 | import unittest | ||
359 | 19 | |||
360 | 20 | from autopilot.testcase import AutopilotTestCase | ||
361 | 21 | |||
362 | 22 | tests_dir = os.path.dirname(os.path.dirname(os.path.dirname( | ||
363 | 23 | os.path.realpath(__file__)))) | ||
364 | 24 | test_app = os.path.join(tests_dir, 'hello_color.py') | ||
365 | 25 | |||
366 | 26 | |||
367 | 27 | class PropertyTest(AutopilotTestCase): | ||
368 | 28 | """Widget properties""" | ||
369 | 29 | |||
370 | 30 | def setUp(self): | ||
371 | 31 | super(PropertyTest, self).setUp() | ||
372 | 32 | self.app = self.launch_test_application(test_app, app_type='gtk') | ||
373 | 33 | |||
374 | 34 | def test_button(self): | ||
375 | 35 | """GtkButton properties""" | ||
376 | 36 | |||
377 | 37 | btn_greet = self.app.select_single('GtkButton', label='Greet') | ||
378 | 38 | self.assertNotEqual(btn_greet, None) | ||
379 | 39 | btn_quit = self.app.select_single('GtkButton', label='gtk-quit') | ||
380 | 40 | self.assertNotEqual(btn_quit, None) | ||
381 | 41 | |||
382 | 42 | self.assertEqual(btn_greet.use_stock, False) | ||
383 | 43 | self.assertEqual(btn_quit.use_stock, True) | ||
384 | 44 | |||
385 | 45 | # only GtkButton, GtkFileChooserButton, and GtkComboBox have | ||
386 | 46 | # focus-on-click, and we don't use the latter two | ||
387 | 47 | self.assertEqual(btn_greet.focus_on_click, True) | ||
388 | 48 | self.assertEqual(btn_quit.focus_on_click, True) | ||
389 | 49 | |||
390 | 50 | # all buttons are visible and thus should have a rect | ||
391 | 51 | self.assertTrue(btn_greet.visible) | ||
392 | 52 | self.assertTrue(btn_quit.visible) | ||
393 | 53 | self.assertEqual(len(btn_greet.globalRect), 4) | ||
394 | 54 | self.assertEqual(len(btn_quit.globalRect), 4) | ||
395 | 55 | |||
396 | 56 | def test_entry(self): | ||
397 | 57 | """GtkEntry properties""" | ||
398 | 58 | |||
399 | 59 | entries = self.app.select_many('GtkEntry') | ||
400 | 60 | self.assertEqual(len(entries), 2) | ||
401 | 61 | # the upper entry is for the name, the lower for the color | ||
402 | 62 | # FIXME: once we have proper names (LP# 1082391), replace this with an | ||
403 | 63 | # assertion | ||
404 | 64 | if entries[0].globalRect[1] < entries[1].globalRect[1]: | ||
405 | 65 | (entry_name, entry_color) = entries | ||
406 | 66 | else: | ||
407 | 67 | (entry_color, entry_name) = entries | ||
408 | 68 | |||
409 | 69 | self.assertTrue(entry_name.visible) | ||
410 | 70 | self.assertTrue(entry_color.visible) | ||
411 | 71 | |||
412 | 72 | # the entries should have the same size and x alignment | ||
413 | 73 | self.assertEqual(entry_name.globalRect[0], entry_color.globalRect[0]) | ||
414 | 74 | self.assertEqual(entry_name.globalRect[2:], entry_color.globalRect[2:]) | ||
415 | 75 | |||
416 | 76 | # FIXME: This isn't necessary for real X, but under Xvfb there is no | ||
417 | 77 | # default focus sometimes | ||
418 | 78 | if not entry_name.has_focus: | ||
419 | 79 | self.mouse.click_object(entry_name) | ||
420 | 80 | |||
421 | 81 | # first entry has default focus | ||
422 | 82 | self.assertEqual(entry_name.has_focus, True) | ||
423 | 83 | self.assertEqual(entry_color.has_focus, False) | ||
424 | 84 | |||
425 | 85 | # both entries are empty by default | ||
426 | 86 | self.assertEqual(entry_name.text, '') | ||
427 | 87 | self.assertEqual(entry_color.text, '') | ||
428 | 88 | |||
429 | 89 | # text-length is an unique property for GtkEntry | ||
430 | 90 | self.assertEqual(entry_name.text_length, 0) | ||
431 | 91 | self.assertEqual(entry_color.text_length, 0) | ||
432 | 92 | |||
433 | 93 | #https://launchpad.net/bugs/1193342 | ||
434 | 94 | @unittest.expectedFailure | ||
435 | 95 | def test_enum_properties(self): | ||
436 | 96 | '''enum properties''' | ||
437 | 97 | |||
438 | 98 | btn_greet = self.app.select_single('GtkButton', label='Greet') | ||
439 | 99 | self.assertTrue(hasattr(btn_greet, 'relief')) | ||
440 | 0 | 100 | ||
441 | === added file 'tests/autopilot/tests/test_widget_tree.py' | |||
442 | --- tests/autopilot/tests/test_widget_tree.py 1970-01-01 00:00:00 +0000 | |||
443 | +++ tests/autopilot/tests/test_widget_tree.py 2013-06-26 09:35:33 +0000 | |||
444 | @@ -0,0 +1,186 @@ | |||
445 | 1 | # blackbox testing of autopilot API against our hello_color.py test GTK program | ||
446 | 2 | # Author: Martin Pitt <martin.pitt@ubuntu.com> | ||
447 | 3 | # Copyright (C) 2013 Canonical Ltd | ||
448 | 4 | # | ||
449 | 5 | # This program is free software: you can redistribute it and/or modify | ||
450 | 6 | # it under the terms of the GNU General Public License version 3 as | ||
451 | 7 | # published by the Free Software Foundation. | ||
452 | 8 | # | ||
453 | 9 | # This program is distributed in the hope that it will be useful, | ||
454 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
455 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
456 | 12 | # GNU General Public License for more details. | ||
457 | 13 | # | ||
458 | 14 | # You should have received a copy of the GNU General Public License | ||
459 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
460 | 16 | |||
461 | 17 | import os.path | ||
462 | 18 | import unittest | ||
463 | 19 | |||
464 | 20 | from autopilot.testcase import AutopilotTestCase | ||
465 | 21 | |||
466 | 22 | tests_dir = os.path.dirname(os.path.dirname(os.path.dirname( | ||
467 | 23 | os.path.realpath(__file__)))) | ||
468 | 24 | test_app = os.path.join(tests_dir, 'hello_color.py') | ||
469 | 25 | |||
470 | 26 | |||
471 | 27 | class WidgetTreeTest(AutopilotTestCase): | ||
472 | 28 | """Widget tree iteration and search""" | ||
473 | 29 | |||
474 | 30 | def setUp(self): | ||
475 | 31 | super(WidgetTreeTest, self).setUp() | ||
476 | 32 | self.app = self.launch_test_application(test_app, app_type='gtk') | ||
477 | 33 | |||
478 | 34 | def test_get_children_recursive(self): | ||
479 | 35 | """Recursive get_children() | ||
480 | 36 | |||
481 | 37 | This should not crash, and deliver valid widgets. | ||
482 | 38 | """ | ||
483 | 39 | widgets = set() | ||
484 | 40 | self._get_widgets(self.app, widgets) | ||
485 | 41 | for c in widgets: | ||
486 | 42 | self.assertIn('.Gtk', str(type(c))) | ||
487 | 43 | self.assertGreaterEqual(c.id, 0) | ||
488 | 44 | # uncomment this to get a dump of all widgets and properties | ||
489 | 45 | #print(type(c)) | ||
490 | 46 | #for p in c.get_properties(): | ||
491 | 47 | # print ' ', p, repr(getattr(c, p)) | ||
492 | 48 | |||
493 | 49 | def test_get_children_by_type(self): | ||
494 | 50 | # multiple instances | ||
495 | 51 | res = self.app.get_children_by_type('GtkWindow') | ||
496 | 52 | self.assertGreaterEqual(len(res), 3) | ||
497 | 53 | self.assertIn('.GtkWindow', str(type(res[0]))) | ||
498 | 54 | |||
499 | 55 | # one qualified instance | ||
500 | 56 | res = self.app.get_children_by_type('GtkWindow', Children=['GtkBox']) | ||
501 | 57 | self.assertGreaterEqual(len(res), 1) | ||
502 | 58 | |||
503 | 59 | # no instances | ||
504 | 60 | self.assertEqual(self.app.get_children_by_type('GtkTable'), []) | ||
505 | 61 | |||
506 | 62 | def test_select_single_unique(self): | ||
507 | 63 | """select_single() on widget types with only one instance""" | ||
508 | 64 | |||
509 | 65 | for wtype in ('GtkMenuBar', 'GtkAboutDialog', 'GtkGrid'): | ||
510 | 66 | w = self.app.select_single(wtype) | ||
511 | 67 | self.assertIn('.' + wtype, str(type(w))) | ||
512 | 68 | |||
513 | 69 | def test_select_single_nonunique(self): | ||
514 | 70 | """select_single() on widget types with multiple instances""" | ||
515 | 71 | |||
516 | 72 | # we have more than one instance of these | ||
517 | 73 | for wtype in ('GtkButton', 'GtkEntry'): | ||
518 | 74 | self.assertRaises(ValueError, self.app.select_single, wtype) | ||
519 | 75 | |||
520 | 76 | # we have no instances of these | ||
521 | 77 | for wtype in ('GtkTable', 'GtkRadioButton'): | ||
522 | 78 | self.assertIs(self.app.select_single(wtype), None) | ||
523 | 79 | |||
524 | 80 | # qualified: visible property is not unique | ||
525 | 81 | self.assertRaises(ValueError, | ||
526 | 82 | self.app.select_single, 'GtkButton', visible=1) | ||
527 | 83 | |||
528 | 84 | # qualified: label property is unique within GtkButton | ||
529 | 85 | w = self.app.select_single('GtkButton', label='gtk-quit') | ||
530 | 86 | self.assertIn('.GtkButton', str(type(w))) | ||
531 | 87 | self.assertEqual(w.label, 'gtk-quit') | ||
532 | 88 | |||
533 | 89 | def test_select_single_noclass(self): | ||
534 | 90 | """select_single() without specifying a class""" | ||
535 | 91 | |||
536 | 92 | # gtk-delete label is unique to our Button | ||
537 | 93 | w = self.app.select_single(label='gtk-delete') | ||
538 | 94 | self.assertIn('.GtkButton', str(type(w))) | ||
539 | 95 | self.assertEqual(w.label, 'gtk-delete') | ||
540 | 96 | |||
541 | 97 | # gtk-quit label is not unique globally, it's also a menu item | ||
542 | 98 | self.assertRaises(ValueError, self.app.select_single, label='gtk-quit') | ||
543 | 99 | |||
544 | 100 | # ... but it is unique for focussable widgets (menus don't allow that) | ||
545 | 101 | w = self.app.select_single(label='gtk-quit', can_focus=1) | ||
546 | 102 | self.assertIn('.GtkButton', str(type(w))) | ||
547 | 103 | self.assertEqual(w.label, 'gtk-quit') | ||
548 | 104 | |||
549 | 105 | def test_select_many_string(self): | ||
550 | 106 | """select_many() with string properties""" | ||
551 | 107 | |||
552 | 108 | # by class, unqualified, multiple instances | ||
553 | 109 | res = self.app.select_many('GtkButton') | ||
554 | 110 | # we have three in our main window, plus some in the about dialog | ||
555 | 111 | self.assertGreaterEqual(len(res), 3) | ||
556 | 112 | self.assertIn('.GtkButton', str(type(res[0]))) | ||
557 | 113 | |||
558 | 114 | # .. but exactly three in the main window | ||
559 | 115 | main_window = self.app.select_single('GtkWindow', Children=['GtkBox'], visible=True) | ||
560 | 116 | res = main_window.select_many('GtkButton') | ||
561 | 117 | self.assertEqual(len(res), 3) | ||
562 | 118 | |||
563 | 119 | # by class, unqualified, single instance | ||
564 | 120 | res = self.app.select_many('GtkMenuBar') | ||
565 | 121 | self.assertEqual(len(res), 1) | ||
566 | 122 | self.assertIn('.GtkMenuBar', str(type(res[0]))) | ||
567 | 123 | |||
568 | 124 | # by class, unqualified, no instance | ||
569 | 125 | res = self.app.select_many('GtkTable') | ||
570 | 126 | self.assertEqual(res, []) | ||
571 | 127 | |||
572 | 128 | # by class, qualified | ||
573 | 129 | res = self.app.select_many('GtkButton', label='Greet') | ||
574 | 130 | self.assertEqual(len(res), 1) | ||
575 | 131 | self.assertIn('.GtkButton', str(type(res[0]))) | ||
576 | 132 | self.assertEqual(res[0].label, 'Greet') | ||
577 | 133 | |||
578 | 134 | # untyped | ||
579 | 135 | res = self.app.select_many(label='gtk-delete') | ||
580 | 136 | self.assertEqual(len(res), 1) | ||
581 | 137 | self.assertIn('.GtkButton', str(type(res[0]))) | ||
582 | 138 | self.assertEqual(res[0].label, 'gtk-delete') | ||
583 | 139 | |||
584 | 140 | res = self.app.select_many(label='gtk-quit') | ||
585 | 141 | # button and menu item | ||
586 | 142 | self.assertEqual(len(res), 2) | ||
587 | 143 | |||
588 | 144 | # https://launchpad.net/bugs/1194763 | ||
589 | 145 | @unittest.expectedFailure | ||
590 | 146 | def test_select_int(self): | ||
591 | 147 | """select_*() with int properties""" | ||
592 | 148 | |||
593 | 149 | # with class | ||
594 | 150 | res = self.app.select_many('GtkButtonBox', border_width=5) | ||
595 | 151 | self.assertEqual(len(res), 1) | ||
596 | 152 | |||
597 | 153 | self.assertNotEqual(self.app.select_single('GtkButtonBox', border_width=5), None) | ||
598 | 154 | |||
599 | 155 | # without class | ||
600 | 156 | res = self.app.select_many(border_width=5) | ||
601 | 157 | self.assertGreater(len(res), 2) | ||
602 | 158 | |||
603 | 159 | self.assertNotEqual(self.app.select_single(border_width=2), None) | ||
604 | 160 | |||
605 | 161 | # https://launchpad.net/bugs/1194763 | ||
606 | 162 | @unittest.expectedFailure | ||
607 | 163 | def test_select_bool(self): | ||
608 | 164 | """select_*() with boolean properties""" | ||
609 | 165 | |||
610 | 166 | # with class | ||
611 | 167 | res = self.app.select_many('GtkButton', visible=True) | ||
612 | 168 | self.assertGreater(len(res), 2) | ||
613 | 169 | |||
614 | 170 | res = self.app.select_many('GtkAboutDialog', visible=False) | ||
615 | 171 | self.assertGreater(len(res), 0) | ||
616 | 172 | |||
617 | 173 | # without class | ||
618 | 174 | res = self.app.select_many(visible=True) | ||
619 | 175 | self.assertGreater(len(res), 5) | ||
620 | 176 | |||
621 | 177 | res = self.app.select_many(visible=False) | ||
622 | 178 | self.assertGreater(len(res), 4) | ||
623 | 179 | |||
624 | 180 | @classmethod | ||
625 | 181 | def _get_widgets(klass, obj, widget_set): | ||
626 | 182 | """Recursively add all children of obj to widget_set""" | ||
627 | 183 | |||
628 | 184 | for c in obj.get_children(): | ||
629 | 185 | widget_set.add(c) | ||
630 | 186 | klass._get_widgets(c, widget_set) | ||
631 | 0 | 187 | ||
632 | === added file 'tests/autopilot/tests/test_xpath_query.py' | |||
633 | --- tests/autopilot/tests/test_xpath_query.py 1970-01-01 00:00:00 +0000 | |||
634 | +++ tests/autopilot/tests/test_xpath_query.py 2013-06-26 09:35:33 +0000 | |||
635 | @@ -0,0 +1,110 @@ | |||
636 | 1 | # blackbox testing of autopilot API against our hello_color.py test GTK program | ||
637 | 2 | # Author: Martin Pitt <martin.pitt@ubuntu.com> | ||
638 | 3 | # Copyright (C) 2013 Canonical Ltd | ||
639 | 4 | # | ||
640 | 5 | # This program is free software: you can redistribute it and/or modify | ||
641 | 6 | # it under the terms of the GNU General Public License version 3 as | ||
642 | 7 | # published by the Free Software Foundation. | ||
643 | 8 | # | ||
644 | 9 | # This program is distributed in the hope that it will be useful, | ||
645 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
646 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
647 | 12 | # GNU General Public License for more details. | ||
648 | 13 | # | ||
649 | 14 | # You should have received a copy of the GNU General Public License | ||
650 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
651 | 16 | |||
652 | 17 | import os.path | ||
653 | 18 | import unittest | ||
654 | 19 | |||
655 | 20 | from autopilot.testcase import AutopilotTestCase | ||
656 | 21 | |||
657 | 22 | tests_dir = os.path.dirname(os.path.dirname(os.path.dirname( | ||
658 | 23 | os.path.realpath(__file__)))) | ||
659 | 24 | test_app = os.path.join(tests_dir, 'hello_color.py') | ||
660 | 25 | |||
661 | 26 | |||
662 | 27 | class XPathQueryTest(AutopilotTestCase): | ||
663 | 28 | """XPath queries""" | ||
664 | 29 | |||
665 | 30 | def setUp(self): | ||
666 | 31 | super(XPathQueryTest, self).setUp() | ||
667 | 32 | self.app = self.launch_test_application(test_app, app_type='gtk') | ||
668 | 33 | |||
669 | 34 | def xtest_have_path(self): | ||
670 | 35 | """All children have a unique path""" | ||
671 | 36 | |||
672 | 37 | widgets = set() | ||
673 | 38 | self._get_widgets(self.app, widgets) | ||
674 | 39 | |||
675 | 40 | seen_paths = set() | ||
676 | 41 | for widget in widgets: | ||
677 | 42 | path = widget.get_class_query_string() | ||
678 | 43 | self.assertNotIn(path, seen_paths) | ||
679 | 44 | seen_paths.add(path) | ||
680 | 45 | |||
681 | 46 | # we can resolve the path back to the widget | ||
682 | 47 | state = self.app.get_state_by_path(path) | ||
683 | 48 | # state is an array with one (path, props) element | ||
684 | 49 | props = state[0][1] | ||
685 | 50 | self.assertEqual(props['id'], widget.id) | ||
686 | 51 | self.assertEqual(props['visible'], widget.visible) | ||
687 | 52 | |||
688 | 53 | def xtest_select_full_path(self): | ||
689 | 54 | """Select widgets with full XPath""" | ||
690 | 55 | |||
691 | 56 | # three buttons in main dialog's ButtonBox | ||
692 | 57 | state = self.app.get_state_by_path('/Root/GtkWindow/GtkBox/GtkButtonBox/GtkButton') | ||
693 | 58 | self.assertEqual(len(state), 3) | ||
694 | 59 | labels = [str(props[1]['label']) for props in state] | ||
695 | 60 | labels.sort() | ||
696 | 61 | self.assertEqual(labels, ['Greet', 'gtk-delete', 'gtk-quit']) | ||
697 | 62 | |||
698 | 63 | # select button with particular label | ||
699 | 64 | for l in ['Greet', 'gtk-delete', 'gtk-quit']: | ||
700 | 65 | state = self.app.get_state_by_path('/Root/GtkWindow/GtkBox/GtkButtonBox/GtkButton[label=%s]' % l) | ||
701 | 66 | self.assertEqual(len(state), 1) | ||
702 | 67 | self.assertEqual(state[0][1]['label'], l) | ||
703 | 68 | |||
704 | 69 | def xtest_select_path_pattern(self): | ||
705 | 70 | """Select widgets with XPath path pattern""" | ||
706 | 71 | |||
707 | 72 | # three buttons in main dialog's ButtonBox | ||
708 | 73 | state = self.app.get_state_by_path('//GtkWindow//GtkButton') | ||
709 | 74 | self.assertEqual(len(state), 3) | ||
710 | 75 | labels = [str(props[1]['label']) for props in state] | ||
711 | 76 | labels.sort() | ||
712 | 77 | self.assertEqual(labels, ['Greet', 'gtk-delete', 'gtk-quit']) | ||
713 | 78 | |||
714 | 79 | # at least four buttons in the whole tree | ||
715 | 80 | state = self.app.get_state_by_path('/Root//GtkButton') | ||
716 | 81 | self.assertGreaterEqual(len(state), 4) | ||
717 | 82 | |||
718 | 83 | def test_select_by_attribute(self): | ||
719 | 84 | """Select widgets with attribute pattern""" | ||
720 | 85 | |||
721 | 86 | state = self.app.get_state_by_path('//*[label=gtk-delete]') | ||
722 | 87 | self.assertEqual(len(state), 1, state) | ||
723 | 88 | self.assertEqual(state[0][1]['label'], 'gtk-delete') | ||
724 | 89 | self.assertTrue(state[0][0].endswith('/GtkButton'), state[0][0]) | ||
725 | 90 | |||
726 | 91 | # https://launchpad.net/bugs/1179806 | ||
727 | 92 | @unittest.expectedFailure | ||
728 | 93 | def test_select_by_attribute_spaces(self): | ||
729 | 94 | """Select widgets with attribute pattern containing spaces""" | ||
730 | 95 | |||
731 | 96 | # none of these work ATM, but are supposed to: | ||
732 | 97 | #state = self.app.get_state_by_path('//*[label=Hello Color!]') | ||
733 | 98 | #state = self.app.get_state_by_path('//*[label=Hello Color!]') | ||
734 | 99 | state = self.app.get_state_by_path('//*[label="Hello Color!"]') | ||
735 | 100 | self.assertEqual(len(state), 1, str(state)) | ||
736 | 101 | self.assertEqual(state[0][1]['label'], 'Hello Color!') | ||
737 | 102 | self.assertTrue(state[0][0].endswith('/GtkLabel'), state[0][0]) | ||
738 | 103 | |||
739 | 104 | @classmethod | ||
740 | 105 | def _get_widgets(klass, obj, widget_set): | ||
741 | 106 | """Recursively add all children of obj to widget_set""" | ||
742 | 107 | |||
743 | 108 | for c in obj.get_children(): | ||
744 | 109 | widget_set.add(c) | ||
745 | 110 | klass._get_widgets(c, widget_set) | ||
746 | 0 | 111 | ||
747 | === added file 'tests/hello_color.py' | |||
748 | --- tests/hello_color.py 1970-01-01 00:00:00 +0000 | |||
749 | +++ tests/hello_color.py 2013-06-26 09:35:33 +0000 | |||
750 | @@ -0,0 +1,53 @@ | |||
751 | 1 | #!/usr/bin/python | ||
752 | 2 | |||
753 | 3 | import sys | ||
754 | 4 | import os.path | ||
755 | 5 | |||
756 | 6 | from gi.repository import Gtk | ||
757 | 7 | |||
758 | 8 | |||
759 | 9 | class HelloColorApp(Gtk.Application): | ||
760 | 10 | def __init__(self): | ||
761 | 11 | self.widgets = Gtk.Builder.new() | ||
762 | 12 | self.widgets.add_from_file((os.path.join(os.path.dirname(sys.argv[0]), 'hello_color.ui'))) | ||
763 | 13 | assert self.widgets.connect_signals(self) is None | ||
764 | 14 | |||
765 | 15 | def run(self): | ||
766 | 16 | self.widgets.get_object('window_app').show() | ||
767 | 17 | Gtk.main() | ||
768 | 18 | |||
769 | 19 | def on_quit(self, *args): | ||
770 | 20 | Gtk.main_quit() | ||
771 | 21 | |||
772 | 22 | def on_file_open(self, *args): | ||
773 | 23 | md = Gtk.FileChooserDialog('Select a file..', | ||
774 | 24 | parent=self.widgets.get_object('window_app'), | ||
775 | 25 | buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, | ||
776 | 26 | Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) | ||
777 | 27 | result = md.run() | ||
778 | 28 | md.hide() | ||
779 | 29 | if result == Gtk.ResponseType.OK: | ||
780 | 30 | self.widgets.get_object('label_status').set_text('Loaded %s' % md.get_filenames()[0]) | ||
781 | 31 | |||
782 | 32 | def on_button_greet(self, *args): | ||
783 | 33 | name = self.widgets.get_object('entry_name').get_text() | ||
784 | 34 | color = self.widgets.get_object('entry_color').get_text() | ||
785 | 35 | |||
786 | 36 | md = Gtk.MessageDialog(message_type=Gtk.MessageType.INFO, | ||
787 | 37 | buttons=Gtk.ButtonsType.CLOSE, | ||
788 | 38 | text='Hello %s, you like %s.' % (name, color)) | ||
789 | 39 | md.run() | ||
790 | 40 | md.hide() | ||
791 | 41 | |||
792 | 42 | def on_button_clear(self, *args): | ||
793 | 43 | self.widgets.get_object('entry_name').set_text('') | ||
794 | 44 | self.widgets.get_object('entry_color').set_text('') | ||
795 | 45 | self.widgets.get_object('label_status').set_text('') | ||
796 | 46 | |||
797 | 47 | def on_about(self, *args): | ||
798 | 48 | d = self.widgets.get_object('dialog_about') | ||
799 | 49 | d.run() | ||
800 | 50 | d.hide() | ||
801 | 51 | |||
802 | 52 | if __name__ == '__main__': | ||
803 | 53 | HelloColorApp().run() | ||
804 | 0 | 54 | ||
805 | === added file 'tests/hello_color.ui' | |||
806 | --- tests/hello_color.ui 1970-01-01 00:00:00 +0000 | |||
807 | +++ tests/hello_color.ui 2013-06-26 09:35:33 +0000 | |||
808 | @@ -0,0 +1,243 @@ | |||
809 | 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
810 | 2 | <interface> | ||
811 | 3 | <!-- interface-requires gtk+ 3.0 --> | ||
812 | 4 | <object class="GtkAboutDialog" id="dialog_about"> | ||
813 | 5 | <property name="can_focus">False</property> | ||
814 | 6 | <property name="border_width">5</property> | ||
815 | 7 | <property name="type_hint">dialog</property> | ||
816 | 8 | <property name="program_name">Hello Color!</property> | ||
817 | 9 | <property name="copyright" translatable="yes">(C) 2013 Canonical Ltd.</property> | ||
818 | 10 | <property name="authors">Martin Pitt</property> | ||
819 | 11 | <property name="license_type">gpl-3-0</property> | ||
820 | 12 | </object> | ||
821 | 13 | <object class="GtkWindow" id="window_app"> | ||
822 | 14 | <property name="can_focus">False</property> | ||
823 | 15 | <signal name="destroy" handler="on_quit" swapped="no"/> | ||
824 | 16 | <child> | ||
825 | 17 | <object class="GtkBox" id="box1"> | ||
826 | 18 | <property name="visible">True</property> | ||
827 | 19 | <property name="can_focus">False</property> | ||
828 | 20 | <property name="orientation">vertical</property> | ||
829 | 21 | <child> | ||
830 | 22 | <object class="GtkMenuBar" id="menubar"> | ||
831 | 23 | <property name="visible">True</property> | ||
832 | 24 | <property name="can_focus">False</property> | ||
833 | 25 | <child> | ||
834 | 26 | <object class="GtkMenuItem" id="menuitem_file"> | ||
835 | 27 | <property name="visible">True</property> | ||
836 | 28 | <property name="can_focus">False</property> | ||
837 | 29 | <property name="label" translatable="yes">_File</property> | ||
838 | 30 | <property name="use_underline">True</property> | ||
839 | 31 | <child type="submenu"> | ||
840 | 32 | <object class="GtkMenu" id="menu1"> | ||
841 | 33 | <property name="visible">True</property> | ||
842 | 34 | <property name="can_focus">False</property> | ||
843 | 35 | <child> | ||
844 | 36 | <object class="GtkImageMenuItem" id="menu_file_open"> | ||
845 | 37 | <property name="label">gtk-open</property> | ||
846 | 38 | <property name="visible">True</property> | ||
847 | 39 | <property name="can_focus">False</property> | ||
848 | 40 | <property name="use_underline">True</property> | ||
849 | 41 | <property name="use_stock">True</property> | ||
850 | 42 | <signal name="activate" handler="on_file_open" swapped="no"/> | ||
851 | 43 | </object> | ||
852 | 44 | </child> | ||
853 | 45 | <child> | ||
854 | 46 | <object class="GtkSeparatorMenuItem" id="separatormenuitem1"> | ||
855 | 47 | <property name="visible">True</property> | ||
856 | 48 | <property name="can_focus">False</property> | ||
857 | 49 | </object> | ||
858 | 50 | </child> | ||
859 | 51 | <child> | ||
860 | 52 | <object class="GtkImageMenuItem" id="menu_item_quit"> | ||
861 | 53 | <property name="label">gtk-quit</property> | ||
862 | 54 | <property name="visible">True</property> | ||
863 | 55 | <property name="can_focus">False</property> | ||
864 | 56 | <property name="use_underline">True</property> | ||
865 | 57 | <property name="use_stock">True</property> | ||
866 | 58 | <signal name="activate" handler="on_quit" swapped="no"/> | ||
867 | 59 | </object> | ||
868 | 60 | </child> | ||
869 | 61 | </object> | ||
870 | 62 | </child> | ||
871 | 63 | </object> | ||
872 | 64 | </child> | ||
873 | 65 | <child> | ||
874 | 66 | <object class="GtkMenuItem" id="menuitem_help"> | ||
875 | 67 | <property name="visible">True</property> | ||
876 | 68 | <property name="can_focus">False</property> | ||
877 | 69 | <property name="label" translatable="yes">_Help</property> | ||
878 | 70 | <property name="use_underline">True</property> | ||
879 | 71 | <child type="submenu"> | ||
880 | 72 | <object class="GtkMenu" id="menu2"> | ||
881 | 73 | <property name="visible">True</property> | ||
882 | 74 | <property name="can_focus">False</property> | ||
883 | 75 | <child> | ||
884 | 76 | <object class="GtkImageMenuItem" id="menu_help_about"> | ||
885 | 77 | <property name="label">gtk-about</property> | ||
886 | 78 | <property name="visible">True</property> | ||
887 | 79 | <property name="can_focus">False</property> | ||
888 | 80 | <property name="use_underline">True</property> | ||
889 | 81 | <property name="use_stock">True</property> | ||
890 | 82 | <signal name="activate" handler="on_about" swapped="no"/> | ||
891 | 83 | </object> | ||
892 | 84 | </child> | ||
893 | 85 | </object> | ||
894 | 86 | </child> | ||
895 | 87 | </object> | ||
896 | 88 | </child> | ||
897 | 89 | </object> | ||
898 | 90 | <packing> | ||
899 | 91 | <property name="expand">False</property> | ||
900 | 92 | <property name="fill">True</property> | ||
901 | 93 | <property name="position">0</property> | ||
902 | 94 | </packing> | ||
903 | 95 | </child> | ||
904 | 96 | <child> | ||
905 | 97 | <object class="GtkGrid" id="grid1"> | ||
906 | 98 | <property name="visible">True</property> | ||
907 | 99 | <property name="can_focus">False</property> | ||
908 | 100 | <property name="halign">start</property> | ||
909 | 101 | <property name="margin_left">5</property> | ||
910 | 102 | <property name="margin_right">5</property> | ||
911 | 103 | <property name="margin_top">5</property> | ||
912 | 104 | <property name="margin_bottom">5</property> | ||
913 | 105 | <property name="row_spacing">5</property> | ||
914 | 106 | <property name="column_spacing">5</property> | ||
915 | 107 | <child> | ||
916 | 108 | <object class="GtkLabel" id="label1"> | ||
917 | 109 | <property name="visible">True</property> | ||
918 | 110 | <property name="can_focus">False</property> | ||
919 | 111 | <property name="label" translatable="yes">Name</property> | ||
920 | 112 | </object> | ||
921 | 113 | <packing> | ||
922 | 114 | <property name="left_attach">0</property> | ||
923 | 115 | <property name="top_attach">0</property> | ||
924 | 116 | <property name="width">1</property> | ||
925 | 117 | <property name="height">1</property> | ||
926 | 118 | </packing> | ||
927 | 119 | </child> | ||
928 | 120 | <child> | ||
929 | 121 | <object class="GtkLabel" id="label2"> | ||
930 | 122 | <property name="visible">True</property> | ||
931 | 123 | <property name="can_focus">False</property> | ||
932 | 124 | <property name="label" translatable="yes">Color</property> | ||
933 | 125 | </object> | ||
934 | 126 | <packing> | ||
935 | 127 | <property name="left_attach">0</property> | ||
936 | 128 | <property name="top_attach">1</property> | ||
937 | 129 | <property name="width">1</property> | ||
938 | 130 | <property name="height">1</property> | ||
939 | 131 | </packing> | ||
940 | 132 | </child> | ||
941 | 133 | <child> | ||
942 | 134 | <object class="GtkEntry" id="entry_name"> | ||
943 | 135 | <property name="visible">True</property> | ||
944 | 136 | <property name="can_focus">True</property> | ||
945 | 137 | <property name="has_focus">True</property> | ||
946 | 138 | <property name="invisible_char">•</property> | ||
947 | 139 | </object> | ||
948 | 140 | <packing> | ||
949 | 141 | <property name="left_attach">1</property> | ||
950 | 142 | <property name="top_attach">0</property> | ||
951 | 143 | <property name="width">1</property> | ||
952 | 144 | <property name="height">1</property> | ||
953 | 145 | </packing> | ||
954 | 146 | </child> | ||
955 | 147 | <child> | ||
956 | 148 | <object class="GtkEntry" id="entry_color"> | ||
957 | 149 | <property name="visible">True</property> | ||
958 | 150 | <property name="can_focus">True</property> | ||
959 | 151 | <property name="invisible_char">•</property> | ||
960 | 152 | </object> | ||
961 | 153 | <packing> | ||
962 | 154 | <property name="left_attach">1</property> | ||
963 | 155 | <property name="top_attach">1</property> | ||
964 | 156 | <property name="width">1</property> | ||
965 | 157 | <property name="height">1</property> | ||
966 | 158 | </packing> | ||
967 | 159 | </child> | ||
968 | 160 | </object> | ||
969 | 161 | <packing> | ||
970 | 162 | <property name="expand">True</property> | ||
971 | 163 | <property name="fill">True</property> | ||
972 | 164 | <property name="position">1</property> | ||
973 | 165 | </packing> | ||
974 | 166 | </child> | ||
975 | 167 | <child> | ||
976 | 168 | <object class="GtkLabel" id="label_status"> | ||
977 | 169 | <property name="visible">True</property> | ||
978 | 170 | <property name="can_focus">False</property> | ||
979 | 171 | <property name="xalign">0</property> | ||
980 | 172 | <property name="xpad">5</property> | ||
981 | 173 | <property name="ypad">10</property> | ||
982 | 174 | </object> | ||
983 | 175 | <packing> | ||
984 | 176 | <property name="expand">False</property> | ||
985 | 177 | <property name="fill">True</property> | ||
986 | 178 | <property name="position">2</property> | ||
987 | 179 | </packing> | ||
988 | 180 | </child> | ||
989 | 181 | <child> | ||
990 | 182 | <object class="GtkButtonBox" id="buttonbox2"> | ||
991 | 183 | <property name="visible">True</property> | ||
992 | 184 | <property name="can_focus">False</property> | ||
993 | 185 | <property name="spacing">10</property> | ||
994 | 186 | <property name="homogeneous">True</property> | ||
995 | 187 | <property name="layout_style">end</property> | ||
996 | 188 | <child> | ||
997 | 189 | <object class="GtkButton" id="button_greet"> | ||
998 | 190 | <property name="label" translatable="yes">Greet</property> | ||
999 | 191 | <property name="visible">True</property> | ||
1000 | 192 | <property name="can_focus">True</property> | ||
1001 | 193 | <property name="receives_default">True</property> | ||
1002 | 194 | <signal name="clicked" handler="on_button_greet" swapped="no"/> | ||
1003 | 195 | </object> | ||
1004 | 196 | <packing> | ||
1005 | 197 | <property name="expand">False</property> | ||
1006 | 198 | <property name="fill">True</property> | ||
1007 | 199 | <property name="position">0</property> | ||
1008 | 200 | </packing> | ||
1009 | 201 | </child> | ||
1010 | 202 | <child> | ||
1011 | 203 | <object class="GtkButton" id="button_clear"> | ||
1012 | 204 | <property name="label">gtk-delete</property> | ||
1013 | 205 | <property name="visible">True</property> | ||
1014 | 206 | <property name="can_focus">True</property> | ||
1015 | 207 | <property name="receives_default">True</property> | ||
1016 | 208 | <property name="use_stock">True</property> | ||
1017 | 209 | <signal name="clicked" handler="on_button_clear" swapped="no"/> | ||
1018 | 210 | </object> | ||
1019 | 211 | <packing> | ||
1020 | 212 | <property name="expand">False</property> | ||
1021 | 213 | <property name="fill">True</property> | ||
1022 | 214 | <property name="position">1</property> | ||
1023 | 215 | </packing> | ||
1024 | 216 | </child> | ||
1025 | 217 | <child> | ||
1026 | 218 | <object class="GtkButton" id="button_quit"> | ||
1027 | 219 | <property name="label">gtk-quit</property> | ||
1028 | 220 | <property name="visible">True</property> | ||
1029 | 221 | <property name="can_focus">True</property> | ||
1030 | 222 | <property name="receives_default">True</property> | ||
1031 | 223 | <property name="use_stock">True</property> | ||
1032 | 224 | <property name="image_position">right</property> | ||
1033 | 225 | <signal name="clicked" handler="on_quit" swapped="no"/> | ||
1034 | 226 | </object> | ||
1035 | 227 | <packing> | ||
1036 | 228 | <property name="expand">False</property> | ||
1037 | 229 | <property name="fill">True</property> | ||
1038 | 230 | <property name="position">2</property> | ||
1039 | 231 | </packing> | ||
1040 | 232 | </child> | ||
1041 | 233 | </object> | ||
1042 | 234 | <packing> | ||
1043 | 235 | <property name="expand">False</property> | ||
1044 | 236 | <property name="fill">True</property> | ||
1045 | 237 | <property name="position">3</property> | ||
1046 | 238 | </packing> | ||
1047 | 239 | </child> | ||
1048 | 240 | </object> | ||
1049 | 241 | </child> | ||
1050 | 242 | </object> | ||
1051 | 243 | </interface> | ||
1052 | 0 | 244 | ||
1053 | === removed file 'tests/test-matching.sh' | |||
1054 | --- tests/test-matching.sh 2013-05-22 09:11:04 +0000 | |||
1055 | +++ tests/test-matching.sh 1970-01-01 00:00:00 +0000 | |||
1056 | @@ -1,25 +0,0 @@ | |||
1057 | 1 | #!/bin/bash | ||
1058 | 2 | # | ||
1059 | 3 | |||
1060 | 4 | # gedit --gtk-module `pwd`/lib/libautopilot.so & | ||
1061 | 5 | # GEDIT_PID=$! | ||
1062 | 6 | # sleep 2 | ||
1063 | 7 | |||
1064 | 8 | EMPTY_SET='[Argument: a(sv) {}]' | ||
1065 | 9 | RES=1 | ||
1066 | 10 | |||
1067 | 11 | RET=`qdbus --literal org.gnome.gedit /com/canonical/Autopilot/Introspection com.canonical.Autopilot.Introspection.GetState '/Root//GtkWindow[opacity=1]'` | ||
1068 | 12 | if [ "$RET" == "$EMPTY_SET" ]; then | ||
1069 | 13 | echo "FAIL" | ||
1070 | 14 | RES=0 | ||
1071 | 15 | fi | ||
1072 | 16 | |||
1073 | 17 | RET=`qdbus --literal org.gnome.gedit /com/canonical/Autopilot/Introspection com.canonical.Autopilot.Introspection.GetState '/Root//GtkWindow/GtkMenu/GtkImageMenuItem[name=BookmarkOpen]'` | ||
1074 | 18 | if [ "$RET" == "$EMPTY_SET" ]; then | ||
1075 | 19 | echo "FAIL" | ||
1076 | 20 | RES=0 | ||
1077 | 21 | fi | ||
1078 | 22 | |||
1079 | 23 | |||
1080 | 24 | # kill $GEDIT_PID | ||
1081 | 25 | exit $RES |
This MP depends on (and contains) the bits for running the tests during package build: https:/ /code.launchpad .net/~pitti/ autopilot- gtk/testsuite/ +merge/ 170607 . So please review that one first.