@@ -579,7 +579,7 @@ def set(
579579 The timeout for the action. Defaults to `None`.
580580 """
581581 if not isinstance (selected , str ):
582- raise TypeError ("`selected` must be a string" )
582+ raise TypeError ("`selected= ` must be a string" )
583583
584584 # Only need to set.
585585 # The Browser will _unset_ the previously selected radio button
@@ -778,10 +778,10 @@ def set(
778778 """
779779 # Having an arr of size 0 is allowed. Will uncheck everything
780780 if not isinstance (selected , list ):
781- raise TypeError ("`selected` must be a list or tuple" )
781+ raise TypeError ("`selected= ` must be a list or tuple" )
782782 for item in selected :
783783 if not isinstance (item , str ):
784- raise TypeError ("`selected` must be a list of strings" )
784+ raise TypeError ("`selected= ` must be a list of strings" )
785785
786786 # Make sure the selected items exist
787787 # Similar to `self.expect_choices(choices = selected)`, but with
@@ -1200,62 +1200,126 @@ def set(
12001200 timeout
12011201 The maximum time to wait for the selection to be set. Defaults to `None`.
12021202 """
1203- if isinstance (selected , str ):
1204- selected = [selected ]
1205- # Open the dropdown
1206- self ._loc_events .click ()
12071203
1208- # Sift through the selected items
1209- # From left to right, we will remove ordered items that are not in the ordered `selected`
1210- # If any selected items are not in the current selection, we will add them at the end
1211- i = 0
1212- while i < self ._loc_events .locator ("> .item" ).count ():
1213- item_loc = self ._loc_events .locator ("> .item" ).nth (i )
1214- item_data_value = item_loc .get_attribute ("data-value" )
1204+ def click_item (data_value : str , error_str : str ) -> None :
1205+ """
1206+ Clicks the item in the dropdown by its `data-value` attribute.
1207+ """
1208+ if not isinstance (data_value , str ):
1209+ raise TypeError (error_str )
1210+
1211+ # Wait for the item to exist
1212+ playwright_expect (
1213+ self ._loc_selectize .locator (f"[data-value='{ data_value } ']" )
1214+ ).to_have_count (1 , timeout = timeout )
1215+ # Click the item
1216+ self ._loc_selectize .locator (f"[data-value='{ data_value } ']" ).click (
1217+ timeout = timeout
1218+ )
1219+
1220+ # Make sure the selectize exists
1221+ playwright_expect (self ._loc_events ).to_have_count (1 , timeout = timeout )
1222+
1223+ if self .loc .get_attribute ("multiple" ) is None :
1224+ # Single element selectize
1225+ if isinstance (selected , list ):
1226+ if len (selected ) != 1 :
1227+ raise ValueError (
1228+ "Expected a `str` value (or a list of a single `str` value) when setting a single-select input."
1229+ )
1230+ selected = selected [0 ]
1231+
1232+ # Open the dropdown
1233+ self ._loc_events .click (timeout = timeout )
1234+
1235+ try :
1236+ # Click the item (which closes the dropdown)
1237+ click_item (selected , "`selected=` value must be a `str`" )
1238+ finally :
1239+ # Be sure to close the dropdown
1240+ # (While this is not necessary on a sucessful `set()`, it is cleaner
1241+ # than a catch all except)
1242+ self ._loc_events .press ("Escape" , timeout = timeout )
1243+
1244+ else :
1245+ # Multiple element selectize
12151246
12161247 def delete_item (item_loc : Locator ) -> None :
12171248 """
12181249 Deletes the item by clicking on it and pressing the Delete key.
12191250 """
1251+
12201252 item_loc .click ()
12211253 self .page .keyboard .press ("Delete" )
12221254
1223- # If the item has no data-value, remove it
1224- if item_data_value is None :
1225- delete_item (item_loc )
1226- continue
1227-
1228- # If there are more items than selected, we need to remove the extra ones
1229- if i >= len (selected ):
1230- delete_item (item_loc )
1231- continue
1232-
1233- selected_data_value = selected [i ]
1234-
1235- # If the item is not the next `selected` value, remove it
1236- if item_data_value != selected_data_value :
1237- delete_item (item_loc )
1238- continue
1239-
1240- # The item is the next `selected` value
1241- # Increment the index!
1242- i += 1
1243-
1244- # Add the remaining items
1245- if i < len (selected ):
1246- for data_value in selected [i :]:
1247- # Click on the item in the dropdown to select it
1248- self ._loc_selectize .locator (f"[data-value='{ data_value } ']" ).click (
1249- timeout = timeout
1255+ if isinstance (selected , str ):
1256+ selected = [selected ]
1257+ if not isinstance (selected , list ):
1258+ raise TypeError (
1259+ "`selected=` must be a single `str` value or a list of `str` values when setting a multiple-select input"
12501260 )
12511261
1252- # Close the dropdown
1253- self ._loc_events .press ("Escape" )
1262+ # Open the dropdown
1263+ self ._loc_events .click ()
1264+
1265+ try :
1266+ # Sift through the selected items
1267+ # From left to right, we will remove ordered items that are not in the
1268+ # ordered `selected`
1269+ # If any selected items are not in the current selection, we will add
1270+ # them at the end
1271+
1272+ # All state transitions examples have an end goal of
1273+ # A,B,C,D,E
1274+ #
1275+ # Items wrapped in `[]` are the item of interest at position `i`
1276+ # Ex: `Z`,i=3 in A,B,C,[Z],E
1277+
1278+ i = 0
1279+ while i < self ._loc_events .locator ("> .item" ).count ():
1280+ item_loc = self ._loc_events .locator ("> .item" ).nth (i )
1281+ item_data_value = item_loc .get_attribute ("data-value" )
1282+
1283+ # If the item has no data-value, remove it
1284+ # Transition: A,B,C,[?],D,E -> A,B,C,[D],E
1285+ if item_data_value is None :
1286+ delete_item (item_loc )
1287+ continue
1288+
1289+ # If there are more items than selected, remove the extras
1290+ # Transition: A,B,C,D,E,[Z] -> A,B,C,D,E,[]
1291+ if i >= len (selected ):
1292+ delete_item (item_loc )
1293+ continue
1294+
1295+ selected_data_value = selected [i ]
1296+
1297+ # If the item is not the next `selected` value, remove it
1298+ # Transition: A,B,[Z],C,D,E -> A,B,[C],D,E
1299+ if item_data_value != selected_data_value :
1300+ delete_item (item_loc )
1301+ continue
1302+
1303+ # The item is the next `selected` value
1304+ # Increment the index! (No need to remove it and add it back)
1305+ # A,B,[C],D,E -> A,B,C,[D],E
1306+ i += 1
1307+
1308+ # Add the remaining items
1309+ # A,B,[] -> A,B,C,D,E
1310+ if i < len (selected ):
1311+ for data_value in selected [i :]:
1312+ click_item (
1313+ data_value , f"`selected[{ i } ]=` value must be a `str`"
1314+ )
1315+
1316+ finally :
1317+ # Be sure to close the dropdown
1318+ self ._loc_events .press ("Escape" , timeout = timeout )
12541319 return
12551320
12561321 def expect_choices (
12571322 self ,
1258- # TODO-future; support patterns?
12591323 choices : ListPatternOrStr ,
12601324 * ,
12611325 timeout : Timeout = None ,
0 commit comments