README

About chrome-protocol

A relatively thin wrapper on top of code that is generated based on the chrome devtool protocol. Aims to provide a few of the basic commands that one would desire when automating actions in chrome or any other browser that supports the protocol.

This is still a work in progress.

  • Very fast.
  • No hidden errors.
  • No hidden sleep calls/timeouts.
  • Simple approach makes it easy to understand what is happening under the hood.

Examples

Look under github.com/4ydx/chrome-protocol/actions. The testing files are the examples. There is one example in the example folder.

  • Click
  • Fill
  • Focus
  • Navigation
  • Screenshot
  • As well as other actions (css style retrieval, javascript evaluation, etc).

I will be working on other actions as I need them for my own personal projects.

You can take the generated code in github.com/4ydx/cdp and create your own higher level actions for interacting with the browser. This will require understanding the Devtools Reference.

Navigation example:

package main

import (
	"github.com/4ydx/chrome-protocol"
	"github.com/4ydx/chrome-protocol/actions"
	"log"
	"time"
)

func main() {
	browser := cdp.NewBrowser("/usr/bin/google-chrome", 9222, "browser.log")

	frame := cdp.Start(browser, cdp.LogBasic)
	defer func() {
		// passing false prevents the browser from stopping immediately
		frame.Stop(false)

		// Give yourself time to view the final page in the browser.
		time.Sleep(3 * time.Second)
		browser.Stop()
	}()

	// Enable page events
	if err := actions.EnablePage(frame, time.Second*2); err != nil {
		panic(err)
	}

	// Navigate
	if _, err := actions.Navigate(frame, "https://google.com", time.Second*5); err != nil {
		panic(err)
	}

	log.Printf("\n-- All completed for %s --\n", frame.FrameID)
}

Creating your own Actions

Actions encapsulate everything you need in order to interact with a browser. An action contains commands and events.

When you construct an action, you need to fill in at least one command that consists of the struct representing the parameters that will be sent with the command, the struct that represents the reply to that command from the server, and the method name of the API call you are making.

It is possible to associate events that the server will send to the client with your action. By specifying events you can be sure that a given action has actually run its course and the browser state is where you would expect it to be.

API methods, command parameters, command responses, possible events, and types are all defined in the Devtools Reference.

This is a possible Navigation action that watches for the FrameStoppedLoadingReply event which helps to ensure that navigation to a url is fully completed.

// Navigate sends the browser to the given URL
func Navigate(frame *cdp.Frame, url string, timeout time.Duration) ([]cdp.Event, error) {
	events := GetNavigationEvents()
	action := cdp.NewAction(frame,
		events,
		[]cdp.Command{
			cdp.Command{ID: frame.RequestID.GetNext(), Method: page.CommandPageNavigate, Params: &page.NavigateArgs{URL: url}, Reply: &page.NavigateReply{}, Timeout: timeout},
		})
	if err := action.Run(); err != nil {
		log.Print(err)
		return events, err
	}
	return events, nil
}

Caveats

  • Concurrent actions are currently not supported.
Expand ▾ Collapse ▴

Documentation

Index

Constants

View Source
const (
	// LogBasic records outgoing commands, their replies, and any specified events.
	LogBasic = LogLevelValue(0)
	// LogDetails records additional details about the reply from the server for a given command/event.
	LogDetails = LogLevelValue(1)
	// LogAll records everything.
	LogAll = LogLevelValue(2)
)

Variables

View Source
var Wait = time.Millisecond * 50

Wait is the default timeout taken as the action wait loop runs. The loop's select is triggered and the action will be checked for completion.

Functions

func GetWebsocket

func GetWebsocket(lg *log.Logger, port int) *websocket.Conn

GetWebsocket returns a websocket connection to the running browser.

func Read

func Read(frame *Frame)

Read reads replies from the server over the websocket.

func SendClose

func SendClose(lg *log.Logger, c *websocket.Conn)

