レコーディングで困った時のAutifyJSステップ集

## はじめに

[Autify](https://autify.com/ja)は、普段ユーザが使うように操作をすれば一連の内容が自動的にレコーディングされるテスト自動化ツールです。SeleniumやPlaywrightといったスクリプトベースのツールとは異なり、プログラミングに馴染みのない人でも簡単に扱えることが特徴です。
一方、if文やwhile文といった複雑な処理、スクロールのような複雑な操作には対応しておらず、**JSステップ**を使ってJavaScriptを記述する必要があります。

本記事では、私がAutifyを使う上で**レコーディングできなかった操作**や**標準コマンドになかったアサーション**を中心にその実現方法を紹介します。あらゆる操作を網羅する目的はないため、その点ご容赦ください。

なお、JSステップを用いているものに関しては、**AutifyのメンテナンスAIの対象外**になります。その他の利用上の注意は[「JSステップ-とはなんですか」](https://autify.zendesk.com/hc/ja/articles/900002403603)をご覧ください。

## ここにない操作、アサーションで困った時

以下のページが参考になります。他にも、[Autifyのヘルプセンター](https://autify.zendesk.com/hc/ja)には様々なFAQが掲載されています。キーワードで検索してみるとヒントが得られるかもしれません。

- [アサーション一覧](https://autify.zendesk.com/hc/ja/articles/900001652023)
  - Autifyが対応している「ページに対するアサーション」と「ページ内の要素に対するアサーション」が一覧化されたものです。「このアサーションがうまくレコーディングできない」という時は、この一覧をで対応しているか確認するとよいでしょう。

- [各種留意点](https://autify.zendesk.com/hc/ja/articles/900001654386)
  - よくある複雑な処理や操作について、Autifyの対応状況が書かれています。「アラートダイアログの操作」や「タブの切り替え」、「特定のブラウザだけうまくいかない」など、一癖ある操作につまづいたら確認するとよいでしょう。

- [Autify JavaScript Snippets](https://autify-javascript-snippets.netlify.app/ja/)
  - AutifyのJSステップで使えるスニペット集です。「この操作をJavaScriptで記述したい」と実装したい操作が分かっている場合に使うとよいでしょう。

## 操作

Autify Recorderでレコーディングできなかった操作とその代替方法です。

### 要素が表示されるまで横スクロール

これは、Autifyスニペット集[「要素が表示されるまでスクロールする」](https://autify-javascript-snippets.netlify.app/ja/snippets/scroll/scroll_to_element/)と同じコードで実現できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/958ca017-ac61-c294-08c0-bbc22380bee6.png" width="70%">

```js
var selector = "<TODO: REPLACE SELECTOR>";  /* 対象要素を特定するセレクタの文字列 */
var element = document.querySelector(selector);

element.scrollIntoView(false);
```

スクロール位置を細かく指定したい場合は、オプションを使います。（参考：[Element.scrollIntoView() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Element/scrollIntoView)）

### 要素内を横スクロール

対象の要素の横スクロールバーを右または左に操作します。ほとんどの場合はスクロールバーの動く部分を左から右へ（要素内を左へ）動かすことが多いでしょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/1138bc1f-643d-404d-a457-52ad95659bf9.png" width="70%">

これを実現するには、Autifyスニペット集[「要素内をスクロールする」](https://autify-javascript-snippets.netlify.app/ja/snippets/scroll/scroll_in_element/)の横スクロール版を使います。`scrollLeft`を使って要素内を左へスクロールしています。

```js
var selector = "<TODO: REPLACE SELECTOR>";  /* 対象要素を特定するセレクタの文字列 */
var count = 3;   /* スクロールを発生させたい回数 */
var scrollRange = 1000;   /* スクロールさせたいピクセル数 */

/* 要素内を左にスクロールする */
function scrollLoop(maxCount, i) {
  if (i <= maxCount) {
    var element = document.querySelector(selector);
    element.scrollLeft = scrollRange;
    if (element.scrollLeft < scrollRange) {
      setTimeout(function () { scrollLoop(maxCount, i + 1); }, 1000);
    }
  }
}
scrollLoop(count, 0);
```

### ユニークなメールアドレスを生成する

会員登録のシナリオなど、テスト実行ごとに毎回新たな文字列を生成したい場合です。2つのパターンを紹介します。

**Autifyのランダムアドレスを使う**

最も簡単に実装できる方法です。Autifyのメールテスト機能を使って、テストシナリオを実行するごとに、ランダムに生成されたメールアドレスを生成することができます。
受信メールのテストもできるので、なるべく手間をかけずにメールテストをしたい場合におすすめです。（参考：[メールテスト](https://autify.zendesk.com/hc/ja/articles/900001934846)）

▼レコーディング中に「✉」アイコンをクリックし、ランダムメールアドレスを作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/5d80b89a-1018-1ad4-e9e4-91104556e90c.png)

▼シナリオ内で使用することができる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/74abe623-184b-7815-20f9-162ffc790026.png)

**日時を使ってユニークなアドレスを発行する**

テスト実行時の日時を取得し、アドレスや氏名や任意のテキストとして使い回す方法です。
シンプルな日時情報であるため、テスト結果を分析する際に実行タイミングが分かりやすいことや、メールアドレスのドメインが決まっている場合に使えるところが利点です。

Autifyスニペット集の[日付を取得する](https://autify-javascript-snippets.netlify.app/ja/snippets/values/get_current_date/)を参考に、分かりやすさのため`yyyymmddhhmmss`にフォーマットします。

```js
/* 日時取得 */
var to_timezone = 9; /* タイムゾーンの指定 */

var from_timezone_offset = new Date().getTimezoneOffset() * 60 * 1000;
var to_timezone_offset = to_timezone * 60 * 60 * 1000;
var now = new Date(Date.now() + from_timezone_offset + to_timezone_offset);

var date = "" + now.getFullYear() + padZero(now.getMonth() + 1) + padZero(now.getDate()) + padZero(now.getHours()) + 
    padZero(now.getMinutes()) + padZero(now.getSeconds());
return date

/* 先頭ゼロ付加 */
function padZero(num) {
    return (num < 10 ? "0" : "") + num;
}
```

日時取得の値を使って、例えば以下のようなJSステップへ使い回すことができます。

```js
/* メールアドレス発行 */
return "autify+" + date + "@sample.com";

/* 氏名発行 */
return "自動テスト" + date;
```

▼入力結果はこちら

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/bb4c8777-35fa-f36d-3708-a8f87d2d0a2f.png" width="70%">

### チェックボックスの操作

チェックボックス要素をON/OFFにする操作は「クリックする」コマンドでレコーディングされます。この場合、チェックボックスの初期状態によってクリック後の状態が変わってしまいます。

チェックボックスの初期状態によらず「ON/OFFにする」操作を行いたい場合、以下のJSステップを使うことで実現できます。

```js
var selector = "<TODO: REPLACE SELECTOR>";  /* 対象要素を特定するセレクタの文字列 */
var expected = true;  /* 期待するチェックボックスの状態 */
var element = document.querySelector(selector);

if (!element) {
    throw new Error("Error: 要素がありません(" + selector + ").");
}

/* 要素がONでない場合、クリックする */
var actual = element.checked;
if (actual !== expected) {
    element.click();

    if (element.checked !== expected) {
        /* 要素がチェックされているかどうかを確認する */
        throw new Error("Error: チェックボックスを" + expected + "にできませんでした(" + selector + ")."
        );
    }
}
```

### 要素を指定回数クリックする

要素を`maxCount`の回数クリックする操作です。2つのパターンを紹介します。

**指定秒数ごとにクリックする**

```js
var selector = "<TODO: REPLACE SELECTOR>";  /* 対象要素を特定するセレクタ */
var maxCount = 5;  /* 最大回数 */
var ms = 1000;  /* 待機時間(ms) */

for (let num = 0; num < maxCount; num++) {
    setTimeout(function () {
        var element = document.querySelector(selector);
        if (!element) {
            throw new Error("Error: 要素がありません(" + num + "回目: " + selector +").");
        }
        element.click();
    }, ms * num);
}
```

**連続する要素を順番にクリックする**

例えばテーブル要素内のセルや、リスト内の要素、クラス名が連番になっている要素など、連続するロケータを持つ要素を順番にクリックしたい場合です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548627/93971c28-6cd5-8d4c-c460-3b0f148ddf83.png" width="60%">

Autifyでレコーディングすることもできますが、数によっては多くのステップが必要でシナリオが長大になりがちです。
そこで、変数を用いたセレクタとfor文を使って1つのJSステップで実現します。
※`num`の初期値はセレクタの文字列を考慮して`1`にしています。

```js
var baseSelector = "<TODO: REPLACE SELECTOR>";  /* 対象要素を特定するセレクタの共通部分 */
var maxCount = 3;  /* 要素の数 */
var ms = 1000;  /* 待機時間(ms) */
for (let num = 1; num < maxCount + 1; num++) {
    setTimeout(function () {
        var selector = baseSelector + "hogehoge" + num;  /* セレクタによって書き換え */
        var element = document.querySelector(selector);
        if (!element) {
            throw new Error("Error: 要素がありません(" + num + "回目: " + selector +").");
        }
        element.click();
    }, ms * num);
}
```

## アサーション

### 存在しないことを確認する

対象要素がDOM内に存在しないことを確認するコードです。ページ内に要素が存在していて「非表示になっている」場合は、このアサーションはPassしてしまうので使い所に注意が必要です。

```js
var selector = "<TODO: REPLACE SELECTOR>";  /* 対象要素を特定するセレクタの文字列 */
var element = document.querySelector(selector);

/* 存在しないことを確認 */
if (element) {
  throw new Error("Error: 存在してはならない要素が存在しています(" + selector + ")");
}
```

### 非表示であることを確認する

ページ内の要素の存在有無に関わらず、非表示であることを確認するコードです。詳細は後述の参考記事に譲りますが、CSSプロパティのdisplayの状態を取得して、非表示（none）であることを確認しています。

```js
/* 非表示確認 */

var selector = "<TODO: REPLACE SELECTOR>";  /* 対象要素を特定するセレクタの文字列 */

/* 要素を探す */
var element = document.querySelector(selector);

/* 非表示または存在しないことを確認 */
if (element) {
  /* 対象の要素が非表示か確認 */
  var isDisplayed = document.defaultView.getComputedStyle(element, null).display;

  if (isDisplayed !== "none") {
    throw new Error("Error: 要素が非表示(none)ではありません(" + selector + ")");
  }
}
```

ややこしいのでPassになる場合とFailになる場合を示します。

- **Pass**:
  - ページ内に要素が存在している＆要素が非表示
  - ページ内に要素が存在していない
- **Fail**:
  - ページ内に要素が存在している＆要素が表示

## 所感：アプリ実装の工夫

Autifyは、ページ内の要素のタグや属性値をヒントにアサーションのコマンドが選べるようになっています。
一方、特定のタグや属性を別のタグや属性で表現している場合はうまくいかないことが多いです。例えばチェックボックスのON/OFFを検証したい場合、見た目はチェックボックスでも、`div`等を使って**チェックボックス風に作られた要素**にはチェックボックス用のアサーションコマンドが使えません。よりAutifyを活用するためには、アプリの実装方法を工夫するのも1つの手かもしれません。

## 参考

- [HOTEL PLANISPHERE - テスト自動化練習サイト](https://hotel.testplanisphere.dev/ja/)
    - 記事中で使用しているサンプルサイト
- [Autify for Web Help Center](https://autify.zendesk.com/hc/ja)
- [【保存版】いろんな言語のyyyymmddhhmmss文字列 - Qiita](https://qiita.com/hirossyi73/items/46e19ffcad758ab36b66)
- [要素の表示・非表示を判定する。javascript・jQuery！ | ルディの脳内ブログ](https://alllearnhobby.com/archives/2312.html)
- [Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API)