From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- testing/marionette/harness/.flake8 | 3 + testing/marionette/harness/MANIFEST.in | 3 + testing/marionette/harness/README.rst | 30 + .../harness/marionette_harness/__init__.py | 35 + .../marionette_harness/marionette_test/__init__.py | 31 + .../marionette_test/decorators.py | 239 + .../marionette_test/testcases.py | 504 ++ .../harness/marionette_harness/runner/__init__.py | 22 + .../harness/marionette_harness/runner/base.py | 1076 ++++ .../harness/marionette_harness/runner/httpd.py | 142 + .../marionette_harness/runner/mixins/__init__.py | 13 + .../runner/mixins/browsermob-proxy-py/History.md | 57 + .../browsermobproxy/__init__.py | 6 + .../browsermob-proxy-py/browsermobproxy/client.py | 326 ++ .../browsermob-proxy-py/browsermobproxy/server.py | 106 + .../browsermobproxy/webdriver_event_listener.py | 34 + .../mixins/browsermob-proxy-py/docs/Makefile | 153 + .../docs/_build/html/.buildinfo | 4 + .../docs/_build/html/_modules/browsermobproxy.html | 98 + .../docs/_build/html/_modules/index.html | 90 + .../docs/_build/html/_sources/client.txt | 8 + .../docs/_build/html/_sources/index.txt | 72 + .../docs/_build/html/_sources/server.txt | 8 + .../docs/_build/html/_static/basic.css | 537 ++ .../docs/_build/html/_static/default.css | 256 + .../docs/_build/html/_static/doctools.js | 238 + .../docs/_build/html/_static/jquery.js | 2 + .../docs/_build/html/_static/pygments.css | 62 + .../docs/_build/html/_static/searchtools.js | 622 +++ .../docs/_build/html/_static/sidebar.js | 159 + .../docs/_build/html/_static/underscore.js | 31 + .../docs/_build/html/_static/websupport.js | 808 +++ .../docs/_build/html/client.html | 404 ++ .../docs/_build/html/genindex.html | 297 ++ .../docs/_build/html/index.html | 174 + .../docs/_build/html/objects.inv | 6 + .../docs/_build/html/py-modindex.html | 112 + .../docs/_build/html/search.html | 105 + .../docs/_build/html/searchindex.js | 1 + .../docs/_build/html/server.html | 157 + .../mixins/browsermob-proxy-py/docs/client.rst | 8 + .../runner/mixins/browsermob-proxy-py/docs/conf.py | 243 + .../mixins/browsermob-proxy-py/docs/index.rst | 72 + .../mixins/browsermob-proxy-py/docs/make.bat | 190 + .../mixins/browsermob-proxy-py/docs/server.rst | 8 + .../runner/mixins/browsermob-proxy-py/readme.md | 88 + .../runner/mixins/browsermob-proxy-py/setup.py | 20 + .../mixins/browsermob-proxy-py/test/test_client.py | 247 + .../mixins/browsermob-proxy-py/test/test_remote.py | 31 + .../browsermob-proxy-py/test/test_webdriver.py | 60 + .../marionette_harness/runner/mixins/browsermob.py | 80 + .../runner/mixins/window_manager.py | 129 + .../harness/marionette_harness/runner/serve.py | 227 + .../harness/marionette_harness/runner/test.cert | 86 + .../harness/marionette_harness/runner/test.key | 28 + .../harness/marionette_harness/runtests.py | 98 + .../tests/harness_unit/conftest.py | 100 + .../tests/harness_unit/test_httpd.py | 90 + .../harness_unit/test_marionette_arguments.py | 32 + .../tests/harness_unit/test_marionette_harness.py | 108 + .../tests/harness_unit/test_marionette_runner.py | 442 ++ .../harness_unit/test_marionette_test_result.py | 54 + .../tests/harness_unit/test_serve.py | 67 + .../marionette_harness/tests/unit-tests.ini | 11 + .../tests/unit/importanotherscript.js | 1 + .../marionette_harness/tests/unit/importscript.js | 1 + .../tests/unit/mn-restartless-unsigned.xpi | Bin 0 -> 1552 bytes .../tests/unit/single_finger_functions.py | 131 + .../tests/unit/test_about_pages.py | 134 + .../tests/unit/test_accessibility.py | 210 + .../marionette_harness/tests/unit/test_addons.py | 58 + .../tests/unit/test_anonymous_content.py | 90 + .../tests/unit/test_browsermobproxy.py | 34 + .../tests/unit/test_capabilities.py | 253 + .../marionette_harness/tests/unit/test_checkbox.py | 17 + .../tests/unit/test_checkbox_chrome.py | 36 + .../marionette_harness/tests/unit/test_chrome.py | 51 + .../tests/unit/test_chrome_async_finish.js | 6 + .../tests/unit/test_chrome_element_css.py | 23 + .../marionette_harness/tests/unit/test_clearing.py | 72 + .../marionette_harness/tests/unit/test_click.py | 254 + .../tests/unit/test_click_chrome.py | 35 + .../tests/unit/test_click_scrolling.py | 117 + .../marionette_harness/tests/unit/test_cookies.py | 115 + .../marionette_harness/tests/unit/test_crash.py | 155 + .../tests/unit/test_data_driven.py | 67 + .../tests/unit/test_date_time_value.py | 29 + .../tests/unit/test_element_retrieval.py | 483 ++ .../tests/unit/test_element_state.py | 162 + .../tests/unit/test_element_state_chrome.py | 85 + .../tests/unit/test_elementsize.py | 17 + .../tests/unit/test_elementsize_chrome.py | 34 + .../marionette_harness/tests/unit/test_errors.py | 77 + .../tests/unit/test_execute_async_script.py | 156 + .../tests/unit/test_execute_isolate.py | 37 + .../tests/unit/test_execute_sandboxes.py | 79 + .../tests/unit/test_execute_script.py | 402 ++ .../marionette_harness/tests/unit/test_expected.py | 228 + .../tests/unit/test_expectedfail.py | 11 + .../tests/unit/test_file_upload.py | 152 + .../tests/unit/test_findelement_chrome.py | 82 + .../tests/unit/test_geckoinstance.py | 25 + .../tests/unit/test_getactiveframe_oop.py | 93 + .../tests/unit/test_implicit_waits.py | 26 + .../tests/unit/test_import_script.py | 138 + .../tests/unit/test_key_actions.py | 91 + .../tests/unit/test_localization.py | 56 + .../marionette_harness/tests/unit/test_log.py | 64 + .../tests/unit/test_marionette.py | 67 + .../tests/unit/test_modal_dialogs.py | 198 + .../tests/unit/test_mouse_action.py | 114 + .../tests/unit/test_navigation.py | 447 ++ .../tests/unit/test_pagesource.py | 33 + .../tests/unit/test_pagesource_chrome.py | 29 + .../marionette_harness/tests/unit/test_position.py | 19 + .../marionette_harness/tests/unit/test_prefs.py | 167 + .../tests/unit/test_profile_management.py | 34 + .../marionette_harness/tests/unit/test_proxy.py | 252 + .../tests/unit/test_quit_restart.py | 173 + .../tests/unit/test_rendered_element.py | 34 + .../marionette_harness/tests/unit/test_report.py | 29 + .../tests/unit/test_run_js_test.py | 10 + .../tests/unit/test_screen_orientation.py | 86 + .../tests/unit/test_screenshot.py | 428 ++ .../marionette_harness/tests/unit/test_select.py | 164 + .../marionette_harness/tests/unit/test_session.py | 56 + .../tests/unit/test_set_window_size.py | 84 + .../tests/unit/test_shadow_dom.py | 80 + .../tests/unit/test_simpletest_chrome.js | 12 + .../tests/unit/test_simpletest_fail.js | 16 + .../tests/unit/test_simpletest_pass.js | 12 + .../tests/unit/test_simpletest_sanity.py | 107 + .../tests/unit/test_simpletest_timeout.js | 16 + .../tests/unit/test_single_finger_desktop.py | 123 + .../tests/unit/test_skip_setup.py | 35 + .../tests/unit/test_switch_frame.py | 183 + .../tests/unit/test_switch_frame_chrome.py | 56 + .../tests/unit/test_switch_remote_frame.py | 118 + .../tests/unit/test_switch_window_chrome.py | 124 + .../tests/unit/test_switch_window_content.py | 171 + .../tests/unit/test_teardown_context_preserved.py | 21 + .../marionette_harness/tests/unit/test_text.py | 224 + .../tests/unit/test_text_chrome.py | 44 + .../marionette_harness/tests/unit/test_timeouts.py | 115 + .../tests/unit/test_transport.py | 172 + .../marionette_harness/tests/unit/test_typing.py | 332 ++ .../tests/unit/test_using_permissions.py | 46 + .../tests/unit/test_visibility.py | 121 + .../marionette_harness/tests/unit/test_wait.py | 347 ++ .../tests/unit/test_window_close_chrome.py | 80 + .../tests/unit/test_window_close_content.py | 81 + .../tests/unit/test_window_handles_chrome.py | 207 + .../tests/unit/test_window_handles_content.py | 96 + .../tests/unit/test_window_position.py | 42 + .../tests/unit/test_window_title.py | 12 + .../tests/unit/test_window_title_chrome.py | 26 + .../tests/unit/test_window_type.py | 27 + .../tests/unit/test_with_using_context.py | 66 + .../marionette_harness/tests/unit/unit-tests.ini | 132 + .../marionette_harness/tests/webapi-tests.ini | 8 + .../harness/marionette_harness/www/black.png | Bin 0 -> 150 bytes .../harness/marionette_harness/www/bug814037.html | 55 + .../www/click_out_of_bounds_overflow.html | 90 + .../harness/marionette_harness/www/clicks.html | 39 + .../marionette_harness/www/cssTransform.html | 61 + .../marionette_harness/www/cssTransform2.html | 20 + .../marionette_harness/www/datetimePage.html | 15 + .../marionette_harness/www/deletingFrame.html | 29 + .../marionette_harness/www/double_click.html | 18 + .../marionette_harness/www/element_bottom.html | 12 + .../marionette_harness/www/element_left.html | 12 + .../www/element_outside_viewport.html | 41 + .../marionette_harness/www/element_right.html | 12 + .../marionette_harness/www/element_top.html | 12 + .../harness/marionette_harness/www/empty.html | 12 + .../harness/marionette_harness/www/formPage.html | 116 + .../harness/marionette_harness/www/frameset.html | 14 + .../marionette_harness/www/framesetPage2.html | 7 + .../harness/marionette_harness/www/hidden.html | 5 + .../harness/marionette_harness/www/html5/blue.jpg | Bin 0 -> 92 bytes .../www/html5/boolean_attributes.html | 2 + .../marionette_harness/www/html5/geolocation.js | 18 + .../harness/marionette_harness/www/html5/green.jpg | Bin 0 -> 92 bytes .../marionette_harness/www/html5/offline.html | 1 + .../harness/marionette_harness/www/html5/red.jpg | Bin 0 -> 92 bytes .../marionette_harness/www/html5/status.html | 1 + .../marionette_harness/www/html5/test.appcache | 11 + .../www/html5/test_html_inputs.html | 2 + .../marionette_harness/www/html5/yellow.jpg | Bin 0 -> 92 bytes .../harness/marionette_harness/www/html5Page.html | 45 + .../marionette_harness/www/javascriptPage.html | 278 ++ .../harness/marionette_harness/www/macbeth.html | 5254 ++++++++++++++++++++ .../marionette_harness/www/modal_dialogs.html | 39 + .../marionette_harness/www/nestedElements.html | 9 + .../harness/marionette_harness/www/rectangles.html | 40 + .../harness/marionette_harness/www/resultPage.html | 17 + .../harness/marionette_harness/www/scroll.html | 30 + .../harness/marionette_harness/www/scroll2.html | 24 + .../harness/marionette_harness/www/scroll3.html | 18 + .../harness/marionette_harness/www/scroll4.html | 15 + .../harness/marionette_harness/www/scroll5.html | 20 + .../harness/marionette_harness/www/shim.js | 282 ++ .../harness/marionette_harness/www/test.html | 38 + .../harness/marionette_harness/www/testAction.html | 94 + .../marionette_harness/www/testPageSource.html | 14 + .../marionette_harness/www/testPageSource.xml | 5 + .../www/testPageSourceWithUnicodeChars.html | 11 + .../harness/marionette_harness/www/testSize.html | 9 + .../marionette_harness/www/test_accessibility.html | 57 + .../www/test_carets_columns.html | 31 + .../marionette_harness/www/test_carets_cursor.html | 31 + .../www/test_carets_display_none.html | 10 + .../marionette_harness/www/test_carets_iframe.html | 20 + .../www/test_carets_longtext.html | 9 + .../www/test_carets_multipleline.html | 24 + .../www/test_carets_multiplerange.html | 19 + .../www/test_carets_selection.html | 38 + .../marionette_harness/www/test_clearing.html | 24 + .../marionette_harness/www/test_dynamic.html | 38 + .../marionette_harness/www/test_iframe.html | 16 + .../marionette_harness/www/test_inner_iframe.html | 13 + .../marionette_harness/www/test_nested_iframe.html | 13 + .../harness/marionette_harness/www/test_oop_1.html | 15 + .../harness/marionette_harness/www/test_oop_2.html | 15 + .../marionette_harness/www/test_shadow_dom.html | 26 + .../marionette_harness/www/test_windows.html | 14 + .../harness/marionette_harness/www/white.png | Bin 0 -> 150 bytes .../marionette_harness/www/windowHandles.html | 16 + .../harness/marionette_harness/www/xhtmlTest.html | 79 + testing/marionette/harness/requirements.txt | 14 + testing/marionette/harness/setup.py | 59 + 231 files changed, 27041 insertions(+) create mode 100644 testing/marionette/harness/.flake8 create mode 100644 testing/marionette/harness/MANIFEST.in create mode 100644 testing/marionette/harness/README.rst create mode 100644 testing/marionette/harness/marionette_harness/__init__.py create mode 100644 testing/marionette/harness/marionette_harness/marionette_test/__init__.py create mode 100644 testing/marionette/harness/marionette_harness/marionette_test/decorators.py create mode 100644 testing/marionette/harness/marionette_harness/marionette_test/testcases.py create mode 100644 testing/marionette/harness/marionette_harness/runner/__init__.py create mode 100644 testing/marionette/harness/marionette_harness/runner/base.py create mode 100644 testing/marionette/harness/marionette_harness/runner/httpd.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/__init__.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/History.md create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/Makefile create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/client.rst create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/conf.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/index.rst create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/make.bat create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/server.rst create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/readme.md create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/setup.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_client.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_remote.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_webdriver.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/browsermob.py create mode 100644 testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py create mode 100755 testing/marionette/harness/marionette_harness/runner/serve.py create mode 100644 testing/marionette/harness/marionette_harness/runner/test.cert create mode 100644 testing/marionette/harness/marionette_harness/runner/test.key create mode 100644 testing/marionette/harness/marionette_harness/runtests.py create mode 100644 testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py create mode 100644 testing/marionette/harness/marionette_harness/tests/harness_unit/test_httpd.py create mode 100644 testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py create mode 100644 testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_harness.py create mode 100644 testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py create mode 100644 testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_test_result.py create mode 100644 testing/marionette/harness/marionette_harness/tests/harness_unit/test_serve.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit-tests.ini create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/importscript.js create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpi create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/single_finger_functions.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_addons.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_anonymous_content.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_browsermobproxy.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_checkbox.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_checkbox_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_chrome_async_finish.js create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_css.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_clearing.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_click.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_cookies.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_crash.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_elementsize_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_errors.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_execute_async_script.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_execute_isolate.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_execute_sandboxes.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_expected.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_expectedfail.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_geckoinstance.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_getactiveframe_oop.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_implicit_waits.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_localization.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_log.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_marionette.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_pagesource_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_position.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_report.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_run_js_test.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_select.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_session.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_set_window_size.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_shadow_dom.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_chrome.js create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_fail.js create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_pass.js create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_sanity.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_timeout.js create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_single_finger_desktop.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_skip_setup.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_switch_remote_frame.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_teardown_context_preserved.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_text.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_text_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_transport.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_typing.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_using_permissions.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_wait.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_window_title.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/test_with_using_context.py create mode 100644 testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini create mode 100644 testing/marionette/harness/marionette_harness/tests/webapi-tests.ini create mode 100644 testing/marionette/harness/marionette_harness/www/black.png create mode 100644 testing/marionette/harness/marionette_harness/www/bug814037.html create mode 100644 testing/marionette/harness/marionette_harness/www/click_out_of_bounds_overflow.html create mode 100644 testing/marionette/harness/marionette_harness/www/clicks.html create mode 100644 testing/marionette/harness/marionette_harness/www/cssTransform.html create mode 100644 testing/marionette/harness/marionette_harness/www/cssTransform2.html create mode 100644 testing/marionette/harness/marionette_harness/www/datetimePage.html create mode 100644 testing/marionette/harness/marionette_harness/www/deletingFrame.html create mode 100644 testing/marionette/harness/marionette_harness/www/double_click.html create mode 100644 testing/marionette/harness/marionette_harness/www/element_bottom.html create mode 100644 testing/marionette/harness/marionette_harness/www/element_left.html create mode 100644 testing/marionette/harness/marionette_harness/www/element_outside_viewport.html create mode 100644 testing/marionette/harness/marionette_harness/www/element_right.html create mode 100644 testing/marionette/harness/marionette_harness/www/element_top.html create mode 100644 testing/marionette/harness/marionette_harness/www/empty.html create mode 100644 testing/marionette/harness/marionette_harness/www/formPage.html create mode 100644 testing/marionette/harness/marionette_harness/www/frameset.html create mode 100644 testing/marionette/harness/marionette_harness/www/framesetPage2.html create mode 100644 testing/marionette/harness/marionette_harness/www/hidden.html create mode 100644 testing/marionette/harness/marionette_harness/www/html5/blue.jpg create mode 100644 testing/marionette/harness/marionette_harness/www/html5/boolean_attributes.html create mode 100644 testing/marionette/harness/marionette_harness/www/html5/geolocation.js create mode 100644 testing/marionette/harness/marionette_harness/www/html5/green.jpg create mode 100644 testing/marionette/harness/marionette_harness/www/html5/offline.html create mode 100644 testing/marionette/harness/marionette_harness/www/html5/red.jpg create mode 100644 testing/marionette/harness/marionette_harness/www/html5/status.html create mode 100644 testing/marionette/harness/marionette_harness/www/html5/test.appcache create mode 100644 testing/marionette/harness/marionette_harness/www/html5/test_html_inputs.html create mode 100644 testing/marionette/harness/marionette_harness/www/html5/yellow.jpg create mode 100644 testing/marionette/harness/marionette_harness/www/html5Page.html create mode 100644 testing/marionette/harness/marionette_harness/www/javascriptPage.html create mode 100644 testing/marionette/harness/marionette_harness/www/macbeth.html create mode 100644 testing/marionette/harness/marionette_harness/www/modal_dialogs.html create mode 100644 testing/marionette/harness/marionette_harness/www/nestedElements.html create mode 100644 testing/marionette/harness/marionette_harness/www/rectangles.html create mode 100644 testing/marionette/harness/marionette_harness/www/resultPage.html create mode 100644 testing/marionette/harness/marionette_harness/www/scroll.html create mode 100644 testing/marionette/harness/marionette_harness/www/scroll2.html create mode 100644 testing/marionette/harness/marionette_harness/www/scroll3.html create mode 100644 testing/marionette/harness/marionette_harness/www/scroll4.html create mode 100644 testing/marionette/harness/marionette_harness/www/scroll5.html create mode 100644 testing/marionette/harness/marionette_harness/www/shim.js create mode 100644 testing/marionette/harness/marionette_harness/www/test.html create mode 100644 testing/marionette/harness/marionette_harness/www/testAction.html create mode 100644 testing/marionette/harness/marionette_harness/www/testPageSource.html create mode 100644 testing/marionette/harness/marionette_harness/www/testPageSource.xml create mode 100644 testing/marionette/harness/marionette_harness/www/testPageSourceWithUnicodeChars.html create mode 100644 testing/marionette/harness/marionette_harness/www/testSize.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_accessibility.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_carets_columns.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_carets_cursor.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_carets_display_none.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_carets_iframe.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_carets_longtext.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_carets_multipleline.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_carets_multiplerange.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_carets_selection.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_clearing.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_dynamic.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_iframe.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_inner_iframe.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_nested_iframe.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_oop_1.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_oop_2.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_shadow_dom.html create mode 100644 testing/marionette/harness/marionette_harness/www/test_windows.html create mode 100644 testing/marionette/harness/marionette_harness/www/white.png create mode 100644 testing/marionette/harness/marionette_harness/www/windowHandles.html create mode 100644 testing/marionette/harness/marionette_harness/www/xhtmlTest.html create mode 100644 testing/marionette/harness/requirements.txt create mode 100644 testing/marionette/harness/setup.py (limited to 'testing/marionette/harness') diff --git a/testing/marionette/harness/.flake8 b/testing/marionette/harness/.flake8 new file mode 100644 index 000000000..23c7990dc --- /dev/null +++ b/testing/marionette/harness/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 99 +exclude = __init__.py,disti/*,build/*,marionette_harness/runner/mixins/*, marionette_harness/tests/* diff --git a/testing/marionette/harness/MANIFEST.in b/testing/marionette/harness/MANIFEST.in new file mode 100644 index 000000000..1e39ed0e4 --- /dev/null +++ b/testing/marionette/harness/MANIFEST.in @@ -0,0 +1,3 @@ +exclude MANIFEST.in +include requirements.txt +recursive-include marionette_harness/www * diff --git a/testing/marionette/harness/README.rst b/testing/marionette/harness/README.rst new file mode 100644 index 000000000..3f8865603 --- /dev/null +++ b/testing/marionette/harness/README.rst @@ -0,0 +1,30 @@ +marionette-harness +================== + +Marionette is an automation driver for Mozilla's Gecko engine. It can remotely +control either the UI or the internal JavaScript of a Gecko platform, such as +Firefox. It can control both the chrome (i.e. menus and functions) or the +content (the webpage loaded inside the browsing context), giving a high level +of control and ability to replicate user actions. In addition to performing +actions on the browser, Marionette can also read the properties and attributes +of the DOM. + +The marionette_harness package contains the test runner for Marionette, and +allows you to run automated tests written in Python for Gecko based +applications. Therefore it offers the necessary testcase classes, which are +based on the unittest framework. + +For more information and the repository please checkout: + +- home and docs: https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette + + +Example +------- + +The following command will run the tests as specified via a manifest file, or +test path, or test folder in Firefox: + + marionette --binary %path_to_firefox% [manifest_file | test_file | test_folder] + +To get an overview about all possible option run `marionette --help`. diff --git a/testing/marionette/harness/marionette_harness/__init__.py b/testing/marionette/harness/marionette_harness/__init__.py new file mode 100644 index 000000000..9ae4e1b29 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/__init__.py @@ -0,0 +1,35 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +__version__ = '4.0.0' + +from .marionette_test import ( + CommonTestCase, + expectedFailure, + MarionetteTestCase, + parameterized, + run_if_e10s, + run_if_manage_instance, + skip, + skip_if_chrome, + skip_if_desktop, + skip_if_e10s, + skip_if_mobile, + SkipTest, + skip_unless_protocol, +) +from .runner import ( + BaseMarionetteArguments, + BaseMarionetteTestRunner, + BrowserMobProxyArguments, + BrowserMobProxyTestCaseMixin, + Marionette, + MarionetteTest, + MarionetteTestResult, + MarionetteTextTestRunner, + TestManifest, + TestResult, + TestResultCollection, + WindowManagerMixin, +) diff --git a/testing/marionette/harness/marionette_harness/marionette_test/__init__.py b/testing/marionette/harness/marionette_harness/marionette_test/__init__.py new file mode 100644 index 000000000..efcf1d38e --- /dev/null +++ b/testing/marionette/harness/marionette_harness/marionette_test/__init__.py @@ -0,0 +1,31 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +__version__ = '3.1.0' + + +from unittest.case import ( + expectedFailure, + skip, + SkipTest, +) + +from .decorators import ( + parameterized, + run_if_e10s, + run_if_manage_instance, + skip_if_chrome, + skip_if_desktop, + skip_if_e10s, + skip_if_mobile, + skip_unless_browser_pref, + skip_unless_protocol, + with_parameters, +) + +from .testcases import ( + CommonTestCase, + MarionetteTestCase, + MetaParameterized, +) diff --git a/testing/marionette/harness/marionette_harness/marionette_test/decorators.py b/testing/marionette/harness/marionette_harness/marionette_test/decorators.py new file mode 100644 index 000000000..63f947ea2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/marionette_test/decorators.py @@ -0,0 +1,239 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import functools +import types + +from unittest.case import ( + SkipTest, +) + + +def parameterized(func_suffix, *args, **kwargs): + r"""Decorator which generates methods given a base method and some data. + + **func_suffix** is used as a suffix for the new created method and must be + unique given a base method. if **func_suffix** countains characters that + are not allowed in normal python function name, these characters will be + replaced with "_". + + This decorator can be used more than once on a single base method. The class + must have a metaclass of :class:`MetaParameterized`. + + Example:: + + # This example will generate two methods: + # + # - MyTestCase.test_it_1 + # - MyTestCase.test_it_2 + # + class MyTestCase(MarionetteTestCase): + @parameterized("1", 5, named='name') + @parameterized("2", 6, named='name2') + def test_it(self, value, named=None): + print value, named + + :param func_suffix: will be used as a suffix for the new method + :param \*args: arguments to pass to the new method + :param \*\*kwargs: named arguments to pass to the new method + """ + def wrapped(func): + if not hasattr(func, 'metaparameters'): + func.metaparameters = [] + func.metaparameters.append((func_suffix, args, kwargs)) + return func + return wrapped + + +def run_if_e10s(reason): + """Decorator which runs a test if e10s mode is active.""" + def decorator(test_item): + if not isinstance(test_item, types.FunctionType): + raise Exception('Decorator only supported for functions') + + @functools.wraps(test_item) + def skip_wrapper(self, *args, **kwargs): + with self.marionette.using_context('chrome'): + multi_process_browser = not self.marionette.execute_script(""" + try { + return Services.appinfo.browserTabsRemoteAutostart; + } catch (e) { + return false; + } + """) + if multi_process_browser: + raise SkipTest(reason) + return test_item(self, *args, **kwargs) + return skip_wrapper + return decorator + + +def run_if_manage_instance(reason): + """Decorator which runs a test if Marionette manages the application instance.""" + def decorator(test_item): + if not isinstance(test_item, types.FunctionType): + raise Exception('Decorator only supported for functions') + + @functools.wraps(test_item) + def skip_wrapper(self, *args, **kwargs): + if self.marionette.instance is None: + raise SkipTest(reason) + return test_item(self, *args, **kwargs) + return skip_wrapper + return decorator + + +def skip_if_chrome(reason): + """Decorator which skips a test if chrome context is active.""" + def decorator(test_item): + if not isinstance(test_item, types.FunctionType): + raise Exception('Decorator only supported for functions') + + @functools.wraps(test_item) + def skip_wrapper(self, *args, **kwargs): + if self.marionette._send_message('getContext', key='value') == 'chrome': + raise SkipTest(reason) + return test_item(self, *args, **kwargs) + return skip_wrapper + return decorator + + +def skip_if_desktop(reason): + """Decorator which skips a test if run on desktop.""" + def decorator(test_item): + if not isinstance(test_item, types.FunctionType): + raise Exception('Decorator only supported for functions') + + @functools.wraps(test_item) + def skip_wrapper(self, *args, **kwargs): + if self.marionette.session_capabilities.get('browserName') == 'firefox': + raise SkipTest(reason) + return test_item(self, *args, **kwargs) + return skip_wrapper + return decorator + + +def skip_if_e10s(reason): + """Decorator which skips a test if e10s mode is active.""" + def decorator(test_item): + if not isinstance(test_item, types.FunctionType): + raise Exception('Decorator only supported for functions') + + @functools.wraps(test_item) + def skip_wrapper(self, *args, **kwargs): + with self.marionette.using_context('chrome'): + multi_process_browser = self.marionette.execute_script(""" + try { + return Services.appinfo.browserTabsRemoteAutostart; + } catch (e) { + return false; + } + """) + if multi_process_browser: + raise SkipTest(reason) + return test_item(self, *args, **kwargs) + return skip_wrapper + return decorator + + +def skip_if_mobile(reason): + """Decorator which skips a test if run on mobile.""" + def decorator(test_item): + if not isinstance(test_item, types.FunctionType): + raise Exception('Decorator only supported for functions') + + @functools.wraps(test_item) + def skip_wrapper(self, *args, **kwargs): + if self.marionette.session_capabilities.get('browserName') == 'fennec': + raise SkipTest(reason) + return test_item(self, *args, **kwargs) + return skip_wrapper + return decorator + + +def skip_unless_browser_pref(reason, pref, predicate=bool): + """Decorator which skips a test based on the value of a browser preference. + + :param reason: Message describing why the test need to be skipped. + :param pref: the preference name + :param predicate: a function that should return false to skip the test. + The function takes one parameter, the preference value. + Defaults to the python built-in bool function. + + Note that the preference must exist, else a failure is raised. + + Example: :: + + class TestSomething(MarionetteTestCase): + @skip_unless_browser_pref("Sessionstore needs to be enabled for crashes", + "browser.sessionstore.resume_from_crash", + lambda value: value is True, + ) + def test_foo(self): + pass # test implementation here + + """ + def decorator(test_item): + if not isinstance(test_item, types.FunctionType): + raise Exception('Decorator only supported for functions') + if not callable(predicate): + raise ValueError('predicate must be callable') + + @functools.wraps(test_item) + def skip_wrapper(self, *args, **kwargs): + value = self.marionette.get_pref(pref) + if value is None: + self.fail("No such browser preference: {0!r}".format(pref)) + if not predicate(value): + raise SkipTest(reason) + return test_item(self, *args, **kwargs) + return skip_wrapper + return decorator + + +def skip_unless_protocol(reason, predicate): + """Decorator which skips a test if the predicate does not match the current protocol level.""" + def decorator(test_item): + if not isinstance(test_item, types.FunctionType): + raise Exception('Decorator only supported for functions') + if not callable(predicate): + raise ValueError('predicate must be callable') + + @functools.wraps(test_item) + def skip_wrapper(self, *args, **kwargs): + level = self.marionette.client.protocol + if not predicate(level): + raise SkipTest(reason) + return test_item(self, *args, **kwargs) + return skip_wrapper + return decorator + + +def with_parameters(parameters): + """Decorator which generates methods given a base method and some data. + + Acts like :func:`parameterized`, but define all methods in one call. + + Example:: + + # This example will generate two methods: + # + # - MyTestCase.test_it_1 + # - MyTestCase.test_it_2 + # + + DATA = [("1", [5], {'named':'name'}), ("2", [6], {'named':'name2'})] + + class MyTestCase(MarionetteTestCase): + @with_parameters(DATA) + def test_it(self, value, named=None): + print value, named + + :param parameters: list of tuples (**func_suffix**, **args**, **kwargs**) + defining parameters like in :func:`todo`. + """ + def wrapped(func): + func.metaparameters = parameters + return func + return wrapped diff --git a/testing/marionette/harness/marionette_harness/marionette_test/testcases.py b/testing/marionette/harness/marionette_harness/marionette_test/testcases.py new file mode 100644 index 000000000..5051e3351 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/marionette_test/testcases.py @@ -0,0 +1,504 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import imp +import os +import re +import sys +import time +import types +import unittest +import warnings +import weakref + +from unittest.case import ( + _ExpectedFailure, + _UnexpectedSuccess, + SkipTest, +) + +from marionette_driver.errors import ( + MarionetteException, + ScriptTimeoutException, + TimeoutException, +) +from mozlog import get_default_logger + + +def _wraps_parameterized(func, func_suffix, args, kwargs): + """Internal: Decorator used in class MetaParameterized.""" + def wrapper(self): + return func(self, *args, **kwargs) + wrapper.__name__ = func.__name__ + '_' + str(func_suffix) + wrapper.__doc__ = '[{0}] {1}'.format(func_suffix, func.__doc__) + return wrapper + + +class MetaParameterized(type): + """ + A metaclass that allow a class to use decorators. + + It can be used like :func:`parameterized` + or :func:`with_parameters` to generate new methods. + """ + + RE_ESCAPE_BAD_CHARS = re.compile(r'[\.\(\) -/]') + + def __new__(cls, name, bases, attrs): + for k, v in attrs.items(): + if callable(v) and hasattr(v, 'metaparameters'): + for func_suffix, args, kwargs in v.metaparameters: + func_suffix = cls.RE_ESCAPE_BAD_CHARS.sub('_', func_suffix) + wrapper = _wraps_parameterized(v, func_suffix, args, kwargs) + if wrapper.__name__ in attrs: + raise KeyError("{0} is already a defined method on {1}" + .format(wrapper.__name__, name)) + attrs[wrapper.__name__] = wrapper + del attrs[k] + + return type.__new__(cls, name, bases, attrs) + + +class JSTest: + head_js_re = re.compile(r"MARIONETTE_HEAD_JS(\s*)=(\s*)['|\"](.*?)['|\"];") + context_re = re.compile(r"MARIONETTE_CONTEXT(\s*)=(\s*)['|\"](.*?)['|\"];") + timeout_re = re.compile(r"MARIONETTE_TIMEOUT(\s*)=(\s*)(\d+);") + inactivity_timeout_re = re.compile(r"MARIONETTE_INACTIVITY_TIMEOUT(\s*)=(\s*)(\d+);") + + +class CommonTestCase(unittest.TestCase): + + __metaclass__ = MetaParameterized + match_re = None + failureException = AssertionError + pydebugger = None + + def __init__(self, methodName, marionette_weakref, fixtures, **kwargs): + super(CommonTestCase, self).__init__(methodName) + self.methodName = methodName + + self._marionette_weakref = marionette_weakref + self.fixtures = fixtures + + self.loglines = [] + self.duration = 0 + self.start_time = 0 + self.expected = kwargs.pop('expected', 'pass') + self.logger = get_default_logger() + + def _enter_pm(self): + if self.pydebugger: + self.pydebugger.post_mortem(sys.exc_info()[2]) + + def _addSkip(self, result, reason): + addSkip = getattr(result, 'addSkip', None) + if addSkip is not None: + addSkip(self, reason) + else: + warnings.warn("TestResult has no addSkip method, skips not reported", + RuntimeWarning, 2) + result.addSuccess(self) + + def run(self, result=None): + # Bug 967566 suggests refactoring run, which would hopefully + # mean getting rid of this inner function, which only sits + # here to reduce code duplication: + def expected_failure(result, exc_info): + addExpectedFailure = getattr(result, "addExpectedFailure", None) + if addExpectedFailure is not None: + addExpectedFailure(self, exc_info) + else: + warnings.warn("TestResult has no addExpectedFailure method, " + "reporting as passes", RuntimeWarning) + result.addSuccess(self) + + self.start_time = time.time() + orig_result = result + if result is None: + result = self.defaultTestResult() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + + result.startTest(self) + + testMethod = getattr(self, self._testMethodName) + if (getattr(self.__class__, "__unittest_skip__", False) or + getattr(testMethod, "__unittest_skip__", False)): + # If the class or method was skipped. + try: + skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') or + getattr(testMethod, '__unittest_skip_why__', '')) + self._addSkip(result, skip_why) + finally: + result.stopTest(self) + self.stop_time = time.time() + return + try: + success = False + try: + if self.expected == "fail": + try: + self.setUp() + except Exception: + raise _ExpectedFailure(sys.exc_info()) + else: + self.setUp() + except SkipTest as e: + self._addSkip(result, str(e)) + except KeyboardInterrupt: + raise + except _ExpectedFailure as e: + expected_failure(result, e.exc_info) + except: + self._enter_pm() + result.addError(self, sys.exc_info()) + else: + try: + if self.expected == 'fail': + try: + testMethod() + except: + raise _ExpectedFailure(sys.exc_info()) + raise _UnexpectedSuccess + else: + testMethod() + except self.failureException: + self._enter_pm() + result.addFailure(self, sys.exc_info()) + except KeyboardInterrupt: + raise + except _ExpectedFailure as e: + expected_failure(result, e.exc_info) + except _UnexpectedSuccess: + addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None) + if addUnexpectedSuccess is not None: + addUnexpectedSuccess(self) + else: + warnings.warn("TestResult has no addUnexpectedSuccess method, " + "reporting as failures", + RuntimeWarning) + result.addFailure(self, sys.exc_info()) + except SkipTest as e: + self._addSkip(result, str(e)) + except: + self._enter_pm() + result.addError(self, sys.exc_info()) + else: + success = True + try: + if self.expected == "fail": + try: + self.tearDown() + except: + raise _ExpectedFailure(sys.exc_info()) + else: + self.tearDown() + except KeyboardInterrupt: + raise + except _ExpectedFailure as e: + expected_failure(result, e.exc_info) + except: + self._enter_pm() + result.addError(self, sys.exc_info()) + success = False + # Here we could handle doCleanups() instead of calling cleanTest directly + self.cleanTest() + + if success: + result.addSuccess(self) + + finally: + result.stopTest(self) + if orig_result is None: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + + @classmethod + def match(cls, filename): + """Determine if the specified filename should be handled by this test class. + + This is done by looking for a match for the filename using cls.match_re. + """ + if not cls.match_re: + return False + m = cls.match_re.match(filename) + return m is not None + + @classmethod + def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette, + fixtures, testvars, **kwargs): + """Add all the tests in the specified file to the specified suite.""" + raise NotImplementedError + + @property + def test_name(self): + if hasattr(self, 'jsFile'): + return os.path.basename(self.jsFile) + else: + return '{0}.py {1}.{2}'.format(self.__class__.__module__, + self.__class__.__name__, + self._testMethodName) + + def id(self): + # TBPL starring requires that the "test name" field of a failure message + # not differ over time. The test name to be used is passed to + # mozlog via the test id, so this is overriden to maintain + # consistency. + return self.test_name + + def setUp(self): + # Convert the marionette weakref to an object, just for the + # duration of the test; this is deleted in tearDown() to prevent + # a persistent circular reference which in turn would prevent + # proper garbage collection. + self.start_time = time.time() + self.marionette = self._marionette_weakref() + if self.marionette.session is None: + self.marionette.start_session() + self.marionette.timeout.reset() + + super(CommonTestCase, self).setUp() + + def cleanTest(self): + self._deleteSession() + + def _deleteSession(self): + if hasattr(self, 'start_time'): + self.duration = time.time() - self.start_time + if hasattr(self.marionette, 'session'): + if self.marionette.session is not None: + try: + self.loglines.extend(self.marionette.get_logs()) + except Exception, inst: + self.loglines = [['Error getting log: {}'.format(inst)]] + try: + self.marionette.delete_session() + except IOError: + # Gecko has crashed? + pass + self.marionette = None + + def setup_SpecialPowers_observer(self): + self.marionette.set_context("chrome") + self.marionette.execute_script(""" +let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer"; +Components.utils.import("resource://gre/modules/Preferences.jsm"); +Preferences.set(SECURITY_PREF, true); + +if (!testUtils.hasOwnProperty("specialPowersObserver")) { + let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Components.interfaces.mozIJSSubScriptLoader); + loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm", + testUtils); + testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver(); + testUtils.specialPowersObserver.init(); +} +""") + + def run_js_test(self, filename, marionette=None): + """Run a JavaScript test file. + + It collects its set of assertions into the current test's results. + + :param filename: The path to the JavaScript test file to execute. + May be relative to the current script. + :param marionette: The Marionette object in which to execute the test. + Defaults to self.marionette. + """ + marionette = marionette or self.marionette + if not os.path.isabs(filename): + # Find the caller's filename and make the path relative to that. + caller_file = sys._getframe(1).f_globals.get('__file__', '') + caller_file = os.path.abspath(caller_file) + filename = os.path.join(os.path.dirname(caller_file), filename) + self.assert_(os.path.exists(filename), + 'Script "{}" must exist' .format(filename)) + original_test_name = self.marionette.test_name + self.marionette.test_name = os.path.basename(filename) + f = open(filename, 'r') + js = f.read() + args = [] + + head_js = JSTest.head_js_re.search(js) + if head_js: + head_js = head_js.group(3) + head = open(os.path.join(os.path.dirname(filename), head_js), 'r') + js = head.read() + js + + context = JSTest.context_re.search(js) + if context: + context = context.group(3) + else: + context = 'content' + + if 'SpecialPowers' in js: + self.setup_SpecialPowers_observer() + + if context == 'content': + js = "var SpecialPowers = window.wrappedJSObject.SpecialPowers;\n" + js + else: + marionette.execute_script(""" + if (typeof(SpecialPowers) == 'undefined') { + let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Components.interfaces.mozIJSSubScriptLoader); + loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js"); + loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js"); + loader.loadSubScript("chrome://specialpowers/content/ChromePowers.js"); + } + """) + + marionette.set_context(context) + + if context != 'chrome': + marionette.navigate('data:text/html,test page') + + timeout = JSTest.timeout_re.search(js) + if timeout: + ms = timeout.group(3) + marionette.timeout.script = int(ms) / 1000.0 + + inactivity_timeout = JSTest.inactivity_timeout_re.search(js) + if inactivity_timeout: + inactivity_timeout = int(inactivity_timeout.group(3)) + + try: + results = marionette.execute_js_script( + js, + args, + inactivity_timeout=inactivity_timeout, + filename=os.path.basename(filename) + ) + + self.assertTrue('timeout' not in filename, + 'expected timeout not triggered') + + if 'fail' in filename: + self.assertTrue(len(results['failures']) > 0, + "expected test failures didn't occur") + else: + for failure in results['failures']: + diag = "" if failure.get('diag') is None else failure['diag'] + name = ("got false, expected true" if failure.get('name') is None else + failure['name']) + self.logger.test_status(self.test_name, name, 'FAIL', + message=diag) + for failure in results['expectedFailures']: + diag = "" if failure.get('diag') is None else failure['diag'] + name = ("got false, expected false" if failure.get('name') is None else + failure['name']) + self.logger.test_status(self.test_name, name, 'FAIL', + expected='FAIL', message=diag) + for failure in results['unexpectedSuccesses']: + diag = "" if failure.get('diag') is None else failure['diag'] + name = ("got true, expected false" if failure.get('name') is None else + failure['name']) + self.logger.test_status(self.test_name, name, 'PASS', + expected='FAIL', message=diag) + self.assertEqual(0, len(results['failures']), + '{} tests failed' .format(len(results['failures']))) + if len(results['unexpectedSuccesses']) > 0: + raise _UnexpectedSuccess('') + if len(results['expectedFailures']) > 0: + raise _ExpectedFailure((AssertionError, AssertionError(''), None)) + + self.assertTrue(results['passed'] + + len(results['failures']) + + len(results['expectedFailures']) + + len(results['unexpectedSuccesses']) > 0, + 'no tests run') + + except ScriptTimeoutException: + if 'timeout' in filename: + # expected exception + pass + else: + self.loglines = marionette.get_logs() + raise + self.marionette.test_name = original_test_name + + +class MarionetteTestCase(CommonTestCase): + + match_re = re.compile(r"test_(.*)\.py$") + + def __init__(self, marionette_weakref, fixtures, methodName='runTest', + filepath='', **kwargs): + self.filepath = filepath + self.testvars = kwargs.pop('testvars', None) + + super(MarionetteTestCase, self).__init__( + methodName, marionette_weakref=marionette_weakref, fixtures=fixtures, **kwargs) + + @classmethod + def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette, + fixtures, testvars, **kwargs): + # since we use imp.load_source to load test modules, if a module + # is loaded with the same name as another one the module would just be + # reloaded. + # + # We may end up by finding too many test in a module then since + # reload() only update the module dict (so old keys are still there!) + # see https://docs.python.org/2/library/functions.html#reload + # + # we get rid of that by removing the module from sys.modules, + # so we ensure that it will be fully loaded by the + # imp.load_source call. + if mod_name in sys.modules: + del sys.modules[mod_name] + + test_mod = imp.load_source(mod_name, filepath) + + for name in dir(test_mod): + obj = getattr(test_mod, name) + if (isinstance(obj, (type, types.ClassType)) and + issubclass(obj, unittest.TestCase)): + testnames = testloader.getTestCaseNames(obj) + for testname in testnames: + suite.addTest(obj(weakref.ref(marionette), + fixtures, + methodName=testname, + filepath=filepath, + testvars=testvars, + **kwargs)) + + def setUp(self): + super(MarionetteTestCase, self).setUp() + self.marionette.test_name = self.test_name + self.marionette.execute_script("log('TEST-START: {0}:{1}')" + .format(self.filepath.replace('\\', '\\\\'), + self.methodName), + sandbox="simpletest") + + def tearDown(self): + # In the case no session is active (eg. the application was quit), start + # a new session for clean-up steps. + if not self.marionette.session: + self.marionette.start_session() + + if not self.marionette.crashed: + try: + self.marionette.clear_imported_scripts() + self.marionette.execute_script("log('TEST-END: {0}:{1}')" + .format(self.filepath.replace('\\', '\\\\'), + self.methodName), + sandbox="simpletest") + self.marionette.test_name = None + except (MarionetteException, IOError): + # We have tried to log the test end when there is no listener + # object that we can access + pass + + super(MarionetteTestCase, self).tearDown() + + def wait_for_condition(self, method, timeout=30): + timeout = float(timeout) + time.time() + while time.time() < timeout: + value = method(self.marionette) + if value: + return value + time.sleep(0.5) + else: + raise TimeoutException("wait_for_condition timed out") diff --git a/testing/marionette/harness/marionette_harness/runner/__init__.py b/testing/marionette/harness/marionette_harness/runner/__init__.py new file mode 100644 index 000000000..e8a0575bb --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/__init__.py @@ -0,0 +1,22 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from .base import ( + BaseMarionetteArguments, + BaseMarionetteTestRunner, + Marionette, + MarionetteTest, + MarionetteTestResult, + MarionetteTextTestRunner, + TestManifest, + TestResult, + TestResultCollection, +) + +from .mixins import ( + BrowserMobProxyTestCaseMixin, + BrowserMobProxyArguments, + BrowserMobTestCase, + WindowManagerMixin, +) diff --git a/testing/marionette/harness/marionette_harness/runner/base.py b/testing/marionette/harness/marionette_harness/runner/base.py new file mode 100644 index 000000000..72f23524b --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/base.py @@ -0,0 +1,1076 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import os +import random +import re +import socket +import sys +import time +import traceback +import unittest + +from argparse import ArgumentParser +from copy import deepcopy + +import mozinfo +import moznetwork +import mozprofile +import mozversion +import serve + +from manifestparser import TestManifest +from manifestparser.filters import tags +from marionette_driver.marionette import Marionette +from moztest.adapters.unit import StructuredTestResult, StructuredTestRunner +from moztest.results import TestResult, TestResultCollection, relevant_line +from serve import iter_proc, iter_url + +here = os.path.abspath(os.path.dirname(__file__)) + + +def update_mozinfo(path=None): + """Walk up directories to find mozinfo.json and update the info.""" + path = path or here + dirs = set() + while path != os.path.expanduser('~'): + if path in dirs: + break + dirs.add(path) + path = os.path.split(path)[0] + + return mozinfo.find_and_update_from_json(*dirs) + + +class MarionetteTest(TestResult): + + @property + def test_name(self): + if self.test_class is not None: + return '{0}.py {1}.{2}'.format(self.test_class.split('.')[0], + self.test_class, + self.name) + else: + return self.name + + +class MarionetteTestResult(StructuredTestResult, TestResultCollection): + + resultClass = MarionetteTest + + def __init__(self, *args, **kwargs): + self.marionette = kwargs.pop('marionette') + TestResultCollection.__init__(self, 'MarionetteTest') + self.passed = 0 + self.testsRun = 0 + self.result_modifiers = [] # used by mixins to modify the result + StructuredTestResult.__init__(self, *args, **kwargs) + + @property + def skipped(self): + return [t for t in self if t.result == 'SKIPPED'] + + @skipped.setter + def skipped(self, value): + pass + + @property + def expectedFailures(self): + return [t for t in self if t.result == 'KNOWN-FAIL'] + + @expectedFailures.setter + def expectedFailures(self, value): + pass + + @property + def unexpectedSuccesses(self): + return [t for t in self if t.result == 'UNEXPECTED-PASS'] + + @unexpectedSuccesses.setter + def unexpectedSuccesses(self, value): + pass + + @property + def tests_passed(self): + return [t for t in self if t.result == 'PASS'] + + @property + def errors(self): + return [t for t in self if t.result == 'ERROR'] + + @errors.setter + def errors(self, value): + pass + + @property + def failures(self): + return [t for t in self if t.result == 'UNEXPECTED-FAIL'] + + @failures.setter + def failures(self, value): + pass + + @property + def duration(self): + if self.stop_time: + return self.stop_time - self.start_time + else: + return 0 + + def add_test_result(self, test, result_expected='PASS', + result_actual='PASS', output='', context=None, **kwargs): + def get_class(test): + return test.__class__.__module__ + '.' + test.__class__.__name__ + + name = str(test).split()[0] + test_class = get_class(test) + if hasattr(test, 'jsFile'): + name = os.path.basename(test.jsFile) + test_class = None + + t = self.resultClass(name=name, test_class=test_class, + time_start=test.start_time, result_expected=result_expected, + context=context, **kwargs) + # call any registered result modifiers + for modifier in self.result_modifiers: + result_expected, result_actual, output, context = modifier( + t, result_expected, result_actual, output, context) + t.finish(result_actual, + time_end=time.time() if test.start_time else 0, + reason=relevant_line(output), + output=output) + self.append(t) + + def addError(self, test, err): + self.add_test_result(test, output=self._exc_info_to_string(err, test), + result_actual='ERROR') + super(MarionetteTestResult, self).addError(test, err) + + def addFailure(self, test, err): + self.add_test_result(test, output=self._exc_info_to_string(err, test), + result_actual='UNEXPECTED-FAIL') + super(MarionetteTestResult, self).addFailure(test, err) + + def addSuccess(self, test): + self.passed += 1 + self.add_test_result(test, result_actual='PASS') + super(MarionetteTestResult, self).addSuccess(test) + + def addExpectedFailure(self, test, err): + """Called when an expected failure/error occured.""" + self.add_test_result(test, output=self._exc_info_to_string(err, test), + result_actual='KNOWN-FAIL') + super(MarionetteTestResult, self).addExpectedFailure(test, err) + + def addUnexpectedSuccess(self, test): + """Called when a test was expected to fail, but succeed.""" + self.add_test_result(test, result_actual='UNEXPECTED-PASS') + super(MarionetteTestResult, self).addUnexpectedSuccess(test) + + def addSkip(self, test, reason): + self.add_test_result(test, output=reason, result_actual='SKIPPED') + super(MarionetteTestResult, self).addSkip(test, reason) + + def getInfo(self, test): + return test.test_name + + def getDescription(self, test): + doc_first_line = test.shortDescription() + if self.descriptions and doc_first_line: + return '\n'.join((str(test), doc_first_line)) + else: + desc = str(test) + if hasattr(test, 'jsFile'): + desc = "{0}, {1}".format(test.jsFile, desc) + return desc + + def printLogs(self, test): + for testcase in test._tests: + if hasattr(testcase, 'loglines') and testcase.loglines: + # Don't dump loglines to the console if they only contain + # TEST-START and TEST-END. + skip_log = True + for line in testcase.loglines: + str_line = ' '.join(line) + if 'TEST-END' not in str_line and 'TEST-START' not in str_line: + skip_log = False + break + if skip_log: + return + self.logger.info('START LOG:') + for line in testcase.loglines: + self.logger.info(' '.join(line).encode('ascii', 'replace')) + self.logger.info('END LOG:') + + def stopTest(self, *args, **kwargs): + unittest._TextTestResult.stopTest(self, *args, **kwargs) + if self.marionette.check_for_crash(): + # this tells unittest.TestSuite not to continue running tests + self.shouldStop = True + test = next((a for a in args if isinstance(a, unittest.TestCase)), + None) + if test: + self.addError(test, sys.exc_info()) + + +class MarionetteTextTestRunner(StructuredTestRunner): + + resultclass = MarionetteTestResult + + def __init__(self, **kwargs): + self.marionette = kwargs.pop('marionette') + self.capabilities = kwargs.pop('capabilities') + + StructuredTestRunner.__init__(self, **kwargs) + + def _makeResult(self): + return self.resultclass(self.stream, + self.descriptions, + self.verbosity, + marionette=self.marionette, + logger=self.logger, + result_callbacks=self.result_callbacks) + + def run(self, test): + result = super(MarionetteTextTestRunner, self).run(test) + result.printLogs(test) + return result + + +class BaseMarionetteArguments(ArgumentParser): + # Bug 1336953 - Until we can remove the socket timeout parameter it has to be + # set a default value which is larger than the longest timeout as defined by the + # WebDriver spec. In that case its 300s for page load. Also add another minute + # so that slow builds have enough time to send the timeout error to the client. + socket_timeout_default = 360.0 + + def __init__(self, **kwargs): + ArgumentParser.__init__(self, **kwargs) + + def dir_path(path): + path = os.path.abspath(os.path.expanduser(path)) + if not os.access(path, os.F_OK): + os.makedirs(path) + return path + + self.argument_containers = [] + self.add_argument('tests', + nargs='*', + default=[], + help='Tests to run. ' + 'One or more paths to test files (Python or JS), ' + 'manifest files (.ini) or directories. ' + 'When a directory is specified, ' + 'all test files in the directory will be run.') + self.add_argument('--binary', + help='path to gecko executable to launch before running the test') + self.add_argument('--address', + help='host:port of running Gecko instance to connect to') + self.add_argument('--emulator', + action='store_true', + help='If no --address is given, then the harness will launch an ' + 'emulator. (See Remote options group.) ' + 'If --address is given, then the harness assumes you are ' + 'running an emulator already, and will launch gecko app ' + 'on that emulator.') + self.add_argument('--app', + help='application to use. see marionette_driver.geckoinstance') + self.add_argument('--app-arg', + dest='app_args', + action='append', + default=[], + help='specify a command line argument to be passed onto the application') + self.add_argument('--profile', + help='profile to use when launching the gecko process. If not passed, ' + 'then a profile will be constructed and used', + type=dir_path) + self.add_argument('--pref', + action='append', + dest='prefs_args', + help="A preference to set. Must be a key-value pair separated by a ':'.") + self.add_argument('--preferences', + action='append', + dest='prefs_files', + help="read preferences from a JSON or INI file. For INI, use " + "'file.ini:section' to specify a particular section.") + self.add_argument('--addon', + action='append', + dest='addons', + help="addon to install; repeat for multiple addons.") + self.add_argument('--repeat', + type=int, + default=0, + help='number of times to repeat the test(s)') + self.add_argument('--testvars', + action='append', + help='path to a json file with any test data required') + self.add_argument('--symbols-path', + help='absolute path to directory containing breakpad symbols, or the ' + 'url of a zip file containing symbols') + self.add_argument('--startup-timeout', + type=int, + default=60, + help='the max number of seconds to wait for a Marionette connection ' + 'after launching a binary') + self.add_argument('--shuffle', + action='store_true', + default=False, + help='run tests in a random order') + self.add_argument('--shuffle-seed', + type=int, + default=random.randint(0, sys.maxint), + help='Use given seed to shuffle tests') + self.add_argument('--total-chunks', + type=int, + help='how many chunks to split the tests up into') + self.add_argument('--this-chunk', + type=int, + help='which chunk to run') + self.add_argument('--server-root', + help='url to a webserver or path to a document root from which content ' + 'resources are served (default: {}).'.format(os.path.join( + os.path.dirname(here), 'www'))) + self.add_argument('--gecko-log', + help="Define the path to store log file. If the path is" + " a directory, the real log file will be created" + " given the format gecko-(timestamp).log. If it is" + " a file, if will be used directly. '-' may be passed" + " to write to stdout. Default: './gecko.log'") + self.add_argument('--logger-name', + default='Marionette-based Tests', + help='Define the name to associate with the logger used') + self.add_argument('--jsdebugger', + action='store_true', + default=False, + help='Enable the jsdebugger for marionette javascript.') + self.add_argument('--pydebugger', + help='Enable python post-mortem debugger when a test fails.' + ' Pass in the debugger you want to use, eg pdb or ipdb.') + self.add_argument('--socket-timeout', + type=float, + default=self.socket_timeout_default, + help='Set the global timeout for marionette socket operations.' + ' Default: %(default)ss.') + self.add_argument('--disable-e10s', + action='store_false', + dest='e10s', + default=True, + help='Disable e10s when running marionette tests.') + self.add_argument('--tag', + action='append', dest='test_tags', + default=None, + help="Filter out tests that don't have the given tag. Can be " + "used multiple times in which case the test must contain " + "at least one of the given tags.") + self.add_argument('--workspace', + action='store', + default=None, + help="Path to directory for Marionette output. " + "(Default: .) (Default profile dest: TMP)", + type=dir_path) + self.add_argument('-v', '--verbose', + action='count', + help='Increase verbosity to include debug messages with -v, ' + 'and trace messages with -vv.') + self.register_argument_container(RemoteMarionetteArguments()) + + def register_argument_container(self, container): + group = self.add_argument_group(container.name) + + for cli, kwargs in container.args: + group.add_argument(*cli, **kwargs) + + self.argument_containers.append(container) + + def parse_known_args(self, args=None, namespace=None): + args, remainder = ArgumentParser.parse_known_args(self, args, namespace) + for container in self.argument_containers: + if hasattr(container, 'parse_args_handler'): + container.parse_args_handler(args) + return (args, remainder) + + def _get_preferences(self, prefs_files, prefs_args): + """Return user defined profile preferences as a dict.""" + # object that will hold the preferences + prefs = mozprofile.prefs.Preferences() + + # add preferences files + if prefs_files: + for prefs_file in prefs_files: + prefs.add_file(prefs_file) + + separator = ':' + cli_prefs = [] + if prefs_args: + misformatted = [] + for pref in prefs_args: + if separator not in pref: + misformatted.append(pref) + else: + cli_prefs.append(pref.split(separator, 1)) + if misformatted: + self._print_message("Warning: Ignoring preferences not in key{}value format: {}\n" + .format(separator, ", ".join(misformatted))) + # string preferences + prefs.add(cli_prefs, cast=True) + + return dict(prefs()) + + def verify_usage(self, args): + if not args.tests: + self.error('You must specify one or more test files, manifests, or directories.') + + missing_tests = [path for path in args.tests if not os.path.exists(path)] + if missing_tests: + self.error("Test file(s) not found: " + " ".join([path for path in missing_tests])) + + if not args.address and not args.binary and not args.emulator: + self.error('You must specify --binary, or --address, or --emulator') + + if args.total_chunks is not None and args.this_chunk is None: + self.error('You must specify which chunk to run.') + + if args.this_chunk is not None and args.total_chunks is None: + self.error('You must specify how many chunks to split the tests into.') + + if args.total_chunks is not None: + if not 1 < args.total_chunks: + self.error('Total chunks must be greater than 1.') + if not 1 <= args.this_chunk <= args.total_chunks: + self.error('Chunk to run must be between 1 and {}.'.format(args.total_chunks)) + + if args.jsdebugger: + args.app_args.append('-jsdebugger') + args.socket_timeout = None + + args.prefs = self._get_preferences(args.prefs_files, args.prefs_args) + + for container in self.argument_containers: + if hasattr(container, 'verify_usage_handler'): + container.verify_usage_handler(args) + + return args + + +class RemoteMarionetteArguments(object): + name = 'Remote (Emulator/Device)' + args = [ + [['--emulator-binary'], + {'help': 'Path to emulator binary. By default mozrunner uses `which emulator`', + 'dest': 'emulator_bin', + }], + [['--adb'], + {'help': 'Path to the adb. By default mozrunner uses `which adb`', + 'dest': 'adb_path' + }], + [['--avd'], + {'help': ('Name of an AVD available in your environment.' + 'See mozrunner.FennecEmulatorRunner'), + }], + [['--avd-home'], + {'help': 'Path to avd parent directory', + }], + [['--device'], + {'help': ('Serial ID to connect to as seen in `adb devices`,' + 'e.g emulator-5444'), + 'dest': 'device_serial', + }], + [['--package'], + {'help': 'Name of Android package, e.g. org.mozilla.fennec', + 'dest': 'package_name', + }], + + ] + + +class Fixtures(object): + def where_is(self, uri, on="http"): + return serve.where_is(uri, on) + + +class BaseMarionetteTestRunner(object): + + textrunnerclass = MarionetteTextTestRunner + driverclass = Marionette + + def __init__(self, address=None, + app=None, app_args=None, binary=None, profile=None, + logger=None, logdir=None, + repeat=0, testvars=None, + symbols_path=None, + shuffle=False, shuffle_seed=random.randint(0, sys.maxint), this_chunk=1, + total_chunks=1, + server_root=None, gecko_log=None, result_callbacks=None, + prefs=None, test_tags=None, + socket_timeout=BaseMarionetteArguments.socket_timeout_default, + startup_timeout=None, addons=None, workspace=None, + verbose=0, e10s=True, emulator=False, **kwargs): + self._appinfo = None + self._appName = None + self._capabilities = None + self._filename_pattern = None + self._version_info = {} + + self.fixture_servers = {} + self.fixtures = Fixtures() + self.extra_kwargs = kwargs + self.test_kwargs = deepcopy(kwargs) + self.address = address + self.app = app + self.app_args = app_args or [] + self.bin = binary + self.emulator = emulator + self.profile = profile + self.addons = addons + self.logger = logger + self.marionette = None + self.logdir = logdir + self.repeat = repeat + self.symbols_path = symbols_path + self.socket_timeout = socket_timeout + self.shuffle = shuffle + self.shuffle_seed = shuffle_seed + self.server_root = server_root + self.this_chunk = this_chunk + self.total_chunks = total_chunks + self.mixin_run_tests = [] + self.manifest_skipped_tests = [] + self.tests = [] + self.result_callbacks = result_callbacks or [] + self.prefs = prefs or {} + self.test_tags = test_tags + self.startup_timeout = startup_timeout + self.workspace = workspace + # If no workspace is set, default location for gecko.log is . + # and default location for profile is TMP + self.workspace_path = workspace or os.getcwd() + self.verbose = verbose + self.e10s = e10s + if self.e10s: + self.prefs.update({ + 'browser.tabs.remote.autostart': True, + 'browser.tabs.remote.force-enable': True, + 'extensions.e10sBlocksEnabling': False + }) + + def gather_debug(test, status): + # No screenshots and page source for skipped tests + if status == "SKIP": + return + + rv = {} + marionette = test._marionette_weakref() + + # In the event we're gathering debug without starting a session, + # skip marionette commands + if marionette.session is not None: + try: + with marionette.using_context(marionette.CONTEXT_CHROME): + rv['screenshot'] = marionette.screenshot() + with marionette.using_context(marionette.CONTEXT_CONTENT): + rv['source'] = marionette.page_source + except Exception as exc: + self.logger.warning('Failed to gather test failure debug: {}'.format(exc)) + return rv + + self.result_callbacks.append(gather_debug) + + # testvars are set up in self.testvars property + self._testvars = None + self.testvars_paths = testvars + + self.test_handlers = [] + + self.reset_test_stats() + + self.logger.info('Using workspace for temporary data: ' + '"{}"'.format(self.workspace_path)) + + if not gecko_log: + self.gecko_log = os.path.join(self.workspace_path or '', 'gecko.log') + else: + self.gecko_log = gecko_log + + self.results = [] + + @property + def filename_pattern(self): + if self._filename_pattern is None: + self._filename_pattern = re.compile( + "^test(((_.+?)+?\.((py)|(js)))|(([A-Z].*?)+?\.js))$") + + return self._filename_pattern + + @property + def testvars(self): + if self._testvars is not None: + return self._testvars + + self._testvars = {} + + def update(d, u): + """Update a dictionary that may contain nested dictionaries.""" + for k, v in u.iteritems(): + o = d.get(k, {}) + if isinstance(v, dict) and isinstance(o, dict): + d[k] = update(d.get(k, {}), v) + else: + d[k] = u[k] + return d + + json_testvars = self._load_testvars() + for j in json_testvars: + self._testvars = update(self._testvars, j) + return self._testvars + + def _load_testvars(self): + data = [] + if self.testvars_paths is not None: + for path in list(self.testvars_paths): + path = os.path.abspath(os.path.expanduser(path)) + if not os.path.exists(path): + raise IOError('--testvars file {} does not exist'.format(path)) + try: + with open(path) as f: + data.append(json.loads(f.read())) + except ValueError as e: + exc, val, tb = sys.exc_info() + msg = "JSON file ({0}) is not properly formatted: {1}" + raise exc, msg.format(os.path.abspath(path), e.message), tb + return data + + @property + def capabilities(self): + if self._capabilities: + return self._capabilities + + self.marionette.start_session() + self._capabilities = self.marionette.session_capabilities + self.marionette.delete_session() + return self._capabilities + + @property + def appinfo(self): + if self._appinfo: + return self._appinfo + + self.marionette.start_session() + with self.marionette.using_context('chrome'): + self._appinfo = self.marionette.execute_script(""" + try { + return Services.appinfo; + } catch (e) { + return null; + }""") + self.marionette.delete_session() + self._appinfo = self._appinfo or {} + return self._appinfo + + @property + def appName(self): + if self._appName: + return self._appName + + self._appName = self.capabilities.get('browserName') + return self._appName + + @property + def bin(self): + return self._bin + + @bin.setter + def bin(self, path): + """Set binary and reset parts of runner accordingly. + Intended use: to change binary between calls to run_tests + """ + self._bin = path + self.tests = [] + self.cleanup() + + @property + def version_info(self): + if not self._version_info: + try: + # TODO: Get version_info in Fennec case + self._version_info = mozversion.get_version(binary=self.bin) + except Exception: + self.logger.warning("Failed to retrieve version information for {}".format( + self.bin)) + return self._version_info + + def reset_test_stats(self): + self.passed = 0 + self.failed = 0 + self.crashed = 0 + self.unexpected_successes = 0 + self.todo = 0 + self.skipped = 0 + self.failures = [] + + def _build_kwargs(self): + if self.logdir and not os.access(self.logdir, os.F_OK): + os.mkdir(self.logdir) + + kwargs = { + 'socket_timeout': self.socket_timeout, + 'prefs': self.prefs, + 'startup_timeout': self.startup_timeout, + 'verbose': self.verbose, + 'symbols_path': self.symbols_path, + } + if self.bin or self.emulator: + kwargs.update({ + 'host': 'localhost', + 'port': 2828, + 'app': self.app, + 'app_args': self.app_args, + 'profile': self.profile, + 'addons': self.addons, + 'gecko_log': self.gecko_log, + # ensure Marionette class takes care of starting gecko instance + 'bin': True, + }) + + if self.bin: + kwargs.update({ + 'bin': self.bin, + }) + + if self.emulator: + kwargs.update({ + 'avd_home': self.extra_kwargs.get('avd_home'), + 'adb_path': self.extra_kwargs.get('adb_path'), + 'emulator_binary': self.extra_kwargs.get('emulator_bin'), + 'avd': self.extra_kwargs.get('avd'), + 'package_name': self.extra_kwargs.get('package_name'), + }) + + if self.address: + host, port = self.address.split(':') + kwargs.update({ + 'host': host, + 'port': int(port), + }) + if self.emulator: + kwargs.update({ + 'connect_to_running_emulator': True, + }) + if not self.bin and not self.emulator: + try: + # Establish a socket connection so we can vertify the data come back + connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + connection.connect((host, int(port))) + connection.close() + except Exception as e: + exc, val, tb = sys.exc_info() + msg = "Connection attempt to {0}:{1} failed with error: {2}" + raise exc, msg.format(host, port, e), tb + if self.workspace: + kwargs['workspace'] = self.workspace_path + return kwargs + + def record_crash(self): + crash = True + try: + crash = self.marionette.check_for_crash() + self.crashed += int(crash) + except Exception: + traceback.print_exc() + return crash + + def _initialize_test_run(self, tests): + assert len(tests) > 0 + assert len(self.test_handlers) > 0 + self.reset_test_stats() + + def _add_tests(self, tests): + for test in tests: + self.add_test(test) + + invalid_tests = [t['filepath'] for t in self.tests + if not self._is_filename_valid(t['filepath'])] + if invalid_tests: + raise Exception("Test file names must be of the form " + "'test_something.py', 'test_something.js', or 'testSomething.js'." + " Invalid test names:\n {}".format('\n '.join(invalid_tests))) + + def _is_filename_valid(self, filename): + filename = os.path.basename(filename) + return self.filename_pattern.match(filename) + + def _log_skipped_tests(self): + for test in self.manifest_skipped_tests: + name = os.path.basename(test['path']) + self.logger.test_start(name) + self.logger.test_end(name, + 'SKIP', + message=test['disabled']) + self.todo += 1 + + def run_tests(self, tests): + start_time = time.time() + self._initialize_test_run(tests) + + if self.marionette is None: + self.marionette = self.driverclass(**self._build_kwargs()) + self.logger.info("Profile path is %s" % self.marionette.profile_path) + + if len(self.fixture_servers) == 0 or \ + any(not server.is_alive for _, server in self.fixture_servers): + self.logger.info("Starting fixture servers") + self.fixture_servers = self.start_fixture_servers() + for url in iter_url(self.fixture_servers): + self.logger.info("Fixture server listening on %s" % url) + + # backwards compatibility + self.marionette.baseurl = serve.where_is("/") + + self._add_tests(tests) + + device_info = None + if self.marionette.instance and self.emulator: + try: + device_info = self.marionette.instance.runner.device.dm.getInfo() + except Exception: + self.logger.warning('Could not get device info', exc_info=True) + + appinfo_e10s = self.appinfo.get('browserTabsRemoteAutostart', False) + self.logger.info("e10s is {}".format("enabled" if appinfo_e10s else "disabled")) + if self.e10s != appinfo_e10s: + message_e10s = ("BaseMarionetteTestRunner configuration (self.e10s) does " + "not match browser appinfo") + self.cleanup() + raise AssertionError(message_e10s) + + self.logger.suite_start(self.tests, + version_info=self.version_info, + device_info=device_info) + + self._log_skipped_tests() + + interrupted = None + try: + counter = self.repeat + while counter >= 0: + round_num = self.repeat - counter + if round_num > 0: + self.logger.info('\nREPEAT {}\n-------'.format(round_num)) + self.run_test_sets() + counter -= 1 + except KeyboardInterrupt: + # in case of KeyboardInterrupt during the test execution + # we want to display current test results. + # so we keep the exception to raise it later. + interrupted = sys.exc_info() + except: + # For any other exception we return immediately and have to + # cleanup running processes + self.cleanup() + raise + + try: + self._print_summary(tests) + self.record_crash() + self.elapsedtime = time.time() - start_time + + for run_tests in self.mixin_run_tests: + run_tests(tests) + if self.shuffle: + self.logger.info("Using shuffle seed: %d" % self.shuffle_seed) + + self.logger.suite_end() + except: + # raise only the exception if we were not interrupted + if not interrupted: + raise + finally: + self.cleanup() + + # reraise previous interruption now + if interrupted: + raise interrupted[0], interrupted[1], interrupted[2] + + def _print_summary(self, tests): + self.logger.info('\nSUMMARY\n-------') + self.logger.info('passed: {}'.format(self.passed)) + if self.unexpected_successes == 0: + self.logger.info('failed: {}'.format(self.failed)) + else: + self.logger.info( + 'failed: {0} (unexpected sucesses: {1})'.format(self.failed, + self.unexpected_successes)) + if self.skipped == 0: + self.logger.info('todo: {}'.format(self.todo)) + else: + self.logger.info('todo: {0} (skipped: {1})'.format(self.todo, self.skipped)) + + if self.failed > 0: + self.logger.info('\nFAILED TESTS\n-------') + for failed_test in self.failures: + self.logger.info('{}'.format(failed_test[0])) + + def start_fixture_servers(self): + root = self.server_root or os.path.join(os.path.dirname(here), "www") + if self.appName == "fennec": + return serve.start(root, host=moznetwork.get_ip()) + else: + return serve.start(root) + + def add_test(self, test, expected='pass'): + filepath = os.path.abspath(test) + + if os.path.isdir(filepath): + for root, dirs, files in os.walk(filepath): + for filename in files: + if filename.endswith('.ini'): + msg_tmpl = ("Ignoring manifest '{0}'; running all tests in '{1}'." + " See --help for details.") + relpath = os.path.relpath(os.path.join(root, filename), filepath) + self.logger.warning(msg_tmpl.format(relpath, filepath)) + elif self._is_filename_valid(filename): + test_file = os.path.join(root, filename) + self.add_test(test_file) + return + + file_ext = os.path.splitext(os.path.split(filepath)[-1])[1] + + if file_ext == '.ini': + manifest = TestManifest() + manifest.read(filepath) + + json_path = update_mozinfo(filepath) + self.logger.info("mozinfo updated from: {}".format(json_path)) + self.logger.info("mozinfo is: {}".format(mozinfo.info)) + + filters = [] + if self.test_tags: + filters.append(tags(self.test_tags)) + + values = { + "appname": self.appName, + "e10s": self.e10s, + "manage_instance": self.marionette.instance is not None, + } + values.update(mozinfo.info) + + manifest_tests = manifest.active_tests(exists=False, + disabled=True, + filters=filters, + **values) + if len(manifest_tests) == 0: + self.logger.error("No tests to run using specified " + "combination of filters: {}".format( + manifest.fmt_filters())) + + target_tests = [] + for test in manifest_tests: + if test.get('disabled'): + self.manifest_skipped_tests.append(test) + else: + target_tests.append(test) + + for i in target_tests: + if not os.path.exists(i["path"]): + raise IOError("test file: {} does not exist".format(i["path"])) + + file_ext = os.path.splitext(os.path.split(i['path'])[-1])[-1] + + self.add_test(i["path"], i["expected"]) + return + + self.tests.append({'filepath': filepath, 'expected': expected}) + + def run_test(self, filepath, expected): + testloader = unittest.TestLoader() + suite = unittest.TestSuite() + self.test_kwargs['expected'] = expected + mod_name = os.path.splitext(os.path.split(filepath)[-1])[0] + for handler in self.test_handlers: + if handler.match(os.path.basename(filepath)): + handler.add_tests_to_suite(mod_name, + filepath, + suite, + testloader, + self.marionette, + self.fixtures, + self.testvars, + **self.test_kwargs) + break + + if suite.countTestCases(): + runner = self.textrunnerclass(logger=self.logger, + marionette=self.marionette, + capabilities=self.capabilities, + result_callbacks=self.result_callbacks) + + results = runner.run(suite) + self.results.append(results) + + self.failed += len(results.failures) + len(results.errors) + if hasattr(results, 'skipped'): + self.skipped += len(results.skipped) + self.todo += len(results.skipped) + self.passed += results.passed + for failure in results.failures + results.errors: + self.failures.append((results.getInfo(failure), failure.output, + 'TEST-UNEXPECTED-FAIL')) + if hasattr(results, 'unexpectedSuccesses'): + self.failed += len(results.unexpectedSuccesses) + self.unexpected_successes += len(results.unexpectedSuccesses) + for failure in results.unexpectedSuccesses: + self.failures.append((results.getInfo(failure), failure.output, + 'TEST-UNEXPECTED-PASS')) + if hasattr(results, 'expectedFailures'): + self.todo += len(results.expectedFailures) + + self.mixin_run_tests = [] + for result in self.results: + result.result_modifiers = [] + + def run_test_set(self, tests): + if self.shuffle: + random.seed(self.shuffle_seed) + random.shuffle(tests) + + for test in tests: + self.run_test(test['filepath'], test['expected']) + if self.record_crash(): + break + + def run_test_sets(self): + if len(self.tests) < 1: + raise Exception('There are no tests to run.') + elif self.total_chunks > len(self.tests): + raise ValueError('Total number of chunks must be between 1 and {}.' + .format(len(self.tests))) + if self.total_chunks > 1: + chunks = [[] for i in range(self.total_chunks)] + for i, test in enumerate(self.tests): + target_chunk = i % self.total_chunks + chunks[target_chunk].append(test) + + self.logger.info('Running chunk {0} of {1} ({2} tests selected from a ' + 'total of {3})'.format(self.this_chunk, self.total_chunks, + len(chunks[self.this_chunk - 1]), + len(self.tests))) + self.tests = chunks[self.this_chunk - 1] + + self.run_test_set(self.tests) + + def cleanup(self): + for proc in iter_proc(self.fixture_servers): + proc.stop() + proc.kill() + self.fixture_servers = {} + + if hasattr(self, 'marionette') and self.marionette: + if self.marionette.instance is not None: + self.marionette.instance.close() + self.marionette.instance = None + + self.marionette.cleanup() + self.marionette = None + + __del__ = cleanup diff --git a/testing/marionette/harness/marionette_harness/runner/httpd.py b/testing/marionette/harness/marionette_harness/runner/httpd.py new file mode 100644 index 000000000..2136e2bcc --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/httpd.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Specialisation of wptserver.server.WebTestHttpd for testing +Marionette. + +""" + +import argparse +import os +import select +import sys +import time +import urlparse + +from wptserve import server, handlers, routes as default_routes + + +here = os.path.abspath(os.path.dirname(__file__)) +default_doc_root = os.path.join(os.path.dirname(here), "www") +default_ssl_cert = os.path.join(here, "test.cert") +default_ssl_key = os.path.join(here, "test.key") + + +@handlers.handler +def upload_handler(request, response): + return 200, [], [request.headers.get("Content-Type")] or [] + + +@handlers.handler +def slow_loading_document(request, response): + time.sleep(5) + return """ +ok +

ok""" + + +class NotAliveError(Exception): + """Occurs when attempting to run a function that requires the HTTPD + to have been started, and it has not. + + """ + pass + + +class FixtureServer(object): + + def __init__(self, doc_root, url="http://127.0.0.1:0", use_ssl=False, + ssl_cert=None, ssl_key=None): + if not os.path.isdir(doc_root): + raise ValueError("Server root is not a directory: %s" % doc_root) + + url = urlparse.urlparse(url) + if url.scheme is None: + raise ValueError("Server scheme not provided") + + scheme, host, port = url.scheme, url.hostname, url.port + if host is None: + host = "127.0.0.1" + if port is None: + port = 0 + + routes = [("POST", "/file_upload", upload_handler), + ("GET", "/slow", slow_loading_document)] + routes.extend(default_routes.routes) + + self._httpd = server.WebTestHttpd(host=host, + port=port, + bind_hostname=True, + doc_root=doc_root, + routes=routes, + use_ssl=True if scheme == "https" else False, + certificate=ssl_cert, + key_file=ssl_key) + + def start(self, block=False): + if self.is_alive: + return + self._httpd.start(block=block) + + def wait(self): + if not self.is_alive: + return + try: + select.select([], [], []) + except KeyboardInterrupt: + self.stop() + + def stop(self): + if not self.is_alive: + return + self._httpd.stop() + + def get_url(self, path): + if not self.is_alive: + raise NotAliveError() + return self._httpd.get_url(path) + + @property + def doc_root(self): + return self._httpd.router.doc_root + + @property + def router(self): + return self._httpd.router + + @property + def routes(self): + return self._httpd.router.routes + + @property + def is_alive(self): + return self._httpd.started + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Specialised HTTP server for testing Marionette.") + parser.add_argument("url", help=""" +service address including scheme, hostname, port, and prefix for document root, +e.g. \"https://0.0.0.0:0/base/\"""") + parser.add_argument( + "-r", dest="doc_root", default=default_doc_root, + help="path to document root (default %(default)s)") + parser.add_argument( + "-c", dest="ssl_cert", default=default_ssl_cert, + help="path to SSL certificate (default %(default)s)") + parser.add_argument( + "-k", dest="ssl_key", default=default_ssl_key, + help="path to SSL certificate key (default %(default)s)") + args = parser.parse_args() + + httpd = FixtureServer(args.doc_root, args.url, + ssl_cert=args.ssl_cert, + ssl_key=args.ssl_key) + httpd.start() + print >>sys.stderr, "%s: started fixture server on %s" % \ + (sys.argv[0], httpd.get_url("/")) + httpd.wait() diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/__init__.py b/testing/marionette/harness/marionette_harness/runner/mixins/__init__.py new file mode 100644 index 000000000..d4abaacb4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/__init__.py @@ -0,0 +1,13 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from .browsermob import ( + BrowserMobProxyTestCaseMixin, + BrowserMobProxyArguments, + BrowserMobTestCase, +) + +from .window_manager import ( + WindowManagerMixin, +) diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/History.md b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/History.md new file mode 100644 index 000000000..65c692bfc --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/History.md @@ -0,0 +1,57 @@ + +0.6.0 / 2014-01-21 +================== + + * Added support for parameters in har creation + * Bug fixes for tests that are out of date + * Setup server constructor to look on path for location of browsermob-proxy executable. As well as looking for a file. Also added example code for using browsermob-proxy with chrome + * Fix project name + * adding docs + +0.5.0 / 2013-05-23 +================== +* Allow proxying of ssl requests with selenium. +* Updating case for proxy type + + +0.4.0 / 2013-03-06 +================== + + * Allow setting basic authentication + * Adding the ability to remap hosts which is available from BrowserMob Proxy Beta 7 + * Merge pull request #6 from lukeis/patch-2 + * Update readme.md + * initial commit of event listener to auto do record + * server.create_proxy is a function, should be called :) + * forgot to add the port + +0.2.0 / 2012-06-18 +================== + + * pep8 --ignore=E501 + * DELETE /proxy/:port/ + * /proxy/:port/limits + * /proxy/:port/blacklist + * /proxy/:port/whitelist + * fixing /proxy/:port/har/pageRef + * fixing /proxy/:port/har/pageRef + * fixing passing in a page ref as the name for the page in /proxy/:port/har + * tests around /proxy/:port/har and some cleanup of the implementation + * make /proxy/:port/headers work + * wrapping selenium_proxy with webdriver_proxy since the project is more than just webdriver + * extending the client to play nice with remote webdriver instances + * create_proxy sounds and feels like a method to me, let's make it so + * ensure the self.process exist, to reduce possibilities of AttributeError + * check the path before attempting to start the server + * wait longer than 3 seconds for the server to come up + +0.1.0 / 2012-03-22 +================== + +* Removed httplib2 in preference for requests +* Added support for setting headers + +0.0.1 / 2012-01-16 +================== + +* Initial version diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py new file mode 100644 index 000000000..5c6d63004 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py @@ -0,0 +1,6 @@ +__version__ = '0.5.0' + +from .server import Server +from .client import Client + +__all__ = ['Server', 'Client', 'browsermobproxy'] diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py new file mode 100644 index 000000000..00b98ca5e --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py @@ -0,0 +1,326 @@ +import requests + +try: + from urllib.parse import urlencode, unquote +except ImportError: + from urllib import urlencode, unquote +import json + + +class Client(object): + def __init__(self, url, params={}): + """ + Initialises a new Client object + + + :param url: This is where the BrowserMob Proxy lives + :param params: URL query (for example httpProxy and httpsProxy vars) + """ + self.host = "http://" + url + if params: + urlparams = "?" + unquote(urlencode(params)) + else: + urlparams = "" + resp = requests.post('{}/proxy'.format(self.host + urlparams)) + jcontent = json.loads(resp.content.decode('utf-8')) + self.port = jcontent['port'] + url_parts = self.host.split(":") + self.proxy = url_parts[1][2:] + ":" + str(self.port) + + def close(self): + """ + shuts down the proxy and closes the port + """ + r = requests.delete('{0}/proxy/{1}'.format(self.host, self.port)) + return r.status_code + + # webdriver integration + # ...as a proxy object + def selenium_proxy(self): + """ + Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy + """ + from selenium import webdriver + return webdriver.Proxy({ + "httpProxy": self.proxy, + "sslProxy": self.proxy, + }) + + def webdriver_proxy(self): + """ + Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy + """ + return self.selenium_proxy() + + # ...as a capability + def add_to_capabilities(self, capabilities): + """ + Adds an 'proxy' entry to a desired capabilities dictionary with the + BrowserMob proxy information + + + :param capabilities: The Desired capabilities object from Selenium WebDriver + """ + capabilities['proxy'] = { + 'proxyType': "MANUAL", + 'httpProxy': self.proxy, + 'sslProxy': self.proxy + } + + def add_to_webdriver_capabilities(self, capabilities): + self.add_to_capabilities(capabilities) + + # browsermob proxy api + @property + def har(self): + """ + Gets the HAR that has been recorded + """ + r = requests.get('{0}/proxy/{1}/har'.format(self.host, self.port)) + + return r.json() + + def new_har(self, ref=None, options={}): + """ + This sets a new HAR to be recorded + + + :param ref: A reference for the HAR. Defaults to None + :param options: A dictionary that will be passed to BrowserMob Proxy \ + with specific keywords. Keywords are: \ + captureHeaders - Boolean, capture headers \ + captureContent - Boolean, capture content bodies \ + captureBinaryContent - Boolean, capture binary content + """ + if ref: + payload = {"initialPageRef": ref} + else: + payload = {} + if options: + payload.update(options) + + r = requests.put('{0}/proxy/{1}/har'.format(self.host, self.port), payload) + if r.status_code == 200: + return (r.status_code, r.json()) + else: + return (r.status_code, None) + + def new_page(self, ref=None): + """ + This sets a new page to be recorded + + + :param ref: A reference for the new page. Defaults to None + """ + if ref: + payload = {"pageRef": ref} + else: + payload = {} + r = requests.put('{0}/proxy/{1}/har/pageRef'.format(self.host, self.port), + payload) + return r.status_code + + def blacklist(self, regexp, status_code): + """ + Sets a list of URL patterns to blacklist + + + :param regex: a comma separated list of regular expressions + :param status_code: the HTTP status code to return for URLs that do not \ + match the blacklist + + """ + r = requests.put('{0}/proxy/{1}/blacklist'.format(self.host, self.port), + {'regex': regexp, 'status': status_code}) + return r.status_code + + def whitelist(self, regexp, status_code): + """ + Sets a list of URL patterns to whitelist + + + :param regex: a comma separated list of regular expressions + :param status_code: the HTTP status code to return for URLs that do not \ + match the whitelist + """ + r = requests.put('{0}/proxy/{1}/whitelist'.format(self.host, self.port), + {'regex': regexp, 'status': status_code}) + return r.status_code + + def basic_authentication(self, domain, username, password): + """ + This add automatic basic authentication + + + :param domain: domain to set authentication credentials for + :param username: valid username to use when authenticating + :param password: valid password to use when authenticating + """ + r = requests.post(url='{0}/proxy/{1}/auth/basic/{2}'.format(self.host, self.port, domain), + data=json.dumps({'username': username, 'password': password}), + headers={'content-type': 'application/json'}) + return r.status_code + + def headers(self, headers): + """ + This sets the headers that will set by the proxy on all requests + + + :param headers: this is a dictionary of the headers to be set + """ + if not isinstance(headers, dict): + raise TypeError("headers needs to be dictionary") + + r = requests.post(url='{0}/proxy/{1}/headers'.format(self.host, self.port), + data=json.dumps(headers), + headers={'content-type': 'application/json'}) + return r.status_code + + def response_interceptor(self, js): + """ + Executes the javascript against each response + + + :param js: the javascript to execute + """ + r = requests.post(url='{0}/proxy/{1}/interceptor/response'.format(self.host, self.port), + data=js, + headers={'content-type': 'x-www-form-urlencoded'}) + return r.status_code + + def request_interceptor(self, js): + """ + Executes the javascript against each request + + + :param js: the javascript to execute + """ + r = requests.post(url='{0}/proxy/{1}/interceptor/request'.format(self.host, self.port), + data=js, + headers={'content-type': 'x-www-form-urlencoded'}) + return r.status_code + + LIMITS = { + 'upstream_kbps': 'upstreamKbps', + 'downstream_kbps': 'downstreamKbps', + 'latency': 'latency' + } + + def limits(self, options): + """ + Limit the bandwidth through the proxy. + + + :param options: A dictionary with all the details you want to set. \ + downstreamKbps - Sets the downstream kbps \ + upstreamKbps - Sets the upstream kbps \ + latency - Add the given latency to each HTTP request + """ + params = {} + + for (k, v) in list(options.items()): + if k not in self.LIMITS: + raise KeyError('invalid key: {}'.format(k)) + + params[self.LIMITS[k]] = int(v) + + if len(list(params.items())) == 0: + raise KeyError("You need to specify one of the valid Keys") + + r = requests.put('{0}/proxy/{1}/limit'.format(self.host, self.port), + params) + return r.status_code + + TIMEOUTS = { + 'request': 'requestTimeout', + 'read': 'readTimeout', + 'connection': 'connectionTimeout', + 'dns': 'dnsCacheTimeout' + } + + def timeouts(self, options): + """ + Configure various timeouts in the proxy + + + :param options: A dictionary with all the details you want to set. \ + request - request timeout (in seconds) \ + read - read timeout (in seconds) \ + connection - connection timeout (in seconds) \ + dns - dns lookup timeout (in seconds) + """ + params = {} + + for (k, v) in list(options.items()): + if k not in self.TIMEOUTS: + raise KeyError('invalid key: {}'.format(k)) + + params[self.TIMEOUTS[k]] = int(v) + + if len(list(params.items())) == 0: + raise KeyError("You need to specify one of the valid Keys") + + r = requests.put('{0}/proxy/{1}/timeout'.format(self.host, self.port), + params) + return r.status_code + + def remap_hosts(self, address, ip_address): + """ + Remap the hosts for a specific URL + + + :param address: url that you wish to remap + :param ip_address: IP Address that will handle all traffic for the address passed in + """ + assert address is not None and ip_address is not None + r = requests.post('{0}/proxy/{1}/hosts'.format(self.host, self.port), + json.dumps({address: ip_address}), + headers={'content-type': 'application/json'}) + return r.status_code + + def wait_for_traffic_to_stop(self, quiet_period, timeout): + """ + Waits for the network to be quiet + + + :param quiet_period: number of miliseconds the network needs to be quiet for + :param timeout: max number of miliseconds to wait + """ + r = requests.put('{0}/proxy/{1}/wait'.format(self.host, self.port), + {'quietPeriodInMs': quiet_period, 'timeoutInMs': timeout}) + return r.status_code + + def clear_dns_cache(self): + """ + Clears the DNS cache associated with the proxy instance + """ + r = requests.delete('{0}/proxy/{1}/dns/cache'.format(self.host, self.port)) + return r.status_code + + def rewrite_url(self, match, replace): + """ + Rewrites the requested url. + + + :param match: a regex to match requests with + :param replace: unicode \ + a string to replace the matches with + """ + params = { + "matchRegex": match, + "replace": replace + } + r = requests.put('{0}/proxy/{1}/rewrite'.format(self.host, self.port), + params) + return r.status_code + + def retry(self, retry_count): + """ + Retries. No idea what its used for, but its in the API... + + + :param retry_count: the number of retries + """ + r = requests.put('{0}/proxy/{1}/retry'.format(self.host, self.port), + {'retrycount': retry_count}) + return r.status_code diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py new file mode 100644 index 000000000..ff6db4efe --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py @@ -0,0 +1,106 @@ +import os +import platform +import socket +import subprocess +import time + +from .client import Client + + +class Server(object): + + def __init__(self, path='browsermob-proxy', options={}): + """ + Initialises a Server object + + :param path: Path to the browsermob proxy batch file + :param options: Dictionary that can hold the port. \ + More items will be added in the future. \ + This defaults to an empty dictionary + """ + path_var_sep = ':' + if platform.system() == 'Windows': + path_var_sep = ';' + if not path.endswith('.bat'): + path += '.bat' + + exec_not_on_path = True + for directory in os.environ['PATH'].split(path_var_sep): + if(os.path.isfile(os.path.join(directory, path))): + exec_not_on_path = False + break + + if not os.path.isfile(path) and exec_not_on_path: + raise IOError("Browsermob-Proxy binary couldn't be found in path" + " provided: {}".format(path)) + + self.path = path + self.port = options.get('port', 8080) + self.process = None + + if platform.system() == 'Darwin': + self.command = ['sh'] + else: + self.command = [] + self.command += [path, '--port={}'.format(self.port)] + + def start(self): + """ + This will start the browsermob proxy and then wait until it can + interact with it + """ + self.log_file = open(os.path.abspath('server.log'), 'w') + self.process = subprocess.Popen(self.command, + stdout=self.log_file, + stderr=subprocess.STDOUT) + count = 0 + while not self._is_listening(): + time.sleep(0.5) + count += 1 + if count == 60: + self.stop() + raise Exception("Can't connect to Browsermob-Proxy") + + def stop(self): + """ + This will stop the process running the proxy + """ + if self.process.poll() is not None: + return + + try: + self.process.kill() + self.process.wait() + except AttributeError: + # kill may not be available under windows environment + pass + + self.log_file.close() + + @property + def url(self): + """ + Gets the url that the proxy is running on. This is not the URL clients + should connect to. + """ + return "http://localhost:{}".format(self.port) + + def create_proxy(self, params={}): + """ + Gets a client class that allow to set all the proxy details that you + may need to. + :param params: Dictionary where you can specify params \ + like httpProxy and httpsProxy + """ + client = Client(self.url[7:], params) + return client + + def _is_listening(self): + try: + socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socket_.settimeout(1) + socket_.connect(("localhost", self.port)) + socket_.close() + return True + except socket.error: + return False diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py new file mode 100644 index 000000000..533542b09 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py @@ -0,0 +1,34 @@ +from selenium.webdriver.support.abstract_event_listener import AbstractEventListener + +class WebDriverEventListener(AbstractEventListener): + + def __init__(self, client, refs={}): + self.client = client + self.hars = [] + self.refs = refs + + def before_navigate_to(self, url, driver): + if len(self.hars) != 0: + self.hars.append(self.client.har) + self.client.new_har("navigate-to-{}".format(url), self.refs) + + def before_navigate_back(self, driver=None): + if driver: + name = "-from-{}".format(driver.current_url) + else: + name = "navigate-back" + self.client.new_page(name) + + def before_navigate_forward(self, driver=None): + if driver: + name = "-from-{}".format(driver.current_url) + else: + name = "navigate-forward" + self.client.new_page(name) + + def before_click(self, element, driver): + name = "click-element-{}".format(element.id) + self.client.new_page(name) + + def before_quit(self, driver): + self.hars.append(self.client.har) diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/Makefile b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/Makefile new file mode 100644 index 000000000..554b2a7cf --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BrowserMobProxy.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BrowserMobProxy.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/BrowserMobProxy" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BrowserMobProxy" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo new file mode 100644 index 000000000..9274f43c2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 22346719ac3713bfe792fb5638c0a301 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html new file mode 100644 index 000000000..d3b83c20f --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html @@ -0,0 +1,98 @@ + + + + + + + + browsermobproxy — BrowserMob Proxy 0.6.0 documentation + + + + + + + + + + + + +

+ +
+
+
+
+ +

Source code for browsermobproxy

+__version__ = '0.5.0'
+
+from server import Server
+from client import Client
+
+__all__ = ['Server', 'Client', 'browsermobproxy']
+
+ +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html new file mode 100644 index 000000000..5650bd363 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html @@ -0,0 +1,90 @@ + + + + + + + + Overview: module code — BrowserMob Proxy 0.6.0 documentation + + + + + + + + + + + + + +
+
+
+
+ +

All modules for which code is available

+ + +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt new file mode 100644 index 000000000..95c74fa9f --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt @@ -0,0 +1,8 @@ +.. toctree:: + :maxdepth: 2 + +:mod:`client` Package +--------------------- +.. automodule:: browsermobproxy +.. autoclass:: Client + :members: diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt new file mode 100644 index 000000000..0a568cf64 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt @@ -0,0 +1,72 @@ +.. BrowserMob Proxy documentation master file, created by + sphinx-quickstart on Fri May 24 12:37:12 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. +.. highlightlang:: python + + + +Welcome to BrowserMob Proxy's documentation! +============================================ + +Python client for the BrowserMob Proxy 2.0 REST API. + +How to install +-------------- + +BrowserMob Proxy is available on PyPI_, so you can install it with ``pip``:: + + $ pip install browsermob-proxy + +Or with `easy_install`:: + + $ easy_install browsermob-proxy + +Or by cloning the repo from GitHub_:: + + $ git clone git://github.com/AutomatedTester/browsermob-proxy-py.git + +Then install it by running:: + + $ python setup.py install + +How to use with selenium-webdriver +---------------------------------- + +Manually:: + + from browsermobproxy import Server + server = Server("path/to/browsermob-proxy") + server.start() + proxy = server.create_proxy() + + from selenium import webdriver + profile = webdriver.FirefoxProfile() + profile.set_proxy(proxy.selenium_proxy()) + driver = webdriver.Firefox(firefox_profile=profile) + + + proxy.new_har("google") + driver.get("http://www.google.co.uk") + proxy.har # returns a HAR JSON blob + + server.stop() + driver.quit() + +Contents: + +.. toctree:: + :maxdepth: 2 + + client.rst + server.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. _GitHub: https://github.com/AutomatedTester/browsermob-proxy-py +.. _PyPI: http://pypi.python.org/pypi/browsermob-proxy diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt new file mode 100644 index 000000000..8af94b70a --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt @@ -0,0 +1,8 @@ +.. toctree:: + :maxdepth: 2 + +:mod:`server` Package +--------------------- +.. automodule:: browsermobproxy +.. autoclass:: Server + :members: \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css new file mode 100644 index 000000000..c959cf0db --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css @@ -0,0 +1,537 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css new file mode 100644 index 000000000..e534a0780 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css @@ -0,0 +1,256 @@ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js new file mode 100644 index 000000000..2036e5f5f --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js @@ -0,0 +1,238 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js new file mode 100644 index 000000000..388377952 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css new file mode 100644 index 000000000..d79caa151 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css @@ -0,0 +1,62 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js new file mode 100644 index 000000000..f5c7e5fee --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js @@ -0,0 +1,622 @@ +/* + * searchtools.js_t + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for the full-text search. + * + * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + + +/** + * Simple result scoring code. + */ +var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + // query found in terms + term: 5 +}; + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, + dataType: "script", cache: true, + complete: function(jqxhr, textstatus) { + if (textstatus != "success") { + document.getElementById("searchindexloader").src = url; + } + }}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + var i; + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + } + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('

' + _('Searching') + '

').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

').appendTo(this.out); + this.output = $('
'); + } + // Prettify the comment rating. + comment.pretty_rating = comment.rating + ' point' + + (comment.rating == 1 ? '' : 's'); + // Make a class (for displaying not yet moderated comments differently) + comment.css_class = comment.displayed ? '' : ' moderate'; + // Create a div for this comment. + var context = $.extend({}, opts, comment); + var div = $(renderTemplate(commentTemplate, context)); + + // If the user has voted on this comment, highlight the correct arrow. + if (comment.vote) { + var direction = (comment.vote == 1) ? 'u' : 'd'; + div.find('#' + direction + 'v' + comment.id).hide(); + div.find('#' + direction + 'u' + comment.id).show(); + } + + if (opts.moderator || comment.text != '[deleted]') { + div.find('a.reply').show(); + if (comment.proposal_diff) + div.find('#sp' + comment.id).show(); + if (opts.moderator && !comment.displayed) + div.find('#cm' + comment.id).show(); + if (opts.moderator || (opts.username == comment.username)) + div.find('#dc' + comment.id).show(); + } + return div; + } + + /** + * A simple template renderer. Placeholders such as <%id%> are replaced + * by context['id'] with items being escaped. Placeholders such as <#id#> + * are not escaped. + */ + function renderTemplate(template, context) { + var esc = $(document.createElement('div')); + + function handle(ph, escape) { + var cur = context; + $.each(ph.split('.'), function() { + cur = cur[this]; + }); + return escape ? esc.text(cur || "").html() : cur; + } + + return template.replace(/<([%#])([\w\.]*)\1>/g, function() { + return handle(arguments[2], arguments[1] == '%' ? true : false); + }); + } + + /** Flash an error message briefly. */ + function showError(message) { + $(document.createElement('div')).attr({'class': 'popup-error'}) + .append($(document.createElement('div')) + .attr({'class': 'error-message'}).text(message)) + .appendTo('body') + .fadeIn("slow") + .delay(2000) + .fadeOut("slow"); + } + + /** Add a link the user uses to open the comments popup. */ + $.fn.comment = function() { + return this.each(function() { + var id = $(this).attr('id').substring(1); + var count = COMMENT_METADATA[id]; + var title = count + ' comment' + (count == 1 ? '' : 's'); + var image = count > 0 ? opts.commentBrightImage : opts.commentImage; + var addcls = count == 0 ? ' nocomment' : ''; + $(this) + .append( + $(document.createElement('a')).attr({ + href: '#', + 'class': 'sphinx-comment-open' + addcls, + id: 'ao' + id + }) + .append($(document.createElement('img')).attr({ + src: image, + alt: 'comment', + title: title + })) + .click(function(event) { + event.preventDefault(); + show($(this).attr('id').substring(2)); + }) + ) + .append( + $(document.createElement('a')).attr({ + href: '#', + 'class': 'sphinx-comment-close hidden', + id: 'ah' + id + }) + .append($(document.createElement('img')).attr({ + src: opts.closeCommentImage, + alt: 'close', + title: 'close' + })) + .click(function(event) { + event.preventDefault(); + hide($(this).attr('id').substring(2)); + }) + ); + }); + }; + + var opts = { + processVoteURL: '/_process_vote', + addCommentURL: '/_add_comment', + getCommentsURL: '/_get_comments', + acceptCommentURL: '/_accept_comment', + deleteCommentURL: '/_delete_comment', + commentImage: '/static/_static/comment.png', + closeCommentImage: '/static/_static/comment-close.png', + loadingImage: '/static/_static/ajax-loader.gif', + commentBrightImage: '/static/_static/comment-bright.png', + upArrow: '/static/_static/up.png', + downArrow: '/static/_static/down.png', + upArrowPressed: '/static/_static/up-pressed.png', + downArrowPressed: '/static/_static/down-pressed.png', + voting: false, + moderator: false + }; + + if (typeof COMMENT_OPTIONS != "undefined") { + opts = jQuery.extend(opts, COMMENT_OPTIONS); + } + + var popupTemplate = '\ +
\ +

\ + Sort by:\ + best rated\ + newest\ + oldest\ +

\ +
Comments
\ +
\ + loading comments...
\ +
    \ +
    \ +

    Add a comment\ + (markup):

    \ +
    \ + reStructured text markup: *emph*, **strong**, \ + ``code``, \ + code blocks: :: and an indented block after blank line
    \ +
    \ + \ +

    \ + \ + Propose a change ▹\ + \ + \ + Propose a change ▿\ + \ +

    \ + \ + \ + \ + \ + \ +
    \ +
    '; + + var commentTemplate = '\ +
    \ +
    \ +
    \ + \ + \ + \ + \ + \ + \ +
    \ +
    \ + \ + \ + \ + \ + \ + \ +
    \ +
    \ +
    \ +

    \ + <%username%>\ + <%pretty_rating%>\ + <%time.delta%>\ +

    \ +
    <#text#>
    \ +

    \ + \ + reply ▿\ + proposal ▹\ + proposal ▿\ + \ + \ +

    \ +
    \
    +<#proposal_diff#>\
    +        
    \ +
      \ +
      \ +
      \ +
      \ + '; + + var replyTemplate = '\ +
    • \ +
      \ +
      \ + \ + \ + \ + \ + \ + \ +
      \ +
    • '; + + $(document).ready(function() { + init(); + }); +})(jQuery); + +$(document).ready(function() { + // add comment anchors for all paragraphs that are commentable + $('.sphinx-has-comment').comment(); + + // highlight search words in search results + $("div.context").each(function() { + var params = $.getQueryParameters(); + var terms = (params.q) ? params.q[0].split(/\s+/) : []; + var result = $(this); + $.each(terms, function() { + result.highlightText(this.toLowerCase(), 'highlighted'); + }); + }); + + // directly open comment window if requested + var anchor = document.location.hash; + if (anchor.substring(0, 9) == '#comment-') { + $('#ao' + anchor.substring(9)).click(); + document.location.hash = '#s' + anchor.substring(9); + } +}); diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html new file mode 100644 index 000000000..8b5de14b9 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html @@ -0,0 +1,404 @@ + + + + + + + + client Package — BrowserMob Proxy 0.6.0 documentation + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +
        +
      +
      +
      +

      client Package

      +
      +
      +class browsermobproxy.Client(url)
      +

      Initialises a new Client object

      +
      +++ + + + +
      Parameters:url – This is where the BrowserMob Proxy lives
      +
      +
      +add_to_capabilities(capabilities)
      +

      Adds an ‘proxy’ entry to a desired capabilities dictionary with the +BrowserMob proxy information

      + +++ + + + +
      Parameters:capabilities – The Desired capabilities object from Selenium WebDriver
      +
      + +
      +
      +basic_authentication(domain, username, password)
      +

      This add automatic basic authentication

      + +++ + + + +
      Parameters:
        +
      • domain – domain to set authentication credentials for
      • +
      • username – valid username to use when authenticating
      • +
      • password – valid password to use when authenticating
      • +
      +
      +
      + +
      +
      +blacklist(regexp, status_code)
      +

      Sets a list of URL patterns to blacklist

      + +++ + + + +
      Parameters:
        +
      • regex – a comma separated list of regular expressions
      • +
      • status_code – the HTTP status code to return for URLs that do not match the blacklist
      • +
      +
      +
      + +
      +
      +clear_dns_cache()
      +

      Clears the DNS cache associated with the proxy instance

      +
      + +
      +
      +close()
      +

      shuts down the proxy and closes the port

      +
      + +
      +
      +har
      +

      Gets the HAR that has been recorded

      +
      + +
      +
      +headers(headers)
      +

      This sets the headers that will set by the proxy on all requests

      + +++ + + + +
      Parameters:headers – this is a dictionary of the headers to be set
      +
      + +
      +
      +limits(options)
      +

      Limit the bandwidth through the proxy.

      + +++ + + + +
      Parameters:options – A dictionary with all the details you want to set. downstreamKbps - Sets the downstream kbps upstreamKbps - Sets the upstream kbps latency - Add the given latency to each HTTP request
      +
      + +
      +
      +new_har(ref=None, options={})
      +

      This sets a new HAR to be recorded

      + +++ + + + +
      Parameters:
        +
      • ref – A reference for the HAR. Defaults to None
      • +
      • options – A dictionary that will be passed to BrowserMob Proxy with specific keywords. Keywords are: captureHeaders - Boolean, capture headers captureContent - Boolean, capture content bodies captureBinaryContent - Boolean, capture binary content
      • +
      +
      +
      + +
      +
      +new_page(ref=None)
      +

      This sets a new page to be recorded

      + +++ + + + +
      Parameters:ref – A reference for the new page. Defaults to None
      +
      + +
      +
      +remap_hosts(address, ip_address)
      +

      Remap the hosts for a specific URL

      + +++ + + + +
      Parameters:
        +
      • address – url that you wish to remap
      • +
      • ip_address – IP Address that will handle all traffic for the address passed in
      • +
      +
      +
      + +
      +
      +request_interceptor(js)
      +

      Executes the javascript against each request

      + +++ + + + +
      Parameters:js – the javascript to execute
      +
      + +
      +
      +response_interceptor(js)
      +

      Executes the javascript against each response

      + +++ + + + +
      Parameters:js – the javascript to execute
      +
      + +
      +
      +retry(retry_count)
      +

      Retries. No idea what its used for, but its in the API...

      + +++ + + + +
      Parameters:retry_count – the number of retries
      +
      + +
      +
      +rewrite_url(match, replace)
      +

      Rewrites the requested url.

      + +++ + + + +
      Parameters:
        +
      • match – a regex to match requests with
      • +
      • replace – unicode a string to replace the matches with
      • +
      +
      +
      + +
      +
      +selenium_proxy()
      +

      Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy

      +
      + +
      +
      +timeouts(options)
      +

      Configure various timeouts in the proxy

      + +++ + + + +
      Parameters:options – A dictionary with all the details you want to set. request - request timeout (in seconds) read - read timeout (in seconds) connection - connection timeout (in seconds) dns - dns lookup timeout (in seconds)
      +
      + +
      +
      +wait_for_traffic_to_stop(quiet_period, timeout)
      +

      Waits for the network to be quiet

      + +++ + + + +
      Parameters:
        +
      • quiet_period – number of seconds the network needs to be quiet for
      • +
      • timeout – max number of seconds to wait
      • +
      +
      +
      + +
      +
      +webdriver_proxy()
      +

      Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy

      +
      + +
      +
      +whitelist(regexp, status_code)
      +

      Sets a list of URL patterns to whitelist

      + +++ + + + +
      Parameters:
        +
      • regex – a comma separated list of regular expressions
      • +
      • status_code – the HTTP status code to return for URLs that do not match the whitelist
      • +
      +
      +
      + + + + + + + + + +
      +
      +

      Previous topic

      +

      Welcome to BrowserMob Proxy’s documentation!

      +

      Next topic

      +

      server Package

      +

      This Page

      + + + +
      +
      +
      + + + + + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html new file mode 100644 index 000000000..6976d22e6 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html @@ -0,0 +1,297 @@ + + + + + + + + + Index — BrowserMob Proxy 0.6.0 documentation + + + + + + + + + + + + + +
      +
      +
      +
      + + +

      Index

      + +
      + A + | B + | C + | H + | L + | N + | R + | S + | T + | U + | W + +
      +

      A

      + + +
      + +
      add_to_capabilities() (browsermobproxy.Client method) +
      + +
      + +

      B

      + + + +
      + +
      basic_authentication() (browsermobproxy.Client method) +
      + + +
      blacklist() (browsermobproxy.Client method) +
      + +
      + +
      browsermobproxy (module), [1] +
      + +
      + +

      C

      + + + +
      + +
      clear_dns_cache() (browsermobproxy.Client method) +
      + + +
      Client (class in browsermobproxy) +
      + +
      + +
      close() (browsermobproxy.Client method) +
      + + +
      create_proxy() (browsermobproxy.Server method) +
      + +
      + +

      H

      + + + +
      + +
      har (browsermobproxy.Client attribute) +
      + +
      + +
      headers() (browsermobproxy.Client method) +
      + +
      + +

      L

      + + +
      + +
      limits() (browsermobproxy.Client method) +
      + +
      + +

      N

      + + + +
      + +
      new_har() (browsermobproxy.Client method) +
      + +
      + +
      new_page() (browsermobproxy.Client method) +
      + +
      + +

      R

      + + + +
      + +
      remap_hosts() (browsermobproxy.Client method) +
      + + +
      request_interceptor() (browsermobproxy.Client method) +
      + + +
      response_interceptor() (browsermobproxy.Client method) +
      + +
      + +
      retry() (browsermobproxy.Client method) +
      + + +
      rewrite_url() (browsermobproxy.Client method) +
      + +
      + +

      S

      + + + +
      + +
      selenium_proxy() (browsermobproxy.Client method) +
      + + +
      Server (class in browsermobproxy) +
      + +
      + +
      start() (browsermobproxy.Server method) +
      + + +
      stop() (browsermobproxy.Server method) +
      + +
      + +

      T

      + + +
      + +
      timeouts() (browsermobproxy.Client method) +
      + +
      + +

      U

      + + +
      + +
      url (browsermobproxy.Server attribute) +
      + +
      + +

      W

      + + + +
      + +
      wait_for_traffic_to_stop() (browsermobproxy.Client method) +
      + + +
      webdriver_proxy() (browsermobproxy.Client method) +
      + +
      + +
      whitelist() (browsermobproxy.Client method) +
      + +
      + + + +
      +
      +
      +
      +
      + + + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html new file mode 100644 index 000000000..59fa22e29 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html @@ -0,0 +1,174 @@ + + + + + + + + Welcome to BrowserMob Proxy’s documentation! — BrowserMob Proxy 0.6.0 documentation + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Welcome to BrowserMob Proxy’s documentation!

      +

      Python client for the BrowserMob Proxy 2.0 REST API.

      +
      +

      How to install

      +

      BrowserMob Proxy is available on PyPI, so you can install it with pip:

      +
      $ pip install browsermob-proxy
      +
      +
      +

      Or with easy_install:

      +
      $ easy_install browsermob-proxy
      +
      +
      +

      Or by cloning the repo from GitHub:

      +
      $ git clone git://github.com/AutomatedTester/browsermob-proxy-py.git
      +
      +
      +

      Then install it by running:

      +
      $ python setup.py install
      +
      +
      +
      +
      +

      How to use with selenium-webdriver

      +

      Manually:

      +
      from browsermobproxy import Server
      +server = Server("path/to/browsermob-proxy")
      +server.start()
      +proxy = server.create_proxy()
      +
      +from selenium import webdriver
      +profile  = webdriver.FirefoxProfile()
      +profile.set_proxy(proxy.selenium_proxy())
      +driver = webdriver.Firefox(firefox_profile=profile)
      +
      +
      +proxy.new_har("google")
      +driver.get("http://www.google.co.uk")
      +proxy.har # returns a HAR JSON blob
      +
      +server.stop()
      +driver.quit()
      +
      +
      +

      Contents:

      + +
      +
      +
      +

      Indices and tables

      + +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Next topic

      +

      client Package

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv new file mode 100644 index 000000000..92c3c7bbd --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv @@ -0,0 +1,6 @@ +# Sphinx inventory version 2 +# Project: BrowserMob Proxy +# Version: 0.6.0 +# The remainder of this file is compressed using zlib. +xڥTn0 kѸriIM 4 ӀMdǺ~{ Qp_}UPDz˱a:!r m/smj78 +rMqHX<)9r~kvdPP{|bCmVm'4 Y,(ŮxPejMWX/F qcO1#均Fc<)XKcKDKO*cJ"kǨ|(S+NŲ#' 7m2oF 5F۔dEŕCtX"33n<3sw4_ \zߝiY/>seq(@EK^z/z \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html new file mode 100644 index 000000000..08a48fbfd --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html @@ -0,0 +1,112 @@ + + + + + + + + Python Module Index — BrowserMob Proxy 0.6.0 documentation + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      + + +

      Python Module Index

      + +
      + b +
      + + + + + + + +
       
      + b
      + browsermobproxy +
      + + +
      +
      +
      +
      +
      + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html new file mode 100644 index 000000000..9adb77d91 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html @@ -0,0 +1,105 @@ + + + + + + + + Search — BrowserMob Proxy 0.6.0 documentation + + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      + +

      Search

      +
      + +

      + Please activate JavaScript to enable the search + functionality. +

      +
      +

      + From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

      +
      + + + +
      + +
      + +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js new file mode 100644 index 000000000..cf2f7d0f2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({envversion:42,terms:{all:[1,2],basic_authent:1,execut:1,whitelist:1,rest:0,bandwidth:1,code:1,comma:1,kbp:1,paramet:[1,2],profil:0,configur:1,param:[],should:2,quiet_period:1,add:1,latenc:1,dict:[],blob:0,har:[0,1],pass:1,match:1,"return":[0,1],string:1,variou:1,get:[0,1,2],read:1,browsermobproxi:[0,1,2],express:1,stop:[0,2],number:1,repo:0,capturecont:1,traffic:1,upstream:1,password:1,ip_address:1,specif:1,list:1,authent:1,server:[],separ:1,retry_count:1,api:[0,1],timeout:1,each:1,through:1,unicod:1,where:1,page:[0,1],www:0,set:[1,2],captur:1,manual:0,idea:1,second:1,remap:1,connect:[1,2],domain:1,arg:2,close:1,process:2,port:[1,2],index:0,statu:1,network:1,item:2,pattern:1,content:[0,1],rewrit:1,retri:1,max:1,"import":0,ref:1,refer:1,shut:1,run:[0,2],webdriver_proxi:1,javascript:1,bodi:1,host:1,dictionari:[1,2],address:1,path:[0,2],wait:[1,2],search:0,quiet:1,against:1,initialis:[1,2],instanc:1,request_interceptor:1,rewrite_url:1,com:0,firefox:0,status_cod:1,modul:0,easy_instal:0,automat:1,down:1,header:1,"boolean":1,empti:2,wait_for_traffic_to_stop:1,regexp:1,regex:1,quit:0,given:1,git:0,from:[0,1],interact:2,json:0,been:1,avail:0,start:[0,2],live:1,basic:1,upstreamkbp:1,until:2,more:2,automatedtest:0,desir:1,option:[1,2],python:0,blacklist:1,hold:2,capturebinarycont:1,cach:1,clear_dns_cach:1,none:1,keyword:1,"default":[1,2],wish:1,setup:0,batch:2,record:1,limit:1,can:[0,2],str:[],remap_host:1,firefoxprofil:0,new_har:[0,1],"int":[],request:1,selenium_proxi:[0,1],capturehead:1,downstreamkbp:1,need:[1,2],replac:1,file:2,pip:0,create_proxi:[0,2],mai:2,downstream:1,want:1,when:1,detail:[1,2],binari:1,valid:1,lookup:1,futur:2,"new":1,you:[0,1,2],respons:1,add_to_cap:1,http:[0,1],allow:2,usernam:1,pypi:0,clone:0,object:[1,2],driver:0,what:1,firefox_profil:0,capabl:1,regular:1,set_proxi:0,associ:1,"class":[1,2],googl:0,handl:1,github:0,response_interceptor:1,url:[1,2],entri:1,clear:1,credenti:1,inform:1,client:[],thi:[1,2],new_pag:1},objtypes:{"0":"py:module","1":"py:method","2":"py:attribute","3":"py:class"},objnames:{"0":["py","module","Python module"],"1":["py","method","Python method"],"2":["py","attribute","Python attribute"],"3":["py","class","Python class"]},filenames:["index","client","server"],titles:["Welcome to BrowserMob Proxy’s documentation!","client Package","server Package"],objects:{"":{browsermobproxy:[2,0,0,"-"]},browsermobproxy:{Client:[1,3,1,""],Server:[2,3,1,""]},"browsermobproxy.Server":{url:[2,2,1,""],start:[2,1,1,""],stop:[2,1,1,""],create_proxy:[2,1,1,""]},"browsermobproxy.Client":{remap_hosts:[1,1,1,""],blacklist:[1,1,1,""],selenium_proxy:[1,1,1,""],response_interceptor:[1,1,1,""],webdriver_proxy:[1,1,1,""],clear_dns_cache:[1,1,1,""],timeouts:[1,1,1,""],whitelist:[1,1,1,""],new_page:[1,1,1,""],basic_authentication:[1,1,1,""],retry:[1,1,1,""],request_interceptor:[1,1,1,""],limits:[1,1,1,""],wait_for_traffic_to_stop:[1,1,1,""],rewrite_url:[1,1,1,""],close:[1,1,1,""],har:[1,2,1,""],add_to_capabilities:[1,1,1,""],headers:[1,1,1,""],new_har:[1,1,1,""]}},titleterms:{welcom:0,packag:[1,2],browsermob:0,selenium:0,webdriv:0,server:2,how:0,client:1,indic:0,tabl:0,instal:0,document:0,proxi:0}}) \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html new file mode 100644 index 000000000..74146e4ce --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html @@ -0,0 +1,157 @@ + + + + + + + + server Package — BrowserMob Proxy 0.6.0 documentation + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +
        +
      +
      +
      +

      server Package

      +
      +
      +class browsermobproxy.Server(path='browsermob-proxy', options={})
      +

      Initialises a Server object

      + +++ + + + + + +
      Args:
      Parameters:
        +
      • path – Path to the browsermob proxy batch file
      • +
      • options – Dictionary that can hold the port. More items will be added in the future. This defaults to an empty dictionary
      • +
      +
      +
      +
      +create_proxy()
      +

      Gets a client class that allow to set all the proxy details that you +may need to.

      +
      + +
      +
      +start()
      +

      This will start the browsermob proxy and then wait until it can +interact with it

      +
      + +
      +
      +stop()
      +

      This will stop the process running the proxy

      +
      + +
      +
      +url
      +

      Gets the url that the proxy is running on. This is not the URL clients +should connect to.

      +
      + +
      + +
      + + +
      +
      +
      +
      +
      +

      Previous topic

      +

      client Package

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/client.rst b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/client.rst new file mode 100644 index 000000000..95c74fa9f --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/client.rst @@ -0,0 +1,8 @@ +.. toctree:: + :maxdepth: 2 + +:mod:`client` Package +--------------------- +.. automodule:: browsermobproxy +.. autoclass:: Client + :members: diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/conf.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/conf.py new file mode 100644 index 000000000..961c48907 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/conf.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +# +# BrowserMob Proxy documentation build configuration file, created by +# sphinx-quickstart on Fri May 24 12:37:12 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('../')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.autodoc'] +autoclass_content = 'both' + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'BrowserMob Proxy' +copyright = u'2014, David Burns' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.6.0' +# The full version, including alpha/beta/rc tags. +release = '0.6.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'BrowserMobProxydoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'BrowserMobProxy.tex', u'BrowserMob Proxy Documentation', + u'David Burns', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'browsermobproxy', u'BrowserMob Proxy Documentation', + [u'David Burns'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'BrowserMobProxy', u'BrowserMob Proxy Documentation', + u'David Burns', 'BrowserMobProxy', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/index.rst b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/index.rst new file mode 100644 index 000000000..0a568cf64 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/index.rst @@ -0,0 +1,72 @@ +.. BrowserMob Proxy documentation master file, created by + sphinx-quickstart on Fri May 24 12:37:12 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. +.. highlightlang:: python + + + +Welcome to BrowserMob Proxy's documentation! +============================================ + +Python client for the BrowserMob Proxy 2.0 REST API. + +How to install +-------------- + +BrowserMob Proxy is available on PyPI_, so you can install it with ``pip``:: + + $ pip install browsermob-proxy + +Or with `easy_install`:: + + $ easy_install browsermob-proxy + +Or by cloning the repo from GitHub_:: + + $ git clone git://github.com/AutomatedTester/browsermob-proxy-py.git + +Then install it by running:: + + $ python setup.py install + +How to use with selenium-webdriver +---------------------------------- + +Manually:: + + from browsermobproxy import Server + server = Server("path/to/browsermob-proxy") + server.start() + proxy = server.create_proxy() + + from selenium import webdriver + profile = webdriver.FirefoxProfile() + profile.set_proxy(proxy.selenium_proxy()) + driver = webdriver.Firefox(firefox_profile=profile) + + + proxy.new_har("google") + driver.get("http://www.google.co.uk") + proxy.har # returns a HAR JSON blob + + server.stop() + driver.quit() + +Contents: + +.. toctree:: + :maxdepth: 2 + + client.rst + server.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. _GitHub: https://github.com/AutomatedTester/browsermob-proxy-py +.. _PyPI: http://pypi.python.org/pypi/browsermob-proxy diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/make.bat b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/make.bat new file mode 100644 index 000000000..567b0da4a --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\BrowserMobProxy.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\BrowserMobProxy.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/server.rst b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/server.rst new file mode 100644 index 000000000..8af94b70a --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/server.rst @@ -0,0 +1,8 @@ +.. toctree:: + :maxdepth: 2 + +:mod:`server` Package +--------------------- +.. automodule:: browsermobproxy +.. autoclass:: Server + :members: \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/readme.md b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/readme.md new file mode 100644 index 000000000..913610f66 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/readme.md @@ -0,0 +1,88 @@ +browsermob-proxy-py +=================== + +Python client for the BrowserMob Proxy 2.0 REST API. + + + +How to use with selenium-webdriver +---------------------------------- + +Manually: + +``` python +from browsermobproxy import Server +server = Server("path/to/browsermob-proxy") +server.start() +proxy = server.create_proxy() + +from selenium import webdriver +profile = webdriver.FirefoxProfile() +profile.set_proxy(proxy.selenium_proxy()) +driver = webdriver.Firefox(firefox_profile=profile) + + +proxy.new_har("google") +driver.get("http://www.google.co.uk") +proxy.har # returns a HAR JSON blob + +server.stop() +driver.quit() + +``` + +for Chrome use + +``` +chrome_options = webdriver.ChromeOptions() +chrome_options.add_argument("--proxy-server={0}".format(proxy.proxy)) +browser = webdriver.Chrome(chrome_options = chrome_options) +``` + +Running Tests +------------- +To run the tests in a CI environment, disable the ones that require human +judgement by using + +```bash +$ py.test -m "not human" test +``` + +If you are going to watch the test, the 'human' ones should display an english +muffin instead of the american flag on the 'pick your version' page. Or at +least it does from Canada. + + +See also +-------- + +* http://proxy.browsermob.com/ +* https://github.com/webmetrics/browsermob-proxy + +Note on Patches/Pull Requests +----------------------------- + +* Fork the project. +* Make your feature addition or bug fix. +* Add tests for it. This is important so I don't break it in a + future version unintentionally. +* Send me a pull request. Bonus points for topic branches. + +Copyright +--------- + +Copyright 2011 David Burns + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/setup.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/setup.py new file mode 100644 index 000000000..f6581cf08 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/setup.py @@ -0,0 +1,20 @@ +from setuptools import setup, find_packages + +setup(name='browsermob-proxy', + version='0.6.0', + description='A library for interacting with the Browsermob Proxy', + author='David Burns', + author_email='david.burns at theautomatedtester dot co dot uk', + url='http://oss.theautomatedtester.co.uk/browsermob-proxy-py', + classifiers=['Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Topic :: Software Development :: Testing', + 'Topic :: Software Development :: Libraries', + 'Programming Language :: Python'], + packages = find_packages(), + install_requires=['requests>=1.1.0'], + ) diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_client.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_client.py new file mode 100644 index 000000000..8968b103f --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_client.py @@ -0,0 +1,247 @@ +import os.path +import pytest +import sys + + +def setup_module(module): + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +class TestClient(object): + def setup_method(self, method): + from browsermobproxy.client import Client + self.client = Client("localhost:9090") + + def teardown_method(self, method): + self.client.close() + + def test_headers_type(self): + """ + /proxy/:port/headers needs to take a dictionary + """ + with pytest.raises(TypeError): + self.client.headers(['foo']) + + def test_headers_content(self): + """ + /proxy/:port/headers needs to take a dictionary + and returns 200 when its successful + """ + s = self.client.headers({'User-Agent': 'rubber ducks floating in a row'}) + assert(s == 200) + + def test_new_har(self): + """ + /proxy/:port/har + and returns 204 when creating a har with a particular name the first time + and returns 200 and the previous har when creating one with the same name + """ + status_code, har = self.client.new_har() + assert(status_code == 204) + assert(har is None) + status_code, har = self.client.new_har() + assert(status_code == 200) + assert('log' in har) + + def test_new_har(self): + """ + /proxy/:port/har + and returns 204 when creating a har with a particular name the first time + and returns 200 and the previous har when creating one with the same name + """ + status_code, har = self.client.new_har("elephants") + assert(status_code == 204) + assert(har is None) + status_code, har = self.client.new_har("elephants") + assert(status_code == 200) + assert('elephants' == har["log"]["pages"][0]['id']) + + def test_new_page_defaults(self): + """ + /proxy/:port/pageRef + adds a new page of 'Page N' when no page name is given + """ + self.client.new_har() + self.client.new_page() + har = self.client.har + assert(len(har["log"]["pages"]) == 2) + assert(har["log"]["pages"][1]["id"] == "Page 2") + + def test_new_named_page(self): + """ + /proxy/:port/pageRef + adds a new page of 'buttress' + """ + self.client.new_har() + self.client.new_page('buttress') + har = self.client.har + assert(len(har["log"]["pages"]) == 2) + assert(har["log"]["pages"][1]["id"] == "buttress") + + def test_single_whitelist(self): + """ + /proxy/:port/whitelist + adds a whitelist + """ + status_code = self.client.whitelist("http://www\\.facebook\\.com/.*", 200) + assert(status_code == 200) + + def test_multiple_whitelists(self): + """ + /proxy/:port/whitelist + adds a whitelist + """ + status_code = self.client.whitelist("http://www\\.facebook\\.com/.*,http://cdn\\.twitter\\.com", 200) + assert(status_code == 200) + + def test_blacklist(self): + """ + /proxy/:port/blacklist + adds a blacklist + """ + status_code = self.client.blacklist("http://www\\.facebook\\.com/.*", 200) + assert(status_code == 200) + + def test_basic_authentication(self): + """ + /proxy/:port/auth/basic + adds automatic basic authentication + """ + status_code = self.client.basic_authentication("www.example.com", "myUsername", "myPassword") + assert(status_code == 200) + + def test_limits_invalid_key(self): + """ + /proxy/:port/limits + pre-sending checking that the parameter is correct + """ + with pytest.raises(KeyError): + self.client.limits({"hurray": "explosions"}) + + def test_limits_key_no_value(self): + """ + /proxy/:port/limits + pre-sending checking that a parameter exists + """ + with pytest.raises(KeyError): + self.client.limits({}) + + def test_limits_all_key_values(self): + """ + /proxy/:port/limits + can send all 3 at once based on the proxy implementation + """ + limits = {"upstream_kbps": 320, "downstream_kbps": 560, "latency": 30} + status_code = self.client.limits(limits) + assert(status_code == 200) + + def test_rewrite(self): + """ + /proxy/:port/rewrite + + """ + match = "/foo" + replace = "/bar" + status_code = self.client.rewrite_url(match, replace) + assert(status_code == 200) + + def test_close(self): + """ + /proxy/:port + close the proxy port + """ + status_code = self.client.close() + assert(status_code == 200) + status_code = self.client.close() + assert(status_code == 404) + + def test_response_interceptor_with_parsing_js(self): + """ + /proxy/:port/interceptor/response + """ + js = 'alert("foo")' + status_code = self.client.response_interceptor(js) + assert(status_code == 200) + + def test_response_interceptor_with_invalid_js(self): + """ + /proxy/:port/interceptor/response + """ + js = 'alert("foo"' + status_code = self.client.response_interceptor(js) + assert(status_code == 500) + + def test_request_interceptor_with_parsing_js(self): + """ + /proxy/:port/interceptor/request + """ + js = 'alert("foo")' + status_code = self.client.request_interceptor(js) + assert(status_code == 200) + + def test_request_interceptor_with_invalid_js(self): + """ + /proxy/:port/interceptor/request + """ + js = 'alert("foo"' + status_code = self.client.request_interceptor(js) + assert(status_code == 500) + + def test_timeouts_invalid_timeouts(self): + """ + /proxy/:port/timeout + pre-sending checking that the parameter is correct + """ + with pytest.raises(KeyError): + self.client.timeouts({"hurray": "explosions"}) + + def test_timeouts_key_no_value(self): + """ + /proxy/:port/timeout + pre-sending checking that a parameter exists + """ + with pytest.raises(KeyError): + self.client.timeouts({}) + + def test_timeouts_all_key_values(self): + """ + /proxy/:port/timeout + can send all 3 at once based on the proxy implementation + """ + timeouts = {"request": 2, "read": 2, "connection": 2, "dns": 3} + status_code = self.client.timeouts(timeouts) + assert(status_code == 200) + + def test_remap_hosts(self): + """ + /proxy/:port/hosts + """ + status_code = self.client.remap_hosts("example.com", "1.2.3.4") + assert(status_code == 200) + + def test_wait_for_traffic_to_stop(self): + """ + /proxy/:port/wait + """ + status_code = self.client.wait_for_traffic_to_stop(2000, 10000) + assert(status_code == 200) + + def test_clear_dns_cache(self): + """ + /proxy/:port/dns/cache + """ + status_code = self.client.clear_dns_cache() + assert(status_code == 200) + + def test_rewrite_url(self): + """ + /proxy/:port/rewrite + """ + status_code = self.client.rewrite_url('http://www.facebook\.com', 'http://microsoft.com') + assert(status_code == 200) + + def test_retry(self): + """ + /proxy/:port/retry + """ + status_code = self.client.retry(4) + assert(status_code == 200) diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_remote.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_remote.py new file mode 100644 index 000000000..9a002f109 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_remote.py @@ -0,0 +1,31 @@ +from selenium import webdriver +import selenium.webdriver.common.desired_capabilities +import os +import sys +import time +import pytest + +def setup_module(module): + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +class TestRemote(object): + def setup_method(self, method): + from browsermobproxy.client import Client + self.client = Client("localhost:9090") + + def teardown_method(self, method): + self.client.close() + + @pytest.mark.human + def test_i_want_my_remote(self): + driver = webdriver.Remote(desired_capabilities=selenium.webdriver.common.desired_capabilities.DesiredCapabilities.FIREFOX, + proxy=self.client) + + self.client.new_har("mtv") + targetURL = "http://www.mtv.com" + self.client.rewrite_url(".*american_flag-384x450\\.jpg", "http://www.foodsubs.com/Photos/englishmuffin.jpg") + + driver.get(targetURL) + time.sleep(5) + + driver.quit() diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_webdriver.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_webdriver.py new file mode 100644 index 000000000..4a35e7162 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_webdriver.py @@ -0,0 +1,60 @@ +from selenium import webdriver +import selenium.webdriver.common.desired_capabilities +from selenium.webdriver.common.proxy import Proxy +import os +import sys +import copy +import time +import pytest + +def setup_module(module): + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +class TestWebDriver(object): + def setup_method(self, method): + from browsermobproxy.client import Client + self.client = Client("localhost:9090") + + def teardown_method(self, method): + self.client.close() + + @pytest.mark.human + def test_i_want_my_by_capability(self): + capabilities = selenium.webdriver.common.desired_capabilities.DesiredCapabilities.FIREFOX + self.client.add_to_capabilities(capabilities) + driver = webdriver.Firefox(capabilities=capabilities) + + self.client.new_har("mtv") + targetURL = "http://www.mtv.com" + self.client.rewrite_url(".*american_flag-384x450\\.jpg", "http://www.foodsubs.com/Photos/englishmuffin.jpg") + + driver.get(targetURL) + + time.sleep(5) + + driver.quit() + + @pytest.mark.human + def test_i_want_my_by_proxy_object(self): + driver = webdriver.Firefox(proxy=self.client) + + self.client.new_har("mtv") + targetURL = "http://www.mtv.com" + self.client.rewrite_url(".*american_flag-384x450\\.jpg", "http://www.foodsubs.com/Photos/englishmuffin.jpg") + + driver.get(targetURL) + + time.sleep(5) + + driver.quit() + + def test_what_things_look_like(self): + bmp_capabilities = copy.deepcopy(selenium.webdriver.common.desired_capabilities.DesiredCapabilities.FIREFOX) + self.client.add_to_capabilities(bmp_capabilities) + + proxy_capabilities = copy.deepcopy(selenium.webdriver.common.desired_capabilities.DesiredCapabilities.FIREFOX) + proxy_addr = 'localhost:{}'.format(self.client.port) + proxy = Proxy({'httpProxy': proxy_addr,'sslProxy': proxy_addr}) + proxy.add_to_capabilities(proxy_capabilities) + + assert bmp_capabilities == proxy_capabilities diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob.py b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob.py new file mode 100644 index 000000000..e4c3cb545 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob.py @@ -0,0 +1,80 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os + +from browsermobproxy import Server +from marionette_harness import MarionetteTestCase + + +class BrowserMobProxyArguments(object): + name = 'Browsermob Proxy' + args = [ + [['--browsermob-script'], + {'help': 'path to the browsermob-proxy shell script or batch file', + }], + [['--browsermob-port'], + {'type': int, + 'help': 'port to run the browsermob proxy on', + }], + ] + + def verify_usage_handler(self, args): + if args.browsermob_script is not None: + if not os.path.exists(args.browsermob_script): + raise ValueError('{} not found'.format(args.browsermob_script)) + + +class BrowserMobProxyTestCaseMixin(object): + + def __init__(self, *args, **kwargs): + self.browsermob_server = None + self.browsermob_port = kwargs.pop('browsermob_port') + self.browsermob_script = kwargs.pop('browsermob_script') + + def setUp(self): + options = {} + if self.browsermob_port: + options['port'] = self.browsermob_port + if not self.browsermob_script: + raise ValueError('Must specify --browsermob-script in order to ' + 'run browsermobproxy tests') + self.browsermob_server = Server( + self.browsermob_script, options=options) + self.browsermob_server.start() + + def create_browsermob_proxy(self): + client = self.browsermob_server.create_proxy() + with self.marionette.using_context('chrome'): + self.marionette.execute_script(""" + Components.utils.import("resource://gre/modules/Preferences.jsm"); + Preferences.set("network.proxy.type", 1); + Preferences.set("network.proxy.http", "localhost"); + Preferences.set("network.proxy.http_port", {port}); + Preferences.set("network.proxy.ssl", "localhost"); + Preferences.set("network.proxy.ssl_port", {port}); + """.format(port=client.port)) + return client + + def tearDown(self): + if self.browsermob_server: + self.browsermob_server.stop() + self.browsermob_server = None + + __del__ = tearDown + + +class BrowserMobTestCase(MarionetteTestCase, BrowserMobProxyTestCaseMixin): + + def __init__(self, *args, **kwargs): + MarionetteTestCase.__init__(self, *args, **kwargs) + BrowserMobProxyTestCaseMixin.__init__(self, *args, **kwargs) + + def setUp(self): + MarionetteTestCase.setUp(self) + BrowserMobProxyTestCaseMixin.setUp(self) + + def tearDown(self): + BrowserMobProxyTestCaseMixin.tearDown(self) + MarionetteTestCase.tearDown(self) diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py new file mode 100644 index 000000000..4316c75d7 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py @@ -0,0 +1,129 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys + +from marionette_driver import By, Wait + + +class WindowManagerMixin(object): + + _menu_item_new_tab = (By.ID, "menu_newNavigatorTab") + + def setUp(self): + super(WindowManagerMixin, self).setUp() + + self.start_window = self.marionette.current_chrome_window_handle + self.start_windows = self.marionette.chrome_window_handles + + self.start_tab = self.marionette.current_window_handle + self.start_tabs = self.marionette.window_handles + + def tearDown(self): + if len(self.marionette.chrome_window_handles) != len(self.start_windows): + raise Exception("Not all windows as opened by the test have been closed") + + if len(self.marionette.window_handles) != len(self.start_tabs): + raise Exception("Not all tabs as opened by the test have been closed") + + super(WindowManagerMixin, self).tearDown() + + def close_all_tabs(self): + current_window_handles = self.marionette.window_handles + + # If the start tab is not present anymore, use the next one of the list + if self.start_tab not in current_window_handles: + self.start_tab = current_window_handles[0] + + current_window_handles.remove(self.start_tab) + for handle in current_window_handles: + self.marionette.switch_to_window(handle) + self.marionette.close() + + # Bug 1311350 - close() doesn't wait for tab to be closed. + Wait(self.marionette).until( + lambda mn: handle not in mn.window_handles, + message="Failed to close tab with handle {}".format(handle) + ) + + self.marionette.switch_to_window(self.start_tab) + + def close_all_windows(self): + current_chrome_window_handles = self.marionette.chrome_window_handles + + # If the start window is not present anymore, use the next one of the list + if self.start_window not in current_chrome_window_handles: + self.start_window = current_chrome_window_handles[0] + current_chrome_window_handles.remove(self.start_window) + + with self.marionette.using_context("chrome"): + for handle in current_chrome_window_handles: + self.marionette.switch_to_window(handle) + self.marionette.close_chrome_window() + + # Bug 1311350 - close_chrome_window() doesn't wait for window to be closed. + Wait(self.marionette).until( + lambda mn: handle not in mn.chrome_window_handles, + message="Failed to close window with handle {}".format(handle) + ) + + self.marionette.switch_to_window(self.start_window) + + def open_tab(self, trigger="menu"): + current_tabs = self.marionette.window_handles + + try: + if callable(trigger): + trigger() + elif trigger == 'menu': + with self.marionette.using_context("chrome"): + self.marionette.find_element(*self._menu_item_new_tab).click() + except Exception: + exc, val, tb = sys.exc_info() + raise exc, 'Failed to trigger opening a new tab: {}'.format(val), tb + else: + Wait(self.marionette).until( + lambda mn: len(mn.window_handles) == len(current_tabs) + 1, + message="No new tab has been opened" + ) + + [new_tab] = list(set(self.marionette.window_handles) - set(current_tabs)) + + return new_tab + + def open_window(self, trigger=None): + current_windows = self.marionette.chrome_window_handles + + def loaded(handle): + with self.marionette.using_context("chrome"): + return self.marionette.execute_script(""" + Components.utils.import("resource://gre/modules/Services.jsm"); + + let win = Services.wm.getOuterWindowWithId(Number(arguments[0])); + return win.document.readyState == "complete"; + """, script_args=[handle]) + + try: + if callable(trigger): + trigger() + else: + with self.marionette.using_context("chrome"): + self.marionette.execute_script("window.open();") + except Exception: + exc, val, tb = sys.exc_info() + raise exc, 'Failed to trigger opening a new window: {}'.format(val), tb + else: + Wait(self.marionette).until( + lambda mn: len(mn.chrome_window_handles) == len(current_windows) + 1, + message="No new window has been opened" + ) + + [new_window] = list(set(self.marionette.chrome_window_handles) - set(current_windows)) + + # Before continuing ensure the window has been completed loading + Wait(self.marionette).until( + lambda _: loaded(new_window), + message="Window with handle '{}'' did not finish loading".format(new_window)) + + return new_window diff --git a/testing/marionette/harness/marionette_harness/runner/serve.py b/testing/marionette/harness/marionette_harness/runner/serve.py new file mode 100755 index 000000000..1969c9170 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/serve.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Spawns necessary HTTP servers for testing Marionette in child +processes. + +""" + +import argparse +import multiprocessing +import os +import sys + +from collections import defaultdict + +import httpd + + +__all__ = ["default_doc_root", + "iter_proc", + "iter_url", + "registered_servers", + "servers", + "start", + "where_is"] +here = os.path.abspath(os.path.dirname(__file__)) + + +class BlockingChannel(object): + + def __init__(self, channel): + self.chan = channel + self.lock = multiprocessing.Lock() + + def call(self, func, args=()): + self.send((func, args)) + return self.recv() + + def send(self, *args): + try: + self.lock.acquire() + self.chan.send(args) + finally: + self.lock.release() + + def recv(self): + try: + self.lock.acquire() + payload = self.chan.recv() + if isinstance(payload, tuple) and len(payload) == 1: + return payload[0] + return payload + except KeyboardInterrupt: + return ("stop", ()) + finally: + self.lock.release() + + +class ServerProxy(multiprocessing.Process, BlockingChannel): + + def __init__(self, channel, init_func, *init_args, **init_kwargs): + multiprocessing.Process.__init__(self) + BlockingChannel.__init__(self, channel) + self.init_func = init_func + self.init_args = init_args + self.init_kwargs = init_kwargs + + def run(self): + server = self.init_func(*self.init_args, **self.init_kwargs) + server.start(block=False) + + try: + while True: + # ["func", ("arg", ...)] + # ["prop", ()] + sattr, fargs = self.recv() + attr = getattr(server, sattr) + + # apply fargs to attr if it is a function + if callable(attr): + rv = attr(*fargs) + + # otherwise attr is a property + else: + rv = attr + + self.send(rv) + + if sattr == "stop": + return + + except KeyboardInterrupt: + server.stop() + + +class ServerProc(BlockingChannel): + + def __init__(self, init_func): + self._init_func = init_func + self.proc = None + + parent_chan, self.child_chan = multiprocessing.Pipe() + BlockingChannel.__init__(self, parent_chan) + + def start(self, doc_root, ssl_config, **kwargs): + self.proc = ServerProxy( + self.child_chan, self._init_func, doc_root, ssl_config, **kwargs) + self.proc.daemon = True + self.proc.start() + + def get_url(self, url): + return self.call("get_url", (url,)) + + @property + def doc_root(self): + return self.call("doc_root", ()) + + def stop(self): + self.call("stop") + if not self.is_alive: + return + self.proc.join() + + def kill(self): + if not self.is_alive: + return + self.proc.terminate() + self.proc.join(0) + + @property + def is_alive(self): + if self.proc is not None: + return self.proc.is_alive() + return False + + +def http_server(doc_root, ssl_config, host="127.0.0.1", **kwargs): + return httpd.FixtureServer(doc_root, url="http://{}:0/".format(host), **kwargs) + + +def https_server(doc_root, ssl_config, host="127.0.0.1", **kwargs): + return httpd.FixtureServer(doc_root, + url="https://{}:0/".format(host), + ssl_key=ssl_config["key_path"], + ssl_cert=ssl_config["cert_path"], + **kwargs) + + +def start_servers(doc_root, ssl_config, **kwargs): + servers = defaultdict() + for schema, builder_fn in registered_servers: + proc = ServerProc(builder_fn) + proc.start(doc_root, ssl_config, **kwargs) + servers[schema] = (proc.get_url("/"), proc) + return servers + + +def start(doc_root=None, **kwargs): + """Start all relevant test servers. + + If no `doc_root` is given the default + testing/marionette/harness/marionette_harness/www directory will be used. + + Additional keyword arguments can be given which will be passed on + to the individual ``FixtureServer``'s in httpd.py. + + """ + doc_root = doc_root or default_doc_root + ssl_config = {"cert_path": httpd.default_ssl_cert, + "key_path": httpd.default_ssl_key} + + global servers + servers = start_servers(doc_root, ssl_config, **kwargs) + return servers + + +def where_is(uri, on="http"): + """Returns the full URL, including scheme, hostname, and port, for + a fixture resource from the server associated with the ``on`` key. + It will by default look for the resource in the "http" server. + + """ + return servers.get(on)[1].get_url(uri) + + +def iter_proc(servers): + for _, (_, proc) in servers.iteritems(): + yield proc + + +def iter_url(servers): + for _, (url, _) in servers.iteritems(): + yield url + + +default_doc_root = os.path.join(os.path.dirname(here), "www") +registered_servers = [("http", http_server), + ("https", https_server)] +servers = defaultdict() + + +def main(args): + global servers + + parser = argparse.ArgumentParser() + parser.add_argument("-r", dest="doc_root", + help="Path to document root. Overrides default.") + args = parser.parse_args() + + servers = start(args.doc_root) + for url in iter_url(servers): + print >>sys.stderr, "{}: listening on {}".format(sys.argv[0], url) + + try: + while any(proc.is_alive for proc in iter_proc(servers)): + for proc in iter_proc(servers): + proc.proc.join(1) + except KeyboardInterrupt: + for proc in iter_proc(servers): + proc.kill() + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/testing/marionette/harness/marionette_harness/runner/test.cert b/testing/marionette/harness/marionette_harness/runner/test.cert new file mode 100644 index 000000000..3fd1cba2b --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/test.cert @@ -0,0 +1,86 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=web-platform-tests + Validity + Not Before: Dec 22 12:09:16 2014 GMT + Not After : Dec 21 12:09:16 2024 GMT + Subject: CN=web-platform.test + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b3:84:d6:8b:01:59:18:85:d1:dc:32:df:38:f7: + 90:85:1b:3e:a5:5e:81:3e:2f:fc:3a:5f:7f:77:ef: + 23:bb:3a:88:27:0f:be:25:46:cd:63:7d:cb:95:d8: + a5:50:10:d2:a2:d2:b7:97:d1:0d:6c:fb:f9:05:e8: + 6f:a8:4b:bd:95:67:9e:7b:94:58:a9:6d:93:fd:e0: + 12:c5:cd:b4:8a:64:52:31:5f:0e:e3:89:84:71:da: + 98:dd:4b:ec:02:25:a5:7d:35:fe:63:da:b3:ac:ec: + a5:46:0f:0d:64:23:5c:6d:f3:ec:cc:28:63:23:c0: + 4b:9a:ec:8f:c1:ee:b1:a2:3e:72:4d:70:b5:09:c1: + eb:b4:10:55:3c:8b:ea:1b:94:7e:4b:74:e6:f4:9f: + 4f:a6:45:30:b5:f0:b8:b4:d1:59:50:65:0a:86:53: + ea:4c:9f:9e:f4:58:6c:31:f5:17:3a:6f:57:8b:cb: + 5f:f0:28:0b:45:92:8d:30:20:49:ff:52:e6:2c:cb: + 18:9a:d7:e6:ee:3e:4f:34:35:15:13:c5:02:da:c5: + 5f:be:fb:5b:ce:8d:bf:b5:35:76:3c:7c:e6:9c:3b: + 26:87:4d:8d:80:e6:16:c6:27:f2:50:49:b6:72:74: + 43:49:49:44:38:bb:78:43:23:ee:16:3e:d9:62:e6: + a5:d7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + 2D:98:A3:99:39:1C:FE:E9:9A:6D:17:94:D2:3A:96:EE:C8:9E:04:22 + X509v3 Authority Key Identifier: + keyid:6A:AB:53:64:92:36:87:23:34:B3:1D:6F:85:4B:F5:DF:5A:5C:74:8F + + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Subject Alternative Name: + DNS:web-platform.test, DNS:www.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--lve-6lad.web-platform.test, DNS:www2.web-platform.test, DNS:www1.web-platform.test + Signature Algorithm: sha256WithRSAEncryption + 33:db:f7:f0:f6:92:16:4f:2d:42:bc:b8:aa:e6:ab:5e:f9:b9: + b0:48:ae:b5:8d:cc:02:7b:e9:6f:4e:75:f7:17:a0:5e:7b:87: + 06:49:48:83:c5:bb:ca:95:07:37:0e:5d:e3:97:de:9e:0c:a4: + 82:30:11:81:49:5d:50:29:72:92:a5:ca:17:b1:7c:f1:32:11: + 17:57:e6:59:c1:ac:e3:3b:26:d2:94:97:50:6a:b9:54:88:84: + 9b:6f:b1:06:f5:80:04:22:10:14:b1:f5:97:25:fc:66:d6:69: + a3:36:08:85:23:ff:8e:3c:2b:e0:6d:e7:61:f1:00:8f:61:3d: + b0:87:ad:72:21:f6:f0:cc:4f:c9:20:bf:83:11:0f:21:f4:b8: + c0:dd:9c:51:d7:bb:27:32:ec:ab:a4:62:14:28:32:da:f2:87: + 80:68:9c:ea:ac:eb:f5:7f:f5:de:f4:c0:39:91:c8:76:a4:ee: + d0:a8:50:db:c1:4b:f9:c4:3d:d9:e8:8e:b6:3f:c0:96:79:12: + d8:fa:4d:0a:b3:36:76:aa:4e:b2:82:2f:a2:d4:0d:db:fd:64: + 77:6f:6e:e9:94:7f:0f:c8:3a:3c:96:3d:cd:4d:6c:ba:66:95: + f7:b4:9d:a4:94:9f:97:b3:9a:0d:dc:18:8c:11:0b:56:65:8e: + 46:4c:e6:5e +-----BEGIN CERTIFICATE----- +MIID2jCCAsKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt +cGxhdGZvcm0tdGVzdHMwHhcNMTQxMjIyMTIwOTE2WhcNMjQxMjIxMTIwOTE2WjAc +MRowGAYDVQQDExF3ZWItcGxhdGZvcm0udGVzdDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALOE1osBWRiF0dwy3zj3kIUbPqVegT4v/Dpff3fvI7s6iCcP +viVGzWN9y5XYpVAQ0qLSt5fRDWz7+QXob6hLvZVnnnuUWKltk/3gEsXNtIpkUjFf +DuOJhHHamN1L7AIlpX01/mPas6zspUYPDWQjXG3z7MwoYyPAS5rsj8HusaI+ck1w +tQnB67QQVTyL6huUfkt05vSfT6ZFMLXwuLTRWVBlCoZT6kyfnvRYbDH1FzpvV4vL +X/AoC0WSjTAgSf9S5izLGJrX5u4+TzQ1FRPFAtrFX777W86Nv7U1djx85pw7JodN +jYDmFsYn8lBJtnJ0Q0lJRDi7eEMj7hY+2WLmpdcCAwEAAaOCASQwggEgMAkGA1Ud +EwQCMAAwHQYDVR0OBBYEFC2Yo5k5HP7pmm0XlNI6lu7IngQiMB8GA1UdIwQYMBaA +FGqrU2SSNocjNLMdb4VL9d9aXHSPMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggr +BgEFBQcDATCBsAYDVR0RBIGoMIGlghF3ZWItcGxhdGZvcm0udGVzdIIVd3d3Lndl +Yi1wbGF0Zm9ybS50ZXN0gil4bi0tbjhqNmRzNTNsd3drcnFodjI4YS53ZWItcGxh +dGZvcm0udGVzdIIeeG4tLWx2ZS02bGFkLndlYi1wbGF0Zm9ybS50ZXN0ghZ3d3cy +LndlYi1wbGF0Zm9ybS50ZXN0ghZ3d3cxLndlYi1wbGF0Zm9ybS50ZXN0MA0GCSqG +SIb3DQEBCwUAA4IBAQAz2/fw9pIWTy1CvLiq5qte+bmwSK61jcwCe+lvTnX3F6Be +e4cGSUiDxbvKlQc3Dl3jl96eDKSCMBGBSV1QKXKSpcoXsXzxMhEXV+ZZwazjOybS +lJdQarlUiISbb7EG9YAEIhAUsfWXJfxm1mmjNgiFI/+OPCvgbedh8QCPYT2wh61y +IfbwzE/JIL+DEQ8h9LjA3ZxR17snMuyrpGIUKDLa8oeAaJzqrOv1f/Xe9MA5kch2 +pO7QqFDbwUv5xD3Z6I62P8CWeRLY+k0KszZ2qk6ygi+i1A3b/WR3b27plH8PyDo8 +lj3NTWy6ZpX3tJ2klJ+Xs5oN3BiMEQtWZY5GTOZe +-----END CERTIFICATE----- \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runner/test.key b/testing/marionette/harness/marionette_harness/runner/test.key new file mode 100644 index 000000000..194a49ec4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runner/test.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzhNaLAVkYhdHc +Mt8495CFGz6lXoE+L/w6X3937yO7OognD74lRs1jfcuV2KVQENKi0reX0Q1s+/kF +6G+oS72VZ557lFipbZP94BLFzbSKZFIxXw7jiYRx2pjdS+wCJaV9Nf5j2rOs7KVG +Dw1kI1xt8+zMKGMjwEua7I/B7rGiPnJNcLUJweu0EFU8i+oblH5LdOb0n0+mRTC1 +8Li00VlQZQqGU+pMn570WGwx9Rc6b1eLy1/wKAtFko0wIEn/UuYsyxia1+buPk80 +NRUTxQLaxV+++1vOjb+1NXY8fOacOyaHTY2A5hbGJ/JQSbZydENJSUQ4u3hDI+4W +Ptli5qXXAgMBAAECggEBAIcwDQSnIjo2ZECHytQykpG6X6XXEksLhc1Lp0lhPC49 +uNR5pX6a4AcBb3PLr0opMQZO2tUoKA0ff3t0e8loKD+/xXhY0Z/dlioEOP7elwv0 +2nS1mhe9spCuxpk4GGXRhdtR8t2tj8s0do3YvgPgITXoEDX6YBZHNGhZpzSrFPgQ +/c3eGCVmzWYuLFfdj5OPQ9bwTaY4JSvDLZT0/WTgiica7VySwfz3HP1fFqNykTiK +ACQREvtxfk5Ym2nT6oni7CM2zOEJL9SXicXI5HO4bERH0ZYh//F3g6mwGiFXUJPd +NKgaTM1oT9kRGkUaEYsRWrddwR8d5mXLvBuTJbgIsSECgYEA1+2uJSYRW1OqbhYP +ms59YQHSs3VjpJpnCV2zNa2Wixs57KS2cOH7B6KrQCogJFLtgCDVLtyoErfVkD7E +FivTgYr1pVCRppJddQzXik31uOINOBVffr7/09g3GcRN+ubHPZPq3K+dD6gHa3Aj +0nH1EjEEV0QpSTQFn87OF2mc9wcCgYEA1NVqMbbzd+9Xft5FXuSbX6E+S02dOGat +SgpnkTM80rjqa6eHdQzqk3JqyteHPgdi1vdYRlSPOj/X+6tySY0Ej9sRnYOfddA2 +kpiDiVkmiqVolyJPY69Utj+E3TzJ1vhCQuYknJmB7zP9tDcTxMeq0l/NaWvGshEK +yC4UTQog1rECgYASOFILfGzWgfbNlzr12xqlRtwanHst9oFfPvLSQrWDQ2bd2wAy +Aj+GY2mD3oobxouX1i1m6OOdwLlalJFDNauBMNKNgoDnx03vhIfjebSURy7KXrNS +JJe9rm7n07KoyzRgs8yLlp3wJkOKA0pihY8iW9R78JpzPNqEo5SsURMXnQKBgBlV +gfuC9H4tPjP6zzUZbyk1701VYsaI6k2q6WMOP0ox+q1v1p7nN7DvaKjWeOG4TVqb +PKW6gQYE/XeWk9cPcyCQigs+1KdYbnaKsvWRaBYO1GFREzQhdarv6qfPCZOOH40J +Cgid+Sp4/NULzU2aGspJ3xCSZKdjge4MFhyJfRkxAoGBAJlwqY4nue0MBLGNpqcs +WwDtSasHvegKAcxGBKL5oWPbLBk7hk+hdqc8f6YqCkCNqv/ooBspL15ESItL+6yT +zt0YkK4oH9tmLDb+rvqZ7ZdXbWSwKITMoCyyHUtT6OKt/RtA0Vdy9LPnP27oSO/C +dk8Qf7KgKZLWo0ZNkvw38tEC +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/runtests.py b/testing/marionette/harness/marionette_harness/runtests.py new file mode 100644 index 000000000..3d3d09375 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/runtests.py @@ -0,0 +1,98 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys + +import mozlog + +from marionette_driver import __version__ as driver_version + +from marionette_harness import ( + __version__, + BaseMarionetteTestRunner, + BaseMarionetteArguments, + BrowserMobProxyArguments, + MarionetteTestCase, +) + + +class MarionetteTestRunner(BaseMarionetteTestRunner): + def __init__(self, **kwargs): + BaseMarionetteTestRunner.__init__(self, **kwargs) + self.test_handlers = [MarionetteTestCase] + + +class MarionetteArguments(BaseMarionetteArguments): + def __init__(self, **kwargs): + BaseMarionetteArguments.__init__(self, **kwargs) + self.register_argument_container(BrowserMobProxyArguments()) + + +class MarionetteHarness(object): + def __init__(self, + runner_class=MarionetteTestRunner, + parser_class=MarionetteArguments, + testcase_class=MarionetteTestCase, + args=None): + self._runner_class = runner_class + self._parser_class = parser_class + self._testcase_class = testcase_class + self.args = args or self.parse_args() + + def parse_args(self, logger_defaults=None): + parser = self._parser_class( + usage='%(prog)s [options] test_file_or_dir ...') + parser.add_argument('--version', action='version', + help="Show version information.", + version="%(prog)s {version}" + " (using marionette-driver: {driver_version}, ".format( + version=__version__, + driver_version=driver_version + )) + mozlog.commandline.add_logging_group(parser) + args = parser.parse_args() + parser.verify_usage(args) + + logger = mozlog.commandline.setup_logging( + args.logger_name, args, logger_defaults or {"tbpl": sys.stdout}) + + args.logger = logger + return vars(args) + + def process_args(self): + if self.args.get('pydebugger'): + self._testcase_class.pydebugger = __import__(self.args['pydebugger']) + + def run(self): + self.process_args() + tests = self.args.pop('tests') + runner = self._runner_class(**self.args) + runner.run_tests(tests) + return runner.failed + runner.crashed + + +def cli(runner_class=MarionetteTestRunner, parser_class=MarionetteArguments, + harness_class=MarionetteHarness, testcase_class=MarionetteTestCase, args=None): + """ + Call the harness to parse args and run tests. + + The following exit codes are expected: + - Test failures: 10 + - Harness/other failures: 1 + - Success: 0 + """ + logger = mozlog.commandline.setup_logging('Marionette test runner', {}) + try: + harness_instance = harness_class(runner_class, parser_class, testcase_class, + args=args) + failed = harness_instance.run() + if failed > 0: + sys.exit(10) + except Exception: + logger.error('Failure during harness execution', exc_info=True) + sys.exit(1) + sys.exit(0) + +if __name__ == "__main__": + cli() diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py new file mode 100644 index 000000000..6dc0a89a1 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py @@ -0,0 +1,100 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import pytest + +from mock import Mock, MagicMock + +from marionette_driver.marionette import Marionette + +from marionette_harness.runner.httpd import FixtureServer + + +@pytest.fixture(scope='module') +def logger(): + """ + Fake logger to help with mocking out other runner-related classes. + """ + import mozlog + return Mock(spec=mozlog.structuredlog.StructuredLogger) + + +@pytest.fixture +def mach_parsed_kwargs(logger): + """ + Parsed and verified dictionary used during simplest + call to mach marionette-test + """ + return { + 'adb_path': None, + 'addons': None, + 'address': None, + 'app': None, + 'app_args': [], + 'avd': None, + 'avd_home': None, + 'binary': u'/path/to/firefox', + 'browsermob_port' : None, + 'browsermob_script' : None, + 'device_serial': None, + 'e10s': True, + 'emulator': False, + 'emulator_bin': None, + 'gecko_log': None, + 'jsdebugger': False, + 'log_errorsummary': None, + 'log_html': None, + 'log_mach': None, + 'log_mach_buffer': None, + 'log_mach_level': None, + 'log_mach_verbose': None, + 'log_raw': None, + 'log_raw_level': None, + 'log_tbpl': None, + 'log_tbpl_buffer': None, + 'log_tbpl_compact': None, + 'log_tbpl_level': None, + 'log_unittest': None, + 'log_xunit': None, + 'logger_name': 'Marionette-based Tests', + 'prefs': {}, + 'prefs_args': None, + 'prefs_files': None, + 'profile': None, + 'pydebugger': None, + 'repeat': 0, + 'server_root': None, + 'shuffle': False, + 'shuffle_seed': 2276870381009474531, + 'socket_timeout': 60.0, + 'startup_timeout': 60, + 'symbols_path': None, + 'test_tags': None, + 'tests': [u'/path/to/unit-tests.ini'], + 'testvars': None, + 'this_chunk': None, + 'timeout': None, + 'total_chunks': None, + 'verbose': None, + 'workspace': None, + 'logger': logger, + } + + +@pytest.fixture +def mock_httpd(request): + """ Mock httpd instance """ + httpd = MagicMock(spec=FixtureServer) + return httpd + + +@pytest.fixture +def mock_marionette(request): + """ Mock marionette instance """ + marionette_class = MagicMock(spec=Marionette) + if 'has_crashed' in request.funcargnames: + marionette_class.check_for_crash.return_value = request.getfuncargvalue( + 'has_crashed' + ) + return marionette_class diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_httpd.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_httpd.py new file mode 100644 index 000000000..bd86b2fff --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_httpd.py @@ -0,0 +1,90 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import os +import types +import urllib2 + +import pytest + +from wptserve.handlers import json_handler + +from marionette_harness.runner import httpd + +here = os.path.abspath(os.path.dirname(__file__)) +parent = os.path.dirname(here) +default_doc_root = os.path.join(os.path.dirname(parent), "www") + + +@pytest.yield_fixture +def server(): + server = httpd.FixtureServer(default_doc_root) + yield server + server.stop() + + +def test_ctor(): + with pytest.raises(ValueError): + httpd.FixtureServer("foo") + httpd.FixtureServer(default_doc_root) + + +def test_start_stop(server): + server.start() + server.stop() + + +def test_get_url(server): + server.start() + url = server.get_url("/") + assert isinstance(url, types.StringTypes) + assert "http://" in url + + server.stop() + with pytest.raises(httpd.NotAliveError): + server.get_url("/") + + +def test_doc_root(server): + server.start() + assert isinstance(server.doc_root, types.StringTypes) + server.stop() + assert isinstance(server.doc_root, types.StringTypes) + + +def test_router(server): + assert server.router is not None + + +def test_routes(server): + assert server.routes is not None + + +def test_is_alive(server): + assert server.is_alive == False + server.start() + assert server.is_alive == True + + +def test_handler(server): + counter = 0 + + @json_handler + def handler(request, response): + return {"count": counter} + + route = ("GET", "/httpd/test_handler", handler) + server.router.register(*route) + server.start() + + url = server.get_url("/httpd/test_handler") + body = urllib2.urlopen(url).read() + res = json.loads(body) + assert res["count"] == counter + + +if __name__ == "__main__": + import sys + sys.exit(pytest.main(["--verbose", __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py new file mode 100644 index 000000000..1a0687028 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py @@ -0,0 +1,32 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +import pytest + +from marionette_harness.runtests import MarionetteArguments + + +@pytest.mark.parametrize("socket_timeout", ['A', '10', '1B-', '1C2', '44.35']) +def test_parse_arg_socket_timeout(socket_timeout): + argv = ['marionette', '--socket-timeout', socket_timeout] + parser = MarionetteArguments() + + def _is_float_convertible(value): + try: + float(value) + return True + except: + return False + + if not _is_float_convertible(socket_timeout): + with pytest.raises(SystemExit) as ex: + parser.parse_args(args=argv) + assert ex.value.code == 2 + else: + args = parser.parse_args(args=argv) + assert hasattr(args, 'socket_timeout') and args.socket_timeout == float(socket_timeout) + + +if __name__ == '__main__': + import sys + sys.exit(pytest.main(['--verbose', __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_harness.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_harness.py new file mode 100644 index 000000000..dfbbfb788 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_harness.py @@ -0,0 +1,108 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import pytest + +from mock import Mock, patch, sentinel + +import marionette_harness.marionette_test as marionette_test + +from marionette_harness.runtests import MarionetteTestRunner, MarionetteHarness, cli + + +@pytest.fixture +def harness_class(request): + """ + Mock based on MarionetteHarness whose run method just returns a number of + failures according to the supplied test parameter + """ + if 'num_fails_crashed' in request.funcargnames: + num_fails_crashed = request.getfuncargvalue('num_fails_crashed') + else: + num_fails_crashed = (0, 0) + harness_cls = Mock(spec=MarionetteHarness) + harness = harness_cls.return_value + if num_fails_crashed is None: + harness.run.side_effect = Exception + else: + harness.run.return_value = sum(num_fails_crashed) + return harness_cls + + +@pytest.fixture +def runner_class(request): + """ + Mock based on MarionetteTestRunner, wherein the runner.failed, + runner.crashed attributes are provided by a test parameter + """ + if 'num_fails_crashed' in request.funcargnames: + failures, crashed = request.getfuncargvalue('num_fails_crashed') + else: + failures = 0 + crashed = 0 + mock_runner_class = Mock(spec=MarionetteTestRunner) + runner = mock_runner_class.return_value + runner.failed = failures + runner.crashed = crashed + return mock_runner_class + + +@pytest.mark.parametrize( + "num_fails_crashed,exit_code", + [((0, 0), 0), ((1, 0), 10), ((0, 1), 10), (None, 1)], +) +def test_cli_exit_code(num_fails_crashed, exit_code, harness_class): + with pytest.raises(SystemExit) as err: + cli(harness_class=harness_class) + assert err.value.code == exit_code + + +@pytest.mark.parametrize("num_fails_crashed", [(0, 0), (1, 0), (1, 1)]) +def test_call_harness_with_parsed_args_yields_num_failures(mach_parsed_kwargs, + runner_class, + num_fails_crashed): + with patch( + 'marionette_harness.runtests.MarionetteHarness.parse_args' + ) as parse_args: + failed_or_crashed = MarionetteHarness(runner_class, + args=mach_parsed_kwargs).run() + parse_args.assert_not_called() + assert failed_or_crashed == sum(num_fails_crashed) + + +def test_call_harness_with_no_args_yields_num_failures(runner_class): + with patch( + 'marionette_harness.runtests.MarionetteHarness.parse_args', + return_value={'tests': []} + ) as parse_args: + failed_or_crashed = MarionetteHarness(runner_class).run() + assert parse_args.call_count == 1 + assert failed_or_crashed == 0 + + +def test_args_passed_to_runner_class(mach_parsed_kwargs, runner_class): + arg_list = mach_parsed_kwargs.keys() + arg_list.remove('tests') + mach_parsed_kwargs.update([(a, getattr(sentinel, a)) for a in arg_list]) + harness = MarionetteHarness(runner_class, args=mach_parsed_kwargs) + harness.process_args = Mock() + harness.run() + for arg in arg_list: + assert harness._runner_class.call_args[1][arg] is getattr(sentinel, arg) + + +def test_harness_sets_up_default_test_handlers(mach_parsed_kwargs): + """ + If the necessary TestCase is not in test_handlers, + tests are omitted silently + """ + harness = MarionetteHarness(args=mach_parsed_kwargs) + mach_parsed_kwargs.pop('tests') + runner = harness._runner_class(**mach_parsed_kwargs) + assert marionette_test.MarionetteTestCase in runner.test_handlers + + +if __name__ == '__main__': + import sys + sys.exit(pytest.main(['--verbose', __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py new file mode 100644 index 000000000..79bdc824e --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py @@ -0,0 +1,442 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import manifestparser +import pytest + +from mock import Mock, patch, mock_open, sentinel, DEFAULT + +from marionette_harness.runtests import MarionetteTestRunner + + +@pytest.fixture +def runner(mach_parsed_kwargs): + """ + MarionetteTestRunner instance initialized with default options. + """ + return MarionetteTestRunner(**mach_parsed_kwargs) + + +@pytest.fixture +def mock_runner(runner, mock_marionette, monkeypatch): + """ + MarionetteTestRunner instance with mocked-out + self.marionette and other properties, + to enable testing runner.run_tests(). + """ + runner.driverclass = mock_marionette + for attr in ['run_test_set', '_capabilities']: + setattr(runner, attr, Mock()) + runner._appName = 'fake_app' + # simulate that browser runs with e10s by default + runner._appinfo = {'browserTabsRemoteAutostart': True} + monkeypatch.setattr('marionette_harness.runner.base.mozversion', Mock()) + return runner + + +@pytest.fixture +def build_kwargs_using(mach_parsed_kwargs): + '''Helper function for test_build_kwargs_* functions''' + def kwarg_builder(new_items, return_socket=False): + mach_parsed_kwargs.update(new_items) + runner = MarionetteTestRunner(**mach_parsed_kwargs) + with patch('marionette_harness.runner.base.socket') as socket: + built_kwargs = runner._build_kwargs() + if return_socket: + return built_kwargs, socket + return built_kwargs + return kwarg_builder + + +@pytest.fixture +def expected_driver_args(runner): + '''Helper fixture for tests of _build_kwargs + with binary/emulator. + Provides a dictionary of certain arguments + related to binary/emulator settings + which we expect to be passed to the + driverclass constructor. Expected values can + be updated in tests as needed. + Provides convenience methods for comparing the + expected arguments to the argument dictionary + created by _build_kwargs. ''' + + class ExpectedDict(dict): + def assert_matches(self, actual): + for (k, v) in self.items(): + assert actual[k] == v + + def assert_keys_not_in(self, actual): + for k in self.keys(): + assert k not in actual + + expected = ExpectedDict(host=None, port=None, bin=None) + for attr in ['app', 'app_args', 'profile', 'addons', 'gecko_log']: + expected[attr] = getattr(runner, attr) + return expected + + +class ManifestFixture: + def __init__(self, name='mock_manifest', + tests=[{'path': u'test_something.py', 'expected': 'pass'}]): + self.filepath = "/path/to/fake/manifest.ini" + self.n_disabled = len([t for t in tests if 'disabled' in t]) + self.n_enabled = len(tests) - self.n_disabled + mock_manifest = Mock(spec=manifestparser.TestManifest, + active_tests=Mock(return_value=tests)) + self.manifest_class = Mock(return_value=mock_manifest) + self.__repr__ = lambda: "".format(name) + +@pytest.fixture +def manifest(): + return ManifestFixture() + +@pytest.fixture(params=['enabled', 'disabled', 'enabled_disabled', 'empty']) +def manifest_with_tests(request): + ''' + Fixture for the contents of mock_manifest, where a manifest + can include enabled tests, disabled tests, both, or neither (empty) + ''' + included = [] + if 'enabled' in request.param: + included += [(u'test_expected_pass.py', 'pass'), + (u'test_expected_fail.py', 'fail')] + if 'disabled' in request.param: + included += [(u'test_pass_disabled.py', 'pass', 'skip-if: true'), + (u'test_fail_disabled.py', 'fail', 'skip-if: true')] + keys = ('path', 'expected', 'disabled') + active_tests = [dict(zip(keys, values)) for values in included] + + return ManifestFixture(request.param, active_tests) + + +def test_args_passed_to_driverclass(mock_runner): + built_kwargs = {'arg1': 'value1', 'arg2': 'value2'} + mock_runner._build_kwargs = Mock(return_value=built_kwargs) + with pytest.raises(IOError): + mock_runner.run_tests(['fake_tests.ini']) + assert mock_runner.driverclass.call_args[1] == built_kwargs + + +def test_build_kwargs_basic_args(build_kwargs_using): + '''Test the functionality of runner._build_kwargs: + make sure that basic arguments (those which should + always be included, irrespective of the runner's settings) + get passed to the call to runner.driverclass''' + + basic_args = ['socket_timeout', 'prefs', + 'startup_timeout', 'verbose', 'symbols_path'] + args_dict = {a: getattr(sentinel, a) for a in basic_args} + # Mock an update method to work with calls to MarionetteTestRunner() + args_dict['prefs'].update = Mock(return_value={}) + built_kwargs = build_kwargs_using([(a, getattr(sentinel, a)) for a in basic_args]) + for arg in basic_args: + assert built_kwargs[arg] is getattr(sentinel, arg) + + +@pytest.mark.parametrize('workspace', ['path/to/workspace', None]) +def test_build_kwargs_with_workspace(build_kwargs_using, workspace): + built_kwargs = build_kwargs_using({'workspace': workspace}) + if workspace: + assert built_kwargs['workspace'] == workspace + else: + assert 'workspace' not in built_kwargs + + +@pytest.mark.parametrize('address', ['host:123', None]) +def test_build_kwargs_with_address(build_kwargs_using, address): + built_kwargs, socket = build_kwargs_using( + {'address': address, 'binary': None, 'emulator': None}, + return_socket=True + ) + assert 'connect_to_running_emulator' not in built_kwargs + if address is not None: + host, port = address.split(":") + assert built_kwargs['host'] == host and built_kwargs['port'] == int(port) + socket.socket().connect.assert_called_with((host, int(port))) + assert socket.socket().close.called + else: + assert not socket.socket.called + + +@pytest.mark.parametrize('address', ['host:123', None]) +@pytest.mark.parametrize('binary', ['path/to/bin', None]) +def test_build_kwargs_with_binary_or_address(expected_driver_args, build_kwargs_using, + binary, address): + built_kwargs = build_kwargs_using({'binary': binary, 'address': address, 'emulator': None}) + if binary: + expected_driver_args['bin'] = binary + if address: + host, port = address.split(":") + expected_driver_args.update({'host': host, 'port': int(port)}) + else: + expected_driver_args.update({'host': 'localhost', 'port': 2828}) + expected_driver_args.assert_matches(built_kwargs) + elif address is None: + expected_driver_args.assert_keys_not_in(built_kwargs) + + +@pytest.mark.parametrize('address', ['host:123', None]) +@pytest.mark.parametrize('emulator', [True, False, None]) +def test_build_kwargs_with_emulator_or_address(expected_driver_args, build_kwargs_using, + emulator, address): + emulator_props = [(a, getattr(sentinel, a)) for a in ['avd_home', 'adb_path', 'emulator_bin']] + built_kwargs = build_kwargs_using( + [('emulator', emulator), ('address', address), ('binary', None)] + emulator_props + ) + if emulator: + expected_driver_args.update(emulator_props) + expected_driver_args['emulator_binary'] = expected_driver_args.pop('emulator_bin') + expected_driver_args['bin'] = True + if address: + expected_driver_args['connect_to_running_emulator'] = True + host, port = address.split(":") + expected_driver_args.update({'host': host, 'port': int(port)}) + else: + expected_driver_args.update({'host': 'localhost', 'port': 2828}) + assert 'connect_to_running_emulator' not in built_kwargs + expected_driver_args.assert_matches(built_kwargs) + elif not address: + expected_driver_args.assert_keys_not_in(built_kwargs) + + +def test_parsing_testvars(mach_parsed_kwargs): + mach_parsed_kwargs.pop('tests') + testvars_json_loads = [ + {"wifi": {"ssid": "blah", "keyManagement": "WPA-PSK", "psk": "foo"}}, + {"wifi": {"PEAP": "bar"}, "device": {"stuff": "buzz"}} + ] + expected_dict = { + "wifi": { + "ssid": "blah", + "keyManagement": "WPA-PSK", + "psk": "foo", + "PEAP": "bar" + }, + "device": {"stuff": "buzz"} + } + with patch( + 'marionette_harness.runtests.MarionetteTestRunner._load_testvars', + return_value=testvars_json_loads + ) as load: + runner = MarionetteTestRunner(**mach_parsed_kwargs) + assert runner.testvars == expected_dict + assert load.call_count == 1 + + +def test_load_testvars_throws_expected_errors(mach_parsed_kwargs): + mach_parsed_kwargs['testvars'] = ['some_bad_path.json'] + runner = MarionetteTestRunner(**mach_parsed_kwargs) + with pytest.raises(IOError) as io_exc: + runner._load_testvars() + assert 'does not exist' in io_exc.value.message + with patch('os.path.exists', return_value=True): + with patch('__builtin__.open', mock_open(read_data='[not {valid JSON]')): + with pytest.raises(Exception) as json_exc: + runner._load_testvars() + assert 'not properly formatted' in json_exc.value.message + + +def _check_crash_counts(has_crashed, runner, mock_marionette): + if has_crashed: + assert mock_marionette.check_for_crash.call_count == 1 + assert runner.crashed == 1 + else: + assert runner.crashed == 0 + + +@pytest.mark.parametrize("has_crashed", [True, False]) +def test_increment_crash_count_in_run_test_set(runner, has_crashed, + mock_marionette): + fake_tests = [{'filepath': i, + 'expected': 'pass'} for i in 'abc'] + + with patch.multiple(runner, run_test=DEFAULT, marionette=mock_marionette): + runner.run_test_set(fake_tests) + if not has_crashed: + assert runner.marionette.check_for_crash.call_count == len(fake_tests) + _check_crash_counts(has_crashed, runner, runner.marionette) + + +@pytest.mark.parametrize("has_crashed", [True, False]) +def test_record_crash(runner, has_crashed, mock_marionette): + with patch.object(runner, 'marionette', mock_marionette): + assert runner.record_crash() == has_crashed + _check_crash_counts(has_crashed, runner, runner.marionette) + + +def test_add_test_module(runner): + tests = ['test_something.py', 'testSomething.js', 'bad_test.py'] + assert len(runner.tests) == 0 + for test in tests: + with patch('os.path.abspath', return_value=test) as abspath: + runner.add_test(test) + assert abspath.called + expected = {'filepath': test, 'expected': 'pass'} + assert expected in runner.tests + # add_test doesn't validate module names; 'bad_test.py' gets through + assert len(runner.tests) == 3 + + +def test_add_test_directory(runner): + test_dir = 'path/to/tests' + dir_contents = [ + (test_dir, ('subdir',), ('test_a.py', 'test_a.js', 'bad_test_a.py', 'bad_test_a.js')), + (test_dir + '/subdir', (), ('test_b.py', 'test_b.js', 'bad_test_b.py', 'bad_test_b.js')), + ] + tests = list(dir_contents[0][2] + dir_contents[1][2]) + assert len(runner.tests) == 0 + # Need to use side effect to make isdir return True for test_dir and False for tests + with patch('os.path.isdir', side_effect=[True] + [False for t in tests]) as isdir: + with patch('os.walk', return_value=dir_contents) as walk: + runner.add_test(test_dir) + assert isdir.called and walk.called + for test in runner.tests: + assert test_dir in test['filepath'] + assert len(runner.tests) == 4 + + +@pytest.mark.parametrize("test_files_exist", [True, False]) +def test_add_test_manifest(mock_runner, manifest_with_tests, monkeypatch, test_files_exist): + monkeypatch.setattr('marionette_harness.runner.base.TestManifest', + manifest_with_tests.manifest_class) + mock_runner.marionette = mock_runner.driverclass() + with patch('marionette_harness.runner.base.os.path.exists', return_value=test_files_exist): + if test_files_exist or manifest_with_tests.n_enabled == 0: + mock_runner.add_test(manifest_with_tests.filepath) + assert len(mock_runner.tests) == manifest_with_tests.n_enabled + assert len(mock_runner.manifest_skipped_tests) == manifest_with_tests.n_disabled + for test in mock_runner.tests: + assert test['filepath'].endswith(test['expected'] + '.py') + else: + pytest.raises(IOError, "mock_runner.add_test(manifest_with_tests.filepath)") + assert manifest_with_tests.manifest_class().read.called + assert manifest_with_tests.manifest_class().active_tests.called + + +def get_kwargs_passed_to_manifest(mock_runner, manifest, monkeypatch, **kwargs): + '''Helper function for test_manifest_* tests. + Returns the kwargs passed to the call to manifest.active_tests.''' + monkeypatch.setattr('marionette_harness.runner.base.TestManifest', manifest.manifest_class) + monkeypatch.setattr('marionette_harness.runner.base.mozinfo.info', + {'mozinfo_key': 'mozinfo_val'}) + for attr in kwargs: + setattr(mock_runner, attr, kwargs[attr]) + mock_runner.marionette = mock_runner.driverclass() + with patch('marionette_harness.runner.base.os.path.exists', return_value=True): + mock_runner.add_test(manifest.filepath) + call_args, call_kwargs = manifest.manifest_class().active_tests.call_args + return call_kwargs + + +def test_manifest_basic_args(mock_runner, manifest, monkeypatch): + kwargs = get_kwargs_passed_to_manifest(mock_runner, manifest, monkeypatch) + assert kwargs['exists'] is False + assert kwargs['disabled'] is True + assert kwargs['appname'] == 'fake_app' + assert 'mozinfo_key' in kwargs and kwargs['mozinfo_key'] == 'mozinfo_val' + + +@pytest.mark.parametrize('e10s', (True, False)) +def test_manifest_with_e10s(mock_runner, manifest, monkeypatch, e10s): + kwargs = get_kwargs_passed_to_manifest(mock_runner, manifest, monkeypatch, e10s=e10s) + assert kwargs['e10s'] == e10s + + +@pytest.mark.parametrize('test_tags', (None, ['tag', 'tag2'])) +def test_manifest_with_test_tags(mock_runner, manifest, monkeypatch, test_tags): + kwargs = get_kwargs_passed_to_manifest(mock_runner, manifest, monkeypatch, test_tags=test_tags) + if test_tags is None: + assert kwargs['filters'] == [] + else: + assert len(kwargs['filters']) == 1 and kwargs['filters'][0].tags == test_tags + + +def test_cleanup_with_manifest(mock_runner, manifest_with_tests, monkeypatch): + monkeypatch.setattr('marionette_harness.runner.base.TestManifest', + manifest_with_tests.manifest_class) + if manifest_with_tests.n_enabled > 0: + context = patch('marionette_harness.runner.base.os.path.exists', return_value=True) + else: + context = pytest.raises(Exception) + with context: + mock_runner.run_tests([manifest_with_tests.filepath]) + assert mock_runner.marionette is None + assert mock_runner.fixture_servers == {} + + +def test_reset_test_stats(mock_runner): + def reset_successful(runner): + stats = ['passed', 'failed', 'unexpected_successes', 'todo', 'skipped', 'failures'] + return all([((s in vars(runner)) and (not vars(runner)[s])) for s in stats]) + assert reset_successful(mock_runner) + mock_runner.passed = 1 + mock_runner.failed = 1 + mock_runner.failures.append(['TEST-UNEXPECTED-FAIL']) + assert not reset_successful(mock_runner) + mock_runner.run_tests([u'test_fake_thing.py']) + assert reset_successful(mock_runner) + + +def test_initialize_test_run(mock_runner): + tests = [u'test_fake_thing.py'] + mock_runner.reset_test_stats = Mock() + mock_runner.run_tests(tests) + assert mock_runner.reset_test_stats.called + with pytest.raises(AssertionError) as test_exc: + mock_runner.run_tests([]) + assert "len(tests)" in str(test_exc.traceback[-1].statement) + with pytest.raises(AssertionError) as hndl_exc: + mock_runner.test_handlers = [] + mock_runner.run_tests(tests) + assert "test_handlers" in str(hndl_exc.traceback[-1].statement) + assert mock_runner.reset_test_stats.call_count == 1 + + +def test_add_tests(mock_runner): + assert len(mock_runner.tests) == 0 + fake_tests = ["test_" + i + ".py" for i in "abc"] + mock_runner.run_tests(fake_tests) + assert len(mock_runner.tests) == 3 + for (test_name, added_test) in zip(fake_tests, mock_runner.tests): + assert added_test['filepath'].endswith(test_name) + + +def test_catch_invalid_test_names(runner): + good_tests = [u'test_ok.py', u'test_is_ok.py', u'test_is_ok.js', u'testIsOk.js'] + bad_tests = [u'bad_test.py', u'testbad.py', u'_test_bad.py', u'testBad.notjs', + u'test_bad.notpy', u'test_bad', u'testbad.js', u'badtest.js', + u'test.py', u'test_.py', u'test.js', u'test_.js'] + with pytest.raises(Exception) as exc: + runner._add_tests(good_tests + bad_tests) + msg = exc.value.message + assert "Test file names must be of the form" in msg + for bad_name in bad_tests: + assert bad_name in msg + for good_name in good_tests: + assert good_name not in msg + +@pytest.mark.parametrize('e10s', (True, False)) +def test_e10s_option_sets_prefs(mach_parsed_kwargs, e10s): + mach_parsed_kwargs['e10s'] = e10s + runner = MarionetteTestRunner(**mach_parsed_kwargs) + e10s_prefs = { + 'browser.tabs.remote.autostart': True, + 'browser.tabs.remote.force-enable': True, + 'extensions.e10sBlocksEnabling': False + } + for k,v in e10s_prefs.iteritems(): + if k == 'extensions.e10sBlocksEnabling' and not e10s: + continue + assert runner.prefs.get(k, False) == (v and e10s) + +def test_e10s_option_clash_raises(mock_runner): + mock_runner.e10s = False + with pytest.raises(AssertionError) as e: + mock_runner.run_tests([u'test_fake_thing.py']) + assert "configuration (self.e10s) does not match browser appinfo" in e.value.message + +if __name__ == '__main__': + import sys + sys.exit(pytest.main(['--verbose', __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_test_result.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_test_result.py new file mode 100644 index 000000000..a69b072cd --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_test_result.py @@ -0,0 +1,54 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import pytest + +from marionette_harness import MarionetteTestResult + + +@pytest.fixture +def empty_marionette_testcase(): + """ Testable MarionetteTestCase class """ + from marionette_harness import MarionetteTestCase + + class EmptyTestCase(MarionetteTestCase): + def test_nothing(self): + pass + + return EmptyTestCase + + +@pytest.fixture +def empty_marionette_test(mock_marionette, empty_marionette_testcase): + return empty_marionette_testcase(lambda: mock_marionette, lambda: mock_httpd, + 'test_nothing') + + +@pytest.mark.parametrize("has_crashed", [True, False]) +def test_crash_is_recorded_as_error(empty_marionette_test, + logger, + has_crashed): + """ Number of errors is incremented by stopTest iff has_crashed is true """ + # collect results from the empty test + result = MarionetteTestResult( + marionette=empty_marionette_test._marionette_weakref(), + logger=logger, verbosity=None, + stream=None, descriptions=None, + ) + result.startTest(empty_marionette_test) + assert len(result.errors) == 0 + assert len(result.failures) == 0 + assert result.testsRun == 1 + assert result.shouldStop is False + result.stopTest(empty_marionette_test) + assert result.shouldStop == has_crashed + if has_crashed: + assert len(result.errors) == 1 + else: + assert len(result.errors) == 0 + + +if __name__ == '__main__': + import sys + sys.exit(pytest.main(['--verbose', __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_serve.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_serve.py new file mode 100644 index 000000000..73684c0d6 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_serve.py @@ -0,0 +1,67 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import types + +import pytest + +from marionette_harness.runner import serve +from marionette_harness.runner.serve import iter_proc, iter_url + + +def teardown_function(func): + for server in [server for server in iter_proc(serve.servers) if server.is_alive]: + server.stop() + server.kill() + + +def test_registered_servers(): + # [(name, factory), ...] + assert serve.registered_servers[0][0] == "http" + assert serve.registered_servers[1][0] == "https" + + +def test_globals(): + assert serve.default_doc_root is not None + assert serve.registered_servers is not None + assert serve.servers is not None + + +def test_start(): + serve.start() + assert len(serve.servers) == 2 + assert "http" in serve.servers + assert "https" in serve.servers + for url in iter_url(serve.servers): + assert isinstance(url, types.StringTypes) + + +def test_start_with_custom_root(tmpdir_factory): + tdir = tmpdir_factory.mktemp("foo") + serve.start(str(tdir)) + for server in iter_proc(serve.servers): + assert server.doc_root == tdir + + +def test_iter_proc(): + serve.start() + for server in iter_proc(serve.servers): + server.stop() + + +def test_iter_url(): + serve.start() + for url in iter_url(serve.servers): + assert isinstance(url, types.StringTypes) + + +def test_where_is(): + serve.start() + assert serve.where_is("/") == serve.servers["http"][1].get_url("/") + assert serve.where_is("/", on="https") == serve.servers["https"][1].get_url("/") + + +if __name__ == "__main__": + import sys + sys.exit(pytest.main(["-s", "--verbose", __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/unit-tests.ini b/testing/marionette/harness/marionette_harness/tests/unit-tests.ini new file mode 100644 index 000000000..8d806afea --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit-tests.ini @@ -0,0 +1,11 @@ +; marionette unit tests +[include:unit/unit-tests.ini] + +; layout tests +[include:../../../../../layout/base/tests/marionette/manifest.ini] + +; microformats tests +[include:../../../../../toolkit/components/microformats/manifest.ini] + +; migration tests +[include:../../../../../browser/components/migration/tests/marionette/manifest.ini] diff --git a/testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js b/testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js new file mode 100644 index 000000000..fa6a1fa7c --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js @@ -0,0 +1 @@ +var testAnotherFunc = function() { return "i'm yet another test function!";}; diff --git a/testing/marionette/harness/marionette_harness/tests/unit/importscript.js b/testing/marionette/harness/marionette_harness/tests/unit/importscript.js new file mode 100644 index 000000000..5a5dd8a18 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/importscript.js @@ -0,0 +1 @@ +var testFunc = function() { return "i'm a test function!";}; diff --git a/testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpi b/testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpi new file mode 100644 index 000000000..c8877b55d Binary files /dev/null and b/testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpi differ diff --git a/testing/marionette/harness/marionette_harness/tests/unit/single_finger_functions.py b/testing/marionette/harness/marionette_harness/tests/unit/single_finger_functions.py new file mode 100644 index 000000000..c2daf9d54 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/single_finger_functions.py @@ -0,0 +1,131 @@ +from marionette_driver.marionette import Actions +from marionette_driver.errors import TimeoutException +from marionette_driver.by import By + + +def wait_for_condition_else_raise(marionette, wait_for_condition, expected, script): + try: + wait_for_condition(lambda m: expected in m.execute_script(script)) + except TimeoutException as e: + raise TimeoutException("{0} got {1} instead of {2}".format( + e.message, marionette.execute_script(script), expected)) + +def press_release(marionette, times, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + action = Actions(marionette) + button = marionette.find_element(By.ID, "button1") + action.press(button).release() + # Insert wait between each press and release chain. + for _ in range(times-1): + action.wait(0.1) + action.press(button).release() + action.perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def move_element(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + ele = marionette.find_element(By.ID, "button1") + drop = marionette.find_element(By.ID, "button2") + action = Actions(marionette) + action.press(ele).move(drop).release() + action.perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;") + wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button2').innerHTML;") + +def move_element_offset(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + ele = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.press(ele).move_by_offset(0,150).move_by_offset(0, 150).release() + action.perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;") + wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button2').innerHTML;") + +def chain(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + marionette.timeout.implicit = 15 + action = Actions(marionette) + button1 = marionette.find_element(By.ID, "button1") + action.press(button1).perform() + button2 = marionette.find_element(By.ID, "delayed") + wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;") + action.move(button2).release().perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('delayed').innerHTML;") + +def chain_flick(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.flick(button, 0, 0, 0, 200).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected1,"return document.getElementById('button1').innerHTML;") + wait_for_condition_else_raise(marionette, wait_for_condition, expected2,"return document.getElementById('buttonFlick').innerHTML;") + + +def wait(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + action = Actions(marionette) + button = marionette.find_element(By.ID, "button1") + action.press(button).wait().release().perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def wait_with_value(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.press(button).wait(0.01).release() + action.perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def context_menu(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.press(button).wait(5).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;") + action.release().perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button1').innerHTML;") + +def long_press_action(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.long_press(button, 5).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def long_press_on_xy_action(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + html = marionette.find_element(By.TAG_NAME, "html") + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + + # Press the center of the button with respect to html. + x = button.rect['x'] + button.rect['width'] / 2.0 + y = button.rect['y'] + button.rect['height'] / 2.0 + action.long_press(html, 5, x, y).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def single_tap(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.tap(button).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def double_tap(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.double_tap(button).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py new file mode 100644 index 000000000..e9992f8a5 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py @@ -0,0 +1,134 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import By, Wait +from marionette_driver.keys import Keys + +from marionette_harness import MarionetteTestCase, skip, skip_if_mobile, WindowManagerMixin + + +class TestAboutPages(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestAboutPages, self).setUp() + + if self.marionette.session_capabilities['platformName'] == 'darwin': + self.mod_key = Keys.META + else: + self.mod_key = Keys.CONTROL + + self.remote_uri = self.marionette.absolute_url("windowHandles.html") + + def tearDown(self): + self.close_all_tabs() + + super(TestAboutPages, self).tearDown() + + def open_tab_with_link(self): + with self.marionette.using_context("content"): + self.marionette.navigate(self.remote_uri) + + link = self.marionette.find_element(By.ID, "new-tab") + link.click() + + @skip_if_mobile("Bug 1333209 - Process killed because of connection loss") + def test_back_forward(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + self.marionette.switch_to_window(new_tab) + + self.marionette.navigate("about:blank") + self.marionette.navigate(self.remote_uri) + self.marionette.navigate("about:support") + + self.marionette.go_back() + self.assertEqual(self.marionette.get_url(), self.remote_uri) + + self.marionette.go_forward() + self.assertEqual(self.marionette.get_url(), "about:support") + + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + @skip_if_mobile("Bug 1333209 - Process killed because of connection loss") + def test_navigate_non_remote_about_pages(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + self.marionette.switch_to_window(new_tab) + + self.marionette.navigate("about:blank") + self.assertEqual(self.marionette.get_url(), "about:blank") + self.marionette.navigate("about:support") + self.assertEqual(self.marionette.get_url(), "about:support") + + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + @skip_if_mobile("On Android no shortcuts are available") + def test_navigate_shortcut_key(self): + def open_with_shortcut(): + self.marionette.navigate(self.remote_uri) + with self.marionette.using_context("chrome"): + main_win = self.marionette.find_element(By.ID, "main-window") + main_win.send_keys(self.mod_key, Keys.SHIFT, 'a') + + new_tab = self.open_tab(trigger=open_with_shortcut) + self.marionette.switch_to_window(new_tab) + + Wait(self.marionette).until(lambda mn: mn.get_url() == "about:addons", + message="'about:addons' hasn't been loaded") + + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + @skip("Bug 1334137 - Intermittent: Process killed because of hang in getCurrentUrl()") + @skip_if_mobile("Interacting with chrome elements not available for Fennec") + def test_type_to_non_remote_tab(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + self.marionette.switch_to_window(new_tab) + + with self.marionette.using_context("chrome"): + urlbar = self.marionette.find_element(By.ID, 'urlbar') + urlbar.send_keys(self.mod_key + 'a') + urlbar.send_keys(self.mod_key + 'x') + urlbar.send_keys('about:support' + Keys.ENTER) + Wait(self.marionette).until(lambda mn: mn.get_url() == "about:support", + message="'about:support' hasn't been loaded") + + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + @skip_if_mobile("Interacting with chrome elements not available for Fennec") + def test_type_to_remote_tab(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + self.marionette.switch_to_window(new_tab) + + # about:blank keeps remoteness from remote_uri + self.marionette.navigate("about:blank") + with self.marionette.using_context("chrome"): + urlbar = self.marionette.find_element(By.ID, 'urlbar') + urlbar.send_keys(self.mod_key + 'a') + urlbar.send_keys(self.mod_key + 'x') + urlbar.send_keys(self.remote_uri + Keys.ENTER) + + Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri, + message="'{}' hasn't been loaded".format(self.remote_uri)) + + @skip_if_mobile("Needs application independent method to open a new tab") + def test_hang(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + + # Close the start tab + self.marionette.close() + self.marionette.switch_to_window(new_tab) + + self.marionette.navigate(self.remote_uri) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py b/testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py new file mode 100644 index 000000000..0d8d9dca5 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py @@ -0,0 +1,210 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import ( + ElementNotAccessibleException, + ElementNotInteractableException +) + +from marionette_harness import MarionetteTestCase + + + +class TestAccessibility(MarionetteTestCase): + def setUp(self): + super(TestAccessibility, self).setUp() + with self.marionette.using_context("chrome"): + self.marionette.set_pref("dom.ipc.processCount", 1) + + def tearDown(self): + with self.marionette.using_context("chrome"): + self.marionette.clear_pref("dom.ipc.processCount") + + # Elements that are accessible with and without the accessibliity API + valid_elementIDs = [ + # Button1 is an accessible button with a valid accessible name + # computed from subtree + "button1", + # Button2 is an accessible button with a valid accessible name + # computed from aria-label + "button2", + # Button13 is an accessible button that is implemented via role="button" + # and is explorable using tabindex="0" + "button13", + # button17 is an accessible button that overrides parent's + # pointer-events:none; property with its own pointer-events:all; + "button17" + ] + + # Elements that are not accessible with the accessibility API + invalid_elementIDs = [ + # Button3 does not have an accessible object + "button3", + # Button4 does not support any accessible actions + "button4", + # Button5 does not have a correct accessibility role and may not be + # manipulated via the accessibility API + "button5", + # Button6 is missing an accesible name + "button6", + # Button7 is not currently visible via the accessibility API and may + # not be manipulated by it + "button7", + # Button8 is not currently visible via the accessibility API and may + # not be manipulated by it (in hidden subtree) + "button8", + # Button14 is accessible button but is not explorable because of lack + # of tabindex that would make it focusable. + "button14" + ] + + # Elements that are either accessible to accessibility API or not accessible + # at all + falsy_elements = [ + # Element is only visible to the accessibility API and may be + # manipulated by it + "button9", + # Element is not currently visible + "button10" + ] + + displayed_elementIDs = [ + "button1", "button2", "button3", "button4", "button5", "button6", + "button9", "no_accessible_but_displayed" + ] + + displayed_but_a11y_hidden_elementIDs = ["button7", "button8"] + + disabled_elementIDs = ["button11", "no_accessible_but_disabled"] + + # Elements that are enabled but otherwise disabled or not explorable via the accessibility API + disabled_accessibility_elementIDs = ["button12", "button15", "button16"] + + # Elements that are reporting selected state + valid_option_elementIDs = ["option1", "option2"] + + def run_element_test(self, ids, testFn): + for id in ids: + element = self.marionette.find_element(By.ID, id) + testFn(element) + + def setup_accessibility(self, enable_a11y_checks=True, navigate=True): + self.marionette.delete_session() + self.marionette.start_session( + {"requiredCapabilities": {"moz:accessibilityChecks": enable_a11y_checks}}) + self.assertEqual( + self.marionette.session_capabilities["moz:accessibilityChecks"], + enable_a11y_checks) + + # Navigate to test_accessibility.html + if navigate: + test_accessibility = self.marionette.absolute_url("test_accessibility.html") + self.marionette.navigate(test_accessibility) + + def test_valid_single_tap(self): + self.setup_accessibility() + # No exception should be raised + self.run_element_test(self.valid_elementIDs, lambda button: button.tap()) + + def test_single_tap_raises_element_not_accessible(self): + self.setup_accessibility() + self.run_element_test(self.invalid_elementIDs, + lambda button: self.assertRaises(ElementNotAccessibleException, + button.tap)) + self.run_element_test(self.falsy_elements, + lambda button: self.assertRaises(ElementNotInteractableException, + button.tap)) + + def test_single_tap_raises_no_exceptions(self): + self.setup_accessibility(False, True) + # No exception should be raised + self.run_element_test(self.invalid_elementIDs, lambda button: button.tap()) + # Elements are invisible + self.run_element_test(self.falsy_elements, + lambda button: self.assertRaises(ElementNotInteractableException, + button.tap)) + + def test_valid_click(self): + self.setup_accessibility() + # No exception should be raised + self.run_element_test(self.valid_elementIDs, lambda button: button.click()) + + def test_click_raises_element_not_accessible(self): + self.setup_accessibility() + self.run_element_test(self.invalid_elementIDs, + lambda button: self.assertRaises(ElementNotAccessibleException, + button.click)) + self.run_element_test(self.falsy_elements, + lambda button: self.assertRaises(ElementNotInteractableException, + button.click)) + + def test_click_raises_no_exceptions(self): + self.setup_accessibility(False, True) + # No exception should be raised + self.run_element_test(self.invalid_elementIDs, lambda button: button.click()) + # Elements are invisible + self.run_element_test(self.falsy_elements, + lambda button: self.assertRaises(ElementNotInteractableException, + button.click)) + + def test_element_visible_but_not_visible_to_accessbility(self): + self.setup_accessibility() + # Elements are displayed but hidden from accessibility API + self.run_element_test(self.displayed_but_a11y_hidden_elementIDs, + lambda element: self.assertRaises(ElementNotAccessibleException, + element.is_displayed)) + + def test_element_is_visible_to_accessibility(self): + self.setup_accessibility() + # No exception should be raised + self.run_element_test(self.displayed_elementIDs, lambda element: element.is_displayed()) + + def test_element_is_not_enabled_to_accessbility(self): + self.setup_accessibility() + # Buttons are enabled but disabled/not-explorable via the accessibility API + self.run_element_test(self.disabled_accessibility_elementIDs, + lambda element: self.assertRaises(ElementNotAccessibleException, + element.is_enabled)) + + # Buttons are enabled but disabled/not-explorable via the accessibility API and thus are not + # clickable via the accessibility API + self.run_element_test(self.disabled_accessibility_elementIDs, + lambda element: self.assertRaises(ElementNotAccessibleException, + element.click)) + + self.setup_accessibility(False, False) + self.run_element_test(self.disabled_accessibility_elementIDs, + lambda element: element.is_enabled()) + self.run_element_test(self.disabled_accessibility_elementIDs, + lambda element: element.click()) + + def test_element_is_enabled_to_accessibility(self): + self.setup_accessibility() + # No exception should be raised + self.run_element_test(self.disabled_elementIDs, lambda element: element.is_enabled()) + + def test_send_keys_raises_no_exception(self): + self.setup_accessibility() + # Sending keys to valid input should not raise any exceptions + self.run_element_test(['input1'], lambda element: element.send_keys("a")) + + self.setup_accessibility(False, False) + # Sending keys to invalid element should not raise any exceptions when raising accessibility + # exceptions is disabled + self.run_element_test(['button5'], lambda element: element.send_keys("abc")) + + def test_send_keys_raises_element_not_accessible(self): + self.setup_accessibility() + # Sending keys to invalid element should raise an exception + self.run_element_test(['button5'], + lambda element: self.assertRaises(ElementNotAccessibleException, + element.send_keys)) + + def test_is_selected_raises_no_exception(self): + self.setup_accessibility() + # No exception should be raised for valid options + self.run_element_test(self.valid_option_elementIDs, lambda element: element.is_selected()) + # No exception should be raised for non-selectable elements + self.run_element_test(self.valid_elementIDs, lambda element: element.is_selected()) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_addons.py b/testing/marionette/harness/marionette_harness/tests/unit/test_addons.py new file mode 100644 index 000000000..25f1c05ab --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_addons.py @@ -0,0 +1,58 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os + +from marionette_driver.addons import Addons, AddonInstallException +from marionette_harness import MarionetteTestCase, skip + + +here = os.path.abspath(os.path.dirname(__file__)) + + +class TestAddons(MarionetteTestCase): + + def setUp(self): + MarionetteTestCase.setUp(self) + self.addons = Addons(self.marionette) + + @property + def all_addon_ids(self): + with self.marionette.using_context('chrome'): + addons = self.marionette.execute_async_script(""" + Components.utils.import("resource://gre/modules/AddonManager.jsm"); + AddonManager.getAllAddons(function(addons){ + let ids = addons.map(function(x) { + return x.id; + }); + marionetteScriptFinished(ids); + }); + """) + + return addons + + def test_install_and_remove_temporary_unsigned_addon(self): + addon_path = os.path.join(here, 'mn-restartless-unsigned.xpi') + + addon_id = self.addons.install(addon_path, temp=True) + self.assertIn(addon_id, self.all_addon_ids) + + self.addons.uninstall(addon_id) + self.assertNotIn(addon_id, self.all_addon_ids) + + def test_install_unsigned_addon(self): + addon_path = os.path.join(here, 'mn-restartless-unsigned.xpi') + + with self.assertRaises(AddonInstallException): + self.addons.install(addon_path) + + @skip("Need to get the test extension signed") + def test_install_and_remove_signed_addon(self): + addon_path = os.path.join(here, 'mn-restartless-signed.xpi') + + addon_id = self.addons.install(addon_path) + self.assertIn(addon_id, self.all_addon_ids) + + self.addons.uninstall(addon_id) + self.assertNotIn(addon_id, self.all_addon_ids) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_anonymous_content.py b/testing/marionette/harness/marionette_harness/tests/unit/test_anonymous_content.py new file mode 100644 index 000000000..1e7779661 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_anonymous_content.py @@ -0,0 +1,90 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import NoSuchElementException +from marionette_driver.marionette import HTMLElement + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestAnonymousNodes(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestAnonymousNodes, self).setUp() + self.marionette.set_context("chrome") + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test_anonymous_content.xul', + 'foo', 'chrome,centerscreen'); + """) + + new_window = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + self.close_all_windows() + + super(TestAnonymousNodes, self).tearDown() + + def test_switch_to_anonymous_frame(self): + self.marionette.find_element(By.ID, "testAnonymousContentBox") + anon_browser_el = self.marionette.find_element(By.ID, "browser") + self.assertTrue("test_anonymous_content.xul" in self.marionette.get_url()) + self.marionette.switch_to_frame(anon_browser_el) + self.assertTrue("test.xul" in self.marionette.get_url()) + self.marionette.find_element(By.ID, "testXulBox") + self.assertRaises(NoSuchElementException, + self.marionette.find_element, By.ID, "testAnonymousContentBox") + + def test_switch_to_anonymous_iframe(self): + self.marionette.find_element(By.ID, "testAnonymousContentBox") + el = self.marionette.find_element(By.ID, "container2") + anon_iframe_el = el.find_element(By.ANON_ATTRIBUTE, {"anonid": "iframe"}) + self.marionette.switch_to_frame(anon_iframe_el) + self.assertTrue("test.xul" in self.marionette.get_url()) + self.marionette.find_element(By.ID, "testXulBox") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, + "testAnonymousContentBox") + + def test_find_anonymous_element_by_attribute(self): + accept_button = (By.ANON_ATTRIBUTE, {"dlgtype": "accept"},) + not_existent = (By.ANON_ATTRIBUTE, {"anonid": "notexistent"},) + + # By using the window document element + start_node = self.marionette.find_element(By.CSS_SELECTOR, ":root") + button = start_node.find_element(*accept_button) + self.assertEquals(HTMLElement, type(button)) + with self.assertRaises(NoSuchElementException): + start_node.find_element(*not_existent) + + # By using the default start node + self.assertEquals(button, self.marionette.find_element(*accept_button)) + with self.assertRaises(NoSuchElementException): + self.marionette.find_element(*not_existent) + + def test_find_anonymous_elements_by_attribute(self): + dialog_buttons = (By.ANON_ATTRIBUTE, {"anonid": "buttons"},) + not_existent = (By.ANON_ATTRIBUTE, {"anonid": "notexistent"},) + + # By using the window document element + start_node = self.marionette.find_element(By.CSS_SELECTOR, ":root") + buttons = start_node.find_elements(*dialog_buttons) + self.assertEquals(1, len(buttons)) + self.assertEquals(HTMLElement, type(buttons[0])) + self.assertListEqual([], start_node.find_elements(*not_existent)) + + # By using the default start node + self.assertListEqual(buttons, self.marionette.find_elements(*dialog_buttons)) + self.assertListEqual([], self.marionette.find_elements(*not_existent)) + + def test_find_anonymous_children(self): + self.assertEquals(HTMLElement, type(self.marionette.find_element(By.ANON, None))) + self.assertEquals(2, len(self.marionette.find_elements(By.ANON, None))) + + frame = self.marionette.find_element(By.ID, "framebox") + with self.assertRaises(NoSuchElementException): + frame.find_element(By.ANON, None) + self.assertListEqual([], frame.find_elements(By.ANON, None)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_browsermobproxy.py b/testing/marionette/harness/marionette_harness/tests/unit/test_browsermobproxy.py new file mode 100644 index 000000000..64b3d1a77 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_browsermobproxy.py @@ -0,0 +1,34 @@ +import datetime + +from marionette_harness.runner import BrowserMobTestCase + + +class TestBrowserMobProxy(BrowserMobTestCase): + """To run this test, you'll need to download the browsermob-proxy from + http://bmp.lightbody.net/, and then pass the path to the startup + script (typically /path/to/browsermob-proxy-2.0.0/bin/browsermob-proxy) + as the --browsermob-script argument when running runtests.py. + + You can additionally pass --browsermob-port to specify the port that + the proxy will run on; it defaults to 8080. + + This test is NOT run in CI, as bmp and dependencies aren't available + there. + """ + + def test_browsermob_proxy_limits(self): + """This illustrates the use of download limits in the proxy, + and verifies that it's slower to load a page @100kbps + than it is to download the same page @1000kbps. + """ + proxy = self.create_browsermob_proxy() + proxy.limits({'downstream_kbps': 1000}) + time1 = datetime.datetime.now() + self.marionette.navigate('http://forecast.weather.gov') + time2 = datetime.datetime.now() + proxy.limits({'downstream_kbps': 100}) + time3 = datetime.datetime.now() + self.marionette.refresh() + time4 = datetime.datetime.now() + self.assertTrue(time4 - time3 > time2 - time1, + "page load @ 100kbps not slower than page load @ 1000kbps") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py new file mode 100644 index 000000000..d3386316d --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py @@ -0,0 +1,253 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import SessionNotCreatedException + +from marionette_harness import MarionetteTestCase + + +class TestCapabilities(MarionetteTestCase): + + def setUp(self): + super(TestCapabilities, self).setUp() + self.caps = self.marionette.session_capabilities + with self.marionette.using_context("chrome"): + self.appinfo = self.marionette.execute_script( + "return Services.appinfo") + self.os_name = self.marionette.execute_script( + "return Services.sysinfo.getProperty('name')").lower() + self.os_version = self.marionette.execute_script( + "return Services.sysinfo.getProperty('version')") + + def test_mandated_capabilities(self): + self.assertIn("browserName", self.caps) + self.assertIn("browserVersion", self.caps) + self.assertIn("platformName", self.caps) + self.assertIn("platformVersion", self.caps) + self.assertIn("acceptInsecureCerts", self.caps) + self.assertIn("timeouts", self.caps) + + self.assertEqual(self.caps["browserName"], self.appinfo["name"].lower()) + self.assertEqual(self.caps["browserVersion"], self.appinfo["version"]) + self.assertEqual(self.caps["platformName"], self.os_name) + self.assertEqual(self.caps["platformVersion"], self.os_version) + self.assertFalse(self.caps["acceptInsecureCerts"]) + self.assertDictEqual(self.caps["timeouts"], + {"implicit": 0, + "page load": 300000, + "script": 30000}) + + def test_supported_features(self): + self.assertIn("rotatable", self.caps) + + def test_additional_capabilities(self): + self.assertIn("moz:processID", self.caps) + self.assertEqual(self.caps["moz:processID"], self.appinfo["processID"]) + self.assertEqual(self.marionette.process_id, self.appinfo["processID"]) + + self.assertIn("moz:profile", self.caps) + if self.marionette.instance is not None: + if self.caps["browserName"] == "fennec": + current_profile = self.marionette.instance.runner.device.app_ctx.remote_profile + else: + current_profile = self.marionette.instance.runner.profile.profile + self.assertEqual(self.caps["moz:profile"], current_profile) + self.assertEqual(self.marionette.profile, current_profile) + + self.assertIn("moz:accessibilityChecks", self.caps) + self.assertFalse(self.caps["moz:accessibilityChecks"]) + self.assertIn("specificationLevel", self.caps) + self.assertEqual(self.caps["specificationLevel"], 0) + + def test_set_specification_level(self): + self.marionette.delete_session() + self.marionette.start_session({"desiredCapabilities": {"specificationLevel": 2}}) + caps = self.marionette.session_capabilities + self.assertEqual(2, caps["specificationLevel"]) + + self.marionette.delete_session() + self.marionette.start_session({"requiredCapabilities": {"specificationLevel": 3}}) + caps = self.marionette.session_capabilities + self.assertEqual(3, caps["specificationLevel"]) + + def test_we_can_pass_in_required_capabilities_on_session_start(self): + self.marionette.delete_session() + capabilities = {"requiredCapabilities": {"browserName": self.appinfo["name"].lower()}} + self.marionette.start_session(capabilities) + caps = self.marionette.session_capabilities + self.assertIn("browserName", caps) + + # Start a new session just to make sure we leave the browser in the + # same state it was before it started the test + self.marionette.start_session() + + def test_capability_types(self): + for value in ["", "invalid", True, 42, []]: + print("testing value {}".format(value)) + with self.assertRaises(SessionNotCreatedException): + print(" with desiredCapabilities") + self.marionette.delete_session() + self.marionette.start_session({"desiredCapabilities": value}) + with self.assertRaises(SessionNotCreatedException): + print(" with requiredCapabilities") + self.marionette.delete_session() + self.marionette.start_session({"requiredCapabilities": value}) + + def test_we_get_valid_uuid4_when_creating_a_session(self): + self.assertNotIn("{", self.marionette.session_id, + "Session ID has {{}} in it: {}".format( + self.marionette.session_id)) + + +class TestCapabilityMatching(MarionetteTestCase): + allowed = [None, "*"] + disallowed = ["", 42, True, {}, []] + + def setUp(self): + MarionetteTestCase.setUp(self) + self.browser_name = self.marionette.session_capabilities["browserName"] + self.platform_name = self.marionette.session_capabilities["platformName"] + self.delete_session() + + def delete_session(self): + if self.marionette.session is not None: + self.marionette.delete_session() + + def test_browser_name_desired(self): + self.marionette.start_session({"desiredCapabilities": {"browserName": self.browser_name}}) + self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name) + + def test_browser_name_required(self): + self.marionette.start_session({"requiredCapabilities": {"browserName": self.browser_name}}) + self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name) + + def test_browser_name_desired_allowed_types(self): + for typ in self.allowed: + self.delete_session() + self.marionette.start_session({"desiredCapabilities": {"browserName": typ}}) + self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name) + + def test_browser_name_desired_disallowed_types(self): + for typ in self.disallowed: + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"desiredCapabilities": {"browserName": typ}}) + + def test_browser_name_required_allowed_types(self): + for typ in self.allowed: + self.delete_session() + self.marionette.start_session({"requiredCapabilities": {"browserName": typ}}) + self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name) + + def test_browser_name_requried_disallowed_types(self): + for typ in self.disallowed: + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"requiredCapabilities": {"browserName": typ}}) + + def test_browser_name_prefers_required(self): + caps = {"desiredCapabilities": {"browserName": "invalid"}, + "requiredCapabilities": {"browserName": "*"}} + self.marionette.start_session(caps) + + def test_browser_name_error_on_invalid_required(self): + with self.assertRaises(SessionNotCreatedException): + caps = {"desiredCapabilities": {"browserName": "*"}, + "requiredCapabilities": {"browserName": "invalid"}} + self.marionette.start_session(caps) + + # TODO(ato): browser version comparison not implemented yet + + def test_platform_name_desired(self): + self.marionette.start_session({"desiredCapabilities": {"platformName": self.platform_name}}) + self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name) + + def test_platform_name_required(self): + self.marionette.start_session({"requiredCapabilities": {"platformName": self.platform_name}}) + self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name) + + def test_platform_name_desired_allowed_types(self): + for typ in self.allowed: + self.delete_session() + self.marionette.start_session({"desiredCapabilities": {"platformName": typ}}) + self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name) + + def test_platform_name_desired_disallowed_types(self): + for typ in self.disallowed: + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"desiredCapabilities": {"platformName": typ}}) + + def test_platform_name_required_allowed_types(self): + for typ in self.allowed: + self.delete_session() + self.marionette.start_session({"requiredCapabilities": {"platformName": typ}}) + self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name) + + def test_platform_name_requried_disallowed_types(self): + for typ in self.disallowed: + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"requiredCapabilities": {"platformName": typ}}) + + def test_platform_name_prefers_required(self): + caps = {"desiredCapabilities": {"platformName": "invalid"}, + "requiredCapabilities": {"platformName": "*"}} + self.marionette.start_session(caps) + + def test_platform_name_error_on_invalid_required(self): + with self.assertRaises(SessionNotCreatedException): + caps = {"desiredCapabilities": {"platformName": "*"}, + "requiredCapabilities": {"platformName": "invalid"}} + self.marionette.start_session(caps) + + # TODO(ato): platform version comparison not imlpemented yet + + def test_accept_insecure_certs(self): + for capability_type in ["desiredCapabilities", "requiredCapabilities"]: + print("testing {}".format(capability_type)) + for value in ["", 42, {}, []]: + print(" type {}".format(type(value))) + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({capability_type: {"acceptInsecureCerts": value}}) + + self.delete_session() + self.marionette.start_session({"desiredCapabilities": {"acceptInsecureCerts": True}}) + self.assertTrue(self.marionette.session_capabilities["acceptInsecureCerts"]) + self.delete_session() + self.marionette.start_session({"requiredCapabilities": {"acceptInsecureCerts": True}}) + + self.assertTrue(self.marionette.session_capabilities["acceptInsecureCerts"]) + + def test_page_load_strategy(self): + for strategy in ["none", "eager", "normal"]: + print("valid strategy {}".format(strategy)) + self.delete_session() + self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": strategy}}) + self.assertEqual(self.marionette.session_capabilities["pageLoadStrategy"], strategy) + + for value in ["", "EAGER", True, 42, {}, []]: + print("invalid strategy {}".format(value)) + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": value}}) + + def test_proxy_default(self): + self.marionette.start_session() + self.assertNotIn("proxy", self.marionette.session_capabilities) + + def test_proxy_desired(self): + self.marionette.start_session({"desiredCapabilities": {"proxy": {"proxyType": "manual"}}}) + self.assertIn("proxy", self.marionette.session_capabilities) + self.assertEqual(self.marionette.session_capabilities["proxy"]["proxyType"], "manual") + self.assertEqual(self.marionette.get_pref("network.proxy.type"), 1) + + def test_proxy_required(self): + self.marionette.start_session({"requiredCapabilities": {"proxy": {"proxyType": "manual"}}}) + self.assertIn("proxy", self.marionette.session_capabilities) + self.assertEqual(self.marionette.session_capabilities["proxy"]["proxyType"], "manual") + self.assertEqual(self.marionette.get_pref("network.proxy.type"), 1) + + def test_timeouts(self): + timeouts = {u"implicit": 123, u"page load": 456, u"script": 789} + caps = {"desiredCapabilities": {"timeouts": timeouts}} + self.marionette.start_session(caps) + self.assertIn("timeouts", self.marionette.session_capabilities) + self.assertDictEqual(self.marionette.session_capabilities["timeouts"], timeouts) + self.assertDictEqual(self.marionette._send_message("getTimeouts"), timeouts) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox.py b/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox.py new file mode 100644 index 000000000..8709d6e32 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox.py @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestCheckbox(MarionetteTestCase): + def test_selected(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + box = self.marionette.find_element(By.NAME, "myCheckBox") + self.assertFalse(box.is_selected()) + box.click() + self.assertTrue(box.is_selected()) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox_chrome.py new file mode 100644 index 000000000..8d800f939 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox_chrome.py @@ -0,0 +1,36 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestSelectedChrome(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestSelectedChrome, self).setUp() + + self.marionette.set_context("chrome") + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test.xul', + '_blank', 'chrome,centerscreen'); + """) + + new_window = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + try: + self.close_all_windows() + finally: + super(TestSelectedChrome, self).tearDown() + + def test_selected(self): + box = self.marionette.find_element(By.ID, "testBox") + self.assertFalse(box.is_selected()) + self.assertFalse(self.marionette.execute_script("arguments[0].checked = true;", [box])) + self.assertTrue(box.is_selected()) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py new file mode 100644 index 000000000..8a9e53bd6 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py @@ -0,0 +1,51 @@ +#Copyright 2007-2009 WebDriver committers +#Copyright 2007-2009 Google Inc. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from marionette_driver import By + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class ChromeTests(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(ChromeTests, self).setUp() + + self.marionette.set_context('chrome') + + def tearDown(self): + self.close_all_windows() + super(ChromeTests, self).tearDown() + + def test_hang_until_timeout(self): + def open_with_menu(): + menu = self.marionette.find_element(By.ID, 'aboutName') + menu.click() + + new_window = self.open_window(trigger=open_with_menu) + self.marionette.switch_to_window(new_window) + + try: + try: + # Raise an exception type which should not be thrown by Marionette + # while running this test. Otherwise it would mask eg. IOError as + # thrown for a socket timeout. + raise NotImplementedError('Exception should not cause a hang when ' + 'closing the chrome window') + finally: + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + except NotImplementedError: + pass diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_async_finish.js b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_async_finish.js new file mode 100644 index 000000000..8d2df3ac2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_async_finish.js @@ -0,0 +1,6 @@ +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_CONTEXT = "chrome"; +ok(true); +(function () { + finish(); +})(); diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_css.py b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_css.py new file mode 100644 index 000000000..fde7e8373 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_css.py @@ -0,0 +1,23 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestChromeElementCSS(MarionetteTestCase): + + def test_we_can_get_css_value_on_chrome_element(self): + self.marionette.navigate("about:blank") + with self.marionette.using_context("chrome"): + element = self.marionette.find_element(By.ID, "identity-icon") + favicon_image = element.value_of_css_property("list-style-image") + + self.assertIn("identity-icon.svg", favicon_image) + + element = self.marionette.find_element(By.ID, "identity-box") + background_colour = element.value_of_css_property("background-color") + + self.assertEqual("transparent", background_colour) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_clearing.py b/testing/marionette/harness/marionette_harness/tests/unit/test_clearing.py new file mode 100644 index 000000000..3fcad45fc --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_clearing.py @@ -0,0 +1,72 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import InvalidElementStateException + +from marionette_harness import MarionetteTestCase + + +class TestClear(MarionetteTestCase): + def testWriteableTextInputShouldClear(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "writableTextInput") + element.clear() + self.assertEqual("", element.get_property("value")) + + def testTextInputShouldNotClearWhenReadOnly(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"readOnlyTextInput") + try: + element.clear() + self.fail("Should not have been able to clear") + except InvalidElementStateException: + pass + + def testWritableTextAreaShouldClear(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"writableTextArea") + element.clear() + self.assertEqual("", element.get_property("value")) + + def testTextAreaShouldNotClearWhenDisabled(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"textAreaNotenabled") + try: + element.clear() + self.fail("Should not have been able to clear") + except InvalidElementStateException: + pass + + def testTextAreaShouldNotClearWhenReadOnly(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"textAreaReadOnly") + try: + element.clear() + self.fail("Should not have been able to clear") + except InvalidElementStateException: + pass + + def testContentEditableAreaShouldClear(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"content-editable") + element.clear() + self.assertEqual("", element.text) + + def testTextInputShouldNotClearWhenDisabled(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + try: + element = self.marionette.find_element(By.ID,"textInputnotenabled") + self.assertFalse(element.is_enabled()) + element.clear() + self.fail("Should not have been able to clear") + except InvalidElementStateException: + pass diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_click.py b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py new file mode 100644 index 000000000..d03062e85 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py @@ -0,0 +1,254 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import urllib + +from marionette_driver.by import By +from marionette_driver import errors +from marionette_driver.wait import Wait + +from marionette_harness import MarionetteTestCase + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + + +# The element in the following HTML is not interactable because it +# is hidden by an overlay when scrolled into the top of the viewport. +# It should be interactable when scrolled in at the bottom of the +# viewport. +fixed_overlay = inline(""" + + +
      overlay
      +
      link + + +""") + + +obscured_overlay = inline(""" + + +
      +link + + +""") + + +class TestLegacyClick(MarionetteTestCase): + """Uses legacy Selenium element displayedness checks.""" + + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.delete_session() + self.marionette.start_session() + + def test_click(self): + self.marionette.navigate(inline(""" + + + """)) + button = self.marionette.find_element(By.TAG_NAME, "button") + button.click() + self.assertEqual(1, self.marionette.execute_script("return window.clicks", sandbox=None)) + + def test_click_number_link(self): + test_html = self.marionette.absolute_url("clicks.html") + self.marionette.navigate(test_html) + self.marionette.find_element(By.LINK_TEXT, "333333").click() + Wait(self.marionette, timeout=30, ignored_exceptions=errors.NoSuchElementException).until( + lambda m: m.find_element(By.ID, "username")) + self.assertEqual(self.marionette.title, "XHTML Test Page") + + def test_clicking_an_element_that_is_not_displayed_raises(self): + test_html = self.marionette.absolute_url("hidden.html") + self.marionette.navigate(test_html) + + with self.assertRaises(errors.ElementNotInteractableException): + self.marionette.find_element(By.ID, "child").click() + + def test_clicking_on_a_multiline_link(self): + test_html = self.marionette.absolute_url("clicks.html") + self.marionette.navigate(test_html) + self.marionette.find_element(By.ID, "overflowLink").click() + self.wait_for_condition(lambda mn: self.marionette.title == "XHTML Test Page") + + def test_scroll_into_view_near_end(self): + self.marionette.navigate(fixed_overlay) + link = self.marionette.find_element(By.TAG_NAME, "a") + link.click() + self.assertTrue(self.marionette.execute_script("return window.clicked", sandbox=None)) + + +class TestClick(TestLegacyClick): + """Uses WebDriver specification compatible element interactability + checks. + """ + + def setUp(self): + TestLegacyClick.setUp(self) + self.marionette.delete_session() + self.marionette.start_session( + {"requiredCapabilities": {"specificationLevel": 1}}) + + def test_click_element_obscured_by_absolute_positioned_element(self): + self.marionette.navigate(obscured_overlay) + overlay = self.marionette.find_element(By.ID, "overlay") + obscured = self.marionette.find_element(By.ID, "obscured") + + overlay.click() + with self.assertRaises(errors.ElementClickInterceptedException): + obscured.click() + + def test_centre_outside_viewport_vertically(self): + self.marionette.navigate(inline(""" + + +
      """)) + + self.marionette.find_element(By.TAG_NAME, "div").click() + + def test_centre_outside_viewport_horizontally(self): + self.marionette.navigate(inline(""" + + +
      """)) + + self.marionette.find_element(By.TAG_NAME, "div").click() + + def test_centre_outside_viewport(self): + self.marionette.navigate(inline(""" + + +
      """)) + + self.marionette.find_element(By.TAG_NAME, "div").click() + + def test_css_transforms(self): + self.marionette.navigate(inline(""" + + +
      """)) + + self.marionette.find_element(By.TAG_NAME, "div").click() + + def test_input_file(self): + self.marionette.navigate(inline("")) + with self.assertRaises(errors.InvalidArgumentException): + self.marionette.find_element(By.TAG_NAME, "input").click() + + def test_container_element(self): + self.marionette.navigate(inline(""" + """)) + option = self.marionette.find_element(By.TAG_NAME, "option") + option.click() + self.assertTrue(option.get_property("selected")) + + def test_container_element_outside_view(self): + self.marionette.navigate(inline(""" + """)) + option = self.marionette.find_element(By.TAG_NAME, "option") + option.click() + self.assertTrue(option.get_property("selected")) + + def test_obscured_element(self): + self.marionette.navigate(obscured_overlay) + overlay = self.marionette.find_element(By.ID, "overlay") + obscured = self.marionette.find_element(By.ID, "obscured") + + overlay.click() + with self.assertRaises(errors.ElementClickInterceptedException): + obscured.click() + self.assertFalse(self.marionette.execute_script("return window.clicked", sandbox=None)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py new file mode 100644 index 000000000..d16b4f105 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py @@ -0,0 +1,35 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestClickChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.root_window = self.marionette.current_window_handle + self.marionette.set_context("chrome") + self.marionette.execute_script( + "window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen')") + self.marionette.switch_to_window("foo") + self.assertNotEqual(self.root_window, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.root_window, self.marionette.current_window_handle) + self.marionette.execute_script("window.close()") + self.marionette.switch_to_window(self.root_window) + MarionetteTestCase.tearDown(self) + + def test_click(self): + def checked(): + return self.marionette.execute_script( + "return arguments[0].checked", + script_args=[box]) + + box = self.marionette.find_element(By.ID, "testBox") + self.assertFalse(checked()) + box.click() + self.assertTrue(checked()) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py b/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py new file mode 100644 index 000000000..437c15e70 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py @@ -0,0 +1,117 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import MoveTargetOutOfBoundsException + +from marionette_harness import MarionetteTestCase, skip, skip_if_mobile + + +class TestClickScrolling(MarionetteTestCase): + + + def test_clicking_on_anchor_scrolls_page(self): + scrollScript = """ + var pageY; + if (typeof(window.pageYOffset) == 'number') { + pageY = window.pageYOffset; + } else { + pageY = document.documentElement.scrollTop; + } + return pageY;""" + + test_html = self.marionette.absolute_url("macbeth.html") + self.marionette.navigate(test_html) + + self.marionette.find_element(By.PARTIAL_LINK_TEXT, "last speech").click() + y_offset = self.marionette.execute_script(scrollScript) + + # Focusing on to click, but not actually following, + # the link will scroll it in to view, which is a few + # pixels further than 0 + + self.assertTrue(y_offset > 300) + + def test_should_scroll_to_click_on_an_element_hidden_by_overflow(self): + test_html = self.marionette.absolute_url("click_out_of_bounds_overflow.html") + self.marionette.navigate(test_html) + + link = self.marionette.find_element(By.ID, "link") + try: + link.click() + except MoveTargetOutOfBoundsException: + self.fail("Should not be out of bounds") + + @skip("Bug 1200197 - Cannot interact with elements hidden inside overflow:scroll") + def test_should_be_able_to_click_on_an_element_hidden_by_overflow(self): + test_html = self.marionette.absolute_url("scroll.html") + self.marionette.navigate(test_html) + + link = self.marionette.find_element(By.ID, "line8") + link.click() + self.assertEqual("line8", self.marionette.find_element(By.ID, "clicked").text) + + def test_should_not_scroll_overflow_elements_which_are_visible(self): + test_html = self.marionette.absolute_url("scroll2.html") + self.marionette.navigate(test_html) + + list_el = self.marionette.find_element(By.TAG_NAME, "ul") + item = list_el.find_element(By.ID, "desired") + item.click() + y_offset = self.marionette.execute_script("return arguments[0].scrollTop;", script_args=[list_el]) + self.assertEqual(0, y_offset) + + def test_should_not_scroll_if_already_scrolled_and_element_is_in_view(self): + test_html = self.marionette.absolute_url("scroll3.html") + self.marionette.navigate(test_html) + + button1 = self.marionette.find_element(By.ID, "button1") + button2 = self.marionette.find_element(By.ID, "button2") + + button2.click() + scroll_top = self.marionette.execute_script("return document.body.scrollTop;") + button1.click() + + self.assertEqual(scroll_top, self.marionette.execute_script("return document.body.scrollTop;")) + + def test_should_be_able_to_click_radio_button_scrolled_into_view(self): + test_html = self.marionette.absolute_url("scroll4.html") + self.marionette.navigate(test_html) + + # If we dont throw we are good + self.marionette.find_element(By.ID, "radio").click() + + def test_should_scroll_elements_if_click_point_is_out_of_view_but_element_is_in_view(self): + test_html = self.marionette.absolute_url("element_outside_viewport.html") + + for s in ["top", "bottom"]: + self.marionette.navigate(test_html) + scroll_y = self.marionette.execute_script("return window.scrollY;") + self.marionette.find_element(By.ID, "{}-70".format(s)).click() + self.assertNotEqual(scroll_y, self.marionette.execute_script("return window.scrollY;")) + + for s in ["left", "right"]: + self.marionette.navigate(test_html) + scroll_x = self.marionette.execute_script("return window.scrollX;") + self.marionette.find_element(By.ID, "{}-70".format(s)).click() + self.assertNotEqual(scroll_x, self.marionette.execute_script("return window.scrollX;")) + + @skip_if_mobile("Bug 1293855 - Lists differ: [70, 70] != [70, 120]") + def test_should_not_scroll_elements_if_click_point_is_in_view(self): + test_html = self.marionette.absolute_url("element_outside_viewport.html") + + for s in ["top", "right", "bottom", "left"]: + for p in ["50", "30"]: + self.marionette.navigate(test_html) + scroll = self.marionette.execute_script("return [window.scrollX, window.scrollY];") + self.marionette.find_element(By.ID, "{0}-{1}".format(s, p)).click() + self.assertEqual(scroll, self.marionette.execute_script("return [window.scrollX, window.scrollY];")) + + @skip("Bug 1003687") + def test_should_scroll_overflow_elements_if_click_point_is_out_of_view_but_element_is_in_view(self): + test_html = self.marionette.absolute_url("scroll5.html") + self.marionette.navigate(test_html) + + self.marionette.find_element(By.ID, "inner").click() + self.assertEqual("clicked", self.marionette.find_element(By.ID, "clicked").text) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_cookies.py b/testing/marionette/harness/marionette_harness/tests/unit/test_cookies.py new file mode 100644 index 000000000..f7841c73e --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_cookies.py @@ -0,0 +1,115 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import calendar +import random +import time + +from marionette_driver.errors import UnsupportedOperationException +from marionette_harness import MarionetteTestCase + + +class CookieTest(MarionetteTestCase): + + def setUp(self): + MarionetteTestCase.setUp(self) + test_url = self.marionette.absolute_url('test.html') + self.marionette.navigate(test_url) + self.COOKIE_A = {"name": "foo", + "value": "bar", + "path": "/", + "secure": False} + + def tearDown(self): + self.marionette.delete_all_cookies() + MarionetteTestCase.tearDown(self) + + def test_add_cookie(self): + self.marionette.add_cookie(self.COOKIE_A) + cookie_returned = str(self.marionette.execute_script("return document.cookie")) + self.assertTrue(self.COOKIE_A["name"] in cookie_returned) + + def test_adding_a_cookie_that_expired_in_the_past(self): + cookie = self.COOKIE_A.copy() + cookie["expiry"] = calendar.timegm(time.gmtime()) - 1 + self.marionette.add_cookie(cookie) + cookies = self.marionette.get_cookies() + self.assertEquals(0, len(cookies)) + + def test_chrome_error(self): + with self.marionette.using_context("chrome"): + self.assertRaises(UnsupportedOperationException, + self.marionette.add_cookie, self.COOKIE_A) + self.assertRaises(UnsupportedOperationException, + self.marionette.delete_cookie, self.COOKIE_A) + self.assertRaises(UnsupportedOperationException, + self.marionette.delete_all_cookies) + self.assertRaises(UnsupportedOperationException, + self.marionette.get_cookies) + + def test_delete_all_cookie(self): + self.marionette.add_cookie(self.COOKIE_A) + cookie_returned = str(self.marionette.execute_script("return document.cookie")) + print cookie_returned + self.assertTrue(self.COOKIE_A["name"] in cookie_returned) + self.marionette.delete_all_cookies() + self.assertFalse(self.marionette.get_cookies()) + + def test_delete_cookie(self): + self.marionette.add_cookie(self.COOKIE_A) + cookie_returned = str(self.marionette.execute_script("return document.cookie")) + self.assertTrue(self.COOKIE_A["name"] in cookie_returned) + self.marionette.delete_cookie("foo") + cookie_returned = str(self.marionette.execute_script("return document.cookie")) + self.assertFalse(self.COOKIE_A["name"] in cookie_returned) + + def test_should_get_cookie_by_name(self): + key = "key_{}".format(int(random.random()*10000000)) + self.marionette.execute_script("document.cookie = arguments[0] + '=set';", [key]) + + cookie = self.marionette.get_cookie(key) + self.assertEquals("set", cookie["value"]) + + def test_get_all_cookies(self): + key1 = "key_{}".format(int(random.random()*10000000)) + key2 = "key_{}".format(int(random.random()*10000000)) + + cookies = self.marionette.get_cookies() + count = len(cookies) + + one = {"name" :key1, + "value": "value"} + two = {"name":key2, + "value": "value"} + + self.marionette.add_cookie(one) + self.marionette.add_cookie(two) + + test_url = self.marionette.absolute_url('test.html') + self.marionette.navigate(test_url) + cookies = self.marionette.get_cookies() + self.assertEquals(count + 2, len(cookies)) + + def test_should_not_delete_cookies_with_a_similar_name(self): + cookieOneName = "fish" + cookie1 = {"name" :cookieOneName, + "value":"cod"} + cookie2 = {"name" :cookieOneName + "x", + "value": "earth"} + self.marionette.add_cookie(cookie1) + self.marionette.add_cookie(cookie2) + + self.marionette.delete_cookie(cookieOneName) + cookies = self.marionette.get_cookies() + + self.assertFalse(cookie1["name"] == cookies[0]["name"], msg=str(cookies)) + self.assertEquals(cookie2["name"] , cookies[0]["name"], msg=str(cookies)) + + def test_we_get_required_elements_when_available(self): + self.marionette.add_cookie(self.COOKIE_A) + cookies = self.marionette.get_cookies() + + self.assertIn("name", cookies[0], 'name not available') + self.assertIn("value", cookies[0], 'value not available') + self.assertIn("httpOnly", cookies[0], 'httpOnly not available') diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py b/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py new file mode 100644 index 000000000..7e74f0857 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py @@ -0,0 +1,155 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import glob +import shutil + +from marionette_driver.errors import MarionetteException +# Import runner module to monkey patch mozcrash module +from mozrunner.base import runner + +from marionette_harness import MarionetteTestCase, expectedFailure, run_if_e10s + + +class MockMozCrash(object): + """Mock object to replace original mozcrash methods.""" + + def __init__(self, marionette): + self.marionette = marionette + + with self.marionette.using_context('chrome'): + self.crash_reporter_enabled = self.marionette.execute_script(""" + try { + Components.classes["@mozilla.org/toolkit/crash-reporter;1"]. + getService(Components.interfaces.nsICrashReporter); + return true; + } catch (exc) { + return false; + } + """) + + def check_for_crashes(self, dump_directory, *args, **kwargs): + minidump_files = glob.glob('{}/*.dmp'.format(dump_directory)) + shutil.rmtree(dump_directory, ignore_errors=True) + + if self.crash_reporter_enabled: + return len(minidump_files) + else: + return len(minidump_files) == 0 + + def log_crashes(self, logger, dump_directory, *args, **kwargs): + return self.check_for_crashes(dump_directory, *args, **kwargs) + + +class BaseCrashTestCase(MarionetteTestCase): + + # Reduce the timeout for faster processing of the tests + socket_timeout = 10 + + def setUp(self): + super(BaseCrashTestCase, self).setUp() + + self.mozcrash_mock = MockMozCrash(self.marionette) + self.crash_count = self.marionette.crashed + self.pid = self.marionette.process_id + self.remote_uri = self.marionette.absolute_url("javascriptPage.html") + + def tearDown(self): + self.marionette.crashed = self.crash_count + + super(BaseCrashTestCase, self).tearDown() + + def crash(self, chrome=True): + context = 'chrome' if chrome else 'content' + sandbox = None if chrome else 'system' + + # Monkey patch mozcrash to avoid crash info output only for our triggered crashes. + mozcrash = runner.mozcrash + runner.mozcrash = self.mozcrash_mock + + socket_timeout = self.marionette.client.socket_timeout + + self.marionette.set_context(context) + try: + self.marionette.client.socket_timeout = self.socket_timeout + self.marionette.execute_script(""" + // Copied from crash me simple + Components.utils.import("resource://gre/modules/ctypes.jsm"); + + // ctypes checks for NULL pointer derefs, so just go near-NULL. + var zero = new ctypes.intptr_t(8); + var badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t)); + var crash = badptr.contents; + """, sandbox=sandbox) + finally: + runner.mozcrash = mozcrash + self.marionette.client.socket_timeout = socket_timeout + + +class TestCrash(BaseCrashTestCase): + + def test_crash_chrome_process(self): + self.assertRaisesRegexp(IOError, 'Process crashed', + self.crash, chrome=True) + self.assertEqual(self.marionette.crashed, 1) + self.assertIsNone(self.marionette.session) + self.assertRaisesRegexp(MarionetteException, 'Please start a session', + self.marionette.get_url) + + self.marionette.start_session() + self.assertNotEqual(self.marionette.process_id, self.pid) + + # TODO: Bug 1314594 - Causes a hang for the communication between the + # chrome and frame script. + # self.marionette.get_url() + + @run_if_e10s("Content crashes only exist in e10s mode") + def test_crash_content_process(self): + # If e10s is disabled the chrome process crashes + self.marionette.navigate(self.remote_uri) + + self.assertRaisesRegexp(IOError, 'Content process crashed', + self.crash, chrome=False) + self.assertEqual(self.marionette.crashed, 1) + self.assertIsNone(self.marionette.session) + self.assertRaisesRegexp(MarionetteException, 'Please start a session', + self.marionette.get_url) + + self.marionette.start_session() + self.assertNotEqual(self.marionette.process_id, self.pid) + self.marionette.get_url() + + @expectedFailure + def test_unexpected_crash(self): + self.crash(chrome=True) + + +class TestCrashInSetUp(BaseCrashTestCase): + + def setUp(self): + super(TestCrashInSetUp, self).setUp() + + self.assertRaisesRegexp(IOError, 'Process crashed', + self.crash, chrome=True) + self.assertEqual(self.marionette.crashed, 1) + self.assertIsNone(self.marionette.session) + + def test_crash_in_setup(self): + self.marionette.start_session() + self.assertNotEqual(self.marionette.process_id, self.pid) + + +class TestCrashInTearDown(BaseCrashTestCase): + + def tearDown(self): + try: + self.assertRaisesRegexp(IOError, 'Process crashed', + self.crash, chrome=True) + finally: + self.assertEqual(self.marionette.crashed, 1) + self.assertIsNone(self.marionette.session) + super(TestCrashInTearDown, self).tearDown() + + def test_crash_in_teardown(self): + pass diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py b/testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py new file mode 100644 index 000000000..8e4ae0d32 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py @@ -0,0 +1,67 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness.marionette_test import ( + parameterized, + with_parameters, + MetaParameterized, + MarionetteTestCase +) + +class Parameterizable(object): + __metaclass__ = MetaParameterized + +class TestDataDriven(MarionetteTestCase): + def test_parameterized(self): + class Test(Parameterizable): + def __init__(self): + self.parameters = [] + + @parameterized('1', 'thing', named=43) + @parameterized('2', 'thing2') + def test(self, thing, named=None): + self.parameters.append((thing, named)) + + self.assertFalse(hasattr(Test, 'test')) + self.assertTrue(hasattr(Test, 'test_1')) + self.assertTrue(hasattr(Test, 'test_2')) + + test = Test() + test.test_1() + test.test_2() + + self.assertEquals(test.parameters, [('thing', 43), ('thing2', None)]) + + def test_with_parameters(self): + DATA = [('1', ('thing',), {'named': 43}), + ('2', ('thing2',), {'named': None})] + + class Test(Parameterizable): + def __init__(self): + self.parameters = [] + + @with_parameters(DATA) + def test(self, thing, named=None): + self.parameters.append((thing, named)) + + self.assertFalse(hasattr(Test, 'test')) + self.assertTrue(hasattr(Test, 'test_1')) + self.assertTrue(hasattr(Test, 'test_2')) + + test = Test() + test.test_1() + test.test_2() + + self.assertEquals(test.parameters, [('thing', 43), ('thing2', None)]) + + def test_parameterized_same_name_raises_error(self): + with self.assertRaises(KeyError): + class Test(Parameterizable): + @parameterized('1', 'thing', named=43) + @parameterized('1', 'thing2') + def test(self, thing, named=None): + pass + + def test_marionette_test_case_is_parameterizable(self): + self.assertTrue(issubclass(MarionetteTestCase.__metaclass__, MetaParameterized)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py b/testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py new file mode 100644 index 000000000..2d224fff2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py @@ -0,0 +1,29 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from datetime import datetime + +from marionette_driver.by import By +from marionette_driver.date_time_value import DateTimeValue +from marionette_harness import MarionetteTestCase + + +class TestDateTime(MarionetteTestCase): + def test_set_date(self): + test_html = self.marionette.absolute_url("datetimePage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "date-test") + dt_value = DateTimeValue(element) + dt_value.date = datetime(1998, 6, 2) + self.assertEqual("1998-06-02", element.get_property("value")) + + def test_set_time(self): + test_html = self.marionette.absolute_url("datetimePage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "time-test") + dt_value = DateTimeValue(element) + dt_value.time = datetime(1998, 11, 19, 9, 8, 7) + self.assertEqual("09:08:07", element.get_property("value")) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py b/testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py new file mode 100644 index 000000000..9023a84ab --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py @@ -0,0 +1,483 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import re +import urllib + +from marionette_driver.by import By +from marionette_driver.errors import NoSuchElementException, InvalidSelectorException +from marionette_driver.marionette import HTMLElement + +from marionette_harness import MarionetteTestCase, skip + + +def inline(doc, doctype="html"): + if doctype == "html": + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + elif doctype == "xhtml": + return "data:application/xhtml+xml,{}".format(urllib.quote( +r""" + + + XHTML might be the future + + + + {} + +""".format(doc))) + + +id_html = inline("

      ", doctype="html") +id_xhtml = inline('

      ', doctype="xhtml") +parent_child_html = inline("

      ", doctype="html") +parent_child_xhtml = inline('

      ', doctype="xhtml") +children_html = inline("

      foo

      bar

      ", doctype="html") +children_xhtml = inline("

      foo

      bar

      ", doctype="xhtml") +class_html = inline("

      ", doctype="html") +class_xhtml = inline('

      ', doctype="xhtml") +name_html = inline("

      ", doctype="html") +name_xhtml = inline('

      ', doctype="xhtml") +link_html = inline("

      foo bar", doctype="html") +link_html_with_trailing_space = inline("

      a link with a trailing space ") +link_xhtml = inline('

      foo bar

      ', doctype="xhtml") + + +class TestFindElementHTML(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.timeout.implicit = 0 + + def test_id(self): + self.marionette.navigate(id_html) + expected = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.ID, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(expected, found) + + def test_child_element(self): + self.marionette.navigate(parent_child_html) + parent = self.marionette.find_element(By.ID, "parent") + child = self.marionette.find_element(By.ID, "child") + found = parent.find_element(By.TAG_NAME, "p") + self.assertEqual(found.tag_name, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(child, found) + + def test_tag_name(self): + self.marionette.navigate(children_html) + el = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.TAG_NAME, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_class_name(self): + self.marionette.navigate(class_html) + el = self.marionette.execute_script("return document.querySelector('.foo')") + found = self.marionette.find_element(By.CLASS_NAME, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_by_name(self): + self.marionette.navigate(name_html) + el = self.marionette.execute_script("return document.querySelector('[name=foo]')") + found = self.marionette.find_element(By.NAME, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_css_selector(self): + self.marionette.navigate(children_html) + el = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.CSS_SELECTOR, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_invalid_css_selector_should_throw(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_element(By.CSS_SELECTOR, "#") + + def test_link_text(self): + self.marionette.navigate(link_html) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.LINK_TEXT, "foo bar") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_link_text_with_trailing_space(self): + self.marionette.navigate(link_html_with_trailing_space) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.LINK_TEXT, "a link with a trailing space") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_partial_link_text(self): + self.marionette.navigate(link_html) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.PARTIAL_LINK_TEXT, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_xpath(self): + self.marionette.navigate(id_html) + el = self.marionette.execute_script("return document.querySelector('#foo')") + found = self.marionette.find_element(By.XPATH, "id('foo')") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_not_found(self): + self.marionette.timeout.implicit = 0 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CLASS_NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CSS_SELECTOR, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.PARTIAL_LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.TAG_NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.XPATH, "cheese") + + def test_not_found_implicit_wait(self): + self.marionette.timeout.implicit = 0.5 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CLASS_NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CSS_SELECTOR, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.PARTIAL_LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.TAG_NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.XPATH, "cheese") + + def test_not_found_from_element(self): + self.marionette.timeout.implicit = 0 + self.marionette.navigate(id_html) + el = self.marionette.find_element(By.ID, "foo") + self.assertRaises(NoSuchElementException, el.find_element, By.CLASS_NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.CSS_SELECTOR, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.ID, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.PARTIAL_LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.TAG_NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.XPATH, "cheese") + + def test_not_found_implicit_wait_from_element(self): + self.marionette.timeout.implicit = 0.5 + self.marionette.navigate(id_html) + el = self.marionette.find_element(By.ID, "foo") + self.assertRaises(NoSuchElementException, el.find_element, By.CLASS_NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.CSS_SELECTOR, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.ID, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.PARTIAL_LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.TAG_NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.XPATH, "cheese") + + def test_css_selector_scope_doesnt_start_at_rootnode(self): + self.marionette.navigate(parent_child_html) + el = self.marionette.find_element(By.ID, "child") + parent = self.marionette.find_element(By.ID, "parent") + found = parent.find_element(By.CSS_SELECTOR, "p") + self.assertEqual(el, found) + + def test_unknown_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_elements("foo", "bar") + + def test_element_id_is_valid_uuid(self): + self.marionette.navigate(id_html) + el = self.marionette.find_element(By.TAG_NAME, "p") + uuid_regex = re.compile('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$') + self.assertIsNotNone(re.search(uuid_regex, el.id), + 'UUID for the WebElement is not valid. ID is {}'\ + .format(el.id)) + + def test_invalid_xpath_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_element(By.XPATH, "count(//input)") + with self.assertRaises(InvalidSelectorException): + parent = self.marionette.execute_script("return document.documentElement") + parent.find_element(By.XPATH, "count(//input)") + + def test_invalid_css_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_element(By.CSS_SELECTOR, "") + with self.assertRaises(InvalidSelectorException): + parent = self.marionette.execute_script("return document.documentElement") + parent.find_element(By.CSS_SELECTOR, "") + + def test_finding_active_element_returns_element(self): + self.marionette.navigate(id_html) + active = self.marionette.execute_script("return document.activeElement") + self.assertEqual(active, self.marionette.get_active_element()) + + +class TestFindElementXHTML(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.timeout.implicit = 0 + + def test_id(self): + self.marionette.navigate(id_xhtml) + expected = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.ID, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(expected, found) + + def test_child_element(self): + self.marionette.navigate(parent_child_xhtml) + parent = self.marionette.find_element(By.ID, "parent") + child = self.marionette.find_element(By.ID, "child") + found = parent.find_element(By.TAG_NAME, "p") + self.assertEqual(found.tag_name, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(child, found) + + def test_tag_name(self): + self.marionette.navigate(children_xhtml) + el = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.TAG_NAME, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_class_name(self): + self.marionette.navigate(class_xhtml) + el = self.marionette.execute_script("return document.querySelector('.foo')") + found = self.marionette.find_element(By.CLASS_NAME, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_by_name(self): + self.marionette.navigate(name_xhtml) + el = self.marionette.execute_script("return document.querySelector('[name=foo]')") + found = self.marionette.find_element(By.NAME, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_css_selector(self): + self.marionette.navigate(children_xhtml) + el = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.CSS_SELECTOR, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_link_text(self): + self.marionette.navigate(link_xhtml) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.LINK_TEXT, "foo bar") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_partial_link_text(self): + self.marionette.navigate(link_xhtml) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.PARTIAL_LINK_TEXT, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_xpath(self): + self.marionette.navigate(id_xhtml) + el = self.marionette.execute_script("return document.querySelector('#foo')") + found = self.marionette.find_element(By.XPATH, "id('foo')") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_css_selector_scope_does_not_start_at_rootnode(self): + self.marionette.navigate(parent_child_xhtml) + el = self.marionette.find_element(By.ID, "child") + parent = self.marionette.find_element(By.ID, "parent") + found = parent.find_element(By.CSS_SELECTOR, "p") + self.assertEqual(el, found) + + def test_active_element(self): + self.marionette.navigate(id_xhtml) + active = self.marionette.execute_script("return document.activeElement") + self.assertEqual(active, self.marionette.get_active_element()) + + +class TestFindElementsHTML(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.timeout.implicit = 0 + + def assertItemsIsInstance(self, items, typ): + for item in items: + self.assertIsInstance(item, typ) + + def test_child_elements(self): + self.marionette.navigate(children_html) + parent = self.marionette.find_element(By.TAG_NAME, "div") + children = self.marionette.find_elements(By.TAG_NAME, "p") + found = parent.find_elements(By.TAG_NAME, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(found, children) + + def test_tag_name(self): + self.marionette.navigate(children_html) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.TAG_NAME, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_class_name(self): + self.marionette.navigate(class_html) + els = self.marionette.execute_script("return document.querySelectorAll('.foo')") + found = self.marionette.find_elements(By.CLASS_NAME, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_by_name(self): + self.marionette.navigate(name_html) + els = self.marionette.execute_script("return document.querySelectorAll('[name=foo]')") + found = self.marionette.find_elements(By.NAME, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_css_selector(self): + self.marionette.navigate(children_html) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.CSS_SELECTOR, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_invalid_css_selector_should_throw(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_elements(By.CSS_SELECTOR, "#") + + def test_link_text(self): + self.marionette.navigate(link_html) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.LINK_TEXT, "foo bar") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_link_text_with_trailing_space(self): + self.marionette.navigate(link_html_with_trailing_space) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.LINK_TEXT, "a link with a trailing space") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + + def test_partial_link_text(self): + self.marionette.navigate(link_html) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.PARTIAL_LINK_TEXT, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_xpath(self): + self.marionette.navigate(children_html) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.XPATH, ".//p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_css_selector_scope_doesnt_start_at_rootnode(self): + self.marionette.navigate(parent_child_html) + els = self.marionette.find_elements(By.ID, "child") + parent = self.marionette.find_element(By.ID, "parent") + found = parent.find_elements(By.CSS_SELECTOR, "p") + self.assertSequenceEqual(els, found) + + def test_unknown_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_element("foo", "bar") + + def test_element_id_is_valid_uuid(self): + self.marionette.navigate(id_html) + els = self.marionette.find_elements(By.TAG_NAME, "p") + uuid_regex = re.compile('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$') + self.assertIsNotNone(re.search(uuid_regex, els[0].id), + 'UUID for the WebElement is not valid. ID is {}'\ + .format(els[0].id)) + + def test_invalid_xpath_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_elements(By.XPATH, "count(//input)") + with self.assertRaises(InvalidSelectorException): + parent = self.marionette.execute_script("return document.documentElement") + parent.find_elements(By.XPATH, "count(//input)") + + def test_invalid_css_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_elements(By.CSS_SELECTOR, "") + with self.assertRaises(InvalidSelectorException): + parent = self.marionette.execute_script("return document.documentElement") + parent.find_elements(By.CSS_SELECTOR, "") + + +class TestFindElementsXHTML(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.timeout.implicit = 0 + + def assertItemsIsInstance(self, items, typ): + for item in items: + self.assertIsInstance(item, typ) + + def test_child_elements(self): + self.marionette.navigate(children_xhtml) + parent = self.marionette.find_element(By.TAG_NAME, "div") + children = self.marionette.find_elements(By.TAG_NAME, "p") + found = parent.find_elements(By.TAG_NAME, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(found, children) + + def test_tag_name(self): + self.marionette.navigate(children_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.TAG_NAME, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_class_name(self): + self.marionette.navigate(class_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('.foo')") + found = self.marionette.find_elements(By.CLASS_NAME, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_by_name(self): + self.marionette.navigate(name_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('[name=foo]')") + found = self.marionette.find_elements(By.NAME, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_css_selector(self): + self.marionette.navigate(children_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.CSS_SELECTOR, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_link_text(self): + self.marionette.navigate(link_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.LINK_TEXT, "foo bar") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_partial_link_text(self): + self.marionette.navigate(link_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.PARTIAL_LINK_TEXT, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + @skip("XHTML namespace not yet supported") + def test_xpath(self): + self.marionette.navigate(children_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.XPATH, "//xhtml:p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_css_selector_scope_doesnt_start_at_rootnode(self): + self.marionette.navigate(parent_child_xhtml) + els = self.marionette.find_elements(By.ID, "child") + parent = self.marionette.find_element(By.ID, "parent") + found = parent.find_elements(By.CSS_SELECTOR, "p") + self.assertSequenceEqual(els, found) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py new file mode 100644 index 000000000..0344b4b9c --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py @@ -0,0 +1,162 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import types +import urllib + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +boolean_attributes = { + "audio": ["autoplay", "controls", "loop", "muted"], + "button": ["autofocus", "disabled", "formnovalidate"], + "details": ["open"], + "dialog": ["open"], + "fieldset": ["disabled"], + "form": ["novalidate"], + "iframe": ["allowfullscreen"], + "img": ["ismap"], + "input": ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"], + "menuitem": ["checked", "default", "disabled"], + "object": ["typemustmatch"], + "ol": ["reversed"], + "optgroup": ["disabled"], + "option": ["disabled", "selected"], + "script": ["async", "defer"], + "select": ["autofocus", "disabled", "multiple", "required"], + "textarea": ["autofocus", "disabled", "readonly", "required"], + "track": ["default"], + "video": ["autoplay", "controls", "loop", "muted"], +} + + +def inline(doc, doctype="html"): + if doctype == "html": + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + elif doctype == "xhtml": + return "data:application/xhtml+xml,{}".format(urllib.quote( +r""" + + + XHTML might be the future + + + + {} + +""".format(doc))) + + +attribute = inline("") +input = inline("") +disabled = inline("") +check = inline("") + + +class TestIsElementEnabled(MarionetteTestCase): + def test_is_enabled(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + l = self.marionette.find_element(By.NAME, "myCheckBox") + self.assertTrue(l.is_enabled()) + self.marionette.execute_script("arguments[0].disabled = true;", [l]) + self.assertFalse(l.is_enabled()) + + +class TestIsElementDisplayed(MarionetteTestCase): + def test_is_displayed(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + l = self.marionette.find_element(By.NAME, "myCheckBox") + self.assertTrue(l.is_displayed()) + self.marionette.execute_script("arguments[0].hidden = true;", [l]) + self.assertFalse(l.is_displayed()) + + +class TestGetElementAttribute(MarionetteTestCase): + def test_normal_attribute(self): + self.marionette.navigate(inline("

      ")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("style") + self.assertIsInstance(attr, types.StringTypes) + self.assertEqual("foo", attr) + + def test_boolean_attributes(self): + for tag, attrs in boolean_attributes.iteritems(): + for attr in attrs: + print("testing boolean attribute <{0} {1}>".format(tag, attr)) + doc = inline("<{0} {1}>".format(tag, attr)) + self.marionette.navigate(doc) + el = self.marionette.find_element(By.TAG_NAME, tag) + res = el.get_attribute(attr) + self.assertIsInstance(res, types.StringTypes) + self.assertEqual("true", res) + + def test_global_boolean_attributes(self): + self.marionette.navigate(inline("

      foo")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("hidden") + self.assertIsNone(attr) + + self.marionette.navigate(inline("

      foo")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("itemscope") + self.assertIsInstance(attr, types.StringTypes) + self.assertEqual("true", attr) + + self.marionette.navigate(inline("

      foo")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("itemscope") + self.assertIsNone(attr) + + # TODO(ato): Test for custom elements + + def test_xhtml(self): + doc = inline("

      ", doctype="xhtml") + self.marionette.navigate(doc) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("hidden") + self.assertIsInstance(attr, types.StringTypes) + self.assertEqual("true", attr) + + +class TestGetElementProperty(MarionetteTestCase): + def test_get(self): + self.marionette.navigate(disabled) + el = self.marionette.find_element(By.TAG_NAME, "input") + prop = el.get_property("disabled") + self.assertIsInstance(prop, bool) + self.assertTrue(prop) + + def test_missing_property_returns_default(self): + self.marionette.navigate(input) + el = self.marionette.find_element(By.TAG_NAME, "input") + prop = el.get_property("checked") + self.assertIsInstance(prop, bool) + self.assertFalse(prop) + + def test_attribute_not_returned(self): + self.marionette.navigate(attribute) + el = self.marionette.find_element(By.TAG_NAME, "input") + self.assertEqual(el.get_property("foo"), None) + + def test_manipulated_element(self): + self.marionette.navigate(check) + el = self.marionette.find_element(By.TAG_NAME, "input") + self.assertEqual(el.get_property("checked"), False) + + el.click() + self.assertEqual(el.get_property("checked"), True) + + el.click() + self.assertEqual(el.get_property("checked"), False) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py new file mode 100644 index 000000000..01ed355c4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py @@ -0,0 +1,85 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase, skip + + +class TestIsElementEnabledChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_enabled(self): + l = self.marionette.find_element(By.ID, "textInput") + self.assertTrue(l.is_enabled()) + self.marionette.execute_script("arguments[0].disabled = true;", [l]) + self.assertFalse(l.is_enabled()) + self.marionette.execute_script("arguments[0].disabled = false;", [l]) + + def test_can_get_element_rect(self): + l = self.marionette.find_element(By.ID, "textInput") + rect = l.rect + self.assertTrue(rect['x'] > 0) + self.assertTrue(rect['y'] > 0) + + +@skip("Switched off in bug 896043, and to be turned on in bug 896046") +class TestIsElementDisplayed(MarionetteTestCase): + def test_isDisplayed(self): + l = self.marionette.find_element(By.ID, "textInput") + self.assertTrue(l.is_displayed()) + self.marionette.execute_script("arguments[0].hidden = true;", [l]) + self.assertFalse(l.is_displayed()) + self.marionette.execute_script("arguments[0].hidden = false;", [l]) + + +class TestGetElementAttributeChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_get(self): + el = self.marionette.execute_script("return window.document.getElementById('textInput');") + self.assertEqual(el.get_attribute("id"), "textInput") + +class TestGetElementProperty(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_get(self): + el = self.marionette.execute_script("return window.document.getElementById('textInput');") + self.assertEqual(el.get_property("id"), "textInput") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py new file mode 100644 index 000000000..ebabd3344 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestElementSize(MarionetteTestCase): + def testShouldReturnTheSizeOfALink(self): + test_html = self.marionette.absolute_url("testSize.html") + self.marionette.navigate(test_html) + shrinko = self.marionette.find_element(By.ID, 'linkId') + size = shrinko.rect + self.assertTrue(size['width'] > 0) + self.assertTrue(size['height'] > 0) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize_chrome.py new file mode 100644 index 000000000..e2bb34715 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize_chrome.py @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestElementSizeChrome(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestElementSizeChrome, self).setUp() + + self.marionette.set_context("chrome") + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test2.xul', + 'foo', 'chrome,centerscreen'); + """) + + new_window = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + self.close_all_windows() + super(TestElementSizeChrome, self).tearDown() + + def testShouldReturnTheSizeOfAnInput(self): + shrinko = self.marionette.find_element(By.ID, 'textInput') + size = shrinko.rect + self.assertTrue(size['width'] > 0) + self.assertTrue(size['height'] > 0) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_errors.py b/testing/marionette/harness/marionette_harness/tests/unit/test_errors.py new file mode 100644 index 000000000..f6a9c285c --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_errors.py @@ -0,0 +1,77 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys + +from marionette_driver import errors + +from marionette_harness import marionette_test + + +def fake_cause(): + try: + raise ValueError("bar") + except ValueError as e: + return sys.exc_info() + +message = "foo" +cause = fake_cause() +stacktrace = "first\nsecond" + +class TestErrors(marionette_test.MarionetteTestCase): + def test_defaults(self): + exc = errors.MarionetteException() + self.assertIsNone(exc.message) + self.assertIsNone(exc.cause) + self.assertIsNone(exc.stacktrace) + + def test_construction(self): + exc = errors.MarionetteException( + message=message, cause=cause, stacktrace=stacktrace) + self.assertEquals(exc.message, message) + self.assertEquals(exc.cause, cause) + self.assertEquals(exc.stacktrace, stacktrace) + + def test_str(self): + exc = errors.MarionetteException( + message=message, cause=cause, stacktrace=stacktrace) + r = str(exc) + self.assertIn(message, r) + self.assertIn(", caused by {0!r}".format(cause[0]), r) + self.assertIn("\nstacktrace:\n\tfirst\n\tsecond", r) + + def test_cause_string(self): + exc = errors.MarionetteException(cause="foo") + self.assertEqual(exc.cause, "foo") + r = str(exc) + self.assertIn(", caused by foo", r) + + def test_cause_tuple(self): + exc = errors.MarionetteException(cause=cause) + self.assertEqual(exc.cause, cause) + r = str(exc) + self.assertIn(", caused by {0!r}".format(cause[0]), r) + + +class TestLookup(marionette_test.MarionetteTestCase): + def test_by_unknown_number(self): + self.assertEqual(errors.MarionetteException, errors.lookup(123456)) + + def test_by_known_string(self): + self.assertEqual(errors.NoSuchElementException, + errors.lookup("no such element")) + + def test_by_unknown_string(self): + self.assertEqual(errors.MarionetteException, errors.lookup("barbera")) + + def test_by_known_unicode_string(self): + self.assertEqual(errors.NoSuchElementException, + errors.lookup(u"no such element")) + + +class TestAllErrors(marionette_test.MarionetteTestCase): + def test_properties(self): + for exc in errors.es_: + self.assertTrue(hasattr(exc, "status"), + "expected exception to have attribute `status'") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_async_script.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_async_script.py new file mode 100644 index 000000000..8a5472b3a --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_async_script.py @@ -0,0 +1,156 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import ( + JavascriptException, + ScriptTimeoutException, +) + +from marionette_harness import MarionetteTestCase + + +class TestExecuteAsyncContent(MarionetteTestCase): + def setUp(self): + super(TestExecuteAsyncContent, self).setUp() + self.marionette.timeout.script = 1 + + def test_execute_async_simple(self): + self.assertEqual(1, self.marionette.execute_async_script("arguments[arguments.length-1](1);")) + + def test_execute_async_ours(self): + self.assertEqual(1, self.marionette.execute_async_script("marionetteScriptFinished(1);")) + + def test_execute_async_timeout(self): + self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "var x = 1;") + + def test_execute_async_unique_timeout(self): + self.assertEqual(2, self.marionette.execute_async_script("setTimeout(function() {marionetteScriptFinished(2);}, 2000);", script_timeout=5000)) + self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "setTimeout(function() {marionetteScriptFinished(3);}, 2000);") + + def test_no_timeout(self): + self.marionette.timeout.script = 10 + self.assertTrue(self.marionette.execute_async_script(""" + var callback = arguments[arguments.length - 1]; + setTimeout(function() { callback(true); }, 500); + """)) + + def test_execute_async_unload(self): + self.marionette.timeout.script = 5 + unload = """ + window.location.href = "about:blank"; + """ + self.assertRaises(JavascriptException, self.marionette.execute_async_script, unload) + + def test_check_window(self): + self.assertTrue(self.marionette.execute_async_script("marionetteScriptFinished(window !=null && window != undefined);")) + + def test_same_context(self): + var1 = 'testing' + self.assertEqual(self.marionette.execute_script(""" + this.testvar = '{}'; + return this.testvar; + """.format(var1)), var1) + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(this.testvar);", new_sandbox=False), var1) + + def test_execute_no_return(self): + self.assertEqual(self.marionette.execute_async_script("marionetteScriptFinished()"), None) + + def test_execute_js_exception(self): + try: + self.marionette.execute_async_script(""" + let a = 1; + foo(bar); + """) + self.assertFalse(True) + except JavascriptException, inst: + self.assertTrue('foo(bar)' in inst.stacktrace) + + def test_execute_async_js_exception(self): + self.assertRaises(JavascriptException, + self.marionette.execute_async_script, """ + var callback = arguments[arguments.length - 1]; + callback(foo()); + """) + + def test_script_finished(self): + self.assertTrue(self.marionette.execute_async_script(""" + marionetteScriptFinished(true); + """)) + + def test_execute_permission(self): + self.assertRaises(JavascriptException, self.marionette.execute_async_script, """ +let prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); +marionetteScriptFinished(4); +""") + + def test_sandbox_reuse(self): + # Sandboxes between `execute_script()` invocations are shared. + self.marionette.execute_async_script("this.foobar = [23, 42];" + "marionetteScriptFinished();") + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(this.foobar);", new_sandbox=False), [23, 42]) + + self.marionette.execute_async_script("global.barfoo = [42, 23];" + "marionetteScriptFinished();") + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(global.barfoo);", new_sandbox=False), [42, 23]) + + def test_sandbox_refresh_arguments(self): + self.marionette.execute_async_script("this.foobar = [arguments[0], arguments[1]];" + "marionetteScriptFinished();", + script_args=[23, 42]) + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(this.foobar);", new_sandbox=False), + [23, 42]) + + self.marionette.execute_async_script("global.barfoo = [arguments[0], arguments[1]];" + "marionetteScriptFinished()", + script_args=[42, 23], new_sandbox=False) + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(global.barfoo);", new_sandbox=False), + [42, 23]) + + # Functions defined in higher privilege scopes, such as the privileged + # content frame script listener.js runs in, cannot be accessed from + # content. This tests that it is possible to introspect the objects on + # `arguments` without getting permission defined errors. This is made + # possible because the last argument is always the callback/complete + # function. + # + # See bug 1290966. + def test_introspection_of_arguments(self): + self.marionette.execute_async_script( + "arguments[0].cheese; __webDriverCallback();", + script_args=[], sandbox=None) + + +class TestExecuteAsyncChrome(TestExecuteAsyncContent): + def setUp(self): + super(TestExecuteAsyncChrome, self).setUp() + self.marionette.set_context("chrome") + + def test_execute_async_unload(self): + pass + + def test_execute_permission(self): + self.assertEqual(5, self.marionette.execute_async_script(""" +var c = Components.classes; +marionetteScriptFinished(5); +""")) + + def test_execute_async_js_exception(self): + # Javascript exceptions are not propagated in chrome code + self.marionette.timeout.script = 0.2 + self.assertRaises(ScriptTimeoutException, + self.marionette.execute_async_script, """ + var callback = arguments[arguments.length - 1]; + setTimeout("callback(foo())", 50); + """) + self.assertRaises(JavascriptException, + self.marionette.execute_async_script, """ + var callback = arguments[arguments.length - 1]; + setTimeout("callback(foo())", 50); + """, debug_script=True) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_isolate.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_isolate.py new file mode 100644 index 000000000..7e09451e4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_isolate.py @@ -0,0 +1,37 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import ScriptTimeoutException + +from marionette_harness import MarionetteTestCase + + +class TestExecuteIsolationContent(MarionetteTestCase): + def setUp(self): + super(TestExecuteIsolationContent, self).setUp() + self.content = True + + def test_execute_async_isolate(self): + # Results from one execute call that has timed out should not + # contaminate a future call. + multiplier = "*3" if self.content else "*1" + self.marionette.timeout.script = 0.5 + self.assertRaises(ScriptTimeoutException, + self.marionette.execute_async_script, + ("setTimeout(function() {{ marionetteScriptFinished(5{}); }}, 3000);" + .format(multiplier))) + + self.marionette.timeout.script = 6 + result = self.marionette.execute_async_script(""" + setTimeout(function() {{ marionetteScriptFinished(10{}); }}, 5000); + """.format(multiplier)) + self.assertEqual(result, 30 if self.content else 10) + +class TestExecuteIsolationChrome(TestExecuteIsolationContent): + def setUp(self): + super(TestExecuteIsolationChrome, self).setUp() + self.marionette.set_context("chrome") + self.content = False + + diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_sandboxes.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_sandboxes.py new file mode 100644 index 000000000..d7cb0444b --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_sandboxes.py @@ -0,0 +1,79 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import JavascriptException + +from marionette_harness import MarionetteTestCase + + +class TestExecuteSandboxes(MarionetteTestCase): + def setUp(self): + super(TestExecuteSandboxes, self).setUp() + + def test_execute_system_sandbox(self): + # Test that "system" sandbox has elevated privileges in execute_script + result = self.marionette.execute_script( + "return Components.interfaces.nsIPermissionManager.ALLOW_ACTION", + sandbox="system") + self.assertEqual(result, 1) + + def test_execute_async_system_sandbox(self): + # Test that "system" sandbox has elevated privileges in + # execute_async_script. + result = self.marionette.execute_async_script(""" + const Ci = Components.interfaces; + let result = Ci.nsIPermissionManager.ALLOW_ACTION; + marionetteScriptFinished(result);""", + sandbox="system") + self.assertEqual(result, 1) + + def test_execute_switch_sandboxes(self): + # Test that sandboxes are retained when switching between them + # for execute_script. + self.marionette.execute_script("foo = 1", sandbox="1") + self.marionette.execute_script("foo = 2", sandbox="2") + foo = self.marionette.execute_script( + "return foo", sandbox="1", new_sandbox=False) + self.assertEqual(foo, 1) + foo = self.marionette.execute_script( + "return foo", sandbox="2", new_sandbox=False) + self.assertEqual(foo, 2) + + def test_execute_new_sandbox(self): + # test that clearing a sandbox does not affect other sandboxes + self.marionette.execute_script("foo = 1", sandbox="1") + self.marionette.execute_script("foo = 2", sandbox="2") + + # deprecate sandbox 1 by asking explicitly for a fresh one + with self.assertRaises(JavascriptException): + self.marionette.execute_script("return foo", + sandbox="1", new_sandbox=True) + + foo = self.marionette.execute_script( + "return foo", sandbox="2", new_sandbox=False) + self.assertEqual(foo, 2) + + def test_execute_async_switch_sandboxes(self): + # Test that sandboxes are retained when switching between them + # for execute_async_script. + self.marionette.execute_async_script( + "foo = 1; marionetteScriptFinished()", sandbox="1") + self.marionette.execute_async_script( + "foo = 2; marionetteScriptFinished()", sandbox='2') + foo = self.marionette.execute_async_script( + "marionetteScriptFinished(foo)", + sandbox="1", + new_sandbox=False) + self.assertEqual(foo, 1) + foo = self.marionette.execute_async_script( + "marionetteScriptFinished(foo)", + sandbox="2", + new_sandbox=False) + self.assertEqual(foo, 2) + + +class TestExecuteSandboxesChrome(TestExecuteSandboxes): + def setUp(self): + super(TestExecuteSandboxesChrome, self).setUp() + self.marionette.set_context("chrome") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py new file mode 100644 index 000000000..1ef4549d3 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py @@ -0,0 +1,402 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import urllib + +from marionette_driver import By, errors +from marionette_driver.marionette import HTMLElement +from marionette_driver.wait import Wait + +from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + + +elements = inline("

      foo

      bar

      ") + +globals = set([ + "atob", + "Audio", + "btoa", + "document", + "navigator", + "URL", + "window", + ]) + + +class TestExecuteSimpleTestContent(MarionetteTestCase): + def test_stack_trace(self): + try: + self.marionette.execute_js_script(""" + let a = 1; + throwHere(); + """, filename="file.js") + self.assertFalse(True) + except errors.JavascriptException as e: + self.assertIn("throwHere is not defined", e.message) + self.assertIn("@file.js:2", e.stacktrace) + + +class TestExecuteContent(MarionetteTestCase): + + def assert_is_defined(self, property, sandbox="default"): + self.assertTrue(self.marionette.execute_script( + "return typeof arguments[0] != 'undefined'", [property], sandbox=sandbox), + "property {} is undefined".format(property)) + + def test_return_number(self): + self.assertEqual(1, self.marionette.execute_script("return 1")) + self.assertEqual(1.5, self.marionette.execute_script("return 1.5")) + + def test_return_boolean(self): + self.assertTrue(self.marionette.execute_script("return true")) + + def test_return_string(self): + self.assertEqual("foo", self.marionette.execute_script("return 'foo'")) + + def test_return_array(self): + self.assertEqual( + [1, 2], self.marionette.execute_script("return [1, 2]")) + self.assertEqual( + [1.25, 1.75], self.marionette.execute_script("return [1.25, 1.75]")) + self.assertEqual( + [True, False], self.marionette.execute_script("return [true, false]")) + self.assertEqual( + ["foo", "bar"], self.marionette.execute_script("return ['foo', 'bar']")) + self.assertEqual( + [1, 1.5, True, "foo"], self.marionette.execute_script("return [1, 1.5, true, 'foo']")) + self.assertEqual( + [1, [2]], self.marionette.execute_script("return [1, [2]]")) + + def test_return_object(self): + self.assertEqual( + {"foo": 1}, self.marionette.execute_script("return {foo: 1}")) + self.assertEqual( + {"foo": 1.5}, self.marionette.execute_script("return {foo: 1.5}")) + self.assertEqual( + {"foo": True}, self.marionette.execute_script("return {foo: true}")) + self.assertEqual( + {"foo": "bar"}, self.marionette.execute_script("return {foo: 'bar'}")) + self.assertEqual( + {"foo": [1, 2]}, self.marionette.execute_script("return {foo: [1, 2]}")) + self.assertEqual( + {"foo": {"bar": [1, 2]}}, + self.marionette.execute_script("return {foo: {bar: [1, 2]}}")) + + def test_no_return_value(self): + self.assertIsNone(self.marionette.execute_script("true")) + + def test_argument_null(self): + self.assertIsNone(self.marionette.execute_script("return arguments[0]", [None])) + + def test_argument_number(self): + self.assertEqual( + 1, self.marionette.execute_script("return arguments[0]", [1])) + self.assertEqual( + 1.5, self.marionette.execute_script("return arguments[0]", [1.5])) + + def test_argument_boolean(self): + self.assertTrue(self.marionette.execute_script("return arguments[0]", [True])) + + def test_argument_string(self): + self.assertEqual( + "foo", self.marionette.execute_script("return arguments[0]", ["foo"])) + + def test_argument_array(self): + self.assertEqual( + [1, 2], self.marionette.execute_script("return arguments[0]", [[1, 2]])) + + def test_argument_object(self): + self.assertEqual({"foo": 1}, self.marionette.execute_script( + "return arguments[0]", [{"foo": 1}])) + + def test_globals(self): + for property in globals: + self.assert_is_defined(property) + self.assert_is_defined("Components") + self.assert_is_defined("window.wrappedJSObject") + + def test_system_globals(self): + for property in globals: + self.assert_is_defined(property, sandbox="system") + self.assert_is_defined("Components", sandbox="system") + self.assert_is_defined("window.wrappedJSObject") + + def test_exception(self): + self.assertRaises(errors.JavascriptException, + self.marionette.execute_script, "return foo") + + def test_stacktrace(self): + with self.assertRaises(errors.JavascriptException) as cm: + self.marionette.execute_script("return b") + + # by default execute_script pass the name of the python file + self.assertIn(os.path.basename(__file__.replace(".pyc", ".py")), + cm.exception.stacktrace) + self.assertIn("b is not defined", cm.exception.message) + self.assertIn("return b", cm.exception.stacktrace) + + def test_permission(self): + with self.assertRaises(errors.JavascriptException): + self.marionette.execute_script(""" + var c = Components.classes["@mozilla.org/preferences-service;1"]; + """) + + def test_return_web_element(self): + self.marionette.navigate(elements) + expected = self.marionette.find_element(By.TAG_NAME, "p") + actual = self.marionette.execute_script( + "return document.querySelector('p')") + self.assertEqual(expected, actual) + + def test_return_web_element_array(self): + self.marionette.navigate(elements) + expected = self.marionette.find_elements(By.TAG_NAME, "p") + actual = self.marionette.execute_script(""" + let els = document.querySelectorAll('p') + return [els[0], els[1]]""") + self.assertEqual(expected, actual) + + # Bug 938228 identifies a problem with unmarshaling NodeList + # objects from the DOM. document.querySelectorAll returns this + # construct. + def test_return_web_element_nodelist(self): + self.marionette.navigate(elements) + expected = self.marionette.find_elements(By.TAG_NAME, "p") + actual = self.marionette.execute_script( + "return document.querySelectorAll('p')") + self.assertEqual(expected, actual) + + def test_sandbox_reuse(self): + # Sandboxes between `execute_script()` invocations are shared. + self.marionette.execute_script("this.foobar = [23, 42];") + self.assertEqual(self.marionette.execute_script( + "return this.foobar;", new_sandbox=False), [23, 42]) + + self.marionette.execute_script("global.barfoo = [42, 23];") + self.assertEqual(self.marionette.execute_script( + "return global.barfoo;", new_sandbox=False), [42, 23]) + + def test_sandbox_refresh_arguments(self): + self.marionette.execute_script( + "this.foobar = [arguments[0], arguments[1]]", [23, 42]) + self.assertEqual(self.marionette.execute_script( + "return this.foobar", new_sandbox=False), [23, 42]) + + def test_wrappedjsobject(self): + try: + self.marionette.execute_script("window.wrappedJSObject.foo = 3") + self.assertEqual( + self.marionette.execute_script("return window.wrappedJSObject.foo"), 3) + finally: + self.marionette.execute_script("delete window.wrappedJSObject.foo") + + def test_system_sandbox_wrappedjsobject(self): + self.marionette.execute_script( + "window.wrappedJSObject.foo = 4", sandbox="system") + self.assertEqual(self.marionette.execute_script( + "return window.wrappedJSObject.foo", sandbox="system"), 4) + + def test_system_dead_object(self): + self.marionette.execute_script( + "window.wrappedJSObject.foo = function() { return 'yo' }", + sandbox="system") + self.marionette.execute_script( + "dump(window.wrappedJSObject.foo)", sandbox="system") + + self.marionette.execute_script( + "window.wrappedJSObject.foo = function() { return 'yolo' }", + sandbox="system") + typ = self.marionette.execute_script( + "return typeof window.wrappedJSObject.foo", sandbox="system") + self.assertEqual("function", typ) + obj = self.marionette.execute_script( + "return window.wrappedJSObject.foo.toString()", sandbox="system") + self.assertIn("yolo", obj) + + def test_lasting_side_effects(self): + def send(script): + return self.marionette._send_message( + "executeScript", {"script": script}, key="value") + + send("window.foo = 1") + foo = send("return window.foo") + self.assertEqual(1, foo) + + for property in globals: + exists = send("return typeof {} != 'undefined'".format(property)) + self.assertTrue(exists, "property {} is undefined".format(property)) + + self.assertTrue(send("return typeof Components.utils == 'undefined'")) + self.assertTrue(send("return typeof window.wrappedJSObject == 'undefined'")) + + def test_no_callback(self): + self.assertTrue(self.marionette.execute_script( + "return typeof arguments[0] == 'undefined'")) + + def test_window_set_timeout_is_not_cancelled(self): + def content_timeout_triggered(mn): + return mn.execute_script("return window.n", sandbox=None) > 0 + + # subsequent call to execute_script after this + # should not cancel the setTimeout event + self.marionette.navigate(inline(""" + """)) + + # as debug builds are inherently slow, + # we need to assert the event did not already fire + self.assertEqual(0, self.marionette.execute_script( + "return window.n", sandbox=None), + "setTimeout already fired") + + # if event was cancelled, this will time out + Wait(self.marionette, timeout=8).until( + content_timeout_triggered, + message="Scheduled setTimeout event was cancelled by call to execute_script") + + def test_privileged_code_inspection(self): + # test permission denied on toString of unload event handler + self.marionette.navigate(inline(""" + """)) + self.marionette.execute_script("", sandbox=None) + + # test inspection of arguments + self.marionette.execute_script("__webDriverArguments.toString()") + + +class TestExecuteChrome(WindowManagerMixin, TestExecuteContent): + + def setUp(self): + super(TestExecuteChrome, self).setUp() + + self.marionette.set_context("chrome") + + def tearDown(self): + super(TestExecuteChrome, self).tearDown() + + def test_permission(self): + self.assertEqual(1, self.marionette.execute_script(""" + var c = Components.classes["@mozilla.org/preferences-service;1"]; return 1;""")) + + @skip_if_mobile("New windows not supported in Fennec") + def test_unmarshal_element_collection(self): + + def open_window_with_js(): + self.marionette.execute_script( + "window.open('chrome://marionette/content/test.xul', 'xul', 'chrome');") + + try: + win = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(win) + + expected = self.marionette.find_elements(By.TAG_NAME, "textbox") + actual = self.marionette.execute_script( + "return document.querySelectorAll('textbox')") + self.assertEqual(expected, actual) + + finally: + self.close_all_windows() + + def test_async_script_timeout(self): + with self.assertRaises(errors.ScriptTimeoutException): + self.marionette.execute_async_script(""" + var cb = arguments[arguments.length - 1]; + setTimeout(function() { cb() }, 250); + """, script_timeout=100) + + @skip_if_mobile("New windows not supported in Fennec") + def test_invalid_chrome_handle(self): + try: + win = self.open_window() + self.marionette.switch_to_window(win) + + # Close new window and don't switch back to the original one + self.marionette.close_chrome_window() + self.assertNotEqual(self.start_window, win) + + # Call execute_script on an invalid chrome handle + with self.marionette.using_context('chrome'): + self.marionette.execute_script(""" + return true; + """) + + finally: + self.close_all_windows() + + def test_lasting_side_effects(self): + pass + + def test_return_web_element(self): + pass + + def test_return_web_element_array(self): + pass + + def test_return_web_element_nodelist(self): + pass + + def test_window_set_timeout_is_not_cancelled(self): + pass + + def test_privileged_code_inspection(self): + pass + + +class TestElementCollections(MarionetteTestCase): + + def assertSequenceIsInstance(self, seq, typ): + for item in seq: + self.assertIsInstance(item, typ) + + def test_array(self): + self.marionette.navigate(inline("

      foo

      bar")) + els = self.marionette.execute_script("return Array.from(document.querySelectorAll('p'))") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_all_collection(self): + self.marionette.navigate(inline("

      foo

      bar")) + els = self.marionette.execute_script("return document.all") + self.assertIsInstance(els, list) + # , , ,

      ,

      + self.assertEqual(5, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_collection(self): + self.marionette.navigate(inline("

      foo

      bar")) + els = self.marionette.execute_script("return document.getElementsByTagName('p')") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_form_controls_collection(self): + self.marionette.navigate(inline("

      ")) + els = self.marionette.execute_script("return document.forms[0].elements") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_options_collection(self): + self.marionette.navigate(inline("")) + els = self.marionette.execute_script("return document.querySelector('select').options") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_node_list(self): + self.marionette.navigate(inline("

      foo

      bar")) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_expected.py b/testing/marionette/harness/marionette_harness/tests/unit/test_expected.py new file mode 100644 index 000000000..ff8717c69 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_expected.py @@ -0,0 +1,228 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import urllib + +from marionette_driver import expected +from marionette_driver.by import By + +from marionette_harness import marionette_test + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + +static_element = inline("""

      foo

      """) +static_elements = static_element + static_element + +remove_element_by_tag_name = \ + """var el = document.getElementsByTagName('{}')[0]; + document.getElementsByTagName("body")[0].remove(el);""" + +hidden_element = inline("

      hidden

      ") + +selected_element = inline("") +unselected_element = inline("") + +enabled_element = inline("") +disabled_element = inline("") + +def no_such_element(marionette): + return marionette.find_element(By.ID, "nosuchelement") + +def no_such_elements(marionette): + return marionette.find_elements(By.ID, "nosuchelement") + +def p(marionette): + return marionette.find_element(By.TAG_NAME, "p") + +def ps(marionette): + return marionette.find_elements(By.TAG_NAME, "p") + +class TestExpected(marionette_test.MarionetteTestCase): + def test_element_present_func(self): + self.marionette.navigate(static_element) + el = expected.element_present(p)(self.marionette) + self.assertIsNotNone(el) + + def test_element_present_locator(self): + self.marionette.navigate(static_element) + el = expected.element_present(By.TAG_NAME, "p")(self.marionette) + self.assertIsNotNone(el) + + def test_element_present_not_present(self): + r = expected.element_present(no_such_element)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_not_present_func(self): + r = expected.element_not_present(no_such_element)(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_element_not_present_locator(self): + r = expected.element_not_present(By.ID, "nosuchelement")(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_element_not_present_is_present(self): + self.marionette.navigate(static_element) + r = expected.element_not_present(p)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_stale(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.assertIsNotNone(el) + self.marionette.execute_script(remove_element_by_tag_name.format("p")) + r = expected.element_stale(el)(self.marionette) + self.assertTrue(r) + + def test_element_stale_is_not_stale(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + r = expected.element_stale(el)(self.marionette) + self.assertFalse(r) + + def test_elements_present_func(self): + self.marionette.navigate(static_elements) + els = expected.elements_present(ps)(self.marionette) + self.assertEqual(len(els), 2) + + def test_elements_present_locator(self): + self.marionette.navigate(static_elements) + els = expected.elements_present(By.TAG_NAME, "p")(self.marionette) + self.assertEqual(len(els), 2) + + def test_elements_present_not_present(self): + r = expected.elements_present(no_such_elements)(self.marionette) + self.assertEqual(r, []) + + def test_elements_not_present_func(self): + r = expected.element_not_present(no_such_elements)(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_elements_not_present_locator(self): + r = expected.element_not_present(By.ID, "nosuchelement")(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_elements_not_present_is_present(self): + self.marionette.navigate(static_elements) + r = expected.elements_not_present(ps)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_displayed(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + visible = expected.element_displayed(el)(self.marionette) + self.assertTrue(visible) + + def test_element_displayed_locator(self): + self.marionette.navigate(static_element) + visible = expected.element_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertTrue(visible) + + def test_element_displayed_when_hidden(self): + self.marionette.navigate(hidden_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + visible = expected.element_displayed(el)(self.marionette) + self.assertFalse(visible) + + def test_element_displayed_when_hidden_locator(self): + self.marionette.navigate(hidden_element) + visible = expected.element_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertFalse(visible) + + def test_element_displayed_when_not_present(self): + self.marionette.navigate("about:blank") + visible = expected.element_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertFalse(visible) + + def test_element_displayed_when_stale_element(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.navigate("about:blank") + missing = expected.element_displayed(el)(self.marionette) + self.assertFalse(missing) + + def test_element_not_displayed(self): + self.marionette.navigate(hidden_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + hidden = expected.element_not_displayed(el)(self.marionette) + self.assertTrue(hidden) + + def test_element_not_displayed_locator(self): + self.marionette.navigate(hidden_element) + hidden = expected.element_not_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertTrue(hidden) + + def test_element_not_displayed_when_visible(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + hidden = expected.element_not_displayed(el)(self.marionette) + self.assertFalse(hidden) + + def test_element_not_displayed_when_visible_locator(self): + self.marionette.navigate(static_element) + hidden = expected.element_not_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertFalse(hidden) + + def test_element_not_displayed_when_stale_element(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.navigate("about:blank") + missing = expected.element_not_displayed(el)(self.marionette) + self.assertTrue(missing) + + def test_element_selected(self): + self.marionette.navigate(selected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + selected = expected.element_selected(el)(self.marionette) + self.assertTrue(selected) + + def test_element_selected_when_not_selected(self): + self.marionette.navigate(unselected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + unselected = expected.element_selected(el)(self.marionette) + self.assertFalse(unselected) + + def test_element_not_selected(self): + self.marionette.navigate(unselected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + unselected = expected.element_not_selected(el)(self.marionette) + self.assertTrue(unselected) + + def test_element_not_selected_when_selected(self): + self.marionette.navigate(selected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + selected = expected.element_not_selected(el)(self.marionette) + self.assertFalse(selected) + + def test_element_enabled(self): + self.marionette.navigate(enabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + enabled = expected.element_enabled(el)(self.marionette) + self.assertTrue(enabled) + + def test_element_enabled_when_disabled(self): + self.marionette.navigate(disabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + disabled = expected.element_enabled(el)(self.marionette) + self.assertFalse(disabled) + + def test_element_not_enabled(self): + self.marionette.navigate(disabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + disabled = expected.element_not_enabled(el)(self.marionette) + self.assertTrue(disabled) + + def test_element_not_enabled_when_enabled(self): + self.marionette.navigate(enabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + enabled = expected.element_not_enabled(el)(self.marionette) + self.assertFalse(enabled) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_expectedfail.py b/testing/marionette/harness/marionette_harness/tests/unit/test_expectedfail.py new file mode 100644 index 000000000..138a36c58 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_expectedfail.py @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestFail(MarionetteTestCase): + def test_fails(self): + # this test is supposed to fail! + self.assertEquals(True, False) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py b/testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py new file mode 100644 index 000000000..f67be9556 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py @@ -0,0 +1,152 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import contextlib +import urllib + +from tempfile import NamedTemporaryFile as tempfile + +from marionette_driver import By, errors, expected +from marionette_driver.wait import Wait +from marionette_harness import MarionetteTestCase, skip + + +single = "data:text/html,{}".format(urllib.quote("")) +multiple = "data:text/html,{}".format(urllib.quote("")) +upload = lambda url: "data:text/html,{}".format(urllib.quote(""" +
      + + +
      """.format(url))) + + +class TestFileUpload(MarionetteTestCase): + def test_sets_one_file(self): + self.marionette.navigate(single) + input = self.input + + exp = None + with tempfile() as f: + input.send_keys(f.name) + exp = [f.name] + + files = self.get_file_names(input) + self.assertEqual(len(files), 1) + self.assertFileNamesEqual(files, exp) + + def test_sets_multiple_files(self): + self.marionette.navigate(multiple) + input = self.input + + exp = None + with contextlib.nested(tempfile(), tempfile()) as (a, b): + input.send_keys(a.name) + input.send_keys(b.name) + exp = [a.name, b.name] + + files = self.get_file_names(input) + self.assertEqual(len(files), 2) + self.assertFileNamesEqual(files, exp) + + def test_sets_multiple_indentical_files(self): + self.marionette.navigate(multiple) + input = self.input + + exp = [] + with tempfile() as f: + input.send_keys(f.name) + input.send_keys(f.name) + exp = f.name + + files = self.get_file_names(input) + self.assertEqual(len(files), 2) + self.assertFileNamesEqual(files, exp) + + def test_clear_file(self): + self.marionette.navigate(single) + input = self.input + + with tempfile() as f: + input.send_keys(f.name) + + self.assertEqual(len(self.get_files(input)), 1) + input.clear() + self.assertEqual(len(self.get_files(input)), 0) + + def test_clear_files(self): + self.marionette.navigate(multiple) + input = self.input + + with contextlib.nested(tempfile(), tempfile()) as (a, b): + input.send_keys(a.name) + input.send_keys(b.name) + + self.assertEqual(len(self.get_files(input)), 2) + input.clear() + self.assertEqual(len(self.get_files(input)), 0) + + def test_illegal_file(self): + self.marionette.navigate(single) + with self.assertRaisesRegexp(errors.MarionetteException, "File not found"): + self.input.send_keys("rochefort") + + def test_upload(self): + self.marionette.navigate( + upload(self.marionette.absolute_url("file_upload"))) + url = self.marionette.get_url() + + with tempfile() as f: + f.write("camembert") + f.flush() + self.input.send_keys(f.name) + self.submit.click() + + Wait(self.marionette).until(lambda m: m.get_url() != url) + self.assertIn("multipart/form-data", self.body.text) + + def test_change_event(self): + self.marionette.navigate(single) + self.marionette.execute_script(""" + window.changeEvs = []; + let el = arguments[arguments.length - 1]; + el.addEventListener("change", ev => window.changeEvs.push(ev)); + console.log(window.changeEvs.length); + """, script_args=(self.input,), sandbox=None) + + with tempfile() as f: + self.input.send_keys(f.name) + + nevs = self.marionette.execute_script( + "return window.changeEvs.length", sandbox=None) + self.assertEqual(1, nevs) + + def find_inputs(self): + return self.marionette.find_elements(By.TAG_NAME, "input") + + @property + def input(self): + return self.find_inputs()[0] + + @property + def submit(self): + return self.find_inputs()[1] + + @property + def body(self): + return Wait(self.marionette).until( + expected.element_present(By.TAG_NAME, "body")) + + def get_file_names(self, el): + fl = self.get_files(el) + return [f["name"] for f in fl] + + def get_files(self, el): + return self.marionette.execute_script( + "return arguments[0].files", script_args=[el]) + + def assertFileNamesEqual(self, act, exp): + # File array returned from browser doesn't contain full path names, + # this cuts off the path of the expected files. + filenames = [f.rsplit("/", 0)[-1] for f in act] + self.assertListEqual(filenames, act) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py new file mode 100644 index 000000000..e6b2d63bf --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py @@ -0,0 +1,82 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import NoSuchElementException +from marionette_driver.marionette import HTMLElement + +from marionette_harness import MarionetteTestCase + + +class TestElementsChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_id(self): + el = self.marionette.execute_script("return window.document.getElementById('textInput');") + found_el = self.marionette.find_element(By.ID, "textInput") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_that_we_can_find_elements_from_css_selectors(self): + el = self.marionette.execute_script("return window.document.getElementById('textInput');") + found_el = self.marionette.find_element(By.CSS_SELECTOR, "#textInput") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_child_element(self): + el = self.marionette.find_element(By.ID, "textInput") + parent = self.marionette.find_element(By.ID, "things") + found_el = parent.find_element(By.TAG_NAME, "textbox") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_child_elements(self): + el = self.marionette.find_element(By.ID, "textInput3") + parent = self.marionette.find_element(By.ID, "things") + found_els = parent.find_elements(By.TAG_NAME, "textbox") + self.assertTrue(el.id in [found_el.id for found_el in found_els]) + + def test_tag_name(self): + el = self.marionette.execute_script("return window.document.getElementsByTagName('vbox')[0];") + found_el = self.marionette.find_element(By.TAG_NAME, "vbox") + self.assertEquals('vbox', found_el.tag_name) + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_class_name(self): + el = self.marionette.execute_script("return window.document.getElementsByClassName('asdf')[0];") + found_el = self.marionette.find_element(By.CLASS_NAME, "asdf") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_xpath(self): + el = self.marionette.execute_script("return window.document.getElementById('testBox');") + found_el = self.marionette.find_element(By.XPATH, "id('testBox')") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_not_found(self): + self.marionette.timeout.implicit = 1 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page") + self.marionette.timeout.implicit = 0 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page") + + def test_timeout(self): + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "myid") + self.marionette.timeout.implicit = 4 + self.marionette.execute_script("window.setTimeout(function() {var b = window.document.createElement('button'); b.id = 'myid'; document.getElementById('things').appendChild(b);}, 1000)") + self.assertEqual(HTMLElement, type(self.marionette.find_element(By.ID, "myid"))) + self.marionette.execute_script("window.document.getElementById('things').removeChild(window.document.getElementById('myid'));") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_geckoinstance.py b/testing/marionette/harness/marionette_harness/tests/unit/test_geckoinstance.py new file mode 100644 index 000000000..540550296 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_geckoinstance.py @@ -0,0 +1,25 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.geckoinstance import apps, GeckoInstance + +from marionette_harness import MarionetteTestCase + + +class TestGeckoInstance(MarionetteTestCase): + + def test_create(self): + """Test that the correct gecko instance is determined.""" + for app in apps: + # If app has been specified we directly return the appropriate instance class + self.assertEqual(type(GeckoInstance.create(app=app, bin="n/a")), + apps[app]) + + # Unknown applications and binaries should fail + self.assertRaises(NotImplementedError, GeckoInstance.create, + app="n/a", bin=self.marionette.bin) + self.assertRaises(NotImplementedError, GeckoInstance.create, + bin="n/a") + self.assertRaises(NotImplementedError, GeckoInstance.create, + bin=None) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_getactiveframe_oop.py b/testing/marionette/harness/marionette_harness/tests/unit/test_getactiveframe_oop.py new file mode 100644 index 000000000..9325d4892 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_getactiveframe_oop.py @@ -0,0 +1,93 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +OOP_BY_DEFAULT = "dom.ipc.browser_frames.oop_by_default" +BROWSER_FRAMES_ENABLED = "dom.mozBrowserFramesEnabeld" + + +class TestGetActiveFrameOOP(MarionetteTestCase): + def setUp(self): + super(TestGetActiveFrameOOP, self).setUp() + with self.marionette.using_context("chrome"): + self.oop_by_default = self.marionette.get_pref(OOP_BY_DEFAULT) + self.mozBrowserFramesEnabled = self.marionette.get_pref(BROWSER_FRAMES_ENABLED) + self.marionette.set_pref(OOP_BY_DEFAULT, True) + self.marionette.set_pref(BROWSER_FRAMES_ENABLED, True) + + def tearDown(self): + with self.marionette.using_context("chrome"): + if self.oop_by_default is None: + self.marionette.clear_pref(OOP_BY_DEFAULT) + else: + self.marionette.set_pref(OOP_BY_DEFAULT, self.oop_by_default) + + if self.mozBrowserFramesEnabled is None: + self.marionette.clear_pref(BROWSER_FRAMES_ENABLED) + else: + self.marionette.set_pref(BROWSER_FRAMES_ENABLED, self.mozBrowserFramesEnabled) + + def test_active_frame_oop(self): + self.marionette.navigate(self.marionette.absolute_url("test.html")) + self.marionette.push_permission('browser', True) + + # Create first OOP frame + self.marionette.execute_script(""" + let iframe1 = document.createElement("iframe"); + iframe1.id = "remote_iframe1"; + iframe1.setAttribute('remote', true); + iframe1.setAttribute('mozbrowser', true); + iframe1.style.height = "100px"; + iframe1.style.width = "100%%"; + iframe1.src = "{}"; + document.body.appendChild(iframe1); + """.format(self.marionette.absolute_url("test_oop_1.html"))) + + # Currently no active frame + self.assertEqual(self.marionette.get_active_frame(), None) + self.assertTrue("test.html" in self.marionette.get_url()) + + # Switch to iframe1, get active frame + frame = self.marionette.find_element(By.ID, 'remote_iframe1') + self.marionette.switch_to_frame(frame) + active_frame1 = self.marionette.get_active_frame() + self.assertNotEqual(active_frame1.id, None) + + # Switch to top-level then back to active frame, verify correct frame + self.marionette.switch_to_frame() + self.marionette.switch_to_frame(active_frame1) + self.assertTrue("test_oop_1.html" in self.marionette.execute_script("return document.wrappedJSObject.location.href")) + + # Create another OOP frame + self.marionette.switch_to_frame() + self.marionette.execute_script(""" + let iframe2 = document.createElement("iframe"); + iframe2.setAttribute('mozbrowser', true); + iframe2.setAttribute('remote', true); + iframe2.id = "remote_iframe2"; + iframe2.style.height = "100px"; + iframe2.style.width = "100%%"; + iframe2.src = "{}"; + document.body.appendChild(iframe2); + """.format(self.marionette.absolute_url("test_oop_2.html"))) + + # Switch to iframe2, get active frame + frame2 = self.marionette.find_element(By.ID, 'remote_iframe2') + self.marionette.switch_to_frame(frame2) + active_frame2 = self.marionette.get_active_frame() + self.assertNotEqual(active_frame2.id, None) + + # Switch to top-level then back to active frame 1, verify correct frame + self.marionette.switch_to_frame() + self.marionette.switch_to_frame(active_frame1) + self.assertTrue("test_oop_1.html" in self.marionette.execute_script("return document.wrappedJSObject.location.href")) + + # Switch to top-level then back to active frame 2, verify correct frame + self.marionette.switch_to_frame() + self.marionette.switch_to_frame(active_frame2) + self.assertTrue("test_oop_2.html" in self.marionette.execute_script("return document.wrappedJSObject.location.href")) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_implicit_waits.py b/testing/marionette/harness/marionette_harness/tests/unit/test_implicit_waits.py new file mode 100644 index 000000000..954443ac3 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_implicit_waits.py @@ -0,0 +1,26 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import NoSuchElementException + +from marionette_harness import MarionetteTestCase + + +class TestImplicitWaits(MarionetteTestCase): + def test_implicitly_wait_for_single_element(self): + test_html = self.marionette.absolute_url("test_dynamic.html") + self.marionette.navigate(test_html) + add = self.marionette.find_element(By.ID, "adder") + self.marionette.timeout.implicit = 30 + add.click() + # all is well if this does not throw + self.marionette.find_element(By.ID, "box0") + + def test_implicit_wait_reaches_timeout(self): + test_html = self.marionette.absolute_url("test_dynamic.html") + self.marionette.navigate(test_html) + self.marionette.timeout.implicit = 3 + with self.assertRaises(NoSuchElementException): + self.marionette.find_element(By.ID, "box0") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py b/testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py new file mode 100644 index 000000000..e86de2bd5 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py @@ -0,0 +1,138 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os + +from marionette_driver.by import By +from marionette_driver.errors import JavascriptException + +from marionette_harness import ( + MarionetteTestCase, + skip_if_chrome, + skip_if_mobile, + WindowManagerMixin, +) + + +class TestImportScriptContent(WindowManagerMixin, MarionetteTestCase): + contexts = set(["chrome", "content"]) + + script_file = os.path.abspath( + os.path.join(__file__, os.path.pardir, "importscript.js")) + another_script_file = os.path.abspath( + os.path.join(__file__, os.path.pardir, "importanotherscript.js")) + + def setUp(self): + super(TestImportScriptContent, self).setUp() + + for context in self.contexts: + with self.marionette.using_context(context): + self.marionette.clear_imported_scripts() + self.reset_context() + + def tearDown(self): + self.close_all_windows() + + super(TestImportScriptContent, self).tearDown() + + def reset_context(self): + self.marionette.set_context("content") + + @property + def current_context(self): + return self.marionette._send_message("getContext", key="value") + + @property + def other_context(self): + return self.contexts.copy().difference([self.current_context]).pop() + + def is_defined(self, symbol): + return self.marionette.execute_script( + "return typeof {} != 'undefined'".format(symbol)) + + def assert_defined(self, symbol, msg=None): + if msg is None: + msg = "Expected symbol {} to be defined".format(symbol) + self.assertTrue(self.is_defined(symbol), msg) + + def assert_undefined(self, symbol, msg=None): + if msg is None: + msg = "Expected symbol {} to be undefined".format(symbol) + self.assertFalse(self.is_defined(symbol), msg) + + def assert_scripts_cleared(self): + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + self.marionette.clear_imported_scripts() + self.assert_undefined("testFunc") + + def test_import_script(self): + self.marionette.import_script(self.script_file) + self.assertEqual( + "i'm a test function!", self.marionette.execute_script("return testFunc();")) + self.assertEqual("i'm a test function!", self.marionette.execute_async_script( + "marionetteScriptFinished(testFunc());")) + + def test_import_script_twice(self): + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + + # TODO(ato): Note that the WebDriver command primitives + # does not allow us to check what scripts have been imported. + # I suspect we must to do this through an xpcshell test. + + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + + def test_import_script_and_clear(self): + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + self.marionette.clear_imported_scripts() + self.assert_scripts_cleared() + self.assert_undefined("testFunc") + with self.assertRaises(JavascriptException): + self.marionette.execute_script("return testFunc()") + with self.assertRaises(JavascriptException): + self.marionette.execute_async_script( + "marionetteScriptFinished(testFunc())") + + def test_clear_scripts_in_other_context(self): + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + + # clearing other context's script file should not affect ours + with self.marionette.using_context(self.other_context): + self.marionette.clear_imported_scripts() + self.assert_undefined("testFunc") + + self.assert_defined("testFunc") + + def test_multiple_imports(self): + self.marionette.import_script(self.script_file) + self.marionette.import_script(self.another_script_file) + self.assert_defined("testFunc") + self.assert_defined("testAnotherFunc") + + @skip_if_chrome("Needs content scope") + @skip_if_mobile("New windows not supported in Fennec") + def test_imports_apply_globally(self): + self.marionette.navigate( + self.marionette.absolute_url("test_windows.html")) + + def open_window_with_link(): + self.marionette.find_element(By.LINK_TEXT, "Open new window").click() + + new_window = self.open_window(trigger=open_window_with_link) + self.marionette.switch_to_window(new_window) + + self.marionette.import_script(self.script_file) + self.marionette.close_chrome_window() + + self.marionette.switch_to_window(self.start_window) + self.assert_defined("testFunc") + + +class TestImportScriptChrome(TestImportScriptContent): + def reset_context(self): + self.marionette.set_context("chrome") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py b/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py new file mode 100644 index 000000000..60e38b3c6 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py @@ -0,0 +1,91 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.keys import Keys +from marionette_driver.marionette import Actions + +from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin + + +class TestKeyActions(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestKeyActions, self).setUp() + if self.marionette.session_capabilities["platformName"] == "darwin": + self.mod_key = Keys.META + else: + self.mod_key = Keys.CONTROL + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + self.reporter_element = self.marionette.find_element(By.ID, "keyReporter") + self.reporter_element.click() + self.key_action = Actions(self.marionette) + + @property + def key_reporter_value(self): + return self.reporter_element.get_property("value") + + def test_key_action_basic_input(self): + self.key_action.key_down("a").key_down("b").key_down("c").perform() + self.assertEqual(self.key_reporter_value, "abc") + + def test_upcase_input(self): + (self.key_action.key_down(Keys.SHIFT) + .key_down("a") + .key_up(Keys.SHIFT) + .key_down("b") + .key_down("c") + .perform()) + self.assertEqual(self.key_reporter_value, "Abc") + + def test_replace_input(self): + self.key_action.key_down("a").key_down("b").key_down("c").perform() + self.assertEqual(self.key_reporter_value, "abc") + (self.key_action.key_down(self.mod_key) + .key_down("a") + .key_up(self.mod_key) + .key_down("x") + .perform()) + self.assertEqual(self.key_reporter_value, "x") + + def test_clear_input(self): + self.key_action.key_down("a").key_down("b").key_down("c").perform() + self.assertEqual(self.key_reporter_value, "abc") + (self.key_action.key_down(self.mod_key) + .key_down("a") + .key_down("x") + .perform()) + self.assertEqual(self.key_reporter_value, "") + self.key_action.key_down("a").key_down("b").key_down("c").perform() + self.assertEqual(self.key_reporter_value, "abc") + + def test_input_with_wait(self): + self.key_action.key_down("a").key_down("b").key_down("c").perform() + (self.key_action.key_down(self.mod_key) + .key_down("a") + .wait(.5) + .key_down("x") + .perform()) + self.assertEqual(self.key_reporter_value, "") + + @skip_if_mobile("Interacting with chrome windows not available for Fennec") + def test_open_in_new_window_shortcut(self): + + def open_window_with_action(): + el = self.marionette.find_element(By.ID, "updatediv") + # Ensure that the element is in the current view port because press() doesn't + # handle that inside the action chain (bug 1295538). + self.marionette.execute_script('arguments[0].scrollIntoView()', script_args=[el]) + (self.key_action.key_down(Keys.SHIFT) + .press(el) + .release() + .key_up(Keys.SHIFT) + .perform()) + + new_window = self.open_window(trigger=open_window_with_action) + self.marionette.switch_to_window(new_window) + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + self.assertEqual(self.key_reporter_value, "") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_localization.py b/testing/marionette/harness/marionette_harness/tests/unit/test_localization.py new file mode 100644 index 000000000..1b89f6f34 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_localization.py @@ -0,0 +1,56 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import By +from marionette_driver.errors import ( + InvalidArgumentException, + NoSuchElementException, + UnknownException +) +from marionette_driver.localization import L10n + +from marionette_harness import MarionetteTestCase + + +class TestL10n(MarionetteTestCase): + + def setUp(self): + super(TestL10n, self).setUp() + + self.l10n = L10n(self.marionette) + + def test_localize_entity(self): + dtds = ['chrome://marionette/content/test_dialog.dtd'] + value = self.l10n.localize_entity(dtds, 'testDialog.title') + + self.assertEqual(value, 'Test Dialog') + + def test_localize_entity_invalid_arguments(self): + dtds = ['chrome://marionette/content/test_dialog.dtd'] + + self.assertRaises(NoSuchElementException, + self.l10n.localize_entity, dtds, 'notExistent') + self.assertRaises(InvalidArgumentException, + self.l10n.localize_entity, dtds[0], 'notExistent') + self.assertRaises(InvalidArgumentException, + self.l10n.localize_entity, dtds, True) + + def test_localize_property(self): + properties = ['chrome://marionette/content/test_dialog.properties'] + + value = self.l10n.localize_property(properties, 'testDialog.title') + self.assertEqual(value, 'Test Dialog') + + self.assertRaises(NoSuchElementException, + self.l10n.localize_property, properties, 'notExistent') + + def test_localize_property_invalid_arguments(self): + properties = ['chrome://global/locale/filepicker.properties'] + + self.assertRaises(NoSuchElementException, + self.l10n.localize_property, properties, 'notExistent') + self.assertRaises(InvalidArgumentException, + self.l10n.localize_property, properties[0], 'notExistent') + self.assertRaises(InvalidArgumentException, + self.l10n.localize_property, properties, True) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_log.py b/testing/marionette/harness/marionette_harness/tests/unit/test_log.py new file mode 100644 index 000000000..dd3cf82b4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_log.py @@ -0,0 +1,64 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestLog(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + # clears log cache + self.marionette.get_logs() + + def test_log(self): + self.marionette.log("foo") + logs = self.marionette.get_logs() + self.assertEqual("INFO", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_log_level(self): + self.marionette.log("foo", "ERROR") + logs = self.marionette.get_logs() + self.assertEqual("ERROR", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_clear(self): + self.marionette.log("foo") + self.assertEqual(1, len(self.marionette.get_logs())) + self.assertEqual(0, len(self.marionette.get_logs())) + + def test_multiple_entries(self): + self.marionette.log("foo") + self.marionette.log("bar") + self.assertEqual(2, len(self.marionette.get_logs())) + + def test_log_from_sync_script(self): + self.marionette.execute_script("log('foo')") + logs = self.marionette.get_logs() + self.assertEqual("INFO", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_log_from_sync_script_level(self): + self.marionette.execute_script("log('foo', 'ERROR')") + logs = self.marionette.get_logs() + self.assertEqual("ERROR", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_log_from_async_script(self): + self.marionette.execute_async_script("log('foo'); arguments[0]();") + logs = self.marionette.get_logs() + self.assertEqual("INFO", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_log_from_async_script_variable_arguments(self): + self.marionette.execute_async_script("log('foo', 'ERROR'); arguments[0]();") + logs = self.marionette.get_logs() + self.assertEqual("ERROR", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + +class TestLogChrome(TestLog): + def setUp(self): + TestLog.setUp(self) + self.marionette.set_context("chrome") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_marionette.py b/testing/marionette/harness/marionette_harness/tests/unit/test_marionette.py new file mode 100644 index 000000000..e68312872 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_marionette.py @@ -0,0 +1,67 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import itertools +import time + +from marionette_driver import errors + +from marionette_harness import MarionetteTestCase, run_if_manage_instance, skip_if_mobile + + +class TestMarionette(MarionetteTestCase): + + def test_correct_test_name(self): + """Test that the correct test name gets set.""" + expected_test_name = '{module}.py {cls}.{func}'.format( + module=__name__, + cls=self.__class__.__name__, + func=self.test_correct_test_name.__name__, + ) + + self.assertEqual(self.marionette.test_name, expected_test_name) + + @run_if_manage_instance("Only runnable if Marionette manages the instance") + @skip_if_mobile("Bug 1322993 - Missing temporary folder") + def test_wait_for_port_non_existing_process(self): + """Test that wait_for_port doesn't run into a timeout if instance is not running.""" + self.marionette.quit() + self.assertIsNotNone(self.marionette.instance.runner.returncode) + start_time = time.time() + self.assertFalse(self.marionette.wait_for_port(timeout=5)) + self.assertLess(time.time() - start_time, 5) + + +class TestProtocol2Errors(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.op = self.marionette.protocol + self.marionette.protocol = 2 + + def tearDown(self): + self.marionette.protocol = self.op + MarionetteTestCase.tearDown(self) + + def test_malformed_packet(self): + req = ["error", "message", "stacktrace"] + ps = [] + for p in [p for i in range(0, len(req) + 1) for p in itertools.permutations(req, i)]: + ps.append(dict((x, None) for x in p)) + + for p in filter(lambda p: len(p) < 3, ps): + self.assertRaises(KeyError, self.marionette._handle_error, p) + + def test_known_error_status(self): + with self.assertRaises(errors.NoSuchElementException): + self.marionette._handle_error( + {"error": errors.NoSuchElementException.status, + "message": None, + "stacktrace": None}) + + def test_unknown_error_status(self): + with self.assertRaises(errors.MarionetteException): + self.marionette._handle_error( + {"error": "barbera", + "message": None, + "stacktrace": None}) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py b/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py new file mode 100644 index 000000000..f7108bdff --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py @@ -0,0 +1,198 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import NoAlertPresentException, ElementNotInteractableException +from marionette_driver.marionette import Alert +from marionette_driver.wait import Wait + +from marionette_harness import MarionetteTestCase, skip_if_e10s + + +class TestTabModals(MarionetteTestCase): + + def setUp(self): + super(TestTabModals, self).setUp() + self.marionette.set_pref("prompts.tab_modal.enabled", True) + self.marionette.navigate(self.marionette.absolute_url('modal_dialogs.html')) + + def tearDown(self): + # Ensure an alert is absent before proceeding past this test. + Wait(self.marionette).until(lambda _: not self.alert_present()) + self.marionette.execute_script("window.onbeforeunload = null;") + self.marionette.clear_pref("prompts.tab_modal.enabled") + super(TestTabModals, self).tearDown() + + def alert_present(self): + try: + Alert(self.marionette).text + return True + except NoAlertPresentException: + return False + + def wait_for_alert(self): + Wait(self.marionette).until(lambda _: self.alert_present()) + + def test_no_alert_raises(self): + self.assertRaises(NoAlertPresentException, Alert(self.marionette).accept) + self.assertRaises(NoAlertPresentException, Alert(self.marionette).dismiss) + + def test_alert_accept(self): + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.accept() + + def test_alert_dismiss(self): + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.dismiss() + + def test_confirm_accept(self): + self.marionette.find_element(By.ID, 'modal-confirm').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.accept() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'confirm-result').text == 'true') + + def test_confirm_dismiss(self): + self.marionette.find_element(By.ID, 'modal-confirm').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.dismiss() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'confirm-result').text == 'false') + + def test_prompt_accept(self): + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.accept() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'prompt-result').text == '') + + def test_prompt_dismiss(self): + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.dismiss() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'prompt-result').text == 'null') + + def test_alert_text(self): + with self.assertRaises(NoAlertPresentException): + alert = self.marionette.switch_to_alert() + alert.text + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertEqual(alert.text, 'Marionette alert') + alert.accept() + + def test_prompt_text(self): + with self.assertRaises(NoAlertPresentException): + alert = self.marionette.switch_to_alert() + alert.text + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertEqual(alert.text, 'Marionette prompt') + alert.accept() + + def test_confirm_text(self): + with self.assertRaises(NoAlertPresentException): + alert = self.marionette.switch_to_alert() + alert.text + self.marionette.find_element(By.ID, 'modal-confirm').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertEqual(alert.text, 'Marionette confirm') + alert.accept() + + def test_set_text_throws(self): + self.assertRaises(NoAlertPresentException, Alert(self.marionette).send_keys, "Foo") + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertRaises(ElementNotInteractableException, alert.send_keys, "Foo") + alert.accept() + + def test_set_text_accept(self): + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.send_keys("Some text!"); + alert.accept() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'prompt-result').text == 'Some text!') + + def test_set_text_dismiss(self): + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.send_keys("Some text!"); + alert.dismiss() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'prompt-result').text == 'null') + + def test_onbeforeunload_dismiss(self): + start_url = self.marionette.get_url() + self.marionette.find_element(By.ID, 'onbeforeunload-handler').click() + self.wait_for_condition( + lambda mn: mn.execute_script(""" + return window.onbeforeunload !== null; + """)) + self.marionette.navigate("about:blank") + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertTrue(alert.text.startswith("This page is asking you to confirm")) + alert.dismiss() + self.assertTrue(self.marionette.get_url().startswith(start_url)) + + def test_onbeforeunload_accept(self): + self.marionette.find_element(By.ID, 'onbeforeunload-handler').click() + self.wait_for_condition( + lambda mn: mn.execute_script(""" + return window.onbeforeunload !== null; + """)) + self.marionette.navigate("about:blank") + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertTrue(alert.text.startswith("This page is asking you to confirm")) + alert.accept() + self.wait_for_condition(lambda mn: mn.get_url() == "about:blank") + + @skip_if_e10s("Bug 1325044") + def test_unrelated_command_when_alert_present(self): + click_handler = self.marionette.find_element(By.ID, 'click-handler') + text = self.marionette.find_element(By.ID, 'click-result').text + self.assertEqual(text, '') + + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + + # Commands succeed, but because the dialog blocks the event loop, + # our actions aren't reflected on the page. + text = self.marionette.find_element(By.ID, 'click-result').text + self.assertEqual(text, '') + click_handler.click() + text = self.marionette.find_element(By.ID, 'click-result').text + self.assertEqual(text, '') + + alert = self.marionette.switch_to_alert() + alert.accept() + + Wait(self.marionette).until(lambda _: not self.alert_present()) + + click_handler.click() + text = self.marionette.find_element(By.ID, 'click-result').text + self.assertEqual(text, 'result') + + +class TestGlobalModals(TestTabModals): + + def setUp(self): + super(TestGlobalModals, self).setUp() + self.marionette.set_pref("prompts.tab_modal.enabled", False) + + def test_unrelated_command_when_alert_present(self): + # The assumptions in this test do not hold on certain platforms, and not when + # e10s is enabled. + pass diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py b/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py new file mode 100644 index 000000000..246068215 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py @@ -0,0 +1,114 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.keys import Keys +from marionette_driver.marionette import Actions + +from marionette_harness import MarionetteTestCase + + +class TestMouseAction(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + if self.marionette.session_capabilities["platformName"] == "darwin": + self.mod_key = Keys.META + else: + self.mod_key = Keys.CONTROL + self.action = Actions(self.marionette) + + def test_click_action(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + link = self.marionette.find_element(By.ID, "mozLink") + self.action.click(link).perform() + self.assertEqual("Clicked", self.marionette.execute_script( + "return document.getElementById('mozLink').innerHTML")) + + def test_clicking_element_out_of_view_succeeds(self): + # The action based click doesn"t check for visibility. + test_html = self.marionette.absolute_url("hidden.html") + self.marionette.navigate(test_html) + el = self.marionette.find_element(By.ID, "child") + self.action.click(el).perform() + + def test_double_click_action(self): + test_html = self.marionette.absolute_url("double_click.html") + self.marionette.navigate(test_html) + el = self.marionette.find_element(By.ID, "one-word-div") + self.action.double_click(el).perform() + el.send_keys(self.mod_key + "c") + rel = self.marionette.find_element(By.ID, "input-field") + rel.send_keys(self.mod_key + "v") + self.assertEqual("zyxw", rel.get_property("value")) + + def test_context_click_action(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + click_el = self.marionette.find_element(By.ID, "resultContainer") + + def context_menu_state(): + with self.marionette.using_context("chrome"): + cm_el = self.marionette.find_element(By.ID, "contentAreaContextMenu") + return cm_el.get_property("state") + + self.assertEqual("closed", context_menu_state()) + self.action.context_click(click_el).perform() + self.wait_for_condition(lambda _: context_menu_state() == "open") + + with self.marionette.using_context("chrome"): + self.marionette.find_element(By.ID, "main-window").send_keys(Keys.ESCAPE) + self.wait_for_condition(lambda _: context_menu_state() == "closed") + + def test_middle_click_action(self): + test_html = self.marionette.absolute_url("clicks.html") + self.marionette.navigate(test_html) + + self.marionette.find_element(By.ID, "addbuttonlistener").click() + + el = self.marionette.find_element(By.ID, "showbutton") + self.action.middle_click(el).perform() + + self.wait_for_condition(lambda _: el.get_property("innerHTML") == "1") + + def test_chrome_click(self): + self.marionette.navigate("about:blank") + data_uri = "data:text/html," + with self.marionette.using_context("chrome"): + urlbar = self.marionette.find_element(By.ID, "urlbar") + urlbar.send_keys(data_uri) + go_button = self.marionette.find_element(By.ID, "urlbar-go-button") + self.action.click(go_button).perform() + self.wait_for_condition(lambda mn: mn.get_url() == data_uri) + + def test_chrome_double_click(self): + self.marionette.navigate("about:blank") + test_word = "quux" + + with self.marionette.using_context("chrome"): + urlbar = self.marionette.find_element(By.ID, "urlbar") + self.assertEqual("", urlbar.get_property("value")) + + urlbar.send_keys(test_word) + self.assertEqual(urlbar.get_property("value"), test_word) + (self.action.double_click(urlbar).perform() + .key_down(self.mod_key) + .key_down("x").perform()) + self.assertEqual(urlbar.get_property("value"), "") + + def test_chrome_context_click_action(self): + self.marionette.set_context("chrome") + def context_menu_state(): + cm_el = self.marionette.find_element(By.ID, "tabContextMenu") + return cm_el.get_property("state") + + currtab = self.marionette.execute_script("return gBrowser.selectedTab") + self.assertEqual("closed", context_menu_state()) + self.action.context_click(currtab).perform() + self.wait_for_condition(lambda _: context_menu_state() == "open") + + (self.marionette.find_element(By.ID, "main-window") + .send_keys(Keys.ESCAPE)) + + self.wait_for_condition(lambda _: context_menu_state() == "closed") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py new file mode 100644 index 000000000..75ed37ecd --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py @@ -0,0 +1,447 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import contextlib +import time +import urllib + +from marionette_driver import By, errors, expected, Wait +from marionette_harness import ( + MarionetteTestCase, + run_if_e10s, + run_if_manage_instance, + skip, + skip_if_mobile, + WindowManagerMixin, +) + + +def inline(doc): + return "data:text/html;charset=utf-8,%s" % urllib.quote(doc) + + +class TestBackForwardNavigation(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestBackForwardNavigation, self).setUp() + + self.test_page = self.marionette.absolute_url('test.html') + + def open_with_link(): + link = self.marionette.find_element(By.ID, "new-blank-tab") + link.click() + + # Always use a blank new tab for an empty history + self.marionette.navigate(self.marionette.absolute_url("windowHandles.html")) + self.new_tab = self.open_tab(open_with_link) + self.marionette.switch_to_window(self.new_tab) + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + lambda _: self.history_length == 1, + message="The newly opened tab doesn't have a browser history length of 1") + + def tearDown(self): + self.marionette.switch_to_parent_frame() + self.close_all_tabs() + + super(TestBackForwardNavigation, self).tearDown() + + @property + def history_length(self): + return self.marionette.execute_script("return window.history.length;") + + def run_test(self, test_pages): + # Helper method to run simple back and forward testcases. + for index, page in enumerate(test_pages): + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.navigate(page["url"]) + else: + self.marionette.navigate(page["url"]) + self.assertEqual(page["url"], self.marionette.get_url()) + self.assertEqual(self.history_length, index + 1) + + for page in test_pages[-2::-1]: + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.go_back() + else: + self.marionette.go_back() + self.assertEqual(page["url"], self.marionette.get_url()) + + for page in test_pages[1::]: + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.go_forward() + else: + self.marionette.go_forward() + self.assertEqual(page["url"], self.marionette.get_url()) + + def test_no_history_items(self): + # Both methods should not raise a failure if no navigation is possible + self.marionette.go_back() + self.marionette.go_forward() + + def test_data_urls(self): + test_pages = [ + {"url": inline("

      foobar

      ")}, + {"url": self.test_page}, + {"url": inline("

      foobar

      ")}, + ] + self.run_test(test_pages) + + def test_same_document_hash_change(self): + test_pages = [ + {"url": "{}#23".format(self.test_page)}, + {"url": self.test_page}, + {"url": "{}#42".format(self.test_page)}, + ] + self.run_test(test_pages) + + @skip("Causes crashes for JS GC (bug 1344863) and a11y (bug 1344868)") + def test_frameset(self): + test_pages = [ + {"url": self.marionette.absolute_url("frameset.html")}, + {"url": self.test_page}, + {"url": self.marionette.absolute_url("frameset.html")}, + ] + self.run_test(test_pages) + + def test_frameset_after_navigating_in_frame(self): + test_element_locator = (By.ID, "email") + + self.marionette.navigate(self.test_page) + self.assertEqual(self.marionette.get_url(), self.test_page) + self.assertEqual(self.history_length, 1) + page = self.marionette.absolute_url("frameset.html") + self.marionette.navigate(page) + self.assertEqual(self.marionette.get_url(), page) + self.assertEqual(self.history_length, 2) + frame = self.marionette.find_element(By.ID, "fifth") + self.marionette.switch_to_frame(frame) + link = self.marionette.find_element(By.ID, "linkId") + link.click() + + # We cannot use get_url() to wait until the target page has been loaded, + # because it will return the URL of the top browsing context and doesn't + # wait for the page load to be complete. + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + expected.element_present(*test_element_locator), + message="Target element 'email' has not been found") + self.assertEqual(self.history_length, 3) + + # Go back to the frame the click navigated away from + self.marionette.go_back() + self.assertEqual(self.marionette.get_url(), page) + with self.assertRaises(errors.NoSuchElementException): + self.marionette.find_element(*test_element_locator) + + # Go back to the non-frameset page + self.marionette.switch_to_parent_frame() + self.marionette.go_back() + self.assertEqual(self.marionette.get_url(), self.test_page) + + # Go forward to the frameset page + self.marionette.go_forward() + self.assertEqual(self.marionette.get_url(), page) + + # Go forward to the frame the click navigated to + # TODO: See above for automatic browser context switches. Hard to do here + frame = self.marionette.find_element(By.ID, "fifth") + self.marionette.switch_to_frame(frame) + self.marionette.go_forward() + self.marionette.find_element(*test_element_locator) + self.assertEqual(self.marionette.get_url(), page) + + def test_image_document_to_html(self): + test_pages = [ + {"url": self.marionette.absolute_url('black.png')}, + {"url": self.test_page}, + {"url": self.marionette.absolute_url('white.png')}, + ] + self.run_test(test_pages) + + def test_image_document_to_image_document(self): + test_pages = [ + {"url": self.marionette.absolute_url('black.png')}, + {"url": self.marionette.absolute_url('white.png')}, + ] + self.run_test(test_pages) + + @run_if_e10s("Requires e10s mode enabled") + def test_remoteness_change(self): + # TODO: Verify that a remoteness change happened + # like: self.assertNotEqual(self.marionette.current_window_handle, self.new_tab) + + # about:robots is always a non-remote page for now + test_pages = [ + {"url": "about:robots"}, + {"url": self.test_page}, + {"url": "about:robots"}, + ] + self.run_test(test_pages) + + def test_navigate_to_requested_about_page_after_error_page(self): + test_pages = [ + {"url": "about:neterror"}, + {"url": self.marionette.absolute_url("test.html")}, + {"url": "about:blocked"}, + ] + self.run_test(test_pages) + + def test_timeout_error(self): + # Bug 1354908 - Disabled on Windows XP due to intermittent failures + caps = self.marionette.session_capabilities + if caps["platformName"] == "windows_nt" and float(caps["platformVersion"]) < 6: + return + + urls = [ + self.marionette.absolute_url('slow'), + self.test_page, + self.marionette.absolute_url('slow'), + ] + + # First, load all pages completely to get them added to the cache + for index, url in enumerate(urls): + self.marionette.navigate(url) + self.assertEqual(url, self.marionette.get_url()) + self.assertEqual(self.history_length, index + 1) + + self.marionette.go_back() + self.assertEqual(urls[1], self.marionette.get_url()) + + # Force triggering a timeout error + self.marionette.timeout.page_load = 0.1 + with self.assertRaises(errors.TimeoutException): + self.marionette.go_back() + self.assertEqual(urls[0], self.marionette.get_url()) + self.marionette.timeout.page_load = 300000 + + self.marionette.go_forward() + self.assertEqual(urls[1], self.marionette.get_url()) + + # Force triggering a timeout error + self.marionette.timeout.page_load = 0.1 + with self.assertRaises(errors.TimeoutException): + self.marionette.go_forward() + self.assertEqual(urls[2], self.marionette.get_url()) + self.marionette.timeout.page_load = 300000 + + def test_certificate_error(self): + test_pages = [ + {"url": self.fixtures.where_is("/test.html", on="https"), + "error": errors.InsecureCertificateException}, + {"url": self.test_page}, + {"url": self.fixtures.where_is("/test.html", on="https"), + "error": errors.InsecureCertificateException}, + ] + self.run_test(test_pages) + + +class TestNavigate(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestNavigate, self).setUp() + + self.marionette.navigate("about:") + self.test_doc = self.marionette.absolute_url("test.html") + self.iframe_doc = self.marionette.absolute_url("test_iframe.html") + + def tearDown(self): + self.marionette.timeout.reset() + self.close_all_tabs() + + super(TestNavigate, self).tearDown() + + @property + def location_href(self): + # Windows 8 has recently seen a proliferation of intermittent + # test failures to do with failing to compare "about:blank" == + # u"about:blank". For the sake of consistenty, we encode the + # returned URL as Unicode here to ensure that the values are + # absolutely of the same type. + # + # (https://bugzilla.mozilla.org/show_bug.cgi?id=1322862) + return self.marionette.execute_script("return window.location.href").encode("utf-8") + + def test_set_location_through_execute_script(self): + self.marionette.execute_script( + "window.location.href = '%s'" % self.test_doc) + Wait(self.marionette).until( + lambda _: self.test_doc == self.location_href) + self.assertEqual("Marionette Test", self.marionette.title) + + def test_navigate_chrome_error(self): + with self.marionette.using_context("chrome"): + self.assertRaises(errors.UnsupportedOperationException, + self.marionette.navigate, "about:blank") + self.assertRaises(errors.UnsupportedOperationException, self.marionette.go_back) + self.assertRaises(errors.UnsupportedOperationException, self.marionette.go_forward) + self.assertRaises(errors.UnsupportedOperationException, self.marionette.refresh) + + def test_get_current_url_returns_top_level_browsing_context_url(self): + self.marionette.navigate(self.iframe_doc) + self.assertEqual(self.iframe_doc, self.location_href) + frame = self.marionette.find_element(By.CSS_SELECTOR, "#test_iframe") + self.marionette.switch_to_frame(frame) + self.assertEqual(self.iframe_doc, self.marionette.get_url()) + + def test_get_current_url(self): + self.marionette.navigate(self.test_doc) + self.assertEqual(self.test_doc, self.marionette.get_url()) + self.marionette.navigate("about:blank") + self.assertEqual("about:blank", self.marionette.get_url()) + + def test_refresh(self): + self.marionette.navigate(self.test_doc) + self.assertEqual("Marionette Test", self.marionette.title) + self.assertTrue(self.marionette.execute_script( + """var elem = window.document.createElement('div'); elem.id = 'someDiv'; + window.document.body.appendChild(elem); return true;""")) + self.assertFalse(self.marionette.execute_script( + "return window.document.getElementById('someDiv') == undefined")) + self.marionette.refresh() + # TODO(ato): Bug 1291320 + time.sleep(0.2) + self.assertEqual("Marionette Test", self.marionette.title) + self.assertTrue(self.marionette.execute_script( + "return window.document.getElementById('someDiv') == undefined")) + + def test_navigate_in_child_frame_changes_to_top(self): + frame_html = self.marionette.absolute_url("frameset.html") + + self.marionette.navigate(frame_html) + frame = self.marionette.find_element(By.NAME, "third") + self.marionette.switch_to_frame(frame) + self.assertRaises(errors.NoSuchElementException, + self.marionette.find_element, By.NAME, "third") + + self.marionette.navigate(frame_html) + self.marionette.find_element(By.NAME, "third") + + @skip_if_mobile("Bug 1323755 - Socket timeout") + def test_invalid_protocol(self): + with self.assertRaises(errors.MarionetteException): + self.marionette.navigate("thisprotocoldoesnotexist://") + + def test_find_element_state_complete(self): + self.marionette.navigate(self.test_doc) + state = self.marionette.execute_script( + "return window.document.readyState") + self.assertEqual("complete", state) + self.assertTrue(self.marionette.find_element(By.ID, "mozLink")) + + def test_error_when_exceeding_page_load_timeout(self): + self.marionette.timeout.page_load = 0.1 + with self.assertRaises(errors.TimeoutException): + self.marionette.navigate(self.marionette.absolute_url("slow")) + + def test_navigate_to_same_image_document_twice(self): + self.marionette.navigate(self.fixtures.where_is("black.png")) + self.assertIn("black.png", self.marionette.title) + self.marionette.navigate(self.fixtures.where_is("black.png")) + self.assertIn("black.png", self.marionette.title) + + def test_navigate_hash_change(self): + doc = inline("

      ") + self.marionette.navigate(doc) + self.marionette.execute_script("window.visited = true", sandbox=None) + self.marionette.navigate("{}#foo".format(doc)) + self.assertTrue(self.marionette.execute_script( + "return window.visited", sandbox=None)) + + @skip_if_mobile("Bug 1334095 - Timeout: No new tab has been opened") + def test_about_blank_for_new_docshell(self): + """ Bug 1312674 - Hang when loading about:blank for a new docshell.""" + def open_with_link(): + link = self.marionette.find_element(By.ID, "new-blank-tab") + link.click() + + # Open a new tab to get a new docshell created + self.marionette.navigate(self.marionette.absolute_url("windowHandles.html")) + new_tab = self.open_tab(trigger=open_with_link) + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.get_url(), "about:blank") + + self.marionette.navigate('about:blank') + self.marionette.close() + self.marionette.switch_to_window(self.start_window) + + @skip("Bug 1332064 - NoSuchElementException: Unable to locate element: :focus") + @run_if_manage_instance("Only runnable if Marionette manages the instance") + @skip_if_mobile("Bug 1322993 - Missing temporary folder") + def test_focus_after_navigation(self): + self.marionette.quit() + self.marionette.start_session() + + self.marionette.navigate(inline("")) + active_el = self.marionette.execute_script("return document.activeElement") + focus_el = self.marionette.find_element(By.CSS_SELECTOR, ":focus") + self.assertEqual(active_el, focus_el) + + +class TestTLSNavigation(MarionetteTestCase): + insecure_tls = {"acceptInsecureCerts": True} + secure_tls = {"acceptInsecureCerts": False} + + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.delete_session() + self.capabilities = self.marionette.start_session( + {"requiredCapabilities": self.insecure_tls}) + + def tearDown(self): + try: + self.marionette.delete_session() + except: + pass + MarionetteTestCase.tearDown(self) + + @contextlib.contextmanager + def safe_session(self): + try: + self.capabilities = self.marionette.start_session( + {"requiredCapabilities": self.secure_tls}) + self.assertFalse(self.capabilities["acceptInsecureCerts"]) + yield self.marionette + finally: + self.marionette.delete_session() + + @contextlib.contextmanager + def unsafe_session(self): + try: + self.capabilities = self.marionette.start_session( + {"requiredCapabilities": self.insecure_tls}) + self.assertTrue(self.capabilities["acceptInsecureCerts"]) + yield self.marionette + finally: + self.marionette.delete_session() + + def test_navigate_by_command(self): + self.marionette.navigate( + self.fixtures.where_is("/test.html", on="https")) + self.assertIn("https", self.marionette.get_url()) + + def test_navigate_by_click(self): + link_url = self.fixtures.where_is("/test.html", on="https") + self.marionette.navigate( + inline("https is the future" % link_url)) + self.marionette.find_element(By.TAG_NAME, "a").click() + self.assertIn("https", self.marionette.get_url()) + + def test_deactivation(self): + invalid_cert_url = self.fixtures.where_is("/test.html", on="https") + + print "with safe session" + with self.safe_session() as session: + with self.assertRaises(errors.InsecureCertificateException): + session.navigate(invalid_cert_url) + + print "with unsafe session" + with self.unsafe_session() as session: + session.navigate(invalid_cert_url) + + print "with safe session again" + with self.safe_session() as session: + with self.assertRaises(errors.InsecureCertificateException): + session.navigate(invalid_cert_url) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py new file mode 100644 index 000000000..c88666986 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py @@ -0,0 +1,33 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestPageSource(MarionetteTestCase): + def testShouldReturnTheSourceOfAPage(self): + test_html = self.marionette.absolute_url("testPageSource.html") + self.marionette.navigate(test_html) + source = self.marionette.page_source + from_web_api = self.marionette.execute_script("return document.documentElement.outerHTML") + self.assertTrue("baz") + self.assertEqual(source, from_web_api) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource_chrome.py new file mode 100644 index 000000000..5f60e6010 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource_chrome.py @@ -0,0 +1,29 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestPageSourceChrome(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestPageSourceChrome, self).setUp() + self.marionette.set_context("chrome") + + def open_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test.xul', + 'foo', 'chrome,centerscreen'); + """) + + new_window = self.open_window(open_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + self.close_all_windows() + super(TestPageSourceChrome, self).tearDown() + + def testShouldReturnXULDetails(self): + source = self.marionette.page_source + self.assertTrue('

      ") +input = inline("") +long = inline("

      foo

      ") +short = inline("") +svg = inline(""" + + + """, mime="image/svg+xml") + + +class ScreenCaptureTestCase(MarionetteTestCase): + + def setUp(self): + super(ScreenCaptureTestCase, self).setUp() + + self._device_pixel_ratio = None + + @property + def device_pixel_ratio(self): + if self._device_pixel_ratio is None: + self._device_pixel_ratio = self.marionette.execute_script(""" + return window.devicePixelRatio + """) + return self._device_pixel_ratio + + @property + def document_element(self): + return self.marionette.find_element(By.CSS_SELECTOR, ":root") + + @property + def page_y_offset(self): + return self.marionette.execute_script("return window.pageYOffset") + + @property + def viewport_dimensions(self): + return self.marionette.execute_script(""" + return [arguments[0].clientWidth, + arguments[0].clientHeight]; + """, script_args=[self.document_element]) + + def assert_png(self, screenshot): + """Test that screenshot is a Base64 encoded PNG file.""" + image = base64.decodestring(screenshot) + self.assertEqual(imghdr.what("", image), "png") + + def assert_formats(self, element=None): + if element is None: + element = self.document_element + + screenshot_default = self.marionette.screenshot(element=element) + screenshot_image = self.marionette.screenshot(element=element, format="base64") + binary1 = self.marionette.screenshot(element=element, format="binary") + binary2 = self.marionette.screenshot(element=element, format="binary") + hash1 = self.marionette.screenshot(element=element, format="hash") + hash2 = self.marionette.screenshot(element=element, format="hash") + + # Valid data should have been returned + self.assert_png(screenshot_image) + self.assertEqual(imghdr.what("", binary1), "png") + self.assertEqual(screenshot_image, base64.b64encode(binary1)) + self.assertEqual(hash1, hashlib.sha256(screenshot_image).hexdigest()) + + # Different formats produce different data + self.assertNotEqual(screenshot_image, binary1) + self.assertNotEqual(screenshot_image, hash1) + self.assertNotEqual(binary1, hash1) + + # A second capture should be identical + self.assertEqual(screenshot_image, screenshot_default) + self.assertEqual(binary1, binary2) + self.assertEqual(hash1, hash2) + + def get_element_dimensions(self, element): + rect = element.rect + return rect["width"], rect["height"] + + def get_image_dimensions(self, screenshot): + self.assert_png(screenshot) + image = base64.decodestring(screenshot) + width, height = struct.unpack(">LL", image[16:24]) + return int(width), int(height) + + def scale(self, rect): + return (int(rect[0] * self.device_pixel_ratio), + int(rect[1] * self.device_pixel_ratio)) + + +class TestScreenCaptureChrome(WindowManagerMixin, ScreenCaptureTestCase): + + def setUp(self): + super(TestScreenCaptureChrome, self).setUp() + self.marionette.set_context("chrome") + + def tearDown(self): + self.close_all_windows() + super(TestScreenCaptureChrome, self).tearDown() + + @property + def window_dimensions(self): + return tuple(self.marionette.execute_script(""" + let el = document.documentElement; + let rect = el.getBoundingClientRect(); + return [rect.width, rect.height]; + """)) + + def open_dialog(self, url=None, width=None, height=None): + if url is None: + url = "chrome://marionette/content/test_dialog.xul" + + def opener(): + features = "chrome" + if height is not None: + features += ",height={}".format(height) + if width is not None: + features += ",width={}".format(width) + + self.marionette.execute_script(""" + window.open(arguments[0], "", arguments[1]); + """, script_args=[url, features]) + + return self.open_window(opener) + + def test_capture_different_context(self): + """Check that screenshots in content and chrome are different.""" + with self.marionette.using_context("content"): + screenshot_content = self.marionette.screenshot() + screenshot_chrome = self.marionette.screenshot() + self.assertNotEqual(screenshot_content, screenshot_chrome) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_capture_element(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + # Ensure we only capture the element + el = self.marionette.find_element(By.ID, "test-list") + screenshot_element = self.marionette.screenshot(element=el) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot_element)) + + # Ensure we do not capture the full window + screenshot_dialog = self.marionette.screenshot() + self.assertNotEqual(screenshot_dialog, screenshot_element) + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_capture_flags(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + textbox = self.marionette.find_element(By.ID, "text-box") + textbox.send_keys("") + screenshot_focus = self.marionette.screenshot() + + self.marionette.execute_script("arguments[0].blur();", script_args=[textbox]) + screenshot_no_focus = self.marionette.screenshot() + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + self.assertNotEqual(screenshot_focus, screenshot_no_focus) + + def test_capture_full_area(self): + # A full capture is not the outer dimensions of the window, + # but instead the bounding box of the window's root node (documentElement). + screenshot_full = self.marionette.screenshot() + screenshot_root = self.marionette.screenshot(element=self.document_element) + + self.assert_png(screenshot_full) + self.assert_png(screenshot_root) + self.assertEqual(screenshot_root, screenshot_full) + self.assertEqual(self.scale(self.get_element_dimensions(self.document_element)), + self.get_image_dimensions(screenshot_full)) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_capture_viewport(self): + # Load a HTML test page into the chrome window to get scrollbars + test_page = self.marionette.absolute_url("test.html") + dialog = self.open_dialog(url=test_page, width=50, height=50) + self.marionette.switch_to_window(dialog) + + # Size of screenshot has to match viewport size + screenshot = self.marionette.screenshot(full=False) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.viewport_dimensions), + self.get_image_dimensions(screenshot)) + self.assertNotEqual(self.scale(self.window_dimensions), + self.get_image_dimensions(screenshot)) + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_capture_window_already_closed(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + self.marionette.close_chrome_window() + + self.assertRaises(NoSuchWindowException, self.marionette.screenshot) + self.marionette.switch_to_window(self.start_window) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_formats(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + self.assert_formats() + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + def test_format_unknown(self): + with self.assertRaises(ValueError): + self.marionette.screenshot(format="cheese") + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_highlight_elements(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + # Highlighting the element itself shouldn't make the image larger + element = self.marionette.find_element(By.ID, "test-list") + screenshot_element = self.marionette.screenshot(element=element) + screenshot_highlight = self.marionette.screenshot(element=element, + highlights=[element]) + self.assertEqual(self.scale(self.get_element_dimensions(element)), + self.get_image_dimensions(screenshot_element)) + self.assertNotEqual(screenshot_element, screenshot_highlight) + + # Highlighting a sub element + button = self.marionette.find_element(By.ID, "choose-button") + screenshot_highlight_button = self.marionette.screenshot(element=element, + highlights=[button]) + self.assertNotEqual(screenshot_element, screenshot_highlight_button) + self.assertNotEqual(screenshot_highlight, screenshot_highlight_button) + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + def test_highlight_element_not_seen(self): + """Check that for not found elements an exception is raised.""" + with self.marionette.using_context('content'): + self.marionette.navigate(box) + content_element = self.marionette.find_element(By.ID, "green") + + self.assertRaisesRegexp(JavascriptException, "Element reference not seen before", + self.marionette.screenshot, highlights=[content_element]) + + chrome_document_element = self.document_element + with self.marionette.using_context('content'): + self.assertRaisesRegexp(JavascriptException, "Element reference not seen before", + self.marionette.screenshot, + highlights=[chrome_document_element]) + + +class TestScreenCaptureContent(WindowManagerMixin, ScreenCaptureTestCase): + + def setUp(self): + super(TestScreenCaptureContent, self).setUp() + self.marionette.set_context("content") + + def tearDown(self): + self.close_all_tabs() + super(TestScreenCaptureContent, self).tearDown() + + @property + def scroll_dimensions(self): + return tuple(self.marionette.execute_script(""" + return [document.body.scrollWidth, document.body.scrollHeight] + """)) + + @skip_if_mobile("Needs application independent method to open a new tab") + def test_capture_tab_already_closed(self): + tab = self.open_tab() + self.marionette.switch_to_window(tab) + self.marionette.close() + + self.assertRaises(NoSuchWindowException, self.marionette.screenshot) + self.marionette.switch_to_window(self.start_tab) + + def test_capture_element(self): + self.marionette.navigate(box) + el = self.marionette.find_element(By.TAG_NAME, "div") + screenshot = self.marionette.screenshot(element=el) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot)) + + @skip("Bug 1213875") + def test_capture_element_scrolled_into_view(self): + self.marionette.navigate(long) + el = self.marionette.find_element(By.TAG_NAME, "p") + screenshot = self.marionette.screenshot(element=el) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot)) + self.assertGreater(self.page_y_offset, 0) + + @skip("Bug 1330560 - AssertionError: u'iVBORw0KGgoA... (images unexpectedly equal)") + def test_capture_flags(self): + self.marionette.navigate(input) + + textbox = self.marionette.find_element(By.ID, "text-input") + textbox.send_keys("") + screenshot_focus = self.marionette.screenshot() + + self.marionette.execute_script("arguments[0].blur();", script_args=[textbox]) + screenshot_no_focus = self.marionette.screenshot() + + self.assertNotEqual(screenshot_focus, screenshot_no_focus) + + @skip_if_mobile("Bug 1330642 - Tuples differ: (1960, 11130) != (1960, 11129)") + def test_capture_html_document_element(self): + self.marionette.navigate(long) + screenshot = self.marionette.screenshot() + self.assert_png(screenshot) + self.assertEqual(self.scale(self.scroll_dimensions), + self.get_image_dimensions(screenshot)) + + def test_capture_svg_document_element(self): + self.marionette.navigate(svg) + screenshot = self.marionette.screenshot() + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(self.document_element)), + self.get_image_dimensions(screenshot)) + + def test_capture_viewport(self): + url = self.marionette.absolute_url("clicks.html") + self.marionette.navigate(short) + self.marionette.navigate(url) + screenshot = self.marionette.screenshot(full=False) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.viewport_dimensions), + self.get_image_dimensions(screenshot)) + + def test_capture_viewport_after_scroll(self): + self.marionette.navigate(long) + before = self.marionette.screenshot() + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.execute_script( + "arguments[0].scrollIntoView()", script_args=[el]) + after = self.marionette.screenshot(full=False) + self.assertNotEqual(before, after) + self.assertGreater(self.page_y_offset, 0) + + def test_formats(self): + self.marionette.navigate(box) + + # Use a smaller region to speed up the test + element = self.marionette.find_element(By.TAG_NAME, "div") + self.assert_formats(element=element) + + def test_format_unknown(self): + with self.assertRaises(ValueError): + self.marionette.screenshot(format="cheese") + + def test_highlight_elements(self): + self.marionette.navigate(box) + element = self.marionette.find_element(By.TAG_NAME, "div") + + # Highlighting the element itself shouldn't make the image larger + screenshot_element = self.marionette.screenshot(element=element) + screenshot_highlight = self.marionette.screenshot(element=element, + highlights=[element]) + self.assertEqual(self.scale(self.get_element_dimensions(element)), + self.get_image_dimensions(screenshot_highlight)) + self.assertNotEqual(screenshot_element, screenshot_highlight) + + # Highlighting a sub element + paragraph = self.marionette.find_element(By.ID, "green") + screenshot_highlight_paragraph = self.marionette.screenshot(element=element, + highlights=[paragraph]) + self.assertNotEqual(screenshot_element, screenshot_highlight_paragraph) + self.assertNotEqual(screenshot_highlight, screenshot_highlight_paragraph) + + def test_scroll_default(self): + self.marionette.navigate(long) + before = self.page_y_offset + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.screenshot(element=el, format="hash") + self.assertNotEqual(before, self.page_y_offset) + + def test_scroll(self): + self.marionette.navigate(long) + before = self.page_y_offset + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.screenshot(element=el, format="hash", scroll=True) + self.assertNotEqual(before, self.page_y_offset) + + def test_scroll_off(self): + self.marionette.navigate(long) + el = self.marionette.find_element(By.TAG_NAME, "p") + before = self.page_y_offset + self.marionette.screenshot(element=el, format="hash", scroll=False) + self.assertEqual(before, self.page_y_offset) + + def test_scroll_no_element(self): + self.marionette.navigate(long) + before = self.page_y_offset + self.marionette.screenshot(format="hash", scroll=True) + self.assertEqual(before, self.page_y_offset) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_select.py b/testing/marionette/harness/marionette_harness/tests/unit/test_select.py new file mode 100644 index 000000000..3c9522bea --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_select.py @@ -0,0 +1,164 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import urllib + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + + +class SelectTestCase(MarionetteTestCase): + def assertSelected(self, option_element): + self.assertTrue(option_element.is_selected(), "