Netscape フォーマットの Cookie File とは

結論

以下のようなテキストファイルです。なお、空白部分は全てタブです。

# Netscape HTTP Cookie File
# This file is generated by yt-dlp.  Do not edit.

.foo.example.com    TRUE    /   FALSE   1647672197  barbarfoobar    1647067397

ファイルを取得するには

Cookie を何らかの形で書き出して上記の形に整形する、という方法も無くはないですが、拡張機能を使うのが楽でしょう。

Chrome (Edge)

chrome.google.com

Firefox

addons.mozilla.org

Cypress で console.log 的にターミナルにプリントデバッグしたいとき

結論

task という機能(コマンド)を使います。公式ドキュメントのとおりです。

docs.cypress.io

cypress/plugins/index.js に上記の内容をコピペします。

プリントデバッグしたい箇所で、

cy.task('log', 'This will be output to the terminal')

のように書きます。

補足

ログの場所はそこそこ見つけづらいので、

  on('task', {
    log(message) {
      console.log(`[cy.log] ${message}`)

      return null
    },
  })

のように装飾してもいいかもしれません。

さらに補足

cy.log('hoge') と書きたい場合には cypress/support/index.js に以下のように書き加えます。

Cypress.Commands.overwrite('log', (subject, message) => cy.task('log', message))

ただしこれは、全ての cy.log に影響を与えます。また、TypeScript の場合は型定義に注意する必要があります。

Cypress でよく使うメソッド

詳細は全てリンク先の公式ドキュメントに書いてあるので、そちらを見るのが確実です。複数の引数を取る場合にいろいろと柔軟な挙動を実現できたりします。

よく使うメソッド

should

マッチャ(を第一引数に取る)用のメソッドです。

describe('Foo', () => {
  it('Bar', () => {
    cy.visit('/')

    cy.url().should('equal', 'http://localhost:3100/')
  })
})

第一引数に取るマッチャ(Chainers)の値は例えば以下のような値です。

  • equal
  • not.equal
  • contain
  • not.contain
  • be.visible
  • have.css
  • not.have.value
  • have.class
  • have.id
  • have.attr

docs.cypress.io

get

要素を取得するためにまずはこれです。

describe('Foo', () => {
  it('Bar', () => {
    cy.visit('/')

    cy.get('title').should('have.text', 'タイトルタグの内容')
  })
})

docs.cypress.io

find

要素を取得します。

get との違いは、get が常に root から要素をたどる一方で、find はレシーバを起点に要素をたどる点です。

そのため cy.find のように、cy をレシーバにすることはできません*1

cy.get('#parent').find('li')

docs.cypress.io

contains

要素を取得します。findと同等ですが、正規表現が使えます。また、cy をレシーバに取ることができます。

正規表現を用いない場合は「その文字列が含まれる」という判定基準で要素を取得します*2。半角スペースの扱いなどで注意点があるので、公式ドキュメントの一読を推奨します。

cy.contains('ul', /^b\w+/)

docs.cypress.io

then

Promise の then ほぼそのまんまです。getfind で要素を絞り込み、その要素に対して何かしたい、というときに使います。

たとえば、cy.get('div') の 7つ目 の要素をテストしたい場合、以下のように変数に格納してからテストすることはできません。

const divElementsSeventh = cy.get('div')[6]

なので、then でチェーンしてテストします。

一つ注意する点は、コールバック関数内で Cypress のコマンドを適用させるためには wrap でくるむ必要が有ることです*3

describe('Foo', () => {
  it('Bar', () => {
    cy.visit('/')

    cy.get('div').then(($divElement) => {
      cy.wrap($divElement.eq(6)).should('have.text', 'Hello, World!')
    })
  })
})

その他のメソッドについて

公式ドキュメントを一日かけて読み通すと良いと思います*4。急がば回れです。

docs.cypress.io

Chainer については Common Assertions をとにかく読み込む

この Common Assertions を何度も読みながら、実際に書いてみて値を変えてみたりするのが一番初期導入が速そうです。

docs.cypress.io

FAQ のドキュメントもおすすめ

FAQ のドキュメントは一読しておくと Tips が得られますのでこれも数時間費やして読んだほうが良いです。

docs.cypress.io

jQuery との関係

jQuery でラップするコマンドがあるので*5、ラップしたオブジェクトに対しては jQuery のメソッドである text() などを用いることができます。さらに chai の expect などを用いることもできます*6

Cypress の世界に引き戻すためには wrap してやる必要があります。

*1:cy.root().find とすれば find を使えるが、それならば get を使うよねという話

*2:完全一致ではない

*3:ここはハマりそう。なので、分かりやすくするため、引数名には "$" を先頭につけるのが慣習っぽい

*4:Commands だけでなく Assertions とかも

*5:Cypress.$ など

