かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その33 )( ESLint を導入する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その32 )( npm の admin-lte package から jQuery がなくなっていたので対応する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Geb のテストを書き進める前に ESLint の導入の仕方を知りたくなったので、先に導入します。
    • 以下の内容で導入します。
      • eslint-config-airbnb-base を利用します。
      • jQuery 用の設定に変更します。

参照したサイト・書籍

  1. ESLint - Pluggable JavaScript linter
    https://eslint.org/

  2. 愚直にESLintを導入した話
    http://tech.mercari.com/entry/2017/07/31/170125

  3. eslint-config-airbnb-base
    https://www.npmjs.com/package/eslint-config-airbnb-base

  4. webpack から ESLint を使う設定
    https://www.pupha.net/archives/3289/

  5. eslint-loader
    https://www.npmjs.com/package/eslint-loader

  6. ESLintでReactとES2015の構文チェック(eslint-config-airbnb)
    http://dackdive.hateblo.jp/entry/2016/05/13/094000

  7. ESLint - Rules
    https://eslint.org/docs/rules/

  8. Can I use eslint-config-airbnb without eslint-plugin-react?
    https://github.com/airbnb/javascript/issues/451

  9. ESLint dollar($) is not defined. (no-undef)
    https://stackoverflow.com/questions/39510736/eslint-dollar-is-not-defined-no-undef

  10. Airbnb JavaScript Style Guide
    https://github.com/airbnb/javascript

目次

  1. ESLint をインストールする
  2. eslint --init を実行する
  3. ECMAScript 6 用の Rule を含まない airbnb-baseairbnb-base/legacy に変更する
  4. webpack から eslint が実行されるよう設定する
  5. 動作確認
  6. インデントと改行コードと文字列のクォーテーションを airbnb-base のものではなく独自ルールに変更する
  7. 修正を進める前に IntelliJ IDEA の ESLint の機能を有効にする
  8. 多めに出力されているメッセージに対応する
    1. jquery.autoKana.js をチェック対象から外す
    2. app.js を削除する
    3. '$' is not defined
    4. Unexpected unnamed function
    5. All 'var' declarations must be at the top of the function scope
    6. Expected a newline after '('
    7. Strings must use doublequote
    8. 'errmsg' used outside of binding context
    9. 'event' is defined but never used
    10. Missing semicolon
    11. Unexpected use of 'location'
  9. 各ファイル毎の出力されているメッセージに対応する
    1. src/main/assets/js/inquiry/input01.js
    2. src/main/assets/js/inquiry/input02.js
    3. src/main/assets/js/lib/class/Form.js
    4. src/main/assets/js/lib/util/converter.js
  10. 最後に

手順

ESLint をインストールする

npm install --save-dev eslint コマンドを実行してインストールします。

f:id:ksby:20171103080757p:plain f:id:ksby:20171103080915p:plain

eslint --init を実行する

git コマンドを実行するために使用している git-cmd.exe を起動してから ./node_modules/.bin/eslint --init コマンドを実行します。CUI で4つ質問事項が表示されますので、下の画像の上の方に表示されている内容を回答します。

  • How would you like to configure ESLint? --> Use a popular style guide
  • Which style guide do you want to follow? --> Airbnb
  • Do you use React? --> No
  • What format do you want your config file to be in? --> JavaScript

f:id:ksby:20171103081501p:plain

https://eslint.org/docs/user-guide/getting-started に書かれていたコマンドが ./node_modules/.bin/eslint --init だったので git-cmd.exe から実行しましたが、/ --> \ に変更してコマンドプロンプトから実行しても同じように動作します。

質問に回答すると npm で eslint-config-airbnb-base と eslint-plugin-import の package がインストールされて、package.json が以下のように変更され、

    ..........
    "cssnano": "^3.10.0",
    "eslint": "^4.10.0",
    "eslint-config-airbnb-base": "^12.1.0",
    "eslint-plugin-import": "^2.8.0",
    "http-proxy-middleware": "^0.17.4",
    ..........
  • 以下の2行が追加されていました。
    • "eslint-config-airbnb-base": "^12.1.0"
    • "eslint-plugin-import": "^2.8.0"

プロジェクトのルート直下に以下の内容の .eslintrc.js が生成されます。

module.exports = {
    "extends": "airbnb-base"
};

ECMAScript 6 用の Rule を含まない airbnb-baseairbnb-base/legacy に変更する

node_modules/eslint-config-airbnb-base/index.js は ECMAScript 6 用の Rule が記述されている './rules/es6' が含まれていますが、

module.exports = {
  extends: [
    './rules/best-practices',
    './rules/errors',
    './rules/node',
    './rules/style',
    './rules/variables',
    './rules/es6',
    './rules/imports',
  ].map(require.resolve),
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
    ecmaFeatures: {
      experimentalObjectRestSpread: true,
    },
  },
  rules: {
    strict: 'error',
  },
};

'./rules/es6' を含まない node_modules/eslint-config-airbnb-base/legacy.js というファイルもあります。

module.exports = {
  extends: [
    './rules/best-practices',
    './rules/errors',
    './rules/node',
    './rules/style',
    './rules/variables'
  ].map(require.resolve),
  env: {
    browser: true,
    node: true,
    amd: false,
    mocha: false,
    jasmine: false
  },
  rules: {
    'comma-dangle': ['error', 'never'],
    'prefer-numeric-literals': 'off',
    'no-restricted-properties': ['error', {
      object: 'arguments',
      property: 'callee',
      message: 'arguments.callee is deprecated',
    }, {
      property: '__defineGetter__',
      message: 'Please use Object.defineProperty instead.',
    }, {
      property: '__defineSetter__',
      message: 'Please use Object.defineProperty instead.',
    }],
  }
};

今回 ECMAScript 6 は使用していないので、legacy.js を使用するよう .eslintrc.js を以下のように変更します。

module.exports = {
    "extends": "airbnb-base/legacy"
};
  • airbnb-baseairbnb-base/legacy に変更します。

webpack から eslint が実行されるよう設定する

npm install --save-dev eslint-loader コマンドを実行して eslint-loader をインストールします。

f:id:ksby:20171103085120p:plain

webpack.config.js を以下のように変更します。

module.exports = {
    ..........
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: "eslint-loader"
            }
        ]
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery"
        })
    ]
};
  • module: { ... } を追加します。

動作確認

npm run spingboot コマンドを実行して webpack を実行してみると、なんか大量にメッセージが出力されました。。。

f:id:ksby:20171104034328p:plain

改行コードとインデントと文字列のクォーテーションで結構メッセージが表示されているようなので、まずは eslint のルールを今 js ファイルを書いているルール(4インデント+CRLF+ダブルクォーテーション)に変更できないか調べてみます。

インデントと改行コードと文字列のクォーテーションを airbnb-base のものではなく独自ルールに変更する

カスタマイズは .eslintrc.js に "rules": { ... } で記述すればできるようなので、.eslintrc.js を以下のように変更します。

