Next.jsにPlaywright導入とnext-i18nextで多言語化


こんにちは、mimiです。

Next.js + TypeScript な環境にPlaywrightとnext-i18nextを導入して、多言語化できてるか簡単なe2eテストをするまでのメモです。

Playwrightとは

Playwrightは、Webテストと自動化のためのフレームワークです。Chromium、Firefox、WebKitを一つのAPIでテストすることができます。Playwrightは、クロスブラウザのWeb自動化を可能にするために構築されており、常に環境に優しく、高機能で、信頼性が高く、高速なWeb自動化を実現します。

https://github.com/microsoft/playwright

Microsoft製(といってもオープンソース)の自動化ツール。puppeteerとかCypressとか仲間ですね。ここではe2eテストツールとして導入しますが、ビジュアルリグレッションテストとかも出来ます。Safariとかでもテストできるのが素敵。且つpuppeteerより早いと思いますしdebugモードもめっちゃいいです。

最近、WordPressもpuppeteerからPlaywrightへの移行計画を発表しました。
Migrating WordPress E2E tests to Playwright – Make WordPress Core

といってもライブラリが膨大なので移行作業なかなか進んでないみたいです。Playwrightに慣れたらPR送ってみようと思います。

そんな訳で、今e2eやるならPlaywrightだな!ということでNext.js + TypeScriptなプロジェクトに導入してみました。

インストール

Getting started | Playwright
に全部書いてあります。
マニュアルでやるなら

yarn add -D @playwright/test
// または
npm i -D @playwright/test

したあとで、

npx playwright install

するとサポートブラウザがインストールされます。テストするブラウザを限定したい場合はnpx playwright install webkit のように指定することもできます。

設定

インストールしただけですぐに使えますが、テストファイルの置き場所とかいくつか設定をカスタムしたくなると思います。その場合はplaywright.config.tsを作ります。

testMatchでテストするファイルを指定します。テストに使うブラウザも設定できたりします。

ex. chromiumとiPhone12の場合

import { PlaywrightTestConfig, devices } from '@playwright/test'

const config: PlaywrightTestConfig = {
    testMatch: '/e2e/**/*.spec.ts',
    use: {
        baseURL: process.env.BASE_URL || 'http://localhost:3000',
        headless: true,
        ignoreHTTPSErrors: true,
        actionTimeout: 10_000
    },
    projects: [
        {
            name: 'chromium',
            use: {...devices['Desktop Chrome']},
        },
        {
            name: 'iphone12',
            use: {...devices["iPhone 12"]}
        }
    ]
}
export default config;

これでe2eフォルダ以下にテストを書けます。

VS Codeのプラグイン Playwright Test for VSCode が神ってるので入れましょう

Playwright Test for VSCode – Visual Studio Marketplace

Playwright使うなら、ノールックで入れたほうがいいプラグインです。

e2eテストの下準備はとりあえずここまで。

テストの内容がすぐ観たい人はこちら

Next.js で多言語化 – next-i18nextとは

e2eテストの例として多言語化できてるかの簡単なテストを書いてみましょう。
ついでに、多言語化の話もメモしておこうと思います。

Next.jsの多言語化はnext-i18nextを使うのが良さそうです。

next-i18nextはi18nextとreact-i18nextを利用していますが、next-i18nextのユーザーは翻訳コンテンツをJSONファイルとして含めるだけで、他のことはあまり気にする必要がありません。

https://github.com/isaachinman/next-i18next

と書いてあるように導入めっっちゃ簡単です。

多言語化のあれこれは基本的に

NextJS i18n/Internationalization – DEV Community

こちらの↑記事を読んだら出来ます。

Next.jsで多言語化するときの基本設定

next-i18nextを使わなくても、Next.jsでは、next/routeruseRouterlocaleを取ることが出来ますし、configでlocaleを設定することで簡単にRoutingの設定ができます。

ここの部分はこの記事が日本語だし分かりやすいと思います。

Next.jsで多言語対応のサイトを作るのが簡単すぎた件

サブディレクトリでルーティング出来るように設定

上記の記事にもあるように、ドメイン切り替えもできますが、今回はサブディレクトリで英語をデフォルトとして言語を切り替えさせたいのでnext.config.jsで以下のように設定しておきます。

module.exports = {
  i18n: {
    locales: ['en', 'ja'],
    defaultLocale: 'en',
  },
}

これを書くだけで、ブラウザの言語情報からenかjaかを自動判別して表示を切り替えてくれます。この場合日本語環境でサイトのトップページにアクセスすると、https://example.com/jaに飛びます。
例えばpages/index.tsx

import type { NextPage } from 'next'
import { useRouter } from "next/router"

const HomePage: NextPage = () => {
  const { locale } = useRouter()

  return <main>Hello world: {locale}</main>
}
export default HomePage

という風に書いているとHello world:の後の表示がja/enと切り替わるでしょう。

ちなみにNext.jsはhtmlタグにlang情報付与されませんが、これを設定するとちゃんと勝手に付与してくれます。多言語化しなくてもjaを付与したかったらlocales: ['ja']にすれば良いだけです。

