Merge lp:~frankban/juju-gui/bug-1117554-gui-tests-in-browser into lp:juju-gui/experimental

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 375
Proposed branch: lp:~frankban/juju-gui/bug-1117554-gui-tests-in-browser
Merge into: lp:juju-gui/experimental
Diff against target: 289 lines (+114/-28)
7 files modified
bin/test-charm (+4/-4)
docs/browser-testing.rst (+1/-1)
lib/deploy_charm_for_testing.py (+24/-4)
test/browser.py (+19/-1)
test/index.html (+5/-2)
test/test_charm_running.py (+30/-7)
test/test_deploy_charm_for_testing.py (+31/-9)
To merge this branch: bzr merge lp:~frankban/juju-gui/bug-1117554-gui-tests-in-browser
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+147356@code.launchpad.net

Description of the change

Added GUI unit tests in the browser test suite.

Juju GUI unit tests are now included in the CI process.
Also added a test exercising the environment connection.

Updated the charm deployment process: "juju set" is no
longer used, and the charm configuration is now provided
by a temporary configuration file.

To run browser tests:

sudo apt-get install python-shelltoolbox python-selenium python-yaml juju
bin/test-charm lp:~frankban/juju-gui/bug-1117554-gui-tests-in-browser

https://codereview.appspot.com/7308068/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+147356_code.launchpad.net,

Message:
Please take a look.

Description:
Added GUI unit tests in the browser test suite.

Juju GUI unit tests are now included in the CI process.
Also added a test exercising the environment connection.

Updated the charm deployment process: "juju set" is no
longer used, and the charm configuration is now provided
by a temporary configuration file.

To run browser tests:

sudo apt-get install python-shelltoolbox python-selenium python-yaml
juju
bin/test-charm lp:~frankban/juju-gui/bug-1117554-gui-tests-in-browser

https://code.launchpad.net/~frankban/juju-gui/bug-1117554-gui-tests-in-browser/+merge/147356

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/7308068/

Affected files:
   A [revision details]
   M bin/test-charm
   M docs/browser-testing.rst
   M lib/deploy_charm_for_testing.py
   M test/browser.py
   M test/index.html
   M test/test_charm_running.py
   M test/test_deploy_charm_for_testing.py

Revision history for this message
Gary Poster (gary) wrote :

Land as is. Really nice. Worked great for me. Thank you.

Gary

https://codereview.appspot.com/7308068/

Revision history for this message
Nicola Larosa (teknico) wrote :

Land as is.

Nice changes. Having difficulties running tests, for some reason. I'll
keep trying.

https://codereview.appspot.com/7308068/diff/1/test/test_charm_running.py
File test/test_charm_running.py (right):

https://codereview.appspot.com/7308068/diff/1/test/test_charm_running.py#newcode41
test/test_charm_running.py:41: msg = '{} failure(s) running {}
tests.'.format(failures, total)
Nice, you can omit the field numbers in 2.7, I didn't know.

https://codereview.appspot.com/7308068/

Revision history for this message
Francesco Banconi (frankban) wrote :

*** Submitted:

Added GUI unit tests in the browser test suite.

Juju GUI unit tests are now included in the CI process.
Also added a test exercising the environment connection.

Updated the charm deployment process: "juju set" is no
longer used, and the charm configuration is now provided
by a temporary configuration file.

To run browser tests:

sudo apt-get install python-shelltoolbox python-selenium python-yaml
juju
bin/test-charm lp:~frankban/juju-gui/bug-1117554-gui-tests-in-browser

R=gary.poster, teknico
CC=
https://codereview.appspot.com/7308068

https://codereview.appspot.com/7308068/

Revision history for this message
Francesco Banconi (frankban) wrote :

Thanks for the reviews Gary and Nicola.

