Download mathly2/tests/test_lesson_map_browser.py from wzender/mathly: direct link, hf CLI and curl.
- Browser
- Download file 8.37 kB
-
https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/tests/test_lesson_map_browser.py
- Command line
-
hf download hf://spaces/wzender/mathly/mathly2/tests/test_lesson_map_browser.py
-
curl -L -o test_lesson_map_browser.py https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/tests/test_lesson_map_browser.py
8.37 kB
| """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" | |
| def bank(): | |
| return load_bank() | |
| 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 == [] | |