From d6440f416c1643104a00a91be50783ea343e1dee Mon Sep 17 00:00:00 2001 From: jebus Date: Wed, 3 Dec 2025 10:49:23 +1300 Subject: [PATCH] [Fix] Frontend Testing Fixtures (#249) Co-authored-by: Tim Lorsbach Co-authored-by: Liam Brydon Reviewed-on: https://git.envipath.com/enviPath/enviPy/pulls/249 --- epdb/management/commands/localize_urls.py | 2 - tests/frontend/frontend_base.py | 69 +++++++++++++ tests/frontend/test_homepage.py | 120 +++++----------------- tests/frontend/test_loginpage.py | 51 +++++++++ tests/frontend/test_packagepage.py | 69 +++++++++++++ 5 files changed, 214 insertions(+), 97 deletions(-) create mode 100644 tests/frontend/frontend_base.py create mode 100644 tests/frontend/test_loginpage.py create mode 100644 tests/frontend/test_packagepage.py diff --git a/epdb/management/commands/localize_urls.py b/epdb/management/commands/localize_urls.py index 9d876fd4..472471de 100644 --- a/epdb/management/commands/localize_urls.py +++ b/epdb/management/commands/localize_urls.py @@ -24,7 +24,6 @@ class Command(BaseCommand): def handle(self, *args, **options): Package = s.GET_PACKAGE_MODEL() - print("Localizing urls for Package") Package.objects.update(url=Replace(F("url"), Value(options["old"]), Value(options["new"]))) MODELS = [ @@ -50,7 +49,6 @@ class Command(BaseCommand): ] for model in MODELS: obj_cls = apps.get_model("epdb", model) - print(f"Localizing urls for {model}") obj_cls.objects.update( url=Replace(F("url"), Value(options["old"]), Value(options["new"])) ) diff --git a/tests/frontend/frontend_base.py b/tests/frontend/frontend_base.py new file mode 100644 index 00000000..61fe9dca --- /dev/null +++ b/tests/frontend/frontend_base.py @@ -0,0 +1,69 @@ +import os + +from django.conf import settings as s +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from django.core.management import call_command +from django.test import override_settings +from playwright.sync_api import sync_playwright + + +@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True) +class EnviPyStaticLiveServerTestCase(StaticLiveServerTestCase): + fixtures = ["test_fixtures_incl_model.jsonl.gz"] + + @staticmethod + def repair_polymorphic_ctypes(): + from django.contrib.contenttypes.models import ContentType + + from epdb.models import EPModel + + for obj in EPModel.objects.filter(polymorphic_ctype__isnull=True): + obj.polymorphic_ctype = ContentType.objects.get_for_model(obj.__class__) + obj.save(update_fields=["polymorphic_ctype"]) + + @classmethod + def setUpClass(cls): + os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" + super().setUpClass() + + cls.playwright = sync_playwright().start() + cls.browser = cls.playwright.chromium.launch() + cls.username = "user0" + cls.password = "SuperSafe" + + def setUp(self): + # DB gets flushed after each test and rolled back to initial fixture state. + # Hence, we have to localize the urls per test. + # The fixtures have "http://localhost:8000/" in all of the URLs + # Use the custom mgmt command to adjust it to the current live_server_url + call_command("localize_urls", old="http://localhost:8000/", new=f"{self.live_server_url}/") + + # Fix broken polymorphic ctypes + EnviPyStaticLiveServerTestCase.repair_polymorphic_ctypes() + + s.SERVER_URL = self.live_server_url + self.context = self.browser.new_context() + self.page = self.context.new_page() + + def tearDown(self): + self.page.wait_for_load_state("networkidle") + self.page.close() + + @classmethod + def tearDownClass(cls): + cls.browser.close() + cls.playwright.stop() + super().tearDownClass() + + def login(self): + """Sign in with the test user, 'user0'""" + self.page.goto(self.live_server_url + "/login") + self.page.get_by_role("textbox", name="Username").click() + self.page.get_by_role("textbox", name="Username").fill(self.username) + self.page.get_by_role("textbox", name="Password").click() + self.page.get_by_role("textbox", name="Password").fill(self.password) + + with self.page.expect_navigation(): + self.page.get_by_role("button", name="Sign In").click() + + return self.page diff --git a/tests/frontend/test_homepage.py b/tests/frontend/test_homepage.py index dd90ce2a..8ceb1efe 100644 --- a/tests/frontend/test_homepage.py +++ b/tests/frontend/test_homepage.py @@ -1,108 +1,38 @@ -import os - -from django.conf import settings as s -from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.test import tag -from playwright.sync_api import expect, sync_playwright +from playwright.sync_api import expect -from epdb.logic import UserManager -from epdb.models import User, ExternalDatabase +from .frontend_base import EnviPyStaticLiveServerTestCase -class TestHomepage(StaticLiveServerTestCase): - @classmethod - def setUpClass(cls): - os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" - super().setUpClass() - cls.playwright = sync_playwright().start() - cls.browser = cls.playwright.chromium.launch() - - def setUp(self): - # Create test data - s.SERVER_URL = self.live_server_url - self.anonymous = UserManager.create_user( - "anonymous", - "anon@envipath.com", - "SuperSafe", - is_active=True, - add_to_group=False, - set_setting=False, - ) - databases = [ - { - "name": "PubChem Compound", - "full_name": "PubChem Compound Database", - "description": "Chemical database of small organic molecules", - "base_url": "https://pubchem.ncbi.nlm.nih.gov", - "url_pattern": "https://pubchem.ncbi.nlm.nih.gov/compound/{id}", - }, - { - "name": "PubChem Substance", - "full_name": "PubChem Substance Database", - "description": "Database of chemical substances", - "base_url": "https://pubchem.ncbi.nlm.nih.gov", - "url_pattern": "https://pubchem.ncbi.nlm.nih.gov/substance/{id}", - }, - { - "name": "ChEBI", - "full_name": "Chemical Entities of Biological Interest", - "description": "Dictionary of molecular entities", - "base_url": "https://www.ebi.ac.uk/chebi", - "url_pattern": "https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:{id}", - }, - { - "name": "RHEA", - "full_name": "RHEA Reaction Database", - "description": "Comprehensive resource of biochemical reactions", - "base_url": "https://www.rhea-db.org", - "url_pattern": "https://www.rhea-db.org/rhea/{id}", - }, - { - "name": "KEGG Reaction", - "full_name": "KEGG Reaction Database", - "description": "Database of biochemical reactions", - "base_url": "https://www.genome.jp", - "url_pattern": "https://www.genome.jp/entry/{id}", - }, - { - "name": "UniProt", - "full_name": "MetaCyc Metabolic Pathway Database", - "description": "UniProt is a freely accessible database of protein sequence and functional information", - "base_url": "https://www.uniprot.org", - "url_pattern": 'https://www.uniprot.org/uniprotkb?query="{id}"', - }, - ] - - for db_info in databases: - ExternalDatabase.objects.get_or_create(name=db_info["name"], defaults=db_info) - self.username = "testuser" - self.password = "password123" - self.user = User.objects.create_user(username=self.username, password=self.password) - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - cls.browser.close() - cls.playwright.stop() +class TestHomepage(EnviPyStaticLiveServerTestCase): + @tag("frontend") + def test_predict(self): + page = self.login() + page.get_by_role("textbox", name="canonical SMILES string").click() + page.get_by_role("textbox", name="canonical SMILES string").fill("CCCN") + page.get_by_role("button", name="Predict!").click() + # Check that the pathway box is visible + expect(page.locator("rect")).to_be_visible(timeout=10000) @tag("frontend") - def test_login(self): + def test_advanced_predict(self): page = self.login() - expect(page.locator("#loggedInButton")).to_be_visible() - page.close() + page.get_by_role("link", name="Advanced").click() + # Check predict page opens correctly + expect(page.get_by_role("heading", name="Predict a Pathway in")).to_be_visible() + page.get_by_role("textbox", name="Name").click() + page.get_by_role("textbox", name="Name").fill("Test Pathway") + page.get_by_role("textbox", name="Description").click() + page.get_by_role("textbox", name="Description").fill("Test Description") + page.get_by_role("textbox", name="SMILES").click() + page.get_by_role("textbox", name="SMILES").fill("OCCCN") + page.locator("#predict-submit-button").click() + # Check that the pathway box is visible + expect(page.locator("rect")).to_be_visible(timeout=10000) @tag("frontend") def test_go_home(self) -> None: page = self.login() page.get_by_role("link").first.click() + # Check the homepage predict box is visible expect(page.get_by_text("SMILES Draw Predict! Caffeine")).to_be_visible() - - def login(self): - page = self.browser.new_page() - page.goto(self.live_server_url + "/login/") - page.get_by_role("textbox", name="Username").click() - page.get_by_role("textbox", name="Username").fill(self.username) - page.get_by_role("textbox", name="Password").click() - page.get_by_role("textbox", name="Password").fill(self.password) - page.get_by_role("button", name="Sign In").click() - return page diff --git a/tests/frontend/test_loginpage.py b/tests/frontend/test_loginpage.py new file mode 100644 index 00000000..fa80ba88 --- /dev/null +++ b/tests/frontend/test_loginpage.py @@ -0,0 +1,51 @@ +from django.test import tag +from playwright.sync_api import expect +from django.conf import settings as s +from .frontend_base import EnviPyStaticLiveServerTestCase + + +class TestLoginPage(EnviPyStaticLiveServerTestCase): + @tag("frontend") + def test_register(self): + page = self.page + page.goto(self.live_server_url + "/login") + page.get_by_text("Register", exact=True).click() + page.get_by_role("textbox", name="Username").click() + page.get_by_role("textbox", name="Username").fill("newuser") + page.get_by_role("textbox", name="Email").click() + page.get_by_role("textbox", name="Email").fill("newuser@new.com") + page.get_by_role("textbox", name="Password", exact=True).click() + page.get_by_role("textbox", name="Password", exact=True).fill("NewUser_1") + page.get_by_role("textbox", name="Repeat Password").click() + page.get_by_role("textbox", name="Repeat Password").fill("NewUser_1") + page.get_by_role("button", name="Sign Up").click() + + if s.ADMIN_APPROVAL_REQUIRED: + expected_text = "Your account has been created! An admin will activate it soon!" + else: + expected_text = ( + "Account has been created! You'll receive a mail to activate your account shortly." + ) + # Check for success text after Sign Up is clicked + expect(page.get_by_text(expected_text)).to_be_visible(timeout=10000) + + if s.ADMIN_APPROVAL_REQUIRED: + from django.contrib.auth import get_user_model + + u = get_user_model().objects.get(username="newuser") + u.is_active = True + u.save() + + page.get_by_role("textbox", name="Username").click() + page.get_by_role("textbox", name="Username").fill("newuser") + page.get_by_role("textbox", name="Password").click() + page.get_by_role("textbox", name="Password").fill("NewUser_1") + page.get_by_role("button", name="Sign In").click() + # Check that the logged in button is visible indicating the user is logged in + expect(page.locator("#loggedInButton")).to_be_visible(timeout=100000000) + + @tag("frontend") + def test_login(self): + page = self.login() + # Check that the logged in button is visible indicating the user is logged in + expect(page.locator("#loggedInButton")).to_be_visible() diff --git a/tests/frontend/test_packagepage.py b/tests/frontend/test_packagepage.py new file mode 100644 index 00000000..019e6487 --- /dev/null +++ b/tests/frontend/test_packagepage.py @@ -0,0 +1,69 @@ +import re +from django.test import tag +from playwright.sync_api import expect + +from .frontend_base import EnviPyStaticLiveServerTestCase + + +class TestPackagePage(EnviPyStaticLiveServerTestCase): + @tag("frontend") + def test_create_package(self): + page = self.login() + page = self.create_package(page) + # Check the package name is correct + expect(page.locator("h2")).to_contain_text("test package") + + @tag("frontend") + def test_package_permissions(self): + page = self.login() + page = self.create_package(page) + page.get_by_role("button", name="Actions").click() + page.get_by_role("button", name="Edit Permissions").click() + # Add read and write permission to enviPath Users group + page.locator("#select_grantee").select_option(label="enviPath Users") + page.locator("#read_new").check() + page.locator("#write_new").check() + page.get_by_role("button", name="+", exact=True).click() + page.get_by_role("button", name="Actions").click() + page.get_by_role("button", name="Edit Permissions").click() + # Check the permissions saved when re-opening the permissions box + expect(page.get_by_text("enviPath Users")).to_be_visible() + + @tag("frontend") + def test_predict_in_package(self): + page = self.login() + page = self.create_package(page) + pathway_button = page.get_by_role("link", name="Pathways") + # Find number of current pathways by extracting it from pathway button + num_pathways = int(re.search(r"Pathways \((\d+)\)", pathway_button.inner_text()).group(1)) + pathway_button.click() + page.get_by_role("button", name="Actions").click() + page.get_by_role("link", name="New Pathway").click() + # Check that the predict page 'in [package_name]' text shows the current package + expect(page.get_by_role("strong").get_by_text("test package")).to_be_visible() + page.get_by_role("textbox", name="Name").click() + page.get_by_role("textbox", name="Name").fill("Test Pathway") + page.get_by_role("textbox", name="Description").click() + page.get_by_role("textbox", name="Description").fill("Test description") + page.get_by_role("textbox", name="SMILES").click() + page.get_by_role("textbox", name="SMILES").fill("OCCCN") + page.locator("#predict-submit-button").click() + # Check a pathway is visible + expect(page.locator("rect")).to_be_visible() + page.get_by_role("link", name="test package").click() + # Check that the package now has one more pathway than initially + expect(page.locator("#docContent")).to_contain_text(f"Pathways ({num_pathways + 1})") + + @staticmethod + def create_package(page): + """Make a new empty package with name 'test package'""" + page.get_by_role("button", name="Browse").click() + page.get_by_role("link", name="Package", exact=True).click() + page.get_by_role("button", name="Actions").click() + page.get_by_role("button", name="New Package").click() + page.get_by_role("textbox", name="Name").click() + page.get_by_role("textbox", name="Name").fill("test package") + page.get_by_role("textbox", name="Description").click() + page.get_by_role("textbox", name="Description").fill("test description") + page.get_by_role("button", name="Submit").click() + return page