module.exports = {
    "extends": "airbnb-base",
    "rules": {
        // this option sets a specific tab width for your code
        // http://eslint.org/docs/rules/indent
        indent: ['error', 4, {
            SwitchCase: 1,
            VariableDeclarator: 1,
            outerIIFEBody: 1,
            // MemberExpression: null,
            FunctionDeclaration: {
                parameters: 1,
                body: 1
            },
            FunctionExpression: {
                parameters: 1,
                body: 1
            },
            CallExpression: {
                arguments: 1
            },
            ArrayExpression: 1,
            ObjectExpression: 1,
            ImportDeclaration: 1,
            flatTernaryExpressions: false,
            ignoredNodes: ['JSXElement', 'JSXElement *']
        }],

        // disallow mixed 'LF' and 'CRLF' as linebreaks
        // http://eslint.org/docs/rules/linebreak-style
        'linebreak-style': ['error', 'windows'],

        // specify whether double or single quotes should be used
        quotes: ['error', 'double', { avoidEscape: true }]
    }
};
  • 改行コードとインデントと文字列のクォーテーションの設定は全て node_modules/eslint-config-airbnb-base/rules/style.js の中に書かれていたので、そこからコピーして設定を変更します。
  • indent の設定は 2-space indent 以外にもいろいろ設定が入っていたので、コピーして 24 のみ変更します。
  • linebreak-style の設定を unixwindows に変更します。
  • quotes の設定を singledouble に変更します。

再度 npm run spingboot コマンドを実行すると改行コードとインデントと文字列のクォーテーションではメッセージが出なくなりました。

f:id:ksby:20171104040602p:plain

修正を進める前に IntelliJ IDEA の ESLint の機能を有効にする

IntelliJ IDEA には ESLint を利用してソースコードをチェックする機能があるので、それを有効にします。

メインメニューから「File」-「Settings...」を選択します。「Settings」ダイアログを表示されるので、画面左上の検索フィールドに "eslint" と入力します。

ESLint の設定画面が表示されるので、以下の画面のように設定します。

f:id:ksby:20171104134640p:plain

  • 「Enable」をチェックします。
  • 「Node interpreter」に "node.exe" のパスを設定します。
  • 「Configuration file」はデフォルトの「Automatic search」を選択したままです。
  • 「Additional rules directory」「Extra eslint options」は何も設定しません。

「OK」ボタンを押してダイアログを閉じた後 js のソースコードを表示すると、以下の画像のように ESLint のチェックに引っかかった行の右側に赤線が表示され、マウスオーバーすると ESLint のメッセージが表示されます。

f:id:ksby:20171104135904p:plain

またエディタ上で右クリックしてコンテキストメニューを表示した後「Analyze」-「Inspect Code...」を選択し、

f:id:ksby:20171104140142p:plain

「Specify Inspection Scope」ダイアログが表示されたらそのまま「OK」を押すと、

f:id:ksby:20171104140341p:plain

画面下部に Inspection の結果が表示されます。この中に「Code quality tools」という項目があり、

f:id:ksby:20171104140639p:plain

展開すると ESLint のメッセージと該当箇所が表示されます。

f:id:ksby:20171104140943p:plain

多めに出力されているメッセージに対応する

ざっと見てまだ多めに出ているメッセージから対応します。

jquery.autoKana.js をチェック対象から外す

jquery.autoKana.js がチェックされてメッセージが出ていましたので、チェック対象から外します。webpack.config.js を以下のように変更します。

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: [
                    /node_modules/,
                    /jquery.autoKana.js$/
                ],
                loader: "eslint-loader"
            }
        ]
    },
  • exclude に /jquery.autoKana.js$/ を追加します。

app.js を削除する

src/main/assets/js/app.js もチェックされていましたが、このファイルは最初に作成したサンプルでもう不要なので削除します。

app.js を削除した後、webpack.config.js を以下のように変更します。

module.exports = {
    entry: {
        "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
        "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
        "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"],
        "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"]
    },
  • "js/app": ["./src/main/assets/js/app.js"] を削除します。

'$' is not defined

ESLint dollar($) is not defined. (no-undef) を見ると "jquery": true を定義すればチェックされないようになるようです。.eslintrc.js を以下のように変更します。

