diff --git a/select.go b/select.go new file mode 100644 index 0000000..89a2df6 --- /dev/null +++ b/select.go @@ -0,0 +1,287 @@ +package selenium + +import ( + "fmt" + "strings" +) + +// SelectElement WebElement that is specific to the Select Dropdown +type SelectElement struct { + element WebElement + isMulti bool +} + +// Select Creates a SelectElement +// @param el The initial WebElement +func Select(el WebElement) (se SelectElement, err error) { + se = SelectElement{} + + tagName, err := el.TagName() + if err != nil || strings.ToLower(tagName) != "select" { + err = fmt.Errorf(`element should have been "select" but was "%s"`, tagName) + return + } + + se.element = el + mult, err2 := el.GetAttribute("multiple") + se.isMulti = (err2 != nil && strings.ToLower(mult) != "false") + + return +} + +// GetElement Gets the raw WebElement +func (s SelectElement) GetElement() WebElement { + return s.element +} + +// IsMultiple Whether this select element support selecting multiple options at the same time? This +// is done by checking the value of the "multiple" attribute. +func (s SelectElement) IsMultiple() bool { + return s.isMulti +} + +// GetOptions Returns all of the options of that Select +func (s SelectElement) GetOptions() ([]WebElement, error) { + return s.element.FindElements(ByTagName, "option") +} + +// GetAllSelectedOptions Returns all of the options of that Select that are selected +func (s SelectElement) GetAllSelectedOptions() ([]WebElement, error) { + // return getOptions().stream().filter(WebElement::isSelected).collect(Collectors.toList()); + + var opts []WebElement + return opts, nil +} + +// GetFirstSelectedOption Returns the first selected option of the Select Element +func (s SelectElement) GetFirstSelectedOption() (opt WebElement, err error) { + opts, err := s.GetAllSelectedOptions() + if err != nil { + return + } + opt = opts[0] + return +} + +// SelectByVisibleText Select all options that display text matching the argument. That is, +// when given "Bar" this would select an option like: +// +// +// +// @param text The visible text to match against +// +func (s SelectElement) SelectByVisibleText(text string) error { + // try to find the option via XPATH ... + options, err := s.element.FindElements(ByXPATH, `.//option[normalize-space(.) = "`+escapeQuotes(text)+`"]`) + if err != nil { + return err + } + + for _, option := range options { + s.setSelected(option, true) + if !s.isMulti { + return nil + } + } + + matched := len(options) > 0 + if !matched && strings.Contains(text, " ") { + subStringWithoutSpace := getLongestSubstringWithoutSpace(text) + var candidates []WebElement + if subStringWithoutSpace == "" { + // hmm, text is either empty or contains only spaces - get all options ... + candidates, err = s.GetOptions() + } else { + // get candidates via XPATH ... + candidates, err = s.element.FindElements(ByXPATH, `.//option[contains(., "`+escapeQuotes(subStringWithoutSpace)+`")]`) + } + + if err != nil { + return err + } + + trimmed := strings.TrimSpace(text) + + for _, option := range candidates { + o, err := option.Text() + if err != nil { + return err + } + if trimmed == strings.TrimSpace(o) { + s.setSelected(option, true) + if !s.isMulti { + return nil + } + matched = true + } + } + } + if !matched { + return fmt.Errorf("cannot locate option with text: %s", text) + } + return nil +} + +// SelectByIndex Select the option at the given index. This is done by examining the "index" attribute of an +// element, and not merely by counting. +// +// @param idx The option at this index will be selected +func (s SelectElement) SelectByIndex(idx int) error { + return s.setSelectedByIndex(idx, true) +} + +// SelectByValue Select all options that have a value matching the argument. That is, when given "foo" this +// would select an option like: +// +// +// +// @param value The value to match against + +func (s SelectElement) SelectByValue(value string) error { + opts, err := s.findOptionsByValue(value) + if err != nil { + return err + } + for _, option := range opts { + s.setSelected(option, true) + if !s.isMulti { + return nil + } + } + return nil +} + +// DeselectAll Clear all selected entries. This is only valid when the SELECT supports multiple selections. +func (s SelectElement) DeselectAll() error { + if !s.isMulti { + return fmt.Errorf("you may only deselect all options of a multi-select") + } + + opts, err := s.GetOptions() + if err != nil { + return err + } + for _, o := range opts { + err = s.setSelected(o, false) + if err != nil { + return err + } + } + return nil +} + +// DeselectByValue Deselect all options that have a value matching the argument. That is, when given "foo" this +// would deselect an option like: +// +// +// +// @param value The value to match against +func (s SelectElement) DeselectByValue(value string) error { + if !s.isMulti { + return fmt.Errorf("you may only deselect all options of a multi-select") + } + + opts, err := s.findOptionsByValue(value) + if err != nil { + return err + } + for _, o := range opts { + err = s.setSelected(o, false) + if err != nil { + return err + } + } + return nil +} + +// DeselectByIndex Deselect the option at the given index. This is done by examining the "index" attribute of an +// element, and not merely by counting. +// +// @param index The option at this index will be deselected +func (s SelectElement) DeselectByIndex(index int) error { + if !s.isMulti { + return fmt.Errorf("you may only deselect all options of a multi-select") + } + + return s.setSelectedByIndex(index, false) +} + +// DeselectByVisibleText Deselect all options that display text matching the argument. That is, +// when given "Bar" this would deselect an option like: +// +// +// +// @param text The visible text to match against +func (s SelectElement) DeselectByVisibleText(text string) error { + if !s.isMulti { + return fmt.Errorf("you may only deselect all options of a multi-select") + } + + options, err := s.element.FindElements(ByXPATH, `.//option[normalize-space(.) = "`+escapeQuotes(text)+`"]`) + if err != nil { + return err + } + if len(options) == 0 { + return fmt.Errorf("Cannot locate option with text: " + text) + } + + for _, option := range options { + err = s.setSelected(option, false) + if err != nil { + return err + } + } + return nil +} + +func escapeQuotes(str string) string { + str1 := strings.Replace(str, `"`, `\"`, -1) + return str1 +} + +func getLongestSubstringWithoutSpace(s string) string { + result := "" + st := strings.Split(s, " ") + for _, t := range st { + if len(t) > len(result) { + result = t + } + } + return result +} + +func (s SelectElement) findOptionsByValue(value string) (opts []WebElement, err error) { + opts, err = s.element.FindElements(ByXPATH, `.//option[@value = "`+escapeQuotes(value)+`"]`) + if err != nil { + return + } + if len(opts) == 0 { + err = fmt.Errorf("Cannot locate option with value: " + value) + } + + return +} + +func (s SelectElement) setSelectedByIndex(index int, selected bool) error { + idx := fmt.Sprintf("%d", index) + opts, err := s.element.FindElements(ByXPATH, `.//option[@index = "`+idx+`"]`) + if err != nil { + return err + } + if len(opts) == 0 { + err = fmt.Errorf("Cannot locate option with index: " + idx) + return err + } + + err = s.setSelected(opts[index], selected) + + return err +} + +func (s SelectElement) setSelected(option WebElement, selected bool) (err error) { + sel, err := option.IsSelected() + if sel != selected && err == nil { + err = option.Click() + } + return err +}