diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 000000000..34fe4d044 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,64 @@ +name: CI-MacOS + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + selenium: + runs-on: macos-latest + + strategy: + matrix: + include: + - PY_VER: py38 + python-version: 3.8 + - PY_VER: py39 + python-version: 3.9 + - PY_VER: py310 + python-version: "3.10" + - PY_VER: py311 + python-version: 3.11 + - PY_VER: py312 + python-version: "3.12" + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v4 + with: + python-version: ${{matrix.python-version}} + + - name: Install test dependencies + run: pip install tox + + - name: Enable Safari Webdriver + run: | + defaults write com.apple.Safari IncludeDevelopMenu YES + defaults write com.apple.Safari AllowRemoteAutomation 1 + sudo safaridriver --enable + + - name: Install timeout util and java + run: | + brew install coreutils + brew install java11 + + - name: Download Selenium Server + run: | + wget https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.16.0/selenium-server-4.16.1.jar -O selenium-server.jar + + - name: Setup standalone + run: /usr/local/opt/openjdk@11/bin/java -jar selenium-server.jar standalone -I 'safari' > selenium-standalone.log 2>&1 & + + + - name: Run tests for macos + run: | + gtimeout 60 bash -c 'while ! wget -O /dev/null -T 1 http://localhost:4444/readyz; do echo waiting for selenium server; sleep 1; done' || (cat selenium-standalone.log && exit 2) + + tox -e tests_macos_selenium -- --cache-clear -n 1 tests || tox -e tests_macos_selenium -- -n 1 --last-failed --last-failed-no-failures none diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..1c51692a0 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +markers = + macos: tests can be runned only on MacOS (Safari) diff --git a/samples/test_google_search.py b/samples/test_google_search.py index e35367c57..46d4fa7f4 100755 --- a/samples/test_google_search.py +++ b/samples/test_google_search.py @@ -32,4 +32,5 @@ def test_filling_splinter_in_the_search_box_returns_splinter_website(self): self.assertTrue(self.browser.is_text_present("https://splinter.readthedocs.io")) -unittest.main() +if __name__ == "__main__": + unittest.main() diff --git a/splinter/driver/webdriver/remote.py b/splinter/driver/webdriver/remote.py index a0971a763..82015dd8f 100644 --- a/splinter/driver/webdriver/remote.py +++ b/splinter/driver/webdriver/remote.py @@ -13,6 +13,7 @@ from splinter.driver.webdriver.setup import _setup_chrome from splinter.driver.webdriver.setup import _setup_edge from splinter.driver.webdriver.setup import _setup_firefox +from splinter.driver.webdriver.setup import _setup_safari # MonkeyPatch RemoteConnection remote_connection.RemoteConnection._request = patch_request # type: ignore @@ -64,5 +65,12 @@ def __init__( options = options or Options() driver = _setup_firefox(Remote, self.config, options, **kwargs) + elif browser_name == "SAFARI": + from selenium.webdriver.safari.options import Options + + options = options or Options() + driver = _setup_safari(Remote, self.config, options, **kwargs) + else: + raise ValueError(f"Unsupporeted browser {browser_name}") super().__init__(driver, wait_time) diff --git a/splinter/driver/webdriver/setup.py b/splinter/driver/webdriver/setup.py index aa720ffa2..e6b0ad00d 100644 --- a/splinter/driver/webdriver/setup.py +++ b/splinter/driver/webdriver/setup.py @@ -93,3 +93,15 @@ def _setup_firefox(driver_class, config=None, options=None, service=None, **kwar rv.fullscreen_window() return rv + + +def _setup_safari(driver_class, config=None, options=None, service=None, **kwargs): + """ + Returns: selenium.webdriver.Safari || selenium.webdriver.Remote + """ + if driver_class == Remote: + rv = driver_class(options=options, **kwargs) + else: + rv = driver_class(options=options, service=service, **kwargs) + + return rv diff --git a/tests/is_element_present.py b/tests/is_element_present.py index f352095ec..15a8032f9 100644 --- a/tests/is_element_present.py +++ b/tests/is_element_present.py @@ -51,7 +51,7 @@ def test_is_element_present_by_xpath_returns_false_if_element_is_not_present(sel def test_is_element_not_present_by_xpath_returns_false_if_element_is_present(self): """should is_element_not_present_by_xpath returns False if element is present""" self.browser.find_by_css(".add-async-element").click() - self.browser.find_by_xpath("//h4") + assert len(self.browser.find_by_xpath("//h4")) > 0 assert not self.browser.is_element_not_present_by_xpath("//h4") def test_is_element_not_present_by_xpath_using_a_custom_wait_time(self): diff --git a/tests/test_webdriver_remote.py b/tests/test_webdriver_remote.py index 12f835dfa..b2640c1cc 100644 --- a/tests/test_webdriver_remote.py +++ b/tests/test_webdriver_remote.py @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. import unittest +from unittest.mock import patch from urllib.request import urlopen import pytest @@ -32,11 +33,11 @@ def setUp(self): def test_support_with_statement(self): "Remote should support with statement" - with Browser("remote"): + with Browser("remote", browser="firefox"): pass + @pytest.mark.skip(reason="Remote should not support custom user agent") def test_should_be_able_to_change_user_agent(self): - "Remote should not support custom user agent" pass @@ -51,9 +52,169 @@ def setUp(self): def test_support_with_statement(self): "Remote should support with statement" - with Browser("remote"): + with Browser("remote", browser="chrome"): pass + @pytest.mark.skip(reason="Remote should not support custom user agent") def test_should_be_able_to_change_user_agent(self): - "Remote should not support custom user agent" pass + + +@pytest.mark.macos +class RemoteBrowserSafariTest(WebDriverTests, unittest.TestCase): + @pytest.fixture(autouse=True, scope="class") + def setup_browser(self, request): + # test with statement. It can't be used as simple test + # because safari doesn't support multisessions + with Browser("remote", browser="safari"): + pass + + request.cls.browser = Browser("remote", browser="safari") + request.addfinalizer(request.cls.browser.quit) + + def setUp(self): + self.browser.visit(EXAMPLE_APP) + + def test_support_with_statement(self): + """ + Remote should support with statement + See setup browser + """ + + @pytest.mark.skip(reason="Remote should not support custom user agent") + def test_should_be_able_to_change_user_agent(self): + pass + + # ------- BEGIN OF MULTISESSION TESTS ------- + # Safari doesn't support multisessions. + # So next tests mock quit of browser. + def get_new_browser(self): + return self.browser + + def test_can_forward_on_history(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_can_forward_on_history() + + def test_create_and_access_a_cookie(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_create_and_access_a_cookie() + + def test_create_many_cookies_at_once_as_dict(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_create_many_cookies_at_once_as_dict() + + def test_create_some_cookies_and_delete_them_all(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_create_some_cookies_and_delete_them_all() + + def test_create_and_delete_a_cookie(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_create_and_delete_a_cookie() + + def test_create_and_delete_many_cookies(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_create_and_delete_many_cookies() + + def test_try_to_destroy_an_absent_cookie_and_nothing_happens(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_try_to_destroy_an_absent_cookie_and_nothing_happens() + + def test_create_and_get_all_cookies(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_create_and_get_all_cookies() + + def test_create_and_use_contains(self): + with patch("splinter.driver.webdriver.remote.WebDriver.quit"): + super().test_create_and_use_contains() + + # ------- END OF MULTISESSION TESTS ------- + + def test_can_fill_more_than_one_field_in_form(self): + "should provide a away to change field value" + self.browser.fill("query", "my name") + self.assertFalse(self.browser.find_by_id("gender-m").checked) + self.assertFalse(self.browser.find_option_by_value("rj").selected) + self.assertFalse(self.browser.find_by_name("some-check").checked) + self.assertTrue(self.browser.find_by_name("checked-checkbox").checked) + # Select of dropdown doesn't work for Safari 17 (remote). Safari as OS user works well + # for some reason select doesn't work for Safari + self.browser.fill_form( + { + "query": "another new query", + "description": "Just another description value in the textarea", + "gender": "M", + # "uf": "rj", + "some-check": True, + "checked-checkbox": False, + }, + ) + query_value = self.browser.find_by_name("query").value + self.assertEqual("another new query", query_value) + desc_value = self.browser.find_by_name("description").value + self.assertEqual("Just another description value in the textarea", desc_value) + self.assertTrue(self.browser.find_by_id("gender-m").checked) + # self.assertTrue(self.browser.find_option_by_value("rj").selected) + self.assertTrue(self.browser.find_by_name("some-check").checked) + self.assertFalse(self.browser.find_by_name("checked-checkbox").checked) + + # ------- BEGIN OF CLICK PROBLEM TESTS ------- + # https://stackoverflow.com/questions/77388720/automation-testing-with-selenium-click-doesnt-works-on-new-safari-17-ios-sonoma + @pytest.mark.xfail + def test_click_element_by_css_selector(self): + super().test_click_element_by_css_selector() + + @pytest.mark.xfail + def test_click_input_by_css_selector(self): + super().test_click_input_by_css_selector() + + @pytest.mark.xfail + def test_clicking_submit_button_doesnt_post_button_value_if_empty(self): + super().test_clicking_submit_button_doesnt_post_button_value_if_empty() + + @pytest.mark.xfail + def test_clicking_submit_button_doesnt_post_button_value_if_name_not_present(self): + super().test_clicking_submit_button_doesnt_post_button_value_if_name_not_present() + + @pytest.mark.xfail + def test_clicking_submit_button_posts_button_value_if_value_present(self): + super().test_clicking_submit_button_posts_button_value_if_value_present() + + @pytest.mark.xfail + def test_clicking_submit_button_posts_empty_value_if_value_not_present(self): + super().test_clicking_submit_button_posts_empty_value_if_value_not_present() + + @pytest.mark.xfail + def test_clicking_submit_input_doesnt_post_input_value_if_empty(self): + super().test_clicking_submit_input_doesnt_post_input_value_if_empty() + + @pytest.mark.xfail + def test_clicking_submit_input_doesnt_post_input_value_if_name_not_present(self): + super().test_clicking_submit_input_doesnt_post_input_value_if_name_not_present() + + @pytest.mark.xfail + def test_clicking_submit_input_posts_empty_value_if_value_not_present(self): + super().test_clicking_submit_input_posts_empty_value_if_value_not_present() + + @pytest.mark.xfail + def test_clicking_submit_input_posts_input_value_if_value_present(self): + super().test_clicking_submit_input_posts_input_value_if_value_present() + + @pytest.mark.xfail + def test_submiting_a_form_and_verifying_page_content(self): + super().test_submiting_a_form_and_verifying_page_content() + + @pytest.mark.xfail + def test_click_links(self): + super().test_click_links() + + # ------- END OF CLICK PROBLEM TESTS ------- + # ------- START OF TYPE PROBLEM TESTS ------- + @pytest.mark.xfail + def test_simple_type(self): + super().test_simple_type() + + @pytest.mark.xfail + def test_simple_type_on_element(self): + super().test_simple_type_on_element() + + # ------- END OF TYPE PROBLEM TESTS ------- diff --git a/tox.ini b/tox.ini index 73668aeee..6d168fe66 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ extras = selenium deps = -rrequirements/test.txt commands= - pytest --ignore-flaky -v {posargs} + pytest --ignore-flaky -m "not macos" -v {posargs} [testenv:tests_windows_selenium] @@ -21,3 +21,10 @@ passenv = EDGEWEBDRIVER commands= pytest --ignore-flaky -v {posargs} + +[testenv:tests_macos_selenium] +extras = selenium +deps = + -rrequirements/test.txt +commands= + pytest --ignore-flaky -m macos -v {posargs}