0d568ec2a4
This can simplify some common use cases, like running a few actions directly, or running no actions at all. It's also an almost entirely backwards compatible change, as all Run call sites should continue to compile and work. Leave Tasks, as it can still be useful for functions to return complex sequences of actions as a single Action.
1107 lines
24 KiB
Go
1107 lines
24 KiB
Go
package chromedp
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
_ "image/png"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/chromedp/cdproto/cdp"
|
|
"github.com/chromedp/cdproto/css"
|
|
"github.com/chromedp/cdproto/dom"
|
|
"github.com/chromedp/cdproto/emulation"
|
|
|
|
"github.com/chromedp/chromedp/kb"
|
|
)
|
|
|
|
func TestNodes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "table.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
len int
|
|
}{
|
|
{"/html/body/table/tbody[1]/tr[2]/td", BySearch, 3},
|
|
{"body > table > tbody:nth-child(2) > tr:nth-child(2) > td:not(:last-child)", ByQueryAll, 2},
|
|
{"body > table > tbody:nth-child(2) > tr:nth-child(2) > td", ByQuery, 1},
|
|
{"#footer", ByID, 1},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var nodes []*cdp.Node
|
|
if err := Run(ctx, Nodes(test.sel, &nodes, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if len(nodes) != test.len {
|
|
t.Errorf("test %d expected to have %d nodes: got %d", i, test.len, len(nodes))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNodeIDs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "table.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
len int
|
|
}{
|
|
{"/html/body/table/tbody[1]/tr[2]/td", BySearch, 3},
|
|
{"body > table > tbody:nth-child(2) > tr:nth-child(2) > td:not(:last-child)", ByQueryAll, 2},
|
|
{"body > table > tbody:nth-child(2) > tr:nth-child(2) > td", ByQuery, 1},
|
|
{"#footer", ByID, 1},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var ids []cdp.NodeID
|
|
if err := Run(ctx, NodeIDs(test.sel, &ids, test.by)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(ids) != test.len {
|
|
t.Errorf("test %d expected to have %d node id's: got %d", i, test.len, len(ids))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFocusBlur(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "js.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{`//*[@id="input1"]`, BySearch},
|
|
{`body > input[type="number"]:nth-child(1)`, ByQueryAll},
|
|
{`body > input[type="number"]:nth-child(1)`, ByQuery},
|
|
{"#input1", ByID},
|
|
}
|
|
|
|
err := Run(ctx, Click("#input1", ByID))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for i, test := range tests {
|
|
if err := Run(ctx, Focus(test.sel, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
var value string
|
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if value != "9999" {
|
|
t.Errorf("test %d expected value is '9999', got: '%s'", i, value)
|
|
}
|
|
if err := Run(ctx, Blur(test.sel, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if value != "0" {
|
|
t.Errorf("test %d expected value is '0', got: '%s'", i, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDimensions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "image.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
width int64
|
|
height int64
|
|
}{
|
|
{"/html/body/img", BySearch, 239, 239},
|
|
{"img", ByQueryAll, 239, 239},
|
|
{"img", ByQuery, 239, 239},
|
|
{"#icon-github", ByID, 120, 120},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var model *dom.BoxModel
|
|
if err := Run(ctx, Dimensions(test.sel, &model)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if model.Height != test.height || model.Width != test.width {
|
|
t.Errorf("test %d expected %dx%d, got: %dx%d", i, test.width, test.height, model.Height, model.Width)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestText(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "form.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
exp string
|
|
}{
|
|
{"#foo", ByID, "insert"},
|
|
{"body > form > span", ByQueryAll, "insert"},
|
|
{"body > form > span:nth-child(2)", ByQuery, "keyword"},
|
|
{"/html/body/form/span[2]", BySearch, "keyword"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var text string
|
|
if err := Run(ctx, Text(test.sel, &text, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if text != test.exp {
|
|
t.Errorf("test %d expected `%s`, got: %s", i, test.exp, text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClear(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
// input fields
|
|
{`//*[@id="form"]/input[1]`, BySearch},
|
|
{`#form > input[type="text"]:nth-child(4)`, ByQuery},
|
|
{`#form > input[type="text"]`, ByQueryAll},
|
|
{`#keyword`, ByID},
|
|
|
|
// textarea fields
|
|
{`//*[@id="bar"]`, BySearch},
|
|
{`#form > textarea`, ByQuery},
|
|
{`#form > textarea`, ByQueryAll},
|
|
{`#bar`, ByID},
|
|
|
|
// input + textarea fields
|
|
{`//*[@id="form"]/input`, BySearch},
|
|
{`#form > input[type="text"]`, ByQueryAll},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "form.html")
|
|
defer cancel()
|
|
|
|
var val string
|
|
err := Run(ctx, Value(test.sel, &val, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
if val == "" {
|
|
t.Errorf("expected `%s` to have non empty value", test.sel)
|
|
}
|
|
if err := Run(ctx, Clear(test.sel, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
if err := Run(ctx, Value(test.sel, &val, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if val != "" {
|
|
t.Errorf("expected empty value for `%s`, got: %s", test.sel, val)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReset(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
value string
|
|
exp string
|
|
}{
|
|
{`//*[@id="keyword"]`, BySearch, "foobar", "chromedp"},
|
|
{`#form > input[type="text"]:nth-child(6)`, ByQuery, "foobar", "foo"},
|
|
{`#form > input[type="text"]`, ByQueryAll, "foobar", "chromedp"},
|
|
{"#bar", ByID, "foobar", "bar"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "form.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, SetValue(test.sel, test.value, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
if err := Run(ctx, Reset(test.sel, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var value string
|
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if value != test.exp {
|
|
t.Errorf("expected value after reset is %s, got: '%s'", test.exp, value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "form.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{`//*[@id="form"]/input[1]`, BySearch},
|
|
{`#form > input[type="text"]:nth-child(4)`, ByQuery},
|
|
{`#form > input[type="text"]`, ByQueryAll},
|
|
{`#keyword`, ByID},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var value string
|
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if value != "chromedp" {
|
|
t.Errorf("test %d expected `chromedp`, got: %s", i, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{`//*[@id="form"]/input[1]`, BySearch},
|
|
{`#form > input[type="text"]:nth-child(4)`, ByQuery},
|
|
{`#form > input[type="text"]`, ByQueryAll},
|
|
{`#bar`, ByID},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "form.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, SetValue(test.sel, "FOOBAR", test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var value string
|
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if value != "FOOBAR" {
|
|
t.Errorf("expected `FOOBAR`, got: %s", value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAttributes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "image.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
exp map[string]string
|
|
}{
|
|
{
|
|
`//*[@id="icon-brankas"]`, BySearch,
|
|
map[string]string{
|
|
"alt": "Brankas - Easy Money Management",
|
|
"id": "icon-brankas",
|
|
"src": "images/brankas.png",
|
|
},
|
|
},
|
|
{
|
|
"body > img:first-child", ByQuery,
|
|
map[string]string{
|
|
"alt": "Brankas - Easy Money Management",
|
|
"id": "icon-brankas",
|
|
"src": "images/brankas.png",
|
|
},
|
|
},
|
|
{
|
|
"body > img:nth-child(2)", ByQueryAll,
|
|
map[string]string{
|
|
"alt": `How people build software`,
|
|
"id": "icon-github",
|
|
"src": "images/github.png",
|
|
},
|
|
},
|
|
{
|
|
"#icon-github", ByID,
|
|
map[string]string{
|
|
"alt": "How people build software",
|
|
"id": "icon-github",
|
|
"src": "images/github.png",
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var attrs map[string]string
|
|
if err := Run(ctx, Attributes(test.sel, &attrs, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(test.exp, attrs) {
|
|
t.Errorf("test %d expected %v, got: %v", i, test.exp, attrs)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAttributesAll(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "image.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
exp []map[string]string
|
|
}{
|
|
{
|
|
"img", ByQueryAll,
|
|
[]map[string]string{
|
|
{
|
|
"alt": "Brankas - Easy Money Management",
|
|
"id": "icon-brankas",
|
|
"src": "images/brankas.png",
|
|
},
|
|
{
|
|
"alt": "How people build software",
|
|
"id": "icon-github",
|
|
"src": "images/github.png",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var attrs []map[string]string
|
|
if err := Run(ctx, AttributesAll(test.sel, &attrs, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(test.exp, attrs) {
|
|
t.Errorf("test %d expected %v, got: %v", i, test.exp, attrs)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetAttributes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
attrs map[string]string
|
|
exp map[string]string
|
|
}{
|
|
{
|
|
`//*[@id="icon-brankas"]`, BySearch,
|
|
map[string]string{"data-url": "brankas"},
|
|
map[string]string{
|
|
"alt": "Brankas - Easy Money Management",
|
|
"id": "icon-brankas",
|
|
"src": "images/brankas.png",
|
|
"data-url": "brankas",
|
|
},
|
|
},
|
|
{
|
|
"body > img:first-child", ByQuery,
|
|
map[string]string{"data-url": "brankas"},
|
|
map[string]string{
|
|
"alt": "Brankas - Easy Money Management",
|
|
"id": "icon-brankas",
|
|
"src": "images/brankas.png",
|
|
"data-url": "brankas",
|
|
},
|
|
},
|
|
{
|
|
"body > img:nth-child(2)", ByQueryAll,
|
|
map[string]string{"width": "100", "height": "200"},
|
|
map[string]string{
|
|
"alt": `How people build software`,
|
|
"id": "icon-github",
|
|
"src": "images/github.png",
|
|
"width": "100",
|
|
"height": "200",
|
|
},
|
|
},
|
|
{
|
|
"#icon-github", ByID,
|
|
map[string]string{"width": "100", "height": "200"},
|
|
map[string]string{
|
|
"alt": "How people build software",
|
|
"id": "icon-github",
|
|
"src": "images/github.png",
|
|
"width": "100",
|
|
"height": "200",
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "image.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, SetAttributes(test.sel, test.attrs, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var attrs map[string]string
|
|
if err := Run(ctx, Attributes(test.sel, &attrs, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(test.exp, attrs) {
|
|
t.Errorf("expected %v, got: %v", test.exp, attrs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAttributeValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "image.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
attr string
|
|
exp string
|
|
}{
|
|
{`//*[@id="icon-brankas"]`, BySearch, "alt", "Brankas - Easy Money Management"},
|
|
{"body > img:first-child", ByQuery, "alt", "Brankas - Easy Money Management"},
|
|
{"body > img:nth-child(2)", ByQueryAll, "alt", "How people build software"},
|
|
{"#icon-github", ByID, "alt", "How people build software"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var value string
|
|
var ok bool
|
|
if err := Run(ctx, AttributeValue(test.sel, test.attr, &value, &ok, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if !ok {
|
|
t.Fatalf("test %d failed to get attribute %s on %s", i, test.attr, test.sel)
|
|
}
|
|
|
|
if value != test.exp {
|
|
t.Errorf("test %d expected %s to be %s, got: %s", i, test.attr, test.exp, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetAttributeValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
attr string
|
|
exp string
|
|
}{
|
|
{`//*[@id="keyword"]`, BySearch, "foo", "bar"},
|
|
{`#form > input[type="text"]:nth-child(6)`, ByQuery, "foo", "bar"},
|
|
{`#form > input[type="text"]`, ByQueryAll, "foo", "bar"},
|
|
{"#bar", ByID, "foo", "bar"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "form.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, SetAttributeValue(test.sel, test.attr, test.exp, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var value string
|
|
var ok bool
|
|
if err := Run(ctx, AttributeValue(test.sel, test.attr, &value, &ok, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if !ok {
|
|
t.Fatalf("failed to get attribute %s on %s", test.attr, test.sel)
|
|
}
|
|
|
|
if value != test.exp {
|
|
t.Errorf("expected %s to be %s, got: %s", test.attr, test.exp, value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemoveAttribute(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
attr string
|
|
}{
|
|
{"/html/body/img", BySearch, "alt"},
|
|
{"img", ByQueryAll, "alt"},
|
|
{"img", ByQuery, "alt"},
|
|
{"#icon-github", ByID, "alt"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "image.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, RemoveAttribute(test.sel, test.attr))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var value string
|
|
var ok bool
|
|
if err := Run(ctx, AttributeValue(test.sel, test.attr, &value, &ok, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if ok || value != "" {
|
|
t.Fatalf("expected attribute %s removed from element %s", test.attr, test.sel)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClick(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{`//*[@id="form"]/input[4]`, BySearch},
|
|
{`#form > input[type="submit"]:nth-child(11)`, ByQuery},
|
|
{`#form > input[type="submit"]:nth-child(11)`, ByQueryAll},
|
|
{"#btn2", ByID},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "form.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, Click(test.sel, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
if err := Run(ctx, WaitVisible("#icon-brankas", ByID)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var title string
|
|
if err := Run(ctx, Title(&title)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if title != "this is title" {
|
|
t.Errorf("expected title to be 'chromedp - Google Search', got: '%s'", title)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDoubleClick(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{`/html/body/input[2]`, BySearch},
|
|
{`body > input[type="button"]:nth-child(2)`, ByQueryAll},
|
|
{`body > input[type="button"]:nth-child(2)`, ByQuery},
|
|
{`#button1`, ByID},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "js.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, DoubleClick(test.sel, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var value string
|
|
if err := Run(ctx, Value("#input1", &value, ByID)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if value != "1" {
|
|
t.Errorf("expected value to be '1', got: '%s'", value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSendKeys(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
keys string
|
|
exp string
|
|
}{
|
|
{`//*[@id="input1"]`, BySearch, "INSERT ", "INSERT some value"},
|
|
{`#box4 > input:nth-child(1)`, ByQuery, "insert ", "insert some value"},
|
|
{`#box4 > textarea`, ByQueryAll, "prefix " + kb.End + "\b\b SUFFIX\n", "prefix textar SUFFIX\n"},
|
|
{"#textarea1", ByID, "insert ", "insert textarea"},
|
|
{"#textarea1", ByID, kb.End + "\b\b\n\naoeu\n\nfoo\n\nbar\n\n", "textar\n\naoeu\n\nfoo\n\nbar\n\n"},
|
|
{"#select1", ByID, kb.ArrowDown + kb.ArrowDown, "three"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "visible.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, SendKeys(test.sel, test.keys, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var val string
|
|
if err := Run(ctx, Value(test.sel, &val, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if val != test.exp {
|
|
t.Errorf("expected value %s, got: %s", test.exp, val)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScreenshot(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "image.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
size int
|
|
}{
|
|
{"/html/body/img", BySearch, 239},
|
|
{"img", ByQueryAll, 239},
|
|
{"#icon-github", ByID, 120},
|
|
}
|
|
|
|
// a smaller viewport speeds up this test
|
|
width, height := 650, 450
|
|
if err := Run(ctx, emulation.SetDeviceMetricsOverride(
|
|
int64(width), int64(height), 1.0, false,
|
|
)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for i, test := range tests {
|
|
var buf []byte
|
|
if err := Run(ctx, Screenshot(test.sel, &buf)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if len(buf) == 0 {
|
|
t.Fatalf("test %d failed to capture screenshot", i)
|
|
}
|
|
config, format, err := image.DecodeConfig(bytes.NewReader(buf))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if want := "png"; format != want {
|
|
t.Fatalf("expected format to be %q, got %q", want, format)
|
|
}
|
|
if config.Width != test.size || config.Height != test.size {
|
|
t.Fatalf("expected dimensions to be %d*%d, got %d*%d",
|
|
test.size, test.size, config.Width, config.Height)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSubmit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{`//*[@id="keyword"]`, BySearch},
|
|
{`#form > input[type="text"]:nth-child(4)`, ByQuery},
|
|
{`#form > input[type="text"]`, ByQueryAll},
|
|
{"#form", ByID},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "form.html")
|
|
defer cancel()
|
|
|
|
err := Run(ctx, Submit(test.sel, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
if err := Run(ctx, WaitVisible("#icon-brankas", ByID)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
var title string
|
|
if err := Run(ctx, Title(&title)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if title != "this is title" {
|
|
t.Errorf("expected title to be 'this is title', got: '%s'", title)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestComputedStyle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{`//*[@id="input1"]`, BySearch},
|
|
{`body > input[type="number"]:nth-child(1)`, ByQueryAll},
|
|
{`body > input[type="number"]:nth-child(1)`, ByQuery},
|
|
{"#input1", ByID},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "js.html")
|
|
defer cancel()
|
|
|
|
var styles []*css.ComputedProperty
|
|
err := Run(ctx, ComputedStyle(test.sel, &styles, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
for _, style := range styles {
|
|
if style.Name == "background-color" {
|
|
if style.Value != "rgb(255, 0, 0)" {
|
|
t.Logf("expected style 'rgb(255, 0, 0)' got: %s", style.Value)
|
|
}
|
|
}
|
|
}
|
|
if err := Run(ctx, Click("#input1", ByID)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
if err := Run(ctx, ComputedStyle(test.sel, &styles, test.by)); err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
for _, style := range styles {
|
|
if style.Name == "background-color" {
|
|
if style.Value != "rgb(255, 255, 0)" {
|
|
t.Fatalf("expected style 'rgb(255, 255, 0)' got: %s", style.Value)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchedStyle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{`//*[@id="input1"]`, BySearch},
|
|
{`body > input[type="number"]:nth-child(1)`, ByQueryAll},
|
|
{`body > input[type="number"]:nth-child(1)`, ByQuery},
|
|
{"#input1", ByID},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
|
test := test
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "js.html")
|
|
defer cancel()
|
|
|
|
var styles *css.GetMatchedStylesForNodeReturns
|
|
err := Run(ctx, MatchedStyle(test.sel, &styles, test.by))
|
|
if err != nil {
|
|
t.Fatalf("got error: %v", err)
|
|
}
|
|
|
|
// TODO: Add logic to check if the style returned is true and valid.
|
|
})
|
|
}
|
|
}
|
|
|
|
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, "%s", 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())
|
|
defer tmpfile.Close()
|
|
if _, err := tmpfile.WriteString(uploadHTML); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tmpfile.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
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) {
|
|
// TODO: refactor the test so the subtests can run in
|
|
// parallel
|
|
//t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "")
|
|
defer cancel()
|
|
|
|
var result string
|
|
if err := Run(ctx,
|
|
Navigate(s.URL),
|
|
test.a,
|
|
Click(`input[name="submit"]`),
|
|
Text(`#result`, &result, ByID, NodeVisible),
|
|
); 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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInnerHTML(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "table.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{"/html/body/table/thead", BySearch},
|
|
{"thead", ByQueryAll},
|
|
{"thead", ByQuery},
|
|
}
|
|
for i, test := range tests {
|
|
var html string
|
|
if err := Run(ctx, InnerHTML(test.sel, &html)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if html == "" {
|
|
t.Fatalf("test %d: InnerHTML is empty", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOuterHTML(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "table.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{"/html/body/table/thead/tr", BySearch},
|
|
{"thead tr", ByQueryAll},
|
|
{"thead tr", ByQuery},
|
|
}
|
|
for i, test := range tests {
|
|
var html string
|
|
if err := Run(ctx, OuterHTML(test.sel, &html)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
if html == "" {
|
|
t.Fatalf("test %d: OuterHTML is empty", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScrollIntoView(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := testAllocate(t, "image.html")
|
|
defer cancel()
|
|
|
|
tests := []struct {
|
|
sel string
|
|
by QueryOption
|
|
}{
|
|
{"/html/body/img", BySearch},
|
|
{"img", ByQueryAll},
|
|
{"img", ByQuery},
|
|
{"#icon-github", ByID},
|
|
}
|
|
for i, test := range tests {
|
|
if err := Run(ctx, ScrollIntoView(test.sel, test.by)); err != nil {
|
|
t.Fatalf("test %d got error: %v", i, err)
|
|
}
|
|
|
|
// TODO test scroll event
|
|
}
|
|
}
|
|
|
|
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>`
|
|
)
|