Help us understand the problem. What is going on with this article?

【Cypress】~7選~ CypressでE2Eテストするときはまずこれ読んどけって話

この記事はAteam Hikkoshi Samurai Inc. & Ateam Connect Inc.(エイチーム引越し侍、エイチームコネクト) Advent Calendar 2019 24日目の記事です。

本日はエイチーム引越し侍中途入社3年目、自称爆速Webエンジニアの加藤が担当します!

みなさんE2Eテスト書いてますか??
引越し侍では10年以上続くレガシーシステムにテストが充実していないというカオスな現場があります。

とりあえずこのプロジェクトでリリースしたらここのCVチェックをしようという
力技で確認し続ける日々…

くそぅ!新規サービスではテストを書いているのに!
レガシーシステムにはテストも入れられないのか!

E2Eテスト導入への障壁

一度うちのエンジニアの神様がE2Eを導入しかけたんですが、
ABテストとかに対応するのめんどくさいとのことでなかなか拡充していかなかったんですよね
その姿を見た僕は「めんどくさいんだろうなぁ」と指をくわえて見てましたが、
中途3年目、気づけば社内エンジニア社歴はTOP7に、神セブンってやつですよ。
しかも僕より上の人たちはマネージメントだったり日々の業務に追われて中々動き出せなさそう。

思い立つ

このテスト文化がなじんできた今声を大にして言うしかない!

唐突な目次

ほぼこれで行ける
js 呼び出せない…
ラベル貼っている要素へのアクセス
ビデオ録画やめる
jsエラーに敏感
IP問題
フロントエンジニアとの協力体制が必要

とりあえず書いてみた

ほぼこれでいける

*.spec.js
cy.visit(url) // urlにアクセス
cy.get('セレクタ') // html上の要素を取得
cy.get('セレクタ').click() // 要素をクリック
cy.get('セレクタ').type('文字列') // 要素に文字列を挿入(テキストボックスとか)
cy.get('セレクタ').select('文字列') // セレクトボックスの中身を選択(valueでもテキスト部でもどちらでも指定可)
cy.wait(msec) // 指定したミリ秒待つ
cy.go('back') // ブラウザバック
cy.go('forward') // 進む

まだ始めたばっかでわかんないけどこれで大体いけんじゃね?
※これもいるよ!ってコメントで教えてもらったら随時追加していきます!

js呼び出せない

Cypress入れようと思った時にNode.jsなんだからそのページで使っているjsとかまで使えんじゃね?
便利ー!と思ってたんですけど呼び出せない…そんな関数ないよってシンプルに怒られます。
まだ読み込み終わってないんかなとwaitしてもダメ。
だれかやれる方法あったらコメントで教えてください。
速攻で記事修正します。

ラベル貼っている要素へのアクセス

なんかラベルを張られている要素にアクセスしようとすると怒られる模様

CypressError: Timed out retrying: cy.type() failed because this element:

<input type="text" name="request[current_zip_code]" value="" id="request_current_zip_code">

is being covered by another element:

<label for="request_current_zip_code">例)1500001</label>

そんなときはforceを入れる

cy.select('文字列', {force: true})
cy.type('文字列', {force: true})

ビデオ録画やめる

なんかテストのたびに動画撮ってる
こっちは勉強中なんだよ!さっさと何回も回したいんだよ!と思ったら
cypress.jsonに下記を追加

cypress.json
 "video": false

jsエラーに敏感

1例ですが、formタグに使っていなく、宣言自体も読み込まれなくなった関数がありまして、
ボタンにonclick属性で指定されている関数でsubmitしている感じだったんですが、
chromeだと遷移できるのに、Cypressちゃんはエラーで停止しました。

# 使っていないsubmitFuncまで発火しエラーにある
<form name="form1" enctype="multipart/form-data" method="get" action="/request/base"  onsubmit="submitFunc(this)">

