From e0256720812e1878de8664cf6ef15d12ecc7ab63 Mon Sep 17 00:00:00 2001 From: Alex Cottner <148472676+alexcottner@users.noreply.github.com> Date: Mon, 5 Feb 2024 09:12:15 -0700 Subject: [PATCH] Fixes #3341 - Adding basic python playwright browser test Fixes #3341 - Adding basic python playwright browser test --- Makefile | 1 + constraints.in | 2 +- constraints.txt | 47 +++++++--------------------- docs/community.rst | 11 +------ pyproject.toml | 2 +- tests/browser.py | 78 ++++++++++++++-------------------------------- 6 files changed, 39 insertions(+), 102 deletions(-) diff --git a/Makefile b/Makefile index c57ebbdbc..5726bf295 100644 --- a/Makefile +++ b/Makefile @@ -116,6 +116,7 @@ functional: install-dev need-kinto-running $(VENV)/bin/py.test tests/functional.py browser-test: need-kinto-running + $(VENV)/bin/playwright install firefox $(VENV)/bin/py.test tests/browser.py clean: diff --git a/constraints.in b/constraints.in index b706aa9ec..1a2c98377 100644 --- a/constraints.in +++ b/constraints.in @@ -33,7 +33,7 @@ bravado_core pytest pytest-cache pytest-cov -selenium +playwright webtest # dev build diff --git a/constraints.txt b/constraints.txt index 0ad1011a8..f24719053 100644 --- a/constraints.txt +++ b/constraints.txt @@ -1,17 +1,15 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --strip-extras +# pip-compile --output-file=constraints.txt --strip-extras constraints.in # arrow==1.3.0 # via isoduration attrs==23.2.0 # via # jsonschema - # outcome # referencing - # trio bcrypt==4.1.2 # via -r constraints.in beautifulsoup4==4.12.3 @@ -23,7 +21,6 @@ build==1.0.3 certifi==2023.11.17 # via # requests - # selenium # sentry-sdk charset-normalizer==3.3.2 # via requests @@ -46,27 +43,21 @@ coverage==7.4.0 dockerflow==2024.1.0 # via -r constraints.in exceptiongroup==1.2.0 - # via - # pytest - # trio - # trio-websocket + # via pytest execnet==2.0.2 # via pytest-cache fqdn==1.5.1 # via jsonschema greenlet==3.0.3 - # via sqlalchemy -h11==0.14.0 - # via wsproto + # via + # playwright + # sqlalchemy hupper==1.12 # via pyramid idna==3.6 # via # jsonschema # requests - # trio -importlib-metadata==7.0.1 - # via build iniconfig==2.0.0 # via pytest iso8601==2.1.0 @@ -96,8 +87,6 @@ msgpack==1.0.7 # via bravado-core newrelic==9.6.0 # via -r constraints.in -outcome==1.3.0.post0 - # via trio packaging==23.2 # via # build @@ -111,10 +100,14 @@ plaster==1.1.2 # pyramid plaster-pastedeploy==1.0.1 # via pyramid +playwright==1.41.1 + # via -r constraints.in pluggy==1.3.0 # via pytest psycopg2==2.9.9 # via -r constraints.in +pyee==11.0.1 + # via playwright pyproject-hooks==1.0.0 # via build pyramid==2.0.2 @@ -130,8 +123,6 @@ pyramid-multiauth==1.0.1 # via -r constraints.in pyramid-tm==2.5 # via -r constraints.in -pysocks==1.7.1 - # via urllib3 pytest==7.4.4 # via # -r constraints.in @@ -176,8 +167,6 @@ rpds-py==0.17.1 # referencing ruff==0.1.15 # via -r constraints.in -selenium==4.12.0 - # via -r constraints.in sentry-sdk==1.39.2 # via -r constraints.in simplejson==3.19.2 @@ -188,10 +177,6 @@ six==1.16.0 # cornice-swagger # python-dateutil # rfc3339-validator -sniffio==1.3.0 - # via trio -sortedcontainers==2.4.0 - # via trio soupsieve==2.5 # via beautifulsoup4 sqlalchemy==2.0.25 @@ -220,16 +205,11 @@ translationstring==1.4 # via # colander # pyramid -trio==0.24.0 - # via - # selenium - # trio-websocket -trio-websocket==0.11.1 - # via selenium types-python-dateutil==2.8.19.20240106 # via arrow typing-extensions==4.9.0 # via + # pyee # sqlalchemy # swagger-spec-validator uri-template==1.3.0 @@ -237,7 +217,6 @@ uri-template==1.3.0 urllib3==2.1.0 # via # requests - # selenium # sentry-sdk venusian==3.1.0 # via @@ -257,10 +236,6 @@ webtest==3.0.0 # via -r constraints.in werkzeug==3.0.1 # via -r constraints.in -wsproto==1.2.0 - # via trio-websocket -zipp==3.17.0 - # via importlib-metadata zope-deprecation==5.0 # via pyramid zope-interface==6.1 diff --git a/docs/community.rst b/docs/community.rst index 1abc7afc3..3751913c3 100644 --- a/docs/community.rst +++ b/docs/community.rst @@ -235,16 +235,7 @@ In another terminal, run the end-to-end tests with: Browser Tests ------------- - -Make sure the `geckodriver `_ binary is available in your path. - -.. note:: - - If your installation of *Firefox* is custom, specify the path of its binary using an alias: - - :: - - alias geckodriver="geckodriver --binary /path/to/firefox" +We use `playwright `_ for browser testing. The tests included in this repo are very simple and verify the admin UI can at least authenticate with the current kinto back-end. Comprehensive unit tests are maintained in the kinto-admin repo. In a terminal, run an instance with the provided ``browser.ini`` configuration: diff --git a/pyproject.toml b/pyproject.toml index b21d218c3..3385307c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ test = [ "pytest", "pytest-cache", "pytest-cov", - "selenium", + "playwright", "webtest", ] dev = [ diff --git a/tests/browser.py b/tests/browser.py index 3496aaf6d..eaca64dfa 100644 --- a/tests/browser.py +++ b/tests/browser.py @@ -1,68 +1,38 @@ -import time +import re import unittest -from urllib.parse import urljoin -import requests -from selenium import webdriver -from selenium.webdriver.common.by import By +from playwright.sync_api import expect, sync_playwright -SERVER_URL = "http://localhost:8888/v1" -DEFAULT_AUTH = ("user", "p4ssw0rd") +baseUrl = "http://localhost:8888/v1/" +auth = {"user": "user", "password": "p4ssw0rd"} + +browser = sync_playwright().start().firefox.launch() +context = browser.new_context(base_url=baseUrl) +page = browser.new_page() class BrowserTest(unittest.TestCase): def setUp(self): - options = webdriver.FirefoxOptions() - options.headless = True - self.driver = webdriver.Firefox(options=options) - self.driver.implicitly_wait(10) # seconds - - @classmethod - def setUpClass(cls): - # Make sure our user exists. - requests.post( - urljoin(SERVER_URL, "/accounts"), - json={"data": {"id": DEFAULT_AUTH[0], "password": DEFAULT_AUTH[1]}}, - ) - - # Create a bucket and a collection for our user. - bucket_url = urljoin(SERVER_URL, "/buckets/workspace") - collection_url = f"{bucket_url}/collections/articles" - session = requests.Session() - session.auth = DEFAULT_AUTH - resp = session.put(bucket_url) - resp.raise_for_status() - resp = session.put(collection_url) - resp.raise_for_status() + request = context.request - def tearDown(self): - self.driver.close() + request.post("accounts", data={"data": {"id": auth["user"], "password": auth["password"]}}) - def test_admin_ui_renders_properly(self): - base_url = urljoin(SERVER_URL, "/v1/admin/") - self.driver.get(base_url) + def test_login_and_view_home_page(self): + page.goto(f"{baseUrl}admin/") - # Load auth page. - header = self.driver.find_element(By.CSS_SELECTOR, ".content div > h1") - self.assertIn("Administration", header.text) - self.assertTrue(header.is_displayed()) + expect(page).to_have_title(re.compile("Kinto Administration")) - # Select Kinto Accounts. - radio = self.driver.find_element(By.XPATH, "//label[contains(.,'Kinto Account Auth')]") - radio.click() + page.get_by_label("Kinto Account Auth").click() + txtUsername = page.get_by_label(re.compile("Username")) + txtPassword = page.get_by_label(re.compile("Password")) - # Fill username and password. - user_field = self.driver.find_element(By.ID, "root_credentials_username") - user_field.send_keys(DEFAULT_AUTH[0]) - user_pass = self.driver.find_element(By.ID, "root_credentials_password") - user_pass.send_keys(DEFAULT_AUTH[1]) - # Login - submit = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']") - submit.click() + txtUsername.fill(auth["user"]) + txtPassword.fill(auth["password"]) + page.get_by_text(re.compile("Sign in using Kinto Account Auth")).click() - # Navigate to simple review page (uses React Hooks and broke a few times) - review_url = base_url + "#/buckets/workspace/collections/articles/simple-review" - self.driver.get(review_url) - time.sleep(1) - self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, ".alert-warning").is_displayed()) + expect(page.get_by_text("Kinto Administration")).to_be_visible() + expect(page.get_by_text("project_name")).to_be_visible() + expect(page.get_by_text("project_version")).to_be_visible() + expect(page.get_by_text("http_api_version")).to_be_visible() + expect(page.get_by_text("project_docs")).to_be_visible()