Unreviewed. Update W3C WebDriver imported tests.
authorbburg@apple.com <bburg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 5 Dec 2018 01:04:22 +0000 (01:04 +0000)
committerbburg@apple.com <bburg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 5 Dec 2018 01:04:22 +0000 (01:04 +0000)
<rdar://problem/46470254>

* imported/w3c/importer.json:
* imported/w3c/tools/webdriver/webdriver/client.py:
* imported/w3c/tools/webdriver/webdriver/error.py:
* imported/w3c/tools/wptrunner/docs/design.rst:
* imported/w3c/tools/wptrunner/requirements.txt:
* imported/w3c/tools/wptrunner/requirements_chrome.txt:
* imported/w3c/tools/wptrunner/requirements_chrome_android.txt:
* imported/w3c/tools/wptrunner/requirements_edge.txt:
* imported/w3c/tools/wptrunner/requirements_firefox.txt:
* imported/w3c/tools/wptrunner/requirements_ie.txt:
* imported/w3c/tools/wptrunner/requirements_opera.txt:
* imported/w3c/tools/wptrunner/requirements_safari.txt:
* imported/w3c/tools/wptrunner/requirements_sauce.txt:
* imported/w3c/tools/wptrunner/tox.ini:
* imported/w3c/tools/wptrunner/wptrunner/browsers/__init__.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/base.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/chrome.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/edge_webdriver.py: Added.
* imported/w3c/tools/wptrunner/wptrunner/browsers/fennec.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/firefox.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/safari.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/sauce.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/servodriver.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/webkit.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/base.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executormarionette.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executorservo.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executorwebdriver.py: Copied from WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py.
* imported/w3c/tools/wptrunner/wptrunner/executors/protocol.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/runner.js: Added.
* imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver.js:
* imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js:
* imported/w3c/tools/wptrunner/wptrunner/formatters.py:
* imported/w3c/tools/wptrunner/wptrunner/manifestexpected.py:
* imported/w3c/tools/wptrunner/wptrunner/stability.py:
* imported/w3c/tools/wptrunner/wptrunner/testdriver-extra.js:
* imported/w3c/tools/wptrunner/wptrunner/testloader.py:
* imported/w3c/tools/wptrunner/wptrunner/testrunner.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/base.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_formatters.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_stability.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_testloader.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_update.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_wpttest.py:
* imported/w3c/tools/wptrunner/wptrunner/update/tree.py:
* imported/w3c/tools/wptrunner/wptrunner/update/update.py:
* imported/w3c/tools/wptrunner/wptrunner/webdriver_server.py:
* imported/w3c/tools/wptrunner/wptrunner/wptcommandline.py:
* imported/w3c/tools/wptrunner/wptrunner/wptmanifest/parser.py:
* imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py:
* imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py:
* imported/w3c/tools/wptrunner/wptrunner/wptrunner.py:
* imported/w3c/tools/wptrunner/wptrunner/wpttest.py:
* imported/w3c/webdriver/tests/add_cookie/add.py:
* imported/w3c/webdriver/tests/add_cookie/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/back/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/conftest.py:
* imported/w3c/webdriver/tests/delete_all_cookies/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/delete_session/delete.py:
* imported/w3c/webdriver/tests/element_clear/clear.py:
* imported/w3c/webdriver/tests/element_clear/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/element_click/center_point.py: Added.
* imported/w3c/webdriver/tests/element_click/interactability.py:
* imported/w3c/webdriver/tests/element_click/scroll_into_view.py:
* imported/w3c/webdriver/tests/element_click/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/element_send_keys/file_upload.py:
* imported/w3c/webdriver/tests/element_send_keys/interactability.py:
* imported/w3c/webdriver/tests/element_send_keys/scroll_into_view.py:
* imported/w3c/webdriver/tests/element_send_keys/send_keys.py:
* imported/w3c/webdriver/tests/execute_async_script/execute_async.py:
* imported/w3c/webdriver/tests/execute_script/execute.py:
* imported/w3c/webdriver/tests/execute_script/promise.py: Added.
* imported/w3c/webdriver/tests/find_element/find.py:
* imported/w3c/webdriver/tests/find_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/find_element_from_element/find.py:
* imported/w3c/webdriver/tests/find_element_from_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py.
* imported/w3c/webdriver/tests/find_elements/find.py:
* imported/w3c/webdriver/tests/find_elements/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/find_elements_from_element/find.py:
* imported/w3c/webdriver/tests/find_elements_from_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py.
* imported/w3c/webdriver/tests/fullscreen_window/fullscreen.py:
* imported/w3c/webdriver/tests/fullscreen_window/stress.py: Added.
* imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py:
* imported/w3c/webdriver/tests/get_active_element/get.py:
* imported/w3c/webdriver/tests/get_active_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_current_url/get.py:
* imported/w3c/webdriver/tests/get_element_attribute/__init__.py: Added.
* imported/w3c/webdriver/tests/get_element_attribute/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_element_css_value/__init__.py: Added.
* imported/w3c/webdriver/tests/get_element_css_value/get.py: Added.
* imported/w3c/webdriver/tests/get_element_css_value/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_element_rect/__init__.py: Added.
* imported/w3c/webdriver/tests/get_element_rect/get.py: Added.
* imported/w3c/webdriver/tests/get_element_rect/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_element_text/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_named_cookie/get.py:
* imported/w3c/webdriver/tests/get_named_cookie/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_page_source/__init__.py: Added.
* imported/w3c/webdriver/tests/get_page_source/source.py: Added.
* imported/w3c/webdriver/tests/get_page_source/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_title/get.py:
* imported/w3c/webdriver/tests/get_window_handle/__init__.py: Added.
* imported/w3c/webdriver/tests/get_window_handle/get.py: Added.
* imported/w3c/webdriver/tests/get_window_handle/user_prompts.py: Added.
* imported/w3c/webdriver/tests/get_window_handles/__init__.py: Added.
* imported/w3c/webdriver/tests/get_window_handles/get.py: Added.
* imported/w3c/webdriver/tests/get_window_handles/user_prompts.py: Added.
* imported/w3c/webdriver/tests/is_element_enabled/__init__.py: Added.
* imported/w3c/webdriver/tests/is_element_enabled/enabled.py: Added.
* imported/w3c/webdriver/tests/is_element_enabled/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/maximize_window/maximize.py:
* imported/w3c/webdriver/tests/maximize_window/stress.py: Added.
* imported/w3c/webdriver/tests/minimize_window/minimize.py:
* imported/w3c/webdriver/tests/minimize_window/stress.py: Added.
* imported/w3c/webdriver/tests/minimize_window/user_prompts.py:
* imported/w3c/webdriver/tests/navigate_to/navigate.py:
* imported/w3c/webdriver/tests/navigate_to/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/new_session/invalid_capabilities.py:
* imported/w3c/webdriver/tests/new_session/platform_name.py:
* imported/w3c/webdriver/tests/new_session/response.py:
* imported/w3c/webdriver/tests/new_session/support/create.py:
* imported/w3c/webdriver/tests/perform_actions/__init__.py: Added.
* imported/w3c/webdriver/tests/perform_actions/conftest.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key_events.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key_modifiers.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key_shortcuts.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key_special_keys.py: Added.
* imported/w3c/webdriver/tests/perform_actions/none.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_contextmenu.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_dblclick.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_modifier_click.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_origin.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_pause_dblclick.py: Added.
* imported/w3c/webdriver/tests/perform_actions/sequence.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/__init__.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/keys.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/mouse.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/refine.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/test_actions_wdspec.html: Added.
* imported/w3c/webdriver/tests/perform_actions/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/perform_actions/validity.py: Added.
* imported/w3c/webdriver/tests/permissions/set.py: Added.
* imported/w3c/webdriver/tests/release_actions/__init__.py: Added.
* imported/w3c/webdriver/tests/release_actions/conftest.py: Added.
* imported/w3c/webdriver/tests/release_actions/release.py: Added.
* imported/w3c/webdriver/tests/release_actions/sequence.py: Added.
* imported/w3c/webdriver/tests/release_actions/support/__init__.py: Added.
* imported/w3c/webdriver/tests/release_actions/support/refine.py: Added.
* imported/w3c/webdriver/tests/release_actions/support/test_actions_wdspec.html: Added.
* imported/w3c/webdriver/tests/send_alert_text/send.py:
* imported/w3c/webdriver/tests/set_timeouts/set.py:
* imported/w3c/webdriver/tests/set_timeouts/user_prompts.py: Added.
* imported/w3c/webdriver/tests/set_window_rect/set.py:
* imported/w3c/webdriver/tests/support/asserts.py:
* imported/w3c/webdriver/tests/support/defaults.py: Added.
* imported/w3c/webdriver/tests/support/fixtures.py:
* imported/w3c/webdriver/tests/support/helpers.py: Added.
* imported/w3c/webdriver/tests/support/http_request.py:
* imported/w3c/webdriver/tests/support/image.py: Added.
* imported/w3c/webdriver/tests/support/inline.py:
* imported/w3c/webdriver/tests/support/sync.py: Added.
* imported/w3c/webdriver/tests/switch_to_frame/switch.py:
* imported/w3c/webdriver/tests/switch_to_window/switch.py:
* imported/w3c/webdriver/tests/take_element_screenshot/__init__.py: Added.
* imported/w3c/webdriver/tests/take_element_screenshot/screenshot.py: Added.
* imported/w3c/webdriver/tests/take_element_screenshot/user_prompts.py: Added.
* imported/w3c/webdriver/tests/take_screenshot/__init__.py: Added.
* imported/w3c/webdriver/tests/take_screenshot/screenshot.py: Added.
* imported/w3c/webdriver/tests/take_screenshot/user_prompts.py: Added.
* imported/w3c/importer.json:
* imported/w3c/tools/webdriver/webdriver/client.py:
* imported/w3c/tools/webdriver/webdriver/error.py:
* imported/w3c/tools/wptrunner/docs/design.rst:
* imported/w3c/tools/wptrunner/requirements.txt:
* imported/w3c/tools/wptrunner/requirements_chrome.txt:
* imported/w3c/tools/wptrunner/requirements_chrome_android.txt:
* imported/w3c/tools/wptrunner/requirements_edge.txt:
* imported/w3c/tools/wptrunner/requirements_firefox.txt:
* imported/w3c/tools/wptrunner/requirements_ie.txt:
* imported/w3c/tools/wptrunner/requirements_opera.txt:
* imported/w3c/tools/wptrunner/requirements_safari.txt:
* imported/w3c/tools/wptrunner/requirements_sauce.txt:
* imported/w3c/tools/wptrunner/tox.ini:
* imported/w3c/tools/wptrunner/wptrunner/browsers/__init__.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/base.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/chrome.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/edge_webdriver.py: Added.
* imported/w3c/tools/wptrunner/wptrunner/browsers/fennec.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/firefox.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/safari.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/sauce.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/servodriver.py:
* imported/w3c/tools/wptrunner/wptrunner/browsers/webkit.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/base.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executormarionette.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executorservo.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executorwebdriver.py: Copied from WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py.
* imported/w3c/tools/wptrunner/wptrunner/executors/protocol.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/runner.js: Added.
* imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver.js:
* imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js:
* imported/w3c/tools/wptrunner/wptrunner/formatters.py:
* imported/w3c/tools/wptrunner/wptrunner/manifestexpected.py:
* imported/w3c/tools/wptrunner/wptrunner/stability.py:
* imported/w3c/tools/wptrunner/wptrunner/testdriver-extra.js:
* imported/w3c/tools/wptrunner/wptrunner/testloader.py:
* imported/w3c/tools/wptrunner/wptrunner/testrunner.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/base.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_formatters.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_stability.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_testloader.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_update.py:
* imported/w3c/tools/wptrunner/wptrunner/tests/test_wpttest.py:
* imported/w3c/tools/wptrunner/wptrunner/update/tree.py:
* imported/w3c/tools/wptrunner/wptrunner/update/update.py:
* imported/w3c/tools/wptrunner/wptrunner/webdriver_server.py:
* imported/w3c/tools/wptrunner/wptrunner/wptcommandline.py:
* imported/w3c/tools/wptrunner/wptrunner/wptmanifest/parser.py:
* imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py:
* imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py:
* imported/w3c/tools/wptrunner/wptrunner/wptrunner.py:
* imported/w3c/tools/wptrunner/wptrunner/wpttest.py:
* imported/w3c/webdriver/tests/add_cookie/add.py:
* imported/w3c/webdriver/tests/add_cookie/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/back/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/conftest.py:
* imported/w3c/webdriver/tests/delete_all_cookies/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/delete_session/delete.py:
* imported/w3c/webdriver/tests/element_clear/clear.py:
* imported/w3c/webdriver/tests/element_clear/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/element_click/center_point.py: Added.
* imported/w3c/webdriver/tests/element_click/interactability.py:
* imported/w3c/webdriver/tests/element_click/scroll_into_view.py:
* imported/w3c/webdriver/tests/element_click/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/element_send_keys/file_upload.py:
* imported/w3c/webdriver/tests/element_send_keys/interactability.py:
* imported/w3c/webdriver/tests/element_send_keys/scroll_into_view.py:
* imported/w3c/webdriver/tests/element_send_keys/send_keys.py:
* imported/w3c/webdriver/tests/execute_async_script/execute_async.py:
* imported/w3c/webdriver/tests/execute_script/execute.py:
* imported/w3c/webdriver/tests/execute_script/promise.py: Added.
* imported/w3c/webdriver/tests/find_element/find.py:
* imported/w3c/webdriver/tests/find_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/find_element_from_element/find.py:
* imported/w3c/webdriver/tests/find_element_from_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py.
* imported/w3c/webdriver/tests/find_elements/find.py:
* imported/w3c/webdriver/tests/find_elements/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/find_elements_from_element/find.py:
* imported/w3c/webdriver/tests/find_elements_from_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py.
* imported/w3c/webdriver/tests/fullscreen_window/fullscreen.py:
* imported/w3c/webdriver/tests/fullscreen_window/stress.py: Added.
* imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py:
* imported/w3c/webdriver/tests/get_active_element/get.py:
* imported/w3c/webdriver/tests/get_active_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_current_url/get.py:
* imported/w3c/webdriver/tests/get_element_attribute/__init__.py: Added.
* imported/w3c/webdriver/tests/get_element_attribute/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_element_css_value/__init__.py: Added.
* imported/w3c/webdriver/tests/get_element_css_value/get.py: Added.
* imported/w3c/webdriver/tests/get_element_css_value/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_element_rect/__init__.py: Added.
* imported/w3c/webdriver/tests/get_element_rect/get.py: Added.
* imported/w3c/webdriver/tests/get_element_rect/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_element_text/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_named_cookie/get.py:
* imported/w3c/webdriver/tests/get_named_cookie/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_page_source/__init__.py: Added.
* imported/w3c/webdriver/tests/get_page_source/source.py: Added.
* imported/w3c/webdriver/tests/get_page_source/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/get_title/get.py:
* imported/w3c/webdriver/tests/get_window_handle/__init__.py: Added.
* imported/w3c/webdriver/tests/get_window_handle/get.py: Added.
* imported/w3c/webdriver/tests/get_window_handle/user_prompts.py: Added.
* imported/w3c/webdriver/tests/get_window_handles/__init__.py: Added.
* imported/w3c/webdriver/tests/get_window_handles/get.py: Added.
* imported/w3c/webdriver/tests/get_window_handles/user_prompts.py: Added.
* imported/w3c/webdriver/tests/is_element_enabled/__init__.py: Added.
* imported/w3c/webdriver/tests/is_element_enabled/enabled.py: Added.
* imported/w3c/webdriver/tests/is_element_enabled/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/maximize_window/maximize.py:
* imported/w3c/webdriver/tests/maximize_window/stress.py: Added.
* imported/w3c/webdriver/tests/minimize_window/minimize.py:
* imported/w3c/webdriver/tests/minimize_window/stress.py: Added.
* imported/w3c/webdriver/tests/minimize_window/user_prompts.py:
* imported/w3c/webdriver/tests/navigate_to/navigate.py:
* imported/w3c/webdriver/tests/navigate_to/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/new_session/invalid_capabilities.py:
* imported/w3c/webdriver/tests/new_session/platform_name.py:
* imported/w3c/webdriver/tests/new_session/response.py:
* imported/w3c/webdriver/tests/new_session/support/create.py:
* imported/w3c/webdriver/tests/perform_actions/__init__.py: Added.
* imported/w3c/webdriver/tests/perform_actions/conftest.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key_events.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key_modifiers.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key_shortcuts.py: Added.
* imported/w3c/webdriver/tests/perform_actions/key_special_keys.py: Added.
* imported/w3c/webdriver/tests/perform_actions/none.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_contextmenu.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_dblclick.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_modifier_click.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_origin.py: Added.
* imported/w3c/webdriver/tests/perform_actions/pointer_pause_dblclick.py: Added.
* imported/w3c/webdriver/tests/perform_actions/sequence.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/__init__.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/keys.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/mouse.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/refine.py: Added.
* imported/w3c/webdriver/tests/perform_actions/support/test_actions_wdspec.html: Added.
* imported/w3c/webdriver/tests/perform_actions/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
* imported/w3c/webdriver/tests/perform_actions/validity.py: Added.
* imported/w3c/webdriver/tests/permissions/set.py: Added.
* imported/w3c/webdriver/tests/release_actions/__init__.py: Added.
* imported/w3c/webdriver/tests/release_actions/conftest.py: Added.
* imported/w3c/webdriver/tests/release_actions/release.py: Added.
* imported/w3c/webdriver/tests/release_actions/sequence.py: Added.
* imported/w3c/webdriver/tests/release_actions/support/__init__.py: Added.
* imported/w3c/webdriver/tests/release_actions/support/refine.py: Added.
* imported/w3c/webdriver/tests/release_actions/support/test_actions_wdspec.html: Added.
* imported/w3c/webdriver/tests/send_alert_text/send.py:
* imported/w3c/webdriver/tests/set_timeouts/set.py:
* imported/w3c/webdriver/tests/set_timeouts/user_prompts.py: Added.
* imported/w3c/webdriver/tests/set_window_rect/set.py:
* imported/w3c/webdriver/tests/support/asserts.py:
* imported/w3c/webdriver/tests/support/defaults.py: Added.
* imported/w3c/webdriver/tests/support/fixtures.py:
* imported/w3c/webdriver/tests/support/helpers.py: Added.
* imported/w3c/webdriver/tests/support/http_request.py:
* imported/w3c/webdriver/tests/support/image.py: Added.
* imported/w3c/webdriver/tests/support/inline.py:
* imported/w3c/webdriver/tests/support/sync.py: Added.
* imported/w3c/webdriver/tests/switch_to_frame/switch.py:
* imported/w3c/webdriver/tests/switch_to_window/switch.py:
* imported/w3c/webdriver/tests/take_element_screenshot/__init__.py: Added.
* imported/w3c/webdriver/tests/take_element_screenshot/screenshot.py: Added.
* imported/w3c/webdriver/tests/take_element_screenshot/user_prompts.py: Added.
* imported/w3c/webdriver/tests/take_screenshot/__init__.py: Added.
* imported/w3c/webdriver/tests/take_screenshot/screenshot.py: Added.
* imported/w3c/webdriver/tests/take_screenshot/user_prompts.py: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@238881 268f45cc-cd09-0410-ab3c-d52691b4dbfc

