22
33
44import re
5- import time
65from typing import Any , Callable
76
87import pytest
98from conftest import ShinyAppProc , create_example_fixture , expect_to_change
109from controls import InputSelectize , InputSwitch
1110from playwright .sync_api import Locator , Page , expect
1211
12+ RERUNS = 3
13+
1314data_frame_app = create_example_fixture ("dataframe" )
1415
1516
@@ -37,7 +38,7 @@ def do():
3738 return do
3839
3940
40- @pytest .mark .flaky
41+ @pytest .mark .flaky ( reruns = RERUNS )
4142def test_grid_mode (
4243 page : Page , data_frame_app : ShinyAppProc , grid : Locator , grid_container : Locator
4344):
@@ -51,23 +52,23 @@ def test_grid_mode(
5152 expect (grid_container ).to_have_class (re .compile (r"\bshiny-data-grid-grid\b" ))
5253
5354
54- @pytest .mark .flaky
55+ @pytest .mark .flaky ( reruns = RERUNS )
5556def test_summary_navigation (
5657 page : Page , data_frame_app : ShinyAppProc , grid_container : Locator , summary : Locator
5758):
5859 page .goto (data_frame_app .url )
5960
6061 # Check that summary responds to navigation
61- expect (summary ).to_have_text (" Viewing rows 1 through 10 of 20" )
62+ expect (summary ).to_have_text (re . compile ( "^ Viewing rows 1 through \\ d+ of 20$" ) )
6263 # Put focus in the table and hit End keystroke
6364 grid_container .locator ("tbody tr:first-child td:first-child" ).click ()
6465 with expect_to_change (lambda : summary .inner_text ()):
6566 page .keyboard .press ("End" )
6667 # Ensure that summary updated
67- expect (summary ).to_have_text (" Viewing rows 11 through 20 of 20" )
68+ expect (summary ).to_have_text (re . compile ( "^ Viewing rows \\ d+ through 20 of 20$" ) )
6869
6970
70- @pytest .mark .flaky
71+ @pytest .mark .flaky ( reruns = RERUNS )
7172def test_full_width (page : Page , data_frame_app : ShinyAppProc , grid_container : Locator ):
7273 page .goto (data_frame_app .url )
7374
@@ -87,7 +88,7 @@ def get_width() -> float:
8788 InputSwitch (page , "fullwidth" ).toggle ()
8889
8990
90- @pytest .mark .flaky
91+ @pytest .mark .flaky ( reruns = RERUNS )
9192def test_table_switch (
9293 page : Page ,
9394 data_frame_app : ShinyAppProc ,
@@ -107,17 +108,18 @@ def test_table_switch(
107108 expect (grid_container ).to_have_class (re .compile (r"\bshiny-data-grid-table\b" ))
108109
109110 # Switching modes resets scroll
110- expect (summary ).to_have_text (" Viewing rows 1 through 10 of 20" )
111+ expect (summary ).to_have_text (re . compile ( "^ Viewing rows 1 through \\ d+ of 20$" ) )
111112
112113 scroll_to_end ()
113- expect (summary ).to_have_text (" Viewing rows 11 through 20 of 20" )
114+ expect (summary ).to_have_text (re . compile ( "^ Viewing rows \\ d+ through 20 of 20$" ) )
114115
115116 # Switch datasets to much longer one
116117 select_dataset .set ("diamonds" )
117- expect (summary ).to_have_text ("Viewing rows 1 through 10 of 53940" )
118+ select_dataset .expect .to_have_value ("diamonds" )
119+ expect (summary ).to_have_text (re .compile ("^Viewing rows 1 through \\ d+ of 53940$" ))
118120
119121
120- @pytest .mark .flaky
122+ @pytest .mark .flaky ( reruns = RERUNS )
121123def test_sort (
122124 page : Page ,
123125 data_frame_app : ShinyAppProc ,
@@ -126,6 +128,7 @@ def test_sort(
126128 page .goto (data_frame_app .url )
127129 select_dataset = InputSelectize (page , "dataset" )
128130 select_dataset .set ("diamonds" )
131+ select_dataset .expect .to_have_value ("diamonds" )
129132
130133 # Test sorting
131134 header_clarity = grid_container .locator ("tr:first-child th:nth-child(4)" )
@@ -143,7 +146,7 @@ def test_sort(
143146 expect (first_cell_depth ).to_have_text ("67.6" )
144147
145148
146- @pytest .mark .flaky
149+ @pytest .mark .flaky ( reruns = RERUNS )
147150def test_multi_selection (
148151 page : Page , data_frame_app : ShinyAppProc , grid_container : Locator , snapshot : Any
149152):
@@ -173,7 +176,7 @@ def detail_text():
173176 assert detail_text () == snapshot
174177
175178
176- @pytest .mark .flaky
179+ @pytest .mark .flaky ( reruns = RERUNS )
177180def test_single_selection (
178181 page : Page , data_frame_app : ShinyAppProc , grid_container : Locator , snapshot : Any
179182):
@@ -204,18 +207,125 @@ def detail_text():
204207 assert detail_text () == snapshot
205208
206209
207- def retry_with_timeout (timeout : float = 30 ):
208- def decorator (func : Callable [[], None ]) -> None :
209- def exec () -> None :
210- start = time .time ()
211- while True :
212- try :
213- return func ()
214- except AssertionError as e :
215- if time .time () - start > timeout :
216- raise e
217- time .sleep (0.1 )
210+ def test_filter_grid (
211+ page : Page ,
212+ data_frame_app : ShinyAppProc ,
213+ grid : Locator ,
214+ summary : Locator ,
215+ snapshot : Any ,
216+ ):
217+ page .goto (data_frame_app .url )
218+ _filter_test_impl (page , data_frame_app , grid , summary , snapshot )
219+
220+
221+ def test_filter_table (
222+ page : Page ,
223+ data_frame_app : ShinyAppProc ,
224+ grid : Locator ,
225+ grid_container : Locator ,
226+ summary : Locator ,
227+ snapshot : Any ,
228+ ):
229+ page .goto (data_frame_app .url )
218230
219- exec ()
231+ InputSwitch (page , "gridstyle" ).toggle ()
232+ expect (grid_container ).not_to_have_class (re .compile (r"\bshiny-data-grid-grid\b" ))
233+ expect (grid_container ).to_have_class (re .compile (r"\bshiny-data-grid-table\b" ))
234+
235+ _filter_test_impl (page , data_frame_app , grid , summary , snapshot )
236+
237+
238+ def _filter_test_impl (
239+ page : Page ,
240+ data_frame_app : ShinyAppProc ,
241+ grid : Locator ,
242+ summary : Locator ,
243+ snapshot : Any ,
244+ ):
245+ filters = grid .locator ("tr.filters" )
246+
247+ filter_subidir_min = filters .locator ("> th:nth-child(1) > div > input:nth-child(1)" )
248+ filter_subidir_max = filters .locator ("> th:nth-child(1) > div > input:nth-child(2)" )
249+ filter_attnr = filters .locator ("> th:nth-child(2) > input" )
250+ filter_num1_min = filters .locator ("> th:nth-child(3) > div > input:nth-child(1)" )
251+ filter_num1_max = filters .locator ("> th:nth-child(3) > div > input:nth-child(2)" )
252+
253+ expect (filter_subidir_min ).to_be_visible ()
254+ expect (filter_subidir_max ).to_be_visible ()
255+ expect (filter_attnr ).to_be_visible ()
256+ expect (filter_num1_min ).to_be_visible ()
257+ expect (filter_num1_max ).to_be_visible ()
258+
259+ expect (summary ).to_be_visible ()
260+ expect (summary ).to_have_text (re .compile (" of 20$" ))
261+
262+ # Placeholder text only appears when filter is focused
263+ expect (page .get_by_placeholder ("Min (1)" , exact = True )).not_to_be_attached ()
264+ expect (page .get_by_placeholder ("Max (20)" , exact = True )).not_to_be_attached ()
265+ filter_subidir_min .focus ()
266+ expect (page .get_by_placeholder ("Min (1)" , exact = True )).to_be_attached ()
267+ expect (page .get_by_placeholder ("Max (20)" , exact = True )).to_be_attached ()
268+ filter_subidir_min .blur ()
269+ expect (page .get_by_placeholder ("Min (1)" , exact = True )).not_to_be_attached ()
270+ expect (page .get_by_placeholder ("Max (20)" , exact = True )).not_to_be_attached ()
271+
272+ # Make sure that filtering input results in correct number of rows
273+
274+ # Test only min
275+ filter_subidir_min .fill ("5" )
276+ expect (summary ).to_have_text (re .compile (" of 16$" ))
277+ # Test min and max
278+ filter_subidir_max .fill ("14" )
279+ expect (summary ).to_have_text (re .compile (" of 10$" ))
280+
281+ # When filtering results in all rows being shown, the summary should not be visible
282+ filter_subidir_max .fill ("11" )
283+ expect (summary ).not_to_be_attached ()
284+
285+ # Test only max
286+ filter_subidir_min .fill ("" )
287+ expect (summary ).to_have_text (re .compile (" of 11" ))
288+
289+ filter_subidir_min .clear ()
290+ filter_subidir_max .clear ()
291+
292+ # Try substring search
293+ filter_attnr .fill ("oc" )
294+ expect (summary ).to_have_text (re .compile (" of 10" ))
295+ filter_num1_min .focus ()
296+ # Ensure other columns' filter placeholders show faceted results
297+ expect (page .get_by_placeholder ("Min (5)" , exact = True )).to_be_attached ()
298+ expect (page .get_by_placeholder ("Max (8)" , exact = True )).to_be_attached ()
299+
300+ # Filter down to zero matching rows
301+ filter_attnr .fill ("q" )
302+ # Summary should be gone
303+ expect (summary ).not_to_be_attached ()
304+ filter_num1_min .focus ()
305+ # Placeholders should not have values
306+ expect (page .get_by_placeholder ("Min" , exact = True )).to_be_attached ()
307+ expect (page .get_by_placeholder ("Max" , exact = True )).to_be_attached ()
308+
309+ filter_attnr .clear ()
310+
311+ # Apply multiple filters, make sure we get the correct results
312+ filter_subidir_max .fill ("8" )
313+ filter_num1_min .fill ("4" )
314+ expect (grid .locator ("tbody tr" )).to_have_count (5 )
315+
316+ # Ensure changing dataset resets filters
317+ select_dataset = InputSelectize (page , "dataset" )
318+ select_dataset .set ("attention" )
319+ select_dataset .expect .to_have_value ("attention" )
320+ expect (page .get_by_text ("Unnamed: 0" )).to_be_attached ()
321+ select_dataset .set ("anagrams" )
322+ select_dataset .expect .to_have_value ("anagrams" )
323+ expect (summary ).to_have_text (re .compile (" of 20" ))
324+
325+
326+ def test_filter_disable (page : Page , data_frame_app : ShinyAppProc ):
327+ page .goto (data_frame_app .url )
220328
221- return decorator
329+ expect (page .locator ("tr.filters" )).to_be_attached ()
330+ InputSwitch (page , "filters" ).toggle ()
331+ expect (page .locator ("tr.filters" )).not_to_be_attached ()
0 commit comments