# onclick
<a onClick="submitForm(document.form1); return false;">

IP問題

弊社ではテストのために、社内IPからのアクセスの時にはテストデータとする処理があります。
本番でテストするなら上記の方法以外でテストデータとする処理を何かしらいれないとなぁ…

フロントエンジニアとの協力体制が必要

<a class="btn-entry" onClick="submitForm(document.form1); return false;">

このリンクをクリックしたいけどclassで指定したくない名前だな…
id追加してとりあえず対応するけど、このid使ってなくね?消しちゃえってなるとやだし、
わかりやすいタグつけたほうがいいだろうなぁ

<a id="cypress_submit_button_1 " class="btn-entry" onClick="submitForm(document.form1); return false;">

こんな感じだとわかるかなぁ
要相談

とりあえず書いてみた

describe('PC 一括CV', () => {
  context('正常な依頼', () => {
    it('正常にCVできること',() => {
      const samurai_top = 'https://hikkoshizamurai.jp.test.hoge.com/'
      const user = {
        currentZipCode: '1010051',
        currentStreetName: '2-11',
        currentBuildingType: 'マンション',
        newZipCode: '1010051',
        lastName: '加藤' + Math.random().toString(32).substring(7),
        firstName: '太郎',
        lastNameKana: 'カトウ',
        firstNameKana: 'タロウ',
        tel: '090' + Math.floor(Math.random() * 100000000), // 重複データにならないようにランダム文字列生成
        email: 'kato.shogo+' + Math.random().toString(32).substring(7) + '@a-tm.co.jp' // 重複データにならないようにランダム文字列生成
      }
      cy.log("トップにアクセスします")
      cy.visit(samurai_top)
      cy.log("フォームに遷移します")
      cy.get('#mainForm-move-entry')
        .click()

      // base
      cy.log("住所情報を入力します")
      cy.get('#request_current_zip_code')
        .type(user.currentZipCode, {force: true})
      cy.get('#request_current_street_name')
        .type(user.currentStreetName, {force: true})
      cy.get('#request_current_building_type')
        .select(user.currentBuildingType, {force: true})
      cy.get('#request_new_zip_code')
        .type(user.newZipCode, {force: true})
      cy.wait(2000)
      cy.get('input[name="submit"]')
        .click()

      // personal
      cy.log("個人情報を入力します")
      cy.get('#request_user_name_last_name')
        .type(user.lastName, {force: true})
      cy.get('#request_user_name_first_name')
        .type(user.firstName, {force: true})
      cy.get('#request_user_name_kana_last_name_kana')
        .type(user.lastNameKana, {force: true})
      cy.get('#request_user_name_kana_first_name_kana')
        .type(user.firstNameKana, {force: true})
      cy.get('#request_whole_tel_no')
        .type(user.tel, {force: true})
      cy.get('#request_email_address')
        .type(user.email, {force: true})
      cy.get('#request_advertise_type_2')
        .click()

      // packages
      cy.log("荷物情報を入力します")
      cy.get('input[name="submit"]')
        .click()

      // confirm
      cy.log("確認画面です")
      cy.get('input[name="submit"]')
        .click()
    })
  })
})

CVできたぜ

いかがでしたでしょうか。
CypressでE2Eテスト書く時の初動になれればと思います。

Ateam Hikkoshi Samurai Inc. & Ateam Connect Inc.(エイチーム引越し侍、エイチームコネクト) Advent Calendar 2019
明日は@zwirky が年末にバグバッシュを技術開発部のメンバー全員でやったことを記事にしてくれます!
お楽しみに!

追伸

株式会社エイチーム引越し侍では、一緒にサイト改善をしてくれるWebエンジニアを募集しています。エイチームグループのエンジニアとして働きたい!という方は是非、以下のリンクから応募してください。
皆様からのご応募、お待ちしております!!

エイチームグループ採用サイト(Web開発エンジニア職)

shgkt
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした