Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapters #479

Open
ro0gr opened this issue Jan 27, 2020 · 0 comments
Open

Adapters #479

ro0gr opened this issue Jan 27, 2020 · 0 comments
Labels

Comments

@ro0gr
Copy link
Collaborator

ro0gr commented Jan 27, 2020

Summary

Provide a way for the user to specify, and customize integration with a certain test environment via exposing adapters as a public API.

Motivation

Implicitness is not always possible

Today ember-cli-page-object supports several test environments, which can be enabled via different APIs:

  • moduleForAcceptance(, gets enabled if there is window.visit available as a global function
  • moduleForComponent(, can be enabled by setContext, and disabled by removeContext on a page object instance.
  • native-dom-helpers, in addition to the rules above, you have to call useNativeEvents( to enable this mode
  • RFC268 - auto-enabled if neither of the conditions above match, and proper version of @ember/test-helpers is installed

The way we currently support multi-context feature complicates ec-page-object internaly, and adds unnecessary mental load to users, so they need to understand how they can enable or disable some mode.

An opportunity to get rid of old test mode gracefully

Today moduleFor is deprecated in recent versions of ember-qunit. Same for "ember-native-dom-helper".

We basically have 2 ways to approach that:

  • just drop "moduleFor" and "native" modes in v2, maybe with deprecations in scope of v1 (so booooring)
  • be kind enough, to provide user with a way to implement their own slim integration for moduleFor, if they still need it for some reason 🎉

Open the door to experimentation

Adapters would also allow the user to create new integrations with alternative test environments.
An immediate example of such integrations can be FastBoot, which already has few different test helpers addons.
Any of that addon can have its own page object adapter.

Detailed design

In order to switch test env, you have to explicitly set an adapter:

import { setAdapter } from 'ember-cli-page-object/adapters';
import ModuleForAcceptanceAdapter from '<some-user-path>/module-for-acceptance';

setAdapter(ModuleForAcceptance);

It eliminates the need for all the implicit decisions on the ember-cli-page-object side which we currently do.

A proposed Adapter interface is the following:

interface Adapter {
  /**
   * Test container Element.
   *
   * In Ember it's usually "#ember-testing"
   */
  get testContainer(): string|Element
  
  visit(path: string): Promise<unknown>
  
  click(element: Element): Promise<unknown>
  
  fillIn(element: Element, content: unknown): Promise<unknown>
  
  triggerEvent(element: Element, eventName: string, eventOptions: TriggerOptions): Promise<unknown>
  
  focus(element: Element): Promise<unknown>
  
  blur(element: Element): Promise<unknown>
}

A noticeable difference to ExecutionContexts is that Adapter is no longer responsible for the Element search logic,
which requires an action to find the element before passing it to adapter.
This would clean up the boundries between Actions and Adapters, and simplify internals and testing a lot.

In case user wants their own Adapter or customize existing behavior, they can extend from a built-in adapter and customize it.
For example, if you want FastBoot integration you should be able to do smth like:

import { setAdapter } from 'ember-cli-page-object/adapters';
import Adapter from 'ember-cli-page-object/adapters/rfc-268';
import { visit } from 'ember-cli-fastboot-testing/test-support';

class FastBootTestingAdapter extends Adapter {
  visit(path: string) {
    return visit(path);
  }
}

setAdapter(FastBootTestingAdapter);

Caution: snippet above is just for demonstration purpose. In order to make it work we still have to figure out jQuery + NodeJS issue.

Control flow

RFC268 mode would be a default one, so you don't need any special configuration to make it work.

However, if you need to switch, you should define a globaly default test mode in Qunit.testStart:

import Qunit from 'qunit';
import { setAdapter } from 'ember-cli-page-object/adapters';
import DefaultAdapter from 'ember-cli-page-object/adapters/rfc-268';

Qunit.testStart(function() {
  // if no other Adapter specified, this one would be used for each test
  setAdapter(DefaultAdapter);
});

and specify your special adapter per each specific test:

import { module } from 'qunit';
import { setAdapter } from 'ember-cli-page-object/adapters';
import SpecialAdapter from 'my-app/tests/helpers/my-special-adapter';

module('my special module', function(hooks) {
  hooks.beforeEach(function() {
    // this Adapter would be set only for this specific module
    setAdapter(SpecialAdapter);
  })
})

How we teach this

We can extract "Extend page objects" from the "Getting Started" section, and put it as a self contained section. One of the new "Extending" section sub-nav should be "Adapters". It would explain how to:

  • write your own adapter
  • setAdapter usage

Also we can start providing a deprecation for old execution contexts, using moduleFor and encourage user to:

  • switch to RFC268
  • or write they own adapter

I expect RFC268 ExecutionContext should be convertable into Adapter seamlessly w/o any breaking changes, so it should not cause any churn for the users who already switch to RFC268.

Drawbacks

Some of the things would become impossible with the adapters.

No more andThen

For example, andThen'like behaviour would not be supported, so there would not be a way to make 2 sync page object action invocations to wait for each other:

  page.click();
  // invoked immediatelly
  page.click();

So this part of moduleFor behaviour would not be possible to support via Adapters.

Action can handle exactly 1 Element

Due to the current ExecutionContext implementation, like

click(selector, container) {
this.$(selector, container).click();
},
,

it's theoretically possible to invoke an action on multiple elements. This behaviour would not be supported as well, since an action must pass a single Element to adapter.

Alternatives

We can drop ExecutionContexts and just use RFC268 as a single possible mode. However, it closes a window of opportunities to integrate with alternative environments for us.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant