Open sukima opened 1 year ago
1 could look like
class FooBar extends PageObject {
myButton = shadowSelector(‘custom-element’, ‘button’);
}
2 might look like
class FooBar extends PageObject {
shadow = shadowSelector(‘custom-element’, class extends PageObject {
theButton = selector(‘button’);
});
get myButton() {
return this.shadow.theButton;
}
}
3 might look like:
class FooBar extends PageObject {
myButton = selector(‘custom-element::shadow button’);
}
It is actually not possible to wrap a ShadowRoot with a PageObject at the moment making this a blocker for using fractal for custom elements.
class FooBar extends PageObject {
get shadow() {
return new PageObject(this.element.shadowRoot);
}
}
Thanks for the issue! In my mind, I've broken this down into two separate issues:
PageObject
s to wrap DocumentFragment
s (this covers ShadowRoot
s because they are DocumentFragment
s)1
is implemented in #110 (which is nice because now PageObject
s can be used on DocumentFragment
s in any other context as well). I haven't touched 2
yet, because I think it requires more thought and maybe experimentation, but I think what I've implemented allows you to implement API-compatible forms of your three options as helpers utilities in your app. If not, let me know!
Oh, and have a look at #110 and tell me what you think -- as long as you don't see any glaring issues, I'll merge it and cut a release so you can try it out.
Looks like #110 would satisfy the feature.
If we were to explore options for a more declarative API what are your thoughts on that interface? How do you feel about introducing an unofficial ::shadow
selector?
I'd definitely like to explore other options first. That would require doing our own parsing of selectors, which is something I'd very much like to avoid.
fractal-page-object
kinda tries to wrap itself pretty tightly around native APIs, and CSS selectors, and as far as I know the ::shadow
pseudo-selector was an abortive experiment in the ecosystem, so I don't think it's a good idea to introduce it into fractal-page-object
. Currently "getting into" the shadow DOM in Javascript requires more than just a single CSS selector/query however you do it, so I think I want to just treat that as a limitation and see what declarative APIs we can build that embrace that.
v0.5.0 released with low-level shadow DOM support
Just out of curiosity would an API like this work?
class FooBar extends PageObject {
somethingWhichHasAShadowRoot = shadowSelector('.something', class extends PageObject {
somethingWithinTheShadowRoot = selector('.something-inside');
});
}
Then it would look like page.somethingWhichHasAShadowRoot.somethingWithinTheShadowRoot.element
but under the hood shadowSelector
performs an extra step to assert this.element
and return new PageObject(this.element.shadowRoot)
?
Or if that doesn't support lazy evaluation maybe a different ShadowPageObject
to extend from that knows to look for this.element.shadowRoot
on demand?
class FooBar extends PageObject {
somethingWhichHasAShadowRoot = selector('.something', class extends ShadowPageObject {
somethingWithinTheShadowRoot = selector('.something-inside');
});
}
@sukima I've put some thought into this, and need to put some more in, but I think making the shadow DOM vs regular DOM distinction on the page object that is the root of the shadow DOM may not be the way to go. If I want to be able to interact with both the element's shadow DOM and its children/descendants in the regular DOM, I'd have to create two separate page objects, e.g.
class FooBar extends PageObject {
somethingWhichHasAShadowRoot = shadowSelector('.something', class extends PageObject {
somethingInTheShadowDOM = selector('.something-shadowy');
});
somethingWhichHasAShadowRoot2 = selector('.something', class extends PageObject {
somethingInTheRegularDOM = selector('.something-not-shadowy');
});
}
and that seems counter to how we like to model our DOMs with page objects. I think what we probably want it to end up looking like is something more like
class FooBar extends PageObject {
somethingWhichHasAShadowRoot = selector('.something', class extends PageObject {
somethingInTheShadowDOM = shadowSelector('.something-shadowy');
somethingInTheRegularDOM = selector('.something-not-shadowy');
});
}
where shadowSelector()
can be used to select something in the shadow DOM attached to its parent, the same as selector()
can be used to select something in the regular DOM "attached to" (i.e. descendants of) its parent.
This forces you to define a page object for the element with the shadow root, which maybe isn't a huge problem, but also might not be ideal. So maybe it would make sense to overload shadowSelector()
to either accept a single selector as its argument, or two selectors, and in the two selector case, the first one selects the element with the shadow root and the second one selects within the shadow DOM. So with
class FooBar extends PageObject {
somethingWhichHasAShadowRoot = selector('.something', class extends PageObject {
somethingInTheShadowDOM = shadowSelector('.something-shadowy');
});
somethingInTheShadowDOM2 = shadowSelector('.something', '.something-shadowy');
}
new FooBar().somethingWhichHasAShadowRoot.somethingInTheShadowDOM.element
would be the same as new FooBar().somethingInTheShadowDOM2.element
.
I'll have to think some more about the feasibility of implementing an API like this, but hopefully it's doable.
But just from an API design standpoint, what do you think?
@bendemboski Apologies, this dropped below the fold. I think we are on the same page. Some thoughts:
shadowSelector
drills into the parent's shadow DOM makes sense to me. I understand it is slightly different then the mental model of multiple selectors separated by spaces that nested selector()
implies. (foo bar
versus foo > bar
). I don't know how best to reconcile this with shadow DOMs and perhaps maybe we shouldn't try.The suggestions you gave would work for my purposes and if you are still comfortable with that proposed interface I would not mind looking deeper into the current source code―no guarantees I'll get something done though 😉
The current
querySelector
/querySelectorAll
doesn’t have a syntax to select within a shadowDOM.Some solutions:
shadowSelector
that knows to use theelement.shadowRoot.querySelector()
. But we would need to allow the helper to take a second query for scoping.this.element.shadowRoot.querySelector
.:shadow
to allow diving into from theselector
helper like:myButton = selector(‘foo-bar::shadow button’);
But that means parsing the string before passing it toquerySelector
.