ZEALS TECH BLOG

ZEALS Developers Blog

Browser Automation at Zeals

Intro

Lately, there has been so much movement at ZEALS. As we've expanded, we've been able to meet so many new clients. This has been both a tremendous boon for us as a company, and presented both interesting and tough problems for us on the engineer team to solve. As the our amount of clients increase, so does the amount of data. And as the data increases, our approach to handling said data must be a scalable solution, to free our hands to allow us to focus on bigger and better things. Recently, people would have to manually fill in web forms, which was draining worker's time. One strategy to increase our productivity is to use a headless browser to automate the task. This was suggested to me by our technology lead, and I went ahead and did the implementation, so I'd like to do a sort of introduction as to how I did it.

So many choices...

We needed a server that would be able to serve multiple requests to fill forms concurrently, would have a fairly simple API that would allow you to have the server open a headless web browser, take care of the form and close down. In the end, I decided that Node.js would be a great candidate. Not that I hadn't heard of Python/Selenium solutions before, but there are plenty of headless browser solutions for Node.js, and I was comfortable with Node, so it just seemed like a decent choice. Within the Node ecosystem, there are choices to be made. The top choices for using JS with a headless browser are:

  1. PhantomJS
  2. CasperJS
  3. Puppeteer

However, there are small details to be considered. While all of these choices are technically controlled by JS, what you get is slightly different.

PhantomJS is "a headless WebKit scriptable with Javascript", in other words it's not Node.js, it's a headless browser with a JS API. Not necessarily available from a Node.js process. CasperJS is a "scripting utility written in Javascript for PhantomJS or SlimerJS", meaning it relies on either one of those headless browser.

On the other hand, we have Puppeteer which "is a Node library which provides a high-level API to control headless Chrome over the DevTools protocol". Given that it was the best choice for what I wanted to do, has the most stars on Github, is provided by the Chrome team, AND has great documentation and a modern API, I had my winner. I can't stress this enough, Puppeteer really blows the competition out of the water. If you have a decent knowledge of async/await in ES7 and are aware of which scripts are being executed in the headless browser, as opposed to Node, then this is really just a joy to use.

On top of that, the fact that I'd would simply be able to require the library in my Node server just...almost brought a tear to my eye. :D

The Implementation

f:id:zeals-engineer:20180917114554p:plain

I won't try to bore you with too many details, but here's the gist of the server:

  • Express for the API
  • Puppeteer for headless browser

And here's a gist of what the server does:

  1. The server would except POSTs with JSON sets of instructions on where the form was, how to fill the form, and what was the indication the form was done being sent
  2. The server parses the instructions from the requester, if they seem to be valid, then continue
  3. A headless browser is opened with Puppeteer, if the browser gets the requested page, then the Express server sends a 200 back to the requester
  4. However, the server's not done yet. It goes through all the instructions, and whether it is successful or not, it logs the details

And to be honest, this was looking back now, not too hard to write. However, there are some gotchas that I think people should be aware of:

Gotchas

1. Headless browsers are browsers.

In Puppeteer's case, it's Chromium. And it does everything that that Chromium does. I mean EVERYTHING. It may seem obvious, but there were some weird things I had to learn. For example, a had one case where sending a webform caused an alert box to popup. Of course, since it's headless, I wasn't able to see that. I just assumed the script was hanging for some strange reason. It wasn't until I turned headless mode off (you can do that), that I realized my mistake. You can handle these sort of events with code, but you have to handle them! My advice is to debug with headless mode turned off, and learn to use events well.

2. Just because you have Puppeteer, doesn't mean you have Chromium.

Of course developing using Puppeteer was simple. I already had the Chromium process on my PC. When it comes time to deploy, you need to make sure whatever server/virtual machine/docker instance you're using has the proper software on it for this work. I went ahead and used docker and if you're interested to see how that works, you can check the official Puppeteer docs!

https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#running-puppeteer-in-docker

See I told you the docs were great.

Closing

Browser automation has been a blast to do, and I hope I can do a bit more in the future and it remains part of our toolset. It's a bit hard to wrap your head around, but when it all comes together it really is beautiful!

Until next time!

- Aaron

React + FirebaseでSignUp実装

こんにちは。

サーバーサイドエンジニアのjoeです。

メンバーのReactキャッチアップのためにサンプルアプリを作ったので、公開したいと思います。

この記事は、React初心者を対象としています。

簡単なイベント操作から、データの保存までの全体像が把握できるようになるかと思います。

また、githubに残したので、ぜひ参考にしてみて下さい。

github.com

Reactプロジェクトの準備

create-react-appコマンドを使用して、新しくプロジェクトを作成します。

// コマンドをインストールしていない方は以下のコマンドでインストール
$ npm install -g create-react-app
// プロジェクトの作成
$ create-react-app react-firebase-sign-up

プロジェクトの作成が終わったら、Reactプロジェクトを起動してみます。

$ cd react-firebase-sign-up
$ yarn start

