PlaywrightでORMを導入し、テストデータの用意を便利にした

# はじめに
PlaywrightでE2Eテストを作成する際のテストデータを用意しやすいように、PlaywrightにSequelizeを導入してみました。
今回はわかりやすいように、Playwrightプロジェクトを新規で作り、Sequelizeでテストデータを用意し、テストを実行するところまで行います。

この記事で扱わないこと
・Playwright、Sequelizeが何かの説明
・DBの用意することに関して

# 目次
1. [プロジェクトの作成と初期設定](#プロジェクトの作成と初期設定)
1. [接続設定](#接続設定)
1. [スキーマを定義](#スキーマを定義)
1. [テストで利用する](#テストで利用する)
1. [まとめ](#まとめ)

# プロジェクトの作成と初期設定
**まずはPlaywrightのプロジェクトを作成します**
Playwrightの初期設定は全てenterで作成しています
```sh
$ mkdir playwright-qiita
$ cd playwright-qiita
$ npm init playwright@latest
```
**今回必要なライブラリ類をインストールします**
```sh
$ npm install sequelize oracledb
```
**tsconfig.jsonファイルを作成します**
```json:tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true,
    "paths": {
      "@/*": ["./*"]
    }
  }
}
```

# 接続設定
**接続のhelper関数を作成します**
```ts:sequelize-connection.helpers.ts
import { Sequelize } from 'sequelize'

export const sequelize = new Sequelize({
  dialect: 'oracle',
  username: 'username',
  password: 'password',
  define: {
    freezeTableName: true,
  },
  dialectOptions: {
    connectString: '(DESCRIPTION=(CONNECT_TIMEOUT=30)(HOST=host.com)'(PORT=1500)),
  }
})
```
**解説**
**1. DB接続先の設定**
```ts
dialect: 'oracle',
```
アクセス先のDBを指定します。今回はoracleを指定していますが、mysql・postgres・sqlite・ mariadb・mssqlが利用できます。
接続するDBによって追加で必要なライブラリが存在するので、インストールをしてください（詳しくは[公式ドキュメント](https://sequelize.org/docs/v6/getting-started/)を確認してください）。
今回はoracleなので、`npm install oracledb`を実行しています。

**2. テーブル名を自動で変更しないように設定**
```ts
define: {
    freezeTableName: true,
}
```
この設定をtrueにしておかなければ、接続時にテーブル名の語尾に`s`をつけてしまいます。
今回接続するテーブルが`ACCOUNT`のため`s`が付与されてしまうと、接続に失敗してしまうので設定しています。

**3. hostやDBを指定**
```ts
dialectOptions: {
    connectString: '(DESCRIPTION=(CONNECT_TIMEOUT=30)(RETRY_COUNT=10)(HOST=host.com)(PORT=1500))',
}
```

今回はtimeoutやportを文字列で設定しましたが、以下のように個別での設定も可能です。
```ts
import { Sequelize } from 'sequelize'

export const sequelize = new Sequelize({
  dialect: 'oracle',
  username: 'username',
  password: 'password',
  host: 'host.com'
  port: 1500
  define: {
    freezeTableName: true,
  },
  dialectOptions: {
    timeout: 30
  }
})
```

# スキーマを定義
**ACCOUNTスキーマの定義を作成します**
DBが以下の状況であることを想定しています
```
テーブル名
ACCOUNT

カラム
ID　　　　　　：NUMBER型・primaryKey
MAIL_ADDRESS：VARCHAR型・ユニーク制約
COURSE      ：NUMBER型・0が無料会員、1が有料会員を示す
```
```ts:Account.ts
import { DataTypes, ModelDefined, Optional } from 'sequelize'
import { sequelize } from '@/sequelize/sequelize-connection.helpers'

interface Account {
  ID: number
  MAIL_ADDRESS: string
  COURSE: number
}

interface AccountCreationAttributes extends Optional<Account, 'ID'> {}

interface AccountModel
  extends ModelDefined<Account, AccountCreationAttributes> {
  COURSE: typeof COURSE
}

export const Account: AccountModel = sequelize.define(
  'ACCOUNT',
  {
    ID: {
      type: DataTypes.INTEGER,
    },
    MAIL_ADDRESS: {
      type: DataTypes.STRING,
    },
    COURSE_ID: {
      type: DataTypes.INTEGER,
    }
  },
  { timestamps: false },
) as AccountModel

const COURSE = {
  // 無料会員
  FREE: 0,
  // 有料会員
  PAID: 1,
} as const

Account.COURSE = COURSE
```
**解説**
**1. スキーマを定義する**
```ts
interface Account {
  ID: number
  MAIL_ADDRESS: string
  COURSE: number
}

export const Account: AccountModel = sequelize.define(
  'ACCOUNT',
  {
    ID: {
      type: DataTypes.INTEGER,
    },
    MAIL_ADDRESS: {
      type: DataTypes.STRING,
    },
    COURSE: {
      type: DataTypes.INTEGER,
    }
  },
  { timestamps: false },
) as AccountModel
```
defineの第1引数にテーブル名、第2引数に各カラムの設定、第3引数にオプションを設定します。
interfaceにてIDやMAIL_ADDRESSの型を設定することで、実際に値を入れる時に型チェックをしてくれます
また、デフォルトでDBにタイムスタンプを追加するので、設定していない場合は`{ timestamps: false }`を設定しておきましょう。

**2. オブジェクトリテラルでわかりにくい数値の値を定義しておく**
```ts
interface AccountModel
  extends ModelDefined<Account, AccountCreationAttributes> {
  COURSE: typeof COURSE
}

const COURSE = {
  // 無料会員
  FREE: 0,
  // 有料会員
  PAID: 1,
} as const

Account.COURSE = COURSE
```
このように設定しておくと、`Account.COURSE.FREE`で0が代入されるので、テストデータを用意するときにCOURSEの値が何のステータスを指すのかをわかりやすくできます。

# テストで利用する
**sample.spec.tsを作成**
実際に先ほど作成したスキーマ定義を使い、テストファイルでテストデータを用意します
スキーマから使える関数が定義されているので、それにIDやMAIL_ADDRESSに値を代入します。
```ts:sample.spec.ts
import { test } from '@playwright/test'
import { Account } from '@/sequelize/entity/Account'

const testPrefix = 'sample'

test('テストデータを用意する', async ({ }) => {
    // 初期データの挿入
    {
        await Account.create({
            ID: 1,
            MAIL_ADDRESS: `${testPrefix}@example.com`,
            COURSE: Account.COURSE.FREE,
        })
    }
})
```

# まとめ
E2Eのテストデータについては、永続化やそれ専用のAPIを作成するなど方法はありますが、今回はSequelizeを利用して、テストデータを用意してみました。
テストデータの用意は高速に実行するには大切なので、誰でもわかりやすく簡単にテストデータを用意できる機構を探していきたいと思います。

# 参考文献
- [Sequelize公式ドキュメント](https://sequelize.org/)
- [Playwright公式ドキュメント](https://playwright.dev/)