*6:cy.log で出力して確認するときに使うデバッグ用という感じがしてます

Cypress にて、ローカルサーバを起動したうえで実行する設定 (scripts) を package.json に書く

結論

以下のようなプロパティを package.json の scripts に書けば良いです。

(ここまで省略)

  "scripts": {
    "cypress:exec": "kill $(lsof -i :3100 | grep node | awk '{print $2}') & next build && next start --port 3100 & wait-on -t 30000 http-get://localhost:3100 && cypress run --browser chrome && kill $(lsof -i :3100 | grep node | awk '{print $2}')",

(以下略)

具体的には

具体的には以下のコマンドが必要です。

  • サーバを起動するコマンド
  • サーバを終了させる(プロセスを kill する)コマンド
  • サーバの起動を待つコマンド
  • Cypress を実行するコマンド

それぞれのコマンドの詳細を見ます。

1. サーバを起動するコマンド

たとえば以下のようなコマンドになります。

$ next build && next start --port 3100

ポート番号は開発用番号とは異なる方が良いでしょう。開発しながらテストを回すことができるためです。

開発環境でのサーバを起動するのか(上記でいえば $ next dev にするのか)どうかというのは個々のケース次第だと思います。prod用の環境変数を使いたい場合やそうでない場合、またバックエンドとの関係など、いろいろなケースがあると思いますので。

2. サーバを終了させる(プロセスを kill する)コマンド

プロセスを一意に選択できて kill できるならばどのようなコマンドでも良いです。たとえば以下のようになります。

$ kill $(lsof -i :3100 | grep node | awk '{print $2}')

3. サーバの起動を待つコマンド

サーバの起動コマンドを実行してからサーバが起動するまでの間に Cypress が走ってしまうと Not found になってしまいます。そのため、サーバの起動を待つコマンドを間に挟みます。

Cypress の公式では、wait-on を使う方法や start-server-and-test を使う方法が紹介されています。自分は最終コミット日時等もふまえて wait-on を使うことにしました*1

$ wait-on -t 30000 http-get://localhost:3100

上記の -t オプションの数字の単位はミリ秒なので注意が必要です。

4. Cypress を実行するコマンド

Cypress を実行するコマンドが必要になります。

$ cypress run --browser chrome

コマンド連結実行時の注意

コマンドを連結して実行する際、以下のコマンドは正常終了を待たずに次のコマンドを実行する必要があります。つまり & でコマンドをつなげます。

  • 最初に実行させる「サーバを終了させるコマンド」
  • 「サーバを起動するコマンド」

それぞれについて見ます。

最初に実行させる「サーバを終了させるコマンド」

最初に実行させる「サーバを終了させるコマンド」は、サーバが起動していないときにはエラーになってしまうため、正常終了を待ってしまうと次のコマンドが実行されないことがあります*2

最初にこのコマンドを実行する理由は「以前のテストでサーバプロセスが残っていた場合にそれを冪等に終わらせるため」です。サーバプロセス残存の有無によってコマンドを使い分けたりするぐらいなら、エラー表示が出たとしても、毎回同じコマンドを叩きたいからです。

丁寧に書けば、サーバの起動有無で分岐もできるかと思います*3

「サーバを起動するコマンド」

「サーバを起動するコマンド」を && でつないでしまうと、サーバを正常終了しないと次のステップに進まないので、& でコマンドをつなげます*4

改めて結論

上記を踏まえると、冒頭のような結論になります。

一度正常に動作することが確認できたならば、個々のコマンドを細かく分割して run-srun-p を使うのも良いと思います。

また、バックエンドを別に起動する場合などの応用パターンも、本ケースをベースにすれば難しくないと思います*5

補足

CI で走らせる場合は微妙に異なってきます。たとえば GitHub Actions ですと、package.json のコマンド内で設定した内容を二重に設定することになる場合もあります*6

github.com

あと、この内容だと --spec path/to/foobar.spec.js のようなオプションで特定の Spec を実行することもできません。一工夫が必要になりますが、本内容をベースにすればそこまで面倒ではないはずです。

感想

  • ここまで書いておいてなんですが、シェルスクリプトで書いたほうが良さそうに思います
  • さらになんですが、Cypress は原則 GUI で操作するのがいいと思います

*1:GitHub Actions https://github.com/marketplace/actions/cypress-io でも用いられているようです

*2:ほとんどの場合そうなると思います

*3:シェルスクリプトを書くのがいいかも

*4:crtl + c でサーバを終了させると exit 1 なので正常終了はしないのですが、まあそこはおいといて

*5:煩雑にはなる

*6:ここらへんは作り方次第で、custom-test-command で上書きするなどすれば使い回せたりする https://github.com/marketplace/actions/cypress-io#custom-test-command

Powered by はてなブログ