Angular end-to-end testing tips
Tags: angular, testing
Published: 2020-11-26 14:52:49

_We all love to write end-to-end specs, don’t we? The reason is that these scenarios act as watchdogs, making refactoring safer and sometimes being the one and only feature documentation existing in the codebase._

The only downside is that sometimes it takes ages to have a proper data setup, resolve CSS classes dependencies and constant CSS/HTML changes. Readability and maintainability are not always perfect too. Well, we have a few simple techniques that can help you overcome most of the issues described above. Written for end-to-end Protractor specs, they can be easily used with a testing framework you prefer.

Let’s check a simple example of markup
```
...
 
    I'm an awesome label
 
...
```
with related specs
```
describe('Awesome page', () => {
  beforeAll(() => {
    browser.driver.get("http://mysite.com/awesome");
  });

  describe('Awesome block', () => {
    const block = element(by.css('.awesome-block'));
    const label = block.element(by.css('.utility-class'));

    it('has awesome label', () => {
      expect(label.getText()).toEqual("I'm an awesome label");
    });
  });
});
```
and try to improve them.

## Separate test specific attributes
If you have engineers working separately with CSS/HTML and Angular/JS components then you have probably faced an issue that markup changes are not safe in terms of spec dependencies.

Front-end engineer can accidentally break specs by just changing the utility-class name or applying different class according to CSS changes. Although this issue can be avoided by checking end-to-end specs selectors whenever a markup change is applied, that’s not very convenient. An alternative solution would be having proper stable semantic classes on every tested element, but that’s just too ideal 😉

The other option is to have a special attribute that is used **ONLY** by testing framework:

`I'm an awesome label`

It looks like we are having obsolete attributes all around our markup. Although using this technique we have obtained a number of benefits:

Every tested element has a separate meaningful name
Markup changes are much easier and "safer"
Specs do not dependent on CSS changes

## Page/Component objects
When writing end-to-end tests, a common pattern is to use page objects. It makes easier to maintain and reuse spec examples. Let’s define simple [page objects](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) for our specs:

```
class PageObject {
  constructor(public finder: ElementFinder) {  }

  protected get element() {
    return this.finder.element.bind(this.finder);
  }

  protected getChild(locator: string) {
    return this.element(by.css(locator));
  }
}

class AwesomeBlock extends PageObject {
  get awesomeLabel() {
    return this.getChild('[data-test=awesome-label]');
  }
}

class AwesomePage extends PageObject {
  visit() {
    browser.driver.get("http://mysite.com/awesome"); 
  }

  get awesomeBlock() {
    return new AwesomeBlock(this.getChild('[data-test=awesome-block]'));
  }
}
```

Test examples will now look like this:

```
const page = new AwesomePage(element(by.css("body")));

describe('Awesome page', () => {
  beforeAll(() => {
    page.visit();
  });

  describe('Awesome block', () => {
    const awesomeBlock = page.awesomeBlock;

    it('has awesome label', () => {
      expect(awesomeBlock.awesomeLabel.getText()).toEqual("I'm an awesome label");
    });
  });
});
```
Much cleaner, no CSS selectors in examples but can we improve this even more? Sure! With a common test-specific attribute on every testable element and TypeScript [decorators](https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Decorators.md) page objects can look a bit fancier:

```
class AwesomeBlock extends PageObject {
  @hasOne awesomeLabel;
}

class AwesomePage extends PageObject {
  visit() {
    browser.driver.get("http://mysite.com/awesome"); 
  }

  @hasOne awesomeBlock: AwesomeBlock;
```
with decorator defined as:
```
export const hasOne = (target: any, propertyKey: string) => {
  Object.defineProperty(target, propertyKey, {
    enumerable: true,
    configurable: true,
    get: function () {
      const child = this.getChild(`[data-test=${_.kebabCase(propertyKey)}]`);
      const PropertyClass = Reflect.getOwnMetadata("design:type", target, propertyKey);
      return new PropertyClass(child);
    },
  });
};
```

Now we have reusable spec examples that are not dependent on CSS changes and a nice DSL to define Page/Component classes.

**The insights and code samples were made by [Railsware](https://railsware.com/services/ruby-on-rails-web-development-services/) engineering team**