Selenium WebDriverでテスト書くのにAsync/Awaitを使ってみた
E2Eテストを書くときにSelenium WebDriverを使っているんだけど、テストをJavaScriptで書くと、下みたいな感じでPromiseのチェーンだらけになるのがあまり好みじゃなかった。
const assert = require('power-assert'); const chrome = require('selenium-webdriver/chrome'); const {Builder, By, Key} = require('selenium-webdriver'); describe('Promiseベースで書いたテスト', function () { this.timeout(20000); let driver; beforeEach(() => { driver = new Builder() .forBrowser('chrome') .setChromeOptions(new chrome.Options().headless()) .build(); }); afterEach(() => { driver.quit(); }); it('then,then,then', () => { return driver.get('https://www.google.com').then(() => { driver.findElement(By.name('q')).sendKeys('webdriver'); return driver.sleep(1000); }).then(() => { driver.findElement(By.name('q')).sendKeys(Key.TAB); driver.findElement(By.name('btnK')).click(); return driver.sleep(1000); }).then(() => { return driver.getTitle(); }).then((title) => { assert(title === 'webdriver - Google 検索'); }); }); });
なので、普段はPythonとか別の言語で書いていた。 ECMAScript2017でAsync/Await入るの見た時に、雰囲気変わるかなーと思いつつ試さずに放置してたので、最近はじめた気分転換のついでに試してみた。
こんな感じになった。
const assert = require('power-assert'); const chrome = require('selenium-webdriver/chrome'); const {Builder, By, Key} = require('selenium-webdriver'); describe('Async/Awaitで書いたテスト', function () { this.timeout(20000); let driver; beforeEach(() => { driver = new Builder() .forBrowser('chrome') .setChromeOptions(new chrome.Options().headless()) .build(); }); afterEach(() => { driver.quit(); }); it('await,await,await', async () => { await driver.get('https://www.google.com'); driver.findElement(By.name('q')).sendKeys('webdriver'); await driver.sleep(1000); driver.findElement(By.name('q')).sendKeys(Key.TAB); driver.findElement(By.name('btnK')).click(); await driver.sleep(1000); const title = await driver.getTitle(); assert(title === 'webdriver - Google 検索'); }); });
多少マシになった感じ?
ちなみにPythonで書くとこんな感じ。
import unittest from selenium import webdriver from selenium.webdriver.common.keys import Keys import os import time class GoogleSearch(unittest.TestCase): def setUp(self): driver_path = os.path.join(os.path.dirname(__file__), 'chromedriver') options = webdriver.chrome.options.Options() options.add_argument('--headless') self.driver = webdriver.Chrome(executable_path=driver_path, chrome_options=options) def test_search_in_google_com(self): driver = self.driver driver.get('https://www.google.com') q = driver.find_element_by_name('q') q.send_keys('webdriver') time.sleep(1) q.send_keys(Keys.TAB) driver.find_element_by_name('btnK').click() time.sleep(1) assert driver.title == 'webdriver - Google 検索' def tearDown(self): self.driver.close() if __name__ == '__main__': unittest.main()
もっと込み入った感じのを書いてみないとなんとも言えないけど、ありかなー?どうかなー?って感じ。
observable-storeとfreezable-storeというのを作ってみた話
きっかけ
ビューを作るのにReactを使っているとpropsで渡すものはなるべくシンプルなオブジェクトにしたいかな...みたいな気分になる。
(雑な例であまり良くないのだけれど)例えば、カウンタアプリケーションで現在のカウントを表示するビューみたいなのを作る時に、propsで渡されるのはCounterモデルのインスタンスで...とかではなくて、単純にcountプロパティを持つオブジェクトってだけにしたい。
// こうじゃなくて function CounterView(props) { const { counter } = props; return <div>{counter.getCount()}</div>; } // こんな感じにしたい function CounterView(props) { const { count } = props; return <div>{count}</div>; }
propsで渡ってくるのはそういうオブジェクトだよということをPropTypesとかflowで保証して、キー名typoして残念undefined...みたいなのは防いでおく。ビュー側では参照しかしないので、とりあえず構造が合っていればモデルのこと知らなくていいみたいな感じにしたい。
逆に参照ではなくて状態を変えるところ、例えばカウンタのカウントを増やすみたいなのは、むき出しのJSONこねこねします...みたいなのじゃなくて、ちゃんとCounterモデルの操作にしたい。
またしても雑な例にすると、次みたいな感じでincrementメソッド呼ぶみたいにしたい。
const counter = new CounterModel(/* 初期化パラメータとか */); counter.increment();
で、その辺りひっくるめてちょっと自分なりのやり方探してみようかなーと、モデルとビューの間に入って橋渡しするくんとしてobservable-storeというのを作ってみた。
observable-store
機能は次のような感じ。
- 状態を保持する。
- 保持している状態は参照可能にする。
- 保持している状態は特定の方法でのみ変更可能にする。
- 保持している状態が変わったらオブザーバーに通知する。
APIはObject.assignと短命に終わったObject.observeの間の子みたいにして、実装にはProxy使ってゴニョゴニョやった。
単純な使い方は次のような感じ
import createObservableStore from '@ushiboy/observable-store'; // observable-storeのインスタンスを生成 const store = createObservableStore({ count: 0 }); // 保持している状態にはstateプロパティからアクセスする const state = store.state; console.log(`count: ${state.count}`); // count: 0; // 状態の編集はassignメソッドから行う store.assign({ count: state.count + 1 }); console.log(`count: ${state.count}`); // count: 1; // stateプロパティを直接変更しようとするとエラーになる try { state.count = state.count + 1; // throw Error } catch (e) { console.log(`${e}`); // Error: Should use assign } // 変更を監視するオブザーバーの登録はobserveメソッドで行う const observer1 = () => { console.log(`change: count:${state.count}`); }; store.observe(observer1); // オブザーバーの解除はunobserveメソッドで行う store.unobserve(observer1);
当初はモデルを保持してモデルの変更を監視して...とか、参照の時にモデルのtoJSON呼んで...とか色々考えていたんだけど、モデル側の作り方を制限したくなくなってバッサリやめた。
で、これを使ってやりたかった感じにカウンタアプリ作ってみると次のようになった。
Reactと合わせて使う上でのポイントは次みたいな感じ。
- Applicationコンポーネントでstoreの監視して、変更があったらsetStateして自身に反映する。
- CounterViewコンポーネントには直接storeは渡さずにstateのみを渡す。
- ボタンがクリックされた時の処理はモデルの操作をユースケースで包んで置いて直接は行わないようにする。
単純なサンプル過ぎて参考にならないんだけど、とりあえずやりたい感じになったと言えばなった。
ただ、observable-storeはassignが行われた時に保持している状態に変更があるかゴリゴリチェックしているんだけど、ここで頑張らなくてもReactの方に頼ってしまって良いかも...と感じた。
そこで、状態の変更をチェックするのをやめて、assignされたからどこか変わっているかもよ...とオブザーバーに知らせるだけに機能を削ってfreezable-storeというのを作ってみた。
freezable-store
使い方はstoreの生成方法が変わるだけで、他は一緒。
import createFreezableStore from '@ushiboy/freezable-store'; // freezable-storeのインスタンスを生成 const store = createFreezableStore({ count: 0 });
Reactと合わせてやる分にはこれでも良いかもなーという印象。
今のところの感想
試しに作ったのがカウンタアプリ程度で扱う状態が単純すぎてなんともなーみたいな感じだけど、状態が複雑になってくるとassignのところとかヤバイかなーどうかなーみたいな感じ。とりあえずもうちょっとドッグフーディング。
Proxy使ったの初めてだった。
PhinxでマイグレーションしつつEloquentのモデルのテストをする方法
PhinxでDBのマイグレーションしているけど、モデルにはIlluminate\Database\Eloquentを利用していて、 PHPUnitで単体テストするときにSQLiteのオンメモリでやりたいなと思ったのでソース見ながら方法探してた。
やり方まとまったのでメモしておく。
こんな感じのモデルを用意して、
こんな感じにテストする。
ポイントはテストのsetUpで、 Illuminate\Database\Capsule\Managerのインスタンスを作って各種設定をしたあとにPDOのインスタンスを取り出して、 それをPhinx\Migration\Managerのインスタンスにセットしてマイグレーションを実行する。
DB設定は一応phinx.ymlから参照するようにしたけど、どうせSQLiteの:memory:しか使わないのであればベタ書きで良いかも。
サンプルコードはこちら。 github.com
Y8 2017 spring in Shibuyaに参加してきた
去年に引き続き、今年も参加してきた。
そして今年は、2nd season(懇親会)の方で猫型さん(@neko_gata_s さん)と「春だ!webフロントエンド開発を語り尽くすぞ!!」というタイトルで漫談してきた。
トークを聴いて
苦労話とか、知らなかった話とか、ためになる話があってなるほどなーという感じで良かった。
- hyperapp – 1kbのビューライブラリ
- ソース読んで見たらすごいコンパクトでびっくりした
- ScalaでウェブAPIを書いている人が設計や実装やその他について話そうか
- V8 for フロントエンドデベロッパー
- JavaScriptを書くときにV8の気持ちを考えるのに参考になった
辺りが特に印象に残った感じ。
漫談したこと
勉強会とかで発表した経験が浅い自分にとって、漫談とはいえ大勢の前でやるのは難易度高すぎて、 最初に猫型さんに誘われた時は、正直「いやいやいやいや…」って感じだったけど、 やっぱり何かしら挑戦しなければと常々思っていたので、良い経験になった。
緊張しまくってたけど、夜の部で懇親会中ということもあり、始まってしまえばそれなりに気楽になった。 だんだんフリーダムになって事前に打ち合わせしていた内容の半分くらい喋らなかったのはご愛嬌。
漫談中のアドリブもそうだけど、漫談後に色々な方にせっかく話しかけて頂いたのにイマイチうまく話すことができなくて、 「なんでもっとうまく話せなかったんだろう…」と一夜明けてからずーっと悶絶しているけど、 「そういうのを次に活かしてサイクル回すと良いよ」というアドバイスを頂いたので、頑張りたい。頑張ろう。
猫型さんは「ニュービーなので…」などと言いながらフロントエンドのツールやライブラリをちゃんと使いこなしつつ、 複雑なSingle Page Applicationの設計をものすごく研究している人なので、 「純粋にWebブラウザでどこまでのアプリケーションが作れるのか」が大好きな自分にとって、 すごく勉強になるし、漫談の打ち合わせで話しているだけでも本当に楽しかった。
自分の関心のある話を誰かと濃く話せるというのはすごい楽しいんだなーと思った。 なので、もっとそういう話をできる人とお近づきになりたい(とういか師匠が欲しい)。
まとめ
主催の@uzullaさん、運営スタッフ、発表者、参加者の皆様ありがとうございました。 今年も楽しい体験をさせて頂きました。
一緒に漫談やってくれた猫型さん、見守ってくれた@hayajoさんありがとうございました。 数年前のあの日、hayajoさんに誘っていただいて行った居酒屋での出会いは自分に確かな影響を与えています。
【NDSiN#12×Niigata.js×C4N】JavaScript 2017春に参加した
久しぶりのNiigata.js絡みのイベントに参加して、「SPAのルーティング」の話をしてきた。
SPAのルーティングに関してはあまり話に出てるの見たことがなくて(この前のWEB+DB PRESS 97号で初めて見たくらい)、みんな当たり前のようにやってる事なのかなーと思っていたけど、自分がSPAなアプリケーションとか、SPA用のルーティングライブラリ作った時に結構やらかしたり学んだりしたことがあったので題材にしてみた。
ホントは更にサーバサイドレンダリングと絡めて、ページロード時のサーバサイドのルーティングや初期化データ取得とのViewの生成、ページロード直後のフロントエンドの動きあたりのとこまで踏み込めると、また深みがあるのでそこまで行きたかったんだけど、まだ自分も模索中みたいなところが多すぎて無理だった(その辺りの良いやり方知りたい…)。
他の発表者さんの発表を聴いてて、みんな落ち着いて話してるなーという感じで、自分もっと場数踏まないとだなーと思った。
2016年にフロントエンド開発に使った環境
今年も終わるのでメモしておく。
去年との大きな違いはwebpackの設定を個別ファイルにするのをやめたくらい。 webpackの設定はgulpfile.js内で関数用意して、引数で対象の環境(development, production, testing)を切り替えるようにした。
こんな感じで使う。
他にはbabelのバージョンが上がったことに伴うあたりが変わったくらいで、そんなに大きな変化はなかったかな…と。
以下、package.jsonとか。
SVGのtext要素でUnicodeのHTML文字参照を使う
意外なやり方だったのでメモしておく。
方法
次のようなsvg#screen1にHTML文字参照を含むコンテンツを持つtext要素を追加したい場合、
text要素をcreateElementNSで生成したらinnerHTMLでコンテンツをセットする。
これで"test@foo"が表示される。
見つけた経緯
レイアウトの調整のために、直接タグを書いていたときには問題なく表示できていた。
<svg width="400" height="20"> <text x="0" y="16" fill="black">test@foo</text> </svg>
動的に描画するためにtextContentやcreateTextNodeを使ったら、"&"が"&"になるので、"test@foo"と表示されてしまった。
直接タグを書いていた時にはできていたので何かあるはずと思って、text要素のインスタンスのプロパティを直接見たらinnerHTMLがあった(SVGの要素なのに)。
気になること
W3Cとかでこのあたりの資料探してみたけどいまいち見つからない...
React.jsの場合
上記のことを踏まえて、dangerouslySetInnerHTMLを使えば実現できる。
確認したブラウザとバージョン
次のブラウザで表示できるのを確認した。
ブラウザ | バージョン |
---|---|
Firefox | 47.0 |
Google Chrome | 52.0.2743.82 |
Safari | 9.1.2 |
ソースコード
こちら: SVG Text · GitHub