diff --git a/e2e/pages/dynamic/components/pages/events/countable.js b/e2e/pages/dynamic/components/pages/events/countable.js new file mode 100644 index 00000000..e69de29b diff --git a/e2e/tests/dynamic/element/click/click_by_selector_with_count.fql b/e2e/tests/dynamic/element/click/click_by_selector_with_count.fql new file mode 100644 index 00000000..167de98b --- /dev/null +++ b/e2e/tests/dynamic/element/click/click_by_selector_with_count.fql @@ -0,0 +1,16 @@ +LET url = @dynamic + "/#/forms" +LET page = DOCUMENT(url, true) + +WAIT_ELEMENT(page, "form") + +LET input = ELEMENT(page, "#text_input") + +INPUT(input, "Foo") + +CLICK(page, "#text_input", 2) + +INPUT(input, "Bar") + +WAIT(100) + +RETURN EXPECT("Bar", input.value) \ No newline at end of file diff --git a/e2e/tests/dynamic/element/click/click_with_count.fql b/e2e/tests/dynamic/element/click/click_with_count.fql new file mode 100644 index 00000000..77403bb9 --- /dev/null +++ b/e2e/tests/dynamic/element/click/click_with_count.fql @@ -0,0 +1,16 @@ +LET url = @dynamic + "/#/forms" +LET page = DOCUMENT(url, true) + +WAIT_ELEMENT(page, "form") + +LET input = ELEMENT(page, "#text_input") + +INPUT(input, "Foo") + +CLICK(input, 2) + +INPUT(input, "Bar") + +WAIT(100) + +RETURN EXPECT("Bar", input.value) \ No newline at end of file diff --git a/go.sum b/go.sum index d113a6c0..3bf476a0 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/derekparker/trie v0.0.0-20190812220523-e66023ee76eb h1:HGjKnH6D1WD8sc9SfCkWkeD4OteWPPXD+ayJY+P1Bgk= github.com/derekparker/trie v0.0.0-20190812220523-e66023ee76eb/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= @@ -29,12 +30,16 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/labstack/echo/v4 v4.1.10 h1:/yhIpO50CBInUbE/nHJtGIyhBv0dJe2cDAYxc3V3uMo= github.com/labstack/echo/v4 v4.1.10/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/mafredri/cdp v0.24.2 h1:Rzhj/EQw9opbiwUpNML7P+4Hvf0/nSYPaDbiCEpILOM= github.com/mafredri/cdp v0.24.2/go.mod h1:hgdiA0yp1uqhSaDOHJWPgXpMbh+LAfUdD9vbN2AM8gE= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= @@ -55,10 +60,13 @@ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:s github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/pkg/drivers/cdp/document.go b/pkg/drivers/cdp/document.go index 31370328..933cad48 100644 --- a/pkg/drivers/cdp/document.go +++ b/pkg/drivers/cdp/document.go @@ -317,14 +317,6 @@ func (doc *HTMLDocument) GetURL() values.String { return values.NewString(doc.frames.Frame.URL) } -func (doc *HTMLDocument) ClickBySelector(ctx context.Context, selector values.String) error { - return doc.element.ClickBySelector(ctx, selector) -} - -func (doc *HTMLDocument) ClickBySelectorAll(ctx context.Context, selector values.String) error { - return doc.element.ClickBySelectorAll(ctx, selector) -} - func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) error { return doc.input.MoveMouseByXY(ctx, float64(x), float64(y)) } diff --git a/pkg/drivers/cdp/element.go b/pkg/drivers/cdp/element.go index 6cc8d578..d0e38290 100644 --- a/pkg/drivers/cdp/element.go +++ b/pkg/drivers/cdp/element.go @@ -1026,16 +1026,16 @@ func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, val return err } -func (el *HTMLElement) Click(ctx context.Context) error { - return el.input.Click(ctx, el.id.objectID) +func (el *HTMLElement) Click(ctx context.Context, count values.Int) error { + return el.input.Click(ctx, el.id.objectID, int(count)) } -func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String) error { - return el.input.ClickBySelector(ctx, el.id.nodeID, selector.String()) +func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String, count values.Int) error { + return el.input.ClickBySelector(ctx, el.id.nodeID, selector.String(), int(count)) } -func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String) error { - return el.input.ClickBySelectorAll(ctx, el.id.nodeID, selector.String()) +func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error { + return el.input.ClickBySelectorAll(ctx, el.id.nodeID, selector.String(), int(count)) } func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values.Int) error { diff --git a/pkg/drivers/cdp/input/manager.go b/pkg/drivers/cdp/input/manager.go index 9a3069df..1379cb7f 100644 --- a/pkg/drivers/cdp/input/manager.go +++ b/pkg/drivers/cdp/input/manager.go @@ -149,7 +149,7 @@ func (m *Manager) MoveMouseByXY(ctx context.Context, x, y float64) error { return m.mouse.Move(ctx, x, y) } -func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID) error { +func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID, count int) error { if err := m.ScrollIntoView(ctx, objectID); err != nil { return err } @@ -160,14 +160,16 @@ func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID) er return err } - if err := m.mouse.Click(ctx, points.X, points.Y, drivers.DefaultInputDelay); err != nil { + delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond + + if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil { return nil } return nil } -func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error { +func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error { if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil { return err } @@ -184,14 +186,16 @@ func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, return err } - if err := m.mouse.Click(ctx, points.X, points.Y, drivers.DefaultInputDelay); err != nil { + delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond + + if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil { return nil } return nil } -func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector string) error { +func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error { if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil { return err } @@ -203,8 +207,7 @@ func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeI } for _, nodeID := range found.NodeIDs { - _, min := core.NumberBoundaries(drivers.DefaultInputDelay * 2) - beforeTypeDelay := time.Duration(min) * time.Millisecond + beforeTypeDelay := time.Duration(core.NumberLowerBoundary(drivers.DefaultMouseDelay*10)) * time.Millisecond time.Sleep(beforeTypeDelay) @@ -214,7 +217,9 @@ func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeI return err } - if err := m.mouse.Click(ctx, points.X, points.Y, drivers.DefaultInputDelay); err != nil { + delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond + + if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil { return nil } } @@ -345,7 +350,8 @@ func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID, } func (m *Manager) ClearByXY(ctx context.Context, points Quad) error { - err := m.mouse.ClickWithCount(ctx, points.X, points.Y, 3, 5) + delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond + err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, 2) if err != nil { return err diff --git a/pkg/drivers/cdp/input/mouse.go b/pkg/drivers/cdp/input/mouse.go index a1dbe1b7..a6fec9c8 100644 --- a/pkg/drivers/cdp/input/mouse.go +++ b/pkg/drivers/cdp/input/mouse.go @@ -18,11 +18,11 @@ func NewMouse(client *cdp.Client) *Mouse { return &Mouse{client, 0, 0} } -func (m *Mouse) Click(ctx context.Context, x, y float64, delay int) error { - return m.ClickWithCount(ctx, x, y, 1, delay) +func (m *Mouse) Click(ctx context.Context, x, y float64, delay time.Duration) error { + return m.ClickWithCount(ctx, x, y, delay, 1) } -func (m *Mouse) ClickWithCount(ctx context.Context, x, y float64, count, delay int) error { +func (m *Mouse) ClickWithCount(ctx context.Context, x, y float64, delay time.Duration, count int) error { if err := m.Move(ctx, x, y); err != nil { return err } @@ -31,9 +31,7 @@ func (m *Mouse) ClickWithCount(ctx context.Context, x, y float64, count, delay i return err } - releaseDelay := randomDuration(delay) - - time.Sleep(releaseDelay * time.Millisecond) + time.Sleep(randomDuration(int(delay))) return m.UpWithCount(ctx, "left", count) } @@ -46,8 +44,8 @@ func (m *Mouse) DownWithCount(ctx context.Context, button string, count int) err return m.client.Input.DispatchMouseEvent( ctx, input.NewDispatchMouseEventArgs("mousePressed", m.x, m.y). - SetClickCount(count). - SetButton(button), + SetButton(button). + SetClickCount(count), ) } @@ -59,8 +57,8 @@ func (m *Mouse) UpWithCount(ctx context.Context, button string, count int) error return m.client.Input.DispatchMouseEvent( ctx, input.NewDispatchMouseEventArgs("mouseReleased", m.x, m.y). - SetClickCount(count). - SetButton(button), + SetButton(button). + SetClickCount(count), ) } diff --git a/pkg/drivers/consts.go b/pkg/drivers/consts.go index 11fc58f9..144dc12a 100644 --- a/pkg/drivers/consts.go +++ b/pkg/drivers/consts.go @@ -3,6 +3,7 @@ package drivers const ( DefaultPageLoadTimeout = 60000 DefaultWaitTimeout = 5000 - DefaultInputDelay = 25 + DefaultKeyboardDelay = 25 + DefaultMouseDelay = 10 DefaultTimeout = 30000 ) diff --git a/pkg/drivers/http/document.go b/pkg/drivers/http/document.go index 41167f25..beb2ad37 100644 --- a/pkg/drivers/http/document.go +++ b/pkg/drivers/http/document.go @@ -207,14 +207,6 @@ func (doc *HTMLDocument) GetParentDocument() drivers.HTMLDocument { return doc.parent } -func (doc *HTMLDocument) ClickBySelector(_ context.Context, _ values.String) error { - return core.ErrNotSupported -} - -func (doc *HTMLDocument) ClickBySelectorAll(_ context.Context, _ values.String) error { - return core.ErrNotSupported -} - func (doc *HTMLDocument) ScrollTop(_ context.Context) error { return core.ErrNotSupported } diff --git a/pkg/drivers/http/element.go b/pkg/drivers/http/element.go index d066d10f..ad0db7f7 100644 --- a/pkg/drivers/http/element.go +++ b/pkg/drivers/http/element.go @@ -489,15 +489,15 @@ func (el *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) { return common.NewIterator(el) } -func (el *HTMLElement) Click(_ context.Context) error { +func (el *HTMLElement) Click(_ context.Context, _ values.Int) error { return core.ErrNotSupported } -func (el *HTMLElement) ClickBySelector(_ context.Context, _ values.String) error { +func (el *HTMLElement) ClickBySelector(_ context.Context, _ values.String, _ values.Int) error { return core.ErrNotSupported } -func (el *HTMLElement) ClickBySelectorAll(_ context.Context, _ values.String) error { +func (el *HTMLElement) ClickBySelectorAll(_ context.Context, _ values.String, _ values.Int) error { return core.ErrNotSupported } diff --git a/pkg/drivers/value.go b/pkg/drivers/value.go index 245d27cc..84e6ec2f 100644 --- a/pkg/drivers/value.go +++ b/pkg/drivers/value.go @@ -43,10 +43,6 @@ type ( ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error) XPath(ctx context.Context, expression values.String) (core.Value, error) - - ClickBySelector(ctx context.Context, selector values.String) error - - ClickBySelectorAll(ctx context.Context, selector values.String) error } // HTMLElement is the most general base interface which most objects in a GetMainFrame implement. @@ -97,7 +93,11 @@ type ( GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) - Click(ctx context.Context) error + Click(ctx context.Context, count values.Int) error + + ClickBySelector(ctx context.Context, selector values.String, count values.Int) error + + ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error Clear(ctx context.Context) error diff --git a/pkg/stdlib/html/click.go b/pkg/stdlib/html/click.go index d6bd85d3..6d79a5c8 100644 --- a/pkg/stdlib/html/click.go +++ b/pkg/stdlib/html/click.go @@ -6,13 +6,15 @@ import ( "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/runtime/values/types" ) // Click dispatches click event on a given element // @param source (Open | GetElement) - Event source. -// @param selector (String, optional) - Optional selector. +// @param selectorOrCount (String | Int, optional) - Optional selector or count of clicks. +// @param count (Int, optional) - Optional count of clicks. func Click(ctx context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 1, 2) + err := core.ValidateArgs(args, 1, 3) if err != nil { return values.False, err @@ -26,7 +28,44 @@ func Click(ctx context.Context, args ...core.Value) (core.Value, error) { // CLICK(elOrDoc) if len(args) == 1 { - return values.True, el.Click(ctx) + return values.True, el.Click(ctx, 1) + } + + if len(args) == 2 { + err := core.ValidateType(args[1], types.String, types.Int) + + if err != nil { + return values.False, err + } + + if args[1].Type() == types.String { + selector := values.ToString(args[1]) + exists, err := el.ExistsBySelector(ctx, selector) + + if err != nil { + return values.False, err + } + + if !exists { + return exists, nil + } + + return exists, el.ClickBySelector(ctx, selector, 1) + } + + return values.True, el.Click(ctx, values.ToInt(args[1])) + } + + err = core.ValidateType(args[1], types.String) + + if err != nil { + return values.False, err + } + + err = core.ValidateType(args[2], types.Int) + + if err != nil { + return values.False, err } // CLICK(doc, selector) @@ -41,5 +80,7 @@ func Click(ctx context.Context, args ...core.Value) (core.Value, error) { return exists, nil } - return exists, el.ClickBySelector(ctx, selector) + count := values.ToInt(args[2]) + + return exists, el.ClickBySelector(ctx, selector, count) } diff --git a/pkg/stdlib/html/click_all.go b/pkg/stdlib/html/click_all.go index 7c6cf75f..5b9643a0 100644 --- a/pkg/stdlib/html/click_all.go +++ b/pkg/stdlib/html/click_all.go @@ -6,14 +6,16 @@ import ( "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/runtime/values/types" ) // ClickAll dispatches click event on all matched element // @param source (Open) - Open. // @param selector (String) - Selector. +// @param count (Int, optional) - Optional count of clicks. // @returns (Boolean) - Returns true if matched at least one element. func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 2) + err := core.ValidateArgs(args, 2, 3) if err != nil { return values.False, err @@ -22,7 +24,7 @@ func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) { el, err := drivers.ToElement(args[0]) if err != nil { - return values.None, err + return values.False, err } selector := values.ToString(args[1]) @@ -37,5 +39,17 @@ func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) { return values.False, nil } - return values.True, el.ClickBySelectorAll(ctx, selector) + count := values.NewInt(1) + + if len(args) == 3 { + err := core.ValidateType(args[2], types.Int) + + if err != nil { + return values.False, err + } + + count = values.ToInt(args[2]) + } + + return values.True, el.ClickBySelectorAll(ctx, selector, count) } diff --git a/pkg/stdlib/html/input.go b/pkg/stdlib/html/input.go index c1c4512f..89011ba9 100644 --- a/pkg/stdlib/html/input.go +++ b/pkg/stdlib/html/input.go @@ -27,7 +27,7 @@ func Input(ctx context.Context, args ...core.Value) (core.Value, error) { return values.False, err } - delay := values.NewInt(drivers.DefaultInputDelay) + delay := values.NewInt(drivers.DefaultKeyboardDelay) // INPUT(el, value) if len(args) == 2 { diff --git a/pkg/stdlib/html/pagination.go b/pkg/stdlib/html/pagination.go index 38cb3239..904778dd 100644 --- a/pkg/stdlib/html/pagination.go +++ b/pkg/stdlib/html/pagination.go @@ -102,7 +102,7 @@ func (i *PagingIterator) Next(ctx context.Context) (core.Value, core.Value, erro return values.None, values.None, core.ErrNoMoreData } - err = i.document.ClickBySelector(ctx, i.selector) + err = i.document.GetElement().ClickBySelector(ctx, i.selector, 1) if err != nil { return values.None, values.None, err diff --git a/revive.toml b/revive.toml index c9a52e70..5b5684da 100644 --- a/revive.toml +++ b/revive.toml @@ -4,10 +4,6 @@ confidence = 0.8 errorCode = 1 warningCode = 0 -[rule.package-comments] - severity = "warning" -[rule.exported] - severity = "warning" [rule.blank-imports] [rule.context-as-argument] [rule.context-keys-type]