1

We have an UI development library consisting of widgets built on top of Angular JS. Let's pick "header-widget" as an example - a header container built with Angular JS, but with specific functionality on top if it.

Say the HTML code of this widget will look like this:

<header class="my-header">
    <div ng-if="configuration.hasTitle">
        <h1 class="my-header-title-text">
            <span class="my-header-title-text">This is a main title</span>
            <span class="my-header-subtitle-text">This is a subtitle</span>
        </h1>
    </div>
</header>

I can create an unit/integration test using Karma i.e. at one point I will have something like this:

element = angular.element("<header config=\"config\"></header>");
$compile(element)(myScope);
myScope.$digest();

expect(element.find(".my-header-title-text").text().trim()).toBe("This is a main title");

and I can create an end to end test using Protractor i.e. at one point I will have something like this:

describe('Some end to end spec', function () {
    it('should have the right title', function () {
        let title = element(by.css(".my-header .my-header-title-text"));
        expect(title.getText()).toMatch("This is a main title");
    });
});

Suppose the developers change the header and the new class for the title becomes "my-header-MAIN-title-text". I would have to go to all places in unit/integration tests as well as in the end to end tests to fix that.

(For the sake of this example, the change is trivial and in theory can be encapsulated by having selectors in separate files, but in practice I speak about bigger changes in the DOM structure of these widgets, etc.)

I want to build a test library - like a test API - on top of the widgets. An encapsulation layer that would offer me things like header.getTitle() and header.getSubtitle() - that I can use in both unit/integration (Karma) as well as in the end to end world (Protractor). This way, if a change like the one mentioned above will occur, I have to go in one place, fix the implementation of the "header.getTitle" and none of the tests should care about.

To do so, I have created something like this (using TypeScript):

import $ from "jquery";

export class MyHeaderImpl implements MyHeadear {
    private selector;

    constructor(element) {
        this.selector = $(element);
    }

    getTitle(): string {
        return this.selector.find(".my-header-title-text").text().trim();
    }

    getSubtitle(): string {
        return this.selector.find(".my-header-subtitle-text").text().trim();
    }
}

It works fine for my unit/integration tests (Karma):

element = angular.element("<header config=\"config\"></header>");
$compile(element)(myScope);
myScope.$digest();

const header = ComponentFactory.createHeader(element);
expect(header.getTitle()).toBe("This is a main title");

However, it will not work on Protractor:

describe('Some end to end spec', function () {
    it('should have the right title', function () {
        let element = element(by.css(".my-header .my-header-title-text"));
        const header = ComponentFactory.createHeader(element);
        expect(title.getText()).toMatch("This is a main title");
    });
});

It throws the following exception

Failed: jquery_1.default is not a function

I am using TypeScript in the test library, as well as with my Protractor tests.

I can replace the jQuery $ with Protractor's element in my test library, but then I would force the unit/integration tests to run against a browser, which will defeat their purpose of being fast.

Does anyone knows how can I create such a test API library that can be used both by Karma tests, as well as by Protractor tests?

One of the best descriptions I could find here on Stack Overflow, but it does not answer my question.

2 Answers2

0

Have you tried to inject the library in karma and use it directly without importing. There are tons of karma adapters so for jquery I think. In protractor you could do the same in protractor config. The test API you described is called Page Object pattern BTW, thou I'm not sure if it's a good idea to mix both type of tests in single spec.

factor5
  • 321
  • 2
  • 12
0

@factor5, here is my answer to your suggestion, as the comment section doesn't allow it.

First, let me summarize my question like this:

is there a way to write (TypeScript) a method that accepts both:

  1. Angular element (https://docs.angularjs.org/api/ng/function/angular.element)
  2. Protractor element (https://www.protractortest.org/#/api?view=ElementFinder)

and then looks inside given element, finds a div, and returns its text? If I define this element argument as the Angular one, I cannot reuse it in the Protractor end to end project. If I define this element argument as "import {element} from protractor", it will force my unit/integration tests to be actually end to end tests, going through the WebDriver.

Second, about Page Object pattern: I go one level below Page Object pattern. Let's take the comment text area, where I type my answer right now.

<div class="js-comment-text-input-container">
    <textarea></textarea>
</div>

I might have a Page Object called QuestionPage,with a method typeComment(), or even have a separate component (PageObject) called CommentSection, where I place methods like typeComment(), sendComment(), editComment(), etc.

But the might be used in many other places, i.e. in a page where user can can add a short bio about, etc. There will be a Page Object called UserProfile, which has a typeBio() that inside also deals with .

At this point, if the structure of changes, you already have two places where you have to go and change.

Therefore, I need a separate library to isolate the interaction with such elements.

  • When I have a generic reusable web component -an angular directive for example, I'd make similar by purpose PageObject for interacting with it, then reuse it in the different contexts it might be found. This helps with the duplication you are talking about. – factor5 Nov 30 '18 at 06:58
  • According to using jquery (the angular.element) and protractor's element in a generic polymorphic method you probably know that they have similar but still different interfaces which makes the task not quite trivial. I'd create a proxy object which wraps both and having same interface. Use the proxy instead in tests. It should pass the invocation down either to protractor or jquery according to current context. It might sound simple, but it wont be. You might end up with whole bunch of adapters for one and another in order to deal with the differences in their API's. – factor5 Nov 30 '18 at 07:17
  • I was afraid of this answer :). Thanks a lot, I thought I would avoid doing the proxy thing, but it seems to be one of the only solutions, and would be not trivial and possibly a nightmare to maintain – Radu Theodor Nov 30 '18 at 09:20