Initial commit of pool and unit tests
This commit is contained in:
parent
c112899581
commit
42c6cca7ed
12
.travis.yml
Normal file
12
.travis.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- tip
|
||||||
|
before_install:
|
||||||
|
- mkdir -p /headless_shell
|
||||||
|
- wget -O /headless_shell/headless_shell.tar.bz2 https://storage.googleapis.com/docker-chrome-headless/headless_shell.tar.bz2
|
||||||
|
- tar -jxf /headless_shell/headless_shell.tar.bz2 -C /headless_shell
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
script:
|
||||||
|
- CHROMEDP_NO_SANDBOX=true go test -v -coverprofile=coverage.out
|
||||||
|
- goveralls -service=travis-ci -coverprofile=coverage.out
|
55
chromedp.go
55
chromedp.go
|
@ -68,13 +68,6 @@ func New(ctxt context.Context, opts ...Option) (*CDP, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup context
|
|
||||||
if ctxt == nil {
|
|
||||||
var cancel func()
|
|
||||||
ctxt, cancel = context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for supplied runner, if none then create one
|
// check for supplied runner, if none then create one
|
||||||
if c.r == nil && c.watch == nil {
|
if c.r == nil && c.watch == nil {
|
||||||
c.r, err = runner.Run(ctxt, c.opts...)
|
c.r, err = runner.Run(ctxt, c.opts...)
|
||||||
|
@ -312,30 +305,30 @@ func (c *CDP) NewTarget(id *string, opts ...client.Option) Action {
|
||||||
|
|
||||||
// NewTargetWithURL creates a new Chrome target, sets it as the active target,
|
// NewTargetWithURL creates a new Chrome target, sets it as the active target,
|
||||||
// and then navigates to the specified url.
|
// and then navigates to the specified url.
|
||||||
func (c *CDP) NewTargetWithURL(urlstr string, id *string, opts ...client.Option) Action {
|
//func (c *CDP) NewTargetWithURL(urlstr string, id *string, opts ...client.Option) Action {
|
||||||
return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
|
// return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
|
||||||
n, err := c.newTarget(ctxt, opts...)
|
// n, err := c.newTarget(ctxt, opts...)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
l := c.GetHandlerByID(n)
|
// l := c.GetHandlerByID(n)
|
||||||
if l == nil {
|
// if l == nil {
|
||||||
return errors.New("could not retrieve newly created target")
|
// return errors.New("could not retrieve newly created target")
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/*err = Navigate(l, urlstr).Do(ctxt)
|
// /*err = Navigate(l, urlstr).Do(ctxt)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if id != nil {
|
// if id != nil {
|
||||||
*id = n
|
// *id = n
|
||||||
}*/
|
// }*/
|
||||||
|
//
|
||||||
return nil
|
// return nil
|
||||||
})
|
// })
|
||||||
}
|
//}
|
||||||
|
|
||||||
// CloseByIndex closes the Chrome target with specified index i.
|
// CloseByIndex closes the Chrome target with specified index i.
|
||||||
func (c *CDP) CloseByIndex(i int) Action {
|
func (c *CDP) CloseByIndex(i int) Action {
|
||||||
|
|
61
chromedp_test.go
Normal file
61
chromedp_test.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package chromedp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pool *Pool
|
||||||
|
|
||||||
|
var defaultContext = context.Background()
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
pool, err = NewPool()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
code := m.Run()
|
||||||
|
|
||||||
|
err = pool.Shutdown()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNavigate(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
c, err := pool.Allocate(defaultContext)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Release()
|
||||||
|
|
||||||
|
err = c.Run(defaultContext, Navigate("https://www.google.com/"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Run(defaultContext, WaitVisible(`#hplogo`, ByID))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlstr string
|
||||||
|
err = c.Run(defaultContext, Location(&urlstr))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(urlstr, "https://www.google.") {
|
||||||
|
t.Errorf("expected to be on google, got: %v", urlstr)
|
||||||
|
}
|
||||||
|
}
|
183
pool.go
Normal file
183
pool.go
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
package chromedp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/knq/chromedp/runner"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultStartPort is the default start port number.
|
||||||
|
DefaultStartPort = 9000
|
||||||
|
|
||||||
|
// DefaultEndPort is the default end port number.
|
||||||
|
DefaultEndPort = 10000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pool provides 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
|
||||||
|
|
||||||
|
rw sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPool creates a new Chrome runner pool.
|
||||||
|
func NewPool(opts ...PoolOption) (*Pool, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p := &Pool{
|
||||||
|
start: DefaultStartPort,
|
||||||
|
end: DefaultEndPort,
|
||||||
|
res: make(map[int]*Res),
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply opts
|
||||||
|
for _, o := range opts {
|
||||||
|
err = o(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
ctxt, cancel := context.WithCancel(ctxt)
|
||||||
|
|
||||||
|
r := &Res{
|
||||||
|
p: p,
|
||||||
|
ctxt: ctxt,
|
||||||
|
cancel: cancel,
|
||||||
|
port: p.next(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// create runner
|
||||||
|
r.r, err = runner.New(append([]runner.CommandLineOption{
|
||||||
|
runner.Headless("", r.port),
|
||||||
|
}, opts...)...)
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start runner
|
||||||
|
err = r.r.Start(ctxt)
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup cdp
|
||||||
|
r.c, err = New(ctxt, WithRunner(r.r))
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rw.Lock()
|
||||||
|
defer p.rw.Unlock()
|
||||||
|
|
||||||
|
p.res[r.port] = r
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next available port number.
|
||||||
|
func (p *Pool) next() int {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
r.p.rw.Lock()
|
||||||
|
defer r.p.rw.Unlock()
|
||||||
|
|
||||||
|
delete(r.p.res, r.port)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -334,6 +334,26 @@ func Path(path string) CommandLineOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Headless is the Chrome command line option to set the default settings for
|
||||||
|
// running the headless_shell executable. If path is empty, then an attempt
|
||||||
|
// will be made to find headless_shell on the path.
|
||||||
|
func Headless(path string, port int) CommandLineOption {
|
||||||
|
if path == "" {
|
||||||
|
path, _ = exec.LookPath("headless_shell")
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(m map[string]interface{}) error {
|
||||||
|
m["exec-path"] = path
|
||||||
|
m["remote-debugging-port"] = port
|
||||||
|
|
||||||
|
if os.Getenv("CHROMEDP_NO_SANDBOX") != "" {
|
||||||
|
m["no-sandbox"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ExecPath is a Chrome command line option to set the exec path.
|
// ExecPath is a Chrome command line option to set the exec path.
|
||||||
func ExecPath(path string) CommandLineOption {
|
func ExecPath(path string) CommandLineOption {
|
||||||
return Flag("exec-path", path)
|
return Flag("exec-path", path)
|
||||||
|
@ -370,6 +390,11 @@ func UserAgent(userAgent string) CommandLineOption {
|
||||||
return Flag("user-agent", userAgent)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// CmdOpt is a Chrome command line option to modify the underlying exec.Cmd
|
// CmdOpt is a Chrome command line option to modify the underlying exec.Cmd
|
||||||
// prior to invocation.
|
// prior to invocation.
|
||||||
func CmdOpt(o func(*exec.Cmd) error) CommandLineOption {
|
func CmdOpt(o func(*exec.Cmd) error) CommandLineOption {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user