midPointのテストにテスト自動化ツール Playwrightを使ってみた

# はじめに
システム開発を行う場合、実装したら終わりということはなく、テストを行わないと完成したとは言えません。しかし、繰り返し改修を行う場合は、このテストにかかる時間がとても増えてきているというのが実感です。
midPointはE2Eテストも主力テストとなることからコードベースで自動化したいと思っていたところ、Playwrightが流行っていて使えそうなため、実験しました。

# Playwrightとは？
- Microsoft社が提供しているオープンソースのテストツールになります
- 詳細は以下のページをご確認ください

https://playwright.dev/

# 環境の前提
- 利用するPlaywrightは`1.17.134`、midPointのバージョンは最新版である`4.9-alpine`（LTS版である`4.8`でないため注意）を利用します
- Linux環境を用意し、midPointはdocker-composeで起動してみます


# Playwrightの準備
- [Playwright](https://playwright.dev/docs/intro#installing-playwright)を見て準備します
```powershell
npm init playwright@latest
```
- Playwrightのconfig設定を変更します
```typescript:playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
 * See https://playwright.dev/docs/test-configuration.
 */
export default defineConfig({
  testDir: './tests',
  /* Run tests in files in parallel */
  fullyParallel: true,
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: 'html',
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    // baseURL: 'http://127.0.0.1:3000',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',

// 追加箇所 ここから
// midPointは利用するブラウザのlocaleで言語設定をするため、
//  locale の設定を追加する必要があります.
    locale: 'ja',
    timezoneId: 'Asia/Tokyo',

// 例えば、http-proxy の設定が必要な場合、mvm, npm とは別に設定をする必要があります
// (参照: https://playwright.dev/docs/network#http-proxy)
// ここまで
  },
//ここから下は元のファイルです
 /* Configure projects for major browsers */
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },

    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },

    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },

    /* Test against mobile viewports. */
    // {
    //   name: 'Mobile Chrome',
    //   use: { ...devices['Pixel 5'] },
    // },
    // {
    //   name: 'Mobile Safari',
    //   use: { ...devices['iPhone 12'] },
    // },

    /* Test against branded browsers. */
    // {
    //   name: 'Microsoft Edge',
    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
    // },
    // {
    //   name: 'Google Chrome',
    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
    // },
  ],

  /* Run your local dev server before starting the tests */
  // webServer: {
  //   command: 'npm run start',
  //   url: 'http://127.0.0.1:3000',
  //   reuseExistingServer: !process.env.CI,
  // },
});
``` 
- テストしてみます
```powershell
npx playwright test
npx playwright show-report
```

- レポートが表示されればOKのようです

![Playwright画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950162/7002e0a1-238e-7b2e-1e03-e2a2d9d5013a.png)

# midPoint4.9のインストール
- midPointの開発元であるEvolveum社ではコンテナ環境（dockerなど）を推奨しています
- 公式のイメージをdocker hubにアップロードしており、さらにdocker-composeも用意しています。そのため、今回も公式のファイルを修正しながら利用します
- docker-composeでは`.env`ファイルを自動で読んでくれるため、変数化するものはそちらに記載します


:::note info
locale、timezoneなどJPにしたい場合は、いろいろ準備が必要なので省略しています。
:::


:::note warn
locale=en_US.utf8で失敗する場合はlocaleファイルがインストールされていません。
`sudo apt install language-pack-en`などして入れてみましょう（Ubuntuの場合）
:::



```yaml:docker-compose.yaml
services:
  midpoint_data:
    image: postgres:16.6
    environment:
     - POSTGRES_PASSWORD=${DB_PASSWORD}
     - POSTGRES_USER=${DB_USER}
     - POSTGRES_INITDB_ARGS=--lc-collate=en_US.utf8 --lc-ctype=en_US.utf8
    networks:
     - net
    volumes:
     - midpoint_data:/var/lib/postgresql/data

  data_init:
    image: evolveum/midpoint:${MP_VER:-latest}-alpine
    command: >
      bash -c "
      cd /opt/midpoint ;
      bin/midpoint.sh init-native ;
      echo ' - - - - - - ' ;
      bin/ninja.sh -B info >/dev/null 2>/tmp/ninja.log ;
      grep -q \"ERROR\" /tmp/ninja.log && (
      bin/ninja.sh run-sql --create --mode REPOSITORY  ;
      bin/ninja.sh run-sql --create --mode AUDIT
      ) ||
      echo -e '\\n Repository init is not needed...' ;
      "
    depends_on:
     - midpoint_data
    environment:
     - MP_SET_midpoint_repository_jdbcUsername=${DB_USER}
     - MP_SET_midpoint_repository_jdbcPassword=${DB_PASSWORD}
     - MP_SET_midpoint_repository_jdbcUrl=jdbc:postgresql://midpoint_data:5432/midpoint
     - MP_SET_midpoint_repository_database=postgresql
     - MP_INIT_CFG=/opt/midpoint/var
    networks:
     - net
    volumes:
     - midpoint_home:/opt/midpoint/var

  midpoint_server:
    image: evolveum/midpoint:${MP_VER:-latest}-alpine
    depends_on:
      data_init:
        condition: service_completed_successfully
      midpoint_data:
        condition: service_started
    command: [ "/opt/midpoint/bin/midpoint.sh", "container" ]
    ports:
      - 8080:8080
    environment:
     - MP_SET_midpoint_repository_jdbcUsername=${DB_USER}
     - MP_SET_midpoint_repository_jdbcPassword=${DB_PASSWORD}
     - MP_SET_midpoint_repository_jdbcUrl=jdbc:postgresql://midpoint_data:5432/midpoint
     - MP_SET_midpoint_repository_database=postgresql
     - MP_SET_midpoint_administrator_initialPassword=Test5ecr3t
     - MP_UNSET_midpoint_repository_hibernateHbm2ddl=1
     - MP_NO_ENV_COMPAT=1
    networks:
     - net
    volumes:
     - midpoint_home:/opt/midpoint/var

networks:
  net:
    driver: bridge

volumes:
  midpoint_data:
  midpoint_home:
```
```text:.env
# DB 接続情報
DB_USER=midpoint
DB_PASSWORD=db.secret.pw.007
MP_VER＝4.9
```

https://raw.githubusercontent.com/Evolveum/midpoint-docker/master/docker-compose.yml

- `docker-compose`を動かすディレクトリに`docker-compose.yaml`と`.env`ファイルを置いて実行します

```powershell
docker compose config
docker compose up -d
```
- 特にエラーが起きなかった場合は、`http://localhost:8080/midpoint`で起動します

![スクリーンショット 2024-12-04 001717.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950162/9b0837f7-e52c-f5ef-aa99-047dd0f36165.png)

- IDとパスワードは`administrator:Test5ecr3t`となります
- 無事にログインできれば、以下のような画面が表示されます

![スクリーンショット 2024-12-04 002043.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950162/47d0d786-3f97-67bc-b128-1e179bcfe9e5.png)

 # Playwright実践
- 今回はお試しなので、ログイン前、ログイン後の画面がキャプチャで異なることを確認していきましょう。シナリオファイルを用意します
```typescript:midPoint49PlaywrightTest.spec.ts
import { test, expect } from '@playwright/test';

test('midPoint49', async ({ page }, testInfo) => {

        await test.step("ログイン画面", async () => {
            await page.goto("http://localhost:8080/midpoint/");
        });

        await test.step("パスワード入力", async () => {
            await page.locator('input[name="username"]').fill('administrator');
            await page.locator('input[name="password"]').fill('Test5ecr3t');
        });

        await test.step("パスワード入力フォーカス", async () => {
            await page.locator('input[name="password"]').click();
        });

        await test.step("スクリーンショット取得", async () => {
            await page.screenshot({ path: `${testInfo.snapshotPath("login.png")}`, fullPage: true });
        });

        await test.step('サインインして画面が変わったことを確認', async () => {
            await page.getByRole('button', { name: 'Sign in' }).click();

            // 画面再描画完了待ち.
            await page.waitForLoadState('networkidle');

            // 画面が変わったことを確認する
            expect(await page.screenshot({ fullPage: true })).not.toMatchSnapshot({ name: "login.png" });
                                                                             
  });
});
```
- テストをしてみます
```powershell
npx playwright test
npx playwright show-report
```

- レポートが表示されればOKです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950162/37570b9d-4251-5a6c-5b3f-ba7b6dcf043f.png)


- どうやらこのように比較をしているようです

![login-diff.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950162/ba7d8dfd-47a4-f494-7790-adad0ceb1fb3.png)



# まとめ
今回はお試しということで簡易なシナリオとなってしまい、本来のE2Eとはちょっと違いますが、利用できそうなことは分かりました。
CIとの組み合わせもできるため、夜間にテストを流しておくといった使い方でテストの効率化も図れそうです。