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

Fix 1204: panic while exporting nil goja values #1205

Merged
merged 6 commits into from
Feb 6, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 43 additions & 0 deletions browser/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package browser

import (
"context"
"errors"

"github.com/dop251/goja"

"github.com/grafana/xk6-browser/k6error"
"github.com/grafana/xk6-browser/k6ext"
)

func panicIfFatalError(ctx context.Context, err error) {
if errors.Is(err, k6error.ErrFatal) {
k6ext.Abort(ctx, err.Error())
}
}

// exportArg exports the value and returns it.
// It returns nil if the value is undefined or null.
func exportArg(gv goja.Value) any {
if !gojaValueExists(gv) {
return nil
}
return gv.Export()
}

// exportArgs returns a slice of exported Goja values.
func exportArgs(gargs []goja.Value) []any {
args := make([]any, 0, len(gargs))
for _, garg := range gargs {
// leaves a nil garg in the array since users might want to
// pass undefined or null as an argument to a function
args = append(args, exportArg(garg))
}
return args
}

// gojaValueExists returns true if a given value is not nil and exists
// (defined and not null) in the goja runtime.
func gojaValueExists(v goja.Value) bool {
return v != nil && !goja.IsUndefined(v) && !goja.IsNull(v)
}
56 changes: 12 additions & 44 deletions browser/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package browser

