angular / angular/components

feat(@angular/cdk/testing): Component Harness Feedback

オープン
#20,871 コメント 10 件 リアクション 9 件 担当者 0 名 GitHub で見る
area: cdk/testing feature P3
主要言語
TypeScript
スター
25k
フォーク
6.8k
平均マージ
1日 8時間
マージ済み PR(30日)
91

説明

**This is an open discussion aiming to regroup Component Harness feedback and improvement ideas.**

Most of the items below are focused on simplifying the API. The current APIs are a bit too complex; this can have a negative impact on `TestHarness` adoption.

# For test authors

## 1. Easier access to harness

`HarnessLoader` is a nice abstraction but it can make harness instantiation cumbersome.

### Actual approach
```ts
let fixture: ComponentFixture;
let loader: HarnessLoader;
let rootLoader: HarnessLoader;

beforeEach(() => {
fixture = TestBed.createComponent(MyDialogButton);
loader = TestbedHarnessEnvironment.loader(fixture);
rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
});

it('loads harnesses', async () => {
const dialogButtonHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, MyDialogButtonHarness);

const buttonHarness = await loader.getHarness(MyButtonHarness);
});
```

It would be nice to have faster harness access like:

### Suggestion 1.A
```ts
let fixture: ComponentFixture;

beforeEach(() => {
fixture = TestBed.createComponent(MyDialogButton);
});

it('loads harnesses', async () => {
const dialogButtonHarness = await TestbedHarnessEnvironment.getHarness(MyDialogButtonHarness, {fixture});

const buttonHarness = await TestbedHarnessEnvironment.getHarness(MyButtonHarness, {fixture});
});
```

or even global functions like `getHarness` and `getProtractorHarness` functions could make the tests even more readable:

### Suggestion 1.B
```ts
let fixture: ComponentFixture;

beforeEach(() => {
fixture = TestBed.createComponent(MyDialogButton);
});

it('loads harnesses', async () => {
const dialogButtonHarness = await getHarness(MyDialogButtonHarness, {fixture});
const buttonHarness = await getHarness(MyButtonHarness, {fixture});
});
```

# For harness authors

## 2. `LocatorFactory` abstraction

The `LocatorFactory` approach _(e.g. `locatorFor()` method returns a function that takes no parameters)_ can be confusing and cumbersome.

### Actual approach
```ts
class MyPopupHarness extends ComponentHarness {
static hostSelector = 'my-popup';

protected getTriggerElement = this.locatorFor('button');

async toggle() {
const trigger = await this.getTriggerElement();
return trigger.click();
}

}
```

### Suggestion 2.A

Simple accessor methods like `get()` or `getOptional()` seem easier to use and more intuitive.
```ts
class MyPopupHarness extends ComponentHarness {
static hostSelector = 'my-popup';

async toggle() {
const trigger = await this.get('button');
return trigger.click();
}
}
```

We can let developers factorize the way they want:
```ts
getTriggerElement() {
return this.get('button');
}
```

## 3. `async / await` vs chaining

I am personally not a big fan of chaining (a.k.a. builder pattern) but in cases like this one where we end up with lots of `await`s, this can simplify the interface:

### Actual approach
```ts
async isDisabled() {
const el = await this.getMessageElement();
const text = await el.text();
return text === 'Disabled';
}
```

### Suggestion 3.A
```ts
async isDisabled() {
return (await this.getMessageElement().text()) === 'Disabled';
}
```

## 4. Trigger any event

`TestElement` should have a `triggerEvent` function that allows harness authors to trigger any event.

### Suggestion 4.A
```ts
el.triggerEvent('dragenter', {})
```

# Common

## 5. Provide synchronous functions

Some environments can query the DOM synchronously (e.g. `TestbedHarnessEnvironment`) or through some under the hood chaining (e.g. Cypress) (Cf. https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Chains-of-Commands).
Harness authors might want to focus on these environments. In that case, they will want to use synchronous functions and keep tests and harnesses easier to read & write.

### Current approach

```ts
class ItemListHarness extends ComponentHarness {
static hostSelector = 'app-item-list';

getItems = this.locatorForAll('li');

async getItemNames() {
const items = await this.getItems();
const itemNames = await Promise.all(items.map(item => item.text()));
return itemNames;
}
}

it('loads harnesses', async () => {
const itemListHarness = await loader.getHarness(ItemListHarness);
expect(await itemListHarness.getItemNames()).toEqual(['🍔', '🍟']);
});
```

### Suggestion 5.A

Providing synchronous alternatives to accessors.

```ts
class ItemListHarness extends ComponentHarness {
static hostSelector = 'app-item-list';

getItemNamesSync() {
return this.getAllSync('li').map(item => item.textSync());
}
}

it('loads harnesses', () => {
const itemListHarness = loader.getHarnessSync(ItemListHarness);
expect(itemListHarness.getItemNamesSync()).toEqual(['🍔', '🍟']);
});
```

## 6. `TestbedHarnessEnvironment ` vs. `TestBedHarnessEnvironment `

`TestbedHarnessEnvironment` could be renamed to `TestBedHarnessEnvironment` to stay consistent with `TestBed` 😉

## 7. Merge `TestBed` and `TestbedHarnessEnvironment`

In some future, wouldn't it be nice to merge `TestbedHarnessEnvironment` with `TestBed` which means moving test harness to the angular repo?

## 8. Cypress support

An external library could provide a `CypressHarnessEnvironment` but as presented in the 5th item, Cypress is based on an abstract chain of commands. `TestElement` doesn't seem to be the right abstraction for this use case especially for getters like `text()`, `getProperty()` etc...

This is the last item on the list but probably the most important one. One of the key features of harnesses is the test environment abstraction and harness reuse through environments (TestBed, Protractor etc...) but if I am using TestBed and Cypress and if I can't reuse my harnesses with Cypress then it somewhat defeats the purpose of harnesses.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

特定のファイル、テスト、エントリーポイントは指定されていません。まず Component Harness APIs と 8 つの個別の提案を確認してください。実装を開始する前に、この議論を、完了基準が定義された 1 つの決定済みの変更に絞り込む必要があります。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
angular, typescript
領域
testing
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
説明が足りない
初心者へのやさしさ
18/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。