この画面が表示できたら、Reactプロジェクトの準備は終わりです。 f:id:zeals-engineer:20180830182740p:plain

Firebaseの準備

まず、TOPからコンソールへ移動を押し、Firebaseにログインします。

firebase.google.com

f:id:zeals-engineer:20180830182321p:plain

プロジェクトを追加ボタンを押し、プロジェクト名を入力した後に、プロジェクトを作成します。

f:id:zeals-engineer:20180830183903p:plain

f:id:zeals-engineer:20180830184110p:plain

Firebaseプロジェクトに入れたら、Authenticationの設定をします。

f:id:zeals-engineer:20180830184505p:plain

プロバイダ、メール/パスワードのステータスが無効になっているので、有効にします。

f:id:zeals-engineer:20180830184713p:plain

ここまでできれば、firebase側の設定は終わりです。

React側でメールアドレス/パスワードのフォームを作り、実際にSign Upしてみましょう。

ReactとFirebaseを連携し、SignUpしてみる

Formの作成

react-firebase-sign-up/src/App.jsのコードは消さずに、そのままFormを追記していきます。

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        {/* ここから */}
        <div className="form">
          <form className="register-form">
            <input type="text" placeholder="email address"/>
            <input type="password" placeholder="password"/>
            <button>Sign Up</button>
          </form>
        </div>
        {/* ここまで追記 */}
      </div>
    );
  }
}

export default App;

ブラウザを確認すると、ダサいですが、メールアドレスとパスワードを入力するFormが出来上がりました!

f:id:zeals-engineer:20180830191757p:plain

ReactとFirebaseの連携

秘匿情報

まず、react-firebase-sign-up/src/config/firebase.jsを作成します。

Firebaseから秘匿情報を確認し、以下のようにコードを書きます。

*秘匿情報なので、git管理されている方は、.gitignoreに、/src/config/firebase.js追記する必要があります。

export default {
  apiKey: "**************",
  authDomain: "**************",
  databaseURL: "**************",
  projectId: "**************",
  storageBucket: "**************",
  messagingSenderId: "**************"
};

秘匿情報の取得方法は、Project Overviewをクリックし、以下のボタンをクリックするとモーダルで表示されます。

f:id:zeals-engineer:20180830192643p:plain

Firebaseを読み込む

まず、プロジェクトにfirebaseをインストールします。インストール後は、サーバを止め、 yarn startで再起動します。

$ yarn add firebase

次に、react-firebase-sign-up/src/App.jsにfirebaseの設定を追記します。

App.jsのコードは以下のようになります。

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

// ここから
import firebase from 'firebase';
import firebaseConfig from './config/firebase';

firebase.initializeApp(firebaseConfig);
// ここまで追記

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <div className="form">
          <form className="register-form">
            <input type="text" placeholder="email address"/>
            <input type="password" placeholder="password"/>
            <button>Sign Up</button>
          </form>
        </div>
      </div>
    );
  }
}

export default App;

最後に、メールアドレス/パスワードをfirebaseにPOSTしてみます。

メールアドレス/パスワードをfirebaseにPOST

POSTするために以下の順番で実装していきます。

1. emailとpasswordをstateに定義

mae.chab.in

2. emailとpassword stateを更新できるようにする

mae.chab.in

3. handleSignUpメソッド実装

developer.mozilla.org

4. firebase authのcreateUserWithEmailAndPasswordメソッドを使用して POST

Interface: Auth  |  Firebase

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import firebase from 'firebase';
import firebaseConfig from './config/firebase';

firebase.initializeApp(firebaseConfig);

class App extends Component {

  constructor(props){
    // 1. Stateの定義
    super(props)
    this.state = {
      email: '',
      password: '', 
    }
  }

  // 3. handleSignUpメソッドの定義
  handleSignUp = e => {
    e.preventDefault()
    // stateからemailとpasswordを取得する
    const { email, password } = this.state;

    // 4. firebaseにemailとpasswordをPOST
    firebase.auth().createUserWithEmailAndPassword(email, password)
      .then(user => {
        console.log(user);
        this.setState({ email: null, password: null })
      })
      .catch(error => {
        console.log('firebase error', error);
      });
  }


  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <div className="form">
          <form className="register-form">
            {/* 2. テキストが入力されるたびに、State emailが更新されるようにする */}
            <input 
              type="text" 
              placeholder="email address"
              onChange={e => this.setState({ email: e.target.value })}
            />
            {/* 2. テキストが入力されるたびに、State passwordが更新されるようにする */}
            <input 
              type="password" 
              placeholder="password"
              onChange={e => this.setState({ password: e.target.value })}
            />
            {/* 3. handleSignUpメソッドをonPress時に実行されるようにする */}
            <button onClick={e => this.handleSignUp(e) }>Sign Up</button>
          </form>
        </div>
      </div>
    );
  }
}

export default App;

ブラウザで、メールアドレス/パスワードを入力!

f:id:zeals-engineer:20180831135805p:plain