SendClose closes the websocket.

func UpdateDOMEvent

func UpdateDOMEvent(frame *Frame, method string, event json.Unmarshaler)

UpdateDOMEvent takes the event and, for a certain subset of events, makes sure that the current DOM object is updated.

func Write

func Write(frame *Frame)

Write writes requests to the server over the websocket.

Types

type Action

type Action struct {
	Commands     []Command
	CommandIndex int
	Events       map[string]Event
}

Action represents a collection of json requests (commands) and any events that those requests might trigger that need to be tracked.

func NewAction

func NewAction(events []Event, commands []Command) *Action

NewAction returns a newly created action with any events that will be triggered by commands the action will take.

func (*Action) Run

func (act *Action) Run(frame *Frame) error

Run sends the current action to websocket code that will create a request. Then the action will wait until all commands and expected events are completed.

type Browser

type Browser struct {
	Port        int
	PID         int
	TempDir     string
	LogFile     *os.File
	Log         *log.Logger
	ConsoleFile *os.File
	Console     *log.Logger
}

Browser contains information required to stop an exec'd browser at a later point in time.

func NewBrowser

func NewBrowser(path string, port int, logfile string, args ...string) *Browser

NewBrowser accepts the path to the browser's binary, the port, and any arguments that need to be passed to the binary.

func (*Browser) Stop

func (b *Browser) Stop()

Stop kills the running browser process.

type Command

type Command struct {
	// Values required to make a chrome devtools protocol request.
	ID     int64          `json:"id"`
	Method string         `json:"method,omitempty"`
	Params json.Marshaler `json:"params,omitempty"`

	Reply   CommandReply  `json:"-"` // The struct that will be filled when a matching command Id is found in a reply over the chrome websocket.
	Timeout time.Duration `json:"-"` // How long until the current command experiences a timeout, which will halt the entire process.
}

Command represents a single json request sent to the server over the websocket.

type CommandReply

type CommandReply interface {
	json.Unmarshaler
	MatchFrameID(frameID string, m []byte) (bool, error)
	GetFrameID() string
}

CommandReply specifies required methods for handling the json encoded replies received from the server.

type Error

type Error struct {
	Code    int64  `json:"code"`    // Error code.
	Message string `json:"message"` // Error message.
}

Error error type that is apart of the Message struct.

func (*Error) Error

func (e *Error) Error() string

Error satisfies the error interface.

type Event

type Event struct {
	Name       string
	Value      CommandReply
	IsRequired bool
	IsFound    bool
}

Event holds the value returned by the server based on a matching MethodType name.

type Frame

type Frame struct {
	*sync.RWMutex
	DOM       *dom.GetFlattenedDocumentReply
	FrameID   string
	LoaderID  string
	RequestID RequestID

	Browser *Browser

	// Conn is the connection to the websocket.
	Conn *websocket.Conn

	// AllComplete will trigger a close on the websocket.
	// Typically AllComplete or the OsInterrupt channels will fire and the write loop will send a request to close the socket.
	AllComplete chan struct{}

	// CacheCompleteChan sends the signal that the cached action is completed (all commands and events).
	CacheCompleteChan chan struct{}

	// CommandChan sends the signal that a command has been completed and an Action can advance.
	CommandChan chan (<-chan time.Time)

	// ActionChan sends Actions to the websocket.
	ActionChan chan []byte

	// CurrentAction stores the Action that is currently active.
	CurrentAction *Action

	// LogLevel specifies how much information should be f.Browser.Logged. Higher number results in more data.
	LogLevel LogLevelValue
}

Frame stores the current FrameID.

func Start

func Start(browser *Browser, logLevel LogLevelValue) *Frame

Start prepares required resources to begin automation.

func (*Frame) AddDOMNode

func (f *Frame) AddDOMNode(node dom.Node)

AddDOMNode allows for setting the Frame DOM value safely.

