diff --git a/e2e/pages/dynamic/components/pages/lists/index.js b/e2e/pages/dynamic/components/pages/lists/index.js index 9284eec9..c3937175 100644 --- a/e2e/pages/dynamic/components/pages/lists/index.js +++ b/e2e/pages/dynamic/components/pages/lists/index.js @@ -4,8 +4,8 @@ const ITEMS = [{"artist":"Lil Tecca","track":"Ransom"},{"artist":"NLE Choppa","t export default class ListsPage extends React.Component { render() { - const items = ITEMS.map((i) => { - return e("li", { className: "list-group-item track"}, [ + const items = ITEMS.map((i, idx) => { + return e("li", { className: "list-group-item track", 'data-index': idx }, [ e("div", { className: "track-details"}, [ e("h5", { className: "track-artist"}, i.artist), e("small", { className: "track-name"}, i.track) diff --git a/e2e/pages/static/list.html b/e2e/pages/static/list.html new file mode 100644 index 00000000..2ca092c3 --- /dev/null +++ b/e2e/pages/static/list.html @@ -0,0 +1,11 @@ + + + + Ferret E2E SPA + + + + + +
  • Lil Tecca
    Ransom
  • NLE Choppa
    Shotta Flow (Feat. Blueface) [Remix]
  • Baby Jesus (DaBaby)
    Suge
  • NLE Choppa
    Shotta Flow 3
  • Lil Tecca
    Lil Tecca - Did It Again
  • NLE Choppa
    Shotta Flow
  • Ynw Melly
    Dangerously In Love (772 Love Pt. 2)
  • POLO G
    Polo G feat. Lil TJay - Pop Out
  • MUSTARD
    Ballin' (feat. Roddy Ricch)
  • Lil Nas X
    Panini
  • Juice WRLD
    Juice Wrld - RUN
  • Shordie Shordie
    Betchua (Bitchuary)
  • Post Malone
    Goodbyes (feat. Young Thug)
  • LIL UZI VERT
    Sanguine Paradise
  • Calboy
    Envy Me
  • Ambjaay
    Uno
  • Lil Tecca
    Lil Tecca - Bossanova
  • Lil Baby
    Baby
  • Lil Tjay
    Lil Tjay - Brothers (Prod by JDONTHATRACK & Protegebeatz)
  • YK Osiris
    Worth It
+ \ No newline at end of file diff --git a/e2e/tests/dynamic/element/siblings/next.fql b/e2e/tests/dynamic/element/siblings/next.fql new file mode 100644 index 00000000..450c41bb --- /dev/null +++ b/e2e/tests/dynamic/element/siblings/next.fql @@ -0,0 +1,12 @@ +LET doc = DOCUMENT(@lab.cdn.dynamic + "/#/lists", { driver:"cdp" }) + +LET current = ELEMENT(doc, ".track") +T::NOT::NONE(current) +LET next = current.nextElementSibling +T::NOT::NONE(next) + +LET currentIdx = TO_INT(current.attributes['data-index']) +LET nextIdx = TO_INT(next.attributes['data-index']) +T::GT(nextIdx, currentIdx) + +RETURN NONE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/siblings/prev.fql b/e2e/tests/dynamic/element/siblings/prev.fql new file mode 100644 index 00000000..aeb7f77c --- /dev/null +++ b/e2e/tests/dynamic/element/siblings/prev.fql @@ -0,0 +1,12 @@ +LET doc = DOCUMENT(@lab.cdn.dynamic + "/#/lists", { driver:"cdp" }) + +LET current = ELEMENT(doc, '[data-index="1"]') +T::NOT::NONE(current) +LET prev = current.previousElementSibling +T::NOT::NONE(prev) + +LET currentIdx = TO_INT(current.attributes['data-index']) +LET prevIdx = TO_INT(prev.attributes['data-index']) +T::LT(prevIdx, currentIdx) + +RETURN NONE \ No newline at end of file diff --git a/e2e/tests/static/element/siblings/next.fql b/e2e/tests/static/element/siblings/next.fql new file mode 100644 index 00000000..da48803e --- /dev/null +++ b/e2e/tests/static/element/siblings/next.fql @@ -0,0 +1,13 @@ +LET url = @lab.cdn.static + '/list.html' +LET doc = DOCUMENT(url) + +LET current = ELEMENT(doc, ".track") +T::NOT::NONE(current) +LET next = current.nextElementSibling.nextElementSibling +T::NOT::NONE(next) + +LET currentIdx = TO_INT(current.attributes['data-index']) +LET nextIdx = TO_INT(next.attributes['data-index']) +T::GT(nextIdx, currentIdx) + +RETURN NONE \ No newline at end of file diff --git a/e2e/tests/static/element/siblings/prev.fql b/e2e/tests/static/element/siblings/prev.fql new file mode 100644 index 00000000..cab52d09 --- /dev/null +++ b/e2e/tests/static/element/siblings/prev.fql @@ -0,0 +1,13 @@ +LET url = @lab.cdn.static + '/list.html' +LET doc = DOCUMENT(url) + +LET current = ELEMENT(doc, '[data-index="1"]') +T::NOT::NONE(current) +LET prev = current.previousElementSibling +T::NOT::NONE(prev) + +LET currentIdx = TO_INT(current.attributes['data-index']) +LET prevIdx = TO_INT(prev.attributes['data-index']) +T::LT(prevIdx, currentIdx) + +RETURN NONE \ No newline at end of file diff --git a/pkg/compiler/compiler_member_test.go b/pkg/compiler/compiler_member_test.go index ebafbcc5..ce6df841 100644 --- a/pkg/compiler/compiler_member_test.go +++ b/pkg/compiler/compiler_member_test.go @@ -215,6 +215,26 @@ func TestMember(t *testing.T) { So(string(out), ShouldEqual, `"Bob"`) }) + + Convey("Computed property with quotes", func() { + c := compiler.New() + + p := c.MustCompile(` + LET obj = { + attributes: { + 'data-index': 1 + } + } + + RETURN obj.attributes['data-index'] + `) + + out, err := p.Run(context.Background()) + + So(err, ShouldBeNil) + + So(string(out), ShouldEqual, "1") + }) }) } diff --git a/pkg/drivers/cdp/dom/element.go b/pkg/drivers/cdp/dom/element.go index 9d03bd31..f1c0fe44 100644 --- a/pkg/drivers/cdp/dom/element.go +++ b/pkg/drivers/cdp/dom/element.go @@ -527,6 +527,51 @@ func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) (core.V ) } +func (el *HTMLElement) GetPreviousElementSibling(ctx context.Context) (core.Value, error) { + return el.getSibling(ctx, templates.GetPreviousElementSibling()) +} + +func (el *HTMLElement) GetNextElementSibling(ctx context.Context) (core.Value, error) { + return el.getSibling(ctx, templates.GetNextElementSibling()) +} + +func (el *HTMLElement) getSibling(ctx context.Context, expr string) (core.Value, error) { + if el.IsDetached() { + return values.None, drivers.ErrDetached + } + + obj, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, expr, runtime.CallArgument{ + ObjectID: &el.id.ObjectID, + }) + + if err != nil { + return values.None, err + } + + if obj.Type != "object" || obj.ObjectID == nil { + return values.None, nil + } + + repl, err := el.client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*obj.ObjectID)) + + if err != nil { + return values.None, err + } + + return LoadHTMLElementWithID( + ctx, + el.logger, + el.client, + el.dom, + el.input, + el.exec, + HTMLElementIdentity{ + NodeID: repl.NodeID, + ObjectID: *obj.ObjectID, + }, + ) +} + func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) { if el.IsDetached() { return values.None, drivers.ErrDetached diff --git a/pkg/drivers/cdp/templates/siblings.go b/pkg/drivers/cdp/templates/siblings.go new file mode 100644 index 00000000..20152363 --- /dev/null +++ b/pkg/drivers/cdp/templates/siblings.go @@ -0,0 +1,12 @@ +package templates + +const getPreviousElementSibling = "(el) => el.previousElementSibling" +const getNextElementSibling = "(el) => el.nextElementSibling" + +func GetPreviousElementSibling() string { + return getPreviousElementSibling +} + +func GetNextElementSibling() string { + return getNextElementSibling +} diff --git a/pkg/drivers/common/getter.go b/pkg/drivers/common/getter.go index 3625fe75..063ad735 100644 --- a/pkg/drivers/common/getter.go +++ b/pkg/drivers/common/getter.go @@ -194,6 +194,10 @@ func GetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value } return values.GetIn(ctx, styles, path[1:]) + case "previousElementSibling": + return el.GetPreviousElementSibling(ctx) + case "nextElementSibling": + return el.GetNextElementSibling(ctx) default: return GetInNode(ctx, el, path) } diff --git a/pkg/drivers/http/element.go b/pkg/drivers/http/element.go index ffee3b91..045a470c 100644 --- a/pkg/drivers/http/element.go +++ b/pkg/drivers/http/element.go @@ -489,6 +489,26 @@ func (el *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) { return common.NewIterator(el) } +func (el *HTMLElement) GetPreviousElementSibling(_ context.Context) (core.Value, error) { + sibling := el.selection.Prev() + + if sibling == nil { + return values.None, nil + } + + return NewHTMLElement(sibling) +} + +func (el *HTMLElement) GetNextElementSibling(_ context.Context) (core.Value, error) { + sibling := el.selection.Next() + + if sibling == nil { + return values.None, nil + } + + return NewHTMLElement(sibling) +} + func (el *HTMLElement) Click(_ context.Context, _ values.Int) error { return core.ErrNotSupported } @@ -608,12 +628,14 @@ func (el *HTMLElement) ensureAttrs() { func (el *HTMLElement) parseAttrs() *values.Object { obj := values.NewObject() - for _, name := range common.Attributes { - val, ok := el.selection.Attr(name) + if len(el.selection.Nodes) == 0 { + return obj + } - if ok { - obj.Set(values.NewString(name), values.NewString(val)) - } + node := el.selection.Nodes[0] + + for _, attr := range node.Attr { + obj.Set(values.NewString(attr.Key), values.NewString(attr.Val)) } return obj diff --git a/pkg/drivers/value.go b/pkg/drivers/value.go index 33ac3a92..5ecc47c3 100644 --- a/pkg/drivers/value.go +++ b/pkg/drivers/value.go @@ -93,6 +93,10 @@ type ( GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) + GetPreviousElementSibling(ctx context.Context) (core.Value, error) + + GetNextElementSibling(ctx context.Context) (core.Value, error) + Click(ctx context.Context, count values.Int) error ClickBySelector(ctx context.Context, selector values.String, count values.Int) error