diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f54b627..3af6a59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,11 @@ jobs: run: | python -m pip install --upgrade pip pip install -e . - pip install ruff pytest pytest-asyncio anyio mypy pytest-cov + pip install ruff pytest pytest-asyncio anyio mypy pytest-cov nest-asyncio playwright pytest-playwright + + - name: Install Playwright browsers + run: | + playwright install - name: Lint with ruff run: | diff --git a/conftest.py b/conftest.py index 4134063..f11920a 100644 --- a/conftest.py +++ b/conftest.py @@ -4,6 +4,7 @@ import threading from unittest.mock import MagicMock +import nest_asyncio import pytest # Mock browser-specific modules before any other imports @@ -11,6 +12,8 @@ sys.modules["pyodide"] = MagicMock() sys.modules["pyodide.ffi"] = MagicMock() +def pytest_configure(): + nest_asyncio.apply() PORT = 8000 diff --git a/index.html b/index.html index 9fcc1b7..120183e 100644 --- a/index.html +++ b/index.html @@ -34,13 +34,14 @@ await pyodide.loadPackage("micropip"); const micropip = pyodide.pyimport("micropip"); - await micropip.install("./dist/imposition-0.1.0-py3-none-any.whl"); + await micropip.install("./dist/imposition-0.1.0-py2.py3-none-any.whl"); const response = await fetch("run_imposition.py"); const pythonScript = await response.text(); try { await pyodide.runPythonAsync(pythonScript); + await pyodide.globals.get("main")(); } catch (e) { console.error("Error running Python script:", e); } diff --git a/pyproject.toml b/pyproject.toml index 2000fb9..77765a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,10 @@ dependencies = [ "pytest-asyncio", "anyio", "mypy", - "pytest-cov" + "pytest-cov", + "playwright", + "pytest-playwright", + "nest-asyncio" ] [tool.hatch.envs.default.scripts] diff --git a/pytest.ini b/pytest.ini index 78c5011..057eccc 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] -asyncio_mode = auto testpaths = tests +norecursedirs = tests/integration diff --git a/run_imposition.py b/run_imposition.py index 35b7a65..2b20d18 100644 --- a/run_imposition.py +++ b/run_imposition.py @@ -1,4 +1,3 @@ -import asyncio import js from pyodide.ffi import JsProxy from imposition.book import Book @@ -6,7 +5,6 @@ from imposition.dom import PyodideDOMAdapter async def main() -> None: - print("Starting Python script") response: JsProxy = await js.pyfetch("test_book.epub") epub_bytes_proxy: JsProxy = await response.bytes() epub_bytes: bytes = epub_bytes_proxy.to_py() @@ -19,6 +17,3 @@ async def main() -> None: rendition.display_toc() rendition.display(book.spine[0]) - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/src/imposition/rendition.py b/src/imposition/rendition.py index a7dfccb..4782b01 100644 --- a/src/imposition/rendition.py +++ b/src/imposition/rendition.py @@ -44,9 +44,9 @@ def __init__(self, book: Book, dom_adapter: DOMAdapter, target_id: str) -> None: def display_toc(self) -> None: """ - Renders the table of contents into the 'toc-container' element. + Renders the table of contents into the 'toc' element. """ - toc_container: DOMElement = self.dom_adapter.get_element_by_id('toc-container') + toc_container: DOMElement = self.dom_adapter.get_element_by_id('toc') toc_container.innerHTML = '' ul: DOMElement = self.dom_adapter.create_element('ul') diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..ad339fe --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,7 @@ +import sys +from unittest.mock import MagicMock + +# Mock browser-specific modules before any other imports +sys.modules["js"] = MagicMock() +sys.modules["pyodide"] = MagicMock() +sys.modules["pyodide.ffi"] = MagicMock() diff --git a/tests/integration/pytest.ini b/tests/integration/pytest.ini new file mode 100644 index 0000000..9709809 --- /dev/null +++ b/tests/integration/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +asyncio_mode = auto +testpaths = . diff --git a/tests/test_integration.py b/tests/integration/test_integration.py similarity index 98% rename from tests/test_integration.py rename to tests/integration/test_integration.py index da303b8..5c2dd1b 100644 --- a/tests/test_integration.py +++ b/tests/integration/test_integration.py @@ -8,7 +8,7 @@ sys.modules["js"] = MagicMock() # Add the 'src' directory to the Python path -project_root = Path(__file__).parent.parent +project_root = Path(__file__).parent.parent.parent sys.path.insert(0, str(project_root / "src")) # Also add the project root to find run_imposition sys.path.insert(0, str(project_root)) diff --git a/tests/screenshots/test_renders_first_chapter_passed.png b/tests/screenshots/test_renders_first_chapter_passed.png index 1d4f28e..565ea32 100644 Binary files a/tests/screenshots/test_renders_first_chapter_passed.png and b/tests/screenshots/test_renders_first_chapter_passed.png differ diff --git a/tests/test_e2e.py b/tests/test_e2e.py new file mode 100644 index 0000000..aa52231 --- /dev/null +++ b/tests/test_e2e.py @@ -0,0 +1,38 @@ +from playwright.sync_api import Page, expect +import os +import pytest + +SCREENSHOT_DIR = "tests/screenshots" + +def test_renders_first_chapter(page: Page, http_server): + os.makedirs(SCREENSHOT_DIR, exist_ok=True) + + # Listen for uncaught exceptions + error_logs = [] + page.on("pageerror", lambda exc: error_logs.append(exc)) + + page.goto(http_server) + + # The viewer div should be present + viewer = page.locator("#viewer") + expect(viewer).to_be_visible() + + # The iframe should be created inside the viewer + iframe = viewer.locator("iframe") + expect(iframe).to_be_visible(timeout=15000) + + # The iframe should contain the cover image + iframe_element = iframe.element_handle() + assert iframe_element is not None + frame = iframe_element.content_frame() + assert frame is not None + expect(frame.locator("img.x-ebookmaker-cover")).to_be_visible() + + # Take a screenshot on success + page.screenshot(path=f"{SCREENSHOT_DIR}/test_renders_first_chapter_passed.png") + + # Assert that there were no uncaught exceptions + if error_logs: + # On failure, take a screenshot + page.screenshot(path=f"{SCREENSHOT_DIR}/test_renders_first_chapter_failed.png") + pytest.fail(f"Uncaught exception in browser console: {error_logs[0]}") diff --git a/tests/test_rendition.py b/tests/test_rendition.py index b1da979..5c6ee0a 100644 --- a/tests/test_rendition.py +++ b/tests/test_rendition.py @@ -45,7 +45,7 @@ def test_display_toc(mock_book, mock_dom_adapter): rendition = Rendition(mock_book, mock_dom_adapter, "viewer") rendition.display_toc() - toc_container = mock_dom_adapter.get_element_by_id("toc-container") + toc_container = mock_dom_adapter.get_element_by_id("toc") assert toc_container.innerHTML == '' assert len(toc_container.children) == 1