E2Eテスト -マークアップチェック編 実装方法-

前回の記事で`clientScripts`メソッドについて説明したので具体的な実装方法について書いていきます。
(一緒にE2E実装を進めてくれた@rainymoment0616さんThanksです:clap:)


##大まかな実装の流れ
①チェックしたい箇所が視覚的に分かるようにテスト用CSSファイルを作成する
②Testcafeの`clientScripts`メソッドを利用して作成したCSSを挿入し、チェックしたい要素にスタイルが当たった状態のスクリーンショットを撮影

かなりざっくりですが、流れはこんな感じです。

それでは、いきます！


##CSSを用意する
まずはテスト用のCSSを作成します。

- 任意の場所にファイルを置きます。

`e2e/stylus/e2e-markup.styl`：リスト系の子要素に誤った要素が入ってないか／h1があるかどうかチェックする用
`e2e/stylus/e2e-img.styl`：alt属性・width属性・height属性が含まれているかチェックする用
`e2e/stylus/e2e-link.styl`：blankにrel="noopener"が付与されてるか／javascript:void(0)が使われてないかチェックする用

今回は用途に合わせて3種類のCSSを作成しました。
チェック項目がたくさんあると見た目が煩雑になって確認しづらくなったり、imgだけ〜linkだけ〜チェックしたい時のために分けています。
CSSはこんな感じ↓

```e2e-markup.styl
// variables
// ------------------------- //
$testcafe-error=#dc143c
$testcafe-error_=rgba(220, 20, 60, .3)
$testcafe-warn=#ffd700
$testcafe-warn_=rgba(255, 215, 0, .3)

// mixins
// ------------------------- //
$grad(color, deg)
  background-size: 20px 20px
  background-image: linear-gradient(deg, transparent, transparent 25%, color 25%, color 50%,	transparent 50%, transparent 75%, color 75%,color)
  background-repeat: initial

h1
  outline: 2px solid #66cdaa
  background: rgba(102, 205, 170, .3)

// 子要素で利用されているもののチェック
ol
  & > *:not(li)
    $grad($testcafe-error, 45deg)
    &:before
      display: block
      content: '【NG】olタグの子要素はliのみだよ！'
      background-color: $testcafe-error
      color: #fff
ul
  & > *:not(li)
    $grad($testcafe-error, 45deg)
    &:before
      display: block
      content: '【NG】ulタグの子要素はliのみだよ！'
      background-color: $testcafe-error
      color: #fff
dl
  & > *:not(dt):not(dd):not(div)
    $grad($testcafe-error, 45deg)
    &:before
      display: block
      content: '【NG】dlタグの子要素はdt, dd, divのみだよ！'
      background-color: $testcafe-error
      color: #fff

// TODO: あるorなしチェック
// 「存在してたら」はできるけど、「なかったら」は厳しそう
hgroup
  outline: 2px solid $testcafe-warn
  background-color: $testcafe-warn_
  &:before
    display: block
    content: '【注意】一部廃止されてるのでhgroupの使用は非推奨'
    background-color: $testcafe-warn

font,
[size]
[color]
  outline: 2px solid $testcafe-error
  background: $testcafe-error_
  &:before
    display: block
    content: '【非推奨】font要素、size属性／color属性は、HTML5で廃止されたよ'
    background-color: $testcafe-error
    color: #fff

time:not([datetime])
  outline: 2px solid $testcafe-warn
  background: $testcafe-warn_
  &:before
    display: block
    content: '【注意】datetime属性ついてないけど大丈夫？'
    background-color: $testcafe-warn

```

stylusやSassなどで書いていく場合はgulpやwebpackでcssにコンパイルしてください。
【gulpを使う場合の例】↓

```stylus.js
const stylusTask = () => {
  return gulp
      .src('e2e/stylus/e2e-*.styl')
      .pipe(stylus())
      .pipe(pleeease({
        stylus: true,
        minifier: true,
        autoprefixer: {
          "browsers": ["last 4 versions"]
        },
        mqpacker: true
      }))
      .pipe(csscomb()).on('error', console.error.bind(console))
      .pipe(cssmin())
      .pipe(gulp.dest('static/testcafe'));
}
exports.stylusTask = stylusTask
```

**Nuxt.js環境で実装しているのでCSSの生成先は`static`にしています**


##処理を書いていく
cssが用意できたら実行するための処理をJavascriptでザクザク書いていきます。

`e2e/detail/markup.js`