Sign Upをおして、firebase上でユーザーが作成されているか確認する。

f:id:zeals-engineer:20180830205506p:plain

確認できたら、SignUp成功!

firebase便利ですね!

brew upgrade で mysql 8.0 にしてしまって bundle install で mysql2 でコケた時の話

どうも!Kodyです!(味わい深いことやってます)

 

今回、オンボーディングの中でインターン生(エンジニア)が環境構築してる時に事件が起こったのでメモ代わりに記しておきます。

bundle install できない...

迂闊に brew update してしまい bundle install するとこんなエラー吐かれた

------------------------------------------------

Installing mysql2 0.4.4 with native extensions

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

:
:
:

An error occurred while installing mysql2 (0.4.4), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.4.4'` succeeds before bundling.

------------------------------------------------

バージョン確認する

------------------------------------------------

mysql --version

# mysql  Ver 14.14 Distrib 8.0.xx, ...

------------------------------------------------

8.0じゃぁあん

完全にMySQLを消し去る

MacでMySQL5.7をアンインストールする

を参考にアンインストール(ありがとうございます)

実際に打ったコマンド(コピペ面倒だったので $は省略してます)

------------------------------------------------

brew remove mysql
brew cleanup
sudo rm /usr/local/mysql
sudo rm -rf /usr/local/var/mysql
sudo rm -rf /usr/local/mysql*
sudo rm ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist
sudo rm -rf /Library/StartupItems/MySQLCOM
sudo rm -rf /Library/PreferencePanes/My*
launchctl unload -w ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist
rm -rf ~/Library/PreferencePanes/My*
sudo rm -rf /Library/Receipts/mysql*
sudo rm -rf /Library/Receipts/MySQL*
sudo rm -rf /private/var/db/receipts/*mysql*

------------------------------------------------

PC再起動

コマンドが使えないことを確認

------------------------------------------------

mysql -v

# mysql: command not found

------------------------------------------------

mysql 5.7をインストール

------------------------------------------------

brew install mysql@5.7

------------------------------------------------

パスを通す

------------------------------------------------

echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile

------------------------------------------------

バージョン確認

------------------------------------------------

mysql --version

# mysql  Ver 14.14 Distrib 5.7.20, ...

------------------------------------------------

これで無事 bundle install できるようになりました!

Read more

LINE Messaging APIでクイックリプライを実装してみました。

こんにちは!!!

ZEALS CTO島田です。

2018年7月30日、LINE Messaging APIにクイックリプライが追加されました。

LINEのMessaging API公開から2年数ヶ月、、 遂にきました。

BOTらしいUXなので好きなんですよねーー、、嬉しいです!

 

早速、実装してみました。

 




  

クイックリプライの背景色がグレーなのが少し見にくいかもしれませんね。

に対応しているそうです。

 

Messengerのクイックリプライでは実現できていないカメラアクション、カメラロールアクションが実装されているのが印象的でした。

尚、Messengerのクイックリプライではユーザーがfacebookへ登録しているメールアドレス、電話番号を取得することができます。

JSONのちがい

LINEのJSONはこんな感じです。(サンプルソース引用)


 {
  "type": "text", // ①
  "text": "Select your favorite food category or send me your location!",
  "quickReply": { // ②
    "items": [
      {
        "type": "action", // ③
        "imageUrl": "https://example.com/sushi.png",
        "action": {
          "type": "message",
          "label": "Sushi",
          "text": "Sushi"
        }
      },
      {
        "type": "action",
        "imageUrl": "https://example.com/tempura.png",
        "action": {
          "type": "message",
          "label": "Tempura",
          "text": "Tempura"
        }
      },
      {
        "type": "action", // ④
        "action": {
          "type": "location",
          "label": "Send location"
        }
      }
    ]
  }
}

Messenger APIでのquick replyのJSON


{
  "recipient":{
    "id":""
  },
  "message":{
    "text": "Here is a quick reply!",
    "quick_replies":[
      {
        "content_type":"text",
        "title":"Search",
        "payload":"",
        "image_url":"http://example.com/img/red.png"
      },
      {
        "content_type":"location"
      }
    ]
  }
}

ネストは違いますが要素には大差ないですね。

 

UXは格段に向上するかと思われますので弊社でも積極的に導入していきたいと思います!

Read more

ZEALS TECH BLOGはじめます!

 

f:id:zeals-engineer:20180802114505p:plain

こんにちは。ZEALS  CTOの島田です。

ZEALSではチャットボット、VUIなどコミュニケーションに関するプロダクトを開発しております。

それらのプロダクトを支える技術について、僭越ではございますが少しずつ発信していければと考えております。

また、このブログでは弊社の開発文化等も発信して参ります。

まだまだ発展途上の組織ですので、拙い知識や未熟な部分がございましたらご指導ご鞭撻のほどよろしくお願いいたします..!

 

それでは何卒よろしくお願いいたします!!

 

Read more