https://codereview.appspot.com/7308068/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/test-charm'
2--- bin/test-charm 2013-02-06 02:22:34 +0000
3+++ bin/test-charm 2013-02-08 12:00:36 +0000
4@@ -1,12 +1,12 @@
5 #!/bin/sh
6 # This script expects that the system has python-shelltoolbox, python-
7-# selenium, and juju installed:
8-# sudo apt-get install python-shelltoolbox python-selenium juju
9+# selenium, python-yaml, and juju installed:
10+# sudo apt-get install python-shelltoolbox python-selenium python-yaml juju
11 # Deploy the charm, using a branch if given.
12 python lib/deploy_charm_for_testing.py "$@"
13 # Figure out the URL of the APP.
14-export APP_URL=http://`juju status -e juju-gui-testing | grep public-address: | cut -d: -f2 | cut -c2-`
15+export APP_URL=https://`juju status -e juju-gui-testing | grep public-address: | cut -d: -f2 | cut -c2-`
16 # Run the browser tests against the app.
17-python test/test_charm_running.py
18+python test/test_charm_running.py -v
19 # Destroy the environment, releasing the resources.
20 yes | juju destroy-environment -e juju-gui-testing
21
22=== modified file 'docs/browser-testing.rst'
23--- docs/browser-testing.rst 2013-02-06 02:22:34 +0000
24+++ docs/browser-testing.rst 2013-02-08 12:00:36 +0000
25@@ -23,7 +23,7 @@
26
27 The test script has a few system dependencies::
28
29- sudo apt-get install python-shelltoolbox python-selenium juju
30+ sudo apt-get install python-shelltoolbox python-selenium python-yaml juju
31
32 It does not require that the Makefile be run.
33
34
35=== modified file 'lib/deploy_charm_for_testing.py'
36--- lib/deploy_charm_for_testing.py 2013-02-06 12:43:42 +0000
37+++ lib/deploy_charm_for_testing.py 2013-02-08 12:00:36 +0000
38@@ -4,6 +4,8 @@
39 import shelltoolbox
40 import sys
41 import time
42+import tempfile
43+import yaml
44
45
46 juju_command = shelltoolbox.command('juju')
47@@ -34,6 +36,21 @@
48 return unit['agent-state']
49
50
51+def make_config_file(options):
52+ """Create a Juju GUI charm config file. Return the config file object.
53+
54+ This function can also be used as a context manager.
55+ """
56+ config = {'juju-gui': options}
57+ config_file = tempfile.NamedTemporaryFile()
58+ config_file.write(yaml.dump(config))
59+ config_file.flush()
60+ # The NamedTemporaryFile instance is returned instead of just the name
61+ # because we want to take advantage of garbage collection-triggered
62+ # deletion of the temp file when it goes out of scope in the caller.
63+ return config_file
64+
65+
66 def wait_for_service(get_state=get_state, sleep=time.sleep):
67 """Wait for the service to start or for it to enter an error state."""
68 while True:
69@@ -45,17 +62,20 @@
70 sleep(10)
71
72
73-def main(argv, print=print, juju=juju, wait_for_service=wait_for_service):
74+def main(argv, print=print, juju=juju, wait_for_service=wait_for_service,
75+ make_config_file=make_config_file):
76 """Deploy the Juju GUI service and wait for it to become available."""
77 branch = get_branch_url(argv)
78 print('Bootstrapping...')
79 juju('bootstrap --environment juju-gui-testing')
80 print('Deploying service...')
81- juju('deploy juju-gui --environment juju-gui-testing')
82+ options = {'serve-tests': True, 'staging': True}
83 if branch is not None:
84 print('Setting branch for charm to deploy...')
85- juju('set juju-gui juju-gui-source={} --environment juju-gui-testing'
86- .format(branch))
87+ options['juju-gui-source'] = branch
88+ with make_config_file(options) as config_file:
89+ juju('deploy --environment juju-gui-testing --config {} '
90+ 'cs:~juju-gui/precise/juju-gui'.format(config_file.name))
91 print('Waiting for service to start...')
92 wait_for_service()
93 print('Exposing the service...')
94
95=== modified file 'test/browser.py'
96--- test/browser.py 2013-02-06 21:05:31 +0000
97+++ test/browser.py 2013-02-08 12:00:36 +0000
98@@ -8,7 +8,9 @@
99 import os
100 import selenium
101 import selenium.webdriver
102+from selenium.webdriver.support import ui
103 import unittest
104+import urlparse
105
106
107 ie = dict(selenium.webdriver.DesiredCapabilities.INTERNETEXPLORER)
108@@ -58,7 +60,7 @@
109
110 @classmethod
111 def setUpClass(cls):
112- global driver # We only want one because they are expensive to set up.
113+ global driver # We only want one because they are expensive to set up.
114 if driver is None:
115 # We sometimes run the tests under different browsers, if none is
116 # specified, use Chrome.
117@@ -76,6 +78,7 @@
118 atexit.register(driver.quit)
119
120 def setUp(self):
121+ self.app_url = os.environ['APP_URL']
122 self.driver = driver
123
124 def run(self, result=None):
125@@ -85,3 +88,18 @@
126 def tearDown(self):
127 successful = self.last_result.wasSuccessful()
128 set_test_result(driver.session_id, successful)
129+
130+ def load(self, path='/'):
131+ """Load a page using the current Selenium driver."""
132+ url = urlparse.urljoin(self.app_url, path)
133+ self.driver.get(url)
134+
135+ def wait_for(self, condition, error=None, timeout=10):
136+ """Wait for condition to be True.
137+
138+ The argument condition is a callable accepting a driver object.
139+ Fail printing the provided error if timeout is exceeded.
140+ Otherwise, return the value returned by the condition call.
141+ """
142+ wait = ui.WebDriverWait(self.driver, timeout)
143+ return wait.until(condition, error)
144
145=== modified file 'test/index.html'
146--- test/index.html 2013-01-24 17:43:57 +0000
147+++ test/index.html 2013-02-08 12:00:36 +0000
148@@ -68,8 +68,11 @@
149 YUI().use(['node', 'event'], function(Y) {
150 // Run the tests.
151 if (window.mochaPhantomJS) {
152- mochaPhantomJS.run(); }
153- else { mocha.run(); }
154+ mochaPhantomJS.run();
155+ } else {
156+ // The global variable testRunner is required by browser tests.
157+ testRunner = mocha.run();
158+ }
159 });
160 </script>
161
162
163=== modified file 'test/test_charm_running.py'
164--- test/test_charm_running.py 2013-02-01 21:02:30 +0000
165+++ test/test_charm_running.py 2013-02-08 12:00:36 +0000
166@@ -1,23 +1,46 @@
167 import browser
168-import os
169 import unittest
170
171
172 class TestBasics(browser.TestCase):
173
174- def setUp(self):
175- super(TestBasics, self).setUp()
176- self.app_url = os.environ['APP_URL']
177-
178 def test_title(self):
179- self.driver.get(self.app_url)
180+ self.load()
181 self.assertTrue('Juju Admin' in self.driver.title)
182
183 def test_environment_name(self):
184- self.driver.get(self.app_url)
185+ self.load()
186 body = self.driver.find_element_by_xpath('//body')
187 self.assertTrue('Environment on ' in body.text)
188
189+ def test_environment_connection(self):
190+ # The GUI connects to the API backend.
191+ self.load()
192+
193+ def connected(driver):
194+ return driver.execute_script('return app.env.get("connected");')
195+ self.wait_for(connected, 'Environment not connected.')
196+
197+ def test_gui_unit_tests(self):
198+ # Ensure Juju GUI unit tests pass.
199+ self.load('/test/')
200+ script = """
201+ var stats = testRunner.stats;
202+ return [testRunner.total, stats.tests, stats.failures];
203+ """
204+
205+ def tests_completed(driver):
206+ total, done, failures = driver.execute_script(script)
207+ # Return when tests completed or a failure occurred.
208+ if (done == total) or failures:
209+ return total, failures
210+
211+ total, failures = self.wait_for(
212+ tests_completed, 'Unable to complete test run.', timeout=30)
213+ if failures:
214+ msg = '{} failure(s) running {} tests.'.format(failures, total)
215+ self.fail(msg)
216+
217
218 if __name__ == '__main__':
219 unittest.main()
220
221=== modified file 'test/test_deploy_charm_for_testing.py'
222--- test/test_deploy_charm_for_testing.py 2013-02-06 12:43:42 +0000
223+++ test/test_deploy_charm_for_testing.py 2013-02-08 12:00:36 +0000
224@@ -87,6 +87,19 @@
225 self.assertEqual(get_state(get_status), 'started')
226
227
228+class MakeConfigFile(object):
229+ """Simulate creating a charm configuration file."""
230+
231+ name = 'my-config-file.yaml'
232+ options = None
233+ # This class is used as a context manager.
234+ __enter__ = lambda self: self
235+ __exit__ = noop
236+
237+ def __init__(self, options):
238+ self.__class__.options = options
239+
240+
241 class TestScript(unittest.TestCase):
242 """The main() function is the entry point when run as a script."""
243
244@@ -101,16 +114,27 @@
245 'Waiting for service to start...',
246 'Exposing the service...'])
247
248+ def test_config_file(self):
249+ # A charm config file is correctly created.
250+ juju_commands = []
251+
252+ def juju(s):
253+ juju_commands.append(s)
254+ main(argv=[], print=noop, juju=juju, wait_for_service=noop,
255+ make_config_file=MakeConfigFile)
256+ options = MakeConfigFile.options
257+ deploy_command = juju_commands[1]
258+ self.assertIn('--config my-config-file.yaml', deploy_command)
259+ self.assertDictEqual({'serve-tests': True, 'staging': True}, options)
260+
261 def test_providing_a_branch(self):
262 # If the user provides a branch name on the command line, it will be
263 # passed to the charm.
264 printed = []
265- juju_commands = []
266
267- def juju(s):
268- juju_commands.append(s)
269- main(argv=['', 'lp:foo'], print=printed.append, juju=juju,
270- wait_for_service=noop)
271+ main(argv=['', 'lp:foo'], print=printed.append, juju=noop,
272+ wait_for_service=noop, make_config_file=MakeConfigFile)
273+ options = MakeConfigFile.options
274 self.assertSequenceEqual(
275 printed,
276 ['Bootstrapping...',
277@@ -118,10 +142,8 @@
278 'Setting branch for charm to deploy...',
279 'Waiting for service to start...',
280 'Exposing the service...'])
281- self.assertIn(
282- ('set juju-gui juju-gui-source=lp:foo --environment '
283- 'juju-gui-testing'),
284- juju_commands)
285+ self.assertIn('juju-gui-source', options)
286+ self.assertEqual('lp:foo', options['juju-gui-source'])
287
288
289 if __name__ == '__main__':

Subscribers

People subscribed via source and target branches