```markup.js
import { Selector, ClientFunction } from 'testcafe'
const config = require('../config')
const { testUrl } = config;

let scriptContent = `
  var headEls = document.getElementsByTagName("head");
  var headEl = headEls.item(0);
  var styleSheetEl = document.createElement('link');
  var cssType = document.createAttribute('rel');
  var cssHref = document.createAttribute('href');
  cssType.nodeValue = 'stylesheet';
  cssHref.nodeValue = '/testcafe/e2e-markup.css';
  styleSheetEl.setAttributeNode(cssType);
  styleSheetEl.setAttributeNode(cssHref);
  headEl.appendChild(styleSheetEl);
`;


// TODO: 読み込むCSSの制御
switch (process.argv[3]) {
  default:
  case 'markup':
    break
  case 'img':
    scriptContent = `
      var headEls = document.getElementsByTagName("head");
      var headEl = headEls.item(0);
      var styleSheetEl = document.createElement('link');
      var cssType = document.createAttribute('rel');
      var cssHref = document.createAttribute('href');
      cssType.nodeValue = 'stylesheet';
      cssHref.nodeValue = '/testcafe/e2e-img.css';
      styleSheetEl.setAttributeNode(cssType);
      styleSheetEl.setAttributeNode(cssHref);
      headEl.appendChild(styleSheetEl);
    `
    break
  case 'link':
    scriptContent = `
      var headEls = document.getElementsByTagName("head");
      var headEl = headEls.item(0);
      var styleSheetEl = document.createElement('link');
      var cssType = document.createAttribute('rel');
      var cssHref = document.createAttribute('href');
      cssType.nodeValue = 'stylesheet';
      cssHref.nodeValue = '/testcafe/e2e-link.css';
      styleSheetEl.setAttributeNode(cssType);
      styleSheetEl.setAttributeNode(cssHref);
      headEl.appendChild(styleSheetEl);
    `
    break
}


const takeCapture = (testName, testUrl, time) => {
  fixture(testName)
    .page(testUrl)
    .clientScripts({
      content: scriptContent
   });
  test
    ('マークアップチェックスクショ', async t => {
      // ↓デバッグモードにする時
      // await t.debug()
      await t.wait(time * 4)
     
      const type = !!process.argv[3] ? process.argv[3] : 'basic'

      await t
        .takeScreenshot({
          path: `markup/${type}/${testName}.png`,
           fullPage: true,
        })
    })
}

const urlList = [
  {
    'testName': 'top',
    'url': `${testUrl}`
  },
  {
    'testName': 'about',
    'url': `${testUrl}/about`
  }
]

urlList.forEach(obj => {
  const delay = !!obj.time ? obj.time : 1000
  takeCapture(obj.testName, obj.url, delay)
})

```


####`process.argv[i]`
チェック項目ごとにcssを分けているので、**コマンドライン引数**を取得し、読み込むcssを制御しています。

```package.json
"scripts": {
    "markup": "testcafe e2e/detail/markup.js",
    "markup_img": "testcafe e2e/detail/markup.js 'img'",
    "markup_link": "testcafe e2e/detail/markup.js 'link'"
  }
```

package.jsonのscriptsにこのように記述しているため、imgとlinkのコマンドを実行する時の引数は`3`になります。

コマンドライン引数を取得するには

```
for (var i = 0; i <= process.argv.length; i++) {
  console.log("argv[" + i + "] = " + process.argv[i]);
}
```
これで引数を調べることができるので環境に合わせて変更してください。

チェック用CSSを読み込んでみるとこのようになります。
左から

- マークアップチェック用CSS
- imgチェック用CSS
- linkチェック用CSS

赤や黄色、ボーダーで目立つような色使いと:before/:after擬似要素で注意書きテキストが表示されるようになっているので分かりやすくなっていると思います!

![markup.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174180/2d0f1474-c9ce-6e87-651e-3fe793405ea2.jpeg)

###おまけ
Nuxtで、サンプル用ページ`pages/ignore/markup.vue`を作成しました。
このままだとルーティング生成されてしまうのでignoreでルーティング除外してください。

```.nuxtignore
# ignoreフォルダにあるページを無視する
pages/ignore/*.vue
```

その他のignoreプロパティは[公式](https://ja.nuxtjs.org/docs/2.x/configuration-glossary/configuration-ignore/)をご確認ください。

以上、E2Eテスト -マークアップチェック編- でした！