Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes panic for the selectOption #1552

Merged
merged 6 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 74 additions & 29 deletions common/element_handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (

"github.com/grafana/xk6-browser/common/js"
"github.com/grafana/xk6-browser/k6ext"

k6common "go.k6.io/k6/js/common"
)

const (
Expand Down Expand Up @@ -479,7 +481,7 @@ func (h *ElementHandle) press(apiCtx context.Context, key string, opts KeyboardO
//nolint:funlen,gocognit,cyclop
func (h *ElementHandle) selectOption(apiCtx context.Context, values sobek.Value) (any, error) {
convertSelectOptionValues := func(values sobek.Value) ([]any, error) {
if sobek.IsNull(values) || sobek.IsUndefined(values) {
if k6common.IsNullish(values) {
return nil, nil
}

Expand All @@ -489,38 +491,41 @@ func (h *ElementHandle) selectOption(apiCtx context.Context, values sobek.Value)
rt = h.execCtx.vu.Runtime()
)
switch values.ExportType().Kind() {
case reflect.Map:
s := reflect.ValueOf(t)
for i := 0; i < s.Len(); i++ {
item := s.Index(i)
switch item.Kind() {
case reflect.TypeOf(nil).Kind():
return nil, fmt.Errorf("options[%d]: expected object, got null", i)
case reflect.TypeOf(&ElementHandle{}).Kind():
opts = append(opts, t.(*ElementHandle))
case reflect.TypeOf(sobek.Object{}).Kind():
obj := values.ToObject(rt)
opt := SelectOption{}
for _, k := range obj.Keys() {
switch k {
case "value":
opt.Value = new(string)
*opt.Value = obj.Get(k).String()
case "label":
opt.Label = new(string)
*opt.Label = obj.Get(k).String()
case "index":
opt.Index = new(int64)
*opt.Index = obj.Get(k).ToInteger()
}
}
opts = append(opts, &opt)
case reflect.String:
case reflect.Slice:
var sl []interface{}
if err := rt.ExportTo(values, &sl); err != nil {
return nil, fmt.Errorf("options: expected array, got %T", values)
}

for _, item := range sl {
switch item := item.(type) {
case string:
opt := SelectOption{Value: new(string)}
*opt.Value = item.String()
*opt.Value = item
opts = append(opts, &opt)
case map[string]interface{}:
opt, err := extractSelectOptionFromMap(item)
if err != nil {
return nil, err
}

opts = append(opts, opt)
default:
return nil, fmt.Errorf("options: expected string or object, got %T", item)
}
}
case reflect.Map:
var raw map[string]interface{}
if err := rt.ExportTo(values, &raw); err != nil {
return nil, fmt.Errorf("options: expected object, got %T", values)
}

opt, err := extractSelectOptionFromMap(raw)
if err != nil {
return nil, err
}

opts = append(opts, opt)
case reflect.TypeOf(&ElementHandle{}).Kind():
opts = append(opts, t.(*ElementHandle))
case reflect.TypeOf(sobek.Object{}).Kind():
Expand All @@ -544,6 +549,8 @@ func (h *ElementHandle) selectOption(apiCtx context.Context, values sobek.Value)
opt := SelectOption{Value: new(string)}
*opt.Value = t.(string)
opts = append(opts, &opt)
default:
return nil, fmt.Errorf("options: unsupported type %T", values)
}

return opts, nil
Expand Down Expand Up @@ -575,6 +582,44 @@ func (h *ElementHandle) selectOption(apiCtx context.Context, values sobek.Value)
return result, nil
}

func extractSelectOptionFromMap(v map[string]interface{}) (*SelectOption, error) {
opt := &SelectOption{}
for k, raw := range v {
switch k {
case "value":
opt.Value = new(string)

v, ok := raw.(string)
if !ok {
return nil, fmt.Errorf("options[%v]: expected string, got %T", k, raw)
}

*opt.Value = v
case "label":
opt.Label = new(string)

v, ok := raw.(string)
if !ok {
return nil, fmt.Errorf("options[%v]: expected string, got %T", k, raw)
}
*opt.Label = v
case "index":
opt.Index = new(int64)

switch raw := raw.(type) {
case int:
*opt.Index = int64(raw)
case int64:
*opt.Index = raw
default:
return nil, fmt.Errorf("options[%v]: expected int, got %T", k, raw)
}
}
}

return opt, nil
}

func (h *ElementHandle) selectText(apiCtx context.Context) error {
fn := `
(node, injected) => {
Expand Down
57 changes: 57 additions & 0 deletions tests/locator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,3 +665,60 @@ func TestLocatorShadowDOM(t *testing.T) {
err = p.Click("#inner-link", common.NewFrameClickOptions(time.Second))
require.NoError(t, err)
}

func TestSelectOption(t *testing.T) {
t.Parallel()

tb := newTestBrowser(t,
withFileServer(),
)
defer tb.Browser.Close()

vu, _, _, cleanUp := startIteration(t)
defer cleanUp()

got := vu.RunPromise(t, `
const page = await browser.newPage();

await page.goto('%s');

const options = page.locator('#numbers-options');

await options.selectOption({label:'Five'});
let selectedValue = await options.inputValue();
if (selectedValue !== 'five') {
throw new Error('Expected "five" but got ' + selectedValue);
}

await options.selectOption({index:5});
selectedValue = await options.inputValue();
if (selectedValue !== 'five') {
throw new Error('Expected "five" but got ' + selectedValue);
}

await options.selectOption({value:'four'});
selectedValue = await options.inputValue();
if (selectedValue !== 'four') {
throw new Error('Expected "four" but got ' + selectedValue);
}

await options.selectOption([{label:'One'}]);
selectedValue = await options.inputValue();
if (selectedValue !== 'one') {
throw new Error('Expected "one" but got ' + selectedValue);
}

await options.selectOption(['two']); // Value
selectedValue = await options.inputValue();
if (selectedValue !== 'two') {
throw new Error('Expected "two" but got ' + selectedValue);
}

await options.selectOption('five'); // Value
selectedValue = await options.inputValue();
if (selectedValue !== 'five') {
throw new Error('Expected "five" but got ' + selectedValue);
}
`, tb.staticURL("select_options.html"))
assert.Equal(t, sobek.Undefined(), got.Result())
}
12 changes: 12 additions & 0 deletions tests/static/select_options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>
<body>
<select name="numbers" id="numbers-options" onchange="selectOnChange(this)" multiple>
<option value="zero">Zero</option>
<option value="one">One</option>
<option value="two">Two</option>
<option value="three">Three</option>
<option value="four">Four</option>
<option value="five">Five</option>
</select>
</body>
</html>
Loading