File size: 8,368 Bytes
72ba2ce e22090e 82cae90 72ba2ce cff6a0c 82cae90 cff6a0c e22090e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | """The home's lessons map and a lesson round in a real browser (game-home-lesson-rounds 2.1-2.3)."""
import os
from datetime import timedelta
import pytest
playwright = pytest.importorskip("playwright.sync_api")
from mathly2 import clock
from mathly2.bank import load_bank
from mathly2.config import Settings
from mathly2.main import create_app
from mathly2.store import InMemoryStore
from mathly2.tests.serve import offline_formulas, only, running
from mathly2.tests.test_home_browser import KID, KID_ID, PHONE, TEACHER, no_sideways_scroll, sign_in, to_home
SHOTS = os.environ.get("MATHLY_SHOTS") # a folder: save screenshots there to look at the screens
STAMP = "2026-10-05T10:00:00+00:00"
@pytest.fixture(scope="module")
def bank():
return load_bank()
@pytest.fixture
def browser():
with playwright.sync_playwright() as p:
b = p.chromium.launch()
yield b
b.close()
def world(bank, n=4):
lessons = [l["id"] for l in bank.lessons if any(s["unit"] == l["id"] and bank.question_keys(s["id"]) for s in bank.skills)][:n]
store = InMemoryStore()
klass = store.create_class("setup", "A", lessons=lessons, teacher_email=TEACHER)
store.add_member(klass["id"], KID)
small = only(bank, lessons)
return create_app(Settings(), bank=small, store=store), store, small, lessons
def shot(page, name):
if SHOTS:
page.screenshot(path=os.path.join(SHOTS, f"{name}.png"), full_page=True)
def right_index(bank, key):
skill = next(s for s in bank.skills if key in bank.question_keys(s["id"]))
from mathly2.bank import question_key
question = next(q for q in skill["questions"] if question_key(skill["id"], q) == key)
return next(i for i, c in enumerate(question["choices"]) if c.get("correct"))
def test_home_is_a_map_of_the_lessons_with_stars_and_a_play_button(browser, bank):
app, store, small, lessons = world(bank)
first = [s["id"] for s in small.skills if s["unit"] == lessons[0]]
for s in first:
store.save_progress(KID_ID, s, started_at=STAMP, mastered_at=STAMP) # lesson 1 perfect
store.set_lesson_dates({lessons[2]: clock.school_day() - timedelta(days=3)}) # lesson 3 behind
with running(app) as url:
page = sign_in(browser, url)
to_home(page)
page.wait_for_selector("#lesson-map")
nodes = page.locator(".lnode")
assert nodes.count() == len(lessons)
first, late = page.locator(f".lnode[data-lesson='{lessons[0]}']"), page.locator(f".lnode[data-lesson='{lessons[2]}']")
assert first.get_attribute("data-stars") == "3" and "👑" in first.text_content()
assert "⏰" in late.text_content()
assert page.locator("#stars-count").inner_text() == f"⭐ 3/{3 * len(lessons)}"
assert page.locator("#play").get_attribute("href") == f"#/round/{lessons[2]}" # the behind lesson comes first
assert page.locator(".lnode.next").get_attribute("data-lesson") == lessons[2]
assert page.locator(".tabs a").nth(1).inner_text() == "העמקה" and page.locator("#behind-card").count() == 0
no_sideways_scroll(page)
shot(page, "home")
assert page.errors == []
def test_a_round_from_the_map_gives_feedback_and_ends_with_stars(browser, bank):
app, store, small, lessons = world(bank, 2)
with running(app) as url:
page = sign_in(browser, url)
to_home(page)
page.click("#play")
page.wait_for_selector(".round-layer #ex-q")
total = int(page.locator("#rd-count").inner_text().split()[-1])
shot(page, "round-question")
for i in range(total):
key = page.locator("#ex-q").get_attribute("data-key")
pick = right_index(small, key) if i < total - 1 else None
if pick is None: # the last one wrong
page.locator(f".ex-choice:not([data-i='{right_index(small, key)}'])").first.click()
page.wait_for_selector(".ex-bad")
shot(page, "round-wrong")
else:
page.click(f".ex-choice[data-i='{pick}']")
page.wait_for_selector(".ex-ok")
page.click("#rd-next")
if i < total - 1:
page.wait_for_function(f"document.querySelector('#rd-count').textContent.includes('{i + 2} מתוך')")
page.wait_for_selector("#rd-result")
assert page.locator("#rd-score").inner_text().replace("\n", " ").startswith(f"{total - 1}")
assert page.locator(".rd-bigstars span.on").count() == 2 # 9 of 10: two stars
shot(page, "round-result")
page.click("#rd-result [data-close]")
page.wait_for_selector(f".lnode[data-lesson='{lessons[0]}'][data-stars='2']") # the map refreshes with the new stars
assert page.evaluate("location.hash") == "#/subject/math"
assert page.errors == []
def test_a_link_to_a_round_opens_the_home_under_it(browser, bank):
app, _, _, lessons = world(bank, 2)
with running(app) as url:
page = sign_in(browser, url, f"#/round/{lessons[1]}")
page.wait_for_selector(".round-layer #ex-q")
assert page.locator("#lesson-map").count() == 1
page.click(".mx-x")
page.wait_for_function("!document.querySelector('.round-layer')")
assert page.errors == []
def test_home_is_one_screen_with_every_lesson_a_test_and_an_assignment(browser, bank):
lessons = [l["id"] for l in bank.lessons]
store = InMemoryStore()
klass = store.create_class("setup", "A", lessons=lessons, teacher_email=TEACHER)
store.add_member(klass["id"], KID)
store.create_test(klass["id"], clock.school_day() + timedelta(days=9), lessons[:2], TEACHER, created_at=clock.now() - timedelta(days=20))
store.create_assignment(klass["id"], "דף", clock.school_day() + timedelta(days=3), TEACHER, "pages", pages=["1"])
with running(create_app(Settings(), bank=bank, store=store)) as url:
page = sign_in(browser, url)
to_home(page)
page.wait_for_selector("#lesson-map")
page.wait_for_selector("#assignments")
assert page.locator(".lnode").count() == len(lessons) and page.locator("#nearest-test #home-sim").count() == 1
assert page.evaluate("document.documentElement.scrollHeight <= window.innerHeight") # nothing to scroll
no_sideways_scroll(page)
shot(page, "home-one-screen")
assert page.errors == []
def test_home_shows_the_current_lesson_and_folds_the_past_and_the_coming_ones(browser, bank):
app, store, small, lessons = world(bank, 5)
for s in (s["id"] for s in small.skills if s["unit"] == lessons[0]):
store.save_progress(KID_ID, s, started_at=STAMP, mastered_at=STAMP) # lesson 1 done
today = clock.school_day()
store.set_lesson_dates({lessons[0]: today - timedelta(days=14), lessons[1]: today - timedelta(days=7), # 2 is behind
lessons[2]: today + timedelta(days=2), lessons[3]: today + timedelta(days=9)}) # 5 has no date
with running(app) as url:
page = sign_in(browser, url)
to_home(page)
page.wait_for_selector("#lesson-now")
assert page.locator("#lesson-now .lnode").get_attribute("data-lesson") == lessons[2]
assert "השיעור הנוכחי" in page.inner_text("#lesson-now")
past = page.locator("#lessons-past")
assert [n.get_attribute("data-lesson") for n in past.locator(".lnode").all()] == lessons[:2]
assert "שיעורים שעברו (2)" in past.locator("summary").inner_text() and "⏰ 1 בפיגור" in past.locator("summary").inner_text()
assert not past.locator(".lnode").first.is_visible() # folded until opened
past.locator("summary").click()
assert past.locator(".lnode").first.is_visible()
later = page.locator("#lessons-later")
assert [n.get_attribute("data-lesson") for n in later.locator(".lnode").all()] == lessons[3:5]
assert page.locator("#play").get_attribute("href") == f"#/round/{lessons[1]}" # the behind lesson is still played first
no_sideways_scroll(page)
shot(page, "home-by-date")
assert page.errors == []
|