Goals:
- page objects should be cross-ecosystem, cross-technology
- simple API
- interop with Ember, and WebDriver (via adapters, which means testing-library would be supported, if anyone wanted to use that).
- all async/awaitable
- componentizable -- encourage separation of structure from the actual page object creation.
- all properties lazily evaluated
Nomenclature:
- Adapter: Converts meaning in to behavior -- "click" means different things to ember and webdriver
- Page Object: a literal object that represents how one would interact with the page
Proposed API:
import { setAdapter, create } from 'unknown package';
import { EmberAdapter } from 'unknown package/ember';
setAdapter(EmberAdapter);
const definition = {
login: {
username: '[data-test-username]',
password: '[data-test-password]',
submit: '[data-test-submit]',
},
list: {
scope: '[data-test-list]',
rows: collection('[data-test-row]', {
click: '[data-test-row-clicker]'
});
}
};
const page = create(definition);
// Usage
await page.login.username.click();
page.login.username.isFocussed; // true
await page.login.username.fillIn('text'); // username is now "text"
page.login.username.dom // <input type='text' data-test-username ... >
page.login.submit.fillIn('text'); // Error!: buttons are not fillable
await page.login.submit.click();
// for typescript, types are optional
// by default, all methods and properties will be available, and will throw runtime errors
// but the type of the interactable will still auto-complete for all methods and properties.
interface Definition {
login: {
submit: ButtonInteractor;
}
};
const typedPage = create<Definition>(definition);
// by default, every entry in the page object would be of type "OmniInteractor"
Usage with qunit-dom
assert.dom(page.login.username.dom).hasClass(...);
Adapters:
I'm thinking globally -- because I don't think it'd make sense to switch dom execution environments mid-test-run?
that's exactly what I was thinking!
In general, I think this sort of thing has to be global scope so that implementation can be minimal and also not require too much integration knowledge with other testing environments. the flexibility would be amazing, but I wouldn't want to enable people to lag behind upgrades or whatever. I'm a huge fan of everything being code-moddable, so, I'd hope no one ever feels they'd be left behind anyway?.
for sure! I imagine this sort of code could live in addon-space? or were you thinking of something else?
Lazy vs Eager:
Lazy, always. Resolving an entire page object would cause perf issues, as well as just being incorrect during async behaviour. your scenario is spot on.
collections:
yeah, this is a big difference for me -- I want everything to be available, and fully typed (so that intellisense is available).
this would also enable you to re-export the definition so you could use the selectors directly, if you needed to, for whatever reason. basically, this just decouples the definition of yoru app from a specific page-object implementation.
page.bar
would return an "Interactable" which just gives you all the APIs for clicking, filling in, typing, selecting, etc. Typescript would help (the royal) you out immensely here.maybe? I think it would be up to the adapter to support.
the definition would be unable to support that, but there could be an extended api -- maybe something like:
or something like that.
but sense this is all definitiony, I wonder if the vanilla object should be renamed "structure", so that it maybe more closely correlates to semantics of the DOM, rather than what is defining your page object?
this is most certainly a goal. I'll add that.
idk, the maintainer isn't on discord very often, so it's really hard to guage what the future of the library is, and if they are open to accepting breaking changes if it means better functionality in the long run.