1#!/usr/bin/python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""A Chromedriver smoke-test that installs and launches a web-app.
7
8  Args:
9    driver_dir: Location of Chromedriver binary on local machine.
10    profile_dir: A user-data-dir containing login token for the app-user.
11    app_id: App ID of web-app in Chrome web-store.
12    app_window_title: The title of the window that should come up on app launch.
13
14    TODO(anandc): Reduce the # of parameters required from the command-line.
15    Maybe read from a JSON file. Also, map appID to expected app window title.
16
17  This script navigates to the app-detail page on Chrome Web Store for the
18  specified app-id. From there, it then installs the app and launches it. It
19  then checks if the resulting new window has the expected title.
20"""
21
22import argparse
23import os
24import shutil
25import tempfile
26import time
27
28from selenium import webdriver
29from selenium.webdriver.chrome.options import Options
30
31CWS_URL = 'https://chrome.google.com/webstore/detail'
32WEBSTORE_BUTTON_LABEL = 'webstore-test-button-label'
33FREE_BUTTON_XPATH = (
34    '//div[contains(@class, \"%s\") and text() = \"Free\"]' %
35    (WEBSTORE_BUTTON_LABEL))
36LAUNCH_BUTTON_XPATH = (
37    '//div[contains(@class, \"%s\") and text() = \"Launch app\"]' %
38    (WEBSTORE_BUTTON_LABEL))
39WAIT_TIME = 2
40
41
42def CreateTempProfileDir(source_dir):
43  """Creates a temporary profile directory, for use by the test.
44
45     This avoids modifying the input user-data-dir by actions that the test
46     performs.
47
48  Args:
49    source_dir: The directory to copy and place in a temp folder.
50
51  Returns:
52    tmp_dir: Name of the temporary folder that was created.
53    profile_dir: Name of the profile-dir under the tmp_dir.
54  """
55
56  tmp_dir = tempfile.mkdtemp()
57  print 'Created folder %s' % (tmp_dir)
58  profile_dir = os.path.join(tmp_dir, 'testuser')
59  # Copy over previous created profile for this execution of Chrome Driver.
60  shutil.copytree(source_dir, profile_dir)
61  return tmp_dir, profile_dir
62
63
64def ParseCmdLineArgs():
65  """Parses command line arguments and returns them.
66
67  Returns:
68    args: Parse command line arguments.
69  """
70  parser = argparse.ArgumentParser()
71  parser.add_argument(
72      '-d', '--driver_dir', required=True,
73      help='path to folder where Chromedriver has been installed.')
74  parser.add_argument(
75      '-p', '--profile_dir', required=True,
76      help='path to user-data-dir with trusted-tester signed in.')
77  parser.add_argument(
78      '-a', '--app_id', required=True,
79      help='app-id of web-store app being tested.')
80  parser.add_argument(
81      '-e', '--app_window_title', required=True,
82      help='Title of the app window that we expect to come up.')
83
84  # Use input json file if specified on command line.
85  args = parser.parse_args()
86  return args
87
88
89def GetLinkAndWait(driver, link_to_get):
90  """Navigates to the specified link.
91
92  Args:
93    driver: Active window for this Chromedriver instance.
94    link_to_get: URL of the destination.
95  """
96  driver.get(link_to_get)
97  # TODO(anandc): Is there any event or state we could wait on? For now,
98  # we have hard-coded sleeps.
99  time.sleep(WAIT_TIME)
100
101
102def ClickAndWait(driver, button_xpath):
103  """Clicks button at the specified XPath of the current document.
104
105  Args:
106    driver: Active window for this Chromedriver instance.
107    button_xpath: XPath in this document to button we want to click.
108  """
109  button = driver.find_element_by_xpath(button_xpath)
110  button.click()
111  time.sleep(WAIT_TIME)
112
113
114def WindowWithTitleExists(driver, title):
115  """Verifies if one of the open windows has the specified title.
116
117  Args:
118    driver: Active window for this Chromedriver instance.
119    title: Title of the window we are looking for.
120
121  Returns:
122    True if an open window in this session with the specified title was found.
123    False otherwise.
124  """
125  for handle in driver.window_handles:
126    driver.switch_to_window(handle)
127    if driver.title == title:
128      return True
129  return False
130
131
132def main():
133
134  args = ParseCmdLineArgs()
135
136  org_profile_dir = args.profile_dir
137  print 'Creating temp-dir using profile-dir %s' % org_profile_dir
138  tmp_dir, profile_dir = CreateTempProfileDir(org_profile_dir)
139
140  options = Options()
141  options.add_argument('--user-data-dir=' + profile_dir)
142  # Suppress the confirmation dialog that comes up.
143  # With M39, this flag will no longer work. See https://crbug/357774.
144  # TODO(anandc): Work with a profile-dir that already has extension downloaded,
145  # and also add support for loading extension from a local directory.
146  options.add_argument('--apps-gallery-install-auto-confirm-for-tests=accept')
147  driver = webdriver.Chrome(args.driver_dir, chrome_options=options)
148
149  try:
150
151    chrome_apps_link = 'chrome://apps'
152    cws_app_detail_link = '%s/%s' % (CWS_URL, args.app_id)
153
154    # Navigate to chrome:apps first.
155    # TODO(anandc): Add check to make sure the app we are testing isn't already
156    # added for this user.
157    GetLinkAndWait(driver, chrome_apps_link)
158
159    # Navigate to the app detail page at the Chrome Web Store.
160    GetLinkAndWait(driver, cws_app_detail_link)
161    # Get the page again, to get all controls. This seems to be a bug, either
162    # in ChromeDriver, or the app-page. Without this additional GET, we don't
163    # get all controls. Even sleeping for 5 seconds doesn't suffice.
164    # TODO(anandc): Investigate why the page doesn't work with just 1 call.
165    GetLinkAndWait(driver, cws_app_detail_link)
166
167    # Install the app by clicking the button that says "Free".
168    ClickAndWait(driver, FREE_BUTTON_XPATH)
169
170    # We should now be at a new tab. Get its handle.
171    current_tab = driver.window_handles[-1]
172    # And switch to it.
173    driver.switch_to_window(current_tab)
174
175    # From this new tab, go to Chrome Apps
176    # TODO(anandc): Add check to make sure the app we are testing is now added.
177    GetLinkAndWait(driver, chrome_apps_link)
178
179    # Back to the app detail page.
180    GetLinkAndWait(driver, cws_app_detail_link)
181    # Again, do this twice, for reasons noted above.
182    GetLinkAndWait(driver, cws_app_detail_link)
183
184    # Click to launch the newly installed app.
185    ClickAndWait(driver, LAUNCH_BUTTON_XPATH)
186
187    # For now, make sure the "connecting" dialog comes up.
188    # TODO(anandc): Add more validation; ideally, wait for the separate app
189    # window to appear.
190    if WindowWithTitleExists(driver, args.app_window_title):
191      print 'Web-App %s launched successfully.' % args.app_window_title
192    else:
193      print 'Web-app %s did not launch successfully.' % args.app_window_title
194
195  except Exception, e:
196    raise e
197  finally:
198    # Cleanup.
199    print 'Deleting %s' % tmp_dir
200    shutil.rmtree(profile_dir)
201    os.rmdir(tmp_dir)
202    driver.quit()
203
204
205if __name__ == '__main__':
206  main()
207