# retoor <retoor@molodetz.nl>
import requests
from tests.conftest import BASE_URL
JSON = {"Accept": "application/json"}
CHOICE = {
"kind": "single_choice",
"prompt": "Which journal mode allows concurrent readers?",
"points": 2,
"options": "DELETE\nWAL",
"correct_indexes": "1",
}
NUMERIC = {
"kind": "numeric",
"prompt": "How many bytes are in a kibibyte?",
"points": 1,
"numeric_value": "1024",
"numeric_tolerance": "0",
}
DIALOG_CONFIRM = ".dialog-confirm"
_counter = [0]
def _author_quiz(title, questions=(CHOICE, NUMERIC), **settings):
_counter[0] += 1
name = f"play{abs(hash(title)) % 10**8}{_counter[0]}"
session = requests.Session()
session.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
},
allow_redirects=True,
)
payload = {"title": title, "description": "d", **settings}
slug = session.post(f"{BASE_URL}/quizzes/create", data=payload, headers=JSON).json()["data"][
"slug"
]
for question in questions:
session.post(f"{BASE_URL}/quizzes/{slug}/questions", data=question, headers=JSON)
session.post(f"{BASE_URL}/quizzes/{slug}/publish", headers=JSON)
return slug
def _start(page, slug):
page.goto(f"{BASE_URL}/quizzes/{slug}", wait_until="domcontentloaded")
page.locator(".quiz-detail-actions button:has-text('Start quiz')").click()
page.wait_for_url("**/attempts/**", wait_until="domcontentloaded")
page.locator("dp-quiz-player").wait_for(state="visible")
def test_starting_opens_the_player(alice):
page, _ = alice
slug = _author_quiz("Player opens quiz")
_start(page, slug)
assert page.locator(".quiz-question").count() == 2
page.locator(".quiz-hud").wait_for(state="visible")
def test_the_player_renders_a_real_form_per_question(alice):
page, _ = alice
slug = _author_quiz("Player forms quiz")
_start(page, slug)
forms = page.locator("form[data-quiz-answer-form]")
assert forms.count() == 2
assert forms.first.get_attribute("method").lower() == "post"
def test_answering_a_choice_shows_the_grade_in_place(alice):
page, _ = alice
slug = _author_quiz("Player grade quiz")
_start(page, slug)
question = page.locator(".quiz-question-single_choice").first
question.locator("input[type='radio']").last.check()
question.locator("button:has-text('Submit answer')").click()
question.locator(".quiz-grade-correct").wait_for(state="visible")
assert "Correct" in question.locator(".quiz-grade-verdict").inner_text()
def test_a_wrong_answer_is_reported(alice):
page, _ = alice
slug = _author_quiz("Player wrong quiz")
_start(page, slug)
question = page.locator(".quiz-question-single_choice").first
question.locator("input[type='radio']").first.check()
question.locator("button:has-text('Submit answer')").click()
question.locator(".quiz-grade-wrong").wait_for(state="visible")
def test_the_hud_updates_after_an_answer(alice):
page, _ = alice
slug = _author_quiz("Player hud quiz")
_start(page, slug)
before = page.locator("[data-quiz-progress]").inner_text()
question = page.locator(".quiz-question-single_choice").first
question.locator("input[type='radio']").last.check()
question.locator("button:has-text('Submit answer')").click()
question.locator(".quiz-grade").wait_for(state="visible")
page.wait_for_function(
"before => document.querySelector('[data-quiz-progress]').textContent !== before",
arg=before,
)
assert "1 / 2 answered" in page.locator("[data-quiz-progress]").inner_text()
assert "2 / 3 points" in page.locator("[data-quiz-score]").inner_text()
def test_an_answered_question_cannot_be_resubmitted(alice):
page, _ = alice
slug = _author_quiz("Player once quiz")
_start(page, slug)
question = page.locator(".quiz-question-single_choice").first
question.locator("input[type='radio']").last.check()
question.locator("button:has-text('Submit answer')").click()
question.locator(".quiz-grade").wait_for(state="visible")
assert question.locator("button:has-text('Submit answer')").count() == 0
assert question.locator("input[type='radio']").first.is_disabled()
def test_a_numeric_question_takes_a_typed_answer(alice):
page, _ = alice
slug = _author_quiz("Player numeric quiz")
_start(page, slug)
question = page.locator(".quiz-question-numeric").first
question.locator("input[name='answer_text']").fill("1024")
question.locator("button:has-text('Submit answer')").click()
question.locator(".quiz-grade-correct").wait_for(state="visible")
def test_the_timer_is_rendered_for_a_limited_quiz(alice):
page, _ = alice
slug = _author_quiz("Player timer quiz", time_limit_seconds="600")
_start(page, slug)
timer = page.locator("[data-quiz-timer]")
timer.wait_for(state="visible")
assert timer.inner_text().strip()
def test_no_timer_is_rendered_without_a_limit(alice):
page, _ = alice
slug = _author_quiz("Player untimed quiz")
_start(page, slug)
page.locator(".quiz-hud").wait_for(state="visible")
assert page.locator("[data-quiz-timer]").count() == 0
def test_the_answer_key_is_not_in_the_page_source(alice):
page, _ = alice
slug = _author_quiz("Player hidden key quiz", questions=(NUMERIC,), reveal_answers="0")
_start(page, slug)
assert "1024" not in page.content()
def test_reloading_resumes_the_same_attempt(alice):
page, _ = alice
slug = _author_quiz("Player resume quiz")
_start(page, slug)
attempt_url = page.url
question = page.locator(".quiz-question-single_choice").first
question.locator("input[type='radio']").last.check()
question.locator("button:has-text('Submit answer')").click()
question.locator(".quiz-grade").wait_for(state="visible")
page.goto(f"{BASE_URL}/quizzes/{slug}", wait_until="domcontentloaded")
page.locator(".quiz-detail-actions a:has-text('Resume attempt')").click()
page.wait_for_url("**/attempts/**", wait_until="domcontentloaded")
assert page.url == attempt_url
assert page.locator(".quiz-question-answered").count() == 1
def test_finishing_reaches_the_results_page(alice):
page, _ = alice
slug = _author_quiz("Player finish quiz", pass_percent="50")
_start(page, slug)
for question in (".quiz-question-single_choice", ".quiz-question-numeric"):
node = page.locator(question).first
if "numeric" in question:
node.locator("input[name='answer_text']").fill("1024")
else:
node.locator("input[type='radio']").last.check()
node.locator("button:has-text('Submit answer')").click()
node.locator(".quiz-grade").wait_for(state="visible")
page.locator("form[data-quiz-finish-form] button").click()
page.locator(DIALOG_CONFIRM).wait_for(state="visible")
page.locator(DIALOG_CONFIRM).click()
page.wait_for_url("**/results", wait_until="domcontentloaded")
page.locator(".quiz-score-card").wait_for(state="visible")
assert "100" in page.locator(".quiz-score-percent").inner_text()
def test_the_results_page_shows_the_pass_verdict(alice):
page, _ = alice
slug = _author_quiz("Player verdict quiz", questions=(NUMERIC,), pass_percent="50")
_start(page, slug)
node = page.locator(".quiz-question-numeric").first
node.locator("input[name='answer_text']").fill("1024")
node.locator("button:has-text('Submit answer')").click()
node.locator(".quiz-grade").wait_for(state="visible")
page.locator("form[data-quiz-finish-form] button").click()
page.locator(DIALOG_CONFIRM).wait_for(state="visible")
page.locator(DIALOG_CONFIRM).click()
page.wait_for_url("**/results", wait_until="domcontentloaded")
page.locator(".quiz-verdict-pass").wait_for(state="visible")
def test_the_results_page_reviews_every_answer(alice):
page, _ = alice
slug = _author_quiz("Player review quiz", allow_review="1")
_start(page, slug)
node = page.locator(".quiz-question-single_choice").first
node.locator("input[type='radio']").last.check()
node.locator("button:has-text('Submit answer')").click()
node.locator(".quiz-grade").wait_for(state="visible")
page.locator("form[data-quiz-finish-form] button").click()
page.locator(DIALOG_CONFIRM).wait_for(state="visible")
page.locator(DIALOG_CONFIRM).click()
page.wait_for_url("**/results", wait_until="domcontentloaded")
page.locator(".quiz-review").wait_for(state="visible")
assert page.locator(".quiz-review-item").count() == 2
def test_the_review_is_hidden_when_the_author_disabled_it(alice):
page, _ = alice
slug = _author_quiz("Player no review quiz", questions=(NUMERIC,), allow_review="0")
_start(page, slug)
node = page.locator(".quiz-question-numeric").first
node.locator("input[name='answer_text']").fill("1024")
node.locator("button:has-text('Submit answer')").click()
node.locator(".quiz-grade").wait_for(state="visible")
page.locator("form[data-quiz-finish-form] button").click()
page.locator(DIALOG_CONFIRM).wait_for(state="visible")
page.locator(DIALOG_CONFIRM).click()
page.wait_for_url("**/results", wait_until="domcontentloaded")
page.locator(".quiz-review-disabled").wait_for(state="visible")
def test_the_results_page_offers_a_replay(alice):
page, _ = alice
slug = _author_quiz("Player replay quiz", questions=(NUMERIC,))
_start(page, slug)
node = page.locator(".quiz-question-numeric").first
node.locator("input[name='answer_text']").fill("1024")
node.locator("button:has-text('Submit answer')").click()
node.locator(".quiz-grade").wait_for(state="visible")
page.locator("form[data-quiz-finish-form] button").click()
page.locator(DIALOG_CONFIRM).wait_for(state="visible")
page.locator(DIALOG_CONFIRM).click()
page.wait_for_url("**/results", wait_until="domcontentloaded")
page.locator(".quiz-results-actions button:has-text('Play again')").click()
page.wait_for_url("**/attempts/**", wait_until="domcontentloaded")
page.locator("dp-quiz-player").wait_for(state="visible")
def test_the_hub_shows_the_completed_badge_afterwards(alice):
page, _ = alice
slug = _author_quiz("Player badge quiz", questions=(NUMERIC,))
_start(page, slug)
node = page.locator(".quiz-question-numeric").first
node.locator("input[name='answer_text']").fill("1024")
node.locator("button:has-text('Submit answer')").click()
node.locator(".quiz-grade").wait_for(state="visible")
page.locator("form[data-quiz-finish-form] button").click()
page.locator(DIALOG_CONFIRM).wait_for(state="visible")
page.locator(DIALOG_CONFIRM).click()
page.wait_for_url("**/results", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/quizzes?filter=done", wait_until="domcontentloaded")
card = page.locator(f".quiz-card:has(a[href='/quizzes/{slug}'])").first
card.wait_for(state="visible")
assert card.locator(".quiz-state-done").count() == 1