Skip to content
Draft
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
11 changes: 8 additions & 3 deletions internal/js/modules/k6/browser/browser/locator_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,14 @@ func mapLocator(vu moduleVU, lo *common.Locator) mapping {
ml := mapLocator(vu, lo.GetByTitle(ptitle, popts))
return rt.ToValue(ml).ToObject(rt), nil
},
"locator": func(selector string) *sobek.Object {
ml := mapLocator(vu, lo.Locator(selector))
return rt.ToValue(ml).ToObject(rt)
"locator": func(selector sobek.Value) (*sobek.Object, error) {
s, err := validateRequiredString(selector, "selector")
if err != nil {
return nil, err
}

ml := mapLocator(vu, lo.Locator(s))
return rt.ToValue(ml).ToObject(rt), nil
},
"innerHTML": func(opts sobek.Value) *sobek.Promise {
return k6ext.Promise(vu.Context(), func() (any, error) {
Expand Down
34 changes: 31 additions & 3 deletions internal/js/modules/k6/browser/browser/page_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,14 @@ func mapPage(vu moduleVU, p *common.Page) mapping { //nolint:gocognit,cyclop
}), nil
},
"keyboard": mapKeyboard(vu, p.GetKeyboard()),
"locator": func(selector string, opts sobek.Value) *sobek.Object {
ml := mapLocator(vu, p.Locator(selector, parseLocatorOptions(rt, opts)))
return rt.ToValue(ml).ToObject(rt)
"locator": func(selector sobek.Value, opts sobek.Value) (*sobek.Object, error) {
s, err := validateRequiredString(selector, "selector")
if err != nil {
return nil, err
}

ml := mapLocator(vu, p.Locator(s, parseLocatorOptions(rt, opts)))
return rt.ToValue(ml).ToObject(rt), nil
},
"mainFrame": func() *sobek.Object {
mf := mapFrame(vu, p.MainFrame())
Expand Down Expand Up @@ -1044,3 +1049,26 @@ func waitForNavigationBodyImpl(vu moduleVU, target interface {
return mapResponse(vu, resp), nil
}), nil
}

func validateRequiredString(v sobek.Value, name string) (string, error) {
var s string

if k6common.IsNullish(v) {
return s, errors.New("missing required argument '" + name + "'")
}

const stringType = string("")

switch v.ExportType() {
case reflect.TypeOf(stringType):
s = v.String()
default:
return s, errors.New("'" + name + "' must be a string")
}

if s == "" {
return s, errors.New("'" + name + "' must be a non-empty string")
}

return s, nil
}
50 changes: 50 additions & 0 deletions internal/js/modules/k6/browser/tests/locator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1270,3 +1270,53 @@ func TestVisibilityWithCORS(t *testing.T) {
})
}
}

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

tests := []struct {
name string
code string
expected string
}{
{
name: "undefined",
code: "undefined",
expected: "missing required argument 'selector'",
},
{
name: "null",
code: "null",
expected: "missing required argument 'selector'",
},
{
name: "empty",
code: "",
expected: "missing required argument 'selector'",
},
{
name: "empty_string",
code: "''",
expected: "'selector' must be a non-empty string",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

// Setup
tb := newTestBrowser(t, withFileServer())
tb.vu.ActivateVU()
tb.vu.StartIteration(t)

// test logic
code := fmt.Sprintf(`
page = await browser.newPage();
page.locator('test').locator(%s);`, tt.code)

_, err := tb.vu.RunAsync(t, code)
require.ErrorContains(t, err, tt.expected)
})
}
}
54 changes: 54 additions & 0 deletions internal/js/modules/k6/browser/tests/page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3436,3 +3436,57 @@ func TestClickInNestedFramesCORS(t *testing.T) {
assert.Equal(t, expectedCount, countD)
})
}

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

if runtime.GOOS == "windows" {
t.Skip("Skipped due to https://github.com/grafana/k6/issues/4937")
}

tests := []struct {
name string
code string
expected string
}{
{
name: "undefined",
code: "undefined",
expected: "missing required argument 'selector'",
},
{
name: "null",
code: "null",
expected: "missing required argument 'selector'",
},
{
name: "empty",
code: "",
expected: "missing required argument 'selector'",
},
{
name: "empty_string",
code: "''",
expected: "'selector' must be a non-empty string",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

// Setup
tb := newTestBrowser(t, withFileServer())
tb.vu.ActivateVU()
tb.vu.StartIteration(t)

// test logic
code := fmt.Sprintf(`
page = await browser.newPage();
page.locator(%s);`, tt.code)

_, err := tb.vu.RunAsync(t, code)
require.ErrorContains(t, err, tt.expected)
})
}
}
Loading