import (
"context"
"errors"
"fmt"
"time"

Expand Down Expand Up @@ -88,7 +87,7 @@ func mapLocator(vu moduleVU, lo *common.Locator) mapping {
if err := popts.Parse(vu.Context(), opts); err != nil {
return fmt.Errorf("parsing locator dispatch event options: %w", err)
}
return lo.DispatchEvent(typ, eventInit.Export(), popts) //nolint:wrapcheck
return lo.DispatchEvent(typ, exportArg(eventInit), popts) //nolint:wrapcheck
},
"waitFor": lo.WaitFor,
}
Expand Down Expand Up @@ -190,16 +189,12 @@ func mapJSHandle(vu moduleVU, jsh common.JSHandleAPI) mapping {
"evaluate": func(pageFunc goja.Value, gargs ...goja.Value) any {
args := make([]any, 0, len(gargs))
for _, a := range gargs {
args = append(args, a.Export())
args = append(args, exportArg(a))
}
return jsh.Evaluate(pageFunc.String(), args...)
},
"evaluateHandle": func(pageFunc goja.Value, gargs ...goja.Value) (mapping, error) {
args := make([]any, 0, len(gargs))
for _, a := range gargs {
args = append(args, a.Export())
}
h, err := jsh.EvaluateHandle(pageFunc.String(), args...)
h, err := jsh.EvaluateHandle(pageFunc.String(), exportArgs(gargs)...)
if err != nil {
return nil, err //nolint:wrapcheck
}
Expand Down Expand Up @@ -258,7 +253,7 @@ func mapElementHandle(vu moduleVU, eh *common.ElementHandle) mapping {
},
"dblclick": eh.Dblclick,
"dispatchEvent": func(typ string, eventInit goja.Value) error {
return eh.DispatchEvent(typ, eventInit.Export()) //nolint:wrapcheck
return eh.DispatchEvent(typ, exportArg(eventInit)) //nolint:wrapcheck
},
"fill": eh.Fill,
"focus": eh.Focus,
Expand Down Expand Up @@ -387,21 +382,13 @@ func mapFrame(vu moduleVU, f *common.Frame) mapping {
if err := popts.Parse(vu.Context(), opts); err != nil {
return fmt.Errorf("parsing frame dispatch event options: %w", err)
}
return f.DispatchEvent(selector, typ, eventInit.Export(), popts) //nolint:wrapcheck
return f.DispatchEvent(selector, typ, exportArg(eventInit), popts) //nolint:wrapcheck
},
"evaluate": func(pageFunction goja.Value, gargs ...goja.Value) any {
args := make([]any, 0, len(gargs))
for _, a := range gargs {
args = append(args, a.Export())
}
return f.Evaluate(pageFunction.String(), args...)
return f.Evaluate(pageFunction.String(), exportArgs(gargs)...)
},
"evaluateHandle": func(pageFunction goja.Value, gargs ...goja.Value) (mapping, error) {
args := make([]any, 0, len(gargs))
for _, a := range gargs {
args = append(args, a.Export())
}
jsh, err := f.EvaluateHandle(pageFunction.String(), args...)
jsh, err := f.EvaluateHandle(pageFunction.String(), exportArgs(gargs)...)
if err != nil {
return nil, err //nolint:wrapcheck
}
Expand Down Expand Up @@ -535,7 +522,7 @@ func mapFrame(vu moduleVU, f *common.Frame) mapping {
}

func parseWaitForFunctionArgs(
ctx context.Context, timeout time.Duration, pageFunc, opts goja.Value, args ...goja.Value,
ctx context.Context, timeout time.Duration, pageFunc, opts goja.Value, gargs ...goja.Value,
) (string, *common.FrameWaitForFunctionOptions, []any, error) {
popts := common.NewFrameWaitForFunctionOptions(timeout)
err := popts.Parse(ctx, opts)
Expand All @@ -549,12 +536,7 @@ func parseWaitForFunctionArgs(
js = fmt.Sprintf("() => (%s)", js)
}

pargs := make([]any, 0, len(args))
for _, arg := range args {
pargs = append(pargs, arg.Export())
}

return js, popts, pargs, nil
return js, popts, exportArgs(gargs), nil
}

// mapPage to the JS module.
Expand Down Expand Up @@ -592,24 +574,16 @@ func mapPage(vu moduleVU, p *common.Page) mapping {
if err := popts.Parse(vu.Context(), opts); err != nil {
return fmt.Errorf("parsing page dispatch event options: %w", err)
}
return p.DispatchEvent(selector, typ, eventInit.Export(), popts) //nolint:wrapcheck
return p.DispatchEvent(selector, typ, exportArg(eventInit), popts) //nolint:wrapcheck
},
"dragAndDrop": p.DragAndDrop,
"emulateMedia": p.EmulateMedia,
"emulateVisionDeficiency": p.EmulateVisionDeficiency,
"evaluate": func(pageFunction goja.Value, gargs ...goja.Value) any {
args := make([]any, 0, len(gargs))
for _, a := range gargs {
args = append(args, a.Export())
}
return p.Evaluate(pageFunction.String(), args...)
return p.Evaluate(pageFunction.String(), exportArgs(gargs)...)
},
"evaluateHandle": func(pageFunc goja.Value, gargs ...goja.Value) (mapping, error) {
args := make([]any, 0, len(gargs))
for _, a := range gargs {
args = append(args, a.Export())
}
jsh, err := p.EvaluateHandle(pageFunc.String(), args...)
jsh, err := p.EvaluateHandle(pageFunc.String(), exportArgs(gargs)...)
if err != nil {
return nil, err //nolint:wrapcheck
}
Expand Down Expand Up @@ -1036,9 +1010,3 @@ func mapBrowser(vu moduleVU) mapping { //nolint:funlen
},
}
}

func panicIfFatalError(ctx context.Context, err error) {
if errors.Is(err, k6error.ErrFatal) {
k6ext.Abort(ctx, err.Error())
}
}
36 changes: 36 additions & 0 deletions examples/dispatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { check } from 'k6';
import { browser } from 'k6/x/browser';

export const options = {
scenarios: {
ui: {
executor: 'shared-iterations',
options: {
browser: {
type: 'chromium',
},
},
},
},
thresholds: {
checks: ["rate==1.0"]
}
}

export default async function() {
const context = browser.newContext();
const page = context.newPage();

try {
await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });

page.locator('a[href="/contacts.php"]')
.dispatchEvent("click");

check(page, {
header: (p) => p.locator("h3").textContent() == "Contact us",
});
} finally {
page.close();
}
}
24 changes: 16 additions & 8 deletions examples/evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,23 @@ export default async function() {
const page = context.newPage();

try {
await page.goto('https://test.k6.io/', { waitUntil: 'load' });

const result = page.evaluate(([x, y]) => {
return Promise.resolve(x * y);
}, [5, 5]);
console.log(result); // tests #120

await page.goto("https://test.k6.io/", { waitUntil: "load" });

// calling evaluate without arguments
let result = page.evaluate(() => {
return Promise.resolve(5 * 42);
});
check(result, {
"result should be 210": (result) => result == 210,
});

// calling evaluate with arguments
result = page.evaluate(([x, y]) => {
return Promise.resolve(x * y);
}, [5, 5]
);
check(result, {
'result is 25': (result) => result == 25,
"result should be 25": (result) => result == 25,
});
} finally {
page.close();
Expand Down