クラッシュレポートからバグの原因を特定する - ReactNative
FabricのCrashlyticsを使ってユーザーがクラッシュした時のログを収集できる
収集したログからバグってる箇所を特定する方法の一つとしてメモ
クラッシュレポートから探る
Fabricの管理画面を開いてクラッシュレポートを見ると、こんな報告があった
undefined is not an object (evaluating 'l.user.id')
Javascriptではよくあるエラー
JSコード部分にバグがあることがわかる
l.user.id
に心当たりあるコードが1箇所の場合はすぐ特定出来るけど、何箇所もある場合どこのことを言ってるの分からない
renderRow
にはメソッド名で心当たりがある
さらに 755:3840
755行目の3840文字目あたり
本番で使われてる js.bundle
の中身のことを言っているので、自分で生成して見てみることにした
$ react-native bundle --platform ios --dev false --entry-file index.ios.js --bundle-output /tmp/index.ios.bundle
$ react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output /tmp/index.android.bundle
とりあえず、iOSで生成した /tmp/index.ios.bundle
を開いて確認してみる
l.user.id
で検索すると、丁度 755行目に対象のコードを発見した
近辺のコードからどのjsファイルか特定できた
ログからだとどうしても特定できなかったときに便利
ReactNativeでE2EテストをするDetoxをちょっとだけ触ってみた(iOS)
Detoxというライブラリを使ってE2Eテストを書いてみる
前提
準備
シミュレーターを動かすのに必要なのでインストールする
$ brew tap facebook/fb $ export CODE_SIGNING_REQUIRED=NO && brew install fbsimctl
$ brew tap wix/brew $ brew install --HEAD applesimutils
$ npm install -g detox-cli
テストしたいプロジェクトに導入する
$ npm install detox mocha --save-dev
初期化コマンドを実行すと
./e2e
フォルダが生成されます
続いてビルドコマンドで成功すれば準備完了
$ detox init $ detox build
動かす
$ detox test
を実行すると ./e2e/xxx.spec.js
ファイルが検出されてテストされる
適当な画面を書いて動かしてみた
テストを実行すると、画面が勝手に動き出して
書いたとおりにUIが変更されるかチェックされる
こんな感じのコード
describe('Example', () => { beforeEach(async () => { await device.reloadReactNative(); }); it('テキスト入力', async () => { await expect(element(by.id('welcome'))).toBeVisible(); await element(by.id('textInput')).typeText('Hello!'); await element(by.id('textInput')).clearText(); await element(by.id('scrollview')).tap(); }); it('スクロールしてメニュー開閉', async () => { await element(by.id('scrollview')).scrollTo('bottom'); await element(by.id('menuButton')).tap(); await expect(element(by.id('menu'))).toBeVisible(); await element(by.id('menuCloseButton')).tap(); await expect(element(by.id('menu'))).toNotExist(); }); })
操作対象とのヒモ付は testID
を付ける
対応してるのは View
, Text
, TextInput
, Switch
, ScrollView
で、ラップしてる場合は自分で渡せるようにしないといけない
例えば
<View testID='welcome'> <Text>Hello</Text> <View>
describe('Example', () => { beforeEach(async () => { await device.reloadReactNative(); }); it('初期画面が表示されてる', async () => { await expect(element(by.id('welcome'))).toBeVisible(); }); })
また、要素に対してタップやスクロールというような操作も実行できる
<TouchableOpacity testID='button' onPress={() => {}}> <Text>Touch</Text> </TouchableOpacity>
it('sample', async () => { await element(by.id('button')).tap() // onPressが実行される });
タップ以外の操作もだいたい出来る
- .tap()
- .longPress()
- .multiTap()
- .tapAtPoint()
- .typeText()
- .replaceText()
- .clearText()
- .scroll()
- .scrollTo()
- .swipe()
詳しくはドキュメントへ: https://github.com/wix/detox/blob/master/docs/APIRef.ActionsOnElement.md#methods
所感
testID
埋め込むのがちょっとめんどくさいのと、書き方によって埋め込みが難しいところもある- OSSのアプリに組み込んでみたらビルドエラーで動かなかったり、まだ安定してない感があった
- 動いてる様子を見るのが楽しかった(たぶん最初だけ)
- 使うとしたらコアで操作の多い重要な部分だけ書くのが良さそう
- Androidはまだサポートしてないみたい
参考リンク
- デモ:GitHub - rskull/detoxDemo
- Detox:GitHub - wix/detox: Gray Box E2E Tests and Automation Library for Mobile Apps
- ドキュメント:https://github.com/wix/detox/blob/master/docs/README.md
- トラブルシューティング:detox/Troubleshooting.RunningTests.md at master · wix/detox · GitHub
- 詳しい記事:How to Test your React Native App Like a Real User – Elad Bogomolny – Medium
バージョン指定でReactNativeのプロジェクトを始める
メモ
$ react-native init App --version v0.46.0
運用してるプロジェクトで入れたモジュールがうまく動いてない時
同じバージョンの新規プロジェクトを作って一番素の状態で想定通り動くかテストしたい時に使う
わりと最終手段だけど、問題の切り分けできて良い
ReactNativeで開発中、Androidのビルドで64K問題のエラーが出てハマった
64K問題
ReactNativeに限った話ではないが
Androidアプリでメソッド数が65536を超えるとビルドエラーになる問題のこと
Androidエンジニアにとっては常識だと思うが、Webから入った人間からすると結構厄介だった
検索すると沢山ヒットする
分かりやすかった記事
こんなエラーが出る
調査する
前提として64K以上いけるMultidex
化はしたくない
ビルドしたapkファイルにいくつメソッドがあるか調べる
dex-method-counts を使う方法があるけど、Android Studioの標準機能で出来る
これ64Kでビルド落ちた時apk作られないから調べられなくない…?
とりあえず、ビルド通ってた時期のapkファイルを調べてみた
めっちゃギリギリ! このあと新しい機能を入れたら落ちるようになったので、まずは原因を調査
$ ./gradlew :app:dependencies
を実行すると依存してるパッケージの一覧が出る
64K問題の原因で多いのは google-services
系を全パッケージ読み込んでるとか、別々のバージョンが入ってるとからしい
compile (project(':react-native-device-info')){ exclude group: "com.google.android.gms" } compile ("com.google.android.gms:play-services-gcm:10.0.1") { force = true; }
exclude
するとコンパイル対象から除外されるので、依存してるreactnativeのパッケージからすべて除外して
最後に除外した play-services-xxx
を必要なもののみ追加する
ここら辺の設定は人による
いろいろやってメソッド数が減ったのは確認できたけど、目的の機能入れたときに結局落ちた
中途半端になってしまったが調査中‥
いい方法を知りたい