自動判別については

When a user visits the application root (generally /), Next.js will try to automatically detect which locale the user prefers based on the Accept-Language header and the current domain.
ユーザーがアプリケーションのルート(一般に/)にアクセスすると、Next.jsはAccept-Languageヘッダーと現在のドメインに基づいて、ユーザーが好むロケールを自動的に検出しようとします。
Automatic Locale Detection

という風にNext.jsの公式サイトに説明があります。もし自動判別をオフしたかったらlocaleDetection: falseを書くだけでオフしてルーティングをカスタマイズできます。詳しくは上記のリンクのドキュメントにあります。

英日切り替え表示とかは上記に紹介したNextJS i18n/Internationalization – DEV Communityに詳しいので参照してみてください。

さて、基本のルーティング設定が出来たので、next-i18nextを使って翻訳情報を追加していきましょう。

Hello world:の部分を書き換えて、日本語のアクセスだったらこんにちは:と切り替わるようにします。

インストール

yarn add next-i18next
// または
npm i next-i18next

設定

さっき書いたconfigをちょっと移動して一行加えます。

next-i18next.config.js というファイルを作成して、以下のように記述します。

module.exports = {
    i18n: {
        defaultLocale: "en",
        locales: ["en", "ja"],
        localePath: "./locales",
    }
}

そしてnext.config.jsを書き換えます。

const { i18n } = require("./next-i18next.config");

module.exports = {
  i18n
}

翻訳が当たるように_app.tsxをappWithTranslationでラップします。

import type { AppProps } from 'next/app'
import { appWithTranslation } from 'next-i18next'

function MyApp({ Component, pageProps }: AppProps) {
  return (
      <Component {...pageProps} />
  )
}
export default appWithTranslation(MyApp)

さらにページ単位で getStaticProps または getServerSideProps を使ってコンポーネントに非同期関数を含める必要があります。

import { serverSideTranslations } from 'next-i18next/serverSideTranslations'

// 中略。export default HomePageの後に以下のように記述します

export async function getStaticProps({ locale }: any) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ["common","home"])),
    }
  }
}

以上で設定完了。

翻訳を当てる

次に翻訳を書きましょう。localesディレクトリに以下のようにファイルを作ります。
今回の記事では出てきませんが、共通の翻訳はcommon.jsonに記述してください。

.
└── locales
    ├── en
    |   └── common.json
    |   └── home.json
    └── ja
        └── common.json
        └── home.json

2つのhome.jsonに以下のように記述して保存します。

./locales/en/home.json

{
    "greeting": "Hello"
}

./locales/ja/home.json

{
    "greeting": "こんにちは"
}

pages/index.jsxを以下のように書き換えます。

import type { NextPage } from 'next'
import { useTranslation } from 'next-i18next'

const HomePage: NextPage = () => {
  const { t } = useTranslation("home")

  return <main><{t("greeting")}</main>
}
export default HomePage

これで日本語環境でトップページにアクセスするとこんにちはと表示されるはずです。

ちなみに、翻訳が見当たらない時はgreetingが代わりに表示されます。とりあえず{t("Something String")}な形式で原文を書いてjson化するのが良いのか、最初からjson化したほうが良いのか知りたい。あと、jsonのファイルサイズどのぐらいまで問題ないのかとか。ご存知の方は教えてください。

e2eテストを書いてみる

さて、残りの多言語化の諸々は参照した記事に任せるとして、本題のe2eテストを書いてみましょう。

すごく愚直な感じですが、chromiumでlocaleをen-USとjaに切り替えてそれぞれのトップページでHelloこんにちはが表示されるか、というテストを書いてみます。このテストの場合はブラウザ毎のテストは要らない気がするのでchromiumだけで良いかなと思います。

./e2e/i18n.spec.tsに以下のように書いてみます。

import { test, chromium, expect } from '@playwright/test'

test('App should be displayed in English if the locale is en', async () => {
    const browser = await chromium.launch()
    const context = await browser.newContext({
        locale: 'en-US'
    })
    const page = await context.newPage();
    await page.goto('http://localhost:3000');
    const login = page.locator('main');
    expect(login).toHaveText('Hello');
});

test('App should be displayed in Japanese if the locale is ja', async () => {
    const browser = await chromium.launch()
    const context = await browser.newContext({
        locale: 'ja'
    })
    const page = await context.newPage();
    await page.goto('http://localhost:3000');
    const login = page.locator('main );
    expect(login).toHaveText('こんにちは');
});

これでnpx playwright testを実行すればテストが走ります。デバックしたかったらnpx playwright test --debugで実際にブラウザを立ち上げて表示を見ながら一つ一つデバックできます。VS Codeのプラグインからだとボタンポチで出来ます。

共通設定などがあれば、global-setup.tsを作成して、globalSetupを作成したら良いです。envの設定とかはそちらから読み込ませると便利です。詳しくはAdvanced: configuration | Playwrightなどを見てみてください。

というわけで、導入から簡単なテストまでの流れをわーっと書いてみました。特に引っかかりがなく出来てしまったのでこれからが怖いです。


この記事を書いた人