Skip to content
This repository has been archived by the owner on Jan 30, 2025. It is now read-only.

Add combo key press #326

Merged
merged 1 commit into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 85 additions & 14 deletions common/keyboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package common
import (
"context"
"fmt"
"strings"
"time"

"github.com/grafana/xk6-browser/api"
Expand Down Expand Up @@ -88,7 +89,8 @@ func (k *Keyboard) Press(key string, opts goja.Value) {
if err := kbdOpts.Parse(k.ctx, opts); err != nil {
k6ext.Panic(k.ctx, "parsing keyboard options: %w", err)
}
if err := k.press(key, kbdOpts); err != nil {

if err := k.comboPress(key, kbdOpts); err != nil {
Comment on lines +92 to +93
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: No need to put an empty line here, but your call :)

k6ext.Panic(k.ctx, "pressing key: %w", err)
}
}
Expand Down Expand Up @@ -190,9 +192,13 @@ func (k *Keyboard) keyDefinitionFromKey(key keyboardlayout.KeyInput) keyboardlay
srcKeyDef, ok = k.layout.KeyDefinition(key)
}
// Try to find with the shift key value
// e.g. for `@`, the shift modifier needs to
// be used.
var foundInShift bool
if !ok {
srcKeyDef = k.layout.ShiftKeyDefinition(key)
shift = k.modifiers | ModifierKeyShift
foundInShift = true
}

