Skip to content
This repository has been archived by the owner on Jan 30, 2025. It is now read-only.

Commit

Permalink
Add capability to set input files in multiple form
Browse files Browse the repository at this point in the history
support playwright parameters for setInputFiles():
string|Array<string>|Object|Array<Object>
  • Loading branch information
bandorko committed Dec 2, 2023
1 parent 6c40fe8 commit 52be1b1
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 22 deletions.
27 changes: 25 additions & 2 deletions common/element_handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package common

import (
"context"
"encoding/base64"
"errors"
"fmt"
"math"
"mime"
"os"
"path/filepath"
"reflect"
"strings"
"time"
Expand Down Expand Up @@ -1253,7 +1257,23 @@ func (h *ElementHandle) SetInputFiles(opts goja.Value) {
}
}

func (h *ElementHandle) setInputFiles(apiCtx context.Context, payload []File) error {
func (h *ElementHandle) resolveFiles(payload []*File) error {
for _, file := range payload {
if file.Path != "" {
buffer, err := os.ReadFile(file.Path)
if err != nil {
return fmt.Errorf("Error while resolving file: %w", err)
}
file.Buffer = base64.StdEncoding.EncodeToString(buffer)
file.Name = filepath.Base(file.Path)
file.Mimetype = mime.TypeByExtension(filepath.Ext(file.Path))
}
}

return nil
}

func (h *ElementHandle) setInputFiles(apiCtx context.Context, payload []*File) error {
fn := `
(node, injected, payload) => {
return injected.setInputFiles(node, payload);
Expand All @@ -1263,7 +1283,10 @@ func (h *ElementHandle) setInputFiles(apiCtx context.Context, payload []File) er
forceCallable: true,
returnByValue: true,
}

err := h.resolveFiles(payload)
if err != nil {
return err
}
result, err := h.evalWithScript(apiCtx, evalOpts, fn, payload)
if err != nil {
return err
Expand Down
55 changes: 41 additions & 14 deletions common/element_handle_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package common
import (
"context"
"fmt"
"reflect"
"strings"
"time"

Expand Down Expand Up @@ -77,15 +78,16 @@ type ElementHandleHoverOptions struct {

// File is the descriptor of a single file.
type File struct {
Name string `json:"name"`
Buffer string `json:"buffer"`
Mime string `json:"mime"`
Path string `json:"-"`
Name string `json:"name"`
Mimetype string `json:"mimeType"`
Buffer string `json:"buffer"`
}

// ElementHandleSetInputFilesOption are options for ElementHandle.SetInputFiles.
type ElementHandleSetInputFilesOption struct {
ElementHandleBaseOptions
Payload []File `json:"payload"`
Payload []*File `json:"payload"`
}

type ElementHandlePressOptions struct {
Expand Down Expand Up @@ -198,6 +200,29 @@ func NewElementHandleSetInputFilesOptions(defaultTimeout time.Duration) *Element
}
}

// addFile to the option. Input value can be a path, or a file descriptor object.
func (o *ElementHandleSetInputFilesOption) addFile(ctx context.Context, opts goja.Value) error {
if !gojaValueExists(opts) {
return nil
}
rt := k6ext.Runtime(ctx)
optsType := opts.ExportType()
switch optsType.Kind() {

Check failure on line 210 in common/element_handle_options.go

View workflow job for this annotation

GitHub Actions / lint

missing cases in switch of type reflect.Kind: Invalid, Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, Float32, Float64, Complex64, Complex128, Array, Chan, Func, Interface, Pointer|Ptr, Slice, Struct, UnsafePointer (exhaustive)
case reflect.Map: // file descriptor object
var file File
if err := rt.ExportTo(opts, &file); err != nil {
return fmt.Errorf("unable to parse SetInputFileOptions; reason: %w", err)
}
o.Payload = append(o.Payload, &file)
case reflect.String: // file path
o.Payload = append(o.Payload, &File{Path: opts.Export().(string)})

Check failure on line 218 in common/element_handle_options.go

View workflow job for this annotation

GitHub Actions / lint

type assertion must be checked (forcetypeassert)
default:
return fmt.Errorf("Cannot parse setInputFiles parameter")
}

return nil
}

// Parse parses the ElementHandleSetInputFilesOption from the given opts.
func (o *ElementHandleSetInputFilesOption) Parse(ctx context.Context, opts goja.Value) error {
rt := k6ext.Runtime(ctx)
Expand All @@ -207,17 +232,19 @@ func (o *ElementHandleSetInputFilesOption) Parse(ctx context.Context, opts goja.
if !gojaValueExists(opts) {
return nil
}
gopts := opts.ToObject(rt)
for _, k := range gopts.Keys() {
if k != "payload" {
continue
}
var p []File
if err := rt.ExportTo(gopts.Get(k), &p); err != nil {
return fmt.Errorf("unable to parse SetInputFileOptions; reason: %w", err)
}
o.Payload = p

optsType := opts.ExportType()
switch optsType.Kind() {

Check failure on line 237 in common/element_handle_options.go

View workflow job for this annotation

GitHub Actions / lint

missing cases in switch of type reflect.Kind: Invalid, Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, Float32, Float64, Complex64, Complex128, Array, Chan, Func, Interface, Map, Pointer|Ptr, String, Struct, UnsafePointer (exhaustive)
case reflect.Slice: // array of filePaths or array of file descriptor objects
gopts := opts.ToObject(rt)
for _, k := range gopts.Keys() {
err := o.addFile(ctx, gopts.Get(k))
if err != nil {
return err
}
}
default: // filePath or file descriptor object
return o.addFile(ctx, opts)
}

return nil
Expand Down
14 changes: 8 additions & 6 deletions common/js/injected_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,15 @@ class InjectedScript {
if (type !== 'file')
return 'error:Not an input[type=file] element';

const files = payloads.map(file => {
const bytes = Uint8Array.from(atob(file.buffer), c => c.charCodeAt(0));
return new File([bytes], file.name, { type: file.mimeType, lastModified: file.lastModifiedMs });
});
const dt = new DataTransfer();
for (const file of files)
dt.items.add(file);
if (payloads != null) {
const files = payloads.map(file => {
const bytes = Uint8Array.from(atob(file.buffer), c => c.charCodeAt(0));
return new File([bytes], file.name, { type: file.mimeType, lastModified: file.lastModifiedMs });
});
for (const file of files)
dt.items.add(file);
}
node.files = dt.files;
node.dispatchEvent(new Event('input', { 'bubbles': true }));
node.dispatchEvent(new Event('change', { 'bubbles': true }));
Expand Down

0 comments on commit 52be1b1

Please sign in to comment.