[Feature] Frontend Testing #140 (#218)

I added playwright for frontend testing and got a couple simple test cases working.
I have updated pyproject.toml but it can also be installed with `pip install pytest-playwright` followed by `playwright install`

With the django server running you can do `playwright codegen http://localhost:8000/` which will generate test code based on the actions you take on the webpage it opens. Be sure to change the target to pytest in the code pop up.

I will add more test cases but @jebus and @t03i feel free to add more. Especially once we are done with the full front-end redesign.

I have put the tests under `tests/frontend/` but I am not sure how to add them to the CI. They give steps for CI integration but maybe we want to somehow include them in our exisiting CI yaml? https://playwright.dev/python/docs/ci-intro

Reviewed-on: enviPath/enviPy#218
Reviewed-by: Tobias O <tobias.olenyi@envipath.com>
Co-authored-by: Liam Brydon <lbry121@aucklanduni.ac.nz>
Co-committed-by: Liam Brydon <lbry121@aucklanduni.ac.nz>
This commit is contained in:
2025-11-26 19:44:35 +13:00
committed by jebus
parent b6c35fea76
commit 7f6f209b4a
6 changed files with 300 additions and 2 deletions

View File

@ -0,0 +1,108 @@
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 epdb.logic import UserManager
from epdb.models import User, ExternalDatabase
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()
@tag("frontend")
def test_login(self):
page = self.login()
expect(page.locator("#loggedInButton")).to_be_visible()
page.close()
@tag("frontend")
def test_go_home(self) -> None:
page = self.login()
page.get_by_role("link").first.click()
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