Adding SetUploadFiles and changing SendKeys behavior

Added high level action SetUploadFiles to set the upload files for a
input[type="file"] node, and modified SendKeys to recognize the nodes.

Additionally, added unit test for both, and updated the
examples/upload/main.go to use the SendKeys variant.
This commit is contained in:
Kenneth Shaw 2017-07-01 10:14:42 +07:00
parent 0b9790e5a8
commit df490a3025
4 changed files with 136 additions and 11 deletions

View File

@ -10,8 +10,6 @@ import (
"os"
cdp "github.com/knq/chromedp"
cdptypes "github.com/knq/chromedp/cdp"
"github.com/knq/chromedp/cdp/dom"
)
var (
@ -97,14 +95,9 @@ func main() {
}
func upload(filepath string, sz *string) cdp.Tasks {
var ids []cdptypes.NodeID
return cdp.Tasks{
cdp.Navigate(fmt.Sprintf("http://localhost:%d", *flagPort)),
cdp.WaitVisible(`input[name="upload"]`),
cdp.NodeIDs(`input[name="upload"]`, &ids, cdp.NodeVisible),
cdp.ActionFunc(func(ctxt context.Context, h cdptypes.Handler) error {
return dom.SetFileInputFiles(ids[0], []string{filepath}).Do(ctxt, h)
}),
cdp.SendKeys(`input[name="upload"]`, filepath, cdp.NodeVisible),
cdp.Click(`input[name="submit"]`),
cdp.Text(`#result`, sz, cdp.ByID, cdp.NodeVisible),
}

View File

@ -147,8 +147,10 @@ func ClickCount(n int) MouseOption {
// KeyAction will synthesize a keyDown, char, and keyUp event for each rune
// contained in keys along with any supplied key options.
//
// Note: only well known, "printable" characters will have "char" events
// synthesized.
// Only well-known, "printable" characters will have char events synthesized.
//
// Please see the chromedp/kb package for implementation details and the list
// of well-known keys.
func KeyAction(keys string, opts ...KeyOption) Action {
return ActionFunc(func(ctxt context.Context, h cdp.Handler) error {
var err error

View File

@ -329,15 +329,48 @@ func DoubleClick(sel interface{}, opts ...QueryOption) Action {
// SendKeys synthesizes the key up, char, and down events as needed for the
// runes in v, sending them to the first node matching the selector.
//
// Note: when selector matches a input[type="file"] node, then dom.SetFileInputFiles
// is used to set the upload path of the input node to v.
func SendKeys(sel interface{}, v string, opts ...QueryOption) Action {
return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error {
if len(nodes) < 1 {
return fmt.Errorf("selector `%s` did not return any nodes", sel)
}
return KeyActionNode(nodes[0], v).Do(ctxt, h)
n := nodes[0]
// grab type attribute from node
typ, attrs := "", n.Attributes
n.RLock()
for i := 0; i < len(attrs); i += 2 {
if attrs[i] == "type" {
typ = attrs[i+1]
}
}
n.RUnlock()
// when working with input[type="file"], call dom.SetFileInputFiles
if n.NodeName == "INPUT" && typ == "file" {
return dom.SetFileInputFiles(n.NodeID, []string{v}).Do(ctxt, h)
}
return KeyActionNode(n, v).Do(ctxt, h)
}, append(opts, NodeVisible)...)
}
// SetUploadFiles sets the files to upload (ie, for a input[type="file"] node)
// for the first node matching the selector.
func SetUploadFiles(sel interface{}, files []string, opts ...QueryOption) Action {
return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error {
if len(nodes) < 1 {
return fmt.Errorf("selector `%s` did not return any nodes", sel)
}
return dom.SetFileInputFiles(nodes[0].NodeID, files).Do(ctxt, h)
}, opts...)
}
// Screenshot takes a screenshot of the first node matching the selector.
func Screenshot(sel interface{}, picbuf *[]byte, opts ...QueryOption) Action {
if picbuf == nil {

View File

@ -2,6 +2,10 @@ package chromedp
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"reflect"
"testing"
"time"
@ -853,3 +857,96 @@ func TestMatchedStyle(t *testing.T) {
})
}
}
func TestFileUpload(t *testing.T) {
t.Parallel()
// create test server
mux := http.NewServeMux()
mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
fmt.Fprintf(res, uploadHTML)
})
mux.HandleFunc("/upload", func(res http.ResponseWriter, req *http.Request) {
f, _, err := req.FormFile("upload")
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return
}
defer f.Close()
buf, err := ioutil.ReadAll(f)
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(res, resultHTML, len(buf))
})
s := httptest.NewServer(mux)
defer s.Close()
// create temporary file on disk
tmpfile, err := ioutil.TempFile("", "chromedp-upload-test")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
if _, err = tmpfile.WriteString(uploadHTML); err != nil {
t.Fatal(err)
}
if err = tmpfile.Close(); err != nil {
t.Fatal(err)
}
c := testAllocate(t, "")
defer c.Release()
tests := []struct {
a Action
}{
{SendKeys(`input[name="upload"]`, tmpfile.Name(), NodeVisible)},
{SetUploadFiles(`input[name="upload"]`, []string{tmpfile.Name()}, NodeVisible)},
}
for i, test := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
test = test
c := testAllocate(t, "")
defer c.Release()
var result string
err = c.Run(defaultContext, Tasks{
Navigate(s.URL),
test.a,
Click(`input[name="submit"]`),
Text(`#result`, &result, ByID, NodeVisible),
})
if err != nil {
t.Fatalf("test %d expected no error, got: %v", i, err)
}
if result != fmt.Sprintf("%d", len(uploadHTML)) {
t.Errorf("test %d expected result to be %d, got: %s", i, len(uploadHTML), result)
}
})
}
}
const (
uploadHTML = `<!doctype html>
<html>
<body>
<form method="POST" action="/upload" enctype="multipart/form-data">
<input name="upload" type="file"/>
<input name="submit" type="submit"/>
</form>
</body>
</html>`
resultHTML = `<!doctype html>
<html>
<body>
<div id="result">%d</div>
</body>
</html>`
)