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
RansomNLE Choppa
Shotta Flow (Feat. Blueface) [Remix]Baby Jesus (DaBaby)
SugeNLE Choppa
Shotta Flow 3Lil Tecca
Lil Tecca - Did It AgainNLE Choppa
Shotta FlowYnw Melly
Dangerously In Love (772 Love Pt. 2)POLO G
Polo G feat. Lil TJay - Pop OutMUSTARD
Ballin' (feat. Roddy Ricch)Lil Nas X
PaniniJuice WRLD
Juice Wrld - RUNShordie Shordie
Betchua (Bitchuary)Post Malone
Goodbyes (feat. Young Thug)LIL UZI VERT
Sanguine ParadiseCalboy
Envy MeAmbjaay
UnoLil Tecca
Lil Tecca - BossanovaLil Baby
BabyLil 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