Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add framework for UI testing via bs4 #62

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ repos:
rev: v0.5.5
hooks:
- id: ruff
args: [ "--fix", "--exit-non-zero-on-fix" ]
args: [--exit-non-zero-on-fix]
files: ^tin/.*
name: ruff lint
- id: ruff-format
Expand Down
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ furo = "*"
myst-parser = "*"
sphinx-copybutton = "*"
sphinxcontrib-django = "*"
beautifulsoup4 = "~=4.12"
lxml = "~=5.2"

[ci]
pytest-cov = "*"
Expand Down
150 changes: 149 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/source/reference_index/tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Tests
~tests.utils
~tests.assertions
~tests.fixtures
~tests.ui_testing
8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ DJANGO_SETTINGS_MODULE = "tin.settings"
python_files = "tests.py test_*.py"
norecursedirs = ["media", "migrations", "sandboxing"]
testpaths = "tin"
addopts = "--doctest-modules tin --import-mode=importlib -n 8"
addopts = "--doctest-modules tin --import-mode=importlib -n auto"
doctest_optionflags = "NORMALIZE_WHITESPACE NUMBER"
filterwarnings = [
"error",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to pytest's bad naming, this is a bit confusing, but the error means to transform all warnings into exceptions. I'm not sure if this is wanted (or needed) but it should help during migrations between e.g. Django 4.2 - Django 5.2

'ignore:.*Tin is using the dummy sandboxing module. This is insecure.:',
"ignore::DeprecationWarning:twisted.*:",
]

[tool.coverage.run]
branch = true
Expand Down Expand Up @@ -82,6 +87,7 @@ extend-exclude = [
"migrations",
]

fix = true
show-fixes = false

line-length = 100
Expand Down
76 changes: 76 additions & 0 deletions tin/apps/assignments/tests/test_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from __future__ import annotations

import pytest
from django.urls import reverse

from tin.tests import Html, login

from .. import views


@login("student")
@pytest.mark.parametrize(
("course_perm", "is_archived", "is_visible"),
(
("-", False, True),
("r", False, True),
("w", False, True),
("-", True, False),
("r", True, False),
("w", True, True),
),
)
def test_can_submit_assignment(
client,
course,
assignment,
course_perm: str,
is_archived: bool,
is_visible: bool,
):
course.archived = is_archived
course.permission = course_perm
course.save()
response = client.get(
reverse("assignments:show", args=[assignment.id]),
)
html = Html.from_response(response)
assert html.has_button("Submit") is is_visible


@pytest.mark.parametrize(
("user", "is_visible"),
(
("student", False),
("teacher", True),
),
)
def test_can_create_assignment(rf, course, student, teacher, user, is_visible):
user_map = {
"student": student,
"teacher": teacher,
}
request = rf.get(
reverse("assignments:add", args=[course.id]),
)
request.user = user_map[user]
response = views.create_view(request, course.id)
html = Html.from_response(response)
assert html.has_button("Create") is is_visible


@login("student")
def test_can_submit_assignment_from_form(client, assignment):
response = client.get(
reverse("assignments:submit", args=[assignment.id]),
)
html = Html.from_response(response)
# no grader has been added yet
assert not html.has_button("Submit")
assignment.save_grader_file("print('Instanced Rendering OP')")

response = client.get(
reverse("assignments:submit", args=[assignment.id]),
)
html = Html.from_response(response)
assert html.has_button("Submit")
1 change: 1 addition & 0 deletions tin/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations

from .assertions import *
from .ui_testing import *
from .utils import *
61 changes: 61 additions & 0 deletions tin/tests/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from __future__ import annotations

import pytest

from .ui_testing import Html


def test_html_has_button_no_href():
raw_html = """
<button>Hi!</button>
<input type="submit" value="Blame the compiler">
"""
html = Html(raw_html)
# with no arguments it should raise a ValueError
with pytest.raises(ValueError, match="At least one of text or href must be provided"):
html.has_button()
assert html.has_button("Hi!")
# has button is case insensitive
assert html.has_button("hi!")
assert html.has_button("Blame the compiler")

raw_html = """
<buttn></buttn>
<input type="list" value="Blame the compiler">
"""
html = Html(raw_html)
assert not html.has_button("Blame the compiler")


def test_html_has_button_with_href():
raw_html = """
<button><a href="https://example.com">Hi!</a></button>
<input type="submit" value="Blame the compiler">
<a href='https://google.com'><button>Google</button></a>
<a class="bob tin-btn" href="https://example.com">Bob</a>
"""
html = Html(raw_html)
assert html.has_button(href="https://example.com")
assert not html.has_button(href="https://example.org")
assert html.has_button("Google", href="https://google.com")
# should also search for <a class="tin-btn"
assert html.has_button("Bob", href="https://example.com")
raw_html = """
<button>Hi!</button>
<input type="submit" value="Blame the compiler">
"""
html = Html(raw_html)
assert not html.has_button(href="https://example.com")


def test_has_text():
raw_html = '<p class="insideTag">This is a really long sentence</p>'
html = Html(raw_html)

# it should be case insensitive by default
assert html.has_text("REALLY long sentence")

assert not html.has_text("REALLY long sentence", case_sensitive=True)
assert html.has_text("really long sentence", case_sensitive=True)

assert not html.has_text("insideTag")
Loading
Loading