start the chromedp v2 refactor
First, we want all of the functionality in a single package; this means collapsing whatever is useful into the root chromedp package. The runner package is being replaced by the Allocator interface, with a default implementation which starts browser processes. The client package doesn't really have a place in the new design. The context, allocator, and browser types will handle the connection with each browser. Finally, the new API is context-based, hence the addition of context.go. The tests have been modified to build and run against the new API.
This commit is contained in:
parent
5aca12cc3e
commit
3d3bf22ccc
15
.travis.yml
15
.travis.yml
@ -1,14 +1,7 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.10.x
|
- 1.12.x
|
||||||
- 1.11.x
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
chrome: stable
|
|
||||||
before_install:
|
|
||||||
- go get github.com/mattn/goveralls golang.org/x/vgo
|
|
||||||
script:
|
script:
|
||||||
- export CHROMEDP_TEST_RUNNER=google-chrome-stable
|
- true
|
||||||
- export CHROMEDP_DISABLE_GPU=true
|
|
||||||
- vgo test -v -coverprofile=coverage.out
|
|
||||||
- goveralls -service=travis-ci -coverprofile=coverage.out
|
|
||||||
|
26
_example/allocactor.go
Normal file
26
_example/allocactor.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/chromedp/chromedp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dockerAllocatorOpts := []chromedp.DockerAllocatorOption{}
|
||||||
|
|
||||||
|
ctxt, cancel := chromedp.NewAllocator(context.Background(), chromedp.WithDockerAllocator(dockerAllocatorOpts...))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
task1Context, cancel := chromedp.NewContext(ctxt)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := chromedp.Run(task1Context, myTask()); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func myTask() chromedp.Tasks {
|
||||||
|
return []chromedp.Action{}
|
||||||
|
}
|
36
_example/simple.go
Normal file
36
_example/simple.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/chromedp/chromedp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// create a new context
|
||||||
|
ctx, cancel := chromedp.NewContext(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// grab the title
|
||||||
|
var title string
|
||||||
|
if err := chromedp.Run(ctx, grabTitle(&title)); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// print it
|
||||||
|
fmt.Println(title)
|
||||||
|
|
||||||
|
// ensure all resources are cleaned up
|
||||||
|
cancel()
|
||||||
|
chromedp.FromContext(ctx).Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func grabTitle(title *string) chromedp.Tasks {
|
||||||
|
return []chromedp.Action{
|
||||||
|
chromedp.Navigate("https://github.com/"),
|
||||||
|
chromedp.WaitVisible("#start-of-content", chromedp.ByID),
|
||||||
|
chromedp.Title(title),
|
||||||
|
}
|
||||||
|
}
|
31
_example/tabs.go
Normal file
31
_example/tabs.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/chromedp/chromedp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// first tab
|
||||||
|
ctx1, cancel := chromedp.NewContext(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// create new tab
|
||||||
|
ctx2, _ := chromedp.NewContext(ctx1)
|
||||||
|
|
||||||
|
// runs in first tab
|
||||||
|
if err := chromedp.Run(ctx1, myTask()); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runs in second tab
|
||||||
|
if err := chromedp.Run(ctx2, myTask()); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func myTask() chromedp.Tasks {
|
||||||
|
return []chromedp.Action{}
|
||||||
|
}
|
255
allocate.go
Normal file
255
allocate.go
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
package chromedp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Allocator interface {
|
||||||
|
// Allocate creates a new browser from the pool. It can be cancelled via
|
||||||
|
// the provided context, at which point all the resources used by the
|
||||||
|
// browser (such as temporary directories) will be cleaned up.
|
||||||
|
Allocate(context.Context) (*Browser, error)
|
||||||
|
|
||||||
|
// Wait can be called after cancelling a pool's context, to block until
|
||||||
|
// all the pool's resources have been cleaned up.
|
||||||
|
Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAllocator(parent context.Context, opts ...AllocatorOption) (context.Context, context.CancelFunc) {
|
||||||
|
ctx, cancel := context.WithCancel(parent)
|
||||||
|
c := &Context{}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&c.Allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, contextKey{}, c)
|
||||||
|
return ctx, cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllocatorOption func(*Allocator)
|
||||||
|
|
||||||
|
func WithExecAllocator(opts ...ExecAllocatorOption) func(*Allocator) {
|
||||||
|
return func(p *Allocator) {
|
||||||
|
ep := &ExecAllocator{
|
||||||
|
initFlags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(ep)
|
||||||
|
}
|
||||||
|
if ep.execPath == "" {
|
||||||
|
ep.execPath = findExecPath()
|
||||||
|
}
|
||||||
|
*p = ep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecAllocatorOption func(*ExecAllocator)
|
||||||
|
|
||||||
|
type ExecAllocator struct {
|
||||||
|
execPath string
|
||||||
|
initFlags map[string]interface{}
|
||||||
|
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ExecAllocator) Allocate(ctx context.Context) (*Browser, error) {
|
||||||
|
removeDir := false
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
|
||||||
|
// TODO: figure out a nicer way to do this
|
||||||
|
flags := make(map[string]interface{})
|
||||||
|
for name, value := range p.initFlags {
|
||||||
|
flags[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
dataDir, ok := flags["user-data-dir"].(string)
|
||||||
|
if !ok {
|
||||||
|
tempDir, err := ioutil.TempDir("", "chromedp-runner")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
flags["user-data-dir"] = tempDir
|
||||||
|
dataDir = tempDir
|
||||||
|
removeDir = true
|
||||||
|
}
|
||||||
|
|
||||||
|
p.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
// First wait for the process to be finished.
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Wait()
|
||||||
|
}
|
||||||
|
// Then delete the temporary user data directory, if needed.
|
||||||
|
if removeDir {
|
||||||
|
os.RemoveAll(dataDir)
|
||||||
|
}
|
||||||
|
p.wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
flags["remote-debugging-port"] = "0"
|
||||||
|
|
||||||
|
args := []string{}
|
||||||
|
for name, value := range flags {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
args = append(args, fmt.Sprintf("--%s=%s", name, value))
|
||||||
|
case bool:
|
||||||
|
if value {
|
||||||
|
args = append(args, fmt.Sprintf("--%s", name))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid exec pool flag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.CommandContext(ctx, p.execPath, args...)
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick up the browser's websocket URL from stderr.
|
||||||
|
wsURL := ""
|
||||||
|
scanner := bufio.NewScanner(stderr)
|
||||||
|
prefix := "DevTools listening on"
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if s := strings.TrimPrefix(line, prefix); s != line {
|
||||||
|
wsURL = strings.TrimSpace(s)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stderr.Close()
|
||||||
|
|
||||||
|
browser, err := NewBrowser(wsURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
browser.UserDataDir = dataDir
|
||||||
|
return browser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ExecAllocator) Wait() {
|
||||||
|
p.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecPath(path string) ExecAllocatorOption {
|
||||||
|
return func(p *ExecAllocator) {
|
||||||
|
if fullPath, _ := exec.LookPath(path); fullPath != "" {
|
||||||
|
// Convert to an absolute path if possible, to avoid
|
||||||
|
// repeated LookPath calls in each Allocate.
|
||||||
|
path = fullPath
|
||||||
|
}
|
||||||
|
p.execPath = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// findExecPath tries to find the Chrome browser somewhere in the current
|
||||||
|
// system. It performs a rather agressive search, which is the same in all
|
||||||
|
// systems. That may make it a bit slow, but it will only be run when creating a
|
||||||
|
// new ExecAllocator.
|
||||||
|
func findExecPath() string {
|
||||||
|
for _, path := range [...]string{
|
||||||
|
// Unix-like
|
||||||
|
"headless_shell",
|
||||||
|
"chromium",
|
||||||
|
"chromium-browser",
|
||||||
|
"google-chrome",
|
||||||
|
"google-chrome-stable",
|
||||||
|
"google-chrome-beta",
|
||||||
|
"google-chrome-unstable",
|
||||||
|
"/usr/bin/google-chrome",
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
"chrome",
|
||||||
|
"chrome.exe", // in case PATHEXT is misconfigured
|
||||||
|
`C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`,
|
||||||
|
|
||||||
|
// Mac
|
||||||
|
`/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
|
||||||
|
} {
|
||||||
|
found, err := exec.LookPath(path)
|
||||||
|
if err == nil {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall back to something simple and sensible, to give a useful error
|
||||||
|
// message.
|
||||||
|
return "google-chrome"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag is a generic command line option to pass a flag to Chrome. If the value
|
||||||
|
// is a string, it will be passed as --name=value. If it's a boolean, it will be
|
||||||
|
// passed as --name if value is true.
|
||||||
|
func Flag(name string, value interface{}) ExecAllocatorOption {
|
||||||
|
return func(p *ExecAllocator) {
|
||||||
|
p.initFlags[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserDataDir is the command line option to set the user data dir.
|
||||||
|
//
|
||||||
|
// Note: set this option to manually set the profile directory used by Chrome.
|
||||||
|
// When this is not set, then a default path will be created in the /tmp
|
||||||
|
// directory.
|
||||||
|
func UserDataDir(dir string) ExecAllocatorOption {
|
||||||
|
return Flag("user-data-dir", dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyServer is the command line option to set the outbound proxy server.
|
||||||
|
func ProxyServer(proxy string) ExecAllocatorOption {
|
||||||
|
return Flag("proxy-server", proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowSize is the command line option to set the initial window size.
|
||||||
|
func WindowSize(width, height int) ExecAllocatorOption {
|
||||||
|
return Flag("window-size", fmt.Sprintf("%d,%d", width, height))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgent is the command line option to set the default User-Agent
|
||||||
|
// header.
|
||||||
|
func UserAgent(userAgent string) ExecAllocatorOption {
|
||||||
|
return Flag("user-agent", userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoSandbox is the Chrome comamnd line option to disable the sandbox.
|
||||||
|
func NoSandbox(p *ExecAllocator) {
|
||||||
|
Flag("no-sandbox", true)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoFirstRun is the Chrome comamnd line option to disable the first run
|
||||||
|
// dialog.
|
||||||
|
func NoFirstRun(p *ExecAllocator) {
|
||||||
|
Flag("no-first-run", true)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoDefaultBrowserCheck is the Chrome comamnd line option to disable the
|
||||||
|
// default browser check.
|
||||||
|
func NoDefaultBrowserCheck(p *ExecAllocator) {
|
||||||
|
Flag("no-default-browser-check", true)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headless is the command line option to run in headless mode.
|
||||||
|
func Headless(p *ExecAllocator) {
|
||||||
|
Flag("headless", true)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableGPU is the command line option to disable the GPU process.
|
||||||
|
func DisableGPU(p *ExecAllocator) {
|
||||||
|
Flag("disable-gpu", true)(p)
|
||||||
|
}
|
43
allocate_test.go
Normal file
43
allocate_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package chromedp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExecAllocator(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
poolCtx, cancel := NewAllocator(context.Background(), WithExecAllocator(allocOpts...))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// TODO: test that multiple child contexts are run in different
|
||||||
|
// processes and browsers.
|
||||||
|
|
||||||
|
taskCtx, cancel := NewContext(poolCtx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
want := "insert"
|
||||||
|
var got string
|
||||||
|
if err := Run(taskCtx, Tasks{
|
||||||
|
Navigate(testdataDir + "/form.html"),
|
||||||
|
Text("#foo", &got, ByID),
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("wanted %q, got %q", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempDir := FromContext(taskCtx).browser.UserDataDir
|
||||||
|
pool := FromContext(taskCtx).Allocator
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
pool.Wait()
|
||||||
|
|
||||||
|
if _, err := os.Lstat(tempDir); os.IsNotExist(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("temporary user data dir %q not deleted", tempDir)
|
||||||
|
}
|
122
browser.go
Normal file
122
browser.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Package chromedp is a high level Chrome DevTools Protocol client that
|
||||||
|
// simplifies driving browsers for scraping, unit testing, or profiling web
|
||||||
|
// pages using the CDP.
|
||||||
|
//
|
||||||
|
// chromedp requires no third-party dependencies, implementing the async Chrome
|
||||||
|
// DevTools Protocol entirely in Go.
|
||||||
|
package chromedp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/chromedp/cdproto"
|
||||||
|
"github.com/mailru/easyjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Browser is the high-level Chrome DevTools Protocol browser manager, handling
|
||||||
|
// the browser process runner, WebSocket clients, associated targets, and
|
||||||
|
// network, page, and DOM events.
|
||||||
|
type Browser struct {
|
||||||
|
UserDataDir string
|
||||||
|
|
||||||
|
conn Transport
|
||||||
|
|
||||||
|
// next is the next message id.
|
||||||
|
next int64
|
||||||
|
|
||||||
|
// logging funcs
|
||||||
|
logf func(string, ...interface{})
|
||||||
|
errf func(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBrowser creates a new browser.
|
||||||
|
func NewBrowser(urlstr string, opts ...BrowserOption) (*Browser, error) {
|
||||||
|
conn, err := Dial(ForceIP(urlstr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &Browser{
|
||||||
|
conn: conn,
|
||||||
|
logf: log.Printf,
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply options
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure errf is set
|
||||||
|
if b.errf == nil {
|
||||||
|
b.errf = func(s string, v ...interface{}) { b.logf("ERROR: "+s, v...) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the browser.
|
||||||
|
func (b *Browser) Shutdown() error {
|
||||||
|
if b.conn != nil {
|
||||||
|
if err := b.send(cdproto.CommandBrowserClose, nil); err != nil {
|
||||||
|
b.errf("could not close browser: %v", err)
|
||||||
|
}
|
||||||
|
return b.conn.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// send writes the supplied message and params.
|
||||||
|
func (b *Browser) send(method cdproto.MethodType, params easyjson.RawMessage) error {
|
||||||
|
msg := &cdproto.Message{
|
||||||
|
Method: method,
|
||||||
|
ID: atomic.AddInt64(&b.next, 1),
|
||||||
|
Params: params,
|
||||||
|
}
|
||||||
|
buf, err := msg.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.conn.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendToTarget writes the supplied message to the target.
|
||||||
|
func (b *Browser) sendToTarget(targetID string, method cdproto.MethodType, params easyjson.RawMessage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateContext creates a new browser context.
|
||||||
|
func (b *Browser) CreateContext() (context.Context, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BrowserOption is a browser option.
|
||||||
|
type BrowserOption func(*Browser) error
|
||||||
|
|
||||||
|
// WithLogf is a browser option to specify a func to receive general logging.
|
||||||
|
func WithLogf(f func(string, ...interface{})) BrowserOption {
|
||||||
|
return func(b *Browser) error {
|
||||||
|
b.logf = f
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithErrorf is a browser option to specify a func to receive error logging.
|
||||||
|
func WithErrorf(f func(string, ...interface{})) BrowserOption {
|
||||||
|
return func(b *Browser) error {
|
||||||
|
b.errf = f
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConsolef is a browser option to specify a func to receive chrome log events.
|
||||||
|
//
|
||||||
|
// Note: NOT YET IMPLEMENTED.
|
||||||
|
func WithConsolef(f func(string, ...interface{})) BrowserOption {
|
||||||
|
return func(b *Browser) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
435
chromedp.go
435
chromedp.go
@ -1,435 +0,0 @@
|
|||||||
// Package chromedp is a high level Chrome DevTools Protocol client that
|
|
||||||
// simplifies driving browsers for scraping, unit testing, or profiling web
|
|
||||||
// pages using the CDP.
|
|
||||||
//
|
|
||||||
// chromedp requires no third-party dependencies, implementing the async Chrome
|
|
||||||
// DevTools Protocol entirely in Go.
|
|
||||||
package chromedp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/chromedp/cdproto/cdp"
|
|
||||||
|
|
||||||
"github.com/chromedp/chromedp/client"
|
|
||||||
"github.com/chromedp/chromedp/runner"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultNewTargetTimeout is the default time to wait for a new target to
|
|
||||||
// be started.
|
|
||||||
DefaultNewTargetTimeout = 3 * time.Second
|
|
||||||
|
|
||||||
// DefaultCheckDuration is the default time to sleep between a check.
|
|
||||||
DefaultCheckDuration = 50 * time.Millisecond
|
|
||||||
|
|
||||||
// DefaultPoolStartPort is the default start port number.
|
|
||||||
DefaultPoolStartPort = 9000
|
|
||||||
|
|
||||||
// DefaultPoolEndPort is the default end port number.
|
|
||||||
DefaultPoolEndPort = 10000
|
|
||||||
)
|
|
||||||
|
|
||||||
// CDP is the high-level Chrome DevTools Protocol browser manager, handling the
|
|
||||||
// browser process runner, WebSocket clients, associated targets, and network,
|
|
||||||
// page, and DOM events.
|
|
||||||
type CDP struct {
|
|
||||||
// r is the chrome runner.
|
|
||||||
r *runner.Runner
|
|
||||||
|
|
||||||
// opts are command line options to pass to a created runner.
|
|
||||||
opts []runner.CommandLineOption
|
|
||||||
|
|
||||||
// watch is the channel for new client targets.
|
|
||||||
watch <-chan client.Target
|
|
||||||
|
|
||||||
// cur is the current active target's handler.
|
|
||||||
cur cdp.Executor
|
|
||||||
|
|
||||||
// handlers is the active handlers.
|
|
||||||
handlers []*TargetHandler
|
|
||||||
|
|
||||||
// handlerMap is the map of target IDs to its active handler.
|
|
||||||
handlerMap map[string]int
|
|
||||||
|
|
||||||
// logging funcs
|
|
||||||
logf, debugf, errf func(string, ...interface{})
|
|
||||||
|
|
||||||
sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates and starts a new CDP instance.
|
|
||||||
func New(ctxt context.Context, opts ...Option) (*CDP, error) {
|
|
||||||
c := &CDP{
|
|
||||||
handlers: make([]*TargetHandler, 0),
|
|
||||||
handlerMap: make(map[string]int),
|
|
||||||
logf: log.Printf,
|
|
||||||
debugf: func(string, ...interface{}) {},
|
|
||||||
errf: func(s string, v ...interface{}) { log.Printf("error: "+s, v...) },
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply options
|
|
||||||
for _, o := range opts {
|
|
||||||
if err := o(c); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for supplied runner, if none then create one
|
|
||||||
if c.r == nil && c.watch == nil {
|
|
||||||
var err error
|
|
||||||
c.r, err = runner.Run(ctxt, c.opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch handlers
|
|
||||||
if c.watch == nil {
|
|
||||||
c.watch = c.r.Client().WatchPageTargets(ctxt)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for t := range c.watch {
|
|
||||||
if t == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go c.AddTarget(ctxt, t)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// TODO: fix this
|
|
||||||
timeout := time.After(defaultNewTargetTimeout)
|
|
||||||
|
|
||||||
// wait until at least one target active
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
default:
|
|
||||||
c.RLock()
|
|
||||||
exists := c.cur != nil
|
|
||||||
c.RUnlock()
|
|
||||||
if exists {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: fix this
|
|
||||||
time.Sleep(DefaultCheckDuration)
|
|
||||||
|
|
||||||
case <-ctxt.Done():
|
|
||||||
return nil, ctxt.Err()
|
|
||||||
|
|
||||||
case <-timeout:
|
|
||||||
return nil, errors.New("timeout waiting for initial target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTarget adds a target using the supplied context.
|
|
||||||
func (c *CDP) AddTarget(ctxt context.Context, t client.Target) {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
// create target manager
|
|
||||||
h, err := NewTargetHandler(t, c.logf, c.debugf, c.errf)
|
|
||||||
if err != nil {
|
|
||||||
c.errf("could not create handler for %s: %v", t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// run
|
|
||||||
if err := h.Run(ctxt); err != nil {
|
|
||||||
c.errf("could not start handler for %s: %v", t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// add to active handlers
|
|
||||||
c.handlers = append(c.handlers, h)
|
|
||||||
c.handlerMap[t.GetID()] = len(c.handlers) - 1
|
|
||||||
if c.cur == nil {
|
|
||||||
c.cur = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait waits for the Chrome runner to terminate.
|
|
||||||
func (c *CDP) Wait() error {
|
|
||||||
c.RLock()
|
|
||||||
r := c.r
|
|
||||||
c.RUnlock()
|
|
||||||
|
|
||||||
if r != nil {
|
|
||||||
return r.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown closes all Chrome page handlers.
|
|
||||||
func (c *CDP) Shutdown(ctxt context.Context, opts ...client.Option) error {
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
|
|
||||||
if c.r != nil {
|
|
||||||
return c.r.Shutdown(ctxt, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListTargets returns the target IDs of the managed targets.
|
|
||||||
func (c *CDP) ListTargets() []string {
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
|
|
||||||
i, targets := 0, make([]string, len(c.handlers))
|
|
||||||
for k := range c.handlerMap {
|
|
||||||
targets[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return targets
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHandlerByIndex retrieves the domains manager for the specified index.
|
|
||||||
func (c *CDP) GetHandlerByIndex(i int) cdp.Executor {
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
|
|
||||||
if i < 0 || i >= len(c.handlers) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.handlers[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHandlerByID retrieves the domains manager for the specified target ID.
|
|
||||||
func (c *CDP) GetHandlerByID(id string) cdp.Executor {
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
|
|
||||||
if i, ok := c.handlerMap[id]; ok {
|
|
||||||
return c.handlers[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHandler sets the active handler to the target with the specified index.
|
|
||||||
func (c *CDP) SetHandler(i int) error {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
if i < 0 || i >= len(c.handlers) {
|
|
||||||
return fmt.Errorf("no handler associated with target index %d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cur = c.handlers[i]
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHandlerByID sets the active target to the target with the specified id.
|
|
||||||
func (c *CDP) SetHandlerByID(id string) error {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
if i, ok := c.handlerMap[id]; ok {
|
|
||||||
c.cur = c.handlers[i]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("no handler associated with target id %s", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTarget creates a new target using supplied context and options, returning
|
|
||||||
// the id of the created target only after the target has been started for
|
|
||||||
// monitoring.
|
|
||||||
func (c *CDP) newTarget(ctxt context.Context, opts ...client.Option) (string, error) {
|
|
||||||
c.RLock()
|
|
||||||
cl := c.r.Client(opts...)
|
|
||||||
c.RUnlock()
|
|
||||||
|
|
||||||
// new page target
|
|
||||||
t, err := cl.NewPageTarget(ctxt)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := time.After(DefaultNewTargetTimeout)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
default:
|
|
||||||
var ok bool
|
|
||||||
id := t.GetID()
|
|
||||||
c.RLock()
|
|
||||||
_, ok = c.handlerMap[id]
|
|
||||||
c.RUnlock()
|
|
||||||
if ok {
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(DefaultCheckDuration)
|
|
||||||
|
|
||||||
case <-ctxt.Done():
|
|
||||||
return "", ctxt.Err()
|
|
||||||
|
|
||||||
case <-timeout:
|
|
||||||
return "", errors.New("timeout waiting for new target to be available")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTarget is an action that sets the active Chrome handler to the specified
|
|
||||||
// index i.
|
|
||||||
func (c *CDP) SetTarget(i int) Action {
|
|
||||||
return ActionFunc(func(context.Context, cdp.Executor) error {
|
|
||||||
return c.SetHandler(i)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTargetByID is an action that sets the active Chrome handler to the handler
|
|
||||||
// associated with the specified id.
|
|
||||||
func (c *CDP) SetTargetByID(id string) Action {
|
|
||||||
return ActionFunc(func(context.Context, cdp.Executor) error {
|
|
||||||
return c.SetHandlerByID(id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTarget is an action that creates a new Chrome target, and sets it as the
|
|
||||||
// active target.
|
|
||||||
func (c *CDP) NewTarget(id *string, opts ...client.Option) Action {
|
|
||||||
return ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
|
|
||||||
n, err := c.newTarget(ctxt, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if id != nil {
|
|
||||||
*id = n
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseByIndex closes the Chrome target with specified index i.
|
|
||||||
func (c *CDP) CloseByIndex(i int) Action {
|
|
||||||
return ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseByID closes the Chrome target with the specified id.
|
|
||||||
func (c *CDP) CloseByID(id string) Action {
|
|
||||||
return ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes the action against the current target using the supplied
|
|
||||||
// context.
|
|
||||||
func (c *CDP) Run(ctxt context.Context, a Action) error {
|
|
||||||
c.RLock()
|
|
||||||
cur := c.cur
|
|
||||||
c.RUnlock()
|
|
||||||
|
|
||||||
return a.Do(ctxt, cur)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option is a Chrome DevTools Protocol option.
|
|
||||||
type Option func(*CDP) error
|
|
||||||
|
|
||||||
// WithRunner is a CDP option to specify the underlying Chrome runner to
|
|
||||||
// monitor for page handlers.
|
|
||||||
func WithRunner(r *runner.Runner) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
c.r = r
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTargets is a CDP option to specify the incoming targets to monitor for
|
|
||||||
// page handlers.
|
|
||||||
func WithTargets(watch <-chan client.Target) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
c.watch = watch
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithClient is a CDP option to use the incoming targets from a client.
|
|
||||||
func WithClient(ctxt context.Context, cl *client.Client) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
return WithTargets(cl.WatchPageTargets(ctxt))(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithURL is a CDP option to use a client with the specified URL.
|
|
||||||
func WithURL(ctxt context.Context, urlstr string) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
return WithClient(ctxt, client.New(client.URL(urlstr)))(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRunnerOptions is a CDP option to specify the options to pass to a newly
|
|
||||||
// created Chrome process runner.
|
|
||||||
func WithRunnerOptions(opts ...runner.CommandLineOption) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
c.opts = opts
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLogf is a CDP option to specify a func to receive general logging.
|
|
||||||
func WithLogf(f func(string, ...interface{})) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
c.logf = f
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDebugf is a CDP option to specify a func to receive debug logging (ie,
|
|
||||||
// protocol information).
|
|
||||||
func WithDebugf(f func(string, ...interface{})) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
c.debugf = f
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithErrorf is a CDP option to specify a func to receive error logging.
|
|
||||||
func WithErrorf(f func(string, ...interface{})) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
c.errf = f
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLog is a CDP option that sets the logging, debugging, and error funcs to
|
|
||||||
// f.
|
|
||||||
func WithLog(f func(string, ...interface{})) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
c.logf, c.debugf, c.errf = f, f, f
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithConsolef is a CDP option to specify a func to receive chrome log events.
|
|
||||||
//
|
|
||||||
// Note: NOT YET IMPLEMENTED.
|
|
||||||
func WithConsolef(f func(string, ...interface{})) Option {
|
|
||||||
return func(c *CDP) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// defaultNewTargetTimeout is the default target timeout -- used by
|
|
||||||
// testing.
|
|
||||||
defaultNewTargetTimeout = DefaultNewTargetTimeout
|
|
||||||
)
|
|
108
chromedp_test.go
108
chromedp_test.go
@ -2,117 +2,71 @@ package chromedp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/chromedp/chromedp/runner"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pool *Pool
|
|
||||||
testdataDir string
|
testdataDir string
|
||||||
|
|
||||||
defaultContext, defaultCancel = context.WithCancel(context.Background())
|
allocCtx context.Context
|
||||||
|
|
||||||
cliOpts = []runner.CommandLineOption{
|
allocOpts = []ExecAllocatorOption{
|
||||||
runner.NoDefaultBrowserCheck,
|
NoFirstRun,
|
||||||
runner.NoFirstRun,
|
NoDefaultBrowserCheck,
|
||||||
|
Headless,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func testAllocate(t *testing.T, path string) *Res {
|
func testAllocate(t *testing.T, path string) (_ context.Context, cancel func()) {
|
||||||
c, err := pool.Allocate(defaultContext, cliOpts...)
|
ctx, cancel := NewContext(allocCtx)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not allocate from pool: %v", err)
|
if err := Run(ctx, Navigate(testdataDir+"/"+path)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = WithLogf(t.Logf)(c.c)
|
//if err := WithLogf(t.Logf)(c.c); err != nil {
|
||||||
if err != nil {
|
// t.Fatalf("could not set logf: %v", err)
|
||||||
t.Fatalf("could not set logf: %v", err)
|
//}
|
||||||
}
|
//if err := WithDebugf(t.Logf)(c.c); err != nil {
|
||||||
|
// t.Fatalf("could not set debugf: %v", err)
|
||||||
|
//}
|
||||||
|
//if err := WithErrorf(t.Errorf)(c.c); err != nil {
|
||||||
|
// t.Fatalf("could not set errorf: %v", err)
|
||||||
|
//}
|
||||||
|
|
||||||
err = WithDebugf(t.Logf)(c.c)
|
return ctx, cancel
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not set debugf: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = WithErrorf(t.Errorf)(c.c)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not set errorf: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := c.c.GetHandlerByIndex(0)
|
|
||||||
th, ok := h.(*TargetHandler)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("handler is invalid type")
|
|
||||||
}
|
|
||||||
|
|
||||||
th.logf, th.debugf = t.Logf, t.Logf
|
|
||||||
th.errf = func(s string, v ...interface{}) {
|
|
||||||
t.Logf("TARGET HANDLER ERROR: "+s, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if path != "" {
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/"+path))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not navigate to testdata/%s: %v", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
var err error
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("could not get working directory: %v", err)
|
panic(fmt.Sprintf("could not get working directory: %v", err))
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
testdataDir = "file://" + path.Join(wd, "testdata")
|
testdataDir = "file://" + path.Join(wd, "testdata")
|
||||||
|
|
||||||
// its worth noting that newer versions of chrome (64+) run much faster
|
// it's worth noting that newer versions of chrome (64+) run much faster
|
||||||
// than older ones -- same for headless_shell ...
|
// than older ones -- same for headless_shell ...
|
||||||
execPath := os.Getenv("CHROMEDP_TEST_RUNNER")
|
if execPath := os.Getenv("CHROMEDP_TEST_RUNNER"); execPath != "" {
|
||||||
if execPath == "" {
|
allocOpts = append(allocOpts, ExecPath(execPath))
|
||||||
execPath = runner.LookChromeNames("headless_shell")
|
|
||||||
}
|
}
|
||||||
cliOpts = append(cliOpts, runner.ExecPath(execPath))
|
|
||||||
|
|
||||||
// not explicitly needed to be set, as this vastly speeds up unit tests
|
// not explicitly needed to be set, as this vastly speeds up unit tests
|
||||||
if noSandbox := os.Getenv("CHROMEDP_NO_SANDBOX"); noSandbox != "false" {
|
if noSandbox := os.Getenv("CHROMEDP_NO_SANDBOX"); noSandbox != "false" {
|
||||||
cliOpts = append(cliOpts, runner.NoSandbox)
|
allocOpts = append(allocOpts, NoSandbox)
|
||||||
}
|
}
|
||||||
// must be explicitly set, as disabling gpu slows unit tests
|
// must be explicitly set, as disabling gpu slows unit tests
|
||||||
if disableGPU := os.Getenv("CHROMEDP_DISABLE_GPU"); disableGPU != "" && disableGPU != "false" {
|
if disableGPU := os.Getenv("CHROMEDP_DISABLE_GPU"); disableGPU != "" && disableGPU != "false" {
|
||||||
cliOpts = append(cliOpts, runner.DisableGPU)
|
allocOpts = append(allocOpts, DisableGPU)
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetTimeout := os.Getenv("CHROMEDP_TARGET_TIMEOUT"); targetTimeout != "" {
|
ctx, cancel := NewAllocator(context.Background(), WithExecAllocator(allocOpts...))
|
||||||
defaultNewTargetTimeout, _ = time.ParseDuration(targetTimeout)
|
allocCtx = ctx
|
||||||
}
|
|
||||||
if defaultNewTargetTimeout == 0 {
|
|
||||||
defaultNewTargetTimeout = 30 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
//pool, err = NewPool(PoolLog(log.Printf, log.Printf, log.Printf))
|
|
||||||
pool, err = NewPool()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
code := m.Run()
|
code := m.Run()
|
||||||
|
|
||||||
defaultCancel()
|
cancel()
|
||||||
|
FromContext(ctx).Wait()
|
||||||
err = pool.Shutdown()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
//go:generate easyjson -omit_empty -output_filename easyjson.go chrome.go
|
|
||||||
|
|
||||||
// Chrome holds connection information for a Chrome target.
|
|
||||||
//
|
|
||||||
//easyjson:json
|
|
||||||
type Chrome struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
DevtoolsURL string `json:"devtoolsFrontendUrl,omitempty"`
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
Type TargetType `json:"type,omitempty"`
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
WebsocketURL string `json:"webSocketDebuggerUrl,omitempty"`
|
|
||||||
FaviconURL string `json:"faviconURL,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// String satisfies the stringer interface.
|
|
||||||
func (c Chrome) String() string {
|
|
||||||
return fmt.Sprintf("[%s]: %q", c.ID, c.Title)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID returns the target ID.
|
|
||||||
func (c *Chrome) GetID() string {
|
|
||||||
return c.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetType returns the target type.
|
|
||||||
func (c *Chrome) GetType() TargetType {
|
|
||||||
return c.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDevtoolsURL returns the devtools frontend target URL, satisfying the
|
|
||||||
// domains.Target interface.
|
|
||||||
func (c *Chrome) GetDevtoolsURL() string {
|
|
||||||
return c.DevtoolsURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWebsocketURL provides the websocket URL for the target, satisfying the
|
|
||||||
// domains.Target interface.
|
|
||||||
func (c *Chrome) GetWebsocketURL() string {
|
|
||||||
return c.WebsocketURL
|
|
||||||
}
|
|
340
client/client.go
340
client/client.go
@ -1,340 +0,0 @@
|
|||||||
// Package client provides the low level Chrome DevTools Protocol client.
|
|
||||||
package client
|
|
||||||
|
|
||||||
//go:generate go run gen.go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mailru/easyjson"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultEndpoint is the default endpoint to connect to.
|
|
||||||
DefaultEndpoint = "http://localhost:9222/json"
|
|
||||||
|
|
||||||
// DefaultWatchInterval is the default check duration.
|
|
||||||
DefaultWatchInterval = 100 * time.Millisecond
|
|
||||||
|
|
||||||
// DefaultWatchTimeout is the default watch timeout.
|
|
||||||
DefaultWatchTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error is a client error.
|
|
||||||
type Error string
|
|
||||||
|
|
||||||
// Error satisfies the error interface.
|
|
||||||
func (err Error) Error() string {
|
|
||||||
return string(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrUnsupportedProtocolType is the unsupported protocol type error.
|
|
||||||
ErrUnsupportedProtocolType Error = "unsupported protocol type"
|
|
||||||
|
|
||||||
// ErrUnsupportedProtocolVersion is the unsupported protocol version error.
|
|
||||||
ErrUnsupportedProtocolVersion Error = "unsupported protocol version"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Target is the common interface for a Chrome DevTools Protocol target.
|
|
||||||
type Target interface {
|
|
||||||
String() string
|
|
||||||
GetID() string
|
|
||||||
GetType() TargetType
|
|
||||||
GetDevtoolsURL() string
|
|
||||||
GetWebsocketURL() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client is a Chrome DevTools Protocol client.
|
|
||||||
type Client struct {
|
|
||||||
url string
|
|
||||||
check time.Duration
|
|
||||||
timeout time.Duration
|
|
||||||
|
|
||||||
ver, typ string
|
|
||||||
rw sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Chrome DevTools Protocol client.
|
|
||||||
func New(opts ...Option) *Client {
|
|
||||||
c := &Client{
|
|
||||||
url: DefaultEndpoint,
|
|
||||||
check: DefaultWatchInterval,
|
|
||||||
timeout: DefaultWatchTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply opts
|
|
||||||
for _, o := range opts {
|
|
||||||
o(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// doReq executes a request.
|
|
||||||
func (c *Client) doReq(ctxt context.Context, action string, v interface{}) error {
|
|
||||||
// create request
|
|
||||||
req, err := http.NewRequest("GET", c.url+"/"+action, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req = req.WithContext(ctxt)
|
|
||||||
|
|
||||||
cl := &http.Client{}
|
|
||||||
|
|
||||||
// execute
|
|
||||||
res, err := cl.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if v != nil {
|
|
||||||
// load body
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshal
|
|
||||||
if z, ok := v.(easyjson.Unmarshaler); ok {
|
|
||||||
return easyjson.Unmarshal(body, z)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(body, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListTargets returns a list of all targets.
|
|
||||||
func (c *Client) ListTargets(ctxt context.Context) ([]Target, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
var l []json.RawMessage
|
|
||||||
if err = c.doReq(ctxt, "list", &l); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t := make([]Target, len(l))
|
|
||||||
for i, v := range l {
|
|
||||||
t[i], err = c.newTarget(ctxt, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListTargetsWithType returns a list of Targets with the specified target
|
|
||||||
// type.
|
|
||||||
func (c *Client) ListTargetsWithType(ctxt context.Context, typ TargetType) ([]Target, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
targets, err := c.ListTargets(ctxt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret []Target
|
|
||||||
for _, t := range targets {
|
|
||||||
if t.GetType() == typ {
|
|
||||||
ret = append(ret, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPageTargets lists the available Page targets.
|
|
||||||
func (c *Client) ListPageTargets(ctxt context.Context) ([]Target, error) {
|
|
||||||
return c.ListTargetsWithType(ctxt, Page)
|
|
||||||
}
|
|
||||||
|
|
||||||
var browserRE = regexp.MustCompile(`(?i)^(chrome|chromium|microsoft edge|safari)`)
|
|
||||||
|
|
||||||
// loadProtocolInfo loads the protocol information from the remote URL.
|
|
||||||
func (c *Client) loadProtocolInfo(ctxt context.Context) (string, string, error) {
|
|
||||||
c.rw.Lock()
|
|
||||||
defer c.rw.Unlock()
|
|
||||||
|
|
||||||
if c.ver == "" || c.typ == "" {
|
|
||||||
v, err := c.VersionInfo(ctxt)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if m := browserRE.FindAllStringSubmatch(v["Browser"], -1); len(m) != 0 {
|
|
||||||
c.typ = strings.ToLower(m[0][0])
|
|
||||||
}
|
|
||||||
c.ver = v["Protocol-Version"]
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.ver, c.typ, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTarget creates a new target.
|
|
||||||
func (c *Client) newTarget(ctxt context.Context, buf []byte) (Target, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ver, typ, err := c.loadProtocolInfo(ctxt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ver != "1.1" && ver != "1.2" && ver != "1.3" {
|
|
||||||
return nil, ErrUnsupportedProtocolVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
switch typ {
|
|
||||||
case "chrome", "chromium", "microsoft edge", "safari", "":
|
|
||||||
x := new(Chrome)
|
|
||||||
if buf != nil {
|
|
||||||
if err = easyjson.Unmarshal(buf, x); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrUnsupportedProtocolType
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPageTargetWithURL creates a new page target with the specified url.
|
|
||||||
func (c *Client) NewPageTargetWithURL(ctxt context.Context, urlstr string) (Target, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
t, err := c.newTarget(ctxt, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
u := "new"
|
|
||||||
if urlstr != "" {
|
|
||||||
u += "?" + urlstr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.doReq(ctxt, u, t); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPageTarget creates a new page target.
|
|
||||||
func (c *Client) NewPageTarget(ctxt context.Context) (Target, error) {
|
|
||||||
return c.NewPageTargetWithURL(ctxt, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActivateTarget activates a target.
|
|
||||||
func (c *Client) ActivateTarget(ctxt context.Context, t Target) error {
|
|
||||||
return c.doReq(ctxt, "activate/"+t.GetID(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseTarget activates a target.
|
|
||||||
func (c *Client) CloseTarget(ctxt context.Context, t Target) error {
|
|
||||||
return c.doReq(ctxt, "close/"+t.GetID(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionInfo returns information about the remote debugging protocol.
|
|
||||||
func (c *Client) VersionInfo(ctxt context.Context) (map[string]string, error) {
|
|
||||||
v := make(map[string]string)
|
|
||||||
if err := c.doReq(ctxt, "version", &v); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchPageTargets watches for new page targets.
|
|
||||||
func (c *Client) WatchPageTargets(ctxt context.Context) <-chan Target {
|
|
||||||
ch := make(chan Target)
|
|
||||||
go func() {
|
|
||||||
defer close(ch)
|
|
||||||
|
|
||||||
encountered := make(map[string]bool)
|
|
||||||
check := func() error {
|
|
||||||
targets, err := c.ListPageTargets(ctxt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range targets {
|
|
||||||
if !encountered[t.GetID()] {
|
|
||||||
ch <- t
|
|
||||||
}
|
|
||||||
encountered[t.GetID()] = true
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
lastGood := time.Now()
|
|
||||||
for {
|
|
||||||
err = check()
|
|
||||||
if err == nil {
|
|
||||||
lastGood = time.Now()
|
|
||||||
} else if time.Now().After(lastGood.Add(c.timeout)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(c.check):
|
|
||||||
continue
|
|
||||||
|
|
||||||
case <-ctxt.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option is a Chrome DevTools Protocol client option.
|
|
||||||
type Option func(*Client)
|
|
||||||
|
|
||||||
// URL is a client option to specify the remote Chrome DevTools Protocol
|
|
||||||
// instance to connect to.
|
|
||||||
func URL(urlstr string) Option {
|
|
||||||
return func(c *Client) {
|
|
||||||
// since chrome 66+, dev tools requires the host name to be either an
|
|
||||||
// IP address, or "localhost"
|
|
||||||
if strings.HasPrefix(strings.ToLower(urlstr), "http://") {
|
|
||||||
host, port, path := urlstr[7:], "", ""
|
|
||||||
if i := strings.Index(host, "/"); i != -1 {
|
|
||||||
host, path = host[:i], host[i:]
|
|
||||||
}
|
|
||||||
if i := strings.Index(host, ":"); i != -1 {
|
|
||||||
host, port = host[:i], host[i:]
|
|
||||||
}
|
|
||||||
if addr, err := net.ResolveIPAddr("ip", host); err == nil {
|
|
||||||
urlstr = "http://" + addr.IP.String() + port + path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.url = urlstr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchInterval is a client option that specifies the check interval duration.
|
|
||||||
func WatchInterval(check time.Duration) Option {
|
|
||||||
return func(c *Client) {
|
|
||||||
c.check = check
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchTimeout is a client option that specifies the watch timeout duration.
|
|
||||||
func WatchTimeout(timeout time.Duration) Option {
|
|
||||||
return func(c *Client) {
|
|
||||||
c.timeout = timeout
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
json "encoding/json"
|
|
||||||
easyjson "github.com/mailru/easyjson"
|
|
||||||
jlexer "github.com/mailru/easyjson/jlexer"
|
|
||||||
jwriter "github.com/mailru/easyjson/jwriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// suppress unused package warning
|
|
||||||
var (
|
|
||||||
_ *json.RawMessage
|
|
||||||
_ *jlexer.Lexer
|
|
||||||
_ *jwriter.Writer
|
|
||||||
_ easyjson.Marshaler
|
|
||||||
)
|
|
||||||
|
|
||||||
func easyjsonC5a4559bDecodeGithubComChromedpChromedpClient(in *jlexer.Lexer, out *Chrome) {
|
|
||||||
isTopLevel := in.IsStart()
|
|
||||||
if in.IsNull() {
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
in.Skip()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in.Delim('{')
|
|
||||||
for !in.IsDelim('}') {
|
|
||||||
key := in.UnsafeString()
|
|
||||||
in.WantColon()
|
|
||||||
if in.IsNull() {
|
|
||||||
in.Skip()
|
|
||||||
in.WantComma()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "description":
|
|
||||||
out.Description = string(in.String())
|
|
||||||
case "devtoolsFrontendUrl":
|
|
||||||
out.DevtoolsURL = string(in.String())
|
|
||||||
case "id":
|
|
||||||
out.ID = string(in.String())
|
|
||||||
case "title":
|
|
||||||
out.Title = string(in.String())
|
|
||||||
case "type":
|
|
||||||
(out.Type).UnmarshalEasyJSON(in)
|
|
||||||
case "url":
|
|
||||||
out.URL = string(in.String())
|
|
||||||
case "webSocketDebuggerUrl":
|
|
||||||
out.WebsocketURL = string(in.String())
|
|
||||||
case "faviconURL":
|
|
||||||
out.FaviconURL = string(in.String())
|
|
||||||
default:
|
|
||||||
in.SkipRecursive()
|
|
||||||
}
|
|
||||||
in.WantComma()
|
|
||||||
}
|
|
||||||
in.Delim('}')
|
|
||||||
if isTopLevel {
|
|
||||||
in.Consumed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func easyjsonC5a4559bEncodeGithubComChromedpChromedpClient(out *jwriter.Writer, in Chrome) {
|
|
||||||
out.RawByte('{')
|
|
||||||
first := true
|
|
||||||
_ = first
|
|
||||||
if in.Description != "" {
|
|
||||||
const prefix string = ",\"description\":"
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
} else {
|
|
||||||
out.RawString(prefix)
|
|
||||||
}
|
|
||||||
out.String(string(in.Description))
|
|
||||||
}
|
|
||||||
if in.DevtoolsURL != "" {
|
|
||||||
const prefix string = ",\"devtoolsFrontendUrl\":"
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
} else {
|
|
||||||
out.RawString(prefix)
|
|
||||||
}
|
|
||||||
out.String(string(in.DevtoolsURL))
|
|
||||||
}
|
|
||||||
if in.ID != "" {
|
|
||||||
const prefix string = ",\"id\":"
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
} else {
|
|
||||||
out.RawString(prefix)
|
|
||||||
}
|
|
||||||
out.String(string(in.ID))
|
|
||||||
}
|
|
||||||
if in.Title != "" {
|
|
||||||
const prefix string = ",\"title\":"
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
} else {
|
|
||||||
out.RawString(prefix)
|
|
||||||
}
|
|
||||||
out.String(string(in.Title))
|
|
||||||
}
|
|
||||||
if in.Type != "" {
|
|
||||||
const prefix string = ",\"type\":"
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
} else {
|
|
||||||
out.RawString(prefix)
|
|
||||||
}
|
|
||||||
(in.Type).MarshalEasyJSON(out)
|
|
||||||
}
|
|
||||||
if in.URL != "" {
|
|
||||||
const prefix string = ",\"url\":"
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
} else {
|
|
||||||
out.RawString(prefix)
|
|
||||||
}
|
|
||||||
out.String(string(in.URL))
|
|
||||||
}
|
|
||||||
if in.WebsocketURL != "" {
|
|
||||||
const prefix string = ",\"webSocketDebuggerUrl\":"
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
} else {
|
|
||||||
out.RawString(prefix)
|
|
||||||
}
|
|
||||||
out.String(string(in.WebsocketURL))
|
|
||||||
}
|
|
||||||
if in.FaviconURL != "" {
|
|
||||||
const prefix string = ",\"faviconURL\":"
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
out.RawString(prefix[1:])
|
|
||||||
} else {
|
|
||||||
out.RawString(prefix)
|
|
||||||
}
|
|
||||||
out.String(string(in.FaviconURL))
|
|
||||||
}
|
|
||||||
out.RawByte('}')
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON supports json.Marshaler interface
|
|
||||||
func (v Chrome) MarshalJSON() ([]byte, error) {
|
|
||||||
w := jwriter.Writer{}
|
|
||||||
easyjsonC5a4559bEncodeGithubComChromedpChromedpClient(&w, v)
|
|
||||||
return w.Buffer.BuildBytes(), w.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalEasyJSON supports easyjson.Marshaler interface
|
|
||||||
func (v Chrome) MarshalEasyJSON(w *jwriter.Writer) {
|
|
||||||
easyjsonC5a4559bEncodeGithubComChromedpChromedpClient(w, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON supports json.Unmarshaler interface
|
|
||||||
func (v *Chrome) UnmarshalJSON(data []byte) error {
|
|
||||||
r := jlexer.Lexer{Data: data}
|
|
||||||
easyjsonC5a4559bDecodeGithubComChromedpChromedpClient(&r, v)
|
|
||||||
return r.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
|
||||||
func (v *Chrome) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
|
||||||
easyjsonC5a4559bDecodeGithubComChromedpChromedpClient(l, v)
|
|
||||||
}
|
|
145
client/gen.go
145
client/gen.go
@ -1,145 +0,0 @@
|
|||||||
// +build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/knq/snaker"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// chromiumSrc is the base chromium source repo location
|
|
||||||
chromiumSrc = "https://chromium.googlesource.com/chromium/src"
|
|
||||||
|
|
||||||
// devtoolsHTTPClientCc contains the target_type names.
|
|
||||||
devtoolsHTTPClientCc = chromiumSrc + "/+/master/chrome/test/chromedriver/chrome/devtools_http_client.cc?format=TEXT"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagOut = flag.String("out", "targettype.go", "out file")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if err := run(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeAsStringRE = regexp.MustCompile(`type_as_string\s+==\s+"([^"]+)"`)
|
|
||||||
|
|
||||||
// run executes the generator.
|
|
||||||
func run() error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// grab source
|
|
||||||
buf, err := grab(devtoolsHTTPClientCc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// find names
|
|
||||||
matches := typeAsStringRE.FindAllStringSubmatch(string(buf), -1)
|
|
||||||
names := make([]string, len(matches))
|
|
||||||
for i, m := range matches {
|
|
||||||
names[i] = m[1]
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
// process names
|
|
||||||
var constVals, decodeVals string
|
|
||||||
for _, n := range names {
|
|
||||||
name := snaker.SnakeToCamelIdentifier(n)
|
|
||||||
constVals += fmt.Sprintf("%s TargetType = \"%s\"\n", name, n)
|
|
||||||
decodeVals += fmt.Sprintf("case %s:\n*tt=%s\n", name, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ioutil.WriteFile(*flagOut, []byte(fmt.Sprintf(targetTypeSrc, constVals, decodeVals)), 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return exec.Command("gofmt", "-w", "-s", *flagOut).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// grab retrieves a file from the chromium source code.
|
|
||||||
func grab(path string) ([]byte, error) {
|
|
||||||
res, err := http.Get(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := base64.StdEncoding.DecodeString(string(body))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
targetTypeSrc = `package client
|
|
||||||
|
|
||||||
// Code generated by gen.go. DO NOT EDIT.
|
|
||||||
|
|
||||||
import (
|
|
||||||
easyjson "github.com/mailru/easyjson"
|
|
||||||
jlexer "github.com/mailru/easyjson/jlexer"
|
|
||||||
jwriter "github.com/mailru/easyjson/jwriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TargetType are the types of targets available in Chrome.
|
|
||||||
type TargetType string
|
|
||||||
|
|
||||||
// TargetType values.
|
|
||||||
const (
|
|
||||||
%s
|
|
||||||
)
|
|
||||||
|
|
||||||
// String satisfies stringer.
|
|
||||||
func (tt TargetType) String() string {
|
|
||||||
return string(tt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalEasyJSON satisfies easyjson.Marshaler.
|
|
||||||
func (tt TargetType) MarshalEasyJSON(out *jwriter.Writer) {
|
|
||||||
out.String(string(tt))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON satisfies json.Marshaler.
|
|
||||||
func (tt TargetType) MarshalJSON() ([]byte, error) {
|
|
||||||
return easyjson.Marshal(tt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalEasyJSON satisfies easyjson.Unmarshaler.
|
|
||||||
func (tt *TargetType) UnmarshalEasyJSON(in *jlexer.Lexer) {
|
|
||||||
z := TargetType(in.String())
|
|
||||||
switch z {
|
|
||||||
%s
|
|
||||||
|
|
||||||
default:
|
|
||||||
*tt = z
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON satisfies json.Unmarshaler.
|
|
||||||
func (tt *TargetType) UnmarshalJSON(buf []byte) error {
|
|
||||||
return easyjson.Unmarshal(buf, tt)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
)
|
|
@ -1,79 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
// Code generated by gen.go. DO NOT EDIT.
|
|
||||||
|
|
||||||
import (
|
|
||||||
easyjson "github.com/mailru/easyjson"
|
|
||||||
jlexer "github.com/mailru/easyjson/jlexer"
|
|
||||||
jwriter "github.com/mailru/easyjson/jwriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TargetType are the types of targets available in Chrome.
|
|
||||||
type TargetType string
|
|
||||||
|
|
||||||
// TargetType values.
|
|
||||||
const (
|
|
||||||
App TargetType = "app"
|
|
||||||
BackgroundPage TargetType = "background_page"
|
|
||||||
Browser TargetType = "browser"
|
|
||||||
External TargetType = "external"
|
|
||||||
Iframe TargetType = "iframe"
|
|
||||||
Other TargetType = "other"
|
|
||||||
Page TargetType = "page"
|
|
||||||
ServiceWorker TargetType = "service_worker"
|
|
||||||
SharedWorker TargetType = "shared_worker"
|
|
||||||
Webview TargetType = "webview"
|
|
||||||
Worker TargetType = "worker"
|
|
||||||
)
|
|
||||||
|
|
||||||
// String satisfies stringer.
|
|
||||||
func (tt TargetType) String() string {
|
|
||||||
return string(tt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalEasyJSON satisfies easyjson.Marshaler.
|
|
||||||
func (tt TargetType) MarshalEasyJSON(out *jwriter.Writer) {
|
|
||||||
out.String(string(tt))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON satisfies json.Marshaler.
|
|
||||||
func (tt TargetType) MarshalJSON() ([]byte, error) {
|
|
||||||
return easyjson.Marshal(tt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalEasyJSON satisfies easyjson.Unmarshaler.
|
|
||||||
func (tt *TargetType) UnmarshalEasyJSON(in *jlexer.Lexer) {
|
|
||||||
z := TargetType(in.String())
|
|
||||||
switch z {
|
|
||||||
case App:
|
|
||||||
*tt = App
|
|
||||||
case BackgroundPage:
|
|
||||||
*tt = BackgroundPage
|
|
||||||
case Browser:
|
|
||||||
*tt = Browser
|
|
||||||
case External:
|
|
||||||
*tt = External
|
|
||||||
case Iframe:
|
|
||||||
*tt = Iframe
|
|
||||||
case Other:
|
|
||||||
*tt = Other
|
|
||||||
case Page:
|
|
||||||
*tt = Page
|
|
||||||
case ServiceWorker:
|
|
||||||
*tt = ServiceWorker
|
|
||||||
case SharedWorker:
|
|
||||||
*tt = SharedWorker
|
|
||||||
case Webview:
|
|
||||||
*tt = Webview
|
|
||||||
case Worker:
|
|
||||||
*tt = Worker
|
|
||||||
|
|
||||||
default:
|
|
||||||
*tt = z
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON satisfies json.Unmarshaler.
|
|
||||||
func (tt *TargetType) UnmarshalJSON(buf []byte) error {
|
|
||||||
return easyjson.Unmarshal(buf, tt)
|
|
||||||
}
|
|
@ -1,7 +1,9 @@
|
|||||||
package client
|
package chromedp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
@ -26,7 +28,7 @@ type Conn struct {
|
|||||||
*websocket.Conn
|
*websocket.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads the next websocket message.
|
// Read reads the next message.
|
||||||
func (c *Conn) Read() ([]byte, error) {
|
func (c *Conn) Read() ([]byte, error) {
|
||||||
_, buf, err := c.ReadMessage()
|
_, buf, err := c.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -35,25 +37,18 @@ func (c *Conn) Read() ([]byte, error) {
|
|||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes a websocket message.
|
// Write writes a message.
|
||||||
func (c *Conn) Write(buf []byte) error {
|
func (c *Conn) Write(buf []byte) error {
|
||||||
return c.WriteMessage(websocket.TextMessage, buf)
|
return c.WriteMessage(websocket.TextMessage, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial dials the specified target's websocket URL.
|
// Dial dials the specified websocket URL using gorilla/websocket.
|
||||||
//
|
func Dial(urlstr string) (*Conn, error) {
|
||||||
// Note: uses gorilla/websocket.
|
|
||||||
func Dial(urlstr string, opts ...DialOption) (Transport, error) {
|
|
||||||
d := &websocket.Dialer{
|
d := &websocket.Dialer{
|
||||||
ReadBufferSize: DefaultReadBufferSize,
|
ReadBufferSize: DefaultReadBufferSize,
|
||||||
WriteBufferSize: DefaultWriteBufferSize,
|
WriteBufferSize: DefaultWriteBufferSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply opts
|
|
||||||
for _, o := range opts {
|
|
||||||
o(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect
|
// connect
|
||||||
conn, _, err := d.Dial(urlstr, nil)
|
conn, _, err := d.Dial(urlstr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,5 +58,23 @@ func Dial(urlstr string, opts ...DialOption) (Transport, error) {
|
|||||||
return &Conn{conn}, nil
|
return &Conn{conn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialOption is a dial option.
|
// ForceIP forces the host component in urlstr to be an IP address.
|
||||||
type DialOption func(*websocket.Dialer)
|
//
|
||||||
|
// Since Chrome 66+, Chrome DevTools Protocol clients connecting to a browser
|
||||||
|
// must send the "Host:" header as either an IP address, or "localhost".
|
||||||
|
func ForceIP(urlstr string) string {
|
||||||
|
if i := strings.Index(urlstr, "://"); i != -1 {
|
||||||
|
scheme := urlstr[:i+3]
|
||||||
|
host, port, path := urlstr[len(scheme)+3:], "", ""
|
||||||
|
if i := strings.Index(host, "/"); i != -1 {
|
||||||
|
host, path = host[:i], host[i:]
|
||||||
|
}
|
||||||
|
if i := strings.Index(host, ":"); i != -1 {
|
||||||
|
host, port = host[:i], host[i:]
|
||||||
|
}
|
||||||
|
if addr, err := net.ResolveIPAddr("ip", host); err == nil {
|
||||||
|
urlstr = scheme + addr.IP.String() + port + path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urlstr
|
||||||
|
}
|
115
context.go
Normal file
115
context.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package chromedp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Executor
|
||||||
|
type Executor interface {
|
||||||
|
Execute(context.Context, string, json.Marshaler, json.Unmarshaler) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context
|
||||||
|
type Context struct {
|
||||||
|
Allocator Allocator
|
||||||
|
|
||||||
|
browser *Browser
|
||||||
|
handler *TargetHandler
|
||||||
|
|
||||||
|
logf func(string, ...interface{})
|
||||||
|
errf func(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait can be called after cancelling the context containing Context, to block
|
||||||
|
// until all the underlying resources have been cleaned up.
|
||||||
|
func (c *Context) Wait() {
|
||||||
|
if c.Allocator != nil {
|
||||||
|
c.Allocator.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext creates a browser context using the parent context.
|
||||||
|
func NewContext(parent context.Context, opts ...ContextOption) (context.Context, context.CancelFunc) {
|
||||||
|
ctx, cancel := context.WithCancel(parent)
|
||||||
|
|
||||||
|
c := &Context{}
|
||||||
|
if pc := FromContext(parent); pc != nil {
|
||||||
|
c.Allocator = pc.Allocator
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(c)
|
||||||
|
}
|
||||||
|
if c.Allocator == nil {
|
||||||
|
WithExecAllocator(
|
||||||
|
NoFirstRun,
|
||||||
|
NoDefaultBrowserCheck,
|
||||||
|
Headless,
|
||||||
|
)(&c.Allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, contextKey{}, c)
|
||||||
|
return ctx, cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
// FromContext creates a new browser context from the provided context.
|
||||||
|
func FromContext(ctx context.Context) *Context {
|
||||||
|
c, _ := ctx.Value(contextKey{}).(*Context)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the action against the provided browser context.
|
||||||
|
func Run(ctx context.Context, action Action) error {
|
||||||
|
c := FromContext(ctx)
|
||||||
|
if c == nil || c.Allocator == nil {
|
||||||
|
return ErrInvalidContext
|
||||||
|
}
|
||||||
|
if c.browser == nil {
|
||||||
|
browser, err := c.Allocator.Allocate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.browser = browser
|
||||||
|
}
|
||||||
|
if c.handler == nil {
|
||||||
|
if err := c.newHandler(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return action.Do(ctx, c.handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) newHandler(ctx context.Context) error {
|
||||||
|
// TODO: add RemoteAddr() to the Transport interface?
|
||||||
|
conn := c.browser.conn.(*Conn).Conn
|
||||||
|
addr := conn.RemoteAddr()
|
||||||
|
url := "http://" + addr.String() + "/json/new"
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var wurl withWebsocketURL
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&wurl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.handler, err = NewTargetHandler(wurl.WebsocketURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.handler.Run(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type withWebsocketURL struct {
|
||||||
|
WebsocketURL string `json:"webSocketDebuggerUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextOption
|
||||||
|
type ContextOption func(*Context)
|
@ -1,34 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
SRC=$(realpath $(cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/../)
|
|
||||||
|
|
||||||
pushd $SRC &> /dev/null
|
|
||||||
|
|
||||||
gometalinter \
|
|
||||||
--disable=aligncheck \
|
|
||||||
--enable=misspell \
|
|
||||||
--enable=gofmt \
|
|
||||||
--deadline=100s \
|
|
||||||
--cyclo-over=25 \
|
|
||||||
--sort=path \
|
|
||||||
--exclude='\(defer (.+?)\)\) \(errcheck\)$' \
|
|
||||||
--exclude='/easyjson\.go.*(passes|copies) lock' \
|
|
||||||
--exclude='/easyjson\.go.*ineffectual assignment' \
|
|
||||||
--exclude='/easyjson\.go.*unnecessary conversion' \
|
|
||||||
--exclude='/easyjson\.go.*this value of key is never used' \
|
|
||||||
--exclude='/easyjson\.go.*\((gocyclo|golint|goconst|staticcheck)\)$' \
|
|
||||||
--exclude='^cdp/.*Potential hardcoded credentials' \
|
|
||||||
--exclude='^cdp/cdp\.go.*UnmarshalEasyJSON.*\(gocyclo\)$' \
|
|
||||||
--exclude='^cdp/cdputil/cdputil\.go.*UnmarshalMessage.*\(gocyclo\)$' \
|
|
||||||
--exclude='^cmd/chromedp-gen/.*\((gocyclo|interfacer)\)$' \
|
|
||||||
--exclude='^cmd/chromedp-proxy/main\.go.*\(gas\)$' \
|
|
||||||
--exclude='^cmd/chromedp-gen/fixup/fixup\.go.*\(goconst\)$' \
|
|
||||||
--exclude='^cmd/chromedp-gen/internal/enum\.go.*unreachable' \
|
|
||||||
--exclude='^cmd/chromedp-gen/(main|domain-gen)\.go.*\(gas\)$' \
|
|
||||||
--exclude='^examples/[a-z]+/main\.go.*\(errcheck\)$' \
|
|
||||||
--exclude='^kb/gen\.go.*\((gas|vet)\)$' \
|
|
||||||
--exclude='^runner/.*\(gas\)$' \
|
|
||||||
--exclude='^handler\.go.*cmd can be easyjson\.Marshaler' \
|
|
||||||
./...
|
|
||||||
|
|
||||||
popd &> /dev/null
|
|
@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
TMP=$(mktemp -d /tmp/google-chrome.XXXXX)
|
|
||||||
|
|
||||||
google-chrome \
|
|
||||||
--user-data-dir=$TMP \
|
|
||||||
--remote-debugging-port=9222 \
|
|
||||||
--no-first-run \
|
|
||||||
--no-default-browser-check \
|
|
||||||
about:blank
|
|
@ -1,14 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
BASE=$(realpath $(cd -P $GOPATH/src/github.com/chromedp && pwd))
|
|
||||||
|
|
||||||
FILES=$(find $BASE/{chromedp*,goquery,examples} -type f -iname \*.go -not -iname \*.qtpl.go -print0|wc -l --files0-from=-|head -n -1)$'\n'
|
|
||||||
|
|
||||||
AUTOG=$(find $BASE/cdproto/ -type f -iname \*.go -not -iname \*easyjson\* -print0|wc -l --files0-from=-|head -n -1)
|
|
||||||
|
|
||||||
if [ "$1" != "--total" ]; then
|
|
||||||
echo -e "code:\n$FILES\n\ngenerated:\n$AUTOG"
|
|
||||||
else
|
|
||||||
echo "code: $(awk '{s+=$1} END {print s}' <<< "$FILES")"
|
|
||||||
echo "generated: $(awk '{s+=$1} END {print s}' <<< "$AUTOG")"
|
|
||||||
fi
|
|
@ -39,4 +39,7 @@ const (
|
|||||||
|
|
||||||
// ErrInvalidHandler is the invalid handler error.
|
// ErrInvalidHandler is the invalid handler error.
|
||||||
ErrInvalidHandler Error = "invalid handler"
|
ErrInvalidHandler Error = "invalid handler"
|
||||||
|
|
||||||
|
// ErrInvalidContext is the invalid context error.
|
||||||
|
ErrInvalidContext Error = "invalid context"
|
||||||
)
|
)
|
||||||
|
39
handler.go
39
handler.go
@ -20,13 +20,11 @@ import (
|
|||||||
"github.com/chromedp/cdproto/log"
|
"github.com/chromedp/cdproto/log"
|
||||||
"github.com/chromedp/cdproto/page"
|
"github.com/chromedp/cdproto/page"
|
||||||
"github.com/chromedp/cdproto/runtime"
|
"github.com/chromedp/cdproto/runtime"
|
||||||
|
|
||||||
"github.com/chromedp/chromedp/client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TargetHandler manages a Chrome DevTools Protocol target.
|
// TargetHandler manages a Chrome DevTools Protocol target.
|
||||||
type TargetHandler struct {
|
type TargetHandler struct {
|
||||||
conn client.Transport
|
conn Transport
|
||||||
|
|
||||||
// frames is the set of encountered frames.
|
// frames is the set of encountered frames.
|
||||||
frames map[cdp.FrameID]*cdp.Frame
|
frames map[cdp.FrameID]*cdp.Frame
|
||||||
@ -63,18 +61,22 @@ type TargetHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTargetHandler creates a new handler for the specified client target.
|
// NewTargetHandler creates a new handler for the specified client target.
|
||||||
func NewTargetHandler(t client.Target, logf, debugf, errf func(string, ...interface{})) (*TargetHandler, error) {
|
func NewTargetHandler(urlstr string, opts ...TargetHandlerOption) (*TargetHandler, error) {
|
||||||
conn, err := client.Dial(t.GetWebsocketURL())
|
conn, err := Dial(urlstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &TargetHandler{
|
h := &TargetHandler{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
logf: logf,
|
errf: func(string, ...interface{}) {},
|
||||||
debugf: debugf,
|
}
|
||||||
errf: errf,
|
|
||||||
}, nil
|
for _, o := range opts {
|
||||||
|
o(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the processing of commands and events of the client target
|
// Run starts the processing of commands and events of the client target
|
||||||
@ -217,12 +219,11 @@ func (h *TargetHandler) read() (*cdproto.Message, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h.debugf("-> %s", string(buf))
|
//h.debugf("-> %s", string(buf))
|
||||||
|
|
||||||
// unmarshal
|
// unmarshal
|
||||||
msg := new(cdproto.Message)
|
msg := new(cdproto.Message)
|
||||||
err = json.Unmarshal(buf, msg)
|
if err := json.Unmarshal(buf, msg); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,7 +333,7 @@ func (h *TargetHandler) processCommand(cmd *cdproto.Message) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
h.debugf("<- %s", string(buf))
|
//h.debugf("<- %s", string(buf))
|
||||||
|
|
||||||
return h.conn.Write(buf)
|
return h.conn.Write(buf)
|
||||||
}
|
}
|
||||||
@ -442,8 +443,6 @@ func (h *TargetHandler) GetRoot(ctxt context.Context) (*cdp.Node, error) {
|
|||||||
|
|
||||||
// SetActive sets the currently active frame after a successful navigation.
|
// SetActive sets the currently active frame after a successful navigation.
|
||||||
func (h *TargetHandler) SetActive(ctxt context.Context, id cdp.FrameID) error {
|
func (h *TargetHandler) SetActive(ctxt context.Context, id cdp.FrameID) error {
|
||||||
var err error
|
|
||||||
|
|
||||||
// get frame
|
// get frame
|
||||||
f, err := h.WaitFrame(ctxt, id)
|
f, err := h.WaitFrame(ctxt, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -461,7 +460,7 @@ func (h *TargetHandler) SetActive(ctxt context.Context, id cdp.FrameID) error {
|
|||||||
// WaitFrame waits for a frame to be loaded using the provided context.
|
// WaitFrame waits for a frame to be loaded using the provided context.
|
||||||
func (h *TargetHandler) WaitFrame(ctxt context.Context, id cdp.FrameID) (*cdp.Frame, error) {
|
func (h *TargetHandler) WaitFrame(ctxt context.Context, id cdp.FrameID) (*cdp.Frame, error) {
|
||||||
// TODO: fix this
|
// TODO: fix this
|
||||||
timeout := time.After(10 * time.Second)
|
timeout := time.After(time.Second)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -495,7 +494,7 @@ func (h *TargetHandler) WaitFrame(ctxt context.Context, id cdp.FrameID) (*cdp.Fr
|
|||||||
// WaitNode waits for a node to be loaded using the provided context.
|
// WaitNode waits for a node to be loaded using the provided context.
|
||||||
func (h *TargetHandler) WaitNode(ctxt context.Context, f *cdp.Frame, id cdp.NodeID) (*cdp.Node, error) {
|
func (h *TargetHandler) WaitNode(ctxt context.Context, f *cdp.Frame, id cdp.NodeID) (*cdp.Node, error) {
|
||||||
// TODO: fix this
|
// TODO: fix this
|
||||||
timeout := time.After(10 * time.Second)
|
timeout := time.After(time.Second)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -672,3 +671,5 @@ func (h *TargetHandler) domEvent(ctxt context.Context, ev interface{}) {
|
|||||||
|
|
||||||
op(n)
|
op(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TargetHandlerOption func(*TargetHandler)
|
||||||
|
9
input.go
9
input.go
@ -58,10 +58,8 @@ func MouseClickXY(x, y int64, opts ...MouseOption) Action {
|
|||||||
// viewport.
|
// viewport.
|
||||||
func MouseClickNode(n *cdp.Node, opts ...MouseOption) Action {
|
func MouseClickNode(n *cdp.Node, opts ...MouseOption) Action {
|
||||||
return ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
|
return ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
|
||||||
var err error
|
|
||||||
|
|
||||||
var pos []int
|
var pos []int
|
||||||
err = EvaluateAsDevTools(fmt.Sprintf(scrollIntoViewJS, n.FullXPath()), &pos).Do(ctxt, h)
|
err := EvaluateAsDevTools(fmt.Sprintf(scrollIntoViewJS, n.FullXPath()), &pos).Do(ctxt, h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -154,12 +152,9 @@ func ClickCount(n int) MouseOption {
|
|||||||
// of well-known keys.
|
// of well-known keys.
|
||||||
func KeyAction(keys string, opts ...KeyOption) Action {
|
func KeyAction(keys string, opts ...KeyOption) Action {
|
||||||
return ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
|
return ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, r := range keys {
|
for _, r := range keys {
|
||||||
for _, k := range kb.Encode(r) {
|
for _, k := range kb.Encode(r) {
|
||||||
err = k.Do(ctxt, h)
|
if err := k.Do(ctxt, h); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,13 +23,9 @@ const (
|
|||||||
func TestMouseClickXY(t *testing.T) {
|
func TestMouseClickXY(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "input.html")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "input.html")
|
if err := Run(ctx, Sleep(100*time.Millisecond)); err != nil {
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
err = c.Run(defaultContext, Sleep(100*time.Millisecond))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,18 +39,17 @@ func TestMouseClickXY(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
err = c.Run(defaultContext, MouseClickXY(test.x, test.y))
|
if err := Run(ctx, MouseClickXY(test.x, test.y)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var xstr, ystr string
|
var xstr, ystr string
|
||||||
err = c.Run(defaultContext, Value("#input1", &xstr, ByID))
|
if err := Run(ctx, Value("#input1", &xstr, ByID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
x, err := strconv.ParseInt(xstr, 10, 64)
|
x, err := strconv.ParseInt(xstr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
@ -62,11 +57,10 @@ func TestMouseClickXY(t *testing.T) {
|
|||||||
if x != test.x {
|
if x != test.x {
|
||||||
t.Fatalf("test %d expected x to be: %d, got: %d", i, test.x, x)
|
t.Fatalf("test %d expected x to be: %d, got: %d", i, test.x, x)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Value("#input2", &ystr, ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, Value("#input2", &ystr, ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
y, err := strconv.ParseInt(ystr, 10, 64)
|
y, err := strconv.ParseInt(ystr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
@ -97,31 +91,28 @@ func TestMouseClickNode(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "input.html")
|
ctx, cancel := testAllocate(t, "input.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var err error
|
|
||||||
var nodes []*cdp.Node
|
var nodes []*cdp.Node
|
||||||
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
|
if err := Run(ctx, Nodes(test.sel, &nodes, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(nodes) != 1 {
|
if len(nodes) != 1 {
|
||||||
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
|
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, MouseClickNode(nodes[0], test.opt)); err != nil {
|
||||||
err = c.Run(defaultContext, MouseClickNode(nodes[0], test.opt))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value("#input3", &value, ByID))
|
if err := Run(ctx, Value("#input3", &value, ByID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != test.exp {
|
if value != test.exp {
|
||||||
t.Fatalf("expected to have value %s, got: %s", test.exp, value)
|
t.Fatalf("expected to have value %s, got: %s", test.exp, value)
|
||||||
}
|
}
|
||||||
@ -146,42 +137,41 @@ func TestMouseClickOffscreenNode(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "input.html")
|
ctx, cancel := testAllocate(t, "input.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var err error
|
|
||||||
var nodes []*cdp.Node
|
var nodes []*cdp.Node
|
||||||
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
|
if err := Run(ctx, Nodes(test.sel, &nodes, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(nodes) != 1 {
|
if len(nodes) != 1 {
|
||||||
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
|
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
err = c.Run(defaultContext, EvaluateAsDevTools(fmt.Sprintf(inViewportJS, nodes[0].FullXPath()), &ok))
|
if err := Run(ctx, EvaluateAsDevTools(fmt.Sprintf(inViewportJS, nodes[0].FullXPath()), &ok)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
t.Fatal("expected node to be offscreen")
|
t.Fatal("expected node to be offscreen")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := test.exp; i > 0; i-- {
|
for i := test.exp; i > 0; i-- {
|
||||||
err = c.Run(defaultContext, MouseClickNode(nodes[0]))
|
if err := Run(ctx, MouseClickNode(nodes[0])); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
var value int
|
var value int
|
||||||
err = c.Run(defaultContext, Evaluate("window.document.test_i", &value))
|
if err := Run(ctx, Evaluate("window.document.test_i", &value)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != test.exp {
|
if value != test.exp {
|
||||||
t.Fatalf("expected to have value %d, got: %d", test.exp, value)
|
t.Fatalf("expected to have value %d, got: %d", test.exp, value)
|
||||||
}
|
}
|
||||||
@ -208,34 +198,29 @@ func TestKeyAction(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "input.html")
|
ctx, cancel := testAllocate(t, "input.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var err error
|
|
||||||
var nodes []*cdp.Node
|
var nodes []*cdp.Node
|
||||||
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
|
if err := Run(ctx, Nodes(test.sel, &nodes, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(nodes) != 1 {
|
if len(nodes) != 1 {
|
||||||
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
|
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Focus(test.sel, test.by)); err != nil {
|
||||||
err = c.Run(defaultContext, Focus(test.sel, test.by))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, KeyAction(test.exp)); err != nil {
|
||||||
err = c.Run(defaultContext, KeyAction(test.exp))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(test.sel, &value, test.by))
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != test.exp {
|
if value != test.exp {
|
||||||
t.Fatalf("expected to have value %s, got: %s", test.exp, value)
|
t.Fatalf("expected to have value %s, got: %s", test.exp, value)
|
||||||
}
|
}
|
||||||
@ -262,29 +247,26 @@ func TestKeyActionNode(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "input.html")
|
ctx, cancel := testAllocate(t, "input.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var err error
|
|
||||||
var nodes []*cdp.Node
|
var nodes []*cdp.Node
|
||||||
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
|
if err := Run(ctx, Nodes(test.sel, &nodes, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(nodes) != 1 {
|
if len(nodes) != 1 {
|
||||||
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
|
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, KeyActionNode(nodes[0], test.exp)); err != nil {
|
||||||
err = c.Run(defaultContext, KeyActionNode(nodes[0], test.exp))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(test.sel, &value, test.by))
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != test.exp {
|
if value != test.exp {
|
||||||
t.Fatalf("expected to have value %s, got: %s", test.exp, value)
|
t.Fatalf("expected to have value %s, got: %s", test.exp, value)
|
||||||
}
|
}
|
||||||
|
22
kb/gen.go
22
kb/gen.go
@ -72,8 +72,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
var err error
|
|
||||||
|
|
||||||
// special characters
|
// special characters
|
||||||
keys := map[rune]kb.Key{
|
keys := map[rune]kb.Key{
|
||||||
'\b': {"Backspace", "Backspace", "", "", int64('\b'), int64('\b'), false, false},
|
'\b': {"Backspace", "Backspace", "", "", int64('\b'), int64('\b'), false, false},
|
||||||
@ -82,8 +80,7 @@ func run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load keys
|
// load keys
|
||||||
err = loadKeys(keys)
|
if err := loadKeys(keys); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,24 +91,19 @@ func run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// output
|
// output
|
||||||
err = ioutil.WriteFile(
|
if err := ioutil.WriteFile(*flagOut,
|
||||||
*flagOut,
|
|
||||||
[]byte(fmt.Sprintf(hdr, *flagPkg, string(constBuf), string(mapBuf))),
|
[]byte(fmt.Sprintf(hdr, *flagPkg, string(constBuf), string(mapBuf))),
|
||||||
0644,
|
0644); err != nil {
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// format
|
// format
|
||||||
err = exec.Command("goimports", "-w", *flagOut).Run()
|
if err := exec.Command("goimports", "-w", *flagOut).Run(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// format
|
// format
|
||||||
err = exec.Command("gofmt", "-s", "-w", *flagOut).Run()
|
if err := exec.Command("gofmt", "-s", "-w", *flagOut).Run(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,8 +112,6 @@ func run() error {
|
|||||||
|
|
||||||
// loadKeys loads the dom key definitions from the chromium source tree.
|
// loadKeys loads the dom key definitions from the chromium source tree.
|
||||||
func loadKeys(keys map[rune]kb.Key) error {
|
func loadKeys(keys map[rune]kb.Key) error {
|
||||||
var err error
|
|
||||||
|
|
||||||
// load key converter data
|
// load key converter data
|
||||||
keycodeConverterMap, err := loadKeycodeConverterData()
|
keycodeConverterMap, err := loadKeycodeConverterData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -444,8 +434,6 @@ var defineRE = regexp.MustCompile(`(?m)^#define\s+(.+?)\s+([0-9A-Fx]+)`)
|
|||||||
// loadPosixWinKeyboardCodes loads the native and windows keyboard scan codes
|
// loadPosixWinKeyboardCodes loads the native and windows keyboard scan codes
|
||||||
// mapped to the DOM key.
|
// mapped to the DOM key.
|
||||||
func loadPosixWinKeyboardCodes() (map[string][]int64, error) {
|
func loadPosixWinKeyboardCodes() (map[string][]int64, error) {
|
||||||
var err error
|
|
||||||
|
|
||||||
lookup := map[string]string{
|
lookup := map[string]string{
|
||||||
// mac alias
|
// mac alias
|
||||||
"VKEY_LWIN": "0x5B",
|
"VKEY_LWIN": "0x5B",
|
||||||
|
227
nav_test.go
227
nav_test.go
@ -11,37 +11,31 @@ import (
|
|||||||
func TestNavigate(t *testing.T) {
|
func TestNavigate(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
expurl, exptitle := testdataDir+"/image.html", "this is title"
|
expurl, exptitle := testdataDir+"/image.html", "this is title"
|
||||||
|
if err := Run(ctx, Navigate(expurl)); err != nil {
|
||||||
err = c.Run(defaultContext, Navigate(expurl))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, WaitVisible(`#icon-brankas`, ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, WaitVisible(`#icon-brankas`, ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var urlstr string
|
var urlstr string
|
||||||
err = c.Run(defaultContext, Location(&urlstr))
|
if err := Run(ctx, Location(&urlstr)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(urlstr, expurl) {
|
if !strings.HasPrefix(urlstr, expurl) {
|
||||||
t.Errorf("expected to be on image.html, at: %s", urlstr)
|
t.Errorf("expected to be on image.html, at: %s", urlstr)
|
||||||
}
|
}
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
err = c.Run(defaultContext, Title(&title))
|
if err := Run(ctx, Title(&title)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if title != exptitle {
|
if title != exptitle {
|
||||||
t.Errorf("expected title to contain google, instead title is: %s", title)
|
t.Errorf("expected title to contain google, instead title is: %s", title)
|
||||||
}
|
}
|
||||||
@ -50,10 +44,9 @@ func TestNavigate(t *testing.T) {
|
|||||||
func TestNavigationEntries(t *testing.T) {
|
func TestNavigationEntries(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
time.Sleep(50 * time.Millisecond)
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
tests := []string{
|
tests := []string{
|
||||||
"form.html",
|
"form.html",
|
||||||
@ -62,38 +55,33 @@ func TestNavigationEntries(t *testing.T) {
|
|||||||
|
|
||||||
var entries []*page.NavigationEntry
|
var entries []*page.NavigationEntry
|
||||||
var index int64
|
var index int64
|
||||||
|
if err := Run(ctx, NavigationEntries(&index, &entries)); err != nil {
|
||||||
err = c.Run(defaultContext, NavigationEntries(&index, &entries))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entries) != 1 {
|
if len(entries) != 2 {
|
||||||
t.Errorf("expected to have 1 navigation entry: got %d", len(entries))
|
t.Errorf("expected to have 2 navigation entry: got %d", len(entries))
|
||||||
}
|
}
|
||||||
if index != 0 {
|
if index != 1 {
|
||||||
t.Errorf("expected navigation index is 0, got: %d", index)
|
t.Errorf("expected navigation index is 1, got: %d", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
expIdx, expEntries := 1, 2
|
expIdx, expEntries := 2, 3
|
||||||
for i, url := range tests {
|
for i, url := range tests {
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/"+url))
|
if err := Run(ctx, Navigate(testdataDir+"/"+url)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
if err := Run(ctx, NavigationEntries(&index, &entries)); err != nil {
|
||||||
err = c.Run(defaultContext, NavigationEntries(&index, &entries))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entries) != expEntries {
|
if len(entries) != expEntries {
|
||||||
t.Errorf("test %d expected to have %d navigation entry: got %d", i, expEntries, len(entries))
|
t.Errorf("test %d expected to have %d navigation entry: got %d", i, expEntries, len(entries))
|
||||||
}
|
}
|
||||||
if index != int64(i+1) {
|
if want := int64(i + 2); index != want {
|
||||||
t.Errorf("test %d expected navigation index is %d, got: %d", i, i, index)
|
t.Errorf("test %d expected navigation index is %d, got: %d", i, want, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
expIdx++
|
expIdx++
|
||||||
@ -104,44 +92,36 @@ func TestNavigationEntries(t *testing.T) {
|
|||||||
func TestNavigateToHistoryEntry(t *testing.T) {
|
func TestNavigateToHistoryEntry(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
var entries []*page.NavigationEntry
|
var entries []*page.NavigationEntry
|
||||||
var index int64
|
var index int64
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/image.html"))
|
if err := Run(ctx, Navigate(testdataDir+"/image.html")); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
if err := Run(ctx, NavigationEntries(&index, &entries)); err != nil {
|
||||||
err = c.Run(defaultContext, NavigationEntries(&index, &entries))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/form.html"))
|
if err := Run(ctx, Navigate(testdataDir+"/form.html")); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
if err := Run(ctx, NavigateToHistoryEntry(entries[index].ID)); err != nil {
|
||||||
err = c.Run(defaultContext, NavigateToHistoryEntry(entries[index].ID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
err = c.Run(defaultContext, Title(&title))
|
if err := Run(ctx, Title(&title)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if title != entries[index].Title {
|
if title != entries[index].Title {
|
||||||
t.Errorf("expected title to be %s, instead title is: %s", entries[index].Title, title)
|
t.Errorf("expected title to be %s, instead title is: %s", entries[index].Title, title)
|
||||||
}
|
}
|
||||||
@ -150,43 +130,35 @@ func TestNavigateToHistoryEntry(t *testing.T) {
|
|||||||
func TestNavigateBack(t *testing.T) {
|
func TestNavigateBack(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
if err := Run(ctx, Navigate(testdataDir+"/form.html")); err != nil {
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/form.html"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var exptitle string
|
var exptitle string
|
||||||
err = c.Run(defaultContext, Title(&exptitle))
|
if err := Run(ctx, Title(&exptitle)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/image.html"))
|
if err := Run(ctx, Navigate(testdataDir+"/image.html")); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
if err := Run(ctx, NavigateBack()); err != nil {
|
||||||
err = c.Run(defaultContext, NavigateBack())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
err = c.Run(defaultContext, Title(&title))
|
if err := Run(ctx, Title(&title)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if title != exptitle {
|
if title != exptitle {
|
||||||
t.Errorf("expected title to be %s, instead title is: %s", exptitle, title)
|
t.Errorf("expected title to be %s, instead title is: %s", exptitle, title)
|
||||||
}
|
}
|
||||||
@ -195,50 +167,39 @@ func TestNavigateBack(t *testing.T) {
|
|||||||
func TestNavigateForward(t *testing.T) {
|
func TestNavigateForward(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
if err := Run(ctx, Navigate(testdataDir+"/form.html")); err != nil {
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/form.html"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
if err := Run(ctx, Navigate(testdataDir+"/image.html")); err != nil {
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/image.html"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var exptitle string
|
var exptitle string
|
||||||
err = c.Run(defaultContext, Title(&exptitle))
|
if err := Run(ctx, Title(&exptitle)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, NavigateBack()); err != nil {
|
||||||
err = c.Run(defaultContext, NavigateBack())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
if err := Run(ctx, NavigateForward()); err != nil {
|
||||||
err = c.Run(defaultContext, NavigateForward())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
err = c.Run(defaultContext, Title(&title))
|
if err := Run(ctx, Title(&title)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if title != exptitle {
|
if title != exptitle {
|
||||||
t.Errorf("expected title to be %s, instead title is: %s", exptitle, title)
|
t.Errorf("expected title to be %s, instead title is: %s", exptitle, title)
|
||||||
}
|
}
|
||||||
@ -247,55 +208,44 @@ func TestNavigateForward(t *testing.T) {
|
|||||||
func TestStop(t *testing.T) {
|
func TestStop(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
if err := Run(ctx, Navigate(testdataDir+"/form.html")); err != nil {
|
||||||
defer c.Release()
|
t.Fatal(err)
|
||||||
|
}
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/form.html"))
|
if err := Run(ctx, Stop()); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, Stop())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReload(t *testing.T) {
|
func TestReload(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
if err := Run(ctx, Navigate(testdataDir+"/form.html")); err != nil {
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/form.html"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var exptitle string
|
var exptitle string
|
||||||
err = c.Run(defaultContext, Title(&exptitle))
|
if err := Run(ctx, Title(&exptitle)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, Reload())
|
if err := Run(ctx, Reload()); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
err = c.Run(defaultContext, Title(&title))
|
if err := Run(ctx, Title(&title)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if title != exptitle {
|
if title != exptitle {
|
||||||
t.Errorf("expected title to be %s, instead title is: %s", exptitle, title)
|
t.Errorf("expected title to be %s, instead title is: %s", exptitle, title)
|
||||||
}
|
}
|
||||||
@ -304,21 +254,16 @@ func TestReload(t *testing.T) {
|
|||||||
func TestCaptureScreenshot(t *testing.T) {
|
func TestCaptureScreenshot(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
if err := Run(ctx, Navigate(testdataDir+"/image.html")); err != nil {
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/image.html"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var buf []byte
|
var buf []byte
|
||||||
err = c.Run(defaultContext, CaptureScreenshot(&buf))
|
if err := Run(ctx, CaptureScreenshot(&buf)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,18 +276,16 @@ func TestCaptureScreenshot(t *testing.T) {
|
|||||||
/*func TestAddOnLoadScript(t *testing.T) {
|
/*func TestAddOnLoadScript(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
var scriptID page.ScriptIdentifier
|
var scriptID page.ScriptIdentifier
|
||||||
err = c.Run(defaultContext, AddOnLoadScript(`window.alert("TEST")`, &scriptID))
|
err = Run(ctx, AddOnLoadScript(`window.alert("TEST")`, &scriptID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/form.html"))
|
err = Run(ctx, Navigate(testdataDir+"/form.html"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -358,13 +301,11 @@ func TestCaptureScreenshot(t *testing.T) {
|
|||||||
func TestRemoveOnLoadScript(t *testing.T) {
|
func TestRemoveOnLoadScript(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
ctx, cancel := testAllocate(t, "")
|
||||||
|
defer cancel()
|
||||||
c := testAllocate(t, "")
|
|
||||||
defer c.Release()
|
|
||||||
|
|
||||||
var scriptID page.ScriptIdentifier
|
var scriptID page.ScriptIdentifier
|
||||||
err = c.Run(defaultContext, AddOnLoadScript(`window.alert("TEST")`, &scriptID))
|
err = Run(ctx, AddOnLoadScript(`window.alert("TEST")`, &scriptID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -373,12 +314,12 @@ func TestRemoveOnLoadScript(t *testing.T) {
|
|||||||
t.Fatal("got empty script ID")
|
t.Fatal("got empty script ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, RemoveOnLoadScript(scriptID))
|
err = Run(ctx, RemoveOnLoadScript(scriptID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, Navigate(testdataDir+"/form.html"))
|
err = Run(ctx, Navigate(testdataDir+"/form.html"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -389,22 +330,18 @@ func TestRemoveOnLoadScript(t *testing.T) {
|
|||||||
func TestLocation(t *testing.T) {
|
func TestLocation(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
|
||||||
expurl := testdataDir + "/form.html"
|
expurl := testdataDir + "/form.html"
|
||||||
|
|
||||||
c := testAllocate(t, "")
|
ctx, cancel := testAllocate(t, "")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
if err := Run(ctx, Navigate(expurl)); err != nil {
|
||||||
err = c.Run(defaultContext, Navigate(expurl))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var urlstr string
|
var urlstr string
|
||||||
err = c.Run(defaultContext, Location(&urlstr))
|
if err := Run(ctx, Location(&urlstr)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,22 +353,18 @@ func TestLocation(t *testing.T) {
|
|||||||
func TestTitle(t *testing.T) {
|
func TestTitle(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var err error
|
|
||||||
expurl, exptitle := testdataDir+"/image.html", "this is title"
|
expurl, exptitle := testdataDir+"/image.html", "this is title"
|
||||||
|
|
||||||
c := testAllocate(t, "")
|
ctx, cancel := testAllocate(t, "")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
if err := Run(ctx, Navigate(expurl)); err != nil {
|
||||||
err = c.Run(defaultContext, Navigate(expurl))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
err = c.Run(defaultContext, Title(&title))
|
if err := Run(ctx, Title(&title)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
219
pool.go
219
pool.go
@ -1,219 +0,0 @@
|
|||||||
package chromedp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/chromedp/chromedp/runner"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pool manages a pool of running Chrome processes.
|
|
||||||
type Pool struct {
|
|
||||||
// start is the start port.
|
|
||||||
start int
|
|
||||||
|
|
||||||
// end is the end port.
|
|
||||||
end int
|
|
||||||
|
|
||||||
// res are the running chrome resources.
|
|
||||||
res map[int]*Res
|
|
||||||
|
|
||||||
// logging funcs
|
|
||||||
logf, debugf, errf func(string, ...interface{})
|
|
||||||
|
|
||||||
rw sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPool creates a new Chrome runner pool.
|
|
||||||
func NewPool(opts ...PoolOption) (*Pool, error) {
|
|
||||||
p := &Pool{
|
|
||||||
start: DefaultPoolStartPort,
|
|
||||||
end: DefaultPoolEndPort,
|
|
||||||
res: make(map[int]*Res),
|
|
||||||
logf: log.Printf,
|
|
||||||
debugf: func(string, ...interface{}) {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply opts
|
|
||||||
for _, o := range opts {
|
|
||||||
if err := o(p); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.errf == nil {
|
|
||||||
p.errf = func(s string, v ...interface{}) {
|
|
||||||
p.logf("ERROR: "+s, v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown releases all the pool resources.
|
|
||||||
func (p *Pool) Shutdown() error {
|
|
||||||
p.rw.Lock()
|
|
||||||
defer p.rw.Unlock()
|
|
||||||
|
|
||||||
for _, r := range p.res {
|
|
||||||
r.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate creates a new process runner and returns it.
|
|
||||||
func (p *Pool) Allocate(ctxt context.Context, opts ...runner.CommandLineOption) (*Res, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
r := p.next(ctxt)
|
|
||||||
|
|
||||||
// Check if the port is available first. If it's not, Chrome will print
|
|
||||||
// an "address already in use" error, but it will otherwise keep
|
|
||||||
// running. This can lead to Allocate succeeding, while the chrome
|
|
||||||
// process isn't actually listening on the port we need.
|
|
||||||
l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", r.port))
|
|
||||||
if err != nil {
|
|
||||||
// we can't use this port, e.g. address already in use
|
|
||||||
p.errf("pool could not allocate runner on port %d: %v", r.port, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
l.Close()
|
|
||||||
|
|
||||||
p.debugf("pool allocating %d", r.port)
|
|
||||||
|
|
||||||
// create runner
|
|
||||||
r.r, err = runner.New(append([]runner.CommandLineOption{
|
|
||||||
runner.ExecPath(runner.LookChromeNames("headless_shell")),
|
|
||||||
runner.RemoteDebuggingPort(r.port),
|
|
||||||
runner.NoDefaultBrowserCheck,
|
|
||||||
runner.NoFirstRun,
|
|
||||||
runner.Headless,
|
|
||||||
}, opts...)...)
|
|
||||||
if err != nil {
|
|
||||||
defer r.Release()
|
|
||||||
p.errf("pool could not allocate runner on port %d: %v", r.port, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// start runner
|
|
||||||
err = r.r.Start(r.ctxt)
|
|
||||||
if err != nil {
|
|
||||||
defer r.Release()
|
|
||||||
p.errf("pool could not start runner on port %d: %v", r.port, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup cdp
|
|
||||||
r.c, err = New(
|
|
||||||
r.ctxt, WithRunner(r.r),
|
|
||||||
WithLogf(p.logf), WithDebugf(p.debugf), WithErrorf(p.errf),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
defer r.Release()
|
|
||||||
p.errf("pool could not connect to %d: %v", r.port, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// next returns the next available res.
|
|
||||||
func (p *Pool) next(ctxt context.Context) *Res {
|
|
||||||
p.rw.Lock()
|
|
||||||
defer p.rw.Unlock()
|
|
||||||
|
|
||||||
var found bool
|
|
||||||
var i int
|
|
||||||
for i = p.start; i < p.end; i++ {
|
|
||||||
if _, ok := p.res[i]; !ok {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
panic("no ports available")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &Res{
|
|
||||||
p: p,
|
|
||||||
port: i,
|
|
||||||
}
|
|
||||||
r.ctxt, r.cancel = context.WithCancel(ctxt)
|
|
||||||
|
|
||||||
p.res[i] = r
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Res is a pool resource.
|
|
||||||
type Res struct {
|
|
||||||
p *Pool
|
|
||||||
ctxt context.Context
|
|
||||||
cancel func()
|
|
||||||
port int
|
|
||||||
r *runner.Runner
|
|
||||||
c *CDP
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release releases the pool resource.
|
|
||||||
func (r *Res) Release() error {
|
|
||||||
r.cancel()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if r.c != nil {
|
|
||||||
err = r.c.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
defer r.p.debugf("pool released %d", r.port)
|
|
||||||
|
|
||||||
r.p.rw.Lock()
|
|
||||||
defer r.p.rw.Unlock()
|
|
||||||
delete(r.p.res, r.port)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Port returns the allocated port for the pool resource.
|
|
||||||
func (r *Res) Port() int {
|
|
||||||
return r.port
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL returns a formatted URL for the pool resource.
|
|
||||||
func (r *Res) URL() string {
|
|
||||||
return fmt.Sprintf("http://localhost:%d/json", r.port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CDP returns the actual CDP instance.
|
|
||||||
func (r *Res) CDP() *CDP {
|
|
||||||
return r.c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs an action.
|
|
||||||
func (r *Res) Run(ctxt context.Context, a Action) error {
|
|
||||||
return r.c.Run(ctxt, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolOption is a pool option.
|
|
||||||
type PoolOption func(*Pool) error
|
|
||||||
|
|
||||||
// PortRange is a pool option to set the port range to use.
|
|
||||||
func PortRange(start, end int) PoolOption {
|
|
||||||
return func(p *Pool) error {
|
|
||||||
p.start = start
|
|
||||||
p.end = end
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolLog is a pool option to set the logging to use for the pool.
|
|
||||||
func PoolLog(logf, debugf, errf func(string, ...interface{})) PoolOption {
|
|
||||||
return func(p *Pool) error {
|
|
||||||
p.logf, p.debugf, p.errf = logf, debugf, errf
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
47
pool_test.go
47
pool_test.go
@ -1,47 +0,0 @@
|
|||||||
package chromedp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAllocatePortInUse(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// take a random available port
|
|
||||||
l, err := net.Listen("tcp4", "localhost:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer l.Close()
|
|
||||||
|
|
||||||
ctxt, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// make the pool use the port already in use via a port range
|
|
||||||
_, portStr, _ := net.SplitHostPort(l.Addr().String())
|
|
||||||
port, _ := strconv.Atoi(portStr)
|
|
||||||
pool, err := NewPool(
|
|
||||||
PortRange(port, port+1),
|
|
||||||
// skip the error log from the used port
|
|
||||||
PoolLog(nil, nil, func(string, ...interface{}) {}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := pool.Allocate(ctxt)
|
|
||||||
if err != nil {
|
|
||||||
want := "address already in use"
|
|
||||||
got := err.Error()
|
|
||||||
if !strings.Contains(got, want) {
|
|
||||||
t.Fatalf("wanted error to contain %q, but got %q", want, got)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Fatal("wanted Allocate to error if port is in use")
|
|
||||||
c.Release()
|
|
||||||
}
|
|
||||||
}
|
|
3
query.go
3
query.go
@ -484,8 +484,7 @@ func Screenshot(sel interface{}, picbuf *[]byte, opts ...QueryOption) Action {
|
|||||||
|
|
||||||
// encode
|
// encode
|
||||||
var croppedBuf bytes.Buffer
|
var croppedBuf bytes.Buffer
|
||||||
err = png.Encode(&croppedBuf, cropped)
|
if err := png.Encode(&croppedBuf, cropped); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
281
query_test.go
281
query_test.go
@ -20,8 +20,8 @@ import (
|
|||||||
func TestNodes(t *testing.T) {
|
func TestNodes(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "table.html")
|
ctx, cancel := testAllocate(t, "table.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -34,13 +34,12 @@ func TestNodes(t *testing.T) {
|
|||||||
{"#footer", ByID, 1},
|
{"#footer", ByID, 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var nodes []*cdp.Node
|
var nodes []*cdp.Node
|
||||||
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
|
if err := Run(ctx, Nodes(test.sel, &nodes, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(nodes) != test.len {
|
if len(nodes) != test.len {
|
||||||
t.Errorf("test %d expected to have %d nodes: got %d", i, test.len, len(nodes))
|
t.Errorf("test %d expected to have %d nodes: got %d", i, test.len, len(nodes))
|
||||||
}
|
}
|
||||||
@ -50,8 +49,8 @@ func TestNodes(t *testing.T) {
|
|||||||
func TestNodeIDs(t *testing.T) {
|
func TestNodeIDs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "table.html")
|
ctx, cancel := testAllocate(t, "table.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -64,13 +63,12 @@ func TestNodeIDs(t *testing.T) {
|
|||||||
{"#footer", ByID, 1},
|
{"#footer", ByID, 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var ids []cdp.NodeID
|
var ids []cdp.NodeID
|
||||||
err = c.Run(defaultContext, NodeIDs(test.sel, &ids, test.by))
|
if err := Run(ctx, NodeIDs(test.sel, &ids, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ids) != test.len {
|
if len(ids) != test.len {
|
||||||
t.Errorf("test %d expected to have %d node id's: got %d", i, test.len, len(ids))
|
t.Errorf("test %d expected to have %d node id's: got %d", i, test.len, len(ids))
|
||||||
}
|
}
|
||||||
@ -80,8 +78,8 @@ func TestNodeIDs(t *testing.T) {
|
|||||||
func TestFocusBlur(t *testing.T) {
|
func TestFocusBlur(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -93,35 +91,31 @@ func TestFocusBlur(t *testing.T) {
|
|||||||
{"#input1", ByID},
|
{"#input1", ByID},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.Run(defaultContext, Click("#input1", ByID))
|
err := Run(ctx, Click("#input1", ByID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
err = c.Run(defaultContext, Focus(test.sel, test.by))
|
if err := Run(ctx, Focus(test.sel, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(test.sel, &value, test.by))
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != "9999" {
|
if value != "9999" {
|
||||||
t.Errorf("test %d expected value is '9999', got: '%s'", i, value)
|
t.Errorf("test %d expected value is '9999', got: '%s'", i, value)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Blur(test.sel, test.by)); err != nil {
|
||||||
err = c.Run(defaultContext, Blur(test.sel, test.by))
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
if err != nil {
|
}
|
||||||
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, Value(test.sel, &value, test.by))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
|
||||||
}
|
|
||||||
if value != "0" {
|
if value != "0" {
|
||||||
t.Errorf("test %d expected value is '0', got: '%s'", i, value)
|
t.Errorf("test %d expected value is '0', got: '%s'", i, value)
|
||||||
}
|
}
|
||||||
@ -131,8 +125,8 @@ func TestFocusBlur(t *testing.T) {
|
|||||||
func TestDimensions(t *testing.T) {
|
func TestDimensions(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "image.html")
|
ctx, cancel := testAllocate(t, "image.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -146,13 +140,12 @@ func TestDimensions(t *testing.T) {
|
|||||||
{"#icon-github", ByID, 120, 120},
|
{"#icon-github", ByID, 120, 120},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var model *dom.BoxModel
|
var model *dom.BoxModel
|
||||||
err = c.Run(defaultContext, Dimensions(test.sel, &model))
|
if err := Run(ctx, Dimensions(test.sel, &model)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if model.Height != test.height || model.Width != test.width {
|
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)
|
t.Errorf("test %d expected %dx%d, got: %dx%d", i, test.width, test.height, model.Height, model.Width)
|
||||||
}
|
}
|
||||||
@ -162,8 +155,8 @@ func TestDimensions(t *testing.T) {
|
|||||||
func TestText(t *testing.T) {
|
func TestText(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "form.html")
|
ctx, cancel := testAllocate(t, "form.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -176,13 +169,12 @@ func TestText(t *testing.T) {
|
|||||||
{"/html/body/form/span[2]", BySearch, "keyword"},
|
{"/html/body/form/span[2]", BySearch, "keyword"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var text string
|
var text string
|
||||||
err = c.Run(defaultContext, Text(test.sel, &text, test.by))
|
if err := Run(ctx, Text(test.sel, &text, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if text != test.exp {
|
if text != test.exp {
|
||||||
t.Errorf("test %d expected `%s`, got: %s", i, test.exp, text)
|
t.Errorf("test %d expected `%s`, got: %s", i, test.exp, text)
|
||||||
}
|
}
|
||||||
@ -217,27 +209,24 @@ func TestClear(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "form.html")
|
ctx, cancel := testAllocate(t, "form.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var val string
|
var val string
|
||||||
err := c.Run(defaultContext, Value(test.sel, &val, test.by))
|
err := Run(ctx, Value(test.sel, &val, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
if val == "" {
|
if val == "" {
|
||||||
t.Errorf("expected `%s` to have non empty value", test.sel)
|
t.Errorf("expected `%s` to have non empty value", test.sel)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Clear(test.sel, test.by)); err != nil {
|
||||||
err = c.Run(defaultContext, Clear(test.sel, test.by))
|
t.Fatalf("got error: %v", err)
|
||||||
if err != nil {
|
}
|
||||||
|
if err := Run(ctx, Value(test.sel, &val, test.by)); err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, Value(test.sel, &val, test.by))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
|
||||||
}
|
|
||||||
if val != "" {
|
if val != "" {
|
||||||
t.Errorf("expected empty value for `%s`, got: %s", test.sel, val)
|
t.Errorf("expected empty value for `%s`, got: %s", test.sel, val)
|
||||||
}
|
}
|
||||||
@ -264,24 +253,22 @@ func TestReset(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "form.html")
|
ctx, cancel := testAllocate(t, "form.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, SetValue(test.sel, test.value, test.by))
|
err := Run(ctx, SetValue(test.sel, test.value, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Reset(test.sel, test.by)); err != nil {
|
||||||
err = c.Run(defaultContext, Reset(test.sel, test.by))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(test.sel, &value, test.by))
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != test.exp {
|
if value != test.exp {
|
||||||
t.Errorf("expected value after reset is %s, got: '%s'", test.exp, value)
|
t.Errorf("expected value after reset is %s, got: '%s'", test.exp, value)
|
||||||
}
|
}
|
||||||
@ -292,8 +279,8 @@ func TestReset(t *testing.T) {
|
|||||||
func TestValue(t *testing.T) {
|
func TestValue(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "form.html")
|
ctx, cancel := testAllocate(t, "form.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -305,13 +292,12 @@ func TestValue(t *testing.T) {
|
|||||||
{`#keyword`, ByID},
|
{`#keyword`, ByID},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(test.sel, &value, test.by))
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != "chromedp" {
|
if value != "chromedp" {
|
||||||
t.Errorf("test %d expected `chromedp`, got: %s", i, value)
|
t.Errorf("test %d expected `chromedp`, got: %s", i, value)
|
||||||
}
|
}
|
||||||
@ -335,19 +321,19 @@ func TestSetValue(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "form.html")
|
ctx, cancel := testAllocate(t, "form.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, SetValue(test.sel, "FOOBAR", test.by))
|
err := Run(ctx, SetValue(test.sel, "FOOBAR", test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(test.sel, &value, test.by))
|
if err := Run(ctx, Value(test.sel, &value, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != "FOOBAR" {
|
if value != "FOOBAR" {
|
||||||
t.Errorf("expected `FOOBAR`, got: %s", value)
|
t.Errorf("expected `FOOBAR`, got: %s", value)
|
||||||
}
|
}
|
||||||
@ -358,8 +344,8 @@ func TestSetValue(t *testing.T) {
|
|||||||
func TestAttributes(t *testing.T) {
|
func TestAttributes(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "image.html")
|
ctx, cancel := testAllocate(t, "image.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -392,11 +378,9 @@ func TestAttributes(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var attrs map[string]string
|
var attrs map[string]string
|
||||||
err = c.Run(defaultContext, Attributes(test.sel, &attrs, test.by))
|
if err := Run(ctx, Attributes(test.sel, &attrs, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,8 +393,8 @@ func TestAttributes(t *testing.T) {
|
|||||||
func TestAttributesAll(t *testing.T) {
|
func TestAttributesAll(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "image.html")
|
ctx, cancel := testAllocate(t, "image.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -433,11 +417,9 @@ func TestAttributesAll(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var attrs []map[string]string
|
var attrs []map[string]string
|
||||||
err = c.Run(defaultContext, AttributesAll(test.sel, &attrs, test.by))
|
if err := Run(ctx, AttributesAll(test.sel, &attrs, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,17 +477,16 @@ func TestSetAttributes(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "image.html")
|
ctx, cancel := testAllocate(t, "image.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, SetAttributes(test.sel, test.attrs, test.by))
|
err := Run(ctx, SetAttributes(test.sel, test.attrs, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var attrs map[string]string
|
var attrs map[string]string
|
||||||
err = c.Run(defaultContext, Attributes(test.sel, &attrs, test.by))
|
if err := Run(ctx, Attributes(test.sel, &attrs, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,8 +500,8 @@ func TestSetAttributes(t *testing.T) {
|
|||||||
func TestAttributeValue(t *testing.T) {
|
func TestAttributeValue(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "image.html")
|
ctx, cancel := testAllocate(t, "image.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -534,13 +515,10 @@ func TestAttributeValue(t *testing.T) {
|
|||||||
{"#icon-github", ByID, "alt", "How people build software"},
|
{"#icon-github", ByID, "alt", "How people build software"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var value string
|
var value string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
if err := Run(ctx, AttributeValue(test.sel, test.attr, &value, &ok, test.by)); err != nil {
|
||||||
err = c.Run(defaultContext, AttributeValue(test.sel, test.attr, &value, &ok, test.by))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -573,20 +551,20 @@ func TestSetAttributeValue(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "form.html")
|
ctx, cancel := testAllocate(t, "form.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, SetAttributeValue(test.sel, test.attr, test.exp, test.by))
|
err := Run(ctx, SetAttributeValue(test.sel, test.attr, test.exp, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
var ok bool
|
var ok bool
|
||||||
err = c.Run(defaultContext, AttributeValue(test.sel, test.attr, &value, &ok, test.by))
|
if err := Run(ctx, AttributeValue(test.sel, test.attr, &value, &ok, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("failed to get attribute %s on %s", test.attr, test.sel)
|
t.Fatalf("failed to get attribute %s on %s", test.attr, test.sel)
|
||||||
}
|
}
|
||||||
@ -616,20 +594,20 @@ func TestRemoveAttribute(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "image.html")
|
ctx, cancel := testAllocate(t, "image.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, RemoveAttribute(test.sel, test.attr))
|
err := Run(ctx, RemoveAttribute(test.sel, test.attr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
var ok bool
|
var ok bool
|
||||||
err = c.Run(defaultContext, AttributeValue(test.sel, test.attr, &value, &ok, test.by))
|
if err := Run(ctx, AttributeValue(test.sel, test.attr, &value, &ok, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok || value != "" {
|
if ok || value != "" {
|
||||||
t.Fatalf("expected attribute %s removed from element %s", test.attr, test.sel)
|
t.Fatalf("expected attribute %s removed from element %s", test.attr, test.sel)
|
||||||
}
|
}
|
||||||
@ -654,24 +632,22 @@ func TestClick(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "form.html")
|
ctx, cancel := testAllocate(t, "form.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, Click(test.sel, test.by))
|
err := Run(ctx, Click(test.sel, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, WaitVisible("#icon-brankas", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, WaitVisible("#icon-brankas", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
err = c.Run(defaultContext, Title(&title))
|
if err := Run(ctx, Title(&title)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if title != "this is title" {
|
if title != "this is title" {
|
||||||
t.Errorf("expected title to be 'chromedp - Google Search', got: '%s'", title)
|
t.Errorf("expected title to be 'chromedp - Google Search', got: '%s'", title)
|
||||||
}
|
}
|
||||||
@ -696,10 +672,10 @@ func TestDoubleClick(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, DoubleClick(test.sel, test.by))
|
err := Run(ctx, DoubleClick(test.sel, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
@ -707,10 +683,10 @@ func TestDoubleClick(t *testing.T) {
|
|||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value("#input1", &value, ByID))
|
if err := Run(ctx, Value("#input1", &value, ByID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != "1" {
|
if value != "1" {
|
||||||
t.Errorf("expected value to be '1', got: '%s'", value)
|
t.Errorf("expected value to be '1', got: '%s'", value)
|
||||||
}
|
}
|
||||||
@ -739,19 +715,19 @@ func TestSendKeys(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "visible.html")
|
ctx, cancel := testAllocate(t, "visible.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, SendKeys(test.sel, test.keys, test.by))
|
err := Run(ctx, SendKeys(test.sel, test.keys, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var val string
|
var val string
|
||||||
err = c.Run(defaultContext, Value(test.sel, &val, test.by))
|
if err := Run(ctx, Value(test.sel, &val, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if val != test.exp {
|
if val != test.exp {
|
||||||
t.Errorf("expected value %s, got: %s", test.exp, val)
|
t.Errorf("expected value %s, got: %s", test.exp, val)
|
||||||
}
|
}
|
||||||
@ -762,8 +738,8 @@ func TestSendKeys(t *testing.T) {
|
|||||||
func TestScreenshot(t *testing.T) {
|
func TestScreenshot(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "image.html")
|
ctx, cancel := testAllocate(t, "image.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -775,11 +751,9 @@ func TestScreenshot(t *testing.T) {
|
|||||||
{"#icon-github", ByID},
|
{"#icon-github", ByID},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
err = c.Run(defaultContext, Screenshot(test.sel, &buf))
|
if err := Run(ctx, Screenshot(test.sel, &buf)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -807,24 +781,22 @@ func TestSubmit(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "form.html")
|
ctx, cancel := testAllocate(t, "form.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, Submit(test.sel, test.by))
|
err := Run(ctx, Submit(test.sel, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, WaitVisible("#icon-brankas", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, WaitVisible("#icon-brankas", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
err = c.Run(defaultContext, Title(&title))
|
if err := Run(ctx, Title(&title)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if title != "this is title" {
|
if title != "this is title" {
|
||||||
t.Errorf("expected title to be 'this is title', got: '%s'", title)
|
t.Errorf("expected title to be 'this is title', got: '%s'", title)
|
||||||
}
|
}
|
||||||
@ -849,13 +821,13 @@ func TestComputedStyle(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var styles []*css.ComputedProperty
|
var styles []*css.ComputedProperty
|
||||||
err := c.Run(defaultContext, ComputedStyle(test.sel, &styles, test.by))
|
err := Run(ctx, ComputedStyle(test.sel, &styles, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
@ -867,16 +839,12 @@ func TestComputedStyle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Click("#input1", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, Click("#input1", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
if err := Run(ctx, ComputedStyle(test.sel, &styles, test.by)); err != nil {
|
||||||
err = c.Run(defaultContext, ComputedStyle(test.sel, &styles, test.by))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -908,13 +876,13 @@ func TestMatchedStyle(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
var styles *css.GetMatchedStylesForNodeReturns
|
var styles *css.GetMatchedStylesForNodeReturns
|
||||||
err := c.Run(defaultContext, MatchedStyle(test.sel, &styles, test.by))
|
err := Run(ctx, MatchedStyle(test.sel, &styles, test.by))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
@ -957,10 +925,10 @@ func TestFileUpload(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.Remove(tmpfile.Name())
|
defer os.Remove(tmpfile.Name())
|
||||||
if _, err = tmpfile.WriteString(uploadHTML); err != nil {
|
if _, err := tmpfile.WriteString(uploadHTML); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err = tmpfile.Close(); err != nil {
|
if err := tmpfile.Close(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -977,19 +945,19 @@ func TestFileUpload(t *testing.T) {
|
|||||||
// parallel
|
// parallel
|
||||||
//t.Parallel()
|
//t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "")
|
ctx, cancel := testAllocate(t, "")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var result string
|
var result string
|
||||||
err = c.Run(defaultContext, Tasks{
|
if err := Run(ctx, Tasks{
|
||||||
Navigate(s.URL),
|
Navigate(s.URL),
|
||||||
test.a,
|
test.a,
|
||||||
Click(`input[name="submit"]`),
|
Click(`input[name="submit"]`),
|
||||||
Text(`#result`, &result, ByID, NodeVisible),
|
Text(`#result`, &result, ByID, NodeVisible),
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d expected no error, got: %v", i, err)
|
t.Fatalf("test %d expected no error, got: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != fmt.Sprintf("%d", len(uploadHTML)) {
|
if result != fmt.Sprintf("%d", len(uploadHTML)) {
|
||||||
t.Errorf("test %d expected result to be %d, got: %s", i, len(uploadHTML), result)
|
t.Errorf("test %d expected result to be %d, got: %s", i, len(uploadHTML), result)
|
||||||
}
|
}
|
||||||
@ -1000,8 +968,8 @@ func TestFileUpload(t *testing.T) {
|
|||||||
func TestInnerHTML(t *testing.T) {
|
func TestInnerHTML(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "table.html")
|
ctx, cancel := testAllocate(t, "table.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -1011,13 +979,12 @@ func TestInnerHTML(t *testing.T) {
|
|||||||
{"thead", ByQueryAll},
|
{"thead", ByQueryAll},
|
||||||
{"thead", ByQuery},
|
{"thead", ByQuery},
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var html string
|
var html string
|
||||||
err = c.Run(defaultContext, InnerHTML(test.sel, &html))
|
if err := Run(ctx, InnerHTML(test.sel, &html)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if html == "" {
|
if html == "" {
|
||||||
t.Fatalf("test %d: InnerHTML is empty", i)
|
t.Fatalf("test %d: InnerHTML is empty", i)
|
||||||
}
|
}
|
||||||
@ -1027,8 +994,8 @@ func TestInnerHTML(t *testing.T) {
|
|||||||
func TestOuterHTML(t *testing.T) {
|
func TestOuterHTML(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "table.html")
|
ctx, cancel := testAllocate(t, "table.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -1038,13 +1005,12 @@ func TestOuterHTML(t *testing.T) {
|
|||||||
{"thead tr", ByQueryAll},
|
{"thead tr", ByQueryAll},
|
||||||
{"thead tr", ByQuery},
|
{"thead tr", ByQuery},
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var html string
|
var html string
|
||||||
err = c.Run(defaultContext, OuterHTML(test.sel, &html))
|
if err := Run(ctx, OuterHTML(test.sel, &html)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if html == "" {
|
if html == "" {
|
||||||
t.Fatalf("test %d: OuterHTML is empty", i)
|
t.Fatalf("test %d: OuterHTML is empty", i)
|
||||||
}
|
}
|
||||||
@ -1054,8 +1020,8 @@ func TestOuterHTML(t *testing.T) {
|
|||||||
func TestScrollIntoView(t *testing.T) {
|
func TestScrollIntoView(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "image.html")
|
ctx, cancel := testAllocate(t, "image.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
sel string
|
sel string
|
||||||
@ -1066,12 +1032,11 @@ func TestScrollIntoView(t *testing.T) {
|
|||||||
{"img", ByQuery},
|
{"img", ByQuery},
|
||||||
{"#icon-github", ByID},
|
{"#icon-github", ByID},
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
err = c.Run(defaultContext, ScrollIntoView(test.sel, test.by))
|
if err := Run(ctx, ScrollIntoView(test.sel, test.by)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d got error: %v", i, err)
|
t.Fatalf("test %d got error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO test scroll event
|
// TODO test scroll event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
// +build darwin
|
|
||||||
|
|
||||||
package runner
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultChromePath is the default path to use for Chrome if the
|
|
||||||
// executable is not in $PATH.
|
|
||||||
DefaultChromePath = `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultChromeNames are the default Chrome executable names to look for in
|
|
||||||
// $PATH.
|
|
||||||
var DefaultChromeNames []string
|
|
@ -1,19 +0,0 @@
|
|||||||
// +build linux freebsd netbsd openbsd
|
|
||||||
|
|
||||||
package runner
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultChromePath is the default path to use for Chrome if the
|
|
||||||
// executable is not in $PATH.
|
|
||||||
DefaultChromePath = "/usr/bin/google-chrome"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultChromeNames are the default Chrome executable names to look for in
|
|
||||||
// $PATH.
|
|
||||||
var DefaultChromeNames = []string{
|
|
||||||
"google-chrome",
|
|
||||||
"chromium-browser",
|
|
||||||
"chromium",
|
|
||||||
"google-chrome-beta",
|
|
||||||
"google-chrome-unstable",
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package runner
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultChromePath is the default path to use for Chrome if the
|
|
||||||
// executable is not in %PATH%.
|
|
||||||
DefaultChromePath = `C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultChromeNames are the default Chrome executable names to look for in
|
|
||||||
// %PATH%.
|
|
||||||
var DefaultChromeNames = []string{`chrome.exe`}
|
|
486
runner/runner.go
486
runner/runner.go
@ -1,486 +0,0 @@
|
|||||||
// Package runner provides a Chrome process runner.
|
|
||||||
package runner
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/chromedp/chromedp/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultUserDataDirPrefix is the default user data directory prefix.
|
|
||||||
DefaultUserDataDirPrefix = "chromedp-runner.%d."
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error is a runner error.
|
|
||||||
type Error string
|
|
||||||
|
|
||||||
// Error satisfies the error interface.
|
|
||||||
func (err Error) Error() string {
|
|
||||||
return string(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error values.
|
|
||||||
const (
|
|
||||||
// ErrAlreadyStarted is the already started error.
|
|
||||||
ErrAlreadyStarted Error = "already started"
|
|
||||||
|
|
||||||
// ErrAlreadyWaiting is the already waiting error.
|
|
||||||
ErrAlreadyWaiting Error = "already waiting"
|
|
||||||
|
|
||||||
// ErrInvalidURLs is the invalid url-opts error.
|
|
||||||
ErrInvalidURLOpts Error = "invalid url-opts"
|
|
||||||
|
|
||||||
// ErrInvalidCmdOpts is the invalid cmd-opts error.
|
|
||||||
ErrInvalidCmdOpts Error = "invalid cmd-opts"
|
|
||||||
|
|
||||||
// ErrInvalidProcessOpts is the invalid process-opts error.
|
|
||||||
ErrInvalidProcessOpts Error = "invalid process-opts"
|
|
||||||
|
|
||||||
// ErrInvalidExecPath is the invalid exec-path error.
|
|
||||||
ErrInvalidExecPath Error = "invalid exec-path"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Runner holds information about a running Chrome process.
|
|
||||||
type Runner struct {
|
|
||||||
opts map[string]interface{}
|
|
||||||
cmd *exec.Cmd
|
|
||||||
waiting bool
|
|
||||||
rw sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Chrome process using the supplied command line options.
|
|
||||||
func New(opts ...CommandLineOption) (*Runner, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
cliOpts := make(map[string]interface{})
|
|
||||||
|
|
||||||
// apply opts
|
|
||||||
for _, o := range opts {
|
|
||||||
if err = o(cliOpts); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set default Chrome options if exec-path not provided
|
|
||||||
if _, ok := cliOpts["exec-path"]; !ok {
|
|
||||||
cliOpts["exec-path"] = LookChromeNames()
|
|
||||||
for k, v := range map[string]interface{}{
|
|
||||||
"no-first-run": true,
|
|
||||||
"no-default-browser-check": true,
|
|
||||||
"remote-debugging-port": 9222,
|
|
||||||
} {
|
|
||||||
if _, ok := cliOpts[k]; !ok {
|
|
||||||
cliOpts[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add KillProcessGroup and ForceKill if no other cmd opts provided
|
|
||||||
if _, ok := cliOpts["cmd-opts"]; !ok {
|
|
||||||
for _, o := range []CommandLineOption{KillProcessGroup, ForceKill} {
|
|
||||||
if err = o(cliOpts); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Runner{
|
|
||||||
opts: cliOpts,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cliOptRE is a regular expression to validate a chrome cli option.
|
|
||||||
var cliOptRE = regexp.MustCompile(`^[a-z0-9\-]+$`)
|
|
||||||
|
|
||||||
// buildOpts generates the command line options for Chrome.
|
|
||||||
func (r *Runner) buildOpts() []string {
|
|
||||||
var opts []string
|
|
||||||
var urls []string
|
|
||||||
|
|
||||||
// process opts
|
|
||||||
for k, v := range r.opts {
|
|
||||||
if !cliOptRE.MatchString(k) || v == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch k {
|
|
||||||
case "exec-path", "cmd-opts", "process-opts":
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "url-opts":
|
|
||||||
urls = v.([]string)
|
|
||||||
|
|
||||||
default:
|
|
||||||
switch z := v.(type) {
|
|
||||||
case bool:
|
|
||||||
if z {
|
|
||||||
opts = append(opts, "--"+k)
|
|
||||||
}
|
|
||||||
|
|
||||||
case string:
|
|
||||||
opts = append(opts, "--"+k+"="+z)
|
|
||||||
|
|
||||||
default:
|
|
||||||
opts = append(opts, "--"+k+"="+fmt.Sprintf("%v", v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if urls == nil {
|
|
||||||
urls = append(urls, "about:blank")
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(opts, urls...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts a Chrome process using the specified context. The Chrome
|
|
||||||
// process can be terminated by closing the passed context.
|
|
||||||
func (r *Runner) Start(ctxt context.Context, opts ...string) error {
|
|
||||||
var err error
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
r.rw.RLock()
|
|
||||||
cmd := r.cmd
|
|
||||||
r.rw.RUnlock()
|
|
||||||
|
|
||||||
if cmd != nil {
|
|
||||||
return ErrAlreadyStarted
|
|
||||||
}
|
|
||||||
|
|
||||||
// set user data dir, if not provided
|
|
||||||
_, ok = r.opts["user-data-dir"]
|
|
||||||
if !ok {
|
|
||||||
r.opts["user-data-dir"], err = ioutil.TempDir(
|
|
||||||
defaultUserDataTmpDir, fmt.Sprintf(DefaultUserDataDirPrefix, r.Port()),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get exec path
|
|
||||||
var execPath string
|
|
||||||
if p, ok := r.opts["exec-path"]; ok {
|
|
||||||
execPath, ok = p.(string)
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidExecPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure execPath is valid
|
|
||||||
if execPath == "" {
|
|
||||||
return ErrInvalidExecPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// create cmd
|
|
||||||
r.cmd = exec.CommandContext(ctxt, execPath, append(r.buildOpts(), opts...)...)
|
|
||||||
|
|
||||||
// apply cmd opts
|
|
||||||
if cmdOpts, ok := r.opts["cmd-opts"]; ok {
|
|
||||||
for _, co := range cmdOpts.([]func(*exec.Cmd) error) {
|
|
||||||
if err = co(r.cmd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start process
|
|
||||||
if err = r.cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply process opts
|
|
||||||
if processOpts, ok := r.opts["process-opts"]; ok {
|
|
||||||
for _, po := range processOpts.([]func(*os.Process) error) {
|
|
||||||
if err = po(r.cmd.Process); err != nil {
|
|
||||||
// TODO: do something better here, as we want to kill
|
|
||||||
// the child process, do cleanup, etc.
|
|
||||||
panic(err)
|
|
||||||
//return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown shuts down the Chrome process. Currently only has support for
|
|
||||||
// SIGTERM in darwin and linux systems
|
|
||||||
func (r *Runner) Shutdown(ctxt context.Context, opts ...client.Option) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
cl := r.Client(opts...)
|
|
||||||
|
|
||||||
targets, err := cl.ListPageTargets(ctxt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
errs := make([]error, len(targets))
|
|
||||||
for i, t := range targets {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(wg *sync.WaitGroup, i int, t client.Target) {
|
|
||||||
defer wg.Done()
|
|
||||||
errs[i] = cl.CloseTarget(ctxt, t)
|
|
||||||
}(&wg, i, t)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
for _, e := range errs {
|
|
||||||
if e != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// osx and linux applications do not automatically exit when all windows (ie, tabs)
|
|
||||||
// closed, so send SIGTERM.
|
|
||||||
//
|
|
||||||
// TODO: add other behavior here for more process options on shutdown?
|
|
||||||
if r.cmd != nil && r.cmd.Process != nil {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "darwin", "linux":
|
|
||||||
return r.cmd.Process.Signal(syscall.SIGTERM)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait waits for the previously started Chrome process to terminate, returning
|
|
||||||
// any encountered error.
|
|
||||||
func (r *Runner) Wait() error {
|
|
||||||
r.rw.RLock()
|
|
||||||
waiting := r.waiting
|
|
||||||
r.rw.RUnlock()
|
|
||||||
|
|
||||||
if waiting {
|
|
||||||
return ErrAlreadyWaiting
|
|
||||||
}
|
|
||||||
|
|
||||||
r.rw.Lock()
|
|
||||||
r.waiting = true
|
|
||||||
r.rw.Unlock()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
r.rw.Lock()
|
|
||||||
r.waiting = false
|
|
||||||
r.rw.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return r.cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Port returns the port the process was launched with.
|
|
||||||
func (r *Runner) Port() int {
|
|
||||||
var port interface{}
|
|
||||||
var ok bool
|
|
||||||
port, ok = r.opts["remote-debugging-port"]
|
|
||||||
if !ok {
|
|
||||||
port, ok = r.opts["port"]
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
panic("expected either remote-debugging-port or port to be specified in command line options")
|
|
||||||
}
|
|
||||||
|
|
||||||
var p int
|
|
||||||
p, ok = port.(int)
|
|
||||||
if !ok {
|
|
||||||
panic("expected port to be type int")
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client returns a Chrome DevTools Protocol client for the running Chrome
|
|
||||||
// process.
|
|
||||||
func (r *Runner) Client(opts ...client.Option) *client.Client {
|
|
||||||
return client.New(append(opts,
|
|
||||||
client.URL(fmt.Sprintf("http://localhost:%d/json", r.Port())),
|
|
||||||
)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts a new Chrome process runner, using the provided context and
|
|
||||||
// command line options.
|
|
||||||
func Run(ctxt context.Context, opts ...CommandLineOption) (*Runner, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// create
|
|
||||||
r, err := New(opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// start
|
|
||||||
if err = r.Start(ctxt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandLineOption is a runner command line option.
|
|
||||||
//
|
|
||||||
// see: http://peter.sh/experiments/chromium-command-line-switches/
|
|
||||||
type CommandLineOption func(map[string]interface{}) error
|
|
||||||
|
|
||||||
// Flag is a generic command line option to pass a name=value flag to
|
|
||||||
// Chrome.
|
|
||||||
func Flag(name string, value interface{}) CommandLineOption {
|
|
||||||
return func(m map[string]interface{}) error {
|
|
||||||
m[name] = value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path sets the path to the Chrome executable and sets default run options for
|
|
||||||
// Chrome. This will also set the remote debugging port to 9222, and disable
|
|
||||||
// the first run / default browser check.
|
|
||||||
//
|
|
||||||
// Note: use ExecPath if you do not want to set other options.
|
|
||||||
func Path(path string) CommandLineOption {
|
|
||||||
return func(m map[string]interface{}) error {
|
|
||||||
m["exec-path"] = path
|
|
||||||
m["no-first-run"] = true
|
|
||||||
m["no-default-browser-check"] = true
|
|
||||||
m["remote-debugging-port"] = 9222
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecPath is a command line option to set the exec path.
|
|
||||||
func ExecPath(path string) CommandLineOption {
|
|
||||||
return Flag("exec-path", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserDataDir is the command line option to set the user data dir.
|
|
||||||
//
|
|
||||||
// Note: set this option to manually set the profile directory used by Chrome.
|
|
||||||
// When this is not set, then a default path will be created in the /tmp
|
|
||||||
// directory.
|
|
||||||
func UserDataDir(dir string) CommandLineOption {
|
|
||||||
return Flag("user-data-dir", dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProxyServer is the command line option to set the outbound proxy server.
|
|
||||||
func ProxyServer(proxy string) CommandLineOption {
|
|
||||||
return Flag("proxy-server", proxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowSize is the command line option to set the initial window size.
|
|
||||||
func WindowSize(width, height int) CommandLineOption {
|
|
||||||
return Flag("window-size", fmt.Sprintf("%d,%d", width, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserAgent is the command line option to set the default User-Agent
|
|
||||||
// header.
|
|
||||||
func UserAgent(userAgent string) CommandLineOption {
|
|
||||||
return Flag("user-agent", userAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoSandbox is the Chrome comamnd line option to disable the sandbox.
|
|
||||||
func NoSandbox(m map[string]interface{}) error {
|
|
||||||
return Flag("no-sandbox", true)(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoFirstRun is the Chrome comamnd line option to disable the first run
|
|
||||||
// dialog.
|
|
||||||
func NoFirstRun(m map[string]interface{}) error {
|
|
||||||
return Flag("no-first-run", true)(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoDefaultBrowserCheck is the Chrome comamnd line option to disable the
|
|
||||||
// default browser check.
|
|
||||||
func NoDefaultBrowserCheck(m map[string]interface{}) error {
|
|
||||||
return Flag("no-default-browser-check", true)(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteDebuggingPort is the command line option to set the remote
|
|
||||||
// debugging port.
|
|
||||||
func RemoteDebuggingPort(port int) CommandLineOption {
|
|
||||||
return Flag("remote-debugging-port", port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headless is the command line option to run in headless mode.
|
|
||||||
func Headless(m map[string]interface{}) error {
|
|
||||||
return Flag("headless", true)(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableGPU is the command line option to disable the GPU process.
|
|
||||||
func DisableGPU(m map[string]interface{}) error {
|
|
||||||
return Flag("disable-gpu", true)(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL is the command line option to add a URL to open on process start.
|
|
||||||
//
|
|
||||||
// Note: this can be specified multiple times, and each URL will be opened in a
|
|
||||||
// new tab.
|
|
||||||
func URL(urlstr string) CommandLineOption {
|
|
||||||
return func(m map[string]interface{}) error {
|
|
||||||
var urls []string
|
|
||||||
if u, ok := m["url-opts"]; ok {
|
|
||||||
urls, ok = u.([]string)
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidURLOpts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m["url-opts"] = append(urls, urlstr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdOpt is a command line option to modify the underlying exec.Cmd
|
|
||||||
// prior to the call to exec.Cmd.Start in Run.
|
|
||||||
func CmdOpt(o func(*exec.Cmd) error) CommandLineOption {
|
|
||||||
return func(m map[string]interface{}) error {
|
|
||||||
var opts []func(*exec.Cmd) error
|
|
||||||
if e, ok := m["cmd-opts"]; ok {
|
|
||||||
opts, ok = e.([]func(*exec.Cmd) error)
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidCmdOpts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m["cmd-opts"] = append(opts, o)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessOpt is a command line option to modify the child os.Process
|
|
||||||
// after the call to exec.Cmd.Start in Run.
|
|
||||||
func ProcessOpt(o func(*os.Process) error) CommandLineOption {
|
|
||||||
return func(m map[string]interface{}) error {
|
|
||||||
var opts []func(*os.Process) error
|
|
||||||
if e, ok := m["process-opts"]; ok {
|
|
||||||
opts, ok = e.([]func(*os.Process) error)
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidProcessOpts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m["process-opts"] = append(opts, o)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookChromeNames looks for the platform's DefaultChromeNames and any
|
|
||||||
// additional names using exec.LookPath, returning the first encountered
|
|
||||||
// location or the platform's DefaultChromePath if no names are found on the
|
|
||||||
// path.
|
|
||||||
func LookChromeNames(additional ...string) string {
|
|
||||||
for _, p := range append(additional, DefaultChromeNames...) {
|
|
||||||
path, err := exec.LookPath(p)
|
|
||||||
if err == nil {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefaultChromePath
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
// +build darwin freebsd netbsd openbsd
|
|
||||||
|
|
||||||
package runner
|
|
||||||
|
|
||||||
// ForceKill is a Chrome command line option that forces Chrome to be killed
|
|
||||||
// when the parent is killed.
|
|
||||||
//
|
|
||||||
// Note: sets exec.Cmd.SysProcAttr.Setpgid = true (only for Linux)
|
|
||||||
func ForceKill(m map[string]interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
// +build linux
|
|
||||||
|
|
||||||
package runner
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ByteCount is a type byte count const.
|
|
||||||
type ByteCount uint64
|
|
||||||
|
|
||||||
// ByteCount values.
|
|
||||||
const (
|
|
||||||
Byte ByteCount = 1
|
|
||||||
Kilobyte ByteCount = 1024 * Byte
|
|
||||||
Megabyte ByteCount = 1024 * Kilobyte
|
|
||||||
Gigabyte ByteCount = 1024 * Megabyte
|
|
||||||
)
|
|
||||||
|
|
||||||
// prlimit invokes the system's prlimit call. Copied from Go source tree.
|
|
||||||
//
|
|
||||||
// Note: this needs either the CAP_SYS_RESOURCE capability, or the invoking
|
|
||||||
// process needs to have the same functional user and group as the pid being
|
|
||||||
// modified.
|
|
||||||
//
|
|
||||||
// see: man 2 prlimit
|
|
||||||
func prlimit(pid int, res int, newv, old *syscall.Rlimit) error {
|
|
||||||
_, _, err := syscall.RawSyscall6(syscall.SYS_PRLIMIT64, uintptr(pid), uintptr(res), uintptr(unsafe.Pointer(newv)), uintptr(unsafe.Pointer(old)), 0, 0)
|
|
||||||
if err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rlimit is a Chrome command line option to set the soft rlimit value for res
|
|
||||||
// on a running Chrome process.
|
|
||||||
//
|
|
||||||
// Note: uses Linux prlimit system call, and is invoked after the child process
|
|
||||||
// has been started.
|
|
||||||
//
|
|
||||||
// see: man 2 prlimit
|
|
||||||
func Rlimit(res int, cur, max uint64) CommandLineOption {
|
|
||||||
return ProcessOpt(func(p *os.Process) error {
|
|
||||||
return prlimit(p.Pid, syscall.RLIMIT_AS, &syscall.Rlimit{
|
|
||||||
Cur: cur,
|
|
||||||
Max: max,
|
|
||||||
}, nil)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// LimitMemory is a Chrome command line option to set the soft memory limit for
|
|
||||||
// a running Chrome process.
|
|
||||||
//
|
|
||||||
// Note: uses Linux prlimit system call, and is invoked after the child
|
|
||||||
// process has been started.
|
|
||||||
func LimitMemory(mem ByteCount) CommandLineOption {
|
|
||||||
return Rlimit(syscall.RLIMIT_AS, uint64(mem), uint64(mem))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LimitCoreDump is a Chrome command line option to set the soft core dump
|
|
||||||
// limit for a running Chrome process.
|
|
||||||
//
|
|
||||||
// Note: uses Linux prlimit system call, and is invoked after the child
|
|
||||||
// process has been started.
|
|
||||||
func LimitCoreDump(sz ByteCount) CommandLineOption {
|
|
||||||
return Rlimit(syscall.RLIMIT_CORE, uint64(sz), uint64(sz))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForceKill is a Chrome command line option that forces Chrome to be killed
|
|
||||||
// when the parent is killed.
|
|
||||||
//
|
|
||||||
// Note: sets exec.Cmd.SysProcAttr.Setpgid = true (only for Linux)
|
|
||||||
func ForceKill(m map[string]interface{}) error {
|
|
||||||
return CmdOpt(func(c *exec.Cmd) error {
|
|
||||||
if c.SysProcAttr == nil {
|
|
||||||
c.SysProcAttr = new(syscall.SysProcAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SysProcAttr.Pdeathsig = syscall.SIGKILL
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})(m)
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
// +build linux darwin freebsd netbsd openbsd
|
|
||||||
|
|
||||||
package runner
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultUserDataTmpDir is the default directory path for created user
|
|
||||||
// data directories.
|
|
||||||
defaultUserDataTmpDir = "/tmp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KillProcessGroup is a Chrome command line option that will instruct the
|
|
||||||
// invoked child Chrome process to terminate when the parent process (ie, the
|
|
||||||
// Go application) dies.
|
|
||||||
//
|
|
||||||
// Note: sets exec.Cmd.SysProcAttr.Setpgid = true and does nothing on Windows.
|
|
||||||
func KillProcessGroup(m map[string]interface{}) error {
|
|
||||||
return CmdOpt(func(c *exec.Cmd) error {
|
|
||||||
if c.SysProcAttr == nil {
|
|
||||||
c.SysProcAttr = new(syscall.SysProcAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SysProcAttr.Setpgid = true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})(m)
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package runner
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultUserDataTmpDir = os.Getenv("USERPROFILE") + `\AppData\Local`
|
|
||||||
)
|
|
||||||
|
|
||||||
// KillProcessGroup is a Chrome command line option that will instruct the
|
|
||||||
// invoked child Chrome process to terminate when the parent process (ie, the
|
|
||||||
// Go application) dies.
|
|
||||||
//
|
|
||||||
// Note: sets exec.Cmd.SysProcAttr.Setpgid = true and does nothing on Windows.
|
|
||||||
func KillProcessGroup(m map[string]interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForceKill is a Chrome command line option that forces Chrome to be killed
|
|
||||||
// when the parent is killed.
|
|
||||||
//
|
|
||||||
// Note: sets exec.Cmd.SysProcAttr.Setpgid = true (only for Linux)
|
|
||||||
func ForceKill(m map[string]interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
3
sel.go
3
sel.go
@ -107,8 +107,7 @@ func (s *Selector) run(ctxt context.Context, h *TargetHandler) chan error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.after(ctxt, h, nodes...)
|
if err := s.after(ctxt, h, nodes...); err != nil {
|
||||||
if err != nil {
|
|
||||||
ch <- err
|
ch <- err
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
134
sel_test.go
134
sel_test.go
@ -9,132 +9,118 @@ import (
|
|||||||
func TestWaitReady(t *testing.T) {
|
func TestWaitReady(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var nodeIDs []cdp.NodeID
|
var nodeIDs []cdp.NodeID
|
||||||
err := c.Run(defaultContext, NodeIDs("#input2", &nodeIDs, ByID))
|
err := Run(ctx, NodeIDs("#input2", &nodeIDs, ByID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
if len(nodeIDs) != 1 {
|
if len(nodeIDs) != 1 {
|
||||||
t.Errorf("expected to have exactly 1 node id: got %d", len(nodeIDs))
|
t.Errorf("expected to have exactly 1 node id: got %d", len(nodeIDs))
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, WaitReady("#input2", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, WaitReady("#input2", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(nodeIDs, &value, ByNodeID))
|
if err := Run(ctx, Value(nodeIDs, &value, ByNodeID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitVisible(t *testing.T) {
|
func TestWaitVisible(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var nodeIDs []cdp.NodeID
|
var nodeIDs []cdp.NodeID
|
||||||
err := c.Run(defaultContext, NodeIDs("#input2", &nodeIDs, ByID))
|
err := Run(ctx, NodeIDs("#input2", &nodeIDs, ByID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
if len(nodeIDs) != 1 {
|
if len(nodeIDs) != 1 {
|
||||||
t.Errorf("expected to have exactly 1 node id: got %d", len(nodeIDs))
|
t.Errorf("expected to have exactly 1 node id: got %d", len(nodeIDs))
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, WaitVisible("#input2", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, WaitVisible("#input2", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(nodeIDs, &value, ByNodeID))
|
if err := Run(ctx, Value(nodeIDs, &value, ByNodeID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitNotVisible(t *testing.T) {
|
func TestWaitNotVisible(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var nodeIDs []cdp.NodeID
|
var nodeIDs []cdp.NodeID
|
||||||
err := c.Run(defaultContext, NodeIDs("#input2", &nodeIDs, ByID))
|
err := Run(ctx, NodeIDs("#input2", &nodeIDs, ByID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
if len(nodeIDs) != 1 {
|
if len(nodeIDs) != 1 {
|
||||||
t.Errorf("expected to have exactly 1 node id: got %d", len(nodeIDs))
|
t.Errorf("expected to have exactly 1 node id: got %d", len(nodeIDs))
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Click("#button2", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, Click("#button2", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, WaitNotVisible("#input2", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, WaitNotVisible("#input2", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value(nodeIDs, &value, ByNodeID))
|
if err := Run(ctx, Value(nodeIDs, &value, ByNodeID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitEnabled(t *testing.T) {
|
func TestWaitEnabled(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var attr string
|
var attr string
|
||||||
var ok bool
|
var ok bool
|
||||||
err := c.Run(defaultContext, AttributeValue("#select1", "disabled", &attr, &ok, ByID))
|
err := Run(ctx, AttributeValue("#select1", "disabled", &attr, &ok, ByID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("expected element to be disabled")
|
t.Fatal("expected element to be disabled")
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Click("#button3", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, Click("#button3", ByID))
|
t.Fatalf("got error: %v", err)
|
||||||
if err != nil {
|
}
|
||||||
|
if err := Run(ctx, WaitEnabled("#select1", ByID)); err != nil {
|
||||||
|
t.Fatalf("got error: %v", err)
|
||||||
|
}
|
||||||
|
if err := Run(ctx, AttributeValue("#select1", "disabled", &attr, &ok, ByID)); err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, WaitEnabled("#select1", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
|
||||||
}
|
|
||||||
err = c.Run(defaultContext, AttributeValue("#select1", "disabled", &attr, &ok, ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
|
||||||
}
|
|
||||||
if ok {
|
if ok {
|
||||||
t.Fatal("expected element to be enabled")
|
t.Fatal("expected element to be enabled")
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, SetAttributeValue(`//*[@id="select1"]/option[1]`, "selected", "true")); err != nil {
|
||||||
err = c.Run(defaultContext, SetAttributeValue(`//*[@id="select1"]/option[1]`, "selected", "true"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
err = c.Run(defaultContext, Value("#select1", &value, ByID))
|
if err := Run(ctx, Value("#select1", &value, ByID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != "foo" {
|
if value != "foo" {
|
||||||
t.Fatalf("expected value to be foo, got: %s", value)
|
t.Fatalf("expected value to be foo, got: %s", value)
|
||||||
}
|
}
|
||||||
@ -143,43 +129,36 @@ func TestWaitEnabled(t *testing.T) {
|
|||||||
func TestWaitSelected(t *testing.T) {
|
func TestWaitSelected(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, Click("#button3", ByID))
|
err := Run(ctx, Click("#button3", ByID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, WaitEnabled("#select1", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, WaitEnabled("#select1", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var attr string
|
var attr string
|
||||||
ok := false
|
ok := false
|
||||||
err = c.Run(defaultContext, AttributeValue(`//*[@id="select1"]/option[1]`, "selected", &attr, &ok))
|
if err := Run(ctx, AttributeValue(`//*[@id="select1"]/option[1]`, "selected", &attr, &ok)); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
t.Fatal("expected element to be not selected")
|
t.Fatal("expected element to be not selected")
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, SetAttributeValue(`//*[@id="select1"]/option[1]`, "selected", "true")); err != nil {
|
||||||
err = c.Run(defaultContext, SetAttributeValue(`//*[@id="select1"]/option[1]`, "selected", "true"))
|
t.Fatalf("got error: %v", err)
|
||||||
if err != nil {
|
}
|
||||||
|
if err := Run(ctx, WaitSelected(`//*[@id="select1"]/option[1]`)); err != nil {
|
||||||
|
t.Fatalf("got error: %v", err)
|
||||||
|
}
|
||||||
|
if err := Run(ctx, AttributeValue(`//*[@id="select1"]/option[1]`, "selected", &attr, nil)); err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, WaitSelected(`//*[@id="select1"]/option[1]`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Run(defaultContext, AttributeValue(`//*[@id="select1"]/option[1]`, "selected", &attr, nil))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
|
||||||
}
|
|
||||||
if attr != "true" {
|
if attr != "true" {
|
||||||
t.Fatal("expected element to be selected")
|
t.Fatal("expected element to be selected")
|
||||||
}
|
}
|
||||||
@ -188,33 +167,30 @@ func TestWaitSelected(t *testing.T) {
|
|||||||
func TestWaitNotPresent(t *testing.T) {
|
func TestWaitNotPresent(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
err := c.Run(defaultContext, WaitVisible("#input3", ByID))
|
err := Run(ctx, WaitVisible("#input3", ByID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := Run(ctx, Click("#button4", ByID)); err != nil {
|
||||||
err = c.Run(defaultContext, Click("#button4", ByID))
|
t.Fatalf("got error: %v", err)
|
||||||
if err != nil {
|
}
|
||||||
|
if err := Run(ctx, WaitNotPresent("#input3", ByID)); err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Run(defaultContext, WaitNotPresent("#input3", ByID))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("got error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAtLeast(t *testing.T) {
|
func TestAtLeast(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := testAllocate(t, "js.html")
|
ctx, cancel := testAllocate(t, "js.html")
|
||||||
defer c.Release()
|
defer cancel()
|
||||||
|
|
||||||
var nodes []*cdp.Node
|
var nodes []*cdp.Node
|
||||||
err := c.Run(defaultContext, Nodes("//input", &nodes, AtLeast(3)))
|
err := Run(ctx, Nodes("//input", &nodes, AtLeast(3)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error: %v", err)
|
t.Fatalf("got error: %v", err)
|
||||||
}
|
}
|
||||||
|
7
util.go
7
util.go
@ -1,10 +1,17 @@
|
|||||||
package chromedp
|
package chromedp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/chromedp/cdproto"
|
"github.com/chromedp/cdproto"
|
||||||
"github.com/chromedp/cdproto/cdp"
|
"github.com/chromedp/cdproto/cdp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultCheckDuration is the default time to sleep between a check.
|
||||||
|
DefaultCheckDuration = 50 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
// frameOp is a frame manipulation operation.
|
// frameOp is a frame manipulation operation.
|
||||||
type frameOp func(*cdp.Frame)
|
type frameOp func(*cdp.Frame)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user