Skip to content

Commit

Permalink
Add W3C Actions (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
x-Xymos authored Oct 28, 2021
1 parent 75ca383 commit 2fb003a
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 7 deletions.
63 changes: 62 additions & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,70 @@ func Example() {
}

fmt.Printf("%s", strings.Replace(output, "\n\n", "\n", -1))

// Example Output:
// Hello WebDriver!
//
// Program exited.

// The following shows an example of using the Actions API.
// Please refer to the WC3 Actions spec for more detailed information.
if err := wd.Get("http://play.golang.org/?simple=1"); err != nil {
panic(err)
}

// Create a point which will be used as an offset to click on the
// code editor text box element on the page.
offset := selenium.Point{X: 100, Y: 100}

// Call StorePointerActions to store a number of Pointer actions which
// will be executed sequentially.
// "mouse1" is used as a unique virtual device identifier for this
// and future actions.
// selenium.MousePointer is used to identify the type of the pointer.
// The stored action chain will move the pointer and click on the code
// editor text box on the page.
selenium.StorePointerActions("mouse1",
selenium.MousePointer,
// using selenium.FromViewport as the move origin
// which calculates the offset from 0,0.
// the other valid option is selenium.FromPointer.
selenium.PointerMoveAction(0, offset, selenium.FromViewport),
selenium.PointerPauseAction(250),
selenium.PointerDownAction(selenium.LeftButton),
selenium.PointerPauseAction(250),
selenium.PointerUpAction(selenium.LeftButton),
)

// Call StoreKeyActions to store a number of Key actions which
// will be executed sequentially.
// "keyboard1" is used as a unique virtual device identifier
// for this and future actions.
// The stored action chain will send keyboard inputs to the browser.
selenium.StoreKeyActions("keyboard1",
selenium.KeyDownAction(selenium.ControlKey),
selenium.KeyPauseAction(50),
selenium.KeyDownAction("a"),
selenium.KeyPauseAction(50),
selenium.KeyUpAction("a"),
selenium.KeyUpAction(selenium.ControlKey),
selenium.KeyDownAction("h"),
selenium.KeyDownAction("e"),
selenium.KeyDownAction("l"),
selenium.KeyDownAction("l"),
selenium.KeyDownAction("o"),
)

// Call PerformActions to execute stored action - based on
// the order of the previous calls, PointerActions will be
// executed first and then KeyActions.
if err := wd.PerformActions(); err != nil {
panic(err)
}

// Call ReleaseActions to release any PointerDown or
// KeyDown Actions that haven't been released through an Action.
if err := wd.ReleaseActions(); err != nil {
panic(err)
}

}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ require (
github.com/google/go-github/v27 v27.0.4
github.com/mediabuyerbot/go-crx3 v1.3.1
google.golang.org/api v0.7.0
)
)
106 changes: 102 additions & 4 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ var remoteErrors = map[int]string{
type remoteWD struct {
id, urlPrefix string
capabilities Capabilities

w3cCompatible bool
w3cCompatible bool
// storedActions stores KeyActions and PointerActions for later execution.
storedActions Actions
browser string
browserVersion semver.Version
}
Expand Down Expand Up @@ -230,6 +231,7 @@ func NewRemote(capabilities Capabilities, urlPrefix string) (WebDriver, error) {
if b := capabilities["browserName"]; b != nil {
wd.browser = b.(string)
}

if _, err := wd.NewSession(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -1095,8 +1097,104 @@ func (wd *remoteWD) KeyUp(keys string) error {
return wd.keyAction("keyUp", keys)
}

// TODO(minusnine): Implement PerformActions and ReleaseActions, for more
// direct access to the W3C specification.
// KeyPauseAction builds a KeyAction which pauses for the supplied duration.
func KeyPauseAction(duration time.Duration) KeyAction {
return KeyAction{
"type": "pause",
"duration": uint(duration / time.Millisecond),
}
}

// KeyUpAction builds a KeyAction press.
func KeyUpAction(key string) KeyAction {
return KeyAction{
"type": "keyUp",
"value": key,
}
}

// KeyDownAction builds a KeyAction which presses and holds
// the specified key.
func KeyDownAction(key string) KeyAction {
return KeyAction{
"type": "keyDown",
"value": key,
}
}

// PointerPause builds a PointerAction which pauses for the supplied duration.
func PointerPauseAction(duration time.Duration) PointerAction {
return PointerAction{
"type": "pause",
"duration": uint(duration / time.Millisecond),
}
}

// PointerMove builds a PointerAction which moves the pointer.
func PointerMoveAction(duration time.Duration, offset Point, origin PointerMoveOrigin) PointerAction {
return PointerAction{
"type": "pointerMove",
"duration": uint(duration / time.Millisecond),
"origin": origin,
"x": offset.X,
"y": offset.Y,
}
}

// PointerUp builds an action which releases the specified pointer key.
func PointerUpAction(button MouseButton) PointerAction {
return PointerAction{
"type": "pointerUp",
"button": button,
}
}

// PointerDown builds a PointerAction which presses
// and holds the specified pointer key.
func PointerDownAction(button MouseButton) PointerAction {
return PointerAction{
"type": "pointerDown",
"button": button,
}
}

func (wd *remoteWD) StoreKeyActions(inputID string, actions ...KeyAction) {
rawActions := []map[string]interface{}{}
for _, action := range actions {
rawActions = append(rawActions, action)
}
wd.storedActions = append(wd.storedActions, map[string]interface{}{
"type": "key",
"id": inputID,
"actions": rawActions,
})
}

func (wd *remoteWD) StorePointerActions(inputID string, pointer PointerType, actions ...PointerAction) {
rawActions := []map[string]interface{}{}
for _, action := range actions {
rawActions = append(rawActions, action)
}
wd.storedActions = append(wd.storedActions, map[string]interface{}{
"type": "pointer",
"id": inputID,
"parameters": map[string]string{"pointerType": string(pointer)},
"actions": rawActions,
})
}

func (wd *remoteWD) PerformActions() error {
err := wd.voidCommand("/session/%s/actions", map[string]interface{}{
"actions": wd.storedActions,
})
wd.storedActions = nil
return err
}

func (wd *remoteWD) ReleaseActions() error {
return voidCommand("DELETE", wd.requestURL("/session/%s/actions", wd.id), nil)
}

func (wd *remoteWD) DismissAlert() error {
return wd.voidCommand("/session/%s/alert/dismiss", nil)
}
Expand Down
54 changes: 53 additions & 1 deletion selenium.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ const (
ByCSSSelector = "css selector"
)

type MouseButton int

// Mouse buttons.
const (
LeftButton = iota
LeftButton MouseButton = iota
MiddleButton
RightButton
)
Expand Down Expand Up @@ -223,6 +225,36 @@ const (
SameSiteEmpty SameSite = ""
)

// PointerType is the type of pointer used by StorePointerActions.
// There are 3 different types according to the WC3 implementation.
type PointerType string

const (
MousePointer PointerType = "mouse"
PenPointer = "pen"
TouchPointer = "touch"
)

// PointerMoveOrigin controls how the offset for
// the pointer move action is calculated.
type PointerMoveOrigin string

const (
// FromViewport calculates the offset from the viewport at 0,0.
FromViewport PointerMoveOrigin = "viewport"
// FromPointer calculates the offset from the current pointer position.
FromPointer = "pointer"
)

// KeyAction represents an activity involving a keyboard key.
type KeyAction map[string]interface{}

// PointerAction represents an activity involving a pointer.
type PointerAction map[string]interface{}

// Actions stores KeyActions and PointerActions for later execution.
type Actions []map[string]interface{}

// WebDriver defines methods supported by WebDriver drivers.
type WebDriver interface {
// Status returns various pieces of information about the server environment.
Expand Down Expand Up @@ -330,6 +362,26 @@ type WebDriver interface {
// ButtonUp causes the left mouse button to be released.
ButtonUp() error

// StoreKeyActions store provided actions until they are executed
// by PerformActions or released by ReleaseActions.
// inputID is a string used as a unique virtual device identifier for this
// and future actions, the value can be set to any valid string
// and used to refer to this specific device in future calls.
StoreKeyActions(inputID string, actions ...KeyAction)

// StorePointerActions store provided actions until they are executed
// by PerformActions or released by ReleaseActions.
// inputID is a string used as a unique virtual device identifier for this
// and future actions, the value can be set to any valid string
// and used to refer to this specific device in future calls.
StorePointerActions(inputID string, pointer PointerType, actions ...PointerAction)

// PerformActions executes actions previously stored by calls to StorePointerActions and StoreKeyActions.
PerformActions() error
// ReleaseActions releases keys and pointer buttons if they are pressed,
// triggering any events as if they were performed by a regular action.
ReleaseActions() error

// SendModifier sends the modifier key to the active element. The modifier
// can be one of ShiftKey, ControlKey, AltKey, MetaKey.
//
Expand Down

0 comments on commit 2fb003a

Please sign in to comment.