Skip to content

glue_scroll

django_spire.testing.playwright.components.glue_scroll

GlueScroll

Playwright component for django_spire/glue/scroll/scroll.html -- the v1 glue-backed infinite scroll.

Rows render client-side from a GlueQuerySetProxy, so assertions have to wait for a fetch to land rather than reading server-rendered HTML.

The base template owns only the loading spinner and the empty state; row markup comes entirely from the consumer's scroll_template_item block. Pass the selector matching one row for the page under test:

scroll = GlueScroll(page, row_selector='.row.border-bottom')
scroll.wait_for_rows()
scroll.search('prod')
scroll.wait_for_row_count(1)
Source code in django_spire/testing/playwright/components/glue_scroll.py
def __init__(
    self,
    page: Page,
    row_selector: str,
    root_selector: str | None = None,
) -> None:
    self.page = page
    self.row_selector = row_selector
    self.root_selector = root_selector

page = page instance-attribute

row_selector = row_selector instance-attribute

root_selector = root_selector instance-attribute

root property

rows property

spinner property

Owned by the base template -- shown while hasMore is true.

row_count

Source code in django_spire/testing/playwright/components/glue_scroll.py
def row_count(self) -> int:
    return self.rows.count()

row_texts

Source code in django_spire/testing/playwright/components/glue_scroll.py
def row_texts(self) -> list[str]:
    rows = self.rows
    return [rows.nth(i).inner_text() for i in range(rows.count())]

is_loading

Source code in django_spire/testing/playwright/components/glue_scroll.py
def is_loading(self) -> bool:
    return self.spinner.is_visible()

search

Fill the scroll's search input -- every consumer's scroll_header block renders one input[type="search"] bound to searchQuery.

Source code in django_spire/testing/playwright/components/glue_scroll.py
def search(self, text: str) -> None:
    """Fill the scroll's search input -- every consumer's scroll_header
    block renders one `input[type="search"]` bound to `searchQuery`.
    """
    self.root.locator('input[type="search"]').fill(text)

scroll_to_bottom

Trigger the IntersectionObserver sentinel that calls loadMoreItems().

Source code in django_spire/testing/playwright/components/glue_scroll.py
def scroll_to_bottom(self) -> None:
    """Trigger the IntersectionObserver sentinel that calls loadMoreItems()."""
    self.page.mouse.wheel(0, 100_000)

wait_for_rows

Source code in django_spire/testing/playwright/components/glue_scroll.py
def wait_for_rows(self, timeout: int = 10_000) -> None:
    self.rows.first.wait_for(timeout=timeout)

wait_for_row_count

Source code in django_spire/testing/playwright/components/glue_scroll.py
def wait_for_row_count(self, count: int, timeout: int = 10_000) -> None:
    self.page.wait_for_function(
        '([selector, expected]) => document.querySelectorAll(selector).length === expected',
        arg=[self.row_selector, count],
        timeout=timeout,
    )

wait_for_row_count_to_increase

Source code in django_spire/testing/playwright/components/glue_scroll.py
def wait_for_row_count_to_increase(self, previous: int, timeout: int = 10_000) -> None:
    self.page.wait_for_function(
        '([selector, previous]) => document.querySelectorAll(selector).length > previous',
        arg=[self.row_selector, previous],
        timeout=timeout,
    )