func (*Frame) Children

func (f *Frame) Children(parentID dom.NodeID) []dom.Node

Children returns a deep copy of the child nodes of the given parentID. NOTE: Expecting that code elsewhere has already populated the frame.DOM object.

func (*Frame) Clear

func (f *Frame) Clear()

Clear the action.

func (*Frame) CommandTimeout

func (f *Frame) CommandTimeout() <-chan time.Time

CommandTimeout once timed out will trigger an error and stop the automation.

func (*Frame) FindByAttribute

func (f *Frame) FindByAttribute(parentID dom.NodeID, attribute, value string) []dom.Node

FindByAttribute will search the existing cached DOM for nodes whose given attribute matches the given value starting at the root specified by nodeID. NOTE: Expecting that code elsewhere has already populated the frame.DOM object.

func (*Frame) GetCommandMethod

func (f *Frame) GetCommandMethod() string

GetCommandMethod returns the method of the command that is currently active or the very last method.

func (*Frame) GetDOM

func (f *Frame) GetDOM() *dom.GetFlattenedDocumentReply

GetDOM allows for getting the Frame DOM value safely. This could be a bit racy depending on when documentUpdated events are fired.

func (*Frame) GetFrameID

func (f *Frame) GetFrameID() string

GetFrameID returns the current frameID.

func (*Frame) HasCommandID

func (f *Frame) HasCommandID(id int64) bool

HasCommandID determines if an id matches the current action's command's unique id.

func (*Frame) HasEvent

func (f *Frame) HasEvent(name string) bool

HasEvent returns true when the action has an event with the given MethodType.

func (*Frame) IsCommandComplete

func (f *Frame) IsCommandComplete() bool

IsCommandComplete indicates that all commands are complete.

func (*Frame) IsComplete

func (f *Frame) IsComplete() bool

IsComplete indicates that all commands and events are complete.

func (*Frame) Log

func (f *Frame) Log()

Log writes the current state of the action to the f.Browser.Log.

func (*Frame) SetCurrentAction

func (f *Frame) SetCurrentAction(act *Action)

SetCurrentAction sets the current action that the frame is evaluating.

func (*Frame) SetDOM

func (f *Frame) SetDOM(dom *dom.GetFlattenedDocumentReply)

SetDOM allows for setting the Frame DOM value safely.

func (*Frame) SetEvent

func (f *Frame) SetEvent(frame *Frame, name string, m Message) error

SetEvent takes the given message and sets an event's params or results's.

func (*Frame) SetResult

func (f *Frame) SetResult(frame *Frame, m Message) error

SetResult applies the message returns to the current command and advances the command.

func (*Frame) Stop

func (f *Frame) Stop(closeBrowser bool)

Stop closes used resources.

func (*Frame) ToJSON

func (f *Frame) ToJSON() []byte

ToJSON encodes the current command. This is the chrome devtools protocol request. In the event that all commands are complete, continue to display the last command for debugging convenience.

type LogLevelValue

type LogLevelValue int

LogLevelValue is the type for loglevel information.

type Message

type Message struct {
	ID     int64           `json:"id,omitempty"`     // Unique message identifier.
	Method string          `json:"method,omitempty"` // Event or command type.
	Params json.RawMessage `json:"params,omitempty"` // Event or command parameters.
	Result json.RawMessage `json:"result,omitempty"` // Command return values.
	Error  *Error          `json:"error,omitempty"`  // Error message.
}

Message is the chrome DevTools Protocol message sent/read over the websocket connection.

type RequestID

type RequestID struct {
	*sync.RWMutex
	Value int64
}

RequestID stores the last value used for chrome devtool protocal requests being sent to the server.

func (*RequestID) GetNext

func (id *RequestID) GetNext() int64

GetNext is a convenience method for creating the unique ids required when performing chrome devtool protocol requests.

Directories

Path Synopsis
actions
example/runtime