173 files changed:
WebDriverTests/ChangeLog
WebDriverTests/imported/w3c/importer.json
WebDriverTests/imported/w3c/tools/webdriver/webdriver/client.py
WebDriverTests/imported/w3c/tools/webdriver/webdriver/error.py
WebDriverTests/imported/w3c/tools/wptrunner/docs/design.rst
WebDriverTests/imported/w3c/tools/wptrunner/requirements.txt
WebDriverTests/imported/w3c/tools/wptrunner/requirements_chrome.txt
WebDriverTests/imported/w3c/tools/wptrunner/requirements_chrome_android.txt
WebDriverTests/imported/w3c/tools/wptrunner/requirements_edge.txt
WebDriverTests/imported/w3c/tools/wptrunner/requirements_firefox.txt
WebDriverTests/imported/w3c/tools/wptrunner/requirements_ie.txt
WebDriverTests/imported/w3c/tools/wptrunner/requirements_opera.txt
WebDriverTests/imported/w3c/tools/wptrunner/requirements_safari.txt
WebDriverTests/imported/w3c/tools/wptrunner/requirements_sauce.txt
WebDriverTests/imported/w3c/tools/wptrunner/tox.ini
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/__init__.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/base.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/chrome.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/edge_webdriver.py [new file with mode: 0644]
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/fennec.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/firefox.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/safari.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/sauce.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/servodriver.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/webkit.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/base.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executormarionette.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorservo.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorwebdriver.py [new file with mode: 0644]
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/protocol.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/runner.js [new file with mode: 0644]
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver.js
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/formatters.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/manifestexpected.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/stability.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/testdriver-extra.js
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/testloader.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/testrunner.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/tests/base.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/tests/test_formatters.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/tests/test_stability.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/tests/test_testloader.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/tests/test_update.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/tests/test_wpttest.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/update/tree.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/update/update.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/webdriver_server.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/wptcommandline.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/wptmanifest/parser.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/wptrunner.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/wpttest.py
WebDriverTests/imported/w3c/webdriver/tests/add_cookie/add.py
WebDriverTests/imported/w3c/webdriver/tests/add_cookie/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/back/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/conftest.py
WebDriverTests/imported/w3c/webdriver/tests/delete_all_cookies/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/delete_session/delete.py
WebDriverTests/imported/w3c/webdriver/tests/element_clear/clear.py
WebDriverTests/imported/w3c/webdriver/tests/element_clear/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/element_click/center_point.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/element_click/interactability.py
WebDriverTests/imported/w3c/webdriver/tests/element_click/scroll_into_view.py
WebDriverTests/imported/w3c/webdriver/tests/element_click/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/element_send_keys/file_upload.py
WebDriverTests/imported/w3c/webdriver/tests/element_send_keys/interactability.py
WebDriverTests/imported/w3c/webdriver/tests/element_send_keys/scroll_into_view.py
WebDriverTests/imported/w3c/webdriver/tests/element_send_keys/send_keys.py
WebDriverTests/imported/w3c/webdriver/tests/execute_async_script/execute_async.py
WebDriverTests/imported/w3c/webdriver/tests/execute_script/execute.py
WebDriverTests/imported/w3c/webdriver/tests/execute_script/promise.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/find_element/find.py
WebDriverTests/imported/w3c/webdriver/tests/find_element/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/find_element_from_element/find.py
WebDriverTests/imported/w3c/webdriver/tests/find_element_from_element/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/find_elements/find.py
WebDriverTests/imported/w3c/webdriver/tests/find_elements/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/find_elements_from_element/find.py
WebDriverTests/imported/w3c/webdriver/tests/find_elements_from_element/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/fullscreen.py
WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/stress.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py
WebDriverTests/imported/w3c/webdriver/tests/get_active_element/get.py
WebDriverTests/imported/w3c/webdriver/tests/get_active_element/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_current_url/get.py
WebDriverTests/imported/w3c/webdriver/tests/get_element_attribute/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_element_attribute/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_element_css_value/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_element_css_value/get.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_element_css_value/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_element_rect/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_element_rect/get.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_element_rect/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_element_text/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_named_cookie/get.py
WebDriverTests/imported/w3c/webdriver/tests/get_named_cookie/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_page_source/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_page_source/source.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_page_source/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_title/get.py
WebDriverTests/imported/w3c/webdriver/tests/get_window_handle/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_window_handle/get.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_window_handle/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_window_handles/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_window_handles/get.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/get_window_handles/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/is_element_enabled/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/is_element_enabled/enabled.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/is_element_enabled/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/maximize_window/maximize.py
WebDriverTests/imported/w3c/webdriver/tests/maximize_window/stress.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/minimize_window/minimize.py
WebDriverTests/imported/w3c/webdriver/tests/minimize_window/stress.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py
WebDriverTests/imported/w3c/webdriver/tests/navigate_to/navigate.py
WebDriverTests/imported/w3c/webdriver/tests/navigate_to/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/new_session/invalid_capabilities.py
WebDriverTests/imported/w3c/webdriver/tests/new_session/platform_name.py
WebDriverTests/imported/w3c/webdriver/tests/new_session/response.py
WebDriverTests/imported/w3c/webdriver/tests/new_session/support/create.py
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/conftest.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/key.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/key_events.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/key_modifiers.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/key_shortcuts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/key_special_keys.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/none.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/pointer.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/pointer_contextmenu.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/pointer_dblclick.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/pointer_modifier_click.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/pointer_origin.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/pointer_pause_dblclick.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/sequence.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/support/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/support/keys.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/support/mouse.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/support/refine.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/support/test_actions_wdspec.html [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/perform_actions/validity.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/permissions/set.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/release_actions/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/release_actions/conftest.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/release_actions/release.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/release_actions/sequence.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/release_actions/support/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/release_actions/support/refine.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/release_actions/support/test_actions_wdspec.html [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/send_alert_text/send.py
WebDriverTests/imported/w3c/webdriver/tests/set_timeouts/set.py
WebDriverTests/imported/w3c/webdriver/tests/set_timeouts/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/set_window_rect/set.py
WebDriverTests/imported/w3c/webdriver/tests/support/asserts.py
WebDriverTests/imported/w3c/webdriver/tests/support/defaults.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/support/fixtures.py
WebDriverTests/imported/w3c/webdriver/tests/support/helpers.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/support/http_request.py
WebDriverTests/imported/w3c/webdriver/tests/support/image.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/support/inline.py
WebDriverTests/imported/w3c/webdriver/tests/support/sync.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/switch_to_frame/switch.py
WebDriverTests/imported/w3c/webdriver/tests/switch_to_window/switch.py
WebDriverTests/imported/w3c/webdriver/tests/take_element_screenshot/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/take_element_screenshot/screenshot.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/take_element_screenshot/user_prompts.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/take_screenshot/__init__.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/take_screenshot/screenshot.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/take_screenshot/user_prompts.py [new file with mode: 0644]

index 268f562..de9c336 100644 (file)
@@ -1,3 +1,353 @@
+2018-12-04  Brian Burg  <bburg@apple.com>
+
+        Unreviewed. Update W3C WebDriver imported tests.
+        <rdar://problem/46470254>
+
+        * imported/w3c/importer.json:
+        * imported/w3c/tools/webdriver/webdriver/client.py:
+        * imported/w3c/tools/webdriver/webdriver/error.py:
+        * imported/w3c/tools/wptrunner/docs/design.rst:
+        * imported/w3c/tools/wptrunner/requirements.txt:
+        * imported/w3c/tools/wptrunner/requirements_chrome.txt:
+        * imported/w3c/tools/wptrunner/requirements_chrome_android.txt:
+        * imported/w3c/tools/wptrunner/requirements_edge.txt:
+        * imported/w3c/tools/wptrunner/requirements_firefox.txt:
+        * imported/w3c/tools/wptrunner/requirements_ie.txt:
+        * imported/w3c/tools/wptrunner/requirements_opera.txt:
+        * imported/w3c/tools/wptrunner/requirements_safari.txt:
+        * imported/w3c/tools/wptrunner/requirements_sauce.txt:
+        * imported/w3c/tools/wptrunner/tox.ini:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/__init__.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/base.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/chrome.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/edge_webdriver.py: Added.
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/fennec.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/firefox.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/safari.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/sauce.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/servodriver.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/webkit.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/base.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executormarionette.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executorservo.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executorwebdriver.py: Copied from WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py.
+        * imported/w3c/tools/wptrunner/wptrunner/executors/protocol.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/runner.js: Added.
+        * imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver.js:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js:
+        * imported/w3c/tools/wptrunner/wptrunner/formatters.py:
+        * imported/w3c/tools/wptrunner/wptrunner/manifestexpected.py:
+        * imported/w3c/tools/wptrunner/wptrunner/stability.py:
+        * imported/w3c/tools/wptrunner/wptrunner/testdriver-extra.js:
+        * imported/w3c/tools/wptrunner/wptrunner/testloader.py:
+        * imported/w3c/tools/wptrunner/wptrunner/testrunner.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/base.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_formatters.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_stability.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_testloader.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_update.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_wpttest.py:
+        * imported/w3c/tools/wptrunner/wptrunner/update/tree.py:
+        * imported/w3c/tools/wptrunner/wptrunner/update/update.py:
+        * imported/w3c/tools/wptrunner/wptrunner/webdriver_server.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptcommandline.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptmanifest/parser.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptrunner.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wpttest.py:
+        * imported/w3c/webdriver/tests/add_cookie/add.py:
+        * imported/w3c/webdriver/tests/add_cookie/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/back/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/conftest.py:
+        * imported/w3c/webdriver/tests/delete_all_cookies/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/delete_session/delete.py:
+        * imported/w3c/webdriver/tests/element_clear/clear.py:
+        * imported/w3c/webdriver/tests/element_clear/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/element_click/center_point.py: Added.
+        * imported/w3c/webdriver/tests/element_click/interactability.py:
+        * imported/w3c/webdriver/tests/element_click/scroll_into_view.py:
+        * imported/w3c/webdriver/tests/element_click/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/element_send_keys/file_upload.py:
+        * imported/w3c/webdriver/tests/element_send_keys/interactability.py:
+        * imported/w3c/webdriver/tests/element_send_keys/scroll_into_view.py:
+        * imported/w3c/webdriver/tests/element_send_keys/send_keys.py:
+        * imported/w3c/webdriver/tests/execute_async_script/execute_async.py:
+        * imported/w3c/webdriver/tests/execute_script/execute.py:
+        * imported/w3c/webdriver/tests/execute_script/promise.py: Added.
+        * imported/w3c/webdriver/tests/find_element/find.py:
+        * imported/w3c/webdriver/tests/find_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/find_element_from_element/find.py:
+        * imported/w3c/webdriver/tests/find_element_from_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/find_elements/find.py:
+        * imported/w3c/webdriver/tests/find_elements/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/find_elements_from_element/find.py:
+        * imported/w3c/webdriver/tests/find_elements_from_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/fullscreen_window/fullscreen.py:
+        * imported/w3c/webdriver/tests/fullscreen_window/stress.py: Added.
+        * imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py:
+        * imported/w3c/webdriver/tests/get_active_element/get.py:
+        * imported/w3c/webdriver/tests/get_active_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_current_url/get.py:
+        * imported/w3c/webdriver/tests/get_element_attribute/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_element_attribute/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_element_css_value/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_element_css_value/get.py: Added.
+        * imported/w3c/webdriver/tests/get_element_css_value/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_element_rect/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_element_rect/get.py: Added.
+        * imported/w3c/webdriver/tests/get_element_rect/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_element_text/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_named_cookie/get.py:
+        * imported/w3c/webdriver/tests/get_named_cookie/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_page_source/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_page_source/source.py: Added.
+        * imported/w3c/webdriver/tests/get_page_source/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_title/get.py:
+        * imported/w3c/webdriver/tests/get_window_handle/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handle/get.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handle/user_prompts.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handles/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handles/get.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handles/user_prompts.py: Added.
+        * imported/w3c/webdriver/tests/is_element_enabled/__init__.py: Added.
+        * imported/w3c/webdriver/tests/is_element_enabled/enabled.py: Added.
+        * imported/w3c/webdriver/tests/is_element_enabled/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/maximize_window/maximize.py:
+        * imported/w3c/webdriver/tests/maximize_window/stress.py: Added.
+        * imported/w3c/webdriver/tests/minimize_window/minimize.py:
+        * imported/w3c/webdriver/tests/minimize_window/stress.py: Added.
+        * imported/w3c/webdriver/tests/minimize_window/user_prompts.py:
+        * imported/w3c/webdriver/tests/navigate_to/navigate.py:
+        * imported/w3c/webdriver/tests/navigate_to/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/new_session/invalid_capabilities.py:
+        * imported/w3c/webdriver/tests/new_session/platform_name.py:
+        * imported/w3c/webdriver/tests/new_session/response.py:
+        * imported/w3c/webdriver/tests/new_session/support/create.py:
+        * imported/w3c/webdriver/tests/perform_actions/__init__.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/conftest.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key_events.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key_modifiers.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key_shortcuts.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key_special_keys.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/none.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_contextmenu.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_dblclick.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_modifier_click.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_origin.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_pause_dblclick.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/sequence.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/__init__.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/keys.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/mouse.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/refine.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/test_actions_wdspec.html: Added.
+        * imported/w3c/webdriver/tests/perform_actions/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/perform_actions/validity.py: Added.
+        * imported/w3c/webdriver/tests/permissions/set.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/__init__.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/conftest.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/release.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/sequence.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/support/__init__.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/support/refine.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/support/test_actions_wdspec.html: Added.
+        * imported/w3c/webdriver/tests/send_alert_text/send.py:
+        * imported/w3c/webdriver/tests/set_timeouts/set.py:
+        * imported/w3c/webdriver/tests/set_timeouts/user_prompts.py: Added.
+        * imported/w3c/webdriver/tests/set_window_rect/set.py:
+        * imported/w3c/webdriver/tests/support/asserts.py:
+        * imported/w3c/webdriver/tests/support/defaults.py: Added.
+        * imported/w3c/webdriver/tests/support/fixtures.py:
+        * imported/w3c/webdriver/tests/support/helpers.py: Added.
+        * imported/w3c/webdriver/tests/support/http_request.py:
+        * imported/w3c/webdriver/tests/support/image.py: Added.
+        * imported/w3c/webdriver/tests/support/inline.py:
+        * imported/w3c/webdriver/tests/support/sync.py: Added.
+        * imported/w3c/webdriver/tests/switch_to_frame/switch.py:
+        * imported/w3c/webdriver/tests/switch_to_window/switch.py:
+        * imported/w3c/webdriver/tests/take_element_screenshot/__init__.py: Added.
+        * imported/w3c/webdriver/tests/take_element_screenshot/screenshot.py: Added.
+        * imported/w3c/webdriver/tests/take_element_screenshot/user_prompts.py: Added.
+        * imported/w3c/webdriver/tests/take_screenshot/__init__.py: Added.
+        * imported/w3c/webdriver/tests/take_screenshot/screenshot.py: Added.
+        * imported/w3c/webdriver/tests/take_screenshot/user_prompts.py: Added.
+        * imported/w3c/importer.json:
+        * imported/w3c/tools/webdriver/webdriver/client.py:
+        * imported/w3c/tools/webdriver/webdriver/error.py:
+        * imported/w3c/tools/wptrunner/docs/design.rst:
+        * imported/w3c/tools/wptrunner/requirements.txt:
+        * imported/w3c/tools/wptrunner/requirements_chrome.txt:
+        * imported/w3c/tools/wptrunner/requirements_chrome_android.txt:
+        * imported/w3c/tools/wptrunner/requirements_edge.txt:
+        * imported/w3c/tools/wptrunner/requirements_firefox.txt:
+        * imported/w3c/tools/wptrunner/requirements_ie.txt:
+        * imported/w3c/tools/wptrunner/requirements_opera.txt:
+        * imported/w3c/tools/wptrunner/requirements_safari.txt:
+        * imported/w3c/tools/wptrunner/requirements_sauce.txt:
+        * imported/w3c/tools/wptrunner/tox.ini:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/__init__.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/base.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/chrome.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/edge_webdriver.py: Added.
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/fennec.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/firefox.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/safari.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/sauce.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/servodriver.py:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/webkit.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/base.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executormarionette.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executorservo.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executorwebdriver.py: Copied from WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorselenium.py.
+        * imported/w3c/tools/wptrunner/wptrunner/executors/protocol.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/runner.js: Added.
+        * imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver.js:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js:
+        * imported/w3c/tools/wptrunner/wptrunner/formatters.py:
+        * imported/w3c/tools/wptrunner/wptrunner/manifestexpected.py:
+        * imported/w3c/tools/wptrunner/wptrunner/stability.py:
+        * imported/w3c/tools/wptrunner/wptrunner/testdriver-extra.js:
+        * imported/w3c/tools/wptrunner/wptrunner/testloader.py:
+        * imported/w3c/tools/wptrunner/wptrunner/testrunner.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/base.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_formatters.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_stability.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_testloader.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_update.py:
+        * imported/w3c/tools/wptrunner/wptrunner/tests/test_wpttest.py:
+        * imported/w3c/tools/wptrunner/wptrunner/update/tree.py:
+        * imported/w3c/tools/wptrunner/wptrunner/update/update.py:
+        * imported/w3c/tools/wptrunner/wptrunner/webdriver_server.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptcommandline.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptmanifest/parser.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wptrunner.py:
+        * imported/w3c/tools/wptrunner/wptrunner/wpttest.py:
+        * imported/w3c/webdriver/tests/add_cookie/add.py:
+        * imported/w3c/webdriver/tests/add_cookie/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/back/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/conftest.py:
+        * imported/w3c/webdriver/tests/delete_all_cookies/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/delete_session/delete.py:
+        * imported/w3c/webdriver/tests/element_clear/clear.py:
+        * imported/w3c/webdriver/tests/element_clear/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/element_click/center_point.py: Added.
+        * imported/w3c/webdriver/tests/element_click/interactability.py:
+        * imported/w3c/webdriver/tests/element_click/scroll_into_view.py:
+        * imported/w3c/webdriver/tests/element_click/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/element_send_keys/file_upload.py:
+        * imported/w3c/webdriver/tests/element_send_keys/interactability.py:
+        * imported/w3c/webdriver/tests/element_send_keys/scroll_into_view.py:
+        * imported/w3c/webdriver/tests/element_send_keys/send_keys.py:
+        * imported/w3c/webdriver/tests/execute_async_script/execute_async.py:
+        * imported/w3c/webdriver/tests/execute_script/execute.py:
+        * imported/w3c/webdriver/tests/execute_script/promise.py: Added.
+        * imported/w3c/webdriver/tests/find_element/find.py:
+        * imported/w3c/webdriver/tests/find_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/find_element_from_element/find.py:
+        * imported/w3c/webdriver/tests/find_element_from_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/find_elements/find.py:
+        * imported/w3c/webdriver/tests/find_elements/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/find_elements_from_element/find.py:
+        * imported/w3c/webdriver/tests/find_elements_from_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/fullscreen_window/fullscreen.py:
+        * imported/w3c/webdriver/tests/fullscreen_window/stress.py: Added.
+        * imported/w3c/webdriver/tests/fullscreen_window/user_prompts.py:
+        * imported/w3c/webdriver/tests/get_active_element/get.py:
+        * imported/w3c/webdriver/tests/get_active_element/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_current_url/get.py:
+        * imported/w3c/webdriver/tests/get_element_attribute/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_element_attribute/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_element_css_value/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_element_css_value/get.py: Added.
+        * imported/w3c/webdriver/tests/get_element_css_value/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_element_rect/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_element_rect/get.py: Added.
+        * imported/w3c/webdriver/tests/get_element_rect/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_element_text/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_named_cookie/get.py:
+        * imported/w3c/webdriver/tests/get_named_cookie/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_page_source/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_page_source/source.py: Added.
+        * imported/w3c/webdriver/tests/get_page_source/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/get_title/get.py:
+        * imported/w3c/webdriver/tests/get_window_handle/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handle/get.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handle/user_prompts.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handles/__init__.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handles/get.py: Added.
+        * imported/w3c/webdriver/tests/get_window_handles/user_prompts.py: Added.
+        * imported/w3c/webdriver/tests/is_element_enabled/__init__.py: Added.
+        * imported/w3c/webdriver/tests/is_element_enabled/enabled.py: Added.
+        * imported/w3c/webdriver/tests/is_element_enabled/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/maximize_window/maximize.py:
+        * imported/w3c/webdriver/tests/maximize_window/stress.py: Added.
+        * imported/w3c/webdriver/tests/minimize_window/minimize.py:
+        * imported/w3c/webdriver/tests/minimize_window/stress.py: Added.
+        * imported/w3c/webdriver/tests/minimize_window/user_prompts.py:
+        * imported/w3c/webdriver/tests/navigate_to/navigate.py:
+        * imported/w3c/webdriver/tests/navigate_to/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/new_session/invalid_capabilities.py:
+        * imported/w3c/webdriver/tests/new_session/platform_name.py:
+        * imported/w3c/webdriver/tests/new_session/response.py:
+        * imported/w3c/webdriver/tests/new_session/support/create.py:
+        * imported/w3c/webdriver/tests/perform_actions/__init__.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/conftest.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key_events.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key_modifiers.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key_shortcuts.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/key_special_keys.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/none.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_contextmenu.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_dblclick.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_modifier_click.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_origin.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/pointer_pause_dblclick.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/sequence.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/__init__.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/keys.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/mouse.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/refine.py: Added.
+        * imported/w3c/webdriver/tests/perform_actions/support/test_actions_wdspec.html: Added.
+        * imported/w3c/webdriver/tests/perform_actions/user_prompts.py: Copied from WebDriverTests/imported/w3c/webdriver/tests/minimize_window/user_prompts.py.
+        * imported/w3c/webdriver/tests/perform_actions/validity.py: Added.
+        * imported/w3c/webdriver/tests/permissions/set.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/__init__.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/conftest.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/release.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/sequence.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/support/__init__.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/support/refine.py: Added.
+        * imported/w3c/webdriver/tests/release_actions/support/test_actions_wdspec.html: Added.
+        * imported/w3c/webdriver/tests/send_alert_text/send.py:
+        * imported/w3c/webdriver/tests/set_timeouts/set.py:
+        * imported/w3c/webdriver/tests/set_timeouts/user_prompts.py: Added.
+        * imported/w3c/webdriver/tests/set_window_rect/set.py:
+        * imported/w3c/webdriver/tests/support/asserts.py:
+        * imported/w3c/webdriver/tests/support/defaults.py: Added.
+        * imported/w3c/webdriver/tests/support/fixtures.py:
+        * imported/w3c/webdriver/tests/support/helpers.py: Added.
+        * imported/w3c/webdriver/tests/support/http_request.py:
+        * imported/w3c/webdriver/tests/support/image.py: Added.
+        * imported/w3c/webdriver/tests/support/inline.py:
+        * imported/w3c/webdriver/tests/support/sync.py: Added.
+        * imported/w3c/webdriver/tests/switch_to_frame/switch.py:
+        * imported/w3c/webdriver/tests/switch_to_window/switch.py:
+        * imported/w3c/webdriver/tests/take_element_screenshot/__init__.py: Added.
+        * imported/w3c/webdriver/tests/take_element_screenshot/screenshot.py: Added.
+        * imported/w3c/webdriver/tests/take_element_screenshot/user_prompts.py: Added.
+        * imported/w3c/webdriver/tests/take_screenshot/__init__.py: Added.
+        * imported/w3c/webdriver/tests/take_screenshot/screenshot.py: Added.
+        * imported/w3c/webdriver/tests/take_screenshot/user_prompts.py: Added.
+
 2018-11-03  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         Unreviewed, fix WebDriver expectations syntax after previous commit
index ae99b94..86e9552 100644 (file)
@@ -1,6 +1,6 @@
 {
     "repository": "https://github.com/w3c/web-platform-tests.git",
-    "revision": "af380aa415d2c1d8de383d3683b8ef55cb77e3c8",
+    "revision": "1f9bc10d3a98e685e5cd7671d5ae40a5ff8828d6",
     "paths_to_import": [
         "tools/webdriver",
         "tools/wptrunner",
index a8c9a06..c5cb9ca 100644 (file)
@@ -376,7 +376,6 @@ class Session(object):
         self.timeouts = None
         self.window = None
         self.find = None
-        self._element_cache = {}
         self.extension = None
         self.extension_cls = extension
 
@@ -404,6 +403,13 @@ class Session(object):
         self.end()
 
     def start(self):
+        """Start a new WebDriver session.
+
+        :return: Dictionary with `capabilities` and `sessionId`.
+
+        :raises error.WebDriverException: If the remote end returns
+            an error.
+        """
         if self.session_id is not None:
             return
 
@@ -422,13 +428,13 @@ class Session(object):
         return value
 
     def end(self):
-        """Tries to close the active session."""
+        """Try to close the active session."""
         if self.session_id is None:
             return
 
         try:
             self.send_command("DELETE", "session/%s" % self.session_id)
-        except error.SessionNotCreatedException:
+        except error.InvalidSessionIdException:
             pass
         finally:
             self.session_id = None
@@ -446,10 +452,10 @@ class Session(object):
             the `value` field returned after parsing the response
             body as JSON.
 
-        :raises ValueError: If the response body does not contain a
-            `value` key.
         :raises error.WebDriverException: If the remote end returns
             an error.
+        :raises ValueError: If the response body does not contain a
+            `value` key.
         """
         response = self.transport.send(
             method, url, body,
@@ -459,7 +465,7 @@ class Session(object):
         if response.status != 200:
             err = error.from_response(response)
 
-            if isinstance(err, error.SessionNotCreatedException):
+            if isinstance(err, error.InvalidSessionIdException):
                 # The driver could have already been deleted the session.
                 self.session_id = None
 
@@ -495,14 +501,9 @@ class Session(object):
         :return: `None` if the HTTP response body was empty, otherwise
             the result of parsing the body as JSON.
 
-        :raises error.SessionNotCreatedException: If there is no active
-            session.
         :raises error.WebDriverException: If the remote end returns
             an error.
         """
-        if self.session_id is None:
-            raise error.SessionNotCreatedException()
-
         url = urlparse.urljoin("session/%s/" % self.session_id, uri)
         return self.send_command(method, url, body)
 
@@ -667,10 +668,6 @@ class Element(object):
         self.id = id
         self.session = session
 
-        if id in self.session._element_cache:
-            raise ValueError("Element already in cache: %s" % id)
-        self.session._element_cache[self.id] = self
-
     def __repr__(self):
         return "<%s %s>" % (self.__class__.__name__, self.id)
 
@@ -681,8 +678,6 @@ class Element(object):
     @classmethod
     def from_json(cls, json, session):
         uuid = json[Element.identifier]
-        if uuid in session._element_cache:
-            return session._element_cache[uuid]
         return cls(uuid, session)
 
     def send_element_command(self, method, uri, body=None):
index b2337ff..23ffc40 100644 (file)
@@ -6,8 +6,10 @@ class WebDriverException(Exception):
     http_status = None
     status_code = None
 
-    def __init__(self, message=None, stacktrace=None):
+    def __init__(self, http_status=None, status_code=None, message=None, stacktrace=None):
         super(WebDriverException, self)
+        self.http_status = http_status
+        self.status_code = status_code
         self.message = message
         self.stacktrace = stacktrace
 
@@ -113,7 +115,7 @@ class NoSuchWindowException(WebDriverException):
 
 
 class ScriptTimeoutException(WebDriverException):
-    http_status = 408
+    http_status = 500
     status_code = "script timeout"
 
 
@@ -128,7 +130,7 @@ class StaleElementReferenceException(WebDriverException):
 
 
 class TimeoutException(WebDriverException):
-    http_status = 408
+    http_status = 500
     status_code = "timeout"
 
 
@@ -171,6 +173,8 @@ def from_response(response):
     """
     if response.status == 200:
         raise UnknownErrorException(
+            response.status,
+            None,
             "Response is not an error:\n"
             "%s" % json.dumps(response.body))
 
@@ -178,6 +182,8 @@ def from_response(response):
         value = response.body["value"]
     else:
         raise UnknownErrorException(
+            response.status,
+            None,
             "Expected 'value' key in response body:\n"
             "%s" % json.dumps(response.body))
 
@@ -187,7 +193,7 @@ def from_response(response):
     stack = value["stacktrace"] or None
 
     cls = get(code)
-    return cls(message, stacktrace=stack)
+    return cls(response.status, code, message, stacktrace=stack)
 
 
 def get(error_code):
index bf108a0..056c0d8 100644 (file)
@@ -15,7 +15,7 @@ requirements:
    of an ``iframe`` test container.
 
  * It must be possible to deal with all kinds of behaviour of the
-   browser runder test, for example, crashing, hanging, etc.
+   browser under test, for example, crashing, hanging, etc.
 
  * It should be possible to add support for new platforms and browsers
    with minimal code changes.
index fcb5922..1fde996 100644 (file)
@@ -1,5 +1,5 @@
 html5lib == 1.0.1
 mozinfo == 0.10
-mozlog==3.8
-mozdebug == 0.1
-urllib3[secure] == 1.22
+mozlog==3.9
+mozdebug==0.1.1
+urllib3[secure]==1.24.1
index f1e6a85..ae80475 100644 (file)
@@ -1,9 +1,9 @@
-marionette_driver==2.6.0
-mozprofile==1.1.0
+marionette_driver==2.7.0
+mozprofile==2.1.0
 mozprocess == 0.26
 mozcrash == 1.0
-mozrunner == 7.0.0
+mozrunner==7.2.0
 mozleak == 0.1
 mozinstall==1.16.0
-mozdownload==1.24
+mozdownload==1.25
 
index fa7985e..4f5dcfb 100644 (file)
@@ -2,7 +2,7 @@
 xfail_strict=true
 
 [tox]
-envlist = {py27}-{base,chrome,edge,firefox,ie,opera,safari,sauce,servo},py27-flake8
+envlist = py27-{base,chrome,edge,firefox,ie,opera,safari,sauce,servo}
 
 [testenv]
 deps =
@@ -23,14 +23,3 @@ deps =
 commands = pytest {posargs:--cov}
 
 setenv = CURRENT_TOX_ENV = {envname}
-
-[testenv:py27-flake8]
-# flake8 versions should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
-deps =
-     flake8==3.5.0
-     pycodestyle==2.3.1
-     pyflakes==1.6.0
-     pep8-naming==0.4.1
-
-commands =
-     flake8 --append-config=../flake8.ini
index d8682e1..f86792d 100644 (file)
@@ -15,7 +15,7 @@ a dictionary with the fields
 "executor_kwargs": String naming a function that takes http server url and
                    timeout multiplier and returns kwargs to use when creating
                    the executor class.
-"env_options": String naming a funtion of no arguments that returns the
+"env_options": String naming a function of no arguments that returns the
                arguments passed to the TestEnvironment.
 
 All classes and functions named in the above dict must be imported into the
@@ -25,6 +25,7 @@ module global scope.
 product_list = ["chrome",
                 "chrome_android",
                 "edge",
+                "edge_webdriver",
                 "fennec",
                 "firefox",
                 "ie",
index dc03ef7..70324be 100644 (file)
@@ -2,12 +2,32 @@ import os
 import platform
 import socket
 from abc import ABCMeta, abstractmethod
+from copy import deepcopy
 
 from ..wptcommandline import require_arg  # noqa: F401
 
 here = os.path.split(__file__)[0]
 
 
+def inherit(super_module, child_globals, product_name):
+    super_wptrunner = super_module.__wptrunner__
+    child_globals["__wptrunner__"] = child_wptrunner = deepcopy(super_wptrunner)
+
+    child_wptrunner["product"] = product_name
+
+    for k in ("check_args", "browser", "browser_kwargs", "executor_kwargs",
+              "env_extras", "env_options"):
+        attr = super_wptrunner[k]
+        child_globals[attr] = getattr(super_module, attr)
+
+    for v in super_module.__wptrunner__["executor"].values():
+        child_globals[v] = getattr(super_module, v)
+
+    if "run_info_extras" in super_wptrunner:
+        attr = super_wptrunner["run_info_extras"]
+        child_globals[attr] = getattr(super_module, attr)
+
+
 def cmd_arg(name, value=None):
     prefix = "-" if platform.system() == "Windows" else "--"
     rv = prefix + name
index 8b0ba45..c46f33c 100644 (file)
@@ -1,16 +1,16 @@
 from .base import Browser, ExecutorBrowser, require_arg
 from ..webdriver_server import ChromeDriverServer
 from ..executors import executor_kwargs as base_executor_kwargs
-from ..executors.executorselenium import (SeleniumTestharnessExecutor,  # noqa: F401
-                                          SeleniumRefTestExecutor)  # noqa: F401
+from ..executors.executorwebdriver import (WebDriverTestharnessExecutor,  # noqa: F401
+                                           WebDriverRefTestExecutor)  # noqa: F401
 from ..executors.executorchrome import ChromeDriverWdspecExecutor  # noqa: F401
 
 
 __wptrunner__ = {"product": "chrome",
                  "check_args": "check_args",
                  "browser": "ChromeBrowser",
-                 "executor": {"testharness": "SeleniumTestharnessExecutor",
-                              "reftest": "SeleniumRefTestExecutor",
+                 "executor": {"testharness": "WebDriverTestharnessExecutor",
+                              "reftest": "WebDriverRefTestExecutor",
                               "wdspec": "ChromeDriverWdspecExecutor"},
                  "browser_kwargs": "browser_kwargs",
                  "executor_kwargs": "executor_kwargs",
@@ -30,29 +30,40 @@ def browser_kwargs(test_type, run_info_data, config, **kwargs):
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
-    from selenium.webdriver import DesiredCapabilities
-
     executor_kwargs = base_executor_kwargs(test_type, server_config,
                                            cache_manager, run_info_data,
                                            **kwargs)
     executor_kwargs["close_after_done"] = True
-    capabilities = dict(DesiredCapabilities.CHROME.items())
-    capabilities.setdefault("goog:chromeOptions", {})["prefs"] = {
-        "profile": {
-            "default_content_setting_values": {
-                "popups": 1
-            }
+
+    capabilities = {
+        "goog:chromeOptions": {
+            "prefs": {
+                "profile": {
+                    "default_content_setting_values": {
+                        "popups": 1
+                    }
+                }
+            },
+            "w3c": True
         }
     }
+
     for (kwarg, capability) in [("binary", "binary"), ("binary_args", "args")]:
         if kwargs[kwarg] is not None:
             capabilities["goog:chromeOptions"][capability] = kwargs[kwarg]
+
+    if kwargs["headless"]:
+        if "args" not in capabilities["goog:chromeOptions"]:
+            capabilities["goog:chromeOptions"]["args"] = []
+        if "--headless" not in capabilities["goog:chromeOptions"]["args"]:
+            capabilities["goog:chromeOptions"]["args"].append("--headless")
+
     if test_type == "testharness":
         capabilities["goog:chromeOptions"]["useAutomationExtension"] = False
         capabilities["goog:chromeOptions"]["excludeSwitches"] = ["enable-automation"]
-    if test_type == "wdspec":
-        capabilities["goog:chromeOptions"]["w3c"] = True
+
     executor_kwargs["capabilities"] = capabilities
+
     return executor_kwargs
 
 
diff --git a/WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/edge_webdriver.py b/WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/edge_webdriver.py
new file mode 100644 (file)
index 0000000..c2545de
--- /dev/null
@@ -0,0 +1,12 @@
+from .base import inherit
+from . import edge
+
+from ..executors.executorwebdriver import (WebDriverTestharnessExecutor,  # noqa: F401
+                                           WebDriverRefTestExecutor)  # noqa: F401
+
+
+inherit(edge, globals(), "edge_webdriver")
+
+# __wptrunner__ magically appears from inherit, F821 is undefined name
+__wptrunner__["executor"]["testharness"] = "WebDriverTestharnessExecutor"  # noqa: F821
+__wptrunner__["executor"]["reftest"] = "WebDriverRefTestExecutor"  # noqa: F821
index db271ac..a7009dd 100644 (file)
@@ -1,8 +1,5 @@
 import os
-import signal
-import sys
 import tempfile
-import traceback
 
 import moznetwork
 from mozprocess import ProcessHandler
@@ -14,14 +11,16 @@ from serve.serve import make_hosts_file
 from .base import (get_free_port,
                    cmd_arg,
                    browser_command)
-from ..executors.executormarionette import MarionetteTestharnessExecutor  # noqa: F401
+from ..executors.executormarionette import (MarionetteTestharnessExecutor,  # noqa: F401
+                                            MarionetteRefTestExecutor)  # noqa: F401
 from .firefox import (get_timeout_multiplier, update_properties, executor_kwargs, FirefoxBrowser)  # noqa: F401
 
 
 __wptrunner__ = {"product": "fennec",
                  "check_args": "check_args",
                  "browser": "FennecBrowser",
-                 "executor": {"testharness": "MarionetteTestharnessExecutor"},
+                 "executor": {"testharness": "MarionetteTestharnessExecutor",
+                              "reftest": "MarionetteRefTestExecutor"},
                  "browser_kwargs": "browser_kwargs",
                  "executor_kwargs": "executor_kwargs",
                  "env_extras": "env_extras",
@@ -29,60 +28,11 @@ __wptrunner__ = {"product": "fennec",
                  "run_info_extras": "run_info_extras",
                  "update_properties": "update_properties"}
 
-class FennecProfile(FirefoxProfile):
-    # WPT-specific prefs are set in FennecBrowser.start()
-    FirefoxProfile.preferences.update({
-        # Make sure Shield doesn't hit the network.
-        "app.normandy.api_url": "",
-        # Increase the APZ content response timeout in tests to 1 minute.
-        "apz.content_response_timeout": 60000,
-        # Enable output of dump()
-        "browser.dom.window.dump.enabled": True,
-        # Disable safebrowsing components
-        "browser.safebrowsing.blockedURIs.enabled": False,
-        "browser.safebrowsing.downloads.enabled": False,
-        "browser.safebrowsing.passwords.enabled": False,
-        "browser.safebrowsing.malware.enabled": False,
-        "browser.safebrowsing.phishing.enabled": False,
-        # Do not restore the last open set of tabs if the browser has crashed
-        "browser.sessionstore.resume_from_crash": False,
-        # Disable Android snippets
-        "browser.snippets.enabled": False,
-        "browser.snippets.syncPromo.enabled": False,
-        "browser.snippets.firstrunHomepage.enabled": False,
-        # Do not allow background tabs to be zombified, otherwise for tests that
-        # open additional tabs, the test harness tab itself might get unloaded
-        "browser.tabs.disableBackgroundZombification": True,
-        # Disable e10s by default
-        "browser.tabs.remote.autostart": False,
-        # Don't warn when exiting the browser
-        "browser.warnOnQuit": False,
-        # Don't send Firefox health reports to the production server
-        "datareporting.healthreport.about.reportUrl": "http://%(server)s/dummy/abouthealthreport/",
-        # Automatically unload beforeunload alerts
-        "dom.disable_beforeunload": True,
-        # Disable the ProcessHangMonitor
-        "dom.ipc.reportProcessHangs": False,
-        # No slow script dialogs
-        "dom.max_chrome_script_run_time": 0,
-        "dom.max_script_run_time": 0,
-        # Make sure opening about:addons won"t hit the network
-        "extensions.webservice.discoverURL": "http://%(server)s/dummy/discoveryURL",
-        # No hang monitor
-        "hangmonitor.timeout": 0,
-
-        "javascript.options.showInConsole": True,
-        # Ensure blocklist updates don't hit the network
-        "services.settings.server": "http://%(server)s/dummy/blocklist/",
-        # Disable password capture, so that tests that include forms aren"t
-        # influenced by the presence of the persistent doorhanger notification
-        "signon.rememberSignons": False,
-    })
-
 
 def check_args(**kwargs):
     pass
 
+
 def browser_kwargs(test_type, run_info_data, config, **kwargs):
     return {"package_name": kwargs["package_name"],
             "device_serial": kwargs["device_serial"],
@@ -93,7 +43,7 @@ def browser_kwargs(test_type, run_info_data, config, **kwargs):
             "symbols_path": kwargs["symbols_path"],
             "stackwalk_binary": kwargs["stackwalk_binary"],
             "certutil_binary": kwargs["certutil_binary"],
-            "ca_certificate_path": kwargs["ssl_env"].ca_cert_path(),
+            "ca_certificate_path": config.ssl_config["ca_cert_path"],
             "stackfix_dir": kwargs["stackfix_dir"],
             "binary_args": kwargs["binary_args"],
             "timeout_multiplier": get_timeout_multiplier(test_type,
@@ -102,7 +52,9 @@ def browser_kwargs(test_type, run_info_data, config, **kwargs):
             "leak_check": kwargs["leak_check"],
             "stylo_threads": kwargs["stylo_threads"],
             "chaos_mode_flags": kwargs["chaos_mode_flags"],
-            "config": config}
+            "config": config,
+            "install_fonts": kwargs["install_fonts"],
+            "tests_root": config.doc_root}
 
 
 def env_extras(**kwargs):
@@ -111,7 +63,8 @@ def env_extras(**kwargs):
 
 def run_info_extras(**kwargs):
     return {"e10s": False,
-            "headless": False}
+            "headless": False,
+            "sw-e10s": False}
 
 
 def env_options():
@@ -147,6 +100,8 @@ class FennecBrowser(FirefoxBrowser):
         FirefoxBrowser.__init__(self, logger, None, prefs_root, test_type, **kwargs)
         self._package_name = package_name
         self.device_serial = device_serial
+        self.tests_root = kwargs["tests_root"]
+        self.install_fonts = kwargs["install_fonts"]
 
     @property
     def package_name(self):
@@ -175,12 +130,32 @@ class FennecBrowser(FirefoxBrowser):
 
         preferences = self.load_prefs()
 
-        self.profile = FennecProfile(preferences=preferences)
+        self.profile = FirefoxProfile(preferences=preferences)
         self.profile.set_preferences({"marionette.port": self.marionette_port,
                                       "dom.disable_open_during_load": False,
                                       "places.history.enabled": False,
                                       "dom.send_after_paint_to_content": True,
                                       "network.preload": True})
+        if self.test_type == "reftest":
+            self.logger.info("Setting android reftest preferences")
+            self.profile.set_preferences({"browser.viewport.desktopWidth": 600,
+                                          # Disable high DPI
+                                          "layout.css.devPixelsPerPx": "1.0",
+                                          # Ensure that the full browser element
+                                          # appears in the screenshot
+                                          "apz.allow_zooming": False,
+                                          "android.widget_paints_background": False,
+                                          # Ensure that scrollbars are always painted
+                                          "ui.scrollbarFadeBeginDelay": 100000})
+
+        if self.install_fonts:
+            self.logger.debug("Copying Ahem font to profile")
+            font_dir = os.path.join(self.profile.profile, "fonts")
+            if not os.path.exists(font_dir):
+                os.makedirs(font_dir)
+            with open(os.path.join(self.tests_root, "fonts", "Ahem.ttf"), "rb") as src:
+                with open(os.path.join(font_dir, "Ahem.ttf"), "wb") as dest:
+                    dest.write(src.read())
 
         if self.leak_check and kwargs.get("check_leaks", True):
             self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks.log")
@@ -209,46 +184,31 @@ class FennecBrowser(FirefoxBrowser):
                                            process_class=ProcessHandler,
                                            process_args={"processOutputLine": [self.on_output]})
 
-        self.logger.debug("Starting Fennec")
+        self.logger.debug("Starting %s" % self.package_name)
         # connect to a running emulator
         self.runner.device.connect()
 
         write_hosts_file(self.config, self.runner.device.device)
 
+        self.runner.stop()
         self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
 
-        # gecko_log comes from logcat when running with device/emulator
-        logcat_args = {
-            "filterspec": "Gecko",
-            "serial": self.runner.device.app_ctx.device_serial
-        }
-        # TODO setting logcat_args["logfile"] yields an almost empty file
-        # even without filterspec
-        logcat_args["stream"] = sys.stdout
-        self.runner.device.start_logcat(**logcat_args)
-
         self.runner.device.device.forward(
             local="tcp:{}".format(self.marionette_port),
             remote="tcp:{}".format(self.marionette_port))
 
-        self.logger.debug("Fennec Started")
+        self.logger.debug("%s Started" % self.package_name)
 
     def stop(self, force=False):
         if self.runner is not None:
-            try:
-                if self.runner.device.connected:
+            if (self.runner.device.connected and
+                len(self.runner.device.device.list_forwards()) > 0):
+                try:
                     self.runner.device.device.remove_forwards(
                         "tcp:{}".format(self.marionette_port))
-            except Exception:
-                traceback.print_exception(*sys.exc_info())
+                except Exception:
+                    self.logger.warning("Failed to remove port forwarding")
             # We assume that stopping the runner prompts the
             # browser to shut down. This allows the leak log to be written
-            for clean, stop_f in [(True, lambda: self.runner.wait(self.shutdown_timeout)),
-                                  (False, lambda: self.runner.stop(signal.SIGTERM)),
-                                  (False, lambda: self.runner.stop(signal.SIGKILL))]:
-                if not force or not clean:
-                    retcode = stop_f()
-                    if retcode is not None:
-                        self.logger.info("Browser exited with return code %s" % retcode)
-                        break
+            self.runner.stop()
         self.logger.debug("stopped")
index 04f2ce8..59f0669 100644 (file)
@@ -83,7 +83,8 @@ def browser_kwargs(test_type, run_info_data, config, **kwargs):
             "asan": run_info_data.get("asan"),
             "stylo_threads": kwargs["stylo_threads"],
             "chaos_mode_flags": kwargs["chaos_mode_flags"],
-            "config": config}
+            "config": config,
+            "headless": kwargs["headless"]}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
@@ -106,6 +107,11 @@ def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
             options["binary"] = kwargs["binary"]
         if kwargs["binary_args"]:
             options["args"] = kwargs["binary_args"]
+        if kwargs["headless"]:
+            if "args" not in options:
+                options["args"] = []
+            if "--headless" not in options["args"]:
+                options["args"].append("--headless")
         options["prefs"] = {
             "network.dns.localDomains": ",".join(server_config.domains_set)
         }
@@ -135,10 +141,18 @@ def env_options():
 
 
 def run_info_extras(**kwargs):
+
+    def get_bool_pref(pref):
+        for key, value in kwargs.get('extra_prefs', []):
+            if pref == key:
+                return value.lower() in ('true', '1')
+        return False
+
     return {"e10s": kwargs["gecko_e10s"],
             "wasm": kwargs.get("wasm", True),
             "verify": kwargs["verify"],
-            "headless": "MOZ_HEADLESS" in os.environ}
+            "headless": "MOZ_HEADLESS" in os.environ,
+            "sw-e10s": get_bool_pref("dom.serviceWorkers.parent_intercept"),}
 
 
 def update_properties():
@@ -155,7 +169,7 @@ class FirefoxBrowser(Browser):
                  symbols_path=None, stackwalk_binary=None, certutil_binary=None,
                  ca_certificate_path=None, e10s=False, stackfix_dir=None,
                  binary_args=None, timeout_multiplier=None, leak_check=False, asan=False,
-                 stylo_threads=1, chaos_mode_flags=None, config=None):
+                 stylo_threads=1, chaos_mode_flags=None, config=None, headless=None, **kwargs):
         Browser.__init__(self, logger)
         self.binary = binary
         self.prefs_root = prefs_root
@@ -183,14 +197,17 @@ class FirefoxBrowser(Browser):
 
         self.asan = asan
         self.lsan_allowed = None
+        self.lsan_max_stack_depth = None
         self.leak_check = leak_check
         self.leak_report_file = None
         self.lsan_handler = None
         self.stylo_threads = stylo_threads
         self.chaos_mode_flags = chaos_mode_flags
+        self.headless = headless
 
     def settings(self, test):
         self.lsan_allowed = test.lsan_allowed
+        self.lsan_max_stack_depth = test.lsan_max_stack_depth
         return {"check_leaks": self.leak_check and not test.leaks,
                 "lsan_allowed": test.lsan_allowed}
 
@@ -206,7 +223,8 @@ class FirefoxBrowser(Browser):
             print "Setting up LSAN"
             self.lsan_handler = mozleak.LSANLeaks(self.logger,
                                                   scope=group_metadata.get("scope", "/"),
-                                                  allowed=self.lsan_allowed)
+                                                  allowed=self.lsan_allowed,
+                                                  maxNumRecordedFrames=self.lsan_max_stack_depth)
 
         env = test_environment(xrePath=os.path.dirname(self.binary),
                                debugger=self.debug_info is not None,
@@ -216,17 +234,21 @@ class FirefoxBrowser(Browser):
         env["STYLO_THREADS"] = str(self.stylo_threads)
         if self.chaos_mode_flags is not None:
             env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
+        if self.headless:
+            env["MOZ_HEADLESS"] = "1"
 
         preferences = self.load_prefs()
 
         self.profile = FirefoxProfile(preferences=preferences)
-        self.profile.set_preferences({"marionette.port": self.marionette_port,
-                                      "dom.disable_open_during_load": False,
-                                      "network.dns.localDomains": ",".join(self.config.domains_set),
-                                      "network.proxy.type": 0,
-                                      "places.history.enabled": False,
-                                      "dom.send_after_paint_to_content": True,
-                                      "network.preload": True})
+        self.profile.set_preferences({
+            "marionette.port": self.marionette_port,
+            "network.dns.localDomains": ",".join(self.config.domains_set),
+
+            # TODO: Remove preferences once Firefox 64 is stable (Bug 905404)
+            "network.proxy.type": 0,
+            "places.history.enabled": False,
+            "network.preload": True,
+        })
         if self.e10s:
             self.profile.set_preferences({"browser.tabs.remote.autostart": True})
 
@@ -249,9 +271,11 @@ class FirefoxBrowser(Browser):
         if self.ca_certificate_path is not None:
             self.setup_ssl()
 
+        args = self.binary_args[:] if self.binary_args else []
+        args += [cmd_arg("marionette"), "about:blank"]
+
         debug_args, cmd = browser_command(self.binary,
-                                          self.binary_args if self.binary_args else [] +
-                                          [cmd_arg("marionette"), "about:blank"],
+                                          args,
                                           self.debug_info)
 
         self.runner = FirefoxRunner(profile=self.profile,
index 670098e..1e18f95 100644 (file)
@@ -1,16 +1,16 @@
 from .base import Browser, ExecutorBrowser, require_arg
 from ..webdriver_server import SafariDriverServer
 from ..executors import executor_kwargs as base_executor_kwargs
-from ..executors.executorselenium import (SeleniumTestharnessExecutor,  # noqa: F401
-                                          SeleniumRefTestExecutor)  # noqa: F401
+from ..executors.executorwebdriver import (WebDriverTestharnessExecutor,  # noqa: F401
+                                           WebDriverRefTestExecutor)  # noqa: F401
 from ..executors.executorsafari import SafariDriverWdspecExecutor  # noqa: F401
 
 
 __wptrunner__ = {"product": "safari",
                  "check_args": "check_args",
                  "browser": "SafariBrowser",
-                 "executor": {"testharness": "SeleniumTestharnessExecutor",
-                              "reftest": "SeleniumRefTestExecutor",
+                 "executor": {"testharness": "WebDriverTestharnessExecutor",
+                              "reftest": "WebDriverRefTestExecutor",
                               "wdspec": "SafariDriverWdspecExecutor"},
                  "browser_kwargs": "browser_kwargs",
                  "executor_kwargs": "executor_kwargs",
index 9aa484a..02cc322 100644 (file)
@@ -133,6 +133,7 @@ class SauceConnect():
         self.sauce_key = kwargs["sauce_key"]
         self.sauce_tunnel_id = kwargs["sauce_tunnel_id"]
         self.sauce_connect_binary = kwargs.get("sauce_connect_binary")
+        self.sauce_init_timeout = kwargs.get("sauce_init_timeout")
         self.sc_process = None
         self.temp_dir = None
         self.env_config = None
@@ -172,12 +173,9 @@ class SauceConnect():
             ",".join(self.env_config.domains_set)
         ])
 
-        # Timeout config vars
-        max_wait = 30
-
         tot_wait = 0
         while not os.path.exists('./sauce_is_ready') and self.sc_process.poll() is None:
-            if tot_wait >= max_wait:
+            if tot_wait >= self.sauce_init_timeout:
                 self.quit()
 
                 raise SauceException("Sauce Connect Proxy was not ready after %d seconds" % tot_wait)
index aa17eba..1632f8f 100644 (file)
@@ -4,7 +4,7 @@ import tempfile
 
 from mozprocess import ProcessHandler
 
-from serve.serve import make_hosts_file
+from tools.serve.serve import make_hosts_file
 
 from .base import Browser, require_arg, get_free_port, browser_command, ExecutorBrowser
 from ..executors import executor_kwargs as base_executor_kwargs
@@ -38,7 +38,7 @@ def browser_kwargs(test_type, run_info_data, config, **kwargs):
         "binary": kwargs["binary"],
         "binary_args": kwargs["binary_args"],
         "debug_info": kwargs["debug_info"],
-        "server_config": config.ssl_config,
+        "server_config": config,
         "user_stylesheets": kwargs.get("user_stylesheets"),
     }
 
@@ -106,7 +106,7 @@ class ServoWebDriverBrowser(Browser):
             self.binary,
             self.binary_args + [
                 "--hard-fail",
-                "--webdriver", str(self.webdriver_port),
+                "--webdriver=%s" % self.webdriver_port,
                 "about:blank",
             ],
             self.debug_info
index 9482f2f..19527a1 100644 (file)
@@ -1,7 +1,7 @@
 from .base import Browser, ExecutorBrowser, require_arg
 from ..executors import executor_kwargs as base_executor_kwargs
-from ..executors.executorselenium import (SeleniumTestharnessExecutor,  # noqa: F401
-                                          SeleniumRefTestExecutor)  # noqa: F401
+from ..executors.executorwebdriver import (WebDriverTestharnessExecutor,  # noqa: F401
+                                           WebDriverRefTestExecutor)  # noqa: F401
 from ..executors.executorwebkit import WebKitDriverWdspecExecutor  # noqa: F401
 from ..webdriver_server import WebKitDriverServer
 
@@ -10,12 +10,13 @@ __wptrunner__ = {"product": "webkit",
                  "check_args": "check_args",
                  "browser": "WebKitBrowser",
                  "browser_kwargs": "browser_kwargs",
-                 "executor": {"testharness": "SeleniumTestharnessExecutor",
-                              "reftest": "SeleniumRefTestExecutor",
+                 "executor": {"testharness": "WebDriverTestharnessExecutor",
+                              "reftest": "WebDriverRefTestExecutor",
                               "wdspec": "WebKitDriverWdspecExecutor"},
                  "executor_kwargs": "executor_kwargs",
                  "env_extras": "env_extras",
-                 "env_options": "env_options"}
+                 "env_options": "env_options",
+                 "run_info_extras": "run_info_extras"}
 
 
 def check_args(**kwargs):
@@ -31,19 +32,22 @@ def browser_kwargs(test_type, run_info_data, config, **kwargs):
 
 
 def capabilities_for_port(server_config, **kwargs):
-    from selenium.webdriver import DesiredCapabilities
-
-    if kwargs["webkit_port"] == "gtk":
-        capabilities = dict(DesiredCapabilities.WEBKITGTK.copy())
-        capabilities["webkitgtk:browserOptions"] = {
-            "binary": kwargs["binary"],
-            "args": kwargs.get("binary_args", []),
-            "certificates": [
-                {"host": server_config["browser_host"],
-                 "certificateFile": kwargs["host_cert_path"]}
-            ]
-        }
-        return capabilities
+    port_name = kwargs["webkit_port"]
+    if port_name in ["gtk", "wpe"]:
+        port_key_map = {"gtk": "webkitgtk"}
+        browser_options_port = port_key_map.get(port_name, port_name)
+        browser_options_key = "%s:browserOptions" % browser_options_port
+
+        return {
+            "browserName": "MiniBrowser",
+            "browserVersion": "2.20",
+            "platformName": "ANY",
+            browser_options_key: {
+                "binary": kwargs["binary"],
+                "args": kwargs.get("binary_args", []),
+                "certificates": [
+                    {"host": server_config["browser_host"],
+                     "certificateFile": kwargs["host_cert_path"]}]}}
 
     return {}
 
@@ -66,6 +70,10 @@ def env_options():
     return {}
 
 
+def run_info_extras(**kwargs):
+    return {"webkit_port": kwargs["webkit_port"]}
+
+
 class WebKitBrowser(Browser):
     """Generic WebKit browser is backed by WebKit's WebDriver implementation,
     which is supplied through ``wptrunner.webdriver.WebKitDriverServer``.
index 1dc962c..e71ce4c 100644 (file)
@@ -508,7 +508,8 @@ class CallbackHandler(object):
 
         self.actions = {
             "click": ClickAction(self.logger, self.protocol),
-            "send_keys": SendKeysAction(self.logger, self.protocol)
+            "send_keys": SendKeysAction(self.logger, self.protocol),
+            "action_sequence": ActionSequenceAction(self.logger, self.protocol)
         }
 
     def __call__(self, result):
@@ -525,26 +526,22 @@ class CallbackHandler(object):
         return True, rv
 
     def process_action(self, url, payload):
-        parent = self.protocol.base.current_window
+        action = payload["action"]
+        self.logger.debug("Got action: %s" % action)
         try:
-            self.protocol.base.set_window(self.test_window)
-            action = payload["action"]
-            self.logger.debug("Got action: %s" % action)
-            try:
-                action_handler = self.actions[action]
-            except KeyError:
-                raise ValueError("Unknown action %s" % action)
-            try:
-                action_handler(payload)
-            except Exception:
-                self.logger.warning("Action %s failed" % action)
-                self.logger.warning(traceback.format_exc())
-                self._send_message("complete", "failure")
-            else:
-                self.logger.debug("Action %s completed" % action)
-                self._send_message("complete", "success")
-        finally:
-            self.protocol.base.set_window(parent)
+            action_handler = self.actions[action]
+        except KeyError:
+            raise ValueError("Unknown action %s" % action)
+        try:
+            action_handler(payload)
+        except Exception:
+            self.logger.warning("Action %s failed" % action)
+            self.logger.warning(traceback.format_exc())
+            self._send_message("complete", "error")
+            raise
+        else:
+            self.logger.debug("Action %s completed" % action)
+            self._send_message("complete", "success")
 
         return False, None
 
@@ -559,13 +556,9 @@ class ClickAction(object):
 
     def __call__(self, payload):
         selector = payload["selector"]
-        elements = self.protocol.select.elements_by_selector(selector)
-        if len(elements) == 0:
-            raise ValueError("Selector matches no elements")
-        elif len(elements) > 1:
-            raise ValueError("Selector matches multiple elements")
+        element = self.protocol.select.element_by_selector(selector)
         self.logger.debug("Clicking element: %s" % selector)
-        self.protocol.click.element(elements[0])
+        self.protocol.click.element(element)
 
 
 class SendKeysAction(object):
@@ -576,10 +569,27 @@ class SendKeysAction(object):
     def __call__(self, payload):
         selector = payload["selector"]
         keys = payload["keys"]
-        elements = self.protocol.select.elements_by_selector(selector)
-        if len(elements) == 0:
-            raise ValueError("Selector matches no elements")
-        elif len(elements) > 1:
-            raise ValueError("Selector matches multiple elements")
+        element = self.protocol.select.element_by_selector(selector)
         self.logger.debug("Sending keys to element: %s" % selector)
-        self.protocol.send_keys.send_keys(elements[0], keys)
+        self.protocol.send_keys.send_keys(element, keys)
+
+
+class ActionSequenceAction(object):
+    def __init__(self, logger, protocol):
+        self.logger = logger
+        self.protocol = protocol
+
+    def __call__(self, payload):
+        # TODO: some sort of shallow error checking
+        actions = payload["actions"]
+        for actionSequence in actions:
+            if actionSequence["type"] == "pointer":
+                for action in actionSequence["actions"]:
+                    if (action["type"] == "pointerMove" and
+                        isinstance(action["origin"], dict)):
+                        action["origin"] = self.get_element(action["origin"]["selector"])
+        self.protocol.action_sequence.send_actions({"actions": actions})
+
+    def get_element(self, selector):
+        element = self.protocol.select.element_by_selector(selector)
+        return element
index 9df4508..84bde16 100644 (file)
@@ -1,6 +1,5 @@
 import json
 import os
-import socket
 import threading
 import traceback
 import urlparse
@@ -20,7 +19,8 @@ from .base import (CallbackHandler,
                    WebDriverProtocol,
                    extra_timeout,
                    strip_server)
-from .protocol import (AssertsProtocolPart,
+from .protocol import (ActionSequenceProtocolPart,
+                       AssertsProtocolPart,
                        BaseProtocolPart,
                        TestharnessProtocolPart,
                        PrefsProtocolPart,
@@ -85,7 +85,7 @@ class MarionetteBaseProtocolPart(BaseProtocolPart):
         if socket_timeout:
             try:
                 self.marionette.timeout.script = socket_timeout / 2
-            except (socket.error, IOError):
+            except IOError:
                 self.logger.debug("Socket closed")
                 return
 
@@ -98,7 +98,7 @@ class MarionetteBaseProtocolPart(BaseProtocolPart):
             except errors.ScriptTimeoutException:
                 self.logger.debug("Script timed out")
                 pass
-            except (socket.timeout, IOError):
+            except IOError:
                 self.logger.debug("Socket closed")
                 break
             except Exception as e:
@@ -110,17 +110,19 @@ class MarionetteTestharnessProtocolPart(TestharnessProtocolPart):
     def __init__(self, parent):
         super(MarionetteTestharnessProtocolPart, self).__init__(parent)
         self.runner_handle = None
+        with open(os.path.join(here, "runner.js")) as f:
+            self.runner_script = f.read()
 
     def setup(self):
         self.marionette = self.parent.marionette
 
     def load_runner(self, url_protocol):
         # Check if we previously had a test window open, and if we did make sure it's closed
-        self.parent.base.execute_script("if (window.win) {window.win.close()}")
+        if self.runner_handle:
+            self._close_windows()
         url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
                                "/testharness_runner.html")
         self.logger.debug("Loading %s" % url)
-        self.runner_handle = self.marionette.current_window_handle
         try:
             self.dismiss_alert(lambda: self.marionette.navigate(url))
         except Exception as e:
@@ -130,10 +132,11 @@ class MarionetteTestharnessProtocolPart(TestharnessProtocolPart):
                 "that your firewall rules or network setup does not "
                 "prevent access.\e%s" % (url, traceback.format_exc(e)))
             raise
-        self.parent.base.execute_script(
-            "document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
+        self.runner_handle = self.marionette.current_window_handle
+        format_map = {"title": threading.current_thread().name.replace("'", '"')}
+        self.parent.base.execute_script(self.runner_script % format_map)
 
-    def close_old_windows(self, url_protocol):
+    def _close_windows(self):
         handles = self.marionette.window_handles
         runner_handle = None
         try:
@@ -146,18 +149,23 @@ class MarionetteTestharnessProtocolPart(TestharnessProtocolPart):
             # but it hopefully doesn't matter too much if that assumption is
             # wrong since we reload the runner in that tab anyway.
             runner_handle = handles.pop(0)
+            self.logger.info("Changing harness_window to %s" % runner_handle)
 
         for handle in handles:
             try:
                 self.dismiss_alert(lambda: self.marionette.switch_to_window(handle))
                 self.marionette.switch_to_window(handle)
+                self.logger.info("Closing window %s" % handle)
                 self.marionette.close()
             except errors.NoSuchWindowException:
                 # We might have raced with the previous test to close this
                 # window, skip it.
                 pass
-
         self.marionette.switch_to_window(runner_handle)
+        return runner_handle
+
+    def close_old_windows(self, url_protocol):
+        runner_handle = self._close_windows()
         if runner_handle != self.runner_handle:
             self.load_runner(url_protocol)
         return self.runner_handle
@@ -284,7 +292,7 @@ class MarionetteStorageProtocolPart(StorageProtocolPart):
             let principal = ssm.createCodebasePrincipal(uri, {});
             let qms = Components.classes["@mozilla.org/dom/quota-manager-service;1"]
                                 .getService(Components.interfaces.nsIQuotaManagerService);
-            qms.clearStoragesForPrincipal(principal, "default", true);
+            qms.clearStoragesForPrincipal(principal, "default", null, true);
             """ % url
         with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
             self.marionette.execute_script(script)
@@ -316,7 +324,7 @@ class MarionetteAssertsProtocolPart(AssertsProtocolPart):
             except errors.NoSuchWindowException:
                 # If the window was already closed
                 self.parent.logger.warning("Failed to get assertion count; window was closed")
-            except (errors.MarionetteException, socket.error):
+            except (errors.MarionetteException, IOError):
                 # This usually happens if the process crashed
                 pass
 
@@ -358,6 +366,16 @@ class MarionetteSendKeysProtocolPart(SendKeysProtocolPart):
         return element.send_keys(keys)
 
 
+class MarionetteActionSequenceProtocolPart(ActionSequenceProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def send_actions(self, actions):
+        actions = self.marionette._to_json(actions)
+        self.logger.info(actions)
+        self.marionette._send_message("WebDriver:PerformActions", actions)
+
+
 class MarionetteTestDriverProtocolPart(TestDriverProtocolPart):
     def setup(self):
         self.marionette = self.parent.marionette
@@ -399,7 +417,7 @@ class MarionetteCoverageProtocolPart(CoverageProtocolPart):
                 error = self.marionette.execute_async_script(script)
                 if error is not None:
                     raise Exception('Failure while resetting counters: %s' % json.dumps(error))
-            except (errors.MarionetteException, socket.error):
+            except (errors.MarionetteException, IOError):
                 # This usually happens if the process crashed
                 pass
 
@@ -419,7 +437,7 @@ class MarionetteCoverageProtocolPart(CoverageProtocolPart):
                 error = self.marionette.execute_async_script(script)
                 if error is not None:
                     raise Exception('Failure while dumping counters: %s' % json.dumps(error))
-            except (errors.MarionetteException, socket.error):
+            except (errors.MarionetteException, IOError):
                 # This usually happens if the process crashed
                 pass
 
@@ -432,6 +450,7 @@ class MarionetteProtocol(Protocol):
                   MarionetteSelectorProtocolPart,
                   MarionetteClickProtocolPart,
                   MarionetteSendKeysProtocolPart,
+                  MarionetteActionSequenceProtocolPart,
                   MarionetteTestDriverProtocolPart,
                   MarionetteAssertsProtocolPart,
                   MarionetteCoverageProtocolPart]
@@ -471,7 +490,7 @@ class MarionetteProtocol(Protocol):
         self.logger.debug("Marionette session started")
 
     def after_connect(self):
-        self.testharness.load_runner(self.executor.last_environment["protocol"])
+        pass
 
     def teardown(self):
         try:
@@ -567,7 +586,7 @@ class ExecuteAsyncScriptRun(object):
         except errors.ScriptTimeoutException:
             self.logger.debug("Got a marionette timeout")
             self.result = False, ("EXTERNAL-TIMEOUT", None)
-        except (socket.timeout, IOError):
+        except IOError:
             # This can happen on a crash
             # Also, should check after the test if the firefox process is still running
             # and otherwise ignore any other result and set it to crash
@@ -598,7 +617,12 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
         TestharnessExecutor.__init__(self, browser, server_config,
                                      timeout_multiplier=timeout_multiplier,
                                      debug_info=debug_info)
-        self.protocol = MarionetteProtocol(self, browser, capabilities, timeout_multiplier, kwargs["e10s"], ccov)
+        self.protocol = MarionetteProtocol(self,
+                                           browser,
+                                           capabilities,
+                                           timeout_multiplier,
+                                           kwargs["e10s"],
+                                           ccov)
         self.script = open(os.path.join(here, "testharness_webdriver.js")).read()
         self.script_resume = open(os.path.join(here, "testharness_webdriver_resume.js")).read()
         self.close_after_done = close_after_done
@@ -610,6 +634,10 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
         if marionette is None:
             do_delayed_imports()
 
+    def setup(self, runner):
+        super(MarionetteTestharnessExecutor, self).setup(runner)
+        self.protocol.testharness.load_runner(self.last_environment["protocol"])
+
     def is_alive(self):
         return self.protocol.is_alive
 
@@ -647,7 +675,6 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
         return (test.result_cls(extra=extra, *data), [])
 
     def do_testharness(self, protocol, url, timeout):
-        protocol.base.execute_script("if (window.win) {window.win.close()}")
         parent_window = protocol.testharness.close_old_windows(protocol)
 
         if timeout is not None:
@@ -667,11 +694,12 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
 
         script = self.script % format_map
 
-        rv = protocol.base.execute_script(script)
+        protocol.base.execute_script(script, async=True)
         test_window = protocol.testharness.get_test_window(self.window_id, parent_window)
 
         handler = CallbackHandler(self.logger, protocol, test_window)
         while True:
+            self.protocol.base.set_window(test_window)
             result = protocol.base.execute_script(
                 self.script_resume % format_map, async=True)
             if result is None:
@@ -727,12 +755,14 @@ class MarionetteRefTestExecutor(RefTestExecutor):
     def teardown(self):
         try:
             self.implementation.teardown()
-            handle = self.protocol.marionette.window_handles[0]
-            self.protocol.marionette.switch_to_window(handle)
+            handles = self.protocol.marionette.window_handles
+            if handles:
+                self.protocol.marionette.switch_to_window(handles[0])
             super(self.__class__, self).teardown()
         except Exception as e:
             # Ignore errors during teardown
-            self.logger.warning(traceback.format_exc(e))
+            self.logger.warning("Exception during reftest teardown:\n%s" %
+                                traceback.format_exc(e))
 
     def is_alive(self):
         return self.protocol.is_alive
@@ -817,11 +847,12 @@ class InternalRefTestImplementation(object):
 
     def run_test(self, test):
         references = self.get_references(test)
+        timeout = (test.timeout * 1000) * self.timeout_multiplier
         rv = self.executor.protocol.marionette._send_message("reftest:run",
                                                              {"test": self.executor.test_url(test),
                                                               "references": references,
                                                               "expected": test.expected(),
-                                                              "timeout": test.timeout * 1000})["value"]
+                                                              "timeout": timeout})["value"]
         return rv
 
     def get_references(self, node):
index d9b6796..0e788c5 100644 (file)
@@ -18,6 +18,7 @@ from .protocol import (BaseProtocolPart,
                        SelectorProtocolPart,
                        ClickProtocolPart,
                        SendKeysProtocolPart,
+                       ActionSequenceProtocolPart,
                        TestDriverProtocolPart)
 from ..testrunner import Stop
 
@@ -26,15 +27,18 @@ here = os.path.join(os.path.split(__file__)[0])
 webdriver = None
 exceptions = None
 RemoteConnection = None
+Command = None
 
 
 def do_delayed_imports():
     global webdriver
     global exceptions
     global RemoteConnection
+    global Command
     from selenium import webdriver
     from selenium.common import exceptions
     from selenium.webdriver.remote.remote_connection import RemoteConnection
+    from selenium.webdriver.remote.command import Command
 
 
 class SeleniumBaseProtocolPart(BaseProtocolPart):
@@ -72,37 +76,42 @@ class SeleniumBaseProtocolPart(BaseProtocolPart):
 class SeleniumTestharnessProtocolPart(TestharnessProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
+        self.runner_handle = None
+        with open(os.path.join(here, "runner.js")) as f:
+            self.runner_script = f.read()
 
     def load_runner(self, url_protocol):
+        if self.runner_handle:
+            self.webdriver.switch_to_window(self.runner_handle)
         url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
                                "/testharness_runner.html")
         self.logger.debug("Loading %s" % url)
         self.webdriver.get(url)
-        self.webdriver.execute_script("document.title = '%s'" %
-                                      threading.current_thread().name.replace("'", '"'))
+        self.runner_handle = self.webdriver.current_window_handle
+        format_map = {"title": threading.current_thread().name.replace("'", '"')}
+        self.parent.base.execute_script(self.runner_script % format_map)
 
     def close_old_windows(self):
-        exclude = self.webdriver.current_window_handle
-        handles = [item for item in self.webdriver.window_handles if item != exclude]
+        handles = [item for item in self.webdriver.window_handles if item != self.runner_handle]
         for handle in handles:
             try:
                 self.webdriver.switch_to_window(handle)
                 self.webdriver.close()
             except exceptions.NoSuchWindowException:
                 pass
-        self.webdriver.switch_to_window(exclude)
-        return exclude
+        self.webdriver.switch_to_window(self.runner_handle)
+        return self.runner_handle
 
     def get_test_window(self, window_id, parent):
         test_window = None
-        if window_id:
-            try:
-                # Try this, it's in Level 1 but nothing supports it yet
-                win_s = self.webdriver.execute_script("return window['%s'];" % self.window_id)
-                win_obj = json.loads(win_s)
-                test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
-            except Exception:
-                pass
+        try:
+            # Try using the JSON serialization of the WindowProxy object,
+            # it's in Level 1 but nothing supports it yet
+            win_s = self.webdriver.execute_script("return window['%s'];" % window_id)
+            win_obj = json.loads(win_s)
+            test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
+        except Exception:
+            pass
 
         if test_window is None:
             after = self.webdriver.window_handles
@@ -133,6 +142,7 @@ class SeleniumClickProtocolPart(ClickProtocolPart):
     def element(self, element):
         return element.click()
 
+
 class SeleniumSendKeysProtocolPart(SendKeysProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
@@ -141,6 +151,14 @@ class SeleniumSendKeysProtocolPart(SendKeysProtocolPart):
         return element.send_keys(keys)
 
 
+class SeleniumActionSequenceProtocolPart(ActionSequenceProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def send_actions(self, actions):
+        self.webdriver.execute(Command.W3C_ACTIONS, {"actions": actions})
+
+
 class SeleniumTestDriverProtocolPart(TestDriverProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
@@ -161,7 +179,8 @@ class SeleniumProtocol(Protocol):
                   SeleniumSelectorProtocolPart,
                   SeleniumClickProtocolPart,
                   SeleniumSendKeysProtocolPart,
-                  SeleniumTestDriverProtocolPart]
+                  SeleniumTestDriverProtocolPart,
+                  SeleniumActionSequenceProtocolPart]
 
     def __init__(self, executor, browser, capabilities, **kwargs):
         do_delayed_imports()
@@ -226,8 +245,12 @@ class SeleniumRun(object):
 
         flag = self.result_flag.wait(timeout + 2 * extra_timeout)
         if self.result is None:
-            assert not flag
-            self.result = False, ("EXTERNAL-TIMEOUT", None)
+            if flag:
+                # flag is True unless we timeout; this *shouldn't* happen, but
+                # it can if self._run fails to set self.result due to raising
+                self.result = False, ("INTERNAL-ERROR", "self._run didn't set a result")
+            else:
+                self.result = False, ("EXTERNAL-TIMEOUT", None)
 
         return self.result
 
@@ -239,11 +262,11 @@ class SeleniumRun(object):
         except (socket.timeout, exceptions.ErrorInResponseException):
             self.result = False, ("CRASH", None)
         except Exception as e:
-            message = getattr(e, "message", "")
+            message = str(getattr(e, "message", ""))
             if message:
                 message += "\n"
             message += traceback.format_exc(e)
-            self.result = False, ("INTERNAL-ERROR", e)
+            self.result = False, ("INTERNAL-ERROR", message)
         finally:
             self.result_flag.set()
 
@@ -295,11 +318,12 @@ class SeleniumTestharnessExecutor(TestharnessExecutor):
 
         parent_window = protocol.testharness.close_old_windows()
         # Now start the test harness
-        protocol.base.execute_script(self.script % format_map)
-        test_window = protocol.testharness.get_test_window(webdriver, parent_window)
+        protocol.base.execute_script(self.script % format_map, async=True)
+        test_window = protocol.testharness.get_test_window(self.window_id, parent_window)
 
         handler = CallbackHandler(self.logger, protocol, test_window)
         while True:
+            self.protocol.base.set_window(test_window)
             result = protocol.base.execute_script(
                 self.script_resume % format_map, async=True)
             done, rv = handler(result)
index caa9714..49b682c 100644 (file)
@@ -8,7 +8,7 @@ import uuid
 
 from mozprocess import ProcessHandler
 
-from serve.serve import make_hosts_file
+from tools.serve.serve import make_hosts_file
 
 from .base import (ConnectionlessProtocol,
                    RefTestImplementation,
diff --git a/WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executorwebdriver.py
new file mode 100644 (file)
index 0000000..40e67d2
--- /dev/null
@@ -0,0 +1,395 @@
+import json
+import os
+import socket
+import threading
+import traceback
+import urlparse
+import uuid
+
+from .base import (CallbackHandler,
+                   RefTestExecutor,
+                   RefTestImplementation,
+                   TestharnessExecutor,
+                   extra_timeout,
+                   strip_server)
+from .protocol import (BaseProtocolPart,
+                       TestharnessProtocolPart,
+                       Protocol,
+                       SelectorProtocolPart,
+                       ClickProtocolPart,
+                       SendKeysProtocolPart,
+                       ActionSequenceProtocolPart,
+                       TestDriverProtocolPart)
+from ..testrunner import Stop
+
+import webdriver as client
+
+here = os.path.join(os.path.split(__file__)[0])
+
+
+class WebDriverBaseProtocolPart(BaseProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def execute_script(self, script, async=False):
+        method = self.webdriver.execute_async_script if async else self.webdriver.execute_script
+        return method(script)
+
+    def set_timeout(self, timeout):
+        try:
+            self.webdriver.timeouts.script = timeout
+        except client.WebDriverException:
+            # workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2057
+            body = {"type": "script", "ms": timeout * 1000}
+            self.webdriver.send_session_command("POST", "timeouts", body)
+
+    @property
+    def current_window(self):
+        return self.webdriver.window_handle
+
+    def set_window(self, handle):
+        self.webdriver.window_handle = handle
+
+    def wait(self):
+        while True:
+            try:
+                self.webdriver.execute_async_script("")
+            except (client.TimeoutException, client.ScriptTimeoutException):
+                pass
+            except (socket.timeout, client.NoSuchWindowException,
+                    client.UnknownErrorException, IOError):
+                break
+            except Exception as e:
+                self.logger.error(traceback.format_exc(e))
+                break
+
+
+class WebDriverTestharnessProtocolPart(TestharnessProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+        self.runner_handle = None
+        with open(os.path.join(here, "runner.js")) as f:
+            self.runner_script = f.read()
+
+    def load_runner(self, url_protocol):
+        if self.runner_handle:
+            self.webdriver.window_handle = self.runner_handle
+        url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
+                               "/testharness_runner.html")
+        self.logger.debug("Loading %s" % url)
+
+        self.webdriver.url = url
+        self.runner_handle = self.webdriver.window_handle
+        format_map = {"title": threading.current_thread().name.replace("'", '"')}
+        self.parent.base.execute_script(self.runner_script % format_map)
+
+    def close_old_windows(self):
+        handles = [item for item in self.webdriver.handles if item != self.runner_handle]
+        for handle in handles:
+            try:
+                self.webdriver.window_handle = handle
+                self.webdriver.close()
+            except client.NoSuchWindowException:
+                pass
+        self.webdriver.window_handle = self.runner_handle
+        return self.runner_handle
+
+    def get_test_window(self, window_id, parent):
+        test_window = None
+        try:
+            # Try using the JSON serialization of the WindowProxy object,
+            # it's in Level 1 but nothing supports it yet
+            win_s = self.webdriver.execute_script("return window['%s'];" % window_id)
+            win_obj = json.loads(win_s)
+            test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
+        except Exception:
+            pass
+
+        if test_window is None:
+            after = self.webdriver.handles
+            if len(after) == 2:
+                test_window = next(iter(set(after) - set([parent])))
+            elif after[0] == parent and len(after) > 2:
+                # Hope the first one here is the test window
+                test_window = after[1]
+            else:
+                raise Exception("unable to find test window")
+
+        assert test_window != parent
+        return test_window
+
+
+class WebDriverSelectorProtocolPart(SelectorProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def elements_by_selector(self, selector):
+        return self.webdriver.find.css(selector)
+
+
+class WebDriverClickProtocolPart(ClickProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def element(self, element):
+        self.logger.info("click " + repr(element))
+        return element.click()
+
+
+class WebDriverSendKeysProtocolPart(SendKeysProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def send_keys(self, element, keys):
+        try:
+            return element.send_keys(keys)
+        except client.UnknownErrorException as e:
+            # workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=1999
+            if (e.http_status != 500 or
+                e.status_code != "unknown error"):
+                raise
+            return element.send_element_command("POST", "value", {"value": list(keys)})
+
+
+class WebDriverActionSequenceProtocolPart(ActionSequenceProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def send_actions(self, actions):
+        self.webdriver.actions.perform(actions)
+
+
+class WebDriverTestDriverProtocolPart(TestDriverProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def send_message(self, message_type, status, message=None):
+        obj = {
+            "type": "testdriver-%s" % str(message_type),
+            "status": str(status)
+        }
+        if message:
+            obj["message"] = str(message)
+        self.webdriver.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
+
+
+class WebDriverProtocol(Protocol):
+    implements = [WebDriverBaseProtocolPart,
+                  WebDriverTestharnessProtocolPart,
+                  WebDriverSelectorProtocolPart,
+                  WebDriverClickProtocolPart,
+                  WebDriverSendKeysProtocolPart,
+                  WebDriverActionSequenceProtocolPart,
+                  WebDriverTestDriverProtocolPart]
+
+    def __init__(self, executor, browser, capabilities, **kwargs):
+        super(WebDriverProtocol, self).__init__(executor, browser)
+        self.capabilities = capabilities
+        self.url = browser.webdriver_url
+        self.webdriver = None
+
+    def connect(self):
+        """Connect to browser via WebDriver."""
+        self.logger.debug("Connecting to WebDriver on URL: %s" % self.url)
+
+        host, port = self.url.split(":")[1].strip("/"), self.url.split(':')[-1].strip("/")
+
+        capabilities = {"alwaysMatch": self.capabilities}
+        self.webdriver = client.Session(host, port, capabilities=capabilities)
+        self.webdriver.start()
+
+
+    def after_conect(self):
+        pass
+
+    def teardown(self):
+        self.logger.debug("Hanging up on WebDriver session")
+        try:
+            self.webdriver.quit()
+        except Exception:
+            pass
+        del self.webdriver
+
+    def is_alive(self):
+        try:
+            # Get a simple property over the connection
+            self.webdriver.window_handle
+        except (socket.timeout, client.UnknownErrorException):
+            return False
+        return True
+
+    def after_connect(self):
+        self.testharness.load_runner(self.executor.last_environment["protocol"])
+
+
+class WebDriverRun(object):
+    def __init__(self, func, protocol, url, timeout):
+        self.func = func
+        self.result = None
+        self.protocol = protocol
+        self.url = url
+        self.timeout = timeout
+        self.result_flag = threading.Event()
+
+    def run(self):
+        timeout = self.timeout
+
+        try:
+            self.protocol.base.set_timeout((timeout + extra_timeout))
+        except client.UnknownErrorException:
+            self.logger.error("Lost WebDriver connection")
+            return Stop
+
+        executor = threading.Thread(target=self._run)
+        executor.start()
+
+        flag = self.result_flag.wait(timeout + 2 * extra_timeout)
+        if self.result is None:
+            if flag:
+                # flag is True unless we timeout; this *shouldn't* happen, but
+                # it can if self._run fails to set self.result due to raising
+                self.result = False, ("INTERNAL-ERROR", "self._run didn't set a result")
+            else:
+                self.result = False, ("EXTERNAL-TIMEOUT", None)
+
+        return self.result
+
+    def _run(self):
+        try:
+            self.result = True, self.func(self.protocol, self.url, self.timeout)
+        except (client.TimeoutException, client.ScriptTimeoutException):
+            self.result = False, ("EXTERNAL-TIMEOUT", None)
+        except (socket.timeout, client.UnknownErrorException):
+            self.result = False, ("CRASH", None)
+        except Exception as e:
+            if (isinstance(e, client.WebDriverException) and
+                    e.http_status == 408 and
+                    e.status_code == "asynchronous script timeout"):
+                # workaround for https://bugs.chromium.org/p/chromedriver/issues/detail?id=2001
+                self.result = False, ("EXTERNAL-TIMEOUT", None)
+            else:
+                message = str(getattr(e, "message", ""))
+                if message:
+                    message += "\n"
+                message += traceback.format_exc(e)
+                self.result = False, ("INTERNAL-ERROR", message)
+        finally:
+            self.result_flag.set()
+
+
+class WebDriverTestharnessExecutor(TestharnessExecutor):
+    supports_testdriver = True
+
+    def __init__(self, browser, server_config, timeout_multiplier=1,
+                 close_after_done=True, capabilities=None, debug_info=None,
+                 **kwargs):
+        """WebDriver-based executor for testharness.js tests"""
+        TestharnessExecutor.__init__(self, browser, server_config,
+                                     timeout_multiplier=timeout_multiplier,
+                                     debug_info=debug_info)
+        self.protocol = WebDriverProtocol(self, browser, capabilities)
+        with open(os.path.join(here, "testharness_webdriver.js")) as f:
+            self.script = f.read()
+        with open(os.path.join(here, "testharness_webdriver_resume.js")) as f:
+            self.script_resume = f.read()
+        self.close_after_done = close_after_done
+        self.window_id = str(uuid.uuid4())
+
+    def is_alive(self):
+        return self.protocol.is_alive()
+
+    def on_environment_change(self, new_environment):
+        if new_environment["protocol"] != self.last_environment["protocol"]:
+            self.protocol.testharness.load_runner(new_environment["protocol"])
+
+    def do_test(self, test):
+        url = self.test_url(test)
+
+        success, data = WebDriverRun(self.do_testharness,
+                                    self.protocol,
+                                    url,
+                                    test.timeout * self.timeout_multiplier).run()
+
+        if success:
+            return self.convert_result(test, data)
+
+        return (test.result_cls(*data), [])
+
+    def do_testharness(self, protocol, url, timeout):
+        format_map = {"abs_url": url,
+                      "url": strip_server(url),
+                      "window_id": self.window_id,
+                      "timeout_multiplier": self.timeout_multiplier,
+                      "timeout": timeout * 1000}
+
+        parent_window = protocol.testharness.close_old_windows()
+        # Now start the test harness
+        protocol.base.execute_script(self.script % format_map, async=True)
+        test_window = protocol.testharness.get_test_window(self.window_id, parent_window)
+
+        handler = CallbackHandler(self.logger, protocol, test_window)
+        while True:
+            self.protocol.base.set_window(test_window)
+            result = protocol.base.execute_script(
+                self.script_resume % format_map, async=True)
+            done, rv = handler(result)
+            if done:
+                break
+        return rv
+
+
+class WebDriverRefTestExecutor(RefTestExecutor):
+    def __init__(self, browser, server_config, timeout_multiplier=1,
+                 screenshot_cache=None, close_after_done=True,
+                 debug_info=None, capabilities=None, **kwargs):
+        """WebDriver-based executor for reftests"""
+        RefTestExecutor.__init__(self,
+                                 browser,
+                                 server_config,
+                                 screenshot_cache=screenshot_cache,
+                                 timeout_multiplier=timeout_multiplier,
+                                 debug_info=debug_info)
+        self.protocol = WebDriverProtocol(self, browser,
+                                          capabilities=capabilities)
+        self.implementation = RefTestImplementation(self)
+        self.close_after_done = close_after_done
+        self.has_window = False
+
+        with open(os.path.join(here, "reftest.js")) as f:
+            self.script = f.read()
+        with open(os.path.join(here, "reftest-wait_webdriver.js")) as f:
+            self.wait_script = f.read()
+
+    def is_alive(self):
+        return self.protocol.is_alive()
+
+    def do_test(self, test):
+        self.protocol.webdriver.window.size = (600, 600)
+
+        result = self.implementation.run_test(test)
+
+        return self.convert_result(test, result)
+
+    def screenshot(self, test, viewport_size, dpi):
+        # https://github.com/w3c/wptrunner/issues/166
+        assert viewport_size is None
+        assert dpi is None
+
+        return WebDriverRun(self._screenshot,
+                           self.protocol,
+                           self.test_url(test),
+                           test.timeout).run()
+
+    def _screenshot(self, protocol, url, timeout):
+        webdriver = protocol.webdriver
+        webdriver.url = url
+
+        webdriver.execute_async_script(self.wait_script)
+
+        screenshot = webdriver.screenshot()
+
+        # strip off the data:img/png, part of the url
+        if screenshot.startswith("data:image/png;base64,"):
+            screenshot = screenshot.split(",", 1)[1]
+
+        return screenshot
index 71fc3c9..74a7cc4 100644 (file)
@@ -1,4 +1,5 @@
 import traceback
+
 from abc import ABCMeta, abstractmethod
 
 
@@ -53,6 +54,10 @@ class Protocol(object):
 
             msg = "Post-connection steps failed"
             self.after_connect()
+        except IOError:
+            self.logger.warning("Timed out waiting for browser to start")
+            self.executor.runner.send_message("init_failed")
+            return
         except Exception:
             if msg is not None:
                 self.logger.warning(msg)
@@ -237,6 +242,14 @@ class SelectorProtocolPart(ProtocolPart):
 
     name = "select"
 
+    def element_by_selector(self, selector):
+        elements = self.elements_by_selector(selector)
+        if len(elements) == 0:
+            raise ValueError("Selector '%s' matches no elements" % selector)
+        elif len(elements) > 1:
+            raise ValueError("Selector '%s' matches multiple elements" % selector)
+        return elements[0]
+
     @abstractmethod
     def elements_by_selector(self, selector):
         """Select elements matching a CSS selector
@@ -274,6 +287,20 @@ class SendKeysProtocolPart(ProtocolPart):
         pass
 
 
+class ActionSequenceProtocolPart(ProtocolPart):
+    """Protocol part for performing trusted clicks"""
+    __metaclass__ = ABCMeta
+
+    name = "action_sequence"
+
+    @abstractmethod
+    def send_actions(self, actions):
+        """Send a sequence of actions to the window.
+
+        :param actions: A protocol-specific handle to an array of actions."""
+        pass
+
+
 class TestDriverProtocolPart(ProtocolPart):
     """Protocol part that implements the basic functionality required for
     all testdriver-based tests."""
diff --git a/WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/runner.js b/WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/runner.js
new file mode 100644 (file)
index 0000000..8b80003
--- /dev/null
@@ -0,0 +1,59 @@
+document.title = '%(title)s';
+
+window.addEventListener(
+  "message",
+  function(event) {
+    window.message_queue.push(event);
+    window.process_next_event();
+  },
+  false
+);
+
+
+window.process_next_event = function() {
+  /* This function handles the next testdriver event. The presence of
+     window.testdriver_callback is used as a switch; when that function
+     is present we are able to handle the next event and when is is not
+     present we must wait. Therefore to drive the event processing, this
+     function must be called in two circumstances:
+       * Every time there is a new event that we may be able to handle
+       * Every time we set the callback function
+     This function unsets the callback, so no further testdriver actions
+     will be run until it is reset, which wptrunner does after it has
+     completed handling the current action.
+   */
+  if (!window.testdriver_callback) {
+    return;
+  }
+  var event = window.message_queue.shift();
+  if (!event) {
+    return;
+  }
+  var data = event.data;
+
+  var payload = undefined;
+
+  switch(data.type) {
+  case "complete":
+    var tests = event.data.tests;
+    var status = event.data.status;
+
+    var subtest_results = tests.map(function(x) {
+      return [x.name, x.status, x.message, x.stack];
+    });
+    payload = [status.status,
+               status.message,
+               status.stack,
+               subtest_results];
+    clearTimeout(window.timer);
+    break;
+  case "action":
+    payload = data;
+    break;
+  default:
+    return;
+  }
+  var callback = window.testdriver_callback;
+  window.testdriver_callback = null;
+  callback([window.url, data.type, payload]);
+};
index 7f00050..37023d8 100644 (file)
@@ -1,25 +1,19 @@
-window.timeout_multiplier = %(timeout_multiplier)d;
-
-window.message_queue = [];
+var callback = arguments[arguments.length - 1];
+var loaded = false;
 
-window.setMessageListener = function(func) {
-  window.current_listener = func;
-  window.addEventListener(
-    "message",
-    func,
-    false
-  );
-};
-
-window.setMessageListener(function(event) {
-  window.message_queue.push(event);
+window.timeout_multiplier = %(timeout_multiplier)d;
+window.url = "%(url)s";
+window.win = window.open("%(abs_url)s", "%(window_id)s");
+window.win.addEventListener('DOMContentLoaded', (e) => {
+  callback();
 });
 
-window.win = window.open("%(abs_url)s", "%(window_id)s");
+
+window.message_queue = [];
+window.testdriver_callback = null;
 
 if (%(timeout)s != null) {
   window.timer = setTimeout(function() {
     window.win.timeout();
-    window.win.close();
   }, %(timeout)s);
 }
index 7a2df98..e78951a 100644 (file)
@@ -1,46 +1,21 @@
 var callback = arguments[arguments.length - 1];
+window.opener.testdriver_callback = function(results) {
+  /**
+   * The current window and its opener belong to the same domain, making it
+   * technically possible for data structures to be shared directly.
+   * Unfortunately, some browser/WebDriver implementations incorrectly
+   * serialize Arrays from foreign realms [1]. This issue does not extend to
+   * the behavior of `JSON.stringify` and `JSON.parse` in these
+   * implementations. Use that API to re-create the data structure in the local
+   * realm to avoid the problem in the non-conforming browsers.
+   *
+   * [1] This has been observed in Edge version 17 and/or the corresponding
+   *     release of Edgedriver
+   */
+  try {
+    results = JSON.parse(JSON.stringify(results));
+  } catch (error) {}
 
-function process_event(event) {
-  var data = event.data;
-
-  var payload = undefined;
-
-  switch(data.type) {
-  case "complete":
-    var tests = event.data.tests;
-    var status = event.data.status;
-
-    var subtest_results = tests.map(function(x) {
-      return [x.name, x.status, x.message, x.stack];
-    });
-    payload = [status.status,
-               status.message,
-               status.stack,
-               subtest_results];
-    clearTimeout(window.timer);
-    break;
-
-  case "action":
-    window.setMessageListener(function(event) {
-      window.message_queue.push(event);
-    });
-    payload = data;
-    break;
-  default:
-    return;
-  }
-
-  callback(["%(url)s", data.type, payload]);
-}
-
-window.removeEventListener("message", window.current_listener);
-if (window.message_queue.length) {
-  var next = window.message_queue.shift();
-  process_event(next);
-} else {
-  window.addEventListener(
-    "message", function f(event) {
-      window.removeEventListener("message", f);
-      process_event(event);
-    }, false);
-}
+  callback(results);
+};
+window.opener.process_next_event();
index 1ea09db..0a54624 100644 (file)
@@ -1,10 +1,57 @@
 import json
+import re
+import sys
 
 from mozlog.structured.formatters.base import BaseFormatter
 
 
+LONE_SURROGATE_RE = re.compile(u"[\uD800-\uDFFF]")
+
+
+def surrogate_replacement_ucs4(match):
+    return "U+" + hex(ord(match.group()))[2:]
+
+
+class SurrogateReplacementUcs2(object):
+    def __init__(self):
+        self.skip = False
+
+    def __call__(self, match):
+        char = match.group()
+
+        if self.skip:
+            self.skip = False
+            return char
+
+        is_low = 0xD800 <= ord(char) <= 0xDBFF
+
+        escape = True
+        if is_low:
+            next_idx = match.end()
+            if next_idx < len(match.string):
+                next_char = match.string[next_idx]
+                if 0xDC00 <= ord(next_char) <= 0xDFFF:
+                    escape = False
+
+        if not escape:
+            self.skip = True
+            return char
+
+        return "U+" + hex(ord(match.group()))[2:]
+
+
+if sys.maxunicode == 0x10FFFF:
+    surrogate_replacement = surrogate_replacement_ucs4
+else:
+    surrogate_replacement = SurrogateReplacementUcs2()
+
+
+def replace_lone_surrogate(data):
+    return LONE_SURROGATE_RE.subn(surrogate_replacement, data)[0]
+
+
 class WptreportFormatter(BaseFormatter):
-    """Formatter that produces results in the format that wpreport expects."""
+    """Formatter that produces results in the format that wptreport expects."""
 
     def __init__(self):
         self.raw_results = {}
@@ -40,7 +87,7 @@ class WptreportFormatter(BaseFormatter):
 
     def create_subtest(self, data):
         test = self.find_or_create_test(data)
-        subtest_name = data["subtest"]
+        subtest_name = replace_lone_surrogate(data["subtest"])
 
         subtest = {
             "name": subtest_name,
@@ -57,7 +104,7 @@ class WptreportFormatter(BaseFormatter):
         if "expected" in data:
             subtest["expected"] = data["expected"]
         if "message" in data:
-            subtest["message"] = data["message"]
+            subtest["message"] = replace_lone_surrogate(data["message"])
 
     def test_end(self, data):
         test = self.find_or_create_test(data)
@@ -67,7 +114,7 @@ class WptreportFormatter(BaseFormatter):
         if "expected" in data:
             test["expected"] = data["expected"]
         if "message" in data:
-            test["message"] = data["message"]
+            test["message"] = replace_lone_surrogate(data["message"])
 
     def assertion_count(self, data):
         test = self.find_or_create_test(data)
index 831f537..7ecef4b 100644 (file)
@@ -156,6 +156,10 @@ class ExpectedManifest(ManifestItem):
     def lsan_allowed(self):
         return lsan_allowed(self)
 
+    @property
+    def lsan_max_stack_depth(self):
+        return int_prop("lsan-max-stack-depth", self)
+
 
 class DirectoryManifest(ManifestItem):
     @property
@@ -190,6 +194,9 @@ class DirectoryManifest(ManifestItem):
     def lsan_allowed(self):
         return lsan_allowed(self)
 
+    @property
+    def lsan_max_stack_depth(self):
+        return int_prop("lsan-max-stack-depth", self)
 
 class TestNode(ManifestItem):
     def __init__(self, name):
@@ -251,6 +258,10 @@ class TestNode(ManifestItem):
     def lsan_allowed(self):
         return lsan_allowed(self)
 
+    @property
+    def lsan_max_stack_depth(self):
+        return int_prop("lsan-max-stack-depth", self)
+
     def append(self, node):
         """Add a subtest to the current test
 
index 859f014..db388bb 100644 (file)
@@ -15,6 +15,11 @@ localpaths = imp.load_source("localpaths", os.path.abspath(os.path.join(here, os
 from wpt.markdown import markdown_adjust, table
 
 
+# If a test takes more than (FLAKY_THRESHOLD*timeout) and does not consistently
+# time out, it is considered slow (potentially flaky).
+FLAKY_THRESHOLD = 0.8
+
+
 class LogActionFilter(BaseHandler):
 
     """Handler that filters out messages not of a given set of actions.
@@ -53,7 +58,8 @@ class LogHandler(reader.LogHandler):
 
         test = {
             "subtests": OrderedDict(),
-            "status": defaultdict(int)
+            "status": defaultdict(int),
+            "longest_duration": defaultdict(float),
         }
         self.results[test_name] = test
         return test
@@ -73,6 +79,10 @@ class LogHandler(reader.LogHandler):
 
         return subtest
 
+    def test_start(self, data):
+        test = self.find_or_create_test(data)
+        test["start_time"] = data["time"]
+
     def test_status(self, data):
         subtest = self.find_or_create_subtest(data)
         subtest["status"][data["status"]] += 1
@@ -82,6 +92,16 @@ class LogHandler(reader.LogHandler):
     def test_end(self, data):
         test = self.find_or_create_test(data)
         test["status"][data["status"]] += 1
+        # Timestamps are in ms since epoch.
+        duration = data["time"] - test.pop("start_time")
+        test["longest_duration"][data["status"]] = max(
+            duration, test["longest_duration"][data["status"]])
+        try:
+            # test_timeout is in seconds; convert it to ms.
+            test["timeout"] = data["extra"]["test_timeout"] * 1000
+        except KeyError:
+            # If a test is skipped, it won't have extra info.
+            pass
 
 
 def is_inconsistent(results_dict, iterations):
@@ -91,9 +111,32 @@ def is_inconsistent(results_dict, iterations):
     return len(results_dict) > 1 or sum(results_dict.values()) != iterations
 
 
+def find_slow_status(test):
+    """Check if a single test almost times out.
+
+    We are interested in tests that almost time out (i.e. likely to be flaky).
+    Therefore, timeout statuses are ignored, including (EXTERNAL-)TIMEOUT.
+    CRASH & ERROR are also ignored because the they override TIMEOUT; a test
+    that both crashes and times out is marked as CRASH, so it won't be flaky.
+
+    Returns:
+        A result status produced by a run that almost times out; None, if no
+        runs almost time out.
+    """
+    if "timeout" not in test:
+        return None
+    threshold = test["timeout"] * FLAKY_THRESHOLD
+    for status in ['PASS', 'FAIL', 'OK']:
+        if (status in test["longest_duration"] and
+            test["longest_duration"][status] > threshold):
+            return status
+    return None
+
+
 def process_results(log, iterations):
     """Process test log and return overall results and list of inconsistent tests."""
     inconsistent = []
+    slow = []
     handler = LogHandler()
     reader.handle_log(reader.read(log), handler)
     results = handler.results
@@ -103,7 +146,17 @@ def process_results(log, iterations):
         for subtest_name, subtest in test["subtests"].iteritems():
             if is_inconsistent(subtest["status"], iterations):
                 inconsistent.append((test_name, subtest_name, subtest["status"], subtest["messages"]))
-    return results, inconsistent
+
+        slow_status = find_slow_status(test)
+        if slow_status is not None:
+            slow.append((
+                test_name,
+                slow_status,
+                test["longest_duration"][slow_status],
+                test["timeout"]
+            ))
+
+    return results, inconsistent, slow
 
 
 def err_string(results_dict, iterations):
@@ -133,6 +186,17 @@ def write_inconsistent(log, inconsistent, iterations):
     table(["Test", "Subtest", "Results", "Messages"], strings, log)
 
 
+def write_slow_tests(log, slow):
+    log("## Slow tests ##\n")
+    strings = [(
+        "`%s`" % markdown_adjust(test),
+        "`%s`" % status,
+        "`%.0f`" % duration,
+        "`%.0f`" % timeout)
+        for test, status, duration, timeout in slow]
+    table(["Test", "Result", "Longest duration (ms)", "Timeout (ms)"], strings, log)
+
+
 def write_results(log, results, iterations, pr_number=None, use_details=False):
     log("## All results ##\n")
     if use_details:
@@ -202,8 +266,8 @@ def run_step(logger, iterations, restart_after_iteration, kwargs_extras, **kwarg
     logger._state.suite_started = False
 
     log.seek(0)
-    results, inconsistent = process_results(log, iterations)
-    return results, inconsistent, iterations
+    results, inconsistent, slow = process_results(log, iterations)
+    return results, inconsistent, slow, iterations
 
 
 def get_steps(logger, repeat_loop, repeat_restart, kwargs_extras):
@@ -242,7 +306,6 @@ def write_summary(logger, step_results, final_result):
 
     logger.info(':::')
 
-
 def check_stability(logger, repeat_loop=10, repeat_restart=5, chaos_mode=True, max_time=None,
                     output_results=True, **kwargs):
     kwargs_extras = [{}]
@@ -264,7 +327,7 @@ def check_stability(logger, repeat_loop=10, repeat_restart=5, chaos_mode=True, m
         logger.info(':::')
         logger.info('::: Running test verification step "%s"...' % desc)
         logger.info(':::')
-        results, inconsistent, iterations = step_func(**kwargs)
+        results, inconsistent, slow, iterations = step_func(**kwargs)
         if output_results:
             write_results(logger.info, results, iterations)
 
@@ -274,6 +337,12 @@ def check_stability(logger, repeat_loop=10, repeat_restart=5, chaos_mode=True, m
             write_summary(logger, step_results, "FAIL")
             return 1
 
+        if slow:
+            step_results.append((desc, "FAIL"))
+            write_slow_tests(logger.info, slow)
+            write_summary(logger, step_results, "FAIL")
+            return 1
+
         step_results.append((desc, "PASS"))
 
     write_summary(logger, step_results, "PASS")
index ef962d3..d77731a 100644 (file)
         window.opener.postMessage({"type": "action", "action": "send_keys", "selector": selector, "keys": keys}, "*");
         return pending_promise;
     };
+
+    window.test_driver_internal.action_sequence = function(actions) {
+        const pending_promise = new Promise(function(resolve, reject) {
+            pending_resolve = resolve;
+            pending_reject = reject;
+        });
+        for (let actionSequence of actions) {
+            if (actionSequence.type == "pointer") {
+                for (let action of actionSequence.actions) {
+                    if (action.type == "pointerMove" && action.origin instanceof Element) {
+                        action.origin = {selector: get_selector(action.origin)};
+                    }
+                }
+            }
+        }
+        window.opener.postMessage({"type": "action", "action": "action_sequence", "actions": actions}, "*");
+        return pending_promise;
+    };
 })();
index 018dc10..a395a50 100644 (file)
@@ -1,5 +1,4 @@
 import hashlib
-import json
 import os
 import urlparse
 from abc import ABCMeta, abstractmethod
@@ -15,15 +14,13 @@ from mozlog import structured
 manifest = None
 manifest_update = None
 download_from_github = None
-manifest_log = None
 
 def do_delayed_imports():
     # This relies on an already loaded module having set the sys.path correctly :(
-    global manifest, manifest_update, download_from_github, manifest_log
+    global manifest, manifest_update, download_from_github
     from manifest import manifest
     from manifest import update as manifest_update
     from manifest.download import download_from_github
-    from manifest import log as manifest_log
 
 
 class TestChunker(object):
@@ -367,6 +364,7 @@ class TestFilter(object):
             if include_tests:
                 yield test_type, test_path, include_tests
 
+
 class TagFilter(object):
     def __init__(self, tags):
         self.tags = set(tags)
@@ -378,14 +376,15 @@ class TagFilter(object):
 
 
 class ManifestLoader(object):
-    def __init__(self, test_paths, force_manifest_update=False, manifest_download=False, types=None, meta_filters=None):
+    def __init__(self, test_paths, force_manifest_update=False, manifest_download=False,
+                 types=None, meta_filters=None):
         do_delayed_imports()
         self.test_paths = test_paths
         self.force_manifest_update = force_manifest_update
         self.manifest_download = manifest_download
         self.types = types
-        self.meta_filters = meta_filters or []
         self.logger = structured.get_default_logger()
+        self.meta_filters = meta_filters
         if self.logger is None:
             self.logger = structured.structuredlog.StructuredLogger("ManifestLoader")
 
@@ -399,54 +398,13 @@ class ManifestLoader(object):
             rv[manifest_file] = path_data
         return rv
 
-    def create_manifest(self, manifest_path, tests_path, url_base="/"):
-        self.update_manifest(manifest_path, tests_path, url_base, recreate=True,
-                             download=self.manifest_download)
-
-    def update_manifest(self, manifest_path, tests_path, url_base="/",
-                        recreate=False, download=False):
-        self.logger.info("Updating test manifest %s" % manifest_path)
-        manifest_log.setup()
-
-        json_data = None
-        if download:
-            # TODO: make this not github-specific
+    def load_manifest(self, tests_path, manifest_path, metadata_path, url_base="/", **kwargs):
+        cache_root = os.path.join(metadata_path, ".cache")
+        if self.manifest_download:
             download_from_github(manifest_path, tests_path)
-
-        if not recreate:
-            try:
-                with open(manifest_path) as f:
-                    json_data = json.load(f)
-            except IOError:
-                self.logger.info("Unable to find test manifest")
-            except ValueError:
-                self.logger.info("Unable to parse test manifest")
-
-        if not json_data:
-            self.logger.info("Creating test manifest")
-            manifest_file = manifest.Manifest(url_base)
-        else:
-            try:
-                manifest_file = manifest.Manifest.from_json(tests_path, json_data)
-            except manifest.ManifestVersionMismatch:
-                manifest_file = manifest.Manifest(url_base)
-
-        manifest_update.update(tests_path, manifest_file, True)
-
-        manifest.write(manifest_file, manifest_path)
-
-    def load_manifest(self, tests_path, manifest_path, url_base="/", **kwargs):
-        if (not os.path.exists(manifest_path) or
-            self.force_manifest_update):
-            self.update_manifest(manifest_path, tests_path, url_base, download=self.manifest_download)
-        manifest_file = manifest.load(tests_path, manifest_path, types=self.types, meta_filters=self.meta_filters)
-        if manifest_file.url_base != url_base:
-            self.logger.info("Updating url_base in manifest from %s to %s" % (manifest_file.url_base,
-                                                                              url_base))
-            manifest_file.url_base = url_base
-            manifest.write(manifest_file, manifest_path)
-
-        return manifest_file
+        return manifest.load_and_update(tests_path, manifest_path, url_base,
+                                        cache_root=cache_root, update=self.force_manifest_update,
+                                        meta_filters=self.meta_filters)
 
 
 def iterfilter(filters, iter):
@@ -506,12 +464,12 @@ class TestLoader(object):
                     self._test_ids += [item.id for item in test_dict[test_type]]
         return self._test_ids
 
-    def get_test(self, manifest_test, inherit_metadata, test_metadata):
+    def get_test(self, manifest_file, manifest_test, inherit_metadata, test_metadata):
         if test_metadata is not None:
             inherit_metadata.append(test_metadata)
             test_metadata = test_metadata.get_test(manifest_test.id)
 
-        return wpttest.from_manifest(manifest_test, inherit_metadata, test_metadata)
+        return wpttest.from_manifest(manifest_file, manifest_test, inherit_metadata, test_metadata)
 
     def load_dir_metadata(self, test_manifest, metadata_path, test_path):
         rv = []
@@ -534,27 +492,24 @@ class TestLoader(object):
 
     def iter_tests(self):
         manifest_items = []
+        manifests_by_url_base = {}
 
         for manifest in sorted(self.manifests.keys(), key=lambda x:x.url_base):
             manifest_iter = iterfilter(self.manifest_filters,
                                        manifest.itertypes(*self.test_types))
             manifest_items.extend(manifest_iter)
+            manifests_by_url_base[manifest.url_base] = manifest
 
         if self.chunker is not None:
             manifest_items = self.chunker(manifest_items)
 
         for test_type, test_path, tests in manifest_items:
-            manifest_file = iter(tests).next().manifest
+            manifest_file = manifests_by_url_base[iter(tests).next().url_base]
             metadata_path = self.manifests[manifest_file]["metadata_path"]
-            inherit_metadata, test_metadata = self.load_metadata(manifest_file, metadata_path, test_path)
 
-            for test in iterfilter(self.meta_filters,
-                                   self.iter_wpttest(inherit_metadata, test_metadata, tests)):
-                yield test_path, test_type, test
-
-    def iter_wpttest(self, inherit_metadata, test_metadata, tests):
-        for manifest_test in tests:
-            yield self.get_test(manifest_test, inherit_metadata, test_metadata)
+            inherit_metadata, test_metadata = self.load_metadata(manifest_file, metadata_path, test_path)
+            for test in tests:
+                yield test_path, test_type, self.get_test(manifest_file, test, inherit_metadata, test_metadata)
 
     def _load_tests(self):
         """Read in the tests from the manifest file and add them to a queue"""
index 46d67e3..e819f24 100644 (file)
@@ -9,6 +9,8 @@ from multiprocessing import Process, current_process, Queue
 
 from mozlog import structuredlog
 
+import wptlogging
+
 # Special value used as a sentinal in various commands
 Stop = object()
 
@@ -40,16 +42,17 @@ for level_name in structuredlog.log_levels:
 
 
 class TestRunner(object):
-    def __init__(self, command_queue, result_queue, executor):
+    def __init__(self, logger, command_queue, result_queue, executor):
         """Class implementing the main loop for running tests.
 
         This class delegates the job of actually running a test to the executor
         that is passed in.
 
+        :param logger: Structured logger
         :param command_queue: subprocess.Queue used to send commands to the
                               process
         :param result_queue: subprocess.Queue used to send results to the
-                             parent TestManager process
+                             parent TestRunnerManager process
         :param executor: TestExecutor object that will actually run a test.
         """
         self.command_queue = command_queue
@@ -57,7 +60,7 @@ class TestRunner(object):
 
         self.executor = executor
         self.name = current_process().name
-        self.logger = MessageLogger(self.send_message)
+        self.logger = logger
 
     def __enter__(self):
         return self
@@ -117,30 +120,31 @@ class TestRunner(object):
 def start_runner(runner_command_queue, runner_result_queue,
                  executor_cls, executor_kwargs,
                  executor_browser_cls, executor_browser_kwargs,
-                 stop_flag):
+                 capture_stdio, stop_flag):
     """Launch a TestRunner in a new process"""
-    def log(level, msg):
-        runner_result_queue.put(("log", (level, {"message": msg})))
+
+    def send_message(command, *args):
+        runner_result_queue.put((command, args))
 
     def handle_error(e):
-        log("critical", traceback.format_exc())
+        logger.critical(traceback.format_exc())
         stop_flag.set()
 
-    try:
-        browser = executor_browser_cls(**executor_browser_kwargs)
-        executor = executor_cls(browser, **executor_kwargs)
-        with TestRunner(runner_command_queue, runner_result_queue, executor) as runner:
-            try:
-                runner.run()
-            except KeyboardInterrupt:
-                stop_flag.set()
-            except Exception as e:
-                handle_error(e)
-    except Exception as e:
-        handle_error(e)
-    finally:
-        runner_command_queue = None
-        runner_result_queue = None
+    logger = MessageLogger(send_message)
+
+    with wptlogging.CaptureIO(logger, capture_stdio):
+        try:
+            browser = executor_browser_cls(**executor_browser_kwargs)
+            executor = executor_cls(browser, **executor_kwargs)
+            with TestRunner(logger, runner_command_queue, runner_result_queue, executor) as runner:
+                try:
+                    runner.run()
+                except KeyboardInterrupt:
+                    stop_flag.set()
+                except Exception as e:
+                    handle_error(e)
+        except Exception as e:
+            handle_error(e)
 
 
 manager_count = 0
@@ -210,7 +214,7 @@ class BrowserManager(object):
         self.command_queue.put((command, args))
 
     def init_timeout(self):
-        # This is called from a seperate thread, so we send a message to the
+        # This is called from a separate thread, so we send a message to the
         # main loop so we get back onto the manager thread
         self.logger.debug("init_failed called from timer")
         self.send_message("init_failed")
@@ -230,10 +234,10 @@ class BrowserManager(object):
             self.init_timer.cancel()
 
     def check_for_crashes(self):
-        self.browser.check_for_crashes()
+        return self.browser.check_for_crashes()
 
     def log_crash(self, test_id):
-        self.browser.log_crash(process=self.browser_pid, test=test_id)
+        return self.browser.log_crash(process=self.browser_pid, test=test_id)
 
     def is_alive(self):
         return self.browser.is_alive()
@@ -255,7 +259,8 @@ RunnerManagerState = _RunnerManagerState()
 class TestRunnerManager(threading.Thread):
     def __init__(self, suite_name, test_queue, test_source_cls, browser_cls, browser_kwargs,
                  executor_cls, executor_kwargs, stop_flag, rerun=1, pause_after_test=False,
-                 pause_on_unexpected=False, restart_on_unexpected=True, debug_info=None):
+                 pause_on_unexpected=False, restart_on_unexpected=True, debug_info=None,
+                 capture_stdio=True):
         """Thread that owns a single TestRunner process and any processes required
         by the TestRunner (e.g. the Firefox binary).
 
@@ -299,7 +304,7 @@ class TestRunnerManager(threading.Thread):
 
         self.test_runner_proc = None
 
-        threading.Thread.__init__(self, name="Thread-TestrunnerManager-%i" % self.manager_number)
+        threading.Thread.__init__(self, name="TestRunnerManager-%i" % self.manager_number)
         # This is started in the actual new thread
         self.logger = None
 
@@ -313,10 +318,12 @@ class TestRunnerManager(threading.Thread):
 
         self.browser = None
 
+        self.capture_stdio = capture_stdio
+
     def run(self):
-        """Main loop for the TestManager.
+        """Main loop for the TestRunnerManager.
 
-        TestManagers generally receive commands from their
+        TestRunnerManagers generally receive commands from their
         TestRunner updating them on the status of a test. They
         may also have a stop flag set by the main thread indicating
         that the manager should shut down the next time the event loop
@@ -479,10 +486,11 @@ class TestRunnerManager(threading.Thread):
                 self.executor_kwargs,
                 executor_browser_cls,
                 executor_browser_kwargs,
+                self.capture_stdio,
                 self.child_stop_flag)
         self.test_runner_proc = Process(target=start_runner,
                                         args=args,
-                                        name="Thread-TestRunner-%i" % self.manager_number)
+                                        name="TestRunner-%i" % self.manager_number)
         self.test_runner_proc.start()
         self.logger.debug("Test runner started")
         # Now we wait for either an init_succeeded event or an init_failed event
@@ -496,6 +504,8 @@ class TestRunnerManager(threading.Thread):
 
     def init_failed(self):
         assert isinstance(self.state, RunnerManagerState.initializing)
+        if self.browser.check_for_crashes():
+            self.browser.log_crash(None)
         self.browser.after_init()
         self.stop_runner(force=True)
         return RunnerManagerState.initializing(self.state.test,
@@ -570,9 +580,8 @@ class TestRunnerManager(threading.Thread):
         expected = test.expected()
         status = status_subns.get(file_result.status, file_result.status)
 
-        if file_result.status in ("TIMEOUT", "EXTERNAL-TIMEOUT", "INTERNAL-ERROR"):
-            if self.browser.check_for_crashes():
-                status = "CRASH"
+        if self.browser.check_for_crashes():
+            status = "CRASH"
 
         self.test_count += 1
         is_unexpected = expected != status
@@ -590,6 +599,8 @@ class TestRunnerManager(threading.Thread):
                                             test.min_assertion_count,
                                             test.max_assertion_count)
 
+        file_result.extra["test_timeout"] = test.timeout * self.executor_kwargs['timeout_multiplier']
+
         self.logger.test_end(test.id,
                              status,
                              message=file_result.message,
@@ -612,10 +623,10 @@ class TestRunnerManager(threading.Thread):
 
     def wait_finished(self):
         assert isinstance(self.state, RunnerManagerState.running)
-        # The browser should be stopped already, but this ensures we do any post-stop
-        # processing
         self.logger.debug("Wait finished")
 
+        # The browser should be stopped already, but this ensures we do any
+        # post-stop processing
         return self.after_test_end(self.state.test, True)
 
     def after_test_end(self, test, restart):
@@ -663,7 +674,7 @@ class TestRunnerManager(threading.Thread):
             self.cleanup()
 
     def teardown(self):
-        self.logger.debug("teardown in testrunnermanager")
+        self.logger.debug("TestRunnerManager teardown")
         self.test_runner_proc = None
         self.command_queue.close()
         self.remote_queue.close()
@@ -682,9 +693,18 @@ class TestRunnerManager(threading.Thread):
             # This might leak a file handle from the queue
             self.logger.warning("Forcibly terminating runner process")
             self.test_runner_proc.terminate()
-            self.test_runner_proc.join(10)
+
+            # Multiprocessing queues are backed by operating system pipes. If
+            # the pipe in the child process had buffered data at the time of
+            # forced termination, the queue is no longer in a usable state
+            # (subsequent attempts to retrieve items may block indefinitely).
+            # Discard the potentially-corrupted queue and create a new one.
+            self.command_queue.close()
+            self.command_queue = Queue()
+            self.remote_queue.close()
+            self.remote_queue = Queue()
         else:
-            self.logger.debug("Testrunner exited with code %i" % self.test_runner_proc.exitcode)
+            self.logger.debug("Runner process exited with code %i" % self.test_runner_proc.exitcode)
 
     def runner_teardown(self):
         self.ensure_runner_stopped()
@@ -694,7 +714,7 @@ class TestRunnerManager(threading.Thread):
         self.remote_queue.put((command, args))
 
     def cleanup(self):
-        self.logger.debug("TestManager cleanup")
+        self.logger.debug("TestRunnerManager cleanup")
         if self.browser:
             self.browser.cleanup()
         while True:
@@ -705,12 +725,17 @@ class TestRunnerManager(threading.Thread):
             else:
                 if cmd == "log":
                     self.log(*data)
+                elif cmd == "runner_teardown":
+                    # It's OK for the "runner_teardown" message to be left in
+                    # the queue during cleanup, as we will already have tried
+                    # to stop the TestRunner in `stop_runner`.
+                    pass
                 else:
-                    self.logger.warning("%r: %r" % (cmd, data))
+                    self.logger.warning("Command left in command_queue during cleanup: %r, %r" % (cmd, data))
         while True:
             try:
                 cmd, data = self.remote_queue.get_nowait()
-                self.logger.warning("%r: %r" % (cmd, data))
+                self.logger.warning("Command left in remote_queue during cleanup: %r, %r" % (cmd, data))
             except Empty:
                 break
 
@@ -734,8 +759,9 @@ class ManagerGroup(object):
                  pause_after_test=False,
                  pause_on_unexpected=False,
                  restart_on_unexpected=True,
-                 debug_info=None):
-        """Main thread object that owns all the TestManager threads."""
+                 debug_info=None,
+                 capture_stdio=True):
+        """Main thread object that owns all the TestRunnerManager threads."""
         self.suite_name = suite_name
         self.size = size
         self.test_source_cls = test_source_cls
@@ -749,6 +775,7 @@ class ManagerGroup(object):
         self.restart_on_unexpected = restart_on_unexpected
         self.debug_info = debug_info
         self.rerun = rerun
+        self.capture_stdio = capture_stdio
 
         self.pool = set()
         # Event that is polled by threads so that they can gracefully exit in the face
@@ -785,19 +812,16 @@ class ManagerGroup(object):
                                         self.pause_after_test,
                                         self.pause_on_unexpected,
                                         self.restart_on_unexpected,
-                                        self.debug_info)
+                                        self.debug_info,
+                                        self.capture_stdio)
             manager.start()
             self.pool.add(manager)
         self.wait()
 
-    def is_alive(self):
-        """Boolean indicating whether any manager in the group is still alive"""
-        return any(manager.is_alive() for manager in self.pool)
-
     def wait(self):
         """Wait for all the managers in the group to finish"""
-        for item in self.pool:
-            item.join()
+        for manager in self.pool:
+            manager.join()
 
     def stop(self):
         """Set the stop flag so that all managers in the group stop as soon
@@ -806,7 +830,7 @@ class ManagerGroup(object):
         self.logger.debug("Stop flag set in ManagerGroup")
 
     def test_count(self):
-        return sum(item.test_count for item in self.pool)
+        return sum(manager.test_count for manager in self.pool)
 
     def unexpected_count(self):
-        return sum(item.unexpected_count for item in self.pool)
+        return sum(manager.unexpected_count for manager in self.pool)
index b5173f3..47a31de 100644 (file)
@@ -18,6 +18,7 @@ if "CURRENT_TOX_ENV" in os.environ:
 
     tox_env_extra_browsers = {
         "chrome": {"chrome_android"},
+        "edge": {"edge_webdriver"},
         "servo": {"servodriver"},
     }
 
index fc2ce5b..9e354f0 100644 (file)
@@ -4,10 +4,13 @@ import time
 from os.path import dirname, join
 from StringIO import StringIO
 
+import mock
+
 from mozlog import handlers, structuredlog
 
 sys.path.insert(0, join(dirname(__file__), "..", ".."))
 
+from wptrunner import formatters
 from wptrunner.formatters import WptreportFormatter
 
 
@@ -62,3 +65,73 @@ def test_wptreport_run_info_optional(capfd):
     output.seek(0)
     output_obj = json.load(output)
     assert "run_info" not in output_obj or output_obj["run_info"] == {}
+
+
+def test_wptreport_lone_surrogate(capfd):
+    output = StringIO()
+    logger = structuredlog.StructuredLogger("test_a")
+    logger.add_handler(handlers.StreamHandler(output, WptreportFormatter()))
+
+    # output a bunch of stuff
+    logger.suite_start(["test-id-1"])  # no run_info arg!
+    logger.test_start("test-id-1")
+    logger.test_status("test-id-1",
+                       subtest=u"Name with surrogate\uD800",
+                       status="FAIL",
+                       message=u"\U0001F601 \uDE0A\uD83D")
+    logger.test_end("test-id-1",
+                    status="PASS",
+                    message=u"\uDE0A\uD83D \U0001F601")
+    logger.suite_end()
+
+    # check nothing got output to stdout/stderr
+    # (note that mozlog outputs exceptions during handling to stderr!)
+    captured = capfd.readouterr()
+    assert captured.out == ""
+    assert captured.err == ""
+
+    # check the actual output of the formatter
+    output.seek(0)
+    output_obj = json.load(output)
+    test = output_obj["results"][0]
+    assert test["message"] == u"U+de0aU+d83d \U0001F601"
+    subtest = test["subtests"][0]
+    assert subtest["name"] == u"Name with surrogateU+d800"
+    assert subtest["message"] == u"\U0001F601 U+de0aU+d83d"
+
+
+def test_wptreport_lone_surrogate_ucs2(capfd):
+    # Since UCS4 is a superset of UCS2 we can meaningfully test the UCS2 code on a
+    # UCS4 build, but not the reverse. However UCS2 is harder to handle and UCS4 is
+    # the commonest (and sensible) configuration, so that's OK.
+    output = StringIO()
+    logger = structuredlog.StructuredLogger("test_a")
+    logger.add_handler(handlers.StreamHandler(output, WptreportFormatter()))
+
+    with mock.patch.object(formatters, 'surrogate_replacement', formatters.SurrogateReplacementUcs2()):
+        # output a bunch of stuff
+        logger.suite_start(["test-id-1"])  # no run_info arg!
+        logger.test_start("test-id-1")
+        logger.test_status("test-id-1",
+                           subtest=u"Name with surrogate\uD800",
+                           status="FAIL",
+                           message=u"\U0001F601 \uDE0A\uD83D \uD83D\uDE0A")
+        logger.test_end("test-id-1",
+                        status="PASS",
+                        message=u"\uDE0A\uD83D \uD83D\uDE0A \U0001F601")
+        logger.suite_end()
+
+    # check nothing got output to stdout/stderr
+    # (note that mozlog outputs exceptions during handling to stderr!)
+    captured = capfd.readouterr()
+    assert captured.out == ""
+    assert captured.err == ""
+
+    # check the actual output of the formatter
+    output.seek(0)
+    output_obj = json.load(output)
+    test = output_obj["results"][0]
+    assert test["message"] == u"U+de0aU+d83d \U0001f60a \U0001F601"
+    subtest = test["subtests"][0]
+    assert subtest["name"] == u"Name with surrogateU+d800"
+    assert subtest["message"] == u"\U0001F601 U+de0aU+d83d \U0001f60a"
index 72cff21..c3c89f2 100644 (file)
@@ -5,8 +5,32 @@ sys.path.insert(0, join(dirname(__file__), "..", ".."))
 
 from wptrunner import stability
 
+
 def test_is_inconsistent():
     assert stability.is_inconsistent({"PASS": 10}, 10) is False
     assert stability.is_inconsistent({"PASS": 9}, 10) is True
     assert stability.is_inconsistent({"PASS": 9, "FAIL": 1}, 10) is True
     assert stability.is_inconsistent({"PASS": 8, "FAIL": 1}, 10) is True
+
+
+def test_find_slow_status():
+    assert stability.find_slow_status({
+        "longest_duration": {"TIMEOUT": 10},
+        "timeout": 10}) is None
+    assert stability.find_slow_status({
+        "longest_duration": {"CRASH": 10},
+        "timeout": 10}) is None
+    assert stability.find_slow_status({
+        "longest_duration": {"ERROR": 10},
+        "timeout": 10}) is None
+    assert stability.find_slow_status({
+        "longest_duration": {"PASS": 1},
+        "timeout": 10}) is None
+    assert stability.find_slow_status({
+        "longest_duration": {"PASS": 81},
+        "timeout": 100}) == "PASS"
+    assert stability.find_slow_status({
+        "longest_duration": {"TIMEOUT": 10, "FAIL": 81},
+        "timeout": 100}) == "FAIL"
+    assert stability.find_slow_status({
+        "longest_duration": {"SKIP": 0}}) is None
index 556b2b7..7f1700c 100644 (file)
@@ -4,6 +4,8 @@ import os
 import sys
 import tempfile
 
+import pytest
+
 sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
 
 from mozlog import structured
@@ -19,6 +21,8 @@ skip: true
 """
 
 
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="NamedTemporaryFile cannot be reopened on Win32")
 def test_filter_unicode():
     tests = make_mock_manifest(("test", "a", 10), ("test", "a/b", 10),
                                ("test", "c", 10))
index 5c654c9..36facdc 100644 (file)
@@ -32,7 +32,7 @@ item_classes = {"testharness": manifest_item.TestharnessTest,
                 "reftest_node": manifest_item.RefTestNode,
                 "manual": manifest_item.ManualTest,
                 "stub": manifest_item.Stub,
-                "wdspec": manifest_item.WebdriverSpecTest,
+                "wdspec": manifest_item.WebDriverSpecTest,
                 "conformancechecker": manifest_item.ConformanceCheckerTest,
                 "visual": manifest_item.VisualTest,
                 "support": manifest_item.SupportFile}
@@ -98,7 +98,7 @@ def create_test_manifest(tests, url_base="/"):
     source_files = []
     for i, (test, _, test_type, _) in enumerate(tests):
         if test_type:
-            source_files.append(SourceFileWithTest(test, str(i) * 40, item_classes[test_type]))
+            source_files.append((SourceFileWithTest(test, str(i) * 40, item_classes[test_type]), True))
     m = manifest.Manifest()
     m.update(source_files)
     return m
index ff7c260..c04df92 100644 (file)
@@ -18,6 +18,10 @@ min-asserts: 1
 tags: [b, c]
 """
 
+dir_ini_2 = """\
+lsan-max-stack-depth: 42
+"""
+
 test_0 = """\
 [0.html]
   prefs: [c:d]
@@ -25,6 +29,19 @@ test_0 = """\
   tags: [a, @Reset]
 """
 
+test_1 = """\
+[1.html]
+  prefs:
+    if os == 'win': [a:b, c:d]
+  expected:
+    if os == 'win': FAIL
+"""
+
+test_2 = """\
+[2.html]
+  lsan-max-stack-depth: 42
+"""
+
 
 def test_metadata_inherit():
     tests = make_mock_manifest(("test", "a", 10), ("test", "a/b", 10),
@@ -43,8 +60,62 @@ def test_metadata_inherit():
                                                     url_base="")
 
     test = tests[0][2].pop()
-    test_obj = wpttest.from_manifest(test, inherit_metadata, test_metadata.get_test(test.id))
+    test_obj = wpttest.from_manifest(tests, test, inherit_metadata, test_metadata.get_test(test.id))
     assert test_obj.max_assertion_count == 3
     assert test_obj.min_assertion_count == 1
     assert test_obj.prefs == {"b": "c", "c": "d"}
     assert test_obj.tags == {"a", "dir:a"}
+
+
+def test_conditional():
+    tests = make_mock_manifest(("test", "a", 10), ("test", "a/b", 10),
+                               ("test", "c", 10))
+
+    test_metadata = manifestexpected.static.compile(BytesIO(test_1),
+                                                    {"os": "win"},
+                                                    data_cls_getter=manifestexpected.data_cls_getter,
+                                                    test_path="a",
+                                                    url_base="")
+
+    test = tests[1][2].pop()
+    test_obj = wpttest.from_manifest(tests, test, [], test_metadata.get_test(test.id))
+    assert test_obj.prefs == {"a": "b", "c": "d"}
+    assert test_obj.expected() == "FAIL"
+
+
+def test_metadata_lsan_stack_depth():
+    tests = make_mock_manifest(("test", "a", 10), ("test", "a/b", 10))
+
+    test_metadata = manifestexpected.static.compile(BytesIO(test_2),
+                                                    {},
+                                                    data_cls_getter=manifestexpected.data_cls_getter,
+                                                    test_path="a",
+                                                    url_base="")
+
+    test = tests[2][2].pop()
+    test_obj = wpttest.from_manifest(tests, test, [], test_metadata.get_test(test.id))
+
+    assert test_obj.lsan_max_stack_depth == 42
+
+    test = tests[1][2].pop()
+    test_obj = wpttest.from_manifest(tests, test, [], test_metadata.get_test(test.id))
+
+    assert test_obj.lsan_max_stack_depth is None
+
+    test_metadata = manifestexpected.static.compile(BytesIO(test_0),
+                                                    {},
+                                                    data_cls_getter=manifestexpected.data_cls_getter,
+                                                    test_path="a",
+                                                    url_base="")
+
+    inherit_metadata = [
+        manifestexpected.static.compile(
+            BytesIO(dir_ini_2),
+            {},
+            data_cls_getter=lambda x,y: manifestexpected.DirectoryManifest)
+    ]
+
+    test = tests[0][2].pop()
+    test_obj = wpttest.from_manifest(tests, test, inherit_metadata, test_metadata.get_test(test.id))
+
+    assert test_obj.lsan_max_stack_depth == 42
index 20daf70..9006df6 100644 (file)
@@ -197,7 +197,7 @@ class GitTree(object):
                        add all files under that path.
         """
         with tempfile.TemporaryFile() as f:
-            sync_tree.git("ls-tree", "-r", "--name-only", "HEAD", stdout=f)
+            sync_tree.git("ls-tree", "-z", "-r", "--name-only", "HEAD", stdout=f)
             f.seek(0)
             ignored_files = sync_tree.git("check-ignore", "--no-index", "--stdin", "-z", stdin=f)
         args = []
index 5685f84..e5678be 100644 (file)
@@ -96,11 +96,38 @@ class UpdateMetadata(Step):
             runner.run()
 
 
+class RemoveObsolete(Step):
+    """Remove metadata files that don't corespond to an existing test file"""
+
+    def create(self, state):
+        if not state.kwargs["remove_obsolete"]:
+            return
+
+        paths = state.kwargs["test_paths"]
+        state.tests_path = state.paths["/"]["tests_path"]
+        state.metadata_path = state.paths["/"]["metadata_path"]
+
+        for url_paths in paths.itervalues():
+            tests_path = url_paths["tests_path"]
+            metadata_path = url_paths["metadata_path"]
+            for dirpath, dirnames, filenames in os.walk(metadata_path):
+                for filename in filenames:
+                    if filename == "__dir__.ini":
+                        continue
+                    if filename.endswith(".ini"):
+                        full_path = os.path.join(dirpath, filename)
+                        rel_path = os.path.relpath(full_path, metadata_path)
+                        test_path = os.path.join(tests_path, rel_path[:-4])
+                        if not os.path.exists(test_path):
+                            os.unlink(full_path)
+
+
 class UpdateRunner(StepRunner):
     """Runner for doing an overall update."""
     steps = [LoadConfig,
              LoadTrees,
              SyncFromUpstream,
+             RemoveObsolete,
              UpdateMetadata]
 
 
index 9ecd009..4ec415c 100644 (file)
@@ -197,7 +197,7 @@ class ServoDriverServer(WebDriverServer):
 
     def make_command(self):
         command = [self.binary,
-                   "--webdriver", str(self.port),
+                   "--webdriver=%s" % self.port,
                    "--hard-fail",
                    "--headless"] + self._args
         if self.binary_args:
index 7e18a8b..6881477 100644 (file)
@@ -192,6 +192,12 @@ scheme host and port.""")
                               help="Path to directory containing extra json files to add to run info")
     config_group.add_argument("--product", action="store", choices=product_choices,
                               default=None, help="Browser against which to run tests")
+    config_group.add_argument("--browser-version", action="store",
+                              default=None, help="Informative string detailing the browser "
+                              "release version. This is included in the run_info data.")
+    config_group.add_argument("--browser-channel", action="store",
+                              default=None, help="Informative string detailing the browser "
+                              "release channel. This is included in the run_info data.")
     config_group.add_argument("--config", action="store", type=abs_path, dest="config",
                               help="Path to config file")
     config_group.add_argument("--install-fonts", action="store_true",
@@ -199,6 +205,10 @@ scheme host and port.""")
                               help="Allow the wptrunner to install fonts on your system")
     config_group.add_argument("--font-dir", action="store", type=abs_path, dest="font_dir",
                               help="Path to local font installation directory", default=None)
+    config_group.add_argument("--headless", action="store_true",
+                              help="Run browser in headless mode", default=None)
+    config_group.add_argument("--no-headless", action="store_false", dest="headless",
+                              help="Don't run browser in headless mode")
 
     build_type = parser.add_mutually_exclusive_group()
     build_type.add_argument("--debug-build", dest="debug", action="store_true",
@@ -287,6 +297,11 @@ scheme host and port.""")
     sauce_group.add_argument("--sauce-connect-binary",
                              dest="sauce_connect_binary",
                              help="Path to Sauce Connect binary")
+    sauce_group.add_argument("--sauce-init-timeout", action="store",
+                             type=int, default=30,
+                             help="Number of seconds to wait for Sauce "
+                                  "Connect tunnel to be available before "
+                                  "aborting")
 
     webkit_group = parser.add_argument_group("WebKit-specific")
     webkit_group.add_argument("--webkit-port", dest="webkit_port",
@@ -354,6 +369,7 @@ def set_from_config(kwargs):
 
     check_paths(kwargs)
 
+
 def get_test_paths(config):
     # Set up test_paths
     test_paths = OrderedDict()
@@ -415,6 +431,9 @@ def check_args(kwargs):
     if kwargs["product"] is None:
         kwargs["product"] = "firefox"
 
+    if kwargs["manifest_update"] is None:
+        kwargs["manifest_update"] = True
+
     if "sauce" in kwargs["product"]:
         kwargs["pause_after_test"] = False
 
@@ -486,6 +505,9 @@ def check_args(kwargs):
         kwargs["certutil_binary"] = path
 
     if kwargs['extra_prefs']:
+        # If a single pref is passed in as a string, make it a list
+        if type(kwargs['extra_prefs']) in (str, unicode):
+            kwargs['extra_prefs'] = [kwargs['extra_prefs']]
         missing = any('=' not in prefarg for prefarg in kwargs['extra_prefs'])
         if missing:
             print >> sys.stderr, "Preferences via --setpref must be in key=value format"
@@ -494,8 +516,7 @@ def check_args(kwargs):
                                  kwargs['extra_prefs']]
 
     if kwargs["reftest_internal"] is None:
-        # Default to the internal reftest implementation on Linux and OSX
-        kwargs["reftest_internal"] = sys.platform.startswith("linux") or sys.platform.startswith("darwin")
+        kwargs["reftest_internal"] = True
 
     return kwargs
 
@@ -554,8 +575,14 @@ def create_parser_update(product_choices=None):
     parser.add_argument("--stability", nargs="?", action="store", const="unstable", default=None,
         help=("Reason for disabling tests. When updating test results, disable tests that have "
               "inconsistent results across many runs with the given reason."))
-    parser.add_argument("--continue", action="store_true", help="Continue a previously started run of the update script")
-    parser.add_argument("--abort", action="store_true", help="Clear state from a previous incomplete run of the update script")
+    parser.add_argument("--no-remove-obsolete", action="store_false", dest="remove_obsolete", default=True,
+                        help=("Don't remove metadata files that no longer correspond to a test file"))
+    parser.add_argument("--no-store-state", action="store_false", dest="store_state",
+                        help="Store state so that steps can be resumed after failure")
+    parser.add_argument("--continue", action="store_true",
+                        help="Continue a previously started run of the update script")
+    parser.add_argument("--abort", action="store_true",
+                        help="Clear state from a previous incomplete run of the update script")
     parser.add_argument("--exclude", action="store", nargs="*",
                         help="List of glob-style paths to exclude when syncing tests")
     parser.add_argument("--include", action="store", nargs="*",
index 9763670..9e0ff02 100644 (file)
@@ -212,6 +212,20 @@ class Tokenizer(object):
         else:
             self.state = self.value_state
 
+    def after_expr_state(self):
+        self.skip_whitespace()
+        c = self.char()
+        if c == "#":
+            self.next_state = self.after_expr_state
+            self.state = self.comment_state
+        elif c == eol:
+            self.next_state = self.after_expr_state
+            self.state = self.eol_state
+        elif c == "[":
+            self.state = self.list_start_state
+        else:
+            self.state = self.value_state
+
     def list_start_state(self):
         yield (token_types.list_start, "[")
         self.consume()
@@ -378,7 +392,7 @@ class Tokenizer(object):
         elif c == ":":
             yield (token_types.separator, c)
             self.consume()
-            self.state = self.value_state
+            self.state = self.after_expr_state
         elif c in parens:
             self.consume()
             yield (token_types.paren, c)
@@ -517,10 +531,12 @@ class Parser(object):
 
     def expect(self, type, value=None):
         if self.token[0] != type:
-            raise ParseError
+            raise ParseError(self.tokenizer.filename, self.tokenizer.line_number,
+                             "Token '{}' doesn't equal expected type '{}'".format(self.token[0], type))
         if value is not None:
             if self.token[1] != value:
-                raise ParseError
+                raise ParseError(self.tokenizer.filename, self.tokenizer.line_number,
+                                 "Token '{}' doesn't equal expected value '{}'".format(self.token[1], value))
 
         self.consume()
 
@@ -539,7 +555,8 @@ class Parser(object):
         while self.token == (token_types.paren, "["):
             self.consume()
             if self.token[0] != token_types.string:
-                raise ParseError
+                raise ParseError(self.tokenizer.filename, self.tokenizer.line_number,
+                                 "Token '{}' is not a string".format(self.token[0]))
             self.tree.append(DataNode(self.token[1]))
             self.consume()
             self.expect(token_types.paren, "]")
@@ -568,7 +585,8 @@ class Parser(object):
         elif self.token[0] == token_types.atom:
             self.atom()
         else:
-            raise ParseError
+            raise ParseError(self.tokenizer.filename, self.tokenizer.line_number,
+                             "Token '{}' is not a known type".format(self.token[0]))
 
     def list_value(self):
         self.tree.append(ListNode())
@@ -586,10 +604,7 @@ class Parser(object):
             self.tree.append(ConditionalNode())
             self.expr_start()
             self.expect(token_types.separator)
-            if self.token[0] == token_types.string:
-                self.value()
-            else:
-                raise ParseError
+            self.value_block()
             self.tree.pop()
 
     def value(self):
index 765c984..98b54dc 100644 (file)
@@ -63,6 +63,42 @@ key:
                   ]]]]]]
         )
 
+    def test_expr_2(self):
+        self.compare(
+            """
+key:
+  if x == 1 : [value1, value2]""",
+            ["DataNode", None,
+             [["KeyValueNode", "key",
+               [["ConditionalNode", None,
+                 [["BinaryExpressionNode", None,
+                   [["BinaryOperatorNode", "==", []],
+                    ["VariableNode", "x", []],
+                       ["NumberNode", "1", []]
+                    ]],
+                  ["ListNode", None,
+                   [["ValueNode", "value1", []],
+                    ["ValueNode", "value2", []]]],
+                  ]]]]]]
+        )
+
+    def test_expr_3(self):
+        self.compare(
+            """
+key:
+  if x == 1: if b: value""",
+            ["DataNode", None,
+             [["KeyValueNode", "key",
+               [["ConditionalNode", None,
+                 [["BinaryExpressionNode", None,
+                   [["BinaryOperatorNode", "==", []],
+                    ["VariableNode", "x", []],
+                       ["NumberNode", "1", []]
+                    ]],
+                     ["ValueNode", "if b: value", []],
+                  ]]]]]]
+        )
+
     def test_atom_0(self):
         with self.assertRaises(parser.ParseError):
             self.parse("key: @Unknown")
@@ -71,5 +107,6 @@ key:
         with self.assertRaises(parser.ParseError):
             self.parse("key: @true")
 
+
 if __name__ == "__main__":
     unittest.main()
index 91a87a9..02280e5 100644 (file)
@@ -223,3 +223,8 @@ class TokenizerTest(unittest.TestCase):
     def test_atom_4(self):
         self.compare(r"""key: [a, @Reset, b]
 """)
+
+    def test_conditional_1(self):
+        self.compare("""foo:
+  if a or b: [1, 2]
+""")
index 004610d..14f6cb9 100644 (file)
@@ -39,6 +39,7 @@ metadata files are used to store the expected test results.
 def setup_logging(*args, **kwargs):
     global logger
     logger = wptlogging.setup(*args, **kwargs)
+    return logger
 
 
 def get_loader(test_paths, product, debug=None, run_info_extras=None, **kwargs):
@@ -47,6 +48,8 @@ def get_loader(test_paths, product, debug=None, run_info_extras=None, **kwargs):
 
     run_info = wpttest.get_run_info(kwargs["run_info"], product,
                                     browser_version=kwargs.get("browser_version"),
+                                    browser_channel=kwargs.get("browser_channel"),
+                                    verify=kwargs.get("verify"),
                                     debug=debug,
                                     extras=run_info_extras)
 
@@ -123,6 +126,8 @@ def get_pause_after_test(test_loader, **kwargs):
     if kwargs["pause_after_test"] is None:
         if kwargs["repeat_until_unexpected"]:
             return False
+        if kwargs["headless"]:
+            return False
         if kwargs["repeat"] == 1 and kwargs["rerun"] == 1 and total_tests == 1:
             return True
         return False
@@ -145,20 +150,13 @@ def run_tests(config, test_paths, product, **kwargs):
         if kwargs["install_fonts"]:
             env_extras.append(FontInstaller(
                 font_dir=kwargs["font_dir"],
-                ahem=os.path.join(kwargs["tests_root"], "fonts/Ahem.ttf")
+                ahem=os.path.join(test_paths["/"]["tests_path"], "fonts/Ahem.ttf")
             ))
 
-        if "test_loader" in kwargs:
-            run_info = wpttest.get_run_info(kwargs["run_info"], product,
-                                            browser_version=kwargs.get("browser_version"),
-                                            debug=None,
-                                            extras=run_info_extras(**kwargs))
-            test_loader = kwargs["test_loader"]
-        else:
-            run_info, test_loader = get_loader(test_paths,
-                                               product,
-                                               run_info_extras=run_info_extras(**kwargs),
-                                               **kwargs)
+        run_info, test_loader = get_loader(test_paths,
+                                           product,
+                                           run_info_extras=run_info_extras(**kwargs),
+                                           **kwargs)
 
         test_source_kwargs = {"processes": kwargs["processes"]}
         if kwargs["run_by_dir"] is False:
@@ -170,6 +168,7 @@ def run_tests(config, test_paths, product, **kwargs):
 
         logger.info("Using %i client processes" % kwargs["processes"])
 
+        skipped_tests = 0
         test_total = 0
         unexpected_total = 0
 
@@ -241,19 +240,16 @@ def run_tests(config, test_paths, product, **kwargs):
                     for test in test_loader.disabled_tests[test_type]:
                         logger.test_start(test.id)
                         logger.test_end(test.id, status="SKIP")
+                        skipped_tests += 1
 
                     if test_type == "testharness":
                         run_tests = {"testharness": []}
                         for test in test_loader.tests["testharness"]:
-                            if test.testdriver and not executor_cls.supports_testdriver:
+                            if (test.testdriver and not executor_cls.supports_testdriver) or (
+                                    test.jsshell and not executor_cls.supports_jsshell):
                                 logger.test_start(test.id)
                                 logger.test_end(test.id, status="SKIP")
-                            elif test.jsshell and not executor_cls.supports_jsshell:
-                                # We expect that tests for JavaScript shells
-                                # will not be run along with tests that run in
-                                # a full web browser, so we silently skip them
-                                # here.
-                                pass
+                                skipped_tests += 1
                             else:
                                 run_tests["testharness"].append(test)
                     else:
@@ -271,26 +267,30 @@ def run_tests(config, test_paths, product, **kwargs):
                                       kwargs["pause_after_test"],
                                       kwargs["pause_on_unexpected"],
                                       kwargs["restart_on_unexpected"],
-                                      kwargs["debug_info"]) as manager_group:
+                                      kwargs["debug_info"],
+                                      not kwargs["no_capture_stdio"]) as manager_group:
                         try:
                             manager_group.run(test_type, run_tests)
                         except KeyboardInterrupt:
                             logger.critical("Main thread got signal")
                             manager_group.stop()
                             raise
-                    test_count += manager_group.test_count()
-                    unexpected_count += manager_group.unexpected_count()
+                        test_count += manager_group.test_count()
+                        unexpected_count += manager_group.unexpected_count()
 
                 test_total += test_count
                 unexpected_total += unexpected_count
                 logger.info("Got %i unexpected results" % unexpected_count)
+                logger.suite_end()
                 if repeat_until_unexpected and unexpected_total > 0:
                     break
-                logger.suite_end()
 
     if test_total == 0:
-        logger.error("No tests ran")
-        return False
+        if skipped_tests > 0:
+            logger.warning("All requested tests were skipped")
+        else:
+            logger.error("No tests ran")
+            return False
 
     if unexpected_total and not kwargs["fail_on_unexpected"]:
         logger.info("Tolerating %s unexpected results" % unexpected_total)
@@ -326,7 +326,7 @@ def start(**kwargs):
     elif kwargs["list_tests"]:
         list_tests(**kwargs)
     elif kwargs["verify"] or kwargs["stability"]:
-        check_stability(**kwargs)
+        return check_stability(**kwargs)
     else:
         return not run_tests(**kwargs)
 
index c29ba97..767a25d 100644 (file)
@@ -67,7 +67,11 @@ def get_run_info(metadata_root, product, **kwargs):
 
 
 class RunInfo(dict):
-    def __init__(self, metadata_root, product, debug, browser_version=None, extras=None):
+    def __init__(self, metadata_root, product, debug,
+                 browser_version=None,
+                 browser_channel=None,
+                 verify=None,
+                 extras=None):
         import mozinfo
         self._update_mozinfo(metadata_root)
         self.update(mozinfo.info)
@@ -89,6 +93,12 @@ class RunInfo(dict):
             self["debug"] = False
         if browser_version:
             self["browser_version"] = browser_version
+        if browser_channel:
+            self["browser_channel"] = browser_channel
+
+        self["verify"] = verify
+        if "wasm" not in self:
+            self["wasm"] = False
         if extras is not None:
             self.update(extras)
 
@@ -136,7 +146,7 @@ class Test(object):
         return metadata
 
     @classmethod
-    def from_manifest(cls, manifest_item, inherit_metadata, test_metadata):
+    def from_manifest(cls, manifest_file, manifest_item, inherit_metadata, test_metadata):
         timeout = cls.long_timeout if manifest_item.timeout == "long" else cls.default_timeout
         protocol = "https" if hasattr(manifest_item, "https") and manifest_item.https else "http"
         return cls(manifest_item.source_file.tests_root,
@@ -225,6 +235,14 @@ class Test(object):
         return lsan_allowed
 
     @property
+    def lsan_max_stack_depth(self):
+        for meta in self.itermeta(None):
+            depth = meta.lsan_max_stack_depth
+            if depth is not None:
+                return depth
+        return None
+
+    @property
     def tags(self):
         tags = set()
         for meta in self.itermeta():
@@ -284,7 +302,7 @@ class TestharnessTest(Test):
         self.scripts = scripts or []
 
     @classmethod
-    def from_manifest(cls, manifest_item, inherit_metadata, test_metadata):
+    def from_manifest(cls, manifest_file, manifest_item, inherit_metadata, test_metadata):
         timeout = cls.long_timeout if manifest_item.timeout == "long" else cls.default_timeout
         protocol = "https" if hasattr(manifest_item, "https") and manifest_item.https else "http"
         testdriver = manifest_item.testdriver if hasattr(manifest_item, "testdriver") else False
@@ -334,6 +352,7 @@ class ReftestTest(Test):
 
     @classmethod
     def from_manifest(cls,
+                      manifest_file,
                       manifest_test,
                       inherit_metadata,
                       test_metadata,
@@ -376,9 +395,10 @@ class ReftestTest(Test):
 
             references_seen.add(comparison_key)
 
-            manifest_node = manifest_test.manifest.get_reference(ref_url)
+            manifest_node = manifest_file.get_reference(ref_url)
             if manifest_node:
-                reference = ReftestTest.from_manifest(manifest_node,
+                reference = ReftestTest.from_manifest(manifest_file,
+                                                      manifest_node,
                                                       [],
                                                       None,
                                                       nodes,
@@ -430,6 +450,6 @@ manifest_test_cls = {"reftest": ReftestTest,
                      "wdspec": WdspecTest}
 
 
-def from_manifest(manifest_test, inherit_metadata, test_metadata):
+def from_manifest(manifest_file, manifest_test, inherit_metadata, test_metadata):
     test_cls = manifest_test_cls[manifest_test.item_type]
-    return test_cls.from_manifest(manifest_test, inherit_metadata, test_metadata)
+    return test_cls.from_manifest(manifest_file, manifest_test, inherit_metadata, test_metadata)
index 49b1d68..24ac645 100644 (file)
@@ -1,8 +1,9 @@
 from datetime import datetime, timedelta
 
-from tests.support.asserts import assert_error, assert_success
-from tests.support.fixtures import clear_all_cookies
+from webdriver.transport import Response
 
+from tests.support.asserts import assert_error, assert_success
+from tests.support.helpers import clear_all_cookies
 
 def add_cookie(session, cookie):
     return session.transport.send(
@@ -10,6 +11,12 @@ def add_cookie(session, cookie):
         {"cookie": cookie})
 
 
+def test_null_parameter_value(session, http):
+    path = "/session/{session_id}/cookie".format(**vars(session))
+    with http.post(path, None) as response:
+        assert_error(Response.from_http(response), "invalid argument")
+
+
 def test_null_response_value(session, url):
     new_cookie = {
         "name": "hello",
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/add_cookie/user_prompts.py b/WebDriverTests/imported/w3c/webdriver/tests/add_cookie/user_prompts.py
new file mode 100644 (file)
index 0000000..f58aacd
--- /dev/null
@@ -0,0 +1,137 @@
+# META: timeout=long
+
+import pytest
+
+from webdriver.error import NoSuchCookieException
+
+from tests.support.asserts import assert_dialog_handled, assert_error, assert_success
+
+
+def add_cookie(session, cookie):
+    return session.transport.send(
+        "POST", "session/{session_id}/cookie".format(**vars(session)),
+        {"cookie": cookie})
+
+
+@pytest.fixture
+def check_user_prompt_closed_without_exception(session, url, create_dialog):
+    def check_user_prompt_closed_without_exception(dialog_type, retval):
+        new_cookie = {
+            "name": "foo",
+            "value": "bar",
+        }
+
+        session.url = url("/common/blank.html")
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = add_cookie(session, new_cookie)
+        assert_success(response)
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert session.cookies("foo")
+
+    return check_user_prompt_closed_without_exception
+
+
+@pytest.fixture
+def check_user_prompt_closed_with_exception(session, url, create_dialog):
+    def check_user_prompt_closed_with_exception(dialog_type, retval):
+        new_cookie = {
+            "name": "foo",
+            "value": "bar",
+        }
+
+        session.url = url("/common/blank.html")
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = add_cookie(session, new_cookie)
+        assert_error(response, "unexpected alert open")
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        with pytest.raises(NoSuchCookieException):
+            assert session.cookies("foo")
+
+    return check_user_prompt_closed_with_exception
+
+
+@pytest.fixture
+def check_user_prompt_not_closed_but_exception(session, url, create_dialog):
+    def check_user_prompt_not_closed_but_exception(dialog_type):
+        new_cookie = {
+            "name": "foo",
+            "value": "bar",
+        }
+
+        session.url = url("/common/blank.html")
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = add_cookie(session, new_cookie)
+        assert_error(response, "unexpected alert open")
+
+        assert session.alert.text == dialog_type
+        session.alert.dismiss()
+
+        with pytest.raises(NoSuchCookieException):
+            assert session.cookies("foo")
+
+    return check_user_prompt_not_closed_but_exception
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "ignore"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_ignore(check_user_prompt_not_closed_but_exception, dialog_type):
+    check_user_prompt_not_closed_but_exception(dialog_type)
+
+
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_default(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/back/user_prompts.py b/WebDriverTests/imported/w3c/webdriver/tests/back/user_prompts.py
new file mode 100644 (file)
index 0000000..7121f7c
--- /dev/null
@@ -0,0 +1,119 @@
+# META: timeout=long
+
+import pytest
+
+from tests.support.asserts import assert_dialog_handled, assert_error, assert_success
+from tests.support.inline import inline
+
+
+def back(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/back".format(**vars(session)))
+
+
+@pytest.fixture
+def pages(session):
+    pages = [
+        inline("<p id=1>"),
+        inline("<p id=2>"),
+    ]
+
+    for page in pages:
+        session.url = page
+
+    return pages
+
+
+@pytest.fixture
+def check_user_prompt_closed_without_exception(session, create_dialog, pages):
+    def check_user_prompt_closed_without_exception(dialog_type, retval):
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = back(session)
+        assert_success(response)
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert session.url == pages[0]
+
+    return check_user_prompt_closed_without_exception
+
+
+@pytest.fixture
+def check_user_prompt_closed_with_exception(session, create_dialog, pages):
+    def check_user_prompt_closed_with_exception(dialog_type, retval):
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = back(session)
+        assert_error(response, "unexpected alert open")
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert session.url == pages[1]
+
+    return check_user_prompt_closed_with_exception
+
+
+@pytest.fixture
+def check_user_prompt_not_closed_but_exception(session, create_dialog, pages):
+    def check_user_prompt_not_closed_but_exception(dialog_type):
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = back(session)
+        assert_error(response, "unexpected alert open")
+
+        assert session.alert.text == dialog_type
+        session.alert.dismiss()
+
+        assert session.url == pages[1]
+
+    return check_user_prompt_not_closed_but_exception
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_accept(check_user_prompt_closed_without_exception, dialog_type):
+    # retval not testable for confirm and prompt because window is gone
+    check_user_prompt_closed_without_exception(dialog_type, None)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_dismiss(check_user_prompt_closed_without_exception, dialog_type):
+    # retval not testable for confirm and prompt because window is gone
+    check_user_prompt_closed_without_exception(dialog_type, None)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "ignore"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_ignore(check_user_prompt_not_closed_but_exception, dialog_type):
+    check_user_prompt_not_closed_but_exception(dialog_type)
+
+
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_default(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
index c396717..42b82c9 100644 (file)
@@ -1,49 +1 @@
-import pytest
-
-from tests.support.fixtures import (
-    add_event_listeners,
-    configuration,
-    closed_window,
-    create_cookie,
-    create_dialog,
-    create_frame,
-    create_window,
-    current_session,
-    http,
-    server_config,
-    session,
-    url)
-
-def pytest_configure(config):
-    # register the capabilities marker
-    config.addinivalue_line("markers",
-        "capabilities: mark test to use capabilities")
-
-
-@pytest.fixture
-def capabilities():
-    """Default capabilities to use for a new WebDriver session."""
-    return {}
-
-
-def pytest_generate_tests(metafunc):
-    if "capabilities" in metafunc.fixturenames:
-        marker = metafunc.definition.get_closest_marker(name="capabilities")
-        if marker:
-            metafunc.parametrize("capabilities", marker.args, ids=None)
-
-
-pytest.fixture()(add_event_listeners)
-pytest.fixture(scope="session")(configuration)
-pytest.fixture()(create_cookie)
-pytest.fixture()(create_dialog)
-pytest.fixture()(create_frame)
-pytest.fixture()(create_window)
-pytest.fixture(scope="function")(current_session)
-pytest.fixture()(http)
-pytest.fixture()(server_config)
-pytest.fixture(scope="function")(session)
-pytest.fixture()(url)
-
-# Fixtures for specific tests
-pytest.fixture()(closed_window)
+pytest_plugins = "tests.support.fixtures"
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/delete_all_cookies/user_prompts.py b/WebDriverTests/imported/w3c/webdriver/tests/delete_all_cookies/user_prompts.py
new file mode 100644 (file)
index 0000000..dca4f3c
--- /dev/null
@@ -0,0 +1,119 @@
+# META: timeout=long
+
+import pytest
+
+from webdriver.error import NoSuchCookieException
+
+from tests.support.asserts import assert_dialog_handled, assert_error, assert_success
+
+
+def delete_all_cookies(session):
+    return session.transport.send(
+        "DELETE", "/session/{session_id}/cookie".format(**vars(session)))
+
+
+@pytest.fixture
+def check_user_prompt_closed_without_exception(session, create_dialog, create_cookie):
+    def check_user_prompt_closed_without_exception(dialog_type, retval):
+        create_cookie("foo", value="bar", path="/common/blank.html")
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = delete_all_cookies(session)
+        assert_success(response)
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert session.cookies() == []
+
+    return check_user_prompt_closed_without_exception
+
+
+@pytest.fixture
+def check_user_prompt_closed_with_exception(session, create_dialog, create_cookie):
+    def check_user_prompt_closed_with_exception(dialog_type, retval):
+        create_cookie("foo", value="bar", path="/common/blank.html")
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = delete_all_cookies(session)
+        assert_error(response, "unexpected alert open")
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert session.cookies() != []
+
+    return check_user_prompt_closed_with_exception
+
+
+@pytest.fixture
+def check_user_prompt_not_closed_but_exception(session, create_dialog, create_cookie):
+    def check_user_prompt_not_closed_but_exception(dialog_type):
+        create_cookie("foo", value="bar", path="/common/blank.html")
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = delete_all_cookies(session)
+        assert_error(response, "unexpected alert open")
+
+        assert session.alert.text == dialog_type
+        session.alert.dismiss()
+
+        assert session.cookies() != []
+
+    return check_user_prompt_not_closed_but_exception
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "ignore"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_ignore(check_user_prompt_not_closed_but_exception, dialog_type):
+    check_user_prompt_not_closed_but_exception(dialog_type)
+
+
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_default(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
index 835f252..d0b4d79 100644 (file)
@@ -13,6 +13,7 @@ def test_null_response_value(session):
     response = delete_session(session)
     value = assert_success(response)
     assert value is None
+
     # Need an explicit call to session.end() to notify the test harness
     # that a new session needs to be created for subsequent tests.
     session.end()
index bc74af4..5d60b99 100644 (file)
@@ -1,6 +1,7 @@
 # META: timeout=long
 
 import pytest
+import time
 
 from webdriver import Element
 
@@ -47,7 +48,7 @@ def test_null_response_value(session):
 
 
 def test_no_browsing_context(session, closed_window):
-    element = Element("foo", session)
+    element = Element("foo" + str(time.time()), session)
 
     response = element_clear(session, element)
     assert_error(response, "no such window")
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/element_clear/user_prompts.py b/WebDriverTests/imported/w3c/webdriver/tests/element_clear/user_prompts.py
new file mode 100644 (file)
index 0000000..cfb850e
--- /dev/null
@@ -0,0 +1,132 @@
+# META: timeout=long
+
+import pytest
+
+from tests.support.asserts import assert_dialog_handled, assert_error, assert_success
+from tests.support.inline import inline
+
+
+def element_clear(session, element):
+    return session.transport.send(
+        "POST", "/session/{session_id}/element/{element_id}/clear".format(
+            session_id=session.session_id,
+            element_id=element.id))
+
+
+@pytest.fixture
+def check_user_prompt_closed_without_exception(session, create_dialog):
+    def check_user_prompt_closed_without_exception(dialog_type, retval):
+        session.url = inline("<input type=text>")
+        element = session.find.css("input", all=False)
+        element.send_keys("foo")
+
+        assert element.property("value") == "foo"
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = element_clear(session, element)
+        assert_success(response)
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert element.property("value") == ""
+
+    return check_user_prompt_closed_without_exception
+
+
+@pytest.fixture
+def check_user_prompt_closed_with_exception(session, create_dialog):
+    def check_user_prompt_closed_with_exception(dialog_type, retval):
+        session.url = inline("<input type=text>")
+        element = session.find.css("input", all=False)
+        element.send_keys("foo")
+
+        assert element.property("value") == "foo"
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = element_clear(session, element)
+        assert_error(response, "unexpected alert open")
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert element.property("value") == "foo"
+
+    return check_user_prompt_closed_with_exception
+
+
+@pytest.fixture
+def check_user_prompt_not_closed_but_exception(session, create_dialog):
+    def check_user_prompt_not_closed_but_exception(dialog_type):
+        session.url = inline("<input type=text>")
+        element = session.find.css("input", all=False)
+        element.send_keys("foo")
+
+        assert element.property("value") == "foo"
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = element_clear(session, element)
+        assert_error(response, "unexpected alert open")
+
+        assert session.alert.text == dialog_type
+        session.alert.dismiss()
+
+        assert element.property("value") == "foo"
+
+    return check_user_prompt_not_closed_but_exception
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "ignore"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_ignore(check_user_prompt_not_closed_but_exception, dialog_type):
+    check_user_prompt_not_closed_but_exception(dialog_type)
+
+
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_default(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/element_click/center_point.py b/WebDriverTests/imported/w3c/webdriver/tests/element_click/center_point.py
new file mode 100644 (file)
index 0000000..da60a08
--- /dev/null
@@ -0,0 +1,65 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_success
+from tests.support.helpers import center_point
+from tests.support.inline import inline
+
+
+def element_click(session, element):
+    return session.transport.send(
+        "POST", "session/{session_id}/element/{element_id}/click".format(
+            session_id=session.session_id,
+            element_id=element.id))
+
+
+def square(size):
+    return inline("""
+        <style>
+        body {{ margin: 0 }}
+
+        div {{
+          background: blue;
+          width: {size}px;
+          height: {size}px;
+        }}
+        </style>
+
+        <div id=target></div>
+
+        <script>
+        window.clicks = [];
+        let div = document.querySelector("div");
+        div.addEventListener("click", function(e) {{ window.clicks.push([e.clientX, e.clientY]) }});
+        </script>
+        """.format(size=size))
+
+
+def assert_one_click(session):
+    """Asserts there has only been one click, and returns that."""
+    clicks = session.execute_script("return window.clicks")
+    assert len(clicks) == 1
+    return tuple(clicks[0])
+
+
+def test_entirely_in_view(session):
+    session.url = square(444)
+    element = session.find.css("#target", all=False)
+
+    response = element_click(session, element)
+    assert_success(response)
+
+    click_point = assert_one_click(session)
+    assert click_point == (222, 222)
+
+
+@pytest.mark.parametrize("size", range(1, 11))
+def test_css_pixel_rounding(session, size):
+    session.url = square(size)
+    element = session.find.css("#target", all=False)
+    expected_click_point = center_point(element)
+
+    response = element_click(session, element)
+    assert_success(response)
+
+    actual_click_point = assert_one_click(session)
+    assert actual_click_point == expected_click_point
index 7af1197..7463d49 100644 (file)
@@ -3,6 +3,7 @@ import pytest
 from tests.support.asserts import assert_error, assert_success
 from tests.support.inline import inline
 
+
 def element_click(session, element):
     return session.transport.send(
         "POST", "session/{session_id}/element/{element_id}/click".format(
@@ -10,6 +11,38 @@ def element_click(session, element):
             element_id=element.id))
 
 
+def test_display_none(session):
+    session.url = inline("""<button style="display: none">foobar</button>""")
+    element = session.find.css("button", all=False)
+
+    response = element_click(session, element)
+    assert_error(response, "element not interactable")
+
+
+def test_visibility_hidden(session):
+    session.url = inline("""<button style="visibility: hidden">foobar</button>""")
+    element = session.find.css("button", all=False)
+
+    response = element_click(session, element)
+    assert_error(response, "element not interactable")
+
+
+def test_hidden(session):
+    session.url = inline("<button hidden>foobar</button>")
+    element = session.find.css("button", all=False)
+
+    response = element_click(session, element)
+    assert_error(response, "element not interactable")
+
+
+def test_disabled(session):
+    session.url = inline("""<button disabled>foobar</button>""")
+    element = session.find.css("button", all=False)
+
+    response = element_click(session, element)
+    assert_success(response)
+
+
 @pytest.mark.parametrize("transform", ["translate(-100px, -100px)", "rotate(50deg)"])
 def test_element_not_interactable_css_transform(session, transform):
     session.url = inline("""
@@ -24,19 +57,46 @@ def test_element_not_interactable_css_transform(session, transform):
 
 def test_element_not_interactable_out_of_view(session):
     session.url = inline("""
-        <div style="width: 500px; height: 100px;
-            position: absolute; left: 0px; top: -150px; background-color: blue;">
-        </div>""")
-    element = session.find.css("div", all=False)
+        <style>
+        input {
+          position: absolute;
+          margin-top: -100vh;
+          background: red;
+        }
+        </style>
+
+        <input>
+        """)
+    element = session.find.css("input", all=False)
+    response = element_click(session, element)
+    assert_error(response, "element not interactable")
+
+
+@pytest.mark.parametrize("tag_name", ["div", "span"])
+def test_zero_sized_element(session, tag_name):
+    session.url = inline("<{0}></{0}>".format(tag_name))
+    element = session.find.css(tag_name, all=False)
+
     response = element_click(session, element)
     assert_error(response, "element not interactable")
 
 
 def test_element_intercepted(session):
     session.url = inline("""
-        <input type=button value=Roger style="position: absolute; left: 10px; top: 10px">
-        <div style="position: absolute; height: 100px; width: 100px; background: rgba(255,0,0,.5); left: 10px; top: 5px"></div>""")
+        <style>
+        div {
+          position: absolute;
+          height: 100vh;
+          width: 100vh;
+          background: blue;
+          top: 0;
+          left: 0;
+        }
+        </style>
 
+        <input type=button value=Roger>
+        <div></div>
+        """)
     element = session.find.css("input", all=False)
     response = element_click(session, element)
     assert_error(response, "element click intercepted")
@@ -44,7 +104,6 @@ def test_element_intercepted(session):
 
 def test_element_intercepted_no_pointer_events(session):
     session.url = inline("""<input type=button value=Roger style="pointer-events: none">""")
-
     element = session.find.css("input", all=False)
     response = element_click(session, element)
     assert_error(response, "element click intercepted")
@@ -52,11 +111,21 @@ def test_element_intercepted_no_pointer_events(session):
 
 def test_element_not_visible_overflow_hidden(session):
     session.url = inline("""
-        <div style="position: absolute; height: 50px; width: 100px; background: rgba(255,0,0,.5); left: 10px; top: 50px; overflow: hidden">
-            ABCDEFGHIJKLMNOPQRSTUVWXYZ
-            <input type=text value=Federer style="position: absolute; top: 50px; left: 10px;">
-        </div>""")
+        <style>
+        div {
+          overflow: hidden;
+          height: 50px;
+          background: green;
+        }
+
+        input {
+          margin-top: 100px;
+          background: red;
+        }
+        </style>
 
+        <div><input></div>
+        """)
     element = session.find.css("input", all=False)
     response = element_click(session, element)
     assert_error(response, "element not interactable")
index dde83a4..4b026e9 100644 (file)
@@ -1,6 +1,7 @@
 import pytest
 
 from tests.support.asserts import assert_error, assert_success
+from tests.support.helpers import center_point
 from tests.support.inline import inline
 
 def element_click(session, element):
@@ -10,6 +11,13 @@ def element_click(session, element):
             element_id=element.id))
 
 
+def assert_one_click(session):
+    """Asserts there has only been one click, and returns that."""
+    clicks = session.execute_script("return window.clicks")
+    assert len(clicks) == 1
+    return tuple(clicks[0])
+
+
 def test_scroll_into_view(session):
     session.url = inline("""
         <input type=text value=Federer
@@ -27,3 +35,38 @@ def test_scroll_into_view(session):
             (rect["top"] + rect["height"]) <= window.innerHeight &&
             (rect["left"] + rect["width"]) <= window.innerWidth;
             """, args=(element,)) is True
+
+
+@pytest.mark.parametrize("offset", range(9, 0, -1))
+def test_partially_visible_does_not_scroll(session, offset):
+    session.url = inline("""
+        <style>
+        body {{
+          margin: 0;
+          padding: 0;
+        }}
+
+        div {{
+          background: blue;
+          height: 200px;
+
+          /* make N pixels visible in the viewport */
+          margin-top: calc(100vh - {offset}px);
+        }}
+        </style>
+
+        <div></div>
+
+        <script>
+        window.clicks = [];
+        let target = document.querySelector("div");
+        target.addEventListener("click", function(e) {{ window.clicks.push([e.clientX, e.clientY]); }});
+        </script>
+        """.format(offset=offset))
+    target = session.find.css("div", all=False)
+    assert session.execute_script("return window.scrollY || document.documentElement.scrollTop") == 0
+    response = element_click(session, target)
+    assert_success(response)
+    assert session.execute_script("return window.scrollY || document.documentElement.scrollTop") == 0
+    click_point = assert_one_click(session)
+    assert click_point == center_point(target)
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/element_click/user_prompts.py b/WebDriverTests/imported/w3c/webdriver/tests/element_click/user_prompts.py
new file mode 100644 (file)
index 0000000..b3706fe
--- /dev/null
@@ -0,0 +1,123 @@
+# META: timeout=long
+
+import pytest
+
+from tests.support.asserts import assert_dialog_handled, assert_error, assert_success
+from tests.support.inline import inline
+
+
+def element_click(session, element):
+    return session.transport.send(
+        "POST", "session/{session_id}/element/{element_id}/click".format(
+            session_id=session.session_id,
+            element_id=element.id))
+
+
+@pytest.fixture
+def check_user_prompt_closed_without_exception(session, create_dialog):
+    def check_user_prompt_closed_without_exception(dialog_type, retval):
+        session.url = inline("<input type=text>")
+        element = session.find.css("input", all=False)
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = element_click(session, element)
+        assert_success(response)
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert session.active_element == element
+
+    return check_user_prompt_closed_without_exception
+
+
+@pytest.fixture
+def check_user_prompt_closed_with_exception(session, create_dialog):
+    def check_user_prompt_closed_with_exception(dialog_type, retval):
+        session.url = inline("<input type=text>")
+        element = session.find.css("input", all=False)
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = element_click(session, element)
+        assert_error(response, "unexpected alert open")
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert session.active_element != element
+
+    return check_user_prompt_closed_with_exception
+
+
+@pytest.fixture
+def check_user_prompt_not_closed_but_exception(session, create_dialog):
+    def check_user_prompt_not_closed_but_exception(dialog_type):
+        session.url = inline("<input type=text>")
+        element = session.find.css("input", all=False)
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = element_click(session, element)
+        assert_error(response, "unexpected alert open")
+
+        assert session.alert.text == dialog_type
+        session.alert.dismiss()
+
+        assert session.active_element != element
+
+    return check_user_prompt_not_closed_but_exception
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "ignore"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_ignore(check_user_prompt_not_closed_but_exception, dialog_type):
+    check_user_prompt_not_closed_but_exception(dialog_type)
+
+
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_default(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
index d1a8b81..7fbab8a 100644 (file)
@@ -1,3 +1,5 @@
+import pytest
+
 from tests.support.asserts import assert_error, assert_files_uploaded, assert_success
 from tests.support.inline import inline
 
@@ -140,3 +142,87 @@ def test_single_file_appends_with_multiple_attribute(session, create_files):
     assert_success(response)
 
     assert_files_uploaded(session, element, files)
+
+
+def test_transparent(session, create_files):
+    files = create_files(["foo"])
+    session.url = inline("""<input type=file style="opacity: 0">""")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, str(files[0]))
+    assert_success(response)
+    assert_files_uploaded(session, element, files)
+
+
+def test_obscured(session, create_files):
+    files = create_files(["foo"])
+    session.url = inline("""
+        <style>
+          div {
+            position: absolute;
+            width: 100vh;
+            height: 100vh;
+            background: blue;
+            top: 0;
+            left: 0;
+          }
+        </style>
+
+        <input type=file>
+        <div></div>
+        """)
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, str(files[0]))
+    assert_success(response)
+    assert_files_uploaded(session, element, files)
+
+
+def test_outside_viewport(session, create_files):
+    files = create_files(["foo"])
+    session.url = inline("""<input type=file style="margin-left: -100vh">""")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, str(files[0]))
+    assert_success(response)
+    assert_files_uploaded(session, element, files)
+
+
+def test_hidden(session, create_files):
+    files = create_files(["foo"])
+    session.url = inline("<input type=file hidden>")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, str(files[0]))
+    assert_success(response)
+    assert_files_uploaded(session, element, files)
+
+
+def test_display_none(session, create_files):
+    files = create_files(["foo"])
+    session.url = inline("""<input type=file style="display: none">""")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, str(files[0]))
+    assert_success(response)
+    assert_files_uploaded(session, element, files)
+
+
+@pytest.mark.capabilities({"strictFileInteractability": True})
+def test_strict_hidden(session, create_files):
+    files = create_files(["foo"])
+    session.url = inline("<input type=file hidden>")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, str(files[0]))
+    assert_error(response, "element not interactable")
+
+
+@pytest.mark.capabilities({"strictFileInteractability": True})
+def test_strict_display_none(session, create_files):
+    files = create_files(["foo"])
+    session.url = inline("""<input type=file style="display: none">""")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, str(files[0]))
+    assert_error(response, "element not interactable")
index 3a809aa..0717537 100644 (file)
@@ -21,11 +21,11 @@ def test_body_is_interactable(session):
     result = session.find.css("input", all=False)
 
     # By default body is the active element
-    assert session.active_element is element
+    assert session.active_element == element
 
     response = element_send_keys(session, element, "foo")
     assert_success(response)
-    assert session.active_element is element
+    assert session.active_element == element
     assert result.property("value") == "foo"
 
 
@@ -41,11 +41,11 @@ def test_document_element_is_interactable(session):
     result = session.find.css("input", all=False)
 
     # By default body is the active element
-    assert session.active_element is body
+    assert session.active_element == body
 
     response = element_send_keys(session, element, "foo")
     assert_success(response)
-    assert session.active_element is element
+    assert session.active_element == element
     assert result.property("value") == "foo"
 
 
@@ -60,11 +60,11 @@ def test_iframe_is_interactable(session):
     frame = session.find.css("iframe", all=False)
 
     # By default the body has the focus
-    assert session.active_element is body
+    assert session.active_element == body
 
     response = element_send_keys(session, frame, "foo")
     assert_success(response)
-    assert session.active_element is frame
+    assert session.active_element == frame
 
     # Any key events are immediately routed to the nested
     # browsing context's active document.
@@ -111,7 +111,7 @@ def test_not_a_focusable_element(session):
     assert_error(response, "element not interactable")
 
 
-def test_not_displayed_element(session):
+def test_display_none(session):
     session.url = inline("""<input style="display: none">""")
     element = session.find.css("input", all=False)
 
@@ -119,7 +119,7 @@ def test_not_displayed_element(session):
     assert_error(response, "element not interactable")
 
 
-def test_hidden_element(session):
+def test_visibility_hidden(session):
     session.url = inline("""<input style="visibility: hidden">""")
     element = session.find.css("input", all=False)
 
@@ -127,7 +127,15 @@ def test_hidden_element(session):
     assert_error(response, "element not interactable")
 
 
-def test_disabled_element(session):
+def test_hidden(session):
+    session.url = inline("<input hidden>")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_error(response, "element not interactable")
+
+
+def test_disabled(session):
     session.url = inline("""<input disabled>""")
     element = session.find.css("input", all=False)
 
index 9ff0a9e..18f87ff 100644 (file)
@@ -1,8 +1,7 @@
 from tests.support.asserts import assert_success
-from tests.support.fixtures import is_element_in_viewport
+from tests.support.helpers import is_element_in_viewport
 from tests.support.inline import inline
 
-
 def element_send_keys(session, element, text):
     return session.transport.send(
         "POST", "/session/{session_id}/element/{element_id}/value".format(
index 0d281d7..03cc948 100644 (file)
@@ -1,6 +1,7 @@
 import pytest
 
 from webdriver import Element
+from webdriver.transport import Response
 
 from tests.support.asserts import assert_error, assert_success
 from tests.support.inline import inline
@@ -14,6 +15,16 @@ def element_send_keys(session, element, text):
         {"text": text})
 
 
+def test_null_parameter_value(session, http):
+    session.url = inline("<input>")
+    element = session.find.css("input", all=False)
+
+    path = "/session/{session_id}/element/{element_id}/value".format(
+        session_id=session.session_id, element_id=element.id)
+    with http.post(path, None) as response:
+        assert_error(Response.from_http(response), "invalid argument")
+
+
 def test_null_response_value(session):
     session.url = inline("<input>")
     element = session.find.css("input", all=False)
index 2b6e615..7c454ea 100644 (file)
@@ -1,5 +1,7 @@
 import pytest
 
+from webdriver.transport import Response
+
 from tests.support.asserts import assert_error, assert_success
 
 
@@ -13,6 +15,12 @@ def execute_async_script(session, script, args=None):
         body)
 
 
+def test_null_parameter_value(session, http):
+    path = "/session/{session_id}/execute/async".format(**vars(session))
+    with http.post(path, None) as response:
+        assert_error(Response.from_http(response), "invalid argument")
+
+
 def test_no_browsing_context(session, closed_window):
     response = execute_async_script(session, "argument[0](1);")
     assert_error(response, "no such window")
index 632d4ed..2af16c1 100644 (file)
@@ -1,5 +1,7 @@
 import pytest
 
+from webdriver.transport import Response
+
 from tests.support.asserts import assert_error, assert_success
 
 
@@ -14,11 +16,22 @@ def execute_script(session, script, args=None):
         body)
 
 
+def test_null_parameter_value(session, http):
+    path = "/session/{session_id}/execute/sync".format(**vars(session))
+    with http.post(path, None) as response:
+        assert_error(Response.from_http(response), "invalid argument")
+
+
 def test_no_browsing_context(session, closed_window):
     response = execute_script(session, "return 1;")
     assert_error(response, "no such window")
 
 
+def test_ending_comment(session):
+    response = execute_script(session, "return 1; // foo")
+    assert_success(response, 1)
+
+
 @pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
 def test_abort_by_user_prompt(session, dialog_type):
     response = execute_script(
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/execute_script/promise.py b/WebDriverTests/imported/w3c/webdriver/tests/execute_script/promise.py
new file mode 100644 (file)
index 0000000..1eab782
--- /dev/null
@@ -0,0 +1,114 @@
+import pytest
+
+from tests.support.asserts import assert_dialog_handled, assert_error, assert_success
+
+
+def execute_script(session, script, args=None):
+    if args is None:
+        args = []
+    body = {"script": script, "args": args}
+
+    return session.transport.send(
+        "POST", "/session/{session_id}/execute/sync".format(
+            session_id=session.session_id),
+        body)
+
+
+def test_promise_resolve(session):
+    response = execute_script(session, """
+        return Promise.resolve('foobar');
+        """)
+    assert_success(response, "foobar")
+
+
+def test_promise_resolve_delayed(session):
+    response = execute_script(session, """
+        return new Promise(
+            (resolve) => setTimeout(
+                () => resolve('foobar'),
+                50
+            )
+        );
+        """)
+    assert_success(response, "foobar")
+
+
+def test_promise_all_resolve(session):
+    response = execute_script(session, """
+        return Promise.all([
+            Promise.resolve(1),
+            Promise.resolve(2)
+        ]);
+        """)
+    assert_success(response, [1, 2])
+
+
+def test_await_promise_resolve(session):
+    response = execute_script(session, """
+        const res = await Promise.resolve('foobar');
+        return res;
+        """)
+    assert_success(response, "foobar")
+
+
+def test_promise_reject(session):
+    response = execute_script(session, """
+        return Promise.reject(new Error('my error'));
+        """)
+    assert_error(response, "javascript error")
+
+
+def test_promise_reject_delayed(session):
+    response = execute_script(session, """
+        return new Promise(
+            (resolve, reject) => setTimeout(
+                () => reject(new Error('my error')),
+                50
+            )
+        );
+        """)
+    assert_error(response, "javascript error")
+
+
+def test_promise_all_reject(session):
+    response = execute_script(session, """
+        return Promise.all([
+            Promise.resolve(1),
+            Promise.reject(new Error('error'))
+        ]);
+        """)
+    assert_error(response, "javascript error")
+
+
+def test_await_promise_reject(session):
+    response = execute_script(session, """
+        await Promise.reject(new Error('my error'));
+        return 'foo';
+        """)
+    assert_error(response, "javascript error")
+
+
+def test_promise_resolve_timeout(session):
+    session.timeouts.script = .1
+    response = execute_script(session, """
+        return new Promise(
+            (resolve) => setTimeout(
+                () => resolve(),
+                1000
+            )
+        );
+        """)
+    assert_error(response, "timeout error")
+
+
+def test_promise_reject_timeout(session):
+    session.timeouts.script = .1
+    response = execute_script(session, """
+        return new Promise(
+            (resolve, reject) => setTimeout(
+                () => reject(new Error('my error')),
+                1000
+            )
+        );
+        """)
+    assert_error(response, "timeout error")
\ No newline at end of file
index ad25e78..20cde54 100644 (file)
@@ -1,5 +1,7 @@
 import pytest
 
+from webdriver.transport import Response
+
 from tests.support.asserts import assert_error, assert_same_element, assert_success
 from tests.support.inline import inline
 
@@ -10,6 +12,12 @@ def find_element(session, using, value):
         {"using": using, "value": value})
 
 
+def test_null_parameter_value(session, http):
+    path = "/session/{session_id}/element".format(**vars(session))
+    with http.post(path, None) as response:
+        assert_error(Response.from_http(response), "invalid argument")
+
+
 def test_no_browsing_context(session, closed_window):
     response = find_element(session, "css selector", "foo")
     assert_error(response, "no such window")
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/find_element/user_prompts.py b/WebDriverTests/imported/w3c/webdriver/tests/find_element/user_prompts.py
new file mode 100644 (file)
index 0000000..613466b
--- /dev/null
@@ -0,0 +1,121 @@
+# META: timeout=long
+
+import pytest
+
+from tests.support.asserts import (
+    assert_error,
+    assert_same_element,
+    assert_success,
+    assert_dialog_handled,
+)
+from tests.support.inline import inline
+
+
+def find_element(session, using, value):
+    return session.transport.send(
+        "POST", "session/{session_id}/element".format(**vars(session)),
+        {"using": using, "value": value})
+
+
+@pytest.fixture
+def check_user_prompt_closed_without_exception(session, create_dialog):
+    def check_user_prompt_closed_without_exception(dialog_type, retval):
+        session.url = inline("<p>bar</p>")
+        element = session.find.css("p", all=False)
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = find_element(session, "css selector", "p")
+        value = assert_success(response)
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+        assert_same_element(session, value, element)
+
+    return check_user_prompt_closed_without_exception
+
+
+@pytest.fixture
+def check_user_prompt_closed_with_exception(session, create_dialog):
+    def check_user_prompt_closed_with_exception(dialog_type, retval):
+        session.url = inline("<p>bar</p>")
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = find_element(session, "css selector", "p")
+        assert_error(response, "unexpected alert open")
+
+        assert_dialog_handled(session, expected_text=dialog_type, expected_retval=retval)
+
+    return check_user_prompt_closed_with_exception
+
+
+@pytest.fixture
+def check_user_prompt_not_closed_but_exception(session, create_dialog):
+    def check_user_prompt_not_closed_but_exception(dialog_type):
+        session.url = inline("<p>bar</p>")
+
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = find_element(session, "css selector", "p")
+        assert_error(response, "unexpected alert open")
+
+        assert session.alert.text == dialog_type
+        session.alert.dismiss()
+
+    return check_user_prompt_not_closed_but_exception
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", True),
+    ("prompt", ""),
+])
+def test_accept_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss(check_user_prompt_closed_without_exception, dialog_type, retval):
+    check_user_prompt_closed_without_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss and notify"})
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_dismiss_and_notify(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "ignore"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_ignore(check_user_prompt_not_closed_but_exception, dialog_type):
+    check_user_prompt_not_closed_but_exception(dialog_type)
+
+
+@pytest.mark.parametrize("dialog_type, retval", [
+    ("alert", None),
+    ("confirm", False),
+    ("prompt", None),
+])
+def test_default(check_user_prompt_closed_with_exception, dialog_type, retval):
+    check_user_prompt_closed_with_exception(dialog_type, retval)
index d44fbb3..56e216b 100644 (file)
@@ -1,5 +1,7 @@
 import pytest
 
+from webdriver.transport import Response
+
 from tests.support.asserts import assert_error, assert_same_element, assert_success
 from tests.support.inline import inline
 
@@ -12,6 +14,16 @@ def find_element(session, element_id, using, value):
         {"using": using, "value": value})
 
 
+def test_null_parameter_value(session, http):
+    session.url = inline("<div><a href=# id=linkText>full link text</a></div>")
+    element = session.find.css("div", all=False)
+
+    path = "/session/{session_id}/element/{element_id}/element".format(
+        session_id=session.session_id, element_id=element.id)
+    with http.post(path, None) as response:
+        assert_error(Response.from_http(response), "invalid argument")
+
+
 def test_no_browsing_context(session, closed_window):
     response = find_element(session, "notReal", "css selector", "foo")
     assert_error(response, "no such window")
@@ -80,7 +92,7 @@ def test_find_element_partial_link_text(session, document, value):
     assert_success(response)
 
 
-@pytest.mark.parametrize("using,value",[("css selector", "#wontExist")])
+@pytest.mark.parametrize("using,value", [("css selector", "#wontExist")])
 def test_no_element(session, using, value):
     # Step 8 - 9
     session.url = inline("<div></div>")
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/find_element_from_element/user_prompts.py b/WebDriverTests/imported/w3c/webdriver/tests/find_element_from_element/user_prompts.py
new file mode 100644 (file)
index 0000000..3fdaf4e
--- /dev/null
@@ -0,0