クラッシュレポートからバグの原因を特定する - ReactNative

FabricのCrashlyticsを使ってユーザーがクラッシュした時のログを収集できる

収集したログからバグってる箇所を特定する方法の一つとしてメモ

クラッシュレポートから探る

Fabricの管理画面を開いてクラッシュレポートを見ると、こんな報告があった

f:id:rskull:20170821145759p:plain

undefined is not an object (evaluating 'l.user.id')

Javascriptではよくあるエラー

JSコード部分にバグがあることがわかる

l.user.id に心当たりあるコードが1箇所の場合はすぐ特定出来るけど、何箇所もある場合どこのことを言ってるの分からない

renderRow にはメソッド名で心当たりがある

さらに 755:3840 755行目の3840文字目あたり

本番で使われてる js.bundle の中身のことを言っているので、自分で生成して見てみることにした

iOS

$ react-native bundle --platform ios --dev false --entry-file index.ios.js --bundle-output /tmp/index.ios.bundle

Android

$ 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テストを書いてみる

github.com

前提

準備

シミュレーターを動かすのに必要なのでインストールする

$ 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が変更されるかチェックされる

f:id:rskull:20170815182823g:plain

デモ: GitHub - rskull/detoxDemo

こんな感じのコード

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はまだサポートしてないみたい

参考リンク

バージョン指定でReactNativeのプロジェクトを始める

メモ

$ react-native init App --version v0.46.0

運用してるプロジェクトで入れたモジュールがうまく動いてない時

同じバージョンの新規プロジェクトを作って一番素の状態で想定通り動くかテストしたい時に使う

わりと最終手段だけど、問題の切り分けできて良い

ReactNativeで開発中、Androidのビルドで64K問題のエラーが出てハマった

64K問題

ReactNativeに限った話ではないが

Androidアプリでメソッド数が65536を超えるとビルドエラーになる問題のこと

Androidエンジニアにとっては常識だと思うが、Webから入った人間からすると結構厄介だった

検索すると沢山ヒットする

分かりやすかった記事

dev.classmethod.jp

こんなエラーが出る

f:id:rskull:20170731233923p:plain

調査する

前提として64K以上いけるMultidex化はしたくない

ビルドしたapkファイルにいくつメソッドがあるか調べる

dex-method-counts を使う方法があるけど、Android Studioの標準機能で出来る

これ64Kでビルド落ちた時apk作られないから調べられなくない…?

とりあえず、ビルド通ってた時期のapkファイルを調べてみた

f:id:rskull:20170731235542p:plain

めっちゃギリギリ! このあと新しい機能を入れたら落ちるようになったので、まずは原因を調査

$ ./gradlew :app:dependencies

を実行すると依存してるパッケージの一覧が出る

64K問題の原因で多いのは google-services 系を全パッケージ読み込んでるとか、別々のバージョンが入ってるとからしい

medium.com

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 を必要なもののみ追加する

ここら辺の設定は人による

いろいろやってメソッド数が減ったのは確認できたけど、目的の機能入れたときに結局落ちた

中途半端になってしまったが調査中‥

いい方法を知りたい