summaryrefslogtreecommitdiffstats
path: root/testing/marionette/harness
diff options
context:
space:
mode:
Diffstat (limited to 'testing/marionette/harness')
-rw-r--r--testing/marionette/harness/.flake83
-rw-r--r--testing/marionette/harness/MANIFEST.in3
-rw-r--r--testing/marionette/harness/README.rst30
-rw-r--r--testing/marionette/harness/marionette_harness/__init__.py35
-rw-r--r--testing/marionette/harness/marionette_harness/marionette_test/__init__.py31
-rw-r--r--testing/marionette/harness/marionette_harness/marionette_test/decorators.py239
-rw-r--r--testing/marionette/harness/marionette_harness/marionette_test/testcases.py504
-rw-r--r--testing/marionette/harness/marionette_harness/runner/__init__.py22
-rw-r--r--testing/marionette/harness/marionette_harness/runner/base.py1076
-rw-r--r--testing/marionette/harness/marionette_harness/runner/httpd.py142
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/__init__.py13
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/History.md57
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py6
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py326
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py106
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py34
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/Makefile153
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo4
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html98
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html90
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt8
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt72
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt8
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css537
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css256
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js238
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js2
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css62
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js622
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js159
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js31
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js808
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html404
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html297
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html174
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv6
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html112
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html105
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js1
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html157
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/client.rst8
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/conf.py243
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/index.rst72
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/make.bat190
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/server.rst8
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/readme.md88
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/setup.py20
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_client.py247
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_remote.py31
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/test/test_webdriver.py60
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/browsermob.py80
-rw-r--r--testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py129
-rwxr-xr-xtesting/marionette/harness/marionette_harness/runner/serve.py227
-rw-r--r--testing/marionette/harness/marionette_harness/runner/test.cert86
-rw-r--r--testing/marionette/harness/marionette_harness/runner/test.key28
-rw-r--r--testing/marionette/harness/marionette_harness/runtests.py98
-rw-r--r--testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py100
-rw-r--r--testing/marionette/harness/marionette_harness/tests/harness_unit/test_httpd.py90
-rw-r--r--testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py32
-rw-r--r--testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_harness.py108
-rw-r--r--testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py442
-rw-r--r--testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_test_result.py54
-rw-r--r--testing/marionette/harness/marionette_harness/tests/harness_unit/test_serve.py67
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit-tests.ini11
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js1
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/importscript.js1
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpibin0 -> 1552 bytes
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/single_finger_functions.py131
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py134
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py210
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_addons.py58
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_anonymous_content.py90
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_browsermobproxy.py34
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py253
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_checkbox.py17
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_checkbox_chrome.py36
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py51
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_chrome_async_finish.js6
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_css.py23
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_clearing.py72
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_click.py254
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py35
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py117
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_cookies.py115
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_crash.py155
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py67
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py29
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py483
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py162
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py85
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py17
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_elementsize_chrome.py34
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_errors.py77
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_execute_async_script.py156
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_execute_isolate.py37
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_execute_sandboxes.py79
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py402
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_expected.py228
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_expectedfail.py11
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py152
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py82
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_geckoinstance.py25
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_getactiveframe_oop.py93
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_implicit_waits.py26
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py138
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py91
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_localization.py56
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_log.py64
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_marionette.py67
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py198
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py114
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py447
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py33
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_pagesource_chrome.py29
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_position.py19
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py167
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py34
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py252
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py173
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py34
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_report.py29
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_run_js_test.py10
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py86
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py428
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_select.py164
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_session.py56
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_set_window_size.py84
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_shadow_dom.py80
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_chrome.js12
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_fail.js16
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_pass.js12
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_sanity.py107
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_timeout.js16
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_single_finger_desktop.py123
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_skip_setup.py35
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py183
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py56
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_switch_remote_frame.py118
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py124
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py171
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_teardown_context_preserved.py21
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_text.py224
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_text_chrome.py44
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py115
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_transport.py172
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_typing.py332
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_using_permissions.py46
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py121
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_wait.py347
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py80
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py81
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py207
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py96
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py42
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_window_title.py12
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py26
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py27
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/test_with_using_context.py66
-rw-r--r--testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini132
-rw-r--r--testing/marionette/harness/marionette_harness/tests/webapi-tests.ini8
-rw-r--r--testing/marionette/harness/marionette_harness/www/black.pngbin0 -> 150 bytes
-rw-r--r--testing/marionette/harness/marionette_harness/www/bug814037.html55
-rw-r--r--testing/marionette/harness/marionette_harness/www/click_out_of_bounds_overflow.html90
-rw-r--r--testing/marionette/harness/marionette_harness/www/clicks.html39
-rw-r--r--testing/marionette/harness/marionette_harness/www/cssTransform.html61
-rw-r--r--testing/marionette/harness/marionette_harness/www/cssTransform2.html20
-rw-r--r--testing/marionette/harness/marionette_harness/www/datetimePage.html15
-rw-r--r--testing/marionette/harness/marionette_harness/www/deletingFrame.html29
-rw-r--r--testing/marionette/harness/marionette_harness/www/double_click.html18
-rw-r--r--testing/marionette/harness/marionette_harness/www/element_bottom.html12
-rw-r--r--testing/marionette/harness/marionette_harness/www/element_left.html12
-rw-r--r--testing/marionette/harness/marionette_harness/www/element_outside_viewport.html41
-rw-r--r--testing/marionette/harness/marionette_harness/www/element_right.html12
-rw-r--r--testing/marionette/harness/marionette_harness/www/element_top.html12
-rw-r--r--testing/marionette/harness/marionette_harness/www/empty.html12
-rw-r--r--testing/marionette/harness/marionette_harness/www/formPage.html116
-rw-r--r--testing/marionette/harness/marionette_harness/www/frameset.html14
-rw-r--r--testing/marionette/harness/marionette_harness/www/framesetPage2.html7
-rw-r--r--testing/marionette/harness/marionette_harness/www/hidden.html5
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/blue.jpgbin0 -> 92 bytes
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/boolean_attributes.html2
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/geolocation.js18
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/green.jpgbin0 -> 92 bytes
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/offline.html1
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/red.jpgbin0 -> 92 bytes
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/status.html1
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/test.appcache11
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/test_html_inputs.html2
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5/yellow.jpgbin0 -> 92 bytes
-rw-r--r--testing/marionette/harness/marionette_harness/www/html5Page.html45
-rw-r--r--testing/marionette/harness/marionette_harness/www/javascriptPage.html278
-rw-r--r--testing/marionette/harness/marionette_harness/www/macbeth.html5254
-rw-r--r--testing/marionette/harness/marionette_harness/www/modal_dialogs.html39
-rw-r--r--testing/marionette/harness/marionette_harness/www/nestedElements.html9
-rw-r--r--testing/marionette/harness/marionette_harness/www/rectangles.html40
-rw-r--r--testing/marionette/harness/marionette_harness/www/resultPage.html17
-rw-r--r--testing/marionette/harness/marionette_harness/www/scroll.html30
-rw-r--r--testing/marionette/harness/marionette_harness/www/scroll2.html24
-rw-r--r--testing/marionette/harness/marionette_harness/www/scroll3.html18
-rw-r--r--testing/marionette/harness/marionette_harness/www/scroll4.html15
-rw-r--r--testing/marionette/harness/marionette_harness/www/scroll5.html20
-rw-r--r--testing/marionette/harness/marionette_harness/www/shim.js282
-rw-r--r--testing/marionette/harness/marionette_harness/www/test.html38
-rw-r--r--testing/marionette/harness/marionette_harness/www/testAction.html94
-rw-r--r--testing/marionette/harness/marionette_harness/www/testPageSource.html14
-rw-r--r--testing/marionette/harness/marionette_harness/www/testPageSource.xml5
-rw-r--r--testing/marionette/harness/marionette_harness/www/testPageSourceWithUnicodeChars.html11
-rw-r--r--testing/marionette/harness/marionette_harness/www/testSize.html9
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_accessibility.html57
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_carets_columns.html31
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_carets_cursor.html31
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_carets_display_none.html10
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_carets_iframe.html20
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_carets_longtext.html9
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_carets_multipleline.html24
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_carets_multiplerange.html19
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_carets_selection.html38
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_clearing.html24
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_dynamic.html38
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_iframe.html16
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_inner_iframe.html13
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_nested_iframe.html13
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_oop_1.html15
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_oop_2.html15
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_shadow_dom.html26
-rw-r--r--testing/marionette/harness/marionette_harness/www/test_windows.html14
-rw-r--r--testing/marionette/harness/marionette_harness/www/white.pngbin0 -> 150 bytes
-rw-r--r--testing/marionette/harness/marionette_harness/www/windowHandles.html16
-rw-r--r--testing/marionette/harness/marionette_harness/www/xhtmlTest.html79
-rw-r--r--testing/marionette/harness/requirements.txt14
-rw-r--r--testing/marionette/harness/setup.py59
231 files changed, 27041 insertions, 0 deletions
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,<html>test page</html>')
+
+ 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 """<!doctype html>
+<title>ok</title>
+<p>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 <target>' where <target> 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <title>browsermobproxy &mdash; BrowserMob Proxy 0.6.0 documentation</title>
+
+ <link rel="stylesheet" href="../_static/default.css" type="text/css" />
+ <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
+
+ <script type="text/javascript">
+ var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: '../',
+ VERSION: '0.6.0',
+ COLLAPSE_INDEX: false,
+ FILE_SUFFIX: '.html',
+ HAS_SOURCE: true
+ };
+ </script>
+ <script type="text/javascript" src="../_static/jquery.js"></script>
+ <script type="text/javascript" src="../_static/underscore.js"></script>
+ <script type="text/javascript" src="../_static/doctools.js"></script>
+ <link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="../index.html" />
+ <link rel="up" title="Module code" href="index.html" />
+ </head>
+ <body>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="../genindex.html" title="General Index"
+ accesskey="I">index</a></li>
+ <li class="right" >
+ <a href="../py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="../index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ <li><a href="index.html" accesskey="U">Module code</a> &raquo;</li>
+ </ul>
+ </div>
+
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body">
+
+ <h1>Source code for browsermobproxy</h1><div class="highlight"><pre>
+<span class="n">__version__</span> <span class="o">=</span> <span class="s">&#39;0.5.0&#39;</span>
+
+<span class="kn">from</span> <span class="nn">server</span> <span class="kn">import</span> <span class="n">Server</span>
+<span class="kn">from</span> <span class="nn">client</span> <span class="kn">import</span> <span class="n">Client</span>
+
+<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;Server&#39;</span><span class="p">,</span> <span class="s">&#39;Client&#39;</span><span class="p">,</span> <span class="s">&#39;browsermobproxy&#39;</span><span class="p">]</span>
+</pre></div>
+
+ </div>
+ </div>
+ </div>
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+<div id="searchbox" style="display: none">
+ <h3>Quick search</h3>
+ <form class="search" action="../search.html" method="get">
+ <input type="text" name="q" />
+ <input type="submit" value="Go" />
+ <input type="hidden" name="check_keywords" value="yes" />
+ <input type="hidden" name="area" value="default" />
+ </form>
+ <p class="searchtip" style="font-size: 90%">
+ Enter search terms or a module, class or function name.
+ </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="../genindex.html" title="General Index"
+ >index</a></li>
+ <li class="right" >
+ <a href="../py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="../index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ <li><a href="index.html" >Module code</a> &raquo;</li>
+ </ul>
+ </div>
+ <div class="footer">
+ &copy; Copyright 2014, David Burns.
+ Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
+ </div>
+ </body>
+</html> \ 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <title>Overview: module code &mdash; BrowserMob Proxy 0.6.0 documentation</title>
+
+ <link rel="stylesheet" href="../_static/default.css" type="text/css" />
+ <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
+
+ <script type="text/javascript">
+ var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: '../',
+ VERSION: '0.6.0',
+ COLLAPSE_INDEX: false,
+ FILE_SUFFIX: '.html',
+ HAS_SOURCE: true
+ };
+ </script>
+ <script type="text/javascript" src="../_static/jquery.js"></script>
+ <script type="text/javascript" src="../_static/underscore.js"></script>
+ <script type="text/javascript" src="../_static/doctools.js"></script>
+ <link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="../index.html" />
+ </head>
+ <body>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="../genindex.html" title="General Index"
+ accesskey="I">index</a></li>
+ <li class="right" >
+ <a href="../py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="../index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body">
+
+ <h1>All modules for which code is available</h1>
+<ul><li><a href="browsermobproxy.html">browsermobproxy</a></li>
+</ul>
+
+ </div>
+ </div>
+ </div>
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+<div id="searchbox" style="display: none">
+ <h3>Quick search</h3>
+ <form class="search" action="../search.html" method="get">
+ <input type="text" name="q" />
+ <input type="submit" value="Go" />
+ <input type="hidden" name="check_keywords" value="yes" />
+ <input type="hidden" name="area" value="default" />
+ </form>
+ <p class="searchtip" style="font-size: 90%">
+ Enter search terms or a module, class or function name.
+ </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="../genindex.html" title="General Index"
+ >index</a></li>
+ <li class="right" >
+ <a href="../py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="../index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+ <div class="footer">
+ &copy; Copyright 2014, David Burns.
+ Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
+ </div>
+ </body>
+</html> \ 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() {
+ $('<a class="headerlink">\u00B6</a>').
+ attr('href', '#' + this.id).
+ attr('title', _('Permalink to this headline')).
+ appendTo(this);
+ });
+ $('dt[id]').each(function() {
+ $('<a class="headerlink">\u00B6</a>').
+ 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);
+ $('<p class="highlight-link"><a href="javascript:Documentation.' +
+ 'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
+ .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<i;r++)v.event.add(t,n,u[n][r])}o.data&&(o.data=v.extend({},o.data))}function Ot(e,t){var n;if(t.nodeType!==1)return;t.clearAttributes&&t.clearAttributes(),t.mergeAttributes&&t.mergeAttributes(e),n=t.nodeName.toLowerCase(),n==="object"?(t.parentNode&&(t.outerHTML=e.outerHTML),v.support.html5Clone&&e.innerHTML&&!v.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):n==="input"&&Et.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):n==="option"?t.selected=e.defaultSelected:n==="input"||n==="textarea"?t.defaultValue=e.defaultValue:n==="script"&&t.text!==e.text&&(t.text=e.text),t.removeAttribute(v.expando)}function Mt(e){return typeof e.getElementsByTagName!="undefined"?e.getElementsByTagName("*"):typeof e.querySelectorAll!="undefined"?e.querySelectorAll("*"):[]}function _t(e){Et.test(e.type)&&(e.defaultChecked=e.checked)}function Qt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Jt.length;while(i--){t=Jt[i]+n;if(t in e)return t}return r}function Gt(e,t){return e=t||e,v.css(e,"display")==="none"||!v.contains(e.ownerDocument,e)}function Yt(e,t){var n,r,i=[],s=0,o=e.length;for(;s<o;s++){n=e[s];if(!n.style)continue;i[s]=v._data(n,"olddisplay"),t?(!i[s]&&n.style.display==="none"&&(n.style.display=""),n.style.display===""&&Gt(n)&&(i[s]=v._data(n,"olddisplay",nn(n.nodeName)))):(r=Dt(n,"display"),!i[s]&&r!=="none"&&v._data(n,"olddisplay",r))}for(s=0;s<o;s++){n=e[s];if(!n.style)continue;if(!t||n.style.display==="none"||n.style.display==="")n.style.display=t?i[s]||"":"none"}return e}function Zt(e,t,n){var r=Rt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function en(e,t,n,r){var i=n===(r?"border":"content")?4:t==="width"?1:0,s=0;for(;i<4;i+=2)n==="margin"&&(s+=v.css(e,n+$t[i],!0)),r?(n==="content"&&(s-=parseFloat(Dt(e,"padding"+$t[i]))||0),n!=="margin"&&(s-=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0)):(s+=parseFloat(Dt(e,"padding"+$t[i]))||0,n!=="padding"&&(s+=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0));return s}function tn(e,t,n){var r=t==="width"?e.offsetWidth:e.offsetHeight,i=!0,s=v.support.boxSizing&&v.css(e,"boxSizing")==="border-box";if(r<=0||r==null){r=Dt(e,t);if(r<0||r==null)r=e.style[t];if(Ut.test(r))return r;i=s&&(v.support.boxSizingReliable||r===e.style[t]),r=parseFloat(r)||0}return r+en(e,t,n||(s?"border":"content"),i)+"px"}function nn(e){if(Wt[e])return Wt[e];var t=v("<"+e+">").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("<!doctype html><html><body>"),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<a;u++)r=o[u],s=/^\+/.test(r),s&&(r=r.substr(1)||"*"),i=e[r]=e[r]||[],i[s?"unshift":"push"](n)}}function kn(e,n,r,i,s,o){s=s||n.dataTypes[0],o=o||{},o[s]=!0;var u,a=e[s],f=0,l=a?a.length:0,c=e===Sn;for(;f<l&&(c||!u);f++)u=a[f](n,r,i),typeof u=="string"&&(!c||o[u]?u=t:(n.dataTypes.unshift(u),u=kn(e,n,r,i,u,o)));return(c||!u)&&!o["*"]&&(u=kn(e,n,r,i,"*",o)),u}function Ln(e,n){var r,i,s=v.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((s[r]?e:i||(i={}))[r]=n[r]);i&&v.extend(!0,e,i)}function An(e,n,r){var i,s,o,u,a=e.contents,f=e.dataTypes,l=e.responseFields;for(s in l)s in r&&(n[l[s]]=r[s]);while(f[0]==="*")f.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("content-type"));if(i)for(s in a)if(a[s]&&a[s].test(i)){f.unshift(s);break}if(f[0]in r)o=f[0];else{for(s in r){if(!f[0]||e.converters[s+" "+f[0]]){o=s;break}u||(u=s)}o=o||u}if(o)return o!==f[0]&&f.unshift(o),r[o]}function On(e,t){var n,r,i,s,o=e.dataTypes.slice(),u=o[0],a={},f=0;e.dataFilter&&(t=e.dataFilter(t,e.dataType));if(o[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=o[++f];)if(i!=="*"){if(u!=="*"&&u!==i){n=a[u+" "+i]||a["* "+i];if(!n)for(r in a){s=r.split(" ");if(s[1]===i){n=a[u+" "+s[0]]||a["* "+s[0]];if(n){n===!0?n=a[r]:a[r]!==!0&&(i=s[0],o.splice(f--,0,i));break}}}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(l){return{state:"parsererror",error:n?l:"No conversion from "+u+" to "+i}}}u=i}return{state:"success",data:t}}function Fn(){try{return new e.XMLHttpRequest}catch(t){}}function In(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function $n(){return setTimeout(function(){qn=t},0),qn=v.now()}function Jn(e,t){v.each(t,function(t,n){var r=(Vn[t]||[]).concat(Vn["*"]),i=0,s=r.length;for(;i<s;i++)if(r[i].call(e,t,n))return})}function Kn(e,t,n){var r,i=0,s=0,o=Xn.length,u=v.Deferred().always(function(){delete a.elem}),a=function(){var t=qn||$n(),n=Math.max(0,f.startTime+f.duration-t),r=n/f.duration||0,i=1-r,s=0,o=f.tweens.length;for(;s<o;s++)f.tweens[s].run(i);return u.notifyWith(e,[f,i,n]),i<1&&o?n:(u.resolveWith(e,[f]),!1)},f=u.promise({elem:e,props:v.extend({},t),opts:v.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:qn||$n(),duration:n.duration,tweens:[],createTween:function(t,n,r){var i=v.Tween(e,f.opts,t,n,f.opts.specialEasing[t]||f.opts.easing);return f.tweens.push(i),i},stop:function(t){var n=0,r=t?f.tweens.length:0;for(;n<r;n++)f.tweens[n].run(1);return t?u.resolveWith(e,[f,t]):u.rejectWith(e,[f,t]),this}}),l=f.props;Qn(l,f.opts.specialEasing);for(;i<o;i++){r=Xn[i].call(f,e,l,f.opts);if(r)return r}return Jn(f,l),v.isFunction(f.opts.start)&&f.opts.start.call(e,f),v.fx.timer(v.extend(a,{anim:f,queue:f.opts.queue,elem:e})),f.progress(f.opts.progress).done(f.opts.done,f.opts.complete).fail(f.opts.fail).always(f.opts.always)}function Qn(e,t){var n,r,i,s,o;for(n in e){r=v.camelCase(n),i=t[r],s=e[n],v.isArray(s)&&(i=s[1],s=e[n]=s[0]),n!==r&&(e[r]=s,delete e[n]),o=v.cssHooks[r];if(o&&"expand"in o){s=o.expand(s),delete e[r];for(n in s)n in e||(e[n]=s[n],t[n]=i)}else t[r]=i}}function Gn(e,t,n){var r,i,s,o,u,a,f,l,c,h=this,p=e.style,d={},m=[],g=e.nodeType&&Gt(e);n.queue||(l=v._queueHooks(e,"fx"),l.unqueued==null&&(l.unqueued=0,c=l.empty.fire,l.empty.fire=function(){l.unqueued||c()}),l.unqueued++,h.always(function(){h.always(function(){l.unqueued--,v.queue(e,"fx").length||l.empty.fire()})})),e.nodeType===1&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],v.css(e,"display")==="inline"&&v.css(e,"float")==="none"&&(!v.support.inlineBlockNeedsLayout||nn(e.nodeName)==="inline"?p.display="inline-block":p.zoom=1)),n.overflow&&(p.overflow="hidden",v.support.shrinkWrapBlocks||h.done(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t){s=t[r];if(Un.exec(s)){delete t[r],a=a||s==="toggle";if(s===(g?"hide":"show"))continue;m.push(r)}}o=m.length;if(o){u=v._data(e,"fxshow")||v._data(e,"fxshow",{}),"hidden"in u&&(g=u.hidden),a&&(u.hidden=!g),g?v(e).show():h.done(function(){v(e).hide()}),h.done(function(){var t;v.removeData(e,"fxshow",!0);for(t in d)v.style(e,t,d[t])});for(r=0;r<o;r++)i=m[r],f=h.createTween(i,g?u[i]:0),d[i]=u[i]||v.style(e,i),i in u||(u[i]=f.start,g&&(f.end=f.start,f.start=i==="width"||i==="height"?1:0))}}function Yn(e,t,n,r,i){return new Yn.prototype.init(e,t,n,r,i)}function Zn(e,t){var n,r={height:e},i=0;t=t?1:0;for(;i<4;i+=2-t)n=$t[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function tr(e){return v.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:!1}var n,r,i=e.document,s=e.location,o=e.navigator,u=e.jQuery,a=e.$,f=Array.prototype.push,l=Array.prototype.slice,c=Array.prototype.indexOf,h=Object.prototype.toString,p=Object.prototype.hasOwnProperty,d=String.prototype.trim,v=function(e,t){return new v.fn.init(e,t,n)},m=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,g=/\S/,y=/\s+/,b=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,w=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\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(;a<f;a++)if((e=arguments[a])!=null)for(n in e){r=u[n],i=e[n];if(u===i)continue;l&&i&&(v.isPlainObject(i)||(s=v.isArray(i)))?(s?(s=!1,o=r&&v.isArray(r)?r:[]):o=r&&v.isPlainObject(r)?r:{},u[n]=v.extend(l,o,i)):i!==t&&(u[n]=i)}return u},v.extend({noConflict:function(t){return e.$===v&&(e.$=a),t&&e.jQuery===v&&(e.jQuery=u),v},isReady:!1,readyWait:1,holdReady:function(e){e?v.readyWait++:v.ready(!0)},ready:function(e){if(e===!0?--v.readyWait:v.isReady)return;if(!i.body)return setTimeout(v.ready,1);v.isReady=!0;if(e!==!0&&--v.readyWait>0)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(;s<o;)if(n.apply(e[s++],r)===!1)break}else if(u){for(i in e)if(n.call(e[i],i,e[i])===!1)break}else for(;s<o;)if(n.call(e[s],s,e[s++])===!1)break;return e},trim:d&&!d.call("\ufeff\u00a0")?function(e){return e==null?"":d.call(e)}:function(e){return e==null?"":(e+"").replace(b,"")},makeArray:function(e,t){var n,r=t||[];return e!=null&&(n=v.type(e),e.length==null||n==="string"||n==="function"||n==="regexp"||v.isWindow(e)?f.call(r,e):v.merge(r,e)),r},inArray:function(e,t,n){var r;if(t){if(c)return c.call(t,e,n);r=t.length,n=n?n<0?Math.max(0,r+n):n:0;for(;n<r;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,s=0;if(typeof r=="number")for(;s<r;s++)e[i++]=n[s];else while(n[s]!==t)e[i++]=n[s++];return e.length=i,e},grep:function(e,t,n){var r,i=[],s=0,o=e.length;n=!!n;for(;s<o;s++)r=!!t(e[s],s),n!==r&&i.push(e[s]);return i},map:function(e,n,r){var i,s,o=[],u=0,a=e.length,f=e instanceof v||a!==t&&typeof a=="number"&&(a>0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u<a;u++)i=n(e[u],u,r),i!=null&&(o[o.length]=i);else for(s in e)i=n(e[s],s,r),i!=null&&(o[o.length]=i);return o.concat.apply([],o)},guid:1,proxy:function(e,n){var r,i,s;return typeof n=="string"&&(r=e[n],n=e,e=r),v.isFunction(e)?(i=l.call(arguments,2),s=function(){return e.apply(n,i.concat(l.call(arguments)))},s.guid=e.guid=e.guid||v.guid++,s):t},access:function(e,n,r,i,s,o,u){var a,f=r==null,l=0,c=e.length;if(r&&typeof r=="object"){for(l in r)v.access(e,n,l,r[l],1,o,i);s=1}else if(i!==t){a=u===t&&v.isFunction(i),f&&(a?(a=n,n=function(e,t,n){return a.call(v(e),n)}):(n.call(e,i),n=null));if(n)for(;l<c;l++)n(e[l],r,a?i.call(e[l],l,n(e[l],r)):i,u);s=1}return s?e:f?n.call(e):c?n(e[0],r):o},now:function(){return(new Date).getTime()}}),v.ready.promise=function(t){if(!r){r=v.Deferred();if(i.readyState==="complete")setTimeout(v.ready,1);else if(i.addEventListener)i.addEventListener("DOMContentLoaded",A,!1),e.addEventListener("load",v.ready,!1);else{i.attachEvent("onreadystatechange",A),e.attachEvent("onload",v.ready);var n=!1;try{n=e.frameElement==null&&i.documentElement}catch(s){}n&&n.doScroll&&function o(){if(!v.isReady){try{n.doScroll("left")}catch(e){return setTimeout(o,50)}v.ready()}}()}}return r.promise(t)},v.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(e,t){O["[object "+t+"]"]=t.toLowerCase()}),n=v(i);var M={};v.Callbacks=function(e){e=typeof e=="string"?M[e]||_(e):v.extend({},e);var n,r,i,s,o,u,a=[],f=!e.once&&[],l=function(t){n=e.memory&&t,r=!0,u=s||0,s=0,o=a.length,i=!0;for(;a&&u<o;u++)if(a[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}i=!1,a&&(f?f.length&&l(f.shift()):n?a=[]:c.disable())},c={add:function(){if(a){var t=a.length;(function r(t){v.each(t,function(t,n){var i=v.type(n);i==="function"?(!e.unique||!c.has(n))&&a.push(n):n&&n.length&&i!=="string"&&r(n)})})(arguments),i?o=a.length:n&&(s=t,l(n))}return this},remove:function(){return a&&v.each(arguments,function(e,t){var n;while((n=v.inArray(t,a,n))>-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<r;t++)n[t]&&v.isFunction(n[t].promise)?n[t].promise().done(o(t,f,n)).fail(s.reject).progress(o(t,a,u)):--i}return i||s.resolveWith(f,n),s.promise()}}),v.support=function(){var t,n,r,s,o,u,a,f,l,c,h,p=i.createElement("div");p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",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></: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="<table><tr><td></td><td>t</td></tr></table>",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="<div></div>",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;i<s;i++)delete r[t[i]];if(!(n?B:v.isEmptyObject)(r))return}}if(!n){delete u[a].data;if(!B(u[a]))return}o?v.cleanData([e],!0):v.support.deleteExpando||u!=u.window?delete u[a]:u[a]=null},_data:function(e,t,n){return v.data(e,t,n,!0)},acceptData:function(e){var t=e.nodeName&&v.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),v.fn.extend({data:function(e,n){var r,i,s,o,u,a=this[0],f=0,l=null;if(e===t){if(this.length){l=v.data(a);if(a.nodeType===1&&!v._data(a,"parsedAttrs")){s=a.attributes;for(u=s.length;f<u;f++)o=s[f].name,o.indexOf("data-")||(o=v.camelCase(o.substring(5)),H(a,o,l[o]));v._data(a,"parsedAttrs",!0)}}return l}return typeof e=="object"?this.each(function(){v.data(this,e)}):(r=e.split(".",2),r[1]=r[1]?"."+r[1]:"",i=r[1]+"!",v.access(this,function(n){if(n===t)return l=this.triggerHandler("getData"+i,[r[0]]),l===t&&a&&(l=v.data(a,e),l=H(a,e,l)),l===t&&r[1]?this.data(r[0]):l;r[1]=n,this.each(function(){var t=v(this);t.triggerHandler("setData"+i,r),v.data(this,e,n),t.triggerHandler("changeData"+i,r)})},null,n,arguments.length>1,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.length<r?v.queue(this[0],e):n===t?this:this.each(function(){var t=v.queue(this,e,n);v._queueHooks(this,e),e==="fx"&&t[0]!=="inprogress"&&v.dequeue(this,e)})},dequeue:function(e){return this.each(function(){v.dequeue(this,e)})},delay:function(e,t){return e=v.fx?v.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,s=v.Deferred(),o=this,u=this.length,a=function(){--i||s.resolveWith(o,[o])};typeof e!="string"&&(n=e,e=t),e=e||"fx";while(u--)r=v._data(o[u],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(a));return a(),s.promise(n)}});var j,F,I,q=/[\t\r\n]/g,R=/\r/g,U=/^(?:button|input)$/i,z=/^(?:button|input|object|select|textarea)$/i,W=/^a(?:rea|)$/i,X=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,V=v.support.getSetAttribute;v.fn.extend({attr:function(e,t){return v.access(this,v.attr,e,t,arguments.length>1)},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<r;n++){i=this[n];if(i.nodeType===1)if(!i.className&&t.length===1)i.className=e;else{s=" "+i.className+" ";for(o=0,u=t.length;o<u;o++)s.indexOf(" "+t[o]+" ")<0&&(s+=t[o]+" ");i.className=v.trim(s)}}}return this},removeClass:function(e){var n,r,i,s,o,u,a;if(v.isFunction(e))return this.each(function(t){v(this).removeClass(e.call(this,t,this.className))});if(e&&typeof e=="string"||e===t){n=(e||"").split(y);for(u=0,a=this.length;u<a;u++){i=this[u];if(i.nodeType===1&&i.className){r=(" "+i.className+" ").replace(q," ");for(s=0,o=n.length;s<o;s++)while(r.indexOf(" "+n[s]+" ")>=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<r;n++)if(this[n].nodeType===1&&(" "+this[n].className+" ").replace(q," ").indexOf(t)>=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<u;a++){n=r[a];if((n.selected||a===i)&&(v.support.optDisabled?!n.disabled:n.getAttribute("disabled")===null)&&(!n.parentNode.disabled||!v.nodeName(n.parentNode,"optgroup"))){t=v(n).val();if(s)return t;o.push(t)}}return o},set:function(e,t){var n=v.makeArray(t);return v(e).find("option").each(function(){this.selected=v.inArray(v(this).val(),n)>=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<r.length;o++)i=r[o],i&&(n=v.propFix[i]||i,s=X.test(i),s||v.attr(e,i,""),e.removeAttribute(V?i:n),s&&n in e&&(e[n]=!1))}},attrHooks:{type:{set:function(e,t){if(U.test(e.nodeName)&&e.parentNode)v.error("type property can't be changed");else if(!v.support.radioValue&&t==="radio"&&v.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}},value:{get:function(e,t){return j&&v.nodeName(e,"button")?j.get(e,t):t in e?e.value:null},set:function(e,t,n){if(j&&v.nodeName(e,"button"))return j.set(e,t,n);e.value=t}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,s,o,u=e.nodeType;if(!e||u===3||u===8||u===2)return;return o=u!==1||!v.isXMLDoc(e),o&&(n=v.propFix[n]||n,s=v.propHooks[n]),r!==t?s&&"set"in s&&(i=s.set(e,r,n))!==t?i:e[n]=r:s&&"get"in s&&(i=s.get(e,n))!==null?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):z.test(e.nodeName)||W.test(e.nodeName)&&e.href?0:t}}}}),F={get:function(e,n){var r,i=v.prop(e,n);return i===!0||typeof i!="boolean"&&(r=e.getAttributeNode(n))&&r.nodeValue!==!1?n.toLowerCase():t},set:function(e,t,n){var r;return t===!1?v.removeAttr(e,n):(r=v.propFix[n]||n,r in e&&(e[r]=!0),e.setAttribute(n,n.toLowerCase())),n}},V||(I={name:!0,id:!0,coords:!0},j=v.valHooks.button={get:function(e,n){var r;return r=e.getAttributeNode(n),r&&(I[n]?r.value!=="":r.specified)?r.value:t},set:function(e,t,n){var r=e.getAttributeNode(n);return r||(r=i.createAttribute(n),e.setAttributeNode(r)),r.value=t+""}},v.each(["width","height"],function(e,t){v.attrHooks[t]=v.extend(v.attrHooks[t],{set:function(e,n){if(n==="")return e.setAttribute(t,"auto"),n}})}),v.attrHooks.contenteditable={get:j.get,set:function(e,t,n){t===""&&(t="false"),j.set(e,t,n)}}),v.support.hrefNormalized||v.each(["href","src","width","height"],function(e,n){v.attrHooks[n]=v.extend(v.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return r===null?t:r}})}),v.support.style||(v.attrHooks.style={get:function(e){return e.style.cssText.toLowerCase()||t},set:function(e,t){return e.style.cssText=t+""}}),v.support.optSelected||(v.propHooks.selected=v.extend(v.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),v.support.enctype||(v.propFix.enctype="encoding"),v.support.checkOn||v.each(["radio","checkbox"],function(){v.valHooks[this]={get:function(e){return e.getAttribute("value")===null?"on":e.value}}}),v.each(["radio","checkbox"],function(){v.valHooks[this]=v.extend(v.valHooks[this],{set:function(e,t){if(v.isArray(t))return e.checked=v.inArray(v(e).val(),t)>=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<n.length;f++){l=J.exec(n[f])||[],c=l[1],h=(l[2]||"").split(".").sort(),g=v.event.special[c]||{},c=(s?g.delegateType:g.bindType)||c,g=v.event.special[c]||{},p=v.extend({type:c,origType:l[1],data:i,handler:r,guid:r.guid,selector:s,needsContext:s&&v.expr.match.needsContext.test(s),namespace:h.join(".")},d),m=a[c];if(!m){m=a[c]=[],m.delegateCount=0;if(!g.setup||g.setup.call(e,i,h,u)===!1)e.addEventListener?e.addEventListener(c,u,!1):e.attachEvent&&e.attachEvent("on"+c,u)}g.add&&(g.add.call(e,p),p.handler.guid||(p.handler.guid=r.guid)),s?m.splice(m.delegateCount++,0,p):m.push(p),v.event.global[c]=!0}e=null},global:{},remove:function(e,t,n,r,i){var s,o,u,a,f,l,c,h,p,d,m,g=v.hasData(e)&&v._data(e);if(!g||!(h=g.events))return;t=v.trim(Z(t||"")).split(" ");for(s=0;s<t.length;s++){o=J.exec(t[s])||[],u=a=o[1],f=o[2];if(!u){for(u in h)v.event.remove(e,u+t[s],n,r,!0);continue}p=v.event.special[u]||{},u=(r?p.delegateType:p.bindType)||u,d=h[u]||[],l=d.length,f=f?new RegExp("(^|\\.)"+f.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(c=0;c<d.length;c++)m=d[c],(i||a===m.origType)&&(!n||n.guid===m.guid)&&(!f||f.test(m.namespace))&&(!r||r===m.selector||r==="**"&&m.selector)&&(d.splice(c--,1),m.selector&&d.delegateCount--,p.remove&&p.remove.call(e,m));d.length===0&&l!==d.length&&((!p.teardown||p.teardown.call(e,f,g.handle)===!1)&&v.removeEvent(e,u,g.handle),delete h[u])}v.isEmptyObject(h)&&(delete g.handle,v.removeData(e,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(n,r,s,o){if(!s||s.nodeType!==3&&s.nodeType!==8){var u,a,f,l,c,h,p,d,m,g,y=n.type||n,b=[];if(Y.test(y+v.event.triggered))return;y.indexOf("!")>=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<m.length&&!n.isPropagationStopped();f++)l=m[f][0],n.type=m[f][1],d=(v._data(l,"events")||{})[n.type]&&v._data(l,"handle"),d&&d.apply(l,r),d=h&&l[h],d&&v.acceptData(l)&&d.apply&&d.apply(l,r)===!1&&n.preventDefault();return n.type=y,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(s.ownerDocument,r)===!1)&&(y!=="click"||!v.nodeName(s,"a"))&&v.acceptData(s)&&h&&s[y]&&(y!=="focus"&&y!=="blur"||n.target.offsetWidth!==0)&&!v.isWindow(s)&&(c=s[h],c&&(s[h]=null),v.event.triggered=y,s[y](),v.event.triggered=t,c&&(s[h]=c)),n.result}return},dispatch:function(n){n=v.event.fix(n||e.event);var r,i,s,o,u,a,f,c,h,p,d=(v._data(this,"events")||{})[n.type]||[],m=d.delegateCount,g=l.call(arguments),y=!n.exclusive&&!n.namespace,b=v.event.special[n.type]||{},w=[];g[0]=n,n.delegateTarget=this;if(b.preDispatch&&b.preDispatch.call(this,n)===!1)return;if(m&&(!n.button||n.type!=="click"))for(s=n.target;s!=this;s=s.parentNode||this)if(s.disabled!==!0||n.type!=="click"){u={},f=[];for(r=0;r<m;r++)c=d[r],h=c.selector,u[h]===t&&(u[h]=c.needsContext?v(h,this).index(s)>=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;r<w.length&&!n.isPropagationStopped();r++){a=w[r],n.currentTarget=a.elem;for(i=0;i<a.matches.length&&!n.isImmediatePropagationStopped();i++){c=a.matches[i];if(y||!n.namespace&&!c.namespace||n.namespace_re&&n.namespace_re.test(c.namespace))n.data=c.data,n.handleObj=c,o=((v.event.special[c.origType]||{}).handle||c.handler).apply(a.elem,g),o!==t&&(n.result=o,o===!1&&(n.preventDefault(),n.stopPropagation()))}}return b.postDispatch&&b.postDispatch.call(this,n),n.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return e.which==null&&(e.which=t.charCode!=null?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,s,o,u=n.button,a=n.fromElement;return e.pageX==null&&n.clientX!=null&&(r=e.target.ownerDocument||i,s=r.documentElement,o=r.body,e.pageX=n.clientX+(s&&s.scrollLeft||o&&o.scrollLeft||0)-(s&&s.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(s&&s.scrollTop||o&&o.scrollTop||0)-(s&&s.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&a&&(e.relatedTarget=a===e.target?n.toElement:a),!e.which&&u!==t&&(e.which=u&1?1:u&2?3:u&4?2:0),e}},fix:function(e){if(e[v.expando])return e;var t,n,r=e,s=v.event.fixHooks[e.type]||{},o=s.props?this.props.concat(s.props):this.props;e=v.Event(r);for(t=o.length;t;)n=o[--t],e[n]=r[n];return e.target||(e.target=r.srcElement||i),e.target.nodeType===3&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,r):e},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(e,t,n){v.isWindow(this)&&(this.onbeforeunload=n)},teardown:function(e,t){this.onbeforeunload===t&&(this.onbeforeunload=null)}}},simulate:function(e,t,n,r){var i=v.extend(new v.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?v.event.trigger(i,null,t):v.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},v.event.handle=v.event.dispatch,v.removeEvent=i.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]=="undefined"&&(e[r]=null),e.detachEvent(r,n))},v.Event=function(e,t){if(!(this instanceof v.Event))return new v.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?tt:et):this.type=e,t&&v.extend(this,t),this.timeStamp=e&&e.timeStamp||v.now(),this[v.expando]=!0},v.Event.prototype={preventDefault:function(){this.isDefaultPrevented=tt;var e=this.originalEvent;if(!e)return;e.preventDefault?e.preventDefault():e.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=tt;var e=this.originalEvent;if(!e)return;e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=tt,this.stopPropagation()},isDefaultPrevented:et,isPropagationStopped:et,isImmediatePropagationStopped:et},v.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){v.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,s=e.handleObj,o=s.selector;if(!i||i!==r&&!v.contains(r,i))e.type=s.origType,n=s.handler.apply(this,arguments),e.type=t;return n}}}),v.support.submitBubbles||(v.event.special.submit={setup:function(){if(v.nodeName(this,"form"))return!1;v.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=v.nodeName(n,"input")||v.nodeName(n,"button")?n.form:t;r&&!v._data(r,"_submit_attached")&&(v.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),v._data(r,"_submit_attached",!0))})},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&v.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){if(v.nodeName(this,"form"))return!1;v.event.remove(this,"._submit")}}),v.support.changeBubbles||(v.event.special.change={setup:function(){if($.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")v.event.add(this,"propertychange._change",function(e){e.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),v.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),v.event.simulate("change",this,e,!0)});return!1}v.event.add(this,"beforeactivate._change",function(e){var t=e.target;$.test(t.nodeName)&&!v._data(t,"_change_attached")&&(v.event.add(t,"change._change",function(e){this.parentNode&&!e.isSimulated&&!e.isTrigger&&v.event.simulate("change",this.parentNode,e,!0)}),v._data(t,"_change_attached",!0))})},handle:function(e){var t=e.target;if(this!==t||e.isSimulated||e.isTrigger||t.type!=="radio"&&t.type!=="checkbox")return e.handleObj.handler.apply(this,arguments)},teardown:function(){return v.event.remove(this,"._change"),!$.test(this.nodeName)}}),v.support.focusinBubbles||v.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){v.event.simulate(t,e.target,v.event.fix(e),!0)};v.event.special[t]={setup:function(){n++===0&&i.addEventListener(e,r,!0)},teardown:function(){--n===0&&i.removeEventListener(e,r,!0)}}}),v.fn.extend({on:function(e,n,r,i,s){var o,u;if(typeof e=="object"){typeof n!="string"&&(r=r||n,n=t);for(u in e)this.on(u,n,r,e[u],s);return this}r==null&&i==null?(i=n,r=n=t):i==null&&(typeof n=="string"?(i=r,r=t):(i=r,r=n,n=t));if(i===!1)i=et;else if(!i)return this;return s===1&&(o=i,i=function(e){return v().off(e),o.apply(this,arguments)},i.guid=o.guid||(o.guid=v.guid++)),this.each(function(){v.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,s;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,v(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if(typeof e=="object"){for(s in e)this.off(s,n,e[s]);return this}if(n===!1||typeof n=="function")r=n,n=t;return r===!1&&(r=et),this.each(function(){v.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},live:function(e,t,n){return v(this.context).on(e,this.selector,t,n),this},die:function(e,t){return v(this.context).off(e,this.selector||"**",t),this},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){v.event.trigger(e,t,this)})},triggerHandler:function(e,t){if(this[0])return v.event.trigger(e,t,this[0],!0)},toggle:function(e){var t=arguments,n=e.guid||v.guid++,r=0,i=function(n){var i=(v._data(this,"lastToggle"+e.guid)||0)%r;return v._data(this,"lastToggle"+e.guid,i+1),n.preventDefault(),t[i].apply(this,arguments)||!1};i.guid=n;while(r<t.length)t[r++].guid=n;return this.click(i)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),v.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){v.fn[t]=function(e,n){return n==null&&(n=e,e=null),arguments.length>0?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<a;u++)if(s=e[u])if(!n||n(s,r,i))o.push(s),f&&t.push(u);return o}function ct(e,t,n,r,i,s){return r&&!r[d]&&(r=ct(r)),i&&!i[d]&&(i=ct(i,s)),N(function(s,o,u,a){var f,l,c,h=[],p=[],d=o.length,v=s||dt(t||"*",u.nodeType?[u]:u,[]),m=e&&(s||!t)?lt(v,h,e,u,a):v,g=n?i||(s?e:d||r)?[]:o:m;n&&n(m,g,u,a);if(r){f=lt(g,p),r(f,[],u,a),l=f.length;while(l--)if(c=f[l])g[p[l]]=!(m[p[l]]=c)}if(s){if(i||e){if(i){f=[],l=g.length;while(l--)(c=g[l])&&f.push(m[l]=c);i(null,g=[],f,a)}l=g.length;while(l--)(c=g[l])&&(f=i?T.call(s,c):h[l])>-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(;a<s;a++)if(n=i.relative[e[a].type])h=[at(ft(h),n)];else{n=i.filter[e[a].type].apply(null,e[a].matches);if(n[d]){r=++a;for(;r<s;r++)if(i.relative[e[r].type])break;return ct(a>1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a<r&&ht(e.slice(a,r)),r<s&&ht(e=e.slice(r)),r<s&&e.join(""))}h.push(n)}return ft(h)}function pt(e,t){var r=t.length>0,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(;r<i;r++)nt(e,t[r],n);return n}function vt(e,t,n,r,s){var o,u,f,l,c,h=ut(e),p=h.length;if(!r&&h.length===1){u=h[0]=h[0].slice(0);if(u.length>2&&(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(;t<n;t++)if(this[t]===e)return t;return-1},N=function(e,t){return e[d]=t==null||t,e},C=function(){var e={},t=[];return N(function(n,r){return t.push(n)>i.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="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!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="<a name='"+d+"'></a><div name='"+d+"'></div>",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<t;n+=2)e.push(n);return e}),odd:st(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:st(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},f=y.compareDocumentPosition?function(e,t){return e===t?(l=!0,0):(!e.compareDocumentPosition||!t.compareDocumentPosition?e.compareDocumentPosition:e.compareDocumentPosition(t)&4)?-1:1}:function(e,t){if(e===t)return l=!0,0;if(e.sourceIndex&&t.sourceIndex)return e.sourceIndex-t.sourceIndex;var n,r,i=[],s=[],o=e.parentNode,u=t.parentNode,a=o;if(o===u)return ot(e,t);if(!o)return-1;if(!u)return 1;while(a)i.unshift(a),a=a.parentNode;a=u;while(a)s.unshift(a),a=a.parentNode;n=i.length,r=s.length;for(var f=0;f<n&&f<r;f++)if(i[f]!==s[f])return ot(i[f],s[f]);return f===n?ot(e,s[f],-1):ot(i[f],t,1)},[0,0].sort(f),h=!l,nt.uniqueSort=function(e){var t,n=[],r=1,i=0;l=h,e.sort(f);if(l){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e},nt.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},a=nt.compile=function(e,t){var n,r=[],i=[],s=A[d][e+" "];if(!s){t||(t=ut(e)),n=t.length;while(n--)s=ht(t[n]),s[d]?r.push(s):i.push(s);s=A(e,pt(i,r))}return s},g.querySelectorAll&&function(){var e,t=vt,n=/'|\\/g,r=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,i=[":focus"],s=[":active"],u=y.matchesSelector||y.mozMatchesSelector||y.webkitMatchesSelector||y.oMatchesSelector||y.msMatchesSelector;K(function(e){e.innerHTML="<select><option selected=''></option></select>",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="<p test=''></p>",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="<input type='hidden'/>",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;t<n;t++)if(v.contains(u[t],this))return!0});o=this.pushStack("","find",e);for(t=0,n=this.length;t<n;t++){r=o.length,v.find(e,this[t],o);if(t>0)for(i=r;i<o.length;i++)for(s=0;s<r;s++)if(o[s]===o[i]){o.splice(i--,1);break}}return o},has:function(e){var t,n=v(e,this),r=n.length;return this.filter(function(){for(t=0;t<r;t++)if(v.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1),"not",e)},filter:function(e){return this.pushStack(ft(this,e,!0),"filter",e)},is:function(e){return!!e&&(typeof e=="string"?st.test(e)?v(e,this.context).index(this[0])>=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<i;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&n.nodeType!==11){if(o?o.index(n)>-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=/<tbody/i,gt=/<|&#?\w+;/,yt=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,wt=new RegExp("<(?:"+ct+")[\\s/>]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,Nt={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_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<div>","</div>"]),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></$2>");try{for(;r<i;r++)n=this[r]||{},n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),n.innerHTML=e);n=0}catch(s){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){return ut(this[0])?this.length?this.pushStack(v(v.isFunction(e)?e():e),"replaceWith",e):this:v.isFunction(e)?this.each(function(t){var n=v(this),r=n.html();n.replaceWith(e.call(this,t,r))}):(typeof e!="string"&&(e=v(e).detach()),this.each(function(){var t=this.nextSibling,n=this.parentNode;v(this).remove(),t?v(t).before(e):v(n).append(e)}))},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=[].concat.apply([],e);var i,s,o,u,a=0,f=e[0],l=[],c=this.length;if(!v.support.checkClone&&c>1&&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;a<c;a++)r.call(n&&v.nodeName(this[a],"table")?Lt(this[a],"tbody"):this[a],a===u?o:v.clone(o,!0,!0))}o=s=null,l.length&&v.each(l,function(e,t){t.src?v.ajax?v.ajax({url:t.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):v.error("no ajax"):v.globalEval((t.text||t.textContent||t.innerHTML||"").replace(Tt,"")),t.parentNode&&t.parentNode.removeChild(t)})}return this}}),v.buildFragment=function(e,n,r){var s,o,u,a=e[0];return n=n||i,n=!n.nodeType&&n[0]||n,n=n.ownerDocument||n,e.length===1&&typeof a=="string"&&a.length<512&&n===i&&a.charAt(0)==="<"&&!bt.test(a)&&(v.support.checkClone||!St.test(a))&&(v.support.html5Clone||!wt.test(a))&&(o=!0,s=v.fragments[a],u=s!==t),s||(s=n.createDocumentFragment(),v.clean(e,n,s,r),o&&(v.fragments[a]=u&&s)),{fragment:s,cacheable:o}},v.fragments={},v.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){v.fn[e]=function(n){var r,i=0,s=[],o=v(n),u=o.length,a=this.length===1&&this[0].parentNode;if((a==null||a&&a.nodeType===11&&a.childNodes.length===1)&&u===1)return o[t](this[0]),this;for(;i<u;i++)r=(i>0?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></$2>"),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]==="<table>"&&!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\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/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("<div>").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<i;r++)n=e[r],Vn[n]=Vn[n]||[],Vn[n].unshift(t)},prefilter:function(e,t){t?Xn.unshift(e):Xn.push(e)}}),v.Tween=Yn,Yn.prototype={constructor:Yn,init:function(e,t,n,r,i,s){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=s||(v.cssNumber[n]?"":"px")},cur:function(){var e=Yn.propHooks[this.prop];return e&&e.get?e.get(this):Yn.propHooks._default.get(this)},run:function(e){var t,n=Yn.propHooks[this.prop];return this.options.duration?this.pos=t=v.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Yn.propHooks._default.set(this),this}},Yn.prototype.init.prototype=Yn.prototype,Yn.propHooks={_default:{get:function(e){var t;return e.elem[e.prop]==null||!!e.elem.style&&e.elem.style[e.prop]!=null?(t=v.css(e.elem,e.prop,!1,""),!t||t==="auto"?0:t):e.elem[e.prop]},set:function(e){v.fx.step[e.prop]?v.fx.step[e.prop](e):e.elem.style&&(e.elem.style[v.cssProps[e.prop]]!=null||v.cssHooks[e.prop])?v.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Yn.propHooks.scrollTop=Yn.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},v.each(["toggle","show","hide"],function(e,t){var n=v.fn[t];v.fn[t]=function(r,i,s){return r==null||typeof r=="boolean"||!e&&v.isFunction(r)&&v.isFunction(i)?n.apply(this,arguments):this.animate(Zn(t,!0),r,i,s)}}),v.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Gt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=v.isEmptyObject(e),s=v.speed(t,n,r),o=function(){var t=Kn(this,v.extend({},e),s);i&&t.stop(!0)};return i||s.queue===!1?this.each(o):this.queue(s.queue,o)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return typeof e!="string"&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=e!=null&&e+"queueHooks",s=v.timers,o=v._data(this);if(n)o[n]&&o[n].stop&&i(o[n]);else for(n in o)o[n]&&o[n].stop&&Wn.test(n)&&i(o[n]);for(n=s.length;n--;)s[n].elem===this&&(e==null||s[n].queue===e)&&(s[n].anim.stop(r),t=!1,s.splice(n,1));(t||!r)&&v.dequeue(this,e)})}}),v.each({slideDown:Zn("show"),slideUp:Zn("hide"),slideToggle:Zn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){v.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),v.speed=function(e,t,n){var r=e&&typeof e=="object"?v.extend({},e):{complete:n||!n&&t||v.isFunction(e)&&e,duration:e,easing:n&&t||t&&!v.isFunction(t)&&t};r.duration=v.fx.off?0:typeof r.duration=="number"?r.duration:r.duration in v.fx.speeds?v.fx.speeds[r.duration]:v.fx.speeds._default;if(r.queue==null||r.queue===!0)r.queue="fx";return r.old=r.complete,r.complete=function(){v.isFunction(r.old)&&r.old.call(this),r.queue&&v.dequeue(this,r.queue)},r},v.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},v.timers=[],v.fx=Yn.prototype.init,v.fx.tick=function(){var e,n=v.timers,r=0;qn=v.now();for(;r<n.length;r++)e=n[r],!e()&&n[r]===e&&n.splice(r--,1);n.length||v.fx.stop(),qn=t},v.fx.timer=function(e){e()&&v.timers.push(e)&&!Rn&&(Rn=setInterval(v.fx.tick,v.fx.interval))},v.fx.interval=13,v.fx.stop=function(){clearInterval(Rn),Rn=null},v.fx.speeds={slow:600,fast:200,_default:400},v.fx.step={},v.expr&&v.expr.filters&&(v.expr.filters.animated=function(e){return v.grep(v.timers,function(t){return e===t.elem}).length});var er=/^(?:body|html)$/i;v.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){v.offset.setOffset(this,e,t)});var n,r,i,s,o,u,a,f={top:0,left:0},l=this[0],c=l&&l.ownerDocument;if(!c)return;return(r=c.body)===l?v.offset.bodyOffset(l):(n=c.documentElement,v.contains(n,l)?(typeof l.getBoundingClientRect!="undefined"&&(f=l.getBoundingClientRect()),i=tr(c),s=n.clientTop||r.clientTop||0,o=n.clientLeft||r.clientLeft||0,u=i.pageYOffset||n.scrollTop,a=i.pageXOffset||n.scrollLeft,{top:f.top+u-s,left:f.left+a-o}):f)},v.offset={bodyOffset:function(e){var t=e.offsetTop,n=e.offsetLeft;return v.support.doesNotIncludeMarginInBodyOffset&&(t+=parseFloat(v.css(e,"marginTop"))||0,n+=parseFloat(v.css(e,"marginLeft"))||0),{top:t,left:n}},setOffset:function(e,t,n){var r=v.css(e,"position");r==="static"&&(e.style.position="relative");var i=v(e),s=i.offset(),o=v.css(e,"top"),u=v.css(e,"left"),a=(r==="absolute"||r==="fixed")&&v.inArray("auto",[o,u])>-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 = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
+ this.dots = $('<span></span>').appendTo(this.title);
+ this.status = $('<p style="display: none"></p>').appendTo(this.out);
+ this.output = $('<ul class="search"/>').appendTo(this.out);
+
+ $('#search-progress').text(_('Preparing search...'));
+ this.startPulse();
+
+ // index already loaded, the browser was quick!
+ if (this.hasIndex())
+ this.query(query);
+ else
+ this.deferQuery(query);
+ },
+
+ /**
+ * execute search (requires search index to be loaded)
+ */
+ query : function(query) {
+ var i;
+ var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
+
+ // stem the searchterms and add them to the correct list
+ var stemmer = new Stemmer();
+ var searchterms = [];
+ var excluded = [];
+ var hlterms = [];
+ var tmp = query.split(/\s+/);
+ var objectterms = [];
+ for (i = 0; i < tmp.length; i++) {
+ if (tmp[i] !== "") {
+ objectterms.push(tmp[i].toLowerCase());
+ }
+
+ if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
+ tmp[i] === "") {
+ // skip this "word"
+ continue;
+ }
+ // stem the word
+ var word = stemmer.stemWord(tmp[i].toLowerCase());
+ var toAppend;
+ // select the correct list
+ if (word[0] == '-') {
+ toAppend = excluded;
+ word = word.substr(1);
+ }
+ else {
+ toAppend = searchterms;
+ hlterms.push(tmp[i].toLowerCase());
+ }
+ // only add if not already in the list
+ if (!$u.contains(toAppend, word))
+ toAppend.push(word);
+ }
+ var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
+
+ // console.debug('SEARCH: searching for:');
+ // console.info('required: ', searchterms);
+ // console.info('excluded: ', excluded);
+
+ // prepare search
+ var terms = this._index.terms;
+ var titleterms = this._index.titleterms;
+
+ // array of [filename, title, anchor, descr, score]
+ var results = [];
+ $('#search-progress').empty();
+
+ // lookup as object
+ for (i = 0; i < objectterms.length; i++) {
+ var others = [].concat(objectterms.slice(0, i),
+ objectterms.slice(i+1, objectterms.length));
+ results = results.concat(this.performObjectSearch(objectterms[i], others));
+ }
+
+ // lookup as search terms in fulltext
+ results = results.concat(this.performTermsSearch(searchterms, excluded, terms, Scorer.term))
+ .concat(this.performTermsSearch(searchterms, excluded, titleterms, Scorer.title));
+
+ // let the scorer override scores with a custom scoring function
+ if (Scorer.score) {
+ for (i = 0; i < results.length; i++)
+ results[i][4] = Scorer.score(results[i]);
+ }
+
+ // now sort the results by score (in opposite order of appearance, since the
+ // display function below uses pop() to retrieve items) and then
+ // alphabetically
+ results.sort(function(a, b) {
+ var left = a[4];
+ var right = b[4];
+ if (left > right) {
+ return 1;
+ } else if (left < right) {
+ return -1;
+ } else {
+ // same score: sort alphabetically
+ left = a[1].toLowerCase();
+ right = b[1].toLowerCase();
+ return (left > right) ? -1 : ((left < right) ? 1 : 0);
+ }
+ });
+
+ // for debugging
+ //Search.lastresults = results.slice(); // a copy
+ //console.info('search results:', Search.lastresults);
+
+ // print the results
+ var resultCount = results.length;
+ function displayNextItem() {
+ // results left, load the summary and display it
+ if (results.length) {
+ var item = results.pop();
+ var listItem = $('<li style="display:none"></li>');
+ if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
+ // dirhtml builder
+ var dirname = item[0] + '/';
+ if (dirname.match(/\/index\/$/)) {
+ dirname = dirname.substring(0, dirname.length-6);
+ } else if (dirname == 'index/') {
+ dirname = '';
+ }
+ listItem.append($('<a/>').attr('href',
+ DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
+ highlightstring + item[2]).html(item[1]));
+ } else {
+ // normal html builders
+ listItem.append($('<a/>').attr('href',
+ item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
+ highlightstring + item[2]).html(item[1]));
+ }
+ if (item[3]) {
+ listItem.append($('<span> (' + item[3] + ')</span>'));
+ Search.output.append(listItem);
+ listItem.slideDown(5, function() {
+ displayNextItem();
+ });
+ } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
+ $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[0] + '.txt',
+ dataType: "text",
+ complete: function(jqxhr, textstatus) {
+ var data = jqxhr.responseText;
+ if (data !== '') {
+ listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
+ }
+ Search.output.append(listItem);
+ listItem.slideDown(5, function() {
+ displayNextItem();
+ });
+ }});
+ } else {
+ // no source available, just display title
+ Search.output.append(listItem);
+ listItem.slideDown(5, function() {
+ displayNextItem();
+ });
+ }
+ }
+ // search finished, update title and status message
+ else {
+ Search.stopPulse();
+ Search.title.text(_('Search Results'));
+ if (!resultCount)
+ Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
+ else
+ Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
+ Search.status.fadeIn(500);
+ }
+ }
+ displayNextItem();
+ },
+
+ /**
+ * search for object names
+ */
+ performObjectSearch : function(object, otherterms) {
+ var filenames = this._index.filenames;
+ var objects = this._index.objects;
+ var objnames = this._index.objnames;
+ var titles = this._index.titles;
+
+ var i;
+ var results = [];
+
+ for (var prefix in objects) {
+ for (var name in objects[prefix]) {
+ var fullname = (prefix ? prefix + '.' : '') + name;
+ if (fullname.toLowerCase().indexOf(object) > -1) {
+ var score = 0;
+ var parts = fullname.split('.');
+ // check for different match types: exact matches of full name or
+ // "last name" (i.e. last dotted part)
+ if (fullname == object || parts[parts.length - 1] == object) {
+ score += Scorer.objNameMatch;
+ // matches in last name
+ } else if (parts[parts.length - 1].indexOf(object) > -1) {
+ score += Scorer.objPartialMatch;
+ }
+ var match = objects[prefix][name];
+ var objname = objnames[match[1]][2];
+ var title = titles[match[0]];
+ // If more than one term searched for, we require other words to be
+ // found in the name/title/description
+ if (otherterms.length > 0) {
+ var haystack = (prefix + ' ' + name + ' ' +
+ objname + ' ' + title).toLowerCase();
+ var allfound = true;
+ for (i = 0; i < otherterms.length; i++) {
+ if (haystack.indexOf(otherterms[i]) == -1) {
+ allfound = false;
+ break;
+ }
+ }
+ if (!allfound) {
+ continue;
+ }
+ }
+ var descr = objname + _(', in ') + title;
+
+ var anchor = match[3];
+ if (anchor === '')
+ anchor = fullname;
+ else if (anchor == '-')
+ anchor = objnames[match[1]][1] + '-' + fullname;
+ // add custom score for some objects according to scorer
+ if (Scorer.objPrio.hasOwnProperty(match[2])) {
+ score += Scorer.objPrio[match[2]];
+ } else {
+ score += Scorer.objPrioDefault;
+ }
+ results.push([filenames[match[0]], fullname, '#'+anchor, descr, score]);
+ }
+ }
+ }
+
+ return results;
+ },
+
+ /**
+ * search for full-text terms in the index
+ */
+ performTermsSearch : function(searchterms, excluded, terms, score) {
+ var filenames = this._index.filenames;
+ var titles = this._index.titles;
+
+ var i, j, file, files;
+ var fileMap = {};
+ var results = [];
+
+ // perform the search on the required terms
+ for (i = 0; i < searchterms.length; i++) {
+ var word = searchterms[i];
+ // no match but word was a required one
+ if ((files = terms[word]) === undefined)
+ break;
+ if (files.length === undefined) {
+ files = [files];
+ }
+ // create the mapping
+ for (j = 0; j < files.length; j++) {
+ file = files[j];
+ if (file in fileMap)
+ fileMap[file].push(word);
+ else
+ fileMap[file] = [word];
+ }
+ }
+
+ // now check if the files don't contain excluded terms
+ for (file in fileMap) {
+ var valid = true;
+
+ // check if all requirements are matched
+ if (fileMap[file].length != searchterms.length)
+ continue;
+
+ // ensure that none of the excluded terms is in the search result
+ for (i = 0; i < excluded.length; i++) {
+ if (terms[excluded[i]] == file ||
+ $u.contains(terms[excluded[i]] || [], file)) {
+ valid = false;
+ break;
+ }
+ }
+
+ // if we have still a valid result we can add it to the result list
+ if (valid) {
+ results.push([filenames[file], titles[file], '', null, score]);
+ }
+ }
+ return results;
+ },
+
+ /**
+ * helper function to return a node containing the
+ * search summary for a given text. keywords is a list
+ * of stemmed words, hlwords is the list of normal, unstemmed
+ * words. the first one is used to find the occurance, the
+ * latter for highlighting it.
+ */
+ makeSearchSummary : function(text, keywords, hlwords) {
+ var textLower = text.toLowerCase();
+ var start = 0;
+ $.each(keywords, function() {
+ var i = textLower.indexOf(this.toLowerCase());
+ if (i > -1)
+ start = i;
+ });
+ start = Math.max(start - 120, 0);
+ var excerpt = ((start > 0) ? '...' : '') +
+ $.trim(text.substr(start, 240)) +
+ ((start + 240 - text.length) ? '...' : '');
+ var rv = $('<div class="context"></div>').text(excerpt);
+ $.each(hlwords, function() {
+ rv = rv.highlightText(this, 'highlighted');
+ });
+ return rv;
+ }
+};
+
+$(document).ready(function() {
+ Search.init();
+}); \ No newline at end of file
diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js
new file mode 100644
index 000000000..874a890a3
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js
@@ -0,0 +1,159 @@
+/*
+ * sidebar.js
+ * ~~~~~~~~~~
+ *
+ * This script makes the Sphinx sidebar collapsible.
+ *
+ * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds
+ * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton
+ * used to collapse and expand the sidebar.
+ *
+ * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden
+ * and the width of the sidebar and the margin-left of the document
+ * are decreased. When the sidebar is expanded the opposite happens.
+ * This script saves a per-browser/per-session cookie used to
+ * remember the position of the sidebar among the pages.
+ * Once the browser is closed the cookie is deleted and the position
+ * reset to the default (expanded).
+ *
+ * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+$(function() {
+
+
+
+
+
+
+
+
+ // global elements used by the functions.
+ // the 'sidebarbutton' element is defined as global after its
+ // creation, in the add_sidebar_button function
+ var bodywrapper = $('.bodywrapper');
+ var sidebar = $('.sphinxsidebar');
+ var sidebarwrapper = $('.sphinxsidebarwrapper');
+
+ // for some reason, the document has no sidebar; do not run into errors
+ if (!sidebar.length) return;
+
+ // original margin-left of the bodywrapper and width of the sidebar
+ // with the sidebar expanded
+ var bw_margin_expanded = bodywrapper.css('margin-left');
+ var ssb_width_expanded = sidebar.width();
+
+ // margin-left of the bodywrapper and width of the sidebar
+ // with the sidebar collapsed
+ var bw_margin_collapsed = '.8em';
+ var ssb_width_collapsed = '.8em';
+
+ // colors used by the current theme
+ var dark_color = $('.related').css('background-color');
+ var light_color = $('.document').css('background-color');
+
+ function sidebar_is_collapsed() {
+ return sidebarwrapper.is(':not(:visible)');
+ }
+
+ function toggle_sidebar() {
+ if (sidebar_is_collapsed())
+ expand_sidebar();
+ else
+ collapse_sidebar();
+ }
+
+ function collapse_sidebar() {
+ sidebarwrapper.hide();
+ sidebar.css('width', ssb_width_collapsed);
+ bodywrapper.css('margin-left', bw_margin_collapsed);
+ sidebarbutton.css({
+ 'margin-left': '0',
+ 'height': bodywrapper.height()
+ });
+ sidebarbutton.find('span').text('»');
+ sidebarbutton.attr('title', _('Expand sidebar'));
+ document.cookie = 'sidebar=collapsed';
+ }
+
+ function expand_sidebar() {
+ bodywrapper.css('margin-left', bw_margin_expanded);
+ sidebar.css('width', ssb_width_expanded);
+ sidebarwrapper.show();
+ sidebarbutton.css({
+ 'margin-left': ssb_width_expanded-12,
+ 'height': bodywrapper.height()
+ });
+ sidebarbutton.find('span').text('«');
+ sidebarbutton.attr('title', _('Collapse sidebar'));
+ document.cookie = 'sidebar=expanded';
+ }
+
+ function add_sidebar_button() {
+ sidebarwrapper.css({
+ 'float': 'left',
+ 'margin-right': '0',
+ 'width': ssb_width_expanded - 28
+ });
+ // create the button
+ sidebar.append(
+ '<div id="sidebarbutton"><span>&laquo;</span></div>'
+ );
+ var sidebarbutton = $('#sidebarbutton');
+ light_color = sidebarbutton.css('background-color');
+ // find the height of the viewport to center the '<<' in the page
+ var viewport_height;
+ if (window.innerHeight)
+ viewport_height = window.innerHeight;
+ else
+ viewport_height = $(window).height();
+ sidebarbutton.find('span').css({
+ 'display': 'block',
+ 'margin-top': (viewport_height - sidebar.position().top - 20) / 2
+ });
+
+ sidebarbutton.click(toggle_sidebar);
+ sidebarbutton.attr('title', _('Collapse sidebar'));
+ sidebarbutton.css({
+ 'color': '#FFFFFF',
+ 'border-left': '1px solid ' + dark_color,
+ 'font-size': '1.2em',
+ 'cursor': 'pointer',
+ 'height': bodywrapper.height(),
+ 'padding-top': '1px',
+ 'margin-left': ssb_width_expanded - 12
+ });
+
+ sidebarbutton.hover(
+ function () {
+ $(this).css('background-color', dark_color);
+ },
+ function () {
+ $(this).css('background-color', light_color);
+ }
+ );
+ }
+
+ function set_position_from_cookie() {
+ if (!document.cookie)
+ return;
+ var items = document.cookie.split(';');
+ for(var k=0; k<items.length; k++) {
+ var key_val = items[k].split('=');
+ var key = key_val[0].replace(/ /, ""); // strip leading spaces
+ if (key == 'sidebar') {
+ var value = key_val[1];
+ if ((value == 'collapsed') && (!sidebar_is_collapsed()))
+ collapse_sidebar();
+ else if ((value == 'expanded') && (sidebar_is_collapsed()))
+ expand_sidebar();
+ }
+ }
+ }
+
+ add_sidebar_button();
+ var sidebarbutton = $('#sidebarbutton');
+ set_position_from_cookie();
+}); \ No newline at end of file
diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js
new file mode 100644
index 000000000..5b55f32be
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js
@@ -0,0 +1,31 @@
+// Underscore.js 1.3.1
+// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the MIT license.
+// Portions of Underscore are inspired or borrowed from Prototype,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore
+(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
+c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
+h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
+b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
+null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
+function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
+e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
+function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
+return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
+c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
+b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
+return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
+d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
+var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
+c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
+a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
+b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
+1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
+b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
+b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),
+function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
+u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
+function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
+true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js
new file mode 100644
index 000000000..19fcda564
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js
@@ -0,0 +1,808 @@
+/*
+ * websupport.js
+ * ~~~~~~~~~~~~~
+ *
+ * sphinx.websupport utilties for all documentation.
+ *
+ * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+(function($) {
+ $.fn.autogrow = function() {
+ return this.each(function() {
+ var textarea = this;
+
+ $.fn.autogrow.resize(textarea);
+
+ $(textarea)
+ .focus(function() {
+ textarea.interval = setInterval(function() {
+ $.fn.autogrow.resize(textarea);
+ }, 500);
+ })
+ .blur(function() {
+ clearInterval(textarea.interval);
+ });
+ });
+ };
+
+ $.fn.autogrow.resize = function(textarea) {
+ var lineHeight = parseInt($(textarea).css('line-height'), 10);
+ var lines = textarea.value.split('\n');
+ var columns = textarea.cols;
+ var lineCount = 0;
+ $.each(lines, function() {
+ lineCount += Math.ceil(this.length / columns) || 1;
+ });
+ var height = lineHeight * (lineCount + 1);
+ $(textarea).css('height', height);
+ };
+})(jQuery);
+
+(function($) {
+ var comp, by;
+
+ function init() {
+ initEvents();
+ initComparator();
+ }
+
+ function initEvents() {
+ $('a.comment-close').live("click", function(event) {
+ event.preventDefault();
+ hide($(this).attr('id').substring(2));
+ });
+ $('a.vote').live("click", function(event) {
+ event.preventDefault();
+ handleVote($(this));
+ });
+ $('a.reply').live("click", function(event) {
+ event.preventDefault();
+ openReply($(this).attr('id').substring(2));
+ });
+ $('a.close-reply').live("click", function(event) {
+ event.preventDefault();
+ closeReply($(this).attr('id').substring(2));
+ });
+ $('a.sort-option').live("click", function(event) {
+ event.preventDefault();
+ handleReSort($(this));
+ });
+ $('a.show-proposal').live("click", function(event) {
+ event.preventDefault();
+ showProposal($(this).attr('id').substring(2));
+ });
+ $('a.hide-proposal').live("click", function(event) {
+ event.preventDefault();
+ hideProposal($(this).attr('id').substring(2));
+ });
+ $('a.show-propose-change').live("click", function(event) {
+ event.preventDefault();
+ showProposeChange($(this).attr('id').substring(2));
+ });
+ $('a.hide-propose-change').live("click", function(event) {
+ event.preventDefault();
+ hideProposeChange($(this).attr('id').substring(2));
+ });
+ $('a.accept-comment').live("click", function(event) {
+ event.preventDefault();
+ acceptComment($(this).attr('id').substring(2));
+ });
+ $('a.delete-comment').live("click", function(event) {
+ event.preventDefault();
+ deleteComment($(this).attr('id').substring(2));
+ });
+ $('a.comment-markup').live("click", function(event) {
+ event.preventDefault();
+ toggleCommentMarkupBox($(this).attr('id').substring(2));
+ });
+ }
+
+ /**
+ * Set comp, which is a comparator function used for sorting and
+ * inserting comments into the list.
+ */
+ function setComparator() {
+ // If the first three letters are "asc", sort in ascending order
+ // and remove the prefix.
+ if (by.substring(0,3) == 'asc') {
+ var i = by.substring(3);
+ comp = function(a, b) { return a[i] - b[i]; };
+ } else {
+ // Otherwise sort in descending order.
+ comp = function(a, b) { return b[by] - a[by]; };
+ }
+
+ // Reset link styles and format the selected sort option.
+ $('a.sel').attr('href', '#').removeClass('sel');
+ $('a.by' + by).removeAttr('href').addClass('sel');
+ }
+
+ /**
+ * Create a comp function. If the user has preferences stored in
+ * the sortBy cookie, use those, otherwise use the default.
+ */
+ function initComparator() {
+ by = 'rating'; // Default to sort by rating.
+ // If the sortBy cookie is set, use that instead.
+ if (document.cookie.length > 0) {
+ var start = document.cookie.indexOf('sortBy=');
+ if (start != -1) {
+ start = start + 7;
+ var end = document.cookie.indexOf(";", start);
+ if (end == -1) {
+ end = document.cookie.length;
+ by = unescape(document.cookie.substring(start, end));
+ }
+ }
+ }
+ setComparator();
+ }
+
+ /**
+ * Show a comment div.
+ */
+ function show(id) {
+ $('#ao' + id).hide();
+ $('#ah' + id).show();
+ var context = $.extend({id: id}, opts);
+ var popup = $(renderTemplate(popupTemplate, context)).hide();
+ popup.find('textarea[name="proposal"]').hide();
+ popup.find('a.by' + by).addClass('sel');
+ var form = popup.find('#cf' + id);
+ form.submit(function(event) {
+ event.preventDefault();
+ addComment(form);
+ });
+ $('#s' + id).after(popup);
+ popup.slideDown('fast', function() {
+ getComments(id);
+ });
+ }
+
+ /**
+ * Hide a comment div.
+ */
+ function hide(id) {
+ $('#ah' + id).hide();
+ $('#ao' + id).show();
+ var div = $('#sc' + id);
+ div.slideUp('fast', function() {
+ div.remove();
+ });
+ }
+
+ /**
+ * Perform an ajax request to get comments for a node
+ * and insert the comments into the comments tree.
+ */
+ function getComments(id) {
+ $.ajax({
+ type: 'GET',
+ url: opts.getCommentsURL,
+ data: {node: id},
+ success: function(data, textStatus, request) {
+ var ul = $('#cl' + id);
+ var speed = 100;
+ $('#cf' + id)
+ .find('textarea[name="proposal"]')
+ .data('source', data.source);
+
+ if (data.comments.length === 0) {
+ ul.html('<li>No comments yet.</li>');
+ ul.data('empty', true);
+ } else {
+ // If there are comments, sort them and put them in the list.
+ var comments = sortComments(data.comments);
+ speed = data.comments.length * 100;
+ appendComments(comments, ul);
+ ul.data('empty', false);
+ }
+ $('#cn' + id).slideUp(speed + 200);
+ ul.slideDown(speed);
+ },
+ error: function(request, textStatus, error) {
+ showError('Oops, there was a problem retrieving the comments.');
+ },
+ dataType: 'json'
+ });
+ }
+
+ /**
+ * Add a comment via ajax and insert the comment into the comment tree.
+ */
+ function addComment(form) {
+ var node_id = form.find('input[name="node"]').val();
+ var parent_id = form.find('input[name="parent"]').val();
+ var text = form.find('textarea[name="comment"]').val();
+ var proposal = form.find('textarea[name="proposal"]').val();
+
+ if (text == '') {
+ showError('Please enter a comment.');
+ return;
+ }
+
+ // Disable the form that is being submitted.
+ form.find('textarea,input').attr('disabled', 'disabled');
+
+ // Send the comment to the server.
+ $.ajax({
+ type: "POST",
+ url: opts.addCommentURL,
+ dataType: 'json',
+ data: {
+ node: node_id,
+ parent: parent_id,
+ text: text,
+ proposal: proposal
+ },
+ success: function(data, textStatus, error) {
+ // Reset the form.
+ if (node_id) {
+ hideProposeChange(node_id);
+ }
+ form.find('textarea')
+ .val('')
+ .add(form.find('input'))
+ .removeAttr('disabled');
+ var ul = $('#cl' + (node_id || parent_id));
+ if (ul.data('empty')) {
+ $(ul).empty();
+ ul.data('empty', false);
+ }
+ insertComment(data.comment);
+ var ao = $('#ao' + node_id);
+ ao.find('img').attr({'src': opts.commentBrightImage});
+ if (node_id) {
+ // if this was a "root" comment, remove the commenting box
+ // (the user can get it back by reopening the comment popup)
+ $('#ca' + node_id).slideUp();
+ }
+ },
+ error: function(request, textStatus, error) {
+ form.find('textarea,input').removeAttr('disabled');
+ showError('Oops, there was a problem adding the comment.');
+ }
+ });
+ }
+
+ /**
+ * Recursively append comments to the main comment list and children
+ * lists, creating the comment tree.
+ */
+ function appendComments(comments, ul) {
+ $.each(comments, function() {
+ var div = createCommentDiv(this);
+ ul.append($(document.createElement('li')).html(div));
+ appendComments(this.children, div.find('ul.comment-children'));
+ // To avoid stagnating data, don't store the comments children in data.
+ this.children = null;
+ div.data('comment', this);
+ });
+ }
+
+ /**
+ * After adding a new comment, it must be inserted in the correct
+ * location in the comment tree.
+ */
+ function insertComment(comment) {
+ var div = createCommentDiv(comment);
+
+ // To avoid stagnating data, don't store the comments children in data.
+ comment.children = null;
+ div.data('comment', comment);
+
+ var ul = $('#cl' + (comment.node || comment.parent));
+ var siblings = getChildren(ul);
+
+ var li = $(document.createElement('li'));
+ li.hide();
+
+ // Determine where in the parents children list to insert this comment.
+ for(i=0; i < siblings.length; i++) {
+ if (comp(comment, siblings[i]) <= 0) {
+ $('#cd' + siblings[i].id)
+ .parent()
+ .before(li.html(div));
+ li.slideDown('fast');
+ return;
+ }
+ }
+
+ // If we get here, this comment rates lower than all the others,
+ // or it is the only comment in the list.
+ ul.append(li.html(div));
+ li.slideDown('fast');
+ }
+
+ function acceptComment(id) {
+ $.ajax({
+ type: 'POST',
+ url: opts.acceptCommentURL,
+ data: {id: id},
+ success: function(data, textStatus, request) {
+ $('#cm' + id).fadeOut('fast');
+ $('#cd' + id).removeClass('moderate');
+ },
+ error: function(request, textStatus, error) {
+ showError('Oops, there was a problem accepting the comment.');
+ }
+ });
+ }
+
+ function deleteComment(id) {
+ $.ajax({
+ type: 'POST',
+ url: opts.deleteCommentURL,
+ data: {id: id},
+ success: function(data, textStatus, request) {
+ var div = $('#cd' + id);
+ if (data == 'delete') {
+ // Moderator mode: remove the comment and all children immediately
+ div.slideUp('fast', function() {
+ div.remove();
+ });
+ return;
+ }
+ // User mode: only mark the comment as deleted
+ div
+ .find('span.user-id:first')
+ .text('[deleted]').end()
+ .find('div.comment-text:first')
+ .text('[deleted]').end()
+ .find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
+ ', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
+ .remove();
+ var comment = div.data('comment');
+ comment.username = '[deleted]';
+ comment.text = '[deleted]';
+ div.data('comment', comment);
+ },
+ error: function(request, textStatus, error) {
+ showError('Oops, there was a problem deleting the comment.');
+ }
+ });
+ }
+
+ function showProposal(id) {
+ $('#sp' + id).hide();
+ $('#hp' + id).show();
+ $('#pr' + id).slideDown('fast');
+ }
+
+ function hideProposal(id) {
+ $('#hp' + id).hide();
+ $('#sp' + id).show();
+ $('#pr' + id).slideUp('fast');
+ }
+
+ function showProposeChange(id) {
+ $('#pc' + id).hide();
+ $('#hc' + id).show();
+ var textarea = $('#pt' + id);
+ textarea.val(textarea.data('source'));
+ $.fn.autogrow.resize(textarea[0]);
+ textarea.slideDown('fast');
+ }
+
+ function hideProposeChange(id) {
+ $('#hc' + id).hide();
+ $('#pc' + id).show();
+ var textarea = $('#pt' + id);
+ textarea.val('').removeAttr('disabled');
+ textarea.slideUp('fast');
+ }
+
+ function toggleCommentMarkupBox(id) {
+ $('#mb' + id).toggle();
+ }
+
+ /** Handle when the user clicks on a sort by link. */
+ function handleReSort(link) {
+ var classes = link.attr('class').split(/\s+/);
+ for (var i=0; i<classes.length; i++) {
+ if (classes[i] != 'sort-option') {
+ by = classes[i].substring(2);
+ }
+ }
+ setComparator();
+ // Save/update the sortBy cookie.
+ var expiration = new Date();
+ expiration.setDate(expiration.getDate() + 365);
+ document.cookie= 'sortBy=' + escape(by) +
+ ';expires=' + expiration.toUTCString();
+ $('ul.comment-ul').each(function(index, ul) {
+ var comments = getChildren($(ul), true);
+ comments = sortComments(comments);
+ appendComments(comments, $(ul).empty());
+ });
+ }
+
+ /**
+ * Function to process a vote when a user clicks an arrow.
+ */
+ function handleVote(link) {
+ if (!opts.voting) {
+ showError("You'll need to login to vote.");
+ return;
+ }
+
+ var id = link.attr('id');
+ if (!id) {
+ // Didn't click on one of the voting arrows.
+ return;
+ }
+ // If it is an unvote, the new vote value is 0,
+ // Otherwise it's 1 for an upvote, or -1 for a downvote.
+ var value = 0;
+ if (id.charAt(1) != 'u') {
+ value = id.charAt(0) == 'u' ? 1 : -1;
+ }
+ // The data to be sent to the server.
+ var d = {
+ comment_id: id.substring(2),
+ value: value
+ };
+
+ // Swap the vote and unvote links.
+ link.hide();
+ $('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
+ .show();
+
+ // The div the comment is displayed in.
+ var div = $('div#cd' + d.comment_id);
+ var data = div.data('comment');
+
+ // If this is not an unvote, and the other vote arrow has
+ // already been pressed, unpress it.
+ if ((d.value !== 0) && (data.vote === d.value * -1)) {
+ $('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
+ $('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
+ }
+
+ // Update the comments rating in the local data.
+ data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
+ data.vote = d.value;
+ div.data('comment', data);
+
+ // Change the rating text.
+ div.find('.rating:first')
+ .text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
+
+ // Send the vote information to the server.
+ $.ajax({
+ type: "POST",
+ url: opts.processVoteURL,
+ data: d,
+ error: function(request, textStatus, error) {
+ showError('Oops, there was a problem casting that vote.');
+ }
+ });
+ }
+
+ /**
+ * Open a reply form used to reply to an existing comment.
+ */
+ function openReply(id) {
+ // Swap out the reply link for the hide link
+ $('#rl' + id).hide();
+ $('#cr' + id).show();
+
+ // Add the reply li to the children ul.
+ var div = $(renderTemplate(replyTemplate, {id: id})).hide();
+ $('#cl' + id)
+ .prepend(div)
+ // Setup the submit handler for the reply form.
+ .find('#rf' + id)
+ .submit(function(event) {
+ event.preventDefault();
+ addComment($('#rf' + id));
+ closeReply(id);
+ })
+ .find('input[type=button]')
+ .click(function() {
+ closeReply(id);
+ });
+ div.slideDown('fast', function() {
+ $('#rf' + id).find('textarea').focus();
+ });
+ }
+
+ /**
+ * Close the reply form opened with openReply.
+ */
+ function closeReply(id) {
+ // Remove the reply div from the DOM.
+ $('#rd' + id).slideUp('fast', function() {
+ $(this).remove();
+ });
+
+ // Swap out the hide link for the reply link
+ $('#cr' + id).hide();
+ $('#rl' + id).show();
+ }
+
+ /**
+ * Recursively sort a tree of comments using the comp comparator.
+ */
+ function sortComments(comments) {
+ comments.sort(comp);
+ $.each(comments, function() {
+ this.children = sortComments(this.children);
+ });
+ return comments;
+ }
+
+ /**
+ * Get the children comments from a ul. If recursive is true,
+ * recursively include childrens' children.
+ */
+ function getChildren(ul, recursive) {
+ var children = [];
+ ul.children().children("[id^='cd']")
+ .each(function() {
+ var comment = $(this).data('comment');
+ if (recursive)
+ comment.children = getChildren($(this).find('#cl' + comment.id), true);
+ children.push(comment);
+ });
+ return children;
+ }
+
+ /** Create a div to display a comment in. */
+ function createCommentDiv(comment) {
+ if (!comment.displayed && !opts.moderator) {
+ return $('<div class="moderate">Thank you! Your comment will show up '
+ + 'once it is has been approved by a moderator.</div>');
+ }
+ // 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 = '\
+ <div class="sphinx-comments" id="sc<%id%>">\
+ <p class="sort-options">\
+ Sort by:\
+ <a href="#" class="sort-option byrating">best rated</a>\
+ <a href="#" class="sort-option byascage">newest</a>\
+ <a href="#" class="sort-option byage">oldest</a>\
+ </p>\
+ <div class="comment-header">Comments</div>\
+ <div class="comment-loading" id="cn<%id%>">\
+ loading comments... <img src="<%loadingImage%>" alt="" /></div>\
+ <ul id="cl<%id%>" class="comment-ul"></ul>\
+ <div id="ca<%id%>">\
+ <p class="add-a-comment">Add a comment\
+ (<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
+ <div class="comment-markup-box" id="mb<%id%>">\
+ reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
+ <tt>``code``</tt>, \
+ code blocks: <tt>::</tt> and an indented block after blank line</div>\
+ <form method="post" id="cf<%id%>" class="comment-form" action="">\
+ <textarea name="comment" cols="80"></textarea>\
+ <p class="propose-button">\
+ <a href="#" id="pc<%id%>" class="show-propose-change">\
+ Propose a change &#9657;\
+ </a>\
+ <a href="#" id="hc<%id%>" class="hide-propose-change">\
+ Propose a change &#9663;\
+ </a>\
+ </p>\
+ <textarea name="proposal" id="pt<%id%>" cols="80"\
+ spellcheck="false"></textarea>\
+ <input type="submit" value="Add comment" />\
+ <input type="hidden" name="node" value="<%id%>" />\
+ <input type="hidden" name="parent" value="" />\
+ </form>\
+ </div>\
+ </div>';
+
+ var commentTemplate = '\
+ <div id="cd<%id%>" class="sphinx-comment<%css_class%>">\
+ <div class="vote">\
+ <div class="arrow">\
+ <a href="#" id="uv<%id%>" class="vote" title="vote up">\
+ <img src="<%upArrow%>" />\
+ </a>\
+ <a href="#" id="uu<%id%>" class="un vote" title="vote up">\
+ <img src="<%upArrowPressed%>" />\
+ </a>\
+ </div>\
+ <div class="arrow">\
+ <a href="#" id="dv<%id%>" class="vote" title="vote down">\
+ <img src="<%downArrow%>" id="da<%id%>" />\
+ </a>\
+ <a href="#" id="du<%id%>" class="un vote" title="vote down">\
+ <img src="<%downArrowPressed%>" />\
+ </a>\
+ </div>\
+ </div>\
+ <div class="comment-content">\
+ <p class="tagline comment">\
+ <span class="user-id"><%username%></span>\
+ <span class="rating"><%pretty_rating%></span>\
+ <span class="delta"><%time.delta%></span>\
+ </p>\
+ <div class="comment-text comment"><#text#></div>\
+ <p class="comment-opts comment">\
+ <a href="#" class="reply hidden" id="rl<%id%>">reply &#9657;</a>\
+ <a href="#" class="close-reply" id="cr<%id%>">reply &#9663;</a>\
+ <a href="#" id="sp<%id%>" class="show-proposal">proposal &#9657;</a>\
+ <a href="#" id="hp<%id%>" class="hide-proposal">proposal &#9663;</a>\
+ <a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\
+ <span id="cm<%id%>" class="moderation hidden">\
+ <a href="#" id="ac<%id%>" class="accept-comment">accept</a>\
+ </span>\
+ </p>\
+ <pre class="proposal" id="pr<%id%>">\
+<#proposal_diff#>\
+ </pre>\
+ <ul class="comment-children" id="cl<%id%>"></ul>\
+ </div>\
+ <div class="clearleft"></div>\
+ </div>\
+ </div>';
+
+ var replyTemplate = '\
+ <li>\
+ <div class="reply-div" id="rd<%id%>">\
+ <form id="rf<%id%>">\
+ <textarea name="comment" cols="80"></textarea>\
+ <input type="submit" value="Add reply" />\
+ <input type="button" value="Cancel" />\
+ <input type="hidden" name="parent" value="<%id%>" />\
+ <input type="hidden" name="node" value="" />\
+ </form>\
+ </div>\
+ </li>';
+
+ $(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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <title>client Package &mdash; BrowserMob Proxy 0.6.0 documentation</title>
+
+ <link rel="stylesheet" href="_static/default.css" type="text/css" />
+ <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+
+ <script type="text/javascript">
+ var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: './',
+ VERSION: '0.6.0',
+ COLLAPSE_INDEX: false,
+ FILE_SUFFIX: '.html',
+ HAS_SOURCE: true
+ };
+ </script>
+ <script type="text/javascript" src="_static/jquery.js"></script>
+ <script type="text/javascript" src="_static/underscore.js"></script>
+ <script type="text/javascript" src="_static/doctools.js"></script>
+ <link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
+ <link rel="next" title="server Package" href="server.html" />
+ <link rel="prev" title="Welcome to BrowserMob Proxy’s documentation!" href="index.html" />
+ </head>
+ <body>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ accesskey="I">index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li class="right" >
+ <a href="server.html" title="server Package"
+ accesskey="N">next</a> |</li>
+ <li class="right" >
+ <a href="index.html" title="Welcome to BrowserMob Proxy’s documentation!"
+ accesskey="P">previous</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body">
+
+ <div class="toctree-wrapper compound">
+<ul class="simple">
+</ul>
+</div>
+<div class="section" id="module-browsermobproxy">
+<span id="client-package"></span><h1><tt class="xref py py-mod docutils literal"><span class="pre">client</span></tt> Package<a class="headerlink" href="#module-browsermobproxy" title="Permalink to this headline">¶</a></h1>
+<dl class="class">
+<dt id="browsermobproxy.Client">
+<em class="property">class </em><tt class="descclassname">browsermobproxy.</tt><tt class="descname">Client</tt><big>(</big><em>url</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client" title="Permalink to this definition">¶</a></dt>
+<dd><p>Initialises a new Client object</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>url</strong> &#8211; This is where the BrowserMob Proxy lives</td>
+</tr>
+</tbody>
+</table>
+<dl class="method">
+<dt id="browsermobproxy.Client.add_to_capabilities">
+<tt class="descname">add_to_capabilities</tt><big>(</big><em>capabilities</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.add_to_capabilities" title="Permalink to this definition">¶</a></dt>
+<dd><p>Adds an &#8216;proxy&#8217; entry to a desired capabilities dictionary with the
+BrowserMob proxy information</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>capabilities</strong> &#8211; The Desired capabilities object from Selenium WebDriver</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.basic_authentication">
+<tt class="descname">basic_authentication</tt><big>(</big><em>domain</em>, <em>username</em>, <em>password</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.basic_authentication" title="Permalink to this definition">¶</a></dt>
+<dd><p>This add automatic basic authentication</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>domain</strong> &#8211; domain to set authentication credentials for</li>
+<li><strong>username</strong> &#8211; valid username to use when authenticating</li>
+<li><strong>password</strong> &#8211; valid password to use when authenticating</li>
+</ul>
+</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.blacklist">
+<tt class="descname">blacklist</tt><big>(</big><em>regexp</em>, <em>status_code</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.blacklist" title="Permalink to this definition">¶</a></dt>
+<dd><p>Sets a list of URL patterns to blacklist</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>regex</strong> &#8211; a comma separated list of regular expressions</li>
+<li><strong>status_code</strong> &#8211; the HTTP status code to return for URLs that do not match the blacklist</li>
+</ul>
+</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.clear_dns_cache">
+<tt class="descname">clear_dns_cache</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Client.clear_dns_cache" title="Permalink to this definition">¶</a></dt>
+<dd><p>Clears the DNS cache associated with the proxy instance</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.close">
+<tt class="descname">close</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Client.close" title="Permalink to this definition">¶</a></dt>
+<dd><p>shuts down the proxy and closes the port</p>
+</dd></dl>
+
+<dl class="attribute">
+<dt id="browsermobproxy.Client.har">
+<tt class="descname">har</tt><a class="headerlink" href="#browsermobproxy.Client.har" title="Permalink to this definition">¶</a></dt>
+<dd><p>Gets the HAR that has been recorded</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.headers">
+<tt class="descname">headers</tt><big>(</big><em>headers</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.headers" title="Permalink to this definition">¶</a></dt>
+<dd><p>This sets the headers that will set by the proxy on all requests</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>headers</strong> &#8211; this is a dictionary of the headers to be set</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.limits">
+<tt class="descname">limits</tt><big>(</big><em>options</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.limits" title="Permalink to this definition">¶</a></dt>
+<dd><p>Limit the bandwidth through the proxy.</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>options</strong> &#8211; 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</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.new_har">
+<tt class="descname">new_har</tt><big>(</big><em>ref=None</em>, <em>options={}</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.new_har" title="Permalink to this definition">¶</a></dt>
+<dd><p>This sets a new HAR to be recorded</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>ref</strong> &#8211; A reference for the HAR. Defaults to None</li>
+<li><strong>options</strong> &#8211; 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</li>
+</ul>
+</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.new_page">
+<tt class="descname">new_page</tt><big>(</big><em>ref=None</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.new_page" title="Permalink to this definition">¶</a></dt>
+<dd><p>This sets a new page to be recorded</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>ref</strong> &#8211; A reference for the new page. Defaults to None</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.remap_hosts">
+<tt class="descname">remap_hosts</tt><big>(</big><em>address</em>, <em>ip_address</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.remap_hosts" title="Permalink to this definition">¶</a></dt>
+<dd><p>Remap the hosts for a specific URL</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>address</strong> &#8211; url that you wish to remap</li>
+<li><strong>ip_address</strong> &#8211; IP Address that will handle all traffic for the address passed in</li>
+</ul>
+</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.request_interceptor">
+<tt class="descname">request_interceptor</tt><big>(</big><em>js</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.request_interceptor" title="Permalink to this definition">¶</a></dt>
+<dd><p>Executes the javascript against each request</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>js</strong> &#8211; the javascript to execute</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.response_interceptor">
+<tt class="descname">response_interceptor</tt><big>(</big><em>js</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.response_interceptor" title="Permalink to this definition">¶</a></dt>
+<dd><p>Executes the javascript against each response</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>js</strong> &#8211; the javascript to execute</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.retry">
+<tt class="descname">retry</tt><big>(</big><em>retry_count</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.retry" title="Permalink to this definition">¶</a></dt>
+<dd><p>Retries. No idea what its used for, but its in the API...</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>retry_count</strong> &#8211; the number of retries</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.rewrite_url">
+<tt class="descname">rewrite_url</tt><big>(</big><em>match</em>, <em>replace</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.rewrite_url" title="Permalink to this definition">¶</a></dt>
+<dd><p>Rewrites the requested url.</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>match</strong> &#8211; a regex to match requests with</li>
+<li><strong>replace</strong> &#8211; unicode a string to replace the matches with</li>
+</ul>
+</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.selenium_proxy">
+<tt class="descname">selenium_proxy</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Client.selenium_proxy" title="Permalink to this definition">¶</a></dt>
+<dd><p>Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.timeouts">
+<tt class="descname">timeouts</tt><big>(</big><em>options</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.timeouts" title="Permalink to this definition">¶</a></dt>
+<dd><p>Configure various timeouts in the proxy</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>options</strong> &#8211; 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)</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.wait_for_traffic_to_stop">
+<tt class="descname">wait_for_traffic_to_stop</tt><big>(</big><em>quiet_period</em>, <em>timeout</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.wait_for_traffic_to_stop" title="Permalink to this definition">¶</a></dt>
+<dd><p>Waits for the network to be quiet</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>quiet_period</strong> &#8211; number of seconds the network needs to be quiet for</li>
+<li><strong>timeout</strong> &#8211; max number of seconds to wait</li>
+</ul>
+</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.webdriver_proxy">
+<tt class="descname">webdriver_proxy</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Client.webdriver_proxy" title="Permalink to this definition">¶</a></dt>
+<dd><p>Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Client.whitelist">
+<tt class="descname">whitelist</tt><big>(</big><em>regexp</em>, <em>status_code</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.whitelist" title="Permalink to this definition">¶</a></dt>
+<dd><p>Sets a list of URL patterns to whitelist</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>regex</strong> &#8211; a comma separated list of regular expressions</li>
+<li><strong>status_code</strong> &#8211; the HTTP status code to return for URLs that do not match the whitelist</li>
+</ul>
+</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
+</dd></dl>
+
+</div>
+
+
+ </div>
+ </div>
+ </div>
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+ <h4>Previous topic</h4>
+ <p class="topless"><a href="index.html"
+ title="previous chapter">Welcome to BrowserMob Proxy&#8217;s documentation!</a></p>
+ <h4>Next topic</h4>
+ <p class="topless"><a href="server.html"
+ title="next chapter"><tt class="docutils literal"><span class="pre">server</span></tt> Package</a></p>
+ <h3>This Page</h3>
+ <ul class="this-page-menu">
+ <li><a href="_sources/client.txt"
+ rel="nofollow">Show Source</a></li>
+ </ul>
+<div id="searchbox" style="display: none">
+ <h3>Quick search</h3>
+ <form class="search" action="search.html" method="get">
+ <input type="text" name="q" />
+ <input type="submit" value="Go" />
+ <input type="hidden" name="check_keywords" value="yes" />
+ <input type="hidden" name="area" value="default" />
+ </form>
+ <p class="searchtip" style="font-size: 90%">
+ Enter search terms or a module, class or function name.
+ </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ >index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li class="right" >
+ <a href="server.html" title="server Package"
+ >next</a> |</li>
+ <li class="right" >
+ <a href="index.html" title="Welcome to BrowserMob Proxy’s documentation!"
+ >previous</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+ <div class="footer">
+ &copy; Copyright 2014, David Burns.
+ Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
+ </div>
+ </body>
+</html> \ 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 @@
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <title>Index &mdash; BrowserMob Proxy 0.6.0 documentation</title>
+
+ <link rel="stylesheet" href="_static/default.css" type="text/css" />
+ <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+
+ <script type="text/javascript">
+ var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: './',
+ VERSION: '0.6.0',
+ COLLAPSE_INDEX: false,
+ FILE_SUFFIX: '.html',
+ HAS_SOURCE: true
+ };
+ </script>
+ <script type="text/javascript" src="_static/jquery.js"></script>
+ <script type="text/javascript" src="_static/underscore.js"></script>
+ <script type="text/javascript" src="_static/doctools.js"></script>
+ <link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
+ </head>
+ <body>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="#" title="General Index"
+ accesskey="I">index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body">
+
+
+<h1 id="index">Index</h1>
+
+<div class="genindex-jumpbox">
+ <a href="#A"><strong>A</strong></a>
+ | <a href="#B"><strong>B</strong></a>
+ | <a href="#C"><strong>C</strong></a>
+ | <a href="#H"><strong>H</strong></a>
+ | <a href="#L"><strong>L</strong></a>
+ | <a href="#N"><strong>N</strong></a>
+ | <a href="#R"><strong>R</strong></a>
+ | <a href="#S"><strong>S</strong></a>
+ | <a href="#T"><strong>T</strong></a>
+ | <a href="#U"><strong>U</strong></a>
+ | <a href="#W"><strong>W</strong></a>
+
+</div>
+<h2 id="A">A</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.add_to_capabilities">add_to_capabilities() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="B">B</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.basic_authentication">basic_authentication() (browsermobproxy.Client method)</a>
+ </dt>
+
+
+ <dt><a href="client.html#browsermobproxy.Client.blacklist">blacklist() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#module-browsermobproxy">browsermobproxy (module)</a>, <a href="server.html#module-browsermobproxy">[1]</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="C">C</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.clear_dns_cache">clear_dns_cache() (browsermobproxy.Client method)</a>
+ </dt>
+
+
+ <dt><a href="client.html#browsermobproxy.Client">Client (class in browsermobproxy)</a>
+ </dt>
+
+ </dl></td>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.close">close() (browsermobproxy.Client method)</a>
+ </dt>
+
+
+ <dt><a href="server.html#browsermobproxy.Server.create_proxy">create_proxy() (browsermobproxy.Server method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="H">H</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.har">har (browsermobproxy.Client attribute)</a>
+ </dt>
+
+ </dl></td>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.headers">headers() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="L">L</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.limits">limits() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="N">N</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.new_har">new_har() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.new_page">new_page() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="R">R</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.remap_hosts">remap_hosts() (browsermobproxy.Client method)</a>
+ </dt>
+
+
+ <dt><a href="client.html#browsermobproxy.Client.request_interceptor">request_interceptor() (browsermobproxy.Client method)</a>
+ </dt>
+
+
+ <dt><a href="client.html#browsermobproxy.Client.response_interceptor">response_interceptor() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.retry">retry() (browsermobproxy.Client method)</a>
+ </dt>
+
+
+ <dt><a href="client.html#browsermobproxy.Client.rewrite_url">rewrite_url() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="S">S</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.selenium_proxy">selenium_proxy() (browsermobproxy.Client method)</a>
+ </dt>
+
+
+ <dt><a href="server.html#browsermobproxy.Server">Server (class in browsermobproxy)</a>
+ </dt>
+
+ </dl></td>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="server.html#browsermobproxy.Server.start">start() (browsermobproxy.Server method)</a>
+ </dt>
+
+
+ <dt><a href="server.html#browsermobproxy.Server.stop">stop() (browsermobproxy.Server method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="T">T</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.timeouts">timeouts() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="U">U</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="server.html#browsermobproxy.Server.url">url (browsermobproxy.Server attribute)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+<h2 id="W">W</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.wait_for_traffic_to_stop">wait_for_traffic_to_stop() (browsermobproxy.Client method)</a>
+ </dt>
+
+
+ <dt><a href="client.html#browsermobproxy.Client.webdriver_proxy">webdriver_proxy() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+ <td style="width: 33%" valign="top"><dl>
+
+ <dt><a href="client.html#browsermobproxy.Client.whitelist">whitelist() (browsermobproxy.Client method)</a>
+ </dt>
+
+ </dl></td>
+</tr></table>
+
+
+
+ </div>
+ </div>
+ </div>
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+
+
+
+<div id="searchbox" style="display: none">
+ <h3>Quick search</h3>
+ <form class="search" action="search.html" method="get">
+ <input type="text" name="q" />
+ <input type="submit" value="Go" />
+ <input type="hidden" name="check_keywords" value="yes" />
+ <input type="hidden" name="area" value="default" />
+ </form>
+ <p class="searchtip" style="font-size: 90%">
+ Enter search terms or a module, class or function name.
+ </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="#" title="General Index"
+ >index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+ <div class="footer">
+ &copy; Copyright 2014, David Burns.
+ Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
+ </div>
+ </body>
+</html> \ 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <title>Welcome to BrowserMob Proxy’s documentation! &mdash; BrowserMob Proxy 0.6.0 documentation</title>
+
+ <link rel="stylesheet" href="_static/default.css" type="text/css" />
+ <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+
+ <script type="text/javascript">
+ var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: './',
+ VERSION: '0.6.0',
+ COLLAPSE_INDEX: false,
+ FILE_SUFFIX: '.html',
+ HAS_SOURCE: true
+ };
+ </script>
+ <script type="text/javascript" src="_static/jquery.js"></script>
+ <script type="text/javascript" src="_static/underscore.js"></script>
+ <script type="text/javascript" src="_static/doctools.js"></script>
+ <link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="#" />
+ <link rel="next" title="client Package" href="client.html" />
+ </head>
+ <body>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ accesskey="I">index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li class="right" >
+ <a href="client.html" title="client Package"
+ accesskey="N">next</a> |</li>
+ <li><a href="#">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body">
+
+ <div class="section" id="welcome-to-browsermob-proxy-s-documentation">
+<h1>Welcome to BrowserMob Proxy&#8217;s documentation!<a class="headerlink" href="#welcome-to-browsermob-proxy-s-documentation" title="Permalink to this headline">¶</a></h1>
+<p>Python client for the BrowserMob Proxy 2.0 REST API.</p>
+<div class="section" id="how-to-install">
+<h2>How to install<a class="headerlink" href="#how-to-install" title="Permalink to this headline">¶</a></h2>
+<p>BrowserMob Proxy is available on <a class="reference external" href="http://pypi.python.org/pypi/browsermob-proxy">PyPI</a>, so you can install it with <tt class="docutils literal"><span class="pre">pip</span></tt>:</p>
+<div class="highlight-python"><div class="highlight"><pre>$ pip install browsermob-proxy
+</pre></div>
+</div>
+<p>Or with <cite>easy_install</cite>:</p>
+<div class="highlight-python"><div class="highlight"><pre>$ easy_install browsermob-proxy
+</pre></div>
+</div>
+<p>Or by cloning the repo from <a class="reference external" href="https://github.com/AutomatedTester/browsermob-proxy-py">GitHub</a>:</p>
+<div class="highlight-python"><div class="highlight"><pre>$ git clone git://github.com/AutomatedTester/browsermob-proxy-py.git
+</pre></div>
+</div>
+<p>Then install it by running:</p>
+<div class="highlight-python"><div class="highlight"><pre>$ python setup.py install
+</pre></div>
+</div>
+</div>
+<div class="section" id="how-to-use-with-selenium-webdriver">
+<h2>How to use with selenium-webdriver<a class="headerlink" href="#how-to-use-with-selenium-webdriver" title="Permalink to this headline">¶</a></h2>
+<p>Manually:</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="kn">from</span> <span class="nn">browsermobproxy</span> <span class="kn">import</span> <span class="n">Server</span>
+<span class="n">server</span> <span class="o">=</span> <span class="n">Server</span><span class="p">(</span><span class="s">&quot;path/to/browsermob-proxy&quot;</span><span class="p">)</span>
+<span class="n">server</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
+<span class="n">proxy</span> <span class="o">=</span> <span class="n">server</span><span class="o">.</span><span class="n">create_proxy</span><span class="p">()</span>
+
+<span class="kn">from</span> <span class="nn">selenium</span> <span class="kn">import</span> <span class="n">webdriver</span>
+<span class="n">profile</span> <span class="o">=</span> <span class="n">webdriver</span><span class="o">.</span><span class="n">FirefoxProfile</span><span class="p">()</span>
+<span class="n">profile</span><span class="o">.</span><span class="n">set_proxy</span><span class="p">(</span><span class="n">proxy</span><span class="o">.</span><span class="n">selenium_proxy</span><span class="p">())</span>
+<span class="n">driver</span> <span class="o">=</span> <span class="n">webdriver</span><span class="o">.</span><span class="n">Firefox</span><span class="p">(</span><span class="n">firefox_profile</span><span class="o">=</span><span class="n">profile</span><span class="p">)</span>
+
+
+<span class="n">proxy</span><span class="o">.</span><span class="n">new_har</span><span class="p">(</span><span class="s">&quot;google&quot;</span><span class="p">)</span>
+<span class="n">driver</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&quot;http://www.google.co.uk&quot;</span><span class="p">)</span>
+<span class="n">proxy</span><span class="o">.</span><span class="n">har</span> <span class="c"># returns a HAR JSON blob</span>
+
+<span class="n">server</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
+<span class="n">driver</span><span class="o">.</span><span class="n">quit</span><span class="p">()</span>
+</pre></div>
+</div>
+<p>Contents:</p>
+<div class="toctree-wrapper compound">
+<ul>
+<li class="toctree-l1"><a class="reference internal" href="client.html"><tt class="docutils literal"><span class="pre">client</span></tt> Package</a></li>
+<li class="toctree-l1"><a class="reference internal" href="server.html"><tt class="docutils literal"><span class="pre">server</span></tt> Package</a></li>
+</ul>
+</div>
+</div>
+</div>
+<div class="section" id="indices-and-tables">
+<h1>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Permalink to this headline">¶</a></h1>
+<ul class="simple">
+<li><a class="reference internal" href="genindex.html"><em>Index</em></a></li>
+<li><a class="reference internal" href="py-modindex.html"><em>Module Index</em></a></li>
+<li><a class="reference internal" href="search.html"><em>Search Page</em></a></li>
+</ul>
+</div>
+
+
+ </div>
+ </div>
+ </div>
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+ <h3><a href="#">Table Of Contents</a></h3>
+ <ul>
+<li><a class="reference internal" href="#">Welcome to BrowserMob Proxy&#8217;s documentation!</a><ul>
+<li><a class="reference internal" href="#how-to-install">How to install</a></li>
+<li><a class="reference internal" href="#how-to-use-with-selenium-webdriver">How to use with selenium-webdriver</a></li>
+</ul>
+</li>
+<li><a class="reference internal" href="#indices-and-tables">Indices and tables</a></li>
+</ul>
+
+ <h4>Next topic</h4>
+ <p class="topless"><a href="client.html"
+ title="next chapter"><tt class="docutils literal"><span class="pre">client</span></tt> Package</a></p>
+ <h3>This Page</h3>
+ <ul class="this-page-menu">
+ <li><a href="_sources/index.txt"
+ rel="nofollow">Show Source</a></li>
+ </ul>
+<div id="searchbox" style="display: none">
+ <h3>Quick search</h3>
+ <form class="search" action="search.html" method="get">
+ <input type="text" name="q" />
+ <input type="submit" value="Go" />
+ <input type="hidden" name="check_keywords" value="yes" />
+ <input type="hidden" name="area" value="default" />
+ </form>
+ <p class="searchtip" style="font-size: 90%">
+ Enter search terms or a module, class or function name.
+ </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ >index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li class="right" >
+ <a href="client.html" title="client Package"
+ >next</a> |</li>
+ <li><a href="#">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+ <div class="footer">
+ &copy; Copyright 2014, David Burns.
+ Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
+ </div>
+ </body>
+</html> \ 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <title>Python Module Index &mdash; BrowserMob Proxy 0.6.0 documentation</title>
+
+ <link rel="stylesheet" href="_static/default.css" type="text/css" />
+ <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+
+ <script type="text/javascript">
+ var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: './',
+ VERSION: '0.6.0',
+ COLLAPSE_INDEX: false,
+ FILE_SUFFIX: '.html',
+ HAS_SOURCE: true
+ };
+ </script>
+ <script type="text/javascript" src="_static/jquery.js"></script>
+ <script type="text/javascript" src="_static/underscore.js"></script>
+ <script type="text/javascript" src="_static/doctools.js"></script>
+ <link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
+
+
+ <script type="text/javascript">
+ DOCUMENTATION_OPTIONS.COLLAPSE_INDEX = true;
+ </script>
+
+
+ </head>
+ <body>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ accesskey="I">index</a></li>
+ <li class="right" >
+ <a href="#" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body">
+
+
+ <h1>Python Module Index</h1>
+
+ <div class="modindex-jumpbox">
+ <a href="#cap-b"><strong>b</strong></a>
+ </div>
+
+ <table class="indextable modindextable" cellspacing="0" cellpadding="2">
+ <tr class="pcap"><td></td><td>&nbsp;</td><td></td></tr>
+ <tr class="cap" id="cap-b"><td></td><td>
+ <strong>b</strong></td><td></td></tr>
+ <tr>
+ <td></td>
+ <td>
+ <a href="server.html#module-browsermobproxy"><tt class="xref">browsermobproxy</tt></a></td><td>
+ <em></em></td></tr>
+ </table>
+
+
+ </div>
+ </div>
+ </div>
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+<div id="searchbox" style="display: none">
+ <h3>Quick search</h3>
+ <form class="search" action="search.html" method="get">
+ <input type="text" name="q" />
+ <input type="submit" value="Go" />
+ <input type="hidden" name="check_keywords" value="yes" />
+ <input type="hidden" name="area" value="default" />
+ </form>
+ <p class="searchtip" style="font-size: 90%">
+ Enter search terms or a module, class or function name.
+ </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ >index</a></li>
+ <li class="right" >
+ <a href="#" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+ <div class="footer">
+ &copy; Copyright 2014, David Burns.
+ Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
+ </div>
+ </body>
+</html> \ 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <title>Search &mdash; BrowserMob Proxy 0.6.0 documentation</title>
+
+ <link rel="stylesheet" href="_static/default.css" type="text/css" />
+ <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+
+ <script type="text/javascript">
+ var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: './',
+ VERSION: '0.6.0',
+ COLLAPSE_INDEX: false,
+ FILE_SUFFIX: '.html',
+ HAS_SOURCE: true
+ };
+ </script>
+ <script type="text/javascript" src="_static/jquery.js"></script>
+ <script type="text/javascript" src="_static/underscore.js"></script>
+ <script type="text/javascript" src="_static/doctools.js"></script>
+ <script type="text/javascript" src="_static/searchtools.js"></script>
+ <link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
+ <script type="text/javascript">
+ jQuery(function() { Search.loadIndex("searchindex.js"); });
+ </script>
+
+ <script type="text/javascript" id="searchindexloader"></script>
+
+
+ </head>
+ <body>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ accesskey="I">index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body">
+
+ <h1 id="search-documentation">Search</h1>
+ <div id="fallback" class="admonition warning">
+ <script type="text/javascript">$('#fallback').hide();</script>
+ <p>
+ Please activate JavaScript to enable the search
+ functionality.
+ </p>
+ </div>
+ <p>
+ 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.
+ </p>
+ <form action="" method="get">
+ <input type="text" name="q" value="" />
+ <input type="submit" value="search" />
+ <span id="search-progress" style="padding-left: 10px"></span>
+ </form>
+
+ <div id="search-results">
+
+ </div>
+
+ </div>
+ </div>
+ </div>
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ >index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+ <div class="footer">
+ &copy; Copyright 2014, David Burns.
+ Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
+ </div>
+ </body>
+</html> \ 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&#8217;s documentation!","<tt class=\"docutils literal\"><span class=\"pre\">client</span></tt> Package","<tt class=\"docutils literal\"><span class=\"pre\">server</span></tt> 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <title>server Package &mdash; BrowserMob Proxy 0.6.0 documentation</title>
+
+ <link rel="stylesheet" href="_static/default.css" type="text/css" />
+ <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+
+ <script type="text/javascript">
+ var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: './',
+ VERSION: '0.6.0',
+ COLLAPSE_INDEX: false,
+ FILE_SUFFIX: '.html',
+ HAS_SOURCE: true
+ };
+ </script>
+ <script type="text/javascript" src="_static/jquery.js"></script>
+ <script type="text/javascript" src="_static/underscore.js"></script>
+ <script type="text/javascript" src="_static/doctools.js"></script>
+ <link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
+ <link rel="prev" title="client Package" href="client.html" />
+ </head>
+ <body>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ accesskey="I">index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li class="right" >
+ <a href="client.html" title="client Package"
+ accesskey="P">previous</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body">
+
+ <div class="toctree-wrapper compound">
+<ul class="simple">
+</ul>
+</div>
+<div class="section" id="module-browsermobproxy">
+<span id="server-package"></span><h1><tt class="xref py py-mod docutils literal"><span class="pre">server</span></tt> Package<a class="headerlink" href="#module-browsermobproxy" title="Permalink to this headline">¶</a></h1>
+<dl class="class">
+<dt id="browsermobproxy.Server">
+<em class="property">class </em><tt class="descclassname">browsermobproxy.</tt><tt class="descname">Server</tt><big>(</big><em>path='browsermob-proxy'</em>, <em>options={}</em><big>)</big><a class="headerlink" href="#browsermobproxy.Server" title="Permalink to this definition">¶</a></dt>
+<dd><p>Initialises a Server object</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Args:</th><td class="field-body"></td>
+</tr>
+<tr class="field-even field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>path</strong> &#8211; Path to the browsermob proxy batch file</li>
+<li><strong>options</strong> &#8211; Dictionary that can hold the port. More items will be added in the future. This defaults to an empty dictionary</li>
+</ul>
+</td>
+</tr>
+</tbody>
+</table>
+<dl class="method">
+<dt id="browsermobproxy.Server.create_proxy">
+<tt class="descname">create_proxy</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Server.create_proxy" title="Permalink to this definition">¶</a></dt>
+<dd><p>Gets a client class that allow to set all the proxy details that you
+may need to.</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Server.start">
+<tt class="descname">start</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Server.start" title="Permalink to this definition">¶</a></dt>
+<dd><p>This will start the browsermob proxy and then wait until it can
+interact with it</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="browsermobproxy.Server.stop">
+<tt class="descname">stop</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Server.stop" title="Permalink to this definition">¶</a></dt>
+<dd><p>This will stop the process running the proxy</p>
+</dd></dl>
+
+<dl class="attribute">
+<dt id="browsermobproxy.Server.url">
+<tt class="descname">url</tt><a class="headerlink" href="#browsermobproxy.Server.url" title="Permalink to this definition">¶</a></dt>
+<dd><p>Gets the url that the proxy is running on. This is not the URL clients
+should connect to.</p>
+</dd></dl>
+
+</dd></dl>
+
+</div>
+
+
+ </div>
+ </div>
+ </div>
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+ <h4>Previous topic</h4>
+ <p class="topless"><a href="client.html"
+ title="previous chapter"><tt class="docutils literal"><span class="pre">client</span></tt> Package</a></p>
+ <h3>This Page</h3>
+ <ul class="this-page-menu">
+ <li><a href="_sources/server.txt"
+ rel="nofollow">Show Source</a></li>
+ </ul>
+<div id="searchbox" style="display: none">
+ <h3>Quick search</h3>
+ <form class="search" action="search.html" method="get">
+ <input type="text" name="q" />
+ <input type="submit" value="Go" />
+ <input type="hidden" name="check_keywords" value="yes" />
+ <input type="hidden" name="area" value="default" />
+ </form>
+ <p class="searchtip" style="font-size: 90%">
+ Enter search terms or a module, class or function name.
+ </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ <div class="related">
+ <h3>Navigation</h3>
+ <ul>
+ <li class="right" style="margin-right: 10px">
+ <a href="genindex.html" title="General Index"
+ >index</a></li>
+ <li class="right" >
+ <a href="py-modindex.html" title="Python Module Index"
+ >modules</a> |</li>
+ <li class="right" >
+ <a href="client.html" title="client Package"
+ >previous</a> |</li>
+ <li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
+ </ul>
+ </div>
+ <div class="footer">
+ &copy; Copyright 2014, David Burns.
+ Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
+ </div>
+ </body>
+</html> \ 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
+# "<project> v<release> 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 <link> 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 ^<target^>` where ^<target^> 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 <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: "<ManifestFixture {}>".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
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpi
Binary files 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 <a> 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("""
+<style>
+* { margin: 0; padding: 0; }
+body { height: 300vh }
+div, a { display: block }
+div {
+ background-color: pink;
+ position: fixed;
+ width: 100%;
+ height: 40px;
+ top: 0;
+}
+a {
+ margin-top: 1000px;
+}
+</style>
+
+<div>overlay</div>
+<a href=#>link</a>
+
+<script>
+window.clicked = false;
+
+let link = document.querySelector("a");
+link.addEventListener("click", () => window.clicked = true);
+</script>
+""")
+
+
+obscured_overlay = inline("""
+<style>
+* { margin: 0; padding: 0; }
+body { height: 100vh }
+#overlay {
+ background-color: pink;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+</style>
+
+<div id=overlay></div>
+<a id=obscured href=#>link</a>
+
+<script>
+window.clicked = false;
+
+let link = document.querySelector("#obscured");
+link.addEventListener("click", () => window.clicked = true);
+</script>
+""")
+
+
+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>click me</button>
+ <script>
+ window.clicks = 0;
+ let button = document.querySelector("button");
+ button.addEventListener("click", () => window.clicks++);
+ </script>
+ """))
+ 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("""
+ <style>
+ * { margin: 0; padding: 0; }
+ div {
+ display: block;
+ position: absolute;
+ background-color: blue;
+ width: 200px;
+ height: 200px;
+
+ /* move centre point off viewport vertically */
+ top: -105px;
+ }
+ </style>
+
+ <div></div>"""))
+
+ self.marionette.find_element(By.TAG_NAME, "div").click()
+
+ def test_centre_outside_viewport_horizontally(self):
+ self.marionette.navigate(inline("""
+ <style>
+ * { margin: 0; padding: 0; }
+ div {
+ display: block;
+ position: absolute;
+ background-color: blue;
+ width: 200px;
+ height: 200px;
+
+ /* move centre point off viewport horizontally */
+ left: -105px;
+ }
+ </style>
+
+ <div></div>"""))
+
+ self.marionette.find_element(By.TAG_NAME, "div").click()
+
+ def test_centre_outside_viewport(self):
+ self.marionette.navigate(inline("""
+ <style>
+ * { margin: 0; padding: 0; }
+ div {
+ display: block;
+ position: absolute;
+ background-color: blue;
+ width: 200px;
+ height: 200px;
+
+ /* move centre point off viewport */
+ left: -105px;
+ top: -105px;
+ }
+ </style>
+
+ <div></div>"""))
+
+ self.marionette.find_element(By.TAG_NAME, "div").click()
+
+ def test_css_transforms(self):
+ self.marionette.navigate(inline("""
+ <style>
+ * { margin: 0; padding: 0; }
+ div {
+ display: block;
+ background-color: blue;
+ width: 200px;
+ height: 200px;
+
+ transform: translateX(-105px);
+ }
+ </style>
+
+ <div></div>"""))
+
+ self.marionette.find_element(By.TAG_NAME, "div").click()
+
+ def test_input_file(self):
+ self.marionette.navigate(inline("<input type=file>"))
+ with self.assertRaises(errors.InvalidArgumentException):
+ self.marionette.find_element(By.TAG_NAME, "input").click()
+
+ def test_container_element(self):
+ self.marionette.navigate(inline("""
+ <select>
+ <option>foo</option>
+ </select>"""))
+ 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("""
+ <select style="margin-top: 100vh">
+ <option>foo</option>
+ </select>"""))
+ 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"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>XHTML might be the future</title>
+ </head>
+
+ <body>
+ {}
+ </body>
+</html>""".format(doc)))
+
+
+id_html = inline("<p id=foo></p>", doctype="html")
+id_xhtml = inline('<p id="foo"></p>', doctype="xhtml")
+parent_child_html = inline("<div id=parent><p id=child></p></div>", doctype="html")
+parent_child_xhtml = inline('<div id="parent"><p id="child"></p></div>', doctype="xhtml")
+children_html = inline("<div><p>foo <p>bar</div>", doctype="html")
+children_xhtml = inline("<div><p>foo</p> <p>bar</p></div>", doctype="xhtml")
+class_html = inline("<p class='foo bar'>", doctype="html")
+class_xhtml = inline('<p class="foo bar"></p>', doctype="xhtml")
+name_html = inline("<p name=foo>", doctype="html")
+name_xhtml = inline('<p name="foo"></p>', doctype="xhtml")
+link_html = inline("<p><a href=#>foo bar</a>", doctype="html")
+link_html_with_trailing_space = inline("<p><a href=#>a link with a trailing space </a>")
+link_xhtml = inline('<p><a href="#">foo bar</a></p>', 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"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>XHTML might be the future</title>
+ </head>
+
+ <body>
+ {}
+ </body>
+</html>""".format(doc)))
+
+
+attribute = inline("<input foo=bar>")
+input = inline("<input>")
+disabled = inline("<input disabled=baz>")
+check = inline("<input type=checkbox>")
+
+
+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("<p style=foo>"))
+ 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("<p hidden>foo"))
+ el = self.marionette.find_element(By.TAG_NAME, "p")
+ attr = el.get_attribute("hidden")
+ self.assertIsInstance(attr, types.StringTypes)
+ self.assertEqual("true", attr)
+
+ self.marionette.navigate(inline("<p>foo"))
+ el = self.marionette.find_element(By.TAG_NAME, "p")
+ attr = el.get_attribute("hidden")
+ self.assertIsNone(attr)
+
+ self.marionette.navigate(inline("<p itemscope>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("<p>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("<p hidden=\"true\">foo</p>", 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("<p>foo</p> <p>bar</p>")
+
+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("""
+ <script>
+ window.n = 0;
+ setTimeout(() => ++window.n, 4000);
+ </script>"""))
+
+ # 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("""
+ <script>
+ window.addEventListener = (type, handler) => handler.toString();
+ </script>"""))
+ 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("<p>foo <p>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("<p>foo <p>bar"))
+ els = self.marionette.execute_script("return document.all")
+ self.assertIsInstance(els, list)
+ # <html>, <head>, <body>, <p>, <p>
+ self.assertEqual(5, len(els))
+ self.assertSequenceIsInstance(els, HTMLElement)
+
+ def test_html_collection(self):
+ self.marionette.navigate(inline("<p>foo <p>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("<form><input><input></form>"))
+ 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("<select><option><option></select>"))
+ 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("<p>foo <p>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("""<p>foo</p>""")
+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("<p style='display: none'>hidden</p>")
+
+selected_element = inline("<option selected>selected</option>")
+unselected_element = inline("<option>unselected</option>")
+
+enabled_element = inline("<input>")
+disabled_element = inline("<input disabled>")
+
+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("<input type=file>"))
+multiple = "data:text/html,{}".format(urllib.quote("<input type=file multiple>"))
+upload = lambda url: "data:text/html,{}".format(urllib.quote("""
+ <form action='{}' method=post enctype='multipart/form-data'>
+ <input type=file>
+ <input type=submit>
+ </form>""".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,<html></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("<p>foobar</p>")},
+ {"url": self.test_page},
+ {"url": inline("<p>foobar</p>")},
+ ]
+ 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("<p id=foo>")
+ 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("<input autofocus>"))
+ 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("<a href=%s>https is the future</a>" % 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("<html" in source)
+ self.assertTrue("PageSource" in source)
+ self.assertEqual(source, from_web_api)
+
+ def testShouldReturnTheSourceOfAPageWhenThereAreUnicodeChars(self):
+ test_html = self.marionette.absolute_url("testPageSourceWithUnicodeChars.html")
+ self.marionette.navigate(test_html)
+ # if we don't throw on the next line we are good!
+ source = self.marionette.page_source
+ from_web_api = self.marionette.execute_script("return document.documentElement.outerHTML")
+ self.assertEqual(source, from_web_api)
+
+ def testShouldReturnAXMLDocumentSource(self):
+ test_xml = self.marionette.absolute_url("testPageSource.xml")
+ self.marionette.navigate(test_xml)
+ source = self.marionette.page_source
+ from_web_api = self.marionette.execute_script("return document.documentElement.outerHTML")
+ import re
+ self.assertEqual(re.sub("\s", "", source), "<xml><foo><bar>baz</bar></foo></xml>")
+ 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('<textbox id="textInput"' in source)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_position.py b/testing/marionette/harness/marionette_harness/tests/unit/test_position.py
new file mode 100644
index 000000000..2cc4d5947
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_position.py
@@ -0,0 +1,19 @@
+# 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 TestPosition(MarionetteTestCase):
+
+ def test_should_get_element_position_back(self):
+ test_url = self.marionette.absolute_url('rectangles.html')
+ self.marionette.navigate(test_url)
+
+ r2 = self.marionette.find_element(By.ID, "r2")
+ location = r2.rect
+ self.assertEqual(11, location['x'])
+ self.assertEqual(10, location['y'])
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
new file mode 100644
index 000000000..9cfbe1df1
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
@@ -0,0 +1,167 @@
+# 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 TestPreferences(MarionetteTestCase):
+ prefs = {
+ "bool": "marionette.test.bool",
+ "int": "marionette.test.int",
+ "string": "marionette.test.string",
+ }
+
+ def tearDown(self):
+ for pref in self.prefs.values():
+ self.marionette.clear_pref(pref)
+
+ super(TestPreferences, self).tearDown()
+
+ def test_clear_pref(self):
+ self.assertIsNone(self.marionette.get_pref(self.prefs["bool"]))
+
+ self.marionette.set_pref(self.prefs["bool"], True)
+ self.assertTrue(self.marionette.get_pref(self.prefs["bool"]))
+
+ self.marionette.clear_pref(self.prefs["bool"])
+ self.assertIsNone(self.marionette.get_pref(self.prefs["bool"]))
+
+ def test_get_and_set_pref(self):
+ # By default none of the preferences are set
+ self.assertIsNone(self.marionette.get_pref(self.prefs["bool"]))
+ self.assertIsNone(self.marionette.get_pref(self.prefs["int"]))
+ self.assertIsNone(self.marionette.get_pref(self.prefs["string"]))
+
+ # Test boolean values
+ self.marionette.set_pref(self.prefs["bool"], True)
+ value = self.marionette.get_pref(self.prefs["bool"])
+ self.assertTrue(value)
+ self.assertEqual(type(value), bool)
+
+ # Test int values
+ self.marionette.set_pref(self.prefs["int"], 42)
+ value = self.marionette.get_pref(self.prefs["int"])
+ self.assertEqual(value, 42)
+ self.assertEqual(type(value), int)
+
+ # Test string values
+ self.marionette.set_pref(self.prefs["string"], "abc")
+ value = self.marionette.get_pref(self.prefs["string"])
+ self.assertEqual(value, "abc")
+ self.assertTrue(isinstance(value, basestring))
+
+ # Test reset value
+ self.marionette.set_pref(self.prefs["string"], None)
+ self.assertIsNone(self.marionette.get_pref(self.prefs["string"]))
+
+ def test_get_set_pref_default_branch(self):
+ pref_default = "marionette.test.pref_default1"
+ self.assertIsNone(self.marionette.get_pref(self.prefs["string"]))
+
+ self.marionette.set_pref(pref_default, "default_value", default_branch=True)
+ self.assertEqual(self.marionette.get_pref(pref_default), "default_value")
+ self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True),
+ "default_value")
+
+ self.marionette.set_pref(pref_default, "user_value")
+ self.assertEqual(self.marionette.get_pref(pref_default), "user_value")
+ self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True),
+ "default_value")
+
+ self.marionette.clear_pref(pref_default)
+ self.assertEqual(self.marionette.get_pref(pref_default), "default_value")
+
+ def test_get_pref_value_type(self):
+ # Without a given value type the properties URL will be returned only
+ pref_complex = "browser.menu.showCharacterEncoding"
+ properties_file = "chrome://browser/locale/browser.properties"
+ self.assertEqual(self.marionette.get_pref(pref_complex, default_branch=True),
+ properties_file)
+
+ # Otherwise the property named like the pref will be translated
+ value = self.marionette.get_pref(pref_complex, default_branch=True,
+ value_type="nsIPrefLocalizedString")
+ self.assertNotEqual(value, properties_file)
+
+ def test_set_prefs(self):
+ # By default none of the preferences are set
+ self.assertIsNone(self.marionette.get_pref(self.prefs["bool"]))
+ self.assertIsNone(self.marionette.get_pref(self.prefs["int"]))
+ self.assertIsNone(self.marionette.get_pref(self.prefs["string"]))
+
+ # Set a value on the default branch first
+ pref_default = "marionette.test.pref_default2"
+ self.assertIsNone(self.marionette.get_pref(pref_default))
+ self.marionette.set_prefs({pref_default: "default_value"}, default_branch=True)
+
+ # Set user values
+ prefs = {self.prefs["bool"]: True, self.prefs["int"]: 42,
+ self.prefs["string"]: "abc", pref_default: "user_value"}
+ self.marionette.set_prefs(prefs)
+
+ self.assertTrue(self.marionette.get_pref(self.prefs["bool"]))
+ self.assertEqual(self.marionette.get_pref(self.prefs["int"]), 42)
+ self.assertEqual(self.marionette.get_pref(self.prefs["string"]), "abc")
+ self.assertEqual(self.marionette.get_pref(pref_default), "user_value")
+ self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True),
+ "default_value")
+
+ def test_using_prefs(self):
+ # Test that multiple preferences can be set with "using_prefs", and that
+ # they are set correctly and unset correctly after leaving the context
+ # manager.
+ pref_not_existent = "marionette.test.not_existent1"
+ pref_default = "marionette.test.pref_default3"
+
+ self.marionette.set_prefs({self.prefs["string"]: "abc",
+ self.prefs["int"]: 42,
+ self.prefs["bool"]: False,
+ })
+ self.assertFalse(self.marionette.get_pref(self.prefs["bool"]))
+ self.assertEqual(self.marionette.get_pref(self.prefs["int"]), 42)
+ self.assertEqual(self.marionette.get_pref(self.prefs["string"]), "abc")
+ self.assertIsNone(self.marionette.get_pref(pref_not_existent))
+
+ with self.marionette.using_prefs({self.prefs["bool"]: True,
+ self.prefs["int"]: 24,
+ self.prefs["string"]: "def",
+ pref_not_existent: "existent"}):
+
+ self.assertTrue(self.marionette.get_pref(self.prefs["bool"]), True)
+ self.assertEquals(self.marionette.get_pref(self.prefs["int"]), 24)
+ self.assertEquals(self.marionette.get_pref(self.prefs["string"]), "def")
+ self.assertEquals(self.marionette.get_pref(pref_not_existent), "existent")
+
+ self.assertFalse(self.marionette.get_pref(self.prefs["bool"]))
+ self.assertEqual(self.marionette.get_pref(self.prefs["int"]), 42)
+ self.assertEqual(self.marionette.get_pref(self.prefs["string"]), "abc")
+ self.assertIsNone(self.marionette.get_pref(pref_not_existent))
+
+ # Using context with default branch
+ self.marionette.set_pref(pref_default, "default_value", default_branch=True)
+ self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True),
+ "default_value")
+
+ with self.marionette.using_prefs({pref_default: "new_value"}, default_branch=True):
+ self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True),
+ "new_value")
+
+ self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True),
+ "default_value")
+
+ def test_using_prefs_exception(self):
+ # Test that throwing an exception inside the context manager doesn"t
+ # prevent the preferences from being restored at context manager exit.
+ self.marionette.set_pref(self.prefs["string"], "abc")
+
+ try:
+ with self.marionette.using_prefs({self.prefs["string"]: "def"}):
+ self.assertEquals(self.marionette.get_pref(self.prefs["string"]), "def")
+ self.marionette.execute_script("return foo.bar.baz;")
+ except JavascriptException:
+ pass
+
+ self.assertEquals(self.marionette.get_pref(self.prefs["string"]), "abc")
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py b/testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py
new file mode 100644
index 000000000..f8ec952b2
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.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_harness import MarionetteTestCase
+
+
+class TestProfileManagement(MarionetteTestCase):
+
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+ self.marionette.enforce_gecko_prefs(
+ {"marionette.test.bool": True,
+ "marionette.test.string": "testing",
+ "marionette.test.int": 3
+ })
+ self.marionette.set_context("chrome")
+
+ def test_preferences_are_set(self):
+ self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
+ self.assertEqual(self.marionette.get_pref("marionette.test.string"), "testing")
+ self.assertEqual(self.marionette.get_pref("marionette.test.int"), 3)
+
+ def test_change_preference(self):
+ self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
+
+ self.marionette.enforce_gecko_prefs({"marionette.test.bool": False})
+
+ self.assertFalse(self.marionette.get_pref("marionette.test.bool"))
+
+ def test_clean_profile(self):
+ self.marionette.restart(clean=True)
+
+ self.assertEqual(self.marionette.get_pref("marionette.test.bool"), None)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py b/testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py
new file mode 100644
index 000000000..887d69b76
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py
@@ -0,0 +1,252 @@
+# 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 InvalidArgumentException
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestProxy(MarionetteTestCase):
+
+ def setUp(self):
+ super(TestProxy, self).setUp()
+ self.marionette.delete_session()
+
+ def test_that_we_can_set_a_autodetect_proxy(self):
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":{
+ "proxyType": "autodetect",
+ }
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = None
+ with self.marionette.using_context('chrome'):
+ result = self.marionette.execute_script("""return {
+ "proxyType" : Services.prefs.getIntPref('network.proxy.type'),
+ }
+ """)
+
+ self.assertEqual(result["proxyType"], 4)
+
+ def test_that_capabilities_returned_have_proxy_details(self):
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":{
+ "proxyType": "autodetect",
+ }
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = self.marionette.session_capabilities
+
+ self.assertEqual(result["proxy"]["proxyType"], "autodetect")
+
+ def test_that_we_can_set_a_system_proxy(self):
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":{
+ "proxyType": "system",
+ }
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = None
+ with self.marionette.using_context('chrome'):
+ result = self.marionette.execute_script("""return {
+ "proxyType" : Services.prefs.getIntPref('network.proxy.type'),
+ }
+ """)
+
+ self.assertEqual(result["proxyType"], 5)
+
+ def test_we_can_set_a_pac_proxy(self):
+ url = "http://marionette.test"
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":{
+ "proxyType": "pac",
+ "proxyAutoconfigUrl": url,
+ }
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = None
+ with self.marionette.using_context('chrome'):
+ result = self.marionette.execute_script("""return {
+ "proxyType" : Services.prefs.getIntPref('network.proxy.type'),
+ "proxyAutoconfigUrl" : Services.prefs.getCharPref('network.proxy.autoconfig_url'),
+ }
+ """)
+
+ self.assertEqual(result["proxyType"], 2)
+ self.assertEqual(result["proxyAutoconfigUrl"], url, 'proxyAutoconfigUrl was not set')
+
+ def test_that_we_can_set_a_manual_proxy(self):
+ port = 4444
+ url = "http://marionette.test"
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":{
+ "proxyType": "manual",
+ "ftpProxy": url,
+ "ftpProxyPort": port,
+ "httpProxy": url,
+ "httpProxyPort": port,
+ "sslProxy": url,
+ "sslProxyPort": port,
+ }
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = None
+ with self.marionette.using_context('chrome'):
+ result = self.marionette.execute_script("""return {
+ "proxyType" : Services.prefs.getIntPref('network.proxy.type'),
+ "httpProxy" : Services.prefs.getCharPref('network.proxy.http'),
+ "httpProxyPort": Services.prefs.getIntPref('network.proxy.http_port'),
+ "sslProxy": Services.prefs.getCharPref('network.proxy.ssl'),
+ "sslProxyPort": Services.prefs.getIntPref('network.proxy.ssl_port'),
+ "ftpProxy": Services.prefs.getCharPref('network.proxy.ftp'),
+ "ftpProxyPort": Services.prefs.getIntPref('network.proxy.ftp_port'),
+ }
+ """)
+
+ self.assertEqual(result["proxyType"], 1)
+ self.assertEqual(result["httpProxy"], url, 'httpProxy was not set')
+ self.assertEqual(result["httpProxyPort"], port, 'httpProxyPort was not set')
+ self.assertEqual(result["sslProxy"], url, 'sslProxy url was not set')
+ self.assertEqual(result["sslProxyPort"], port, 'sslProxyPort was not set')
+ self.assertEqual(result["ftpProxy"], url, 'ftpProxy was not set')
+ self.assertEqual(result["ftpProxyPort"], port, 'ftpProxyPort was not set')
+
+ def test_we_can_set_a_manual_proxy_with_a_socks_proxy_with_socks_version(self):
+ port = 4444
+ url = "http://marionette.test"
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":{
+ "proxyType": "manual",
+ "socksProxy": url,
+ "socksProxyPort": port,
+ "socksVersion": 4,
+ "socksUsername": "cake",
+ "socksPassword": "made with cake"
+ }
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = None
+ with self.marionette.using_context('chrome'):
+ result = self.marionette.execute_script("""return {
+ "proxyType" : Services.prefs.getIntPref('network.proxy.type'),
+ "socksProxy" : Services.prefs.getCharPref('network.proxy.socks'),
+ "socksProxyPort": Services.prefs.getIntPref('network.proxy.socks_port'),
+ "socksVersion": Services.prefs.getIntPref('network.proxy.socks_version'),
+ }
+ """)
+ self.assertEqual(result["socksProxy"], url, 'socksProxy was not set')
+ self.assertEqual(result["socksProxyPort"], port, 'socksProxyPort was not set')
+ self.assertEqual(result["socksVersion"], 4, 'socksVersion was not set to 4')
+
+ def test_we_can_set_a_manual_proxy_with_a_socks_proxy_with_no_socks_version(self):
+ port = 4444
+ url = "http://marionette.test"
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":{
+ "proxyType": "manual",
+ "socksProxy": url,
+ "socksProxyPort": port,
+ "socksUsername": "cake",
+ "socksPassword": "made with cake"
+ }
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = None
+ with self.marionette.using_context('chrome'):
+ result = self.marionette.execute_script("""return {
+ "proxyType" : Services.prefs.getIntPref('network.proxy.type'),
+ "socksProxy" : Services.prefs.getCharPref('network.proxy.socks'),
+ "socksProxyPort": Services.prefs.getIntPref('network.proxy.socks_port'),
+ "socksVersion": Services.prefs.getIntPref('network.proxy.socks_version'),
+
+ }
+ """)
+ self.assertEqual(result["socksProxy"], url, 'socksProxy was not set')
+ self.assertEqual(result["socksProxyPort"], port, 'socksProxyPort was not set')
+ self.assertEqual(result["socksVersion"], 5, 'socksVersion was not set to 5')
+
+ def test_when_not_all_manual_proxy_details_are_in_capabilities(self):
+ port = 4444
+ url = "http://marionette.test"
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":{
+ "proxyType": "manual",
+ "ftpProxy": url,
+ "ftpProxyPort": port,
+ }
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = None
+ with self.marionette.using_context('chrome'):
+ result = self.marionette.execute_script("""return {
+ "proxyType" : Services.prefs.getIntPref('network.proxy.type'),
+ "httpProxy" : Services.prefs.getCharPref('network.proxy.http'),
+ "httpProxyPort": Services.prefs.getIntPref('network.proxy.http_port'),
+ "sslProxy": Services.prefs.getCharPref('network.proxy.ssl'),
+ "sslProxyPort": Services.prefs.getIntPref('network.proxy.ssl_port'),
+ "ftpProxy": Services.prefs.getCharPref('network.proxy.ftp'),
+ "ftpProxyPort": Services.prefs.getIntPref('network.proxy.ftp_port'),
+ }
+ """)
+
+ self.assertEqual(result["proxyType"], 1)
+ self.assertNotEqual(result["httpProxy"], url,
+ 'httpProxy was set. {}'.format(result["httpProxy"]))
+ self.assertNotEqual(result["httpProxyPort"], port, 'httpProxyPort was set')
+ self.assertNotEqual(result["sslProxy"], url, 'sslProxy url was set')
+ self.assertNotEqual(result["sslProxyPort"], port, 'sslProxyPort was set')
+ self.assertEqual(result["ftpProxy"], url, 'ftpProxy was set')
+ self.assertEqual(result["ftpProxyPort"], port, 'ftpProxyPort was set')
+
+
+
+ def test_proxy_is_a_string_should_throw_invalid_argument(self):
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy":"I really should be a dictionary"
+ }
+ }
+ try:
+ self.marionette.start_session(capabilities)
+ self.fail("We should have started a session because proxy should be a dict")
+ except InvalidArgumentException as e:
+ assert e.message == "Value of 'proxy' should be an object"
+
+ def test_proxy_is_passed_in_with_no_proxy_doesnt_set_it(self):
+ capabilities = {"requiredCapabilities":
+ {
+ "proxy": {"proxyType": "NOPROXY"},
+ }
+ }
+ self.marionette.start_session(capabilities)
+ result = None
+ with self.marionette.using_context('chrome'):
+ result = self.marionette.execute_script("""return {
+ "proxyType": Services.prefs.getIntPref('network.proxy.type'),
+ };
+ """)
+
+ self.assertEqual(result["proxyType"], 0)
+
+ def tearDown(self):
+ if not self.marionette.session:
+ self.marionette.start_session()
+ else:
+ self.marionette.restart(clean=True)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py b/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py
new file mode 100644
index 000000000..38c678556
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py
@@ -0,0 +1,173 @@
+# 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 MarionetteException
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestQuitRestart(MarionetteTestCase):
+
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+
+ self.pid = self.marionette.process_id
+ self.session_id = self.marionette.session_id
+
+ self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3)
+ self.marionette.set_pref("browser.startup.page", 3)
+
+ def tearDown(self):
+ # Ensure to restart a session if none exist for clean-up
+ if not self.marionette.session:
+ self.marionette.start_session()
+
+ self.marionette.clear_pref("browser.startup.page")
+
+ MarionetteTestCase.tearDown(self)
+
+ def test_force_restart(self):
+ self.marionette.restart()
+ self.assertEqual(self.marionette.session_id, self.session_id)
+
+ # A forced restart will cause a new process id
+ self.assertNotEqual(self.marionette.process_id, self.pid)
+
+ # If a preference value is not forced, a restart will cause a reset
+ self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3)
+
+ def test_force_quit(self):
+ self.marionette.quit()
+
+ self.assertEqual(self.marionette.session, None)
+ with self.assertRaisesRegexp(MarionetteException, "Please start a session"):
+ self.marionette.get_url()
+
+ self.marionette.start_session()
+ self.assertNotEqual(self.marionette.session_id, self.session_id)
+ self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3)
+
+ def test_in_app_clean_restart(self):
+ with self.assertRaises(ValueError):
+ self.marionette.restart(in_app=True, clean=True)
+
+ def test_in_app_restart(self):
+ self.marionette.restart(in_app=True)
+ self.assertEqual(self.marionette.session_id, self.session_id)
+
+ # An in-app restart will keep the same process id only on Linux
+ if self.marionette.session_capabilities['platformName'] == 'linux':
+ self.assertEqual(self.marionette.process_id, self.pid)
+ else:
+ self.assertNotEqual(self.marionette.process_id, self.pid)
+
+ # If a preference value is not forced, a restart will cause a reset
+ self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3)
+
+ def test_in_app_restart_with_callback(self):
+ self.marionette.restart(in_app=True,
+ callback=lambda: self.shutdown(restart=True))
+
+ self.assertEqual(self.marionette.session_id, self.session_id)
+
+ # An in-app restart will keep the same process id only on Linux
+ if self.marionette.session_capabilities['platformName'] == 'linux':
+ self.assertEqual(self.marionette.process_id, self.pid)
+ else:
+ self.assertNotEqual(self.marionette.process_id, self.pid)
+
+ # If a preference value is not forced, a restart will cause a reset
+ self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3)
+
+ def test_in_app_quit(self):
+ self.marionette.quit(in_app=True)
+
+ self.assertEqual(self.marionette.session, None)
+ with self.assertRaisesRegexp(MarionetteException, "Please start a session"):
+ self.marionette.get_url()
+
+ self.marionette.start_session()
+ self.assertNotEqual(self.marionette.session_id, self.session_id)
+ self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3)
+
+ def test_in_app_quit_with_callback(self):
+ self.marionette.quit(in_app=True, callback=self.shutdown)
+ self.assertEqual(self.marionette.session, None)
+ with self.assertRaisesRegexp(MarionetteException, "Please start a session"):
+ self.marionette.get_url()
+
+ self.marionette.start_session()
+ self.assertNotEqual(self.marionette.session_id, self.session_id)
+ self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3)
+
+ def test_reset_context_after_quit_by_set_context(self):
+ # Check that we are in content context which is used by default in Marionette
+ self.assertNotIn('chrome://', self.marionette.get_url(),
+ "Context doesn't default to content")
+
+ self.marionette.set_context('chrome')
+ self.marionette.quit(in_app=True)
+ self.assertEqual(self.marionette.session, None)
+ self.marionette.start_session()
+ self.assertNotIn('chrome://', self.marionette.get_url(),
+ "Not in content context after quit with using_context")
+
+ def test_reset_context_after_quit_by_using_context(self):
+ # Check that we are in content context which is used by default in Marionette
+ self.assertNotIn('chrome://', self.marionette.get_url(),
+ "Context doesn't default to content")
+
+ with self.marionette.using_context('chrome'):
+ self.marionette.quit(in_app=True)
+ self.assertEqual(self.marionette.session, None)
+ self.marionette.start_session()
+ self.assertNotIn('chrome://', self.marionette.get_url(),
+ "Not in content context after quit with using_context")
+
+ def test_keep_context_after_restart_by_set_context(self):
+ # Check that we are in content context which is used by default in Marionette
+ self.assertNotIn('chrome://', self.marionette.get_url(),
+ "Context doesn't default to content")
+
+ # restart while we are in chrome context
+ self.marionette.set_context('chrome')
+ self.marionette.restart(in_app=True)
+
+ # An in-app restart will keep the same process id only on Linux
+ if self.marionette.session_capabilities['platformName'] == 'linux':
+ self.assertEqual(self.marionette.process_id, self.pid)
+ else:
+ self.assertNotEqual(self.marionette.process_id, self.pid)
+
+ self.assertIn('chrome://', self.marionette.get_url(),
+ "Not in chrome context after a restart with set_context")
+
+ def test_keep_context_after_restart_by_using_context(self):
+ # Check that we are in content context which is used by default in Marionette
+ self.assertNotIn('chrome://', self.marionette.get_url(),
+ "Context doesn't default to content")
+
+ # restart while we are in chrome context
+ with self.marionette.using_context('chrome'):
+ self.marionette.restart(in_app=True)
+
+ # An in-app restart will keep the same process id only on Linux
+ if self.marionette.session_capabilities['platformName'] == 'linux':
+ self.assertEqual(self.marionette.process_id, self.pid)
+ else:
+ self.assertNotEqual(self.marionette.process_id, self.pid)
+
+ self.assertIn('chrome://', self.marionette.get_url(),
+ "Not in chrome context after a restart with using_context")
+
+ def shutdown(self, restart=False):
+ self.marionette.set_context("chrome")
+ self.marionette.execute_script("""
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ let flags = Ci.nsIAppStartup.eAttemptQuit
+ if(arguments[0]) {
+ flags |= Ci.nsIAppStartup.eRestart;
+ }
+ Services.startup.quit(flags);
+ """, script_args=[restart])
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py b/testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py
new file mode 100644
index 000000000..508870e91
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py
@@ -0,0 +1,34 @@
+#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.by import By
+
+from marionette_harness import MarionetteTestCase
+
+
+class RenderedElementTests(MarionetteTestCase):
+
+ def testWeCanGetComputedStyleValueOnElement(self):
+ test_url = self.marionette.absolute_url('javascriptPage.html')
+ self.marionette.navigate(test_url)
+ element = self.marionette.find_element(By.ID, "green-parent")
+ backgroundColour = element.value_of_css_property("background-color")
+
+ self.assertEqual("rgb(0, 128, 0)", backgroundColour)
+
+ element = self.marionette.find_element(By.ID, "red-item")
+ backgroundColour = element.value_of_css_property("background-color")
+
+ self.assertEqual("rgb(255, 0, 0)", backgroundColour)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_report.py b/testing/marionette/harness/marionette_harness/tests/unit/test_report.py
new file mode 100644
index 000000000..f22c3db4b
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_report.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, expectedFailure, skip
+
+
+class TestReport(MarionetteTestCase):
+
+ def test_pass(self):
+ assert True
+
+ def test_fail(self):
+ assert False
+
+ @skip('Skip Message')
+ def test_skip(self):
+ assert False
+
+ @expectedFailure
+ def test_expected_fail(self):
+ assert False
+
+ @expectedFailure
+ def test_unexpected_pass(self):
+ assert True
+
+ def test_error(self):
+ raise Exception()
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_run_js_test.py b/testing/marionette/harness/marionette_harness/tests/unit/test_run_js_test.py
new file mode 100644
index 000000000..134223ce1
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_run_js_test.py
@@ -0,0 +1,10 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestRunJSTest(MarionetteTestCase):
+ def test_basic(self):
+ self.run_js_test('test_simpletest_pass.js')
+ self.run_js_test('test_simpletest_fail.js')
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py b/testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py
new file mode 100644
index 000000000..830795a1e
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py
@@ -0,0 +1,86 @@
+# 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 errors
+from mozrunner.devices.emulator_screen import EmulatorScreen
+
+from marionette_harness import MarionetteTestCase, skip_if_desktop, skip_if_mobile
+
+
+default_orientation = "portrait-primary"
+unknown_orientation = "Unknown screen orientation: {}"
+
+
+class TestScreenOrientation(MarionetteTestCase):
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+ self.is_mobile = self.marionette.session_capabilities.get("rotatable", False)
+
+ def tearDown(self):
+ if self.is_mobile:
+ self.marionette.set_orientation(default_orientation)
+ self.assertEqual(self.marionette.orientation, default_orientation, "invalid state")
+ MarionetteTestCase.tearDown(self)
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_orientation_to_portrait_primary(self):
+ self.marionette.set_orientation("portrait-primary")
+ new_orientation = self.marionette.orientation
+ self.assertEqual(new_orientation, "portrait-primary")
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_orientation_to_landscape_primary(self):
+ self.marionette.set_orientation("landscape-primary")
+ new_orientation = self.marionette.orientation
+ self.assertEqual(new_orientation, "landscape-primary")
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_orientation_to_portrait_secondary(self):
+ self.marionette.set_orientation("portrait-secondary")
+ new_orientation = self.marionette.orientation
+ self.assertEqual(new_orientation, "portrait-secondary")
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_orientation_to_landscape_secondary(self):
+ self.marionette.set_orientation("landscape-secondary")
+ new_orientation = self.marionette.orientation
+ self.assertEqual(new_orientation, "landscape-secondary")
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_orientation_to_shorthand_portrait(self):
+ # Set orientation to something other than portrait-primary first, since the default is
+ # portrait-primary.
+ self.marionette.set_orientation("landscape-primary")
+ self.assertEqual(self.marionette.orientation, "landscape-primary", "invalid state")
+
+ self.marionette.set_orientation("portrait")
+ new_orientation = self.marionette.orientation
+ self.assertEqual(new_orientation, "portrait-primary")
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_orientation_to_shorthand_landscape(self):
+ self.marionette.set_orientation("landscape")
+ new_orientation = self.marionette.orientation
+ self.assertEqual(new_orientation, "landscape-primary")
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_orientation_with_mixed_casing(self):
+ self.marionette.set_orientation("lAnDsCaPe")
+ new_orientation = self.marionette.orientation
+ self.assertEqual(new_orientation, "landscape-primary")
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_invalid_orientation(self):
+ with self.assertRaisesRegexp(errors.MarionetteException, unknown_orientation.format("cheese")):
+ self.marionette.set_orientation("cheese")
+
+ @skip_if_desktop("Not supported in Firefox")
+ def test_set_null_orientation(self):
+ with self.assertRaisesRegexp(errors.MarionetteException, unknown_orientation.format("null")):
+ self.marionette.set_orientation(None)
+
+ @skip_if_mobile("Specific test for Firefox")
+ def test_unsupported_operation_on_desktop(self):
+ with self.assertRaises(errors.UnsupportedOperationException):
+ self.marionette.set_orientation("landscape-primary")
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
new file mode 100644
index 000000000..aa0e6ab1c
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
@@ -0,0 +1,428 @@
+# 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 base64
+import hashlib
+import imghdr
+import struct
+import urllib
+
+from marionette_driver import By
+from marionette_driver.errors import JavascriptException, NoSuchWindowException
+from marionette_harness import (
+ MarionetteTestCase,
+ skip,
+ skip_if_mobile,
+ WindowManagerMixin,
+)
+
+
+def inline(doc, mime="text/html;charset=utf-8"):
+ return "data:{0},{1}".format(mime, urllib.quote(doc))
+
+
+box = inline("<body><div id='box'><p id='green' style='width: 50px; height: 50px; "
+ "background: silver;'></p></div></body>")
+input = inline("<body><input id='text-input'></input></body>")
+long = inline("<body style='height: 300vh'><p style='margin-top: 100vh'>foo</p></body>")
+short = inline("<body style='height: 10vh'></body>")
+svg = inline("""
+ <svg xmlns="http://www.w3.org/2000/svg" height="20" width="20">
+ <rect height="20" width="20"/>
+ </svg>""", 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(), "<option> element not selected")
+ self.assertTrue(self.marionette.execute_script(
+ "return arguments[0].selected", script_args=[option_element], sandbox=None),
+ "<option> selected attribute not updated")
+
+ def assertNotSelected(self, option_element):
+ self.assertFalse(option_element.is_selected(), "<option> is selected")
+ self.assertFalse(self.marionette.execute_script(
+ "return arguments[0].selected", script_args=[option_element], sandbox=None),
+ "<option> selected attribute not updated")
+
+
+class TestSelect(SelectTestCase):
+ def test_single(self):
+ self.marionette.navigate(inline("""
+ <select>
+ <option>first
+ <option>second
+ </select>"""))
+ select = self.marionette.find_element(By.TAG_NAME, "select")
+ options = self.marionette.find_elements(By.TAG_NAME, "option")
+
+ self.assertSelected(options[0])
+ options[1].click()
+ self.assertSelected(options[1])
+
+ def test_deselect(self):
+ self.marionette.navigate(inline("""
+ <select>
+ <option>first
+ <option>second
+ <option>third
+ </select>"""))
+ select = self.marionette.find_element(By.TAG_NAME, "select")
+ options = self.marionette.find_elements(By.TAG_NAME, "option")
+
+ options[0].click()
+ self.assertSelected(options[0])
+ options[1].click()
+ self.assertSelected(options[1])
+ options[2].click()
+ self.assertSelected(options[2])
+ options[0].click()
+ self.assertSelected(options[0])
+
+ def test_out_of_view(self):
+ self.marionette.navigate(inline("""
+ <select>
+ <option>1
+ <option>2
+ <option>3
+ <option>4
+ <option>5
+ <option>6
+ <option>7
+ <option>8
+ <option>9
+ <option>10
+ <option>11
+ <option>12
+ <option>13
+ <option>14
+ <option>15
+ <option>16
+ <option>17
+ <option>18
+ <option>19
+ <option>20
+ </select>"""))
+ select = self.marionette.find_element(By.TAG_NAME, "select")
+ options = self.marionette.find_elements(By.TAG_NAME, "option")
+
+ options[14].click()
+ self.assertSelected(options[14])
+
+
+class TestSelectMultiple(SelectTestCase):
+ def test_single(self):
+ self.marionette.navigate(inline("<select multiple> <option>first </select>"))
+ option = self.marionette.find_element(By.TAG_NAME, "option")
+ option.click()
+ self.assertSelected(option)
+
+ def test_multiple(self):
+ self.marionette.navigate(inline("""
+ <select multiple>
+ <option>first
+ <option>second
+ <option>third
+ </select>"""))
+ select = self.marionette.find_element(By.TAG_NAME, "select")
+ options = select.find_elements(By.TAG_NAME, "option")
+
+ options[1].click()
+ self.assertSelected(options[1])
+
+ options[2].click()
+ self.assertSelected(options[2])
+ self.assertSelected(options[1])
+
+ def test_deselect_selected(self):
+ self.marionette.navigate(inline("<select multiple> <option>first </select>"))
+ option = self.marionette.find_element(By.TAG_NAME, "option")
+ option.click()
+ self.assertSelected(option)
+ option.click()
+ self.assertNotSelected(option)
+
+ def test_deselect_preselected(self):
+ self.marionette.navigate(inline("""
+ <select multiple>
+ <option selected>first
+ </select>"""))
+ option = self.marionette.find_element(By.TAG_NAME, "option")
+ self.assertSelected(option)
+ option.click()
+ self.assertNotSelected(option)
+
+ def test_out_of_view(self):
+ self.marionette.navigate(inline("""
+ <select multiple>
+ <option>1
+ <option>2
+ <option>3
+ <option>4
+ <option>5
+ <option>6
+ <option>7
+ <option>8
+ <option>9
+ <option>10
+ <option>11
+ <option>12
+ <option>13
+ <option>14
+ <option>15
+ <option>16
+ <option>17
+ <option>18
+ <option>19
+ <option>20
+ </select>"""))
+ select = self.marionette.find_element(By.TAG_NAME, "select")
+ options = self.marionette.find_elements(By.TAG_NAME, "option")
+
+ options[-1].click()
+ self.assertSelected(options[-1])
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_session.py b/testing/marionette/harness/marionette_harness/tests/unit/test_session.py
new file mode 100644
index 000000000..1676df51f
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_session.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 errors
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestSession(MarionetteTestCase):
+ def setUp(self):
+ super(TestSession, self).setUp()
+ self.marionette.delete_session()
+
+ def test_new_session_returns_capabilities(self):
+ # Sends newSession
+ caps = self.marionette.start_session()
+
+ # Check that session was created. This implies the server
+ # sent us the sessionId and status fields.
+ self.assertIsNotNone(self.marionette.session)
+
+ # Required capabilities mandated by WebDriver spec
+ self.assertIn("browserName", caps)
+ self.assertIn("browserVersion", caps)
+ self.assertIn("platformName", caps)
+ self.assertIn("platformVersion", caps)
+
+ # Optional capabilities we want Marionette to support
+ self.assertIn("rotatable", caps)
+
+ def test_get_session_id(self):
+ # Sends newSession
+ self.marionette.start_session()
+
+ self.assertTrue(self.marionette.session_id is not None)
+ self.assertTrue(isinstance(self.marionette.session_id, unicode))
+
+ def test_set_the_session_id(self):
+ # Sends newSession
+ self.marionette.start_session(session_id="ILoveCheese")
+
+ self.assertEqual(self.marionette.session_id, "ILoveCheese")
+ self.assertTrue(isinstance(self.marionette.session_id, unicode))
+
+ def test_session_already_started(self):
+ self.marionette.start_session()
+ self.assertTrue(isinstance(self.marionette.session_id, unicode))
+ with self.assertRaises(errors.SessionNotCreatedException):
+ self.marionette._send_message("newSession", {})
+
+ def test_no_session(self):
+ with self.assertRaises(errors.InvalidSessionIdException):
+ self.marionette.get_url()
+ self.marionette.start_session()
+ self.marionette.get_url()
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_set_window_size.py b/testing/marionette/harness/marionette_harness/tests/unit/test_set_window_size.py
new file mode 100644
index 000000000..e1bd5e684
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_set_window_size.py
@@ -0,0 +1,84 @@
+# 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 TestSetWindowSize(MarionetteTestCase):
+ def setUp(self):
+ super(MarionetteTestCase, self).setUp()
+ self.start_size = self.marionette.window_size
+ self.max_width = self.marionette.execute_script("return window.screen.availWidth;")
+ self.max_height = self.marionette.execute_script("return window.screen.availHeight;")
+
+ def tearDown(self):
+ # WebDriver spec says a resize cannot result in window being maximized, an
+ # error is returned if that is the case; therefore if the window is maximized
+ # at the start of this test, returning to the original size via set_window_size
+ # size will result in error; so reset to original size minus 1 pixel width
+ if self.start_size['width'] == self.max_width and self.start_size['height'] == self.max_height:
+ self.start_size['width']-=1
+ self.marionette.set_window_size(self.start_size['width'], self.start_size['height'])
+ super(MarionetteTestCase, self).tearDown()
+
+ def test_that_we_can_get_and_set_window_size(self):
+ # event handler
+ self.marionette.execute_script("""
+ window.wrappedJSObject.rcvd_event = false;
+ window.onresize = function() {
+ window.wrappedJSObject.rcvd_event = true;
+ };
+ """)
+
+ # valid size
+ width = self.max_width - 100
+ height = self.max_height - 100
+ self.marionette.set_window_size(width, height)
+ self.wait_for_condition(lambda m: m.execute_script("return window.wrappedJSObject.rcvd_event;"))
+ size = self.marionette.window_size
+ self.assertEqual(size['width'], width,
+ "Window width is {0} but should be {1}".format(size['width'], width))
+ self.assertEqual(size['height'], height,
+ "Window height is {0} but should be {1}".format(size['height'], height))
+
+ def test_that_we_can_get_new_size_when_set_window_size(self):
+ actual = self.marionette.window_size
+ width = actual['width'] - 50
+ height = actual['height'] - 50
+ size = self.marionette.set_window_size(width, height)
+ self.assertIsNotNone(size, "Response is None")
+ self.assertEqual(size['width'], width,
+ "New width is {0} but should be {1}".format(size['width'], width))
+ self.assertEqual(size['height'], height,
+ "New height is {0} but should be {1}".format(size['height'], height))
+
+ def test_possible_to_request_window_larger_than_screen(self):
+ self.marionette.set_window_size(4 * self.max_width, 4 * self.max_height)
+ size = self.marionette.window_size
+
+ # In X the window size may be greater than the bounds of the screen
+ self.assertGreaterEqual(size["width"], self.max_width)
+ self.assertGreaterEqual(size["height"], self.max_height)
+
+ def test_that_we_can_maximise_the_window(self):
+ # valid size
+ width = self.max_width - 100
+ height = self.max_height - 100
+ self.marionette.set_window_size(width, height)
+
+ # event handler
+ self.marionette.execute_script("""
+ window.wrappedJSObject.rcvd_event = false;
+ window.onresize = function() {
+ window.wrappedJSObject.rcvd_event = true;
+ };
+ """)
+ self.marionette.maximize_window()
+ self.wait_for_condition(lambda m: m.execute_script("return window.wrappedJSObject.rcvd_event;"))
+
+ size = self.marionette.window_size
+ self.assertGreaterEqual(size['width'], self.max_width,
+ "Window width does not use availWidth, current width: {0}, max width: {1}".format(size['width'], self.max_width))
+ self.assertGreaterEqual(size['height'], self.max_height,
+ "Window height does not use availHeight. current width: {0}, max width: {1}".format(size['height'], self.max_height))
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_shadow_dom.py b/testing/marionette/harness/marionette_harness/tests/unit/test_shadow_dom.py
new file mode 100644
index 000000000..3f91d7cc0
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_shadow_dom.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/.
+
+from marionette_driver.by import By
+from marionette_driver.errors import (
+ NoSuchElementException,
+ StaleElementException,
+ UnsupportedOperationException,
+)
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestShadowDom(MarionetteTestCase):
+
+ def setUp(self):
+ super(TestShadowDom, self).setUp()
+ self.marionette.set_pref("dom.webcomponents.enabled", True)
+ self.marionette.navigate(self.marionette.absolute_url("test_shadow_dom.html"))
+
+ self.host = self.marionette.find_element(By.ID, "host")
+ self.marionette.switch_to_shadow_root(self.host)
+ self.button = self.marionette.find_element(By.ID, "button")
+
+ def tearDown(self):
+ self.marionette.clear_pref("dom.webcomponents.enabled")
+ super(TestShadowDom, self).tearDown()
+
+ def test_chrome_error(self):
+ with self.marionette.using_context("chrome"):
+ self.assertRaises(UnsupportedOperationException,
+ self.marionette.switch_to_shadow_root)
+
+ def test_shadow_dom(self):
+ # Button in shadow root should be actionable
+ self.button.click()
+
+ def test_shadow_dom_after_switch_away_from_shadow_root(self):
+ # Button in shadow root should be actionable
+ self.button.click()
+ self.marionette.switch_to_shadow_root()
+ # After switching back to top content, button should be stale
+ self.assertRaises(StaleElementException, self.button.click)
+
+ def test_shadow_dom_raises_stale_element_exception_when_button_remove(self):
+ self.marionette.execute_script(
+ 'document.getElementById("host").shadowRoot.getElementById("button").remove();')
+ # After removing button from shadow DOM, button should be stale
+ self.assertRaises(StaleElementException, self.button.click)
+
+ def test_shadow_dom_raises_stale_element_exception_when_host_removed(self):
+ self.marionette.execute_script('document.getElementById("host").remove();')
+ # After removing shadow DOM host element, button should be stale
+ self.assertRaises(StaleElementException, self.button.click)
+
+ def test_non_existent_shadow_dom(self):
+ # Jump back to top level content
+ self.marionette.switch_to_shadow_root()
+ # When no ShadowRoot is found, switch_to_shadow_root throws NoSuchElementException
+ self.assertRaises(NoSuchElementException, self.marionette.switch_to_shadow_root,
+ self.marionette.find_element(By.ID, "empty-host"))
+
+ def test_inner_shadow_dom(self):
+ # Button in shadow root should be actionable
+ self.button.click()
+ self.inner_host = self.marionette.find_element(By.ID, "inner-host")
+ self.marionette.switch_to_shadow_root(self.inner_host)
+ self.inner_button = self.marionette.find_element(By.ID, "inner-button")
+ # Nested nutton in nested shadow root should be actionable
+ self.inner_button.click()
+ self.marionette.switch_to_shadow_root()
+ # After jumping back to parent shadow root, button should again be actionable but inner
+ # button should now be stale
+ self.button.click()
+ self.assertRaises(StaleElementException, self.inner_button.click)
+ self.marionette.switch_to_shadow_root()
+ # After switching back to top content, both buttons should now be stale
+ self.assertRaises(StaleElementException, self.button.click)
+ self.assertRaises(StaleElementException, self.inner_button.click)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_chrome.js b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_chrome.js
new file mode 100644
index 000000000..d5edffa67
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_chrome.js
@@ -0,0 +1,12 @@
+/* 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/. */
+
+MARIONETTE_TIMEOUT = 1000;
+MARIONETTE_CONTEXT = 'chrome';
+
+is(2, 2, "test for is()");
+isnot(2, 3, "test for isnot()");
+ok(2 == 2, "test for ok()");
+finish();
+
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_fail.js b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_fail.js
new file mode 100644
index 000000000..16d9aea59
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_fail.js
@@ -0,0 +1,16 @@
+/* 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/. */
+
+MARIONETTE_TIMEOUT = 1000;
+
+/* this test will fail */
+
+setTimeout(function() {
+ is(1, 2, "is(1,2) should fail", TEST_UNEXPECTED_FAIL, TEST_PASS);
+ finish();
+}, 100);
+isnot(1, 1, "isnot(1,1) should fail", TEST_UNEXPECTED_FAIL, TEST_PASS);
+ok(1 == 2, "ok(1==2) should fail", TEST_UNEXPECTED_FAIL, TEST_PASS);
+
+
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_pass.js b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_pass.js
new file mode 100644
index 000000000..93ee67619
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_pass.js
@@ -0,0 +1,12 @@
+/* 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/. */
+
+MARIONETTE_TIMEOUT = 1000;
+
+is(2, 2, "test for is()");
+isnot(2, 3, "test for isnot()");
+ok(2 == 2, "test for ok()");
+
+setTimeout(finish, 100);
+
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_sanity.py b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_sanity.py
new file mode 100644
index 000000000..8f9728561
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_sanity.py
@@ -0,0 +1,107 @@
+# 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 SimpletestSanityTest(MarionetteTestCase):
+ callFinish = "return finish();"
+
+ def run_sync(self, test):
+ return self.marionette.execute_js_script(test, async=False)
+
+ def run_async(self, test):
+ return self.marionette.execute_js_script(test)
+
+ def test_is(self):
+ def runtests():
+ sentFail1 = "is(true, false, 'isTest1', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
+ sentFail2 = "is(true, false, 'isTest2', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
+ sentPass1 = "is(true, true, 'isTest3');" + self.callFinish
+ sentPass2 = "is(true, true, 'isTest4');" + self.callFinish
+
+ self.assertEqual(1, len(self.run_sync(sentFail1)["failures"]))
+ self.assertEqual(0, self.run_sync(sentFail2)["passed"])
+ self.assertEqual(1, self.run_sync(sentPass1)["passed"])
+ self.assertEqual(0, len(self.run_sync(sentPass2)["failures"]))
+
+ self.marionette.timeout.script = 1
+ self.assertEqual(1, len(self.run_async(sentFail1)["failures"]))
+ self.assertEqual(0, self.run_async(sentFail2)["passed"])
+ self.assertEqual(1, self.run_async(sentPass1)["passed"])
+ self.assertEqual(0, len(self.run_async(sentPass2)["failures"]))
+
+ self.marionette.set_context("content")
+ runtests()
+ self.marionette.set_context("chrome")
+ runtests()
+
+ def test_isnot(self):
+ def runtests():
+ sentFail1 = "isnot(true, true, 'isnotTest3', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
+ sentFail2 = "isnot(true, true, 'isnotTest4', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
+ sentPass1 = "isnot(true, false, 'isnotTest1');" + self.callFinish
+ sentPass2 = "isnot(true, false, 'isnotTest2');" + self.callFinish
+
+ self.assertEqual(1, len(self.run_sync(sentFail1)["failures"]));
+ self.assertEqual(0, self.run_sync(sentFail2)["passed"]);
+ self.assertEqual(0, len(self.run_sync(sentPass1)["failures"]));
+ self.assertEqual(1, self.run_sync(sentPass2)["passed"]);
+
+ self.marionette.timeout.script = 1
+ self.assertEqual(1, len(self.run_async(sentFail1)["failures"]));
+ self.assertEqual(0, self.run_async(sentFail2)["passed"]);
+ self.assertEqual(0, len(self.run_async(sentPass1)["failures"]));
+ self.assertEqual(1, self.run_async(sentPass2)["passed"]);
+
+ self.marionette.set_context("content")
+ runtests()
+ self.marionette.set_context("chrome")
+ runtests()
+
+ def test_ok(self):
+ def runtests():
+ sentFail1 = "ok(1==2, 'testOk1', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
+ sentFail2 = "ok(1==2, 'testOk2', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
+ sentPass1 = "ok(1==1, 'testOk3');" + self.callFinish
+ sentPass2 = "ok(1==1, 'testOk4');" + self.callFinish
+
+ self.assertEqual(1, len(self.run_sync(sentFail1)["failures"]));
+ self.assertEqual(0, self.run_sync(sentFail2)["passed"]);
+ self.assertEqual(0, len(self.run_sync(sentPass1)["failures"]));
+ self.assertEqual(1, self.run_sync(sentPass2)["passed"]);
+
+ self.marionette.timeout.script = 1
+ self.assertEqual(1, len(self.run_async(sentFail1)["failures"]));
+ self.assertEqual(0, self.run_async(sentFail2)["passed"]);
+ self.assertEqual(0, len(self.run_async(sentPass1)["failures"]));
+ self.assertEqual(1, self.run_async(sentPass2)["passed"]);
+
+ self.marionette.set_context("content")
+ runtests()
+ self.marionette.set_context("chrome")
+ runtests()
+
+ def test_todo(self):
+ def runtests():
+ sentFail1 = "todo(1==1, 'testTodo1', TEST_UNEXPECTED_PASS, TEST_KNOWN_FAIL);" + self.callFinish
+ sentFail2 = "todo(1==1, 'testTodo2', TEST_UNEXPECTED_PASS, TEST_KNOWN_FAIL);" + self.callFinish
+ sentPass1 = "todo(1==2, 'testTodo3');" + self.callFinish
+ sentPass2 = "todo(1==2, 'testTodo4');" + self.callFinish
+
+ self.assertEqual(1, len(self.run_sync(sentFail1)["unexpectedSuccesses"]));
+ self.assertEqual(0, len(self.run_sync(sentFail2)["expectedFailures"]));
+ self.assertEqual(0, len(self.run_sync(sentPass1)["unexpectedSuccesses"]));
+ self.assertEqual(1, len(self.run_sync(sentPass2)["expectedFailures"]));
+
+ self.marionette.timeout.script = 1
+ self.assertEqual(1, len(self.run_async(sentFail1)["unexpectedSuccesses"]));
+ self.assertEqual(0, len(self.run_async(sentFail2)["expectedFailures"]));
+ self.assertEqual(0, len(self.run_async(sentPass1)["unexpectedSuccesses"]));
+ self.assertEqual(1, len(self.run_async(sentPass2)["expectedFailures"]));
+
+ self.marionette.set_context("content")
+ runtests()
+ self.marionette.set_context("chrome")
+ runtests()
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_timeout.js b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_timeout.js
new file mode 100644
index 000000000..9792a936a
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_timeout.js
@@ -0,0 +1,16 @@
+/* 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/. */
+
+MARIONETTE_TIMEOUT = 100;
+
+/* this test will timeout */
+
+function do_test() {
+ is(1, 1);
+ isnot(1, 2);
+ ok(1 == 1);
+ finish();
+}
+
+setTimeout(do_test, 1000);
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_single_finger_desktop.py b/testing/marionette/harness/marionette_harness/tests/unit/test_single_finger_desktop.py
new file mode 100644
index 000000000..8ac80c3c5
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_single_finger_desktop.py
@@ -0,0 +1,123 @@
+# 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 sys
+
+from marionette_driver.errors import MarionetteException
+from marionette_driver import Actions, By
+
+from marionette_harness import MarionetteTestCase, skip
+
+# add this directory to the path
+sys.path.append(os.path.dirname(__file__))
+
+from single_finger_functions import (
+ chain, chain_flick, context_menu, double_tap,
+ long_press_action, long_press_on_xy_action,
+ move_element, move_element_offset, press_release, single_tap, wait,
+ wait_with_value
+)
+
+
+class testSingleFingerMouse(MarionetteTestCase):
+ def setUp(self):
+ super(MarionetteTestCase, self).setUp()
+ # set context menu related preferences needed for some tests
+ self.marionette.set_context("chrome")
+ self.enabled = self.marionette.execute_script("""
+let prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+let value = false;
+try {
+ value = prefs.getBoolPref("ui.click_hold_context_menus");
+}
+catch (e) {}
+prefs.setBoolPref("ui.click_hold_context_menus", true);
+return value;
+""")
+ self.wait_time = self.marionette.execute_script("""
+let prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+let value = 750;
+try {
+ value = prefs.getIntPref("ui.click_hold_context_menus.delay");
+}
+catch (e) {}
+prefs.setIntPref("ui.click_hold_context_menus.delay", value);
+return value;
+""")
+ self.marionette.set_context("content")
+
+ def tearDown(self):
+ self.marionette.set_context("chrome")
+ self.marionette.execute_script(
+ """
+let prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+prefs.setBoolPref("ui.click_hold_context_menus", arguments[0]);
+""", [self.enabled])
+ self.marionette.execute_script(
+ """
+let prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+prefs.setIntPref("ui.click_hold_context_menus.delay", arguments[0]);
+""", [self.wait_time])
+ self.marionette.set_context("content")
+ super(MarionetteTestCase, self).tearDown()
+
+ def test_press_release(self):
+ press_release(self.marionette, 1, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click")
+
+ def test_press_release_twice(self):
+ press_release(self.marionette, 2, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click-mousemove-mousedown-mouseup-click")
+
+ def test_move_element(self):
+ move_element(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "button2-mousemove-mouseup")
+
+ def test_move_by_offset(self):
+ move_element_offset(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "button2-mousemove-mouseup")
+
+ def test_wait(self):
+ wait(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click")
+
+ def test_wait_with_value(self):
+ wait_with_value(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click")
+
+ @skip("Bug 1191066")
+ def test_context_menu(self):
+ context_menu(self.marionette, self.wait_for_condition,
+ "button1-mousemove-mousedown-contextmenu",
+ "button1-mousemove-mousedown-contextmenu-mouseup-click")
+
+ @skip("Bug 1191066")
+ def test_long_press_action(self):
+ long_press_action(self.marionette, self.wait_for_condition,
+ "button1-mousemove-mousedown-contextmenu-mouseup-click")
+
+ @skip("Bug 1191066")
+ def test_long_press_on_xy_action(self):
+ long_press_on_xy_action(self.marionette, self.wait_for_condition,
+ "button1-mousemove-mousedown-contextmenu-mouseup-click")
+
+ @skip("Bug 865334")
+ def test_long_press_fail(self):
+ testAction = self.marionette.absolute_url("testAction.html")
+ self.marionette.navigate(testAction)
+ button = self.marionette.find_element(By.ID, "button1Copy")
+ action = Actions(self.marionette)
+ action.press(button).long_press(button, 5)
+ self.assertRaises(MarionetteException, action.perform)
+
+ def test_chain(self):
+ chain(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "delayed-mousemove-mouseup")
+
+ def test_chain_flick(self):
+ chain_flick(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mousemove", "buttonFlick-mousemove-mouseup")
+
+ def test_single_tap(self):
+ single_tap(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click")
+
+ def test_double_tap(self):
+ double_tap(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click-mousemove-mousedown-mouseup-click")
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_skip_setup.py b/testing/marionette/harness/marionette_harness/tests/unit/test_skip_setup.py
new file mode 100644
index 000000000..9a0432fb7
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_skip_setup.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_harness import MarionetteTestCase, SkipTest
+
+
+class TestSetUpSkipped(MarionetteTestCase):
+
+ testVar = {'test':'SkipTest'}
+
+ def setUp(self):
+ try:
+ self.testVar['email']
+ except KeyError:
+ raise SkipTest('email key not present in dict, skip ...')
+ MarionetteTestCase.setUp(self)
+
+ def test_assert(self):
+ assert True
+
+class TestSetUpNotSkipped(MarionetteTestCase):
+
+ testVar = {'test':'SkipTest'}
+
+ def setUp(self):
+ try:
+ self.testVar['test']
+ except KeyError:
+ raise SkipTest('email key not present in dict, skip ...')
+ MarionetteTestCase.setUp(self)
+
+ def test_assert(self):
+ assert True
+
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py
new file mode 100644
index 000000000..18eb34169
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py
@@ -0,0 +1,183 @@
+# 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 (
+ JavascriptException,
+ NoSuchFrameException,
+)
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestSwitchFrame(MarionetteTestCase):
+ def test_switch_simple(self):
+ start_url = "test_iframe.html"
+ verify_title = "Marionette IFrame Test"
+ test_html = self.marionette.absolute_url(start_url)
+ self.marionette.navigate(test_html)
+ self.assertEqual(self.marionette.get_active_frame(), None)
+ frame = self.marionette.find_element(By.ID, "test_iframe")
+ self.marionette.switch_to_frame(frame)
+ self.assertTrue(start_url in self.marionette.get_url())
+ inner_frame_element = self.marionette.get_active_frame()
+ # test that we can switch back to main frame, then switch back to the
+ # inner frame with the value we got from get_active_frame
+ self.marionette.switch_to_frame()
+ self.assertEqual(verify_title, self.marionette.title)
+ self.marionette.switch_to_frame(inner_frame_element)
+ self.assertTrue(start_url in self.marionette.get_url())
+
+ def test_switch_nested(self):
+ start_url = "test_nested_iframe.html"
+ verify_title = "Marionette IFrame Test"
+ test_html = self.marionette.absolute_url(start_url)
+ self.marionette.navigate(test_html)
+ frame = self.marionette.find_element(By.ID, "test_iframe")
+ self.assertEqual(self.marionette.get_active_frame(), None)
+ self.marionette.switch_to_frame(frame)
+ self.assertTrue(start_url in self.marionette.get_url())
+ inner_frame_element = self.marionette.get_active_frame()
+ # test that we can switch back to main frame, then switch back to the
+ # inner frame with the value we got from get_active_frame
+ self.marionette.switch_to_frame()
+ self.assertEqual(verify_title, self.marionette.title)
+ self.marionette.switch_to_frame(inner_frame_element)
+ self.assertTrue(start_url in self.marionette.get_url())
+ inner_frame = self.marionette.find_element(By.ID, 'inner_frame')
+ self.marionette.switch_to_frame(inner_frame)
+ self.assertTrue(start_url in self.marionette.get_url())
+ self.marionette.switch_to_frame() # go back to main frame
+ self.assertTrue(start_url in self.marionette.get_url())
+ #test that we're using the right window object server-side
+ self.assertTrue("test_nested_iframe.html" in self.marionette.execute_script("return window.location.href;"))
+
+ def test_stack_trace(self):
+ start_url = "test_iframe.html"
+ verify_title = "Marionette IFrame Test"
+ test_html = self.marionette.absolute_url(start_url)
+ self.marionette.navigate(test_html)
+ frame = self.marionette.find_element(By.ID, "test_iframe")
+ self.assertEqual(self.marionette.get_active_frame(), None)
+ self.marionette.switch_to_frame(frame)
+ self.assertTrue(start_url in self.marionette.get_url())
+ inner_frame_element = self.marionette.get_active_frame()
+ # test that we can switch back to main frame, then switch back to the
+ # inner frame with the value we got from get_active_frame
+ self.marionette.switch_to_frame()
+ self.assertEqual(verify_title, self.marionette.title)
+ self.marionette.switch_to_frame(inner_frame_element)
+ self.assertTrue(start_url in self.marionette.get_url())
+
+ try:
+ self.marionette.execute_async_script("foo();")
+ except JavascriptException as e:
+ self.assertTrue("foo" in e.message)
+
+ def test_should_be_able_to_carry_on_working_if_the_frame_is_deleted_from_under_us(self):
+ test_html = self.marionette.absolute_url("deletingFrame.html")
+ self.marionette.navigate(test_html)
+
+ self.marionette.switch_to_frame(self.marionette.find_element(By.ID,
+ 'iframe1'))
+ killIframe = self.marionette.find_element(By.ID, "killIframe")
+ killIframe.click()
+ self.marionette.switch_to_frame()
+
+ self.assertEqual(0, len(self.marionette.find_elements(By.ID, "iframe1")))
+
+ addIFrame = self.marionette.find_element(By.ID, "addBackFrame")
+ addIFrame.click()
+ self.marionette.find_element(By.ID, "iframe1")
+
+ self.marionette.switch_to_frame(self.marionette.find_element(By.ID,
+ "iframe1"))
+
+ self.marionette.find_element(By.ID, "checkbox")
+
+ def test_should_allow_a_user_to_switch_from_an_iframe_back_to_the_main_content_of_the_page(self):
+ test_iframe = self.marionette.absolute_url("test_iframe.html")
+ self.marionette.navigate(test_iframe)
+ self.marionette.switch_to_frame(0)
+ self.marionette.switch_to_default_content()
+ header = self.marionette.find_element(By.ID, "iframe_page_heading")
+ self.assertEqual(header.text, "This is the heading")
+
+ def test_should_be_able_to_switch_to_a_frame_by_its_index(self):
+ test_html = self.marionette.absolute_url("frameset.html")
+ self.marionette.navigate(test_html)
+ self.marionette.switch_to_frame(2)
+ element = self.marionette.find_element(By.ID, "email")
+ self.assertEquals("email", element.get_attribute("type"))
+
+ def test_should_be_able_to_switch_to_a_frame_using_a_previously_located_element(self):
+ test_html = self.marionette.absolute_url("frameset.html")
+ self.marionette.navigate(test_html)
+ frame = self.marionette.find_element(By.NAME, "third")
+ self.marionette.switch_to_frame(frame)
+
+ element = self.marionette.find_element(By.ID, "email")
+ self.assertEquals("email", element.get_attribute("type"))
+
+ def test_switch_to_frame_with_out_of_bounds_index(self):
+ self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
+ count = self.marionette.execute_script("return window.frames.length;")
+ self.assertRaises(NoSuchFrameException, self.marionette.switch_to_frame, count)
+
+ def test_switch_to_frame_with_negative_index(self):
+ self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
+ self.assertRaises(NoSuchFrameException, self.marionette.switch_to_frame, -1)
+
+ def test_switch_to_parent_frame(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)
+
+ # If we don't find the following element we aren't on the right page
+ self.marionette.find_element(By.ID, "checky")
+ form_page_title = self.marionette.execute_script("return document.title")
+ self.assertEqual("We Leave From Here", form_page_title)
+
+ self.marionette.switch_to_parent_frame()
+
+ current_page_title = self.marionette.execute_script("return document.title")
+ self.assertEqual("Unique title", current_page_title)
+
+ def test_switch_to_parent_frame_from_default_context_is_a_noop(self):
+ formpage = self.marionette.absolute_url("formPage.html")
+ self.marionette.navigate(formpage)
+
+ self.marionette.switch_to_parent_frame()
+
+ form_page_title = self.marionette.execute_script("return document.title")
+ self.assertEqual("We Leave From Here", form_page_title)
+
+ def test_should_be_able_to_switch_to_parent_from_second_level(self):
+ frame_html = self.marionette.absolute_url("frameset.html")
+ self.marionette.navigate(frame_html)
+ frame = self.marionette.find_element(By.NAME, "fourth")
+ self.marionette.switch_to_frame(frame)
+
+ second_level = self.marionette.find_element(By.NAME, "child1")
+ self.marionette.switch_to_frame(second_level)
+ self.marionette.find_element(By.NAME, "myCheckBox")
+
+ self.marionette.switch_to_parent_frame()
+
+ second_level = self.marionette.find_element(By.NAME, "child1")
+
+ def test_should_be_able_to_switch_to_parent_from_iframe(self):
+ frame_html = self.marionette.absolute_url("test_iframe.html")
+ self.marionette.navigate(frame_html)
+ frame = self.marionette.find_element(By.ID, "test_iframe")
+ self.marionette.switch_to_frame(frame)
+
+ current_page_title = self.marionette.execute_script("return document.title")
+ self.assertEqual("Marionette Test", current_page_title)
+
+ self.marionette.switch_to_parent_frame()
+
+ parent_page_title = self.marionette.execute_script("return document.title")
+ self.assertEqual("Marionette IFrame Test", parent_page_title)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py
new file mode 100644
index 000000000..03c13026e
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.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.errors import JavascriptException
+
+from marionette_harness import MarionetteTestCase, WindowManagerMixin
+
+
+class TestSwitchFrameChrome(WindowManagerMixin, MarionetteTestCase):
+
+ def setUp(self):
+ super(TestSwitchFrameChrome, self).setUp()
+ self.marionette.set_context("chrome")
+
+ def open_window_with_js():
+ self.marionette.execute_script("""
+ window.open('chrome://marionette/content/test.xul',
+ 'foo', 'chrome,centerscreen');
+ """)
+
+ new_window = self.open_window(trigger=open_window_with_js)
+ self.marionette.switch_to_window(new_window)
+ self.assertNotEqual(self.start_window, self.marionette.current_chrome_window_handle)
+
+ def tearDown(self):
+ self.close_all_windows()
+ super(TestSwitchFrameChrome, self).tearDown()
+
+ def test_switch_simple(self):
+ self.assertIn("test.xul", self.marionette.get_url(), "Initial navigation has failed")
+ self.marionette.switch_to_frame(0)
+ self.assertIn("test2.xul", self.marionette.get_url(),"Switching by index failed")
+ self.marionette.switch_to_frame()
+ self.assertEqual(None, self.marionette.get_active_frame(), "Switiching by null failed")
+ self.assertIn("test.xul", self.marionette.get_url(), "Switching by null failed")
+ self.marionette.switch_to_frame("iframe")
+ self.assertIn("test2.xul", self.marionette.get_url(), "Switching by name failed")
+ self.marionette.switch_to_frame()
+ self.assertIn("test.xul", self.marionette.get_url(), "Switching by null failed")
+ self.marionette.switch_to_frame("iframename")
+ self.assertIn("test2.xul", self.marionette.get_url(), "Switching by name failed")
+ iframe_element = self.marionette.get_active_frame()
+ self.marionette.switch_to_frame()
+ self.assertIn("test.xul", self.marionette.get_url(), "Switching by null failed")
+ self.marionette.switch_to_frame(iframe_element)
+ self.assertIn("test2.xul", self.marionette.get_url(), "Switching by element failed")
+
+ def test_stack_trace(self):
+ self.assertIn("test.xul", self.marionette.get_url(), "Initial navigation has failed")
+ self.marionette.switch_to_frame(0)
+ self.assertRaises(JavascriptException, self.marionette.execute_async_script, "foo();")
+ try:
+ self.marionette.execute_async_script("foo();")
+ except JavascriptException as e:
+ self.assertIn("foo", e.message)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_remote_frame.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_remote_frame.py
new file mode 100644
index 000000000..07ddeef2a
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_remote_frame.py
@@ -0,0 +1,118 @@
+# 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.mozBrowserFramesEnabled"
+
+
+class TestSwitchRemoteFrame(MarionetteTestCase):
+ def setUp(self):
+ super(TestSwitchRemoteFrame, 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)
+
+ self.multi_process_browser = self.marionette.execute_script("""
+ try {
+ return Services.appinfo.browserTabsRemoteAutostart;
+ } catch (e) {
+ return false;
+ }""")
+
+ 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)
+
+ @property
+ def is_main_process(self):
+ return self.marionette.execute_script("""
+ return Components.classes["@mozilla.org/xre/app-info;1"].
+ getService(Components.interfaces.nsIXULRuntime).
+ processType == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+ """, sandbox="system")
+
+ def test_remote_frame(self):
+ self.marionette.navigate(self.marionette.absolute_url("test.html"))
+ self.marionette.push_permission('browser', True)
+ self.marionette.execute_script("""
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute('mozbrowser', true);
+ iframe.setAttribute('remote', true);
+ iframe.id = "remote_iframe";
+ iframe.style.height = "100px";
+ iframe.style.width = "100%%";
+ iframe.src = "{}";
+ document.body.appendChild(iframe);
+ """.format(self.marionette.absolute_url("test.html")))
+ remote_iframe = self.marionette.find_element(By.ID, "remote_iframe")
+ self.marionette.switch_to_frame(remote_iframe)
+ main_process = self.is_main_process
+ self.assertFalse(main_process)
+
+ def test_remote_frame_revisit(self):
+ # test if we can revisit a remote frame (this takes a different codepath)
+ self.marionette.navigate(self.marionette.absolute_url("test.html"))
+ self.marionette.push_permission('browser', True)
+ self.marionette.execute_script("""
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute('mozbrowser', true);
+ iframe.setAttribute('remote', true);
+ iframe.id = "remote_iframe";
+ iframe.style.height = "100px";
+ iframe.style.width = "100%%";
+ iframe.src = "{}";
+ document.body.appendChild(iframe);
+ """.format(self.marionette.absolute_url("test.html")))
+ self.marionette.switch_to_frame(self.marionette.find_element(By.ID,
+ "remote_iframe"))
+ main_process = self.is_main_process
+ self.assertFalse(main_process)
+ self.marionette.switch_to_frame()
+ main_process = self.is_main_process
+ should_be_main_process = not self.multi_process_browser
+ self.assertEqual(main_process, should_be_main_process)
+ self.marionette.switch_to_frame(self.marionette.find_element(By.ID,
+ "remote_iframe"))
+ main_process = self.is_main_process
+ self.assertFalse(main_process)
+
+ def test_we_can_switch_to_a_remote_frame_by_index(self):
+ # test if we can revisit a remote frame (this takes a different codepath)
+ self.marionette.navigate(self.marionette.absolute_url("test.html"))
+ self.marionette.push_permission('browser', True)
+ self.marionette.execute_script("""
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute('mozbrowser', true);
+ iframe.setAttribute('remote', true);
+ iframe.id = "remote_iframe";
+ iframe.style.height = "100px";
+ iframe.style.width = "100%%";
+ iframe.src = "{}";
+ document.body.appendChild(iframe);
+ """.format(self.marionette.absolute_url("test.html")))
+ self.marionette.switch_to_frame(0)
+ main_process = self.is_main_process
+ self.assertFalse(main_process)
+ self.marionette.switch_to_frame()
+ main_process = self.is_main_process
+ should_be_main_process = not self.multi_process_browser
+ self.assertEqual(main_process, should_be_main_process)
+ self.marionette.switch_to_frame(0)
+ main_process = self.is_main_process
+ self.assertFalse(main_process)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py
new file mode 100644
index 000000000..0ad63b6ce
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py
@@ -0,0 +1,124 @@
+# 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 sys
+from unittest import skipIf
+
+from marionette_driver import By
+
+# add this directory to the path
+sys.path.append(os.path.dirname(__file__))
+
+from test_switch_window_content import TestSwitchToWindowContent
+
+
+class TestSwitchWindowChrome(TestSwitchToWindowContent):
+
+ def setUp(self):
+ super(TestSwitchWindowChrome, self).setUp()
+
+ self.marionette.set_context("chrome")
+
+ def tearDown(self):
+ self.close_all_windows()
+
+ super(TestSwitchWindowChrome, self).tearDown()
+
+ def open_window_in_background(self):
+ with self.marionette.using_context("chrome"):
+ self.marionette.execute_script("""
+ window.open("about:blank", null, "location=1,toolbar=1");
+ window.focus();
+ """)
+
+ def open_window_in_foreground(self):
+ with self.marionette.using_context("content"):
+ self.marionette.navigate(self.test_page)
+ link = self.marionette.find_element(By.ID, "new-window")
+ link.click()
+
+ @skipIf(sys.platform.startswith("linux"),
+ "Bug 1335457 - Fails to open a background window on Linux")
+ def test_switch_tabs_for_new_background_window_without_focus_change(self):
+ # Bug 1334981 - with testmode enabled getMostRecentWindow detects the wrong window
+ with self.marionette.using_prefs({"focusmanager.testmode": False}):
+ # Open an addition tab in the original window so we can better check
+ # the selected index in thew new window to be opened.
+ second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
+ self.marionette.switch_to_window(second_tab, focus=True)
+ second_tab_index = self.get_selected_tab_index()
+ self.assertNotEqual(second_tab_index, self.selected_tab_index)
+
+ # Opens a new background window, but we are interested in the tab
+ tab_in_new_window = self.open_tab(trigger=self.open_window_in_background)
+ self.assertEqual(self.marionette.current_window_handle, second_tab)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+ self.assertEqual(self.get_selected_tab_index(), second_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.empty_page)
+
+ # Switch to the tab in the new window but don't focus it
+ self.marionette.switch_to_window(tab_in_new_window, focus=False)
+ self.assertEqual(self.marionette.current_window_handle, tab_in_new_window)
+ self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
+ self.assertEqual(self.get_selected_tab_index(), second_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), "about:blank")
+
+ def test_switch_tabs_for_new_foreground_window_with_focus_change(self):
+ # Open an addition tab in the original window so we can better check
+ # the selected index in thew new window to be opened.
+ second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
+ self.marionette.switch_to_window(second_tab, focus=True)
+ second_tab_index = self.get_selected_tab_index()
+ self.assertNotEqual(second_tab_index, self.selected_tab_index)
+
+ # Opens a new window, but we are interested in the tab
+ tab_in_new_window = self.open_tab(trigger=self.open_window_in_foreground)
+ self.assertEqual(self.marionette.current_window_handle, second_tab)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+ self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ self.marionette.switch_to_window(tab_in_new_window)
+ self.assertEqual(self.marionette.current_window_handle, tab_in_new_window)
+ self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
+ self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.empty_page)
+
+ self.marionette.switch_to_window(second_tab, focus=True)
+ self.assertEqual(self.marionette.current_window_handle, second_tab)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+ # Bug 1335085 - The focus doesn't change even as requested so.
+ # self.assertEqual(self.get_selected_tab_index(), second_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ def test_switch_tabs_for_new_foreground_window_without_focus_change(self):
+ # Open an addition tab in the original window so we can better check
+ # the selected index in thew new window to be opened.
+ second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
+ self.marionette.switch_to_window(second_tab, focus=True)
+ second_tab_index = self.get_selected_tab_index()
+ self.assertNotEqual(second_tab_index, self.selected_tab_index)
+
+ # Opens a new window, but we are interested in the tab which automatically
+ # gets the focus.
+ self.open_tab(trigger=self.open_window_in_foreground)
+ self.assertEqual(self.marionette.current_window_handle, second_tab)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+ self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ # Switch to the second tab in the first window, but don't focus it.
+ self.marionette.switch_to_window(second_tab, focus=False)
+ self.assertEqual(self.marionette.current_window_handle, second_tab)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+ self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py
new file mode 100644
index 000000000..fbab1898f
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py
@@ -0,0 +1,171 @@
+# This Source Code Form is subject to the terms of the Mozilla ublic
+# 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 Actions, By, Wait
+from marionette_driver.keys import Keys
+
+from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin
+
+
+class TestSwitchToWindowContent(WindowManagerMixin, MarionetteTestCase):
+
+ def setUp(self):
+ super(TestSwitchToWindowContent, self).setUp()
+
+ if self.marionette.session_capabilities["platformName"] == "darwin":
+ self.mod_key = Keys.META
+ else:
+ self.mod_key = Keys.CONTROL
+
+ self.empty_page = self.marionette.absolute_url("empty.html")
+ self.test_page = self.marionette.absolute_url("windowHandles.html")
+
+ self.selected_tab_index = self.get_selected_tab_index()
+
+ with self.marionette.using_context("content"):
+ self.marionette.navigate(self.test_page)
+
+ def tearDown(self):
+ self.close_all_tabs()
+
+ super(TestSwitchToWindowContent, self).tearDown()
+
+ def get_selected_tab_index(self):
+ with self.marionette.using_context("chrome"):
+ return self.marionette.execute_script("""
+ Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+ let win = null;
+
+ if (AppConstants.MOZ_APP_NAME == "fennec") {
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ win = Services.wm.getMostRecentWindow("navigator:browser");
+ } else {
+ Components.utils.import("resource:///modules/RecentWindow.jsm");
+ win = RecentWindow.getMostRecentBrowserWindow();
+ }
+
+ let tabBrowser = null;
+
+ // Fennec
+ if (win.BrowserApp) {
+ tabBrowser = win.BrowserApp;
+
+ // Firefox
+ } else if (win.gBrowser) {
+ tabBrowser = win.gBrowser;
+
+ } else {
+ return null;
+ }
+
+ for (let i = 0; i < tabBrowser.tabs.length; i++) {
+ if (tabBrowser.tabs[i] == tabBrowser.selectedTab) {
+ return i;
+ }
+ }
+ """)
+
+ def open_tab_in_background(self):
+ with self.marionette.using_context("content"):
+ link = self.marionette.find_element(By.ID, "new-tab")
+
+ action = Actions(self.marionette)
+ action.key_down(self.mod_key).click(link).perform()
+
+ def open_tab_in_foreground(self):
+ with self.marionette.using_context("content"):
+ link = self.marionette.find_element(By.ID, "new-tab")
+ link.click()
+
+ def test_switch_tabs_with_focus_change(self):
+ new_tab = self.open_tab(self.open_tab_in_foreground)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
+
+ with self.marionette.using_context("content"):
+ Wait(self.marionette).until(
+ lambda _: self.marionette.get_url() == self.empty_page,
+ message="{} has been loaded in the newly opened tab.".format(self.empty_page))
+
+ self.marionette.switch_to_window(self.start_tab, focus=True)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ self.marionette.switch_to_window(new_tab)
+ self.marionette.close()
+ self.marionette.switch_to_window(self.start_tab)
+
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ def test_switch_tabs_without_focus_change(self):
+ new_tab = self.open_tab(self.open_tab_in_foreground)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ # Switch to new tab first because it is already selected
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+
+ self.marionette.switch_to_window(self.start_tab, focus=False)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
+
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ self.marionette.switch_to_window(new_tab)
+ self.marionette.close()
+
+ self.marionette.switch_to_window(self.start_tab)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ def test_switch_from_content_to_chrome_window_should_not_change_selected_tab(self):
+ new_tab = self.open_tab(self.open_tab_in_foreground)
+
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ new_tab_index = self.get_selected_tab_index()
+
+ self.marionette.switch_to_window(self.start_window)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ self.assertEqual(self.get_selected_tab_index(), new_tab_index)
+
+ @skip_if_mobile("New windows not supported in Fennec")
+ def test_switch_to_new_private_browsing_window_has_to_register_browsers(self):
+ # Test that tabs (browsers) are correctly registered for a newly opened
+ # private browsing window. This has to also happen without explicitely
+ # switching to the tab itself before using any commands in content scope.
+ #
+ # Note: Not sure why this only affects private browsing windows only.
+
+ def open_private_browsing_window():
+ with self.marionette.using_context("content"):
+ self.marionette.navigate("about:privatebrowsing")
+ button = self.marionette.find_element(By.ID, "startPrivateBrowsing")
+ button.click()
+
+ new_window = self.open_window(open_private_browsing_window)
+ self.marionette.switch_to_window(new_window)
+ self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
+ self.assertNotEqual(self.marionette.current_window_handle, self.start_tab)
+
+ with self.marionette.using_context("content"):
+ self.marionette.execute_script(" return true; ")
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_teardown_context_preserved.py b/testing/marionette/harness/marionette_harness/tests/unit/test_teardown_context_preserved.py
new file mode 100644
index 000000000..843152bc5
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_teardown_context_preserved.py
@@ -0,0 +1,21 @@
+# 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, SkipTest
+
+
+class TestTearDownContext(MarionetteTestCase):
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+ def tearDown(self):
+ self.assertEqual(self.get_context(), self.marionette.CONTEXT_CHROME)
+ MarionetteTestCase.tearDown(self)
+
+ def get_context(self):
+ return self.marionette._send_message("getContext", key="value")
+
+ def test_skipped_teardown_ok(self):
+ raise SkipTest("This should leave our teardown method in chrome context")
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_text.py b/testing/marionette/harness/marionette_harness/tests/unit/test_text.py
new file mode 100644
index 000000000..e2025e9b6
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_text.py
@@ -0,0 +1,224 @@
+# 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_harness import MarionetteTestCase, skip_if_mobile
+
+
+class TestText(MarionetteTestCase):
+ def test_getText(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ l = self.marionette.find_element(By.ID, "mozLink")
+ self.assertEqual("Click me!", l.text)
+
+ def test_clearText(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ l = self.marionette.find_element(By.NAME, "myInput")
+ self.assertEqual("asdf", self.marionette.execute_script("return arguments[0].value;", [l]))
+ l.clear()
+ self.assertEqual("", self.marionette.execute_script("return arguments[0].value;", [l]))
+
+ def test_sendKeys(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ l = self.marionette.find_element(By.NAME, "myInput")
+ self.assertEqual("asdf", self.marionette.execute_script("return arguments[0].value;", [l]))
+
+ # Set caret position to the middle of the input text.
+ self.marionette.execute_script(
+ """var el = arguments[0];
+ el.selectionStart = el.selectionEnd = el.value.length / 2;""",
+ script_args=[l])
+
+ l.send_keys("o")
+ self.assertEqual("asodf", self.marionette.execute_script("return arguments[0].value;", [l]))
+
+ def test_send_keys_to_type_input(self):
+ test_html = self.marionette.absolute_url("html5/test_html_inputs.html")
+ self.marionette.navigate(test_html)
+ num_input = self.marionette.find_element(By.ID, 'number')
+ self.assertEqual("", self.marionette.execute_script("return arguments[0].value", [num_input]))
+ num_input.send_keys("1234")
+ self.assertEqual('1234', self.marionette.execute_script("return arguments[0].value", [num_input]))
+
+ def test_should_fire_key_press_events(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys("a")
+
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertIn("press:", result.text)
+
+ def test_should_fire_key_down_events(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys("a")
+
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertIn("down:", result.text)
+
+ def test_should_fire_key_up_events(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys("a")
+
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertIn("up:", result.text)
+
+ def test_should_type_lowercase_characters(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys("abc def")
+
+ self.assertEqual("abc def", key_reporter.get_property("value"))
+
+ def test_should_type_uppercase_characters(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys("ABC DEF")
+
+ self.assertEqual("ABC DEF", key_reporter.get_property("value"))
+
+ def test_should_type_a_quote_characters(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys('"')
+
+ self.assertEqual('"', key_reporter.get_property("value"))
+
+ def test_should_type_an_at_character(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys('@')
+
+ self.assertEqual("@", key_reporter.get_property("value"))
+
+ def test_should_type_a_mix_of_upper_and_lower_case_character(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys("me@EXampLe.com")
+
+ self.assertEqual("me@EXampLe.com", key_reporter.get_property("value"))
+
+ def test_arrow_keys_are_not_printable(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ key_reporter = self.marionette.find_element(By.ID, "keyReporter")
+ key_reporter.send_keys(Keys.ARROW_LEFT)
+
+ self.assertEqual("", key_reporter.get_property("value"))
+
+ def test_will_simulate_a_key_up_when_entering_text_into_input_elements(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "keyUp")
+ element.send_keys("I like cheese")
+
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertEqual(result.text, "I like cheese")
+
+ def test_will_simulate_a_key_down_when_entering_text_into_input_elements(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "keyDown")
+ element.send_keys("I like cheese")
+
+ result = self.marionette.find_element(By.ID, "result")
+ # Because the key down gets the result before the input element is
+ # filled, we're a letter short here
+ self.assertEqual(result.text, "I like chees")
+
+ def test_will_simulate_a_key_press_when_entering_text_into_input_elements(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "keyPress")
+ element.send_keys("I like cheese")
+
+ result = self.marionette.find_element(By.ID, "result")
+ # Because the key down gets the result before the input element is
+ # filled, we're a letter short here
+ self.assertEqual(result.text, "I like chees")
+
+ def test_will_simulate_a_keyup_when_entering_text_into_textareas(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "keyUpArea")
+ element.send_keys("I like cheese")
+
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertEqual(result.text, "I like cheese")
+
+ def test_will_simulate_a_keydown_when_entering_text_into_textareas(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "keyDownArea")
+ element.send_keys("I like cheese")
+
+ result = self.marionette.find_element(By.ID, "result")
+ # Because the key down gets the result before the input element is
+ # filled, we're a letter short here
+ self.assertEqual(result.text, "I like chees")
+
+ def test_will_simulate_a_keypress_when_entering_text_into_textareas(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "keyPressArea")
+ element.send_keys("I like cheese")
+
+ result = self.marionette.find_element(By.ID, "result")
+ # Because the key down gets the result before the input element is
+ # filled, we're a letter short here
+ self.assertEqual(result.text, "I like chees")
+
+ @skip_if_mobile("Bug 1333069 - Assertion: 'down: 40' not found in u''")
+ def test_should_report_key_code_of_arrow_keys_up_down_events(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ result = self.marionette.find_element(By.ID, "result")
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ element.send_keys(Keys.ARROW_DOWN)
+ self.assertIn("down: 40", result.text.strip())
+ self.assertIn("up: 40", result.text.strip())
+
+ element.send_keys(Keys.ARROW_UP)
+ self.assertIn("down: 38", result.text.strip())
+ self.assertIn("up: 38", result.text.strip())
+
+ element.send_keys(Keys.ARROW_LEFT)
+ self.assertIn("down: 37", result.text.strip())
+ self.assertIn("up: 37", result.text.strip())
+
+ element.send_keys(Keys.ARROW_RIGHT)
+ self.assertIn("down: 39", result.text.strip())
+ self.assertIn("up: 39", result.text.strip())
+
+ # And leave no rubbish/printable keys in the "keyReporter"
+ self.assertEqual("", element.get_property("value"))
+
+ def testNumericNonShiftKeys(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ numericLineCharsNonShifted = "`1234567890-=[]\\,.'/42"
+ element.send_keys(numericLineCharsNonShifted)
+ self.assertEqual(numericLineCharsNonShifted, element.get_property("value"))
+
+ def testShouldTypeAnInteger(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ element.send_keys(1234)
+ self.assertEqual("1234", element.get_property("value"))
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_text_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_text_chrome.py
new file mode 100644
index 000000000..e0b63de16
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_text_chrome.py
@@ -0,0 +1,44 @@
+# 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, WindowManagerMixin
+
+
+@skip("Disabled in bug 896043 and when working on Chrome code re-enable for bug 896046")
+class TestTextChrome(WindowManagerMixin, MarionetteTestCase):
+
+ def setUp(self):
+ super(TestTextChrome, self).setUp()
+ self.marionette.set_context("chrome")
+
+ def open_window_with_js():
+ self.marionette.execute_script("""
+ window.open('chrome://marionette/content/test.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(TestTextChrome, self).tearDown()
+
+ def test_getText(self):
+ box = self.marionette.find_element(By.ID, "textInput")
+ self.assertEqual("test", box.text)
+
+ def test_clearText(self):
+ box = self.marionette.find_element(By.ID, "textInput")
+ self.assertEqual("test", box.text)
+ box.clear()
+ self.assertEqual("", box.text)
+
+ def test_sendKeys(self):
+ box = self.marionette.find_element(By.ID, "textInput")
+ self.assertEqual("test", box.text)
+ box.send_keys("at")
+ self.assertEqual("attest", box.text)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py b/testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py
new file mode 100644
index 000000000..354e6a7eb
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.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/.
+
+from marionette_driver.by import By
+from marionette_driver.errors import (
+ MarionetteException,
+ NoSuchElementException,
+ ScriptTimeoutException,
+)
+from marionette_driver.marionette import HTMLElement
+
+from marionette_harness import MarionetteTestCase, run_if_manage_instance, skip_if_mobile
+
+
+class TestTimeouts(MarionetteTestCase):
+ def tearDown(self):
+ self.marionette.timeout.reset()
+ MarionetteTestCase.tearDown(self)
+
+ def test_page_timeout_notdefinetimeout_pass(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+
+ def test_page_timeout_fail(self):
+ self.marionette.timeout.page_load = 0
+ test_html = self.marionette.absolute_url("test.html")
+ self.assertRaises(MarionetteException, self.marionette.navigate, test_html)
+
+ def test_page_timeout_pass(self):
+ self.marionette.timeout.page_load = 60
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+
+ def test_search_timeout_notfound_settimeout(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ 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_search_timeout_found_settimeout(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ button = self.marionette.find_element(By.ID, "createDivButton")
+ button.click()
+ self.marionette.timeout.implicit = 8
+ self.assertEqual(HTMLElement, type(self.marionette.find_element(By.ID, "newDiv")))
+
+ def test_search_timeout_found(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ button = self.marionette.find_element(By.ID, "createDivButton")
+ button.click()
+ self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "newDiv")
+
+ @run_if_manage_instance("Only runnable if Marionette manages the instance")
+ @skip_if_mobile("Bug 1322993 - Missing temporary folder")
+ def test_reset_timeout(self):
+ timeouts = [getattr(self.marionette.timeout, f) for f in (
+ 'implicit', 'page_load', 'script',)]
+
+ def do_check(callback):
+ for timeout in timeouts:
+ timeout = 10000
+ self.assertEqual(timeout, 10000)
+ callback()
+ for timeout in timeouts:
+ self.assertNotEqual(timeout, 10000)
+
+ def callback_quit():
+ self.marionette.quit()
+ self.marionette.start_session()
+
+ do_check(self.marionette.restart)
+ do_check(callback_quit)
+
+ def test_execute_async_timeout_settimeout(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ self.marionette.timeout.script = 1
+ self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "var x = 1;")
+
+ def test_no_timeout_settimeout(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ self.marionette.timeout.script = 1
+ self.assertTrue(self.marionette.execute_async_script("""
+ var callback = arguments[arguments.length - 1];
+ setTimeout(function() { callback(true); }, 500);
+ """))
+
+ def test_compat_input_types(self):
+ # When using the spec-incompatible input format which we have
+ # for backwards compatibility, it should be possible to send ms
+ # as a string type and have the server parseInt it to an integer.
+ body = {"type": "script", "ms": "30000"}
+ self.marionette._send_message("setTimeouts", body)
+
+ def test_deprecated_set_timeouts_command(self):
+ body = {"implicit": 3000}
+ self.marionette._send_message("timeouts", body)
+
+ def test_deprecated_set_search_timeout(self):
+ self.marionette.set_search_timeout(1000)
+ self.assertEqual(1, self.marionette.timeout.implicit)
+
+ def test_deprecated_set_script_timeout(self):
+ self.marionette.set_script_timeout(2000)
+ self.assertEqual(2, self.marionette.timeout.script)
+
+ def test_deprecated_set_page_load_timeout(self):
+ self.marionette.set_page_load_timeout(3000)
+ self.assertEqual(3, self.marionette.timeout.page_load)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_transport.py b/testing/marionette/harness/marionette_harness/tests/unit/test_transport.py
new file mode 100644
index 000000000..39e36a9b2
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_transport.py
@@ -0,0 +1,172 @@
+# 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
+
+from marionette_driver.transport import (
+ Command,
+ Proto2Command,
+ Proto2Response,
+ Response,
+)
+
+from marionette_harness import MarionetteTestCase, skip_unless_protocol
+
+
+get_current_url = ("getCurrentUrl", None)
+execute_script = ("executeScript", {"script": "return 42"})
+
+
+class TestMessageSequencing(MarionetteTestCase):
+ @property
+ def last_id(self):
+ return self.marionette.client.last_id
+
+ @last_id.setter
+ def last_id(self, new_id):
+ self.marionette.client.last_id = new_id
+
+ def send(self, name, params):
+ self.last_id = self.last_id + 1
+ cmd = Command(self.last_id, name, params)
+ self.marionette.client.send(cmd)
+ return self.last_id
+
+ @skip_unless_protocol("Skip for level < 3", lambda level: level >= 3)
+ def test_discard_older_messages(self):
+ first = self.send(*get_current_url)
+ second = self.send(*execute_script)
+ resp = self.marionette.client.receive()
+ self.assertEqual(second, resp.id)
+
+ @skip_unless_protocol("Skip for level < 3", lambda level: level >= 3)
+ def test_last_id_incremented(self):
+ before = self.last_id
+ self.send(*get_current_url)
+ self.assertGreater(self.last_id, before)
+
+
+class MessageTestCase(MarionetteTestCase):
+ def assert_attr(self, obj, attr):
+ self.assertTrue(hasattr(obj, attr),
+ "object does not have attribute {}".format(attr))
+
+
+class TestCommand(MessageTestCase):
+ def create(self, msgid="msgid", name="name", params="params"):
+ return Command(msgid, name, params)
+
+ def test_initialise(self):
+ cmd = self.create()
+ self.assert_attr(cmd, "id")
+ self.assert_attr(cmd, "name")
+ self.assert_attr(cmd, "params")
+ self.assertEqual("msgid", cmd.id)
+ self.assertEqual("name", cmd.name)
+ self.assertEqual("params", cmd.params)
+
+ def test_stringify(self):
+ cmd = self.create()
+ string = str(cmd)
+ self.assertIn("Command", string)
+ self.assertIn("id=msgid", string)
+ self.assertIn("name=name", string)
+ self.assertIn("params=params", string)
+
+ def test_to_msg(self):
+ cmd = self.create()
+ msg = json.loads(cmd.to_msg())
+ self.assertEquals(msg[0], Command.TYPE)
+ self.assertEquals(msg[1], "msgid")
+ self.assertEquals(msg[2], "name")
+ self.assertEquals(msg[3], "params")
+
+ def test_from_msg(self):
+ msg = [Command.TYPE, "msgid", "name", "params"]
+ payload = json.dumps(msg)
+ cmd = Command.from_msg(payload)
+ self.assertEquals(msg[1], cmd.id)
+ self.assertEquals(msg[2], cmd.name)
+ self.assertEquals(msg[3], cmd.params)
+
+
+class TestResponse(MessageTestCase):
+ def create(self, msgid="msgid", error="error", result="result"):
+ return Response(msgid, error, result)
+
+ def test_initialise(self):
+ resp = self.create()
+ self.assert_attr(resp, "id")
+ self.assert_attr(resp, "error")
+ self.assert_attr(resp, "result")
+ self.assertEqual("msgid", resp.id)
+ self.assertEqual("error", resp.error)
+ self.assertEqual("result", resp.result)
+
+ def test_stringify(self):
+ resp = self.create()
+ string = str(resp)
+ self.assertIn("Response", string)
+ self.assertIn("id=msgid", string)
+ self.assertIn("error=error", string)
+ self.assertIn("result=result", string)
+
+ def test_to_msg(self):
+ resp = self.create()
+ msg = json.loads(resp.to_msg())
+ self.assertEquals(msg[0], Response.TYPE)
+ self.assertEquals(msg[1], "msgid")
+ self.assertEquals(msg[2], "error")
+ self.assertEquals(msg[3], "result")
+
+ def test_from_msg(self):
+ msg = [Response.TYPE, "msgid", "error", "result"]
+ payload = json.dumps(msg)
+ resp = Response.from_msg(payload)
+ self.assertEquals(msg[1], resp.id)
+ self.assertEquals(msg[2], resp.error)
+ self.assertEquals(msg[3], resp.result)
+
+
+class TestProto2Command(MessageTestCase):
+ def create(self, name="name", params="params"):
+ return Proto2Command(name, params)
+
+ def test_initialise(self):
+ cmd = self.create()
+ self.assert_attr(cmd, "id")
+ self.assert_attr(cmd, "name")
+ self.assert_attr(cmd, "params")
+ self.assertEqual(None, cmd.id)
+ self.assertEqual("name", cmd.name)
+ self.assertEqual("params", cmd.params)
+
+ def test_from_data_unknown(self):
+ with self.assertRaises(ValueError):
+ cmd = Proto2Command.from_data({})
+
+
+class TestProto2Response(MessageTestCase):
+ def create(self, error="error", result="result"):
+ return Proto2Response(error, result)
+
+ def test_initialise(self):
+ resp = self.create()
+ self.assert_attr(resp, "id")
+ self.assert_attr(resp, "error")
+ self.assert_attr(resp, "result")
+ self.assertEqual(None, resp.id)
+ self.assertEqual("error", resp.error)
+ self.assertEqual("result", resp.result)
+
+ def test_from_data_error(self):
+ data = {"error": "error"}
+ resp = Proto2Response.from_data(data)
+ self.assertEqual(data, resp.error)
+ self.assertEqual(None, resp.result)
+
+ def test_from_data_result(self):
+ resp = Proto2Response.from_data("result")
+ self.assertEqual(None, resp.error)
+ self.assertEqual("result", resp.result)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_typing.py b/testing/marionette/harness/marionette_harness/tests/unit/test_typing.py
new file mode 100644
index 000000000..ca63a0dc7
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_typing.py
@@ -0,0 +1,332 @@
+# 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.errors import ElementNotInteractableException
+from marionette_driver.keys import Keys
+
+from marionette_harness import MarionetteTestCase, skip, skip_if_mobile
+
+
+def inline(doc):
+ return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+
+
+class TypingTestCase(MarionetteTestCase):
+
+ def setUp(self):
+ super(TypingTestCase, self).setUp()
+
+ if self.marionette.session_capabilities["platformName"] == "darwin":
+ self.mod_key = Keys.META
+ else:
+ self.mod_key = Keys.CONTROL
+
+
+class TestTypingChrome(TypingTestCase):
+
+ def setUp(self):
+ super(TestTypingChrome, self).setUp()
+ self.marionette.set_context("chrome")
+
+ @skip_if_mobile("Interacting with chrome elements not available for Fennec")
+ def test_cut_and_paste_shortcuts(self):
+ with self.marionette.using_context("content"):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ self.assertEqual("", keyReporter.get_property("value"))
+ keyReporter.send_keys("zyxwvutsr")
+ self.assertEqual("zyxwvutsr", keyReporter.get_property("value"))
+
+ # select all and cut
+ keyReporter.send_keys(self.mod_key, "a")
+ keyReporter.send_keys(self.mod_key, "x")
+ self.assertEqual("", keyReporter.get_property("value"))
+
+ url_bar = self.marionette.find_element(By.ID, "urlbar")
+
+ # Clear contents first
+ url_bar.send_keys(self.mod_key, "a")
+ url_bar.send_keys(Keys.BACK_SPACE)
+ self.assertEqual("", url_bar.get_attribute("value"))
+
+ url_bar.send_keys(self.mod_key, "v")
+ self.assertEqual("zyxwvutsr", url_bar.get_property("value"))
+
+
+class TestTypingContent(TypingTestCase):
+
+ def testShouldFireKeyPressEvents(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys("a")
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertTrue("press:" in result.text)
+
+ def testShouldFireKeyDownEvents(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys("I")
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertTrue("down" in result.text)
+
+ def testShouldFireKeyUpEvents(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys("a")
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertTrue("up:" in result.text)
+
+ def testShouldTypeLowerCaseLetters(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys("abc def")
+ self.assertEqual("abc def", keyReporter.get_property("value"))
+
+ def testShouldBeAbleToTypeCapitalLetters(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys("ABC DEF")
+ self.assertEqual("ABC DEF", keyReporter.get_property("value"))
+
+ def testCutAndPasteShortcuts(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ self.assertEqual("", keyReporter.get_property("value"))
+ keyReporter.send_keys("zyxwvutsr")
+ self.assertEqual("zyxwvutsr", keyReporter.get_property("value"))
+
+ # select all and cut
+ keyReporter.send_keys(self.mod_key, "a")
+ keyReporter.send_keys(self.mod_key, "x")
+ self.assertEqual("", keyReporter.get_property("value"))
+
+ keyReporter.send_keys(self.mod_key, "v")
+ self.assertEqual("zyxwvutsr", keyReporter.get_property("value"))
+
+ def testShouldBeAbleToTypeQuoteMarks(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys("\"")
+ self.assertEqual("\"", keyReporter.get_property("value"))
+
+ def testShouldBeAbleToTypeTheAtCharacter(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys("@")
+ self.assertEqual("@", keyReporter.get_property("value"))
+
+ def testShouldBeAbleToMixUpperAndLowerCaseLetters(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys("me@eXample.com")
+ self.assertEqual("me@eXample.com", keyReporter.get_property("value"))
+
+ def testArrowKeysShouldNotBePrintable(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ keyReporter = self.marionette.find_element(By.ID, "keyReporter")
+ keyReporter.send_keys(Keys.ARROW_LEFT)
+ self.assertEqual("", keyReporter.get_property("value"))
+
+ def testWillSimulateAKeyUpWhenEnteringTextIntoInputElements(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyUp")
+ element.send_keys("I like cheese")
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertEqual(result.text, "I like cheese")
+
+ def testWillSimulateAKeyDownWhenEnteringTextIntoInputElements(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyDown")
+ element.send_keys("I like cheese")
+ result = self.marionette.find_element(By.ID, "result")
+ # Because the key down gets the result before the input element is
+ # filled, we're a letter short here
+ self.assertEqual(result.text, "I like chees")
+
+ def testWillSimulateAKeyPressWhenEnteringTextIntoInputElements(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyPress")
+ element.send_keys("I like cheese")
+ result = self.marionette.find_element(By.ID, "result")
+ # Because the key down gets the result before the input element is
+ # filled, we're a letter short here
+ self.assertEqual(result.text, "I like chees")
+
+ def testWillSimulateAKeyUpWhenEnteringTextIntoTextAreas(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyUpArea")
+ element.send_keys("I like cheese")
+ result = self.marionette.find_element(By.ID, "result")
+ self.assertEqual("I like cheese", result.text)
+
+ def testWillSimulateAKeyDownWhenEnteringTextIntoTextAreas(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyDownArea")
+ element.send_keys("I like cheese")
+ result = self.marionette.find_element(By.ID, "result")
+ # Because the key down gets the result before the input element is
+ # filled, we're a letter short here
+ self.assertEqual(result.text, "I like chees")
+
+ def testWillSimulateAKeyPressWhenEnteringTextIntoTextAreas(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyPressArea")
+ element.send_keys("I like cheese")
+ result = self.marionette.find_element(By.ID, "result")
+ # Because the key down gets the result before the input element is
+ # filled, we're a letter short here
+ self.assertEqual(result.text, "I like chees")
+
+ @skip_if_mobile("Bug 1324752 - Arrow keys cannot be sent in Fennec")
+ def testShouldReportKeyCodeOfArrowKeysUpDownEvents(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ result = self.marionette.find_element(By.ID, "result")
+ element = self.marionette.find_element(By.ID, "keyReporter")
+
+ element.send_keys(Keys.ARROW_DOWN)
+
+ self.assertIn("down: 40", result.text.strip())
+ self.assertIn("up: 40", result.text.strip())
+
+ element.send_keys(Keys.ARROW_UP)
+ self.assertIn("down: 38", result.text.strip())
+ self.assertIn("up: 38", result.text.strip())
+
+ element.send_keys(Keys.ARROW_LEFT)
+ self.assertIn("down: 37", result.text.strip())
+ self.assertIn("up: 37", result.text.strip())
+
+ element.send_keys(Keys.ARROW_RIGHT)
+ self.assertIn("down: 39", result.text.strip())
+ self.assertIn("up: 39", result.text.strip())
+
+ # And leave no rubbish/printable keys in the "keyReporter"
+ self.assertEqual("", element.get_property("value"))
+
+ @skip("Reenable in Bug 1068728")
+ def testNumericShiftKeys(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ result = self.marionette.find_element(By.ID, "result")
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ numericShiftsEtc = "~!@#$%^&*()_+{}:i\"<>?|END~"
+ element.send_keys(numericShiftsEtc)
+ self.assertEqual(numericShiftsEtc, element.get_property("value"))
+ self.assertIn(" up: 16", result.text.strip())
+
+ def testLowerCaseAlphaKeys(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ lowerAlphas = "abcdefghijklmnopqrstuvwxyz"
+ element.send_keys(lowerAlphas)
+ self.assertEqual(lowerAlphas, element.get_property("value"))
+
+ @skip("Reenable in Bug 1068735")
+ def testUppercaseAlphaKeys(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ result = self.marionette.find_element(By.ID, "result")
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ upperAlphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ element.send_keys(upperAlphas)
+ self.assertEqual(upperAlphas, element.get_property("value"))
+ self.assertIn(" up: 16", result.text.strip())
+
+ @skip("Reenable in Bug 1068726")
+ def testAllPrintableKeys(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ result = self.marionette.find_element(By.ID, "result")
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ allPrintable = "!\"#$%&'()*+,-./0123456789:<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
+ element.send_keys(allPrintable)
+
+ self.assertTrue(allPrintable, element.get_property("value"))
+ self.assertIn(" up: 16", result.text.strip())
+
+ @skip("Reenable in Bug 1068733")
+ def testSpecialSpaceKeys(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ element.send_keys("abcd" + Keys.SPACE + "fgh" + Keys.SPACE + "ij")
+ self.assertEqual("abcd fgh ij", element.get_property("value"))
+
+ def testShouldTypeAnInteger(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ element = self.marionette.find_element(By.ID, "keyReporter")
+ element.send_keys(1234)
+ self.assertEqual("1234", element.get_property("value"))
+
+ def testShouldSendKeysToElementsWithoutTheValueAttribute(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ # If we don't get an error below we are good
+ self.marionette.find_element(By.TAG_NAME, "body").send_keys("foo")
+
+ def test_not_interactable_if_hidden(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ not_displayed = self.marionette.find_element(By.ID, "notDisplayed")
+ self.assertRaises(ElementNotInteractableException, not_displayed.send_keys, "foo")
+
+ def test_appends_to_input_text(self):
+ self.marionette.navigate(inline("<input>"))
+ el = self.marionette.find_element(By.TAG_NAME, "input")
+ el.send_keys("foo")
+ el.send_keys("bar")
+ self.assertEqual("foobar", el.get_property("value"))
+
+ def test_appends_to_textarea(self):
+ self.marionette.navigate(inline("<textarea></textarea>"))
+ textarea = self.marionette.find_element(By.TAG_NAME, "textarea")
+ textarea.send_keys("foo")
+ textarea.send_keys("bar")
+ self.assertEqual("foobar", textarea.get_property("value"))
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_using_permissions.py b/testing/marionette/harness/marionette_harness/tests/unit/test_using_permissions.py
new file mode 100644
index 000000000..71e271dd4
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_using_permissions.py
@@ -0,0 +1,46 @@
+# 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 TestUsingPermssions(MarionetteTestCase):
+
+ def test_using_permissions(self):
+ # Test that multiple permissions can be set with 'using_permissions',
+ # and that they are set correctly and unset correctly after leaving
+ # the context manager.
+ original_perm = self.marionette.get_permission('systemXHR')
+ original_alarm = self.marionette.get_permission('alarms')
+ new_perm = True if original_perm != 1 else False
+ new_alarm = True if original_alarm != 1 else False
+ with self.marionette.using_permissions({'systemXHR': new_perm,
+ 'alarms': new_alarm}):
+ now_perm = self.marionette.get_permission('systemXHR')
+ now_alarm = self.marionette.get_permission('alarms')
+ self.assertEquals(new_perm, now_perm)
+ self.assertNotEquals(now_perm, original_perm)
+ self.assertEquals(new_alarm, now_alarm)
+ self.assertNotEquals(now_alarm, original_alarm)
+ self.assertEquals(original_perm,
+ self.marionette.get_permission('systemXHR'))
+ self.assertEquals(original_alarm,
+ self.marionette.get_permission('alarms'))
+
+ def test_exception_using_permissions(self):
+ # Test that throwing an exception inside the context manager doesn't
+ # prevent the permissions from being restored at context manager exit.
+ original_perm = self.marionette.get_permission('systemXHR')
+ new_perm = True if original_perm != 1 else False
+ with self.marionette.using_permissions({'systemXHR': new_perm}):
+ now_perm = self.marionette.get_permission('systemXHR')
+ self.assertEquals(new_perm, now_perm)
+ self.assertNotEquals(now_perm, original_perm)
+ self.assertRaises(JavascriptException,
+ self.marionette.execute_script,
+ "return foo.bar.baz;")
+ self.assertEquals(original_perm,
+ self.marionette.get_permission('systemXHR'))
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py b/testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py
new file mode 100644
index 000000000..750ecf20a
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py
@@ -0,0 +1,121 @@
+# 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 TestVisibility(MarionetteTestCase):
+
+ def testShouldAllowTheUserToTellIfAnElementIsDisplayedOrNot(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ self.assertTrue(self.marionette.find_element(By.ID, "displayed").is_displayed())
+ self.assertFalse(self.marionette.find_element(By.ID, "none").is_displayed())
+ self.assertFalse(self.marionette.find_element(By.ID,
+ "suppressedParagraph").is_displayed())
+ self.assertFalse(self.marionette.find_element(By.ID, "hidden").is_displayed())
+
+ def testVisibilityShouldTakeIntoAccountParentVisibility(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ childDiv = self.marionette.find_element(By.ID, "hiddenchild")
+ hiddenLink = self.marionette.find_element(By.ID, "hiddenlink")
+
+ self.assertFalse(childDiv.is_displayed())
+ self.assertFalse(hiddenLink.is_displayed())
+
+ def testShouldCountElementsAsVisibleIfStylePropertyHasBeenSet(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ shown = self.marionette.find_element(By.ID, "visibleSubElement")
+ self.assertTrue(shown.is_displayed())
+
+ def testShouldModifyTheVisibilityOfAnElementDynamically(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+ element = self.marionette.find_element(By.ID, "hideMe")
+ self.assertTrue(element.is_displayed())
+ element.click()
+ self.assertFalse(element.is_displayed())
+
+ def testHiddenInputElementsAreNeverVisible(self):
+ test_html = self.marionette.absolute_url("javascriptPage.html")
+ self.marionette.navigate(test_html)
+
+ shown = self.marionette.find_element(By.NAME, "hidden")
+
+ self.assertFalse(shown.is_displayed())
+
+ def testShouldSayElementsWithNegativeTransformAreNotDisplayed(self):
+ test_html = self.marionette.absolute_url("cssTransform.html")
+ self.marionette.navigate(test_html)
+
+ elementX = self.marionette.find_element(By.ID, 'parentX')
+ self.assertFalse(elementX.is_displayed())
+ elementY = self.marionette.find_element(By.ID, 'parentY')
+ self.assertFalse(elementY.is_displayed())
+
+ def testShouldSayElementsWithParentWithNegativeTransformAreNotDisplayed(self):
+ test_html = self.marionette.absolute_url("cssTransform.html")
+ self.marionette.navigate(test_html)
+
+ elementX = self.marionette.find_element(By.ID, 'childX')
+ self.assertFalse(elementX.is_displayed())
+ elementY = self.marionette.find_element(By.ID, 'childY')
+ self.assertFalse(elementY.is_displayed())
+
+ def testShouldSayElementWithZeroTransformIsVisible(self):
+ test_html = self.marionette.absolute_url("cssTransform.html")
+ self.marionette.navigate(test_html)
+
+ zero_tranform = self.marionette.find_element(By.ID, 'zero-tranform')
+ self.assertTrue(zero_tranform.is_displayed())
+
+ def testShouldSayElementIsVisibleWhenItHasNegativeTransformButElementisntInANegativeSpace(self):
+ test_html = self.marionette.absolute_url("cssTransform2.html")
+ self.marionette.navigate(test_html)
+ negative_percent__tranform = self.marionette.find_element(By.ID, 'negative-percentage-transformY')
+ self.assertTrue(negative_percent__tranform.is_displayed())
+
+ def testShouldSayElementIsInvisibleWhenOverflowXIsHiddenAndOutOfViewport(self):
+ test_html = self.marionette.absolute_url("bug814037.html")
+ self.marionette.navigate(test_html)
+ overflow_x = self.marionette.find_element(By.ID, "assertMe2")
+ self.assertFalse(overflow_x.is_displayed())
+
+ def testShouldShowElementNotVisibleWithHiddenAttribute(self):
+ test_html = self.marionette.absolute_url("hidden.html")
+ self.marionette.navigate(test_html)
+ singleHidden = self.marionette.find_element(By.ID, 'singleHidden')
+ self.assertFalse(singleHidden.is_displayed())
+
+ def testShouldShowElementNotVisibleWhenParentElementHasHiddenAttribute(self):
+ test_html = self.marionette.absolute_url("hidden.html")
+ self.marionette.navigate(test_html)
+ child = self.marionette.find_element(By.ID, 'child')
+ self.assertFalse(child.is_displayed())
+
+ def testShouldClickOnELementPartiallyOffLeft(self):
+ test_html = self.marionette.absolute_url("element_left.html")
+ self.marionette.navigate(test_html)
+ self.marionette.find_element(By.CSS_SELECTOR, '.element').click()
+
+ def testShouldClickOnELementPartiallyOffRight(self):
+ test_html = self.marionette.absolute_url("element_right.html")
+ self.marionette.navigate(test_html)
+ self.marionette.find_element(By.CSS_SELECTOR, '.element').click()
+
+ def testShouldClickOnELementPartiallyOffTop(self):
+ test_html = self.marionette.absolute_url("element_top.html")
+ self.marionette.navigate(test_html)
+ self.marionette.find_element(By.CSS_SELECTOR, '.element').click()
+
+ def testShouldClickOnELementPartiallyOffBottom(self):
+ test_html = self.marionette.absolute_url("element_bottom.html")
+ self.marionette.navigate(test_html)
+ self.marionette.find_element(By.CSS_SELECTOR, '.element').click()
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_wait.py b/testing/marionette/harness/marionette_harness/tests/unit/test_wait.py
new file mode 100644
index 000000000..6a4872773
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_wait.py
@@ -0,0 +1,347 @@
+# 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 time
+
+from marionette_driver import errors, wait
+from marionette_driver.wait import Wait
+
+from marionette_harness import MarionetteTestCase
+
+
+class TickingClock(object):
+
+ def __init__(self, incr=1):
+ self.ticks = 0
+ self.increment = incr
+
+ def sleep(self, dur=None):
+ dur = dur if dur is not None else self.increment
+ self.ticks += dur
+
+ @property
+ def now(self):
+ return self.ticks
+
+
+class SequenceClock(object):
+
+ def __init__(self, times):
+ self.times = times
+ self.i = 0
+
+ @property
+ def now(self):
+ if len(self.times) > self.i:
+ self.i += 1
+ return self.times[self.i - 1]
+
+ def sleep(self, dur):
+ pass
+
+
+class MockMarionette(object):
+
+ def __init__(self):
+ self.waited = 0
+
+ def exception(self, e=None, wait=1):
+ self.wait()
+ if self.waited == wait:
+ if e is None:
+ e = Exception
+ raise e
+
+ def true(self, wait=1):
+ self.wait()
+ if self.waited == wait:
+ return True
+ return None
+
+ def false(self, wait=1):
+ self.wait()
+ return False
+
+ def none(self, wait=1):
+ self.wait()
+ return None
+
+ def value(self, value, wait=1):
+ self.wait()
+ if self.waited == wait:
+ return value
+ return None
+
+ def wait(self):
+ self.waited += 1
+
+
+def at_third_attempt(clock, end):
+ return clock.now == 2
+
+
+def now(clock, end):
+ return True
+
+
+class SystemClockTest(MarionetteTestCase):
+
+ def setUp(self):
+ super(SystemClockTest, self).setUp()
+ self.clock = wait.SystemClock()
+
+ def test_construction_initializes_time(self):
+ self.assertEqual(self.clock._time, time)
+
+ def test_sleep(self):
+ start = time.time()
+ self.clock.sleep(0.1)
+ end = time.time() - start
+ self.assertGreater(end, 0)
+
+ def test_time_now(self):
+ self.assertIsNotNone(self.clock.now)
+
+
+class FormalWaitTest(MarionetteTestCase):
+
+ def setUp(self):
+ super(FormalWaitTest, self).setUp()
+ self.m = MockMarionette()
+ self.m.timeout = 123
+
+ def test_construction_with_custom_timeout(self):
+ wt = Wait(self.m, timeout=42)
+ self.assertEqual(wt.timeout, 42)
+
+ def test_construction_with_custom_interval(self):
+ wt = Wait(self.m, interval=42)
+ self.assertEqual(wt.interval, 42)
+
+ def test_construction_with_custom_clock(self):
+ c = TickingClock(1)
+ wt = Wait(self.m, clock=c)
+ self.assertEqual(wt.clock, c)
+
+ def test_construction_with_custom_exception(self):
+ wt = Wait(self.m, ignored_exceptions=Exception)
+ self.assertIn(Exception, wt.exceptions)
+ self.assertEqual(len(wt.exceptions), 1)
+
+ def test_construction_with_custom_exception_list(self):
+ exc = [Exception, ValueError]
+ wt = Wait(self.m, ignored_exceptions=exc)
+ for e in exc:
+ self.assertIn(e, wt.exceptions)
+ self.assertEqual(len(wt.exceptions), len(exc))
+
+ def test_construction_with_custom_exception_tuple(self):
+ exc = (Exception, ValueError)
+ wt = Wait(self.m, ignored_exceptions=exc)
+ for e in exc:
+ self.assertIn(e, wt.exceptions)
+ self.assertEqual(len(wt.exceptions), len(exc))
+
+ def test_duplicate_exceptions(self):
+ wt = Wait(self.m, ignored_exceptions=[Exception, Exception])
+ self.assertIn(Exception, wt.exceptions)
+ self.assertEqual(len(wt.exceptions), 1)
+
+ def test_default_timeout(self):
+ self.assertEqual(wait.DEFAULT_TIMEOUT, 5)
+
+ def test_default_interval(self):
+ self.assertEqual(wait.DEFAULT_INTERVAL, 0.1)
+
+ def test_end_property(self):
+ wt = Wait(self.m)
+ self.assertIsNotNone(wt.end)
+
+ def test_marionette_property(self):
+ wt = Wait(self.m)
+ self.assertEqual(wt.marionette, self.m)
+
+ def test_clock_property(self):
+ wt = Wait(self.m)
+ self.assertIsInstance(wt.clock, wait.SystemClock)
+
+ def test_timeout_uses_default_if_marionette_timeout_is_none(self):
+ self.m.timeout = None
+ wt = Wait(self.m)
+ self.assertEqual(wt.timeout, wait.DEFAULT_TIMEOUT)
+
+
+class PredicatesTest(MarionetteTestCase):
+
+ def test_until(self):
+ c = wait.SystemClock()
+ self.assertFalse(wait.until_pred(c, sys.maxint))
+ self.assertTrue(wait.until_pred(c, 0))
+
+
+class WaitUntilTest(MarionetteTestCase):
+
+ def setUp(self):
+ super(WaitUntilTest, self).setUp()
+
+ self.m = MockMarionette()
+ self.clock = TickingClock()
+ self.wt = Wait(self.m, timeout=10, interval=1, clock=self.clock)
+
+ def test_true(self):
+ r = self.wt.until(lambda x: x.true())
+ self.assertTrue(r)
+ self.assertEqual(self.clock.ticks, 0)
+
+ def test_true_within_timeout(self):
+ r = self.wt.until(lambda x: x.true(wait=5))
+ self.assertTrue(r)
+ self.assertEqual(self.clock.ticks, 4)
+
+ def test_timeout(self):
+ with self.assertRaises(errors.TimeoutException):
+ r = self.wt.until(lambda x: x.true(wait=15))
+ self.assertEqual(self.clock.ticks, 10)
+
+ def test_exception_raises_immediately(self):
+ with self.assertRaises(TypeError):
+ self.wt.until(lambda x: x.exception(e=TypeError))
+ self.assertEqual(self.clock.ticks, 0)
+
+ def test_ignored_exception(self):
+ self.wt.exceptions = (TypeError,)
+ with self.assertRaises(errors.TimeoutException):
+ self.wt.until(lambda x: x.exception(e=TypeError))
+
+ def test_ignored_exception_wrapped_in_timeoutexception(self):
+ self.wt.exceptions = (TypeError,)
+
+ exc = None
+ try:
+ self.wt.until(lambda x: x.exception(e=TypeError))
+ except Exception as e:
+ exc = e
+
+ s = str(exc)
+ self.assertIsNotNone(exc)
+ self.assertIsInstance(exc, errors.TimeoutException)
+ self.assertIn(", caused by {0!r}".format(TypeError), s)
+ self.assertIn("self.wt.until(lambda x: x.exception(e=TypeError))", s)
+
+ def test_ignored_exception_after_timeout_is_not_raised(self):
+ with self.assertRaises(errors.TimeoutException):
+ r = self.wt.until(lambda x: x.exception(wait=15))
+ self.assertEqual(self.clock.ticks, 10)
+
+ def test_keyboard_interrupt(self):
+ with self.assertRaises(KeyboardInterrupt):
+ self.wt.until(lambda x: x.exception(e=KeyboardInterrupt))
+
+ def test_system_exit(self):
+ with self.assertRaises(SystemExit):
+ self.wt.until(lambda x: x.exception(SystemExit))
+
+ def test_true_condition_returns_immediately(self):
+ r = self.wt.until(lambda x: x.true())
+ self.assertIsInstance(r, bool)
+ self.assertTrue(r)
+ self.assertEqual(self.clock.ticks, 0)
+
+ def test_value(self):
+ r = self.wt.until(lambda x: "foo")
+ self.assertEqual(r, "foo")
+ self.assertEqual(self.clock.ticks, 0)
+
+ def test_custom_predicate(self):
+ r = self.wt.until(lambda x: x.true(wait=2), is_true=at_third_attempt)
+ self.assertTrue(r)
+ self.assertEqual(self.clock.ticks, 1)
+
+ def test_custom_predicate_times_out(self):
+ with self.assertRaises(errors.TimeoutException):
+ self.wt.until(lambda x: x.true(wait=4), is_true=at_third_attempt)
+
+ self.assertEqual(self.clock.ticks, 2)
+
+ def test_timeout_elapsed_duration(self):
+ with self.assertRaisesRegexp(errors.TimeoutException,
+ "Timed out after 2.0 seconds"):
+ self.wt.until(lambda x: x.true(wait=4), is_true=at_third_attempt)
+
+ def test_timeout_elapsed_rounding(self):
+ wt = Wait(self.m, clock=SequenceClock([1, 0.01, 1]), timeout=0)
+ with self.assertRaisesRegexp(errors.TimeoutException,
+ "Timed out after 1.0 seconds"):
+ wt.until(lambda x: x.true(), is_true=now)
+
+ def test_timeout_elapsed_interval_by_delayed_condition_return(self):
+ def callback(mn):
+ self.clock.sleep(11)
+ return mn.false()
+
+ with self.assertRaisesRegexp(errors.TimeoutException,
+ "Timed out after 11.0 seconds"):
+ self.wt.until(callback)
+ # With a delayed conditional return > timeout, only 1 iteration is
+ # possible
+ self.assertEqual(self.m.waited, 1)
+
+ def test_timeout_with_delayed_condition_return(self):
+ def callback(mn):
+ self.clock.sleep(.5)
+ return mn.false()
+
+ with self.assertRaisesRegexp(errors.TimeoutException,
+ "Timed out after 10.0 seconds"):
+ self.wt.until(callback)
+ # With a delayed conditional return < interval, 10 iterations should be
+ # possible
+ self.assertEqual(self.m.waited, 10)
+
+ def test_timeout_interval_shorter_than_delayed_condition_return(self):
+ def callback(mn):
+ self.clock.sleep(2)
+ return mn.false()
+
+ with self.assertRaisesRegexp(errors.TimeoutException,
+ "Timed out after 10.0 seconds"):
+ self.wt.until(callback)
+ # With a delayed return of the conditional which takes twice that long than the interval,
+ # half of the iterations should be possible
+ self.assertEqual(self.m.waited, 5)
+
+ def test_message(self):
+ self.wt.exceptions = (TypeError,)
+ exc = None
+ try:
+ self.wt.until(lambda x: x.exception(e=TypeError), message="hooba")
+ except errors.TimeoutException as e:
+ exc = e
+
+ result = str(exc)
+ self.assertIn("seconds with message: hooba, caused by", result)
+
+ def test_no_message(self):
+ self.wt.exceptions = (TypeError,)
+ exc = None
+ try:
+ self.wt.until(lambda x: x.exception(e=TypeError), message="")
+ except errors.TimeoutException as e:
+ exc = e
+
+ result = str(exc)
+ self.assertIn("seconds, caused by", result)
+
+ def test_message_has_none_as_its_value(self):
+ self.wt.exceptions = (TypeError,)
+ exc = None
+ try:
+ self.wt.until(False, None, None)
+ except errors.TimeoutException as e:
+ exc = e
+
+ result = str(exc)
+ self.assertNotIn("with message:", result)
+ self.assertNotIn("secondsNone", result)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py
new file mode 100644
index 000000000..18ae191c1
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.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/.
+
+from marionette_harness import MarionetteTestCase, WindowManagerMixin
+
+
+class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
+
+ def setUp(self):
+ super(TestCloseWindow, self).setUp()
+
+ self.marionette.set_context("chrome")
+
+ def tearDown(self):
+ self.close_all_windows()
+ self.close_all_tabs()
+
+ super(TestCloseWindow, self).tearDown()
+
+ def test_close_chrome_window_for_browser_window(self):
+ win = self.open_window()
+ self.marionette.switch_to_window(win)
+
+ self.assertNotIn(win, self.marionette.window_handles)
+ chrome_window_handles = self.marionette.close_chrome_window()
+ self.assertNotIn(win, chrome_window_handles)
+ self.assertListEqual(self.start_windows, chrome_window_handles)
+ self.assertNotIn(win, self.marionette.window_handles)
+
+ def test_close_chrome_window_for_non_browser_window(self):
+
+ def open_window_with_js():
+ self.marionette.execute_script("""
+ window.open('chrome://marionette/content/test.xul',
+ 'foo', 'chrome,centerscreen');
+ """)
+
+ win = self.open_window(trigger=open_window_with_js)
+ self.marionette.switch_to_window(win)
+
+ self.assertIn(win, self.marionette.window_handles)
+ chrome_window_handles = self.marionette.close_chrome_window()
+ self.assertNotIn(win, chrome_window_handles)
+ self.assertListEqual(self.start_windows, chrome_window_handles)
+ self.assertNotIn(win, self.marionette.window_handles)
+
+ def test_close_chrome_window_for_last_open_window(self):
+ self.close_all_windows()
+
+ self.assertListEqual([], self.marionette.close_chrome_window())
+ self.assertListEqual([self.start_tab], self.marionette.window_handles)
+ self.assertListEqual([self.start_window], self.marionette.chrome_window_handles)
+ self.assertIsNotNone(self.marionette.session)
+
+ def test_close_window_for_browser_tab(self):
+ tab = self.open_tab()
+ self.marionette.switch_to_window(tab)
+
+ window_handles = self.marionette.close()
+ self.assertNotIn(tab, window_handles)
+ self.assertListEqual(self.start_tabs, window_handles)
+
+ def test_close_window_for_browser_window_with_single_tab(self):
+ win = self.open_window()
+ self.marionette.switch_to_window(win)
+
+ self.assertEqual(len(self.start_tabs) + 1, len(self.marionette.window_handles))
+ window_handles = self.marionette.close()
+ self.assertNotIn(win, window_handles)
+ self.assertListEqual(self.start_tabs, window_handles)
+ self.assertListEqual(self.start_windows, self.marionette.chrome_window_handles)
+
+ def test_close_window_for_last_open_tab(self):
+ self.close_all_tabs()
+
+ self.assertListEqual([], self.marionette.close())
+ self.assertListEqual([self.start_tab], self.marionette.window_handles)
+ self.assertListEqual([self.start_window], self.marionette.chrome_window_handles)
+ self.assertIsNotNone(self.marionette.session)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py
new file mode 100644
index 000000000..8e6485e54
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py
@@ -0,0 +1,81 @@
+# 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, skip_if_mobile, WindowManagerMixin
+
+
+class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
+
+ def tearDown(self):
+ self.close_all_windows()
+ self.close_all_tabs()
+
+ super(TestCloseWindow, self).tearDown()
+
+ @skip_if_mobile("Interacting with chrome windows not available for Fennec")
+ def test_close_chrome_window_for_browser_window(self):
+ win = self.open_window()
+ self.marionette.switch_to_window(win)
+
+ self.assertNotIn(win, self.marionette.window_handles)
+ chrome_window_handles = self.marionette.close_chrome_window()
+ self.assertNotIn(win, chrome_window_handles)
+ self.assertListEqual(self.start_windows, chrome_window_handles)
+ self.assertNotIn(win, self.marionette.window_handles)
+
+ @skip_if_mobile("Interacting with chrome windows not available for Fennec")
+ def test_close_chrome_window_for_non_browser_window(self):
+
+ def open_window_with_js():
+ with self.marionette.using_context("chrome"):
+ self.marionette.execute_script("""
+ window.open('chrome://marionette/content/test.xul',
+ 'foo', 'chrome,centerscreen');
+ """)
+
+ win = self.open_window(trigger=open_window_with_js)
+ self.marionette.switch_to_window(win)
+
+ self.assertIn(win, self.marionette.window_handles)
+ chrome_window_handles = self.marionette.close_chrome_window()
+ self.assertNotIn(win, chrome_window_handles)
+ self.assertListEqual(self.start_windows, chrome_window_handles)
+ self.assertNotIn(win, self.marionette.window_handles)
+
+ @skip_if_mobile("Interacting with chrome windows not available for Fennec")
+ def test_close_chrome_window_for_last_open_window(self):
+ self.close_all_windows()
+
+ self.assertListEqual([], self.marionette.close_chrome_window())
+ self.assertListEqual([self.start_tab], self.marionette.window_handles)
+ self.assertListEqual([self.start_window], self.marionette.chrome_window_handles)
+ self.assertIsNotNone(self.marionette.session)
+
+ @skip_if_mobile("Needs application independent method to open a new tab")
+ def test_close_window_for_browser_tab(self):
+ tab = self.open_tab()
+ self.marionette.switch_to_window(tab)
+
+ window_handles = self.marionette.close()
+ self.assertNotIn(tab, window_handles)
+ self.assertListEqual(self.start_tabs, window_handles)
+
+ @skip_if_mobile("Interacting with chrome windows not available for Fennec")
+ def test_close_window_for_browser_window_with_single_tab(self):
+ win = self.open_window()
+ self.marionette.switch_to_window(win)
+
+ self.assertEqual(len(self.start_tabs) + 1, len(self.marionette.window_handles))
+ window_handles = self.marionette.close()
+ self.assertNotIn(win, window_handles)
+ self.assertListEqual(self.start_tabs, window_handles)
+ self.assertListEqual(self.start_windows, self.marionette.chrome_window_handles)
+
+ def test_close_window_for_last_open_tab(self):
+ self.close_all_tabs()
+
+ self.assertListEqual([], self.marionette.close())
+ self.assertListEqual([self.start_tab], self.marionette.window_handles)
+ self.assertListEqual([self.start_window], self.marionette.chrome_window_handles)
+ self.assertIsNotNone(self.marionette.session)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
new file mode 100644
index 000000000..7260d6324
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
@@ -0,0 +1,207 @@
+# 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_harness import MarionetteTestCase, WindowManagerMixin
+
+
+class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
+
+ def setUp(self):
+ super(TestWindowHandles, self).setUp()
+
+ self.empty_page = self.marionette.absolute_url("empty.html")
+ self.test_page = self.marionette.absolute_url("windowHandles.html")
+ self.marionette.navigate(self.test_page)
+
+ self.marionette.set_context("chrome")
+
+ def tearDown(self):
+ self.close_all_windows()
+ self.close_all_tabs()
+
+ super(TestWindowHandles, self).tearDown()
+
+ def test_chrome_window_handles_with_scopes(self):
+ # Open a browser and a non-browser (about window) chrome window
+ self.open_window(
+ trigger=lambda: self.marionette.execute_script("window.open();"))
+ self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+
+ self.open_window(
+ trigger=lambda: self.marionette.find_element(By.ID, "aboutName").click())
+ self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 2)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+
+ chrome_window_handles_in_chrome_scope = self.marionette.chrome_window_handles
+ window_handles_in_chrome_scope = self.marionette.window_handles
+
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.chrome_window_handles,
+ chrome_window_handles_in_chrome_scope)
+ self.assertEqual(self.marionette.window_handles,
+ window_handles_in_chrome_scope)
+
+ def test_chrome_window_handles_after_opening_new_window(self):
+ def open_with_link():
+ with self.marionette.using_context("content"):
+ link = self.marionette.find_element(By.ID, "new-window")
+ link.click()
+
+ # We open a new window but are actually interested in the new tab
+ new_win = self.open_window(trigger=open_with_link)
+ self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+
+ # Check that the new tab has the correct page loaded
+ self.marionette.switch_to_window(new_win)
+ self.assertEqual(self.marionette.current_chrome_window_handle, new_win)
+ with self.marionette.using_context("content"):
+ Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
+ lambda mn: mn.get_url() == self.empty_page,
+ message="{} did not load after opening a new tab".format(self.empty_page))
+
+ # Ensure navigate works in our current window
+ other_page = self.marionette.absolute_url("test.html")
+ with self.marionette.using_context("content"):
+ self.marionette.navigate(other_page)
+ self.assertEqual(self.marionette.get_url(), other_page)
+
+ # Close the opened window and carry on in our original tab.
+ self.marionette.close()
+ self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
+
+ self.marionette.switch_to_window(self.start_window)
+ self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ def test_window_handles_after_opening_new_tab(self):
+ def open_with_link():
+ with self.marionette.using_context("content"):
+ link = self.marionette.find_element(By.ID, "new-tab")
+ link.click()
+
+ new_tab = self.open_tab(trigger=open_with_link)
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ with self.marionette.using_context("content"):
+ Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
+ lambda mn: mn.get_url() == self.empty_page,
+ message="{} did not load after opening a new tab".format(self.empty_page))
+
+ # Ensure navigate works in our current tab
+ other_page = self.marionette.absolute_url("test.html")
+ with self.marionette.using_context("content"):
+ self.marionette.navigate(other_page)
+ self.assertEqual(self.marionette.get_url(), other_page)
+
+ self.marionette.switch_to_window(self.start_tab)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ self.marionette.switch_to_window(new_tab)
+ self.marionette.close()
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+ self.marionette.switch_to_window(self.start_tab)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ def test_window_handles_after_opening_new_window(self):
+ def open_with_link():
+ with self.marionette.using_context("content"):
+ link = self.marionette.find_element(By.ID, "new-window")
+ link.click()
+
+ # We open a new window but are actually interested in the new tab
+ new_tab = self.open_tab(trigger=open_with_link)
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ # Check that the new tab has the correct page loaded
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ with self.marionette.using_context("content"):
+ Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
+ lambda mn: mn.get_url() == self.empty_page,
+ message="{} did not load after opening a new tab".format(self.empty_page))
+
+ # Ensure navigate works in our current window
+ other_page = self.marionette.absolute_url("test.html")
+ with self.marionette.using_context("content"):
+ self.marionette.navigate(other_page)
+ self.assertEqual(self.marionette.get_url(), other_page)
+
+ # Close the opened window and carry on in our original tab.
+ self.marionette.close()
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+ self.marionette.switch_to_window(self.start_tab)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ def test_window_handles_after_closing_original_tab(self):
+ def open_with_link():
+ with self.marionette.using_context("content"):
+ link = self.marionette.find_element(By.ID, "new-tab")
+ link.click()
+
+ new_tab = self.open_tab(trigger=open_with_link)
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ self.marionette.close()
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ with self.marionette.using_context("content"):
+ Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
+ lambda mn: mn.get_url() == self.empty_page,
+ message="{} did not load after opening a new tab".format(self.empty_page))
+
+ def test_window_handles_no_switch(self):
+ """Regression test for bug 1294456.
+ This test is testing the case where Marionette attempts to send a
+ command to a window handle when the browser has opened and selected
+ a new tab. Before bug 1294456 landed, the Marionette driver was getting
+ confused about which window handle the client cared about, and assumed
+ it was the window handle for the newly opened and selected tab.
+
+ This caused Marionette to think that the browser needed to do a remoteness
+ flip in the e10s case, since the tab opened by menu_newNavigatorTab is
+ about:newtab (which is currently non-remote). This meant that commands
+ sent to what should have been the original window handle would be
+ queued and never sent, since the remoteness flip in the new tab was
+ never going to happen.
+ """
+ def open_with_menu():
+ menu_new_tab = self.marionette.find_element(By.ID, 'menu_newNavigatorTab')
+ menu_new_tab.click()
+
+ new_tab = self.open_tab(trigger=open_with_menu)
+
+ # We still have the default tab set as our window handle. This
+ # get_url command should be sent immediately, and not be forever-queued.
+ with self.marionette.using_context("content"):
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+
+ self.marionette.close()
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+ self.marionette.switch_to_window(self.start_tab)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
new file mode 100644
index 000000000..b6ad3a6c3
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
@@ -0,0 +1,96 @@
+# 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_harness import MarionetteTestCase, WindowManagerMixin
+
+
+class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
+
+ def setUp(self):
+ super(TestWindowHandles, self).setUp()
+
+ self.empty_page = self.marionette.absolute_url("empty.html")
+ self.test_page = self.marionette.absolute_url("windowHandles.html")
+ self.marionette.navigate(self.test_page)
+
+ def tearDown(self):
+ self.close_all_tabs()
+
+ super(TestWindowHandles, self).tearDown()
+
+ def test_window_handles_after_opening_new_tab(self):
+ def open_with_link():
+ link = self.marionette.find_element(By.ID, "new-tab")
+ link.click()
+
+ new_tab = self.open_tab(trigger=open_with_link)
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
+ lambda mn: mn.get_url() == self.empty_page,
+ message="{} did not load after opening a new tab".format(self.empty_page))
+
+ self.marionette.switch_to_window(self.start_tab)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ self.marionette.switch_to_window(new_tab)
+ self.marionette.close()
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+ self.marionette.switch_to_window(self.start_tab)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ def test_window_handles_after_opening_new_window(self):
+ def open_with_link():
+ link = self.marionette.find_element(By.ID, "new-window")
+ link.click()
+
+ # We open a new window but are actually interested in the new tab
+ new_tab = self.open_tab(trigger=open_with_link)
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ # Check that the new tab has the correct page loaded
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ Wait(self.marionette, self.marionette.timeout.page_load).until(
+ lambda _: self.marionette.get_url() == self.empty_page,
+ message="The expected page '{}' has not been loaded".format(self.empty_page))
+
+ # Ensure navigate works in our current window
+ other_page = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(other_page)
+ self.assertEqual(self.marionette.get_url(), other_page)
+
+ # Close the opened window and carry on in our original tab.
+ self.marionette.close()
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+ self.marionette.switch_to_window(self.start_tab)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ def test_window_handles_after_closing_original_tab(self):
+ def open_with_link():
+ link = self.marionette.find_element(By.ID, "new-tab")
+ link.click()
+
+ new_tab = self.open_tab(trigger=open_with_link)
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
+ self.assertEqual(self.marionette.current_window_handle, self.start_tab)
+
+ self.marionette.close()
+ self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
+
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.current_window_handle, new_tab)
+ Wait(self.marionette, self.marionette.timeout.page_load).until(
+ lambda _: self.marionette.get_url() == self.empty_page,
+ message="The expected page '{}' has not been loaded".format(self.empty_page))
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py
new file mode 100644
index 000000000..ac5365806
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py
@@ -0,0 +1,42 @@
+#Copyright 2007-2009 WebDriver committers
+#
+#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.errors import InvalidArgumentException
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestWindowPosition(MarionetteTestCase):
+ def test_get_types(self):
+ position = self.marionette.get_window_position()
+ self.assertTrue(isinstance(position["x"], int))
+ self.assertTrue(isinstance(position["y"], int))
+
+ def test_set_types(self):
+ for x, y in (["a", "b"], [1.2, 3.4], [True, False], [[], []], [{}, {}]):
+ with self.assertRaises(InvalidArgumentException):
+ self.marionette.set_window_position(x, y)
+
+ def test_out_of_bounds_arguments(self):
+ with self.assertRaises(InvalidArgumentException):
+ self.marionette.set_window_position(-1, 0)
+ with self.assertRaises(InvalidArgumentException):
+ self.marionette.set_window_position(0, -1)
+
+ def test_move(self):
+ old_position = self.marionette.get_window_position()
+ new_position = {"x": old_position["x"] + 10, "y": old_position["y"] + 10}
+ self.marionette.set_window_position(new_position["x"], new_position["y"])
+ self.assertNotEqual(old_position['x'], new_position["x"])
+ self.assertNotEqual(old_position['y'], new_position["y"])
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_title.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title.py
new file mode 100644
index 000000000..4f6e3ccf7
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title.py
@@ -0,0 +1,12 @@
+# 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 TestTitle(MarionetteTestCase):
+ def test_get_html_title(self):
+ test_html = self.marionette.absolute_url("test.html")
+ self.marionette.navigate(test_html)
+ self.assertEqual('Marionette Test', self.marionette.title)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py
new file mode 100644
index 000000000..7bee682fd
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.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_harness import MarionetteTestCase
+
+
+class TestTitleChrome(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_chrome_title(self):
+ title = self.marionette.execute_script("return window.document.documentElement.getAttribute('title');")
+ self.assertEqual(title, self.marionette.title)
+ self.assertEqual('Title Test', self.marionette.title)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py
new file mode 100644
index 000000000..6c5e75f51
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py
@@ -0,0 +1,27 @@
+# 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 TestWindowTypeChrome(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_window_type(self):
+ window_type = self.marionette.execute_script("return window.document.documentElement.getAttribute('windowtype');")
+ self.assertEqual(window_type, self.marionette.get_window_type())
+ self.assertEqual('Test Type', self.marionette.get_window_type())
+
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_with_using_context.py b/testing/marionette/harness/marionette_harness/tests/unit/test_with_using_context.py
new file mode 100644
index 000000000..1b2d60d2d
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_with_using_context.py
@@ -0,0 +1,66 @@
+# 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.decorators import using_context
+from marionette_driver.errors import MarionetteException
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestSetContext(MarionetteTestCase):
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+
+ # shortcuts to improve readability of these tests
+ self.chrome = self.marionette.CONTEXT_CHROME
+ self.content = self.marionette.CONTEXT_CONTENT
+
+ test_url = self.marionette.absolute_url("empty.html")
+ self.marionette.navigate(test_url)
+ self.marionette.set_context(self.content)
+ self.assertEquals(self.get_context(), self.content)
+
+ def get_context(self):
+ return self.marionette._send_message("getContext", key="value")
+
+ def test_set_different_context_using_with_block(self):
+ with self.marionette.using_context(self.chrome):
+ self.assertEquals(self.get_context(), self.chrome)
+ self.assertEquals(self.get_context(), self.content)
+
+ def test_set_same_context_using_with_block(self):
+ with self.marionette.using_context(self.content):
+ self.assertEquals(self.get_context(), self.content)
+ self.assertEquals(self.get_context(), self.content)
+
+ def test_nested_with_blocks(self):
+ with self.marionette.using_context(self.chrome):
+ self.assertEquals(self.get_context(), self.chrome)
+ with self.marionette.using_context(self.content):
+ self.assertEquals(self.get_context(), self.content)
+ self.assertEquals(self.get_context(), self.chrome)
+ self.assertEquals(self.get_context(), self.content)
+
+ def test_set_scope_while_in_with_block(self):
+ with self.marionette.using_context(self.chrome):
+ self.assertEquals(self.get_context(), self.chrome)
+ self.marionette.set_context(self.content)
+ self.assertEquals(self.get_context(), self.content)
+ self.assertEquals(self.get_context(), self.content)
+
+ def test_exception_raised_while_in_with_block_is_propagated(self):
+ with self.assertRaises(MarionetteException):
+ with self.marionette.using_context(self.chrome):
+ raise MarionetteException
+ self.assertEquals(self.get_context(), self.content)
+
+ def test_with_using_context_decorator(self):
+ @using_context('content')
+ def inner_content(m):
+ self.assertEquals(self.get_context(), 'content')
+ @using_context('chrome')
+ def inner_chrome(m):
+ self.assertEquals(self.get_context(), 'chrome')
+ inner_content(self.marionette)
+ inner_chrome(self.marionette)
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
new file mode 100644
index 000000000..573096378
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
@@ -0,0 +1,132 @@
+[test_marionette.py]
+[test_geckoinstance.py]
+[test_data_driven.py]
+[test_session.py]
+[test_capabilities.py]
+[test_accessibility.py]
+[test_expectedfail.py]
+expected = fail
+[test_import_script.py]
+[test_click.py]
+[test_click_chrome.py]
+skip-if = appname == 'fennec'
+[test_checkbox.py]
+[test_checkbox_chrome.py]
+skip-if = appname == 'fennec'
+[test_elementsize.py]
+[test_elementsize_chrome.py]
+skip-if = appname == 'fennec'
+[test_position.py]
+[test_rendered_element.py]
+[test_chrome_element_css.py]
+skip-if = appname == 'fennec'
+[test_element_state.py]
+[test_element_state_chrome.py]
+skip-if = appname == 'fennec'
+[test_text.py]
+[test_text_chrome.py]
+skip-if = true # "Bug 896046"
+
+[test_clearing.py]
+[test_typing.py]
+
+[test_log.py]
+
+[test_about_pages.py]
+
+[test_execute_async_script.py]
+[test_execute_script.py]
+[test_simpletest_fail.js]
+[test_element_retrieval.py]
+[test_findelement_chrome.py]
+skip-if = appname == 'fennec'
+
+[test_navigation.py]
+
+[test_timeouts.py]
+
+[test_single_finger_desktop.py]
+skip-if = appname == 'fennec' || os == "win" # Bug 1025040
+
+[test_simpletest_pass.js]
+[test_simpletest_sanity.py]
+[test_simpletest_chrome.js]
+[test_simpletest_timeout.js]
+[test_anonymous_content.py]
+skip-if = appname == 'fennec'
+[test_switch_frame.py]
+skip-if = os == "win" # Bug 1078237
+[test_switch_frame_chrome.py]
+skip-if = appname == 'fennec'
+[test_switch_remote_frame.py]
+skip-if = appname == 'fennec'
+[test_switch_window_chrome.py]
+skip-if = appname == 'fennec'
+[test_switch_window_content.py]
+
+[test_pagesource.py]
+[test_pagesource_chrome.py]
+skip-if = appname == 'fennec'
+
+[test_visibility.py]
+[test_window_handles_chrome.py]
+skip-if = appname == 'fennec'
+[test_window_handles_content.py]
+[test_window_close_chrome.py]
+skip-if = appname == 'fennec'
+[test_window_close_content.py]
+[test_window_position.py]
+skip-if = appname == 'fennec'
+
+[test_screenshot.py]
+[test_cookies.py]
+[test_window_title.py]
+[test_window_title_chrome.py]
+skip-if = appname == 'fennec'
+[test_window_type.py]
+skip-if = appname == 'fennec'
+[test_implicit_waits.py]
+[test_wait.py]
+[test_expected.py]
+[test_date_time_value.py]
+[test_getactiveframe_oop.py]
+skip-if = true # Bug 925688
+[test_chrome_async_finish.js]
+[test_screen_orientation.py]
+[test_errors.py]
+
+[test_execute_isolate.py]
+[test_click_scrolling.py]
+[test_profile_management.py]
+skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993
+[test_quit_restart.py]
+skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993
+[test_set_window_size.py]
+skip-if = os == "linux" || appname == 'fennec' # Bug 1085717
+[test_with_using_context.py]
+
+[test_modal_dialogs.py]
+skip-if = appname == 'fennec' # Bug 1325738
+[test_key_actions.py]
+[test_mouse_action.py]
+skip-if = appname == 'fennec'
+[test_teardown_context_preserved.py]
+[test_file_upload.py]
+skip-if = appname == 'fennec' || os == "win" # http://bugs.python.org/issue14574
+
+[test_execute_sandboxes.py]
+[test_using_permissions.py]
+[test_prefs.py]
+
+[test_shadow_dom.py]
+
+[test_chrome.py]
+skip-if = appname == 'fennec'
+
+[test_addons.py]
+skip-if = appname == 'fennec' # Bug 1330598
+
+[test_select.py]
+[test_crash.py]
+skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993
+[test_localization.py]
diff --git a/testing/marionette/harness/marionette_harness/tests/webapi-tests.ini b/testing/marionette/harness/marionette_harness/tests/webapi-tests.ini
new file mode 100644
index 000000000..2c9dd1dce
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/webapi-tests.ini
@@ -0,0 +1,8 @@
+[include:../../../../../dom/system/gonk/tests/marionette/manifest.ini]
+[include:../../../../../dom/system/tests/marionette/manifest.ini]
+skip-if = android_version > '15' # Bug 1203072
+[include:../../../../../dom/events/test/marionette/manifest.ini]
+[include:../../../../../dom/wifi/test/marionette/manifest.ini]
+[include:../../../../../dom/tethering/tests/marionette/manifest.ini]
+skip-if = android_version > '15' # Bug 1203075
+[include:../../../../../dom/network/tests/marionette/manifest.ini]
diff --git a/testing/marionette/harness/marionette_harness/www/black.png b/testing/marionette/harness/marionette_harness/www/black.png
new file mode 100644
index 000000000..b62a3a7bc
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/black.png
Binary files differ
diff --git a/testing/marionette/harness/marionette_harness/www/bug814037.html b/testing/marionette/harness/marionette_harness/www/bug814037.html
new file mode 100644
index 000000000..22950134e
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/bug814037.html
@@ -0,0 +1,55 @@
+<html>
+<head>
+<style>
+body {
+ width: 100%;
+ margin: 0px;
+ transition: transform 300ms ease;
+ overflow-x: hidden;
+}
+
+body.section1 {
+ transform: translateX(0%);
+}
+
+body.section2 {
+ transform: translateX(-100%);
+}
+
+section {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+}
+
+#section1 {
+ left: 0px;
+}
+
+#section2 {
+ left: 100%;
+}
+.mypossie {
+ position:absolute;
+ left: -1000px;
+}
+</style>
+
+</head>
+ <body class="section1">
+ <section id="section1">
+ <div id="assertMe1">
+ <p>Section 1</p>
+ </div>
+ <button id="b1" onclick="var sect = document.getElementsByTagName('body')[0]; sect.classList.add('section2'); sect.classList.remove('section1');">Show section 2</button>
+ </section>
+
+ <section id="section2">
+ <div id="assertMe2">
+ <p>Section 2</p>
+ </div>
+ <button id="b2" onclick="var sect = document.getElementsByTagName('body')[0]; sect.classList.add('section1'); sect.classList.remove('section2'); ">Show section 1</button>
+ </section>
+ <section class='mypossie'>out in left field!</section>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/click_out_of_bounds_overflow.html b/testing/marionette/harness/marionette_harness/www/click_out_of_bounds_overflow.html
new file mode 100644
index 000000000..f0bee9b46
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/click_out_of_bounds_overflow.html
@@ -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/. -->
+
+<!DOCTYPE html>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<body>
+<div style="height: 100px; overflow: auto;">
+ <table>
+ <tbody>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td>data</td></tr>
+ <tr><td><a href="#clicked" id="link">click me</a></td></tr>
+ </tbody>
+ </table>
+</div>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/clicks.html b/testing/marionette/harness/marionette_harness/www/clicks.html
new file mode 100644
index 000000000..e48cb1b2f
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/clicks.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+ <!-- 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/. -->
+ <title>clicks</title>
+</head>
+<body>
+<h1>Testing Clicks</h1>
+
+<iframe id="source" src="click_source.html">
+
+</iframe>
+
+<iframe id="target" name="target">
+
+</iframe>
+
+<a href="xhtmlTest.html" id="normal">I'm a normal link</a>
+<a href="#" id="anchor">I go to an anchor</a>
+<a href="javascript:window.open('xhtmlTest.html', '_blank')" id="new-window">I open a window with javascript</a>
+<a href="xhtmlTest.html" id="twoClientRects"><span></span><span>Click me</span></a>
+<a href="xhtmlTest.html" id="link-with-enclosed-image"><img id="enclosed-image" src="./icon.gif"/></a>
+<a href="xhtmlTest.html" id="link-with-enclosed-span"><span id="enclosed-span" style="color: rgb(0, 255, 0)">I'm a green link</span></a>
+<p style="background: none repeat scroll 0% 0% rgb(0, 255, 0); width: 5em;"><a id="overflowLink" href="xhtmlTest.html">looooooooooong short looooooooooong</a>
+ </p>
+<div id="bubblesTo" onclick="window.bubbledClick = true;">
+ <a id="bubblesFrom">I bubble</a>
+</div>
+<div id="addbuttonlistener" onclick="var el = document.getElementById('showbutton');el.addEventListener('mousedown', function (evt) { document.getElementById('showbutton').innerHTML = evt.button; }, false);">
+ click to add an event listener
+</div>
+<div id="showbutton">
+ this will show the button clicked
+</div>
+<a href="xhtmlTest.html">333333</a>
+<p><a href="xhtmlTest.html" id="embeddedBlock"><span style="display: block;">I have a span</span><div>And a div</div><span>And another span</span></a></p>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/cssTransform.html b/testing/marionette/harness/marionette_harness/www/cssTransform.html
new file mode 100644
index 000000000..c3b99648a
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/cssTransform.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<style>
+#parentY {
+ transform: translateY(-10000px);
+ -webkit-transform: translateY(-10000px);
+ -o-transform: translateY(-10000px);
+ -ms-transform: translateY(-10000px);
+ -moz-transform: translateY(-10000px);
+}
+#parentX {
+ transform: translateX(-10000px);
+ -webkit-transform: translateX(-10000px);
+ -o-transform: translateX(-10000px);
+ -ms-transform: translateX(-10000px);
+ -moz-transform: translateX(-10000px);
+}
+#transformX {
+ transform: translateX(-10000px);
+ -webkit-transform: translateX(-10000px);
+ -o-transform: translateX(-10000px);
+ -ms-transform: translateX(-10000px);
+ -moz-transform: translateX(-10000px);
+}
+#transformY {
+ transform: translateY(-10000px);
+ -webkit-transform: translateY(-10000px);
+ -o-transform: translateY(-10000px);
+ -ms-transform: translateY(-10000px);
+ -moz-transform: translateY(-10000px);
+}
+
+#zero-transform {
+ transform: translateY(0px);
+ -webkit-transform: translateY(0px);
+ -o-transform: translateY(0px);
+ -ms-transform: translateY(0px);
+ -moz-transform: translateY(0px);
+ transform: translateX(0px);
+ -webkit-transform: translateX(0px);
+ -o-transform: translateX(0px);
+ -ms-transform: translateX(0px);
+ -moz-transform: translateX(0px);
+}
+</style>
+<div id='zero-tranform'>
+You shouldn't see anything other than this sentence on the page
+</div>
+<div id='parentY'>
+ I have a hidden child
+ <div id='childY'>
+ I am a hidden child
+ </div>
+</div>
+<div id='parentX'>
+ I have a hidden child
+ <div id='childX'>
+ I am a hidden child
+ </div>
+</div>
+<div id='transformX'>I am a hidden element </div>
+<div id='transformY'>I am a hidden element </div>
diff --git a/testing/marionette/harness/marionette_harness/www/cssTransform2.html b/testing/marionette/harness/marionette_harness/www/cssTransform2.html
new file mode 100644
index 000000000..602924bfb
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/cssTransform2.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<style>
+#negative-percentage-transformY{
+ transform: translateY(-75px);
+ -webkit-transform: translateY(-75%);
+ -o-transform: translateY(-75%);
+ -ms-transform: translateY(-75%);
+ -moz-transform: translateY(-75%);
+}
+.block {
+ display = block;
+}
+</style>
+<div class='block'>
+ <br/>
+</div>
+ <br/>
+<div class='block'>
+</div>
+<div id='negative-percentage-transformY'>I am not a hidden element </div>
diff --git a/testing/marionette/harness/marionette_harness/www/datetimePage.html b/testing/marionette/harness/marionette_harness/www/datetimePage.html
new file mode 100644
index 000000000..87a05f6d2
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/datetimePage.html
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <!-- 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/. -->
+<head>
+ <title>Testing date and time inputs</title>
+</head>
+<body>
+ <form>
+ <input id="date-test" type="date"/>
+ <input id="time-test" type="time"/>
+ </form>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/deletingFrame.html b/testing/marionette/harness/marionette_harness/www/deletingFrame.html
new file mode 100644
index 000000000..d891605af
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/deletingFrame.html
@@ -0,0 +1,29 @@
+<html>
+<head>
+ <title></title>
+</head>
+ <script type="text/javascript">
+ function remove() {
+ var iframe = document.getElementById("iframe1");
+ var myDiv = document.getElementById("myDiv");
+ myDiv.removeChild(iframe);
+ }
+ function addBack() {
+ var iframe = '<iframe src="javascriptPage.html" marginheight="0" marginwidth="0" topmargin="0" leftmargin="600" allowtransparency="true" frameborder="0" height="600" scrolling="no" width="120" id="iframe1"></iframe>';
+ var myDiv2 = document.getElementById("myDiv2");
+ myDiv2.innerHTML = iframe;
+ }
+ </script>
+<body>
+
+<div id='myDiv'>
+<iframe src="formPage.html" marginheight="0" marginwidth="0" topmargin="0" leftmargin="0" allowtransparency="true"
+ frameborder="1" height="900" scrolling="no" width="500" id="iframe1"></iframe>
+
+ </div>
+<div id='myDiv2'>
+
+</div>
+<input type='button' id='addBackFrame' value='Add back frame' onclick='addBack();' />
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/double_click.html b/testing/marionette/harness/marionette_harness/www/double_click.html
new file mode 100644
index 000000000..fb3ec217a
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/double_click.html
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <!-- 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/. -->
+ <head>
+ <title>Testing Double Click</title>
+ </head>
+ <div>
+ <p id="one-word-div">zyxw</p>
+ </div>
+
+ <div>
+ <form>
+ <input type="text" id="input-field" size="80"/>
+ </form>
+ </div>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/element_bottom.html b/testing/marionette/harness/marionette_harness/www/element_bottom.html
new file mode 100644
index 000000000..470199a07
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/element_bottom.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+ .element{
+ position: absolute;
+ bottom: -50px;
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<div class='element'></div>
+
diff --git a/testing/marionette/harness/marionette_harness/www/element_left.html b/testing/marionette/harness/marionette_harness/www/element_left.html
new file mode 100644
index 000000000..c98b945f9
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/element_left.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+ .element {
+ position: absolute;
+ left: -50px;
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<div class='element'></div>
+
diff --git a/testing/marionette/harness/marionette_harness/www/element_outside_viewport.html b/testing/marionette/harness/marionette_harness/www/element_outside_viewport.html
new file mode 100644
index 000000000..69b66b875
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/element_outside_viewport.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<style>
+ div {
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ }
+ .top { background-color: red; }
+ #top-70 { left: 80px; top: 0; }
+ #top-50 { left: 190px; top: 20px; }
+ #top-30 { left: 300px; top: 40px; }
+
+ .right { background-color: black; }
+ #right-70 { top: 80px; right: -140px;}
+ #right-50 { top: 190px; right: -120px;}
+ #right-30 { top: 300px; right: -100px;}
+
+ .bottom { background-color: blue; }
+ #bottom-70 { right: -50px; bottom: -140px; }
+ #bottom-50 { right: 60px; bottom: -120px; }
+ #bottom-30 { right: 170px; bottom: -100px; }
+
+ .left { background-color: green; }
+ #left-70 { bottom: -50px; left: 0; }
+ #left-50 { bottom: 60px; left: 20px; }
+ #left-30 { bottom: 170px; left: 40px; }
+</style>
+<body onload="window.scrollTo(70, 70);">
+ <div id="top-70" class="top"></div>
+ <div id="top-50" class="top"></div>
+ <div id="top-30" class="top"></div>
+ <div id="right-70" class="right"></div>
+ <div id="right-50" class="right"></div>
+ <div id="right-30" class="right"></div>
+ <div id="bottom-70" class="bottom"></div>
+ <div id="bottom-50" class="bottom"></div>
+ <div id="bottom-30" class="bottom"></div>
+ <div id="left-70" class="left"></div>
+ <div id="left-50" class="left"></div>
+ <div id="left-30" class="left"></div>
+</body>
diff --git a/testing/marionette/harness/marionette_harness/www/element_right.html b/testing/marionette/harness/marionette_harness/www/element_right.html
new file mode 100644
index 000000000..ff8774ab6
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/element_right.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+ .element{
+ position: absolute;
+ right: -50px;
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<div class='element'></div>
+
diff --git a/testing/marionette/harness/marionette_harness/www/element_top.html b/testing/marionette/harness/marionette_harness/www/element_top.html
new file mode 100644
index 000000000..b975e34c4
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/element_top.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+ .element{
+ position: absolute;
+ top: -50px;
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<div class='element'></div>
+
diff --git a/testing/marionette/harness/marionette_harness/www/empty.html b/testing/marionette/harness/marionette_harness/www/empty.html
new file mode 100644
index 000000000..0a9d08cd6
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/empty.html
@@ -0,0 +1,12 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>Marionette Test</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/formPage.html b/testing/marionette/harness/marionette_harness/www/formPage.html
new file mode 100644
index 000000000..ee1ff9f2b
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/formPage.html
@@ -0,0 +1,116 @@
+<html>
+<head>
+ <title>We Leave From Here</title>
+
+ <script type="text/javascript">
+ function changePage() {
+ var newLocation = '/common/page/3';
+ window.location = newLocation;
+ }
+ </script>
+</head>
+<body>
+There should be a form here:
+
+<form method="get" action="resultPage.html" name="login">
+ <input type="email" id="email"/>
+ <input type="submit" id="submitButton" value="Hello there"/>
+</form>
+
+<form method="get" action="resultPage.html" name="optional" style="display: block">
+ Here's a checkbox:
+ <input type="checkbox" id="checky" name="checky" value="furrfu"/>
+ <input type="checkbox" id="checkedchecky" name="checkedchecky" checked="checked" />
+ <input type="checkbox" id="disabledchecky" disabled="disabled" name="disabledchecky" />
+ <input type="checkbox" id="randomly_disabled_checky" disabled="somerandomstring" checked="checked" name="randomlydisabledchecky" />
+ <br/>
+ <select name="selectomatic">
+ <option selected="selected" id="non_multi_option" value="one">One</option>
+ <option value="two">Two</option>
+ <option value="four">Four</option>
+ <option value="still learning how to count, apparently">Still learning how to count, apparently</option>
+ </select>
+
+ <select name="multi" id="multi" multiple="multiple">
+ <option selected="selected" value="eggs">Eggs</option>
+ <option value="ham">Ham</option>
+ <option selected="selected" value="sausages">Sausages</option>
+ <option value="onion gravy">Onion gravy</option>
+ </select>
+
+ <select name="no-select" disabled="disabled">
+ <option value="foo">Foo</option>
+ </select>
+
+ <select name="select_empty_multiple" multiple>
+ <option id="multi_1" value="select_1">select_1</option>
+ <option id="multi_2" value="select_2">select_2</option>
+ <option id="multi_3" value="select_3">select_3</option>
+ <option id="multi_4" value="select_4">select_4</option>
+ </select>
+
+ <select name="multi_true" multiple="true">
+ <option id="multi_true_1" value="select_1">select_1</option>
+ <option id="multi_true_2" value="select_2">select_2</option>
+ </select>
+
+ <select name="multi_false" multiple="false">
+ <option id="multi_false_1" value="select_1">select_1</option>
+ <option id="multi_false_2" value="select_2">select_2</option>
+ </select>
+
+ <select id="invisi_select" style="opacity:0;">
+ <option selected value="apples">Apples</option>
+ <option value="oranges">Oranges</option>
+ </select>
+
+ <select name="select-default">
+ <option>One</option>
+ <option>Two</option>
+ <option>Four</option>
+ <option>Still learning how to count, apparently</option>
+ </select>
+
+ <select name="select_with_spaces">
+ <option>One</option>
+ <option> Two </option>
+ <option>
+ Four
+ </option>
+ <option>
+ Still learning how to count,
+ apparently
+ </option>
+ </select>
+
+ <select>
+ <option id="blankOption"></option>
+ <option id="optionEmptyValueSet" value="">nothing</option>
+ </select>
+
+ <br/>
+
+ <input type="radio" id="cheese" name="snack" value="cheese"/>Cheese<br/>
+ <input type="radio" id="peas" name="snack" value="peas"/>Peas<br/>
+ <input type="radio" id="cheese_and_peas" name="snack" value="cheese and peas" checked/>Cheese and peas<br/>
+ <input type="radio" id="nothing" name="snack" value="nowt" disabled="disabled"/>Not a sausage<br/>
+ <input type="radio" id="randomly_disabled_nothing" name="snack" value="funny nowt" disabled="somedisablingstring"/>Not another sausage
+
+ <input type="hidden" name="hidden" value="fromage" />
+
+ <p id="cheeseLiker">I like cheese</p>
+ <input type="submit" value="Click!"/>
+
+ <input type="radio" id="lone_disabled_selected_radio" name="not_a_snack" value="cumberland" checked="checked" disabled="disabled" />Cumberland sausage
+</form>
+
+<input type='button' id='killIframe' onclick='top.remove();' value="Kill containing iframe" />
+
+<form method="get" action="formPage.html">
+ <p>
+ <label for="checkbox-with-label" id="label-for-checkbox-with-label">Label</label><input type="checkbox" id="checkbox-with-label" />
+ </p>
+</form>
+<input id="vsearchGadget" name="SearchableText" type="text" size="18" value="" title="Hvad søger du?" accesskey="4" class="inputLabel" />
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/frameset.html b/testing/marionette/harness/marionette_harness/www/frameset.html
new file mode 100644
index 000000000..209d705b5
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/frameset.html
@@ -0,0 +1,14 @@
+<html>
+ <head>
+ <title>Unique title</title>
+ </head>
+<frameset cols="*, *, *, *, *, *, *">
+ <frame name="first" src="page/1"/>
+ <frame name="second" src="page/2?title=Fish"/>
+ <frame name="third" src="formPage.html"/>
+ <frame name="fourth" src="framesetPage2.html"/>
+ <frame id="fifth" src="xhtmlTest.html"/>
+ <frame id="sixth" src="test_iframe.html"/>
+ <frame id="sixth.iframe1" src="page/3"/>
+</frameset>
+</html> \ No newline at end of file
diff --git a/testing/marionette/harness/marionette_harness/www/framesetPage2.html b/testing/marionette/harness/marionette_harness/www/framesetPage2.html
new file mode 100644
index 000000000..5190ceb6c
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/framesetPage2.html
@@ -0,0 +1,7 @@
+<html>
+<head></head>
+<frameset cols="*, *">
+ <frame name="child1" src="test.html"/>
+ <frame name="child2" src="test.html"/>
+</frameset>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/hidden.html b/testing/marionette/harness/marionette_harness/www/hidden.html
new file mode 100644
index 000000000..0e8097e97
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/hidden.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<div id='singleHidden' hidden>This will not be visible</div>
+<div id='parent' hidden>
+ <div id='child'>My parent is hidden so you can't see me</div>
+</div> \ No newline at end of file
diff --git a/testing/marionette/harness/marionette_harness/www/html5/blue.jpg b/testing/marionette/harness/marionette_harness/www/html5/blue.jpg
new file mode 100644
index 000000000..8ea27c42f
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/blue.jpg
Binary files differ
diff --git a/testing/marionette/harness/marionette_harness/www/html5/boolean_attributes.html b/testing/marionette/harness/marionette_harness/www/html5/boolean_attributes.html
new file mode 100644
index 000000000..eb0d458b8
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/boolean_attributes.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<input id='disabled' disabled> \ No newline at end of file
diff --git a/testing/marionette/harness/marionette_harness/www/html5/geolocation.js b/testing/marionette/harness/marionette_harness/www/html5/geolocation.js
new file mode 100644
index 000000000..f07af148e
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/geolocation.js
@@ -0,0 +1,18 @@
+function success(position) {
+ var message = document.getElementById("status");
+ message.innerHTML ="<img src='http://maps.google.com/maps/api/staticmap?center=" + position.coords.latitude + "," + position.coords.longitude + "&size=300x200&maptype=roadmap&zoom=12&&markers=size:mid|color:red|" + position.coords.latitude + "," + position.coords.longitude + "&sensor=false' />";
+ message.innerHTML += "<p>Longitude: " + position.coords.longitude + "</p>";
+ message.innerHTML += "<p>Latitude: " + position.coords.latitude + "</p>";
+ message.innerHTML += "<p>Altitude: " + position.coords.altitude + "</p>";
+}
+
+function error(msg) {
+ var message = document.getElementById("status");
+ message.innerHTML = "Failed to get geolocation.";
+}
+
+if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(success, error);
+} else {
+ error('Geolocation is not supported.');
+} \ No newline at end of file
diff --git a/testing/marionette/harness/marionette_harness/www/html5/green.jpg b/testing/marionette/harness/marionette_harness/www/html5/green.jpg
new file mode 100644
index 000000000..6a0d3bea4
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/green.jpg
Binary files differ
diff --git a/testing/marionette/harness/marionette_harness/www/html5/offline.html b/testing/marionette/harness/marionette_harness/www/html5/offline.html
new file mode 100644
index 000000000..c24178b5f
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/offline.html
@@ -0,0 +1 @@
+<html><head><title>Offline</title></head><body></body></html>
diff --git a/testing/marionette/harness/marionette_harness/www/html5/red.jpg b/testing/marionette/harness/marionette_harness/www/html5/red.jpg
new file mode 100644
index 000000000..f296e2719
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/red.jpg
Binary files differ
diff --git a/testing/marionette/harness/marionette_harness/www/html5/status.html b/testing/marionette/harness/marionette_harness/www/html5/status.html
new file mode 100644
index 000000000..394116a52
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/status.html
@@ -0,0 +1 @@
+<html><head><title>Online</title></head><body></body></html>
diff --git a/testing/marionette/harness/marionette_harness/www/html5/test.appcache b/testing/marionette/harness/marionette_harness/www/html5/test.appcache
new file mode 100644
index 000000000..3bc4e0025
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/test.appcache
@@ -0,0 +1,11 @@
+CACHE MANIFEST
+
+CACHE:
+# Additional items to cache.
+yellow.jpg
+red.jpg
+blue.jpg
+green.jpg
+
+FALLBACK:
+status.html offline.html
diff --git a/testing/marionette/harness/marionette_harness/www/html5/test_html_inputs.html b/testing/marionette/harness/marionette_harness/www/html5/test_html_inputs.html
new file mode 100644
index 000000000..96c9b97e3
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/test_html_inputs.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<input id='number' type=number> \ No newline at end of file
diff --git a/testing/marionette/harness/marionette_harness/www/html5/yellow.jpg b/testing/marionette/harness/marionette_harness/www/html5/yellow.jpg
new file mode 100644
index 000000000..7c609b371
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5/yellow.jpg
Binary files differ
diff --git a/testing/marionette/harness/marionette_harness/www/html5Page.html b/testing/marionette/harness/marionette_harness/www/html5Page.html
new file mode 100644
index 000000000..4197cd122
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/html5Page.html
@@ -0,0 +1,45 @@
+<html manifest="html5/test.appcache">
+<!--
+Copyright 2011 Software Freedom Conservancy.
+
+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.
+-->
+
+
+<head>
+<title>HTML5</title>
+</head>
+<body>
+
+<h3>Geolocation Test</h3>
+<div id="status">Location unknown</div>
+<script language="javascript" type="text/javascript" src="html5/geolocation.js"></script>
+
+<h3>Application Cache Test</h3>
+<div id="images">
+ <p>Current network status: <span id="state"></span></p>
+ <script>
+ var state = document.getElementById('state')
+ setInterval(function () {
+ state.className = navigator.onLine ? 'online' : 'offline';
+ state.innerHTML = navigator.onLine ? 'online' : 'offline';
+ }, 250);
+ </script>
+ <img id="red" src="html5/red.jpg">
+ <img id="blue" src="html5/blue.jpg">
+ <img id="green" src="html5/green.jpg">
+ <img id="yellow" src="html5/yellow.jpg">
+</div>
+
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/javascriptPage.html b/testing/marionette/harness/marionette_harness/www/javascriptPage.html
new file mode 100644
index 000000000..b47c902c5
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/javascriptPage.html
@@ -0,0 +1,278 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <!-- 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/. -->
+<head>
+ <title>Testing Javascript</title>
+ <script type="text/javascript">
+ var seen = {};
+
+ function updateContent(input) {
+ document.getElementById('result').innerHTML = "<p>" + input.value + "</p>";
+ }
+
+ function displayMessage(message) {
+ document.getElementById('result').innerHTML = "<p>" + message + "</p>";
+ }
+
+ function appendMessage(message) {
+ document.getElementById('result').innerHTML += message + " ";
+ }
+
+ function register(message) {
+ if (!seen[message]) {
+ appendMessage(message);
+ seen[message] = true;
+ }
+ }
+
+ function delayedShowHide(delay, show) {
+ var blackBox = document.getElementById('clickToHide');
+ window.setTimeout(function() {
+ blackBox.style.display = show ? '' : 'none';
+ }, delay);
+ }
+ </script>
+ <script type="text/javascript">
+ var startList = function() {
+ // Ugh. Let's hope no-one is faking their user agent when running the tests
+ if (navigator.userAgent.indexOf("MSIE") != -1) {
+ var navRoot = document.getElementById("nav");
+ for (var i = 0; i < navRoot.childNodes.length; i++) {
+ var node = navRoot.childNodes[i];
+ if (node.nodeName == "LI") {
+ node.onmouseover = function() {
+ this.className += " over";
+ };
+ node.onmouseout = function() {
+ this.className = this.className.replace(" over", "");
+ };
+ }
+ }
+ }
+ };
+ window.onload=startList;
+ </script>
+ <style type="text/css">
+ #nav {
+ padding: 0; margin: 0; list-style: none;
+ }
+ #nav li {
+ float: left; position: relative; width: 10em;
+ }
+ #nav li ul {
+ display: none; position: absolute; top: 1em; left: 0;
+ }
+ #nav li > ul { top: auto; left: auto; }
+ #nav li:hover ul, #nav li.over ul{ display: block; background: white; }
+ </style>
+</head>
+<body>
+<h1>Type Stuff</h1>
+
+<div>
+ <ul id="nav">
+ <li id="menu1">Menu 1
+ <ul>
+ <li id="item1" onclick="displayMessage('item 1');">Item 1</li>
+ <li>Item 2</li>
+ </ul>
+ </li>
+ </ul>
+</div>
+
+<div id="resultContainer" height="30">&nbsp;
+ <div id="result" style="width:300;height:60">
+ <p>&nbsp;</p>
+ </div>
+
+</div>
+
+<div id="formageddon">
+ <form action="#">
+ Key Up: <input type="text" id="keyUp" onkeyup="javascript:updateContent(this)"/><br/>
+ Key Down: <input type="text" id="keyDown" onkeydown="javascript:updateContent(this)"/><br/>
+ Key Press: <input type="text" id="keyPress" onkeypress="javascript:updateContent(this)"/><br/>
+ Change: <input type="text" id="change" onkeypress="javascript:displayMessage('change')"/><br/>
+ <textarea id="keyDownArea" onkeydown="javascript:updateContent(this)" rows="2" cols="15"></textarea>
+ <textarea id="keyPressArea" onkeypress="javascript:updateContent(this)" rows="2" cols="15"></textarea>
+ <textarea id="keyUpArea" onkeyup="javascript:updateContent(this)" rows="2" cols="15"></textarea>
+ <select id="selector" onchange="javascript:updateContent(this)">
+ <option value="foo">Foo</option>
+ <option value="bar">Bar</option>
+ </select>
+ <input type="checkbox" id="checkbox" value="checkbox thing" onchange="javascript:updateContent(this)"/>
+ <input id="clickField" type="text" onclick="document.getElementById('clickField').value='Clicked';" value="Hello"/>
+ <input id="doubleClickField" type="text" onclick="document.getElementById('doubleClickField').value='Clicked';" ondblclick="document.getElementById('doubleClickField').value='DoubleClicked';" oncontextmenu="document.getElementById('doubleClickField').value='ContextClicked'; return false;" value="DoubleHello"/>
+ <input id="clearMe" value="Something" onchange="displayMessage('Cleared')"/>
+ </form>
+</div>
+
+<div>
+ <p><a href="#" onclick="javascript:document.title='Changed'">Change the page title!</a></p>
+
+ <p><a onclick="javascript:document.title='Changed'" id="nohref">No href</a></p>
+
+ <p><a id="updatediv" href="#" onclick="javascript:document.getElementById('dynamo').innerHTML = 'Fish and chips!';">Update a
+ div</a></p>
+</div>
+
+<div id="dynamo">What's for dinner?</div>
+
+<div id="mousedown" onmousedown="javascript:displayMessage('mouse down');">
+ <p>Click for the mouse down event</p>
+ <span><p id="child">Here's some text</p></span>
+</div>
+
+<div id="mouseup" onmouseup="javascript:displayMessage('mouse up');">
+ <p>Click for the mouse up event</p>
+</div>
+
+<div id="mouseclick" onclick="javascript:displayMessage('mouse click');">
+ <p>Click for the mouse click event</p>
+</div>
+
+<div id="mousebutton">
+ <p>Click to show button</p>
+</div>
+
+<div id="error" onclick="document.getElementById('doesnotexist').innerHTML = 'cheese';">
+ Clicking this causes a JS exception in the click handler
+</div>
+
+<div>
+ <form action="resultPage.html" id="on-form">
+ <input id="theworks"
+ onfocus="appendMessage('focus')"
+ onkeydown="appendMessage('keydown')"
+ onkeypress="appendMessage('keypress')"
+ onkeyup="appendMessage('keyup')"
+ onblur="appendMessage('blur')"
+ onchange="appendMessage('change')"
+ />
+
+ <input id="changeable" name="changeable" onfocus="appendMessage('focus')" onchange="appendMessage('change')" onblur="appendMessage('blur')"/>
+
+ <button type="button" id="plainButton"
+ onfocus="appendMessage('focus')"
+ onkeydown="appendMessage('keydown')"
+ onkeypress="appendMessage('keypress')"
+ onkeyup="appendMessage('keyup')"
+ onblur="appendMessage('blur')"
+ onclick="appendMessage('click')"
+ onmousedown="appendMessage('mousedown ')"
+ onmouseup="appendMessage('mouseup ')"
+ onmouseover="register('mouseover ')"
+ onmousemove="register('mousemove ')"
+ >
+ <b>Go somewhere</b>
+ </button>
+ <button type="submit" id="submittingButton"><emph>submit</emph></button>
+ <button type="button" id="jsSubmitButton" onclick="javascript:document.getElementById('on-form').submit();">Submitomatic</button>
+
+ <button type="button" id="switchFocus" onclick="document.getElementById('theworks').focus();">Switch focus</button>
+ <button type="button" onclick="var element = document.getElementById('switchFocus'); var clickEvent = document.createEvent('MouseEvents'); clickEvent.initMouseEvent('click', true, true, null, 0, 0, 0, 0, 0,false, false, false, false, 0, element);element.dispatchEvent(clickEvent);">Do magic</button><br/>
+ <label id="labelForCheckbox" for="labeledCheckbox" onclick="appendMessage('labelclick')">Toggle checkbox</label><input type="checkbox" id="labeledCheckbox" onclick="appendMessage('chboxclick')"/>
+ </form>
+
+ <form action="javascriptPage.html" id="submitListeningForm" onsubmit="appendMessage('form-onsubmit '); return false;">
+ <p>
+ <input id="submitListeningForm-text" type="text" onsubmit="appendMessage('text-onsubmit ')" onclick="appendMessage('text-onclick ');" />
+ <input id="submitListeningForm-submit" type="submit" onsubmit="appendMessage('submit-onsubmit ')" onclick="appendMessage('submit-onclick ');" />
+ </p>
+ </form>
+</div>
+
+<p id="suppressedParagraph" style="display: none">A paragraph suppressed using CSS display=none</p>
+
+<div>
+ <p id="displayed">Displayed</p>
+
+ <form action="#"><input type="hidden" name="hidden" /> </form>
+
+ <p id="none" style="display: none;">Display set to none</p>
+
+ <p id="hidden" style="visibility: hidden;">Hidden</p>
+
+ <div id="hiddenparent" style="height: 2em; display: none;">
+ <div id="hiddenchild">
+ <a href="#" id="hiddenlink">ok</a>
+ </div>
+ </div>
+
+ <div style="visibility: hidden;">
+ <span>
+ <input id="unclickable" />
+ <input type="checkbox" id="untogglable" checked="checked" />Check box you can't see
+ </span>
+ </div>
+
+ <p id="outer" style="visibility: hidden">A <b id="visibleSubElement" style="visibility: visible">sub-element that is explicitly visible</b> using CSS visibility=visible</p>
+</div>
+
+<div>
+ <form>
+ <input type="text" id="keyReporter" size="80"
+ onkeyup="appendMessage('up: ' + event.keyCode)"
+ onkeypress="appendMessage('press: ' + event.keyCode)"
+ onkeydown="displayMessage(''); appendMessage('down: ' + event.keyCode)" />
+ <input name="suppress" onkeydown="if (event.preventDefault) event.preventDefault(); event.returnValue = false; return false;" onkeypress="appendMessage('press');"/>
+ </form>
+</div>
+
+<!-- Used for testing styles -->
+<div style="background-color: green;" id="green-parent">
+ <p id="style1">This should be greenish</p>
+ <ul>
+ <li id="green-item">So should this</li>
+ <li id="red-item" style="background-color: red;">But this is red</li>
+ </ul>
+</div>
+
+<a href="#" id="close" onclick="window.close();">Close window</a>
+
+<div id="delete" onclick="var d = document.getElementById('deleted'); this.removeChild(d);">
+ <p id="deleted">I should be deleted when you click my containing div</p>
+ <p>Whereas, I should not</p>
+</div>
+
+<div>
+ <span id="hideMe" onclick="this.style.display = 'none';">Click to hide me.</span>
+</div>
+
+<div style="margin-top: 10px;">
+ Click actions delayed by 3000ms:
+ <div id="clickToShow" onclick="delayedShowHide(3000, true);"
+ style="float: left;width: 100px;height:100px;border: 1px solid black;">
+ Click to show black box
+ </div>
+ <div id="clickToHide" onclick="delayedShowHide(3000, false);"
+ style="float: left;width: 100px;height:100px;border: 1px solid black;
+ background-color: black; color: white; display: none;">
+ Click to hide black box
+ </div>
+ <div style="clear: both"></div>
+</div>
+
+<a id="new_window" onmouseup="window.open('closeable_window.html', 'close_me')" href="#">Click me to open a new window</a>
+
+<a id="throwing-mouseover" onmouseover="throw new Error()" href="#throwing-mouseover">Mouse over me will throw a JS error</a>
+
+<div id="parent">
+ <span id="movable" onmouseover="var p = document.getElementById('movable'); displayMessage('parent matches? ' + (p != event.relatedTarget));">
+ Click on me to show the related target
+ </span>
+</div>
+
+<div id="zero" style="width:0;height:0">
+ <div>
+ <img src="map.png">
+ </div>
+</div>
+
+<input type='text' id='notDisplayed' style='display:none'>
+</body>
+</html>
+
+
diff --git a/testing/marionette/harness/marionette_harness/www/macbeth.html b/testing/marionette/harness/marionette_harness/www/macbeth.html
new file mode 100644
index 000000000..2404f1d72
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/macbeth.html
@@ -0,0 +1,5254 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
+ <html>
+ <head>
+ <title>Macbeth: Entire Play
+ </title>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ </HEAD>
+ <body bgcolor="#ffffff" text="#000000">
+
+ <!-- Originally from http://shakespeare.mit.edu/macbeth/full.html -->
+
+<a href="#5.8.86">Quick link to last speech</a>
+
+<H3>ACT I</h3>
+<h3>SCENE I. A desert place.</h3>
+<p><blockquote>
+<i>Thunder and lightning. Enter three Witches</i>
+</blockquote>
+
+<A NAME=speech1><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.1.1>When shall we three meet again</A><br>
+<A NAME=1.1.2>In thunder, lightning, or in rain?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.1.3>When the hurlyburly's done,</A><br>
+<A NAME=1.1.4>When the battle's lost and won.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.1.5>That will be ere the set of sun.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.1.6>Where the place?</A><br>
+</blockquote>
+
+<A NAME=speech5><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.1.7> Upon the heath.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.1.8>There to meet with Macbeth.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.1.9>I come, Graymalkin!</A><br>
+</blockquote>
+
+<A NAME=speech8><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.1.10>Paddock calls.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.1.11>Anon.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>ALL</b></a>
+<blockquote>
+<A NAME=1.1.12>Fair is foul, and foul is fair:</A><br>
+<A NAME=1.1.13>Hover through the fog and filthy air.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE II. A camp near Forres.</h3>
+<p><blockquote>
+<i>Alarum within. Enter DUNCAN, MALCOLM, DONALBAIN, LENNOX, with Attendants, meeting a bleeding Sergeant</i>
+</blockquote>
+
+<A NAME=speech1><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.2.1>What bloody man is that? He can report,</A><br>
+<A NAME=1.2.2>As seemeth by his plight, of the revolt</A><br>
+<A NAME=1.2.3>The newest state.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=1.2.4> This is the sergeant</A><br>
+<A NAME=1.2.5>Who like a good and hardy soldier fought</A><br>
+<A NAME=1.2.6>'Gainst my captivity. Hail, brave friend!</A><br>
+<A NAME=1.2.7>Say to the king the knowledge of the broil</A><br>
+<A NAME=1.2.8>As thou didst leave it.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>Sergeant</b></a>
+<blockquote>
+<A NAME=1.2.9>Doubtful it stood;</A><br>
+<A NAME=1.2.10>As two spent swimmers, that do cling together</A><br>
+<A NAME=1.2.11>And choke their art. The merciless Macdonwald--</A><br>
+<A NAME=1.2.12>Worthy to be a rebel, for to that</A><br>
+<A NAME=1.2.13>The multiplying villanies of nature</A><br>
+<A NAME=1.2.14>Do swarm upon him--from the western isles</A><br>
+<A NAME=1.2.15>Of kerns and gallowglasses is supplied;</A><br>
+<A NAME=1.2.16>And fortune, on his damned quarrel smiling,</A><br>
+<A NAME=1.2.17>Show'd like a rebel's whore: but all's too weak:</A><br>
+<A NAME=1.2.18>For brave Macbeth--well he deserves that name--</A><br>
+<A NAME=1.2.19>Disdaining fortune, with his brandish'd steel,</A><br>
+<A NAME=1.2.20>Which smoked with bloody execution,</A><br>
+<A NAME=1.2.21>Like valour's minion carved out his passage</A><br>
+<A NAME=1.2.22>Till he faced the slave;</A><br>
+<A NAME=1.2.23>Which ne'er shook hands, nor bade farewell to him,</A><br>
+<A NAME=1.2.24>Till he unseam'd him from the nave to the chaps,</A><br>
+<A NAME=1.2.25>And fix'd his head upon our battlements.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.2.26>O valiant cousin! worthy gentleman!</A><br>
+</blockquote>
+
+<A NAME=speech5><b>Sergeant</b></a>
+<blockquote>
+<A NAME=1.2.27>As whence the sun 'gins his reflection</A><br>
+<A NAME=1.2.28>Shipwrecking storms and direful thunders break,</A><br>
+<A NAME=1.2.29>So from that spring whence comfort seem'd to come</A><br>
+<A NAME=1.2.30>Discomfort swells. Mark, king of Scotland, mark:</A><br>
+<A NAME=1.2.31>No sooner justice had with valour arm'd</A><br>
+<A NAME=1.2.32>Compell'd these skipping kerns to trust their heels,</A><br>
+<A NAME=1.2.33>But the Norweyan lord surveying vantage,</A><br>
+<A NAME=1.2.34>With furbish'd arms and new supplies of men</A><br>
+<A NAME=1.2.35>Began a fresh assault.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.2.36>Dismay'd not this</A><br>
+<A NAME=1.2.37>Our captains, Macbeth and Banquo?</A><br>
+</blockquote>
+
+<A NAME=speech7><b>Sergeant</b></a>
+<blockquote>
+<A NAME=1.2.38>Yes;</A><br>
+<A NAME=1.2.39>As sparrows eagles, or the hare the lion.</A><br>
+<A NAME=1.2.40>If I say sooth, I must report they were</A><br>
+<A NAME=1.2.41>As cannons overcharged with double cracks, so they</A><br>
+<A NAME=1.2.42>Doubly redoubled strokes upon the foe:</A><br>
+<A NAME=1.2.43>Except they meant to bathe in reeking wounds,</A><br>
+<A NAME=1.2.44>Or memorise another Golgotha,</A><br>
+<A NAME=1.2.45>I cannot tell.</A><br>
+<A NAME=1.2.46>But I am faint, my gashes cry for help.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.2.47>So well thy words become thee as thy wounds;</A><br>
+<A NAME=1.2.48>They smack of honour both. Go get him surgeons.</A><br>
+<p><i>Exit Sergeant, attended</i></p>
+<A NAME=1.2.49>Who comes here?</A><br>
+<p><i>Enter ROSS</i></p>
+</blockquote>
+
+<A NAME=speech9><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=1.2.50> The worthy thane of Ross.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>LENNOX</b></a>
+<blockquote>
+<A NAME=1.2.51>What a haste looks through his eyes! So should he look</A><br>
+<A NAME=1.2.52>That seems to speak things strange.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>ROSS</b></a>
+<blockquote>
+<A NAME=1.2.53>God save the king!</A><br>
+</blockquote>
+
+<A NAME=speech12><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.2.54>Whence camest thou, worthy thane?</A><br>
+</blockquote>
+
+<A NAME=speech13><b>ROSS</b></a>
+<blockquote>
+<A NAME=1.2.55>From Fife, great king;</A><br>
+<A NAME=1.2.56>Where the Norweyan banners flout the sky</A><br>
+<A NAME=1.2.57>And fan our people cold. Norway himself,</A><br>
+<A NAME=1.2.58>With terrible numbers,</A><br>
+<A NAME=1.2.59>Assisted by that most disloyal traitor</A><br>
+<A NAME=1.2.60>The thane of Cawdor, began a dismal conflict;</A><br>
+<A NAME=1.2.61>Till that Bellona's bridegroom, lapp'd in proof,</A><br>
+<A NAME=1.2.62>Confronted him with self-comparisons,</A><br>
+<A NAME=1.2.63>Point against point rebellious, arm 'gainst arm.</A><br>
+<A NAME=1.2.64>Curbing his lavish spirit: and, to conclude,</A><br>
+<A NAME=1.2.65>The victory fell on us.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.2.66>Great happiness!</A><br>
+</blockquote>
+
+<A NAME=speech15><b>ROSS</b></a>
+<blockquote>
+<A NAME=1.2.67>That now</A><br>
+<A NAME=1.2.68>Sweno, the Norways' king, craves composition:</A><br>
+<A NAME=1.2.69>Nor would we deign him burial of his men</A><br>
+<A NAME=1.2.70>Till he disbursed at Saint Colme's inch</A><br>
+<A NAME=1.2.71>Ten thousand dollars to our general use.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.2.72>No more that thane of Cawdor shall deceive</A><br>
+<A NAME=1.2.73>Our bosom interest: go pronounce his present death,</A><br>
+<A NAME=1.2.74>And with his former title greet Macbeth.</A><br>
+</blockquote>
+
+<A NAME=speech17><b>ROSS</b></a>
+<blockquote>
+<A NAME=1.2.75>I'll see it done.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.2.76>What he hath lost noble Macbeth hath won.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE III. A heath near Forres.</h3>
+<p><blockquote>
+<i>Thunder. Enter the three Witches</i>
+</blockquote>
+
+<A NAME=speech1><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.1>Where hast thou been, sister?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.3.2>Killing swine.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.3.3>Sister, where thou?</A><br>
+</blockquote>
+
+<A NAME=speech4><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.4>A sailor's wife had chestnuts in her lap,</A><br>
+<A NAME=1.3.5>And munch'd, and munch'd, and munch'd:--</A><br>
+<A NAME=1.3.6>'Give me,' quoth I:</A><br>
+<A NAME=1.3.7>'Aroint thee, witch!' the rump-fed ronyon cries.</A><br>
+<A NAME=1.3.8>Her husband's to Aleppo gone, master o' the Tiger:</A><br>
+<A NAME=1.3.9>But in a sieve I'll thither sail,</A><br>
+<A NAME=1.3.10>And, like a rat without a tail,</A><br>
+<A NAME=1.3.11>I'll do, I'll do, and I'll do.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.3.12>I'll give thee a wind.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.13>Thou'rt kind.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.3.14>And I another.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.15>I myself have all the other,</A><br>
+<A NAME=1.3.16>And the very ports they blow,</A><br>
+<A NAME=1.3.17>All the quarters that they know</A><br>
+<A NAME=1.3.18>I' the shipman's card.</A><br>
+<A NAME=1.3.19>I will drain him dry as hay:</A><br>
+<A NAME=1.3.20>Sleep shall neither night nor day</A><br>
+<A NAME=1.3.21>Hang upon his pent-house lid;</A><br>
+<A NAME=1.3.22>He shall live a man forbid:</A><br>
+<A NAME=1.3.23>Weary se'nnights nine times nine</A><br>
+<A NAME=1.3.24>Shall he dwindle, peak and pine:</A><br>
+<A NAME=1.3.25>Though his bark cannot be lost,</A><br>
+<A NAME=1.3.26>Yet it shall be tempest-tost.</A><br>
+<A NAME=1.3.27>Look what I have.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.3.28>Show me, show me.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.29>Here I have a pilot's thumb,</A><br>
+<A NAME=1.3.30>Wreck'd as homeward he did come.</A><br>
+<p><i>Drum within</i></p>
+</blockquote>
+
+<A NAME=speech11><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.3.31>A drum, a drum!</A><br>
+<A NAME=1.3.32>Macbeth doth come.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>ALL</b></a>
+<blockquote>
+<A NAME=1.3.33>The weird sisters, hand in hand,</A><br>
+<A NAME=1.3.34>Posters of the sea and land,</A><br>
+<A NAME=1.3.35>Thus do go about, about:</A><br>
+<A NAME=1.3.36>Thrice to thine and thrice to mine</A><br>
+<A NAME=1.3.37>And thrice again, to make up nine.</A><br>
+<A NAME=1.3.38>Peace! the charm's wound up.</A><br>
+<p><i>Enter MACBETH and BANQUO</i></p>
+</blockquote>
+
+<A NAME=speech13><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.39>So foul and fair a day I have not seen.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.40>How far is't call'd to Forres? What are these</A><br>
+<A NAME=1.3.41>So wither'd and so wild in their attire,</A><br>
+<A NAME=1.3.42>That look not like the inhabitants o' the earth,</A><br>
+<A NAME=1.3.43>And yet are on't? Live you? or are you aught</A><br>
+<A NAME=1.3.44>That man may question? You seem to understand me,</A><br>
+<A NAME=1.3.45>By each at once her chappy finger laying</A><br>
+<A NAME=1.3.46>Upon her skinny lips: you should be women,</A><br>
+<A NAME=1.3.47>And yet your beards forbid me to interpret</A><br>
+<A NAME=1.3.48>That you are so.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.49> Speak, if you can: what are you?</A><br>
+</blockquote>
+
+<A NAME=speech16><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.50>All hail, Macbeth! hail to thee, thane of Glamis!</A><br>
+</blockquote>
+
+<A NAME=speech17><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.3.51>All hail, Macbeth, hail to thee, thane of Cawdor!</A><br>
+</blockquote>
+
+<A NAME=speech18><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.3.52>All hail, Macbeth, thou shalt be king hereafter!</A><br>
+</blockquote>
+
+<A NAME=speech19><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.53>Good sir, why do you start; and seem to fear</A><br>
+<A NAME=1.3.54>Things that do sound so fair? I' the name of truth,</A><br>
+<A NAME=1.3.55>Are ye fantastical, or that indeed</A><br>
+<A NAME=1.3.56>Which outwardly ye show? My noble partner</A><br>
+<A NAME=1.3.57>You greet with present grace and great prediction</A><br>
+<A NAME=1.3.58>Of noble having and of royal hope,</A><br>
+<A NAME=1.3.59>That he seems rapt withal: to me you speak not.</A><br>
+<A NAME=1.3.60>If you can look into the seeds of time,</A><br>
+<A NAME=1.3.61>And say which grain will grow and which will not,</A><br>
+<A NAME=1.3.62>Speak then to me, who neither beg nor fear</A><br>
+<A NAME=1.3.63>Your favours nor your hate.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.64>Hail!</A><br>
+</blockquote>
+
+<A NAME=speech21><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.3.65>Hail!</A><br>
+</blockquote>
+
+<A NAME=speech22><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.3.66>Hail!</A><br>
+</blockquote>
+
+<A NAME=speech23><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.67>Lesser than Macbeth, and greater.</A><br>
+</blockquote>
+
+<A NAME=speech24><b>Second Witch</b></a>
+<blockquote>
+<A NAME=1.3.68>Not so happy, yet much happier.</A><br>
+</blockquote>
+
+<A NAME=speech25><b>Third Witch</b></a>
+<blockquote>
+<A NAME=1.3.69>Thou shalt get kings, though thou be none:</A><br>
+<A NAME=1.3.70>So all hail, Macbeth and Banquo!</A><br>
+</blockquote>
+
+<A NAME=speech26><b>First Witch</b></a>
+<blockquote>
+<A NAME=1.3.71>Banquo and Macbeth, all hail!</A><br>
+</blockquote>
+
+<A NAME=speech27><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.72>Stay, you imperfect speakers, tell me more:</A><br>
+<A NAME=1.3.73>By Sinel's death I know I am thane of Glamis;</A><br>
+<A NAME=1.3.74>But how of Cawdor? the thane of Cawdor lives,</A><br>
+<A NAME=1.3.75>A prosperous gentleman; and to be king</A><br>
+<A NAME=1.3.76>Stands not within the prospect of belief,</A><br>
+<A NAME=1.3.77>No more than to be Cawdor. Say from whence</A><br>
+<A NAME=1.3.78>You owe this strange intelligence? or why</A><br>
+<A NAME=1.3.79>Upon this blasted heath you stop our way</A><br>
+<A NAME=1.3.80>With such prophetic greeting? Speak, I charge you.</A><br>
+<p><i>Witches vanish</i></p>
+</blockquote>
+
+<A NAME=speech28><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.81>The earth hath bubbles, as the water has,</A><br>
+<A NAME=1.3.82>And these are of them. Whither are they vanish'd?</A><br>
+</blockquote>
+
+<A NAME=speech29><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.83>Into the air; and what seem'd corporal melted</A><br>
+<A NAME=1.3.84>As breath into the wind. Would they had stay'd!</A><br>
+</blockquote>
+
+<A NAME=speech30><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.85>Were such things here as we do speak about?</A><br>
+<A NAME=1.3.86>Or have we eaten on the insane root</A><br>
+<A NAME=1.3.87>That takes the reason prisoner?</A><br>
+</blockquote>
+
+<A NAME=speech31><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.88>Your children shall be kings.</A><br>
+</blockquote>
+
+<A NAME=speech32><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.89>You shall be king.</A><br>
+</blockquote>
+
+<A NAME=speech33><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.90>And thane of Cawdor too: went it not so?</A><br>
+</blockquote>
+
+<A NAME=speech34><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.91>To the selfsame tune and words. Who's here?</A><br>
+<p><i>Enter ROSS and ANGUS</i></p>
+</blockquote>
+
+<A NAME=speech35><b>ROSS</b></a>
+<blockquote>
+<A NAME=1.3.92>The king hath happily received, Macbeth,</A><br>
+<A NAME=1.3.93>The news of thy success; and when he reads</A><br>
+<A NAME=1.3.94>Thy personal venture in the rebels' fight,</A><br>
+<A NAME=1.3.95>His wonders and his praises do contend</A><br>
+<A NAME=1.3.96>Which should be thine or his: silenced with that,</A><br>
+<A NAME=1.3.97>In viewing o'er the rest o' the selfsame day,</A><br>
+<A NAME=1.3.98>He finds thee in the stout Norweyan ranks,</A><br>
+<A NAME=1.3.99>Nothing afeard of what thyself didst make,</A><br>
+<A NAME=1.3.100>Strange images of death. As thick as hail</A><br>
+<A NAME=1.3.101>Came post with post; and every one did bear</A><br>
+<A NAME=1.3.102>Thy praises in his kingdom's great defence,</A><br>
+<A NAME=1.3.103>And pour'd them down before him.</A><br>
+</blockquote>
+
+<A NAME=speech36><b>ANGUS</b></a>
+<blockquote>
+<A NAME=1.3.104>We are sent</A><br>
+<A NAME=1.3.105>To give thee from our royal master thanks;</A><br>
+<A NAME=1.3.106>Only to herald thee into his sight,</A><br>
+<A NAME=1.3.107>Not pay thee.</A><br>
+</blockquote>
+
+<A NAME=speech37><b>ROSS</b></a>
+<blockquote>
+<A NAME=1.3.108>And, for an earnest of a greater honour,</A><br>
+<A NAME=1.3.109>He bade me, from him, call thee thane of Cawdor:</A><br>
+<A NAME=1.3.110>In which addition, hail, most worthy thane!</A><br>
+<A NAME=1.3.111>For it is thine.</A><br>
+</blockquote>
+
+<A NAME=speech38><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.112> What, can the devil speak true?</A><br>
+</blockquote>
+
+<A NAME=speech39><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.113>The thane of Cawdor lives: why do you dress me</A><br>
+<A NAME=1.3.114>In borrow'd robes?</A><br>
+</blockquote>
+
+<A NAME=speech40><b>ANGUS</b></a>
+<blockquote>
+<A NAME=1.3.115> Who was the thane lives yet;</A><br>
+<A NAME=1.3.116>But under heavy judgment bears that life</A><br>
+<A NAME=1.3.117>Which he deserves to lose. Whether he was combined</A><br>
+<A NAME=1.3.118>With those of Norway, or did line the rebel</A><br>
+<A NAME=1.3.119>With hidden help and vantage, or that with both</A><br>
+<A NAME=1.3.120>He labour'd in his country's wreck, I know not;</A><br>
+<A NAME=1.3.121>But treasons capital, confess'd and proved,</A><br>
+<A NAME=1.3.122>Have overthrown him.</A><br>
+</blockquote>
+
+<A NAME=speech41><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.123>[Aside] Glamis, and thane of Cawdor!</A><br>
+<A NAME=1.3.124>The greatest is behind.</A><br>
+<p><i>To ROSS and ANGUS</i></p>
+<A NAME=1.3.125>Thanks for your pains.</A><br>
+<p><i>To BANQUO</i></p>
+<A NAME=1.3.126>Do you not hope your children shall be kings,</A><br>
+<A NAME=1.3.127>When those that gave the thane of Cawdor to me</A><br>
+<A NAME=1.3.128>Promised no less to them?</A><br>
+</blockquote>
+
+<A NAME=speech42><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.129>That trusted home</A><br>
+<A NAME=1.3.130>Might yet enkindle you unto the crown,</A><br>
+<A NAME=1.3.131>Besides the thane of Cawdor. But 'tis strange:</A><br>
+<A NAME=1.3.132>And oftentimes, to win us to our harm,</A><br>
+<A NAME=1.3.133>The instruments of darkness tell us truths,</A><br>
+<A NAME=1.3.134>Win us with honest trifles, to betray's</A><br>
+<A NAME=1.3.135>In deepest consequence.</A><br>
+<A NAME=1.3.136>Cousins, a word, I pray you.</A><br>
+</blockquote>
+
+<A NAME=speech43><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.137>[Aside] Two truths are told,</A><br>
+<A NAME=1.3.138>As happy prologues to the swelling act</A><br>
+<A NAME=1.3.139>Of the imperial theme.--I thank you, gentlemen.</A><br>
+<p><i>Aside</i></p>
+<A NAME=1.3.140>Cannot be ill, cannot be good: if ill,</A><br>
+<A NAME=1.3.141>Why hath it given me earnest of success,</A><br>
+<A NAME=1.3.142>Commencing in a truth? I am thane of Cawdor:</A><br>
+<A NAME=1.3.143>If good, why do I yield to that suggestion</A><br>
+<A NAME=1.3.144>Whose horrid image doth unfix my hair</A><br>
+<A NAME=1.3.145>And make my seated heart knock at my ribs,</A><br>
+<A NAME=1.3.146>Against the use of nature? Present fears</A><br>
+<A NAME=1.3.147>Are less than horrible imaginings:</A><br>
+<A NAME=1.3.148>My thought, whose murder yet is but fantastical,</A><br>
+<A NAME=1.3.149>Shakes so my single state of man that function</A><br>
+<A NAME=1.3.150>Is smother'd in surmise, and nothing is</A><br>
+<A NAME=1.3.151>But what is not.</A><br>
+</blockquote>
+
+<A NAME=speech44><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.152> Look, how our partner's rapt.</A><br>
+</blockquote>
+
+<A NAME=speech45><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.153>[Aside] If chance will have me king, why, chance may crown me,</A><br>
+<A NAME=1.3.154>Without my stir.</A><br>
+</blockquote>
+
+<A NAME=speech46><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.155> New horrors come upon him,</A><br>
+<A NAME=1.3.156>Like our strange garments, cleave not to their mould</A><br>
+<A NAME=1.3.157>But with the aid of use.</A><br>
+</blockquote>
+
+<A NAME=speech47><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.158>[Aside] Come what come may,</A><br>
+<A NAME=1.3.159>Time and the hour runs through the roughest day.</A><br>
+</blockquote>
+
+<A NAME=speech48><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.160>Worthy Macbeth, we stay upon your leisure.</A><br>
+</blockquote>
+
+<A NAME=speech49><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.161>Give me your favour: my dull brain was wrought</A><br>
+<A NAME=1.3.162>With things forgotten. Kind gentlemen, your pains</A><br>
+<A NAME=1.3.163>Are register'd where every day I turn</A><br>
+<A NAME=1.3.164>The leaf to read them. Let us toward the king.</A><br>
+<A NAME=1.3.165>Think upon what hath chanced, and, at more time,</A><br>
+<A NAME=1.3.166>The interim having weigh'd it, let us speak</A><br>
+<A NAME=1.3.167>Our free hearts each to other.</A><br>
+</blockquote>
+
+<A NAME=speech50><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.3.168>Very gladly.</A><br>
+</blockquote>
+
+<A NAME=speech51><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.3.169>Till then, enough. Come, friends.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE IV. Forres. The palace.</h3>
+<p><blockquote>
+<i>Flourish. Enter DUNCAN, MALCOLM, DONALBAIN, LENNOX, and Attendants</i>
+</blockquote>
+
+<A NAME=speech1><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.4.1>Is execution done on Cawdor? Are not</A><br>
+<A NAME=1.4.2>Those in commission yet return'd?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=1.4.3>My liege,</A><br>
+<A NAME=1.4.4>They are not yet come back. But I have spoke</A><br>
+<A NAME=1.4.5>With one that saw him die: who did report</A><br>
+<A NAME=1.4.6>That very frankly he confess'd his treasons,</A><br>
+<A NAME=1.4.7>Implored your highness' pardon and set forth</A><br>
+<A NAME=1.4.8>A deep repentance: nothing in his life</A><br>
+<A NAME=1.4.9>Became him like the leaving it; he died</A><br>
+<A NAME=1.4.10>As one that had been studied in his death</A><br>
+<A NAME=1.4.11>To throw away the dearest thing he owed,</A><br>
+<A NAME=1.4.12>As 'twere a careless trifle.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.4.13>There's no art</A><br>
+<A NAME=1.4.14>To find the mind's construction in the face:</A><br>
+<A NAME=1.4.15>He was a gentleman on whom I built</A><br>
+<A NAME=1.4.16>An absolute trust.</A><br>
+<p><i>Enter MACBETH, BANQUO, ROSS, and ANGUS</i></p>
+<A NAME=1.4.17>O worthiest cousin!</A><br>
+<A NAME=1.4.18>The sin of my ingratitude even now</A><br>
+<A NAME=1.4.19>Was heavy on me: thou art so far before</A><br>
+<A NAME=1.4.20>That swiftest wing of recompense is slow</A><br>
+<A NAME=1.4.21>To overtake thee. Would thou hadst less deserved,</A><br>
+<A NAME=1.4.22>That the proportion both of thanks and payment</A><br>
+<A NAME=1.4.23>Might have been mine! only I have left to say,</A><br>
+<A NAME=1.4.24>More is thy due than more than all can pay.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.4.25>The service and the loyalty I owe,</A><br>
+<A NAME=1.4.26>In doing it, pays itself. Your highness' part</A><br>
+<A NAME=1.4.27>Is to receive our duties; and our duties</A><br>
+<A NAME=1.4.28>Are to your throne and state children and servants,</A><br>
+<A NAME=1.4.29>Which do but what they should, by doing every thing</A><br>
+<A NAME=1.4.30>Safe toward your love and honour.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.4.31>Welcome hither:</A><br>
+<A NAME=1.4.32>I have begun to plant thee, and will labour</A><br>
+<A NAME=1.4.33>To make thee full of growing. Noble Banquo,</A><br>
+<A NAME=1.4.34>That hast no less deserved, nor must be known</A><br>
+<A NAME=1.4.35>No less to have done so, let me enfold thee</A><br>
+<A NAME=1.4.36>And hold thee to my heart.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.4.37>There if I grow,</A><br>
+<A NAME=1.4.38>The harvest is your own.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.4.39>My plenteous joys,</A><br>
+<A NAME=1.4.40>Wanton in fulness, seek to hide themselves</A><br>
+<A NAME=1.4.41>In drops of sorrow. Sons, kinsmen, thanes,</A><br>
+<A NAME=1.4.42>And you whose places are the nearest, know</A><br>
+<A NAME=1.4.43>We will establish our estate upon</A><br>
+<A NAME=1.4.44>Our eldest, Malcolm, whom we name hereafter</A><br>
+<A NAME=1.4.45>The Prince of Cumberland; which honour must</A><br>
+<A NAME=1.4.46>Not unaccompanied invest him only,</A><br>
+<A NAME=1.4.47>But signs of nobleness, like stars, shall shine</A><br>
+<A NAME=1.4.48>On all deservers. From hence to Inverness,</A><br>
+<A NAME=1.4.49>And bind us further to you.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.4.50>The rest is labour, which is not used for you:</A><br>
+<A NAME=1.4.51>I'll be myself the harbinger and make joyful</A><br>
+<A NAME=1.4.52>The hearing of my wife with your approach;</A><br>
+<A NAME=1.4.53>So humbly take my leave.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.4.54>My worthy Cawdor!</A><br>
+</blockquote>
+
+<A NAME=speech10><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.4.55>[Aside] The Prince of Cumberland! that is a step</A><br>
+<A NAME=1.4.56>On which I must fall down, or else o'erleap,</A><br>
+<A NAME=1.4.57>For in my way it lies. Stars, hide your fires;</A><br>
+<A NAME=1.4.58>Let not light see my black and deep desires:</A><br>
+<A NAME=1.4.59>The eye wink at the hand; yet let that be,</A><br>
+<A NAME=1.4.60>Which the eye fears, when it is done, to see.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+
+<A NAME=speech11><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.4.61>True, worthy Banquo; he is full so valiant,</A><br>
+<A NAME=1.4.62>And in his commendations I am fed;</A><br>
+<A NAME=1.4.63>It is a banquet to me. Let's after him,</A><br>
+<A NAME=1.4.64>Whose care is gone before to bid us welcome:</A><br>
+<A NAME=1.4.65>It is a peerless kinsman.</A><br>
+<p><i>Flourish. Exeunt</i></p>
+</blockquote>
+<h3>SCENE V. Inverness. Macbeth's castle.</h3>
+<p><blockquote>
+<i>Enter LADY MACBETH, reading a letter</i>
+</blockquote>
+
+<A NAME=speech1><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.1>'They met me in the day of success: and I have</A><br>
+<A NAME=1.5.2>learned by the perfectest report, they have more in</A><br>
+<A NAME=1.5.3>them than mortal knowledge. When I burned in desire</A><br>
+<A NAME=1.5.4>to question them further, they made themselves air,</A><br>
+<A NAME=1.5.5>into which they vanished. Whiles I stood rapt in</A><br>
+<A NAME=1.5.6>the wonder of it, came missives from the king, who</A><br>
+<A NAME=1.5.7>all-hailed me 'Thane of Cawdor;' by which title,</A><br>
+<A NAME=1.5.8>before, these weird sisters saluted me, and referred</A><br>
+<A NAME=1.5.9>me to the coming on of time, with 'Hail, king that</A><br>
+<A NAME=1.5.10>shalt be!' This have I thought good to deliver</A><br>
+<A NAME=1.5.11>thee, my dearest partner of greatness, that thou</A><br>
+<A NAME=1.5.12>mightst not lose the dues of rejoicing, by being</A><br>
+<A NAME=1.5.13>ignorant of what greatness is promised thee. Lay it</A><br>
+<A NAME=1.5.14>to thy heart, and farewell.'</A><br>
+<A NAME=1.5.15>Glamis thou art, and Cawdor; and shalt be</A><br>
+<A NAME=1.5.16>What thou art promised: yet do I fear thy nature;</A><br>
+<A NAME=1.5.17>It is too full o' the milk of human kindness</A><br>
+<A NAME=1.5.18>To catch the nearest way: thou wouldst be great;</A><br>
+<A NAME=1.5.19>Art not without ambition, but without</A><br>
+<A NAME=1.5.20>The illness should attend it: what thou wouldst highly,</A><br>
+<A NAME=1.5.21>That wouldst thou holily; wouldst not play false,</A><br>
+<A NAME=1.5.22>And yet wouldst wrongly win: thou'ldst have, great Glamis,</A><br>
+<A NAME=1.5.23>That which cries 'Thus thou must do, if thou have it;</A><br>
+<A NAME=1.5.24>And that which rather thou dost fear to do</A><br>
+<A NAME=1.5.25>Than wishest should be undone.' Hie thee hither,</A><br>
+<A NAME=1.5.26>That I may pour my spirits in thine ear;</A><br>
+<A NAME=1.5.27>And chastise with the valour of my tongue</A><br>
+<A NAME=1.5.28>All that impedes thee from the golden round,</A><br>
+<A NAME=1.5.29>Which fate and metaphysical aid doth seem</A><br>
+<A NAME=1.5.30>To have thee crown'd withal.</A><br>
+<p><i>Enter a Messenger</i></p>
+<A NAME=1.5.31>What is your tidings?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Messenger</b></a>
+<blockquote>
+<A NAME=1.5.32>The king comes here to-night.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.33>Thou'rt mad to say it:</A><br>
+<A NAME=1.5.34>Is not thy master with him? who, were't so,</A><br>
+<A NAME=1.5.35>Would have inform'd for preparation.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>Messenger</b></a>
+<blockquote>
+<A NAME=1.5.36>So please you, it is true: our thane is coming:</A><br>
+<A NAME=1.5.37>One of my fellows had the speed of him,</A><br>
+<A NAME=1.5.38>Who, almost dead for breath, had scarcely more</A><br>
+<A NAME=1.5.39>Than would make up his message.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.40>Give him tending;</A><br>
+<A NAME=1.5.41>He brings great news.</A><br>
+<p><i>Exit Messenger</i></p>
+<A NAME=1.5.42>The raven himself is hoarse</A><br>
+<A NAME=1.5.43>That croaks the fatal entrance of Duncan</A><br>
+<A NAME=1.5.44>Under my battlements. Come, you spirits</A><br>
+<A NAME=1.5.45>That tend on mortal thoughts, unsex me here,</A><br>
+<A NAME=1.5.46>And fill me from the crown to the toe top-full</A><br>
+<A NAME=1.5.47>Of direst cruelty! make thick my blood;</A><br>
+<A NAME=1.5.48>Stop up the access and passage to remorse,</A><br>
+<A NAME=1.5.49>That no compunctious visitings of nature</A><br>
+<A NAME=1.5.50>Shake my fell purpose, nor keep peace between</A><br>
+<A NAME=1.5.51>The effect and it! Come to my woman's breasts,</A><br>
+<A NAME=1.5.52>And take my milk for gall, you murdering ministers,</A><br>
+<A NAME=1.5.53>Wherever in your sightless substances</A><br>
+<A NAME=1.5.54>You wait on nature's mischief! Come, thick night,</A><br>
+<A NAME=1.5.55>And pall thee in the dunnest smoke of hell,</A><br>
+<A NAME=1.5.56>That my keen knife see not the wound it makes,</A><br>
+<A NAME=1.5.57>Nor heaven peep through the blanket of the dark,</A><br>
+<A NAME=1.5.58>To cry 'Hold, hold!'</A><br>
+<p><i>Enter MACBETH</i></p>
+<A NAME=1.5.59>Great Glamis! worthy Cawdor!</A><br>
+<A NAME=1.5.60>Greater than both, by the all-hail hereafter!</A><br>
+<A NAME=1.5.61>Thy letters have transported me beyond</A><br>
+<A NAME=1.5.62>This ignorant present, and I feel now</A><br>
+<A NAME=1.5.63>The future in the instant.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.64>My dearest love,</A><br>
+<A NAME=1.5.65>Duncan comes here to-night.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.66>And when goes hence?</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.67>To-morrow, as he purposes.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.68>O, never</A><br>
+<A NAME=1.5.69>Shall sun that morrow see!</A><br>
+<A NAME=1.5.70>Your face, my thane, is as a book where men</A><br>
+<A NAME=1.5.71>May read strange matters. To beguile the time,</A><br>
+<A NAME=1.5.72>Look like the time; bear welcome in your eye,</A><br>
+<A NAME=1.5.73>Your hand, your tongue: look like the innocent flower,</A><br>
+<A NAME=1.5.74>But be the serpent under't. He that's coming</A><br>
+<A NAME=1.5.75>Must be provided for: and you shall put</A><br>
+<A NAME=1.5.76>This night's great business into my dispatch;</A><br>
+<A NAME=1.5.77>Which shall to all our nights and days to come</A><br>
+<A NAME=1.5.78>Give solely sovereign sway and masterdom.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.79>We will speak further.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.5.80>Only look up clear;</A><br>
+<A NAME=1.5.81>To alter favour ever is to fear:</A><br>
+<A NAME=1.5.82>Leave all the rest to me.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE VI. Before Macbeth's castle.</h3>
+<p><blockquote>
+<i>Hautboys and torches. Enter DUNCAN, MALCOLM, DONALBAIN, BANQUO, LENNOX, MACDUFF, ROSS, ANGUS, and Attendants</i>
+</blockquote>
+
+<A NAME=speech1><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.6.1>This castle hath a pleasant seat; the air</A><br>
+<A NAME=1.6.2>Nimbly and sweetly recommends itself</A><br>
+<A NAME=1.6.3>Unto our gentle senses.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>BANQUO</b></a>
+<blockquote>
+<A NAME=1.6.4>This guest of summer,</A><br>
+<A NAME=1.6.5>The temple-haunting martlet, does approve,</A><br>
+<A NAME=1.6.6>By his loved mansionry, that the heaven's breath</A><br>
+<A NAME=1.6.7>Smells wooingly here: no jutty, frieze,</A><br>
+<A NAME=1.6.8>Buttress, nor coign of vantage, but this bird</A><br>
+<A NAME=1.6.9>Hath made his pendent bed and procreant cradle:</A><br>
+<A NAME=1.6.10>Where they most breed and haunt, I have observed,</A><br>
+<A NAME=1.6.11>The air is delicate.</A><br>
+<p><i>Enter LADY MACBETH</i></p>
+</blockquote>
+
+<A NAME=speech3><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.6.12>See, see, our honour'd hostess!</A><br>
+<A NAME=1.6.13>The love that follows us sometime is our trouble,</A><br>
+<A NAME=1.6.14>Which still we thank as love. Herein I teach you</A><br>
+<A NAME=1.6.15>How you shall bid God 'ild us for your pains,</A><br>
+<A NAME=1.6.16>And thank us for your trouble.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.6.17>All our service</A><br>
+<A NAME=1.6.18>In every point twice done and then done double</A><br>
+<A NAME=1.6.19>Were poor and single business to contend</A><br>
+<A NAME=1.6.20>Against those honours deep and broad wherewith</A><br>
+<A NAME=1.6.21>Your majesty loads our house: for those of old,</A><br>
+<A NAME=1.6.22>And the late dignities heap'd up to them,</A><br>
+<A NAME=1.6.23>We rest your hermits.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.6.24>Where's the thane of Cawdor?</A><br>
+<A NAME=1.6.25>We coursed him at the heels, and had a purpose</A><br>
+<A NAME=1.6.26>To be his purveyor: but he rides well;</A><br>
+<A NAME=1.6.27>And his great love, sharp as his spur, hath holp him</A><br>
+<A NAME=1.6.28>To his home before us. Fair and noble hostess,</A><br>
+<A NAME=1.6.29>We are your guest to-night.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.6.30>Your servants ever</A><br>
+<A NAME=1.6.31>Have theirs, themselves and what is theirs, in compt,</A><br>
+<A NAME=1.6.32>To make their audit at your highness' pleasure,</A><br>
+<A NAME=1.6.33>Still to return your own.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>DUNCAN</b></a>
+<blockquote>
+<A NAME=1.6.34>Give me your hand;</A><br>
+<A NAME=1.6.35>Conduct me to mine host: we love him highly,</A><br>
+<A NAME=1.6.36>And shall continue our graces towards him.</A><br>
+<A NAME=1.6.37>By your leave, hostess.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE VII. Macbeth's castle.</h3>
+<p><blockquote>
+<i>Hautboys and torches. Enter a Sewer, and divers Servants with dishes and service, and pass over the stage. Then enter MACBETH</i>
+</blockquote>
+
+<A NAME=speech1><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.1>If it were done when 'tis done, then 'twere well</A><br>
+<A NAME=1.7.2>It were done quickly: if the assassination</A><br>
+<A NAME=1.7.3>Could trammel up the consequence, and catch</A><br>
+<A NAME=1.7.4>With his surcease success; that but this blow</A><br>
+<A NAME=1.7.5>Might be the be-all and the end-all here,</A><br>
+<A NAME=1.7.6>But here, upon this bank and shoal of time,</A><br>
+<A NAME=1.7.7>We'ld jump the life to come. But in these cases</A><br>
+<A NAME=1.7.8>We still have judgment here; that we but teach</A><br>
+<A NAME=1.7.9>Bloody instructions, which, being taught, return</A><br>
+<A NAME=1.7.10>To plague the inventor: this even-handed justice</A><br>
+<A NAME=1.7.11>Commends the ingredients of our poison'd chalice</A><br>
+<A NAME=1.7.12>To our own lips. He's here in double trust;</A><br>
+<A NAME=1.7.13>First, as I am his kinsman and his subject,</A><br>
+<A NAME=1.7.14>Strong both against the deed; then, as his host,</A><br>
+<A NAME=1.7.15>Who should against his murderer shut the door,</A><br>
+<A NAME=1.7.16>Not bear the knife myself. Besides, this Duncan</A><br>
+<A NAME=1.7.17>Hath borne his faculties so meek, hath been</A><br>
+<A NAME=1.7.18>So clear in his great office, that his virtues</A><br>
+<A NAME=1.7.19>Will plead like angels, trumpet-tongued, against</A><br>
+<A NAME=1.7.20>The deep damnation of his taking-off;</A><br>
+<A NAME=1.7.21>And pity, like a naked new-born babe,</A><br>
+<A NAME=1.7.22>Striding the blast, or heaven's cherubim, horsed</A><br>
+<A NAME=1.7.23>Upon the sightless couriers of the air,</A><br>
+<A NAME=1.7.24>Shall blow the horrid deed in every eye,</A><br>
+<A NAME=1.7.25>That tears shall drown the wind. I have no spur</A><br>
+<A NAME=1.7.26>To prick the sides of my intent, but only</A><br>
+<A NAME=1.7.27>Vaulting ambition, which o'erleaps itself</A><br>
+<A NAME=1.7.28>And falls on the other.</A><br>
+<p><i>Enter LADY MACBETH</i></p>
+<A NAME=1.7.29>How now! what news?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.30>He has almost supp'd: why have you left the chamber?</A><br>
+</blockquote>
+
+<A NAME=speech3><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.31>Hath he ask'd for me?</A><br>
+</blockquote>
+
+<A NAME=speech4><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.32>Know you not he has?</A><br>
+</blockquote>
+
+<A NAME=speech5><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.33>We will proceed no further in this business:</A><br>
+<A NAME=1.7.34>He hath honour'd me of late; and I have bought</A><br>
+<A NAME=1.7.35>Golden opinions from all sorts of people,</A><br>
+<A NAME=1.7.36>Which would be worn now in their newest gloss,</A><br>
+<A NAME=1.7.37>Not cast aside so soon.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.38>Was the hope drunk</A><br>
+<A NAME=1.7.39>Wherein you dress'd yourself? hath it slept since?</A><br>
+<A NAME=1.7.40>And wakes it now, to look so green and pale</A><br>
+<A NAME=1.7.41>At what it did so freely? From this time</A><br>
+<A NAME=1.7.42>Such I account thy love. Art thou afeard</A><br>
+<A NAME=1.7.43>To be the same in thine own act and valour</A><br>
+<A NAME=1.7.44>As thou art in desire? Wouldst thou have that</A><br>
+<A NAME=1.7.45>Which thou esteem'st the ornament of life,</A><br>
+<A NAME=1.7.46>And live a coward in thine own esteem,</A><br>
+<A NAME=1.7.47>Letting 'I dare not' wait upon 'I would,'</A><br>
+<A NAME=1.7.48>Like the poor cat i' the adage?</A><br>
+</blockquote>
+
+<A NAME=speech7><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.49>Prithee, peace:</A><br>
+<A NAME=1.7.50>I dare do all that may become a man;</A><br>
+<A NAME=1.7.51>Who dares do more is none.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.52>What beast was't, then,</A><br>
+<A NAME=1.7.53>That made you break this enterprise to me?</A><br>
+<A NAME=1.7.54>When you durst do it, then you were a man;</A><br>
+<A NAME=1.7.55>And, to be more than what you were, you would</A><br>
+<A NAME=1.7.56>Be so much more the man. Nor time nor place</A><br>
+<A NAME=1.7.57>Did then adhere, and yet you would make both:</A><br>
+<A NAME=1.7.58>They have made themselves, and that their fitness now</A><br>
+<A NAME=1.7.59>Does unmake you. I have given suck, and know</A><br>
+<A NAME=1.7.60>How tender 'tis to love the babe that milks me:</A><br>
+<A NAME=1.7.61>I would, while it was smiling in my face,</A><br>
+<A NAME=1.7.62>Have pluck'd my nipple from his boneless gums,</A><br>
+<A NAME=1.7.63>And dash'd the brains out, had I so sworn as you</A><br>
+<A NAME=1.7.64>Have done to this.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.65> If we should fail?</A><br>
+</blockquote>
+
+<A NAME=speech10><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.66>We fail!</A><br>
+<A NAME=1.7.67>But screw your courage to the sticking-place,</A><br>
+<A NAME=1.7.68>And we'll not fail. When Duncan is asleep--</A><br>
+<A NAME=1.7.69>Whereto the rather shall his day's hard journey</A><br>
+<A NAME=1.7.70>Soundly invite him--his two chamberlains</A><br>
+<A NAME=1.7.71>Will I with wine and wassail so convince</A><br>
+<A NAME=1.7.72>That memory, the warder of the brain,</A><br>
+<A NAME=1.7.73>Shall be a fume, and the receipt of reason</A><br>
+<A NAME=1.7.74>A limbeck only: when in swinish sleep</A><br>
+<A NAME=1.7.75>Their drenched natures lie as in a death,</A><br>
+<A NAME=1.7.76>What cannot you and I perform upon</A><br>
+<A NAME=1.7.77>The unguarded Duncan? what not put upon</A><br>
+<A NAME=1.7.78>His spongy officers, who shall bear the guilt</A><br>
+<A NAME=1.7.79>Of our great quell?</A><br>
+</blockquote>
+
+<A NAME=speech11><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.80>Bring forth men-children only;</A><br>
+<A NAME=1.7.81>For thy undaunted mettle should compose</A><br>
+<A NAME=1.7.82>Nothing but males. Will it not be received,</A><br>
+<A NAME=1.7.83>When we have mark'd with blood those sleepy two</A><br>
+<A NAME=1.7.84>Of his own chamber and used their very daggers,</A><br>
+<A NAME=1.7.85>That they have done't?</A><br>
+</blockquote>
+
+<A NAME=speech12><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.86>Who dares receive it other,</A><br>
+<A NAME=1.7.87>As we shall make our griefs and clamour roar</A><br>
+<A NAME=1.7.88>Upon his death?</A><br>
+</blockquote>
+
+<A NAME=speech13><b>MACBETH</b></a>
+<blockquote>
+<A NAME=1.7.89> I am settled, and bend up</A><br>
+<A NAME=1.7.90>Each corporal agent to this terrible feat.</A><br>
+<A NAME=1.7.91>Away, and mock the time with fairest show:</A><br>
+<A NAME=1.7.92>False face must hide what the false heart doth know.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote><p>
+<H3>ACT II</h3>
+<h3>SCENE I. Court of Macbeth's castle.</h3>
+<p><blockquote>
+<i>Enter BANQUO, and FLEANCE bearing a torch before him</i>
+</blockquote>
+
+<A NAME=speech1><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.1.1>How goes the night, boy?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>FLEANCE</b></a>
+<blockquote>
+<A NAME=2.1.2>The moon is down; I have not heard the clock.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.1.3>And she goes down at twelve.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>FLEANCE</b></a>
+<blockquote>
+<A NAME=2.1.4>I take't, 'tis later, sir.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.1.5>Hold, take my sword. There's husbandry in heaven;</A><br>
+<A NAME=2.1.6>Their candles are all out. Take thee that too.</A><br>
+<A NAME=2.1.7>A heavy summons lies like lead upon me,</A><br>
+<A NAME=2.1.8>And yet I would not sleep: merciful powers,</A><br>
+<A NAME=2.1.9>Restrain in me the cursed thoughts that nature</A><br>
+<A NAME=2.1.10>Gives way to in repose!</A><br>
+<p><i>Enter MACBETH, and a Servant with a torch</i></p>
+<A NAME=2.1.11>Give me my sword.</A><br>
+<A NAME=2.1.12>Who's there?</A><br>
+</blockquote>
+
+<A NAME=speech6><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.1.13>A friend.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.1.14>What, sir, not yet at rest? The king's a-bed:</A><br>
+<A NAME=2.1.15>He hath been in unusual pleasure, and</A><br>
+<A NAME=2.1.16>Sent forth great largess to your offices.</A><br>
+<A NAME=2.1.17>This diamond he greets your wife withal,</A><br>
+<A NAME=2.1.18>By the name of most kind hostess; and shut up</A><br>
+<A NAME=2.1.19>In measureless content.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.1.20>Being unprepared,</A><br>
+<A NAME=2.1.21>Our will became the servant to defect;</A><br>
+<A NAME=2.1.22>Which else should free have wrought.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.1.23>All's well.</A><br>
+<A NAME=2.1.24>I dreamt last night of the three weird sisters:</A><br>
+<A NAME=2.1.25>To you they have show'd some truth.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.1.26>I think not of them:</A><br>
+<A NAME=2.1.27>Yet, when we can entreat an hour to serve,</A><br>
+<A NAME=2.1.28>We would spend it in some words upon that business,</A><br>
+<A NAME=2.1.29>If you would grant the time.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.1.30>At your kind'st leisure.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.1.31>If you shall cleave to my consent, when 'tis,</A><br>
+<A NAME=2.1.32>It shall make honour for you.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.1.33>So I lose none</A><br>
+<A NAME=2.1.34>In seeking to augment it, but still keep</A><br>
+<A NAME=2.1.35>My bosom franchised and allegiance clear,</A><br>
+<A NAME=2.1.36>I shall be counsell'd.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.1.37>Good repose the while!</A><br>
+</blockquote>
+
+<A NAME=speech15><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.1.38>Thanks, sir: the like to you!</A><br>
+<p><i>Exeunt BANQUO and FLEANCE</i></p>
+</blockquote>
+
+<A NAME=speech16><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.1.39>Go bid thy mistress, when my drink is ready,</A><br>
+<A NAME=2.1.40>She strike upon the bell. Get thee to bed.</A><br>
+<p><i>Exit Servant</i></p>
+<A NAME=2.1.41>Is this a dagger which I see before me,</A><br>
+<A NAME=2.1.42>The handle toward my hand? Come, let me clutch thee.</A><br>
+<A NAME=2.1.43>I have thee not, and yet I see thee still.</A><br>
+<A NAME=2.1.44>Art thou not, fatal vision, sensible</A><br>
+<A NAME=2.1.45>To feeling as to sight? or art thou but</A><br>
+<A NAME=2.1.46>A dagger of the mind, a false creation,</A><br>
+<A NAME=2.1.47>Proceeding from the heat-oppressed brain?</A><br>
+<A NAME=2.1.48>I see thee yet, in form as palpable</A><br>
+<A NAME=2.1.49>As this which now I draw.</A><br>
+<A NAME=2.1.50>Thou marshall'st me the way that I was going;</A><br>
+<A NAME=2.1.51>And such an instrument I was to use.</A><br>
+<A NAME=2.1.52>Mine eyes are made the fools o' the other senses,</A><br>
+<A NAME=2.1.53>Or else worth all the rest; I see thee still,</A><br>
+<A NAME=2.1.54>And on thy blade and dudgeon gouts of blood,</A><br>
+<A NAME=2.1.55>Which was not so before. There's no such thing:</A><br>
+<A NAME=2.1.56>It is the bloody business which informs</A><br>
+<A NAME=2.1.57>Thus to mine eyes. Now o'er the one halfworld</A><br>
+<A NAME=2.1.58>Nature seems dead, and wicked dreams abuse</A><br>
+<A NAME=2.1.59>The curtain'd sleep; witchcraft celebrates</A><br>
+<A NAME=2.1.60>Pale Hecate's offerings, and wither'd murder,</A><br>
+<A NAME=2.1.61>Alarum'd by his sentinel, the wolf,</A><br>
+<A NAME=2.1.62>Whose howl's his watch, thus with his stealthy pace.</A><br>
+<A NAME=2.1.63>With Tarquin's ravishing strides, towards his design</A><br>
+<A NAME=2.1.64>Moves like a ghost. Thou sure and firm-set earth,</A><br>
+<A NAME=2.1.65>Hear not my steps, which way they walk, for fear</A><br>
+<A NAME=2.1.66>Thy very stones prate of my whereabout,</A><br>
+<A NAME=2.1.67>And take the present horror from the time,</A><br>
+<A NAME=2.1.68>Which now suits with it. Whiles I threat, he lives:</A><br>
+<A NAME=2.1.69>Words to the heat of deeds too cold breath gives.</A><br>
+<p><i>A bell rings</i></p>
+<A NAME=2.1.70>I go, and it is done; the bell invites me.</A><br>
+<A NAME=2.1.71>Hear it not, Duncan; for it is a knell</A><br>
+<A NAME=2.1.72>That summons thee to heaven or to hell.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+<h3>SCENE II. The same.</h3>
+<p><blockquote>
+<i>Enter LADY MACBETH</i>
+</blockquote>
+
+<A NAME=speech1><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.1>That which hath made them drunk hath made me bold;</A><br>
+<A NAME=2.2.2>What hath quench'd them hath given me fire.</A><br>
+<A NAME=2.2.3>Hark! Peace!</A><br>
+<A NAME=2.2.4>It was the owl that shriek'd, the fatal bellman,</A><br>
+<A NAME=2.2.5>Which gives the stern'st good-night. He is about it:</A><br>
+<A NAME=2.2.6>The doors are open; and the surfeited grooms</A><br>
+<A NAME=2.2.7>Do mock their charge with snores: I have drugg'd</A><br>
+<A NAME=2.2.8>their possets,</A><br>
+<A NAME=2.2.9>That death and nature do contend about them,</A><br>
+<A NAME=2.2.10>Whether they live or die.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.11>[Within] Who's there? what, ho!</A><br>
+</blockquote>
+
+<A NAME=speech3><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.12>Alack, I am afraid they have awaked,</A><br>
+<A NAME=2.2.13>And 'tis not done. The attempt and not the deed</A><br>
+<A NAME=2.2.14>Confounds us. Hark! I laid their daggers ready;</A><br>
+<A NAME=2.2.15>He could not miss 'em. Had he not resembled</A><br>
+<A NAME=2.2.16>My father as he slept, I had done't.</A><br>
+<p><i>Enter MACBETH</i></p>
+<A NAME=2.2.17>My husband!</A><br>
+</blockquote>
+
+<A NAME=speech4><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.18>I have done the deed. Didst thou not hear a noise?</A><br>
+</blockquote>
+
+<A NAME=speech5><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.19>I heard the owl scream and the crickets cry.</A><br>
+<A NAME=2.2.20>Did not you speak?</A><br>
+</blockquote>
+
+<A NAME=speech6><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.21> When?</A><br>
+</blockquote>
+
+<A NAME=speech7><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.22>Now.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.23>As I descended?</A><br>
+</blockquote>
+
+<A NAME=speech9><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.24>Ay.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.25>Hark!</A><br>
+<A NAME=2.2.26>Who lies i' the second chamber?</A><br>
+</blockquote>
+
+<A NAME=speech11><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.27>Donalbain.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.28>This is a sorry sight.</A><br>
+<p><i>Looking on his hands</i></p>
+</blockquote>
+
+<A NAME=speech13><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.29>A foolish thought, to say a sorry sight.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.30>There's one did laugh in's sleep, and one cried</A><br>
+<A NAME=2.2.31>'Murder!'</A><br>
+<A NAME=2.2.32>That they did wake each other: I stood and heard them:</A><br>
+<A NAME=2.2.33>But they did say their prayers, and address'd them</A><br>
+<A NAME=2.2.34>Again to sleep.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.35> There are two lodged together.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.36>One cried 'God bless us!' and 'Amen' the other;</A><br>
+<A NAME=2.2.37>As they had seen me with these hangman's hands.</A><br>
+<A NAME=2.2.38>Listening their fear, I could not say 'Amen,'</A><br>
+<A NAME=2.2.39>When they did say 'God bless us!'</A><br>
+</blockquote>
+
+<A NAME=speech17><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.40>Consider it not so deeply.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.41>But wherefore could not I pronounce 'Amen'?</A><br>
+<A NAME=2.2.42>I had most need of blessing, and 'Amen'</A><br>
+<A NAME=2.2.43>Stuck in my throat.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.44>These deeds must not be thought</A><br>
+<A NAME=2.2.45>After these ways; so, it will make us mad.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.46>Methought I heard a voice cry 'Sleep no more!</A><br>
+<A NAME=2.2.47>Macbeth does murder sleep', the innocent sleep,</A><br>
+<A NAME=2.2.48>Sleep that knits up the ravell'd sleeve of care,</A><br>
+<A NAME=2.2.49>The death of each day's life, sore labour's bath,</A><br>
+<A NAME=2.2.50>Balm of hurt minds, great nature's second course,</A><br>
+<A NAME=2.2.51>Chief nourisher in life's feast,--</A><br>
+</blockquote>
+
+<A NAME=speech21><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.52>What do you mean?</A><br>
+</blockquote>
+
+<A NAME=speech22><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.53>Still it cried 'Sleep no more!' to all the house:</A><br>
+<A NAME=2.2.54>'Glamis hath murder'd sleep, and therefore Cawdor</A><br>
+<A NAME=2.2.55>Shall sleep no more; Macbeth shall sleep no more.'</A><br>
+</blockquote>
+
+<A NAME=speech23><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.56>Who was it that thus cried? Why, worthy thane,</A><br>
+<A NAME=2.2.57>You do unbend your noble strength, to think</A><br>
+<A NAME=2.2.58>So brainsickly of things. Go get some water,</A><br>
+<A NAME=2.2.59>And wash this filthy witness from your hand.</A><br>
+<A NAME=2.2.60>Why did you bring these daggers from the place?</A><br>
+<A NAME=2.2.61>They must lie there: go carry them; and smear</A><br>
+<A NAME=2.2.62>The sleepy grooms with blood.</A><br>
+</blockquote>
+
+<A NAME=speech24><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.63>I'll go no more:</A><br>
+<A NAME=2.2.64>I am afraid to think what I have done;</A><br>
+<A NAME=2.2.65>Look on't again I dare not.</A><br>
+</blockquote>
+
+<A NAME=speech25><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.66>Infirm of purpose!</A><br>
+<A NAME=2.2.67>Give me the daggers: the sleeping and the dead</A><br>
+<A NAME=2.2.68>Are but as pictures: 'tis the eye of childhood</A><br>
+<A NAME=2.2.69>That fears a painted devil. If he do bleed,</A><br>
+<A NAME=2.2.70>I'll gild the faces of the grooms withal;</A><br>
+<A NAME=2.2.71>For it must seem their guilt.</A><br>
+<p><i>Exit. Knocking within</i></p>
+</blockquote>
+
+<A NAME=speech26><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.72>Whence is that knocking?</A><br>
+<A NAME=2.2.73>How is't with me, when every noise appals me?</A><br>
+<A NAME=2.2.74>What hands are here? ha! they pluck out mine eyes.</A><br>
+<A NAME=2.2.75>Will all great Neptune's ocean wash this blood</A><br>
+<A NAME=2.2.76>Clean from my hand? No, this my hand will rather</A><br>
+<A NAME=2.2.77>The multitudinous seas in incarnadine,</A><br>
+<A NAME=2.2.78>Making the green one red.</A><br>
+<p><i>Re-enter LADY MACBETH</i></p>
+</blockquote>
+
+<A NAME=speech27><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.79>My hands are of your colour; but I shame</A><br>
+<A NAME=2.2.80>To wear a heart so white.</A><br>
+<p><i>Knocking within</i></p>
+<A NAME=2.2.81>I hear a knocking</A><br>
+<A NAME=2.2.82>At the south entry: retire we to our chamber;</A><br>
+<A NAME=2.2.83>A little water clears us of this deed:</A><br>
+<A NAME=2.2.84>How easy is it, then! Your constancy</A><br>
+<A NAME=2.2.85>Hath left you unattended.</A><br>
+<p><i>Knocking within</i></p>
+<A NAME=2.2.86>Hark! more knocking.</A><br>
+<A NAME=2.2.87>Get on your nightgown, lest occasion call us,</A><br>
+<A NAME=2.2.88>And show us to be watchers. Be not lost</A><br>
+<A NAME=2.2.89>So poorly in your thoughts.</A><br>
+</blockquote>
+
+<A NAME=speech28><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.2.90>To know my deed, 'twere best not know myself.</A><br>
+<p><i>Knocking within</i></p>
+<A NAME=2.2.91>Wake Duncan with thy knocking! I would thou couldst!</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE III. The same.</h3>
+<p><blockquote>
+<i>Knocking within. Enter a Porter</i>
+</blockquote>
+
+<A NAME=speech1><b>Porter</b></a>
+<blockquote>
+<A NAME=2.3.1>Here's a knocking indeed! If a</A><br>
+<A NAME=2.3.2>man were porter of hell-gate, he should have</A><br>
+<A NAME=2.3.3>old turning the key.</A><br>
+<p><i>Knocking within</i></p>
+<A NAME=2.3.4>Knock,</A><br>
+<A NAME=2.3.5>knock, knock! Who's there, i' the name of</A><br>
+<A NAME=2.3.6>Beelzebub? Here's a farmer, that hanged</A><br>
+<A NAME=2.3.7>himself on the expectation of plenty: come in</A><br>
+<A NAME=2.3.8>time; have napkins enow about you; here</A><br>
+<A NAME=2.3.9>you'll sweat for't.</A><br>
+<p><i>Knocking within</i></p>
+<A NAME=2.3.10>Knock,</A><br>
+<A NAME=2.3.11>knock! Who's there, in the other devil's</A><br>
+<A NAME=2.3.12>name? Faith, here's an equivocator, that could</A><br>
+<A NAME=2.3.13>swear in both the scales against either scale;</A><br>
+<A NAME=2.3.14>who committed treason enough for God's sake,</A><br>
+<A NAME=2.3.15>yet could not equivocate to heaven: O, come</A><br>
+<A NAME=2.3.16>in, equivocator.</A><br>
+<p><i>Knocking within</i></p>
+<A NAME=2.3.17>Knock,</A><br>
+<A NAME=2.3.18>knock, knock! Who's there? Faith, here's an</A><br>
+<A NAME=2.3.19>English tailor come hither, for stealing out of</A><br>
+<A NAME=2.3.20>a French hose: come in, tailor; here you may</A><br>
+<A NAME=2.3.21>roast your goose.</A><br>
+<p><i>Knocking within</i></p>
+<A NAME=2.3.22>Knock,</A><br>
+<A NAME=2.3.23>knock; never at quiet! What are you? But</A><br>
+<A NAME=2.3.24>this place is too cold for hell. I'll devil-porter</A><br>
+<A NAME=2.3.25>it no further: I had thought to have let in</A><br>
+<A NAME=2.3.26>some of all professions that go the primrose</A><br>
+<A NAME=2.3.27>way to the everlasting bonfire.</A><br>
+<p><i>Knocking within</i></p>
+<A NAME=2.3.28>Anon, anon! I pray you, remember the porter.</A><br>
+<p><i>Opens the gate</i></p>
+<p><i>Enter MACDUFF and LENNOX</i></p>
+</blockquote>
+
+<A NAME=speech2><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.29>Was it so late, friend, ere you went to bed,</A><br>
+<A NAME=2.3.30>That you do lie so late?</A><br>
+</blockquote>
+
+<A NAME=speech3><b>Porter</b></a>
+<blockquote>
+<A NAME=2.3.31>'Faith sir, we were carousing till the</A><br>
+<A NAME=2.3.32>second cock: and drink, sir, is a great</A><br>
+<A NAME=2.3.33>provoker of three things.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.34>What three things does drink especially provoke?</A><br>
+</blockquote>
+
+<A NAME=speech5><b>Porter</b></a>
+<blockquote>
+<A NAME=2.3.35>Marry, sir, nose-painting, sleep, and</A><br>
+<A NAME=2.3.36>urine. Lechery, sir, it provokes, and unprovokes;</A><br>
+<A NAME=2.3.37>it provokes the desire, but it takes</A><br>
+<A NAME=2.3.38>away the performance: therefore, much drink</A><br>
+<A NAME=2.3.39>may be said to be an equivocator with lechery:</A><br>
+<A NAME=2.3.40>it makes him, and it mars him; it sets</A><br>
+<A NAME=2.3.41>him on, and it takes him off; it persuades him,</A><br>
+<A NAME=2.3.42>and disheartens him; makes him stand to, and</A><br>
+<A NAME=2.3.43>not stand to; in conclusion, equivocates him</A><br>
+<A NAME=2.3.44>in a sleep, and, giving him the lie, leaves him.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.45>I believe drink gave thee the lie last night.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>Porter</b></a>
+<blockquote>
+<A NAME=2.3.46>That it did, sir, i' the very throat on</A><br>
+<A NAME=2.3.47>me: but I requited him for his lie; and, I</A><br>
+<A NAME=2.3.48>think, being too strong for him, though he took</A><br>
+<A NAME=2.3.49>up my legs sometime, yet I made a shift to cast</A><br>
+<A NAME=2.3.50>him.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.51>Is thy master stirring?</A><br>
+<p><i>Enter MACBETH</i></p>
+<A NAME=2.3.52>Our knocking has awaked him; here he comes.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>LENNOX</b></a>
+<blockquote>
+<A NAME=2.3.53>Good morrow, noble sir.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.54>Good morrow, both.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.55>Is the king stirring, worthy thane?</A><br>
+</blockquote>
+
+<A NAME=speech12><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.56>Not yet.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.57>He did command me to call timely on him:</A><br>
+<A NAME=2.3.58>I have almost slipp'd the hour.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.59>I'll bring you to him.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.60>I know this is a joyful trouble to you;</A><br>
+<A NAME=2.3.61>But yet 'tis one.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.62>The labour we delight in physics pain.</A><br>
+<A NAME=2.3.63>This is the door.</A><br>
+</blockquote>
+
+<A NAME=speech17><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.64> I'll make so bold to call,</A><br>
+<A NAME=2.3.65>For 'tis my limited service.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+
+<A NAME=speech18><b>LENNOX</b></a>
+<blockquote>
+<A NAME=2.3.66>Goes the king hence to-day?</A><br>
+</blockquote>
+
+<A NAME=speech19><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.67>He does: he did appoint so.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>LENNOX</b></a>
+<blockquote>
+<A NAME=2.3.68>The night has been unruly: where we lay,</A><br>
+<A NAME=2.3.69>Our chimneys were blown down; and, as they say,</A><br>
+<A NAME=2.3.70>Lamentings heard i' the air; strange screams of death,</A><br>
+<A NAME=2.3.71>And prophesying with accents terrible</A><br>
+<A NAME=2.3.72>Of dire combustion and confused events</A><br>
+<A NAME=2.3.73>New hatch'd to the woeful time: the obscure bird</A><br>
+<A NAME=2.3.74>Clamour'd the livelong night: some say, the earth</A><br>
+<A NAME=2.3.75>Was feverous and did shake.</A><br>
+</blockquote>
+
+<A NAME=speech21><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.76>'Twas a rough night.</A><br>
+</blockquote>
+
+<A NAME=speech22><b>LENNOX</b></a>
+<blockquote>
+<A NAME=2.3.77>My young remembrance cannot parallel</A><br>
+<A NAME=2.3.78>A fellow to it.</A><br>
+<p><i>Re-enter MACDUFF</i></p>
+</blockquote>
+
+<A NAME=speech23><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.79>O horror, horror, horror! Tongue nor heart</A><br>
+<A NAME=2.3.80>Cannot conceive nor name thee!</A><br>
+</blockquote>
+
+<A NAME=speech24><b>MACBETH</b></a>
+
+<A NAME=speech25><b>LENNOX</b></a>
+<blockquote>
+<A NAME=2.3.81>What's the matter.</A><br>
+</blockquote>
+
+<A NAME=speech26><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.82>Confusion now hath made his masterpiece!</A><br>
+<A NAME=2.3.83>Most sacrilegious murder hath broke ope</A><br>
+<A NAME=2.3.84>The Lord's anointed temple, and stole thence</A><br>
+<A NAME=2.3.85>The life o' the building!</A><br>
+</blockquote>
+
+<A NAME=speech27><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.86>What is 't you say? the life?</A><br>
+</blockquote>
+
+<A NAME=speech28><b>LENNOX</b></a>
+<blockquote>
+<A NAME=2.3.87>Mean you his majesty?</A><br>
+</blockquote>
+
+<A NAME=speech29><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.88>Approach the chamber, and destroy your sight</A><br>
+<A NAME=2.3.89>With a new Gorgon: do not bid me speak;</A><br>
+<A NAME=2.3.90>See, and then speak yourselves.</A><br>
+<p><i>Exeunt MACBETH and LENNOX</i></p>
+<A NAME=2.3.91>Awake, awake!</A><br>
+<A NAME=2.3.92>Ring the alarum-bell. Murder and treason!</A><br>
+<A NAME=2.3.93>Banquo and Donalbain! Malcolm! awake!</A><br>
+<A NAME=2.3.94>Shake off this downy sleep, death's counterfeit,</A><br>
+<A NAME=2.3.95>And look on death itself! up, up, and see</A><br>
+<A NAME=2.3.96>The great doom's image! Malcolm! Banquo!</A><br>
+<A NAME=2.3.97>As from your graves rise up, and walk like sprites,</A><br>
+<A NAME=2.3.98>To countenance this horror! Ring the bell.</A><br>
+<p><i>Bell rings</i></p>
+<p><i>Enter LADY MACBETH</i></p>
+</blockquote>
+
+<A NAME=speech30><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.99>What's the business,</A><br>
+<A NAME=2.3.100>That such a hideous trumpet calls to parley</A><br>
+<A NAME=2.3.101>The sleepers of the house? speak, speak!</A><br>
+</blockquote>
+
+<A NAME=speech31><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.102>O gentle lady,</A><br>
+<A NAME=2.3.103>'Tis not for you to hear what I can speak:</A><br>
+<A NAME=2.3.104>The repetition, in a woman's ear,</A><br>
+<A NAME=2.3.105>Would murder as it fell.</A><br>
+<p><i>Enter BANQUO</i></p>
+<A NAME=2.3.106>O Banquo, Banquo,</A><br>
+<A NAME=2.3.107>Our royal master 's murder'd!</A><br>
+</blockquote>
+
+<A NAME=speech32><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.108>Woe, alas!</A><br>
+<A NAME=2.3.109>What, in our house?</A><br>
+</blockquote>
+
+<A NAME=speech33><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.3.110>Too cruel any where.</A><br>
+<A NAME=2.3.111>Dear Duff, I prithee, contradict thyself,</A><br>
+<A NAME=2.3.112>And say it is not so.</A><br>
+<p><i>Re-enter MACBETH and LENNOX, with ROSS</i></p>
+</blockquote>
+
+<A NAME=speech34><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.113>Had I but died an hour before this chance,</A><br>
+<A NAME=2.3.114>I had lived a blessed time; for, from this instant,</A><br>
+<A NAME=2.3.115>There 's nothing serious in mortality:</A><br>
+<A NAME=2.3.116>All is but toys: renown and grace is dead;</A><br>
+<A NAME=2.3.117>The wine of life is drawn, and the mere lees</A><br>
+<A NAME=2.3.118>Is left this vault to brag of.</A><br>
+<p><i>Enter MALCOLM and DONALBAIN</i></p>
+</blockquote>
+
+<A NAME=speech35><b>DONALBAIN</b></a>
+<blockquote>
+<A NAME=2.3.119>What is amiss?</A><br>
+</blockquote>
+
+<A NAME=speech36><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.120> You are, and do not know't:</A><br>
+<A NAME=2.3.121>The spring, the head, the fountain of your blood</A><br>
+<A NAME=2.3.122>Is stopp'd; the very source of it is stopp'd.</A><br>
+</blockquote>
+
+<A NAME=speech37><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.123>Your royal father 's murder'd.</A><br>
+</blockquote>
+
+<A NAME=speech38><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=2.3.124>O, by whom?</A><br>
+</blockquote>
+
+<A NAME=speech39><b>LENNOX</b></a>
+<blockquote>
+<A NAME=2.3.125>Those of his chamber, as it seem'd, had done 't:</A><br>
+<A NAME=2.3.126>Their hands and faces were an badged with blood;</A><br>
+<A NAME=2.3.127>So were their daggers, which unwiped we found</A><br>
+<A NAME=2.3.128>Upon their pillows:</A><br>
+<A NAME=2.3.129>They stared, and were distracted; no man's life</A><br>
+<A NAME=2.3.130>Was to be trusted with them.</A><br>
+</blockquote>
+
+<A NAME=speech40><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.131>O, yet I do repent me of my fury,</A><br>
+<A NAME=2.3.132>That I did kill them.</A><br>
+</blockquote>
+
+<A NAME=speech41><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.133>Wherefore did you so?</A><br>
+</blockquote>
+
+<A NAME=speech42><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.134>Who can be wise, amazed, temperate and furious,</A><br>
+<A NAME=2.3.135>Loyal and neutral, in a moment? No man:</A><br>
+<A NAME=2.3.136>The expedition my violent love</A><br>
+<A NAME=2.3.137>Outrun the pauser, reason. Here lay Duncan,</A><br>
+<A NAME=2.3.138>His silver skin laced with his golden blood;</A><br>
+<A NAME=2.3.139>And his gash'd stabs look'd like a breach in nature</A><br>
+<A NAME=2.3.140>For ruin's wasteful entrance: there, the murderers,</A><br>
+<A NAME=2.3.141>Steep'd in the colours of their trade, their daggers</A><br>
+<A NAME=2.3.142>Unmannerly breech'd with gore: who could refrain,</A><br>
+<A NAME=2.3.143>That had a heart to love, and in that heart</A><br>
+<A NAME=2.3.144>Courage to make 's love kno wn?</A><br>
+</blockquote>
+
+<A NAME=speech43><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.145>Help me hence, ho!</A><br>
+</blockquote>
+
+<A NAME=speech44><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.146>Look to the lady.</A><br>
+</blockquote>
+
+<A NAME=speech45><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=2.3.147>[Aside to DONALBAIN] Why do we hold our tongues,</A><br>
+<A NAME=2.3.148>That most may claim this argument for ours?</A><br>
+</blockquote>
+
+<A NAME=speech46><b>DONALBAIN</b></a>
+<blockquote>
+<A NAME=2.3.149>[Aside to MALCOLM] What should be spoken here,</A><br>
+<A NAME=2.3.150>where our fate,</A><br>
+<A NAME=2.3.151>Hid in an auger-hole, may rush, and seize us?</A><br>
+<A NAME=2.3.152>Let 's away;</A><br>
+<A NAME=2.3.153>Our tears are not yet brew'd.</A><br>
+</blockquote>
+
+<A NAME=speech47><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=2.3.154>[Aside to DONALBAIN] Nor our strong sorrow</A><br>
+<A NAME=2.3.155>Upon the foot of motion.</A><br>
+</blockquote>
+
+<A NAME=speech48><b>BANQUO</b></a>
+<blockquote>
+<A NAME=2.3.156>Look to the lady:</A><br>
+<p><i>LADY MACBETH is carried out</i></p>
+<A NAME=2.3.157>And when we have our naked frailties hid,</A><br>
+<A NAME=2.3.158>That suffer in exposure, let us meet,</A><br>
+<A NAME=2.3.159>And question this most bloody piece of work,</A><br>
+<A NAME=2.3.160>To know it further. Fears and scruples shake us:</A><br>
+<A NAME=2.3.161>In the great hand of God I stand; and thence</A><br>
+<A NAME=2.3.162>Against the undivulged pretence I fight</A><br>
+<A NAME=2.3.163>Of treasonous malice.</A><br>
+</blockquote>
+
+<A NAME=speech49><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.3.164>And so do I.</A><br>
+</blockquote>
+
+<A NAME=speech50><b>ALL</b></a>
+<blockquote>
+<A NAME=2.3.165>So all.</A><br>
+</blockquote>
+
+<A NAME=speech51><b>MACBETH</b></a>
+<blockquote>
+<A NAME=2.3.166>Let's briefly put on manly readiness,</A><br>
+<A NAME=2.3.167>And meet i' the hall together.</A><br>
+</blockquote>
+
+<A NAME=speech52><b>ALL</b></a>
+<blockquote>
+<A NAME=2.3.168>Well contented.</A><br>
+<p><i>Exeunt all but Malcolm and Donalbain.</i></p>
+</blockquote>
+
+<A NAME=speech53><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=2.3.169>What will you do? Let's not consort with them:</A><br>
+<A NAME=2.3.170>To show an unfelt sorrow is an office</A><br>
+<A NAME=2.3.171>Which the false man does easy. I'll to England.</A><br>
+</blockquote>
+
+<A NAME=speech54><b>DONALBAIN</b></a>
+<blockquote>
+<A NAME=2.3.172>To Ireland, I; our separated fortune</A><br>
+<A NAME=2.3.173>Shall keep us both the safer: where we are,</A><br>
+<A NAME=2.3.174>There's daggers in men's smiles: the near in blood,</A><br>
+<A NAME=2.3.175>The nearer bloody.</A><br>
+</blockquote>
+
+<A NAME=speech55><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=2.3.176> This murderous shaft that's shot</A><br>
+<A NAME=2.3.177>Hath not yet lighted, and our safest way</A><br>
+<A NAME=2.3.178>Is to avoid the aim. Therefore, to horse;</A><br>
+<A NAME=2.3.179>And let us not be dainty of leave-taking,</A><br>
+<A NAME=2.3.180>But shift away: there's warrant in that theft</A><br>
+<A NAME=2.3.181>Which steals itself, when there's no mercy left.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE IV. Outside Macbeth's castle.</h3>
+<p><blockquote>
+<i>Enter ROSS and an old Man</i>
+</blockquote>
+
+<A NAME=speech1><b>Old Man</b></a>
+<blockquote>
+<A NAME=2.4.1>Threescore and ten I can remember well:</A><br>
+<A NAME=2.4.2>Within the volume of which time I have seen</A><br>
+<A NAME=2.4.3>Hours dreadful and things strange; but this sore night</A><br>
+<A NAME=2.4.4>Hath trifled former knowings.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.5>Ah, good father,</A><br>
+<A NAME=2.4.6>Thou seest, the heavens, as troubled with man's act,</A><br>
+<A NAME=2.4.7>Threaten his bloody stage: by the clock, 'tis day,</A><br>
+<A NAME=2.4.8>And yet dark night strangles the travelling lamp:</A><br>
+<A NAME=2.4.9>Is't night's predominance, or the day's shame,</A><br>
+<A NAME=2.4.10>That darkness does the face of earth entomb,</A><br>
+<A NAME=2.4.11>When living light should kiss it?</A><br>
+</blockquote>
+
+<A NAME=speech3><b>Old Man</b></a>
+<blockquote>
+<A NAME=2.4.12>'Tis unnatural,</A><br>
+<A NAME=2.4.13>Even like the deed that's done. On Tuesday last,</A><br>
+<A NAME=2.4.14>A falcon, towering in her pride of place,</A><br>
+<A NAME=2.4.15>Was by a mousing owl hawk'd at and kill'd.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.16>And Duncan's horses--a thing most strange and certain--</A><br>
+<A NAME=2.4.17>Beauteous and swift, the minions of their race,</A><br>
+<A NAME=2.4.18>Turn'd wild in nature, broke their stalls, flung out,</A><br>
+<A NAME=2.4.19>Contending 'gainst obedience, as they would make</A><br>
+<A NAME=2.4.20>War with mankind.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>Old Man</b></a>
+<blockquote>
+<A NAME=2.4.21>'Tis said they eat each other.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.22>They did so, to the amazement of mine eyes</A><br>
+<A NAME=2.4.23>That look'd upon't. Here comes the good Macduff.</A><br>
+<p><i>Enter MACDUFF</i></p>
+<A NAME=2.4.24>How goes the world, sir, now?</A><br>
+</blockquote>
+
+<A NAME=speech7><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.4.25>Why, see you not?</A><br>
+</blockquote>
+
+<A NAME=speech8><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.26>Is't known who did this more than bloody deed?</A><br>
+</blockquote>
+
+<A NAME=speech9><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.4.27>Those that Macbeth hath slain.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.28>Alas, the day!</A><br>
+<A NAME=2.4.29>What good could they pretend?</A><br>
+</blockquote>
+
+<A NAME=speech11><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.4.30>They were suborn'd:</A><br>
+<A NAME=2.4.31>Malcolm and Donalbain, the king's two sons,</A><br>
+<A NAME=2.4.32>Are stol'n away and fled; which puts upon them</A><br>
+<A NAME=2.4.33>Suspicion of the deed.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.34>'Gainst nature still!</A><br>
+<A NAME=2.4.35>Thriftless ambition, that wilt ravin up</A><br>
+<A NAME=2.4.36>Thine own life's means! Then 'tis most like</A><br>
+<A NAME=2.4.37>The sovereignty will fall upon Macbeth.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.4.38>He is already named, and gone to Scone</A><br>
+<A NAME=2.4.39>To be invested.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.40> Where is Duncan's body?</A><br>
+</blockquote>
+
+<A NAME=speech15><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.4.41>Carried to Colmekill,</A><br>
+<A NAME=2.4.42>The sacred storehouse of his predecessors,</A><br>
+<A NAME=2.4.43>And guardian of their bones.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.44>Will you to Scone?</A><br>
+</blockquote>
+
+<A NAME=speech17><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.4.45>No, cousin, I'll to Fife.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.46>Well, I will thither.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=2.4.47>Well, may you see things well done there: adieu!</A><br>
+<A NAME=2.4.48>Lest our old robes sit easier than our new!</A><br>
+</blockquote>
+
+<A NAME=speech20><b>ROSS</b></a>
+<blockquote>
+<A NAME=2.4.49>Farewell, father.</A><br>
+</blockquote>
+
+<A NAME=speech21><b>Old Man</b></a>
+<blockquote>
+<A NAME=2.4.50>God's benison go with you; and with those</A><br>
+<A NAME=2.4.51>That would make good of bad, and friends of foes!</A><br>
+<p><i>Exeunt</i></p>
+</blockquote><p>
+<H3>ACT III</h3>
+<h3>SCENE I. Forres. The palace.</h3>
+<p><blockquote>
+<i>Enter BANQUO</i>
+</blockquote>
+
+<A NAME=speech1><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.1.1>Thou hast it now: king, Cawdor, Glamis, all,</A><br>
+<A NAME=3.1.2>As the weird women promised, and, I fear,</A><br>
+<A NAME=3.1.3>Thou play'dst most foully for't: yet it was said</A><br>
+<A NAME=3.1.4>It should not stand in thy posterity,</A><br>
+<A NAME=3.1.5>But that myself should be the root and father</A><br>
+<A NAME=3.1.6>Of many kings. If there come truth from them--</A><br>
+<A NAME=3.1.7>As upon thee, Macbeth, their speeches shine--</A><br>
+<A NAME=3.1.8>Why, by the verities on thee made good,</A><br>
+<A NAME=3.1.9>May they not be my oracles as well,</A><br>
+<A NAME=3.1.10>And set me up in hope? But hush! no more.</A><br>
+<p><i>Sennet sounded. Enter MACBETH, as king, LADY MACBETH, as queen, LENNOX, ROSS, Lords, Ladies, and Attendants</i></p>
+</blockquote>
+
+<A NAME=speech2><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.11>Here's our chief guest.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.12>If he had been forgotten,</A><br>
+<A NAME=3.1.13>It had been as a gap in our great feast,</A><br>
+<A NAME=3.1.14>And all-thing unbecoming.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.15>To-night we hold a solemn supper sir,</A><br>
+<A NAME=3.1.16>And I'll request your presence.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.1.17>Let your highness</A><br>
+<A NAME=3.1.18>Command upon me; to the which my duties</A><br>
+<A NAME=3.1.19>Are with a most indissoluble tie</A><br>
+<A NAME=3.1.20>For ever knit.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.21> Ride you this afternoon?</A><br>
+</blockquote>
+
+<A NAME=speech7><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.1.22>Ay, my good lord.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.23>We should have else desired your good advice,</A><br>
+<A NAME=3.1.24>Which still hath been both grave and prosperous,</A><br>
+<A NAME=3.1.25>In this day's council; but we'll take to-morrow.</A><br>
+<A NAME=3.1.26>Is't far you ride?</A><br>
+</blockquote>
+
+<A NAME=speech9><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.1.27>As far, my lord, as will fill up the time</A><br>
+<A NAME=3.1.28>'Twixt this and supper: go not my horse the better,</A><br>
+<A NAME=3.1.29>I must become a borrower of the night</A><br>
+<A NAME=3.1.30>For a dark hour or twain.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.31>Fail not our feast.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.1.32>My lord, I will not.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.33>We hear, our bloody cousins are bestow'd</A><br>
+<A NAME=3.1.34>In England and in Ireland, not confessing</A><br>
+<A NAME=3.1.35>Their cruel parricide, filling their hearers</A><br>
+<A NAME=3.1.36>With strange invention: but of that to-morrow,</A><br>
+<A NAME=3.1.37>When therewithal we shall have cause of state</A><br>
+<A NAME=3.1.38>Craving us jointly. Hie you to horse: adieu,</A><br>
+<A NAME=3.1.39>Till you return at night. Goes Fleance with you?</A><br>
+</blockquote>
+
+<A NAME=speech13><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.1.40>Ay, my good lord: our time does call upon 's.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.41>I wish your horses swift and sure of foot;</A><br>
+<A NAME=3.1.42>And so I do commend you to their backs. Farewell.</A><br>
+<p><i>Exit BANQUO</i></p>
+<A NAME=3.1.43>Let every man be master of his time</A><br>
+<A NAME=3.1.44>Till seven at night: to make society</A><br>
+<A NAME=3.1.45>The sweeter welcome, we will keep ourself</A><br>
+<A NAME=3.1.46>Till supper-time alone: while then, God be with you!</A><br>
+<p><i>Exeunt all but MACBETH, and an attendant</i></p>
+<A NAME=3.1.47>Sirrah, a word with you: attend those men</A><br>
+<A NAME=3.1.48>Our pleasure?</A><br>
+</blockquote>
+
+<A NAME=speech15><b>ATTENDANT</b></a>
+<blockquote>
+<A NAME=3.1.49>They are, my lord, without the palace gate.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.50>Bring them before us.</A><br>
+<p><i>Exit Attendant</i></p>
+<A NAME=3.1.51>To be thus is nothing;</A><br>
+<A NAME=3.1.52>But to be safely thus.--Our fears in Banquo</A><br>
+<A NAME=3.1.53>Stick deep; and in his royalty of nature</A><br>
+<A NAME=3.1.54>Reigns that which would be fear'd: 'tis much he dares;</A><br>
+<A NAME=3.1.55>And, to that dauntless temper of his mind,</A><br>
+<A NAME=3.1.56>He hath a wisdom that doth guide his valour</A><br>
+<A NAME=3.1.57>To act in safety. There is none but he</A><br>
+<A NAME=3.1.58>Whose being I do fear: and, under him,</A><br>
+<A NAME=3.1.59>My Genius is rebuked; as, it is said,</A><br>
+<A NAME=3.1.60>Mark Antony's was by Caesar. He chid the sisters</A><br>
+<A NAME=3.1.61>When first they put the name of king upon me,</A><br>
+<A NAME=3.1.62>And bade them speak to him: then prophet-like</A><br>
+<A NAME=3.1.63>They hail'd him father to a line of kings:</A><br>
+<A NAME=3.1.64>Upon my head they placed a fruitless crown,</A><br>
+<A NAME=3.1.65>And put a barren sceptre in my gripe,</A><br>
+<A NAME=3.1.66>Thence to be wrench'd with an unlineal hand,</A><br>
+<A NAME=3.1.67>No son of mine succeeding. If 't be so,</A><br>
+<A NAME=3.1.68>For Banquo's issue have I filed my mind;</A><br>
+<A NAME=3.1.69>For them the gracious Duncan have I murder'd;</A><br>
+<A NAME=3.1.70>Put rancours in the vessel of my peace</A><br>
+<A NAME=3.1.71>Only for them; and mine eternal jewel</A><br>
+<A NAME=3.1.72>Given to the common enemy of man,</A><br>
+<A NAME=3.1.73>To make them kings, the seed of Banquo kings!</A><br>
+<A NAME=3.1.74>Rather than so, come fate into the list.</A><br>
+<A NAME=3.1.75>And champion me to the utterance! Who's there!</A><br>
+<p><i>Re-enter Attendant, with two Murderers</i></p>
+<A NAME=3.1.76>Now go to the door, and stay there till we call.</A><br>
+<p><i>Exit Attendant</i></p>
+<A NAME=3.1.77>Was it not yesterday we spoke together?</A><br>
+</blockquote>
+
+<A NAME=speech17><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.1.78>It was, so please your highness.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.79>Well then, now</A><br>
+<A NAME=3.1.80>Have you consider'd of my speeches? Know</A><br>
+<A NAME=3.1.81>That it was he in the times past which held you</A><br>
+<A NAME=3.1.82>So under fortune, which you thought had been</A><br>
+<A NAME=3.1.83>Our innocent self: this I made good to you</A><br>
+<A NAME=3.1.84>In our last conference, pass'd in probation with you,</A><br>
+<A NAME=3.1.85>How you were borne in hand, how cross'd,</A><br>
+<A NAME=3.1.86>the instruments,</A><br>
+<A NAME=3.1.87>Who wrought with them, and all things else that might</A><br>
+<A NAME=3.1.88>To half a soul and to a notion crazed</A><br>
+<A NAME=3.1.89>Say 'Thus did Banquo.'</A><br>
+</blockquote>
+
+<A NAME=speech19><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.1.90>You made it known to us.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.91>I did so, and went further, which is now</A><br>
+<A NAME=3.1.92>Our point of second meeting. Do you find</A><br>
+<A NAME=3.1.93>Your patience so predominant in your nature</A><br>
+<A NAME=3.1.94>That you can let this go? Are you so gospell'd</A><br>
+<A NAME=3.1.95>To pray for this good man and for his issue,</A><br>
+<A NAME=3.1.96>Whose heavy hand hath bow'd you to the grave</A><br>
+<A NAME=3.1.97>And beggar'd yours for ever?</A><br>
+</blockquote>
+
+<A NAME=speech21><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.1.98>We are men, my liege.</A><br>
+</blockquote>
+
+<A NAME=speech22><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.99>Ay, in the catalogue ye go for men;</A><br>
+<A NAME=3.1.100>As hounds and greyhounds, mongrels, spaniels, curs,</A><br>
+<A NAME=3.1.101>Shoughs, water-rugs and demi-wolves, are clept</A><br>
+<A NAME=3.1.102>All by the name of dogs: the valued file</A><br>
+<A NAME=3.1.103>Distinguishes the swift, the slow, the subtle,</A><br>
+<A NAME=3.1.104>The housekeeper, the hunter, every one</A><br>
+<A NAME=3.1.105>According to the gift which bounteous nature</A><br>
+<A NAME=3.1.106>Hath in him closed; whereby he does receive</A><br>
+<A NAME=3.1.107>Particular addition. from the bill</A><br>
+<A NAME=3.1.108>That writes them all alike: and so of men.</A><br>
+<A NAME=3.1.109>Now, if you have a station in the file,</A><br>
+<A NAME=3.1.110>Not i' the worst rank of manhood, say 't;</A><br>
+<A NAME=3.1.111>And I will put that business in your bosoms,</A><br>
+<A NAME=3.1.112>Whose execution takes your enemy off,</A><br>
+<A NAME=3.1.113>Grapples you to the heart and love of us,</A><br>
+<A NAME=3.1.114>Who wear our health but sickly in his life,</A><br>
+<A NAME=3.1.115>Which in his death were perfect.</A><br>
+</blockquote>
+
+<A NAME=speech23><b>Second Murderer</b></a>
+<blockquote>
+<A NAME=3.1.116>I am one, my liege,</A><br>
+<A NAME=3.1.117>Whom the vile blows and buffets of the world</A><br>
+<A NAME=3.1.118>Have so incensed that I am reckless what</A><br>
+<A NAME=3.1.119>I do to spite the world.</A><br>
+</blockquote>
+
+<A NAME=speech24><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.1.120>And I another</A><br>
+<A NAME=3.1.121>So weary with disasters, tugg'd with fortune,</A><br>
+<A NAME=3.1.122>That I would set my lie on any chance,</A><br>
+<A NAME=3.1.123>To mend it, or be rid on't.</A><br>
+</blockquote>
+
+<A NAME=speech25><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.124>Both of you</A><br>
+<A NAME=3.1.125>Know Banquo was your enemy.</A><br>
+</blockquote>
+
+<A NAME=speech26><b>Both Murderers</b></a>
+<blockquote>
+<A NAME=3.1.126>True, my lord.</A><br>
+</blockquote>
+
+<A NAME=speech27><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.127>So is he mine; and in such bloody distance,</A><br>
+<A NAME=3.1.128>That every minute of his being thrusts</A><br>
+<A NAME=3.1.129>Against my near'st of life: and though I could</A><br>
+<A NAME=3.1.130>With barefaced power sweep him from my sight</A><br>
+<A NAME=3.1.131>And bid my will avouch it, yet I must not,</A><br>
+<A NAME=3.1.132>For certain friends that are both his and mine,</A><br>
+<A NAME=3.1.133>Whose loves I may not drop, but wail his fall</A><br>
+<A NAME=3.1.134>Who I myself struck down; and thence it is,</A><br>
+<A NAME=3.1.135>That I to your assistance do make love,</A><br>
+<A NAME=3.1.136>Masking the business from the common eye</A><br>
+<A NAME=3.1.137>For sundry weighty reasons.</A><br>
+</blockquote>
+
+<A NAME=speech28><b>Second Murderer</b></a>
+<blockquote>
+<A NAME=3.1.138>We shall, my lord,</A><br>
+<A NAME=3.1.139>Perform what you command us.</A><br>
+</blockquote>
+
+<A NAME=speech29><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.1.140>Though our lives--</A><br>
+</blockquote>
+
+<A NAME=speech30><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.141>Your spirits shine through you. Within this hour at most</A><br>
+<A NAME=3.1.142>I will advise you where to plant yourselves;</A><br>
+<A NAME=3.1.143>Acquaint you with the perfect spy o' the time,</A><br>
+<A NAME=3.1.144>The moment on't; for't must be done to-night,</A><br>
+<A NAME=3.1.145>And something from the palace; always thought</A><br>
+<A NAME=3.1.146>That I require a clearness: and with him--</A><br>
+<A NAME=3.1.147>To leave no rubs nor botches in the work--</A><br>
+<A NAME=3.1.148>Fleance his son, that keeps him company,</A><br>
+<A NAME=3.1.149>Whose absence is no less material to me</A><br>
+<A NAME=3.1.150>Than is his father's, must embrace the fate</A><br>
+<A NAME=3.1.151>Of that dark hour. Resolve yourselves apart:</A><br>
+<A NAME=3.1.152>I'll come to you anon.</A><br>
+</blockquote>
+
+<A NAME=speech31><b>Both Murderers</b></a>
+<blockquote>
+<A NAME=3.1.153>We are resolved, my lord.</A><br>
+</blockquote>
+
+<A NAME=speech32><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.1.154>I'll call upon you straight: abide within.</A><br>
+<p><i>Exeunt Murderers</i></p>
+<A NAME=3.1.155>It is concluded. Banquo, thy soul's flight,</A><br>
+<A NAME=3.1.156>If it find heaven, must find it out to-night.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+<h3>SCENE II. The palace.</h3>
+<p><blockquote>
+<i>Enter LADY MACBETH and a Servant</i>
+</blockquote>
+
+<A NAME=speech1><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.1>Is Banquo gone from court?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Servant</b></a>
+<blockquote>
+<A NAME=3.2.2>Ay, madam, but returns again to-night.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.3>Say to the king, I would attend his leisure</A><br>
+<A NAME=3.2.4>For a few words.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>Servant</b></a>
+<blockquote>
+<A NAME=3.2.5> Madam, I will.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+
+<A NAME=speech5><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.6>Nought's had, all's spent,</A><br>
+<A NAME=3.2.7>Where our desire is got without content:</A><br>
+<A NAME=3.2.8>'Tis safer to be that which we destroy</A><br>
+<A NAME=3.2.9>Than by destruction dwell in doubtful joy.</A><br>
+<p><i>Enter MACBETH</i></p>
+<A NAME=3.2.10>How now, my lord! why do you keep alone,</A><br>
+<A NAME=3.2.11>Of sorriest fancies your companions making,</A><br>
+<A NAME=3.2.12>Using those thoughts which should indeed have died</A><br>
+<A NAME=3.2.13>With them they think on? Things without all remedy</A><br>
+<A NAME=3.2.14>Should be without regard: what's done is done.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.15>We have scotch'd the snake, not kill'd it:</A><br>
+<A NAME=3.2.16>She'll close and be herself, whilst our poor malice</A><br>
+<A NAME=3.2.17>Remains in danger of her former tooth.</A><br>
+<A NAME=3.2.18>But let the frame of things disjoint, both the</A><br>
+<A NAME=3.2.19>worlds suffer,</A><br>
+<A NAME=3.2.20>Ere we will eat our meal in fear and sleep</A><br>
+<A NAME=3.2.21>In the affliction of these terrible dreams</A><br>
+<A NAME=3.2.22>That shake us nightly: better be with the dead,</A><br>
+<A NAME=3.2.23>Whom we, to gain our peace, have sent to peace,</A><br>
+<A NAME=3.2.24>Than on the torture of the mind to lie</A><br>
+<A NAME=3.2.25>In restless ecstasy. Duncan is in his grave;</A><br>
+<A NAME=3.2.26>After life's fitful fever he sleeps well;</A><br>
+<A NAME=3.2.27>Treason has done his worst: nor steel, nor poison,</A><br>
+<A NAME=3.2.28>Malice domestic, foreign levy, nothing,</A><br>
+<A NAME=3.2.29>Can touch him further.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.30>Come on;</A><br>
+<A NAME=3.2.31>Gentle my lord, sleek o'er your rugged looks;</A><br>
+<A NAME=3.2.32>Be bright and jovial among your guests to-night.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.33>So shall I, love; and so, I pray, be you:</A><br>
+<A NAME=3.2.34>Let your remembrance apply to Banquo;</A><br>
+<A NAME=3.2.35>Present him eminence, both with eye and tongue:</A><br>
+<A NAME=3.2.36>Unsafe the while, that we</A><br>
+<A NAME=3.2.37>Must lave our honours in these flattering streams,</A><br>
+<A NAME=3.2.38>And make our faces vizards to our hearts,</A><br>
+<A NAME=3.2.39>Disguising what they are.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.40>You must leave this.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.41>O, full of scorpions is my mind, dear wife!</A><br>
+<A NAME=3.2.42>Thou know'st that Banquo, and his Fleance, lives.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.43>But in them nature's copy's not eterne.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.44>There's comfort yet; they are assailable;</A><br>
+<A NAME=3.2.45>Then be thou jocund: ere the bat hath flown</A><br>
+<A NAME=3.2.46>His cloister'd flight, ere to black Hecate's summons</A><br>
+<A NAME=3.2.47>The shard-borne beetle with his drowsy hums</A><br>
+<A NAME=3.2.48>Hath rung night's yawning peal, there shall be done</A><br>
+<A NAME=3.2.49>A deed of dreadful note.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.50>What's to be done?</A><br>
+</blockquote>
+
+<A NAME=speech14><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.2.51>Be innocent of the knowledge, dearest chuck,</A><br>
+<A NAME=3.2.52>Till thou applaud the deed. Come, seeling night,</A><br>
+<A NAME=3.2.53>Scarf up the tender eye of pitiful day;</A><br>
+<A NAME=3.2.54>And with thy bloody and invisible hand</A><br>
+<A NAME=3.2.55>Cancel and tear to pieces that great bond</A><br>
+<A NAME=3.2.56>Which keeps me pale! Light thickens; and the crow</A><br>
+<A NAME=3.2.57>Makes wing to the rooky wood:</A><br>
+<A NAME=3.2.58>Good things of day begin to droop and drowse;</A><br>
+<A NAME=3.2.59>While night's black agents to their preys do rouse.</A><br>
+<A NAME=3.2.60>Thou marvell'st at my words: but hold thee still;</A><br>
+<A NAME=3.2.61>Things bad begun make strong themselves by ill.</A><br>
+<A NAME=3.2.62>So, prithee, go with me.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE III. A park near the palace.</h3>
+<p><blockquote>
+<i>Enter three Murderers</i>
+</blockquote>
+
+<A NAME=speech1><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.3.1>But who did bid thee join with us?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Third Murderer</b></a>
+<blockquote>
+<A NAME=3.3.2>Macbeth.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>Second Murderer</b></a>
+<blockquote>
+<A NAME=3.3.3>He needs not our mistrust, since he delivers</A><br>
+<A NAME=3.3.4>Our offices and what we have to do</A><br>
+<A NAME=3.3.5>To the direction just.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.3.6>Then stand with us.</A><br>
+<A NAME=3.3.7>The west yet glimmers with some streaks of day:</A><br>
+<A NAME=3.3.8>Now spurs the lated traveller apace</A><br>
+<A NAME=3.3.9>To gain the timely inn; and near approaches</A><br>
+<A NAME=3.3.10>The subject of our watch.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>Third Murderer</b></a>
+<blockquote>
+<A NAME=3.3.11>Hark! I hear horses.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.3.12>[Within] Give us a light there, ho!</A><br>
+</blockquote>
+
+<A NAME=speech7><b>Second Murderer</b></a>
+<blockquote>
+<A NAME=3.3.13>Then 'tis he: the rest</A><br>
+<A NAME=3.3.14>That are within the note of expectation</A><br>
+<A NAME=3.3.15>Already are i' the court.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.3.16>His horses go about.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>Third Murderer</b></a>
+<blockquote>
+<A NAME=3.3.17>Almost a mile: but he does usually,</A><br>
+<A NAME=3.3.18>So all men do, from hence to the palace gate</A><br>
+<A NAME=3.3.19>Make it their walk.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>Second Murderer</b></a>
+<blockquote>
+<A NAME=3.3.20>A light, a light!</A><br>
+<p><i>Enter BANQUO, and FLEANCE with a torch</i></p>
+</blockquote>
+
+<A NAME=speech11><b>Third Murderer</b></a>
+<blockquote>
+<A NAME=3.3.21>'Tis he.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.3.22>Stand to't.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.3.23>It will be rain to-night.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.3.24>Let it come down.</A><br>
+<p><i>They set upon BANQUO</i></p>
+</blockquote>
+
+<A NAME=speech15><b>BANQUO</b></a>
+<blockquote>
+<A NAME=3.3.25>O, treachery! Fly, good Fleance, fly, fly, fly!</A><br>
+<A NAME=3.3.26>Thou mayst revenge. O slave!</A><br>
+<p><i>Dies. FLEANCE escapes</i></p>
+</blockquote>
+
+<A NAME=speech16><b>Third Murderer</b></a>
+<blockquote>
+<A NAME=3.3.27>Who did strike out the light?</A><br>
+</blockquote>
+
+<A NAME=speech17><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.3.28>Wast not the way?</A><br>
+</blockquote>
+
+<A NAME=speech18><b>Third Murderer</b></a>
+<blockquote>
+<A NAME=3.3.29>There's but one down; the son is fled.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>Second Murderer</b></a>
+<blockquote>
+<A NAME=3.3.30>We have lost</A><br>
+<A NAME=3.3.31>Best half of our affair.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.3.32>Well, let's away, and say how much is done.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE IV. The same. Hall in the palace.</h3>
+<p><blockquote>
+<i>A banquet prepared. Enter MACBETH, LADY MACBETH, ROSS, LENNOX, Lords, and Attendants</i>
+</blockquote>
+
+<A NAME=speech1><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.1>You know your own degrees; sit down: at first</A><br>
+<A NAME=3.4.2>And last the hearty welcome.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Lords</b></a>
+<blockquote>
+<A NAME=3.4.3>Thanks to your majesty.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.4>Ourself will mingle with society,</A><br>
+<A NAME=3.4.5>And play the humble host.</A><br>
+<A NAME=3.4.6>Our hostess keeps her state, but in best time</A><br>
+<A NAME=3.4.7>We will require her welcome.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.8>Pronounce it for me, sir, to all our friends;</A><br>
+<A NAME=3.4.9>For my heart speaks they are welcome.</A><br>
+<p><i>First Murderer appears at the door</i></p>
+</blockquote>
+
+<A NAME=speech5><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.10>See, they encounter thee with their hearts' thanks.</A><br>
+<A NAME=3.4.11>Both sides are even: here I'll sit i' the midst:</A><br>
+<A NAME=3.4.12>Be large in mirth; anon we'll drink a measure</A><br>
+<A NAME=3.4.13>The table round.</A><br>
+<p><i>Approaching the door</i></p>
+<A NAME=3.4.14>There's blood on thy face.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.4.15>'Tis Banquo's then.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.16>'Tis better thee without than he within.</A><br>
+<A NAME=3.4.17>Is he dispatch'd?</A><br>
+</blockquote>
+
+<A NAME=speech8><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.4.18>My lord, his throat is cut; that I did for him.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.19>Thou art the best o' the cut-throats: yet he's good</A><br>
+<A NAME=3.4.20>That did the like for Fleance: if thou didst it,</A><br>
+<A NAME=3.4.21>Thou art the nonpareil.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.4.22>Most royal sir,</A><br>
+<A NAME=3.4.23>Fleance is 'scaped.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.24>Then comes my fit again: I had else been perfect,</A><br>
+<A NAME=3.4.25>Whole as the marble, founded as the rock,</A><br>
+<A NAME=3.4.26>As broad and general as the casing air:</A><br>
+<A NAME=3.4.27>But now I am cabin'd, cribb'd, confined, bound in</A><br>
+<A NAME=3.4.28>To saucy doubts and fears. But Banquo's safe?</A><br>
+</blockquote>
+
+<A NAME=speech12><b>First Murderer</b></a>
+<blockquote>
+<A NAME=3.4.29>Ay, my good lord: safe in a ditch he bides,</A><br>
+<A NAME=3.4.30>With twenty trenched gashes on his head;</A><br>
+<A NAME=3.4.31>The least a death to nature.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.32>Thanks for that:</A><br>
+<A NAME=3.4.33>There the grown serpent lies; the worm that's fled</A><br>
+<A NAME=3.4.34>Hath nature that in time will venom breed,</A><br>
+<A NAME=3.4.35>No teeth for the present. Get thee gone: to-morrow</A><br>
+<A NAME=3.4.36>We'll hear, ourselves, again.</A><br>
+<p><i>Exit Murderer</i></p>
+</blockquote>
+
+<A NAME=speech14><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.37>My royal lord,</A><br>
+<A NAME=3.4.38>You do not give the cheer: the feast is sold</A><br>
+<A NAME=3.4.39>That is not often vouch'd, while 'tis a-making,</A><br>
+<A NAME=3.4.40>'Tis given with welcome: to feed were best at home;</A><br>
+<A NAME=3.4.41>From thence the sauce to meat is ceremony;</A><br>
+<A NAME=3.4.42>Meeting were bare without it.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.43>Sweet remembrancer!</A><br>
+<A NAME=3.4.44>Now, good digestion wait on appetite,</A><br>
+<A NAME=3.4.45>And health on both!</A><br>
+</blockquote>
+
+<A NAME=speech16><b>LENNOX</b></a>
+<blockquote>
+<A NAME=3.4.46>May't please your highness sit.</A><br>
+<p><i>The GHOST OF BANQUO enters, and sits in MACBETH's place</i></p>
+</blockquote>
+
+<A NAME=speech17><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.47>Here had we now our country's honour roof'd,</A><br>
+<A NAME=3.4.48>Were the graced person of our Banquo present;</A><br>
+<A NAME=3.4.49>Who may I rather challenge for unkindness</A><br>
+<A NAME=3.4.50>Than pity for mischance!</A><br>
+</blockquote>
+
+<A NAME=speech18><b>ROSS</b></a>
+<blockquote>
+<A NAME=3.4.51>His absence, sir,</A><br>
+<A NAME=3.4.52>Lays blame upon his promise. Please't your highness</A><br>
+<A NAME=3.4.53>To grace us with your royal company.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.54>The table's full.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>LENNOX</b></a>
+<blockquote>
+<A NAME=3.4.55> Here is a place reserved, sir.</A><br>
+</blockquote>
+
+<A NAME=speech21><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.56>Where?</A><br>
+</blockquote>
+
+<A NAME=speech22><b>LENNOX</b></a>
+<blockquote>
+<A NAME=3.4.57>Here, my good lord. What is't that moves your highness?</A><br>
+</blockquote>
+
+<A NAME=speech23><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.58>Which of you have done this?</A><br>
+</blockquote>
+
+<A NAME=speech24><b>Lords</b></a>
+<blockquote>
+<A NAME=3.4.59>What, my good lord?</A><br>
+</blockquote>
+
+<A NAME=speech25><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.60>Thou canst not say I did it: never shake</A><br>
+<A NAME=3.4.61>Thy gory locks at me.</A><br>
+</blockquote>
+
+<A NAME=speech26><b>ROSS</b></a>
+<blockquote>
+<A NAME=3.4.62>Gentlemen, rise: his highness is not well.</A><br>
+</blockquote>
+
+<A NAME=speech27><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.63>Sit, worthy friends: my lord is often thus,</A><br>
+<A NAME=3.4.64>And hath been from his youth: pray you, keep seat;</A><br>
+<A NAME=3.4.65>The fit is momentary; upon a thought</A><br>
+<A NAME=3.4.66>He will again be well: if much you note him,</A><br>
+<A NAME=3.4.67>You shall offend him and extend his passion:</A><br>
+<A NAME=3.4.68>Feed, and regard him not. Are you a man?</A><br>
+</blockquote>
+
+<A NAME=speech28><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.69>Ay, and a bold one, that dare look on that</A><br>
+<A NAME=3.4.70>Which might appal the devil.</A><br>
+</blockquote>
+
+<A NAME=speech29><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.71>O proper stuff!</A><br>
+<A NAME=3.4.72>This is the very painting of your fear:</A><br>
+<A NAME=3.4.73>This is the air-drawn dagger which, you said,</A><br>
+<A NAME=3.4.74>Led you to Duncan. O, these flaws and starts,</A><br>
+<A NAME=3.4.75>Impostors to true fear, would well become</A><br>
+<A NAME=3.4.76>A woman's story at a winter's fire,</A><br>
+<A NAME=3.4.77>Authorized by her grandam. Shame itself!</A><br>
+<A NAME=3.4.78>Why do you make such faces? When all's done,</A><br>
+<A NAME=3.4.79>You look but on a stool.</A><br>
+</blockquote>
+
+<A NAME=speech30><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.80>Prithee, see there! behold! look! lo!</A><br>
+<A NAME=3.4.81>how say you?</A><br>
+<A NAME=3.4.82>Why, what care I? If thou canst nod, speak too.</A><br>
+<A NAME=3.4.83>If charnel-houses and our graves must send</A><br>
+<A NAME=3.4.84>Those that we bury back, our monuments</A><br>
+<A NAME=3.4.85>Shall be the maws of kites.</A><br>
+<p><i>GHOST OF BANQUO vanishes</i></p>
+</blockquote>
+
+<A NAME=speech31><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.86>What, quite unmann'd in folly?</A><br>
+</blockquote>
+
+<A NAME=speech32><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.87>If I stand here, I saw him.</A><br>
+</blockquote>
+
+<A NAME=speech33><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.88>Fie, for shame!</A><br>
+</blockquote>
+
+<A NAME=speech34><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.89>Blood hath been shed ere now, i' the olden time,</A><br>
+<A NAME=3.4.90>Ere human statute purged the gentle weal;</A><br>
+<A NAME=3.4.91>Ay, and since too, murders have been perform'd</A><br>
+<A NAME=3.4.92>Too terrible for the ear: the times have been,</A><br>
+<A NAME=3.4.93>That, when the brains were out, the man would die,</A><br>
+<A NAME=3.4.94>And there an end; but now they rise again,</A><br>
+<A NAME=3.4.95>With twenty mortal murders on their crowns,</A><br>
+<A NAME=3.4.96>And push us from our stools: this is more strange</A><br>
+<A NAME=3.4.97>Than such a murder is.</A><br>
+</blockquote>
+
+<A NAME=speech35><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.98>My worthy lord,</A><br>
+<A NAME=3.4.99>Your noble friends do lack you.</A><br>
+</blockquote>
+
+<A NAME=speech36><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.100>I do forget.</A><br>
+<A NAME=3.4.101>Do not muse at me, my most worthy friends,</A><br>
+<A NAME=3.4.102>I have a strange infirmity, which is nothing</A><br>
+<A NAME=3.4.103>To those that know me. Come, love and health to all;</A><br>
+<A NAME=3.4.104>Then I'll sit down. Give me some wine; fill full.</A><br>
+<A NAME=3.4.105>I drink to the general joy o' the whole table,</A><br>
+<A NAME=3.4.106>And to our dear friend Banquo, whom we miss;</A><br>
+<A NAME=3.4.107>Would he were here! to all, and him, we thirst,</A><br>
+<A NAME=3.4.108>And all to all.</A><br>
+</blockquote>
+
+<A NAME=speech37><b>Lords</b></a>
+<blockquote>
+<A NAME=3.4.109> Our duties, and the pledge.</A><br>
+<p><i>Re-enter GHOST OF BANQUO</i></p>
+</blockquote>
+
+<A NAME=speech38><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.110>Avaunt! and quit my sight! let the earth hide thee!</A><br>
+<A NAME=3.4.111>Thy bones are marrowless, thy blood is cold;</A><br>
+<A NAME=3.4.112>Thou hast no speculation in those eyes</A><br>
+<A NAME=3.4.113>Which thou dost glare with!</A><br>
+</blockquote>
+
+<A NAME=speech39><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.114>Think of this, good peers,</A><br>
+<A NAME=3.4.115>But as a thing of custom: 'tis no other;</A><br>
+<A NAME=3.4.116>Only it spoils the pleasure of the time.</A><br>
+</blockquote>
+
+<A NAME=speech40><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.117>What man dare, I dare:</A><br>
+<A NAME=3.4.118>Approach thou like the rugged Russian bear,</A><br>
+<A NAME=3.4.119>The arm'd rhinoceros, or the Hyrcan tiger;</A><br>
+<A NAME=3.4.120>Take any shape but that, and my firm nerves</A><br>
+<A NAME=3.4.121>Shall never tremble: or be alive again,</A><br>
+<A NAME=3.4.122>And dare me to the desert with thy sword;</A><br>
+<A NAME=3.4.123>If trembling I inhabit then, protest me</A><br>
+<A NAME=3.4.124>The baby of a girl. Hence, horrible shadow!</A><br>
+<A NAME=3.4.125>Unreal mockery, hence!</A><br>
+<p><i>GHOST OF BANQUO vanishes</i></p>
+<A NAME=3.4.126>Why, so: being gone,</A><br>
+<A NAME=3.4.127>I am a man again. Pray you, sit still.</A><br>
+</blockquote>
+
+<A NAME=speech41><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.128>You have displaced the mirth, broke the good meeting,</A><br>
+<A NAME=3.4.129>With most admired disorder.</A><br>
+</blockquote>
+
+<A NAME=speech42><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.130>Can such things be,</A><br>
+<A NAME=3.4.131>And overcome us like a summer's cloud,</A><br>
+<A NAME=3.4.132>Without our special wonder? You make me strange</A><br>
+<A NAME=3.4.133>Even to the disposition that I owe,</A><br>
+<A NAME=3.4.134>When now I think you can behold such sights,</A><br>
+<A NAME=3.4.135>And keep the natural ruby of your cheeks,</A><br>
+<A NAME=3.4.136>When mine is blanched with fear.</A><br>
+</blockquote>
+
+<A NAME=speech43><b>ROSS</b></a>
+<blockquote>
+<A NAME=3.4.137>What sights, my lord?</A><br>
+</blockquote>
+
+<A NAME=speech44><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.138>I pray you, speak not; he grows worse and worse;</A><br>
+<A NAME=3.4.139>Question enrages him. At once, good night:</A><br>
+<A NAME=3.4.140>Stand not upon the order of your going,</A><br>
+<A NAME=3.4.141>But go at once.</A><br>
+</blockquote>
+
+<A NAME=speech45><b>LENNOX</b></a>
+<blockquote>
+<A NAME=3.4.142> Good night; and better health</A><br>
+<A NAME=3.4.143>Attend his majesty!</A><br>
+</blockquote>
+
+<A NAME=speech46><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.144>A kind good night to all!</A><br>
+<p><i>Exeunt all but MACBETH and LADY MACBETH</i></p>
+</blockquote>
+
+<A NAME=speech47><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.145>It will have blood; they say, blood will have blood:</A><br>
+<A NAME=3.4.146>Stones have been known to move and trees to speak;</A><br>
+<A NAME=3.4.147>Augurs and understood relations have</A><br>
+<A NAME=3.4.148>By magot-pies and choughs and rooks brought forth</A><br>
+<A NAME=3.4.149>The secret'st man of blood. What is the night?</A><br>
+</blockquote>
+
+<A NAME=speech48><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.150>Almost at odds with morning, which is which.</A><br>
+</blockquote>
+
+<A NAME=speech49><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.151>How say'st thou, that Macduff denies his person</A><br>
+<A NAME=3.4.152>At our great bidding?</A><br>
+</blockquote>
+
+<A NAME=speech50><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.153>Did you send to him, sir?</A><br>
+</blockquote>
+
+<A NAME=speech51><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.154>I hear it by the way; but I will send:</A><br>
+<A NAME=3.4.155>There's not a one of them but in his house</A><br>
+<A NAME=3.4.156>I keep a servant fee'd. I will to-morrow,</A><br>
+<A NAME=3.4.157>And betimes I will, to the weird sisters:</A><br>
+<A NAME=3.4.158>More shall they speak; for now I am bent to know,</A><br>
+<A NAME=3.4.159>By the worst means, the worst. For mine own good,</A><br>
+<A NAME=3.4.160>All causes shall give way: I am in blood</A><br>
+<A NAME=3.4.161>Stepp'd in so far that, should I wade no more,</A><br>
+<A NAME=3.4.162>Returning were as tedious as go o'er:</A><br>
+<A NAME=3.4.163>Strange things I have in head, that will to hand;</A><br>
+<A NAME=3.4.164>Which must be acted ere they may be scann'd.</A><br>
+</blockquote>
+
+<A NAME=speech52><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.165>You lack the season of all natures, sleep.</A><br>
+</blockquote>
+
+<A NAME=speech53><b>MACBETH</b></a>
+<blockquote>
+<A NAME=3.4.166>Come, we'll to sleep. My strange and self-abuse</A><br>
+<A NAME=3.4.167>Is the initiate fear that wants hard use:</A><br>
+<A NAME=3.4.168>We are yet but young in deed.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE V. A Heath.</h3>
+<p><blockquote>
+<i>Thunder. Enter the three Witches meeting HECATE</i>
+</blockquote>
+
+<A NAME=speech1><b>First Witch</b></a>
+<blockquote>
+<A NAME=3.5.1>Why, how now, Hecate! you look angerly.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>HECATE</b></a>
+<blockquote>
+<A NAME=3.5.2>Have I not reason, beldams as you are,</A><br>
+<A NAME=3.5.3>Saucy and overbold? How did you dare</A><br>
+<A NAME=3.5.4>To trade and traffic with Macbeth</A><br>
+<A NAME=3.5.5>In riddles and affairs of death;</A><br>
+<A NAME=3.5.6>And I, the mistress of your charms,</A><br>
+<A NAME=3.5.7>The close contriver of all harms,</A><br>
+<A NAME=3.5.8>Was never call'd to bear my part,</A><br>
+<A NAME=3.5.9>Or show the glory of our art?</A><br>
+<A NAME=3.5.10>And, which is worse, all you have done</A><br>
+<A NAME=3.5.11>Hath been but for a wayward son,</A><br>
+<A NAME=3.5.12>Spiteful and wrathful, who, as others do,</A><br>
+<A NAME=3.5.13>Loves for his own ends, not for you.</A><br>
+<A NAME=3.5.14>But make amends now: get you gone,</A><br>
+<A NAME=3.5.15>And at the pit of Acheron</A><br>
+<A NAME=3.5.16>Meet me i' the morning: thither he</A><br>
+<A NAME=3.5.17>Will come to know his destiny:</A><br>
+<A NAME=3.5.18>Your vessels and your spells provide,</A><br>
+<A NAME=3.5.19>Your charms and every thing beside.</A><br>
+<A NAME=3.5.20>I am for the air; this night I'll spend</A><br>
+<A NAME=3.5.21>Unto a dismal and a fatal end:</A><br>
+<A NAME=3.5.22>Great business must be wrought ere noon:</A><br>
+<A NAME=3.5.23>Upon the corner of the moon</A><br>
+<A NAME=3.5.24>There hangs a vaporous drop profound;</A><br>
+<A NAME=3.5.25>I'll catch it ere it come to ground:</A><br>
+<A NAME=3.5.26>And that distill'd by magic sleights</A><br>
+<A NAME=3.5.27>Shall raise such artificial sprites</A><br>
+<A NAME=3.5.28>As by the strength of their illusion</A><br>
+<A NAME=3.5.29>Shall draw him on to his confusion:</A><br>
+<A NAME=3.5.30>He shall spurn fate, scorn death, and bear</A><br>
+<A NAME=3.5.31>He hopes 'bove wisdom, grace and fear:</A><br>
+<A NAME=3.5.32>And you all know, security</A><br>
+<A NAME=3.5.33>Is mortals' chiefest enemy.</A><br>
+<p><i>Music and a song within: 'Come away, come away,' & c</i></p>
+<A NAME=3.5.34>Hark! I am call'd; my little spirit, see,</A><br>
+<A NAME=3.5.35>Sits in a foggy cloud, and stays for me.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+
+<A NAME=speech3><b>First Witch</b></a>
+<blockquote>
+<A NAME=3.5.36>Come, let's make haste; she'll soon be back again.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE VI. Forres. The palace.</h3>
+<p><blockquote>
+<i>Enter LENNOX and another Lord</i>
+</blockquote>
+
+<A NAME=speech1><b>LENNOX</b></a>
+<blockquote>
+<A NAME=3.6.1>My former speeches have but hit your thoughts,</A><br>
+<A NAME=3.6.2>Which can interpret further: only, I say,</A><br>
+<A NAME=3.6.3>Things have been strangely borne. The</A><br>
+<A NAME=3.6.4>gracious Duncan</A><br>
+<A NAME=3.6.5>Was pitied of Macbeth: marry, he was dead:</A><br>
+<A NAME=3.6.6>And the right-valiant Banquo walk'd too late;</A><br>
+<A NAME=3.6.7>Whom, you may say, if't please you, Fleance kill'd,</A><br>
+<A NAME=3.6.8>For Fleance fled: men must not walk too late.</A><br>
+<A NAME=3.6.9>Who cannot want the thought how monstrous</A><br>
+<A NAME=3.6.10>It was for Malcolm and for Donalbain</A><br>
+<A NAME=3.6.11>To kill their gracious father? damned fact!</A><br>
+<A NAME=3.6.12>How it did grieve Macbeth! did he not straight</A><br>
+<A NAME=3.6.13>In pious rage the two delinquents tear,</A><br>
+<A NAME=3.6.14>That were the slaves of drink and thralls of sleep?</A><br>
+<A NAME=3.6.15>Was not that nobly done? Ay, and wisely too;</A><br>
+<A NAME=3.6.16>For 'twould have anger'd any heart alive</A><br>
+<A NAME=3.6.17>To hear the men deny't. So that, I say,</A><br>
+<A NAME=3.6.18>He has borne all things well: and I do think</A><br>
+<A NAME=3.6.19>That had he Duncan's sons under his key--</A><br>
+<A NAME=3.6.20>As, an't please heaven, he shall not--they</A><br>
+<A NAME=3.6.21>should find</A><br>
+<A NAME=3.6.22>What 'twere to kill a father; so should Fleance.</A><br>
+<A NAME=3.6.23>But, peace! for from broad words and 'cause he fail'd</A><br>
+<A NAME=3.6.24>His presence at the tyrant's feast, I hear</A><br>
+<A NAME=3.6.25>Macduff lives in disgrace: sir, can you tell</A><br>
+<A NAME=3.6.26>Where he bestows himself?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Lord</b></a>
+<blockquote>
+<A NAME=3.6.27>The son of Duncan,</A><br>
+<A NAME=3.6.28>From whom this tyrant holds the due of birth</A><br>
+<A NAME=3.6.29>Lives in the English court, and is received</A><br>
+<A NAME=3.6.30>Of the most pious Edward with such grace</A><br>
+<A NAME=3.6.31>That the malevolence of fortune nothing</A><br>
+<A NAME=3.6.32>Takes from his high respect: thither Macduff</A><br>
+<A NAME=3.6.33>Is gone to pray the holy king, upon his aid</A><br>
+<A NAME=3.6.34>To wake Northumberland and warlike Siward:</A><br>
+<A NAME=3.6.35>That, by the help of these--with Him above</A><br>
+<A NAME=3.6.36>To ratify the work--we may again</A><br>
+<A NAME=3.6.37>Give to our tables meat, sleep to our nights,</A><br>
+<A NAME=3.6.38>Free from our feasts and banquets bloody knives,</A><br>
+<A NAME=3.6.39>Do faithful homage and receive free honours:</A><br>
+<A NAME=3.6.40>All which we pine for now: and this report</A><br>
+<A NAME=3.6.41>Hath so exasperate the king that he</A><br>
+<A NAME=3.6.42>Prepares for some attempt of war.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>LENNOX</b></a>
+<blockquote>
+<A NAME=3.6.43>Sent he to Macduff?</A><br>
+</blockquote>
+
+<A NAME=speech4><b>Lord</b></a>
+<blockquote>
+<A NAME=3.6.44>He did: and with an absolute 'Sir, not I,'</A><br>
+<A NAME=3.6.45>The cloudy messenger turns me his back,</A><br>
+<A NAME=3.6.46>And hums, as who should say 'You'll rue the time</A><br>
+<A NAME=3.6.47>That clogs me with this answer.'</A><br>
+</blockquote>
+
+<A NAME=speech5><b>LENNOX</b></a>
+<blockquote>
+<A NAME=3.6.48>And that well might</A><br>
+<A NAME=3.6.49>Advise him to a caution, to hold what distance</A><br>
+<A NAME=3.6.50>His wisdom can provide. Some holy angel</A><br>
+<A NAME=3.6.51>Fly to the court of England and unfold</A><br>
+<A NAME=3.6.52>His message ere he come, that a swift blessing</A><br>
+<A NAME=3.6.53>May soon return to this our suffering country</A><br>
+<A NAME=3.6.54>Under a hand accursed!</A><br>
+</blockquote>
+
+<A NAME=speech6><b>Lord</b></a>
+<blockquote>
+<A NAME=3.6.55>I'll send my prayers with him.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote><p>
+<H3>ACT IV</h3>
+<h3>SCENE I. A cavern. In the middle, a boiling cauldron.</h3>
+<p><blockquote>
+<i>Thunder. Enter the three Witches</i>
+</blockquote>
+
+<A NAME=speech1><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.1>Thrice the brinded cat hath mew'd.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Second Witch</b></a>
+<blockquote>
+<A NAME=4.1.2>Thrice and once the hedge-pig whined.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>Third Witch</b></a>
+<blockquote>
+<A NAME=4.1.3>Harpier cries 'Tis time, 'tis time.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.4>Round about the cauldron go;</A><br>
+<A NAME=4.1.5>In the poison'd entrails throw.</A><br>
+<A NAME=4.1.6>Toad, that under cold stone</A><br>
+<A NAME=4.1.7>Days and nights has thirty-one</A><br>
+<A NAME=4.1.8>Swelter'd venom sleeping got,</A><br>
+<A NAME=4.1.9>Boil thou first i' the charmed pot.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>ALL</b></a>
+<blockquote>
+<A NAME=4.1.10>Double, double toil and trouble;</A><br>
+<A NAME=4.1.11>Fire burn, and cauldron bubble.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>Second Witch</b></a>
+<blockquote>
+<A NAME=4.1.12>Fillet of a fenny snake,</A><br>
+<A NAME=4.1.13>In the cauldron boil and bake;</A><br>
+<A NAME=4.1.14>Eye of newt and toe of frog,</A><br>
+<A NAME=4.1.15>Wool of bat and tongue of dog,</A><br>
+<A NAME=4.1.16>Adder's fork and blind-worm's sting,</A><br>
+<A NAME=4.1.17>Lizard's leg and owlet's wing,</A><br>
+<A NAME=4.1.18>For a charm of powerful trouble,</A><br>
+<A NAME=4.1.19>Like a hell-broth boil and bubble.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>ALL</b></a>
+<blockquote>
+<A NAME=4.1.20>Double, double toil and trouble;</A><br>
+<A NAME=4.1.21>Fire burn and cauldron bubble.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>Third Witch</b></a>
+<blockquote>
+<A NAME=4.1.22>Scale of dragon, tooth of wolf,</A><br>
+<A NAME=4.1.23>Witches' mummy, maw and gulf</A><br>
+<A NAME=4.1.24>Of the ravin'd salt-sea shark,</A><br>
+<A NAME=4.1.25>Root of hemlock digg'd i' the dark,</A><br>
+<A NAME=4.1.26>Liver of blaspheming Jew,</A><br>
+<A NAME=4.1.27>Gall of goat, and slips of yew</A><br>
+<A NAME=4.1.28>Silver'd in the moon's eclipse,</A><br>
+<A NAME=4.1.29>Nose of Turk and Tartar's lips,</A><br>
+<A NAME=4.1.30>Finger of birth-strangled babe</A><br>
+<A NAME=4.1.31>Ditch-deliver'd by a drab,</A><br>
+<A NAME=4.1.32>Make the gruel thick and slab:</A><br>
+<A NAME=4.1.33>Add thereto a tiger's chaudron,</A><br>
+<A NAME=4.1.34>For the ingredients of our cauldron.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>ALL</b></a>
+<blockquote>
+<A NAME=4.1.35>Double, double toil and trouble;</A><br>
+<A NAME=4.1.36>Fire burn and cauldron bubble.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>Second Witch</b></a>
+<blockquote>
+<A NAME=4.1.37>Cool it with a baboon's blood,</A><br>
+<A NAME=4.1.38>Then the charm is firm and good.</A><br>
+<p><i>Enter HECATE to the other three Witches</i></p>
+</blockquote>
+
+<A NAME=speech11><b>HECATE</b></a>
+<blockquote>
+<A NAME=4.1.39>O well done! I commend your pains;</A><br>
+<A NAME=4.1.40>And every one shall share i' the gains;</A><br>
+<A NAME=4.1.41>And now about the cauldron sing,</A><br>
+<A NAME=4.1.42>Live elves and fairies in a ring,</A><br>
+<A NAME=4.1.43>Enchanting all that you put in.</A><br>
+<p><i>Music and a song: 'Black spirits,' & c</i></p>
+<p><i>HECATE retires</i></p>
+</blockquote>
+
+<A NAME=speech12><b>Second Witch</b></a>
+<blockquote>
+<A NAME=4.1.44>By the pricking of my thumbs,</A><br>
+<A NAME=4.1.45>Something wicked this way comes.</A><br>
+<A NAME=4.1.46>Open, locks,</A><br>
+<A NAME=4.1.47>Whoever knocks!</A><br>
+<p><i>Enter MACBETH</i></p>
+</blockquote>
+
+<A NAME=speech13><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.48>How now, you secret, black, and midnight hags!</A><br>
+<A NAME=4.1.49>What is't you do?</A><br>
+</blockquote>
+
+<A NAME=speech14><b>ALL</b></a>
+<blockquote>
+<A NAME=4.1.50> A deed without a name.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.51>I conjure you, by that which you profess,</A><br>
+<A NAME=4.1.52>Howe'er you come to know it, answer me:</A><br>
+<A NAME=4.1.53>Though you untie the winds and let them fight</A><br>
+<A NAME=4.1.54>Against the churches; though the yesty waves</A><br>
+<A NAME=4.1.55>Confound and swallow navigation up;</A><br>
+<A NAME=4.1.56>Though bladed corn be lodged and trees blown down;</A><br>
+<A NAME=4.1.57>Though castles topple on their warders' heads;</A><br>
+<A NAME=4.1.58>Though palaces and pyramids do slope</A><br>
+<A NAME=4.1.59>Their heads to their foundations; though the treasure</A><br>
+<A NAME=4.1.60>Of nature's germens tumble all together,</A><br>
+<A NAME=4.1.61>Even till destruction sicken; answer me</A><br>
+<A NAME=4.1.62>To what I ask you.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.63> Speak.</A><br>
+</blockquote>
+
+<A NAME=speech17><b>Second Witch</b></a>
+<blockquote>
+<A NAME=4.1.64>Demand.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>Third Witch</b></a>
+<blockquote>
+<A NAME=4.1.65>We'll answer.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.66>Say, if thou'dst rather hear it from our mouths,</A><br>
+<A NAME=4.1.67>Or from our masters?</A><br>
+</blockquote>
+
+<A NAME=speech20><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.68>Call 'em; let me see 'em.</A><br>
+</blockquote>
+
+<A NAME=speech21><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.69>Pour in sow's blood, that hath eaten</A><br>
+<A NAME=4.1.70>Her nine farrow; grease that's sweaten</A><br>
+<A NAME=4.1.71>From the murderer's gibbet throw</A><br>
+<A NAME=4.1.72>Into the flame.</A><br>
+</blockquote>
+
+<A NAME=speech22><b>ALL</b></a>
+<blockquote>
+<A NAME=4.1.73> Come, high or low;</A><br>
+<A NAME=4.1.74>Thyself and office deftly show!</A><br>
+<p><i>Thunder. First Apparition: an armed Head</i></p>
+</blockquote>
+
+<A NAME=speech23><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.75>Tell me, thou unknown power,--</A><br>
+</blockquote>
+
+<A NAME=speech24><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.76>He knows thy thought:</A><br>
+<A NAME=4.1.77>Hear his speech, but say thou nought.</A><br>
+</blockquote>
+
+<A NAME=speech25><b>First Apparition</b></a>
+<blockquote>
+<A NAME=4.1.78>Macbeth! Macbeth! Macbeth! beware Macduff;</A><br>
+<A NAME=4.1.79>Beware the thane of Fife. Dismiss me. Enough.</A><br>
+<p><i>Descends</i></p>
+</blockquote>
+
+<A NAME=speech26><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.80>Whate'er thou art, for thy good caution, thanks;</A><br>
+<A NAME=4.1.81>Thou hast harp'd my fear aright: but one</A><br>
+<A NAME=4.1.82>word more,--</A><br>
+</blockquote>
+
+<A NAME=speech27><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.83>He will not be commanded: here's another,</A><br>
+<A NAME=4.1.84>More potent than the first.</A><br>
+<p><i>Thunder. Second Apparition: A bloody Child</i></p>
+</blockquote>
+
+<A NAME=speech28><b>Second Apparition</b></a>
+<blockquote>
+<A NAME=4.1.85>Macbeth! Macbeth! Macbeth!</A><br>
+</blockquote>
+
+<A NAME=speech29><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.86>Had I three ears, I'ld hear thee.</A><br>
+</blockquote>
+
+<A NAME=speech30><b>Second Apparition</b></a>
+<blockquote>
+<A NAME=4.1.87>Be bloody, bold, and resolute; laugh to scorn</A><br>
+<A NAME=4.1.88>The power of man, for none of woman born</A><br>
+<A NAME=4.1.89>Shall harm Macbeth.</A><br>
+<p><i>Descends</i></p>
+</blockquote>
+
+<A NAME=speech31><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.90>Then live, Macduff: what need I fear of thee?</A><br>
+<A NAME=4.1.91>But yet I'll make assurance double sure,</A><br>
+<A NAME=4.1.92>And take a bond of fate: thou shalt not live;</A><br>
+<A NAME=4.1.93>That I may tell pale-hearted fear it lies,</A><br>
+<A NAME=4.1.94>And sleep in spite of thunder.</A><br>
+<p><i>Thunder. Third Apparition: a Child crowned, with a tree in his hand</i></p>
+<A NAME=4.1.95>What is this</A><br>
+<A NAME=4.1.96>That rises like the issue of a king,</A><br>
+<A NAME=4.1.97>And wears upon his baby-brow the round</A><br>
+<A NAME=4.1.98>And top of sovereignty?</A><br>
+</blockquote>
+
+<A NAME=speech32><b>ALL</b></a>
+<blockquote>
+<A NAME=4.1.99>Listen, but speak not to't.</A><br>
+</blockquote>
+
+<A NAME=speech33><b>Third Apparition</b></a>
+<blockquote>
+<A NAME=4.1.100>Be lion-mettled, proud; and take no care</A><br>
+<A NAME=4.1.101>Who chafes, who frets, or where conspirers are:</A><br>
+<A NAME=4.1.102>Macbeth shall never vanquish'd be until</A><br>
+<A NAME=4.1.103>Great Birnam wood to high Dunsinane hill</A><br>
+<A NAME=4.1.104>Shall come against him.</A><br>
+<p><i>Descends</i></p>
+</blockquote>
+
+<A NAME=speech34><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.105>That will never be</A><br>
+<A NAME=4.1.106>Who can impress the forest, bid the tree</A><br>
+<A NAME=4.1.107>Unfix his earth-bound root? Sweet bodements! good!</A><br>
+<A NAME=4.1.108>Rebellion's head, rise never till the wood</A><br>
+<A NAME=4.1.109>Of Birnam rise, and our high-placed Macbeth</A><br>
+<A NAME=4.1.110>Shall live the lease of nature, pay his breath</A><br>
+<A NAME=4.1.111>To time and mortal custom. Yet my heart</A><br>
+<A NAME=4.1.112>Throbs to know one thing: tell me, if your art</A><br>
+<A NAME=4.1.113>Can tell so much: shall Banquo's issue ever</A><br>
+<A NAME=4.1.114>Reign in this kingdom?</A><br>
+</blockquote>
+
+<A NAME=speech35><b>ALL</b></a>
+<blockquote>
+<A NAME=4.1.115>Seek to know no more.</A><br>
+</blockquote>
+
+<A NAME=speech36><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.116>I will be satisfied: deny me this,</A><br>
+<A NAME=4.1.117>And an eternal curse fall on you! Let me know.</A><br>
+<A NAME=4.1.118>Why sinks that cauldron? and what noise is this?</A><br>
+<p><i>Hautboys</i></p>
+</blockquote>
+
+<A NAME=speech37><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.119>Show!</A><br>
+</blockquote>
+
+<A NAME=speech38><b>Second Witch</b></a>
+<blockquote>
+<A NAME=4.1.120>Show!</A><br>
+</blockquote>
+
+<A NAME=speech39><b>Third Witch</b></a>
+<blockquote>
+<A NAME=4.1.121>Show!</A><br>
+</blockquote>
+
+<A NAME=speech40><b>ALL</b></a>
+<blockquote>
+<A NAME=4.1.122>Show his eyes, and grieve his heart;</A><br>
+<A NAME=4.1.123>Come like shadows, so depart!</A><br>
+<p><i>A show of Eight Kings, the last with a glass in his hand; GHOST OF BANQUO following</i></p>
+</blockquote>
+
+<A NAME=speech41><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.124>Thou art too like the spirit of Banquo: down!</A><br>
+<A NAME=4.1.125>Thy crown does sear mine eye-balls. And thy hair,</A><br>
+<A NAME=4.1.126>Thou other gold-bound brow, is like the first.</A><br>
+<A NAME=4.1.127>A third is like the former. Filthy hags!</A><br>
+<A NAME=4.1.128>Why do you show me this? A fourth! Start, eyes!</A><br>
+<A NAME=4.1.129>What, will the line stretch out to the crack of doom?</A><br>
+<A NAME=4.1.130>Another yet! A seventh! I'll see no more:</A><br>
+<A NAME=4.1.131>And yet the eighth appears, who bears a glass</A><br>
+<A NAME=4.1.132>Which shows me many more; and some I see</A><br>
+<A NAME=4.1.133>That two-fold balls and treble scepters carry:</A><br>
+<A NAME=4.1.134>Horrible sight! Now, I see, 'tis true;</A><br>
+<A NAME=4.1.135>For the blood-bolter'd Banquo smiles upon me,</A><br>
+<A NAME=4.1.136>And points at them for his.</A><br>
+<p><i>Apparitions vanish</i></p>
+<A NAME=4.1.137>What, is this so?</A><br>
+</blockquote>
+
+<A NAME=speech42><b>First Witch</b></a>
+<blockquote>
+<A NAME=4.1.138>Ay, sir, all this is so: but why</A><br>
+<A NAME=4.1.139>Stands Macbeth thus amazedly?</A><br>
+<A NAME=4.1.140>Come, sisters, cheer we up his sprites,</A><br>
+<A NAME=4.1.141>And show the best of our delights:</A><br>
+<A NAME=4.1.142>I'll charm the air to give a sound,</A><br>
+<A NAME=4.1.143>While you perform your antic round:</A><br>
+<A NAME=4.1.144>That this great king may kindly say,</A><br>
+<A NAME=4.1.145>Our duties did his welcome pay.</A><br>
+<p><i>Music. The witches dance and then vanish, with HECATE</i></p>
+</blockquote>
+
+<A NAME=speech43><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.146>Where are they? Gone? Let this pernicious hour</A><br>
+<A NAME=4.1.147>Stand aye accursed in the calendar!</A><br>
+<A NAME=4.1.148>Come in, without there!</A><br>
+<p><i>Enter LENNOX</i></p>
+</blockquote>
+
+<A NAME=speech44><b>LENNOX</b></a>
+<blockquote>
+<A NAME=4.1.149>What's your grace's will?</A><br>
+</blockquote>
+
+<A NAME=speech45><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.150>Saw you the weird sisters?</A><br>
+</blockquote>
+
+<A NAME=speech46><b>LENNOX</b></a>
+<blockquote>
+<A NAME=4.1.151>No, my lord.</A><br>
+</blockquote>
+
+<A NAME=speech47><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.152>Came they not by you?</A><br>
+</blockquote>
+
+<A NAME=speech48><b>LENNOX</b></a>
+<blockquote>
+<A NAME=4.1.153>No, indeed, my lord.</A><br>
+</blockquote>
+
+<A NAME=speech49><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.154>Infected be the air whereon they ride;</A><br>
+<A NAME=4.1.155>And damn'd all those that trust them! I did hear</A><br>
+<A NAME=4.1.156>The galloping of horse: who was't came by?</A><br>
+</blockquote>
+
+<A NAME=speech50><b>LENNOX</b></a>
+<blockquote>
+<A NAME=4.1.157>'Tis two or three, my lord, that bring you word</A><br>
+<A NAME=4.1.158>Macduff is fled to England.</A><br>
+</blockquote>
+
+<A NAME=speech51><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.159>Fled to England!</A><br>
+</blockquote>
+
+<A NAME=speech52><b>LENNOX</b></a>
+<blockquote>
+<A NAME=4.1.160>Ay, my good lord.</A><br>
+</blockquote>
+
+<A NAME=speech53><b>MACBETH</b></a>
+<blockquote>
+<A NAME=4.1.161>Time, thou anticipatest my dread exploits:</A><br>
+<A NAME=4.1.162>The flighty purpose never is o'ertook</A><br>
+<A NAME=4.1.163>Unless the deed go with it; from this moment</A><br>
+<A NAME=4.1.164>The very firstlings of my heart shall be</A><br>
+<A NAME=4.1.165>The firstlings of my hand. And even now,</A><br>
+<A NAME=4.1.166>To crown my thoughts with acts, be it thought and done:</A><br>
+<A NAME=4.1.167>The castle of Macduff I will surprise;</A><br>
+<A NAME=4.1.168>Seize upon Fife; give to the edge o' the sword</A><br>
+<A NAME=4.1.169>His wife, his babes, and all unfortunate souls</A><br>
+<A NAME=4.1.170>That trace him in his line. No boasting like a fool;</A><br>
+<A NAME=4.1.171>This deed I'll do before this purpose cool.</A><br>
+<A NAME=4.1.172>But no more sights!--Where are these gentlemen?</A><br>
+<A NAME=4.1.173>Come, bring me where they are.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE II. Fife. Macduff's castle.</h3>
+<p><blockquote>
+<i>Enter LADY MACDUFF, her Son, and ROSS</i>
+</blockquote>
+
+<A NAME=speech1><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.1>What had he done, to make him fly the land?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.2.2>You must have patience, madam.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.3>He had none:</A><br>
+<A NAME=4.2.4>His flight was madness: when our actions do not,</A><br>
+<A NAME=4.2.5>Our fears do make us traitors.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.2.6>You know not</A><br>
+<A NAME=4.2.7>Whether it was his wisdom or his fear.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.8>Wisdom! to leave his wife, to leave his babes,</A><br>
+<A NAME=4.2.9>His mansion and his titles in a place</A><br>
+<A NAME=4.2.10>From whence himself does fly? He loves us not;</A><br>
+<A NAME=4.2.11>He wants the natural touch: for the poor wren,</A><br>
+<A NAME=4.2.12>The most diminutive of birds, will fight,</A><br>
+<A NAME=4.2.13>Her young ones in her nest, against the owl.</A><br>
+<A NAME=4.2.14>All is the fear and nothing is the love;</A><br>
+<A NAME=4.2.15>As little is the wisdom, where the flight</A><br>
+<A NAME=4.2.16>So runs against all reason.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.2.17>My dearest coz,</A><br>
+<A NAME=4.2.18>I pray you, school yourself: but for your husband,</A><br>
+<A NAME=4.2.19>He is noble, wise, judicious, and best knows</A><br>
+<A NAME=4.2.20>The fits o' the season. I dare not speak</A><br>
+<A NAME=4.2.21>much further;</A><br>
+<A NAME=4.2.22>But cruel are the times, when we are traitors</A><br>
+<A NAME=4.2.23>And do not know ourselves, when we hold rumour</A><br>
+<A NAME=4.2.24>From what we fear, yet know not what we fear,</A><br>
+<A NAME=4.2.25>But float upon a wild and violent sea</A><br>
+<A NAME=4.2.26>Each way and move. I take my leave of you:</A><br>
+<A NAME=4.2.27>Shall not be long but I'll be here again:</A><br>
+<A NAME=4.2.28>Things at the worst will cease, or else climb upward</A><br>
+<A NAME=4.2.29>To what they were before. My pretty cousin,</A><br>
+<A NAME=4.2.30>Blessing upon you!</A><br>
+</blockquote>
+
+<A NAME=speech7><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.31>Father'd he is, and yet he's fatherless.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.2.32>I am so much a fool, should I stay longer,</A><br>
+<A NAME=4.2.33>It would be my disgrace and your discomfort:</A><br>
+<A NAME=4.2.34>I take my leave at once.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+
+<A NAME=speech9><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.35>Sirrah, your father's dead;</A><br>
+<A NAME=4.2.36>And what will you do now? How will you live?</A><br>
+</blockquote>
+
+<A NAME=speech10><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.37>As birds do, mother.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.38>What, with worms and flies?</A><br>
+</blockquote>
+
+<A NAME=speech12><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.39>With what I get, I mean; and so do they.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.40>Poor bird! thou'ldst never fear the net nor lime,</A><br>
+<A NAME=4.2.41>The pitfall nor the gin.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.42>Why should I, mother? Poor birds they are not set for.</A><br>
+<A NAME=4.2.43>My father is not dead, for all your saying.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.44>Yes, he is dead; how wilt thou do for a father?</A><br>
+</blockquote>
+
+<A NAME=speech16><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.45>Nay, how will you do for a husband?</A><br>
+</blockquote>
+
+<A NAME=speech17><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.46>Why, I can buy me twenty at any market.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.47>Then you'll buy 'em to sell again.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.48>Thou speak'st with all thy wit: and yet, i' faith,</A><br>
+<A NAME=4.2.49>With wit enough for thee.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.50>Was my father a traitor, mother?</A><br>
+</blockquote>
+
+<A NAME=speech21><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.51>Ay, that he was.</A><br>
+</blockquote>
+
+<A NAME=speech22><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.52>What is a traitor?</A><br>
+</blockquote>
+
+<A NAME=speech23><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.53>Why, one that swears and lies.</A><br>
+</blockquote>
+
+<A NAME=speech24><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.54>And be all traitors that do so?</A><br>
+</blockquote>
+
+<A NAME=speech25><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.55>Every one that does so is a traitor, and must be hanged.</A><br>
+</blockquote>
+
+<A NAME=speech26><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.56>And must they all be hanged that swear and lie?</A><br>
+</blockquote>
+
+<A NAME=speech27><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.57>Every one.</A><br>
+</blockquote>
+
+<A NAME=speech28><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.58>Who must hang them?</A><br>
+</blockquote>
+
+<A NAME=speech29><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.59>Why, the honest men.</A><br>
+</blockquote>
+
+<A NAME=speech30><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.60>Then the liars and swearers are fools,</A><br>
+<A NAME=4.2.61>for there are liars and swearers enow to beat</A><br>
+<A NAME=4.2.62>the honest men and hang up them.</A><br>
+</blockquote>
+
+<A NAME=speech31><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.63>Now, God help thee, poor monkey!</A><br>
+<A NAME=4.2.64>But how wilt thou do for a father?</A><br>
+</blockquote>
+
+<A NAME=speech32><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.65>If he were dead, you'ld weep for</A><br>
+<A NAME=4.2.66>him: if you would not, it were a good sign</A><br>
+<A NAME=4.2.67>that I should quickly have a new father.</A><br>
+</blockquote>
+
+<A NAME=speech33><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.68>Poor prattler, how thou talk'st!</A><br>
+<p><i>Enter a Messenger</i></p>
+</blockquote>
+
+<A NAME=speech34><b>Messenger</b></a>
+<blockquote>
+<A NAME=4.2.69>Bless you, fair dame! I am not to you known,</A><br>
+<A NAME=4.2.70>Though in your state of honour I am perfect.</A><br>
+<A NAME=4.2.71>I doubt some danger does approach you nearly:</A><br>
+<A NAME=4.2.72>If you will take a homely man's advice,</A><br>
+<A NAME=4.2.73>Be not found here; hence, with your little ones.</A><br>
+<A NAME=4.2.74>To fright you thus, methinks, I am too savage;</A><br>
+<A NAME=4.2.75>To do worse to you were fell cruelty,</A><br>
+<A NAME=4.2.76>Which is too nigh your person. Heaven preserve you!</A><br>
+<A NAME=4.2.77>I dare abide no longer.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+
+<A NAME=speech35><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.78>Whither should I fly?</A><br>
+<A NAME=4.2.79>I have done no harm. But I remember now</A><br>
+<A NAME=4.2.80>I am in this earthly world; where to do harm</A><br>
+<A NAME=4.2.81>Is often laudable, to do good sometime</A><br>
+<A NAME=4.2.82>Accounted dangerous folly: why then, alas,</A><br>
+<A NAME=4.2.83>Do I put up that womanly defence,</A><br>
+<A NAME=4.2.84>To say I have done no harm?</A><br>
+<p><i>Enter Murderers</i></p>
+<A NAME=4.2.85>What are these faces?</A><br>
+</blockquote>
+
+<A NAME=speech36><b>First Murderer</b></a>
+<blockquote>
+<A NAME=4.2.86>Where is your husband?</A><br>
+</blockquote>
+
+<A NAME=speech37><b>LADY MACDUFF</b></a>
+<blockquote>
+<A NAME=4.2.87>I hope, in no place so unsanctified</A><br>
+<A NAME=4.2.88>Where such as thou mayst find him.</A><br>
+</blockquote>
+
+<A NAME=speech38><b>First Murderer</b></a>
+<blockquote>
+<A NAME=4.2.89>He's a traitor.</A><br>
+</blockquote>
+
+<A NAME=speech39><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.90>Thou liest, thou shag-hair'd villain!</A><br>
+</blockquote>
+
+<A NAME=speech40><b>First Murderer</b></a>
+<blockquote>
+<A NAME=4.2.91>What, you egg!</A><br>
+<p><i>Stabbing him</i></p>
+<A NAME=4.2.92>Young fry of treachery!</A><br>
+</blockquote>
+
+<A NAME=speech41><b>Son</b></a>
+<blockquote>
+<A NAME=4.2.93>He has kill'd me, mother:</A><br>
+<A NAME=4.2.94>Run away, I pray you!</A><br>
+<p><i>Dies</i></p>
+<p><i>Exit LADY MACDUFF, crying 'Murder!' Exeunt Murderers, following her</i></p>
+</blockquote>
+<h3>SCENE III. England. Before the King's palace.</h3>
+<p><blockquote>
+<i>Enter MALCOLM and MACDUFF</i>
+</blockquote>
+
+<A NAME=speech1><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.1>Let us seek out some desolate shade, and there</A><br>
+<A NAME=4.3.2>Weep our sad bosoms empty.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.3>Let us rather</A><br>
+<A NAME=4.3.4>Hold fast the mortal sword, and like good men</A><br>
+<A NAME=4.3.5>Bestride our down-fall'n birthdom: each new morn</A><br>
+<A NAME=4.3.6>New widows howl, new orphans cry, new sorrows</A><br>
+<A NAME=4.3.7>Strike heaven on the face, that it resounds</A><br>
+<A NAME=4.3.8>As if it felt with Scotland and yell'd out</A><br>
+<A NAME=4.3.9>Like syllable of dolour.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.10>What I believe I'll wail,</A><br>
+<A NAME=4.3.11>What know believe, and what I can redress,</A><br>
+<A NAME=4.3.12>As I shall find the time to friend, I will.</A><br>
+<A NAME=4.3.13>What you have spoke, it may be so perchance.</A><br>
+<A NAME=4.3.14>This tyrant, whose sole name blisters our tongues,</A><br>
+<A NAME=4.3.15>Was once thought honest: you have loved him well.</A><br>
+<A NAME=4.3.16>He hath not touch'd you yet. I am young;</A><br>
+<A NAME=4.3.17>but something</A><br>
+<A NAME=4.3.18>You may deserve of him through me, and wisdom</A><br>
+<A NAME=4.3.19>To offer up a weak poor innocent lamb</A><br>
+<A NAME=4.3.20>To appease an angry god.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.21>I am not treacherous.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.22>But Macbeth is.</A><br>
+<A NAME=4.3.23>A good and virtuous nature may recoil</A><br>
+<A NAME=4.3.24>In an imperial charge. But I shall crave</A><br>
+<A NAME=4.3.25>your pardon;</A><br>
+<A NAME=4.3.26>That which you are my thoughts cannot transpose:</A><br>
+<A NAME=4.3.27>Angels are bright still, though the brightest fell;</A><br>
+<A NAME=4.3.28>Though all things foul would wear the brows of grace,</A><br>
+<A NAME=4.3.29>Yet grace must still look so.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.30>I have lost my hopes.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.31>Perchance even there where I did find my doubts.</A><br>
+<A NAME=4.3.32>Why in that rawness left you wife and child,</A><br>
+<A NAME=4.3.33>Those precious motives, those strong knots of love,</A><br>
+<A NAME=4.3.34>Without leave-taking? I pray you,</A><br>
+<A NAME=4.3.35>Let not my jealousies be your dishonours,</A><br>
+<A NAME=4.3.36>But mine own safeties. You may be rightly just,</A><br>
+<A NAME=4.3.37>Whatever I shall think.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.38>Bleed, bleed, poor country!</A><br>
+<A NAME=4.3.39>Great tyranny! lay thou thy basis sure,</A><br>
+<A NAME=4.3.40>For goodness dare not cheque thee: wear thou</A><br>
+<A NAME=4.3.41>thy wrongs;</A><br>
+<A NAME=4.3.42>The title is affeer'd! Fare thee well, lord:</A><br>
+<A NAME=4.3.43>I would not be the villain that thou think'st</A><br>
+<A NAME=4.3.44>For the whole space that's in the tyrant's grasp,</A><br>
+<A NAME=4.3.45>And the rich East to boot.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.46>Be not offended:</A><br>
+<A NAME=4.3.47>I speak not as in absolute fear of you.</A><br>
+<A NAME=4.3.48>I think our country sinks beneath the yoke;</A><br>
+<A NAME=4.3.49>It weeps, it bleeds; and each new day a gash</A><br>
+<A NAME=4.3.50>Is added to her wounds: I think withal</A><br>
+<A NAME=4.3.51>There would be hands uplifted in my right;</A><br>
+<A NAME=4.3.52>And here from gracious England have I offer</A><br>
+<A NAME=4.3.53>Of goodly thousands: but, for all this,</A><br>
+<A NAME=4.3.54>When I shall tread upon the tyrant's head,</A><br>
+<A NAME=4.3.55>Or wear it on my sword, yet my poor country</A><br>
+<A NAME=4.3.56>Shall have more vices than it had before,</A><br>
+<A NAME=4.3.57>More suffer and more sundry ways than ever,</A><br>
+<A NAME=4.3.58>By him that shall succeed.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.59>What should he be?</A><br>
+</blockquote>
+
+<A NAME=speech11><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.60>It is myself I mean: in whom I know</A><br>
+<A NAME=4.3.61>All the particulars of vice so grafted</A><br>
+<A NAME=4.3.62>That, when they shall be open'd, black Macbeth</A><br>
+<A NAME=4.3.63>Will seem as pure as snow, and the poor state</A><br>
+<A NAME=4.3.64>Esteem him as a lamb, being compared</A><br>
+<A NAME=4.3.65>With my confineless harms.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.66>Not in the legions</A><br>
+<A NAME=4.3.67>Of horrid hell can come a devil more damn'd</A><br>
+<A NAME=4.3.68>In evils to top Macbeth.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.69>I grant him bloody,</A><br>
+<A NAME=4.3.70>Luxurious, avaricious, false, deceitful,</A><br>
+<A NAME=4.3.71>Sudden, malicious, smacking of every sin</A><br>
+<A NAME=4.3.72>That has a name: but there's no bottom, none,</A><br>
+<A NAME=4.3.73>In my voluptuousness: your wives, your daughters,</A><br>
+<A NAME=4.3.74>Your matrons and your maids, could not fill up</A><br>
+<A NAME=4.3.75>The cistern of my lust, and my desire</A><br>
+<A NAME=4.3.76>All continent impediments would o'erbear</A><br>
+<A NAME=4.3.77>That did oppose my will: better Macbeth</A><br>
+<A NAME=4.3.78>Than such an one to reign.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.79>Boundless intemperance</A><br>
+<A NAME=4.3.80>In nature is a tyranny; it hath been</A><br>
+<A NAME=4.3.81>The untimely emptying of the happy throne</A><br>
+<A NAME=4.3.82>And fall of many kings. But fear not yet</A><br>
+<A NAME=4.3.83>To take upon you what is yours: you may</A><br>
+<A NAME=4.3.84>Convey your pleasures in a spacious plenty,</A><br>
+<A NAME=4.3.85>And yet seem cold, the time you may so hoodwink.</A><br>
+<A NAME=4.3.86>We have willing dames enough: there cannot be</A><br>
+<A NAME=4.3.87>That vulture in you, to devour so many</A><br>
+<A NAME=4.3.88>As will to greatness dedicate themselves,</A><br>
+<A NAME=4.3.89>Finding it so inclined.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.90>With this there grows</A><br>
+<A NAME=4.3.91>In my most ill-composed affection such</A><br>
+<A NAME=4.3.92>A stanchless avarice that, were I king,</A><br>
+<A NAME=4.3.93>I should cut off the nobles for their lands,</A><br>
+<A NAME=4.3.94>Desire his jewels and this other's house:</A><br>
+<A NAME=4.3.95>And my more-having would be as a sauce</A><br>
+<A NAME=4.3.96>To make me hunger more; that I should forge</A><br>
+<A NAME=4.3.97>Quarrels unjust against the good and loyal,</A><br>
+<A NAME=4.3.98>Destroying them for wealth.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.99>This avarice</A><br>
+<A NAME=4.3.100>Sticks deeper, grows with more pernicious root</A><br>
+<A NAME=4.3.101>Than summer-seeming lust, and it hath been</A><br>
+<A NAME=4.3.102>The sword of our slain kings: yet do not fear;</A><br>
+<A NAME=4.3.103>Scotland hath foisons to fill up your will.</A><br>
+<A NAME=4.3.104>Of your mere own: all these are portable,</A><br>
+<A NAME=4.3.105>With other graces weigh'd.</A><br>
+</blockquote>
+
+<A NAME=speech17><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.106>But I have none: the king-becoming graces,</A><br>
+<A NAME=4.3.107>As justice, verity, temperance, stableness,</A><br>
+<A NAME=4.3.108>Bounty, perseverance, mercy, lowliness,</A><br>
+<A NAME=4.3.109>Devotion, patience, courage, fortitude,</A><br>
+<A NAME=4.3.110>I have no relish of them, but abound</A><br>
+<A NAME=4.3.111>In the division of each several crime,</A><br>
+<A NAME=4.3.112>Acting it many ways. Nay, had I power, I should</A><br>
+<A NAME=4.3.113>Pour the sweet milk of concord into hell,</A><br>
+<A NAME=4.3.114>Uproar the universal peace, confound</A><br>
+<A NAME=4.3.115>All unity on earth.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.116>O Scotland, Scotland!</A><br>
+</blockquote>
+
+<A NAME=speech19><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.117>If such a one be fit to govern, speak:</A><br>
+<A NAME=4.3.118>I am as I have spoken.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.119>Fit to govern!</A><br>
+<A NAME=4.3.120>No, not to live. O nation miserable,</A><br>
+<A NAME=4.3.121>With an untitled tyrant bloody-scepter'd,</A><br>
+<A NAME=4.3.122>When shalt thou see thy wholesome days again,</A><br>
+<A NAME=4.3.123>Since that the truest issue of thy throne</A><br>
+<A NAME=4.3.124>By his own interdiction stands accursed,</A><br>
+<A NAME=4.3.125>And does blaspheme his breed? Thy royal father</A><br>
+<A NAME=4.3.126>Was a most sainted king: the queen that bore thee,</A><br>
+<A NAME=4.3.127>Oftener upon her knees than on her feet,</A><br>
+<A NAME=4.3.128>Died every day she lived. Fare thee well!</A><br>
+<A NAME=4.3.129>These evils thou repeat'st upon thyself</A><br>
+<A NAME=4.3.130>Have banish'd me from Scotland. O my breast,</A><br>
+<A NAME=4.3.131>Thy hope ends here!</A><br>
+</blockquote>
+
+<A NAME=speech21><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.132>Macduff, this noble passion,</A><br>
+<A NAME=4.3.133>Child of integrity, hath from my soul</A><br>
+<A NAME=4.3.134>Wiped the black scruples, reconciled my thoughts</A><br>
+<A NAME=4.3.135>To thy good truth and honour. Devilish Macbeth</A><br>
+<A NAME=4.3.136>By many of these trains hath sought to win me</A><br>
+<A NAME=4.3.137>Into his power, and modest wisdom plucks me</A><br>
+<A NAME=4.3.138>From over-credulous haste: but God above</A><br>
+<A NAME=4.3.139>Deal between thee and me! for even now</A><br>
+<A NAME=4.3.140>I put myself to thy direction, and</A><br>
+<A NAME=4.3.141>Unspeak mine own detraction, here abjure</A><br>
+<A NAME=4.3.142>The taints and blames I laid upon myself,</A><br>
+<A NAME=4.3.143>For strangers to my nature. I am yet</A><br>
+<A NAME=4.3.144>Unknown to woman, never was forsworn,</A><br>
+<A NAME=4.3.145>Scarcely have coveted what was mine own,</A><br>
+<A NAME=4.3.146>At no time broke my faith, would not betray</A><br>
+<A NAME=4.3.147>The devil to his fellow and delight</A><br>
+<A NAME=4.3.148>No less in truth than life: my first false speaking</A><br>
+<A NAME=4.3.149>Was this upon myself: what I am truly,</A><br>
+<A NAME=4.3.150>Is thine and my poor country's to command:</A><br>
+<A NAME=4.3.151>Whither indeed, before thy here-approach,</A><br>
+<A NAME=4.3.152>Old Siward, with ten thousand warlike men,</A><br>
+<A NAME=4.3.153>Already at a point, was setting forth.</A><br>
+<A NAME=4.3.154>Now we'll together; and the chance of goodness</A><br>
+<A NAME=4.3.155>Be like our warranted quarrel! Why are you silent?</A><br>
+</blockquote>
+
+<A NAME=speech22><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.156>Such welcome and unwelcome things at once</A><br>
+<A NAME=4.3.157>'Tis hard to reconcile.</A><br>
+<p><i>Enter a Doctor</i></p>
+</blockquote>
+
+<A NAME=speech23><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.158>Well; more anon.--Comes the king forth, I pray you?</A><br>
+</blockquote>
+
+<A NAME=speech24><b>Doctor</b></a>
+<blockquote>
+<A NAME=4.3.159>Ay, sir; there are a crew of wretched souls</A><br>
+<A NAME=4.3.160>That stay his cure: their malady convinces</A><br>
+<A NAME=4.3.161>The great assay of art; but at his touch--</A><br>
+<A NAME=4.3.162>Such sanctity hath heaven given his hand--</A><br>
+<A NAME=4.3.163>They presently amend.</A><br>
+</blockquote>
+
+<A NAME=speech25><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.164>I thank you, doctor.</A><br>
+<p><i>Exit Doctor</i></p>
+</blockquote>
+
+<A NAME=speech26><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.165>What's the disease he means?</A><br>
+</blockquote>
+
+<A NAME=speech27><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.166>'Tis call'd the evil:</A><br>
+<A NAME=4.3.167>A most miraculous work in this good king;</A><br>
+<A NAME=4.3.168>Which often, since my here-remain in England,</A><br>
+<A NAME=4.3.169>I have seen him do. How he solicits heaven,</A><br>
+<A NAME=4.3.170>Himself best knows: but strangely-visited people,</A><br>
+<A NAME=4.3.171>All swoln and ulcerous, pitiful to the eye,</A><br>
+<A NAME=4.3.172>The mere despair of surgery, he cures,</A><br>
+<A NAME=4.3.173>Hanging a golden stamp about their necks,</A><br>
+<A NAME=4.3.174>Put on with holy prayers: and 'tis spoken,</A><br>
+<A NAME=4.3.175>To the succeeding royalty he leaves</A><br>
+<A NAME=4.3.176>The healing benediction. With this strange virtue,</A><br>
+<A NAME=4.3.177>He hath a heavenly gift of prophecy,</A><br>
+<A NAME=4.3.178>And sundry blessings hang about his throne,</A><br>
+<A NAME=4.3.179>That speak him full of grace.</A><br>
+<p><i>Enter ROSS</i></p>
+</blockquote>
+
+<A NAME=speech28><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.180>See, who comes here?</A><br>
+</blockquote>
+
+<A NAME=speech29><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.181>My countryman; but yet I know him not.</A><br>
+</blockquote>
+
+<A NAME=speech30><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.182>My ever-gentle cousin, welcome hither.</A><br>
+</blockquote>
+
+<A NAME=speech31><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.183>I know him now. Good God, betimes remove</A><br>
+<A NAME=4.3.184>The means that makes us strangers!</A><br>
+</blockquote>
+
+<A NAME=speech32><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.185>Sir, amen.</A><br>
+</blockquote>
+
+<A NAME=speech33><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.186>Stands Scotland where it did?</A><br>
+</blockquote>
+
+<A NAME=speech34><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.187>Alas, poor country!</A><br>
+<A NAME=4.3.188>Almost afraid to know itself. It cannot</A><br>
+<A NAME=4.3.189>Be call'd our mother, but our grave; where nothing,</A><br>
+<A NAME=4.3.190>But who knows nothing, is once seen to smile;</A><br>
+<A NAME=4.3.191>Where sighs and groans and shrieks that rend the air</A><br>
+<A NAME=4.3.192>Are made, not mark'd; where violent sorrow seems</A><br>
+<A NAME=4.3.193>A modern ecstasy; the dead man's knell</A><br>
+<A NAME=4.3.194>Is there scarce ask'd for who; and good men's lives</A><br>
+<A NAME=4.3.195>Expire before the flowers in their caps,</A><br>
+<A NAME=4.3.196>Dying or ere they sicken.</A><br>
+</blockquote>
+
+<A NAME=speech35><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.197>O, relation</A><br>
+<A NAME=4.3.198>Too nice, and yet too true!</A><br>
+</blockquote>
+
+<A NAME=speech36><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.199>What's the newest grief?</A><br>
+</blockquote>
+
+<A NAME=speech37><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.200>That of an hour's age doth hiss the speaker:</A><br>
+<A NAME=4.3.201>Each minute teems a new one.</A><br>
+</blockquote>
+
+<A NAME=speech38><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.202>How does my wife?</A><br>
+</blockquote>
+
+<A NAME=speech39><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.203>Why, well.</A><br>
+</blockquote>
+
+<A NAME=speech40><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.204> And all my children?</A><br>
+</blockquote>
+
+<A NAME=speech41><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.205>Well too.</A><br>
+</blockquote>
+
+<A NAME=speech42><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.206>The tyrant has not batter'd at their peace?</A><br>
+</blockquote>
+
+<A NAME=speech43><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.207>No; they were well at peace when I did leave 'em.</A><br>
+</blockquote>
+
+<A NAME=speech44><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.208>But not a niggard of your speech: how goes't?</A><br>
+</blockquote>
+
+<A NAME=speech45><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.209>When I came hither to transport the tidings,</A><br>
+<A NAME=4.3.210>Which I have heavily borne, there ran a rumour</A><br>
+<A NAME=4.3.211>Of many worthy fellows that were out;</A><br>
+<A NAME=4.3.212>Which was to my belief witness'd the rather,</A><br>
+<A NAME=4.3.213>For that I saw the tyrant's power a-foot:</A><br>
+<A NAME=4.3.214>Now is the time of help; your eye in Scotland</A><br>
+<A NAME=4.3.215>Would create soldiers, make our women fight,</A><br>
+<A NAME=4.3.216>To doff their dire distresses.</A><br>
+</blockquote>
+
+<A NAME=speech46><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.217>Be't their comfort</A><br>
+<A NAME=4.3.218>We are coming thither: gracious England hath</A><br>
+<A NAME=4.3.219>Lent us good Siward and ten thousand men;</A><br>
+<A NAME=4.3.220>An older and a better soldier none</A><br>
+<A NAME=4.3.221>That Christendom gives out.</A><br>
+</blockquote>
+
+<A NAME=speech47><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.222>Would I could answer</A><br>
+<A NAME=4.3.223>This comfort with the like! But I have words</A><br>
+<A NAME=4.3.224>That would be howl'd out in the desert air,</A><br>
+<A NAME=4.3.225>Where hearing should not latch them.</A><br>
+</blockquote>
+
+<A NAME=speech48><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.226>What concern they?</A><br>
+<A NAME=4.3.227>The general cause? or is it a fee-grief</A><br>
+<A NAME=4.3.228>Due to some single breast?</A><br>
+</blockquote>
+
+<A NAME=speech49><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.229>No mind that's honest</A><br>
+<A NAME=4.3.230>But in it shares some woe; though the main part</A><br>
+<A NAME=4.3.231>Pertains to you alone.</A><br>
+</blockquote>
+
+<A NAME=speech50><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.232>If it be mine,</A><br>
+<A NAME=4.3.233>Keep it not from me, quickly let me have it.</A><br>
+</blockquote>
+
+<A NAME=speech51><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.234>Let not your ears despise my tongue for ever,</A><br>
+<A NAME=4.3.235>Which shall possess them with the heaviest sound</A><br>
+<A NAME=4.3.236>That ever yet they heard.</A><br>
+</blockquote>
+
+<A NAME=speech52><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.237>Hum! I guess at it.</A><br>
+</blockquote>
+
+<A NAME=speech53><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.238>Your castle is surprised; your wife and babes</A><br>
+<A NAME=4.3.239>Savagely slaughter'd: to relate the manner,</A><br>
+<A NAME=4.3.240>Were, on the quarry of these murder'd deer,</A><br>
+<A NAME=4.3.241>To add the death of you.</A><br>
+</blockquote>
+
+<A NAME=speech54><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.242>Merciful heaven!</A><br>
+<A NAME=4.3.243>What, man! ne'er pull your hat upon your brows;</A><br>
+<A NAME=4.3.244>Give sorrow words: the grief that does not speak</A><br>
+<A NAME=4.3.245>Whispers the o'er-fraught heart and bids it break.</A><br>
+</blockquote>
+
+<A NAME=speech55><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.246>My children too?</A><br>
+</blockquote>
+
+<A NAME=speech56><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.247> Wife, children, servants, all</A><br>
+<A NAME=4.3.248>That could be found.</A><br>
+</blockquote>
+
+<A NAME=speech57><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.249>And I must be from thence!</A><br>
+<A NAME=4.3.250>My wife kill'd too?</A><br>
+</blockquote>
+
+<A NAME=speech58><b>ROSS</b></a>
+<blockquote>
+<A NAME=4.3.251>I have said.</A><br>
+</blockquote>
+
+<A NAME=speech59><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.252>Be comforted:</A><br>
+<A NAME=4.3.253>Let's make us medicines of our great revenge,</A><br>
+<A NAME=4.3.254>To cure this deadly grief.</A><br>
+</blockquote>
+
+<A NAME=speech60><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.255>He has no children. All my pretty ones?</A><br>
+<A NAME=4.3.256>Did you say all? O hell-kite! All?</A><br>
+<A NAME=4.3.257>What, all my pretty chickens and their dam</A><br>
+<A NAME=4.3.258>At one fell swoop?</A><br>
+</blockquote>
+
+<A NAME=speech61><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.259>Dispute it like a man.</A><br>
+</blockquote>
+
+<A NAME=speech62><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.260>I shall do so;</A><br>
+<A NAME=4.3.261>But I must also feel it as a man:</A><br>
+<A NAME=4.3.262>I cannot but remember such things were,</A><br>
+<A NAME=4.3.263>That were most precious to me. Did heaven look on,</A><br>
+<A NAME=4.3.264>And would not take their part? Sinful Macduff,</A><br>
+<A NAME=4.3.265>They were all struck for thee! naught that I am,</A><br>
+<A NAME=4.3.266>Not for their own demerits, but for mine,</A><br>
+<A NAME=4.3.267>Fell slaughter on their souls. Heaven rest them now!</A><br>
+</blockquote>
+
+<A NAME=speech63><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.268>Be this the whetstone of your sword: let grief</A><br>
+<A NAME=4.3.269>Convert to anger; blunt not the heart, enrage it.</A><br>
+</blockquote>
+
+<A NAME=speech64><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=4.3.270>O, I could play the woman with mine eyes</A><br>
+<A NAME=4.3.271>And braggart with my tongue! But, gentle heavens,</A><br>
+<A NAME=4.3.272>Cut short all intermission; front to front</A><br>
+<A NAME=4.3.273>Bring thou this fiend of Scotland and myself;</A><br>
+<A NAME=4.3.274>Within my sword's length set him; if he 'scape,</A><br>
+<A NAME=4.3.275>Heaven forgive him too!</A><br>
+</blockquote>
+
+<A NAME=speech65><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=4.3.276>This tune goes manly.</A><br>
+<A NAME=4.3.277>Come, go we to the king; our power is ready;</A><br>
+<A NAME=4.3.278>Our lack is nothing but our leave; Macbeth</A><br>
+<A NAME=4.3.279>Is ripe for shaking, and the powers above</A><br>
+<A NAME=4.3.280>Put on their instruments. Receive what cheer you may:</A><br>
+<A NAME=4.3.281>The night is long that never finds the day.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote><p>
+<H3>ACT V</h3>
+<h3>SCENE I. Dunsinane. Ante-room in the castle.</h3>
+<p><blockquote>
+<i>Enter a Doctor of Physic and a Waiting-Gentlewoman</i>
+</blockquote>
+
+<A NAME=speech1><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.1>I have two nights watched with you, but can perceive</A><br>
+<A NAME=5.1.2>no truth in your report. When was it she last walked?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.3>Since his majesty went into the field, I have seen</A><br>
+<A NAME=5.1.4>her rise from her bed, throw her night-gown upon</A><br>
+<A NAME=5.1.5>her, unlock her closet, take forth paper, fold it,</A><br>
+<A NAME=5.1.6>write upon't, read it, afterwards seal it, and again</A><br>
+<A NAME=5.1.7>return to bed; yet all this while in a most fast sleep.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.8>A great perturbation in nature, to receive at once</A><br>
+<A NAME=5.1.9>the benefit of sleep, and do the effects of</A><br>
+<A NAME=5.1.10>watching! In this slumbery agitation, besides her</A><br>
+<A NAME=5.1.11>walking and other actual performances, what, at any</A><br>
+<A NAME=5.1.12>time, have you heard her say?</A><br>
+</blockquote>
+
+<A NAME=speech4><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.13>That, sir, which I will not report after her.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.14>You may to me: and 'tis most meet you should.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.15>Neither to you nor any one; having no witness to</A><br>
+<A NAME=5.1.16>confirm my speech.</A><br>
+<p><i>Enter LADY MACBETH, with a taper</i></p>
+<A NAME=5.1.17>Lo you, here she comes! This is her very guise;</A><br>
+<A NAME=5.1.18>and, upon my life, fast asleep. Observe her; stand close.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.19>How came she by that light?</A><br>
+</blockquote>
+
+<A NAME=speech8><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.20>Why, it stood by her: she has light by her</A><br>
+<A NAME=5.1.21>continually; 'tis her command.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.22>You see, her eyes are open.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.23>Ay, but their sense is shut.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.24>What is it she does now? Look, how she rubs her hands.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.25>It is an accustomed action with her, to seem thus</A><br>
+<A NAME=5.1.26>washing her hands: I have known her continue in</A><br>
+<A NAME=5.1.27>this a quarter of an hour.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=5.1.28>Yet here's a spot.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.29>Hark! she speaks: I will set down what comes from</A><br>
+<A NAME=5.1.30>her, to satisfy my remembrance the more strongly.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=5.1.31>Out, damned spot! out, I say!--One: two: why,</A><br>
+<A NAME=5.1.32>then, 'tis time to do't.--Hell is murky!--Fie, my</A><br>
+<A NAME=5.1.33>lord, fie! a soldier, and afeard? What need we</A><br>
+<A NAME=5.1.34>fear who knows it, when none can call our power to</A><br>
+<A NAME=5.1.35>account?--Yet who would have thought the old man</A><br>
+<A NAME=5.1.36>to have had so much blood in him.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.37>Do you mark that?</A><br>
+</blockquote>
+
+<A NAME=speech17><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=5.1.38>The thane of Fife had a wife: where is she now?--</A><br>
+<A NAME=5.1.39>What, will these hands ne'er be clean?--No more o'</A><br>
+<A NAME=5.1.40>that, my lord, no more o' that: you mar all with</A><br>
+<A NAME=5.1.41>this starting.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.42>Go to, go to; you have known what you should not.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.43>She has spoke what she should not, I am sure of</A><br>
+<A NAME=5.1.44>that: heaven knows what she has known.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=5.1.45>Here's the smell of the blood still: all the</A><br>
+<A NAME=5.1.46>perfumes of Arabia will not sweeten this little</A><br>
+<A NAME=5.1.47>hand. Oh, oh, oh!</A><br>
+</blockquote>
+
+<A NAME=speech21><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.48>What a sigh is there! The heart is sorely charged.</A><br>
+</blockquote>
+
+<A NAME=speech22><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.49>I would not have such a heart in my bosom for the</A><br>
+<A NAME=5.1.50>dignity of the whole body.</A><br>
+</blockquote>
+
+<A NAME=speech23><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.51>Well, well, well,--</A><br>
+</blockquote>
+
+<A NAME=speech24><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.52>Pray God it be, sir.</A><br>
+</blockquote>
+
+<A NAME=speech25><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.53>This disease is beyond my practise: yet I have known</A><br>
+<A NAME=5.1.54>those which have walked in their sleep who have died</A><br>
+<A NAME=5.1.55>holily in their beds.</A><br>
+</blockquote>
+
+<A NAME=speech26><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=5.1.56>Wash your hands, put on your nightgown; look not so</A><br>
+<A NAME=5.1.57>pale.--I tell you yet again, Banquo's buried; he</A><br>
+<A NAME=5.1.58>cannot come out on's grave.</A><br>
+</blockquote>
+
+<A NAME=speech27><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.59>Even so?</A><br>
+</blockquote>
+
+<A NAME=speech28><b>LADY MACBETH</b></a>
+<blockquote>
+<A NAME=5.1.60>To bed, to bed! there's knocking at the gate:</A><br>
+<A NAME=5.1.61>come, come, come, come, give me your hand. What's</A><br>
+<A NAME=5.1.62>done cannot be undone.--To bed, to bed, to bed!</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+
+<A NAME=speech29><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.63>Will she go now to bed?</A><br>
+</blockquote>
+
+<A NAME=speech30><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.64>Directly.</A><br>
+</blockquote>
+
+<A NAME=speech31><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.1.65>Foul whisperings are abroad: unnatural deeds</A><br>
+<A NAME=5.1.66>Do breed unnatural troubles: infected minds</A><br>
+<A NAME=5.1.67>To their deaf pillows will discharge their secrets:</A><br>
+<A NAME=5.1.68>More needs she the divine than the physician.</A><br>
+<A NAME=5.1.69>God, God forgive us all! Look after her;</A><br>
+<A NAME=5.1.70>Remove from her the means of all annoyance,</A><br>
+<A NAME=5.1.71>And still keep eyes upon her. So, good night:</A><br>
+<A NAME=5.1.72>My mind she has mated, and amazed my sight.</A><br>
+<A NAME=5.1.73>I think, but dare not speak.</A><br>
+</blockquote>
+
+<A NAME=speech32><b>Gentlewoman</b></a>
+<blockquote>
+<A NAME=5.1.74>Good night, good doctor.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE II. The country near Dunsinane.</h3>
+<p><blockquote>
+<i>Drum and colours. Enter MENTEITH, CAITHNESS, ANGUS, LENNOX, and Soldiers</i>
+</blockquote>
+
+<A NAME=speech1><b>MENTEITH</b></a>
+<blockquote>
+<A NAME=5.2.1>The English power is near, led on by Malcolm,</A><br>
+<A NAME=5.2.2>His uncle Siward and the good Macduff:</A><br>
+<A NAME=5.2.3>Revenges burn in them; for their dear causes</A><br>
+<A NAME=5.2.4>Would to the bleeding and the grim alarm</A><br>
+<A NAME=5.2.5>Excite the mortified man.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>ANGUS</b></a>
+<blockquote>
+<A NAME=5.2.6>Near Birnam wood</A><br>
+<A NAME=5.2.7>Shall we well meet them; that way are they coming.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>CAITHNESS</b></a>
+<blockquote>
+<A NAME=5.2.8>Who knows if Donalbain be with his brother?</A><br>
+</blockquote>
+
+<A NAME=speech4><b>LENNOX</b></a>
+<blockquote>
+<A NAME=5.2.9>For certain, sir, he is not: I have a file</A><br>
+<A NAME=5.2.10>Of all the gentry: there is Siward's son,</A><br>
+<A NAME=5.2.11>And many unrough youths that even now</A><br>
+<A NAME=5.2.12>Protest their first of manhood.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>MENTEITH</b></a>
+<blockquote>
+<A NAME=5.2.13>What does the tyrant?</A><br>
+</blockquote>
+
+<A NAME=speech6><b>CAITHNESS</b></a>
+<blockquote>
+<A NAME=5.2.14>Great Dunsinane he strongly fortifies:</A><br>
+<A NAME=5.2.15>Some say he's mad; others that lesser hate him</A><br>
+<A NAME=5.2.16>Do call it valiant fury: but, for certain,</A><br>
+<A NAME=5.2.17>He cannot buckle his distemper'd cause</A><br>
+<A NAME=5.2.18>Within the belt of rule.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>ANGUS</b></a>
+<blockquote>
+<A NAME=5.2.19>Now does he feel</A><br>
+<A NAME=5.2.20>His secret murders sticking on his hands;</A><br>
+<A NAME=5.2.21>Now minutely revolts upbraid his faith-breach;</A><br>
+<A NAME=5.2.22>Those he commands move only in command,</A><br>
+<A NAME=5.2.23>Nothing in love: now does he feel his title</A><br>
+<A NAME=5.2.24>Hang loose about him, like a giant's robe</A><br>
+<A NAME=5.2.25>Upon a dwarfish thief.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MENTEITH</b></a>
+<blockquote>
+<A NAME=5.2.26>Who then shall blame</A><br>
+<A NAME=5.2.27>His pester'd senses to recoil and start,</A><br>
+<A NAME=5.2.28>When all that is within him does condemn</A><br>
+<A NAME=5.2.29>Itself for being there?</A><br>
+</blockquote>
+
+<A NAME=speech9><b>CAITHNESS</b></a>
+<blockquote>
+<A NAME=5.2.30>Well, march we on,</A><br>
+<A NAME=5.2.31>To give obedience where 'tis truly owed:</A><br>
+<A NAME=5.2.32>Meet we the medicine of the sickly weal,</A><br>
+<A NAME=5.2.33>And with him pour we in our country's purge</A><br>
+<A NAME=5.2.34>Each drop of us.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>LENNOX</b></a>
+<blockquote>
+<A NAME=5.2.35> Or so much as it needs,</A><br>
+<A NAME=5.2.36>To dew the sovereign flower and drown the weeds.</A><br>
+<A NAME=5.2.37>Make we our march towards Birnam.</A><br>
+<p><i>Exeunt, marching</i></p>
+</blockquote>
+<h3>SCENE III. Dunsinane. A room in the castle.</h3>
+<p><blockquote>
+<i>Enter MACBETH, Doctor, and Attendants</i>
+</blockquote>
+
+<A NAME=speech1><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.1>Bring me no more reports; let them fly all:</A><br>
+<A NAME=5.3.2>Till Birnam wood remove to Dunsinane,</A><br>
+<A NAME=5.3.3>I cannot taint with fear. What's the boy Malcolm?</A><br>
+<A NAME=5.3.4>Was he not born of woman? The spirits that know</A><br>
+<A NAME=5.3.5>All mortal consequences have pronounced me thus:</A><br>
+<A NAME=5.3.6>'Fear not, Macbeth; no man that's born of woman</A><br>
+<A NAME=5.3.7>Shall e'er have power upon thee.' Then fly,</A><br>
+<A NAME=5.3.8>false thanes,</A><br>
+<A NAME=5.3.9>And mingle with the English epicures:</A><br>
+<A NAME=5.3.10>The mind I sway by and the heart I bear</A><br>
+<A NAME=5.3.11>Shall never sag with doubt nor shake with fear.</A><br>
+<p><i>Enter a Servant</i></p>
+<A NAME=5.3.12>The devil damn thee black, thou cream-faced loon!</A><br>
+<A NAME=5.3.13>Where got'st thou that goose look?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>Servant</b></a>
+<blockquote>
+<A NAME=5.3.14>There is ten thousand--</A><br>
+</blockquote>
+
+<A NAME=speech3><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.15>Geese, villain!</A><br>
+</blockquote>
+
+<A NAME=speech4><b>Servant</b></a>
+<blockquote>
+<A NAME=5.3.16>Soldiers, sir.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.17>Go prick thy face, and over-red thy fear,</A><br>
+<A NAME=5.3.18>Thou lily-liver'd boy. What soldiers, patch?</A><br>
+<A NAME=5.3.19>Death of thy soul! those linen cheeks of thine</A><br>
+<A NAME=5.3.20>Are counsellors to fear. What soldiers, whey-face?</A><br>
+</blockquote>
+
+<A NAME=speech6><b>Servant</b></a>
+<blockquote>
+<A NAME=5.3.21>The English force, so please you.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.22>Take thy face hence.</A><br>
+<p><i>Exit Servant</i></p>
+<A NAME=5.3.23>Seyton!--I am sick at heart,</A><br>
+<A NAME=5.3.24>When I behold--Seyton, I say!--This push</A><br>
+<A NAME=5.3.25>Will cheer me ever, or disseat me now.</A><br>
+<A NAME=5.3.26>I have lived long enough: my way of life</A><br>
+<A NAME=5.3.27>Is fall'n into the sear, the yellow leaf;</A><br>
+<A NAME=5.3.28>And that which should accompany old age,</A><br>
+<A NAME=5.3.29>As honour, love, obedience, troops of friends,</A><br>
+<A NAME=5.3.30>I must not look to have; but, in their stead,</A><br>
+<A NAME=5.3.31>Curses, not loud but deep, mouth-honour, breath,</A><br>
+<A NAME=5.3.32>Which the poor heart would fain deny, and dare not. Seyton!</A><br>
+<p><i>Enter SEYTON</i></p>
+</blockquote>
+
+<A NAME=speech8><b>SEYTON</b></a>
+<blockquote>
+<A NAME=5.3.33>What is your gracious pleasure?</A><br>
+</blockquote>
+
+<A NAME=speech9><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.34>What news more?</A><br>
+</blockquote>
+
+<A NAME=speech10><b>SEYTON</b></a>
+<blockquote>
+<A NAME=5.3.35>All is confirm'd, my lord, which was reported.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.36>I'll fight till from my bones my flesh be hack'd.</A><br>
+<A NAME=5.3.37>Give me my armour.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>SEYTON</b></a>
+<blockquote>
+<A NAME=5.3.38>'Tis not needed yet.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.39>I'll put it on.</A><br>
+<A NAME=5.3.40>Send out more horses; skirr the country round;</A><br>
+<A NAME=5.3.41>Hang those that talk of fear. Give me mine armour.</A><br>
+<A NAME=5.3.42>How does your patient, doctor?</A><br>
+</blockquote>
+
+<A NAME=speech14><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.3.43>Not so sick, my lord,</A><br>
+<A NAME=5.3.44>As she is troubled with thick coming fancies,</A><br>
+<A NAME=5.3.45>That keep her from her rest.</A><br>
+</blockquote>
+
+<A NAME=speech15><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.46>Cure her of that.</A><br>
+<A NAME=5.3.47>Canst thou not minister to a mind diseased,</A><br>
+<A NAME=5.3.48>Pluck from the memory a rooted sorrow,</A><br>
+<A NAME=5.3.49>Raze out the written troubles of the brain</A><br>
+<A NAME=5.3.50>And with some sweet oblivious antidote</A><br>
+<A NAME=5.3.51>Cleanse the stuff'd bosom of that perilous stuff</A><br>
+<A NAME=5.3.52>Which weighs upon the heart?</A><br>
+</blockquote>
+
+<A NAME=speech16><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.3.53>Therein the patient</A><br>
+<A NAME=5.3.54>Must minister to himself.</A><br>
+</blockquote>
+
+<A NAME=speech17><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.55>Throw physic to the dogs; I'll none of it.</A><br>
+<A NAME=5.3.56>Come, put mine armour on; give me my staff.</A><br>
+<A NAME=5.3.57>Seyton, send out. Doctor, the thanes fly from me.</A><br>
+<A NAME=5.3.58>Come, sir, dispatch. If thou couldst, doctor, cast</A><br>
+<A NAME=5.3.59>The water of my land, find her disease,</A><br>
+<A NAME=5.3.60>And purge it to a sound and pristine health,</A><br>
+<A NAME=5.3.61>I would applaud thee to the very echo,</A><br>
+<A NAME=5.3.62>That should applaud again.--Pull't off, I say.--</A><br>
+<A NAME=5.3.63>What rhubarb, cyme, or what purgative drug,</A><br>
+<A NAME=5.3.64>Would scour these English hence? Hear'st thou of them?</A><br>
+</blockquote>
+
+<A NAME=speech18><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.3.65>Ay, my good lord; your royal preparation</A><br>
+<A NAME=5.3.66>Makes us hear something.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.3.67>Bring it after me.</A><br>
+<A NAME=5.3.68>I will not be afraid of death and bane,</A><br>
+<A NAME=5.3.69>Till Birnam forest come to Dunsinane.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>Doctor</b></a>
+<blockquote>
+<A NAME=5.3.70>[Aside] Were I from Dunsinane away and clear,</A><br>
+<A NAME=5.3.71>Profit again should hardly draw me here.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE IV. Country near Birnam wood.</h3>
+<p><blockquote>
+<i>Drum and colours. Enter MALCOLM, SIWARD and YOUNG SIWARD, MACDUFF, MENTEITH, CAITHNESS, ANGUS, LENNOX, ROSS, and Soldiers, marching</i>
+</blockquote>
+
+<A NAME=speech1><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.4.1>Cousins, I hope the days are near at hand</A><br>
+<A NAME=5.4.2>That chambers will be safe.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>MENTEITH</b></a>
+<blockquote>
+<A NAME=5.4.3>We doubt it nothing.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.4.4>What wood is this before us?</A><br>
+</blockquote>
+
+<A NAME=speech4><b>MENTEITH</b></a>
+<blockquote>
+<A NAME=5.4.5>The wood of Birnam.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.4.6>Let every soldier hew him down a bough</A><br>
+<A NAME=5.4.7>And bear't before him: thereby shall we shadow</A><br>
+<A NAME=5.4.8>The numbers of our host and make discovery</A><br>
+<A NAME=5.4.9>Err in report of us.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>Soldiers</b></a>
+<blockquote>
+<A NAME=5.4.10>It shall be done.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.4.11>We learn no other but the confident tyrant</A><br>
+<A NAME=5.4.12>Keeps still in Dunsinane, and will endure</A><br>
+<A NAME=5.4.13>Our setting down before 't.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.4.14>'Tis his main hope:</A><br>
+<A NAME=5.4.15>For where there is advantage to be given,</A><br>
+<A NAME=5.4.16>Both more and less have given him the revolt,</A><br>
+<A NAME=5.4.17>And none serve with him but constrained things</A><br>
+<A NAME=5.4.18>Whose hearts are absent too.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=5.4.19>Let our just censures</A><br>
+<A NAME=5.4.20>Attend the true event, and put we on</A><br>
+<A NAME=5.4.21>Industrious soldiership.</A><br>
+</blockquote>
+
+<A NAME=speech10><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.4.22>The time approaches</A><br>
+<A NAME=5.4.23>That will with due decision make us know</A><br>
+<A NAME=5.4.24>What we shall say we have and what we owe.</A><br>
+<A NAME=5.4.25>Thoughts speculative their unsure hopes relate,</A><br>
+<A NAME=5.4.26>But certain issue strokes must arbitrate:</A><br>
+<A NAME=5.4.27>Towards which advance the war.</A><br>
+<p><i>Exeunt, marching</i></p>
+</blockquote>
+<h3>SCENE V. Dunsinane. Within the castle.</h3>
+<p><blockquote>
+<i>Enter MACBETH, SEYTON, and Soldiers, with drum and colours</i>
+</blockquote>
+
+<A NAME=speech1><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.5.1>Hang out our banners on the outward walls;</A><br>
+<A NAME=5.5.2>The cry is still 'They come:' our castle's strength</A><br>
+<A NAME=5.5.3>Will laugh a siege to scorn: here let them lie</A><br>
+<A NAME=5.5.4>Till famine and the ague eat them up:</A><br>
+<A NAME=5.5.5>Were they not forced with those that should be ours,</A><br>
+<A NAME=5.5.6>We might have met them dareful, beard to beard,</A><br>
+<A NAME=5.5.7>And beat them backward home.</A><br>
+<p><i>A cry of women within</i></p>
+<A NAME=5.5.8>What is that noise?</A><br>
+</blockquote>
+
+<A NAME=speech2><b>SEYTON</b></a>
+<blockquote>
+<A NAME=5.5.9>It is the cry of women, my good lord.</A><br>
+<p><i>Exit</i></p>
+</blockquote>
+
+<A NAME=speech3><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.5.10>I have almost forgot the taste of fears;</A><br>
+<A NAME=5.5.11>The time has been, my senses would have cool'd</A><br>
+<A NAME=5.5.12>To hear a night-shriek; and my fell of hair</A><br>
+<A NAME=5.5.13>Would at a dismal treatise rouse and stir</A><br>
+<A NAME=5.5.14>As life were in't: I have supp'd full with horrors;</A><br>
+<A NAME=5.5.15>Direness, familiar to my slaughterous thoughts</A><br>
+<A NAME=5.5.16>Cannot once start me.</A><br>
+<p><i>Re-enter SEYTON</i></p>
+<A NAME=5.5.17>Wherefore was that cry?</A><br>
+</blockquote>
+
+<A NAME=speech4><b>SEYTON</b></a>
+<blockquote>
+<A NAME=5.5.18>The queen, my lord, is dead.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.5.19>She should have died hereafter;</A><br>
+<A NAME=5.5.20>There would have been a time for such a word.</A><br>
+<A NAME=5.5.21>To-morrow, and to-morrow, and to-morrow,</A><br>
+<A NAME=5.5.22>Creeps in this petty pace from day to day</A><br>
+<A NAME=5.5.23>To the last syllable of recorded time,</A><br>
+<A NAME=5.5.24>And all our yesterdays have lighted fools</A><br>
+<A NAME=5.5.25>The way to dusty death. Out, out, brief candle!</A><br>
+<A NAME=5.5.26>Life's but a walking shadow, a poor player</A><br>
+<A NAME=5.5.27>That struts and frets his hour upon the stage</A><br>
+<A NAME=5.5.28>And then is heard no more: it is a tale</A><br>
+<A NAME=5.5.29>Told by an idiot, full of sound and fury,</A><br>
+<A NAME=5.5.30>Signifying nothing.</A><br>
+<p><i>Enter a Messenger</i></p>
+<A NAME=5.5.31>Thou comest to use thy tongue; thy story quickly.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>Messenger</b></a>
+<blockquote>
+<A NAME=5.5.32>Gracious my lord,</A><br>
+<A NAME=5.5.33>I should report that which I say I saw,</A><br>
+<A NAME=5.5.34>But know not how to do it.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.5.35>Well, say, sir.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>Messenger</b></a>
+<blockquote>
+<A NAME=5.5.36>As I did stand my watch upon the hill,</A><br>
+<A NAME=5.5.37>I look'd toward Birnam, and anon, methought,</A><br>
+<A NAME=5.5.38>The wood began to move.</A><br>
+</blockquote>
+
+<A NAME=speech9><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.5.39>Liar and slave!</A><br>
+</blockquote>
+
+<A NAME=speech10><b>Messenger</b></a>
+<blockquote>
+<A NAME=5.5.40>Let me endure your wrath, if't be not so:</A><br>
+<A NAME=5.5.41>Within this three mile may you see it coming;</A><br>
+<A NAME=5.5.42>I say, a moving grove.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.5.43>If thou speak'st false,</A><br>
+<A NAME=5.5.44>Upon the next tree shalt thou hang alive,</A><br>
+<A NAME=5.5.45>Till famine cling thee: if thy speech be sooth,</A><br>
+<A NAME=5.5.46>I care not if thou dost for me as much.</A><br>
+<A NAME=5.5.47>I pull in resolution, and begin</A><br>
+<A NAME=5.5.48>To doubt the equivocation of the fiend</A><br>
+<A NAME=5.5.49>That lies like truth: 'Fear not, till Birnam wood</A><br>
+<A NAME=5.5.50>Do come to Dunsinane:' and now a wood</A><br>
+<A NAME=5.5.51>Comes toward Dunsinane. Arm, arm, and out!</A><br>
+<A NAME=5.5.52>If this which he avouches does appear,</A><br>
+<A NAME=5.5.53>There is nor flying hence nor tarrying here.</A><br>
+<A NAME=5.5.54>I gin to be aweary of the sun,</A><br>
+<A NAME=5.5.55>And wish the estate o' the world were now undone.</A><br>
+<A NAME=5.5.56>Ring the alarum-bell! Blow, wind! come, wrack!</A><br>
+<A NAME=5.5.57>At least we'll die with harness on our back.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE VI. Dunsinane. Before the castle.</h3>
+<p><blockquote>
+<i>Drum and colours. Enter MALCOLM, SIWARD, MACDUFF, and their Army, with boughs</i>
+</blockquote>
+
+<A NAME=speech1><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.6.1>Now near enough: your leafy screens throw down.</A><br>
+<A NAME=5.6.2>And show like those you are. You, worthy uncle,</A><br>
+<A NAME=5.6.3>Shall, with my cousin, your right-noble son,</A><br>
+<A NAME=5.6.4>Lead our first battle: worthy Macduff and we</A><br>
+<A NAME=5.6.5>Shall take upon 's what else remains to do,</A><br>
+<A NAME=5.6.6>According to our order.</A><br>
+</blockquote>
+
+<A NAME=speech2><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.6.7>Fare you well.</A><br>
+<A NAME=5.6.8>Do we but find the tyrant's power to-night,</A><br>
+<A NAME=5.6.9>Let us be beaten, if we cannot fight.</A><br>
+</blockquote>
+
+<A NAME=speech3><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=5.6.10>Make all our trumpets speak; give them all breath,</A><br>
+<A NAME=5.6.11>Those clamorous harbingers of blood and death.</A><br>
+<p><i>Exeunt</i></p>
+</blockquote>
+<h3>SCENE VII. Another part of the field.</h3>
+<p><blockquote>
+<i>Alarums. Enter MACBETH</i>
+</blockquote>
+
+<A NAME=speech1><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.7.1>They have tied me to a stake; I cannot fly,</A><br>
+<A NAME=5.7.2>But, bear-like, I must fight the course. What's he</A><br>
+<A NAME=5.7.3>That was not born of woman? Such a one</A><br>
+<A NAME=5.7.4>Am I to fear, or none.</A><br>
+<p><i>Enter YOUNG SIWARD</i></p>
+</blockquote>
+
+<A NAME=speech2><b>YOUNG SIWARD</b></a>
+<blockquote>
+<A NAME=5.7.5>What is thy name?</A><br>
+</blockquote>
+
+<A NAME=speech3><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.7.6> Thou'lt be afraid to hear it.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>YOUNG SIWARD</b></a>
+<blockquote>
+<A NAME=5.7.7>No; though thou call'st thyself a hotter name</A><br>
+<A NAME=5.7.8>Than any is in hell.</A><br>
+</blockquote>
+
+<A NAME=speech5><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.7.9>My name's Macbeth.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>YOUNG SIWARD</b></a>
+<blockquote>
+<A NAME=5.7.10>The devil himself could not pronounce a title</A><br>
+<A NAME=5.7.11>More hateful to mine ear.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.7.12>No, nor more fearful.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>YOUNG SIWARD</b></a>
+<blockquote>
+<A NAME=5.7.13>Thou liest, abhorred tyrant; with my sword</A><br>
+<A NAME=5.7.14>I'll prove the lie thou speak'st.</A><br>
+<p><i>They fight and YOUNG SIWARD is slain</i></p>
+</blockquote>
+
+<A NAME=speech9><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.7.15>Thou wast born of woman</A><br>
+<A NAME=5.7.16>But swords I smile at, weapons laugh to scorn,</A><br>
+<A NAME=5.7.17>Brandish'd by man that's of a woman born.</A><br>
+<p><i>Exit</i></p>
+<p><i>Alarums. Enter MACDUFF</i></p>
+</blockquote>
+
+<A NAME=speech10><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=5.7.18>That way the noise is. Tyrant, show thy face!</A><br>
+<A NAME=5.7.19>If thou be'st slain and with no stroke of mine,</A><br>
+<A NAME=5.7.20>My wife and children's ghosts will haunt me still.</A><br>
+<A NAME=5.7.21>I cannot strike at wretched kerns, whose arms</A><br>
+<A NAME=5.7.22>Are hired to bear their staves: either thou, Macbeth,</A><br>
+<A NAME=5.7.23>Or else my sword with an unbatter'd edge</A><br>
+<A NAME=5.7.24>I sheathe again undeeded. There thou shouldst be;</A><br>
+<A NAME=5.7.25>By this great clatter, one of greatest note</A><br>
+<A NAME=5.7.26>Seems bruited. Let me find him, fortune!</A><br>
+<A NAME=5.7.27>And more I beg not.</A><br>
+<p><i>Exit. Alarums</i></p>
+<p><i>Enter MALCOLM and SIWARD</i></p>
+</blockquote>
+
+<A NAME=speech11><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.7.28>This way, my lord; the castle's gently render'd:</A><br>
+<A NAME=5.7.29>The tyrant's people on both sides do fight;</A><br>
+<A NAME=5.7.30>The noble thanes do bravely in the war;</A><br>
+<A NAME=5.7.31>The day almost itself professes yours,</A><br>
+<A NAME=5.7.32>And little is to do.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.7.33>We have met with foes</A><br>
+<A NAME=5.7.34>That strike beside us.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.7.35>Enter, sir, the castle.</A><br>
+<p><i>Exeunt. Alarums</i></p>
+</blockquote>
+<h3>SCENE VIII. Another part of the field.</h3>
+<p><blockquote>
+<i>Enter MACBETH</i>
+</blockquote>
+
+<A NAME=speech1><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.8.1>Why should I play the Roman fool, and die</A><br>
+<A NAME=5.8.2>On mine own sword? whiles I see lives, the gashes</A><br>
+<A NAME=5.8.3>Do better upon them.</A><br>
+<p><i>Enter MACDUFF</i></p>
+</blockquote>
+
+<A NAME=speech2><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=5.8.4>Turn, hell-hound, turn!</A><br>
+</blockquote>
+
+<A NAME=speech3><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.8.5>Of all men else I have avoided thee:</A><br>
+<A NAME=5.8.6>But get thee back; my soul is too much charged</A><br>
+<A NAME=5.8.7>With blood of thine already.</A><br>
+</blockquote>
+
+<A NAME=speech4><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=5.8.8>I have no words:</A><br>
+<A NAME=5.8.9>My voice is in my sword: thou bloodier villain</A><br>
+<A NAME=5.8.10>Than terms can give thee out!</A><br>
+<p><i>They fight</i></p>
+</blockquote>
+
+<A NAME=speech5><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.8.11>Thou losest labour:</A><br>
+<A NAME=5.8.12>As easy mayst thou the intrenchant air</A><br>
+<A NAME=5.8.13>With thy keen sword impress as make me bleed:</A><br>
+<A NAME=5.8.14>Let fall thy blade on vulnerable crests;</A><br>
+<A NAME=5.8.15>I bear a charmed life, which must not yield,</A><br>
+<A NAME=5.8.16>To one of woman born.</A><br>
+</blockquote>
+
+<A NAME=speech6><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=5.8.17>Despair thy charm;</A><br>
+<A NAME=5.8.18>And let the angel whom thou still hast served</A><br>
+<A NAME=5.8.19>Tell thee, Macduff was from his mother's womb</A><br>
+<A NAME=5.8.20>Untimely ripp'd.</A><br>
+</blockquote>
+
+<A NAME=speech7><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.8.21>Accursed be that tongue that tells me so,</A><br>
+<A NAME=5.8.22>For it hath cow'd my better part of man!</A><br>
+<A NAME=5.8.23>And be these juggling fiends no more believed,</A><br>
+<A NAME=5.8.24>That palter with us in a double sense;</A><br>
+<A NAME=5.8.25>That keep the word of promise to our ear,</A><br>
+<A NAME=5.8.26>And break it to our hope. I'll not fight with thee.</A><br>
+</blockquote>
+
+<A NAME=speech8><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=5.8.27>Then yield thee, coward,</A><br>
+<A NAME=5.8.28>And live to be the show and gaze o' the time:</A><br>
+<A NAME=5.8.29>We'll have thee, as our rarer monsters are,</A><br>
+<A NAME=5.8.30>Painted on a pole, and underwrit,</A><br>
+<A NAME=5.8.31>'Here may you see the tyrant.'</A><br>
+</blockquote>
+
+<A NAME=speech9><b>MACBETH</b></a>
+<blockquote>
+<A NAME=5.8.32>I will not yield,</A><br>
+<A NAME=5.8.33>To kiss the ground before young Malcolm's feet,</A><br>
+<A NAME=5.8.34>And to be baited with the rabble's curse.</A><br>
+<A NAME=5.8.35>Though Birnam wood be come to Dunsinane,</A><br>
+<A NAME=5.8.36>And thou opposed, being of no woman born,</A><br>
+<A NAME=5.8.37>Yet I will try the last. Before my body</A><br>
+<A NAME=5.8.38>I throw my warlike shield. Lay on, Macduff,</A><br>
+<A NAME=5.8.39>And damn'd be him that first cries, 'Hold, enough!'</A><br>
+<p><i>Exeunt, fighting. Alarums</i></p>
+<p><i>Retreat. Flourish. Enter, with drum and colours, MALCOLM, SIWARD, ROSS, the other Thanes, and Soldiers</i></p>
+</blockquote>
+
+<A NAME=speech10><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.8.40>I would the friends we miss were safe arrived.</A><br>
+</blockquote>
+
+<A NAME=speech11><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.8.41>Some must go off: and yet, by these I see,</A><br>
+<A NAME=5.8.42>So great a day as this is cheaply bought.</A><br>
+</blockquote>
+
+<A NAME=speech12><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.8.43>Macduff is missing, and your noble son.</A><br>
+</blockquote>
+
+<A NAME=speech13><b>ROSS</b></a>
+<blockquote>
+<A NAME=5.8.44>Your son, my lord, has paid a soldier's debt:</A><br>
+<A NAME=5.8.45>He only lived but till he was a man;</A><br>
+<A NAME=5.8.46>The which no sooner had his prowess confirm'd</A><br>
+<A NAME=5.8.47>In the unshrinking station where he fought,</A><br>
+<A NAME=5.8.48>But like a man he died.</A><br>
+</blockquote>
+
+<A NAME=speech14><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.8.49>Then he is dead?</A><br>
+</blockquote>
+
+<A NAME=speech15><b>ROSS</b></a>
+<blockquote>
+<A NAME=5.8.50>Ay, and brought off the field: your cause of sorrow</A><br>
+<A NAME=5.8.51>Must not be measured by his worth, for then</A><br>
+<A NAME=5.8.52>It hath no end.</A><br>
+</blockquote>
+
+<A NAME=speech16><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.8.53> Had he his hurts before?</A><br>
+</blockquote>
+
+<A NAME=speech17><b>ROSS</b></a>
+<blockquote>
+<A NAME=5.8.54>Ay, on the front.</A><br>
+</blockquote>
+
+<A NAME=speech18><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.8.55> Why then, God's soldier be he!</A><br>
+<A NAME=5.8.56>Had I as many sons as I have hairs,</A><br>
+<A NAME=5.8.57>I would not wish them to a fairer death:</A><br>
+<A NAME=5.8.58>And so, his knell is knoll'd.</A><br>
+</blockquote>
+
+<A NAME=speech19><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.8.59>He's worth more sorrow,</A><br>
+<A NAME=5.8.60>And that I'll spend for him.</A><br>
+</blockquote>
+
+<A NAME=speech20><b>SIWARD</b></a>
+<blockquote>
+<A NAME=5.8.61>He's worth no more</A><br>
+<A NAME=5.8.62>They say he parted well, and paid his score:</A><br>
+<A NAME=5.8.63>And so, God be with him! Here comes newer comfort.</A><br>
+<p><i>Re-enter MACDUFF, with MACBETH's head</i></p>
+</blockquote>
+
+<A NAME=speech21><b>MACDUFF</b></a>
+<blockquote>
+<A NAME=5.8.64>Hail, king! for so thou art: behold, where stands</A><br>
+<A NAME=5.8.65>The usurper's cursed head: the time is free:</A><br>
+<A NAME=5.8.66>I see thee compass'd with thy kingdom's pearl,</A><br>
+<A NAME=5.8.67>That speak my salutation in their minds;</A><br>
+<A NAME=5.8.68>Whose voices I desire aloud with mine:</A><br>
+<A NAME=5.8.69>Hail, King of Scotland!</A><br>
+</blockquote>
+
+<A NAME=speech22><b>ALL</b></a>
+<blockquote>
+<A NAME=5.8.70>Hail, King of Scotland!</A><br>
+<p><i>Flourish</i></p>
+</blockquote>
+
+<A NAME=speech23><b>MALCOLM</b></a>
+<blockquote>
+<A NAME=5.8.71>We shall not spend a large expense of time</A><br>
+<A NAME=5.8.72>Before we reckon with your several loves,</A><br>
+<A NAME=5.8.73>And make us even with you. My thanes and kinsmen,</A><br>
+<A NAME=5.8.74>Henceforth be earls, the first that ever Scotland</A><br>
+<A NAME=5.8.75>In such an honour named. What's more to do,</A><br>
+<A NAME=5.8.76>Which would be planted newly with the time,</A><br>
+<A NAME=5.8.77>As calling home our exiled friends abroad</A><br>
+<A NAME=5.8.78>That fled the snares of watchful tyranny;</A><br>
+<A NAME=5.8.79>Producing forth the cruel ministers</A><br>
+<A NAME=5.8.80>Of this dead butcher and his fiend-like queen,</A><br>
+<A NAME=5.8.81>Who, as 'tis thought, by self and violent hands</A><br>
+<A NAME=5.8.82>Took off her life; this, and what needful else</A><br>
+<A NAME=5.8.83>That calls upon us, by the grace of Grace,</A><br>
+<A NAME=5.8.84>We will perform in measure, time and place:</A><br>
+<A NAME=5.8.85>So, thanks to all at once and to each one,</A><br>
+<A NAME=5.8.86>Whom we invite to see us crown'd at Scone.</A><br>
+<p><i>Flourish. Exeunt</i></p>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/modal_dialogs.html b/testing/marionette/harness/marionette_harness/www/modal_dialogs.html
new file mode 100644
index 000000000..8da5b92a7
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/modal_dialogs.html
@@ -0,0 +1,39 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Marionette Test</title>
+ <script type="text/javascript">
+ function handleAlert () {
+ window.alert('Marionette alert');
+ }
+
+ function handleConfirm () {
+ var alertAccepted = window.confirm('Marionette confirm');
+ document.getElementById('confirm-result').innerHTML = alertAccepted;
+ }
+
+ function handlePrompt () {
+ var promptText = window.prompt('Marionette prompt');
+ document.getElementById('prompt-result').innerHTML = promptText === null ? 'null' : promptText;
+ }
+
+ function onBeforeUnload () {
+ window.onbeforeunload = function () { return "Are you sure?"; }
+ }
+ </script>
+</head>
+<body>
+ <a href="#" id="modal-alert" onclick="handleAlert()">Open an alert dialog.</a>
+ <a href="#" id="modal-confirm" onclick="handleConfirm()">Open a confirm dialog.</a>
+ <a href="#" id="modal-prompt" onclick="handlePrompt()">Open a prompt dialog.</a>
+ <a href="#" id="onbeforeunload-handler" onclick="onBeforeUnload()">Add an onbeforeunload handler.</a>
+ <a href="#" id="click-handler" onclick="document.getElementById('click-result').innerHTML='result';">Make text appear.</a>
+ <div id="confirm-result"></div>
+ <div id="prompt-result"></div>
+ <div id="click-result"></div>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/nestedElements.html b/testing/marionette/harness/marionette_harness/www/nestedElements.html
new file mode 100644
index 000000000..618bf3231
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/nestedElements.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<a href="1.html">hello world</a>
+<a href="1.html">hello world</a><a href="1.html">hello world</a>
+<div name="div1">
+ <a href="2.html" name="link1">hello world</a>
+ <a href="2.html" name="link2">hello world</a>
+</div>
+
+<a href="1.html">hello world</a><a href="1.html">hello world</a><a href="1.html">hello world</a>
diff --git a/testing/marionette/harness/marionette_harness/www/rectangles.html b/testing/marionette/harness/marionette_harness/www/rectangles.html
new file mode 100644
index 000000000..59871c6de
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/rectangles.html
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>Rectangles</title>
+ <style type="text/css">
+ div {
+ position: absolute;
+ margin: 0;
+ border: 0;
+ padding: 0;
+ }
+ #r1 {
+ background-color: blue;
+ left: 10px;
+ top: 10px;
+ width: 100px;
+ height: 50px;
+ }
+ #r2 {
+ background-color: red;
+ left: 11px;
+ top: 10px;
+ width: 48.666666667px;
+ height: 49.333333333px;
+ }
+ #r3 {
+ background-color: yellow;
+ left: 60px;
+ top: 10px;
+ width: 50px;
+ height: 25px;
+ }
+ </style>
+</head>
+ <body>
+ <div id="r1">r1</div>
+ <div id="r2">r2</div>
+ <div id="r3">r3</div>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/resultPage.html b/testing/marionette/harness/marionette_harness/www/resultPage.html
new file mode 100644
index 000000000..342e9930b
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/resultPage.html
@@ -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/. -->
+
+<html>
+<head>
+ <title>We Arrive Here</title>
+</head>
+<body>
+
+
+<div>
+ <input type='text' id='email'/>
+</div>
+</body>
+</html>
+
diff --git a/testing/marionette/harness/marionette_harness/www/scroll.html b/testing/marionette/harness/marionette_harness/www/scroll.html
new file mode 100644
index 000000000..8a654bb50
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/scroll.html
@@ -0,0 +1,30 @@
+<!-- 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/. -->
+
+<html>
+<head></head>
+<body>
+ <script>
+ function dump(event) {
+ var elt = event.target || event.srcElement;
+ document.getElementById('clicked').innerHTML = elt.innerHTML;
+ }
+ </script>
+ <div style='height: 150px'></div>
+ <ul style='overflow: scroll; width: 150px; height: 80px; background-color: yellow' onclick="dump(event)">
+ <li id='line1'>line1</li>
+ <li id='line2'>line2</li>
+ <li id='line3'>line3</li>
+ <li id='line4'>line4</li>
+ <li id='line5'>line5</li>
+ <li id='line6'>line6</li>
+ <li id='line7'>line7</li>
+ <li id='line8'>line8</li>
+ <li id='line9'>line9</li>
+ </ul>
+ <div>
+ Clicked: <span id='clicked'></span>
+ </div>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/scroll2.html b/testing/marionette/harness/marionette_harness/www/scroll2.html
new file mode 100644
index 000000000..bd00f0e22
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/scroll2.html
@@ -0,0 +1,24 @@
+<!-- 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/. -->
+
+<html>
+<head></head>
+<body>
+ <ul style='overflow: scroll; height: 100px;'>
+ <li></li>
+ <li></li>
+ <li id="desired">Text</li>
+ <li></li>
+ <li></li>
+ <li></li>
+ <li></li>
+ <li></li>
+ <li></li>
+ <li></li>
+ <li></li>
+ <li></li>
+ <li></li>
+ </ul>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/scroll3.html b/testing/marionette/harness/marionette_harness/www/scroll3.html
new file mode 100644
index 000000000..b615e38c3
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/scroll3.html
@@ -0,0 +1,18 @@
+<!-- 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/. -->
+
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
+ <style type="text/css"></style>
+</head>
+<body>
+<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
+<button id="button1">Button1</button>
+<br><br><br><br>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button id="button2">Button2</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/scroll4.html b/testing/marionette/harness/marionette_harness/www/scroll4.html
new file mode 100644
index 000000000..ce0df0313
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/scroll4.html
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
+ <style type="text/css"></style>
+</head>
+<body>
+<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
+<input type="radio" id="radio">
+<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/scroll5.html b/testing/marionette/harness/marionette_harness/www/scroll5.html
new file mode 100644
index 000000000..3dd00721e
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/scroll5.html
@@ -0,0 +1,20 @@
+<!-- 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/. -->
+
+<html>
+<head></head>
+<body>
+ <script>
+ function dump(text) {
+ document.getElementById('clicked').innerHTML = text;
+ }
+ </script>
+ <div style='overflow: scroll; width: 150px; height: 200px; background-color: yellow' id="outer">
+ <div style="width: 150px; height: 5000px; background-color: red;" onclick="dump('clicked')" id="inner"></div>
+ </div>
+ <div>
+ Clicked: <span id='clicked'></span>
+ </div>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/shim.js b/testing/marionette/harness/marionette_harness/www/shim.js
new file mode 100644
index 000000000..2a74e6949
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/shim.js
@@ -0,0 +1,282 @@
+/**
+* mouse_event_shim.js: generate mouse events from touch events.
+*
+* This library listens for touch events and generates mousedown, mousemove
+* mouseup, and click events to match them. It captures and dicards any
+* real mouse events (non-synthetic events with isTrusted true) that are
+* send by gecko so that there are not duplicates.
+*
+* This library does emit mouseover/mouseout and mouseenter/mouseleave
+* events. You can turn them off by setting MouseEventShim.trackMouseMoves to
+* false. This means that mousemove events will always have the same target
+* as the mousedown even that began the series. You can also call
+* MouseEventShim.setCapture() from a mousedown event handler to prevent
+* mouse tracking until the next mouseup event.
+*
+* This library does not support multi-touch but should be sufficient
+* to do drags based on mousedown/mousemove/mouseup events.
+*
+* This library does not emit dblclick events or contextmenu events
+*/
+
+'use strict';
+
+(function() {
+ // Make sure we don't run more than once
+ if (MouseEventShim)
+ return;
+
+ // Bail if we're not on running on a platform that sends touch
+ // events. We don't need the shim code for mouse events.
+ try {
+ document.createEvent('TouchEvent');
+ } catch (e) {
+ return;
+ }
+
+ var starttouch; // The Touch object that we started with
+ var target; // The element the touch is currently over
+ var emitclick; // Will we be sending a click event after mouseup?
+
+ // Use capturing listeners to discard all mouse events from gecko
+ window.addEventListener('mousedown', discardEvent, true);
+ window.addEventListener('mouseup', discardEvent, true);
+ window.addEventListener('mousemove', discardEvent, true);
+ window.addEventListener('click', discardEvent, true);
+
+ function discardEvent(e) {
+ if (e.isTrusted) {
+ e.stopImmediatePropagation(); // so it goes no further
+ if (e.type === 'click')
+ e.preventDefault(); // so it doesn't trigger a change event
+ }
+ }
+
+ // Listen for touch events that bubble up to the window.
+ // If other code has called stopPropagation on the touch events
+ // then we'll never see them. Also, we'll honor the defaultPrevented
+ // state of the event and will not generate synthetic mouse events
+ window.addEventListener('touchstart', handleTouchStart);
+ window.addEventListener('touchmove', handleTouchMove);
+ window.addEventListener('touchend', handleTouchEnd);
+ window.addEventListener('touchcancel', handleTouchEnd); // Same as touchend
+
+ function handleTouchStart(e) {
+ // If we're already handling a touch, ignore this one
+ if (starttouch)
+ return;
+
+ // Ignore any event that has already been prevented
+ if (e.defaultPrevented)
+ return;
+
+ // Sometimes an unknown gecko bug causes us to get a touchstart event
+ // for an iframe target that we can't use because it is cross origin.
+ // Don't start handling a touch in that case
+ try {
+ e.changedTouches[0].target.ownerDocument;
+ }
+ catch (e) {
+ // Ignore the event if we can't see the properties of the target
+ return;
+ }
+
+ // If there is more than one simultaneous touch, ignore all but the first
+ starttouch = e.changedTouches[0];
+ target = starttouch.target;
+ emitclick = true;
+
+ // Move to the position of the touch
+ emitEvent('mousemove', target, starttouch);
+
+ // Now send a synthetic mousedown
+ var result = emitEvent('mousedown', target, starttouch);
+
+ // If the mousedown was prevented, pass that on to the touch event.
+ // And remember not to send a click event
+ if (!result) {
+ e.preventDefault();
+ emitclick = false;
+ }
+ }
+
+ function handleTouchEnd(e) {
+ if (!starttouch)
+ return;
+
+ // End a MouseEventShim.setCapture() call
+ if (MouseEventShim.capturing) {
+ MouseEventShim.capturing = false;
+ MouseEventShim.captureTarget = null;
+ }
+
+ for (var i = 0; i < e.changedTouches.length; i++) {
+ var touch = e.changedTouches[i];
+ // If the ended touch does not have the same id, skip it
+ if (touch.identifier !== starttouch.identifier)
+ continue;
+
+ emitEvent('mouseup', target, touch);
+
+ // If target is still the same element we started and the touch did not
+ // move more than the threshold and if the user did not prevent
+ // the mousedown, then send a click event, too.
+ if (emitclick)
+ emitEvent('click', starttouch.target, touch);
+
+ starttouch = null;
+ return;
+ }
+ }
+
+ function handleTouchMove(e) {
+ if (!starttouch)
+ return;
+
+ for (var i = 0; i < e.changedTouches.length; i++) {
+ var touch = e.changedTouches[i];
+ // If the ended touch does not have the same id, skip it
+ if (touch.identifier !== starttouch.identifier)
+ continue;
+
+ // Don't send a mousemove if the touchmove was prevented
+ if (e.defaultPrevented)
+ return;
+
+ // See if we've moved too much to emit a click event
+ var dx = Math.abs(touch.screenX - starttouch.screenX);
+ var dy = Math.abs(touch.screenY - starttouch.screenY);
+ if (dx > MouseEventShim.dragThresholdX ||
+ dy > MouseEventShim.dragThresholdY) {
+ emitclick = false;
+ }
+
+ var tracking = MouseEventShim.trackMouseMoves &&
+ !MouseEventShim.capturing;
+
+ if (tracking) {
+ // If the touch point moves, then the element it is over
+ // may have changed as well. Note that calling elementFromPoint()
+ // forces a layout if one is needed.
+ // XXX: how expensive is it to do this on each touchmove?
+ // Can we listen for (non-standard) touchleave events instead?
+ var oldtarget = target;
+ var newtarget = document.elementFromPoint(touch.clientX, touch.clientY);
+ if (newtarget === null) {
+ // this can happen as the touch is moving off of the screen, e.g.
+ newtarget = oldtarget;
+ }
+ if (newtarget !== oldtarget) {
+ leave(oldtarget, newtarget, touch); // mouseout, mouseleave
+ target = newtarget;
+ }
+ }
+ else if (MouseEventShim.captureTarget) {
+ target = MouseEventShim.captureTarget;
+ }
+
+ emitEvent('mousemove', target, touch);
+
+ if (tracking && newtarget !== oldtarget) {
+ enter(newtarget, oldtarget, touch); // mouseover, mouseenter
+ }
+ }
+ }
+
+ // Return true if element a contains element b
+ function contains(a, b) {
+ return (a.compareDocumentPosition(b) & 16) !== 0;
+ }
+
+ // A touch has left oldtarget and entered newtarget
+ // Send out all the events that are required
+ function leave(oldtarget, newtarget, touch) {
+ emitEvent('mouseout', oldtarget, touch, newtarget);
+
+ // If the touch has actually left oldtarget (and has not just moved
+ // into a child of oldtarget) send a mouseleave event. mouseleave
+ // events don't bubble, so we have to repeat this up the hierarchy.
+ for (var e = oldtarget; !contains(e, newtarget); e = e.parentNode) {
+ emitEvent('mouseleave', e, touch, newtarget);
+ }
+ }
+
+ // A touch has entered newtarget from oldtarget
+ // Send out all the events that are required.
+ function enter(newtarget, oldtarget, touch) {
+ emitEvent('mouseover', newtarget, touch, oldtarget);
+
+ // Emit non-bubbling mouseenter events if the touch actually entered
+ // newtarget and wasn't already in some child of it
+ for (var e = newtarget; !contains(e, oldtarget); e = e.parentNode) {
+ emitEvent('mouseenter', e, touch, oldtarget);
+ }
+ }
+
+ function emitEvent(type, target, touch, relatedTarget) {
+ var synthetic = document.createEvent('MouseEvents');
+ var bubbles = (type !== 'mouseenter' && type !== 'mouseleave');
+ var count =
+ (type === 'mousedown' || type === 'mouseup' || type === 'click') ? 1 : 0;
+
+ synthetic.initMouseEvent(type,
+ bubbles, // canBubble
+ true, // cancelable
+ window,
+ count, // detail: click count
+ touch.screenX,
+ touch.screenY,
+ touch.clientX,
+ touch.clientY,
+ false, // ctrlKey: we don't have one
+ false, // altKey: we don't have one
+ false, // shiftKey: we don't have one
+ false, // metaKey: we don't have one
+ 0, // we're simulating the left button
+ relatedTarget || null);
+
+ try {
+ return target.dispatchEvent(synthetic);
+ }
+ catch (e) {
+ console.warn('Exception calling dispatchEvent', type, e);
+ return true;
+ }
+ }
+}());
+
+var MouseEventShim = {
+ // It is a known gecko bug that synthetic events have timestamps measured
+ // in microseconds while regular events have timestamps measured in
+ // milliseconds. This utility function returns a the timestamp converted
+ // to milliseconds, if necessary.
+ getEventTimestamp: function(e) {
+ if (e.isTrusted) // XXX: Are real events always trusted?
+ return e.timeStamp;
+ else
+ return e.timeStamp / 1000;
+ },
+
+ // Set this to false if you don't care about mouseover/out events
+ // and don't want the target of mousemove events to follow the touch
+ trackMouseMoves: true,
+
+ // Call this function from a mousedown event handler if you want to guarantee
+ // that the mousemove and mouseup events will go to the same element
+ // as the mousedown even if they leave the bounds of the element. This is
+ // like setting trackMouseMoves to false for just one drag. It is a
+ // substitute for event.target.setCapture(true)
+ setCapture: function(target) {
+ this.capturing = true; // Will be set back to false on mouseup
+ if (target)
+ this.captureTarget = target;
+ },
+
+ capturing: false,
+
+ // Keep these in sync with ui.dragThresholdX and ui.dragThresholdY prefs.
+ // If a touch ever moves more than this many pixels from its starting point
+ // then we will not synthesize a click event when the touch ends.
+ dragThresholdX: 25,
+ dragThresholdY: 25
+};
diff --git a/testing/marionette/harness/marionette_harness/www/test.html b/testing/marionette/harness/marionette_harness/www/test.html
new file mode 100644
index 000000000..053ff171d
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test.html
@@ -0,0 +1,38 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>Marionette Test</title>
+</head>
+<body>
+ <h1 id="testh1">Test Page</h1>
+ <script type="text/javascript">
+ window.ready = true;
+ function addDelayedElement() {
+ setTimeout(createDiv, 2000);
+ function createDiv() {
+ var newDiv = document.createElement("div");
+ newDiv.id = "newDiv";
+ var newContent = document.createTextNode("I am a newly created div!");
+ newDiv.appendChild(newContent);
+ document.body.appendChild(newDiv);
+ }
+ }
+ function clicked() {
+ var link = document.getElementById("mozLink");
+ link.innerHTML = "Clicked";
+ }
+ </script>
+ <a href="#" id="mozLink" class="linkClass" onclick="clicked()">Click me!</a>
+ <div id="testDiv">
+ <a href="#" id="divLink" class="linkClass" onclick="clicked()">Div click me!</a>
+ <a href="#" id="divLink2" class="linkClass" onclick="clicked()">Div click me!</a>
+ </div>
+ <input name="myInput" type="text" value="asdf"/>
+ <input name="myCheckBox" type="checkbox" />
+ <input id="createDivButton" type="button" value="create a div" onclick="addDelayedElement()" />
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/testAction.html b/testing/marionette/harness/marionette_harness/www/testAction.html
new file mode 100644
index 000000000..eb7e44f3e
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/testAction.html
@@ -0,0 +1,94 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+
+<html>
+<meta charset="UTF-8">
+<head>
+<title>Marionette Test</title>
+</head>
+<body>
+ <h1 id="testh1">Test Page</h1>
+ <button id="button1" style="position:absolute;left:0px;top:55px;" type="button" allowevents=true>button1</button>
+ <button id="button2" style="position:absolute;left:0px;top:355px;" type="button" allowevents=true>button2</button>
+ <button id="button3" style="position:absolute;left:0px;top:455px;" type="button" allowevents=true>button3</button>
+ <button id="button4" style="position:absolute;left:100px;top:455px;" type="button" allowevents=true>button4</button>
+ <button id="buttonScroll" style="position:absolute;left:100px;top:855px;" type="button" allowevents=true>buttonScroll</button>
+ <h2 id="hidden" style="visibility: hidden" class="linkClass">Hidden</h2>
+ <button id="buttonFlick" style="position:absolute;left:0px;top:255px;" type="button" allowevents=true>buttonFlick</button>
+ <script type="text/javascript">
+ var button3Timer = null;
+ var button4Timer = null;
+ //appends passed in text to the innerHTML of the event's target
+ function appendText(text) {
+ return function(evt) {
+ var element;
+ if (evt.type.indexOf("touch") !== -1) {
+ if (evt.type == "touchstart") {
+ element = evt.target;
+ }
+ else {
+ //since the target of touchstart is the target of all subsequent events, then
+ //changedTouches holds the current coordinates of this touch event, so we
+ //use these coordinates to find the element under the touch event
+ var touches = evt.changedTouches;
+ var x = touches[0].clientX;
+ var y = touches[0].clientY;
+ element = document.elementFromPoint(x,y);
+ }
+ }
+ //handle mouse events or contextmenu
+ else {
+ element = evt.target;
+ }
+ element.innerHTML += text;
+ };
+ };
+ //use this function outside of attachListeners when you want to test sendMouseOnlyEvents on a target
+ function attachMouseListeners(element) {
+ element.addEventListener("contextmenu", appendText("-contextmenu"), false);
+ element.addEventListener("mousedown", appendText("-mousedown"), false);
+ element.addEventListener("mousemove", appendText("-mousemove"), false);
+ element.addEventListener("mouseup", appendText("-mouseup"), false);
+ element.addEventListener("click", appendText("-click"), false);
+ };
+ function attachListeners(id) {
+ var element = document.getElementById(id);
+ element.addEventListener("touchstart", appendText("-touchstart"), false);
+ element.addEventListener("touchmove", appendText("-touchmove"), false);
+ element.addEventListener("touchend", appendText("-touchend"), false);
+ element.addEventListener("touchcancel", appendText("-touchcancel"), false);
+ attachMouseListeners(element);
+ };
+ //for tracking time on an element
+ function addTimers(id, timer) {
+ var element = document.getElementById(id);
+ element.addEventListener("touchstart", function(evt) { timer = (new Date()).getTime();}, false);
+ element.addEventListener("touchend", function(evt) { timer = (new Date()).getTime() - timer; evt.target.innerHTML += "-" + timer;}, false);
+ }
+ attachListeners("button1");
+ attachListeners("button2");
+ attachListeners("button3");
+ attachListeners("button4");
+ attachListeners("buttonScroll");
+ addTimers("button3");
+ addTimers("button4");
+ var buttonFlick = document.getElementById("buttonFlick");
+ attachMouseListeners(buttonFlick);
+ function createDelayed() {
+ var newButton = document.createElement("button");
+ newButton.id = "delayed";
+ newButton.setAttribute("style", "position:absolute;left:220px;top:455px;");
+ var content = document.createTextNode("delayed");
+ newButton.appendChild(content);
+ document.body.appendChild(newButton);
+ newButton.addEventListener("mousemove", appendText("-mousemove"), false);
+ newButton.addEventListener("mouseup", appendText("-mouseup"), false);
+ newButton.addEventListener("click", appendText("-click"), false);
+ };
+ window.setTimeout(createDelayed, 5000);
+ </script>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/testPageSource.html b/testing/marionette/harness/marionette_harness/www/testPageSource.html
new file mode 100644
index 000000000..f19b9d30c
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/testPageSource.html
@@ -0,0 +1,14 @@
+
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>PageSource Test</title>
+</head>
+<body>
+ <p> Check the PageSource
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/testPageSource.xml b/testing/marionette/harness/marionette_harness/www/testPageSource.xml
new file mode 100644
index 000000000..1480a1f38
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/testPageSource.xml
@@ -0,0 +1,5 @@
+<xml>
+ <foo>
+ <bar>baz</bar>
+ </foo>
+</xml>
diff --git a/testing/marionette/harness/marionette_harness/www/testPageSourceWithUnicodeChars.html b/testing/marionette/harness/marionette_harness/www/testPageSourceWithUnicodeChars.html
new file mode 100644
index 000000000..d16cf52c8
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/testPageSourceWithUnicodeChars.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="pragma" content="no-cache"/>
+ <!--
+ - the « section[id^="wifi-"] » selector.
+ -->
+ </body>
+</html>
+
diff --git a/testing/marionette/harness/marionette_harness/www/testSize.html b/testing/marionette/harness/marionette_harness/www/testSize.html
new file mode 100644
index 000000000..1df27ad23
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/testSize.html
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>Test page for element size</title>
+ </head>
+ <body>
+ <p>Let's get the size of <a href='#' id='linkId'>some really cool link</a></p>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_accessibility.html b/testing/marionette/harness/marionette_harness/www/test_accessibility.html
new file mode 100644
index 000000000..8cc9fd649
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_accessibility.html
@@ -0,0 +1,57 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+
+<html>
+<meta charset="UTF-8">
+<head>
+<title>Marionette Test</title>
+</head>
+<body>
+ <button id="button1">button1</button>
+ <button id="button2" aria-label="button2"></button>
+ <span id="button3">I am a bad button with no accessible</span>
+ <h1 id="button4">I am a bad button that is actually a header</h1>
+ <h1 id="button5">
+ I am a bad button that is actually an actionable header with a listener
+ </h1>
+ <button id="button6"></button>
+ <button id="button7" aria-hidden="true">button7</button>
+ <div aria-hidden="true">
+ <button id="button8">button8</button>
+ </div>
+ <button id="button9" style="position:absolute;left:-100px;top:-455px;">
+ button9
+ </button>
+ <button id="button10" style="visibility:hidden;">
+ button10
+ </button>
+ <span id="no_accessible_but_displayed">I have no accessible object</span>
+ <button id="button11" disabled>button11</button>
+ <button id="button12" aria-disabled="true">button12</button>
+ <span id="no_accessible_but_disabled" disabled>I have no accessible object</span>
+ <span id="button13" tabindex="0" role="button" aria-label="Span button">Span button</span>
+ <span id="button14" role="button" aria-label="Span button">Unexplorable Span button</span>
+ <button id="button15" style="pointer-events:none;">button15</button>
+ <div style="pointer-events:none;">
+ <button id="button16">button16</button>
+ </div>
+ <div style="pointer-events:none;">
+ <button style="pointer-events:all;" id="button17">button17</button>
+ </div>
+ <input id="input1" title="My Input 1" name="myInput1" type="text" value="asdf"/>
+ <select>
+ <option id="option1" value="val1">Val1</option>
+ <option id="option2" value="val2" selected>Val2</option>
+ </select>
+ <script>
+ 'use strict';
+ document.getElementById('button5').addEventListener('click', function() {
+ // A pseudo button that has a listener but is missing button semantics.
+ return true;
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_carets_columns.html b/testing/marionette/harness/marionette_harness/www/test_carets_columns.html
new file mode 100644
index 000000000..495236108
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_carets_columns.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <style>
+ #columns {
+ -moz-column-count: 2;
+ -webkit-column-count: 2;
+ -moz-column-rule: 1px solid lightgray;
+ -webkit-column-rule: 1px solid lightgray;
+ border: 1px solid lightblue;
+ width: 450px;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="columns">
+ <div id="columns-inner" style="border: 1px solid red;" contenteditable="true">
+ <p id="before-image-1">Before image 1</p>
+ <p><img width="100px" height="30px" src="data:image/gif;base64,R0lGODlhAQABAIABAAD/AP///ywAAAAAAQABAAACAkQBADs="></p>
+ <p>After image 1</p>
+ <p>Before image 2</p>
+ <p><img width="100px" height="30px" src="data:image/gif;base64,R0lGODlhAQABAIABAAD/AP///ywAAAAAAQABAAACAkQBADs="></p>
+ <p>After image 2</p>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_carets_cursor.html b/testing/marionette/harness/marionette_harness/www/test_carets_cursor.html
new file mode 100644
index 000000000..fdbd6fe7a
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_carets_cursor.html
@@ -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/. -->
+
+<!DOCTYPE html>
+<html id="html">
+ <head>
+ <title>Marionette tests for AccessibleCaret in cursor mode</title>
+ <style>
+ .block {
+ width: 10em;
+ height: 6em;
+ word-wrap: break-word;
+ overflow: auto;
+ }
+ </style>
+ </head>
+ <body>
+ <div>
+ <input id="input" value="ABCDEFGHI">
+ <input id="input-padding" style="padding: 1em;" value="ABCDEFGHI">
+ </div>
+ <br>
+ <div>
+ <textarea name="textarea" id="textarea" rows="4" cols="6">ABCDEFGHI</textarea>
+ <textarea id="textarea-one-line" rows="3">ABCDEFGHI</textarea>
+ </div>
+ <br>
+ <div class="block" contenteditable="true" id="contenteditable">ABCDEFGHI</div>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_carets_display_none.html b/testing/marionette/harness/marionette_harness/www/test_carets_display_none.html
new file mode 100644
index 000000000..766f32001
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_carets_display_none.html
@@ -0,0 +1,10 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html id="html" style="display: none">
+ <body>
+ <div id="content">ABC DEF GHI</div>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_carets_iframe.html b/testing/marionette/harness/marionette_harness/www/test_carets_iframe.html
new file mode 100644
index 000000000..f6e6df9ba
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_carets_iframe.html
@@ -0,0 +1,20 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html id="html">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>Marionette tests for AccessibleCaret in selection mode (iframe)</title>
+ </head>
+ <body>
+ <style>
+ *
+ {
+ -moz-user-select:none;
+ }
+ </style>
+ <iframe id="frame" src="test_carets_longtext.html" style="width: 10em; height: 8em;"></iframe>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_carets_longtext.html b/testing/marionette/harness/marionette_harness/www/test_carets_longtext.html
new file mode 100644
index 000000000..7e2495509
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_carets_longtext.html
@@ -0,0 +1,9 @@
+<html>
+ <head>
+ <title>Bug 1094072: Orientation change test for AccessibleCaret positions</title>
+ </head>
+ <body id="bd">
+ <h3 id="longtext">long long text for orientation change test long long text for orientation change test long long text for orientation change test long long text for orientation change test</h3>
+ <div contenteditable="true" id="bottomtext">bottom text</div>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_carets_multipleline.html b/testing/marionette/harness/marionette_harness/www/test_carets_multipleline.html
new file mode 100644
index 000000000..ff46a954b
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_carets_multipleline.html
@@ -0,0 +1,24 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html id="html">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>Bug 1019441: Marionette tests for AccessibleCaret (multiple lines)</title>
+ </head>
+ <body>
+ <style>
+ *
+ {
+ -moz-user-select:none;
+ }
+ </style>
+ <div><textarea id="textarea2" style="width: 10em; height: 6em; overflow: auto;">First Line&#13;&#10;&#13;&#10;Second Line&#13;&#10;&#13;&#10;Third Line</textarea></div>
+ <br>
+ <div style="width: 10em; height: 6em; overflow: auto; -moz-user-select:text" id="contenteditable2" contenteditable="true">First Line<br><br>Second Line<br><br>Third Line</div>
+ <br>
+ <div style="width: 10em; height: 6em; overflow: auto; -moz-user-select:text" id="content2">First Line<br><br>Second Line<br><br>Third Line</div>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_carets_multiplerange.html b/testing/marionette/harness/marionette_harness/www/test_carets_multiplerange.html
new file mode 100644
index 000000000..394630c1f
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_carets_multiplerange.html
@@ -0,0 +1,19 @@
+<html>
+<style>
+h4 {
+ -moz-user-select: none;
+}
+</style>
+<body id=bd>
+<h3 id=sel1>user can select this 1</h3>
+<h3 id=sel2>user can select this 2</h3>
+<h3 id=sel3>user can select this 3</h3>
+<h4 id=nonsel1>user cannot select this 1</h4>
+<h4 id=nonsel2>user cannot select this 2</h4>
+<h3 id=sel4>user can select this 4</h3>
+<h3 id=sel5>user can select this 5</h3>
+<h4 id=nonsel3>user cannot select this 3</h4>
+<h3 id=sel6>user can select this 6</h3>
+<h3 id=sel7>user can select this 7</h3>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_carets_selection.html b/testing/marionette/harness/marionette_harness/www/test_carets_selection.html
new file mode 100644
index 000000000..f58b92fbf
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_carets_selection.html
@@ -0,0 +1,38 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html id="html">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>Marionette tests for AccessibleCaret in selection mode</title>
+ <style>
+ .block {
+ width: 10em;
+ height: 4em;
+ word-wrap: break-word;
+ overflow: auto;
+ }
+ </style>
+ </head>
+ <body>
+ <div>
+ <input id="input" value="ABC DEF GHI">
+ <input id="input-padding" style="padding: 1em;" value="ABC DEF GHI">
+ </div>
+ <br>
+ <div>
+ <textarea id="textarea" rows="4" cols="8">ABC DEF GHI JKL MNO PQR</textarea>
+ <textarea id="textarea-one-line" rows="4" cols="12">ABC DEF GHI</textarea>
+ </div>
+ <br>
+ <div><textarea dir="rtl" id="textarea-rtl" rows="8" cols="8">موزيلا فيرفكس موزيلا فيرفكس</textarea></div>
+ <br>
+ <div class="block" contenteditable="true" id="contenteditable">ABC DEF GHI</div>
+ <br>
+ <div class="block" id="content">ABC DEF GHI</div>
+ <br>
+ <div style="-moz-user-select: none;" id="non-selectable">Non-selectable</div>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_clearing.html b/testing/marionette/harness/marionette_harness/www/test_clearing.html
new file mode 100644
index 000000000..2aa3c6a21
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_clearing.html
@@ -0,0 +1,24 @@
+<html>
+ <!-- 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/. -->
+ <body>
+ <input id="writableTextInput" type="text" value="Test"/>
+
+ <input id="readOnlyTextInput" type="text" readonly value="Test"/>
+
+ <input id="textInputnotenabled" type="text" disabled="true" value="Test"/>
+
+ <textarea id="writableTextArea" rows="2" cols="20">
+ This is a sample text area which is supposed to be cleared
+ </textarea>
+
+ <textarea id="textAreaReadOnly" readonly rows="5" cols="20">
+ text area which is not supposed to be cleared</textarea>
+
+ <textarea rows="5" id="textAreaNotenabled" disabled="true" cols="20">
+ text area which is not supposed to be cleared</textarea>
+
+ <div id="content-editable" contentEditable="true">This is a contentEditable area</div>
+ </body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_dynamic.html b/testing/marionette/harness/marionette_harness/www/test_dynamic.html
new file mode 100644
index 000000000..dccc2c6ac
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_dynamic.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title></title>
+ <script type="text/javascript">
+ var next = 0;
+
+ function addMore() {
+ var box = document.createElement('DIV');
+ box.id = 'box' + next++;
+ box.className = 'redbox';
+ box.style.width = '150px';
+ box.style.height = '150px';
+ box.style.backgroundColor = 'red';
+ box.style.border = '1px solid black';
+ box.style.margin = '5px';
+ window.setTimeout(function() {
+ document.body.appendChild(box);
+ }, 1000);
+ }
+
+ function reveal() {
+ var elem = document.getElementById('revealed');
+ window.setTimeout(function() {
+ elem.style.display = '';
+ }, 1000);
+ }
+ </script>
+ </head>
+ <body>
+ <input id="adder" type="button" value="Add a box!" onclick="addMore()"/>
+
+ <input id="reveal" type="button" value="Reveal a new input" onclick="reveal();" />
+
+ <input id="revealed" style="display:none;" />
+ </body>
+ </html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_iframe.html b/testing/marionette/harness/marionette_harness/www/test_iframe.html
new file mode 100644
index 000000000..7ed88665c
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_iframe.html
@@ -0,0 +1,16 @@
+<!-- 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/. -->
+
+<!doctype html>
+<html>
+<head>
+<title>Marionette IFrame Test</title>
+</head>
+<body>
+ <h1 id="iframe_page_heading">This is the heading</h1>
+
+ <iframe src="test.html" id="test_iframe"></iframe>
+ <iframe src="test.html" id="test_iframe" name="test_iframe_name"></iframe>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_inner_iframe.html b/testing/marionette/harness/marionette_harness/www/test_inner_iframe.html
new file mode 100644
index 000000000..c836fc6e4
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_inner_iframe.html
@@ -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/. -->
+
+<!doctype html>
+<html>
+<head>
+<title>Inner Iframe</title>
+</head>
+<body>
+ <iframe src="test.html" id="inner_frame"></iframe>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_nested_iframe.html b/testing/marionette/harness/marionette_harness/www/test_nested_iframe.html
new file mode 100644
index 000000000..b4482183e
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_nested_iframe.html
@@ -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/. -->
+
+<!doctype html>
+<html>
+<head>
+<title>Marionette IFrame Test</title>
+</head>
+<body>
+ <iframe src="test_inner_iframe.html" id="test_iframe"></iframe>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_oop_1.html b/testing/marionette/harness/marionette_harness/www/test_oop_1.html
new file mode 100644
index 000000000..62e4ad047
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_oop_1.html
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>OOP Test Frame 1</title>
+</head>
+<body>
+ <h1 id="testh1">OOP Test Frame 1</h1>
+ Hello!
+</body>
+</html>
+
diff --git a/testing/marionette/harness/marionette_harness/www/test_oop_2.html b/testing/marionette/harness/marionette_harness/www/test_oop_2.html
new file mode 100644
index 000000000..05aca5adb
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_oop_2.html
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>OOP Test Frame 2</title>
+</head>
+<body>
+ <h1 id="testh1">OOP Test Frame 2</h1>
+ Hello!
+</body>
+</html>
+
diff --git a/testing/marionette/harness/marionette_harness/www/test_shadow_dom.html b/testing/marionette/harness/marionette_harness/www/test_shadow_dom.html
new file mode 100644
index 000000000..3ee893e6d
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_shadow_dom.html
@@ -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/. -->
+
+<!DOCTYPE html>
+
+<html>
+<meta charset="UTF-8">
+<head>
+<title>Marionette Test</title>
+</head>
+<body>
+ <div id="host"></div>
+ <div id="empty-host"></div>
+ <script>
+ 'use strict';
+ var host = document.getElementById('host');
+ var root = host.createShadowRoot();
+ root.innerHTML = '<button id="button">Foo</button>' +
+ '<div id="inner-host"></div>';
+ var innerHost = host.shadowRoot.getElementById('inner-host');
+ var innerRoot = innerHost.createShadowRoot();
+ innerRoot.innerHTML = '<button id="inner-button">Bar</button>';
+ </script>
+</body>
+</html>
diff --git a/testing/marionette/harness/marionette_harness/www/test_windows.html b/testing/marionette/harness/marionette_harness/www/test_windows.html
new file mode 100644
index 000000000..da1fd799e
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/test_windows.html
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>XHTML Test Page</title>
+</head>
+<body>
+ <p><a href="resultPage.html" onClick='javascript:window.open("resultPage.html",null, "menubar=0,location=1,resizable=1,scrollbars=1,status=0,width=700,height=375");' name="windowOne">Open new window</a></p>
+</body>
+</html>
+
diff --git a/testing/marionette/harness/marionette_harness/www/white.png b/testing/marionette/harness/marionette_harness/www/white.png
new file mode 100644
index 000000000..8a68c1154
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/white.png
Binary files differ
diff --git a/testing/marionette/harness/marionette_harness/www/windowHandles.html b/testing/marionette/harness/marionette_harness/www/windowHandles.html
new file mode 100644
index 000000000..165526c8a
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/windowHandles.html
@@ -0,0 +1,16 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>Marionette New Tab Link</title>
+</head>
+<body>
+ <a href="empty.html" id="new-tab" target="_blank">New Tab</a>
+ <a href="about:blank" id="new-blank-tab" target="_blank">New blank Tab</a>
+
+ <a href="" id="new-window" onClick='javascript:window.open("empty.html", null, "location=1,toolbar=1");'>New Window</a>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/marionette/harness/marionette_harness/www/xhtmlTest.html b/testing/marionette/harness/marionette_harness/www/xhtmlTest.html
new file mode 100644
index 000000000..146def33a
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/xhtmlTest.html
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <!-- 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/. -->
+<head>
+ <title>XHTML Test Page</title>
+</head>
+<body>
+<div class="navigation">
+ <p><a href="resultPage.html" target="result" name="windowOne">Open new window</a></p>
+ <p><a href="iframes.html" target="_blank" name="windowTwo">Create a new anonymous window</a></p>
+ <p><a href="test_iframe.html" name="sameWindow">Open page with iframes in same window</a></p>
+ <p><a href="javascriptPage.html" target="result" name="windowThree">Open a window with a close button</a></p>
+</div>
+
+<a name="notext"><b></b></a>
+
+<div class="content">
+ <h1 class="header">XHTML Might Be The Future</h1>
+
+ <p>If you'd like to go elsewhere then <a href="resultPage.html">click me</a>.</p>
+
+ <p>Alternatively, <a href="resultPage.html" id="linkId">this goes to the same place</a>.</p>
+
+ <form name="someForm">
+ <input id="username" type="text" value="change"/>
+ </form>
+
+ This link has the same text as another link: <a href="resultPage.html">click me</a>.
+</div>
+
+<div class="extraDiv">Another div starts here.<p/>
+ <h2 class="nameA nameBnoise nameC">An H2 title</h2>
+ <p class="nameC">Some more text</p>
+</div>
+
+<div>
+ <a id="id1" href="#">Foo</a>
+ <ul id="id2" />
+ <span id="id3"/>
+</div>
+
+<div>
+ <table id="table" ></table>
+</div>
+
+<span id="amazing">
+<div>
+ <div>
+ <div>
+ <span/>
+ <a>I have width</a>
+ </div>
+ </div>
+</div>
+</span>
+
+<a name="text" />
+<p id="spaces"> </p>
+<p id="empty"></p>
+<a href="foo" id="linkWithEqualsSign">Link=equalssign</a>
+
+<p class=" spaceAround ">Spaced out</p>
+
+<span id="my_span">
+ <div>first_div</div>
+ <div>second_div</div>
+ <span>first_span</span>
+ <span>second_span</span>
+</span>
+
+<div id="parent">I'm a parent
+ <div id="child">I'm a child</div>
+</div>
+
+<div id="only-exists-on-xhtmltest">Woo woo</div>
+</body>
+</html>
diff --git a/testing/marionette/harness/requirements.txt b/testing/marionette/harness/requirements.txt
new file mode 100644
index 000000000..75ab9ce92
--- /dev/null
+++ b/testing/marionette/harness/requirements.txt
@@ -0,0 +1,14 @@
+browsermob-proxy >= 0.6.0
+manifestparser >= 1.1
+marionette-driver >= 2.2.0
+mozcrash >= 0.5
+mozdevice >= 0.44
+mozinfo >= 0.8
+mozlog >= 3.0
+moznetwork >= 0.21
+mozprocess >= 0.9
+mozprofile >= 0.7
+mozrunner >= 6.13
+moztest >= 0.8
+mozversion >= 1.1
+wptserve >= 1.3.0
diff --git a/testing/marionette/harness/setup.py b/testing/marionette/harness/setup.py
new file mode 100644
index 000000000..d28a970c1
--- /dev/null
+++ b/testing/marionette/harness/setup.py
@@ -0,0 +1,59 @@
+import os
+import re
+
+from setuptools import find_packages, setup
+
+
+THIS_DIR = os.path.dirname(os.path.realpath(__name__))
+
+
+def read(*parts):
+ with open(os.path.join(THIS_DIR, *parts)) as f:
+ return f.read()
+
+
+def get_version():
+ return re.findall("__version__ = '([\d\.]+)'",
+ read('marionette_harness', '__init__.py'), re.M)[0]
+
+
+setup(name='marionette-harness',
+ version=get_version(),
+ description="Marionette test automation harness",
+ long_description=open('README.rst').read(),
+ # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)',
+ 'Operating System :: MacOS :: MacOS X',
+ 'Operating System :: Microsoft :: Windows',
+ 'Operating System :: POSIX',
+ 'Topic :: Software Development :: Quality Assurance',
+ 'Topic :: Software Development :: Testing',
+ 'Topic :: Utilities',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.7',
+ ],
+ keywords='mozilla',
+ author='Auto-tools',
+ author_email='tools-marionette@lists.mozilla.org',
+ url='https://wiki.mozilla.org/Auto-tools/Projects/Marionette',
+ license='Mozilla Public License 2.0 (MPL 2.0)',
+ packages=find_packages(),
+ package_data={
+ 'marionette_harness': [
+ 'runner/test.cert',
+ 'runner/test.key'
+ ],
+ },
+ # Needed to include package data as specified in MANIFEST.in
+ include_package_data=True,
+ install_requires=read('requirements.txt').splitlines(),
+ zip_safe=False,
+ entry_points="""
+ # -*- Entry points: -*-
+ [console_scripts]
+ marionette = marionette_harness.runtests:cli
+ """,
+ )