Skip to content

Commit

Permalink
Add E2E tests for accordion and autoresize (#601)
Browse files Browse the repository at this point in the history
Co-authored-by: Barret Schloerke <[email protected]>
  • Loading branch information
karangattu and schloerke authored Jul 21, 2023
1 parent 2d5acbe commit a50c637
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 8 deletions.
146 changes: 138 additions & 8 deletions e2e/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ def expect_locator_values_in_list(
*,
page: Page,
loc_container: Locator,
el_type: str,
el_type: Locator | str,
arr_name: str,
arr: ListPatternOrStr,
is_checked: bool | MISSING_TYPE = MISSING,
Expand All @@ -949,13 +949,20 @@ def expect_locator_values_in_list(

# Make sure the locator has len(uniq_arr) input elements
_MultipleDomItems.assert_arr_is_unique(arr, f"`{arr_name}` must be unique")
is_checked_str = _MultipleDomItems.checked_css_str(is_checked)

item_selector = f"{el_type}{is_checked_str}"
if isinstance(el_type, Locator):
if not isinstance(is_checked, MISSING_TYPE):
raise RuntimeError(
"`is_checked` cannot be specified if `el_type` is a Locator"
)
loc_item = el_type
else:
is_checked_str = _MultipleDomItems.checked_css_str(is_checked)
loc_item = page.locator(f"{el_type}{is_checked_str}")

# If there are no items, then we should not have any elements
if len(arr) == 0:
playwright_expect(loc_container.locator(item_selector)).to_have_count(
playwright_expect(loc_container.locator(el_type)).to_have_count(
0, timeout=timeout
)
return
Expand All @@ -964,7 +971,7 @@ def expect_locator_values_in_list(
# Find all items in set
for item, i in zip(arr, range(len(arr))):
# Get all elements of type
has_locator = page.locator(item_selector)
has_locator = loc_item
# Get the `n`th matching element
has_locator = has_locator.nth(i)
# Make sure that element has the correct attribute value
Expand All @@ -982,7 +989,7 @@ def expect_locator_values_in_list(
# Make sure other items are not in set
# If we know all elements are contained in the container,
# and all elements all unique, then it should have a count of `len(arr)`
loc_inputs = loc_container.locator(item_selector)
loc_inputs = loc_container.locator(loc_item)
try:
playwright_expect(loc_inputs).to_have_count(len(arr), timeout=timeout)
except AssertionError as e:
Expand All @@ -992,14 +999,14 @@ def expect_locator_values_in_list(
playwright_expect(loc_container_orig).to_have_count(1, timeout=timeout)

# Expecting the container to contain {len(arr)} items
playwright_expect(loc_container_orig.locator(item_selector)).to_have_count(
playwright_expect(loc_container_orig.locator(loc_item)).to_have_count(
len(arr), timeout=timeout
)

for item, i in zip(arr, range(len(arr))):
# Expecting item `{i}` to be `{item}`
playwright_expect(
loc_container_orig.locator(item_selector).nth(i)
loc_container_orig.locator(loc_item).nth(i)
).to_have_attribute(key, item, timeout=timeout)

# Could not find the reason why. Raising the original error.
Expand Down Expand Up @@ -2096,6 +2103,7 @@ def expect_value(
*,
timeout: Timeout = None,
) -> None:
"""Note this function will trim value and output text value before comparing them"""
self.expect.to_have_text(value, timeout=timeout)


Expand Down Expand Up @@ -2565,3 +2573,125 @@ def expect_min_height(self, value: StyleValue, *, timeout: Timeout = None) -> No

def expect_height(self, value: StyleValue, *, timeout: Timeout = None) -> None:
expect_to_have_style(self.loc_container, "height", value, timeout=timeout)


### Experimental below


class Accordion(
_WidthLocM,
_InputWithContainer,
):
# *args: AccordionPanel | TagAttrs,
# id: Optional[str] = None,
# open: Optional[bool | str | list[str]] = None,
# multiple: bool = True,
# class_: Optional[str] = None,
# width: Optional[CssUnit] = None,
# height: Optional[CssUnit] = None,
# **kwargs: TagAttrValue,
def __init__(self, page: Page, id: str) -> None:
super().__init__(
page,
id=id,
loc="> div.accordion-item",
loc_container=f"div#{id}.accordion.shiny-bound-input",
)
self.loc_open = self.loc.locator(
# Return self
"xpath=.",
# Simple approach as position is not needed
has=page.locator(
"> div.accordion-collapse.show",
),
)

def expect_height(self, value: StyleValue, *, timeout: Timeout = None) -> None:
expect_to_have_style(self.loc_container, "height", value, timeout=timeout)

def expect_width(self, value: StyleValue, *, timeout: Timeout = None) -> None:
expect_to_have_style(self.loc_container, "width", value, timeout=timeout)

def expect_open(
self,
value: list[PatternOrStr],
*,
timeout: Timeout = None,
) -> None:
_MultipleDomItems.expect_locator_values_in_list(
page=self.page,
loc_container=self.loc_container,
el_type=self.page.locator(
"> div.accordion-item",
has=self.page.locator("> div.accordion-collapse.show"),
),
# el_type="> div.accordion-item:has(> div.accordion-collapse.show)",
arr_name="value",
arr=value,
key="data-value",
timeout=timeout,
)

def expect_panels(
self,
value: list[PatternOrStr],
*,
timeout: Timeout = None,
) -> None:
_MultipleDomItems.expect_locator_values_in_list(
page=self.page,
loc_container=self.loc_container,
el_type="> div.accordion-item",
arr_name="value",
arr=value,
key="data-value",
timeout=timeout,
)

def accordion_panel(
self,
data_value: str,
) -> AccordionPanel:
return AccordionPanel(self.page, self.id, data_value)


class AccordionPanel(
_WidthLocM,
_InputWithContainer,
):
# self,
# *args: TagChild | TagAttrs,
# data_value: str,
# icon: TagChild | None,
# title: TagChild | None,
# id: str | None,
# **kwargs: TagAttrValue,
def __init__(self, page: Page, id: str, data_value: str) -> None:
super().__init__(
page,
id=id,
loc=f"> div.accordion-item[data-value='{data_value}']",
loc_container=f"div#{id}.accordion.shiny-bound-input",
)

self.loc_label = self.loc.locator(
"> .accordion-header > .accordion-button > .accordion-title"
)

self.loc_icon = self.loc.locator(
"> .accordion-header > .accordion-button > .accordion-icon"
)

self.loc_body = self.loc.locator("> .accordion-collapse")

def expect_label(self, value: PatternOrStr, *, timeout: Timeout = None) -> None:
playwright_expect(self.loc_label).to_have_text(value, timeout=timeout)

def expect_body(self, value: PatternOrStr, *, timeout: Timeout = None) -> None:
playwright_expect(self.loc_body).to_have_text(value, timeout=timeout)

def expect_icon(self, value: PatternOrStr, *, timeout: Timeout = None) -> None:
playwright_expect(self.loc_icon).to_have_text(value, timeout=timeout)

def expect_open(self, is_open: bool, *, timeout: Timeout = None) -> None:
_expect_class_value(self.loc_body, "show", is_open, timeout=timeout)
File renamed without changes.
78 changes: 78 additions & 0 deletions e2e/experimental/accordion/test_accordion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from conftest import ShinyAppProc
from controls import Accordion, InputActionButton, OutputTextVerbatim
from playwright.sync_api import Page


def test_accordion(page: Page, local_app: ShinyAppProc) -> None:
page.goto(local_app.url)

acc = Accordion(page, "acc")
acc_panel_A = acc.accordion_panel("Section A")
output_txt_verbatim = OutputTextVerbatim(page, "acc_txt")
alternate_button = InputActionButton(page, "alternate")
open_all_button = InputActionButton(page, "open_all")
close_all_button = InputActionButton(page, "close_all")
toggle_b_button = InputActionButton(page, "toggle_b")
toggle_updates_button = InputActionButton(page, "toggle_updates")
toggle_efg_button = InputActionButton(page, "toggle_efg")
acc.expect_width(None)
acc.expect_height(None)

# initial state - by default only A is open
acc.expect_panels(["Section A", "Section B", "Section C", "Section D"])
output_txt_verbatim.expect_value("input.acc(): ('Section A',)")
acc.expect_open(["Section A"])
acc_panel_A.expect_label("Section A")
acc_panel_A.expect_body("Some narrative for section A")
acc_panel_A.expect_open(True)

alternate_button.click()
acc.expect_open(["Section B", "Section D"])
output_txt_verbatim.expect_value("input.acc(): ('Section B', 'Section D')")

alternate_button.click()
acc.expect_open(["Section A", "Section C"])
output_txt_verbatim.expect_value("input.acc(): ('Section A', 'Section C')")

open_all_button.click()
acc.expect_open(["Section A", "Section B", "Section C", "Section D"])
output_txt_verbatim.expect_value(
"input.acc(): ('Section A', 'Section B', 'Section C', 'Section D')"
)

close_all_button.click()
acc.expect_open([])
output_txt_verbatim.expect_value("input.acc(): None")

toggle_b_button.click()
acc.expect_open(["Section B"])
output_txt_verbatim.expect_value("input.acc(): ('Section B',)")

acc_panel_updated_A = acc.accordion_panel("updated_section_a")
toggle_updates_button.click()
acc_panel_updated_A.expect_label("Updated title")
acc_panel_updated_A.expect_body("Updated body")
acc_panel_updated_A.expect_icon("Look! An icon! -->")

acc.expect_panels(["updated_section_a", "Section B", "Section C", "Section D"])
output_txt_verbatim.expect_value("input.acc(): ('updated_section_a', 'Section B')")

toggle_efg_button.click()
acc.expect_panels(
[
"updated_section_a",
"Section B",
"Section C",
"Section D",
"Section E",
"Section F",
"Section G",
]
)
acc.expect_open(
["updated_section_a", "Section B", "Section E", "Section F", "Section G"]
)
# will be uncommented once https://github.com/rstudio/bslib/issues/565 is fixed
# output_txt_verbatim.expect_value(
# "input.acc(): ('updated_section_a', 'Section B', 'Section E', 'Section F', 'Section G')"
# )
34 changes: 34 additions & 0 deletions e2e/experimental/test_autoresize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from conftest import ShinyAppProc, x_create_doc_example_fixture
from controls import InputTextArea, OutputTextVerbatim
from playwright.sync_api import Locator, Page

app = x_create_doc_example_fixture("input_text_area")

resize_number = 6


def get_box_height(locator: Locator) -> float:
bounding_box = locator.bounding_box()
if bounding_box is not None:
return bounding_box["height"]
else:
return 0


def test_autoresize(page: Page, app: ShinyAppProc) -> None:
page.goto(app.url)

input_area = InputTextArea(page, "caption")
output_txt_verbatim = OutputTextVerbatim(page, "value")
input_area.expect_height(None)
input_area.expect_width(None)
input_area.set("test value")
# use bounding box approach since height is dynamic
initial_height = get_box_height(input_area.loc)
output_txt_verbatim.expect_value("test value")
for _ in range(resize_number):
input_area.loc.press("Enter")
input_area.loc.type("end value")
return_txt = "\n" * resize_number
output_txt_verbatim.expect_value(f"test value{return_txt}end value")
assert get_box_height(input_area.loc) > initial_height

0 comments on commit a50c637

Please sign in to comment.