var keyDef keyboardlayout.KeyDefinition
Expand All @@ -217,7 +223,18 @@ func (k *Keyboard) keyDefinitionFromKey(key keyboardlayout.KeyInput) keyboardlay
if srcKeyDef.Text != "" {
keyDef.Text = srcKeyDef.Text
}
if shift != 0 && srcKeyDef.ShiftKey != "" {
// Shift is only used on keys which are `KeyX`` (where X is
// A-Z), or on keys which require shift to be pressed e.g.
// `@`, and shift must be pressed as well as a shiftKey
// text value present for the key.
// Not all keys have a text value when shift is pressed
// e.g. `Control`.
// When a key such as `2` is pressed, we must ignore shift
// otherwise we would type `@`.
isKeyXOrOnShiftLayerAndShiftUsed := (strings.HasPrefix(string(key), "Key") || foundInShift) &&
shift != 0 &&
srcKeyDef.ShiftKey != ""
if isKeyXOrOnShiftLayerAndShiftUsed {
keyDef.Key = srcKeyDef.ShiftKey
keyDef.Text = srcKeyDef.ShiftKey
}
Expand All @@ -242,13 +259,56 @@ func (k *Keyboard) modifierBitFromKeyName(key string) int64 {
return 0
}

func (k *Keyboard) comboPress(keys string, opts *KeyboardOptions) error {
if opts.Delay > 0 {
if err := wait(k.ctx, opts.Delay); err != nil {
return err
}
}

kk := split(keys)
for _, key := range kk {
if err := k.down(key); err != nil {
return fmt.Errorf("cannot do key down: %w", err)
}
}

for i := range kk {
key := kk[len(kk)-i-1]
if err := k.up(key); err != nil {
return fmt.Errorf("cannot do key up: %w", err)
}
}

return nil
}

// This splits the string on `+`.
// If `+` on it's own is passed, it will return ["+"].
// If `++` is passed in, it will return ["+", ""].
// If `+++` is passed in, it will return ["+", "+"].
func split(keys string) []string {
var (
kk = make([]string, 0)
s strings.Builder
)
for _, r := range keys {
if r == '+' && s.Len() > 0 {
kk = append(kk, s.String())
s.Reset()
} else {
s.WriteRune(r)
}
}
kk = append(kk, s.String())

return kk
}

func (k *Keyboard) press(key string, opts *KeyboardOptions) error {
if opts.Delay != 0 {
t := time.NewTimer(time.Duration(opts.Delay) * time.Millisecond)
select {
case <-k.ctx.Done():
t.Stop()
case <-t.C:
if opts.Delay > 0 {
if err := wait(k.ctx, opts.Delay); err != nil {
return err
}
}
if err := k.down(key); err != nil {
Expand All @@ -260,12 +320,9 @@ func (k *Keyboard) press(key string, opts *KeyboardOptions) error {
func (k *Keyboard) typ(text string, opts *KeyboardOptions) error {
layout := keyboardlayout.GetKeyboardLayout(k.layoutName)
for _, c := range text {
if opts.Delay != 0 {
t := time.NewTimer(time.Duration(opts.Delay) * time.Millisecond)
select {
case <-k.ctx.Done():
t.Stop()
case <-t.C:
if opts.Delay > 0 {
if err := wait(k.ctx, opts.Delay); err != nil {
return err
}
}
keyInput := keyboardlayout.KeyInput(c)
Expand All @@ -281,3 +338,17 @@ func (k *Keyboard) typ(text string, opts *KeyboardOptions) error {
}
return nil
}

func wait(ctx context.Context, delay int64) error {
t := time.NewTimer(time.Duration(delay) * time.Millisecond)
select {
case <-ctx.Done():
if !t.Stop() {
<-t.C
}
return fmt.Errorf("%w", ctx.Err())
case <-t.C:
}

return nil
}
78 changes: 78 additions & 0 deletions common/keyboard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package common

import (
"testing"

"github.com/grafana/xk6-browser/k6ext/k6test"

"github.com/stretchr/testify/assert"
)

func TestSplit(t *testing.T) {
type args struct {
keys string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "empty slice on empty string",
args: args{
keys: "",
},
want: []string{""},
},
{
name: "empty slice on string without separator",
args: args{
keys: "HelloWorld!",
},
want: []string{"HelloWorld!"},
},
{
name: "string split with separator",
args: args{
keys: "Hello+World+!",
},
want: []string{"Hello", "World", "!"},
},
{
name: "do not split on single +",
args: args{
keys: "+",
},
want: []string{"+"},
},
{
name: "split ++ to + and ''",
args: args{
keys: "++",
},
want: []string{"+", ""},
},
{
name: "split +++ to + and +",
args: args{
keys: "+++",
},
want: []string{"+", "+"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := split(tt.args.keys)

assert.Equal(t, tt.want, got)
})
}
}

func TestKeyboardPress(t *testing.T) {
t.Run("panics when '' empty key passed in", func(t *testing.T) {
vu := k6test.NewVU(t)
k := NewKeyboard(vu.Context(), nil)
assert.Panics(t, func() { k.Press("", nil) })
})
}
90 changes: 86 additions & 4 deletions tests/keyboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ func TestKeyboardPress(t *testing.T) {
})

t.Run("combo", func(t *testing.T) {
t.Skip("FIXME") // See https://github.com/grafana/xk6-browser/issues/285
p := tb.NewPage(nil)
cp, ok := p.(*common.Page)
require.True(t, ok)
Expand All @@ -57,11 +56,37 @@ func TestKeyboardPress(t *testing.T) {
el := p.Query("input")
p.Focus("input", nil)

kb.Press("Shift++", nil)
kb.Press("Shift+=", nil)
kb.Press("Shift+@", nil)
kb.Press("Shift+6", nil)
kb.Press("Shift+KeyA", nil)
kb.Press("Shift+b", nil)
kb.Press("C", nil)
kb.Press("d", nil)
require.Equal(t, "AbCd", el.InputValue(nil))
kb.Press("Shift+C", nil)

kb.Press("Control+KeyI", nil)
kb.Press("Control+J", nil)
kb.Press("Control+k", nil)

require.Equal(t, "+=@6AbC", el.InputValue(nil))
})

t.Run("meta", func(t *testing.T) {
t.Skip("FIXME") // See https://github.com/grafana/xk6-browser/issues/424
p := tb.NewPage(nil)
cp, ok := p.(*common.Page)
require.True(t, ok)
kb := cp.Keyboard

p.SetContent(`<input>`, nil)
el := p.Query("input")
p.Focus("input", nil)

kb.Press("Shift+KeyA", nil)
kb.Press("Shift+b", nil)
kb.Press("Shift+C", nil)

require.Equal(t, "AbC", el.InputValue(nil))

metaKey := "Control"
if runtime.GOOS == "darwin" {
Expand All @@ -72,6 +97,63 @@ func TestKeyboardPress(t *testing.T) {
assert.Equal(t, "", el.InputValue(nil))
})

t.Run("type does not split on +", func(t *testing.T) {
p := tb.NewPage(nil)
cp, ok := p.(*common.Page)
require.True(t, ok)
kb := cp.Keyboard
imiric marked this conversation as resolved.
Show resolved Hide resolved

p.SetContent(`<textarea>`, nil)
el := p.Query("textarea")
p.Focus("textarea", nil)

kb.Type("L+m+KeyN", nil)
assert.Equal(t, "L+m+KeyN", el.InputValue(nil))
})

t.Run("capitalization", func(t *testing.T) {
p := tb.NewPage(nil)
cp, ok := p.(*common.Page)
require.True(t, ok)
kb := cp.Keyboard

p.SetContent(`<textarea>`, nil)
el := p.Query("textarea")
p.Focus("textarea", nil)

kb.Press("C", nil)
kb.Press("d", nil)
kb.Press("KeyE", nil)

kb.Down("Shift")
kb.Down("f")
kb.Up("f")
kb.Down("G")
kb.Up("G")
kb.Down("KeyH")
kb.Up("KeyH")
kb.Up("Shift")

assert.Equal(t, "CdefGH", el.InputValue(nil))
})

t.Run("type not affected by shift", func(t *testing.T) {
p := tb.NewPage(nil)
cp, ok := p.(*common.Page)
require.True(t, ok)
kb := cp.Keyboard

p.SetContent(`<textarea>`, nil)
el := p.Query("textarea")
p.Focus("textarea", nil)

kb.Down("Shift")
kb.Type("oPqR", nil)
kb.Up("Shift")

assert.Equal(t, "oPqR", el.InputValue(nil))
})

t.Run("newline", func(t *testing.T) {
p := tb.NewPage(nil)
cp, ok := p.(*common.Page)
Expand Down
1 change: 0 additions & 1 deletion tests/locator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,6 @@ func TestLocatorElementState(t *testing.T) {
name string
do func(api.Locator, *testBrowser)
}{

{
"IsChecked", func(l api.Locator, tb *testBrowser) { l.IsChecked(timeout(tb)) },
},
Expand Down