Closed tiffon closed 4 years ago
I am a big fan of layering as opposed to the aspect injection. Any reason what you describe should not be a layer on top of Playwright that exposes component.element: ElementHandle
?
Nope, that would work, too. I just wanted to drop the suggestion. Will close as this can be revisited, later, should there be any interest in it.
It would be sick to be able to enhance ElementHandles (and maybe other things) with additional functionality, akin to extension methods in C# (which are also sick).
For some context, back in the days of yore I had to convert the E2E tests for a web-based IDE project (built in React) from Ruby to JS / WebdriverIO. The Ruby stuff followed the page-model approach, which gets unwieldy fast, IMO. And, since the project source was in JS and already had a (React) component hierarchy, I figured to shift the tests from monolithic(-ish) page-models to something that reflected the component hierarchy. Not sure if that's an anti-pattern or not, but it was great and allowed us to very comfortably invest heavily in E2E testing, including publishing packages that our downstream teams used to interface with the foundational elements of the IDE we were responsible for.
The basic idea was to write WebdriverIO interfaces for significant components. For instance, an alert / notification component (Alert.js) could have a WebdriverIO interface (Alert.webdriver.js) that exported functions:
countVisible(): number
getActiveAlerts(): Element[]
findByTitle(string | RegExp): Element
mustFindByTitle(string | RegExp): Element
(throws if not found)findByBody(string | RegExp): Element
mustFindByBodyTitle(string | RegExp): Element
(throws if not found)getTitle(Element): string | null
getBody(Element): string | null
close(Element)
These were intended to be highly composable. So, if you say had a strack-trace alert (StackTraceAlert.js) when something goes awry, you might have (StackTraceAlert.webdriver.js) which composes the above and exports functions:
countVisible(): number
– UsescountVisible()
from above but only returns the number of stack-trace alerts... this way, it doesn't need to know how to find alerts just how to determine if they're stack trace alertsgetActiveAlerts(): Element[]
– Finds only the alerts that are stack-trace alertsfind(string | RegExp): Element
mustFind(string | RegExp): Element
Then, you might write a test for it that verifies the stack trace, the copy button and maybe that it's type 'error' not 'info'.
(We built a lot of assertions into these interfaces. E.g. the
find()
andmustFind()
functions inStackTraceAlert.webdriver
would verify the found element was indeed anerror
alert since that was a constant built-in expectation of the component.)And, this is kind of a lame example because it's just testing a simple component. But, when testing user-flows or things that require complex flows to set up the context, it's a handy simplification.
Okay, it's a bit cumbersome to need to pass
ElementHandles
into utility functions:I mean, it's fine, and I preferred that over writing classes, but extension methods might be an interesting possibility:
Maybe enhancements like this can happen in a way that is similar to protypal inheritance. So, for instance, StackTraceAlert.webdriver wouldn't need to add a
.close()
extension method because its extensions are on top ofAlert.webdriver
which already added.close()
method.The above APIs are just trying to show the idea and they probably aren't the best examples. But, I can say we definitely found organizing our E2E tests like this to be very liberating. And, some form of support for it Playwright would be really powerful (IMO).
Also, since Playwright is written in TypeScript,
ElementHandle
s enhanced with extension methods could all be typed, too, which is just too good to be true.Lastly, I'd be interested in contributing to the effort if this or similar functionality ends up getting the green-light.