diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /testing/marionette/harness/marionette_harness/tests | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/marionette/harness/marionette_harness/tests')
104 files changed, 10967 insertions, 0 deletions
diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py new file mode 100644 index 000000000..6dc0a89a1 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py @@ -0,0 +1,100 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import pytest + +from mock import Mock, MagicMock + +from marionette_driver.marionette import Marionette + +from marionette_harness.runner.httpd import FixtureServer + + +@pytest.fixture(scope='module') +def logger(): + """ + Fake logger to help with mocking out other runner-related classes. + """ + import mozlog + return Mock(spec=mozlog.structuredlog.StructuredLogger) + + +@pytest.fixture +def mach_parsed_kwargs(logger): + """ + Parsed and verified dictionary used during simplest + call to mach marionette-test + """ + return { + 'adb_path': None, + 'addons': None, + 'address': None, + 'app': None, + 'app_args': [], + 'avd': None, + 'avd_home': None, + 'binary': u'/path/to/firefox', + 'browsermob_port' : None, + 'browsermob_script' : None, + 'device_serial': None, + 'e10s': True, + 'emulator': False, + 'emulator_bin': None, + 'gecko_log': None, + 'jsdebugger': False, + 'log_errorsummary': None, + 'log_html': None, + 'log_mach': None, + 'log_mach_buffer': None, + 'log_mach_level': None, + 'log_mach_verbose': None, + 'log_raw': None, + 'log_raw_level': None, + 'log_tbpl': None, + 'log_tbpl_buffer': None, + 'log_tbpl_compact': None, + 'log_tbpl_level': None, + 'log_unittest': None, + 'log_xunit': None, + 'logger_name': 'Marionette-based Tests', + 'prefs': {}, + 'prefs_args': None, + 'prefs_files': None, + 'profile': None, + 'pydebugger': None, + 'repeat': 0, + 'server_root': None, + 'shuffle': False, + 'shuffle_seed': 2276870381009474531, + 'socket_timeout': 60.0, + 'startup_timeout': 60, + 'symbols_path': None, + 'test_tags': None, + 'tests': [u'/path/to/unit-tests.ini'], + 'testvars': None, + 'this_chunk': None, + 'timeout': None, + 'total_chunks': None, + 'verbose': None, + 'workspace': None, + 'logger': logger, + } + + +@pytest.fixture +def mock_httpd(request): + """ Mock httpd instance """ + httpd = MagicMock(spec=FixtureServer) + return httpd + + +@pytest.fixture +def mock_marionette(request): + """ Mock marionette instance """ + marionette_class = MagicMock(spec=Marionette) + if 'has_crashed' in request.funcargnames: + marionette_class.check_for_crash.return_value = request.getfuncargvalue( + 'has_crashed' + ) + return marionette_class diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_httpd.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_httpd.py new file mode 100644 index 000000000..bd86b2fff --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_httpd.py @@ -0,0 +1,90 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import os +import types +import urllib2 + +import pytest + +from wptserve.handlers import json_handler + +from marionette_harness.runner import httpd + +here = os.path.abspath(os.path.dirname(__file__)) +parent = os.path.dirname(here) +default_doc_root = os.path.join(os.path.dirname(parent), "www") + + +@pytest.yield_fixture +def server(): + server = httpd.FixtureServer(default_doc_root) + yield server + server.stop() + + +def test_ctor(): + with pytest.raises(ValueError): + httpd.FixtureServer("foo") + httpd.FixtureServer(default_doc_root) + + +def test_start_stop(server): + server.start() + server.stop() + + +def test_get_url(server): + server.start() + url = server.get_url("/") + assert isinstance(url, types.StringTypes) + assert "http://" in url + + server.stop() + with pytest.raises(httpd.NotAliveError): + server.get_url("/") + + +def test_doc_root(server): + server.start() + assert isinstance(server.doc_root, types.StringTypes) + server.stop() + assert isinstance(server.doc_root, types.StringTypes) + + +def test_router(server): + assert server.router is not None + + +def test_routes(server): + assert server.routes is not None + + +def test_is_alive(server): + assert server.is_alive == False + server.start() + assert server.is_alive == True + + +def test_handler(server): + counter = 0 + + @json_handler + def handler(request, response): + return {"count": counter} + + route = ("GET", "/httpd/test_handler", handler) + server.router.register(*route) + server.start() + + url = server.get_url("/httpd/test_handler") + body = urllib2.urlopen(url).read() + res = json.loads(body) + assert res["count"] == counter + + +if __name__ == "__main__": + import sys + sys.exit(pytest.main(["--verbose", __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py new file mode 100644 index 000000000..1a0687028 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py @@ -0,0 +1,32 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +import pytest + +from marionette_harness.runtests import MarionetteArguments + + +@pytest.mark.parametrize("socket_timeout", ['A', '10', '1B-', '1C2', '44.35']) +def test_parse_arg_socket_timeout(socket_timeout): + argv = ['marionette', '--socket-timeout', socket_timeout] + parser = MarionetteArguments() + + def _is_float_convertible(value): + try: + float(value) + return True + except: + return False + + if not _is_float_convertible(socket_timeout): + with pytest.raises(SystemExit) as ex: + parser.parse_args(args=argv) + assert ex.value.code == 2 + else: + args = parser.parse_args(args=argv) + assert hasattr(args, 'socket_timeout') and args.socket_timeout == float(socket_timeout) + + +if __name__ == '__main__': + import sys + sys.exit(pytest.main(['--verbose', __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_harness.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_harness.py new file mode 100644 index 000000000..dfbbfb788 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_harness.py @@ -0,0 +1,108 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import pytest + +from mock import Mock, patch, sentinel + +import marionette_harness.marionette_test as marionette_test + +from marionette_harness.runtests import MarionetteTestRunner, MarionetteHarness, cli + + +@pytest.fixture +def harness_class(request): + """ + Mock based on MarionetteHarness whose run method just returns a number of + failures according to the supplied test parameter + """ + if 'num_fails_crashed' in request.funcargnames: + num_fails_crashed = request.getfuncargvalue('num_fails_crashed') + else: + num_fails_crashed = (0, 0) + harness_cls = Mock(spec=MarionetteHarness) + harness = harness_cls.return_value + if num_fails_crashed is None: + harness.run.side_effect = Exception + else: + harness.run.return_value = sum(num_fails_crashed) + return harness_cls + + +@pytest.fixture +def runner_class(request): + """ + Mock based on MarionetteTestRunner, wherein the runner.failed, + runner.crashed attributes are provided by a test parameter + """ + if 'num_fails_crashed' in request.funcargnames: + failures, crashed = request.getfuncargvalue('num_fails_crashed') + else: + failures = 0 + crashed = 0 + mock_runner_class = Mock(spec=MarionetteTestRunner) + runner = mock_runner_class.return_value + runner.failed = failures + runner.crashed = crashed + return mock_runner_class + + +@pytest.mark.parametrize( + "num_fails_crashed,exit_code", + [((0, 0), 0), ((1, 0), 10), ((0, 1), 10), (None, 1)], +) +def test_cli_exit_code(num_fails_crashed, exit_code, harness_class): + with pytest.raises(SystemExit) as err: + cli(harness_class=harness_class) + assert err.value.code == exit_code + + +@pytest.mark.parametrize("num_fails_crashed", [(0, 0), (1, 0), (1, 1)]) +def test_call_harness_with_parsed_args_yields_num_failures(mach_parsed_kwargs, + runner_class, + num_fails_crashed): + with patch( + 'marionette_harness.runtests.MarionetteHarness.parse_args' + ) as parse_args: + failed_or_crashed = MarionetteHarness(runner_class, + args=mach_parsed_kwargs).run() + parse_args.assert_not_called() + assert failed_or_crashed == sum(num_fails_crashed) + + +def test_call_harness_with_no_args_yields_num_failures(runner_class): + with patch( + 'marionette_harness.runtests.MarionetteHarness.parse_args', + return_value={'tests': []} + ) as parse_args: + failed_or_crashed = MarionetteHarness(runner_class).run() + assert parse_args.call_count == 1 + assert failed_or_crashed == 0 + + +def test_args_passed_to_runner_class(mach_parsed_kwargs, runner_class): + arg_list = mach_parsed_kwargs.keys() + arg_list.remove('tests') + mach_parsed_kwargs.update([(a, getattr(sentinel, a)) for a in arg_list]) + harness = MarionetteHarness(runner_class, args=mach_parsed_kwargs) + harness.process_args = Mock() + harness.run() + for arg in arg_list: + assert harness._runner_class.call_args[1][arg] is getattr(sentinel, arg) + + +def test_harness_sets_up_default_test_handlers(mach_parsed_kwargs): + """ + If the necessary TestCase is not in test_handlers, + tests are omitted silently + """ + harness = MarionetteHarness(args=mach_parsed_kwargs) + mach_parsed_kwargs.pop('tests') + runner = harness._runner_class(**mach_parsed_kwargs) + assert marionette_test.MarionetteTestCase in runner.test_handlers + + +if __name__ == '__main__': + import sys + sys.exit(pytest.main(['--verbose', __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py new file mode 100644 index 000000000..79bdc824e --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py @@ -0,0 +1,442 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import manifestparser +import pytest + +from mock import Mock, patch, mock_open, sentinel, DEFAULT + +from marionette_harness.runtests import MarionetteTestRunner + + +@pytest.fixture +def runner(mach_parsed_kwargs): + """ + MarionetteTestRunner instance initialized with default options. + """ + return MarionetteTestRunner(**mach_parsed_kwargs) + + +@pytest.fixture +def mock_runner(runner, mock_marionette, monkeypatch): + """ + MarionetteTestRunner instance with mocked-out + self.marionette and other properties, + to enable testing runner.run_tests(). + """ + runner.driverclass = mock_marionette + for attr in ['run_test_set', '_capabilities']: + setattr(runner, attr, Mock()) + runner._appName = 'fake_app' + # simulate that browser runs with e10s by default + runner._appinfo = {'browserTabsRemoteAutostart': True} + monkeypatch.setattr('marionette_harness.runner.base.mozversion', Mock()) + return runner + + +@pytest.fixture +def build_kwargs_using(mach_parsed_kwargs): + '''Helper function for test_build_kwargs_* functions''' + def kwarg_builder(new_items, return_socket=False): + mach_parsed_kwargs.update(new_items) + runner = MarionetteTestRunner(**mach_parsed_kwargs) + with patch('marionette_harness.runner.base.socket') as socket: + built_kwargs = runner._build_kwargs() + if return_socket: + return built_kwargs, socket + return built_kwargs + return kwarg_builder + + +@pytest.fixture +def expected_driver_args(runner): + '''Helper fixture for tests of _build_kwargs + with binary/emulator. + Provides a dictionary of certain arguments + related to binary/emulator settings + which we expect to be passed to the + driverclass constructor. Expected values can + be updated in tests as needed. + Provides convenience methods for comparing the + expected arguments to the argument dictionary + created by _build_kwargs. ''' + + class ExpectedDict(dict): + def assert_matches(self, actual): + for (k, v) in self.items(): + assert actual[k] == v + + def assert_keys_not_in(self, actual): + for k in self.keys(): + assert k not in actual + + expected = ExpectedDict(host=None, port=None, bin=None) + for attr in ['app', 'app_args', 'profile', 'addons', 'gecko_log']: + expected[attr] = getattr(runner, attr) + return expected + + +class ManifestFixture: + def __init__(self, name='mock_manifest', + tests=[{'path': u'test_something.py', 'expected': 'pass'}]): + self.filepath = "/path/to/fake/manifest.ini" + self.n_disabled = len([t for t in tests if 'disabled' in t]) + self.n_enabled = len(tests) - self.n_disabled + mock_manifest = Mock(spec=manifestparser.TestManifest, + active_tests=Mock(return_value=tests)) + self.manifest_class = Mock(return_value=mock_manifest) + self.__repr__ = lambda: "<ManifestFixture {}>".format(name) + +@pytest.fixture +def manifest(): + return ManifestFixture() + +@pytest.fixture(params=['enabled', 'disabled', 'enabled_disabled', 'empty']) +def manifest_with_tests(request): + ''' + Fixture for the contents of mock_manifest, where a manifest + can include enabled tests, disabled tests, both, or neither (empty) + ''' + included = [] + if 'enabled' in request.param: + included += [(u'test_expected_pass.py', 'pass'), + (u'test_expected_fail.py', 'fail')] + if 'disabled' in request.param: + included += [(u'test_pass_disabled.py', 'pass', 'skip-if: true'), + (u'test_fail_disabled.py', 'fail', 'skip-if: true')] + keys = ('path', 'expected', 'disabled') + active_tests = [dict(zip(keys, values)) for values in included] + + return ManifestFixture(request.param, active_tests) + + +def test_args_passed_to_driverclass(mock_runner): + built_kwargs = {'arg1': 'value1', 'arg2': 'value2'} + mock_runner._build_kwargs = Mock(return_value=built_kwargs) + with pytest.raises(IOError): + mock_runner.run_tests(['fake_tests.ini']) + assert mock_runner.driverclass.call_args[1] == built_kwargs + + +def test_build_kwargs_basic_args(build_kwargs_using): + '''Test the functionality of runner._build_kwargs: + make sure that basic arguments (those which should + always be included, irrespective of the runner's settings) + get passed to the call to runner.driverclass''' + + basic_args = ['socket_timeout', 'prefs', + 'startup_timeout', 'verbose', 'symbols_path'] + args_dict = {a: getattr(sentinel, a) for a in basic_args} + # Mock an update method to work with calls to MarionetteTestRunner() + args_dict['prefs'].update = Mock(return_value={}) + built_kwargs = build_kwargs_using([(a, getattr(sentinel, a)) for a in basic_args]) + for arg in basic_args: + assert built_kwargs[arg] is getattr(sentinel, arg) + + +@pytest.mark.parametrize('workspace', ['path/to/workspace', None]) +def test_build_kwargs_with_workspace(build_kwargs_using, workspace): + built_kwargs = build_kwargs_using({'workspace': workspace}) + if workspace: + assert built_kwargs['workspace'] == workspace + else: + assert 'workspace' not in built_kwargs + + +@pytest.mark.parametrize('address', ['host:123', None]) +def test_build_kwargs_with_address(build_kwargs_using, address): + built_kwargs, socket = build_kwargs_using( + {'address': address, 'binary': None, 'emulator': None}, + return_socket=True + ) + assert 'connect_to_running_emulator' not in built_kwargs + if address is not None: + host, port = address.split(":") + assert built_kwargs['host'] == host and built_kwargs['port'] == int(port) + socket.socket().connect.assert_called_with((host, int(port))) + assert socket.socket().close.called + else: + assert not socket.socket.called + + +@pytest.mark.parametrize('address', ['host:123', None]) +@pytest.mark.parametrize('binary', ['path/to/bin', None]) +def test_build_kwargs_with_binary_or_address(expected_driver_args, build_kwargs_using, + binary, address): + built_kwargs = build_kwargs_using({'binary': binary, 'address': address, 'emulator': None}) + if binary: + expected_driver_args['bin'] = binary + if address: + host, port = address.split(":") + expected_driver_args.update({'host': host, 'port': int(port)}) + else: + expected_driver_args.update({'host': 'localhost', 'port': 2828}) + expected_driver_args.assert_matches(built_kwargs) + elif address is None: + expected_driver_args.assert_keys_not_in(built_kwargs) + + +@pytest.mark.parametrize('address', ['host:123', None]) +@pytest.mark.parametrize('emulator', [True, False, None]) +def test_build_kwargs_with_emulator_or_address(expected_driver_args, build_kwargs_using, + emulator, address): + emulator_props = [(a, getattr(sentinel, a)) for a in ['avd_home', 'adb_path', 'emulator_bin']] + built_kwargs = build_kwargs_using( + [('emulator', emulator), ('address', address), ('binary', None)] + emulator_props + ) + if emulator: + expected_driver_args.update(emulator_props) + expected_driver_args['emulator_binary'] = expected_driver_args.pop('emulator_bin') + expected_driver_args['bin'] = True + if address: + expected_driver_args['connect_to_running_emulator'] = True + host, port = address.split(":") + expected_driver_args.update({'host': host, 'port': int(port)}) + else: + expected_driver_args.update({'host': 'localhost', 'port': 2828}) + assert 'connect_to_running_emulator' not in built_kwargs + expected_driver_args.assert_matches(built_kwargs) + elif not address: + expected_driver_args.assert_keys_not_in(built_kwargs) + + +def test_parsing_testvars(mach_parsed_kwargs): + mach_parsed_kwargs.pop('tests') + testvars_json_loads = [ + {"wifi": {"ssid": "blah", "keyManagement": "WPA-PSK", "psk": "foo"}}, + {"wifi": {"PEAP": "bar"}, "device": {"stuff": "buzz"}} + ] + expected_dict = { + "wifi": { + "ssid": "blah", + "keyManagement": "WPA-PSK", + "psk": "foo", + "PEAP": "bar" + }, + "device": {"stuff": "buzz"} + } + with patch( + 'marionette_harness.runtests.MarionetteTestRunner._load_testvars', + return_value=testvars_json_loads + ) as load: + runner = MarionetteTestRunner(**mach_parsed_kwargs) + assert runner.testvars == expected_dict + assert load.call_count == 1 + + +def test_load_testvars_throws_expected_errors(mach_parsed_kwargs): + mach_parsed_kwargs['testvars'] = ['some_bad_path.json'] + runner = MarionetteTestRunner(**mach_parsed_kwargs) + with pytest.raises(IOError) as io_exc: + runner._load_testvars() + assert 'does not exist' in io_exc.value.message + with patch('os.path.exists', return_value=True): + with patch('__builtin__.open', mock_open(read_data='[not {valid JSON]')): + with pytest.raises(Exception) as json_exc: + runner._load_testvars() + assert 'not properly formatted' in json_exc.value.message + + +def _check_crash_counts(has_crashed, runner, mock_marionette): + if has_crashed: + assert mock_marionette.check_for_crash.call_count == 1 + assert runner.crashed == 1 + else: + assert runner.crashed == 0 + + +@pytest.mark.parametrize("has_crashed", [True, False]) +def test_increment_crash_count_in_run_test_set(runner, has_crashed, + mock_marionette): + fake_tests = [{'filepath': i, + 'expected': 'pass'} for i in 'abc'] + + with patch.multiple(runner, run_test=DEFAULT, marionette=mock_marionette): + runner.run_test_set(fake_tests) + if not has_crashed: + assert runner.marionette.check_for_crash.call_count == len(fake_tests) + _check_crash_counts(has_crashed, runner, runner.marionette) + + +@pytest.mark.parametrize("has_crashed", [True, False]) +def test_record_crash(runner, has_crashed, mock_marionette): + with patch.object(runner, 'marionette', mock_marionette): + assert runner.record_crash() == has_crashed + _check_crash_counts(has_crashed, runner, runner.marionette) + + +def test_add_test_module(runner): + tests = ['test_something.py', 'testSomething.js', 'bad_test.py'] + assert len(runner.tests) == 0 + for test in tests: + with patch('os.path.abspath', return_value=test) as abspath: + runner.add_test(test) + assert abspath.called + expected = {'filepath': test, 'expected': 'pass'} + assert expected in runner.tests + # add_test doesn't validate module names; 'bad_test.py' gets through + assert len(runner.tests) == 3 + + +def test_add_test_directory(runner): + test_dir = 'path/to/tests' + dir_contents = [ + (test_dir, ('subdir',), ('test_a.py', 'test_a.js', 'bad_test_a.py', 'bad_test_a.js')), + (test_dir + '/subdir', (), ('test_b.py', 'test_b.js', 'bad_test_b.py', 'bad_test_b.js')), + ] + tests = list(dir_contents[0][2] + dir_contents[1][2]) + assert len(runner.tests) == 0 + # Need to use side effect to make isdir return True for test_dir and False for tests + with patch('os.path.isdir', side_effect=[True] + [False for t in tests]) as isdir: + with patch('os.walk', return_value=dir_contents) as walk: + runner.add_test(test_dir) + assert isdir.called and walk.called + for test in runner.tests: + assert test_dir in test['filepath'] + assert len(runner.tests) == 4 + + +@pytest.mark.parametrize("test_files_exist", [True, False]) +def test_add_test_manifest(mock_runner, manifest_with_tests, monkeypatch, test_files_exist): + monkeypatch.setattr('marionette_harness.runner.base.TestManifest', + manifest_with_tests.manifest_class) + mock_runner.marionette = mock_runner.driverclass() + with patch('marionette_harness.runner.base.os.path.exists', return_value=test_files_exist): + if test_files_exist or manifest_with_tests.n_enabled == 0: + mock_runner.add_test(manifest_with_tests.filepath) + assert len(mock_runner.tests) == manifest_with_tests.n_enabled + assert len(mock_runner.manifest_skipped_tests) == manifest_with_tests.n_disabled + for test in mock_runner.tests: + assert test['filepath'].endswith(test['expected'] + '.py') + else: + pytest.raises(IOError, "mock_runner.add_test(manifest_with_tests.filepath)") + assert manifest_with_tests.manifest_class().read.called + assert manifest_with_tests.manifest_class().active_tests.called + + +def get_kwargs_passed_to_manifest(mock_runner, manifest, monkeypatch, **kwargs): + '''Helper function for test_manifest_* tests. + Returns the kwargs passed to the call to manifest.active_tests.''' + monkeypatch.setattr('marionette_harness.runner.base.TestManifest', manifest.manifest_class) + monkeypatch.setattr('marionette_harness.runner.base.mozinfo.info', + {'mozinfo_key': 'mozinfo_val'}) + for attr in kwargs: + setattr(mock_runner, attr, kwargs[attr]) + mock_runner.marionette = mock_runner.driverclass() + with patch('marionette_harness.runner.base.os.path.exists', return_value=True): + mock_runner.add_test(manifest.filepath) + call_args, call_kwargs = manifest.manifest_class().active_tests.call_args + return call_kwargs + + +def test_manifest_basic_args(mock_runner, manifest, monkeypatch): + kwargs = get_kwargs_passed_to_manifest(mock_runner, manifest, monkeypatch) + assert kwargs['exists'] is False + assert kwargs['disabled'] is True + assert kwargs['appname'] == 'fake_app' + assert 'mozinfo_key' in kwargs and kwargs['mozinfo_key'] == 'mozinfo_val' + + +@pytest.mark.parametrize('e10s', (True, False)) +def test_manifest_with_e10s(mock_runner, manifest, monkeypatch, e10s): + kwargs = get_kwargs_passed_to_manifest(mock_runner, manifest, monkeypatch, e10s=e10s) + assert kwargs['e10s'] == e10s + + +@pytest.mark.parametrize('test_tags', (None, ['tag', 'tag2'])) +def test_manifest_with_test_tags(mock_runner, manifest, monkeypatch, test_tags): + kwargs = get_kwargs_passed_to_manifest(mock_runner, manifest, monkeypatch, test_tags=test_tags) + if test_tags is None: + assert kwargs['filters'] == [] + else: + assert len(kwargs['filters']) == 1 and kwargs['filters'][0].tags == test_tags + + +def test_cleanup_with_manifest(mock_runner, manifest_with_tests, monkeypatch): + monkeypatch.setattr('marionette_harness.runner.base.TestManifest', + manifest_with_tests.manifest_class) + if manifest_with_tests.n_enabled > 0: + context = patch('marionette_harness.runner.base.os.path.exists', return_value=True) + else: + context = pytest.raises(Exception) + with context: + mock_runner.run_tests([manifest_with_tests.filepath]) + assert mock_runner.marionette is None + assert mock_runner.fixture_servers == {} + + +def test_reset_test_stats(mock_runner): + def reset_successful(runner): + stats = ['passed', 'failed', 'unexpected_successes', 'todo', 'skipped', 'failures'] + return all([((s in vars(runner)) and (not vars(runner)[s])) for s in stats]) + assert reset_successful(mock_runner) + mock_runner.passed = 1 + mock_runner.failed = 1 + mock_runner.failures.append(['TEST-UNEXPECTED-FAIL']) + assert not reset_successful(mock_runner) + mock_runner.run_tests([u'test_fake_thing.py']) + assert reset_successful(mock_runner) + + +def test_initialize_test_run(mock_runner): + tests = [u'test_fake_thing.py'] + mock_runner.reset_test_stats = Mock() + mock_runner.run_tests(tests) + assert mock_runner.reset_test_stats.called + with pytest.raises(AssertionError) as test_exc: + mock_runner.run_tests([]) + assert "len(tests)" in str(test_exc.traceback[-1].statement) + with pytest.raises(AssertionError) as hndl_exc: + mock_runner.test_handlers = [] + mock_runner.run_tests(tests) + assert "test_handlers" in str(hndl_exc.traceback[-1].statement) + assert mock_runner.reset_test_stats.call_count == 1 + + +def test_add_tests(mock_runner): + assert len(mock_runner.tests) == 0 + fake_tests = ["test_" + i + ".py" for i in "abc"] + mock_runner.run_tests(fake_tests) + assert len(mock_runner.tests) == 3 + for (test_name, added_test) in zip(fake_tests, mock_runner.tests): + assert added_test['filepath'].endswith(test_name) + + +def test_catch_invalid_test_names(runner): + good_tests = [u'test_ok.py', u'test_is_ok.py', u'test_is_ok.js', u'testIsOk.js'] + bad_tests = [u'bad_test.py', u'testbad.py', u'_test_bad.py', u'testBad.notjs', + u'test_bad.notpy', u'test_bad', u'testbad.js', u'badtest.js', + u'test.py', u'test_.py', u'test.js', u'test_.js'] + with pytest.raises(Exception) as exc: + runner._add_tests(good_tests + bad_tests) + msg = exc.value.message + assert "Test file names must be of the form" in msg + for bad_name in bad_tests: + assert bad_name in msg + for good_name in good_tests: + assert good_name not in msg + +@pytest.mark.parametrize('e10s', (True, False)) +def test_e10s_option_sets_prefs(mach_parsed_kwargs, e10s): + mach_parsed_kwargs['e10s'] = e10s + runner = MarionetteTestRunner(**mach_parsed_kwargs) + e10s_prefs = { + 'browser.tabs.remote.autostart': True, + 'browser.tabs.remote.force-enable': True, + 'extensions.e10sBlocksEnabling': False + } + for k,v in e10s_prefs.iteritems(): + if k == 'extensions.e10sBlocksEnabling' and not e10s: + continue + assert runner.prefs.get(k, False) == (v and e10s) + +def test_e10s_option_clash_raises(mock_runner): + mock_runner.e10s = False + with pytest.raises(AssertionError) as e: + mock_runner.run_tests([u'test_fake_thing.py']) + assert "configuration (self.e10s) does not match browser appinfo" in e.value.message + +if __name__ == '__main__': + import sys + sys.exit(pytest.main(['--verbose', __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_test_result.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_test_result.py new file mode 100644 index 000000000..a69b072cd --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_test_result.py @@ -0,0 +1,54 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import pytest + +from marionette_harness import MarionetteTestResult + + +@pytest.fixture +def empty_marionette_testcase(): + """ Testable MarionetteTestCase class """ + from marionette_harness import MarionetteTestCase + + class EmptyTestCase(MarionetteTestCase): + def test_nothing(self): + pass + + return EmptyTestCase + + +@pytest.fixture +def empty_marionette_test(mock_marionette, empty_marionette_testcase): + return empty_marionette_testcase(lambda: mock_marionette, lambda: mock_httpd, + 'test_nothing') + + +@pytest.mark.parametrize("has_crashed", [True, False]) +def test_crash_is_recorded_as_error(empty_marionette_test, + logger, + has_crashed): + """ Number of errors is incremented by stopTest iff has_crashed is true """ + # collect results from the empty test + result = MarionetteTestResult( + marionette=empty_marionette_test._marionette_weakref(), + logger=logger, verbosity=None, + stream=None, descriptions=None, + ) + result.startTest(empty_marionette_test) + assert len(result.errors) == 0 + assert len(result.failures) == 0 + assert result.testsRun == 1 + assert result.shouldStop is False + result.stopTest(empty_marionette_test) + assert result.shouldStop == has_crashed + if has_crashed: + assert len(result.errors) == 1 + else: + assert len(result.errors) == 0 + + +if __name__ == '__main__': + import sys + sys.exit(pytest.main(['--verbose', __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_serve.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_serve.py new file mode 100644 index 000000000..73684c0d6 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_serve.py @@ -0,0 +1,67 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import types + +import pytest + +from marionette_harness.runner import serve +from marionette_harness.runner.serve import iter_proc, iter_url + + +def teardown_function(func): + for server in [server for server in iter_proc(serve.servers) if server.is_alive]: + server.stop() + server.kill() + + +def test_registered_servers(): + # [(name, factory), ...] + assert serve.registered_servers[0][0] == "http" + assert serve.registered_servers[1][0] == "https" + + +def test_globals(): + assert serve.default_doc_root is not None + assert serve.registered_servers is not None + assert serve.servers is not None + + +def test_start(): + serve.start() + assert len(serve.servers) == 2 + assert "http" in serve.servers + assert "https" in serve.servers + for url in iter_url(serve.servers): + assert isinstance(url, types.StringTypes) + + +def test_start_with_custom_root(tmpdir_factory): + tdir = tmpdir_factory.mktemp("foo") + serve.start(str(tdir)) + for server in iter_proc(serve.servers): + assert server.doc_root == tdir + + +def test_iter_proc(): + serve.start() + for server in iter_proc(serve.servers): + server.stop() + + +def test_iter_url(): + serve.start() + for url in iter_url(serve.servers): + assert isinstance(url, types.StringTypes) + + +def test_where_is(): + serve.start() + assert serve.where_is("/") == serve.servers["http"][1].get_url("/") + assert serve.where_is("/", on="https") == serve.servers["https"][1].get_url("/") + + +if __name__ == "__main__": + import sys + sys.exit(pytest.main(["-s", "--verbose", __file__])) diff --git a/testing/marionette/harness/marionette_harness/tests/unit-tests.ini b/testing/marionette/harness/marionette_harness/tests/unit-tests.ini new file mode 100644 index 000000000..8d806afea --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit-tests.ini @@ -0,0 +1,11 @@ +; marionette unit tests +[include:unit/unit-tests.ini] + +; layout tests +[include:../../../../../layout/base/tests/marionette/manifest.ini] + +; microformats tests +[include:../../../../../toolkit/components/microformats/manifest.ini] + +; migration tests +[include:../../../../../browser/components/migration/tests/marionette/manifest.ini] diff --git a/testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js b/testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js new file mode 100644 index 000000000..fa6a1fa7c --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js @@ -0,0 +1 @@ +var testAnotherFunc = function() { return "i'm yet another test function!";}; diff --git a/testing/marionette/harness/marionette_harness/tests/unit/importscript.js b/testing/marionette/harness/marionette_harness/tests/unit/importscript.js new file mode 100644 index 000000000..5a5dd8a18 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/importscript.js @@ -0,0 +1 @@ +var testFunc = function() { return "i'm a test function!";}; diff --git a/testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpi b/testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpi Binary files differnew file mode 100644 index 000000000..c8877b55d --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/mn-restartless-unsigned.xpi diff --git a/testing/marionette/harness/marionette_harness/tests/unit/single_finger_functions.py b/testing/marionette/harness/marionette_harness/tests/unit/single_finger_functions.py new file mode 100644 index 000000000..c2daf9d54 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/single_finger_functions.py @@ -0,0 +1,131 @@ +from marionette_driver.marionette import Actions +from marionette_driver.errors import TimeoutException +from marionette_driver.by import By + + +def wait_for_condition_else_raise(marionette, wait_for_condition, expected, script): + try: + wait_for_condition(lambda m: expected in m.execute_script(script)) + except TimeoutException as e: + raise TimeoutException("{0} got {1} instead of {2}".format( + e.message, marionette.execute_script(script), expected)) + +def press_release(marionette, times, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + action = Actions(marionette) + button = marionette.find_element(By.ID, "button1") + action.press(button).release() + # Insert wait between each press and release chain. + for _ in range(times-1): + action.wait(0.1) + action.press(button).release() + action.perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def move_element(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + ele = marionette.find_element(By.ID, "button1") + drop = marionette.find_element(By.ID, "button2") + action = Actions(marionette) + action.press(ele).move(drop).release() + action.perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;") + wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button2').innerHTML;") + +def move_element_offset(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + ele = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.press(ele).move_by_offset(0,150).move_by_offset(0, 150).release() + action.perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;") + wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button2').innerHTML;") + +def chain(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + marionette.timeout.implicit = 15 + action = Actions(marionette) + button1 = marionette.find_element(By.ID, "button1") + action.press(button1).perform() + button2 = marionette.find_element(By.ID, "delayed") + wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;") + action.move(button2).release().perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('delayed').innerHTML;") + +def chain_flick(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.flick(button, 0, 0, 0, 200).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected1,"return document.getElementById('button1').innerHTML;") + wait_for_condition_else_raise(marionette, wait_for_condition, expected2,"return document.getElementById('buttonFlick').innerHTML;") + + +def wait(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + action = Actions(marionette) + button = marionette.find_element(By.ID, "button1") + action.press(button).wait().release().perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def wait_with_value(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.press(button).wait(0.01).release() + action.perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def context_menu(marionette, wait_for_condition, expected1, expected2): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.press(button).wait(5).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;") + action.release().perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button1').innerHTML;") + +def long_press_action(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.long_press(button, 5).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def long_press_on_xy_action(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + html = marionette.find_element(By.TAG_NAME, "html") + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + + # Press the center of the button with respect to html. + x = button.rect['x'] + button.rect['width'] / 2.0 + y = button.rect['y'] + button.rect['height'] / 2.0 + action.long_press(html, 5, x, y).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def single_tap(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.tap(button).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") + +def double_tap(marionette, wait_for_condition, expected): + testAction = marionette.absolute_url("testAction.html") + marionette.navigate(testAction) + button = marionette.find_element(By.ID, "button1") + action = Actions(marionette) + action.double_tap(button).perform() + wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py new file mode 100644 index 000000000..e9992f8a5 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py @@ -0,0 +1,134 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import By, Wait +from marionette_driver.keys import Keys + +from marionette_harness import MarionetteTestCase, skip, skip_if_mobile, WindowManagerMixin + + +class TestAboutPages(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestAboutPages, self).setUp() + + if self.marionette.session_capabilities['platformName'] == 'darwin': + self.mod_key = Keys.META + else: + self.mod_key = Keys.CONTROL + + self.remote_uri = self.marionette.absolute_url("windowHandles.html") + + def tearDown(self): + self.close_all_tabs() + + super(TestAboutPages, self).tearDown() + + def open_tab_with_link(self): + with self.marionette.using_context("content"): + self.marionette.navigate(self.remote_uri) + + link = self.marionette.find_element(By.ID, "new-tab") + link.click() + + @skip_if_mobile("Bug 1333209 - Process killed because of connection loss") + def test_back_forward(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + self.marionette.switch_to_window(new_tab) + + self.marionette.navigate("about:blank") + self.marionette.navigate(self.remote_uri) + self.marionette.navigate("about:support") + + self.marionette.go_back() + self.assertEqual(self.marionette.get_url(), self.remote_uri) + + self.marionette.go_forward() + self.assertEqual(self.marionette.get_url(), "about:support") + + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + @skip_if_mobile("Bug 1333209 - Process killed because of connection loss") + def test_navigate_non_remote_about_pages(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + self.marionette.switch_to_window(new_tab) + + self.marionette.navigate("about:blank") + self.assertEqual(self.marionette.get_url(), "about:blank") + self.marionette.navigate("about:support") + self.assertEqual(self.marionette.get_url(), "about:support") + + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + @skip_if_mobile("On Android no shortcuts are available") + def test_navigate_shortcut_key(self): + def open_with_shortcut(): + self.marionette.navigate(self.remote_uri) + with self.marionette.using_context("chrome"): + main_win = self.marionette.find_element(By.ID, "main-window") + main_win.send_keys(self.mod_key, Keys.SHIFT, 'a') + + new_tab = self.open_tab(trigger=open_with_shortcut) + self.marionette.switch_to_window(new_tab) + + Wait(self.marionette).until(lambda mn: mn.get_url() == "about:addons", + message="'about:addons' hasn't been loaded") + + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + @skip("Bug 1334137 - Intermittent: Process killed because of hang in getCurrentUrl()") + @skip_if_mobile("Interacting with chrome elements not available for Fennec") + def test_type_to_non_remote_tab(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + self.marionette.switch_to_window(new_tab) + + with self.marionette.using_context("chrome"): + urlbar = self.marionette.find_element(By.ID, 'urlbar') + urlbar.send_keys(self.mod_key + 'a') + urlbar.send_keys(self.mod_key + 'x') + urlbar.send_keys('about:support' + Keys.ENTER) + Wait(self.marionette).until(lambda mn: mn.get_url() == "about:support", + message="'about:support' hasn't been loaded") + + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + @skip_if_mobile("Interacting with chrome elements not available for Fennec") + def test_type_to_remote_tab(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + self.marionette.switch_to_window(new_tab) + + # about:blank keeps remoteness from remote_uri + self.marionette.navigate("about:blank") + with self.marionette.using_context("chrome"): + urlbar = self.marionette.find_element(By.ID, 'urlbar') + urlbar.send_keys(self.mod_key + 'a') + urlbar.send_keys(self.mod_key + 'x') + urlbar.send_keys(self.remote_uri + Keys.ENTER) + + Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri, + message="'{}' hasn't been loaded".format(self.remote_uri)) + + @skip_if_mobile("Needs application independent method to open a new tab") + def test_hang(self): + # Bug 1311041 - Prevent changing of window handle by forcing the test + # to be run in a new tab. + new_tab = self.open_tab(trigger=self.open_tab_with_link) + + # Close the start tab + self.marionette.close() + self.marionette.switch_to_window(new_tab) + + self.marionette.navigate(self.remote_uri) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py b/testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py new file mode 100644 index 000000000..0d8d9dca5 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py @@ -0,0 +1,210 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import ( + ElementNotAccessibleException, + ElementNotInteractableException +) + +from marionette_harness import MarionetteTestCase + + + +class TestAccessibility(MarionetteTestCase): + def setUp(self): + super(TestAccessibility, self).setUp() + with self.marionette.using_context("chrome"): + self.marionette.set_pref("dom.ipc.processCount", 1) + + def tearDown(self): + with self.marionette.using_context("chrome"): + self.marionette.clear_pref("dom.ipc.processCount") + + # Elements that are accessible with and without the accessibliity API + valid_elementIDs = [ + # Button1 is an accessible button with a valid accessible name + # computed from subtree + "button1", + # Button2 is an accessible button with a valid accessible name + # computed from aria-label + "button2", + # Button13 is an accessible button that is implemented via role="button" + # and is explorable using tabindex="0" + "button13", + # button17 is an accessible button that overrides parent's + # pointer-events:none; property with its own pointer-events:all; + "button17" + ] + + # Elements that are not accessible with the accessibility API + invalid_elementIDs = [ + # Button3 does not have an accessible object + "button3", + # Button4 does not support any accessible actions + "button4", + # Button5 does not have a correct accessibility role and may not be + # manipulated via the accessibility API + "button5", + # Button6 is missing an accesible name + "button6", + # Button7 is not currently visible via the accessibility API and may + # not be manipulated by it + "button7", + # Button8 is not currently visible via the accessibility API and may + # not be manipulated by it (in hidden subtree) + "button8", + # Button14 is accessible button but is not explorable because of lack + # of tabindex that would make it focusable. + "button14" + ] + + # Elements that are either accessible to accessibility API or not accessible + # at all + falsy_elements = [ + # Element is only visible to the accessibility API and may be + # manipulated by it + "button9", + # Element is not currently visible + "button10" + ] + + displayed_elementIDs = [ + "button1", "button2", "button3", "button4", "button5", "button6", + "button9", "no_accessible_but_displayed" + ] + + displayed_but_a11y_hidden_elementIDs = ["button7", "button8"] + + disabled_elementIDs = ["button11", "no_accessible_but_disabled"] + + # Elements that are enabled but otherwise disabled or not explorable via the accessibility API + disabled_accessibility_elementIDs = ["button12", "button15", "button16"] + + # Elements that are reporting selected state + valid_option_elementIDs = ["option1", "option2"] + + def run_element_test(self, ids, testFn): + for id in ids: + element = self.marionette.find_element(By.ID, id) + testFn(element) + + def setup_accessibility(self, enable_a11y_checks=True, navigate=True): + self.marionette.delete_session() + self.marionette.start_session( + {"requiredCapabilities": {"moz:accessibilityChecks": enable_a11y_checks}}) + self.assertEqual( + self.marionette.session_capabilities["moz:accessibilityChecks"], + enable_a11y_checks) + + # Navigate to test_accessibility.html + if navigate: + test_accessibility = self.marionette.absolute_url("test_accessibility.html") + self.marionette.navigate(test_accessibility) + + def test_valid_single_tap(self): + self.setup_accessibility() + # No exception should be raised + self.run_element_test(self.valid_elementIDs, lambda button: button.tap()) + + def test_single_tap_raises_element_not_accessible(self): + self.setup_accessibility() + self.run_element_test(self.invalid_elementIDs, + lambda button: self.assertRaises(ElementNotAccessibleException, + button.tap)) + self.run_element_test(self.falsy_elements, + lambda button: self.assertRaises(ElementNotInteractableException, + button.tap)) + + def test_single_tap_raises_no_exceptions(self): + self.setup_accessibility(False, True) + # No exception should be raised + self.run_element_test(self.invalid_elementIDs, lambda button: button.tap()) + # Elements are invisible + self.run_element_test(self.falsy_elements, + lambda button: self.assertRaises(ElementNotInteractableException, + button.tap)) + + def test_valid_click(self): + self.setup_accessibility() + # No exception should be raised + self.run_element_test(self.valid_elementIDs, lambda button: button.click()) + + def test_click_raises_element_not_accessible(self): + self.setup_accessibility() + self.run_element_test(self.invalid_elementIDs, + lambda button: self.assertRaises(ElementNotAccessibleException, + button.click)) + self.run_element_test(self.falsy_elements, + lambda button: self.assertRaises(ElementNotInteractableException, + button.click)) + + def test_click_raises_no_exceptions(self): + self.setup_accessibility(False, True) + # No exception should be raised + self.run_element_test(self.invalid_elementIDs, lambda button: button.click()) + # Elements are invisible + self.run_element_test(self.falsy_elements, + lambda button: self.assertRaises(ElementNotInteractableException, + button.click)) + + def test_element_visible_but_not_visible_to_accessbility(self): + self.setup_accessibility() + # Elements are displayed but hidden from accessibility API + self.run_element_test(self.displayed_but_a11y_hidden_elementIDs, + lambda element: self.assertRaises(ElementNotAccessibleException, + element.is_displayed)) + + def test_element_is_visible_to_accessibility(self): + self.setup_accessibility() + # No exception should be raised + self.run_element_test(self.displayed_elementIDs, lambda element: element.is_displayed()) + + def test_element_is_not_enabled_to_accessbility(self): + self.setup_accessibility() + # Buttons are enabled but disabled/not-explorable via the accessibility API + self.run_element_test(self.disabled_accessibility_elementIDs, + lambda element: self.assertRaises(ElementNotAccessibleException, + element.is_enabled)) + + # Buttons are enabled but disabled/not-explorable via the accessibility API and thus are not + # clickable via the accessibility API + self.run_element_test(self.disabled_accessibility_elementIDs, + lambda element: self.assertRaises(ElementNotAccessibleException, + element.click)) + + self.setup_accessibility(False, False) + self.run_element_test(self.disabled_accessibility_elementIDs, + lambda element: element.is_enabled()) + self.run_element_test(self.disabled_accessibility_elementIDs, + lambda element: element.click()) + + def test_element_is_enabled_to_accessibility(self): + self.setup_accessibility() + # No exception should be raised + self.run_element_test(self.disabled_elementIDs, lambda element: element.is_enabled()) + + def test_send_keys_raises_no_exception(self): + self.setup_accessibility() + # Sending keys to valid input should not raise any exceptions + self.run_element_test(['input1'], lambda element: element.send_keys("a")) + + self.setup_accessibility(False, False) + # Sending keys to invalid element should not raise any exceptions when raising accessibility + # exceptions is disabled + self.run_element_test(['button5'], lambda element: element.send_keys("abc")) + + def test_send_keys_raises_element_not_accessible(self): + self.setup_accessibility() + # Sending keys to invalid element should raise an exception + self.run_element_test(['button5'], + lambda element: self.assertRaises(ElementNotAccessibleException, + element.send_keys)) + + def test_is_selected_raises_no_exception(self): + self.setup_accessibility() + # No exception should be raised for valid options + self.run_element_test(self.valid_option_elementIDs, lambda element: element.is_selected()) + # No exception should be raised for non-selectable elements + self.run_element_test(self.valid_elementIDs, lambda element: element.is_selected()) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_addons.py b/testing/marionette/harness/marionette_harness/tests/unit/test_addons.py new file mode 100644 index 000000000..25f1c05ab --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_addons.py @@ -0,0 +1,58 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os + +from marionette_driver.addons import Addons, AddonInstallException +from marionette_harness import MarionetteTestCase, skip + + +here = os.path.abspath(os.path.dirname(__file__)) + + +class TestAddons(MarionetteTestCase): + + def setUp(self): + MarionetteTestCase.setUp(self) + self.addons = Addons(self.marionette) + + @property + def all_addon_ids(self): + with self.marionette.using_context('chrome'): + addons = self.marionette.execute_async_script(""" + Components.utils.import("resource://gre/modules/AddonManager.jsm"); + AddonManager.getAllAddons(function(addons){ + let ids = addons.map(function(x) { + return x.id; + }); + marionetteScriptFinished(ids); + }); + """) + + return addons + + def test_install_and_remove_temporary_unsigned_addon(self): + addon_path = os.path.join(here, 'mn-restartless-unsigned.xpi') + + addon_id = self.addons.install(addon_path, temp=True) + self.assertIn(addon_id, self.all_addon_ids) + + self.addons.uninstall(addon_id) + self.assertNotIn(addon_id, self.all_addon_ids) + + def test_install_unsigned_addon(self): + addon_path = os.path.join(here, 'mn-restartless-unsigned.xpi') + + with self.assertRaises(AddonInstallException): + self.addons.install(addon_path) + + @skip("Need to get the test extension signed") + def test_install_and_remove_signed_addon(self): + addon_path = os.path.join(here, 'mn-restartless-signed.xpi') + + addon_id = self.addons.install(addon_path) + self.assertIn(addon_id, self.all_addon_ids) + + self.addons.uninstall(addon_id) + self.assertNotIn(addon_id, self.all_addon_ids) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_anonymous_content.py b/testing/marionette/harness/marionette_harness/tests/unit/test_anonymous_content.py new file mode 100644 index 000000000..1e7779661 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_anonymous_content.py @@ -0,0 +1,90 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import NoSuchElementException +from marionette_driver.marionette import HTMLElement + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestAnonymousNodes(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestAnonymousNodes, self).setUp() + self.marionette.set_context("chrome") + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test_anonymous_content.xul', + 'foo', 'chrome,centerscreen'); + """) + + new_window = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + self.close_all_windows() + + super(TestAnonymousNodes, self).tearDown() + + def test_switch_to_anonymous_frame(self): + self.marionette.find_element(By.ID, "testAnonymousContentBox") + anon_browser_el = self.marionette.find_element(By.ID, "browser") + self.assertTrue("test_anonymous_content.xul" in self.marionette.get_url()) + self.marionette.switch_to_frame(anon_browser_el) + self.assertTrue("test.xul" in self.marionette.get_url()) + self.marionette.find_element(By.ID, "testXulBox") + self.assertRaises(NoSuchElementException, + self.marionette.find_element, By.ID, "testAnonymousContentBox") + + def test_switch_to_anonymous_iframe(self): + self.marionette.find_element(By.ID, "testAnonymousContentBox") + el = self.marionette.find_element(By.ID, "container2") + anon_iframe_el = el.find_element(By.ANON_ATTRIBUTE, {"anonid": "iframe"}) + self.marionette.switch_to_frame(anon_iframe_el) + self.assertTrue("test.xul" in self.marionette.get_url()) + self.marionette.find_element(By.ID, "testXulBox") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, + "testAnonymousContentBox") + + def test_find_anonymous_element_by_attribute(self): + accept_button = (By.ANON_ATTRIBUTE, {"dlgtype": "accept"},) + not_existent = (By.ANON_ATTRIBUTE, {"anonid": "notexistent"},) + + # By using the window document element + start_node = self.marionette.find_element(By.CSS_SELECTOR, ":root") + button = start_node.find_element(*accept_button) + self.assertEquals(HTMLElement, type(button)) + with self.assertRaises(NoSuchElementException): + start_node.find_element(*not_existent) + + # By using the default start node + self.assertEquals(button, self.marionette.find_element(*accept_button)) + with self.assertRaises(NoSuchElementException): + self.marionette.find_element(*not_existent) + + def test_find_anonymous_elements_by_attribute(self): + dialog_buttons = (By.ANON_ATTRIBUTE, {"anonid": "buttons"},) + not_existent = (By.ANON_ATTRIBUTE, {"anonid": "notexistent"},) + + # By using the window document element + start_node = self.marionette.find_element(By.CSS_SELECTOR, ":root") + buttons = start_node.find_elements(*dialog_buttons) + self.assertEquals(1, len(buttons)) + self.assertEquals(HTMLElement, type(buttons[0])) + self.assertListEqual([], start_node.find_elements(*not_existent)) + + # By using the default start node + self.assertListEqual(buttons, self.marionette.find_elements(*dialog_buttons)) + self.assertListEqual([], self.marionette.find_elements(*not_existent)) + + def test_find_anonymous_children(self): + self.assertEquals(HTMLElement, type(self.marionette.find_element(By.ANON, None))) + self.assertEquals(2, len(self.marionette.find_elements(By.ANON, None))) + + frame = self.marionette.find_element(By.ID, "framebox") + with self.assertRaises(NoSuchElementException): + frame.find_element(By.ANON, None) + self.assertListEqual([], frame.find_elements(By.ANON, None)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_browsermobproxy.py b/testing/marionette/harness/marionette_harness/tests/unit/test_browsermobproxy.py new file mode 100644 index 000000000..64b3d1a77 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_browsermobproxy.py @@ -0,0 +1,34 @@ +import datetime + +from marionette_harness.runner import BrowserMobTestCase + + +class TestBrowserMobProxy(BrowserMobTestCase): + """To run this test, you'll need to download the browsermob-proxy from + http://bmp.lightbody.net/, and then pass the path to the startup + script (typically /path/to/browsermob-proxy-2.0.0/bin/browsermob-proxy) + as the --browsermob-script argument when running runtests.py. + + You can additionally pass --browsermob-port to specify the port that + the proxy will run on; it defaults to 8080. + + This test is NOT run in CI, as bmp and dependencies aren't available + there. + """ + + def test_browsermob_proxy_limits(self): + """This illustrates the use of download limits in the proxy, + and verifies that it's slower to load a page @100kbps + than it is to download the same page @1000kbps. + """ + proxy = self.create_browsermob_proxy() + proxy.limits({'downstream_kbps': 1000}) + time1 = datetime.datetime.now() + self.marionette.navigate('http://forecast.weather.gov') + time2 = datetime.datetime.now() + proxy.limits({'downstream_kbps': 100}) + time3 = datetime.datetime.now() + self.marionette.refresh() + time4 = datetime.datetime.now() + self.assertTrue(time4 - time3 > time2 - time1, + "page load @ 100kbps not slower than page load @ 1000kbps") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py new file mode 100644 index 000000000..d3386316d --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py @@ -0,0 +1,253 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import SessionNotCreatedException + +from marionette_harness import MarionetteTestCase + + +class TestCapabilities(MarionetteTestCase): + + def setUp(self): + super(TestCapabilities, self).setUp() + self.caps = self.marionette.session_capabilities + with self.marionette.using_context("chrome"): + self.appinfo = self.marionette.execute_script( + "return Services.appinfo") + self.os_name = self.marionette.execute_script( + "return Services.sysinfo.getProperty('name')").lower() + self.os_version = self.marionette.execute_script( + "return Services.sysinfo.getProperty('version')") + + def test_mandated_capabilities(self): + self.assertIn("browserName", self.caps) + self.assertIn("browserVersion", self.caps) + self.assertIn("platformName", self.caps) + self.assertIn("platformVersion", self.caps) + self.assertIn("acceptInsecureCerts", self.caps) + self.assertIn("timeouts", self.caps) + + self.assertEqual(self.caps["browserName"], self.appinfo["name"].lower()) + self.assertEqual(self.caps["browserVersion"], self.appinfo["version"]) + self.assertEqual(self.caps["platformName"], self.os_name) + self.assertEqual(self.caps["platformVersion"], self.os_version) + self.assertFalse(self.caps["acceptInsecureCerts"]) + self.assertDictEqual(self.caps["timeouts"], + {"implicit": 0, + "page load": 300000, + "script": 30000}) + + def test_supported_features(self): + self.assertIn("rotatable", self.caps) + + def test_additional_capabilities(self): + self.assertIn("moz:processID", self.caps) + self.assertEqual(self.caps["moz:processID"], self.appinfo["processID"]) + self.assertEqual(self.marionette.process_id, self.appinfo["processID"]) + + self.assertIn("moz:profile", self.caps) + if self.marionette.instance is not None: + if self.caps["browserName"] == "fennec": + current_profile = self.marionette.instance.runner.device.app_ctx.remote_profile + else: + current_profile = self.marionette.instance.runner.profile.profile + self.assertEqual(self.caps["moz:profile"], current_profile) + self.assertEqual(self.marionette.profile, current_profile) + + self.assertIn("moz:accessibilityChecks", self.caps) + self.assertFalse(self.caps["moz:accessibilityChecks"]) + self.assertIn("specificationLevel", self.caps) + self.assertEqual(self.caps["specificationLevel"], 0) + + def test_set_specification_level(self): + self.marionette.delete_session() + self.marionette.start_session({"desiredCapabilities": {"specificationLevel": 2}}) + caps = self.marionette.session_capabilities + self.assertEqual(2, caps["specificationLevel"]) + + self.marionette.delete_session() + self.marionette.start_session({"requiredCapabilities": {"specificationLevel": 3}}) + caps = self.marionette.session_capabilities + self.assertEqual(3, caps["specificationLevel"]) + + def test_we_can_pass_in_required_capabilities_on_session_start(self): + self.marionette.delete_session() + capabilities = {"requiredCapabilities": {"browserName": self.appinfo["name"].lower()}} + self.marionette.start_session(capabilities) + caps = self.marionette.session_capabilities + self.assertIn("browserName", caps) + + # Start a new session just to make sure we leave the browser in the + # same state it was before it started the test + self.marionette.start_session() + + def test_capability_types(self): + for value in ["", "invalid", True, 42, []]: + print("testing value {}".format(value)) + with self.assertRaises(SessionNotCreatedException): + print(" with desiredCapabilities") + self.marionette.delete_session() + self.marionette.start_session({"desiredCapabilities": value}) + with self.assertRaises(SessionNotCreatedException): + print(" with requiredCapabilities") + self.marionette.delete_session() + self.marionette.start_session({"requiredCapabilities": value}) + + def test_we_get_valid_uuid4_when_creating_a_session(self): + self.assertNotIn("{", self.marionette.session_id, + "Session ID has {{}} in it: {}".format( + self.marionette.session_id)) + + +class TestCapabilityMatching(MarionetteTestCase): + allowed = [None, "*"] + disallowed = ["", 42, True, {}, []] + + def setUp(self): + MarionetteTestCase.setUp(self) + self.browser_name = self.marionette.session_capabilities["browserName"] + self.platform_name = self.marionette.session_capabilities["platformName"] + self.delete_session() + + def delete_session(self): + if self.marionette.session is not None: + self.marionette.delete_session() + + def test_browser_name_desired(self): + self.marionette.start_session({"desiredCapabilities": {"browserName": self.browser_name}}) + self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name) + + def test_browser_name_required(self): + self.marionette.start_session({"requiredCapabilities": {"browserName": self.browser_name}}) + self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name) + + def test_browser_name_desired_allowed_types(self): + for typ in self.allowed: + self.delete_session() + self.marionette.start_session({"desiredCapabilities": {"browserName": typ}}) + self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name) + + def test_browser_name_desired_disallowed_types(self): + for typ in self.disallowed: + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"desiredCapabilities": {"browserName": typ}}) + + def test_browser_name_required_allowed_types(self): + for typ in self.allowed: + self.delete_session() + self.marionette.start_session({"requiredCapabilities": {"browserName": typ}}) + self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name) + + def test_browser_name_requried_disallowed_types(self): + for typ in self.disallowed: + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"requiredCapabilities": {"browserName": typ}}) + + def test_browser_name_prefers_required(self): + caps = {"desiredCapabilities": {"browserName": "invalid"}, + "requiredCapabilities": {"browserName": "*"}} + self.marionette.start_session(caps) + + def test_browser_name_error_on_invalid_required(self): + with self.assertRaises(SessionNotCreatedException): + caps = {"desiredCapabilities": {"browserName": "*"}, + "requiredCapabilities": {"browserName": "invalid"}} + self.marionette.start_session(caps) + + # TODO(ato): browser version comparison not implemented yet + + def test_platform_name_desired(self): + self.marionette.start_session({"desiredCapabilities": {"platformName": self.platform_name}}) + self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name) + + def test_platform_name_required(self): + self.marionette.start_session({"requiredCapabilities": {"platformName": self.platform_name}}) + self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name) + + def test_platform_name_desired_allowed_types(self): + for typ in self.allowed: + self.delete_session() + self.marionette.start_session({"desiredCapabilities": {"platformName": typ}}) + self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name) + + def test_platform_name_desired_disallowed_types(self): + for typ in self.disallowed: + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"desiredCapabilities": {"platformName": typ}}) + + def test_platform_name_required_allowed_types(self): + for typ in self.allowed: + self.delete_session() + self.marionette.start_session({"requiredCapabilities": {"platformName": typ}}) + self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name) + + def test_platform_name_requried_disallowed_types(self): + for typ in self.disallowed: + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"requiredCapabilities": {"platformName": typ}}) + + def test_platform_name_prefers_required(self): + caps = {"desiredCapabilities": {"platformName": "invalid"}, + "requiredCapabilities": {"platformName": "*"}} + self.marionette.start_session(caps) + + def test_platform_name_error_on_invalid_required(self): + with self.assertRaises(SessionNotCreatedException): + caps = {"desiredCapabilities": {"platformName": "*"}, + "requiredCapabilities": {"platformName": "invalid"}} + self.marionette.start_session(caps) + + # TODO(ato): platform version comparison not imlpemented yet + + def test_accept_insecure_certs(self): + for capability_type in ["desiredCapabilities", "requiredCapabilities"]: + print("testing {}".format(capability_type)) + for value in ["", 42, {}, []]: + print(" type {}".format(type(value))) + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({capability_type: {"acceptInsecureCerts": value}}) + + self.delete_session() + self.marionette.start_session({"desiredCapabilities": {"acceptInsecureCerts": True}}) + self.assertTrue(self.marionette.session_capabilities["acceptInsecureCerts"]) + self.delete_session() + self.marionette.start_session({"requiredCapabilities": {"acceptInsecureCerts": True}}) + + self.assertTrue(self.marionette.session_capabilities["acceptInsecureCerts"]) + + def test_page_load_strategy(self): + for strategy in ["none", "eager", "normal"]: + print("valid strategy {}".format(strategy)) + self.delete_session() + self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": strategy}}) + self.assertEqual(self.marionette.session_capabilities["pageLoadStrategy"], strategy) + + for value in ["", "EAGER", True, 42, {}, []]: + print("invalid strategy {}".format(value)) + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": value}}) + + def test_proxy_default(self): + self.marionette.start_session() + self.assertNotIn("proxy", self.marionette.session_capabilities) + + def test_proxy_desired(self): + self.marionette.start_session({"desiredCapabilities": {"proxy": {"proxyType": "manual"}}}) + self.assertIn("proxy", self.marionette.session_capabilities) + self.assertEqual(self.marionette.session_capabilities["proxy"]["proxyType"], "manual") + self.assertEqual(self.marionette.get_pref("network.proxy.type"), 1) + + def test_proxy_required(self): + self.marionette.start_session({"requiredCapabilities": {"proxy": {"proxyType": "manual"}}}) + self.assertIn("proxy", self.marionette.session_capabilities) + self.assertEqual(self.marionette.session_capabilities["proxy"]["proxyType"], "manual") + self.assertEqual(self.marionette.get_pref("network.proxy.type"), 1) + + def test_timeouts(self): + timeouts = {u"implicit": 123, u"page load": 456, u"script": 789} + caps = {"desiredCapabilities": {"timeouts": timeouts}} + self.marionette.start_session(caps) + self.assertIn("timeouts", self.marionette.session_capabilities) + self.assertDictEqual(self.marionette.session_capabilities["timeouts"], timeouts) + self.assertDictEqual(self.marionette._send_message("getTimeouts"), timeouts) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox.py b/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox.py new file mode 100644 index 000000000..8709d6e32 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox.py @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestCheckbox(MarionetteTestCase): + def test_selected(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + box = self.marionette.find_element(By.NAME, "myCheckBox") + self.assertFalse(box.is_selected()) + box.click() + self.assertTrue(box.is_selected()) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox_chrome.py new file mode 100644 index 000000000..8d800f939 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_checkbox_chrome.py @@ -0,0 +1,36 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestSelectedChrome(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestSelectedChrome, self).setUp() + + self.marionette.set_context("chrome") + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test.xul', + '_blank', 'chrome,centerscreen'); + """) + + new_window = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + try: + self.close_all_windows() + finally: + super(TestSelectedChrome, self).tearDown() + + def test_selected(self): + box = self.marionette.find_element(By.ID, "testBox") + self.assertFalse(box.is_selected()) + self.assertFalse(self.marionette.execute_script("arguments[0].checked = true;", [box])) + self.assertTrue(box.is_selected()) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py new file mode 100644 index 000000000..8a9e53bd6 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome.py @@ -0,0 +1,51 @@ +#Copyright 2007-2009 WebDriver committers +#Copyright 2007-2009 Google Inc. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from marionette_driver import By + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class ChromeTests(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(ChromeTests, self).setUp() + + self.marionette.set_context('chrome') + + def tearDown(self): + self.close_all_windows() + super(ChromeTests, self).tearDown() + + def test_hang_until_timeout(self): + def open_with_menu(): + menu = self.marionette.find_element(By.ID, 'aboutName') + menu.click() + + new_window = self.open_window(trigger=open_with_menu) + self.marionette.switch_to_window(new_window) + + try: + try: + # Raise an exception type which should not be thrown by Marionette + # while running this test. Otherwise it would mask eg. IOError as + # thrown for a socket timeout. + raise NotImplementedError('Exception should not cause a hang when ' + 'closing the chrome window') + finally: + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + except NotImplementedError: + pass diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_async_finish.js b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_async_finish.js new file mode 100644 index 000000000..8d2df3ac2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_async_finish.js @@ -0,0 +1,6 @@ +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_CONTEXT = "chrome"; +ok(true); +(function () { + finish(); +})(); diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_css.py b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_css.py new file mode 100644 index 000000000..fde7e8373 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_css.py @@ -0,0 +1,23 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestChromeElementCSS(MarionetteTestCase): + + def test_we_can_get_css_value_on_chrome_element(self): + self.marionette.navigate("about:blank") + with self.marionette.using_context("chrome"): + element = self.marionette.find_element(By.ID, "identity-icon") + favicon_image = element.value_of_css_property("list-style-image") + + self.assertIn("identity-icon.svg", favicon_image) + + element = self.marionette.find_element(By.ID, "identity-box") + background_colour = element.value_of_css_property("background-color") + + self.assertEqual("transparent", background_colour) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_clearing.py b/testing/marionette/harness/marionette_harness/tests/unit/test_clearing.py new file mode 100644 index 000000000..3fcad45fc --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_clearing.py @@ -0,0 +1,72 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import InvalidElementStateException + +from marionette_harness import MarionetteTestCase + + +class TestClear(MarionetteTestCase): + def testWriteableTextInputShouldClear(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "writableTextInput") + element.clear() + self.assertEqual("", element.get_property("value")) + + def testTextInputShouldNotClearWhenReadOnly(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"readOnlyTextInput") + try: + element.clear() + self.fail("Should not have been able to clear") + except InvalidElementStateException: + pass + + def testWritableTextAreaShouldClear(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"writableTextArea") + element.clear() + self.assertEqual("", element.get_property("value")) + + def testTextAreaShouldNotClearWhenDisabled(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"textAreaNotenabled") + try: + element.clear() + self.fail("Should not have been able to clear") + except InvalidElementStateException: + pass + + def testTextAreaShouldNotClearWhenReadOnly(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"textAreaReadOnly") + try: + element.clear() + self.fail("Should not have been able to clear") + except InvalidElementStateException: + pass + + def testContentEditableAreaShouldClear(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID,"content-editable") + element.clear() + self.assertEqual("", element.text) + + def testTextInputShouldNotClearWhenDisabled(self): + test_html = self.marionette.absolute_url("test_clearing.html") + self.marionette.navigate(test_html) + try: + element = self.marionette.find_element(By.ID,"textInputnotenabled") + self.assertFalse(element.is_enabled()) + element.clear() + self.fail("Should not have been able to clear") + except InvalidElementStateException: + pass diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_click.py b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py new file mode 100644 index 000000000..d03062e85 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py @@ -0,0 +1,254 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import urllib + +from marionette_driver.by import By +from marionette_driver import errors +from marionette_driver.wait import Wait + +from marionette_harness import MarionetteTestCase + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + + +# The <a> element in the following HTML is not interactable because it +# is hidden by an overlay when scrolled into the top of the viewport. +# It should be interactable when scrolled in at the bottom of the +# viewport. +fixed_overlay = inline(""" +<style> +* { margin: 0; padding: 0; } +body { height: 300vh } +div, a { display: block } +div { + background-color: pink; + position: fixed; + width: 100%; + height: 40px; + top: 0; +} +a { + margin-top: 1000px; +} +</style> + +<div>overlay</div> +<a href=#>link</a> + +<script> +window.clicked = false; + +let link = document.querySelector("a"); +link.addEventListener("click", () => window.clicked = true); +</script> +""") + + +obscured_overlay = inline(""" +<style> +* { margin: 0; padding: 0; } +body { height: 100vh } +#overlay { + background-color: pink; + position: absolute; + width: 100%; + height: 100%; +} +</style> + +<div id=overlay></div> +<a id=obscured href=#>link</a> + +<script> +window.clicked = false; + +let link = document.querySelector("#obscured"); +link.addEventListener("click", () => window.clicked = true); +</script> +""") + + +class TestLegacyClick(MarionetteTestCase): + """Uses legacy Selenium element displayedness checks.""" + + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.delete_session() + self.marionette.start_session() + + def test_click(self): + self.marionette.navigate(inline(""" + <button>click me</button> + <script> + window.clicks = 0; + let button = document.querySelector("button"); + button.addEventListener("click", () => window.clicks++); + </script> + """)) + button = self.marionette.find_element(By.TAG_NAME, "button") + button.click() + self.assertEqual(1, self.marionette.execute_script("return window.clicks", sandbox=None)) + + def test_click_number_link(self): + test_html = self.marionette.absolute_url("clicks.html") + self.marionette.navigate(test_html) + self.marionette.find_element(By.LINK_TEXT, "333333").click() + Wait(self.marionette, timeout=30, ignored_exceptions=errors.NoSuchElementException).until( + lambda m: m.find_element(By.ID, "username")) + self.assertEqual(self.marionette.title, "XHTML Test Page") + + def test_clicking_an_element_that_is_not_displayed_raises(self): + test_html = self.marionette.absolute_url("hidden.html") + self.marionette.navigate(test_html) + + with self.assertRaises(errors.ElementNotInteractableException): + self.marionette.find_element(By.ID, "child").click() + + def test_clicking_on_a_multiline_link(self): + test_html = self.marionette.absolute_url("clicks.html") + self.marionette.navigate(test_html) + self.marionette.find_element(By.ID, "overflowLink").click() + self.wait_for_condition(lambda mn: self.marionette.title == "XHTML Test Page") + + def test_scroll_into_view_near_end(self): + self.marionette.navigate(fixed_overlay) + link = self.marionette.find_element(By.TAG_NAME, "a") + link.click() + self.assertTrue(self.marionette.execute_script("return window.clicked", sandbox=None)) + + +class TestClick(TestLegacyClick): + """Uses WebDriver specification compatible element interactability + checks. + """ + + def setUp(self): + TestLegacyClick.setUp(self) + self.marionette.delete_session() + self.marionette.start_session( + {"requiredCapabilities": {"specificationLevel": 1}}) + + def test_click_element_obscured_by_absolute_positioned_element(self): + self.marionette.navigate(obscured_overlay) + overlay = self.marionette.find_element(By.ID, "overlay") + obscured = self.marionette.find_element(By.ID, "obscured") + + overlay.click() + with self.assertRaises(errors.ElementClickInterceptedException): + obscured.click() + + def test_centre_outside_viewport_vertically(self): + self.marionette.navigate(inline(""" + <style> + * { margin: 0; padding: 0; } + div { + display: block; + position: absolute; + background-color: blue; + width: 200px; + height: 200px; + + /* move centre point off viewport vertically */ + top: -105px; + } + </style> + + <div></div>""")) + + self.marionette.find_element(By.TAG_NAME, "div").click() + + def test_centre_outside_viewport_horizontally(self): + self.marionette.navigate(inline(""" + <style> + * { margin: 0; padding: 0; } + div { + display: block; + position: absolute; + background-color: blue; + width: 200px; + height: 200px; + + /* move centre point off viewport horizontally */ + left: -105px; + } + </style> + + <div></div>""")) + + self.marionette.find_element(By.TAG_NAME, "div").click() + + def test_centre_outside_viewport(self): + self.marionette.navigate(inline(""" + <style> + * { margin: 0; padding: 0; } + div { + display: block; + position: absolute; + background-color: blue; + width: 200px; + height: 200px; + + /* move centre point off viewport */ + left: -105px; + top: -105px; + } + </style> + + <div></div>""")) + + self.marionette.find_element(By.TAG_NAME, "div").click() + + def test_css_transforms(self): + self.marionette.navigate(inline(""" + <style> + * { margin: 0; padding: 0; } + div { + display: block; + background-color: blue; + width: 200px; + height: 200px; + + transform: translateX(-105px); + } + </style> + + <div></div>""")) + + self.marionette.find_element(By.TAG_NAME, "div").click() + + def test_input_file(self): + self.marionette.navigate(inline("<input type=file>")) + with self.assertRaises(errors.InvalidArgumentException): + self.marionette.find_element(By.TAG_NAME, "input").click() + + def test_container_element(self): + self.marionette.navigate(inline(""" + <select> + <option>foo</option> + </select>""")) + option = self.marionette.find_element(By.TAG_NAME, "option") + option.click() + self.assertTrue(option.get_property("selected")) + + def test_container_element_outside_view(self): + self.marionette.navigate(inline(""" + <select style="margin-top: 100vh"> + <option>foo</option> + </select>""")) + option = self.marionette.find_element(By.TAG_NAME, "option") + option.click() + self.assertTrue(option.get_property("selected")) + + def test_obscured_element(self): + self.marionette.navigate(obscured_overlay) + overlay = self.marionette.find_element(By.ID, "overlay") + obscured = self.marionette.find_element(By.ID, "obscured") + + overlay.click() + with self.assertRaises(errors.ElementClickInterceptedException): + obscured.click() + self.assertFalse(self.marionette.execute_script("return window.clicked", sandbox=None)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py new file mode 100644 index 000000000..d16b4f105 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click_chrome.py @@ -0,0 +1,35 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestClickChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.root_window = self.marionette.current_window_handle + self.marionette.set_context("chrome") + self.marionette.execute_script( + "window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen')") + self.marionette.switch_to_window("foo") + self.assertNotEqual(self.root_window, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.root_window, self.marionette.current_window_handle) + self.marionette.execute_script("window.close()") + self.marionette.switch_to_window(self.root_window) + MarionetteTestCase.tearDown(self) + + def test_click(self): + def checked(): + return self.marionette.execute_script( + "return arguments[0].checked", + script_args=[box]) + + box = self.marionette.find_element(By.ID, "testBox") + self.assertFalse(checked()) + box.click() + self.assertTrue(checked()) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py b/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py new file mode 100644 index 000000000..437c15e70 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py @@ -0,0 +1,117 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import MoveTargetOutOfBoundsException + +from marionette_harness import MarionetteTestCase, skip, skip_if_mobile + + +class TestClickScrolling(MarionetteTestCase): + + + def test_clicking_on_anchor_scrolls_page(self): + scrollScript = """ + var pageY; + if (typeof(window.pageYOffset) == 'number') { + pageY = window.pageYOffset; + } else { + pageY = document.documentElement.scrollTop; + } + return pageY;""" + + test_html = self.marionette.absolute_url("macbeth.html") + self.marionette.navigate(test_html) + + self.marionette.find_element(By.PARTIAL_LINK_TEXT, "last speech").click() + y_offset = self.marionette.execute_script(scrollScript) + + # Focusing on to click, but not actually following, + # the link will scroll it in to view, which is a few + # pixels further than 0 + + self.assertTrue(y_offset > 300) + + def test_should_scroll_to_click_on_an_element_hidden_by_overflow(self): + test_html = self.marionette.absolute_url("click_out_of_bounds_overflow.html") + self.marionette.navigate(test_html) + + link = self.marionette.find_element(By.ID, "link") + try: + link.click() + except MoveTargetOutOfBoundsException: + self.fail("Should not be out of bounds") + + @skip("Bug 1200197 - Cannot interact with elements hidden inside overflow:scroll") + def test_should_be_able_to_click_on_an_element_hidden_by_overflow(self): + test_html = self.marionette.absolute_url("scroll.html") + self.marionette.navigate(test_html) + + link = self.marionette.find_element(By.ID, "line8") + link.click() + self.assertEqual("line8", self.marionette.find_element(By.ID, "clicked").text) + + def test_should_not_scroll_overflow_elements_which_are_visible(self): + test_html = self.marionette.absolute_url("scroll2.html") + self.marionette.navigate(test_html) + + list_el = self.marionette.find_element(By.TAG_NAME, "ul") + item = list_el.find_element(By.ID, "desired") + item.click() + y_offset = self.marionette.execute_script("return arguments[0].scrollTop;", script_args=[list_el]) + self.assertEqual(0, y_offset) + + def test_should_not_scroll_if_already_scrolled_and_element_is_in_view(self): + test_html = self.marionette.absolute_url("scroll3.html") + self.marionette.navigate(test_html) + + button1 = self.marionette.find_element(By.ID, "button1") + button2 = self.marionette.find_element(By.ID, "button2") + + button2.click() + scroll_top = self.marionette.execute_script("return document.body.scrollTop;") + button1.click() + + self.assertEqual(scroll_top, self.marionette.execute_script("return document.body.scrollTop;")) + + def test_should_be_able_to_click_radio_button_scrolled_into_view(self): + test_html = self.marionette.absolute_url("scroll4.html") + self.marionette.navigate(test_html) + + # If we dont throw we are good + self.marionette.find_element(By.ID, "radio").click() + + def test_should_scroll_elements_if_click_point_is_out_of_view_but_element_is_in_view(self): + test_html = self.marionette.absolute_url("element_outside_viewport.html") + + for s in ["top", "bottom"]: + self.marionette.navigate(test_html) + scroll_y = self.marionette.execute_script("return window.scrollY;") + self.marionette.find_element(By.ID, "{}-70".format(s)).click() + self.assertNotEqual(scroll_y, self.marionette.execute_script("return window.scrollY;")) + + for s in ["left", "right"]: + self.marionette.navigate(test_html) + scroll_x = self.marionette.execute_script("return window.scrollX;") + self.marionette.find_element(By.ID, "{}-70".format(s)).click() + self.assertNotEqual(scroll_x, self.marionette.execute_script("return window.scrollX;")) + + @skip_if_mobile("Bug 1293855 - Lists differ: [70, 70] != [70, 120]") + def test_should_not_scroll_elements_if_click_point_is_in_view(self): + test_html = self.marionette.absolute_url("element_outside_viewport.html") + + for s in ["top", "right", "bottom", "left"]: + for p in ["50", "30"]: + self.marionette.navigate(test_html) + scroll = self.marionette.execute_script("return [window.scrollX, window.scrollY];") + self.marionette.find_element(By.ID, "{0}-{1}".format(s, p)).click() + self.assertEqual(scroll, self.marionette.execute_script("return [window.scrollX, window.scrollY];")) + + @skip("Bug 1003687") + def test_should_scroll_overflow_elements_if_click_point_is_out_of_view_but_element_is_in_view(self): + test_html = self.marionette.absolute_url("scroll5.html") + self.marionette.navigate(test_html) + + self.marionette.find_element(By.ID, "inner").click() + self.assertEqual("clicked", self.marionette.find_element(By.ID, "clicked").text) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_cookies.py b/testing/marionette/harness/marionette_harness/tests/unit/test_cookies.py new file mode 100644 index 000000000..f7841c73e --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_cookies.py @@ -0,0 +1,115 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import calendar +import random +import time + +from marionette_driver.errors import UnsupportedOperationException +from marionette_harness import MarionetteTestCase + + +class CookieTest(MarionetteTestCase): + + def setUp(self): + MarionetteTestCase.setUp(self) + test_url = self.marionette.absolute_url('test.html') + self.marionette.navigate(test_url) + self.COOKIE_A = {"name": "foo", + "value": "bar", + "path": "/", + "secure": False} + + def tearDown(self): + self.marionette.delete_all_cookies() + MarionetteTestCase.tearDown(self) + + def test_add_cookie(self): + self.marionette.add_cookie(self.COOKIE_A) + cookie_returned = str(self.marionette.execute_script("return document.cookie")) + self.assertTrue(self.COOKIE_A["name"] in cookie_returned) + + def test_adding_a_cookie_that_expired_in_the_past(self): + cookie = self.COOKIE_A.copy() + cookie["expiry"] = calendar.timegm(time.gmtime()) - 1 + self.marionette.add_cookie(cookie) + cookies = self.marionette.get_cookies() + self.assertEquals(0, len(cookies)) + + def test_chrome_error(self): + with self.marionette.using_context("chrome"): + self.assertRaises(UnsupportedOperationException, + self.marionette.add_cookie, self.COOKIE_A) + self.assertRaises(UnsupportedOperationException, + self.marionette.delete_cookie, self.COOKIE_A) + self.assertRaises(UnsupportedOperationException, + self.marionette.delete_all_cookies) + self.assertRaises(UnsupportedOperationException, + self.marionette.get_cookies) + + def test_delete_all_cookie(self): + self.marionette.add_cookie(self.COOKIE_A) + cookie_returned = str(self.marionette.execute_script("return document.cookie")) + print cookie_returned + self.assertTrue(self.COOKIE_A["name"] in cookie_returned) + self.marionette.delete_all_cookies() + self.assertFalse(self.marionette.get_cookies()) + + def test_delete_cookie(self): + self.marionette.add_cookie(self.COOKIE_A) + cookie_returned = str(self.marionette.execute_script("return document.cookie")) + self.assertTrue(self.COOKIE_A["name"] in cookie_returned) + self.marionette.delete_cookie("foo") + cookie_returned = str(self.marionette.execute_script("return document.cookie")) + self.assertFalse(self.COOKIE_A["name"] in cookie_returned) + + def test_should_get_cookie_by_name(self): + key = "key_{}".format(int(random.random()*10000000)) + self.marionette.execute_script("document.cookie = arguments[0] + '=set';", [key]) + + cookie = self.marionette.get_cookie(key) + self.assertEquals("set", cookie["value"]) + + def test_get_all_cookies(self): + key1 = "key_{}".format(int(random.random()*10000000)) + key2 = "key_{}".format(int(random.random()*10000000)) + + cookies = self.marionette.get_cookies() + count = len(cookies) + + one = {"name" :key1, + "value": "value"} + two = {"name":key2, + "value": "value"} + + self.marionette.add_cookie(one) + self.marionette.add_cookie(two) + + test_url = self.marionette.absolute_url('test.html') + self.marionette.navigate(test_url) + cookies = self.marionette.get_cookies() + self.assertEquals(count + 2, len(cookies)) + + def test_should_not_delete_cookies_with_a_similar_name(self): + cookieOneName = "fish" + cookie1 = {"name" :cookieOneName, + "value":"cod"} + cookie2 = {"name" :cookieOneName + "x", + "value": "earth"} + self.marionette.add_cookie(cookie1) + self.marionette.add_cookie(cookie2) + + self.marionette.delete_cookie(cookieOneName) + cookies = self.marionette.get_cookies() + + self.assertFalse(cookie1["name"] == cookies[0]["name"], msg=str(cookies)) + self.assertEquals(cookie2["name"] , cookies[0]["name"], msg=str(cookies)) + + def test_we_get_required_elements_when_available(self): + self.marionette.add_cookie(self.COOKIE_A) + cookies = self.marionette.get_cookies() + + self.assertIn("name", cookies[0], 'name not available') + self.assertIn("value", cookies[0], 'value not available') + self.assertIn("httpOnly", cookies[0], 'httpOnly not available') diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py b/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py new file mode 100644 index 000000000..7e74f0857 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py @@ -0,0 +1,155 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import glob +import shutil + +from marionette_driver.errors import MarionetteException +# Import runner module to monkey patch mozcrash module +from mozrunner.base import runner + +from marionette_harness import MarionetteTestCase, expectedFailure, run_if_e10s + + +class MockMozCrash(object): + """Mock object to replace original mozcrash methods.""" + + def __init__(self, marionette): + self.marionette = marionette + + with self.marionette.using_context('chrome'): + self.crash_reporter_enabled = self.marionette.execute_script(""" + try { + Components.classes["@mozilla.org/toolkit/crash-reporter;1"]. + getService(Components.interfaces.nsICrashReporter); + return true; + } catch (exc) { + return false; + } + """) + + def check_for_crashes(self, dump_directory, *args, **kwargs): + minidump_files = glob.glob('{}/*.dmp'.format(dump_directory)) + shutil.rmtree(dump_directory, ignore_errors=True) + + if self.crash_reporter_enabled: + return len(minidump_files) + else: + return len(minidump_files) == 0 + + def log_crashes(self, logger, dump_directory, *args, **kwargs): + return self.check_for_crashes(dump_directory, *args, **kwargs) + + +class BaseCrashTestCase(MarionetteTestCase): + + # Reduce the timeout for faster processing of the tests + socket_timeout = 10 + + def setUp(self): + super(BaseCrashTestCase, self).setUp() + + self.mozcrash_mock = MockMozCrash(self.marionette) + self.crash_count = self.marionette.crashed + self.pid = self.marionette.process_id + self.remote_uri = self.marionette.absolute_url("javascriptPage.html") + + def tearDown(self): + self.marionette.crashed = self.crash_count + + super(BaseCrashTestCase, self).tearDown() + + def crash(self, chrome=True): + context = 'chrome' if chrome else 'content' + sandbox = None if chrome else 'system' + + # Monkey patch mozcrash to avoid crash info output only for our triggered crashes. + mozcrash = runner.mozcrash + runner.mozcrash = self.mozcrash_mock + + socket_timeout = self.marionette.client.socket_timeout + + self.marionette.set_context(context) + try: + self.marionette.client.socket_timeout = self.socket_timeout + self.marionette.execute_script(""" + // Copied from crash me simple + Components.utils.import("resource://gre/modules/ctypes.jsm"); + + // ctypes checks for NULL pointer derefs, so just go near-NULL. + var zero = new ctypes.intptr_t(8); + var badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t)); + var crash = badptr.contents; + """, sandbox=sandbox) + finally: + runner.mozcrash = mozcrash + self.marionette.client.socket_timeout = socket_timeout + + +class TestCrash(BaseCrashTestCase): + + def test_crash_chrome_process(self): + self.assertRaisesRegexp(IOError, 'Process crashed', + self.crash, chrome=True) + self.assertEqual(self.marionette.crashed, 1) + self.assertIsNone(self.marionette.session) + self.assertRaisesRegexp(MarionetteException, 'Please start a session', + self.marionette.get_url) + + self.marionette.start_session() + self.assertNotEqual(self.marionette.process_id, self.pid) + + # TODO: Bug 1314594 - Causes a hang for the communication between the + # chrome and frame script. + # self.marionette.get_url() + + @run_if_e10s("Content crashes only exist in e10s mode") + def test_crash_content_process(self): + # If e10s is disabled the chrome process crashes + self.marionette.navigate(self.remote_uri) + + self.assertRaisesRegexp(IOError, 'Content process crashed', + self.crash, chrome=False) + self.assertEqual(self.marionette.crashed, 1) + self.assertIsNone(self.marionette.session) + self.assertRaisesRegexp(MarionetteException, 'Please start a session', + self.marionette.get_url) + + self.marionette.start_session() + self.assertNotEqual(self.marionette.process_id, self.pid) + self.marionette.get_url() + + @expectedFailure + def test_unexpected_crash(self): + self.crash(chrome=True) + + +class TestCrashInSetUp(BaseCrashTestCase): + + def setUp(self): + super(TestCrashInSetUp, self).setUp() + + self.assertRaisesRegexp(IOError, 'Process crashed', + self.crash, chrome=True) + self.assertEqual(self.marionette.crashed, 1) + self.assertIsNone(self.marionette.session) + + def test_crash_in_setup(self): + self.marionette.start_session() + self.assertNotEqual(self.marionette.process_id, self.pid) + + +class TestCrashInTearDown(BaseCrashTestCase): + + def tearDown(self): + try: + self.assertRaisesRegexp(IOError, 'Process crashed', + self.crash, chrome=True) + finally: + self.assertEqual(self.marionette.crashed, 1) + self.assertIsNone(self.marionette.session) + super(TestCrashInTearDown, self).tearDown() + + def test_crash_in_teardown(self): + pass diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py b/testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py new file mode 100644 index 000000000..8e4ae0d32 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py @@ -0,0 +1,67 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness.marionette_test import ( + parameterized, + with_parameters, + MetaParameterized, + MarionetteTestCase +) + +class Parameterizable(object): + __metaclass__ = MetaParameterized + +class TestDataDriven(MarionetteTestCase): + def test_parameterized(self): + class Test(Parameterizable): + def __init__(self): + self.parameters = [] + + @parameterized('1', 'thing', named=43) + @parameterized('2', 'thing2') + def test(self, thing, named=None): + self.parameters.append((thing, named)) + + self.assertFalse(hasattr(Test, 'test')) + self.assertTrue(hasattr(Test, 'test_1')) + self.assertTrue(hasattr(Test, 'test_2')) + + test = Test() + test.test_1() + test.test_2() + + self.assertEquals(test.parameters, [('thing', 43), ('thing2', None)]) + + def test_with_parameters(self): + DATA = [('1', ('thing',), {'named': 43}), + ('2', ('thing2',), {'named': None})] + + class Test(Parameterizable): + def __init__(self): + self.parameters = [] + + @with_parameters(DATA) + def test(self, thing, named=None): + self.parameters.append((thing, named)) + + self.assertFalse(hasattr(Test, 'test')) + self.assertTrue(hasattr(Test, 'test_1')) + self.assertTrue(hasattr(Test, 'test_2')) + + test = Test() + test.test_1() + test.test_2() + + self.assertEquals(test.parameters, [('thing', 43), ('thing2', None)]) + + def test_parameterized_same_name_raises_error(self): + with self.assertRaises(KeyError): + class Test(Parameterizable): + @parameterized('1', 'thing', named=43) + @parameterized('1', 'thing2') + def test(self, thing, named=None): + pass + + def test_marionette_test_case_is_parameterizable(self): + self.assertTrue(issubclass(MarionetteTestCase.__metaclass__, MetaParameterized)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py b/testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py new file mode 100644 index 000000000..2d224fff2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py @@ -0,0 +1,29 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from datetime import datetime + +from marionette_driver.by import By +from marionette_driver.date_time_value import DateTimeValue +from marionette_harness import MarionetteTestCase + + +class TestDateTime(MarionetteTestCase): + def test_set_date(self): + test_html = self.marionette.absolute_url("datetimePage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "date-test") + dt_value = DateTimeValue(element) + dt_value.date = datetime(1998, 6, 2) + self.assertEqual("1998-06-02", element.get_property("value")) + + def test_set_time(self): + test_html = self.marionette.absolute_url("datetimePage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "time-test") + dt_value = DateTimeValue(element) + dt_value.time = datetime(1998, 11, 19, 9, 8, 7) + self.assertEqual("09:08:07", element.get_property("value")) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py b/testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py new file mode 100644 index 000000000..9023a84ab --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py @@ -0,0 +1,483 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import re +import urllib + +from marionette_driver.by import By +from marionette_driver.errors import NoSuchElementException, InvalidSelectorException +from marionette_driver.marionette import HTMLElement + +from marionette_harness import MarionetteTestCase, skip + + +def inline(doc, doctype="html"): + if doctype == "html": + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + elif doctype == "xhtml": + return "data:application/xhtml+xml,{}".format(urllib.quote( +r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>XHTML might be the future</title> + </head> + + <body> + {} + </body> +</html>""".format(doc))) + + +id_html = inline("<p id=foo></p>", doctype="html") +id_xhtml = inline('<p id="foo"></p>', doctype="xhtml") +parent_child_html = inline("<div id=parent><p id=child></p></div>", doctype="html") +parent_child_xhtml = inline('<div id="parent"><p id="child"></p></div>', doctype="xhtml") +children_html = inline("<div><p>foo <p>bar</div>", doctype="html") +children_xhtml = inline("<div><p>foo</p> <p>bar</p></div>", doctype="xhtml") +class_html = inline("<p class='foo bar'>", doctype="html") +class_xhtml = inline('<p class="foo bar"></p>', doctype="xhtml") +name_html = inline("<p name=foo>", doctype="html") +name_xhtml = inline('<p name="foo"></p>', doctype="xhtml") +link_html = inline("<p><a href=#>foo bar</a>", doctype="html") +link_html_with_trailing_space = inline("<p><a href=#>a link with a trailing space </a>") +link_xhtml = inline('<p><a href="#">foo bar</a></p>', doctype="xhtml") + + +class TestFindElementHTML(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.timeout.implicit = 0 + + def test_id(self): + self.marionette.navigate(id_html) + expected = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.ID, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(expected, found) + + def test_child_element(self): + self.marionette.navigate(parent_child_html) + parent = self.marionette.find_element(By.ID, "parent") + child = self.marionette.find_element(By.ID, "child") + found = parent.find_element(By.TAG_NAME, "p") + self.assertEqual(found.tag_name, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(child, found) + + def test_tag_name(self): + self.marionette.navigate(children_html) + el = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.TAG_NAME, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_class_name(self): + self.marionette.navigate(class_html) + el = self.marionette.execute_script("return document.querySelector('.foo')") + found = self.marionette.find_element(By.CLASS_NAME, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_by_name(self): + self.marionette.navigate(name_html) + el = self.marionette.execute_script("return document.querySelector('[name=foo]')") + found = self.marionette.find_element(By.NAME, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_css_selector(self): + self.marionette.navigate(children_html) + el = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.CSS_SELECTOR, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_invalid_css_selector_should_throw(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_element(By.CSS_SELECTOR, "#") + + def test_link_text(self): + self.marionette.navigate(link_html) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.LINK_TEXT, "foo bar") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_link_text_with_trailing_space(self): + self.marionette.navigate(link_html_with_trailing_space) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.LINK_TEXT, "a link with a trailing space") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_partial_link_text(self): + self.marionette.navigate(link_html) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.PARTIAL_LINK_TEXT, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_xpath(self): + self.marionette.navigate(id_html) + el = self.marionette.execute_script("return document.querySelector('#foo')") + found = self.marionette.find_element(By.XPATH, "id('foo')") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_not_found(self): + self.marionette.timeout.implicit = 0 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CLASS_NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CSS_SELECTOR, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.PARTIAL_LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.TAG_NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.XPATH, "cheese") + + def test_not_found_implicit_wait(self): + self.marionette.timeout.implicit = 0.5 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CLASS_NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CSS_SELECTOR, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.PARTIAL_LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.TAG_NAME, "cheese") + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.XPATH, "cheese") + + def test_not_found_from_element(self): + self.marionette.timeout.implicit = 0 + self.marionette.navigate(id_html) + el = self.marionette.find_element(By.ID, "foo") + self.assertRaises(NoSuchElementException, el.find_element, By.CLASS_NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.CSS_SELECTOR, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.ID, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.PARTIAL_LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.TAG_NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.XPATH, "cheese") + + def test_not_found_implicit_wait_from_element(self): + self.marionette.timeout.implicit = 0.5 + self.marionette.navigate(id_html) + el = self.marionette.find_element(By.ID, "foo") + self.assertRaises(NoSuchElementException, el.find_element, By.CLASS_NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.CSS_SELECTOR, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.ID, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.PARTIAL_LINK_TEXT, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.TAG_NAME, "cheese") + self.assertRaises(NoSuchElementException, el.find_element, By.XPATH, "cheese") + + def test_css_selector_scope_doesnt_start_at_rootnode(self): + self.marionette.navigate(parent_child_html) + el = self.marionette.find_element(By.ID, "child") + parent = self.marionette.find_element(By.ID, "parent") + found = parent.find_element(By.CSS_SELECTOR, "p") + self.assertEqual(el, found) + + def test_unknown_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_elements("foo", "bar") + + def test_element_id_is_valid_uuid(self): + self.marionette.navigate(id_html) + el = self.marionette.find_element(By.TAG_NAME, "p") + uuid_regex = re.compile('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$') + self.assertIsNotNone(re.search(uuid_regex, el.id), + 'UUID for the WebElement is not valid. ID is {}'\ + .format(el.id)) + + def test_invalid_xpath_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_element(By.XPATH, "count(//input)") + with self.assertRaises(InvalidSelectorException): + parent = self.marionette.execute_script("return document.documentElement") + parent.find_element(By.XPATH, "count(//input)") + + def test_invalid_css_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_element(By.CSS_SELECTOR, "") + with self.assertRaises(InvalidSelectorException): + parent = self.marionette.execute_script("return document.documentElement") + parent.find_element(By.CSS_SELECTOR, "") + + def test_finding_active_element_returns_element(self): + self.marionette.navigate(id_html) + active = self.marionette.execute_script("return document.activeElement") + self.assertEqual(active, self.marionette.get_active_element()) + + +class TestFindElementXHTML(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.timeout.implicit = 0 + + def test_id(self): + self.marionette.navigate(id_xhtml) + expected = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.ID, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(expected, found) + + def test_child_element(self): + self.marionette.navigate(parent_child_xhtml) + parent = self.marionette.find_element(By.ID, "parent") + child = self.marionette.find_element(By.ID, "child") + found = parent.find_element(By.TAG_NAME, "p") + self.assertEqual(found.tag_name, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(child, found) + + def test_tag_name(self): + self.marionette.navigate(children_xhtml) + el = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.TAG_NAME, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_class_name(self): + self.marionette.navigate(class_xhtml) + el = self.marionette.execute_script("return document.querySelector('.foo')") + found = self.marionette.find_element(By.CLASS_NAME, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_by_name(self): + self.marionette.navigate(name_xhtml) + el = self.marionette.execute_script("return document.querySelector('[name=foo]')") + found = self.marionette.find_element(By.NAME, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_css_selector(self): + self.marionette.navigate(children_xhtml) + el = self.marionette.execute_script("return document.querySelector('p')") + found = self.marionette.find_element(By.CSS_SELECTOR, "p") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_link_text(self): + self.marionette.navigate(link_xhtml) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.LINK_TEXT, "foo bar") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_partial_link_text(self): + self.marionette.navigate(link_xhtml) + el = self.marionette.execute_script("return document.querySelector('a')") + found = self.marionette.find_element(By.PARTIAL_LINK_TEXT, "foo") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_xpath(self): + self.marionette.navigate(id_xhtml) + el = self.marionette.execute_script("return document.querySelector('#foo')") + found = self.marionette.find_element(By.XPATH, "id('foo')") + self.assertIsInstance(found, HTMLElement) + self.assertEqual(el, found) + + def test_css_selector_scope_does_not_start_at_rootnode(self): + self.marionette.navigate(parent_child_xhtml) + el = self.marionette.find_element(By.ID, "child") + parent = self.marionette.find_element(By.ID, "parent") + found = parent.find_element(By.CSS_SELECTOR, "p") + self.assertEqual(el, found) + + def test_active_element(self): + self.marionette.navigate(id_xhtml) + active = self.marionette.execute_script("return document.activeElement") + self.assertEqual(active, self.marionette.get_active_element()) + + +class TestFindElementsHTML(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.timeout.implicit = 0 + + def assertItemsIsInstance(self, items, typ): + for item in items: + self.assertIsInstance(item, typ) + + def test_child_elements(self): + self.marionette.navigate(children_html) + parent = self.marionette.find_element(By.TAG_NAME, "div") + children = self.marionette.find_elements(By.TAG_NAME, "p") + found = parent.find_elements(By.TAG_NAME, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(found, children) + + def test_tag_name(self): + self.marionette.navigate(children_html) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.TAG_NAME, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_class_name(self): + self.marionette.navigate(class_html) + els = self.marionette.execute_script("return document.querySelectorAll('.foo')") + found = self.marionette.find_elements(By.CLASS_NAME, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_by_name(self): + self.marionette.navigate(name_html) + els = self.marionette.execute_script("return document.querySelectorAll('[name=foo]')") + found = self.marionette.find_elements(By.NAME, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_css_selector(self): + self.marionette.navigate(children_html) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.CSS_SELECTOR, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_invalid_css_selector_should_throw(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_elements(By.CSS_SELECTOR, "#") + + def test_link_text(self): + self.marionette.navigate(link_html) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.LINK_TEXT, "foo bar") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_link_text_with_trailing_space(self): + self.marionette.navigate(link_html_with_trailing_space) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.LINK_TEXT, "a link with a trailing space") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + + def test_partial_link_text(self): + self.marionette.navigate(link_html) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.PARTIAL_LINK_TEXT, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_xpath(self): + self.marionette.navigate(children_html) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.XPATH, ".//p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_css_selector_scope_doesnt_start_at_rootnode(self): + self.marionette.navigate(parent_child_html) + els = self.marionette.find_elements(By.ID, "child") + parent = self.marionette.find_element(By.ID, "parent") + found = parent.find_elements(By.CSS_SELECTOR, "p") + self.assertSequenceEqual(els, found) + + def test_unknown_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_element("foo", "bar") + + def test_element_id_is_valid_uuid(self): + self.marionette.navigate(id_html) + els = self.marionette.find_elements(By.TAG_NAME, "p") + uuid_regex = re.compile('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$') + self.assertIsNotNone(re.search(uuid_regex, els[0].id), + 'UUID for the WebElement is not valid. ID is {}'\ + .format(els[0].id)) + + def test_invalid_xpath_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_elements(By.XPATH, "count(//input)") + with self.assertRaises(InvalidSelectorException): + parent = self.marionette.execute_script("return document.documentElement") + parent.find_elements(By.XPATH, "count(//input)") + + def test_invalid_css_selector(self): + with self.assertRaises(InvalidSelectorException): + self.marionette.find_elements(By.CSS_SELECTOR, "") + with self.assertRaises(InvalidSelectorException): + parent = self.marionette.execute_script("return document.documentElement") + parent.find_elements(By.CSS_SELECTOR, "") + + +class TestFindElementsXHTML(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.timeout.implicit = 0 + + def assertItemsIsInstance(self, items, typ): + for item in items: + self.assertIsInstance(item, typ) + + def test_child_elements(self): + self.marionette.navigate(children_xhtml) + parent = self.marionette.find_element(By.TAG_NAME, "div") + children = self.marionette.find_elements(By.TAG_NAME, "p") + found = parent.find_elements(By.TAG_NAME, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(found, children) + + def test_tag_name(self): + self.marionette.navigate(children_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.TAG_NAME, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_class_name(self): + self.marionette.navigate(class_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('.foo')") + found = self.marionette.find_elements(By.CLASS_NAME, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_by_name(self): + self.marionette.navigate(name_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('[name=foo]')") + found = self.marionette.find_elements(By.NAME, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_css_selector(self): + self.marionette.navigate(children_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.CSS_SELECTOR, "p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_link_text(self): + self.marionette.navigate(link_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.LINK_TEXT, "foo bar") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_partial_link_text(self): + self.marionette.navigate(link_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('a')") + found = self.marionette.find_elements(By.PARTIAL_LINK_TEXT, "foo") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + @skip("XHTML namespace not yet supported") + def test_xpath(self): + self.marionette.navigate(children_xhtml) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + found = self.marionette.find_elements(By.XPATH, "//xhtml:p") + self.assertItemsIsInstance(found, HTMLElement) + self.assertSequenceEqual(els, found) + + def test_css_selector_scope_doesnt_start_at_rootnode(self): + self.marionette.navigate(parent_child_xhtml) + els = self.marionette.find_elements(By.ID, "child") + parent = self.marionette.find_element(By.ID, "parent") + found = parent.find_elements(By.CSS_SELECTOR, "p") + self.assertSequenceEqual(els, found) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py new file mode 100644 index 000000000..0344b4b9c --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py @@ -0,0 +1,162 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import types +import urllib + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +boolean_attributes = { + "audio": ["autoplay", "controls", "loop", "muted"], + "button": ["autofocus", "disabled", "formnovalidate"], + "details": ["open"], + "dialog": ["open"], + "fieldset": ["disabled"], + "form": ["novalidate"], + "iframe": ["allowfullscreen"], + "img": ["ismap"], + "input": ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"], + "menuitem": ["checked", "default", "disabled"], + "object": ["typemustmatch"], + "ol": ["reversed"], + "optgroup": ["disabled"], + "option": ["disabled", "selected"], + "script": ["async", "defer"], + "select": ["autofocus", "disabled", "multiple", "required"], + "textarea": ["autofocus", "disabled", "readonly", "required"], + "track": ["default"], + "video": ["autoplay", "controls", "loop", "muted"], +} + + +def inline(doc, doctype="html"): + if doctype == "html": + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + elif doctype == "xhtml": + return "data:application/xhtml+xml,{}".format(urllib.quote( +r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>XHTML might be the future</title> + </head> + + <body> + {} + </body> +</html>""".format(doc))) + + +attribute = inline("<input foo=bar>") +input = inline("<input>") +disabled = inline("<input disabled=baz>") +check = inline("<input type=checkbox>") + + +class TestIsElementEnabled(MarionetteTestCase): + def test_is_enabled(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + l = self.marionette.find_element(By.NAME, "myCheckBox") + self.assertTrue(l.is_enabled()) + self.marionette.execute_script("arguments[0].disabled = true;", [l]) + self.assertFalse(l.is_enabled()) + + +class TestIsElementDisplayed(MarionetteTestCase): + def test_is_displayed(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + l = self.marionette.find_element(By.NAME, "myCheckBox") + self.assertTrue(l.is_displayed()) + self.marionette.execute_script("arguments[0].hidden = true;", [l]) + self.assertFalse(l.is_displayed()) + + +class TestGetElementAttribute(MarionetteTestCase): + def test_normal_attribute(self): + self.marionette.navigate(inline("<p style=foo>")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("style") + self.assertIsInstance(attr, types.StringTypes) + self.assertEqual("foo", attr) + + def test_boolean_attributes(self): + for tag, attrs in boolean_attributes.iteritems(): + for attr in attrs: + print("testing boolean attribute <{0} {1}>".format(tag, attr)) + doc = inline("<{0} {1}>".format(tag, attr)) + self.marionette.navigate(doc) + el = self.marionette.find_element(By.TAG_NAME, tag) + res = el.get_attribute(attr) + self.assertIsInstance(res, types.StringTypes) + self.assertEqual("true", res) + + def test_global_boolean_attributes(self): + self.marionette.navigate(inline("<p hidden>foo")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("hidden") + self.assertIsInstance(attr, types.StringTypes) + self.assertEqual("true", attr) + + self.marionette.navigate(inline("<p>foo")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("hidden") + self.assertIsNone(attr) + + self.marionette.navigate(inline("<p itemscope>foo")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("itemscope") + self.assertIsInstance(attr, types.StringTypes) + self.assertEqual("true", attr) + + self.marionette.navigate(inline("<p>foo")) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("itemscope") + self.assertIsNone(attr) + + # TODO(ato): Test for custom elements + + def test_xhtml(self): + doc = inline("<p hidden=\"true\">foo</p>", doctype="xhtml") + self.marionette.navigate(doc) + el = self.marionette.find_element(By.TAG_NAME, "p") + attr = el.get_attribute("hidden") + self.assertIsInstance(attr, types.StringTypes) + self.assertEqual("true", attr) + + +class TestGetElementProperty(MarionetteTestCase): + def test_get(self): + self.marionette.navigate(disabled) + el = self.marionette.find_element(By.TAG_NAME, "input") + prop = el.get_property("disabled") + self.assertIsInstance(prop, bool) + self.assertTrue(prop) + + def test_missing_property_returns_default(self): + self.marionette.navigate(input) + el = self.marionette.find_element(By.TAG_NAME, "input") + prop = el.get_property("checked") + self.assertIsInstance(prop, bool) + self.assertFalse(prop) + + def test_attribute_not_returned(self): + self.marionette.navigate(attribute) + el = self.marionette.find_element(By.TAG_NAME, "input") + self.assertEqual(el.get_property("foo"), None) + + def test_manipulated_element(self): + self.marionette.navigate(check) + el = self.marionette.find_element(By.TAG_NAME, "input") + self.assertEqual(el.get_property("checked"), False) + + el.click() + self.assertEqual(el.get_property("checked"), True) + + el.click() + self.assertEqual(el.get_property("checked"), False) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py new file mode 100644 index 000000000..01ed355c4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state_chrome.py @@ -0,0 +1,85 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase, skip + + +class TestIsElementEnabledChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_enabled(self): + l = self.marionette.find_element(By.ID, "textInput") + self.assertTrue(l.is_enabled()) + self.marionette.execute_script("arguments[0].disabled = true;", [l]) + self.assertFalse(l.is_enabled()) + self.marionette.execute_script("arguments[0].disabled = false;", [l]) + + def test_can_get_element_rect(self): + l = self.marionette.find_element(By.ID, "textInput") + rect = l.rect + self.assertTrue(rect['x'] > 0) + self.assertTrue(rect['y'] > 0) + + +@skip("Switched off in bug 896043, and to be turned on in bug 896046") +class TestIsElementDisplayed(MarionetteTestCase): + def test_isDisplayed(self): + l = self.marionette.find_element(By.ID, "textInput") + self.assertTrue(l.is_displayed()) + self.marionette.execute_script("arguments[0].hidden = true;", [l]) + self.assertFalse(l.is_displayed()) + self.marionette.execute_script("arguments[0].hidden = false;", [l]) + + +class TestGetElementAttributeChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_get(self): + el = self.marionette.execute_script("return window.document.getElementById('textInput');") + self.assertEqual(el.get_attribute("id"), "textInput") + +class TestGetElementProperty(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_get(self): + el = self.marionette.execute_script("return window.document.getElementById('textInput');") + self.assertEqual(el.get_property("id"), "textInput") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py new file mode 100644 index 000000000..ebabd3344 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestElementSize(MarionetteTestCase): + def testShouldReturnTheSizeOfALink(self): + test_html = self.marionette.absolute_url("testSize.html") + self.marionette.navigate(test_html) + shrinko = self.marionette.find_element(By.ID, 'linkId') + size = shrinko.rect + self.assertTrue(size['width'] > 0) + self.assertTrue(size['height'] > 0) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize_chrome.py new file mode 100644 index 000000000..e2bb34715 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize_chrome.py @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestElementSizeChrome(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestElementSizeChrome, self).setUp() + + self.marionette.set_context("chrome") + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test2.xul', + 'foo', 'chrome,centerscreen'); + """) + + new_window = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + self.close_all_windows() + super(TestElementSizeChrome, self).tearDown() + + def testShouldReturnTheSizeOfAnInput(self): + shrinko = self.marionette.find_element(By.ID, 'textInput') + size = shrinko.rect + self.assertTrue(size['width'] > 0) + self.assertTrue(size['height'] > 0) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_errors.py b/testing/marionette/harness/marionette_harness/tests/unit/test_errors.py new file mode 100644 index 000000000..f6a9c285c --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_errors.py @@ -0,0 +1,77 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys + +from marionette_driver import errors + +from marionette_harness import marionette_test + + +def fake_cause(): + try: + raise ValueError("bar") + except ValueError as e: + return sys.exc_info() + +message = "foo" +cause = fake_cause() +stacktrace = "first\nsecond" + +class TestErrors(marionette_test.MarionetteTestCase): + def test_defaults(self): + exc = errors.MarionetteException() + self.assertIsNone(exc.message) + self.assertIsNone(exc.cause) + self.assertIsNone(exc.stacktrace) + + def test_construction(self): + exc = errors.MarionetteException( + message=message, cause=cause, stacktrace=stacktrace) + self.assertEquals(exc.message, message) + self.assertEquals(exc.cause, cause) + self.assertEquals(exc.stacktrace, stacktrace) + + def test_str(self): + exc = errors.MarionetteException( + message=message, cause=cause, stacktrace=stacktrace) + r = str(exc) + self.assertIn(message, r) + self.assertIn(", caused by {0!r}".format(cause[0]), r) + self.assertIn("\nstacktrace:\n\tfirst\n\tsecond", r) + + def test_cause_string(self): + exc = errors.MarionetteException(cause="foo") + self.assertEqual(exc.cause, "foo") + r = str(exc) + self.assertIn(", caused by foo", r) + + def test_cause_tuple(self): + exc = errors.MarionetteException(cause=cause) + self.assertEqual(exc.cause, cause) + r = str(exc) + self.assertIn(", caused by {0!r}".format(cause[0]), r) + + +class TestLookup(marionette_test.MarionetteTestCase): + def test_by_unknown_number(self): + self.assertEqual(errors.MarionetteException, errors.lookup(123456)) + + def test_by_known_string(self): + self.assertEqual(errors.NoSuchElementException, + errors.lookup("no such element")) + + def test_by_unknown_string(self): + self.assertEqual(errors.MarionetteException, errors.lookup("barbera")) + + def test_by_known_unicode_string(self): + self.assertEqual(errors.NoSuchElementException, + errors.lookup(u"no such element")) + + +class TestAllErrors(marionette_test.MarionetteTestCase): + def test_properties(self): + for exc in errors.es_: + self.assertTrue(hasattr(exc, "status"), + "expected exception to have attribute `status'") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_async_script.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_async_script.py new file mode 100644 index 000000000..8a5472b3a --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_async_script.py @@ -0,0 +1,156 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import ( + JavascriptException, + ScriptTimeoutException, +) + +from marionette_harness import MarionetteTestCase + + +class TestExecuteAsyncContent(MarionetteTestCase): + def setUp(self): + super(TestExecuteAsyncContent, self).setUp() + self.marionette.timeout.script = 1 + + def test_execute_async_simple(self): + self.assertEqual(1, self.marionette.execute_async_script("arguments[arguments.length-1](1);")) + + def test_execute_async_ours(self): + self.assertEqual(1, self.marionette.execute_async_script("marionetteScriptFinished(1);")) + + def test_execute_async_timeout(self): + self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "var x = 1;") + + def test_execute_async_unique_timeout(self): + self.assertEqual(2, self.marionette.execute_async_script("setTimeout(function() {marionetteScriptFinished(2);}, 2000);", script_timeout=5000)) + self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "setTimeout(function() {marionetteScriptFinished(3);}, 2000);") + + def test_no_timeout(self): + self.marionette.timeout.script = 10 + self.assertTrue(self.marionette.execute_async_script(""" + var callback = arguments[arguments.length - 1]; + setTimeout(function() { callback(true); }, 500); + """)) + + def test_execute_async_unload(self): + self.marionette.timeout.script = 5 + unload = """ + window.location.href = "about:blank"; + """ + self.assertRaises(JavascriptException, self.marionette.execute_async_script, unload) + + def test_check_window(self): + self.assertTrue(self.marionette.execute_async_script("marionetteScriptFinished(window !=null && window != undefined);")) + + def test_same_context(self): + var1 = 'testing' + self.assertEqual(self.marionette.execute_script(""" + this.testvar = '{}'; + return this.testvar; + """.format(var1)), var1) + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(this.testvar);", new_sandbox=False), var1) + + def test_execute_no_return(self): + self.assertEqual(self.marionette.execute_async_script("marionetteScriptFinished()"), None) + + def test_execute_js_exception(self): + try: + self.marionette.execute_async_script(""" + let a = 1; + foo(bar); + """) + self.assertFalse(True) + except JavascriptException, inst: + self.assertTrue('foo(bar)' in inst.stacktrace) + + def test_execute_async_js_exception(self): + self.assertRaises(JavascriptException, + self.marionette.execute_async_script, """ + var callback = arguments[arguments.length - 1]; + callback(foo()); + """) + + def test_script_finished(self): + self.assertTrue(self.marionette.execute_async_script(""" + marionetteScriptFinished(true); + """)) + + def test_execute_permission(self): + self.assertRaises(JavascriptException, self.marionette.execute_async_script, """ +let prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); +marionetteScriptFinished(4); +""") + + def test_sandbox_reuse(self): + # Sandboxes between `execute_script()` invocations are shared. + self.marionette.execute_async_script("this.foobar = [23, 42];" + "marionetteScriptFinished();") + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(this.foobar);", new_sandbox=False), [23, 42]) + + self.marionette.execute_async_script("global.barfoo = [42, 23];" + "marionetteScriptFinished();") + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(global.barfoo);", new_sandbox=False), [42, 23]) + + def test_sandbox_refresh_arguments(self): + self.marionette.execute_async_script("this.foobar = [arguments[0], arguments[1]];" + "marionetteScriptFinished();", + script_args=[23, 42]) + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(this.foobar);", new_sandbox=False), + [23, 42]) + + self.marionette.execute_async_script("global.barfoo = [arguments[0], arguments[1]];" + "marionetteScriptFinished()", + script_args=[42, 23], new_sandbox=False) + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(global.barfoo);", new_sandbox=False), + [42, 23]) + + # Functions defined in higher privilege scopes, such as the privileged + # content frame script listener.js runs in, cannot be accessed from + # content. This tests that it is possible to introspect the objects on + # `arguments` without getting permission defined errors. This is made + # possible because the last argument is always the callback/complete + # function. + # + # See bug 1290966. + def test_introspection_of_arguments(self): + self.marionette.execute_async_script( + "arguments[0].cheese; __webDriverCallback();", + script_args=[], sandbox=None) + + +class TestExecuteAsyncChrome(TestExecuteAsyncContent): + def setUp(self): + super(TestExecuteAsyncChrome, self).setUp() + self.marionette.set_context("chrome") + + def test_execute_async_unload(self): + pass + + def test_execute_permission(self): + self.assertEqual(5, self.marionette.execute_async_script(""" +var c = Components.classes; +marionetteScriptFinished(5); +""")) + + def test_execute_async_js_exception(self): + # Javascript exceptions are not propagated in chrome code + self.marionette.timeout.script = 0.2 + self.assertRaises(ScriptTimeoutException, + self.marionette.execute_async_script, """ + var callback = arguments[arguments.length - 1]; + setTimeout("callback(foo())", 50); + """) + self.assertRaises(JavascriptException, + self.marionette.execute_async_script, """ + var callback = arguments[arguments.length - 1]; + setTimeout("callback(foo())", 50); + """, debug_script=True) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_isolate.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_isolate.py new file mode 100644 index 000000000..7e09451e4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_isolate.py @@ -0,0 +1,37 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import ScriptTimeoutException + +from marionette_harness import MarionetteTestCase + + +class TestExecuteIsolationContent(MarionetteTestCase): + def setUp(self): + super(TestExecuteIsolationContent, self).setUp() + self.content = True + + def test_execute_async_isolate(self): + # Results from one execute call that has timed out should not + # contaminate a future call. + multiplier = "*3" if self.content else "*1" + self.marionette.timeout.script = 0.5 + self.assertRaises(ScriptTimeoutException, + self.marionette.execute_async_script, + ("setTimeout(function() {{ marionetteScriptFinished(5{}); }}, 3000);" + .format(multiplier))) + + self.marionette.timeout.script = 6 + result = self.marionette.execute_async_script(""" + setTimeout(function() {{ marionetteScriptFinished(10{}); }}, 5000); + """.format(multiplier)) + self.assertEqual(result, 30 if self.content else 10) + +class TestExecuteIsolationChrome(TestExecuteIsolationContent): + def setUp(self): + super(TestExecuteIsolationChrome, self).setUp() + self.marionette.set_context("chrome") + self.content = False + + diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_sandboxes.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_sandboxes.py new file mode 100644 index 000000000..d7cb0444b --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_sandboxes.py @@ -0,0 +1,79 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import JavascriptException + +from marionette_harness import MarionetteTestCase + + +class TestExecuteSandboxes(MarionetteTestCase): + def setUp(self): + super(TestExecuteSandboxes, self).setUp() + + def test_execute_system_sandbox(self): + # Test that "system" sandbox has elevated privileges in execute_script + result = self.marionette.execute_script( + "return Components.interfaces.nsIPermissionManager.ALLOW_ACTION", + sandbox="system") + self.assertEqual(result, 1) + + def test_execute_async_system_sandbox(self): + # Test that "system" sandbox has elevated privileges in + # execute_async_script. + result = self.marionette.execute_async_script(""" + const Ci = Components.interfaces; + let result = Ci.nsIPermissionManager.ALLOW_ACTION; + marionetteScriptFinished(result);""", + sandbox="system") + self.assertEqual(result, 1) + + def test_execute_switch_sandboxes(self): + # Test that sandboxes are retained when switching between them + # for execute_script. + self.marionette.execute_script("foo = 1", sandbox="1") + self.marionette.execute_script("foo = 2", sandbox="2") + foo = self.marionette.execute_script( + "return foo", sandbox="1", new_sandbox=False) + self.assertEqual(foo, 1) + foo = self.marionette.execute_script( + "return foo", sandbox="2", new_sandbox=False) + self.assertEqual(foo, 2) + + def test_execute_new_sandbox(self): + # test that clearing a sandbox does not affect other sandboxes + self.marionette.execute_script("foo = 1", sandbox="1") + self.marionette.execute_script("foo = 2", sandbox="2") + + # deprecate sandbox 1 by asking explicitly for a fresh one + with self.assertRaises(JavascriptException): + self.marionette.execute_script("return foo", + sandbox="1", new_sandbox=True) + + foo = self.marionette.execute_script( + "return foo", sandbox="2", new_sandbox=False) + self.assertEqual(foo, 2) + + def test_execute_async_switch_sandboxes(self): + # Test that sandboxes are retained when switching between them + # for execute_async_script. + self.marionette.execute_async_script( + "foo = 1; marionetteScriptFinished()", sandbox="1") + self.marionette.execute_async_script( + "foo = 2; marionetteScriptFinished()", sandbox='2') + foo = self.marionette.execute_async_script( + "marionetteScriptFinished(foo)", + sandbox="1", + new_sandbox=False) + self.assertEqual(foo, 1) + foo = self.marionette.execute_async_script( + "marionetteScriptFinished(foo)", + sandbox="2", + new_sandbox=False) + self.assertEqual(foo, 2) + + +class TestExecuteSandboxesChrome(TestExecuteSandboxes): + def setUp(self): + super(TestExecuteSandboxesChrome, self).setUp() + self.marionette.set_context("chrome") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py new file mode 100644 index 000000000..1ef4549d3 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py @@ -0,0 +1,402 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import urllib + +from marionette_driver import By, errors +from marionette_driver.marionette import HTMLElement +from marionette_driver.wait import Wait + +from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + + +elements = inline("<p>foo</p> <p>bar</p>") + +globals = set([ + "atob", + "Audio", + "btoa", + "document", + "navigator", + "URL", + "window", + ]) + + +class TestExecuteSimpleTestContent(MarionetteTestCase): + def test_stack_trace(self): + try: + self.marionette.execute_js_script(""" + let a = 1; + throwHere(); + """, filename="file.js") + self.assertFalse(True) + except errors.JavascriptException as e: + self.assertIn("throwHere is not defined", e.message) + self.assertIn("@file.js:2", e.stacktrace) + + +class TestExecuteContent(MarionetteTestCase): + + def assert_is_defined(self, property, sandbox="default"): + self.assertTrue(self.marionette.execute_script( + "return typeof arguments[0] != 'undefined'", [property], sandbox=sandbox), + "property {} is undefined".format(property)) + + def test_return_number(self): + self.assertEqual(1, self.marionette.execute_script("return 1")) + self.assertEqual(1.5, self.marionette.execute_script("return 1.5")) + + def test_return_boolean(self): + self.assertTrue(self.marionette.execute_script("return true")) + + def test_return_string(self): + self.assertEqual("foo", self.marionette.execute_script("return 'foo'")) + + def test_return_array(self): + self.assertEqual( + [1, 2], self.marionette.execute_script("return [1, 2]")) + self.assertEqual( + [1.25, 1.75], self.marionette.execute_script("return [1.25, 1.75]")) + self.assertEqual( + [True, False], self.marionette.execute_script("return [true, false]")) + self.assertEqual( + ["foo", "bar"], self.marionette.execute_script("return ['foo', 'bar']")) + self.assertEqual( + [1, 1.5, True, "foo"], self.marionette.execute_script("return [1, 1.5, true, 'foo']")) + self.assertEqual( + [1, [2]], self.marionette.execute_script("return [1, [2]]")) + + def test_return_object(self): + self.assertEqual( + {"foo": 1}, self.marionette.execute_script("return {foo: 1}")) + self.assertEqual( + {"foo": 1.5}, self.marionette.execute_script("return {foo: 1.5}")) + self.assertEqual( + {"foo": True}, self.marionette.execute_script("return {foo: true}")) + self.assertEqual( + {"foo": "bar"}, self.marionette.execute_script("return {foo: 'bar'}")) + self.assertEqual( + {"foo": [1, 2]}, self.marionette.execute_script("return {foo: [1, 2]}")) + self.assertEqual( + {"foo": {"bar": [1, 2]}}, + self.marionette.execute_script("return {foo: {bar: [1, 2]}}")) + + def test_no_return_value(self): + self.assertIsNone(self.marionette.execute_script("true")) + + def test_argument_null(self): + self.assertIsNone(self.marionette.execute_script("return arguments[0]", [None])) + + def test_argument_number(self): + self.assertEqual( + 1, self.marionette.execute_script("return arguments[0]", [1])) + self.assertEqual( + 1.5, self.marionette.execute_script("return arguments[0]", [1.5])) + + def test_argument_boolean(self): + self.assertTrue(self.marionette.execute_script("return arguments[0]", [True])) + + def test_argument_string(self): + self.assertEqual( + "foo", self.marionette.execute_script("return arguments[0]", ["foo"])) + + def test_argument_array(self): + self.assertEqual( + [1, 2], self.marionette.execute_script("return arguments[0]", [[1, 2]])) + + def test_argument_object(self): + self.assertEqual({"foo": 1}, self.marionette.execute_script( + "return arguments[0]", [{"foo": 1}])) + + def test_globals(self): + for property in globals: + self.assert_is_defined(property) + self.assert_is_defined("Components") + self.assert_is_defined("window.wrappedJSObject") + + def test_system_globals(self): + for property in globals: + self.assert_is_defined(property, sandbox="system") + self.assert_is_defined("Components", sandbox="system") + self.assert_is_defined("window.wrappedJSObject") + + def test_exception(self): + self.assertRaises(errors.JavascriptException, + self.marionette.execute_script, "return foo") + + def test_stacktrace(self): + with self.assertRaises(errors.JavascriptException) as cm: + self.marionette.execute_script("return b") + + # by default execute_script pass the name of the python file + self.assertIn(os.path.basename(__file__.replace(".pyc", ".py")), + cm.exception.stacktrace) + self.assertIn("b is not defined", cm.exception.message) + self.assertIn("return b", cm.exception.stacktrace) + + def test_permission(self): + with self.assertRaises(errors.JavascriptException): + self.marionette.execute_script(""" + var c = Components.classes["@mozilla.org/preferences-service;1"]; + """) + + def test_return_web_element(self): + self.marionette.navigate(elements) + expected = self.marionette.find_element(By.TAG_NAME, "p") + actual = self.marionette.execute_script( + "return document.querySelector('p')") + self.assertEqual(expected, actual) + + def test_return_web_element_array(self): + self.marionette.navigate(elements) + expected = self.marionette.find_elements(By.TAG_NAME, "p") + actual = self.marionette.execute_script(""" + let els = document.querySelectorAll('p') + return [els[0], els[1]]""") + self.assertEqual(expected, actual) + + # Bug 938228 identifies a problem with unmarshaling NodeList + # objects from the DOM. document.querySelectorAll returns this + # construct. + def test_return_web_element_nodelist(self): + self.marionette.navigate(elements) + expected = self.marionette.find_elements(By.TAG_NAME, "p") + actual = self.marionette.execute_script( + "return document.querySelectorAll('p')") + self.assertEqual(expected, actual) + + def test_sandbox_reuse(self): + # Sandboxes between `execute_script()` invocations are shared. + self.marionette.execute_script("this.foobar = [23, 42];") + self.assertEqual(self.marionette.execute_script( + "return this.foobar;", new_sandbox=False), [23, 42]) + + self.marionette.execute_script("global.barfoo = [42, 23];") + self.assertEqual(self.marionette.execute_script( + "return global.barfoo;", new_sandbox=False), [42, 23]) + + def test_sandbox_refresh_arguments(self): + self.marionette.execute_script( + "this.foobar = [arguments[0], arguments[1]]", [23, 42]) + self.assertEqual(self.marionette.execute_script( + "return this.foobar", new_sandbox=False), [23, 42]) + + def test_wrappedjsobject(self): + try: + self.marionette.execute_script("window.wrappedJSObject.foo = 3") + self.assertEqual( + self.marionette.execute_script("return window.wrappedJSObject.foo"), 3) + finally: + self.marionette.execute_script("delete window.wrappedJSObject.foo") + + def test_system_sandbox_wrappedjsobject(self): + self.marionette.execute_script( + "window.wrappedJSObject.foo = 4", sandbox="system") + self.assertEqual(self.marionette.execute_script( + "return window.wrappedJSObject.foo", sandbox="system"), 4) + + def test_system_dead_object(self): + self.marionette.execute_script( + "window.wrappedJSObject.foo = function() { return 'yo' }", + sandbox="system") + self.marionette.execute_script( + "dump(window.wrappedJSObject.foo)", sandbox="system") + + self.marionette.execute_script( + "window.wrappedJSObject.foo = function() { return 'yolo' }", + sandbox="system") + typ = self.marionette.execute_script( + "return typeof window.wrappedJSObject.foo", sandbox="system") + self.assertEqual("function", typ) + obj = self.marionette.execute_script( + "return window.wrappedJSObject.foo.toString()", sandbox="system") + self.assertIn("yolo", obj) + + def test_lasting_side_effects(self): + def send(script): + return self.marionette._send_message( + "executeScript", {"script": script}, key="value") + + send("window.foo = 1") + foo = send("return window.foo") + self.assertEqual(1, foo) + + for property in globals: + exists = send("return typeof {} != 'undefined'".format(property)) + self.assertTrue(exists, "property {} is undefined".format(property)) + + self.assertTrue(send("return typeof Components.utils == 'undefined'")) + self.assertTrue(send("return typeof window.wrappedJSObject == 'undefined'")) + + def test_no_callback(self): + self.assertTrue(self.marionette.execute_script( + "return typeof arguments[0] == 'undefined'")) + + def test_window_set_timeout_is_not_cancelled(self): + def content_timeout_triggered(mn): + return mn.execute_script("return window.n", sandbox=None) > 0 + + # subsequent call to execute_script after this + # should not cancel the setTimeout event + self.marionette.navigate(inline(""" + <script> + window.n = 0; + setTimeout(() => ++window.n, 4000); + </script>""")) + + # as debug builds are inherently slow, + # we need to assert the event did not already fire + self.assertEqual(0, self.marionette.execute_script( + "return window.n", sandbox=None), + "setTimeout already fired") + + # if event was cancelled, this will time out + Wait(self.marionette, timeout=8).until( + content_timeout_triggered, + message="Scheduled setTimeout event was cancelled by call to execute_script") + + def test_privileged_code_inspection(self): + # test permission denied on toString of unload event handler + self.marionette.navigate(inline(""" + <script> + window.addEventListener = (type, handler) => handler.toString(); + </script>""")) + self.marionette.execute_script("", sandbox=None) + + # test inspection of arguments + self.marionette.execute_script("__webDriverArguments.toString()") + + +class TestExecuteChrome(WindowManagerMixin, TestExecuteContent): + + def setUp(self): + super(TestExecuteChrome, self).setUp() + + self.marionette.set_context("chrome") + + def tearDown(self): + super(TestExecuteChrome, self).tearDown() + + def test_permission(self): + self.assertEqual(1, self.marionette.execute_script(""" + var c = Components.classes["@mozilla.org/preferences-service;1"]; return 1;""")) + + @skip_if_mobile("New windows not supported in Fennec") + def test_unmarshal_element_collection(self): + + def open_window_with_js(): + self.marionette.execute_script( + "window.open('chrome://marionette/content/test.xul', 'xul', 'chrome');") + + try: + win = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(win) + + expected = self.marionette.find_elements(By.TAG_NAME, "textbox") + actual = self.marionette.execute_script( + "return document.querySelectorAll('textbox')") + self.assertEqual(expected, actual) + + finally: + self.close_all_windows() + + def test_async_script_timeout(self): + with self.assertRaises(errors.ScriptTimeoutException): + self.marionette.execute_async_script(""" + var cb = arguments[arguments.length - 1]; + setTimeout(function() { cb() }, 250); + """, script_timeout=100) + + @skip_if_mobile("New windows not supported in Fennec") + def test_invalid_chrome_handle(self): + try: + win = self.open_window() + self.marionette.switch_to_window(win) + + # Close new window and don't switch back to the original one + self.marionette.close_chrome_window() + self.assertNotEqual(self.start_window, win) + + # Call execute_script on an invalid chrome handle + with self.marionette.using_context('chrome'): + self.marionette.execute_script(""" + return true; + """) + + finally: + self.close_all_windows() + + def test_lasting_side_effects(self): + pass + + def test_return_web_element(self): + pass + + def test_return_web_element_array(self): + pass + + def test_return_web_element_nodelist(self): + pass + + def test_window_set_timeout_is_not_cancelled(self): + pass + + def test_privileged_code_inspection(self): + pass + + +class TestElementCollections(MarionetteTestCase): + + def assertSequenceIsInstance(self, seq, typ): + for item in seq: + self.assertIsInstance(item, typ) + + def test_array(self): + self.marionette.navigate(inline("<p>foo <p>bar")) + els = self.marionette.execute_script("return Array.from(document.querySelectorAll('p'))") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_all_collection(self): + self.marionette.navigate(inline("<p>foo <p>bar")) + els = self.marionette.execute_script("return document.all") + self.assertIsInstance(els, list) + # <html>, <head>, <body>, <p>, <p> + self.assertEqual(5, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_collection(self): + self.marionette.navigate(inline("<p>foo <p>bar")) + els = self.marionette.execute_script("return document.getElementsByTagName('p')") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_form_controls_collection(self): + self.marionette.navigate(inline("<form><input><input></form>")) + els = self.marionette.execute_script("return document.forms[0].elements") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_options_collection(self): + self.marionette.navigate(inline("<select><option><option></select>")) + els = self.marionette.execute_script("return document.querySelector('select').options") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_node_list(self): + self.marionette.navigate(inline("<p>foo <p>bar")) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_expected.py b/testing/marionette/harness/marionette_harness/tests/unit/test_expected.py new file mode 100644 index 000000000..ff8717c69 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_expected.py @@ -0,0 +1,228 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import urllib + +from marionette_driver import expected +from marionette_driver.by import By + +from marionette_harness import marionette_test + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + +static_element = inline("""<p>foo</p>""") +static_elements = static_element + static_element + +remove_element_by_tag_name = \ + """var el = document.getElementsByTagName('{}')[0]; + document.getElementsByTagName("body")[0].remove(el);""" + +hidden_element = inline("<p style='display: none'>hidden</p>") + +selected_element = inline("<option selected>selected</option>") +unselected_element = inline("<option>unselected</option>") + +enabled_element = inline("<input>") +disabled_element = inline("<input disabled>") + +def no_such_element(marionette): + return marionette.find_element(By.ID, "nosuchelement") + +def no_such_elements(marionette): + return marionette.find_elements(By.ID, "nosuchelement") + +def p(marionette): + return marionette.find_element(By.TAG_NAME, "p") + +def ps(marionette): + return marionette.find_elements(By.TAG_NAME, "p") + +class TestExpected(marionette_test.MarionetteTestCase): + def test_element_present_func(self): + self.marionette.navigate(static_element) + el = expected.element_present(p)(self.marionette) + self.assertIsNotNone(el) + + def test_element_present_locator(self): + self.marionette.navigate(static_element) + el = expected.element_present(By.TAG_NAME, "p")(self.marionette) + self.assertIsNotNone(el) + + def test_element_present_not_present(self): + r = expected.element_present(no_such_element)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_not_present_func(self): + r = expected.element_not_present(no_such_element)(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_element_not_present_locator(self): + r = expected.element_not_present(By.ID, "nosuchelement")(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_element_not_present_is_present(self): + self.marionette.navigate(static_element) + r = expected.element_not_present(p)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_stale(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.assertIsNotNone(el) + self.marionette.execute_script(remove_element_by_tag_name.format("p")) + r = expected.element_stale(el)(self.marionette) + self.assertTrue(r) + + def test_element_stale_is_not_stale(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + r = expected.element_stale(el)(self.marionette) + self.assertFalse(r) + + def test_elements_present_func(self): + self.marionette.navigate(static_elements) + els = expected.elements_present(ps)(self.marionette) + self.assertEqual(len(els), 2) + + def test_elements_present_locator(self): + self.marionette.navigate(static_elements) + els = expected.elements_present(By.TAG_NAME, "p")(self.marionette) + self.assertEqual(len(els), 2) + + def test_elements_present_not_present(self): + r = expected.elements_present(no_such_elements)(self.marionette) + self.assertEqual(r, []) + + def test_elements_not_present_func(self): + r = expected.element_not_present(no_such_elements)(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_elements_not_present_locator(self): + r = expected.element_not_present(By.ID, "nosuchelement")(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_elements_not_present_is_present(self): + self.marionette.navigate(static_elements) + r = expected.elements_not_present(ps)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_displayed(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + visible = expected.element_displayed(el)(self.marionette) + self.assertTrue(visible) + + def test_element_displayed_locator(self): + self.marionette.navigate(static_element) + visible = expected.element_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertTrue(visible) + + def test_element_displayed_when_hidden(self): + self.marionette.navigate(hidden_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + visible = expected.element_displayed(el)(self.marionette) + self.assertFalse(visible) + + def test_element_displayed_when_hidden_locator(self): + self.marionette.navigate(hidden_element) + visible = expected.element_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertFalse(visible) + + def test_element_displayed_when_not_present(self): + self.marionette.navigate("about:blank") + visible = expected.element_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertFalse(visible) + + def test_element_displayed_when_stale_element(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.navigate("about:blank") + missing = expected.element_displayed(el)(self.marionette) + self.assertFalse(missing) + + def test_element_not_displayed(self): + self.marionette.navigate(hidden_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + hidden = expected.element_not_displayed(el)(self.marionette) + self.assertTrue(hidden) + + def test_element_not_displayed_locator(self): + self.marionette.navigate(hidden_element) + hidden = expected.element_not_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertTrue(hidden) + + def test_element_not_displayed_when_visible(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + hidden = expected.element_not_displayed(el)(self.marionette) + self.assertFalse(hidden) + + def test_element_not_displayed_when_visible_locator(self): + self.marionette.navigate(static_element) + hidden = expected.element_not_displayed(By.TAG_NAME, "p")(self.marionette) + self.assertFalse(hidden) + + def test_element_not_displayed_when_stale_element(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.navigate("about:blank") + missing = expected.element_not_displayed(el)(self.marionette) + self.assertTrue(missing) + + def test_element_selected(self): + self.marionette.navigate(selected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + selected = expected.element_selected(el)(self.marionette) + self.assertTrue(selected) + + def test_element_selected_when_not_selected(self): + self.marionette.navigate(unselected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + unselected = expected.element_selected(el)(self.marionette) + self.assertFalse(unselected) + + def test_element_not_selected(self): + self.marionette.navigate(unselected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + unselected = expected.element_not_selected(el)(self.marionette) + self.assertTrue(unselected) + + def test_element_not_selected_when_selected(self): + self.marionette.navigate(selected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + selected = expected.element_not_selected(el)(self.marionette) + self.assertFalse(selected) + + def test_element_enabled(self): + self.marionette.navigate(enabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + enabled = expected.element_enabled(el)(self.marionette) + self.assertTrue(enabled) + + def test_element_enabled_when_disabled(self): + self.marionette.navigate(disabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + disabled = expected.element_enabled(el)(self.marionette) + self.assertFalse(disabled) + + def test_element_not_enabled(self): + self.marionette.navigate(disabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + disabled = expected.element_not_enabled(el)(self.marionette) + self.assertTrue(disabled) + + def test_element_not_enabled_when_enabled(self): + self.marionette.navigate(enabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + enabled = expected.element_not_enabled(el)(self.marionette) + self.assertFalse(enabled) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_expectedfail.py b/testing/marionette/harness/marionette_harness/tests/unit/test_expectedfail.py new file mode 100644 index 000000000..138a36c58 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_expectedfail.py @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestFail(MarionetteTestCase): + def test_fails(self): + # this test is supposed to fail! + self.assertEquals(True, False) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py b/testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py new file mode 100644 index 000000000..f67be9556 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py @@ -0,0 +1,152 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import contextlib +import urllib + +from tempfile import NamedTemporaryFile as tempfile + +from marionette_driver import By, errors, expected +from marionette_driver.wait import Wait +from marionette_harness import MarionetteTestCase, skip + + +single = "data:text/html,{}".format(urllib.quote("<input type=file>")) +multiple = "data:text/html,{}".format(urllib.quote("<input type=file multiple>")) +upload = lambda url: "data:text/html,{}".format(urllib.quote(""" + <form action='{}' method=post enctype='multipart/form-data'> + <input type=file> + <input type=submit> + </form>""".format(url))) + + +class TestFileUpload(MarionetteTestCase): + def test_sets_one_file(self): + self.marionette.navigate(single) + input = self.input + + exp = None + with tempfile() as f: + input.send_keys(f.name) + exp = [f.name] + + files = self.get_file_names(input) + self.assertEqual(len(files), 1) + self.assertFileNamesEqual(files, exp) + + def test_sets_multiple_files(self): + self.marionette.navigate(multiple) + input = self.input + + exp = None + with contextlib.nested(tempfile(), tempfile()) as (a, b): + input.send_keys(a.name) + input.send_keys(b.name) + exp = [a.name, b.name] + + files = self.get_file_names(input) + self.assertEqual(len(files), 2) + self.assertFileNamesEqual(files, exp) + + def test_sets_multiple_indentical_files(self): + self.marionette.navigate(multiple) + input = self.input + + exp = [] + with tempfile() as f: + input.send_keys(f.name) + input.send_keys(f.name) + exp = f.name + + files = self.get_file_names(input) + self.assertEqual(len(files), 2) + self.assertFileNamesEqual(files, exp) + + def test_clear_file(self): + self.marionette.navigate(single) + input = self.input + + with tempfile() as f: + input.send_keys(f.name) + + self.assertEqual(len(self.get_files(input)), 1) + input.clear() + self.assertEqual(len(self.get_files(input)), 0) + + def test_clear_files(self): + self.marionette.navigate(multiple) + input = self.input + + with contextlib.nested(tempfile(), tempfile()) as (a, b): + input.send_keys(a.name) + input.send_keys(b.name) + + self.assertEqual(len(self.get_files(input)), 2) + input.clear() + self.assertEqual(len(self.get_files(input)), 0) + + def test_illegal_file(self): + self.marionette.navigate(single) + with self.assertRaisesRegexp(errors.MarionetteException, "File not found"): + self.input.send_keys("rochefort") + + def test_upload(self): + self.marionette.navigate( + upload(self.marionette.absolute_url("file_upload"))) + url = self.marionette.get_url() + + with tempfile() as f: + f.write("camembert") + f.flush() + self.input.send_keys(f.name) + self.submit.click() + + Wait(self.marionette).until(lambda m: m.get_url() != url) + self.assertIn("multipart/form-data", self.body.text) + + def test_change_event(self): + self.marionette.navigate(single) + self.marionette.execute_script(""" + window.changeEvs = []; + let el = arguments[arguments.length - 1]; + el.addEventListener("change", ev => window.changeEvs.push(ev)); + console.log(window.changeEvs.length); + """, script_args=(self.input,), sandbox=None) + + with tempfile() as f: + self.input.send_keys(f.name) + + nevs = self.marionette.execute_script( + "return window.changeEvs.length", sandbox=None) + self.assertEqual(1, nevs) + + def find_inputs(self): + return self.marionette.find_elements(By.TAG_NAME, "input") + + @property + def input(self): + return self.find_inputs()[0] + + @property + def submit(self): + return self.find_inputs()[1] + + @property + def body(self): + return Wait(self.marionette).until( + expected.element_present(By.TAG_NAME, "body")) + + def get_file_names(self, el): + fl = self.get_files(el) + return [f["name"] for f in fl] + + def get_files(self, el): + return self.marionette.execute_script( + "return arguments[0].files", script_args=[el]) + + def assertFileNamesEqual(self, act, exp): + # File array returned from browser doesn't contain full path names, + # this cuts off the path of the expected files. + filenames = [f.rsplit("/", 0)[-1] for f in act] + self.assertListEqual(filenames, act) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py new file mode 100644 index 000000000..e6b2d63bf --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_findelement_chrome.py @@ -0,0 +1,82 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import NoSuchElementException +from marionette_driver.marionette import HTMLElement + +from marionette_harness import MarionetteTestCase + + +class TestElementsChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_id(self): + el = self.marionette.execute_script("return window.document.getElementById('textInput');") + found_el = self.marionette.find_element(By.ID, "textInput") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_that_we_can_find_elements_from_css_selectors(self): + el = self.marionette.execute_script("return window.document.getElementById('textInput');") + found_el = self.marionette.find_element(By.CSS_SELECTOR, "#textInput") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_child_element(self): + el = self.marionette.find_element(By.ID, "textInput") + parent = self.marionette.find_element(By.ID, "things") + found_el = parent.find_element(By.TAG_NAME, "textbox") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_child_elements(self): + el = self.marionette.find_element(By.ID, "textInput3") + parent = self.marionette.find_element(By.ID, "things") + found_els = parent.find_elements(By.TAG_NAME, "textbox") + self.assertTrue(el.id in [found_el.id for found_el in found_els]) + + def test_tag_name(self): + el = self.marionette.execute_script("return window.document.getElementsByTagName('vbox')[0];") + found_el = self.marionette.find_element(By.TAG_NAME, "vbox") + self.assertEquals('vbox', found_el.tag_name) + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_class_name(self): + el = self.marionette.execute_script("return window.document.getElementsByClassName('asdf')[0];") + found_el = self.marionette.find_element(By.CLASS_NAME, "asdf") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_xpath(self): + el = self.marionette.execute_script("return window.document.getElementById('testBox');") + found_el = self.marionette.find_element(By.XPATH, "id('testBox')") + self.assertEqual(HTMLElement, type(found_el)) + self.assertEqual(el, found_el) + + def test_not_found(self): + self.marionette.timeout.implicit = 1 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page") + self.marionette.timeout.implicit = 0 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page") + + def test_timeout(self): + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "myid") + self.marionette.timeout.implicit = 4 + self.marionette.execute_script("window.setTimeout(function() {var b = window.document.createElement('button'); b.id = 'myid'; document.getElementById('things').appendChild(b);}, 1000)") + self.assertEqual(HTMLElement, type(self.marionette.find_element(By.ID, "myid"))) + self.marionette.execute_script("window.document.getElementById('things').removeChild(window.document.getElementById('myid'));") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_geckoinstance.py b/testing/marionette/harness/marionette_harness/tests/unit/test_geckoinstance.py new file mode 100644 index 000000000..540550296 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_geckoinstance.py @@ -0,0 +1,25 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.geckoinstance import apps, GeckoInstance + +from marionette_harness import MarionetteTestCase + + +class TestGeckoInstance(MarionetteTestCase): + + def test_create(self): + """Test that the correct gecko instance is determined.""" + for app in apps: + # If app has been specified we directly return the appropriate instance class + self.assertEqual(type(GeckoInstance.create(app=app, bin="n/a")), + apps[app]) + + # Unknown applications and binaries should fail + self.assertRaises(NotImplementedError, GeckoInstance.create, + app="n/a", bin=self.marionette.bin) + self.assertRaises(NotImplementedError, GeckoInstance.create, + bin="n/a") + self.assertRaises(NotImplementedError, GeckoInstance.create, + bin=None) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_getactiveframe_oop.py b/testing/marionette/harness/marionette_harness/tests/unit/test_getactiveframe_oop.py new file mode 100644 index 000000000..9325d4892 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_getactiveframe_oop.py @@ -0,0 +1,93 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +OOP_BY_DEFAULT = "dom.ipc.browser_frames.oop_by_default" +BROWSER_FRAMES_ENABLED = "dom.mozBrowserFramesEnabeld" + + +class TestGetActiveFrameOOP(MarionetteTestCase): + def setUp(self): + super(TestGetActiveFrameOOP, self).setUp() + with self.marionette.using_context("chrome"): + self.oop_by_default = self.marionette.get_pref(OOP_BY_DEFAULT) + self.mozBrowserFramesEnabled = self.marionette.get_pref(BROWSER_FRAMES_ENABLED) + self.marionette.set_pref(OOP_BY_DEFAULT, True) + self.marionette.set_pref(BROWSER_FRAMES_ENABLED, True) + + def tearDown(self): + with self.marionette.using_context("chrome"): + if self.oop_by_default is None: + self.marionette.clear_pref(OOP_BY_DEFAULT) + else: + self.marionette.set_pref(OOP_BY_DEFAULT, self.oop_by_default) + + if self.mozBrowserFramesEnabled is None: + self.marionette.clear_pref(BROWSER_FRAMES_ENABLED) + else: + self.marionette.set_pref(BROWSER_FRAMES_ENABLED, self.mozBrowserFramesEnabled) + + def test_active_frame_oop(self): + self.marionette.navigate(self.marionette.absolute_url("test.html")) + self.marionette.push_permission('browser', True) + + # Create first OOP frame + self.marionette.execute_script(""" + let iframe1 = document.createElement("iframe"); + iframe1.id = "remote_iframe1"; + iframe1.setAttribute('remote', true); + iframe1.setAttribute('mozbrowser', true); + iframe1.style.height = "100px"; + iframe1.style.width = "100%%"; + iframe1.src = "{}"; + document.body.appendChild(iframe1); + """.format(self.marionette.absolute_url("test_oop_1.html"))) + + # Currently no active frame + self.assertEqual(self.marionette.get_active_frame(), None) + self.assertTrue("test.html" in self.marionette.get_url()) + + # Switch to iframe1, get active frame + frame = self.marionette.find_element(By.ID, 'remote_iframe1') + self.marionette.switch_to_frame(frame) + active_frame1 = self.marionette.get_active_frame() + self.assertNotEqual(active_frame1.id, None) + + # Switch to top-level then back to active frame, verify correct frame + self.marionette.switch_to_frame() + self.marionette.switch_to_frame(active_frame1) + self.assertTrue("test_oop_1.html" in self.marionette.execute_script("return document.wrappedJSObject.location.href")) + + # Create another OOP frame + self.marionette.switch_to_frame() + self.marionette.execute_script(""" + let iframe2 = document.createElement("iframe"); + iframe2.setAttribute('mozbrowser', true); + iframe2.setAttribute('remote', true); + iframe2.id = "remote_iframe2"; + iframe2.style.height = "100px"; + iframe2.style.width = "100%%"; + iframe2.src = "{}"; + document.body.appendChild(iframe2); + """.format(self.marionette.absolute_url("test_oop_2.html"))) + + # Switch to iframe2, get active frame + frame2 = self.marionette.find_element(By.ID, 'remote_iframe2') + self.marionette.switch_to_frame(frame2) + active_frame2 = self.marionette.get_active_frame() + self.assertNotEqual(active_frame2.id, None) + + # Switch to top-level then back to active frame 1, verify correct frame + self.marionette.switch_to_frame() + self.marionette.switch_to_frame(active_frame1) + self.assertTrue("test_oop_1.html" in self.marionette.execute_script("return document.wrappedJSObject.location.href")) + + # Switch to top-level then back to active frame 2, verify correct frame + self.marionette.switch_to_frame() + self.marionette.switch_to_frame(active_frame2) + self.assertTrue("test_oop_2.html" in self.marionette.execute_script("return document.wrappedJSObject.location.href")) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_implicit_waits.py b/testing/marionette/harness/marionette_harness/tests/unit/test_implicit_waits.py new file mode 100644 index 000000000..954443ac3 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_implicit_waits.py @@ -0,0 +1,26 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import NoSuchElementException + +from marionette_harness import MarionetteTestCase + + +class TestImplicitWaits(MarionetteTestCase): + def test_implicitly_wait_for_single_element(self): + test_html = self.marionette.absolute_url("test_dynamic.html") + self.marionette.navigate(test_html) + add = self.marionette.find_element(By.ID, "adder") + self.marionette.timeout.implicit = 30 + add.click() + # all is well if this does not throw + self.marionette.find_element(By.ID, "box0") + + def test_implicit_wait_reaches_timeout(self): + test_html = self.marionette.absolute_url("test_dynamic.html") + self.marionette.navigate(test_html) + self.marionette.timeout.implicit = 3 + with self.assertRaises(NoSuchElementException): + self.marionette.find_element(By.ID, "box0") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py b/testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py new file mode 100644 index 000000000..e86de2bd5 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py @@ -0,0 +1,138 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os + +from marionette_driver.by import By +from marionette_driver.errors import JavascriptException + +from marionette_harness import ( + MarionetteTestCase, + skip_if_chrome, + skip_if_mobile, + WindowManagerMixin, +) + + +class TestImportScriptContent(WindowManagerMixin, MarionetteTestCase): + contexts = set(["chrome", "content"]) + + script_file = os.path.abspath( + os.path.join(__file__, os.path.pardir, "importscript.js")) + another_script_file = os.path.abspath( + os.path.join(__file__, os.path.pardir, "importanotherscript.js")) + + def setUp(self): + super(TestImportScriptContent, self).setUp() + + for context in self.contexts: + with self.marionette.using_context(context): + self.marionette.clear_imported_scripts() + self.reset_context() + + def tearDown(self): + self.close_all_windows() + + super(TestImportScriptContent, self).tearDown() + + def reset_context(self): + self.marionette.set_context("content") + + @property + def current_context(self): + return self.marionette._send_message("getContext", key="value") + + @property + def other_context(self): + return self.contexts.copy().difference([self.current_context]).pop() + + def is_defined(self, symbol): + return self.marionette.execute_script( + "return typeof {} != 'undefined'".format(symbol)) + + def assert_defined(self, symbol, msg=None): + if msg is None: + msg = "Expected symbol {} to be defined".format(symbol) + self.assertTrue(self.is_defined(symbol), msg) + + def assert_undefined(self, symbol, msg=None): + if msg is None: + msg = "Expected symbol {} to be undefined".format(symbol) + self.assertFalse(self.is_defined(symbol), msg) + + def assert_scripts_cleared(self): + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + self.marionette.clear_imported_scripts() + self.assert_undefined("testFunc") + + def test_import_script(self): + self.marionette.import_script(self.script_file) + self.assertEqual( + "i'm a test function!", self.marionette.execute_script("return testFunc();")) + self.assertEqual("i'm a test function!", self.marionette.execute_async_script( + "marionetteScriptFinished(testFunc());")) + + def test_import_script_twice(self): + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + + # TODO(ato): Note that the WebDriver command primitives + # does not allow us to check what scripts have been imported. + # I suspect we must to do this through an xpcshell test. + + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + + def test_import_script_and_clear(self): + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + self.marionette.clear_imported_scripts() + self.assert_scripts_cleared() + self.assert_undefined("testFunc") + with self.assertRaises(JavascriptException): + self.marionette.execute_script("return testFunc()") + with self.assertRaises(JavascriptException): + self.marionette.execute_async_script( + "marionetteScriptFinished(testFunc())") + + def test_clear_scripts_in_other_context(self): + self.marionette.import_script(self.script_file) + self.assert_defined("testFunc") + + # clearing other context's script file should not affect ours + with self.marionette.using_context(self.other_context): + self.marionette.clear_imported_scripts() + self.assert_undefined("testFunc") + + self.assert_defined("testFunc") + + def test_multiple_imports(self): + self.marionette.import_script(self.script_file) + self.marionette.import_script(self.another_script_file) + self.assert_defined("testFunc") + self.assert_defined("testAnotherFunc") + + @skip_if_chrome("Needs content scope") + @skip_if_mobile("New windows not supported in Fennec") + def test_imports_apply_globally(self): + self.marionette.navigate( + self.marionette.absolute_url("test_windows.html")) + + def open_window_with_link(): + self.marionette.find_element(By.LINK_TEXT, "Open new window").click() + + new_window = self.open_window(trigger=open_window_with_link) + self.marionette.switch_to_window(new_window) + + self.marionette.import_script(self.script_file) + self.marionette.close_chrome_window() + + self.marionette.switch_to_window(self.start_window) + self.assert_defined("testFunc") + + +class TestImportScriptChrome(TestImportScriptContent): + def reset_context(self): + self.marionette.set_context("chrome") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py b/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py new file mode 100644 index 000000000..60e38b3c6 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py @@ -0,0 +1,91 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.keys import Keys +from marionette_driver.marionette import Actions + +from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin + + +class TestKeyActions(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestKeyActions, self).setUp() + if self.marionette.session_capabilities["platformName"] == "darwin": + self.mod_key = Keys.META + else: + self.mod_key = Keys.CONTROL + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + self.reporter_element = self.marionette.find_element(By.ID, "keyReporter") + self.reporter_element.click() + self.key_action = Actions(self.marionette) + + @property + def key_reporter_value(self): + return self.reporter_element.get_property("value") + + def test_key_action_basic_input(self): + self.key_action.key_down("a").key_down("b").key_down("c").perform() + self.assertEqual(self.key_reporter_value, "abc") + + def test_upcase_input(self): + (self.key_action.key_down(Keys.SHIFT) + .key_down("a") + .key_up(Keys.SHIFT) + .key_down("b") + .key_down("c") + .perform()) + self.assertEqual(self.key_reporter_value, "Abc") + + def test_replace_input(self): + self.key_action.key_down("a").key_down("b").key_down("c").perform() + self.assertEqual(self.key_reporter_value, "abc") + (self.key_action.key_down(self.mod_key) + .key_down("a") + .key_up(self.mod_key) + .key_down("x") + .perform()) + self.assertEqual(self.key_reporter_value, "x") + + def test_clear_input(self): + self.key_action.key_down("a").key_down("b").key_down("c").perform() + self.assertEqual(self.key_reporter_value, "abc") + (self.key_action.key_down(self.mod_key) + .key_down("a") + .key_down("x") + .perform()) + self.assertEqual(self.key_reporter_value, "") + self.key_action.key_down("a").key_down("b").key_down("c").perform() + self.assertEqual(self.key_reporter_value, "abc") + + def test_input_with_wait(self): + self.key_action.key_down("a").key_down("b").key_down("c").perform() + (self.key_action.key_down(self.mod_key) + .key_down("a") + .wait(.5) + .key_down("x") + .perform()) + self.assertEqual(self.key_reporter_value, "") + + @skip_if_mobile("Interacting with chrome windows not available for Fennec") + def test_open_in_new_window_shortcut(self): + + def open_window_with_action(): + el = self.marionette.find_element(By.ID, "updatediv") + # Ensure that the element is in the current view port because press() doesn't + # handle that inside the action chain (bug 1295538). + self.marionette.execute_script('arguments[0].scrollIntoView()', script_args=[el]) + (self.key_action.key_down(Keys.SHIFT) + .press(el) + .release() + .key_up(Keys.SHIFT) + .perform()) + + new_window = self.open_window(trigger=open_window_with_action) + self.marionette.switch_to_window(new_window) + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + self.assertEqual(self.key_reporter_value, "") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_localization.py b/testing/marionette/harness/marionette_harness/tests/unit/test_localization.py new file mode 100644 index 000000000..1b89f6f34 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_localization.py @@ -0,0 +1,56 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import By +from marionette_driver.errors import ( + InvalidArgumentException, + NoSuchElementException, + UnknownException +) +from marionette_driver.localization import L10n + +from marionette_harness import MarionetteTestCase + + +class TestL10n(MarionetteTestCase): + + def setUp(self): + super(TestL10n, self).setUp() + + self.l10n = L10n(self.marionette) + + def test_localize_entity(self): + dtds = ['chrome://marionette/content/test_dialog.dtd'] + value = self.l10n.localize_entity(dtds, 'testDialog.title') + + self.assertEqual(value, 'Test Dialog') + + def test_localize_entity_invalid_arguments(self): + dtds = ['chrome://marionette/content/test_dialog.dtd'] + + self.assertRaises(NoSuchElementException, + self.l10n.localize_entity, dtds, 'notExistent') + self.assertRaises(InvalidArgumentException, + self.l10n.localize_entity, dtds[0], 'notExistent') + self.assertRaises(InvalidArgumentException, + self.l10n.localize_entity, dtds, True) + + def test_localize_property(self): + properties = ['chrome://marionette/content/test_dialog.properties'] + + value = self.l10n.localize_property(properties, 'testDialog.title') + self.assertEqual(value, 'Test Dialog') + + self.assertRaises(NoSuchElementException, + self.l10n.localize_property, properties, 'notExistent') + + def test_localize_property_invalid_arguments(self): + properties = ['chrome://global/locale/filepicker.properties'] + + self.assertRaises(NoSuchElementException, + self.l10n.localize_property, properties, 'notExistent') + self.assertRaises(InvalidArgumentException, + self.l10n.localize_property, properties[0], 'notExistent') + self.assertRaises(InvalidArgumentException, + self.l10n.localize_property, properties, True) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_log.py b/testing/marionette/harness/marionette_harness/tests/unit/test_log.py new file mode 100644 index 000000000..dd3cf82b4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_log.py @@ -0,0 +1,64 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestLog(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + # clears log cache + self.marionette.get_logs() + + def test_log(self): + self.marionette.log("foo") + logs = self.marionette.get_logs() + self.assertEqual("INFO", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_log_level(self): + self.marionette.log("foo", "ERROR") + logs = self.marionette.get_logs() + self.assertEqual("ERROR", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_clear(self): + self.marionette.log("foo") + self.assertEqual(1, len(self.marionette.get_logs())) + self.assertEqual(0, len(self.marionette.get_logs())) + + def test_multiple_entries(self): + self.marionette.log("foo") + self.marionette.log("bar") + self.assertEqual(2, len(self.marionette.get_logs())) + + def test_log_from_sync_script(self): + self.marionette.execute_script("log('foo')") + logs = self.marionette.get_logs() + self.assertEqual("INFO", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_log_from_sync_script_level(self): + self.marionette.execute_script("log('foo', 'ERROR')") + logs = self.marionette.get_logs() + self.assertEqual("ERROR", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_log_from_async_script(self): + self.marionette.execute_async_script("log('foo'); arguments[0]();") + logs = self.marionette.get_logs() + self.assertEqual("INFO", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + def test_log_from_async_script_variable_arguments(self): + self.marionette.execute_async_script("log('foo', 'ERROR'); arguments[0]();") + logs = self.marionette.get_logs() + self.assertEqual("ERROR", logs[0][0]) + self.assertEqual("foo", logs[0][1]) + + +class TestLogChrome(TestLog): + def setUp(self): + TestLog.setUp(self) + self.marionette.set_context("chrome") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_marionette.py b/testing/marionette/harness/marionette_harness/tests/unit/test_marionette.py new file mode 100644 index 000000000..e68312872 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_marionette.py @@ -0,0 +1,67 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import itertools +import time + +from marionette_driver import errors + +from marionette_harness import MarionetteTestCase, run_if_manage_instance, skip_if_mobile + + +class TestMarionette(MarionetteTestCase): + + def test_correct_test_name(self): + """Test that the correct test name gets set.""" + expected_test_name = '{module}.py {cls}.{func}'.format( + module=__name__, + cls=self.__class__.__name__, + func=self.test_correct_test_name.__name__, + ) + + self.assertEqual(self.marionette.test_name, expected_test_name) + + @run_if_manage_instance("Only runnable if Marionette manages the instance") + @skip_if_mobile("Bug 1322993 - Missing temporary folder") + def test_wait_for_port_non_existing_process(self): + """Test that wait_for_port doesn't run into a timeout if instance is not running.""" + self.marionette.quit() + self.assertIsNotNone(self.marionette.instance.runner.returncode) + start_time = time.time() + self.assertFalse(self.marionette.wait_for_port(timeout=5)) + self.assertLess(time.time() - start_time, 5) + + +class TestProtocol2Errors(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.op = self.marionette.protocol + self.marionette.protocol = 2 + + def tearDown(self): + self.marionette.protocol = self.op + MarionetteTestCase.tearDown(self) + + def test_malformed_packet(self): + req = ["error", "message", "stacktrace"] + ps = [] + for p in [p for i in range(0, len(req) + 1) for p in itertools.permutations(req, i)]: + ps.append(dict((x, None) for x in p)) + + for p in filter(lambda p: len(p) < 3, ps): + self.assertRaises(KeyError, self.marionette._handle_error, p) + + def test_known_error_status(self): + with self.assertRaises(errors.NoSuchElementException): + self.marionette._handle_error( + {"error": errors.NoSuchElementException.status, + "message": None, + "stacktrace": None}) + + def test_unknown_error_status(self): + with self.assertRaises(errors.MarionetteException): + self.marionette._handle_error( + {"error": "barbera", + "message": None, + "stacktrace": None}) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py b/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py new file mode 100644 index 000000000..f7108bdff --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py @@ -0,0 +1,198 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import NoAlertPresentException, ElementNotInteractableException +from marionette_driver.marionette import Alert +from marionette_driver.wait import Wait + +from marionette_harness import MarionetteTestCase, skip_if_e10s + + +class TestTabModals(MarionetteTestCase): + + def setUp(self): + super(TestTabModals, self).setUp() + self.marionette.set_pref("prompts.tab_modal.enabled", True) + self.marionette.navigate(self.marionette.absolute_url('modal_dialogs.html')) + + def tearDown(self): + # Ensure an alert is absent before proceeding past this test. + Wait(self.marionette).until(lambda _: not self.alert_present()) + self.marionette.execute_script("window.onbeforeunload = null;") + self.marionette.clear_pref("prompts.tab_modal.enabled") + super(TestTabModals, self).tearDown() + + def alert_present(self): + try: + Alert(self.marionette).text + return True + except NoAlertPresentException: + return False + + def wait_for_alert(self): + Wait(self.marionette).until(lambda _: self.alert_present()) + + def test_no_alert_raises(self): + self.assertRaises(NoAlertPresentException, Alert(self.marionette).accept) + self.assertRaises(NoAlertPresentException, Alert(self.marionette).dismiss) + + def test_alert_accept(self): + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.accept() + + def test_alert_dismiss(self): + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.dismiss() + + def test_confirm_accept(self): + self.marionette.find_element(By.ID, 'modal-confirm').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.accept() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'confirm-result').text == 'true') + + def test_confirm_dismiss(self): + self.marionette.find_element(By.ID, 'modal-confirm').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.dismiss() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'confirm-result').text == 'false') + + def test_prompt_accept(self): + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.accept() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'prompt-result').text == '') + + def test_prompt_dismiss(self): + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.dismiss() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'prompt-result').text == 'null') + + def test_alert_text(self): + with self.assertRaises(NoAlertPresentException): + alert = self.marionette.switch_to_alert() + alert.text + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertEqual(alert.text, 'Marionette alert') + alert.accept() + + def test_prompt_text(self): + with self.assertRaises(NoAlertPresentException): + alert = self.marionette.switch_to_alert() + alert.text + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertEqual(alert.text, 'Marionette prompt') + alert.accept() + + def test_confirm_text(self): + with self.assertRaises(NoAlertPresentException): + alert = self.marionette.switch_to_alert() + alert.text + self.marionette.find_element(By.ID, 'modal-confirm').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertEqual(alert.text, 'Marionette confirm') + alert.accept() + + def test_set_text_throws(self): + self.assertRaises(NoAlertPresentException, Alert(self.marionette).send_keys, "Foo") + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertRaises(ElementNotInteractableException, alert.send_keys, "Foo") + alert.accept() + + def test_set_text_accept(self): + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.send_keys("Some text!"); + alert.accept() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'prompt-result').text == 'Some text!') + + def test_set_text_dismiss(self): + self.marionette.find_element(By.ID, 'modal-prompt').click() + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + alert.send_keys("Some text!"); + alert.dismiss() + self.wait_for_condition(lambda mn: mn.find_element(By.ID, 'prompt-result').text == 'null') + + def test_onbeforeunload_dismiss(self): + start_url = self.marionette.get_url() + self.marionette.find_element(By.ID, 'onbeforeunload-handler').click() + self.wait_for_condition( + lambda mn: mn.execute_script(""" + return window.onbeforeunload !== null; + """)) + self.marionette.navigate("about:blank") + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertTrue(alert.text.startswith("This page is asking you to confirm")) + alert.dismiss() + self.assertTrue(self.marionette.get_url().startswith(start_url)) + + def test_onbeforeunload_accept(self): + self.marionette.find_element(By.ID, 'onbeforeunload-handler').click() + self.wait_for_condition( + lambda mn: mn.execute_script(""" + return window.onbeforeunload !== null; + """)) + self.marionette.navigate("about:blank") + self.wait_for_alert() + alert = self.marionette.switch_to_alert() + self.assertTrue(alert.text.startswith("This page is asking you to confirm")) + alert.accept() + self.wait_for_condition(lambda mn: mn.get_url() == "about:blank") + + @skip_if_e10s("Bug 1325044") + def test_unrelated_command_when_alert_present(self): + click_handler = self.marionette.find_element(By.ID, 'click-handler') + text = self.marionette.find_element(By.ID, 'click-result').text + self.assertEqual(text, '') + + self.marionette.find_element(By.ID, 'modal-alert').click() + self.wait_for_alert() + + # Commands succeed, but because the dialog blocks the event loop, + # our actions aren't reflected on the page. + text = self.marionette.find_element(By.ID, 'click-result').text + self.assertEqual(text, '') + click_handler.click() + text = self.marionette.find_element(By.ID, 'click-result').text + self.assertEqual(text, '') + + alert = self.marionette.switch_to_alert() + alert.accept() + + Wait(self.marionette).until(lambda _: not self.alert_present()) + + click_handler.click() + text = self.marionette.find_element(By.ID, 'click-result').text + self.assertEqual(text, 'result') + + +class TestGlobalModals(TestTabModals): + + def setUp(self): + super(TestGlobalModals, self).setUp() + self.marionette.set_pref("prompts.tab_modal.enabled", False) + + def test_unrelated_command_when_alert_present(self): + # The assumptions in this test do not hold on certain platforms, and not when + # e10s is enabled. + pass diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py b/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py new file mode 100644 index 000000000..246068215 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py @@ -0,0 +1,114 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.keys import Keys +from marionette_driver.marionette import Actions + +from marionette_harness import MarionetteTestCase + + +class TestMouseAction(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + if self.marionette.session_capabilities["platformName"] == "darwin": + self.mod_key = Keys.META + else: + self.mod_key = Keys.CONTROL + self.action = Actions(self.marionette) + + def test_click_action(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + link = self.marionette.find_element(By.ID, "mozLink") + self.action.click(link).perform() + self.assertEqual("Clicked", self.marionette.execute_script( + "return document.getElementById('mozLink').innerHTML")) + + def test_clicking_element_out_of_view_succeeds(self): + # The action based click doesn"t check for visibility. + test_html = self.marionette.absolute_url("hidden.html") + self.marionette.navigate(test_html) + el = self.marionette.find_element(By.ID, "child") + self.action.click(el).perform() + + def test_double_click_action(self): + test_html = self.marionette.absolute_url("double_click.html") + self.marionette.navigate(test_html) + el = self.marionette.find_element(By.ID, "one-word-div") + self.action.double_click(el).perform() + el.send_keys(self.mod_key + "c") + rel = self.marionette.find_element(By.ID, "input-field") + rel.send_keys(self.mod_key + "v") + self.assertEqual("zyxw", rel.get_property("value")) + + def test_context_click_action(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + click_el = self.marionette.find_element(By.ID, "resultContainer") + + def context_menu_state(): + with self.marionette.using_context("chrome"): + cm_el = self.marionette.find_element(By.ID, "contentAreaContextMenu") + return cm_el.get_property("state") + + self.assertEqual("closed", context_menu_state()) + self.action.context_click(click_el).perform() + self.wait_for_condition(lambda _: context_menu_state() == "open") + + with self.marionette.using_context("chrome"): + self.marionette.find_element(By.ID, "main-window").send_keys(Keys.ESCAPE) + self.wait_for_condition(lambda _: context_menu_state() == "closed") + + def test_middle_click_action(self): + test_html = self.marionette.absolute_url("clicks.html") + self.marionette.navigate(test_html) + + self.marionette.find_element(By.ID, "addbuttonlistener").click() + + el = self.marionette.find_element(By.ID, "showbutton") + self.action.middle_click(el).perform() + + self.wait_for_condition(lambda _: el.get_property("innerHTML") == "1") + + def test_chrome_click(self): + self.marionette.navigate("about:blank") + data_uri = "data:text/html,<html></html>" + with self.marionette.using_context("chrome"): + urlbar = self.marionette.find_element(By.ID, "urlbar") + urlbar.send_keys(data_uri) + go_button = self.marionette.find_element(By.ID, "urlbar-go-button") + self.action.click(go_button).perform() + self.wait_for_condition(lambda mn: mn.get_url() == data_uri) + + def test_chrome_double_click(self): + self.marionette.navigate("about:blank") + test_word = "quux" + + with self.marionette.using_context("chrome"): + urlbar = self.marionette.find_element(By.ID, "urlbar") + self.assertEqual("", urlbar.get_property("value")) + + urlbar.send_keys(test_word) + self.assertEqual(urlbar.get_property("value"), test_word) + (self.action.double_click(urlbar).perform() + .key_down(self.mod_key) + .key_down("x").perform()) + self.assertEqual(urlbar.get_property("value"), "") + + def test_chrome_context_click_action(self): + self.marionette.set_context("chrome") + def context_menu_state(): + cm_el = self.marionette.find_element(By.ID, "tabContextMenu") + return cm_el.get_property("state") + + currtab = self.marionette.execute_script("return gBrowser.selectedTab") + self.assertEqual("closed", context_menu_state()) + self.action.context_click(currtab).perform() + self.wait_for_condition(lambda _: context_menu_state() == "open") + + (self.marionette.find_element(By.ID, "main-window") + .send_keys(Keys.ESCAPE)) + + self.wait_for_condition(lambda _: context_menu_state() == "closed") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py new file mode 100644 index 000000000..75ed37ecd --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py @@ -0,0 +1,447 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import contextlib +import time +import urllib + +from marionette_driver import By, errors, expected, Wait +from marionette_harness import ( + MarionetteTestCase, + run_if_e10s, + run_if_manage_instance, + skip, + skip_if_mobile, + WindowManagerMixin, +) + + +def inline(doc): + return "data:text/html;charset=utf-8,%s" % urllib.quote(doc) + + +class TestBackForwardNavigation(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestBackForwardNavigation, self).setUp() + + self.test_page = self.marionette.absolute_url('test.html') + + def open_with_link(): + link = self.marionette.find_element(By.ID, "new-blank-tab") + link.click() + + # Always use a blank new tab for an empty history + self.marionette.navigate(self.marionette.absolute_url("windowHandles.html")) + self.new_tab = self.open_tab(open_with_link) + self.marionette.switch_to_window(self.new_tab) + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + lambda _: self.history_length == 1, + message="The newly opened tab doesn't have a browser history length of 1") + + def tearDown(self): + self.marionette.switch_to_parent_frame() + self.close_all_tabs() + + super(TestBackForwardNavigation, self).tearDown() + + @property + def history_length(self): + return self.marionette.execute_script("return window.history.length;") + + def run_test(self, test_pages): + # Helper method to run simple back and forward testcases. + for index, page in enumerate(test_pages): + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.navigate(page["url"]) + else: + self.marionette.navigate(page["url"]) + self.assertEqual(page["url"], self.marionette.get_url()) + self.assertEqual(self.history_length, index + 1) + + for page in test_pages[-2::-1]: + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.go_back() + else: + self.marionette.go_back() + self.assertEqual(page["url"], self.marionette.get_url()) + + for page in test_pages[1::]: + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.go_forward() + else: + self.marionette.go_forward() + self.assertEqual(page["url"], self.marionette.get_url()) + + def test_no_history_items(self): + # Both methods should not raise a failure if no navigation is possible + self.marionette.go_back() + self.marionette.go_forward() + + def test_data_urls(self): + test_pages = [ + {"url": inline("<p>foobar</p>")}, + {"url": self.test_page}, + {"url": inline("<p>foobar</p>")}, + ] + self.run_test(test_pages) + + def test_same_document_hash_change(self): + test_pages = [ + {"url": "{}#23".format(self.test_page)}, + {"url": self.test_page}, + {"url": "{}#42".format(self.test_page)}, + ] + self.run_test(test_pages) + + @skip("Causes crashes for JS GC (bug 1344863) and a11y (bug 1344868)") + def test_frameset(self): + test_pages = [ + {"url": self.marionette.absolute_url("frameset.html")}, + {"url": self.test_page}, + {"url": self.marionette.absolute_url("frameset.html")}, + ] + self.run_test(test_pages) + + def test_frameset_after_navigating_in_frame(self): + test_element_locator = (By.ID, "email") + + self.marionette.navigate(self.test_page) + self.assertEqual(self.marionette.get_url(), self.test_page) + self.assertEqual(self.history_length, 1) + page = self.marionette.absolute_url("frameset.html") + self.marionette.navigate(page) + self.assertEqual(self.marionette.get_url(), page) + self.assertEqual(self.history_length, 2) + frame = self.marionette.find_element(By.ID, "fifth") + self.marionette.switch_to_frame(frame) + link = self.marionette.find_element(By.ID, "linkId") + link.click() + + # We cannot use get_url() to wait until the target page has been loaded, + # because it will return the URL of the top browsing context and doesn't + # wait for the page load to be complete. + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + expected.element_present(*test_element_locator), + message="Target element 'email' has not been found") + self.assertEqual(self.history_length, 3) + + # Go back to the frame the click navigated away from + self.marionette.go_back() + self.assertEqual(self.marionette.get_url(), page) + with self.assertRaises(errors.NoSuchElementException): + self.marionette.find_element(*test_element_locator) + + # Go back to the non-frameset page + self.marionette.switch_to_parent_frame() + self.marionette.go_back() + self.assertEqual(self.marionette.get_url(), self.test_page) + + # Go forward to the frameset page + self.marionette.go_forward() + self.assertEqual(self.marionette.get_url(), page) + + # Go forward to the frame the click navigated to + # TODO: See above for automatic browser context switches. Hard to do here + frame = self.marionette.find_element(By.ID, "fifth") + self.marionette.switch_to_frame(frame) + self.marionette.go_forward() + self.marionette.find_element(*test_element_locator) + self.assertEqual(self.marionette.get_url(), page) + + def test_image_document_to_html(self): + test_pages = [ + {"url": self.marionette.absolute_url('black.png')}, + {"url": self.test_page}, + {"url": self.marionette.absolute_url('white.png')}, + ] + self.run_test(test_pages) + + def test_image_document_to_image_document(self): + test_pages = [ + {"url": self.marionette.absolute_url('black.png')}, + {"url": self.marionette.absolute_url('white.png')}, + ] + self.run_test(test_pages) + + @run_if_e10s("Requires e10s mode enabled") + def test_remoteness_change(self): + # TODO: Verify that a remoteness change happened + # like: self.assertNotEqual(self.marionette.current_window_handle, self.new_tab) + + # about:robots is always a non-remote page for now + test_pages = [ + {"url": "about:robots"}, + {"url": self.test_page}, + {"url": "about:robots"}, + ] + self.run_test(test_pages) + + def test_navigate_to_requested_about_page_after_error_page(self): + test_pages = [ + {"url": "about:neterror"}, + {"url": self.marionette.absolute_url("test.html")}, + {"url": "about:blocked"}, + ] + self.run_test(test_pages) + + def test_timeout_error(self): + # Bug 1354908 - Disabled on Windows XP due to intermittent failures + caps = self.marionette.session_capabilities + if caps["platformName"] == "windows_nt" and float(caps["platformVersion"]) < 6: + return + + urls = [ + self.marionette.absolute_url('slow'), + self.test_page, + self.marionette.absolute_url('slow'), + ] + + # First, load all pages completely to get them added to the cache + for index, url in enumerate(urls): + self.marionette.navigate(url) + self.assertEqual(url, self.marionette.get_url()) + self.assertEqual(self.history_length, index + 1) + + self.marionette.go_back() + self.assertEqual(urls[1], self.marionette.get_url()) + + # Force triggering a timeout error + self.marionette.timeout.page_load = 0.1 + with self.assertRaises(errors.TimeoutException): + self.marionette.go_back() + self.assertEqual(urls[0], self.marionette.get_url()) + self.marionette.timeout.page_load = 300000 + + self.marionette.go_forward() + self.assertEqual(urls[1], self.marionette.get_url()) + + # Force triggering a timeout error + self.marionette.timeout.page_load = 0.1 + with self.assertRaises(errors.TimeoutException): + self.marionette.go_forward() + self.assertEqual(urls[2], self.marionette.get_url()) + self.marionette.timeout.page_load = 300000 + + def test_certificate_error(self): + test_pages = [ + {"url": self.fixtures.where_is("/test.html", on="https"), + "error": errors.InsecureCertificateException}, + {"url": self.test_page}, + {"url": self.fixtures.where_is("/test.html", on="https"), + "error": errors.InsecureCertificateException}, + ] + self.run_test(test_pages) + + +class TestNavigate(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestNavigate, self).setUp() + + self.marionette.navigate("about:") + self.test_doc = self.marionette.absolute_url("test.html") + self.iframe_doc = self.marionette.absolute_url("test_iframe.html") + + def tearDown(self): + self.marionette.timeout.reset() + self.close_all_tabs() + + super(TestNavigate, self).tearDown() + + @property + def location_href(self): + # Windows 8 has recently seen a proliferation of intermittent + # test failures to do with failing to compare "about:blank" == + # u"about:blank". For the sake of consistenty, we encode the + # returned URL as Unicode here to ensure that the values are + # absolutely of the same type. + # + # (https://bugzilla.mozilla.org/show_bug.cgi?id=1322862) + return self.marionette.execute_script("return window.location.href").encode("utf-8") + + def test_set_location_through_execute_script(self): + self.marionette.execute_script( + "window.location.href = '%s'" % self.test_doc) + Wait(self.marionette).until( + lambda _: self.test_doc == self.location_href) + self.assertEqual("Marionette Test", self.marionette.title) + + def test_navigate_chrome_error(self): + with self.marionette.using_context("chrome"): + self.assertRaises(errors.UnsupportedOperationException, + self.marionette.navigate, "about:blank") + self.assertRaises(errors.UnsupportedOperationException, self.marionette.go_back) + self.assertRaises(errors.UnsupportedOperationException, self.marionette.go_forward) + self.assertRaises(errors.UnsupportedOperationException, self.marionette.refresh) + + def test_get_current_url_returns_top_level_browsing_context_url(self): + self.marionette.navigate(self.iframe_doc) + self.assertEqual(self.iframe_doc, self.location_href) + frame = self.marionette.find_element(By.CSS_SELECTOR, "#test_iframe") + self.marionette.switch_to_frame(frame) + self.assertEqual(self.iframe_doc, self.marionette.get_url()) + + def test_get_current_url(self): + self.marionette.navigate(self.test_doc) + self.assertEqual(self.test_doc, self.marionette.get_url()) + self.marionette.navigate("about:blank") + self.assertEqual("about:blank", self.marionette.get_url()) + + def test_refresh(self): + self.marionette.navigate(self.test_doc) + self.assertEqual("Marionette Test", self.marionette.title) + self.assertTrue(self.marionette.execute_script( + """var elem = window.document.createElement('div'); elem.id = 'someDiv'; + window.document.body.appendChild(elem); return true;""")) + self.assertFalse(self.marionette.execute_script( + "return window.document.getElementById('someDiv') == undefined")) + self.marionette.refresh() + # TODO(ato): Bug 1291320 + time.sleep(0.2) + self.assertEqual("Marionette Test", self.marionette.title) + self.assertTrue(self.marionette.execute_script( + "return window.document.getElementById('someDiv') == undefined")) + + def test_navigate_in_child_frame_changes_to_top(self): + frame_html = self.marionette.absolute_url("frameset.html") + + self.marionette.navigate(frame_html) + frame = self.marionette.find_element(By.NAME, "third") + self.marionette.switch_to_frame(frame) + self.assertRaises(errors.NoSuchElementException, + self.marionette.find_element, By.NAME, "third") + + self.marionette.navigate(frame_html) + self.marionette.find_element(By.NAME, "third") + + @skip_if_mobile("Bug 1323755 - Socket timeout") + def test_invalid_protocol(self): + with self.assertRaises(errors.MarionetteException): + self.marionette.navigate("thisprotocoldoesnotexist://") + + def test_find_element_state_complete(self): + self.marionette.navigate(self.test_doc) + state = self.marionette.execute_script( + "return window.document.readyState") + self.assertEqual("complete", state) + self.assertTrue(self.marionette.find_element(By.ID, "mozLink")) + + def test_error_when_exceeding_page_load_timeout(self): + self.marionette.timeout.page_load = 0.1 + with self.assertRaises(errors.TimeoutException): + self.marionette.navigate(self.marionette.absolute_url("slow")) + + def test_navigate_to_same_image_document_twice(self): + self.marionette.navigate(self.fixtures.where_is("black.png")) + self.assertIn("black.png", self.marionette.title) + self.marionette.navigate(self.fixtures.where_is("black.png")) + self.assertIn("black.png", self.marionette.title) + + def test_navigate_hash_change(self): + doc = inline("<p id=foo>") + self.marionette.navigate(doc) + self.marionette.execute_script("window.visited = true", sandbox=None) + self.marionette.navigate("{}#foo".format(doc)) + self.assertTrue(self.marionette.execute_script( + "return window.visited", sandbox=None)) + + @skip_if_mobile("Bug 1334095 - Timeout: No new tab has been opened") + def test_about_blank_for_new_docshell(self): + """ Bug 1312674 - Hang when loading about:blank for a new docshell.""" + def open_with_link(): + link = self.marionette.find_element(By.ID, "new-blank-tab") + link.click() + + # Open a new tab to get a new docshell created + self.marionette.navigate(self.marionette.absolute_url("windowHandles.html")) + new_tab = self.open_tab(trigger=open_with_link) + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.get_url(), "about:blank") + + self.marionette.navigate('about:blank') + self.marionette.close() + self.marionette.switch_to_window(self.start_window) + + @skip("Bug 1332064 - NoSuchElementException: Unable to locate element: :focus") + @run_if_manage_instance("Only runnable if Marionette manages the instance") + @skip_if_mobile("Bug 1322993 - Missing temporary folder") + def test_focus_after_navigation(self): + self.marionette.quit() + self.marionette.start_session() + + self.marionette.navigate(inline("<input autofocus>")) + active_el = self.marionette.execute_script("return document.activeElement") + focus_el = self.marionette.find_element(By.CSS_SELECTOR, ":focus") + self.assertEqual(active_el, focus_el) + + +class TestTLSNavigation(MarionetteTestCase): + insecure_tls = {"acceptInsecureCerts": True} + secure_tls = {"acceptInsecureCerts": False} + + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.delete_session() + self.capabilities = self.marionette.start_session( + {"requiredCapabilities": self.insecure_tls}) + + def tearDown(self): + try: + self.marionette.delete_session() + except: + pass + MarionetteTestCase.tearDown(self) + + @contextlib.contextmanager + def safe_session(self): + try: + self.capabilities = self.marionette.start_session( + {"requiredCapabilities": self.secure_tls}) + self.assertFalse(self.capabilities["acceptInsecureCerts"]) + yield self.marionette + finally: + self.marionette.delete_session() + + @contextlib.contextmanager + def unsafe_session(self): + try: + self.capabilities = self.marionette.start_session( + {"requiredCapabilities": self.insecure_tls}) + self.assertTrue(self.capabilities["acceptInsecureCerts"]) + yield self.marionette + finally: + self.marionette.delete_session() + + def test_navigate_by_command(self): + self.marionette.navigate( + self.fixtures.where_is("/test.html", on="https")) + self.assertIn("https", self.marionette.get_url()) + + def test_navigate_by_click(self): + link_url = self.fixtures.where_is("/test.html", on="https") + self.marionette.navigate( + inline("<a href=%s>https is the future</a>" % link_url)) + self.marionette.find_element(By.TAG_NAME, "a").click() + self.assertIn("https", self.marionette.get_url()) + + def test_deactivation(self): + invalid_cert_url = self.fixtures.where_is("/test.html", on="https") + + print "with safe session" + with self.safe_session() as session: + with self.assertRaises(errors.InsecureCertificateException): + session.navigate(invalid_cert_url) + + print "with unsafe session" + with self.unsafe_session() as session: + session.navigate(invalid_cert_url) + + print "with safe session again" + with self.safe_session() as session: + with self.assertRaises(errors.InsecureCertificateException): + session.navigate(invalid_cert_url) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py new file mode 100644 index 000000000..c88666986 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py @@ -0,0 +1,33 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestPageSource(MarionetteTestCase): + def testShouldReturnTheSourceOfAPage(self): + test_html = self.marionette.absolute_url("testPageSource.html") + self.marionette.navigate(test_html) + source = self.marionette.page_source + from_web_api = self.marionette.execute_script("return document.documentElement.outerHTML") + self.assertTrue("<html" in source) + self.assertTrue("PageSource" in source) + self.assertEqual(source, from_web_api) + + def testShouldReturnTheSourceOfAPageWhenThereAreUnicodeChars(self): + test_html = self.marionette.absolute_url("testPageSourceWithUnicodeChars.html") + self.marionette.navigate(test_html) + # if we don't throw on the next line we are good! + source = self.marionette.page_source + from_web_api = self.marionette.execute_script("return document.documentElement.outerHTML") + self.assertEqual(source, from_web_api) + + def testShouldReturnAXMLDocumentSource(self): + test_xml = self.marionette.absolute_url("testPageSource.xml") + self.marionette.navigate(test_xml) + source = self.marionette.page_source + from_web_api = self.marionette.execute_script("return document.documentElement.outerHTML") + import re + self.assertEqual(re.sub("\s", "", source), "<xml><foo><bar>baz</bar></foo></xml>") + self.assertEqual(source, from_web_api) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource_chrome.py new file mode 100644 index 000000000..5f60e6010 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource_chrome.py @@ -0,0 +1,29 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestPageSourceChrome(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestPageSourceChrome, self).setUp() + self.marionette.set_context("chrome") + + def open_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test.xul', + 'foo', 'chrome,centerscreen'); + """) + + new_window = self.open_window(open_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + self.close_all_windows() + super(TestPageSourceChrome, self).tearDown() + + def testShouldReturnXULDetails(self): + source = self.marionette.page_source + self.assertTrue('<textbox id="textInput"' in source) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_position.py b/testing/marionette/harness/marionette_harness/tests/unit/test_position.py new file mode 100644 index 000000000..2cc4d5947 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_position.py @@ -0,0 +1,19 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestPosition(MarionetteTestCase): + + def test_should_get_element_position_back(self): + test_url = self.marionette.absolute_url('rectangles.html') + self.marionette.navigate(test_url) + + r2 = self.marionette.find_element(By.ID, "r2") + location = r2.rect + self.assertEqual(11, location['x']) + self.assertEqual(10, location['y']) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py new file mode 100644 index 000000000..9cfbe1df1 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py @@ -0,0 +1,167 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import JavascriptException + +from marionette_harness import MarionetteTestCase + + +class TestPreferences(MarionetteTestCase): + prefs = { + "bool": "marionette.test.bool", + "int": "marionette.test.int", + "string": "marionette.test.string", + } + + def tearDown(self): + for pref in self.prefs.values(): + self.marionette.clear_pref(pref) + + super(TestPreferences, self).tearDown() + + def test_clear_pref(self): + self.assertIsNone(self.marionette.get_pref(self.prefs["bool"])) + + self.marionette.set_pref(self.prefs["bool"], True) + self.assertTrue(self.marionette.get_pref(self.prefs["bool"])) + + self.marionette.clear_pref(self.prefs["bool"]) + self.assertIsNone(self.marionette.get_pref(self.prefs["bool"])) + + def test_get_and_set_pref(self): + # By default none of the preferences are set + self.assertIsNone(self.marionette.get_pref(self.prefs["bool"])) + self.assertIsNone(self.marionette.get_pref(self.prefs["int"])) + self.assertIsNone(self.marionette.get_pref(self.prefs["string"])) + + # Test boolean values + self.marionette.set_pref(self.prefs["bool"], True) + value = self.marionette.get_pref(self.prefs["bool"]) + self.assertTrue(value) + self.assertEqual(type(value), bool) + + # Test int values + self.marionette.set_pref(self.prefs["int"], 42) + value = self.marionette.get_pref(self.prefs["int"]) + self.assertEqual(value, 42) + self.assertEqual(type(value), int) + + # Test string values + self.marionette.set_pref(self.prefs["string"], "abc") + value = self.marionette.get_pref(self.prefs["string"]) + self.assertEqual(value, "abc") + self.assertTrue(isinstance(value, basestring)) + + # Test reset value + self.marionette.set_pref(self.prefs["string"], None) + self.assertIsNone(self.marionette.get_pref(self.prefs["string"])) + + def test_get_set_pref_default_branch(self): + pref_default = "marionette.test.pref_default1" + self.assertIsNone(self.marionette.get_pref(self.prefs["string"])) + + self.marionette.set_pref(pref_default, "default_value", default_branch=True) + self.assertEqual(self.marionette.get_pref(pref_default), "default_value") + self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True), + "default_value") + + self.marionette.set_pref(pref_default, "user_value") + self.assertEqual(self.marionette.get_pref(pref_default), "user_value") + self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True), + "default_value") + + self.marionette.clear_pref(pref_default) + self.assertEqual(self.marionette.get_pref(pref_default), "default_value") + + def test_get_pref_value_type(self): + # Without a given value type the properties URL will be returned only + pref_complex = "browser.menu.showCharacterEncoding" + properties_file = "chrome://browser/locale/browser.properties" + self.assertEqual(self.marionette.get_pref(pref_complex, default_branch=True), + properties_file) + + # Otherwise the property named like the pref will be translated + value = self.marionette.get_pref(pref_complex, default_branch=True, + value_type="nsIPrefLocalizedString") + self.assertNotEqual(value, properties_file) + + def test_set_prefs(self): + # By default none of the preferences are set + self.assertIsNone(self.marionette.get_pref(self.prefs["bool"])) + self.assertIsNone(self.marionette.get_pref(self.prefs["int"])) + self.assertIsNone(self.marionette.get_pref(self.prefs["string"])) + + # Set a value on the default branch first + pref_default = "marionette.test.pref_default2" + self.assertIsNone(self.marionette.get_pref(pref_default)) + self.marionette.set_prefs({pref_default: "default_value"}, default_branch=True) + + # Set user values + prefs = {self.prefs["bool"]: True, self.prefs["int"]: 42, + self.prefs["string"]: "abc", pref_default: "user_value"} + self.marionette.set_prefs(prefs) + + self.assertTrue(self.marionette.get_pref(self.prefs["bool"])) + self.assertEqual(self.marionette.get_pref(self.prefs["int"]), 42) + self.assertEqual(self.marionette.get_pref(self.prefs["string"]), "abc") + self.assertEqual(self.marionette.get_pref(pref_default), "user_value") + self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True), + "default_value") + + def test_using_prefs(self): + # Test that multiple preferences can be set with "using_prefs", and that + # they are set correctly and unset correctly after leaving the context + # manager. + pref_not_existent = "marionette.test.not_existent1" + pref_default = "marionette.test.pref_default3" + + self.marionette.set_prefs({self.prefs["string"]: "abc", + self.prefs["int"]: 42, + self.prefs["bool"]: False, + }) + self.assertFalse(self.marionette.get_pref(self.prefs["bool"])) + self.assertEqual(self.marionette.get_pref(self.prefs["int"]), 42) + self.assertEqual(self.marionette.get_pref(self.prefs["string"]), "abc") + self.assertIsNone(self.marionette.get_pref(pref_not_existent)) + + with self.marionette.using_prefs({self.prefs["bool"]: True, + self.prefs["int"]: 24, + self.prefs["string"]: "def", + pref_not_existent: "existent"}): + + self.assertTrue(self.marionette.get_pref(self.prefs["bool"]), True) + self.assertEquals(self.marionette.get_pref(self.prefs["int"]), 24) + self.assertEquals(self.marionette.get_pref(self.prefs["string"]), "def") + self.assertEquals(self.marionette.get_pref(pref_not_existent), "existent") + + self.assertFalse(self.marionette.get_pref(self.prefs["bool"])) + self.assertEqual(self.marionette.get_pref(self.prefs["int"]), 42) + self.assertEqual(self.marionette.get_pref(self.prefs["string"]), "abc") + self.assertIsNone(self.marionette.get_pref(pref_not_existent)) + + # Using context with default branch + self.marionette.set_pref(pref_default, "default_value", default_branch=True) + self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True), + "default_value") + + with self.marionette.using_prefs({pref_default: "new_value"}, default_branch=True): + self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True), + "new_value") + + self.assertEqual(self.marionette.get_pref(pref_default, default_branch=True), + "default_value") + + def test_using_prefs_exception(self): + # Test that throwing an exception inside the context manager doesn"t + # prevent the preferences from being restored at context manager exit. + self.marionette.set_pref(self.prefs["string"], "abc") + + try: + with self.marionette.using_prefs({self.prefs["string"]: "def"}): + self.assertEquals(self.marionette.get_pref(self.prefs["string"]), "def") + self.marionette.execute_script("return foo.bar.baz;") + except JavascriptException: + pass + + self.assertEquals(self.marionette.get_pref(self.prefs["string"]), "abc") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py b/testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py new file mode 100644 index 000000000..f8ec952b2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestProfileManagement(MarionetteTestCase): + + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.enforce_gecko_prefs( + {"marionette.test.bool": True, + "marionette.test.string": "testing", + "marionette.test.int": 3 + }) + self.marionette.set_context("chrome") + + def test_preferences_are_set(self): + self.assertTrue(self.marionette.get_pref("marionette.test.bool")) + self.assertEqual(self.marionette.get_pref("marionette.test.string"), "testing") + self.assertEqual(self.marionette.get_pref("marionette.test.int"), 3) + + def test_change_preference(self): + self.assertTrue(self.marionette.get_pref("marionette.test.bool")) + + self.marionette.enforce_gecko_prefs({"marionette.test.bool": False}) + + self.assertFalse(self.marionette.get_pref("marionette.test.bool")) + + def test_clean_profile(self): + self.marionette.restart(clean=True) + + self.assertEqual(self.marionette.get_pref("marionette.test.bool"), None) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py b/testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py new file mode 100644 index 000000000..887d69b76 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py @@ -0,0 +1,252 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import InvalidArgumentException + +from marionette_harness import MarionetteTestCase + + +class TestProxy(MarionetteTestCase): + + def setUp(self): + super(TestProxy, self).setUp() + self.marionette.delete_session() + + def test_that_we_can_set_a_autodetect_proxy(self): + capabilities = {"requiredCapabilities": + { + "proxy":{ + "proxyType": "autodetect", + } + } + } + self.marionette.start_session(capabilities) + result = None + with self.marionette.using_context('chrome'): + result = self.marionette.execute_script("""return { + "proxyType" : Services.prefs.getIntPref('network.proxy.type'), + } + """) + + self.assertEqual(result["proxyType"], 4) + + def test_that_capabilities_returned_have_proxy_details(self): + capabilities = {"requiredCapabilities": + { + "proxy":{ + "proxyType": "autodetect", + } + } + } + self.marionette.start_session(capabilities) + result = self.marionette.session_capabilities + + self.assertEqual(result["proxy"]["proxyType"], "autodetect") + + def test_that_we_can_set_a_system_proxy(self): + capabilities = {"requiredCapabilities": + { + "proxy":{ + "proxyType": "system", + } + } + } + self.marionette.start_session(capabilities) + result = None + with self.marionette.using_context('chrome'): + result = self.marionette.execute_script("""return { + "proxyType" : Services.prefs.getIntPref('network.proxy.type'), + } + """) + + self.assertEqual(result["proxyType"], 5) + + def test_we_can_set_a_pac_proxy(self): + url = "http://marionette.test" + capabilities = {"requiredCapabilities": + { + "proxy":{ + "proxyType": "pac", + "proxyAutoconfigUrl": url, + } + } + } + self.marionette.start_session(capabilities) + result = None + with self.marionette.using_context('chrome'): + result = self.marionette.execute_script("""return { + "proxyType" : Services.prefs.getIntPref('network.proxy.type'), + "proxyAutoconfigUrl" : Services.prefs.getCharPref('network.proxy.autoconfig_url'), + } + """) + + self.assertEqual(result["proxyType"], 2) + self.assertEqual(result["proxyAutoconfigUrl"], url, 'proxyAutoconfigUrl was not set') + + def test_that_we_can_set_a_manual_proxy(self): + port = 4444 + url = "http://marionette.test" + capabilities = {"requiredCapabilities": + { + "proxy":{ + "proxyType": "manual", + "ftpProxy": url, + "ftpProxyPort": port, + "httpProxy": url, + "httpProxyPort": port, + "sslProxy": url, + "sslProxyPort": port, + } + } + } + self.marionette.start_session(capabilities) + result = None + with self.marionette.using_context('chrome'): + result = self.marionette.execute_script("""return { + "proxyType" : Services.prefs.getIntPref('network.proxy.type'), + "httpProxy" : Services.prefs.getCharPref('network.proxy.http'), + "httpProxyPort": Services.prefs.getIntPref('network.proxy.http_port'), + "sslProxy": Services.prefs.getCharPref('network.proxy.ssl'), + "sslProxyPort": Services.prefs.getIntPref('network.proxy.ssl_port'), + "ftpProxy": Services.prefs.getCharPref('network.proxy.ftp'), + "ftpProxyPort": Services.prefs.getIntPref('network.proxy.ftp_port'), + } + """) + + self.assertEqual(result["proxyType"], 1) + self.assertEqual(result["httpProxy"], url, 'httpProxy was not set') + self.assertEqual(result["httpProxyPort"], port, 'httpProxyPort was not set') + self.assertEqual(result["sslProxy"], url, 'sslProxy url was not set') + self.assertEqual(result["sslProxyPort"], port, 'sslProxyPort was not set') + self.assertEqual(result["ftpProxy"], url, 'ftpProxy was not set') + self.assertEqual(result["ftpProxyPort"], port, 'ftpProxyPort was not set') + + def test_we_can_set_a_manual_proxy_with_a_socks_proxy_with_socks_version(self): + port = 4444 + url = "http://marionette.test" + capabilities = {"requiredCapabilities": + { + "proxy":{ + "proxyType": "manual", + "socksProxy": url, + "socksProxyPort": port, + "socksVersion": 4, + "socksUsername": "cake", + "socksPassword": "made with cake" + } + } + } + self.marionette.start_session(capabilities) + result = None + with self.marionette.using_context('chrome'): + result = self.marionette.execute_script("""return { + "proxyType" : Services.prefs.getIntPref('network.proxy.type'), + "socksProxy" : Services.prefs.getCharPref('network.proxy.socks'), + "socksProxyPort": Services.prefs.getIntPref('network.proxy.socks_port'), + "socksVersion": Services.prefs.getIntPref('network.proxy.socks_version'), + } + """) + self.assertEqual(result["socksProxy"], url, 'socksProxy was not set') + self.assertEqual(result["socksProxyPort"], port, 'socksProxyPort was not set') + self.assertEqual(result["socksVersion"], 4, 'socksVersion was not set to 4') + + def test_we_can_set_a_manual_proxy_with_a_socks_proxy_with_no_socks_version(self): + port = 4444 + url = "http://marionette.test" + capabilities = {"requiredCapabilities": + { + "proxy":{ + "proxyType": "manual", + "socksProxy": url, + "socksProxyPort": port, + "socksUsername": "cake", + "socksPassword": "made with cake" + } + } + } + self.marionette.start_session(capabilities) + result = None + with self.marionette.using_context('chrome'): + result = self.marionette.execute_script("""return { + "proxyType" : Services.prefs.getIntPref('network.proxy.type'), + "socksProxy" : Services.prefs.getCharPref('network.proxy.socks'), + "socksProxyPort": Services.prefs.getIntPref('network.proxy.socks_port'), + "socksVersion": Services.prefs.getIntPref('network.proxy.socks_version'), + + } + """) + self.assertEqual(result["socksProxy"], url, 'socksProxy was not set') + self.assertEqual(result["socksProxyPort"], port, 'socksProxyPort was not set') + self.assertEqual(result["socksVersion"], 5, 'socksVersion was not set to 5') + + def test_when_not_all_manual_proxy_details_are_in_capabilities(self): + port = 4444 + url = "http://marionette.test" + capabilities = {"requiredCapabilities": + { + "proxy":{ + "proxyType": "manual", + "ftpProxy": url, + "ftpProxyPort": port, + } + } + } + self.marionette.start_session(capabilities) + result = None + with self.marionette.using_context('chrome'): + result = self.marionette.execute_script("""return { + "proxyType" : Services.prefs.getIntPref('network.proxy.type'), + "httpProxy" : Services.prefs.getCharPref('network.proxy.http'), + "httpProxyPort": Services.prefs.getIntPref('network.proxy.http_port'), + "sslProxy": Services.prefs.getCharPref('network.proxy.ssl'), + "sslProxyPort": Services.prefs.getIntPref('network.proxy.ssl_port'), + "ftpProxy": Services.prefs.getCharPref('network.proxy.ftp'), + "ftpProxyPort": Services.prefs.getIntPref('network.proxy.ftp_port'), + } + """) + + self.assertEqual(result["proxyType"], 1) + self.assertNotEqual(result["httpProxy"], url, + 'httpProxy was set. {}'.format(result["httpProxy"])) + self.assertNotEqual(result["httpProxyPort"], port, 'httpProxyPort was set') + self.assertNotEqual(result["sslProxy"], url, 'sslProxy url was set') + self.assertNotEqual(result["sslProxyPort"], port, 'sslProxyPort was set') + self.assertEqual(result["ftpProxy"], url, 'ftpProxy was set') + self.assertEqual(result["ftpProxyPort"], port, 'ftpProxyPort was set') + + + + def test_proxy_is_a_string_should_throw_invalid_argument(self): + capabilities = {"requiredCapabilities": + { + "proxy":"I really should be a dictionary" + } + } + try: + self.marionette.start_session(capabilities) + self.fail("We should have started a session because proxy should be a dict") + except InvalidArgumentException as e: + assert e.message == "Value of 'proxy' should be an object" + + def test_proxy_is_passed_in_with_no_proxy_doesnt_set_it(self): + capabilities = {"requiredCapabilities": + { + "proxy": {"proxyType": "NOPROXY"}, + } + } + self.marionette.start_session(capabilities) + result = None + with self.marionette.using_context('chrome'): + result = self.marionette.execute_script("""return { + "proxyType": Services.prefs.getIntPref('network.proxy.type'), + }; + """) + + self.assertEqual(result["proxyType"], 0) + + def tearDown(self): + if not self.marionette.session: + self.marionette.start_session() + else: + self.marionette.restart(clean=True) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py b/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py new file mode 100644 index 000000000..38c678556 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py @@ -0,0 +1,173 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import MarionetteException + +from marionette_harness import MarionetteTestCase + + +class TestQuitRestart(MarionetteTestCase): + + def setUp(self): + MarionetteTestCase.setUp(self) + + self.pid = self.marionette.process_id + self.session_id = self.marionette.session_id + + self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3) + self.marionette.set_pref("browser.startup.page", 3) + + def tearDown(self): + # Ensure to restart a session if none exist for clean-up + if not self.marionette.session: + self.marionette.start_session() + + self.marionette.clear_pref("browser.startup.page") + + MarionetteTestCase.tearDown(self) + + def test_force_restart(self): + self.marionette.restart() + self.assertEqual(self.marionette.session_id, self.session_id) + + # A forced restart will cause a new process id + self.assertNotEqual(self.marionette.process_id, self.pid) + + # If a preference value is not forced, a restart will cause a reset + self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3) + + def test_force_quit(self): + self.marionette.quit() + + self.assertEqual(self.marionette.session, None) + with self.assertRaisesRegexp(MarionetteException, "Please start a session"): + self.marionette.get_url() + + self.marionette.start_session() + self.assertNotEqual(self.marionette.session_id, self.session_id) + self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3) + + def test_in_app_clean_restart(self): + with self.assertRaises(ValueError): + self.marionette.restart(in_app=True, clean=True) + + def test_in_app_restart(self): + self.marionette.restart(in_app=True) + self.assertEqual(self.marionette.session_id, self.session_id) + + # An in-app restart will keep the same process id only on Linux + if self.marionette.session_capabilities['platformName'] == 'linux': + self.assertEqual(self.marionette.process_id, self.pid) + else: + self.assertNotEqual(self.marionette.process_id, self.pid) + + # If a preference value is not forced, a restart will cause a reset + self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3) + + def test_in_app_restart_with_callback(self): + self.marionette.restart(in_app=True, + callback=lambda: self.shutdown(restart=True)) + + self.assertEqual(self.marionette.session_id, self.session_id) + + # An in-app restart will keep the same process id only on Linux + if self.marionette.session_capabilities['platformName'] == 'linux': + self.assertEqual(self.marionette.process_id, self.pid) + else: + self.assertNotEqual(self.marionette.process_id, self.pid) + + # If a preference value is not forced, a restart will cause a reset + self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3) + + def test_in_app_quit(self): + self.marionette.quit(in_app=True) + + self.assertEqual(self.marionette.session, None) + with self.assertRaisesRegexp(MarionetteException, "Please start a session"): + self.marionette.get_url() + + self.marionette.start_session() + self.assertNotEqual(self.marionette.session_id, self.session_id) + self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3) + + def test_in_app_quit_with_callback(self): + self.marionette.quit(in_app=True, callback=self.shutdown) + self.assertEqual(self.marionette.session, None) + with self.assertRaisesRegexp(MarionetteException, "Please start a session"): + self.marionette.get_url() + + self.marionette.start_session() + self.assertNotEqual(self.marionette.session_id, self.session_id) + self.assertNotEqual(self.marionette.get_pref("browser.startup.page"), 3) + + def test_reset_context_after_quit_by_set_context(self): + # Check that we are in content context which is used by default in Marionette + self.assertNotIn('chrome://', self.marionette.get_url(), + "Context doesn't default to content") + + self.marionette.set_context('chrome') + self.marionette.quit(in_app=True) + self.assertEqual(self.marionette.session, None) + self.marionette.start_session() + self.assertNotIn('chrome://', self.marionette.get_url(), + "Not in content context after quit with using_context") + + def test_reset_context_after_quit_by_using_context(self): + # Check that we are in content context which is used by default in Marionette + self.assertNotIn('chrome://', self.marionette.get_url(), + "Context doesn't default to content") + + with self.marionette.using_context('chrome'): + self.marionette.quit(in_app=True) + self.assertEqual(self.marionette.session, None) + self.marionette.start_session() + self.assertNotIn('chrome://', self.marionette.get_url(), + "Not in content context after quit with using_context") + + def test_keep_context_after_restart_by_set_context(self): + # Check that we are in content context which is used by default in Marionette + self.assertNotIn('chrome://', self.marionette.get_url(), + "Context doesn't default to content") + + # restart while we are in chrome context + self.marionette.set_context('chrome') + self.marionette.restart(in_app=True) + + # An in-app restart will keep the same process id only on Linux + if self.marionette.session_capabilities['platformName'] == 'linux': + self.assertEqual(self.marionette.process_id, self.pid) + else: + self.assertNotEqual(self.marionette.process_id, self.pid) + + self.assertIn('chrome://', self.marionette.get_url(), + "Not in chrome context after a restart with set_context") + + def test_keep_context_after_restart_by_using_context(self): + # Check that we are in content context which is used by default in Marionette + self.assertNotIn('chrome://', self.marionette.get_url(), + "Context doesn't default to content") + + # restart while we are in chrome context + with self.marionette.using_context('chrome'): + self.marionette.restart(in_app=True) + + # An in-app restart will keep the same process id only on Linux + if self.marionette.session_capabilities['platformName'] == 'linux': + self.assertEqual(self.marionette.process_id, self.pid) + else: + self.assertNotEqual(self.marionette.process_id, self.pid) + + self.assertIn('chrome://', self.marionette.get_url(), + "Not in chrome context after a restart with using_context") + + def shutdown(self, restart=False): + self.marionette.set_context("chrome") + self.marionette.execute_script(""" + Components.utils.import("resource://gre/modules/Services.jsm"); + let flags = Ci.nsIAppStartup.eAttemptQuit + if(arguments[0]) { + flags |= Ci.nsIAppStartup.eRestart; + } + Services.startup.quit(flags); + """, script_args=[restart]) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py b/testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py new file mode 100644 index 000000000..508870e91 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py @@ -0,0 +1,34 @@ +#Copyright 2007-2009 WebDriver committers +#Copyright 2007-2009 Google Inc. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class RenderedElementTests(MarionetteTestCase): + + def testWeCanGetComputedStyleValueOnElement(self): + test_url = self.marionette.absolute_url('javascriptPage.html') + self.marionette.navigate(test_url) + element = self.marionette.find_element(By.ID, "green-parent") + backgroundColour = element.value_of_css_property("background-color") + + self.assertEqual("rgb(0, 128, 0)", backgroundColour) + + element = self.marionette.find_element(By.ID, "red-item") + backgroundColour = element.value_of_css_property("background-color") + + self.assertEqual("rgb(255, 0, 0)", backgroundColour) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_report.py b/testing/marionette/harness/marionette_harness/tests/unit/test_report.py new file mode 100644 index 000000000..f22c3db4b --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_report.py @@ -0,0 +1,29 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase, expectedFailure, skip + + +class TestReport(MarionetteTestCase): + + def test_pass(self): + assert True + + def test_fail(self): + assert False + + @skip('Skip Message') + def test_skip(self): + assert False + + @expectedFailure + def test_expected_fail(self): + assert False + + @expectedFailure + def test_unexpected_pass(self): + assert True + + def test_error(self): + raise Exception() diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_run_js_test.py b/testing/marionette/harness/marionette_harness/tests/unit/test_run_js_test.py new file mode 100644 index 000000000..134223ce1 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_run_js_test.py @@ -0,0 +1,10 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from marionette_harness import MarionetteTestCase + + +class TestRunJSTest(MarionetteTestCase): + def test_basic(self): + self.run_js_test('test_simpletest_pass.js') + self.run_js_test('test_simpletest_fail.js') diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py b/testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py new file mode 100644 index 000000000..830795a1e --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py @@ -0,0 +1,86 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import errors +from mozrunner.devices.emulator_screen import EmulatorScreen + +from marionette_harness import MarionetteTestCase, skip_if_desktop, skip_if_mobile + + +default_orientation = "portrait-primary" +unknown_orientation = "Unknown screen orientation: {}" + + +class TestScreenOrientation(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.is_mobile = self.marionette.session_capabilities.get("rotatable", False) + + def tearDown(self): + if self.is_mobile: + self.marionette.set_orientation(default_orientation) + self.assertEqual(self.marionette.orientation, default_orientation, "invalid state") + MarionetteTestCase.tearDown(self) + + @skip_if_desktop("Not supported in Firefox") + def test_set_orientation_to_portrait_primary(self): + self.marionette.set_orientation("portrait-primary") + new_orientation = self.marionette.orientation + self.assertEqual(new_orientation, "portrait-primary") + + @skip_if_desktop("Not supported in Firefox") + def test_set_orientation_to_landscape_primary(self): + self.marionette.set_orientation("landscape-primary") + new_orientation = self.marionette.orientation + self.assertEqual(new_orientation, "landscape-primary") + + @skip_if_desktop("Not supported in Firefox") + def test_set_orientation_to_portrait_secondary(self): + self.marionette.set_orientation("portrait-secondary") + new_orientation = self.marionette.orientation + self.assertEqual(new_orientation, "portrait-secondary") + + @skip_if_desktop("Not supported in Firefox") + def test_set_orientation_to_landscape_secondary(self): + self.marionette.set_orientation("landscape-secondary") + new_orientation = self.marionette.orientation + self.assertEqual(new_orientation, "landscape-secondary") + + @skip_if_desktop("Not supported in Firefox") + def test_set_orientation_to_shorthand_portrait(self): + # Set orientation to something other than portrait-primary first, since the default is + # portrait-primary. + self.marionette.set_orientation("landscape-primary") + self.assertEqual(self.marionette.orientation, "landscape-primary", "invalid state") + + self.marionette.set_orientation("portrait") + new_orientation = self.marionette.orientation + self.assertEqual(new_orientation, "portrait-primary") + + @skip_if_desktop("Not supported in Firefox") + def test_set_orientation_to_shorthand_landscape(self): + self.marionette.set_orientation("landscape") + new_orientation = self.marionette.orientation + self.assertEqual(new_orientation, "landscape-primary") + + @skip_if_desktop("Not supported in Firefox") + def test_set_orientation_with_mixed_casing(self): + self.marionette.set_orientation("lAnDsCaPe") + new_orientation = self.marionette.orientation + self.assertEqual(new_orientation, "landscape-primary") + + @skip_if_desktop("Not supported in Firefox") + def test_set_invalid_orientation(self): + with self.assertRaisesRegexp(errors.MarionetteException, unknown_orientation.format("cheese")): + self.marionette.set_orientation("cheese") + + @skip_if_desktop("Not supported in Firefox") + def test_set_null_orientation(self): + with self.assertRaisesRegexp(errors.MarionetteException, unknown_orientation.format("null")): + self.marionette.set_orientation(None) + + @skip_if_mobile("Specific test for Firefox") + def test_unsupported_operation_on_desktop(self): + with self.assertRaises(errors.UnsupportedOperationException): + self.marionette.set_orientation("landscape-primary") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py new file mode 100644 index 000000000..aa0e6ab1c --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py @@ -0,0 +1,428 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import base64 +import hashlib +import imghdr +import struct +import urllib + +from marionette_driver import By +from marionette_driver.errors import JavascriptException, NoSuchWindowException +from marionette_harness import ( + MarionetteTestCase, + skip, + skip_if_mobile, + WindowManagerMixin, +) + + +def inline(doc, mime="text/html;charset=utf-8"): + return "data:{0},{1}".format(mime, urllib.quote(doc)) + + +box = inline("<body><div id='box'><p id='green' style='width: 50px; height: 50px; " + "background: silver;'></p></div></body>") +input = inline("<body><input id='text-input'></input></body>") +long = inline("<body style='height: 300vh'><p style='margin-top: 100vh'>foo</p></body>") +short = inline("<body style='height: 10vh'></body>") +svg = inline(""" + <svg xmlns="http://www.w3.org/2000/svg" height="20" width="20"> + <rect height="20" width="20"/> + </svg>""", mime="image/svg+xml") + + +class ScreenCaptureTestCase(MarionetteTestCase): + + def setUp(self): + super(ScreenCaptureTestCase, self).setUp() + + self._device_pixel_ratio = None + + @property + def device_pixel_ratio(self): + if self._device_pixel_ratio is None: + self._device_pixel_ratio = self.marionette.execute_script(""" + return window.devicePixelRatio + """) + return self._device_pixel_ratio + + @property + def document_element(self): + return self.marionette.find_element(By.CSS_SELECTOR, ":root") + + @property + def page_y_offset(self): + return self.marionette.execute_script("return window.pageYOffset") + + @property + def viewport_dimensions(self): + return self.marionette.execute_script(""" + return [arguments[0].clientWidth, + arguments[0].clientHeight]; + """, script_args=[self.document_element]) + + def assert_png(self, screenshot): + """Test that screenshot is a Base64 encoded PNG file.""" + image = base64.decodestring(screenshot) + self.assertEqual(imghdr.what("", image), "png") + + def assert_formats(self, element=None): + if element is None: + element = self.document_element + + screenshot_default = self.marionette.screenshot(element=element) + screenshot_image = self.marionette.screenshot(element=element, format="base64") + binary1 = self.marionette.screenshot(element=element, format="binary") + binary2 = self.marionette.screenshot(element=element, format="binary") + hash1 = self.marionette.screenshot(element=element, format="hash") + hash2 = self.marionette.screenshot(element=element, format="hash") + + # Valid data should have been returned + self.assert_png(screenshot_image) + self.assertEqual(imghdr.what("", binary1), "png") + self.assertEqual(screenshot_image, base64.b64encode(binary1)) + self.assertEqual(hash1, hashlib.sha256(screenshot_image).hexdigest()) + + # Different formats produce different data + self.assertNotEqual(screenshot_image, binary1) + self.assertNotEqual(screenshot_image, hash1) + self.assertNotEqual(binary1, hash1) + + # A second capture should be identical + self.assertEqual(screenshot_image, screenshot_default) + self.assertEqual(binary1, binary2) + self.assertEqual(hash1, hash2) + + def get_element_dimensions(self, element): + rect = element.rect + return rect["width"], rect["height"] + + def get_image_dimensions(self, screenshot): + self.assert_png(screenshot) + image = base64.decodestring(screenshot) + width, height = struct.unpack(">LL", image[16:24]) + return int(width), int(height) + + def scale(self, rect): + return (int(rect[0] * self.device_pixel_ratio), + int(rect[1] * self.device_pixel_ratio)) + + +class TestScreenCaptureChrome(WindowManagerMixin, ScreenCaptureTestCase): + + def setUp(self): + super(TestScreenCaptureChrome, self).setUp() + self.marionette.set_context("chrome") + + def tearDown(self): + self.close_all_windows() + super(TestScreenCaptureChrome, self).tearDown() + + @property + def window_dimensions(self): + return tuple(self.marionette.execute_script(""" + let el = document.documentElement; + let rect = el.getBoundingClientRect(); + return [rect.width, rect.height]; + """)) + + def open_dialog(self, url=None, width=None, height=None): + if url is None: + url = "chrome://marionette/content/test_dialog.xul" + + def opener(): + features = "chrome" + if height is not None: + features += ",height={}".format(height) + if width is not None: + features += ",width={}".format(width) + + self.marionette.execute_script(""" + window.open(arguments[0], "", arguments[1]); + """, script_args=[url, features]) + + return self.open_window(opener) + + def test_capture_different_context(self): + """Check that screenshots in content and chrome are different.""" + with self.marionette.using_context("content"): + screenshot_content = self.marionette.screenshot() + screenshot_chrome = self.marionette.screenshot() + self.assertNotEqual(screenshot_content, screenshot_chrome) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_capture_element(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + # Ensure we only capture the element + el = self.marionette.find_element(By.ID, "test-list") + screenshot_element = self.marionette.screenshot(element=el) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot_element)) + + # Ensure we do not capture the full window + screenshot_dialog = self.marionette.screenshot() + self.assertNotEqual(screenshot_dialog, screenshot_element) + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_capture_flags(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + textbox = self.marionette.find_element(By.ID, "text-box") + textbox.send_keys("") + screenshot_focus = self.marionette.screenshot() + + self.marionette.execute_script("arguments[0].blur();", script_args=[textbox]) + screenshot_no_focus = self.marionette.screenshot() + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + self.assertNotEqual(screenshot_focus, screenshot_no_focus) + + def test_capture_full_area(self): + # A full capture is not the outer dimensions of the window, + # but instead the bounding box of the window's root node (documentElement). + screenshot_full = self.marionette.screenshot() + screenshot_root = self.marionette.screenshot(element=self.document_element) + + self.assert_png(screenshot_full) + self.assert_png(screenshot_root) + self.assertEqual(screenshot_root, screenshot_full) + self.assertEqual(self.scale(self.get_element_dimensions(self.document_element)), + self.get_image_dimensions(screenshot_full)) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_capture_viewport(self): + # Load a HTML test page into the chrome window to get scrollbars + test_page = self.marionette.absolute_url("test.html") + dialog = self.open_dialog(url=test_page, width=50, height=50) + self.marionette.switch_to_window(dialog) + + # Size of screenshot has to match viewport size + screenshot = self.marionette.screenshot(full=False) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.viewport_dimensions), + self.get_image_dimensions(screenshot)) + self.assertNotEqual(self.scale(self.window_dimensions), + self.get_image_dimensions(screenshot)) + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_capture_window_already_closed(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + self.marionette.close_chrome_window() + + self.assertRaises(NoSuchWindowException, self.marionette.screenshot) + self.marionette.switch_to_window(self.start_window) + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_formats(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + self.assert_formats() + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + def test_format_unknown(self): + with self.assertRaises(ValueError): + self.marionette.screenshot(format="cheese") + + @skip_if_mobile("Fennec doesn't support other chrome windows") + def test_highlight_elements(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + # Highlighting the element itself shouldn't make the image larger + element = self.marionette.find_element(By.ID, "test-list") + screenshot_element = self.marionette.screenshot(element=element) + screenshot_highlight = self.marionette.screenshot(element=element, + highlights=[element]) + self.assertEqual(self.scale(self.get_element_dimensions(element)), + self.get_image_dimensions(screenshot_element)) + self.assertNotEqual(screenshot_element, screenshot_highlight) + + # Highlighting a sub element + button = self.marionette.find_element(By.ID, "choose-button") + screenshot_highlight_button = self.marionette.screenshot(element=element, + highlights=[button]) + self.assertNotEqual(screenshot_element, screenshot_highlight_button) + self.assertNotEqual(screenshot_highlight, screenshot_highlight_button) + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + def test_highlight_element_not_seen(self): + """Check that for not found elements an exception is raised.""" + with self.marionette.using_context('content'): + self.marionette.navigate(box) + content_element = self.marionette.find_element(By.ID, "green") + + self.assertRaisesRegexp(JavascriptException, "Element reference not seen before", + self.marionette.screenshot, highlights=[content_element]) + + chrome_document_element = self.document_element + with self.marionette.using_context('content'): + self.assertRaisesRegexp(JavascriptException, "Element reference not seen before", + self.marionette.screenshot, + highlights=[chrome_document_element]) + + +class TestScreenCaptureContent(WindowManagerMixin, ScreenCaptureTestCase): + + def setUp(self): + super(TestScreenCaptureContent, self).setUp() + self.marionette.set_context("content") + + def tearDown(self): + self.close_all_tabs() + super(TestScreenCaptureContent, self).tearDown() + + @property + def scroll_dimensions(self): + return tuple(self.marionette.execute_script(""" + return [document.body.scrollWidth, document.body.scrollHeight] + """)) + + @skip_if_mobile("Needs application independent method to open a new tab") + def test_capture_tab_already_closed(self): + tab = self.open_tab() + self.marionette.switch_to_window(tab) + self.marionette.close() + + self.assertRaises(NoSuchWindowException, self.marionette.screenshot) + self.marionette.switch_to_window(self.start_tab) + + def test_capture_element(self): + self.marionette.navigate(box) + el = self.marionette.find_element(By.TAG_NAME, "div") + screenshot = self.marionette.screenshot(element=el) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot)) + + @skip("Bug 1213875") + def test_capture_element_scrolled_into_view(self): + self.marionette.navigate(long) + el = self.marionette.find_element(By.TAG_NAME, "p") + screenshot = self.marionette.screenshot(element=el) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot)) + self.assertGreater(self.page_y_offset, 0) + + @skip("Bug 1330560 - AssertionError: u'iVBORw0KGgoA... (images unexpectedly equal)") + def test_capture_flags(self): + self.marionette.navigate(input) + + textbox = self.marionette.find_element(By.ID, "text-input") + textbox.send_keys("") + screenshot_focus = self.marionette.screenshot() + + self.marionette.execute_script("arguments[0].blur();", script_args=[textbox]) + screenshot_no_focus = self.marionette.screenshot() + + self.assertNotEqual(screenshot_focus, screenshot_no_focus) + + @skip_if_mobile("Bug 1330642 - Tuples differ: (1960, 11130) != (1960, 11129)") + def test_capture_html_document_element(self): + self.marionette.navigate(long) + screenshot = self.marionette.screenshot() + self.assert_png(screenshot) + self.assertEqual(self.scale(self.scroll_dimensions), + self.get_image_dimensions(screenshot)) + + def test_capture_svg_document_element(self): + self.marionette.navigate(svg) + screenshot = self.marionette.screenshot() + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(self.document_element)), + self.get_image_dimensions(screenshot)) + + def test_capture_viewport(self): + url = self.marionette.absolute_url("clicks.html") + self.marionette.navigate(short) + self.marionette.navigate(url) + screenshot = self.marionette.screenshot(full=False) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.viewport_dimensions), + self.get_image_dimensions(screenshot)) + + def test_capture_viewport_after_scroll(self): + self.marionette.navigate(long) + before = self.marionette.screenshot() + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.execute_script( + "arguments[0].scrollIntoView()", script_args=[el]) + after = self.marionette.screenshot(full=False) + self.assertNotEqual(before, after) + self.assertGreater(self.page_y_offset, 0) + + def test_formats(self): + self.marionette.navigate(box) + + # Use a smaller region to speed up the test + element = self.marionette.find_element(By.TAG_NAME, "div") + self.assert_formats(element=element) + + def test_format_unknown(self): + with self.assertRaises(ValueError): + self.marionette.screenshot(format="cheese") + + def test_highlight_elements(self): + self.marionette.navigate(box) + element = self.marionette.find_element(By.TAG_NAME, "div") + + # Highlighting the element itself shouldn't make the image larger + screenshot_element = self.marionette.screenshot(element=element) + screenshot_highlight = self.marionette.screenshot(element=element, + highlights=[element]) + self.assertEqual(self.scale(self.get_element_dimensions(element)), + self.get_image_dimensions(screenshot_highlight)) + self.assertNotEqual(screenshot_element, screenshot_highlight) + + # Highlighting a sub element + paragraph = self.marionette.find_element(By.ID, "green") + screenshot_highlight_paragraph = self.marionette.screenshot(element=element, + highlights=[paragraph]) + self.assertNotEqual(screenshot_element, screenshot_highlight_paragraph) + self.assertNotEqual(screenshot_highlight, screenshot_highlight_paragraph) + + def test_scroll_default(self): + self.marionette.navigate(long) + before = self.page_y_offset + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.screenshot(element=el, format="hash") + self.assertNotEqual(before, self.page_y_offset) + + def test_scroll(self): + self.marionette.navigate(long) + before = self.page_y_offset + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.screenshot(element=el, format="hash", scroll=True) + self.assertNotEqual(before, self.page_y_offset) + + def test_scroll_off(self): + self.marionette.navigate(long) + el = self.marionette.find_element(By.TAG_NAME, "p") + before = self.page_y_offset + self.marionette.screenshot(element=el, format="hash", scroll=False) + self.assertEqual(before, self.page_y_offset) + + def test_scroll_no_element(self): + self.marionette.navigate(long) + before = self.page_y_offset + self.marionette.screenshot(format="hash", scroll=True) + self.assertEqual(before, self.page_y_offset) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_select.py b/testing/marionette/harness/marionette_harness/tests/unit/test_select.py new file mode 100644 index 000000000..3c9522bea --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_select.py @@ -0,0 +1,164 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import urllib + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + + +class SelectTestCase(MarionetteTestCase): + def assertSelected(self, option_element): + self.assertTrue(option_element.is_selected(), "<option> element not selected") + self.assertTrue(self.marionette.execute_script( + "return arguments[0].selected", script_args=[option_element], sandbox=None), + "<option> selected attribute not updated") + + def assertNotSelected(self, option_element): + self.assertFalse(option_element.is_selected(), "<option> is selected") + self.assertFalse(self.marionette.execute_script( + "return arguments[0].selected", script_args=[option_element], sandbox=None), + "<option> selected attribute not updated") + + +class TestSelect(SelectTestCase): + def test_single(self): + self.marionette.navigate(inline(""" + <select> + <option>first + <option>second + </select>""")) + select = self.marionette.find_element(By.TAG_NAME, "select") + options = self.marionette.find_elements(By.TAG_NAME, "option") + + self.assertSelected(options[0]) + options[1].click() + self.assertSelected(options[1]) + + def test_deselect(self): + self.marionette.navigate(inline(""" + <select> + <option>first + <option>second + <option>third + </select>""")) + select = self.marionette.find_element(By.TAG_NAME, "select") + options = self.marionette.find_elements(By.TAG_NAME, "option") + + options[0].click() + self.assertSelected(options[0]) + options[1].click() + self.assertSelected(options[1]) + options[2].click() + self.assertSelected(options[2]) + options[0].click() + self.assertSelected(options[0]) + + def test_out_of_view(self): + self.marionette.navigate(inline(""" + <select> + <option>1 + <option>2 + <option>3 + <option>4 + <option>5 + <option>6 + <option>7 + <option>8 + <option>9 + <option>10 + <option>11 + <option>12 + <option>13 + <option>14 + <option>15 + <option>16 + <option>17 + <option>18 + <option>19 + <option>20 + </select>""")) + select = self.marionette.find_element(By.TAG_NAME, "select") + options = self.marionette.find_elements(By.TAG_NAME, "option") + + options[14].click() + self.assertSelected(options[14]) + + +class TestSelectMultiple(SelectTestCase): + def test_single(self): + self.marionette.navigate(inline("<select multiple> <option>first </select>")) + option = self.marionette.find_element(By.TAG_NAME, "option") + option.click() + self.assertSelected(option) + + def test_multiple(self): + self.marionette.navigate(inline(""" + <select multiple> + <option>first + <option>second + <option>third + </select>""")) + select = self.marionette.find_element(By.TAG_NAME, "select") + options = select.find_elements(By.TAG_NAME, "option") + + options[1].click() + self.assertSelected(options[1]) + + options[2].click() + self.assertSelected(options[2]) + self.assertSelected(options[1]) + + def test_deselect_selected(self): + self.marionette.navigate(inline("<select multiple> <option>first </select>")) + option = self.marionette.find_element(By.TAG_NAME, "option") + option.click() + self.assertSelected(option) + option.click() + self.assertNotSelected(option) + + def test_deselect_preselected(self): + self.marionette.navigate(inline(""" + <select multiple> + <option selected>first + </select>""")) + option = self.marionette.find_element(By.TAG_NAME, "option") + self.assertSelected(option) + option.click() + self.assertNotSelected(option) + + def test_out_of_view(self): + self.marionette.navigate(inline(""" + <select multiple> + <option>1 + <option>2 + <option>3 + <option>4 + <option>5 + <option>6 + <option>7 + <option>8 + <option>9 + <option>10 + <option>11 + <option>12 + <option>13 + <option>14 + <option>15 + <option>16 + <option>17 + <option>18 + <option>19 + <option>20 + </select>""")) + select = self.marionette.find_element(By.TAG_NAME, "select") + options = self.marionette.find_elements(By.TAG_NAME, "option") + + options[-1].click() + self.assertSelected(options[-1]) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_session.py b/testing/marionette/harness/marionette_harness/tests/unit/test_session.py new file mode 100644 index 000000000..1676df51f --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_session.py @@ -0,0 +1,56 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import errors + +from marionette_harness import MarionetteTestCase + + +class TestSession(MarionetteTestCase): + def setUp(self): + super(TestSession, self).setUp() + self.marionette.delete_session() + + def test_new_session_returns_capabilities(self): + # Sends newSession + caps = self.marionette.start_session() + + # Check that session was created. This implies the server + # sent us the sessionId and status fields. + self.assertIsNotNone(self.marionette.session) + + # Required capabilities mandated by WebDriver spec + self.assertIn("browserName", caps) + self.assertIn("browserVersion", caps) + self.assertIn("platformName", caps) + self.assertIn("platformVersion", caps) + + # Optional capabilities we want Marionette to support + self.assertIn("rotatable", caps) + + def test_get_session_id(self): + # Sends newSession + self.marionette.start_session() + + self.assertTrue(self.marionette.session_id is not None) + self.assertTrue(isinstance(self.marionette.session_id, unicode)) + + def test_set_the_session_id(self): + # Sends newSession + self.marionette.start_session(session_id="ILoveCheese") + + self.assertEqual(self.marionette.session_id, "ILoveCheese") + self.assertTrue(isinstance(self.marionette.session_id, unicode)) + + def test_session_already_started(self): + self.marionette.start_session() + self.assertTrue(isinstance(self.marionette.session_id, unicode)) + with self.assertRaises(errors.SessionNotCreatedException): + self.marionette._send_message("newSession", {}) + + def test_no_session(self): + with self.assertRaises(errors.InvalidSessionIdException): + self.marionette.get_url() + self.marionette.start_session() + self.marionette.get_url() diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_set_window_size.py b/testing/marionette/harness/marionette_harness/tests/unit/test_set_window_size.py new file mode 100644 index 000000000..e1bd5e684 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_set_window_size.py @@ -0,0 +1,84 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestSetWindowSize(MarionetteTestCase): + def setUp(self): + super(MarionetteTestCase, self).setUp() + self.start_size = self.marionette.window_size + self.max_width = self.marionette.execute_script("return window.screen.availWidth;") + self.max_height = self.marionette.execute_script("return window.screen.availHeight;") + + def tearDown(self): + # WebDriver spec says a resize cannot result in window being maximized, an + # error is returned if that is the case; therefore if the window is maximized + # at the start of this test, returning to the original size via set_window_size + # size will result in error; so reset to original size minus 1 pixel width + if self.start_size['width'] == self.max_width and self.start_size['height'] == self.max_height: + self.start_size['width']-=1 + self.marionette.set_window_size(self.start_size['width'], self.start_size['height']) + super(MarionetteTestCase, self).tearDown() + + def test_that_we_can_get_and_set_window_size(self): + # event handler + self.marionette.execute_script(""" + window.wrappedJSObject.rcvd_event = false; + window.onresize = function() { + window.wrappedJSObject.rcvd_event = true; + }; + """) + + # valid size + width = self.max_width - 100 + height = self.max_height - 100 + self.marionette.set_window_size(width, height) + self.wait_for_condition(lambda m: m.execute_script("return window.wrappedJSObject.rcvd_event;")) + size = self.marionette.window_size + self.assertEqual(size['width'], width, + "Window width is {0} but should be {1}".format(size['width'], width)) + self.assertEqual(size['height'], height, + "Window height is {0} but should be {1}".format(size['height'], height)) + + def test_that_we_can_get_new_size_when_set_window_size(self): + actual = self.marionette.window_size + width = actual['width'] - 50 + height = actual['height'] - 50 + size = self.marionette.set_window_size(width, height) + self.assertIsNotNone(size, "Response is None") + self.assertEqual(size['width'], width, + "New width is {0} but should be {1}".format(size['width'], width)) + self.assertEqual(size['height'], height, + "New height is {0} but should be {1}".format(size['height'], height)) + + def test_possible_to_request_window_larger_than_screen(self): + self.marionette.set_window_size(4 * self.max_width, 4 * self.max_height) + size = self.marionette.window_size + + # In X the window size may be greater than the bounds of the screen + self.assertGreaterEqual(size["width"], self.max_width) + self.assertGreaterEqual(size["height"], self.max_height) + + def test_that_we_can_maximise_the_window(self): + # valid size + width = self.max_width - 100 + height = self.max_height - 100 + self.marionette.set_window_size(width, height) + + # event handler + self.marionette.execute_script(""" + window.wrappedJSObject.rcvd_event = false; + window.onresize = function() { + window.wrappedJSObject.rcvd_event = true; + }; + """) + self.marionette.maximize_window() + self.wait_for_condition(lambda m: m.execute_script("return window.wrappedJSObject.rcvd_event;")) + + size = self.marionette.window_size + self.assertGreaterEqual(size['width'], self.max_width, + "Window width does not use availWidth, current width: {0}, max width: {1}".format(size['width'], self.max_width)) + self.assertGreaterEqual(size['height'], self.max_height, + "Window height does not use availHeight. current width: {0}, max width: {1}".format(size['height'], self.max_height)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_shadow_dom.py b/testing/marionette/harness/marionette_harness/tests/unit/test_shadow_dom.py new file mode 100644 index 000000000..3f91d7cc0 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_shadow_dom.py @@ -0,0 +1,80 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import ( + NoSuchElementException, + StaleElementException, + UnsupportedOperationException, +) + +from marionette_harness import MarionetteTestCase + + +class TestShadowDom(MarionetteTestCase): + + def setUp(self): + super(TestShadowDom, self).setUp() + self.marionette.set_pref("dom.webcomponents.enabled", True) + self.marionette.navigate(self.marionette.absolute_url("test_shadow_dom.html")) + + self.host = self.marionette.find_element(By.ID, "host") + self.marionette.switch_to_shadow_root(self.host) + self.button = self.marionette.find_element(By.ID, "button") + + def tearDown(self): + self.marionette.clear_pref("dom.webcomponents.enabled") + super(TestShadowDom, self).tearDown() + + def test_chrome_error(self): + with self.marionette.using_context("chrome"): + self.assertRaises(UnsupportedOperationException, + self.marionette.switch_to_shadow_root) + + def test_shadow_dom(self): + # Button in shadow root should be actionable + self.button.click() + + def test_shadow_dom_after_switch_away_from_shadow_root(self): + # Button in shadow root should be actionable + self.button.click() + self.marionette.switch_to_shadow_root() + # After switching back to top content, button should be stale + self.assertRaises(StaleElementException, self.button.click) + + def test_shadow_dom_raises_stale_element_exception_when_button_remove(self): + self.marionette.execute_script( + 'document.getElementById("host").shadowRoot.getElementById("button").remove();') + # After removing button from shadow DOM, button should be stale + self.assertRaises(StaleElementException, self.button.click) + + def test_shadow_dom_raises_stale_element_exception_when_host_removed(self): + self.marionette.execute_script('document.getElementById("host").remove();') + # After removing shadow DOM host element, button should be stale + self.assertRaises(StaleElementException, self.button.click) + + def test_non_existent_shadow_dom(self): + # Jump back to top level content + self.marionette.switch_to_shadow_root() + # When no ShadowRoot is found, switch_to_shadow_root throws NoSuchElementException + self.assertRaises(NoSuchElementException, self.marionette.switch_to_shadow_root, + self.marionette.find_element(By.ID, "empty-host")) + + def test_inner_shadow_dom(self): + # Button in shadow root should be actionable + self.button.click() + self.inner_host = self.marionette.find_element(By.ID, "inner-host") + self.marionette.switch_to_shadow_root(self.inner_host) + self.inner_button = self.marionette.find_element(By.ID, "inner-button") + # Nested nutton in nested shadow root should be actionable + self.inner_button.click() + self.marionette.switch_to_shadow_root() + # After jumping back to parent shadow root, button should again be actionable but inner + # button should now be stale + self.button.click() + self.assertRaises(StaleElementException, self.inner_button.click) + self.marionette.switch_to_shadow_root() + # After switching back to top content, both buttons should now be stale + self.assertRaises(StaleElementException, self.button.click) + self.assertRaises(StaleElementException, self.inner_button.click) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_chrome.js b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_chrome.js new file mode 100644 index 000000000..d5edffa67 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_chrome.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +MARIONETTE_TIMEOUT = 1000; +MARIONETTE_CONTEXT = 'chrome'; + +is(2, 2, "test for is()"); +isnot(2, 3, "test for isnot()"); +ok(2 == 2, "test for ok()"); +finish(); + diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_fail.js b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_fail.js new file mode 100644 index 000000000..16d9aea59 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_fail.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +MARIONETTE_TIMEOUT = 1000; + +/* this test will fail */ + +setTimeout(function() { + is(1, 2, "is(1,2) should fail", TEST_UNEXPECTED_FAIL, TEST_PASS); + finish(); +}, 100); +isnot(1, 1, "isnot(1,1) should fail", TEST_UNEXPECTED_FAIL, TEST_PASS); +ok(1 == 2, "ok(1==2) should fail", TEST_UNEXPECTED_FAIL, TEST_PASS); + + diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_pass.js b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_pass.js new file mode 100644 index 000000000..93ee67619 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_pass.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +MARIONETTE_TIMEOUT = 1000; + +is(2, 2, "test for is()"); +isnot(2, 3, "test for isnot()"); +ok(2 == 2, "test for ok()"); + +setTimeout(finish, 100); + diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_sanity.py b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_sanity.py new file mode 100644 index 000000000..8f9728561 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_sanity.py @@ -0,0 +1,107 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class SimpletestSanityTest(MarionetteTestCase): + callFinish = "return finish();" + + def run_sync(self, test): + return self.marionette.execute_js_script(test, async=False) + + def run_async(self, test): + return self.marionette.execute_js_script(test) + + def test_is(self): + def runtests(): + sentFail1 = "is(true, false, 'isTest1', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish + sentFail2 = "is(true, false, 'isTest2', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish + sentPass1 = "is(true, true, 'isTest3');" + self.callFinish + sentPass2 = "is(true, true, 'isTest4');" + self.callFinish + + self.assertEqual(1, len(self.run_sync(sentFail1)["failures"])) + self.assertEqual(0, self.run_sync(sentFail2)["passed"]) + self.assertEqual(1, self.run_sync(sentPass1)["passed"]) + self.assertEqual(0, len(self.run_sync(sentPass2)["failures"])) + + self.marionette.timeout.script = 1 + self.assertEqual(1, len(self.run_async(sentFail1)["failures"])) + self.assertEqual(0, self.run_async(sentFail2)["passed"]) + self.assertEqual(1, self.run_async(sentPass1)["passed"]) + self.assertEqual(0, len(self.run_async(sentPass2)["failures"])) + + self.marionette.set_context("content") + runtests() + self.marionette.set_context("chrome") + runtests() + + def test_isnot(self): + def runtests(): + sentFail1 = "isnot(true, true, 'isnotTest3', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish + sentFail2 = "isnot(true, true, 'isnotTest4', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish + sentPass1 = "isnot(true, false, 'isnotTest1');" + self.callFinish + sentPass2 = "isnot(true, false, 'isnotTest2');" + self.callFinish + + self.assertEqual(1, len(self.run_sync(sentFail1)["failures"])); + self.assertEqual(0, self.run_sync(sentFail2)["passed"]); + self.assertEqual(0, len(self.run_sync(sentPass1)["failures"])); + self.assertEqual(1, self.run_sync(sentPass2)["passed"]); + + self.marionette.timeout.script = 1 + self.assertEqual(1, len(self.run_async(sentFail1)["failures"])); + self.assertEqual(0, self.run_async(sentFail2)["passed"]); + self.assertEqual(0, len(self.run_async(sentPass1)["failures"])); + self.assertEqual(1, self.run_async(sentPass2)["passed"]); + + self.marionette.set_context("content") + runtests() + self.marionette.set_context("chrome") + runtests() + + def test_ok(self): + def runtests(): + sentFail1 = "ok(1==2, 'testOk1', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish + sentFail2 = "ok(1==2, 'testOk2', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish + sentPass1 = "ok(1==1, 'testOk3');" + self.callFinish + sentPass2 = "ok(1==1, 'testOk4');" + self.callFinish + + self.assertEqual(1, len(self.run_sync(sentFail1)["failures"])); + self.assertEqual(0, self.run_sync(sentFail2)["passed"]); + self.assertEqual(0, len(self.run_sync(sentPass1)["failures"])); + self.assertEqual(1, self.run_sync(sentPass2)["passed"]); + + self.marionette.timeout.script = 1 + self.assertEqual(1, len(self.run_async(sentFail1)["failures"])); + self.assertEqual(0, self.run_async(sentFail2)["passed"]); + self.assertEqual(0, len(self.run_async(sentPass1)["failures"])); + self.assertEqual(1, self.run_async(sentPass2)["passed"]); + + self.marionette.set_context("content") + runtests() + self.marionette.set_context("chrome") + runtests() + + def test_todo(self): + def runtests(): + sentFail1 = "todo(1==1, 'testTodo1', TEST_UNEXPECTED_PASS, TEST_KNOWN_FAIL);" + self.callFinish + sentFail2 = "todo(1==1, 'testTodo2', TEST_UNEXPECTED_PASS, TEST_KNOWN_FAIL);" + self.callFinish + sentPass1 = "todo(1==2, 'testTodo3');" + self.callFinish + sentPass2 = "todo(1==2, 'testTodo4');" + self.callFinish + + self.assertEqual(1, len(self.run_sync(sentFail1)["unexpectedSuccesses"])); + self.assertEqual(0, len(self.run_sync(sentFail2)["expectedFailures"])); + self.assertEqual(0, len(self.run_sync(sentPass1)["unexpectedSuccesses"])); + self.assertEqual(1, len(self.run_sync(sentPass2)["expectedFailures"])); + + self.marionette.timeout.script = 1 + self.assertEqual(1, len(self.run_async(sentFail1)["unexpectedSuccesses"])); + self.assertEqual(0, len(self.run_async(sentFail2)["expectedFailures"])); + self.assertEqual(0, len(self.run_async(sentPass1)["unexpectedSuccesses"])); + self.assertEqual(1, len(self.run_async(sentPass2)["expectedFailures"])); + + self.marionette.set_context("content") + runtests() + self.marionette.set_context("chrome") + runtests() diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_timeout.js b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_timeout.js new file mode 100644 index 000000000..9792a936a --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_simpletest_timeout.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +MARIONETTE_TIMEOUT = 100; + +/* this test will timeout */ + +function do_test() { + is(1, 1); + isnot(1, 2); + ok(1 == 1); + finish(); +} + +setTimeout(do_test, 1000); diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_single_finger_desktop.py b/testing/marionette/harness/marionette_harness/tests/unit/test_single_finger_desktop.py new file mode 100644 index 000000000..8ac80c3c5 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_single_finger_desktop.py @@ -0,0 +1,123 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import sys + +from marionette_driver.errors import MarionetteException +from marionette_driver import Actions, By + +from marionette_harness import MarionetteTestCase, skip + +# add this directory to the path +sys.path.append(os.path.dirname(__file__)) + +from single_finger_functions import ( + chain, chain_flick, context_menu, double_tap, + long_press_action, long_press_on_xy_action, + move_element, move_element_offset, press_release, single_tap, wait, + wait_with_value +) + + +class testSingleFingerMouse(MarionetteTestCase): + def setUp(self): + super(MarionetteTestCase, self).setUp() + # set context menu related preferences needed for some tests + self.marionette.set_context("chrome") + self.enabled = self.marionette.execute_script(""" +let prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); +let value = false; +try { + value = prefs.getBoolPref("ui.click_hold_context_menus"); +} +catch (e) {} +prefs.setBoolPref("ui.click_hold_context_menus", true); +return value; +""") + self.wait_time = self.marionette.execute_script(""" +let prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); +let value = 750; +try { + value = prefs.getIntPref("ui.click_hold_context_menus.delay"); +} +catch (e) {} +prefs.setIntPref("ui.click_hold_context_menus.delay", value); +return value; +""") + self.marionette.set_context("content") + + def tearDown(self): + self.marionette.set_context("chrome") + self.marionette.execute_script( + """ +let prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); +prefs.setBoolPref("ui.click_hold_context_menus", arguments[0]); +""", [self.enabled]) + self.marionette.execute_script( + """ +let prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); +prefs.setIntPref("ui.click_hold_context_menus.delay", arguments[0]); +""", [self.wait_time]) + self.marionette.set_context("content") + super(MarionetteTestCase, self).tearDown() + + def test_press_release(self): + press_release(self.marionette, 1, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click") + + def test_press_release_twice(self): + press_release(self.marionette, 2, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click-mousemove-mousedown-mouseup-click") + + def test_move_element(self): + move_element(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "button2-mousemove-mouseup") + + def test_move_by_offset(self): + move_element_offset(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "button2-mousemove-mouseup") + + def test_wait(self): + wait(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click") + + def test_wait_with_value(self): + wait_with_value(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click") + + @skip("Bug 1191066") + def test_context_menu(self): + context_menu(self.marionette, self.wait_for_condition, + "button1-mousemove-mousedown-contextmenu", + "button1-mousemove-mousedown-contextmenu-mouseup-click") + + @skip("Bug 1191066") + def test_long_press_action(self): + long_press_action(self.marionette, self.wait_for_condition, + "button1-mousemove-mousedown-contextmenu-mouseup-click") + + @skip("Bug 1191066") + def test_long_press_on_xy_action(self): + long_press_on_xy_action(self.marionette, self.wait_for_condition, + "button1-mousemove-mousedown-contextmenu-mouseup-click") + + @skip("Bug 865334") + def test_long_press_fail(self): + testAction = self.marionette.absolute_url("testAction.html") + self.marionette.navigate(testAction) + button = self.marionette.find_element(By.ID, "button1Copy") + action = Actions(self.marionette) + action.press(button).long_press(button, 5) + self.assertRaises(MarionetteException, action.perform) + + def test_chain(self): + chain(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "delayed-mousemove-mouseup") + + def test_chain_flick(self): + chain_flick(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mousemove", "buttonFlick-mousemove-mouseup") + + def test_single_tap(self): + single_tap(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click") + + def test_double_tap(self): + double_tap(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click-mousemove-mousedown-mouseup-click") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_skip_setup.py b/testing/marionette/harness/marionette_harness/tests/unit/test_skip_setup.py new file mode 100644 index 000000000..9a0432fb7 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_skip_setup.py @@ -0,0 +1,35 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase, SkipTest + + +class TestSetUpSkipped(MarionetteTestCase): + + testVar = {'test':'SkipTest'} + + def setUp(self): + try: + self.testVar['email'] + except KeyError: + raise SkipTest('email key not present in dict, skip ...') + MarionetteTestCase.setUp(self) + + def test_assert(self): + assert True + +class TestSetUpNotSkipped(MarionetteTestCase): + + testVar = {'test':'SkipTest'} + + def setUp(self): + try: + self.testVar['test'] + except KeyError: + raise SkipTest('email key not present in dict, skip ...') + MarionetteTestCase.setUp(self) + + def test_assert(self): + assert True + diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py new file mode 100644 index 000000000..18eb34169 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py @@ -0,0 +1,183 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import ( + JavascriptException, + NoSuchFrameException, +) + +from marionette_harness import MarionetteTestCase + + +class TestSwitchFrame(MarionetteTestCase): + def test_switch_simple(self): + start_url = "test_iframe.html" + verify_title = "Marionette IFrame Test" + test_html = self.marionette.absolute_url(start_url) + self.marionette.navigate(test_html) + self.assertEqual(self.marionette.get_active_frame(), None) + frame = self.marionette.find_element(By.ID, "test_iframe") + self.marionette.switch_to_frame(frame) + self.assertTrue(start_url in self.marionette.get_url()) + inner_frame_element = self.marionette.get_active_frame() + # test that we can switch back to main frame, then switch back to the + # inner frame with the value we got from get_active_frame + self.marionette.switch_to_frame() + self.assertEqual(verify_title, self.marionette.title) + self.marionette.switch_to_frame(inner_frame_element) + self.assertTrue(start_url in self.marionette.get_url()) + + def test_switch_nested(self): + start_url = "test_nested_iframe.html" + verify_title = "Marionette IFrame Test" + test_html = self.marionette.absolute_url(start_url) + self.marionette.navigate(test_html) + frame = self.marionette.find_element(By.ID, "test_iframe") + self.assertEqual(self.marionette.get_active_frame(), None) + self.marionette.switch_to_frame(frame) + self.assertTrue(start_url in self.marionette.get_url()) + inner_frame_element = self.marionette.get_active_frame() + # test that we can switch back to main frame, then switch back to the + # inner frame with the value we got from get_active_frame + self.marionette.switch_to_frame() + self.assertEqual(verify_title, self.marionette.title) + self.marionette.switch_to_frame(inner_frame_element) + self.assertTrue(start_url in self.marionette.get_url()) + inner_frame = self.marionette.find_element(By.ID, 'inner_frame') + self.marionette.switch_to_frame(inner_frame) + self.assertTrue(start_url in self.marionette.get_url()) + self.marionette.switch_to_frame() # go back to main frame + self.assertTrue(start_url in self.marionette.get_url()) + #test that we're using the right window object server-side + self.assertTrue("test_nested_iframe.html" in self.marionette.execute_script("return window.location.href;")) + + def test_stack_trace(self): + start_url = "test_iframe.html" + verify_title = "Marionette IFrame Test" + test_html = self.marionette.absolute_url(start_url) + self.marionette.navigate(test_html) + frame = self.marionette.find_element(By.ID, "test_iframe") + self.assertEqual(self.marionette.get_active_frame(), None) + self.marionette.switch_to_frame(frame) + self.assertTrue(start_url in self.marionette.get_url()) + inner_frame_element = self.marionette.get_active_frame() + # test that we can switch back to main frame, then switch back to the + # inner frame with the value we got from get_active_frame + self.marionette.switch_to_frame() + self.assertEqual(verify_title, self.marionette.title) + self.marionette.switch_to_frame(inner_frame_element) + self.assertTrue(start_url in self.marionette.get_url()) + + try: + self.marionette.execute_async_script("foo();") + except JavascriptException as e: + self.assertTrue("foo" in e.message) + + def test_should_be_able_to_carry_on_working_if_the_frame_is_deleted_from_under_us(self): + test_html = self.marionette.absolute_url("deletingFrame.html") + self.marionette.navigate(test_html) + + self.marionette.switch_to_frame(self.marionette.find_element(By.ID, + 'iframe1')) + killIframe = self.marionette.find_element(By.ID, "killIframe") + killIframe.click() + self.marionette.switch_to_frame() + + self.assertEqual(0, len(self.marionette.find_elements(By.ID, "iframe1"))) + + addIFrame = self.marionette.find_element(By.ID, "addBackFrame") + addIFrame.click() + self.marionette.find_element(By.ID, "iframe1") + + self.marionette.switch_to_frame(self.marionette.find_element(By.ID, + "iframe1")) + + self.marionette.find_element(By.ID, "checkbox") + + def test_should_allow_a_user_to_switch_from_an_iframe_back_to_the_main_content_of_the_page(self): + test_iframe = self.marionette.absolute_url("test_iframe.html") + self.marionette.navigate(test_iframe) + self.marionette.switch_to_frame(0) + self.marionette.switch_to_default_content() + header = self.marionette.find_element(By.ID, "iframe_page_heading") + self.assertEqual(header.text, "This is the heading") + + def test_should_be_able_to_switch_to_a_frame_by_its_index(self): + test_html = self.marionette.absolute_url("frameset.html") + self.marionette.navigate(test_html) + self.marionette.switch_to_frame(2) + element = self.marionette.find_element(By.ID, "email") + self.assertEquals("email", element.get_attribute("type")) + + def test_should_be_able_to_switch_to_a_frame_using_a_previously_located_element(self): + test_html = self.marionette.absolute_url("frameset.html") + self.marionette.navigate(test_html) + frame = self.marionette.find_element(By.NAME, "third") + self.marionette.switch_to_frame(frame) + + element = self.marionette.find_element(By.ID, "email") + self.assertEquals("email", element.get_attribute("type")) + + def test_switch_to_frame_with_out_of_bounds_index(self): + self.marionette.navigate(self.marionette.absolute_url("test_iframe.html")) + count = self.marionette.execute_script("return window.frames.length;") + self.assertRaises(NoSuchFrameException, self.marionette.switch_to_frame, count) + + def test_switch_to_frame_with_negative_index(self): + self.marionette.navigate(self.marionette.absolute_url("test_iframe.html")) + self.assertRaises(NoSuchFrameException, self.marionette.switch_to_frame, -1) + + def test_switch_to_parent_frame(self): + frame_html = self.marionette.absolute_url("frameset.html") + self.marionette.navigate(frame_html) + frame = self.marionette.find_element(By.NAME, "third") + self.marionette.switch_to_frame(frame) + + # If we don't find the following element we aren't on the right page + self.marionette.find_element(By.ID, "checky") + form_page_title = self.marionette.execute_script("return document.title") + self.assertEqual("We Leave From Here", form_page_title) + + self.marionette.switch_to_parent_frame() + + current_page_title = self.marionette.execute_script("return document.title") + self.assertEqual("Unique title", current_page_title) + + def test_switch_to_parent_frame_from_default_context_is_a_noop(self): + formpage = self.marionette.absolute_url("formPage.html") + self.marionette.navigate(formpage) + + self.marionette.switch_to_parent_frame() + + form_page_title = self.marionette.execute_script("return document.title") + self.assertEqual("We Leave From Here", form_page_title) + + def test_should_be_able_to_switch_to_parent_from_second_level(self): + frame_html = self.marionette.absolute_url("frameset.html") + self.marionette.navigate(frame_html) + frame = self.marionette.find_element(By.NAME, "fourth") + self.marionette.switch_to_frame(frame) + + second_level = self.marionette.find_element(By.NAME, "child1") + self.marionette.switch_to_frame(second_level) + self.marionette.find_element(By.NAME, "myCheckBox") + + self.marionette.switch_to_parent_frame() + + second_level = self.marionette.find_element(By.NAME, "child1") + + def test_should_be_able_to_switch_to_parent_from_iframe(self): + frame_html = self.marionette.absolute_url("test_iframe.html") + self.marionette.navigate(frame_html) + frame = self.marionette.find_element(By.ID, "test_iframe") + self.marionette.switch_to_frame(frame) + + current_page_title = self.marionette.execute_script("return document.title") + self.assertEqual("Marionette Test", current_page_title) + + self.marionette.switch_to_parent_frame() + + parent_page_title = self.marionette.execute_script("return document.title") + self.assertEqual("Marionette IFrame Test", parent_page_title) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py new file mode 100644 index 000000000..03c13026e --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py @@ -0,0 +1,56 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import JavascriptException + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestSwitchFrameChrome(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestSwitchFrameChrome, self).setUp() + self.marionette.set_context("chrome") + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test.xul', + 'foo', 'chrome,centerscreen'); + """) + + new_window = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(new_window) + self.assertNotEqual(self.start_window, self.marionette.current_chrome_window_handle) + + def tearDown(self): + self.close_all_windows() + super(TestSwitchFrameChrome, self).tearDown() + + def test_switch_simple(self): + self.assertIn("test.xul", self.marionette.get_url(), "Initial navigation has failed") + self.marionette.switch_to_frame(0) + self.assertIn("test2.xul", self.marionette.get_url(),"Switching by index failed") + self.marionette.switch_to_frame() + self.assertEqual(None, self.marionette.get_active_frame(), "Switiching by null failed") + self.assertIn("test.xul", self.marionette.get_url(), "Switching by null failed") + self.marionette.switch_to_frame("iframe") + self.assertIn("test2.xul", self.marionette.get_url(), "Switching by name failed") + self.marionette.switch_to_frame() + self.assertIn("test.xul", self.marionette.get_url(), "Switching by null failed") + self.marionette.switch_to_frame("iframename") + self.assertIn("test2.xul", self.marionette.get_url(), "Switching by name failed") + iframe_element = self.marionette.get_active_frame() + self.marionette.switch_to_frame() + self.assertIn("test.xul", self.marionette.get_url(), "Switching by null failed") + self.marionette.switch_to_frame(iframe_element) + self.assertIn("test2.xul", self.marionette.get_url(), "Switching by element failed") + + def test_stack_trace(self): + self.assertIn("test.xul", self.marionette.get_url(), "Initial navigation has failed") + self.marionette.switch_to_frame(0) + self.assertRaises(JavascriptException, self.marionette.execute_async_script, "foo();") + try: + self.marionette.execute_async_script("foo();") + except JavascriptException as e: + self.assertIn("foo", e.message) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_remote_frame.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_remote_frame.py new file mode 100644 index 000000000..07ddeef2a --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_remote_frame.py @@ -0,0 +1,118 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +OOP_BY_DEFAULT = "dom.ipc.browser_frames.oop_by_default" +BROWSER_FRAMES_ENABLED = "dom.mozBrowserFramesEnabled" + + +class TestSwitchRemoteFrame(MarionetteTestCase): + def setUp(self): + super(TestSwitchRemoteFrame, self).setUp() + with self.marionette.using_context('chrome'): + self.oop_by_default = self.marionette.get_pref(OOP_BY_DEFAULT) + self.mozBrowserFramesEnabled = self.marionette.get_pref(BROWSER_FRAMES_ENABLED) + self.marionette.set_pref(OOP_BY_DEFAULT, True) + self.marionette.set_pref(BROWSER_FRAMES_ENABLED, True) + + self.multi_process_browser = self.marionette.execute_script(""" + try { + return Services.appinfo.browserTabsRemoteAutostart; + } catch (e) { + return false; + }""") + + def tearDown(self): + with self.marionette.using_context("chrome"): + if self.oop_by_default is None: + self.marionette.clear_pref(OOP_BY_DEFAULT) + else: + self.marionette.set_pref(OOP_BY_DEFAULT, self.oop_by_default) + + if self.mozBrowserFramesEnabled is None: + self.marionette.clear_pref(BROWSER_FRAMES_ENABLED) + else: + self.marionette.set_pref(BROWSER_FRAMES_ENABLED, self.mozBrowserFramesEnabled) + + @property + def is_main_process(self): + return self.marionette.execute_script(""" + return Components.classes["@mozilla.org/xre/app-info;1"]. + getService(Components.interfaces.nsIXULRuntime). + processType == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + """, sandbox="system") + + def test_remote_frame(self): + self.marionette.navigate(self.marionette.absolute_url("test.html")) + self.marionette.push_permission('browser', True) + self.marionette.execute_script(""" + let iframe = document.createElement("iframe"); + iframe.setAttribute('mozbrowser', true); + iframe.setAttribute('remote', true); + iframe.id = "remote_iframe"; + iframe.style.height = "100px"; + iframe.style.width = "100%%"; + iframe.src = "{}"; + document.body.appendChild(iframe); + """.format(self.marionette.absolute_url("test.html"))) + remote_iframe = self.marionette.find_element(By.ID, "remote_iframe") + self.marionette.switch_to_frame(remote_iframe) + main_process = self.is_main_process + self.assertFalse(main_process) + + def test_remote_frame_revisit(self): + # test if we can revisit a remote frame (this takes a different codepath) + self.marionette.navigate(self.marionette.absolute_url("test.html")) + self.marionette.push_permission('browser', True) + self.marionette.execute_script(""" + let iframe = document.createElement("iframe"); + iframe.setAttribute('mozbrowser', true); + iframe.setAttribute('remote', true); + iframe.id = "remote_iframe"; + iframe.style.height = "100px"; + iframe.style.width = "100%%"; + iframe.src = "{}"; + document.body.appendChild(iframe); + """.format(self.marionette.absolute_url("test.html"))) + self.marionette.switch_to_frame(self.marionette.find_element(By.ID, + "remote_iframe")) + main_process = self.is_main_process + self.assertFalse(main_process) + self.marionette.switch_to_frame() + main_process = self.is_main_process + should_be_main_process = not self.multi_process_browser + self.assertEqual(main_process, should_be_main_process) + self.marionette.switch_to_frame(self.marionette.find_element(By.ID, + "remote_iframe")) + main_process = self.is_main_process + self.assertFalse(main_process) + + def test_we_can_switch_to_a_remote_frame_by_index(self): + # test if we can revisit a remote frame (this takes a different codepath) + self.marionette.navigate(self.marionette.absolute_url("test.html")) + self.marionette.push_permission('browser', True) + self.marionette.execute_script(""" + let iframe = document.createElement("iframe"); + iframe.setAttribute('mozbrowser', true); + iframe.setAttribute('remote', true); + iframe.id = "remote_iframe"; + iframe.style.height = "100px"; + iframe.style.width = "100%%"; + iframe.src = "{}"; + document.body.appendChild(iframe); + """.format(self.marionette.absolute_url("test.html"))) + self.marionette.switch_to_frame(0) + main_process = self.is_main_process + self.assertFalse(main_process) + self.marionette.switch_to_frame() + main_process = self.is_main_process + should_be_main_process = not self.multi_process_browser + self.assertEqual(main_process, should_be_main_process) + self.marionette.switch_to_frame(0) + main_process = self.is_main_process + self.assertFalse(main_process) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py new file mode 100644 index 000000000..0ad63b6ce --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py @@ -0,0 +1,124 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import sys +from unittest import skipIf + +from marionette_driver import By + +# add this directory to the path +sys.path.append(os.path.dirname(__file__)) + +from test_switch_window_content import TestSwitchToWindowContent + + +class TestSwitchWindowChrome(TestSwitchToWindowContent): + + def setUp(self): + super(TestSwitchWindowChrome, self).setUp() + + self.marionette.set_context("chrome") + + def tearDown(self): + self.close_all_windows() + + super(TestSwitchWindowChrome, self).tearDown() + + def open_window_in_background(self): + with self.marionette.using_context("chrome"): + self.marionette.execute_script(""" + window.open("about:blank", null, "location=1,toolbar=1"); + window.focus(); + """) + + def open_window_in_foreground(self): + with self.marionette.using_context("content"): + self.marionette.navigate(self.test_page) + link = self.marionette.find_element(By.ID, "new-window") + link.click() + + @skipIf(sys.platform.startswith("linux"), + "Bug 1335457 - Fails to open a background window on Linux") + def test_switch_tabs_for_new_background_window_without_focus_change(self): + # Bug 1334981 - with testmode enabled getMostRecentWindow detects the wrong window + with self.marionette.using_prefs({"focusmanager.testmode": False}): + # Open an addition tab in the original window so we can better check + # the selected index in thew new window to be opened. + second_tab = self.open_tab(trigger=self.open_tab_in_foreground) + self.marionette.switch_to_window(second_tab, focus=True) + second_tab_index = self.get_selected_tab_index() + self.assertNotEqual(second_tab_index, self.selected_tab_index) + + # Opens a new background window, but we are interested in the tab + tab_in_new_window = self.open_tab(trigger=self.open_window_in_background) + self.assertEqual(self.marionette.current_window_handle, second_tab) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + self.assertEqual(self.get_selected_tab_index(), second_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.empty_page) + + # Switch to the tab in the new window but don't focus it + self.marionette.switch_to_window(tab_in_new_window, focus=False) + self.assertEqual(self.marionette.current_window_handle, tab_in_new_window) + self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window) + self.assertEqual(self.get_selected_tab_index(), second_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), "about:blank") + + def test_switch_tabs_for_new_foreground_window_with_focus_change(self): + # Open an addition tab in the original window so we can better check + # the selected index in thew new window to be opened. + second_tab = self.open_tab(trigger=self.open_tab_in_foreground) + self.marionette.switch_to_window(second_tab, focus=True) + second_tab_index = self.get_selected_tab_index() + self.assertNotEqual(second_tab_index, self.selected_tab_index) + + # Opens a new window, but we are interested in the tab + tab_in_new_window = self.open_tab(trigger=self.open_window_in_foreground) + self.assertEqual(self.marionette.current_window_handle, second_tab) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + self.assertNotEqual(self.get_selected_tab_index(), second_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + self.marionette.switch_to_window(tab_in_new_window) + self.assertEqual(self.marionette.current_window_handle, tab_in_new_window) + self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window) + self.assertNotEqual(self.get_selected_tab_index(), second_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.empty_page) + + self.marionette.switch_to_window(second_tab, focus=True) + self.assertEqual(self.marionette.current_window_handle, second_tab) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + # Bug 1335085 - The focus doesn't change even as requested so. + # self.assertEqual(self.get_selected_tab_index(), second_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + def test_switch_tabs_for_new_foreground_window_without_focus_change(self): + # Open an addition tab in the original window so we can better check + # the selected index in thew new window to be opened. + second_tab = self.open_tab(trigger=self.open_tab_in_foreground) + self.marionette.switch_to_window(second_tab, focus=True) + second_tab_index = self.get_selected_tab_index() + self.assertNotEqual(second_tab_index, self.selected_tab_index) + + # Opens a new window, but we are interested in the tab which automatically + # gets the focus. + self.open_tab(trigger=self.open_window_in_foreground) + self.assertEqual(self.marionette.current_window_handle, second_tab) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + self.assertNotEqual(self.get_selected_tab_index(), second_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + # Switch to the second tab in the first window, but don't focus it. + self.marionette.switch_to_window(second_tab, focus=False) + self.assertEqual(self.marionette.current_window_handle, second_tab) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + self.assertNotEqual(self.get_selected_tab_index(), second_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py new file mode 100644 index 000000000..fbab1898f --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_content.py @@ -0,0 +1,171 @@ +# This Source Code Form is subject to the terms of the Mozilla ublic +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import Actions, By, Wait +from marionette_driver.keys import Keys + +from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin + + +class TestSwitchToWindowContent(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestSwitchToWindowContent, self).setUp() + + if self.marionette.session_capabilities["platformName"] == "darwin": + self.mod_key = Keys.META + else: + self.mod_key = Keys.CONTROL + + self.empty_page = self.marionette.absolute_url("empty.html") + self.test_page = self.marionette.absolute_url("windowHandles.html") + + self.selected_tab_index = self.get_selected_tab_index() + + with self.marionette.using_context("content"): + self.marionette.navigate(self.test_page) + + def tearDown(self): + self.close_all_tabs() + + super(TestSwitchToWindowContent, self).tearDown() + + def get_selected_tab_index(self): + with self.marionette.using_context("chrome"): + return self.marionette.execute_script(""" + Components.utils.import("resource://gre/modules/AppConstants.jsm"); + + let win = null; + + if (AppConstants.MOZ_APP_NAME == "fennec") { + Components.utils.import("resource://gre/modules/Services.jsm"); + win = Services.wm.getMostRecentWindow("navigator:browser"); + } else { + Components.utils.import("resource:///modules/RecentWindow.jsm"); + win = RecentWindow.getMostRecentBrowserWindow(); + } + + let tabBrowser = null; + + // Fennec + if (win.BrowserApp) { + tabBrowser = win.BrowserApp; + + // Firefox + } else if (win.gBrowser) { + tabBrowser = win.gBrowser; + + } else { + return null; + } + + for (let i = 0; i < tabBrowser.tabs.length; i++) { + if (tabBrowser.tabs[i] == tabBrowser.selectedTab) { + return i; + } + } + """) + + def open_tab_in_background(self): + with self.marionette.using_context("content"): + link = self.marionette.find_element(By.ID, "new-tab") + + action = Actions(self.marionette) + action.key_down(self.mod_key).click(link).perform() + + def open_tab_in_foreground(self): + with self.marionette.using_context("content"): + link = self.marionette.find_element(By.ID, "new-tab") + link.click() + + def test_switch_tabs_with_focus_change(self): + new_tab = self.open_tab(self.open_tab_in_foreground) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index) + + with self.marionette.using_context("content"): + Wait(self.marionette).until( + lambda _: self.marionette.get_url() == self.empty_page, + message="{} has been loaded in the newly opened tab.".format(self.empty_page)) + + self.marionette.switch_to_window(self.start_tab, focus=True) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + self.marionette.switch_to_window(new_tab) + self.marionette.close() + self.marionette.switch_to_window(self.start_tab) + + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + def test_switch_tabs_without_focus_change(self): + new_tab = self.open_tab(self.open_tab_in_foreground) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + # Switch to new tab first because it is already selected + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + + self.marionette.switch_to_window(self.start_tab, focus=False) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index) + + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + self.marionette.switch_to_window(new_tab) + self.marionette.close() + + self.marionette.switch_to_window(self.start_tab) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + def test_switch_from_content_to_chrome_window_should_not_change_selected_tab(self): + new_tab = self.open_tab(self.open_tab_in_foreground) + + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + new_tab_index = self.get_selected_tab_index() + + self.marionette.switch_to_window(self.start_window) + self.assertEqual(self.marionette.current_window_handle, new_tab) + self.assertEqual(self.get_selected_tab_index(), new_tab_index) + + @skip_if_mobile("New windows not supported in Fennec") + def test_switch_to_new_private_browsing_window_has_to_register_browsers(self): + # Test that tabs (browsers) are correctly registered for a newly opened + # private browsing window. This has to also happen without explicitely + # switching to the tab itself before using any commands in content scope. + # + # Note: Not sure why this only affects private browsing windows only. + + def open_private_browsing_window(): + with self.marionette.using_context("content"): + self.marionette.navigate("about:privatebrowsing") + button = self.marionette.find_element(By.ID, "startPrivateBrowsing") + button.click() + + new_window = self.open_window(open_private_browsing_window) + self.marionette.switch_to_window(new_window) + self.assertEqual(self.marionette.current_chrome_window_handle, new_window) + self.assertNotEqual(self.marionette.current_window_handle, self.start_tab) + + with self.marionette.using_context("content"): + self.marionette.execute_script(" return true; ") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_teardown_context_preserved.py b/testing/marionette/harness/marionette_harness/tests/unit/test_teardown_context_preserved.py new file mode 100644 index 000000000..843152bc5 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_teardown_context_preserved.py @@ -0,0 +1,21 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase, SkipTest + + +class TestTearDownContext(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context(self.marionette.CONTEXT_CHROME) + + def tearDown(self): + self.assertEqual(self.get_context(), self.marionette.CONTEXT_CHROME) + MarionetteTestCase.tearDown(self) + + def get_context(self): + return self.marionette._send_message("getContext", key="value") + + def test_skipped_teardown_ok(self): + raise SkipTest("This should leave our teardown method in chrome context") diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_text.py b/testing/marionette/harness/marionette_harness/tests/unit/test_text.py new file mode 100644 index 000000000..e2025e9b6 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_text.py @@ -0,0 +1,224 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.keys import Keys + +from marionette_harness import MarionetteTestCase, skip_if_mobile + + +class TestText(MarionetteTestCase): + def test_getText(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + l = self.marionette.find_element(By.ID, "mozLink") + self.assertEqual("Click me!", l.text) + + def test_clearText(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + l = self.marionette.find_element(By.NAME, "myInput") + self.assertEqual("asdf", self.marionette.execute_script("return arguments[0].value;", [l])) + l.clear() + self.assertEqual("", self.marionette.execute_script("return arguments[0].value;", [l])) + + def test_sendKeys(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + l = self.marionette.find_element(By.NAME, "myInput") + self.assertEqual("asdf", self.marionette.execute_script("return arguments[0].value;", [l])) + + # Set caret position to the middle of the input text. + self.marionette.execute_script( + """var el = arguments[0]; + el.selectionStart = el.selectionEnd = el.value.length / 2;""", + script_args=[l]) + + l.send_keys("o") + self.assertEqual("asodf", self.marionette.execute_script("return arguments[0].value;", [l])) + + def test_send_keys_to_type_input(self): + test_html = self.marionette.absolute_url("html5/test_html_inputs.html") + self.marionette.navigate(test_html) + num_input = self.marionette.find_element(By.ID, 'number') + self.assertEqual("", self.marionette.execute_script("return arguments[0].value", [num_input])) + num_input.send_keys("1234") + self.assertEqual('1234', self.marionette.execute_script("return arguments[0].value", [num_input])) + + def test_should_fire_key_press_events(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys("a") + + result = self.marionette.find_element(By.ID, "result") + self.assertIn("press:", result.text) + + def test_should_fire_key_down_events(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys("a") + + result = self.marionette.find_element(By.ID, "result") + self.assertIn("down:", result.text) + + def test_should_fire_key_up_events(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys("a") + + result = self.marionette.find_element(By.ID, "result") + self.assertIn("up:", result.text) + + def test_should_type_lowercase_characters(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys("abc def") + + self.assertEqual("abc def", key_reporter.get_property("value")) + + def test_should_type_uppercase_characters(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys("ABC DEF") + + self.assertEqual("ABC DEF", key_reporter.get_property("value")) + + def test_should_type_a_quote_characters(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys('"') + + self.assertEqual('"', key_reporter.get_property("value")) + + def test_should_type_an_at_character(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys('@') + + self.assertEqual("@", key_reporter.get_property("value")) + + def test_should_type_a_mix_of_upper_and_lower_case_character(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys("me@EXampLe.com") + + self.assertEqual("me@EXampLe.com", key_reporter.get_property("value")) + + def test_arrow_keys_are_not_printable(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + key_reporter = self.marionette.find_element(By.ID, "keyReporter") + key_reporter.send_keys(Keys.ARROW_LEFT) + + self.assertEqual("", key_reporter.get_property("value")) + + def test_will_simulate_a_key_up_when_entering_text_into_input_elements(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "keyUp") + element.send_keys("I like cheese") + + result = self.marionette.find_element(By.ID, "result") + self.assertEqual(result.text, "I like cheese") + + def test_will_simulate_a_key_down_when_entering_text_into_input_elements(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "keyDown") + element.send_keys("I like cheese") + + result = self.marionette.find_element(By.ID, "result") + # Because the key down gets the result before the input element is + # filled, we're a letter short here + self.assertEqual(result.text, "I like chees") + + def test_will_simulate_a_key_press_when_entering_text_into_input_elements(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "keyPress") + element.send_keys("I like cheese") + + result = self.marionette.find_element(By.ID, "result") + # Because the key down gets the result before the input element is + # filled, we're a letter short here + self.assertEqual(result.text, "I like chees") + + def test_will_simulate_a_keyup_when_entering_text_into_textareas(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "keyUpArea") + element.send_keys("I like cheese") + + result = self.marionette.find_element(By.ID, "result") + self.assertEqual(result.text, "I like cheese") + + def test_will_simulate_a_keydown_when_entering_text_into_textareas(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "keyDownArea") + element.send_keys("I like cheese") + + result = self.marionette.find_element(By.ID, "result") + # Because the key down gets the result before the input element is + # filled, we're a letter short here + self.assertEqual(result.text, "I like chees") + + def test_will_simulate_a_keypress_when_entering_text_into_textareas(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "keyPressArea") + element.send_keys("I like cheese") + + result = self.marionette.find_element(By.ID, "result") + # Because the key down gets the result before the input element is + # filled, we're a letter short here + self.assertEqual(result.text, "I like chees") + + @skip_if_mobile("Bug 1333069 - Assertion: 'down: 40' not found in u''") + def test_should_report_key_code_of_arrow_keys_up_down_events(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + result = self.marionette.find_element(By.ID, "result") + element = self.marionette.find_element(By.ID, "keyReporter") + element.send_keys(Keys.ARROW_DOWN) + self.assertIn("down: 40", result.text.strip()) + self.assertIn("up: 40", result.text.strip()) + + element.send_keys(Keys.ARROW_UP) + self.assertIn("down: 38", result.text.strip()) + self.assertIn("up: 38", result.text.strip()) + + element.send_keys(Keys.ARROW_LEFT) + self.assertIn("down: 37", result.text.strip()) + self.assertIn("up: 37", result.text.strip()) + + element.send_keys(Keys.ARROW_RIGHT) + self.assertIn("down: 39", result.text.strip()) + self.assertIn("up: 39", result.text.strip()) + + # And leave no rubbish/printable keys in the "keyReporter" + self.assertEqual("", element.get_property("value")) + + def testNumericNonShiftKeys(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "keyReporter") + numericLineCharsNonShifted = "`1234567890-=[]\\,.'/42" + element.send_keys(numericLineCharsNonShifted) + self.assertEqual(numericLineCharsNonShifted, element.get_property("value")) + + def testShouldTypeAnInteger(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "keyReporter") + element.send_keys(1234) + self.assertEqual("1234", element.get_property("value")) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_text_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_text_chrome.py new file mode 100644 index 000000000..e0b63de16 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_text_chrome.py @@ -0,0 +1,44 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase, skip, WindowManagerMixin + + +@skip("Disabled in bug 896043 and when working on Chrome code re-enable for bug 896046") +class TestTextChrome(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestTextChrome, self).setUp() + self.marionette.set_context("chrome") + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test.xul', + 'foo', 'chrome,centerscreen'); + """) + + new_window = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(new_window) + + def tearDown(self): + self.close_all_windows() + super(TestTextChrome, self).tearDown() + + def test_getText(self): + box = self.marionette.find_element(By.ID, "textInput") + self.assertEqual("test", box.text) + + def test_clearText(self): + box = self.marionette.find_element(By.ID, "textInput") + self.assertEqual("test", box.text) + box.clear() + self.assertEqual("", box.text) + + def test_sendKeys(self): + box = self.marionette.find_element(By.ID, "textInput") + self.assertEqual("test", box.text) + box.send_keys("at") + self.assertEqual("attest", box.text) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py b/testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py new file mode 100644 index 000000000..354e6a7eb --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py @@ -0,0 +1,115 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By +from marionette_driver.errors import ( + MarionetteException, + NoSuchElementException, + ScriptTimeoutException, +) +from marionette_driver.marionette import HTMLElement + +from marionette_harness import MarionetteTestCase, run_if_manage_instance, skip_if_mobile + + +class TestTimeouts(MarionetteTestCase): + def tearDown(self): + self.marionette.timeout.reset() + MarionetteTestCase.tearDown(self) + + def test_page_timeout_notdefinetimeout_pass(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + + def test_page_timeout_fail(self): + self.marionette.timeout.page_load = 0 + test_html = self.marionette.absolute_url("test.html") + self.assertRaises(MarionetteException, self.marionette.navigate, test_html) + + def test_page_timeout_pass(self): + self.marionette.timeout.page_load = 60 + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + + def test_search_timeout_notfound_settimeout(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + self.marionette.timeout.implicit = 1 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page") + self.marionette.timeout.implicit = 0 + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page") + + def test_search_timeout_found_settimeout(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + button = self.marionette.find_element(By.ID, "createDivButton") + button.click() + self.marionette.timeout.implicit = 8 + self.assertEqual(HTMLElement, type(self.marionette.find_element(By.ID, "newDiv"))) + + def test_search_timeout_found(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + button = self.marionette.find_element(By.ID, "createDivButton") + button.click() + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "newDiv") + + @run_if_manage_instance("Only runnable if Marionette manages the instance") + @skip_if_mobile("Bug 1322993 - Missing temporary folder") + def test_reset_timeout(self): + timeouts = [getattr(self.marionette.timeout, f) for f in ( + 'implicit', 'page_load', 'script',)] + + def do_check(callback): + for timeout in timeouts: + timeout = 10000 + self.assertEqual(timeout, 10000) + callback() + for timeout in timeouts: + self.assertNotEqual(timeout, 10000) + + def callback_quit(): + self.marionette.quit() + self.marionette.start_session() + + do_check(self.marionette.restart) + do_check(callback_quit) + + def test_execute_async_timeout_settimeout(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + self.marionette.timeout.script = 1 + self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "var x = 1;") + + def test_no_timeout_settimeout(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + self.marionette.timeout.script = 1 + self.assertTrue(self.marionette.execute_async_script(""" + var callback = arguments[arguments.length - 1]; + setTimeout(function() { callback(true); }, 500); + """)) + + def test_compat_input_types(self): + # When using the spec-incompatible input format which we have + # for backwards compatibility, it should be possible to send ms + # as a string type and have the server parseInt it to an integer. + body = {"type": "script", "ms": "30000"} + self.marionette._send_message("setTimeouts", body) + + def test_deprecated_set_timeouts_command(self): + body = {"implicit": 3000} + self.marionette._send_message("timeouts", body) + + def test_deprecated_set_search_timeout(self): + self.marionette.set_search_timeout(1000) + self.assertEqual(1, self.marionette.timeout.implicit) + + def test_deprecated_set_script_timeout(self): + self.marionette.set_script_timeout(2000) + self.assertEqual(2, self.marionette.timeout.script) + + def test_deprecated_set_page_load_timeout(self): + self.marionette.set_page_load_timeout(3000) + self.assertEqual(3, self.marionette.timeout.page_load) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_transport.py b/testing/marionette/harness/marionette_harness/tests/unit/test_transport.py new file mode 100644 index 000000000..39e36a9b2 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_transport.py @@ -0,0 +1,172 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import json + +from marionette_driver.transport import ( + Command, + Proto2Command, + Proto2Response, + Response, +) + +from marionette_harness import MarionetteTestCase, skip_unless_protocol + + +get_current_url = ("getCurrentUrl", None) +execute_script = ("executeScript", {"script": "return 42"}) + + +class TestMessageSequencing(MarionetteTestCase): + @property + def last_id(self): + return self.marionette.client.last_id + + @last_id.setter + def last_id(self, new_id): + self.marionette.client.last_id = new_id + + def send(self, name, params): + self.last_id = self.last_id + 1 + cmd = Command(self.last_id, name, params) + self.marionette.client.send(cmd) + return self.last_id + + @skip_unless_protocol("Skip for level < 3", lambda level: level >= 3) + def test_discard_older_messages(self): + first = self.send(*get_current_url) + second = self.send(*execute_script) + resp = self.marionette.client.receive() + self.assertEqual(second, resp.id) + + @skip_unless_protocol("Skip for level < 3", lambda level: level >= 3) + def test_last_id_incremented(self): + before = self.last_id + self.send(*get_current_url) + self.assertGreater(self.last_id, before) + + +class MessageTestCase(MarionetteTestCase): + def assert_attr(self, obj, attr): + self.assertTrue(hasattr(obj, attr), + "object does not have attribute {}".format(attr)) + + +class TestCommand(MessageTestCase): + def create(self, msgid="msgid", name="name", params="params"): + return Command(msgid, name, params) + + def test_initialise(self): + cmd = self.create() + self.assert_attr(cmd, "id") + self.assert_attr(cmd, "name") + self.assert_attr(cmd, "params") + self.assertEqual("msgid", cmd.id) + self.assertEqual("name", cmd.name) + self.assertEqual("params", cmd.params) + + def test_stringify(self): + cmd = self.create() + string = str(cmd) + self.assertIn("Command", string) + self.assertIn("id=msgid", string) + self.assertIn("name=name", string) + self.assertIn("params=params", string) + + def test_to_msg(self): + cmd = self.create() + msg = json.loads(cmd.to_msg()) + self.assertEquals(msg[0], Command.TYPE) + self.assertEquals(msg[1], "msgid") + self.assertEquals(msg[2], "name") + self.assertEquals(msg[3], "params") + + def test_from_msg(self): + msg = [Command.TYPE, "msgid", "name", "params"] + payload = json.dumps(msg) + cmd = Command.from_msg(payload) + self.assertEquals(msg[1], cmd.id) + self.assertEquals(msg[2], cmd.name) + self.assertEquals(msg[3], cmd.params) + + +class TestResponse(MessageTestCase): + def create(self, msgid="msgid", error="error", result="result"): + return Response(msgid, error, result) + + def test_initialise(self): + resp = self.create() + self.assert_attr(resp, "id") + self.assert_attr(resp, "error") + self.assert_attr(resp, "result") + self.assertEqual("msgid", resp.id) + self.assertEqual("error", resp.error) + self.assertEqual("result", resp.result) + + def test_stringify(self): + resp = self.create() + string = str(resp) + self.assertIn("Response", string) + self.assertIn("id=msgid", string) + self.assertIn("error=error", string) + self.assertIn("result=result", string) + + def test_to_msg(self): + resp = self.create() + msg = json.loads(resp.to_msg()) + self.assertEquals(msg[0], Response.TYPE) + self.assertEquals(msg[1], "msgid") + self.assertEquals(msg[2], "error") + self.assertEquals(msg[3], "result") + + def test_from_msg(self): + msg = [Response.TYPE, "msgid", "error", "result"] + payload = json.dumps(msg) + resp = Response.from_msg(payload) + self.assertEquals(msg[1], resp.id) + self.assertEquals(msg[2], resp.error) + self.assertEquals(msg[3], resp.result) + + +class TestProto2Command(MessageTestCase): + def create(self, name="name", params="params"): + return Proto2Command(name, params) + + def test_initialise(self): + cmd = self.create() + self.assert_attr(cmd, "id") + self.assert_attr(cmd, "name") + self.assert_attr(cmd, "params") + self.assertEqual(None, cmd.id) + self.assertEqual("name", cmd.name) + self.assertEqual("params", cmd.params) + + def test_from_data_unknown(self): + with self.assertRaises(ValueError): + cmd = Proto2Command.from_data({}) + + +class TestProto2Response(MessageTestCase): + def create(self, error="error", result="result"): + return Proto2Response(error, result) + + def test_initialise(self): + resp = self.create() + self.assert_attr(resp, "id") + self.assert_attr(resp, "error") + self.assert_attr(resp, "result") + self.assertEqual(None, resp.id) + self.assertEqual("error", resp.error) + self.assertEqual("result", resp.result) + + def test_from_data_error(self): + data = {"error": "error"} + resp = Proto2Response.from_data(data) + self.assertEqual(data, resp.error) + self.assertEqual(None, resp.result) + + def test_from_data_result(self): + resp = Proto2Response.from_data("result") + self.assertEqual(None, resp.error) + self.assertEqual("result", resp.result) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_typing.py b/testing/marionette/harness/marionette_harness/tests/unit/test_typing.py new file mode 100644 index 000000000..ca63a0dc7 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_typing.py @@ -0,0 +1,332 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import urllib + +from marionette_driver.by import By +from marionette_driver.errors import ElementNotInteractableException +from marionette_driver.keys import Keys + +from marionette_harness import MarionetteTestCase, skip, skip_if_mobile + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + + +class TypingTestCase(MarionetteTestCase): + + def setUp(self): + super(TypingTestCase, self).setUp() + + if self.marionette.session_capabilities["platformName"] == "darwin": + self.mod_key = Keys.META + else: + self.mod_key = Keys.CONTROL + + +class TestTypingChrome(TypingTestCase): + + def setUp(self): + super(TestTypingChrome, self).setUp() + self.marionette.set_context("chrome") + + @skip_if_mobile("Interacting with chrome elements not available for Fennec") + def test_cut_and_paste_shortcuts(self): + with self.marionette.using_context("content"): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + self.assertEqual("", keyReporter.get_property("value")) + keyReporter.send_keys("zyxwvutsr") + self.assertEqual("zyxwvutsr", keyReporter.get_property("value")) + + # select all and cut + keyReporter.send_keys(self.mod_key, "a") + keyReporter.send_keys(self.mod_key, "x") + self.assertEqual("", keyReporter.get_property("value")) + + url_bar = self.marionette.find_element(By.ID, "urlbar") + + # Clear contents first + url_bar.send_keys(self.mod_key, "a") + url_bar.send_keys(Keys.BACK_SPACE) + self.assertEqual("", url_bar.get_attribute("value")) + + url_bar.send_keys(self.mod_key, "v") + self.assertEqual("zyxwvutsr", url_bar.get_property("value")) + + +class TestTypingContent(TypingTestCase): + + def testShouldFireKeyPressEvents(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys("a") + result = self.marionette.find_element(By.ID, "result") + self.assertTrue("press:" in result.text) + + def testShouldFireKeyDownEvents(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys("I") + result = self.marionette.find_element(By.ID, "result") + self.assertTrue("down" in result.text) + + def testShouldFireKeyUpEvents(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys("a") + result = self.marionette.find_element(By.ID, "result") + self.assertTrue("up:" in result.text) + + def testShouldTypeLowerCaseLetters(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys("abc def") + self.assertEqual("abc def", keyReporter.get_property("value")) + + def testShouldBeAbleToTypeCapitalLetters(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys("ABC DEF") + self.assertEqual("ABC DEF", keyReporter.get_property("value")) + + def testCutAndPasteShortcuts(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + self.assertEqual("", keyReporter.get_property("value")) + keyReporter.send_keys("zyxwvutsr") + self.assertEqual("zyxwvutsr", keyReporter.get_property("value")) + + # select all and cut + keyReporter.send_keys(self.mod_key, "a") + keyReporter.send_keys(self.mod_key, "x") + self.assertEqual("", keyReporter.get_property("value")) + + keyReporter.send_keys(self.mod_key, "v") + self.assertEqual("zyxwvutsr", keyReporter.get_property("value")) + + def testShouldBeAbleToTypeQuoteMarks(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys("\"") + self.assertEqual("\"", keyReporter.get_property("value")) + + def testShouldBeAbleToTypeTheAtCharacter(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys("@") + self.assertEqual("@", keyReporter.get_property("value")) + + def testShouldBeAbleToMixUpperAndLowerCaseLetters(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys("me@eXample.com") + self.assertEqual("me@eXample.com", keyReporter.get_property("value")) + + def testArrowKeysShouldNotBePrintable(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + keyReporter = self.marionette.find_element(By.ID, "keyReporter") + keyReporter.send_keys(Keys.ARROW_LEFT) + self.assertEqual("", keyReporter.get_property("value")) + + def testWillSimulateAKeyUpWhenEnteringTextIntoInputElements(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyUp") + element.send_keys("I like cheese") + result = self.marionette.find_element(By.ID, "result") + self.assertEqual(result.text, "I like cheese") + + def testWillSimulateAKeyDownWhenEnteringTextIntoInputElements(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyDown") + element.send_keys("I like cheese") + result = self.marionette.find_element(By.ID, "result") + # Because the key down gets the result before the input element is + # filled, we're a letter short here + self.assertEqual(result.text, "I like chees") + + def testWillSimulateAKeyPressWhenEnteringTextIntoInputElements(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyPress") + element.send_keys("I like cheese") + result = self.marionette.find_element(By.ID, "result") + # Because the key down gets the result before the input element is + # filled, we're a letter short here + self.assertEqual(result.text, "I like chees") + + def testWillSimulateAKeyUpWhenEnteringTextIntoTextAreas(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyUpArea") + element.send_keys("I like cheese") + result = self.marionette.find_element(By.ID, "result") + self.assertEqual("I like cheese", result.text) + + def testWillSimulateAKeyDownWhenEnteringTextIntoTextAreas(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyDownArea") + element.send_keys("I like cheese") + result = self.marionette.find_element(By.ID, "result") + # Because the key down gets the result before the input element is + # filled, we're a letter short here + self.assertEqual(result.text, "I like chees") + + def testWillSimulateAKeyPressWhenEnteringTextIntoTextAreas(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyPressArea") + element.send_keys("I like cheese") + result = self.marionette.find_element(By.ID, "result") + # Because the key down gets the result before the input element is + # filled, we're a letter short here + self.assertEqual(result.text, "I like chees") + + @skip_if_mobile("Bug 1324752 - Arrow keys cannot be sent in Fennec") + def testShouldReportKeyCodeOfArrowKeysUpDownEvents(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + result = self.marionette.find_element(By.ID, "result") + element = self.marionette.find_element(By.ID, "keyReporter") + + element.send_keys(Keys.ARROW_DOWN) + + self.assertIn("down: 40", result.text.strip()) + self.assertIn("up: 40", result.text.strip()) + + element.send_keys(Keys.ARROW_UP) + self.assertIn("down: 38", result.text.strip()) + self.assertIn("up: 38", result.text.strip()) + + element.send_keys(Keys.ARROW_LEFT) + self.assertIn("down: 37", result.text.strip()) + self.assertIn("up: 37", result.text.strip()) + + element.send_keys(Keys.ARROW_RIGHT) + self.assertIn("down: 39", result.text.strip()) + self.assertIn("up: 39", result.text.strip()) + + # And leave no rubbish/printable keys in the "keyReporter" + self.assertEqual("", element.get_property("value")) + + @skip("Reenable in Bug 1068728") + def testNumericShiftKeys(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + result = self.marionette.find_element(By.ID, "result") + element = self.marionette.find_element(By.ID, "keyReporter") + numericShiftsEtc = "~!@#$%^&*()_+{}:i\"<>?|END~" + element.send_keys(numericShiftsEtc) + self.assertEqual(numericShiftsEtc, element.get_property("value")) + self.assertIn(" up: 16", result.text.strip()) + + def testLowerCaseAlphaKeys(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyReporter") + lowerAlphas = "abcdefghijklmnopqrstuvwxyz" + element.send_keys(lowerAlphas) + self.assertEqual(lowerAlphas, element.get_property("value")) + + @skip("Reenable in Bug 1068735") + def testUppercaseAlphaKeys(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + result = self.marionette.find_element(By.ID, "result") + element = self.marionette.find_element(By.ID, "keyReporter") + upperAlphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + element.send_keys(upperAlphas) + self.assertEqual(upperAlphas, element.get_property("value")) + self.assertIn(" up: 16", result.text.strip()) + + @skip("Reenable in Bug 1068726") + def testAllPrintableKeys(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + result = self.marionette.find_element(By.ID, "result") + element = self.marionette.find_element(By.ID, "keyReporter") + allPrintable = "!\"#$%&'()*+,-./0123456789:<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + element.send_keys(allPrintable) + + self.assertTrue(allPrintable, element.get_property("value")) + self.assertIn(" up: 16", result.text.strip()) + + @skip("Reenable in Bug 1068733") + def testSpecialSpaceKeys(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyReporter") + element.send_keys("abcd" + Keys.SPACE + "fgh" + Keys.SPACE + "ij") + self.assertEqual("abcd fgh ij", element.get_property("value")) + + def testShouldTypeAnInteger(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + element = self.marionette.find_element(By.ID, "keyReporter") + element.send_keys(1234) + self.assertEqual("1234", element.get_property("value")) + + def testShouldSendKeysToElementsWithoutTheValueAttribute(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + # If we don't get an error below we are good + self.marionette.find_element(By.TAG_NAME, "body").send_keys("foo") + + def test_not_interactable_if_hidden(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + not_displayed = self.marionette.find_element(By.ID, "notDisplayed") + self.assertRaises(ElementNotInteractableException, not_displayed.send_keys, "foo") + + def test_appends_to_input_text(self): + self.marionette.navigate(inline("<input>")) + el = self.marionette.find_element(By.TAG_NAME, "input") + el.send_keys("foo") + el.send_keys("bar") + self.assertEqual("foobar", el.get_property("value")) + + def test_appends_to_textarea(self): + self.marionette.navigate(inline("<textarea></textarea>")) + textarea = self.marionette.find_element(By.TAG_NAME, "textarea") + textarea.send_keys("foo") + textarea.send_keys("bar") + self.assertEqual("foobar", textarea.get_property("value")) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_using_permissions.py b/testing/marionette/harness/marionette_harness/tests/unit/test_using_permissions.py new file mode 100644 index 000000000..71e271dd4 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_using_permissions.py @@ -0,0 +1,46 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.errors import JavascriptException + +from marionette_harness import MarionetteTestCase + + +class TestUsingPermssions(MarionetteTestCase): + + def test_using_permissions(self): + # Test that multiple permissions can be set with 'using_permissions', + # and that they are set correctly and unset correctly after leaving + # the context manager. + original_perm = self.marionette.get_permission('systemXHR') + original_alarm = self.marionette.get_permission('alarms') + new_perm = True if original_perm != 1 else False + new_alarm = True if original_alarm != 1 else False + with self.marionette.using_permissions({'systemXHR': new_perm, + 'alarms': new_alarm}): + now_perm = self.marionette.get_permission('systemXHR') + now_alarm = self.marionette.get_permission('alarms') + self.assertEquals(new_perm, now_perm) + self.assertNotEquals(now_perm, original_perm) + self.assertEquals(new_alarm, now_alarm) + self.assertNotEquals(now_alarm, original_alarm) + self.assertEquals(original_perm, + self.marionette.get_permission('systemXHR')) + self.assertEquals(original_alarm, + self.marionette.get_permission('alarms')) + + def test_exception_using_permissions(self): + # Test that throwing an exception inside the context manager doesn't + # prevent the permissions from being restored at context manager exit. + original_perm = self.marionette.get_permission('systemXHR') + new_perm = True if original_perm != 1 else False + with self.marionette.using_permissions({'systemXHR': new_perm}): + now_perm = self.marionette.get_permission('systemXHR') + self.assertEquals(new_perm, now_perm) + self.assertNotEquals(now_perm, original_perm) + self.assertRaises(JavascriptException, + self.marionette.execute_script, + "return foo.bar.baz;") + self.assertEquals(original_perm, + self.marionette.get_permission('systemXHR')) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py b/testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py new file mode 100644 index 000000000..750ecf20a --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py @@ -0,0 +1,121 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.by import By + +from marionette_harness import MarionetteTestCase + + +class TestVisibility(MarionetteTestCase): + + def testShouldAllowTheUserToTellIfAnElementIsDisplayedOrNot(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + self.assertTrue(self.marionette.find_element(By.ID, "displayed").is_displayed()) + self.assertFalse(self.marionette.find_element(By.ID, "none").is_displayed()) + self.assertFalse(self.marionette.find_element(By.ID, + "suppressedParagraph").is_displayed()) + self.assertFalse(self.marionette.find_element(By.ID, "hidden").is_displayed()) + + def testVisibilityShouldTakeIntoAccountParentVisibility(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + childDiv = self.marionette.find_element(By.ID, "hiddenchild") + hiddenLink = self.marionette.find_element(By.ID, "hiddenlink") + + self.assertFalse(childDiv.is_displayed()) + self.assertFalse(hiddenLink.is_displayed()) + + def testShouldCountElementsAsVisibleIfStylePropertyHasBeenSet(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + shown = self.marionette.find_element(By.ID, "visibleSubElement") + self.assertTrue(shown.is_displayed()) + + def testShouldModifyTheVisibilityOfAnElementDynamically(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.ID, "hideMe") + self.assertTrue(element.is_displayed()) + element.click() + self.assertFalse(element.is_displayed()) + + def testHiddenInputElementsAreNeverVisible(self): + test_html = self.marionette.absolute_url("javascriptPage.html") + self.marionette.navigate(test_html) + + shown = self.marionette.find_element(By.NAME, "hidden") + + self.assertFalse(shown.is_displayed()) + + def testShouldSayElementsWithNegativeTransformAreNotDisplayed(self): + test_html = self.marionette.absolute_url("cssTransform.html") + self.marionette.navigate(test_html) + + elementX = self.marionette.find_element(By.ID, 'parentX') + self.assertFalse(elementX.is_displayed()) + elementY = self.marionette.find_element(By.ID, 'parentY') + self.assertFalse(elementY.is_displayed()) + + def testShouldSayElementsWithParentWithNegativeTransformAreNotDisplayed(self): + test_html = self.marionette.absolute_url("cssTransform.html") + self.marionette.navigate(test_html) + + elementX = self.marionette.find_element(By.ID, 'childX') + self.assertFalse(elementX.is_displayed()) + elementY = self.marionette.find_element(By.ID, 'childY') + self.assertFalse(elementY.is_displayed()) + + def testShouldSayElementWithZeroTransformIsVisible(self): + test_html = self.marionette.absolute_url("cssTransform.html") + self.marionette.navigate(test_html) + + zero_tranform = self.marionette.find_element(By.ID, 'zero-tranform') + self.assertTrue(zero_tranform.is_displayed()) + + def testShouldSayElementIsVisibleWhenItHasNegativeTransformButElementisntInANegativeSpace(self): + test_html = self.marionette.absolute_url("cssTransform2.html") + self.marionette.navigate(test_html) + negative_percent__tranform = self.marionette.find_element(By.ID, 'negative-percentage-transformY') + self.assertTrue(negative_percent__tranform.is_displayed()) + + def testShouldSayElementIsInvisibleWhenOverflowXIsHiddenAndOutOfViewport(self): + test_html = self.marionette.absolute_url("bug814037.html") + self.marionette.navigate(test_html) + overflow_x = self.marionette.find_element(By.ID, "assertMe2") + self.assertFalse(overflow_x.is_displayed()) + + def testShouldShowElementNotVisibleWithHiddenAttribute(self): + test_html = self.marionette.absolute_url("hidden.html") + self.marionette.navigate(test_html) + singleHidden = self.marionette.find_element(By.ID, 'singleHidden') + self.assertFalse(singleHidden.is_displayed()) + + def testShouldShowElementNotVisibleWhenParentElementHasHiddenAttribute(self): + test_html = self.marionette.absolute_url("hidden.html") + self.marionette.navigate(test_html) + child = self.marionette.find_element(By.ID, 'child') + self.assertFalse(child.is_displayed()) + + def testShouldClickOnELementPartiallyOffLeft(self): + test_html = self.marionette.absolute_url("element_left.html") + self.marionette.navigate(test_html) + self.marionette.find_element(By.CSS_SELECTOR, '.element').click() + + def testShouldClickOnELementPartiallyOffRight(self): + test_html = self.marionette.absolute_url("element_right.html") + self.marionette.navigate(test_html) + self.marionette.find_element(By.CSS_SELECTOR, '.element').click() + + def testShouldClickOnELementPartiallyOffTop(self): + test_html = self.marionette.absolute_url("element_top.html") + self.marionette.navigate(test_html) + self.marionette.find_element(By.CSS_SELECTOR, '.element').click() + + def testShouldClickOnELementPartiallyOffBottom(self): + test_html = self.marionette.absolute_url("element_bottom.html") + self.marionette.navigate(test_html) + self.marionette.find_element(By.CSS_SELECTOR, '.element').click() diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_wait.py b/testing/marionette/harness/marionette_harness/tests/unit/test_wait.py new file mode 100644 index 000000000..6a4872773 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_wait.py @@ -0,0 +1,347 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys +import time + +from marionette_driver import errors, wait +from marionette_driver.wait import Wait + +from marionette_harness import MarionetteTestCase + + +class TickingClock(object): + + def __init__(self, incr=1): + self.ticks = 0 + self.increment = incr + + def sleep(self, dur=None): + dur = dur if dur is not None else self.increment + self.ticks += dur + + @property + def now(self): + return self.ticks + + +class SequenceClock(object): + + def __init__(self, times): + self.times = times + self.i = 0 + + @property + def now(self): + if len(self.times) > self.i: + self.i += 1 + return self.times[self.i - 1] + + def sleep(self, dur): + pass + + +class MockMarionette(object): + + def __init__(self): + self.waited = 0 + + def exception(self, e=None, wait=1): + self.wait() + if self.waited == wait: + if e is None: + e = Exception + raise e + + def true(self, wait=1): + self.wait() + if self.waited == wait: + return True + return None + + def false(self, wait=1): + self.wait() + return False + + def none(self, wait=1): + self.wait() + return None + + def value(self, value, wait=1): + self.wait() + if self.waited == wait: + return value + return None + + def wait(self): + self.waited += 1 + + +def at_third_attempt(clock, end): + return clock.now == 2 + + +def now(clock, end): + return True + + +class SystemClockTest(MarionetteTestCase): + + def setUp(self): + super(SystemClockTest, self).setUp() + self.clock = wait.SystemClock() + + def test_construction_initializes_time(self): + self.assertEqual(self.clock._time, time) + + def test_sleep(self): + start = time.time() + self.clock.sleep(0.1) + end = time.time() - start + self.assertGreater(end, 0) + + def test_time_now(self): + self.assertIsNotNone(self.clock.now) + + +class FormalWaitTest(MarionetteTestCase): + + def setUp(self): + super(FormalWaitTest, self).setUp() + self.m = MockMarionette() + self.m.timeout = 123 + + def test_construction_with_custom_timeout(self): + wt = Wait(self.m, timeout=42) + self.assertEqual(wt.timeout, 42) + + def test_construction_with_custom_interval(self): + wt = Wait(self.m, interval=42) + self.assertEqual(wt.interval, 42) + + def test_construction_with_custom_clock(self): + c = TickingClock(1) + wt = Wait(self.m, clock=c) + self.assertEqual(wt.clock, c) + + def test_construction_with_custom_exception(self): + wt = Wait(self.m, ignored_exceptions=Exception) + self.assertIn(Exception, wt.exceptions) + self.assertEqual(len(wt.exceptions), 1) + + def test_construction_with_custom_exception_list(self): + exc = [Exception, ValueError] + wt = Wait(self.m, ignored_exceptions=exc) + for e in exc: + self.assertIn(e, wt.exceptions) + self.assertEqual(len(wt.exceptions), len(exc)) + + def test_construction_with_custom_exception_tuple(self): + exc = (Exception, ValueError) + wt = Wait(self.m, ignored_exceptions=exc) + for e in exc: + self.assertIn(e, wt.exceptions) + self.assertEqual(len(wt.exceptions), len(exc)) + + def test_duplicate_exceptions(self): + wt = Wait(self.m, ignored_exceptions=[Exception, Exception]) + self.assertIn(Exception, wt.exceptions) + self.assertEqual(len(wt.exceptions), 1) + + def test_default_timeout(self): + self.assertEqual(wait.DEFAULT_TIMEOUT, 5) + + def test_default_interval(self): + self.assertEqual(wait.DEFAULT_INTERVAL, 0.1) + + def test_end_property(self): + wt = Wait(self.m) + self.assertIsNotNone(wt.end) + + def test_marionette_property(self): + wt = Wait(self.m) + self.assertEqual(wt.marionette, self.m) + + def test_clock_property(self): + wt = Wait(self.m) + self.assertIsInstance(wt.clock, wait.SystemClock) + + def test_timeout_uses_default_if_marionette_timeout_is_none(self): + self.m.timeout = None + wt = Wait(self.m) + self.assertEqual(wt.timeout, wait.DEFAULT_TIMEOUT) + + +class PredicatesTest(MarionetteTestCase): + + def test_until(self): + c = wait.SystemClock() + self.assertFalse(wait.until_pred(c, sys.maxint)) + self.assertTrue(wait.until_pred(c, 0)) + + +class WaitUntilTest(MarionetteTestCase): + + def setUp(self): + super(WaitUntilTest, self).setUp() + + self.m = MockMarionette() + self.clock = TickingClock() + self.wt = Wait(self.m, timeout=10, interval=1, clock=self.clock) + + def test_true(self): + r = self.wt.until(lambda x: x.true()) + self.assertTrue(r) + self.assertEqual(self.clock.ticks, 0) + + def test_true_within_timeout(self): + r = self.wt.until(lambda x: x.true(wait=5)) + self.assertTrue(r) + self.assertEqual(self.clock.ticks, 4) + + def test_timeout(self): + with self.assertRaises(errors.TimeoutException): + r = self.wt.until(lambda x: x.true(wait=15)) + self.assertEqual(self.clock.ticks, 10) + + def test_exception_raises_immediately(self): + with self.assertRaises(TypeError): + self.wt.until(lambda x: x.exception(e=TypeError)) + self.assertEqual(self.clock.ticks, 0) + + def test_ignored_exception(self): + self.wt.exceptions = (TypeError,) + with self.assertRaises(errors.TimeoutException): + self.wt.until(lambda x: x.exception(e=TypeError)) + + def test_ignored_exception_wrapped_in_timeoutexception(self): + self.wt.exceptions = (TypeError,) + + exc = None + try: + self.wt.until(lambda x: x.exception(e=TypeError)) + except Exception as e: + exc = e + + s = str(exc) + self.assertIsNotNone(exc) + self.assertIsInstance(exc, errors.TimeoutException) + self.assertIn(", caused by {0!r}".format(TypeError), s) + self.assertIn("self.wt.until(lambda x: x.exception(e=TypeError))", s) + + def test_ignored_exception_after_timeout_is_not_raised(self): + with self.assertRaises(errors.TimeoutException): + r = self.wt.until(lambda x: x.exception(wait=15)) + self.assertEqual(self.clock.ticks, 10) + + def test_keyboard_interrupt(self): + with self.assertRaises(KeyboardInterrupt): + self.wt.until(lambda x: x.exception(e=KeyboardInterrupt)) + + def test_system_exit(self): + with self.assertRaises(SystemExit): + self.wt.until(lambda x: x.exception(SystemExit)) + + def test_true_condition_returns_immediately(self): + r = self.wt.until(lambda x: x.true()) + self.assertIsInstance(r, bool) + self.assertTrue(r) + self.assertEqual(self.clock.ticks, 0) + + def test_value(self): + r = self.wt.until(lambda x: "foo") + self.assertEqual(r, "foo") + self.assertEqual(self.clock.ticks, 0) + + def test_custom_predicate(self): + r = self.wt.until(lambda x: x.true(wait=2), is_true=at_third_attempt) + self.assertTrue(r) + self.assertEqual(self.clock.ticks, 1) + + def test_custom_predicate_times_out(self): + with self.assertRaises(errors.TimeoutException): + self.wt.until(lambda x: x.true(wait=4), is_true=at_third_attempt) + + self.assertEqual(self.clock.ticks, 2) + + def test_timeout_elapsed_duration(self): + with self.assertRaisesRegexp(errors.TimeoutException, + "Timed out after 2.0 seconds"): + self.wt.until(lambda x: x.true(wait=4), is_true=at_third_attempt) + + def test_timeout_elapsed_rounding(self): + wt = Wait(self.m, clock=SequenceClock([1, 0.01, 1]), timeout=0) + with self.assertRaisesRegexp(errors.TimeoutException, + "Timed out after 1.0 seconds"): + wt.until(lambda x: x.true(), is_true=now) + + def test_timeout_elapsed_interval_by_delayed_condition_return(self): + def callback(mn): + self.clock.sleep(11) + return mn.false() + + with self.assertRaisesRegexp(errors.TimeoutException, + "Timed out after 11.0 seconds"): + self.wt.until(callback) + # With a delayed conditional return > timeout, only 1 iteration is + # possible + self.assertEqual(self.m.waited, 1) + + def test_timeout_with_delayed_condition_return(self): + def callback(mn): + self.clock.sleep(.5) + return mn.false() + + with self.assertRaisesRegexp(errors.TimeoutException, + "Timed out after 10.0 seconds"): + self.wt.until(callback) + # With a delayed conditional return < interval, 10 iterations should be + # possible + self.assertEqual(self.m.waited, 10) + + def test_timeout_interval_shorter_than_delayed_condition_return(self): + def callback(mn): + self.clock.sleep(2) + return mn.false() + + with self.assertRaisesRegexp(errors.TimeoutException, + "Timed out after 10.0 seconds"): + self.wt.until(callback) + # With a delayed return of the conditional which takes twice that long than the interval, + # half of the iterations should be possible + self.assertEqual(self.m.waited, 5) + + def test_message(self): + self.wt.exceptions = (TypeError,) + exc = None + try: + self.wt.until(lambda x: x.exception(e=TypeError), message="hooba") + except errors.TimeoutException as e: + exc = e + + result = str(exc) + self.assertIn("seconds with message: hooba, caused by", result) + + def test_no_message(self): + self.wt.exceptions = (TypeError,) + exc = None + try: + self.wt.until(lambda x: x.exception(e=TypeError), message="") + except errors.TimeoutException as e: + exc = e + + result = str(exc) + self.assertIn("seconds, caused by", result) + + def test_message_has_none_as_its_value(self): + self.wt.exceptions = (TypeError,) + exc = None + try: + self.wt.until(False, None, None) + except errors.TimeoutException as e: + exc = e + + result = str(exc) + self.assertNotIn("with message:", result) + self.assertNotIn("secondsNone", result) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py new file mode 100644 index 000000000..18ae191c1 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_chrome.py @@ -0,0 +1,80 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestCloseWindow(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestCloseWindow, self).setUp() + + self.marionette.set_context("chrome") + + def tearDown(self): + self.close_all_windows() + self.close_all_tabs() + + super(TestCloseWindow, self).tearDown() + + def test_close_chrome_window_for_browser_window(self): + win = self.open_window() + self.marionette.switch_to_window(win) + + self.assertNotIn(win, self.marionette.window_handles) + chrome_window_handles = self.marionette.close_chrome_window() + self.assertNotIn(win, chrome_window_handles) + self.assertListEqual(self.start_windows, chrome_window_handles) + self.assertNotIn(win, self.marionette.window_handles) + + def test_close_chrome_window_for_non_browser_window(self): + + def open_window_with_js(): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test.xul', + 'foo', 'chrome,centerscreen'); + """) + + win = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(win) + + self.assertIn(win, self.marionette.window_handles) + chrome_window_handles = self.marionette.close_chrome_window() + self.assertNotIn(win, chrome_window_handles) + self.assertListEqual(self.start_windows, chrome_window_handles) + self.assertNotIn(win, self.marionette.window_handles) + + def test_close_chrome_window_for_last_open_window(self): + self.close_all_windows() + + self.assertListEqual([], self.marionette.close_chrome_window()) + self.assertListEqual([self.start_tab], self.marionette.window_handles) + self.assertListEqual([self.start_window], self.marionette.chrome_window_handles) + self.assertIsNotNone(self.marionette.session) + + def test_close_window_for_browser_tab(self): + tab = self.open_tab() + self.marionette.switch_to_window(tab) + + window_handles = self.marionette.close() + self.assertNotIn(tab, window_handles) + self.assertListEqual(self.start_tabs, window_handles) + + def test_close_window_for_browser_window_with_single_tab(self): + win = self.open_window() + self.marionette.switch_to_window(win) + + self.assertEqual(len(self.start_tabs) + 1, len(self.marionette.window_handles)) + window_handles = self.marionette.close() + self.assertNotIn(win, window_handles) + self.assertListEqual(self.start_tabs, window_handles) + self.assertListEqual(self.start_windows, self.marionette.chrome_window_handles) + + def test_close_window_for_last_open_tab(self): + self.close_all_tabs() + + self.assertListEqual([], self.marionette.close()) + self.assertListEqual([self.start_tab], self.marionette.window_handles) + self.assertListEqual([self.start_window], self.marionette.chrome_window_handles) + self.assertIsNotNone(self.marionette.session) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py new file mode 100644 index 000000000..8e6485e54 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py @@ -0,0 +1,81 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin + + +class TestCloseWindow(WindowManagerMixin, MarionetteTestCase): + + def tearDown(self): + self.close_all_windows() + self.close_all_tabs() + + super(TestCloseWindow, self).tearDown() + + @skip_if_mobile("Interacting with chrome windows not available for Fennec") + def test_close_chrome_window_for_browser_window(self): + win = self.open_window() + self.marionette.switch_to_window(win) + + self.assertNotIn(win, self.marionette.window_handles) + chrome_window_handles = self.marionette.close_chrome_window() + self.assertNotIn(win, chrome_window_handles) + self.assertListEqual(self.start_windows, chrome_window_handles) + self.assertNotIn(win, self.marionette.window_handles) + + @skip_if_mobile("Interacting with chrome windows not available for Fennec") + def test_close_chrome_window_for_non_browser_window(self): + + def open_window_with_js(): + with self.marionette.using_context("chrome"): + self.marionette.execute_script(""" + window.open('chrome://marionette/content/test.xul', + 'foo', 'chrome,centerscreen'); + """) + + win = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(win) + + self.assertIn(win, self.marionette.window_handles) + chrome_window_handles = self.marionette.close_chrome_window() + self.assertNotIn(win, chrome_window_handles) + self.assertListEqual(self.start_windows, chrome_window_handles) + self.assertNotIn(win, self.marionette.window_handles) + + @skip_if_mobile("Interacting with chrome windows not available for Fennec") + def test_close_chrome_window_for_last_open_window(self): + self.close_all_windows() + + self.assertListEqual([], self.marionette.close_chrome_window()) + self.assertListEqual([self.start_tab], self.marionette.window_handles) + self.assertListEqual([self.start_window], self.marionette.chrome_window_handles) + self.assertIsNotNone(self.marionette.session) + + @skip_if_mobile("Needs application independent method to open a new tab") + def test_close_window_for_browser_tab(self): + tab = self.open_tab() + self.marionette.switch_to_window(tab) + + window_handles = self.marionette.close() + self.assertNotIn(tab, window_handles) + self.assertListEqual(self.start_tabs, window_handles) + + @skip_if_mobile("Interacting with chrome windows not available for Fennec") + def test_close_window_for_browser_window_with_single_tab(self): + win = self.open_window() + self.marionette.switch_to_window(win) + + self.assertEqual(len(self.start_tabs) + 1, len(self.marionette.window_handles)) + window_handles = self.marionette.close() + self.assertNotIn(win, window_handles) + self.assertListEqual(self.start_tabs, window_handles) + self.assertListEqual(self.start_windows, self.marionette.chrome_window_handles) + + def test_close_window_for_last_open_tab(self): + self.close_all_tabs() + + self.assertListEqual([], self.marionette.close()) + self.assertListEqual([self.start_tab], self.marionette.window_handles) + self.assertListEqual([self.start_window], self.marionette.chrome_window_handles) + self.assertIsNotNone(self.marionette.session) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py new file mode 100644 index 000000000..7260d6324 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py @@ -0,0 +1,207 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import By, Wait + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestWindowHandles(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestWindowHandles, self).setUp() + + self.empty_page = self.marionette.absolute_url("empty.html") + self.test_page = self.marionette.absolute_url("windowHandles.html") + self.marionette.navigate(self.test_page) + + self.marionette.set_context("chrome") + + def tearDown(self): + self.close_all_windows() + self.close_all_tabs() + + super(TestWindowHandles, self).tearDown() + + def test_chrome_window_handles_with_scopes(self): + # Open a browser and a non-browser (about window) chrome window + self.open_window( + trigger=lambda: self.marionette.execute_script("window.open();")) + self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + + self.open_window( + trigger=lambda: self.marionette.find_element(By.ID, "aboutName").click()) + self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 2) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + + chrome_window_handles_in_chrome_scope = self.marionette.chrome_window_handles + window_handles_in_chrome_scope = self.marionette.window_handles + + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.chrome_window_handles, + chrome_window_handles_in_chrome_scope) + self.assertEqual(self.marionette.window_handles, + window_handles_in_chrome_scope) + + def test_chrome_window_handles_after_opening_new_window(self): + def open_with_link(): + with self.marionette.using_context("content"): + link = self.marionette.find_element(By.ID, "new-window") + link.click() + + # We open a new window but are actually interested in the new tab + new_win = self.open_window(trigger=open_with_link) + self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + + # Check that the new tab has the correct page loaded + self.marionette.switch_to_window(new_win) + self.assertEqual(self.marionette.current_chrome_window_handle, new_win) + with self.marionette.using_context("content"): + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + lambda mn: mn.get_url() == self.empty_page, + message="{} did not load after opening a new tab".format(self.empty_page)) + + # Ensure navigate works in our current window + other_page = self.marionette.absolute_url("test.html") + with self.marionette.using_context("content"): + self.marionette.navigate(other_page) + self.assertEqual(self.marionette.get_url(), other_page) + + # Close the opened window and carry on in our original tab. + self.marionette.close() + self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows)) + + self.marionette.switch_to_window(self.start_window) + self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + def test_window_handles_after_opening_new_tab(self): + def open_with_link(): + with self.marionette.using_context("content"): + link = self.marionette.find_element(By.ID, "new-tab") + link.click() + + new_tab = self.open_tab(trigger=open_with_link) + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + with self.marionette.using_context("content"): + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + lambda mn: mn.get_url() == self.empty_page, + message="{} did not load after opening a new tab".format(self.empty_page)) + + # Ensure navigate works in our current tab + other_page = self.marionette.absolute_url("test.html") + with self.marionette.using_context("content"): + self.marionette.navigate(other_page) + self.assertEqual(self.marionette.get_url(), other_page) + + self.marionette.switch_to_window(self.start_tab) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + self.marionette.switch_to_window(new_tab) + self.marionette.close() + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs)) + + self.marionette.switch_to_window(self.start_tab) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + def test_window_handles_after_opening_new_window(self): + def open_with_link(): + with self.marionette.using_context("content"): + link = self.marionette.find_element(By.ID, "new-window") + link.click() + + # We open a new window but are actually interested in the new tab + new_tab = self.open_tab(trigger=open_with_link) + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + # Check that the new tab has the correct page loaded + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + with self.marionette.using_context("content"): + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + lambda mn: mn.get_url() == self.empty_page, + message="{} did not load after opening a new tab".format(self.empty_page)) + + # Ensure navigate works in our current window + other_page = self.marionette.absolute_url("test.html") + with self.marionette.using_context("content"): + self.marionette.navigate(other_page) + self.assertEqual(self.marionette.get_url(), other_page) + + # Close the opened window and carry on in our original tab. + self.marionette.close() + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs)) + + self.marionette.switch_to_window(self.start_tab) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + def test_window_handles_after_closing_original_tab(self): + def open_with_link(): + with self.marionette.using_context("content"): + link = self.marionette.find_element(By.ID, "new-tab") + link.click() + + new_tab = self.open_tab(trigger=open_with_link) + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + self.marionette.close() + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs)) + + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + with self.marionette.using_context("content"): + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + lambda mn: mn.get_url() == self.empty_page, + message="{} did not load after opening a new tab".format(self.empty_page)) + + def test_window_handles_no_switch(self): + """Regression test for bug 1294456. + This test is testing the case where Marionette attempts to send a + command to a window handle when the browser has opened and selected + a new tab. Before bug 1294456 landed, the Marionette driver was getting + confused about which window handle the client cared about, and assumed + it was the window handle for the newly opened and selected tab. + + This caused Marionette to think that the browser needed to do a remoteness + flip in the e10s case, since the tab opened by menu_newNavigatorTab is + about:newtab (which is currently non-remote). This meant that commands + sent to what should have been the original window handle would be + queued and never sent, since the remoteness flip in the new tab was + never going to happen. + """ + def open_with_menu(): + menu_new_tab = self.marionette.find_element(By.ID, 'menu_newNavigatorTab') + menu_new_tab.click() + + new_tab = self.open_tab(trigger=open_with_menu) + + # We still have the default tab set as our window handle. This + # get_url command should be sent immediately, and not be forever-queued. + with self.marionette.using_context("content"): + self.assertEqual(self.marionette.get_url(), self.test_page) + + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + + self.marionette.close() + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs)) + + self.marionette.switch_to_window(self.start_tab) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py new file mode 100644 index 000000000..b6ad3a6c3 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py @@ -0,0 +1,96 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import By, Wait + +from marionette_harness import MarionetteTestCase, WindowManagerMixin + + +class TestWindowHandles(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestWindowHandles, self).setUp() + + self.empty_page = self.marionette.absolute_url("empty.html") + self.test_page = self.marionette.absolute_url("windowHandles.html") + self.marionette.navigate(self.test_page) + + def tearDown(self): + self.close_all_tabs() + + super(TestWindowHandles, self).tearDown() + + def test_window_handles_after_opening_new_tab(self): + def open_with_link(): + link = self.marionette.find_element(By.ID, "new-tab") + link.click() + + new_tab = self.open_tab(trigger=open_with_link) + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + lambda mn: mn.get_url() == self.empty_page, + message="{} did not load after opening a new tab".format(self.empty_page)) + + self.marionette.switch_to_window(self.start_tab) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + self.assertEqual(self.marionette.get_url(), self.test_page) + + self.marionette.switch_to_window(new_tab) + self.marionette.close() + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs)) + + self.marionette.switch_to_window(self.start_tab) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + def test_window_handles_after_opening_new_window(self): + def open_with_link(): + link = self.marionette.find_element(By.ID, "new-window") + link.click() + + # We open a new window but are actually interested in the new tab + new_tab = self.open_tab(trigger=open_with_link) + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + # Check that the new tab has the correct page loaded + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + Wait(self.marionette, self.marionette.timeout.page_load).until( + lambda _: self.marionette.get_url() == self.empty_page, + message="The expected page '{}' has not been loaded".format(self.empty_page)) + + # Ensure navigate works in our current window + other_page = self.marionette.absolute_url("test.html") + self.marionette.navigate(other_page) + self.assertEqual(self.marionette.get_url(), other_page) + + # Close the opened window and carry on in our original tab. + self.marionette.close() + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs)) + + self.marionette.switch_to_window(self.start_tab) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + self.assertEqual(self.marionette.get_url(), self.test_page) + + def test_window_handles_after_closing_original_tab(self): + def open_with_link(): + link = self.marionette.find_element(By.ID, "new-tab") + link.click() + + new_tab = self.open_tab(trigger=open_with_link) + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1) + self.assertEqual(self.marionette.current_window_handle, self.start_tab) + + self.marionette.close() + self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs)) + + self.marionette.switch_to_window(new_tab) + self.assertEqual(self.marionette.current_window_handle, new_tab) + Wait(self.marionette, self.marionette.timeout.page_load).until( + lambda _: self.marionette.get_url() == self.empty_page, + message="The expected page '{}' has not been loaded".format(self.empty_page)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py new file mode 100644 index 000000000..ac5365806 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py @@ -0,0 +1,42 @@ +#Copyright 2007-2009 WebDriver committers +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from marionette_driver.errors import InvalidArgumentException + +from marionette_harness import MarionetteTestCase + + +class TestWindowPosition(MarionetteTestCase): + def test_get_types(self): + position = self.marionette.get_window_position() + self.assertTrue(isinstance(position["x"], int)) + self.assertTrue(isinstance(position["y"], int)) + + def test_set_types(self): + for x, y in (["a", "b"], [1.2, 3.4], [True, False], [[], []], [{}, {}]): + with self.assertRaises(InvalidArgumentException): + self.marionette.set_window_position(x, y) + + def test_out_of_bounds_arguments(self): + with self.assertRaises(InvalidArgumentException): + self.marionette.set_window_position(-1, 0) + with self.assertRaises(InvalidArgumentException): + self.marionette.set_window_position(0, -1) + + def test_move(self): + old_position = self.marionette.get_window_position() + new_position = {"x": old_position["x"] + 10, "y": old_position["y"] + 10} + self.marionette.set_window_position(new_position["x"], new_position["y"]) + self.assertNotEqual(old_position['x'], new_position["x"]) + self.assertNotEqual(old_position['y'], new_position["y"]) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_title.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title.py new file mode 100644 index 000000000..4f6e3ccf7 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title.py @@ -0,0 +1,12 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestTitle(MarionetteTestCase): + def test_get_html_title(self): + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + self.assertEqual('Marionette Test', self.marionette.title) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py new file mode 100644 index 000000000..7bee682fd --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_title_chrome.py @@ -0,0 +1,26 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestTitleChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_get_chrome_title(self): + title = self.marionette.execute_script("return window.document.documentElement.getAttribute('title');") + self.assertEqual(title, self.marionette.title) + self.assertEqual('Title Test', self.marionette.title) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py new file mode 100644 index 000000000..6c5e75f51 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_type.py @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestWindowTypeChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.current_window_handle + self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');") + self.marionette.switch_to_window('foo') + self.assertNotEqual(self.win, self.marionette.current_window_handle) + + def tearDown(self): + self.assertNotEqual(self.win, self.marionette.current_window_handle) + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_get_window_type(self): + window_type = self.marionette.execute_script("return window.document.documentElement.getAttribute('windowtype');") + self.assertEqual(window_type, self.marionette.get_window_type()) + self.assertEqual('Test Type', self.marionette.get_window_type()) + diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_with_using_context.py b/testing/marionette/harness/marionette_harness/tests/unit/test_with_using_context.py new file mode 100644 index 000000000..1b2d60d2d --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_with_using_context.py @@ -0,0 +1,66 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver.decorators import using_context +from marionette_driver.errors import MarionetteException + +from marionette_harness import MarionetteTestCase + + +class TestSetContext(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + + # shortcuts to improve readability of these tests + self.chrome = self.marionette.CONTEXT_CHROME + self.content = self.marionette.CONTEXT_CONTENT + + test_url = self.marionette.absolute_url("empty.html") + self.marionette.navigate(test_url) + self.marionette.set_context(self.content) + self.assertEquals(self.get_context(), self.content) + + def get_context(self): + return self.marionette._send_message("getContext", key="value") + + def test_set_different_context_using_with_block(self): + with self.marionette.using_context(self.chrome): + self.assertEquals(self.get_context(), self.chrome) + self.assertEquals(self.get_context(), self.content) + + def test_set_same_context_using_with_block(self): + with self.marionette.using_context(self.content): + self.assertEquals(self.get_context(), self.content) + self.assertEquals(self.get_context(), self.content) + + def test_nested_with_blocks(self): + with self.marionette.using_context(self.chrome): + self.assertEquals(self.get_context(), self.chrome) + with self.marionette.using_context(self.content): + self.assertEquals(self.get_context(), self.content) + self.assertEquals(self.get_context(), self.chrome) + self.assertEquals(self.get_context(), self.content) + + def test_set_scope_while_in_with_block(self): + with self.marionette.using_context(self.chrome): + self.assertEquals(self.get_context(), self.chrome) + self.marionette.set_context(self.content) + self.assertEquals(self.get_context(), self.content) + self.assertEquals(self.get_context(), self.content) + + def test_exception_raised_while_in_with_block_is_propagated(self): + with self.assertRaises(MarionetteException): + with self.marionette.using_context(self.chrome): + raise MarionetteException + self.assertEquals(self.get_context(), self.content) + + def test_with_using_context_decorator(self): + @using_context('content') + def inner_content(m): + self.assertEquals(self.get_context(), 'content') + @using_context('chrome') + def inner_chrome(m): + self.assertEquals(self.get_context(), 'chrome') + inner_content(self.marionette) + inner_chrome(self.marionette) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini new file mode 100644 index 000000000..573096378 --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini @@ -0,0 +1,132 @@ +[test_marionette.py] +[test_geckoinstance.py] +[test_data_driven.py] +[test_session.py] +[test_capabilities.py] +[test_accessibility.py] +[test_expectedfail.py] +expected = fail +[test_import_script.py] +[test_click.py] +[test_click_chrome.py] +skip-if = appname == 'fennec' +[test_checkbox.py] +[test_checkbox_chrome.py] +skip-if = appname == 'fennec' +[test_elementsize.py] +[test_elementsize_chrome.py] +skip-if = appname == 'fennec' +[test_position.py] +[test_rendered_element.py] +[test_chrome_element_css.py] +skip-if = appname == 'fennec' +[test_element_state.py] +[test_element_state_chrome.py] +skip-if = appname == 'fennec' +[test_text.py] +[test_text_chrome.py] +skip-if = true # "Bug 896046" + +[test_clearing.py] +[test_typing.py] + +[test_log.py] + +[test_about_pages.py] + +[test_execute_async_script.py] +[test_execute_script.py] +[test_simpletest_fail.js] +[test_element_retrieval.py] +[test_findelement_chrome.py] +skip-if = appname == 'fennec' + +[test_navigation.py] + +[test_timeouts.py] + +[test_single_finger_desktop.py] +skip-if = appname == 'fennec' || os == "win" # Bug 1025040 + +[test_simpletest_pass.js] +[test_simpletest_sanity.py] +[test_simpletest_chrome.js] +[test_simpletest_timeout.js] +[test_anonymous_content.py] +skip-if = appname == 'fennec' +[test_switch_frame.py] +skip-if = os == "win" # Bug 1078237 +[test_switch_frame_chrome.py] +skip-if = appname == 'fennec' +[test_switch_remote_frame.py] +skip-if = appname == 'fennec' +[test_switch_window_chrome.py] +skip-if = appname == 'fennec' +[test_switch_window_content.py] + +[test_pagesource.py] +[test_pagesource_chrome.py] +skip-if = appname == 'fennec' + +[test_visibility.py] +[test_window_handles_chrome.py] +skip-if = appname == 'fennec' +[test_window_handles_content.py] +[test_window_close_chrome.py] +skip-if = appname == 'fennec' +[test_window_close_content.py] +[test_window_position.py] +skip-if = appname == 'fennec' + +[test_screenshot.py] +[test_cookies.py] +[test_window_title.py] +[test_window_title_chrome.py] +skip-if = appname == 'fennec' +[test_window_type.py] +skip-if = appname == 'fennec' +[test_implicit_waits.py] +[test_wait.py] +[test_expected.py] +[test_date_time_value.py] +[test_getactiveframe_oop.py] +skip-if = true # Bug 925688 +[test_chrome_async_finish.js] +[test_screen_orientation.py] +[test_errors.py] + +[test_execute_isolate.py] +[test_click_scrolling.py] +[test_profile_management.py] +skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993 +[test_quit_restart.py] +skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993 +[test_set_window_size.py] +skip-if = os == "linux" || appname == 'fennec' # Bug 1085717 +[test_with_using_context.py] + +[test_modal_dialogs.py] +skip-if = appname == 'fennec' # Bug 1325738 +[test_key_actions.py] +[test_mouse_action.py] +skip-if = appname == 'fennec' +[test_teardown_context_preserved.py] +[test_file_upload.py] +skip-if = appname == 'fennec' || os == "win" # http://bugs.python.org/issue14574 + +[test_execute_sandboxes.py] +[test_using_permissions.py] +[test_prefs.py] + +[test_shadow_dom.py] + +[test_chrome.py] +skip-if = appname == 'fennec' + +[test_addons.py] +skip-if = appname == 'fennec' # Bug 1330598 + +[test_select.py] +[test_crash.py] +skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993 +[test_localization.py] diff --git a/testing/marionette/harness/marionette_harness/tests/webapi-tests.ini b/testing/marionette/harness/marionette_harness/tests/webapi-tests.ini new file mode 100644 index 000000000..2c9dd1dce --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/webapi-tests.ini @@ -0,0 +1,8 @@ +[include:../../../../../dom/system/gonk/tests/marionette/manifest.ini] +[include:../../../../../dom/system/tests/marionette/manifest.ini] +skip-if = android_version > '15' # Bug 1203072 +[include:../../../../../dom/events/test/marionette/manifest.ini] +[include:../../../../../dom/wifi/test/marionette/manifest.ini] +[include:../../../../../dom/tethering/tests/marionette/manifest.ini] +skip-if = android_version > '15' # Bug 1203075 +[include:../../../../../dom/network/tests/marionette/manifest.ini] |