Skip to content

Commit

Permalink
Merge pull request #162 from modlinltd/add-e2e-browser-tests
Browse files Browse the repository at this point in the history
Add e2e browser tests
  • Loading branch information
asfaltboy committed Mar 28, 2024
2 parents b1305eb + e1dc447 commit 2b2432f
Show file tree
Hide file tree
Showing 17 changed files with 167 additions and 65 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: "CodeQL"

on:
push:
branches: [ "develop", "master" ]
branches: ["develop", "master"]
pull_request:
branches: [ "develop" ]
branches: ["develop"]
schedule:
- cron: "17 4 * * 5"

Expand All @@ -20,11 +20,11 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ javascript, python ]
language: [javascript, python]

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pythonpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
Expand Down
31 changes: 16 additions & 15 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ on:
- pull_request

jobs:
build:
build-and-test:
runs-on: ubuntu-latest
container: mcr.microsoft.com/playwright:v1.42.1-jammy
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"]
python-version: ["3.8", "3.9", "3.10", "pypy-3.8"]

steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions coveralls
- name: Test with tox
run: tox -p auto
env:
TOX_PARALLEL_NO_SPINNER: 1
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions coveralls
- name: Test with tox
run: tox -p auto
env:
TOX_PARALLEL_NO_SPINNER: 1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ django_advanced_filters.egg-info/
tests/db.sqlite*
/.python-version
/.cache/
__pycache__/
/.eggs/
/htmlcov/
/advanced_filters/.coverage
Expand All @@ -18,5 +19,6 @@ tests/db.sqlite*
/tests/local.db
/.venv
.vscode/settings.json
Sublimerge.vcs-cache
pyrightconfig.json
.ropeproject
11 changes: 11 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[settings]
line_length=88
combine_as_imports = yes
default_section = THIRDPARTY
include_trailing_comma = yes
known_django = django
known_first_party = tests,advanced_filters
multi_line_output = 3
no_lines_before=LOCALFOLDER
sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,FIRSTPARTY,LOCALFOLDER
skip_glob = */migrations/*.py
13 changes: 0 additions & 13 deletions advanced_filters/tests/conftest.py

This file was deleted.

File renamed without changes.
25 changes: 25 additions & 0 deletions advanced_filters/tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest

from tests.factories import ClientFactory, SalesRepFactory


@pytest.fixture(scope="session")
def base_url(live_server):
return live_server.url


@pytest.fixture
@pytest.mark.usefixtures("db")
def user():
return SalesRepFactory()


@pytest.fixture()
def client(client, user):
client.force_login(user)
return client


@pytest.fixture
def three_clients(user):
return ClientFactory.create_batch(3, assigned_to=user)
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from django.db.models import Q
from django.urls import reverse

from ..models import AdvancedFilter
from .factories import AdvancedFilterFactory
from advanced_filters.models import AdvancedFilter
from advanced_filters.tests.factories import AdvancedFilterFactory

URL_NAME_CHANGE = "admin:advanced_filters_advancedfilter_change"
URL_NAME_ADD = "admin:advanced_filters_advancedfilter_add"
Expand Down Expand Up @@ -42,7 +42,9 @@ def test_change_and_goto(client, user, settings, advanced_filter):
res = client.post(url, data=form_data)
assert res.status_code == 302
url = res["location"]
assert url.endswith("%s?_afilter=1" % reverse(URL_NAME_CLIENT_CHANGELIST))
changelist_url = reverse(URL_NAME_CLIENT_CHANGELIST)
new_filter_id = AdvancedFilter.objects.last().pk
assert url.endswith(f"{changelist_url}?_afilter={new_filter_id}")


def test_create_page_disabled(client, user):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.contrib.auth.models import Permission
from django.urls import reverse_lazy

from ..models import AdvancedFilter
from advanced_filters.models import AdvancedFilter

URL_CLIENT_CHANGELIST = reverse_lazy("admin:customers_client_changelist")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import json
import sys
from datetime import timedelta
from operator import attrgetter
from operator import itemgetter

import django
import factory
import pytest
from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import force_str
from django.urls import reverse
from tests.factories import ClientFactory

from tests.factories import ClientFactory

URL_NAME = "afilters_get_field_choices"


def parse_json(content):
return json.loads(force_str(content))


def assert_json(content, expect):
assert json.loads(force_str(content)) == expect
assert parse_json(content) == expect


def assert_view_error(client, error, exception=None, **view_kwargs):
""" Ensure view either raises exception or returns a 400 json error """
"""Ensure view either raises exception or returns a 400 json error"""
view_url = reverse(URL_NAME, kwargs=view_kwargs)

if exception is not None:
Expand Down Expand Up @@ -71,11 +73,6 @@ def test_field_with_choices(client):
)


@pytest.fixture
def three_clients(user):
return ClientFactory.create_batch(3, assigned_to=user)


def test_disabled_field(three_clients, client, settings):
settings.ADVANCED_FILTERS_DISABLE_FOR_FIELDS = ("email",)
view_url = reverse(
Expand All @@ -98,10 +95,10 @@ def test_database_choices(three_clients, client):
URL_NAME, kwargs=dict(model="customers.Client", field_name="email")
)
response = client.get(view_url)
assert_json(
response.content,
{"results": [dict(id=e.email, text=e.email) for e in three_clients]},
)
result = parse_json(response.content)
data = (dict(id=e.email, text=e.email) for e in three_clients)
sort_func = itemgetter("id")
assert sorted(result["results"], key=sort_func) == sorted(data, key=sort_func)


def test_more_than_max_database_choices(user, client, settings):
Expand Down
72 changes: 72 additions & 0 deletions advanced_filters/tests/integration/test_list_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import re

from django.contrib.auth.models import Permission
from playwright.sync_api import Page, expect
import pytest

TEXT = {
"login_page_title": re.compile(r".*Log in.*"),
"client_page_title": re.compile(r".*Select client to change.*"),
"advanced_filter_button_text": "Advanced Filter",
"advanced_filter_modal_heading": "Create advanced filter",
"advanced_filter_title_label": "Title",
"advanced_filter_add_another_rule_link": "Add another filter",
}
SELECTOR = {
"modal_id": "#advanced_filters",
}

expect.set_options(timeout=2_000)


@pytest.fixture(autouse=True)
def grant_permissions(user):
# grant permission to our admin user
user.user_permissions.add(Permission.objects.get(codename="change_client"))


def authenticate(base_url, page: Page):
page.goto(f"{base_url}/admin/")
# Expect a title "to contain" a substring.
expect(page).to_have_title(TEXT["login_page_title"])
# Expect auth form to have a well known structure
page.get_by_label("Username").fill("user")
page.get_by_label("Password").fill("test")
page.get_by_text("Log in").click()
expect(page.get_by_text("Welcome, user")).not_to_be_empty()


@pytest.mark.only_browser("chromium")
def test_advanced_filter_modal_shown(page: Page, base_url):
# GIVEN a logged in user
authenticate(base_url, page)

# WHEN the user navigates to the list page
page.goto(f"{base_url}/admin/customers/client/")

# THEN the client list page should load
expect(page).to_have_title(TEXT["client_page_title"])
# the page should contain an unordered list with a link to the filter
tools_list = page.get_by_role("listitem").filter(
has_text=TEXT["advanced_filter_button_text"]
)
advanced_filter_link = tools_list.get_by_role(
"link", name=TEXT["advanced_filter_button_text"]
)
expect(advanced_filter_link).to_be_visible()
# when the button is clicked, the modal is displayed
advanced_filter_link.click()
modal = page.locator(SELECTOR["modal_id"])
# the modal contains a heading
expect(
modal.get_by_role("heading", name=TEXT["advanced_filter_modal_heading"])
).to_be_visible()
# and a form with a mandatory title field
expect(modal.get_by_label(TEXT["advanced_filter_title_label"])).to_be_visible()
# TODO: the following assertion demonstrates a bug
# below it, the modal contains a blank "extra" filter, with the required fields
# expect(modal.locator("select[name=form-0-field]")).to_be_visible()
# and a button to add another filter
expect(
modal.get_by_role("link", name=TEXT["advanced_filter_add_another_rule_link"])
).to_be_visible()
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.black]
skip-string-normalization = "true"
5 changes: 5 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ DJANGO_SETTINGS_MODULE=tests.test_project.settings
addopts = --cov=advanced_filters --cov-report=term-missing --doctest-modules
testpaths = advanced_filters
pythonpath = . tests

env =
D:CI=true
D:MOZ_HEADLESS=1
DJANGO_ALLOW_ASYNC_UNSAFE=true
3 changes: 3 additions & 0 deletions test-reqs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ factory-boy==3.3.0
pycodestyle==2.10.0
pytest-django==4.5.2
pytest-cov==4.1.0
pytest-playwright==0.4.4
pytest-env==1.1.3
tzdata==2024.1
13 changes: 5 additions & 8 deletions tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ class Meta:
is_superuser = False

@classmethod
def _prepare(cls, create, **kwargs):
password = kwargs.pop('password', None)
user = super()._prepare(create, **kwargs)
if password:
user.set_password(password)
if create:
user.save()
return user
def _create(cls, model_class, *args, **kwargs):
"""Override the default ``_create`` with our custom call."""
manager = cls._get_manager(model_class)
# avoid ``manager.create(*args, **kwargs)`` = encrypt password
return manager.create_user(*args, **kwargs)


class ClientFactory(factory.django.DjangoModelFactory):
Expand Down
4 changes: 1 addition & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[tox]
envlist =
py{37,py37}-django{22,32}
py{38,39}-django{22,32,40}
pypy38-django22
py310-django{32,40}
Expand All @@ -11,6 +10,7 @@ max-line-length = 120

[testenv]
usedevelop = true
passenv = CI, PLAYWRIGHT_BROWSERS_PATH
deps =
-rtest-reqs.txt
django22: Django>=2.2,<3.0
Expand All @@ -29,11 +29,9 @@ commands =

[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
pypy-3.7: pypy37
pypy-3.8: pypy38

[gh-actions:env]
Expand Down

0 comments on commit 2b2432f

Please sign in to comment.