module.exports = {
    "extends": "airbnb-base/legacy",
    "env": {
        "jquery": true
    },
    ..........
  • "env": { "jquery": true } を追加します。

Unexpected unnamed function

無名関数は多用するので、この Rule は無効にします。.eslintrc.js を以下のように変更します。

    "rules": {
        // require function expressions to have a name
        // http://eslint.org/docs/rules/func-names
        'func-names': 'off',
  • 'func-names': 'off' を追加します。

All 'var' declarations must be at the top of the function scope

変数宣言は関数スコープ内の最初に行うこと、というメッセージのようですが、エラーが出ている原因は require("vendor/autokana/jquery.autoKana.js");require("jquery-ui/ui/widgets/autocomplete.js"); を上に書いているので、その下の var で宣言している部分でチェックに引っかかっているためのようです。

var より require を上にまとめておきたいので、この Rule は無効にします。.eslintrc.js を以下のように変更します。

    "rules": {
        // requires to declare all vars on top of their containing scope
        'vars-on-top': 'off',
  • 'vars-on-top': 'off' を追加します。

Expected a newline after '('

https://eslint.org/docs/rules/function-paren-newline を見ると、( の後に続けて引数を記述するのではなく、一旦改行をして次の行から書くこと、というメッセージのようです。なんか縦に長くなってソースが見にくくなる気しかしないので、これは無効にします。

    "rules": {
        ..........

        // require function expressions to have a name
        // http://eslint.org/docs/rules/func-names
        'func-names': 'off',

        // enforce consistent line breaks inside function parentheses
        // https://eslint.org/docs/rules/function-paren-newline
        'function-paren-newline': 'off',

        // this option sets a specific tab width for your code
        // http://eslint.org/docs/rules/indent
        indent: ['error', 4, {
  • 'function-paren-newline': 'off' を追加します。

Strings must use doublequote

文字列のクォーテーションはダブルクォーテーションを使用するルールを指定しましたが、シングルクォーテーションになっているところがありました。こちらは全てダブルクォーテーションに修正します。

'errmsg' used outside of binding context

src/main/assets/js/inquiry/input02.js で errmsg 変数を block スコープ毎に var で宣言していたので引っかかっているようです。関数の最初で var で宣言するように修正します。

'event' is defined but never used

イベントハンドラの無名関数の引数に使用している/いないに関わらず event という引数を書いていたので、そこで引っかかっているようです。使用されていない event 引数は削除します。

Missing semicolon

単純に末尾のセミコロンのつけ忘れです。修正します。

Unexpected use of 'location'

node_modules/eslint-config-airbnb-base/rules/variables.js を見ると以下のように定義されており、

const restrictedGlobals = require('eslint-restricted-globals');

module.exports = {
  rules: {
    ..........

    // disallow specific globals
    'no-restricted-globals': ['error', 'isFinite', 'isNaN'].concat(restrictedGlobals),

node_modules/eslint-restricted-globals/index.js には以下のように定義されていました。

module.exports = [
    ..........
    'length',
    'location',
    'locationbar',

locationグローバル変数として使用すると no-restricted-globals の Rule に引っかかりエラーになるようです。

ソースコードを修正して対応します。locationwindow.location に変更します。window は node_modules/eslint-restricted-globals/index.js に定義されておらず、グローバル変数として利用しても Rule に引っかかりません。

各ファイル毎の出力されているメッセージに対応する

src/main/assets/js/inquiry/input01.js

  • 'idList' is assigned a value but never used は宣言した変数を関数内で使用していないことが原因なので、変数を宣言している行を削除します。
  • 'validator' is already declared in the upper scope はソースの上で宣言している var validator = require("lib/util/validator.js"); と同じ validator という変数を使用していたので、validatorvalidateFunction に変更します。

src/main/assets/js/inquiry/input02.js

  • 'validator' is already declared in the upper scope は input01.js と同様に validatorvalidateFunction に変更します。
  • A space is required after '{'A space is required before '}'IntelliJ IDEA のフォーマッターがスペースを入れないようになっているので、この Rule を無効に変更します。.eslintrc.js を以下のように変更します。
    "rules": {
        ..........

        // require padding inside curly braces
        'object-curly-spacing': ['error', 'never']
    }
  • 'event' is already declared in the upper scopeevente に変更します。

src/main/assets/js/lib/class/Form.js

  • 'Form' was used before it was definedmodule.exports = Form; の後に function Form(idList) { ... } の記述があるために出力されていますが、module.exports の記述を先頭にしたいので .eslintrc.js に no-use-before-define の設定を node_modules/eslint-config-airbnb-base/rules/variables.js からコピーして functions: truefunctions: false に変更します。
    "rules": {
        ..........

        // disallow use of variables before they are defined
        'no-use-before-define': ['error', { functions: false, classes: true, variables: true }]
    }
  • Line 21 exceeds the maximum line length of 100 は1行の最大値を 120 に変更したいので、.eslintrc.js に max-len の設定を node_modules/eslint-config-airbnb-base/rules/style.js からコピーして 100120 に変更します。
    "rules": {
        ..........

        // specify the maximum length of a line in your program
        // http://eslint.org/docs/rules/max-len
        'max-len': ['error', 120, 2, {
            ignoreUrls: true,
            ignoreComments: false,
            ignoreRegExpLiterals: true,
            ignoreStrings: true,
            ignoreTemplateLiterals: true
        }]
    }
  • Assignment to property of function parameter 'form' は関数の引数で渡された変数を変更しようとしているから出ているメッセージらしいのですが、form を引数に渡さないようにする方法が今だによく分かっていないので、ソースの先頭に /* eslint-disable no-param-reassign,lines-around-directive */ を記述して Form.js だけこの Rule を無効にします。

src/main/assets/js/lib/util/converter.js

  • Multiple spaces found before ... はソースの右側にスペース複数を入れてからコメントを書いていたためでした。コメントは移動してコメントだけの行になるようにします。

以上で全ての対応が完了です。

最後に

動きはしていたので今回はリンターを導入してもそんなに指摘は受けないかな、と思っていましたが、いつものように結構エラーが出ました。。。 ESLint は eslint-config-airbnb や eslint-config-airbnb-base といった使える設定が公開されているし、導入例の記事もいろいろ公開されていたので導入しやすかったです。また Airbnb JavaScript Style Guide は有名なようなので、普段 Javascript を実装しない自分としては読んでおきたいと思います。

また今回の調査をしていた時に prettier という Javascript のフォーマッターの記事を見かけました。Using External tools: ESLint autofix, React Native and Prettier を見ると IntelliJ IDEA でもサポートされているようなので、時間があれば見てみたいと思います。

最後に .eslintrc.js の最終版を載せておきます。

module.exports = {
    "extends": "airbnb-base/legacy",
    "env": {
        "browser": true,
        "jquery": true
    },
    "rules": {
        // requires to declare all vars on top of their containing scope
        'vars-on-top': 'off',

        // require function expressions to have a name
        // http://eslint.org/docs/rules/func-names
        'func-names': 'off',

        // enforce consistent line breaks inside function parentheses
        // https://eslint.org/docs/rules/function-paren-newline
        'function-paren-newline': 'off',

        // this option sets a specific tab width for your code
        // http://eslint.org/docs/rules/indent
        indent: ['error', 4, {
            SwitchCase: 1,
            VariableDeclarator: 1,
            outerIIFEBody: 1,
            // MemberExpression: null,
            FunctionDeclaration: {
                parameters: 1,
                body: 1
            },
            FunctionExpression: {
                parameters: 1,
                body: 1
            },
            CallExpression: {
                arguments: 1
            },
            ArrayExpression: 1,
            ObjectExpression: 1,
            ImportDeclaration: 1,
            flatTernaryExpressions: false,
            ignoredNodes: ['JSXElement', 'JSXElement *']
        }],

        // disallow mixed 'LF' and 'CRLF' as linebreaks
        // http://eslint.org/docs/rules/linebreak-style
        'linebreak-style': ['error', 'windows'],

        // specify whether double or single quotes should be used
        quotes: ['error', 'double', { avoidEscape: true }],

        // require padding inside curly braces
        'object-curly-spacing': ['error', 'never'],

        // disallow use of variables before they are defined
        'no-use-before-define': ['error', { functions: false, classes: true, variables: true }],

        // specify the maximum length of a line in your program
        // http://eslint.org/docs/rules/max-len
        'max-len': ['error', 120, 2, {
            ignoreUrls: true,
            ignoreComments: false,
            ignoreRegExpLiterals: true,
            ignoreStrings: true,
            ignoreTemplateLiterals: true
        }]
    }
};

履歴

2017/11/05
初版発行。
2017/11/06
* "browser": true, は node_modules/eslint-config-airbnb-base/legacy.js で設定済なので削除しました。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( 番外編 )( webpack で jQuery だけバンドルしないで外部ファイルを利用するには? )

概要

記事一覧はこちらです。

既存のページで既に jQuery を使用しており、そこに webpack でバンドルした js ファイルを導入したい場合に、jQuery だけバンドルしないで外部ファイルの jQuery を利用する方法が知りたくなったので調べてみます。

参照したサイト・書籍

  1. webpackでCDNから取ってきたりした外部のjQueryなどを利用する方法
    http://frontend-takuyan.hateblo.jp/entry/jquery-in-webpack

  2. webpack - Externals
    https://webpack.js.org/configuration/externals/

目次

  1. webpack.config.js を変更する
  2. 動作確認

本文

webpack.config.js を変更する

webpackでCDNから取ってきたりした外部のjQueryなどを利用する方法 の記事にずばりその通りの内容が記載されていました。webpack.config.js に以下の記述を追加すればいいそうです。

var webpack = require('webpack');

module.exports = {
    entry: {
        "js/app": ["./src/main/assets/js/app.js"],
        "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
        "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
        "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"],
        "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"]
    },
    output: {
        path: __dirname + "/src/main/resources/static",
        publicPath: "/",
        filename: "[name].js"
    },
    resolve: {
        modules: [
            "node_modules",
            "src/main/assets/js"
        ],
        alias: {
            jquery: "jquery"
        }
    },
    externals: {
        jquery: "jQuery"
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery"
        })
    ]
};
  • externals: { jquery: "jQuery" } を追加します。ただし "jquery" ではなく、必ず "jQuery" と記述する必要があります。"jquery" だと正常に動作しませんでした。

動作確認

src/main/resources/static/vendor の下に node_modules/jquery/dist/jquery.min.js をコピーします。

src/main/resources/templates/web/inquiry/input01.html, input02.html を以下のように変更します。

<!-- REQUIRED JS SCRIPTS -->
<script src="/vendor/jquery.min.js"></script>
<script src="/js/inquiry/input01.js"></script>

</body>
</html>
  • 各画面用の js ファイルの上に <script src="/vendor/jquery.min.js"></script> を追加します。

コマンドプロンプトを起動して npm run springboot コマンドを実行し、Tomcat も起動します。

http://localhost:9080/inquiry/input/01/ にアクセスすると入力画面1が表示されます。

f:id:ksby:20171103000442p:plain

「次へ」ボタンをクリックすると入力チェックのエラーメッセージが表示されます。Javascript の処理が問題なく動作していますね。

f:id:ksby:20171103000859p:plain

autokana も動作しており、またデータを全て入力すると入力チェックOKの状態になります。

f:id:ksby:20171103001233p:plain

「次へ」ボタンをクリックして入力画面2へ遷移した後、郵便番号を入力すると、ajax でデータを取得して autocomplete で住所の選択肢も表示されます。

f:id:ksby:20171103002805p:plain

動作も問題なさそうです。

履歴

2017/11/03
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その32 )( npm の admin-lte package から jQuery がなくなっていたので対応する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その31 )( テスト対象のブラウザに Headless Chrome と HtmlUnit を追加する+Chrome, Firefox, HtmlUnit で連続テストする gradle タスクを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 今回作成しているプロジェクトは git clone してコマンドをいくつか実行するだけで利用可能になるようにしたつもりでいたのですが、試してみたところ admin-lte package から jQuery がなくなっており、エラーが出て動作させることができませんでした。動作できるように修正します。

参照したサイト・書籍

目次

  1. git clone から動作させようとしてエラーが出るところまでやってみる
    1. git clone する
    2. IntelliJ IDEA で boot-npm-geb-sample プロジェクトを開く
    3. npm install コマンドを実行する
    4. clean タスク → Rebuild Project → build タスクを実行する
  2. エラーの原因を修正する

手順

git clone から動作させようとしてエラーが出るところまでやってみる

git clone する

C:\project-test フォルダを作成して、この下に git clone します。

コマンドプロンプトを起動して C:\project-test フォルダに移動した後、git clone -b feature/1-issue https://github.com/ksby/ksbysample-boot-miscellaneous.git コマンドを実行します。

f:id:ksby:20171101002751p:plain

C:\project-test の下に ksbysample-boot-miscellaneous フォルダが作成され、その下に boot-npm-geb-sample プロジェクトが出来ます。

f:id:ksby:20171101003016p:plain

IntelliJ IDEA で boot-npm-geb-sample プロジェクトを開く

IntelliJ IDEA の「Welcome to IntelliJ IDEA」ダイアログを表示した後、「Open」ボタンをクリックします。

f:id:ksby:20171101003534p:plain

「Open File or Project」ダイアログが表示されますので、C:\project-test\ksbysample-boot-miscellaneous\boot-npm-geb-sample フォルダを選択して「OK」ボタンをクリックします。

f:id:ksby:20171101003756p:plain

「Import Project from Gradle」ダイアログが表示されますので、「Create directories for empty content roots automatically」をチェックした後「OK」ボタンをクリックします。

f:id:ksby:20171101004047p:plain

IntelliJ IDEA のメイン画面が開きますので、画面右下の処理中を示すプログレスバーが消えるまで待ちます。

処理が終了すると画面右側の Gradle projects が下の画像のようになります。

f:id:ksby:20171101004723p:plain

npm install コマンドを実行する

コマンドプロンプトで C:\project-test\ksbysample-boot-miscellaneous\boot-npm-geb-sample へ移動した後、npm install コマンドを実行します。

f:id:ksby:20171101005601p:plain

次に npm run springboot コマンドを実行します。。。が、Module not found: Error: Can't resolve 'admin-lte/plugins/jQuery/jquery-2.2.3.min.js' in ... というエラーメッセージが出力されました。なぜか jquery のファイルがないようです。。。

f:id:ksby:20171101005809p:plain f:id:ksby:20171101010017p:plain f:id:ksby:20171101010200p:plain f:id:ksby:20171101010442p:plain f:id:ksby:20171101010635p:plain f:id:ksby:20171101010735p:plain

clean タスク → Rebuild Project → build タスクを実行する

IntelliJ IDEA から clean タスク → Rebuild Project → build タスクを実行してみると、こちらは BUILD SUCCESSFUL のメッセージが出力されて正常に終了しました。

f:id:ksby:20171101011530p:plain

エラーの原因を修正する

以前は node_modules/admin-lte/plugins/jQuery/jquery-2.2.3.min.js というファイルが存在したのですが、node_modules/admin-lte/plugins の下を見ると jQuery フォルダ自体がなくなっていました。。。 admin-ltejQuery を同梱しなくなったんですね。

f:id:ksby:20171101014925p:plain

ということであれば普通に npm で jQuery をインストールして、そちらを使うように変更します。

まずは npm install --save jquery コマンドを実行して jQuery をインストールします。

f:id:ksby:20171101014758p:plain

次に webpack.config.js を以下のように変更します。

var webpack = require('webpack');

module.exports = {
    entry: {
        "js/app": ["./src/main/assets/js/app.js"],
        "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
        "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
        "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"],
        "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"]
    },
    output: {
        path: __dirname + "/src/main/resources/static",
        publicPath: "/",
        filename: "[name].js"
    },
    resolve: {
        modules: [
            "node_modules",
            "src/main/assets/js"
        ],
        alias: {
            jquery: "jquery"
        }
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery"
        })
    ]
};
  • "admin-lte/plugins/jQuery/jquery-2.2.3.min.js""jquery" に変更します(全部で3ヶ所)。

これで変更は完了です。再び npm run springboot コマンドを実行してみると、今度はエラーメッセージは出ませんでした(下の方に白い文字で出ているメッセージは Tomcat が起動していないことによるものです)。

f:id:ksby:20171101015521p:plain

Tomcat を起動して。。。と思ったら spring.profiles.active を指定していないことによるエラーが出ました。

f:id:ksby:20171101015932p:plain

IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択し、「Run/Debug Configurations」ダイアログが表示されたら、画面左側の一覧から「Spring Boot」-「Application」を選択して、画面右側の「VM options」に -Dspring.profiles.active=develop -Dfile.encoding=UTF-8 を入力後「OK」ボタンをクリックします。

f:id:ksby:20171101020810p:plain

再度 Tomcat を起動したら、今度は問題なく起動しました。

f:id:ksby:20171101021041p:plain

ブラウザで http://localhost:9080/inquiry/input/01/ にアクセスすると入力画面1も表示されます。

f:id:ksby:20171101021221p:plain

何も入力せずに「次へ」ボタンを押すと入力エラーのある入力項目にエラーメッセージが表示されますので、Javascript も正常に動作しているようです。

f:id:ksby:20171101021447p:plain

ちょっとつまずきましたが、以前と比較すると試せるようになるまで全然簡単になりました。Edit Configuration の設定も何か簡単に出来る方法があるといいのですが。。。と思い Web で検索すると以下の記事を見つけました。これまで使用したことがありませんでしたが、share チェックボックスをチェックすればよいようです。そのうち試してみたいと思います。

How do I share IntelliJ Run/Debug configurations between projects? https://stackoverflow.com/questions/24642147/how-do-i-share-intellij-run-debug-configurations-between-projects

履歴

2017/11/01
初版発行。

Java SE を 8u144 → 8u152 へバージョンアップ

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

Java SE を 8u144 → 8u152 へバージョンアップする

  1. OracleJava SE Downloads を見ると 8u152 がダウンロードできるようになっていました。以下のページに説明があります。

    8u152 へバージョンアップします。

  2. jdk-8u152-windows-x64.exe をダウンロードして C:\Java\jdk1.8.0_152 へインストールした後、環境変数 JAVA_HOME のパスを C:\Java\jdk1.8.0_152 へ変更します。

    コマンドプロンプトから java -version を実行し、1.8.0_152 に変更されていることを確認します。

    f:id:ksby:20171029235511p:plain

  3. IntelliJ IDEA を再起動した後、プロジェクトで使用する Java SE を 8u152 へ変更します。

  4. 開いているプロジェクトを閉じて「Welcome to IntelliJ IDEA」ダイアログを表示します。

  5. ダイアログ下部の「Configure」-「Project Defaults」-「Project Structure」を選択します。

    f:id:ksby:20171029235702p:plain

  6. 「Default Project Structure」ダイアログが表示されます。画面左側で「Project Settings」-「Project」を選択後、画面右側の「Project SDK」の「New...」ボタンをクリックし、表示されるメニューから「JDK」を選択します。

    f:id:ksby:20171029235937p:plain

  7. 「Select Home Directory for JDK」ダイアログが表示されます。C:\Java\jdk1.8.0_152 を選択した後、「OK」ボタンをクリックします。

    f:id:ksby:20171030000120p:plain

  8. 「Default Project Structure」ダイアログに戻るので、今度は「Project SDK」の「Edit」ボタンをクリックします。

    f:id:ksby:20171030000304p:plain

  9. 画面左側で「Platform Settings」-「SDKs」が選択された状態になるので、画面右上の入力フィールドで "1.8" → "1.8.0_152" へ変更します。

    f:id:ksby:20171030000507p:plain

  10. 次に中央のリストから「1.8.0_144」を選択した後、リストの上の「-」ボタンをクリックして削除します。

    f:id:ksby:20171030000806p:plain

  11. 「OK」ボタンをクリックして「Default Project Structure」ダイアログを閉じます。

  12. 「Welcome to IntelliJ IDEA」ダイアログに戻ったら、ksbysample-webapp-lending プロジェクトを開きます。

  13. IntelliJ IDEA のメイン画面が開いたら、メニューから「File」-「Project Structure...」を選択します。

  14. 「Project Structure」ダイアログが表示されます。以下の画像の状態になっているので、

    f:id:ksby:20171030001028p:plain

    「Project SDK」と「Project language level」を選択し直します。

    f:id:ksby:20171030001240p:plain

  15. 「OK」ボタンをクリックして「Project Structure」ダイアログを閉じます。

  16. メイン画面に戻ると画面右下に「Indexing...」の表示が出るので、終了するまで待ちます。

    f:id:ksby:20171030001415p:plain

  17. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20171030002030p:plain

  18. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20171030002532p:plain

  19. 特に問題は発生しませんでした。8u152 で開発を進めます。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その31 )( テスト対象のブラウザに Headless Chrome と HtmlUnit を追加する+Chrome, Firefox, HtmlUnit で連続テストする gradle タスクを作成する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その30 )( Geb を 2.0 へバージョンアップする+Firefox headless モードを使用する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • テスト対象のブラウザに Headless ChromeHtmlUnit を追加します。
    • ChromeFirefox (どちらも Headless モード)と HtmlUnit で連続してテストする gradle のタスクを作成します。

参照したサイト・書籍

  1. ヘッドレス Chrome ことはじめ
    https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja

  2. Headless Chrome and Selenium on Windows?
    https://stackoverflow.com/questions/43880619/headless-chrome-and-selenium-on-windows

  3. SeleniumHQ/htmlunit-driver
    https://github.com/SeleniumHQ/htmlunit-driver

  4. Where to find 64 bit version of chromedriver.exe for Selenium WebDriver?
    https://stackoverflow.com/questions/23081507/where-to-find-64-bit-version-of-chromedriver-exe-for-selenium-webdriver

  5. 【入門】Geb+SpockではじめるWebテストクロスブラウザテスト編~ / Setting up and running of the cross-browser test
    http://yfj2.hateblo.jp/entry/2014/11/09/004011

  6. ChromeDriver - WebDriver for Chrome
    https://sites.google.com/a/chromium.org/chromedriver/

  7. geb/geb-example-gradle/build.gradle
    https://github.com/geb/geb-example-gradle/blob/master/build.gradle

目次

  1. Chrome と HtmlUnit 用の Selenium WebDriver をインストールする
  2. ChromeDriver をダウンロードして配置する
  3. GebConfig.groovy を修正する
  4. build.gradle にタスクを定義する
  5. 動作確認

手順

ChromeHtmlUnit 用の Selenium WebDriver をインストールする

build.gradle を以下のように変更します。

dependencies {
    ..........

    // for Geb + Spock
    testCompile("org.gebish:geb-spock:2.0")
    testCompile("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:htmlunit-driver:2.27")
    testCompile("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")
}
  • 以下の行を追加します。
    • testCompile("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    • testCompile("org.seleniumhq.selenium:htmlunit-driver:2.27")
  • HtmlUnit 用の WebDriver として selenium-htmlunit-driver がありますが、https://mvnrepository.com/selenium-htmlunit-driver を見てみると最新バージョンが 2016/2 にリリースされた 2.52.0 で、3.x 系がリリースされていませんでした。さすがに 3.6.0 と組み合わせるのは無理だろうと思い Web で探してみると、WebDriver compatible を謳っている htmlunit-driver が見つかり、こちらは 2017/6 に 2.27 がリリースされていましたので、htmlunit-driver をインストールすることにします。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

ChromeDriver をダウンロードして配置する

Chrome を操作するためには Firefox の geckodriver と同じように ChromeDriver が必要です。ダウンロードして配置します。

ChromeDrive の Downloads ページ の Latest Release の右側に表示されている「ChromeDriver 2.33」リンクをクリックして次のページへ進んだ後、「chromedriver_win32.zip」リンクをクリックして chromedriver_win32.zip をダウンロードします。64bit版は提供されていませんが、chromedriver_win32.zip のファイルで 64bit の Windows + Chrome でも問題なく動作します。

ダウンロード後、C:\chromedriver\2.33 ディレクトリを作成した後、chromedriver_win32.zip を解凍して出来る chromedriver.exe をその下に配置します。

f:id:ksby:20171029004812p:plain

GebConfig.groovy を修正する

src/test/resources/GebConfig.groovy を以下のように変更します。

import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.firefox.FirefoxOptions
import org.openqa.selenium.htmlunit.HtmlUnitDriver

System.setProperty("webdriver.gecko.driver", "C:/geckodriver/0.19.0/geckodriver.bat")
System.setProperty("webdriver.chrome.driver", "C:/chromedriver/2.33/chromedriver.exe")
driver = {
    FirefoxOptions firefoxOptions = new FirefoxOptions()
    firefoxOptions.setHeadless(true)
    new FirefoxDriver(firefoxOptions)
}
baseUrl = "http://localhost:8080"
waiting {
    timeout = 15
}

environments {

    chrome {
        driver = {
            ChromeOptions chromeOptions = new ChromeOptions()
            chromeOptions.setHeadless(true)
            new ChromeDriver(chromeOptions)
        }
    }

    firefox {
        driver = {
            FirefoxOptions firefoxOptions = new FirefoxOptions()
            firefoxOptions.setHeadless(true)
            new FirefoxDriver(firefoxOptions)
        }
    }

    htmlunit {
        new HtmlUnitDriver(true)
    }

}
  • System.setProperty("webdriver.gecko.driver", "C:/geckodriver/0.19.0/geckodriver.bat")driver の中に書いていたのを外に出します。
  • System.setProperty("webdriver.chrome.driver", "C:/chromedriver/2.33/chromedriver.exe") を追加します。
  • environments { ... } の定義を追加し、この中に chrome, firefox, htmlunit の driver の定義を記述します。

build.gradle にタスクを定義する

build.gradle に各ブラウザ毎のテスト用タスクと、全てのブラウザのテスト用タスクを連続実行するためのタスクを追加します。このタスクは geb/geb-example-gradle/build.gradle からコピーしました。

..........

test {
    // test タスクの jvmArgs は tasks.withType(Test) { ... } で定義している
    exclude "geb/**"
}

// for Geb + Spock Integration Test
def drivers = ["chrome", "firefox", "htmlunit"]
drivers.each { driver ->
    task "${driver}Test"(type: Test) {
        // 前回実行時以降に何も更新されていなくても必ず実行する
        outputs.upToDateWhen { false }
        systemProperty "geb.env", driver
        exclude "ksbysample/**"
    }
}
task gebTest {
    dependsOn drivers.collect { tasks["${it}Test"] }
    enabled = false
}

tasks.withType(Test) {
    jvmArgs = ['-Dspring.profiles.active=unittest']
}

// for Doma-Gen
task domaGen {
    ..........
  • test タスクから jvmArgs = ['-Dspring.profiles.active=unittest'] を削除します。
  • 記述していた task gebTest(type: Test) { ... } を削除します。
  • // for Geb + Spock Integration Test から下の部分を記述します。リストからタスクを動的に作成したり、動的に作成されたタスクを依存関係に指定する方法があるとは、初めて知りました。。。
  • tasks.withType(Test) { ... } を追加し、Test タイプのタスク共通の設定はこちらに記述します。今回は jvmArgs のみ記述します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

更新すると Gradle projects の other の下に chromeTest, firefoxTest, gebTest, htmlunitTest の4つのタスクが表示されます。

f:id:ksby:20171029085625p:plain

動作確認

最初に clean タスク → Rebuild Project → build タスクを実行して BUILD SUCCESSFUL のメッセージが出力されることを確認します。追加した chromeTest, firefoxTest, gebTest, htmlunitTest のタスクが実行されていないことも確認できます。

f:id:ksby:20171029090212p:plain

chromeTest タスクを実行します。ブラウザの画面が表示されず、テストは成功します。また ChromeDriver のメッセージが出力されており、Chrome がテストに使われていることが分かります。

f:id:ksby:20171029090534p:plain

firefoxTest タスクを実行します。こちらもブラウザの画面は表示されず、テストは成功します。

f:id:ksby:20171029090804p:plain

htmlunitTest タスクを実行します。HtmlUnit はブラウザではないので当然画面は表示されず、テストは成功します。

f:id:ksby:20171029091445p:plain

最後に gebTest タスクを実行します。chromeTest, firefoxTest, htmlunitTest の3つのタスクが実行されてそれぞれ成功し、gebTest タスクは build.gradle 内で enabled = false を記述しているので SKIP されることが確認できます。

f:id:ksby:20171029091741p:plain

ちなみに src/test/groovy/geb/gebspec/SimpleTestSpec.groovy をテストが失敗するように変更してから、

class SimpleTestSpec extends GebSpec {

    def "動作確認用"() {
        ..........

        then: "「お名前(漢字)」の必須チェックエラーのメッセージが表示される"
        $("#form-group-name .js-errmsg").displayed == true
//        $("#form-group-name .js-errmsg").text() == "お名前(漢字)を入力してください"
        $("#form-group-name .js-errmsg").text() == "エラーです"
    }

}

gebTest タスクを実行すると chromeTest タスクでエラーが出て先に進みません。

f:id:ksby:20171029092304p:plain

履歴

2017/10/29
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その30 )( Geb を 2.0 へバージョンアップする+Firefox headless モードを使用する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その29 )( Geb をインストールする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Geb の 2.0 がリリースされたのでバージョンアップします。
    • geckodriver.exe の場所を jvm のオプション -Dwebdriver.gecko.driver= で指定するのではなく System.setProperty("webdriver.gecko.driver", "...") で指定するよう変更します。
    • Firefox に headless モードがあるようなので、headless モードを使用するよう設定を変更します。

参照したサイト・書籍

  1. Geb - Very Groovy Browser Automation
    http://www.gebish.org/

  2. Headless mode
    https://developer.mozilla.org/en-US/Firefox/Headless_mode

  3. Disable HttpClient logging
    https://stackoverflow.com/questions/4915414/disable-httpclient-logging

  4. How do I disable Firefox logging in Selenium using Geckodriver?
    https://stackoverflow.com/questions/41387794/how-do-i-disable-firefox-logging-in-selenium-using-geckodriver

目次

  1. Geb を 2.0-rc-1 → 2.0 へバージョンアップする
  2. geckodriver.exe の場所は System.setProperty("webdriver.gecko.driver", "...") で指定する
  3. Firefox headless モードを使用する
    1. GebConfig.groovy を変更する
    2. 動作確認
    3. 大量に出力される DEBUG ログ [Forwarding ... on session ... to remote] DEBUG org.apache.http を抑制する
    4. Marionette DEBUG のログを抑制する

手順

Geb を 2.0-rc-1 → 2.0 へバージョンアップする

build.gradle の以下の点を変更します。

dependencies {
    ..........
    def seleniumVersion = "3.6.0"

    ..........

    // for Geb + Spock
    testCompile("org.gebish:geb-spock:2.0")
    testCompile("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")
}
  • org.gebish:geb-spock のバージョン番号を 2.0-rc-12.0 に変更します。
  • def seleniumVersion = "3.6.0" を追加し、selenium のモジュールのバージョン番号を ${seleniumVersion} で指定するよう変更します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

Tomcat を起動した後、test, gebTest タスクを実行して問題ないことを確認します(画面キャプチャは省略します)。この後もテストを繰り返すので Tomcat は起動したままにします。

geckodriver.exe の場所は System.setProperty("webdriver.gecko.driver", "...") で指定する

前回の記事では geckordriver.exe を build.gradle の gebTest タスクと、IntelliJ IDEA の「Run」-「Edit Configurations...」メニューで開いた先の JUnit の設定のところに記述しましたが、GebConfig.groovy に System.setProperty("webdriver.gecko.driver", "...") で定義すれば利用可能になることが分かったので、設定を変更します。

src/test/resources/GebConfig.groovy を以下のように変更します。

import org.openqa.selenium.firefox.FirefoxDriver

driver = {
    System.setProperty("webdriver.gecko.driver", "C:/geckodriver/0.19.0/geckodriver.exe")
    new FirefoxDriver()
}
baseUrl = "http://localhost:8080"
waiting {
    timeout = 15
}

build.gradle の gebTest を以下のように変更します。

test {
    jvmArgs = ['-Dspring.profiles.active=unittest']
    exclude "geb/**"
}

task gebTest(type: Test) {
    jvmArgs = ['-Dspring.profiles.active=unittest']
    exclude "ksbysample/**"
}
  • jvmArgs の指定を '-Dwebdriver.gecko.driver=C:/geckodriver/0.19.0/geckodriver.exe' を削除して test タスクと同じにします。

IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurations」ダイアログを表示した後、「JUnit」の「VM Options」から -Dwebdriver.gecko.driver=C:/geckodriver/0.19.0/geckodriver.exe を削除します。

f:id:ksby:20171028080101p:plain

動作確認します。

最初に clean タスク → Rebuild Project → build タスクを実行して BUILD SUCCESSFUL のメッセージが出力されることを確認します(画面キャプチャは省略します)。

次に gebTest タスクを実行すると Firefox が起動し、テストは成功します。

f:id:ksby:20171028080601p:plain

最後に src/test/groovy/geb/gebspec/SimpleTestSpec.groovy を開き、左側のアイコンから Run '動作確認用()' を選択します。

f:id:ksby:20171028081146p:plain

こちらも Firefox が起動し、テストは成功します。

f:id:ksby:20171028082241p:plain

Firefox headless モードを使用する

Firefox に headless モードが実装されているらしいので、そちらを使うように設定してみます。

GebConfig.groovy を変更する

src/test/resources/GebConfig.groovy を以下のように変更します。

import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.firefox.FirefoxOptions

driver = {
    System.setProperty("webdriver.gecko.driver", "C:/geckodriver/0.19.0/geckodriver.exe")
    FirefoxOptions firefoxOptions = new FirefoxOptions()
    firefoxOptions.setHeadless(true)
    new FirefoxDriver(firefoxOptions)
}
baseUrl = "http://localhost:8080"
waiting {
    timeout = 15
}
  • 以下の2行を追加します。
    • FirefoxOptions firefoxOptions = new FirefoxOptions()
    • firefoxOptions.setHeadless(true)
  • new FirefoxDriver() の引数に firefoxOptions を追加します。

動作確認

動作確認します。

最初に clean タスク → Rebuild Project → build タスクを実行して BUILD SUCCESSFUL のメッセージが出力されることを確認します(画面キャプチャは省略します)。

次に gebTest タスクを実行してみると Firefox の画面は表示されず、テストは成功します。ただし DEBUG ログが大量に出ます。。。 これは出来れば出ないようにしたいですね。

f:id:ksby:20171028124025p:plain

DEBUG メッセージに対応する前に、うまく動いているのかちょっと分かりづらいので、テストの内容を少し変更してみます。

src/test/groovy/geb/page/inquiry/InquiryInput01Page.groovy を以下のように変更します。

package geb.page.inquiry

import geb.Page

class InquiryInput01Page extends Page {

    static url = "/inquiry/input/01"
    static at = { title == "入力フォーム - 入力画面1" }
    static content = {
        btnNext { $(".js-btn-next") }
    }

}
  • static content { ... } を追加し、その中に btnNext { $(".js-btn-next") } を記述します。

src/test/groovy/geb/gebspec/SimpleTestSpec.groovy を以下のように変更し、最後でテストが失敗するようにします。

package geb.gebspec

import geb.page.inquiry.InquiryInput01Page
import geb.spock.GebSpec

class SimpleTestSpec extends GebSpec {

    def "動作確認用"() {
        given: "入力画面1へアクセスする"
        to InquiryInput01Page
        waitFor { at InquiryInput01Page }
        $("#form-group-name .js-errmsg").displayed == false

        when: "何も入力せずに「次へ」ボタンをクリックする"
        btnNext.click(InquiryInput01Page)

        then: "「お名前(漢字)」の必須チェックエラーのメッセージが表示される"
        $("#form-group-name .js-errmsg").displayed == true
//        $("#form-group-name .js-errmsg").text() == "お名前(漢字)を入力してください"
        $("#form-group-name .js-errmsg").text() == "エラーです"
    }

}

gebTest タスクを実行してみると今度はエラーになりました。Spock はエラーメッセージが分かりやすくていいですね。

f:id:ksby:20171028125357p:plain

src/test/groovy/geb/gebspec/SimpleTestSpec.groovy を以下のように変更し、今度は成功するようにします。

package geb.gebspec

import geb.page.inquiry.InquiryInput01Page
import geb.spock.GebSpec

class SimpleTestSpec extends GebSpec {

    def "動作確認用"() {
        given: "入力画面1へアクセスする"
        to InquiryInput01Page
        waitFor { at InquiryInput01Page }
        $("#form-group-name .js-errmsg").displayed == false

        when: "何も入力せずに「次へ」ボタンをクリックする"
        btnNext.click(InquiryInput01Page)

        then: "「お名前(漢字)」の必須チェックエラーのメッセージが表示される"
        $("#form-group-name .js-errmsg").displayed == true
        $("#form-group-name .js-errmsg").text() == "お名前(漢字)を入力してください"
//        $("#form-group-name .js-errmsg").text() == "エラーです"
    }

}

gebTest タスクを実行してみると今度は成功します。headless モードでも問題なく動いているようです。

f:id:ksby:20171028130043p:plain

大量に出力される DEBUG ログ [Forwarding ... on session ... to remote] DEBUG org.apache.http を抑制する

何か情報がないか Google で検索すると、stackoverflow の以下の QA を見つけました。

これを読むと logback.xml があれば抑制できるようです。

src/test/resources の下に logback.xml を新規作成し、以下の内容を記述します。色々試してみたところ <logger .../> の定義がなくても logback.xml があれば DEBUG ログを抑制できるようです。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        Geb+Spock で Firefox を headless モードで動かした時に大量に以下の DEBUG ログ
        が出力されるのを抑制するために作成しているファイルである。ファイルがあればログを抑
        制できるので、特に logger の定義は記述していない。

        [Forwarding ... on session ... to remote] DEBUG org.apache.http
     -->
</configuration>

gebTest タスクを実行してみると [Forwarding ... on session ... to remote] DEBUG org.apache.http の DEBUG メッセージは出力されました。ただし、まだ Marionette DEBUG というログが出力されています。

f:id:ksby:20171028133928p:plain

Marionette DEBUG のログを抑制する

Google で検索していろいろ見たのですが、stackoverflow の以下の QA を見て解決しました。

geckodriver.exe を配置した C:\geckodriver\0.19.0 の下に geckodriver.bat を新規作成し、以下の内容を記述します。

@echo off
C:\geckodriver\0.19.0\geckodriver.exe --log info %* > NUL 2>&1

src/test/groovy/GebConfig.groovy を以下のように変更します。

import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.firefox.FirefoxOptions

driver = {
    System.setProperty("webdriver.gecko.driver", "C:/geckodriver/0.19.0/geckodriver.bat")
    FirefoxOptions firefoxOptions = new FirefoxOptions()
    firefoxOptions.setHeadless(true)
    new FirefoxDriver(firefoxOptions)
}
baseUrl = "http://localhost:8080"
waiting {
    timeout = 15
}
  • System.setProperty("webdriver.gecko.driver", "...") の第2引数に設定していたプログラム名を C:/geckodriver/0.19.0/geckodriver.exeC:/geckodriver/0.19.0/geckodriver.bat に変更します。

gebTest タスクを実行すると Marionette DEBUG のログは全く出力されなくなりました。

f:id:ksby:20171028143338p:plain

履歴

2017/10/28
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その29 )( Geb をインストールする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その28 )( Spring Boot を 1.5.4 → 1.5.7 へ、error-prone を 2.0.15 → 2.1.1 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Geb + Spock でテストを作成するために、まずは Geb をインストールして使えるようにします。

参照したサイト・書籍

  1. Geb - Very Groovy Browser Automation
    http://www.gebish.org/

  2. Web画面自動テストフレームワークGeb」の紹介
    http://lab.astamuse.co.jp/entry/geb_test_01

  3. Geb with spock
    https://www.slideshare.net/MonikaGurram/geb-with-spock-24710923

  4. mozilla/geckodriver
    https://github.com/mozilla/geckodriver

  5. Gradle – How to exclude some tests
    https://www.mkyong.com/gradle/gradle-how-to-exclude-some-tests/

  6. Getting Started With Gradle: Integration Testing
    https://www.petrikainulainen.net/programming/gradle/getting-started-with-gradle-integration-testing/

  7. Spring Boot and Gradle: Separating tests
    https://moelholm.com/2016/10/22/spring-boot-separating-tests/

目次

  1. 方針
  2. Geb に必要なモジュールをインストールする
  3. GebConfig.groovy を作成する
  4. テスト用のパッケージを作成する
  5. 入力画面1の Page Objects を作成する
  6. 簡単なテストを作成する
  7. geckodriver をインストールする
  8. gebTest タスクを作成する
  9. 動作確認
  10. IntelliJ IDEA 上からテストを実行できるようにする

手順

方針

  • テストは Geb + Spock の組み合わせで作成します。geb-core ではなく geb-spock を指定してインストールします。
  • ブラウザは Firefox を利用します。今回テストを作成している時にインストールしている Firefox のバージョンは 56.0.2 (64ビット) です。
  • Geb は現時点で最新の 2.0-rc-1、selenium は 3.6.0 をインストールします。
  • Geb のテストは src/test/groovy/geb の下に作成します。src の下に main, test とは別にディレクトリは作成しません。
src/test/groovy
├ geb
│ ├ gebspec
│ └ page
└ ksbysample
   └ webapp
      └ bootnpmgeb
  • gradle の test タスクでは Geb のテストは実行されないようにし、Geb のテストだけを実行する gebTest タスクを作成します。

Geb に必要なモジュールをインストールする

Geb + Spock でテストを作成するために必要なモジュールをインストールします。1.5. Installation & usage を参考に、build.gradle の dependencies に以下の3行を追加します。

dependencies {
    ..........

    // for Geb + Spock
    testCompile("org.gebish:geb-spock:2.0-rc-1")
    testCompile("org.seleniumhq.selenium:selenium-firefox-driver:3.6.0")
    testCompile("org.seleniumhq.selenium:selenium-support:3.6.0")
}

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

GebConfig.groovy を作成する

共通の設定を記述するための GebConfig.groovy を src/test/resources の下に作成します。ファイルを作成した後、7. Configuration を参考に以下のように記述します。

driver = "firefox"
baseUrl = "http://localhost:8080"
waiting {
    timeout = 15
}

テスト用のパッケージを作成する

src/test/groovy の下に geb パッケージを作成し、geb の下に gebspec, page パッケージを作成します。

f:id:ksby:20171027005122p:plain

入力画面1の Page Objects を作成する

src/test/groovy/geb/page の下に inquiry パッケージを作成した後、inquiry の下に InquiryInput01Page.groovy を作成して以下の内容を記述します。

package geb.page.inquiry

import geb.Page

class InquiryInput01Page extends Page {

    static url = "/inquiry/input/01"
    static at = { title == "入力フォーム - 入力画面1" }

}

簡単なテストを作成する

動作確認のために簡単なテストを作成します。src/test/groovy/geb/gebspec の下に SimpleTestSpec.groovy を作成して以下の内容を記述します。

package geb.gebspec

import geb.page.inquiry.InquiryInput01Page
import geb.spock.GebSpec

class SimpleTestSpec extends GebSpec {

    def "動作確認用"() {
        expect: "入力画面1へアクセスする"
        to InquiryInput01Page
        waitFor { at InquiryInput01Page }
    }

}

geckodriver をインストールする

Web画面自動テストフレームワーク「Geb」の紹介 の記事によると Firefox の 47.0以降 から geckodriver を gradle でのダウンロードとは別にインストールする必要があるとのことですので、ダウンロード&インストールします。

https://github.com/mozilla/geckodriver/releases を見ると最新バージョンは v0.19.0 です。下にある geckodriver-v0.19.0-win64.zip のリンクをクリックしてダウンロードします。

ダウンロードした geckodriver-v0.19.0-win64.zip を解凍すると geckodriver.exe が出来ますので、C:\geckodriver\0.19.0 の下に配置します。

f:id:ksby:20171027011630p:plain

gebTest タスクを作成する

build.gradle に gebTest タスクを追加します。また test タスクで ksbysample パッケージ配下のテストが実行されないようにします。

test {
    jvmArgs = ['-Dspring.profiles.active=unittest']
    exclude "geb/**"
}

task gebTest(type: Test) {
    jvmArgs = [
            '-Dspring.profiles.active=unittest'
            , '-Dwebdriver.gecko.driver=C:/geckodriver/0.19.0/geckodriver.exe'
    ]
    exclude "ksbysample/**"
}
  • test タスクに exclude "geb/**" を追加します。
  • gebTest タスクを追加します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして反映します。

動作確認

Tomcat を起動した後、gebTest タスクを実行してみます。。。が、java.lang.ClassNotFoundException: org.openqa.selenium.MutableCapabilities というエラーメッセージが出て失敗しました。

f:id:ksby:20171027062307p:plain

モジュールを見てみると selenium 関連で4つ表示されているのですが、selenium-apiselenium-remote-driver が 2.53.1 と古いバージョンになっています。全て 3.6.0 になるよう調整します。

f:id:ksby:20171027062538p:plain

build.gradle を以下のように変更します。

dependencies {
    ..........

    // for Geb + Spock
    testCompile("org.gebish:geb-spock:2.0-rc-1")
    testCompile("org.seleniumhq.selenium:selenium-firefox-driver:3.6.0")
    testCompile("org.seleniumhq.selenium:selenium-support:3.6.0")
    testCompile("org.seleniumhq.selenium:selenium-api:3.6.0")
    testCompile("org.seleniumhq.selenium:selenium-remote-driver:3.6.0")
}
  • 以下の2行を追加します。
    • testCompile("org.seleniumhq.selenium:selenium-api:3.6.0")
    • testCompile("org.seleniumhq.selenium:selenium-remote-driver:3.6.0")

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると、今度は全て 3.6.0 になりました。

f:id:ksby:20171027063123p:plain

再度 getTest タスクを実行すると、今度は Firefox が起動して入力画面1にアクセスし、テストが成功しました。

f:id:ksby:20171027063323p:plain

Tomcat を停止した後、clean タスク → Rebuild Project → build タスクを実行すると BUILD SUCCESSFUL のメッセージが出力されました。test タスク中に Firefox は起動しなかったので、Geb で作成したテストは実行されませんでした。

f:id:ksby:20171027063830p:plain

IntelliJ IDEA 上からテストを実行できるようにする

今の設定だけでは IntelliJ IDEA のエディタの左側のメニューから Run 動作確認用() を選択してもテストを実行できません。

f:id:ksby:20171027065029p:plain

Caused by: java.lang.IllegalStateException: The path to the driver executable must be set by the webdriver.gecko.driver system property; for more information, see https://github.com/mozilla/geckodriver. The latest version can be downloaded from https://github.com/mozilla/geckodriver/releases というエラーメッセージが出力されます。

f:id:ksby:20171027065225p:plain

IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurations」ダイアログを表示した後、「JUnit」の「VM Options」に -Dwebdriver.gecko.driver=C:/geckodriver/0.19.0/geckodriver.exe を追加します。

f:id:ksby:20171027065606p:plain

再度 Run 動作確認用() を選択してテストを実行すると Firefox が起動して入力画面1にアクセスし、テストが成功しました。

f:id:ksby:20171027065818p:plain

履歴

2017/10/27
初版発行。