提交 fb5ae183 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓)

graceful deal with brower, exceptions and restart

上级 aa17915e
......@@ -21,12 +21,10 @@ class Bridge {
, options.puppet.constructor.name
, options.port)
this.options = {
puppet: options.puppet
, port: options.port || 8788 // W(87) X(88), ascii char code ;-]
}
this.puppet = options.puppet
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
}
toString() { return `Bridge({puppet: ${this.options.puppet.constructor.name}, port: ${this.options.port}})` }
toString() { return `Bridge({puppet: ${this.puppet.constructor.name}, port: ${this.port}})` }
init() {
log.verbose('PuppetWebBridge', 'init()')
......@@ -65,7 +63,7 @@ class Bridge {
return co.call(this, function* () {
const injectio = this.getInjectio()
let retObj = yield this.execute(injectio, this.options.port)
let retObj = yield this.execute(injectio, this.port)
if (retObj && /^2/.test(retObj.code)) {
log.verbose('PuppetWebBridge', 'inject() injectio injected, with code[%d] message[%s] port[%d]'
, retObj.code, retObj.message, retObj.port)
......@@ -198,15 +196,21 @@ class Bridge {
const wechatyScript = `return Wechaty.${wechatyFunc}.apply(undefined, ${argsDecoded})`
log.silly('PuppetWebBridge', 'proxyWechaty(%s, ...args) %s', wechatyFunc, wechatyScript)
return this.execute(wechatyScript)
.catch(e => {
log.warn('PuppetWebBridge', 'proxyWechaty() exception: %s', e.message)
throw e
})
return this.execute('return typeof Wechaty === "undefined"')
.then(noWechaty => {
if (noWechaty) {
throw new Error('there is no Wechaty in browser(yet)')
}
})
.then(() => this.execute(wechatyScript))
.catch(e => {
log.warn('PuppetWebBridge', 'proxyWechaty() exception: %s', e.message)
throw e
})
}
execute(script, ...args) {
return this.options.puppet.browser.execute(script, ...args)
return this.puppet.browser.execute(script, ...args)
.catch(e => {
log.warn('PuppetWebBridge', 'execute() exception: %s', e.message)
throw e
......
......@@ -176,9 +176,9 @@ class Browser extends EventEmitter {
this.getBrowserPids()
.then(pids => {
if (pids.length === 0) {
resolve('clean')
resolve('clean() no browser process, confirm clean')
} else {
reject(new Error('dirty'))
reject(new Error('clean() found browser process, not clean, dirty'))
}
})
.catch(e => reject(e))
......@@ -254,6 +254,33 @@ class Browser extends EventEmitter {
})
}
readyLive() {
log.verbose('PuppetWebBrowser', 'readyLive()')
if (this.dead()) {
return Promise.reject(new Error('this.dead() true'))
}
return new Promise((resolve, reject) => {
this.execute('return 1+1')
.then(r => {
if (r === 2) {
resolve(true) // browser ok, living
return
}
const errMsg = 'deadEx() found dead browser coz 1+1 = ' + r + ' (not 2)'
log.verbose('PuppetWebBrowser', errMsg)
this.dead(errMsg)
reject(new Error(errMsg)) // browser not ok, dead
return
})
.catch(e => {
const errMsg = 'deadEx() found dead browser coz 1+1 = ' + e.message
log.verbose('PuppetWebBrowser', errMsg)
this.dead(errMsg)
reject(new Error(errMsg)) // browser not live
return
})
})
}
dead(forceReason) {
let errMsg
let dead = false
......
......@@ -136,7 +136,7 @@ return (function(port) {
Wechaty.vars.inited = true
retObj.code = 200
retObj.message = 'init(): success on port ' + port
retObj.message = 'Wechaty Init Succ on port: ' + port
return retObj
}
......@@ -151,13 +151,13 @@ return (function(port) {
}
if (!document.body) {
log('initClog() not ready because document.body not ready')
// log('initClog() not ready because document.body not ready')
return false
}
var i = document.createElement('iframe')
if (!i) {
log('initClog() not ready because document.createElement fail')
// log('initClog() not ready because document.createElement fail')
return false
}
......@@ -343,7 +343,7 @@ return (function(port) {
}
}
function ding() { log('recv ding'); return 'dong' }
function ding(data) { log('recv ding'); return data || 'dong' }
function hookEvents() {
var rootScope = Wechaty.glue.rootScope
var appScope = Wechaty.glue.appScope
......
......@@ -182,12 +182,12 @@ class PuppetWeb extends Puppet {
onBrowserDead(e) {
// because this function is async, so maybe entry more than one times.
// guard by variable: onBrowserDeadBusy to prevent the 2nd time entrance.
if (this.onBrowserDeadBusy) {
// guard by variable: onBrowserBirthing to prevent the 2nd time entrance.
if (this.onBrowserBirthing) {
log.warn('PuppetWeb', 'onBrowserDead() Im busy, dont call me again before I return. this time will return and do nothing')
return
}
this.onBrowserDeadBusy = true
this.onBrowserBirthing = true
const TIMEOUT = 180000 // 180s / 3m
this.watchDog(`onBrowserDead() set a timeout of ${Math.floor(TIMEOUT/1000)} seconds to prevent unknown state change`, {timeout: TIMEOUT})
......@@ -226,7 +226,7 @@ class PuppetWeb extends Puppet {
})
.then(() => { // Finally
log.verbose('PuppetWeb', 'onBrowserDead() new browser borned')
this.onBrowserDeadBusy = false
this.onBrowserBirthing = false
})
}
......@@ -309,7 +309,10 @@ class PuppetWeb extends Puppet {
} else if (!this.bridge) { // no bridge, quiting???
log.verbose('PuppetWeb', 'onServerDisconnect() no bridge. maybe Im quiting, do nothing')
return
} else { // browser is alive, and we have a bridge to it
}
this.browser.readyLive()
.then(r => { // browser is alive, and we have a bridge to it
log.verbose('PuppetWeb', 'onServerDisconnect() re-initing bridge')
// must use setTimeout to wait a while.
// because the browser has just refreshed, need some time to re-init to ready.
......@@ -321,7 +324,11 @@ class PuppetWeb extends Puppet {
.catch(e => log.error('PuppetWeb', 'onServerDisconnect() exception: [%s]', e))
}, 1000) // 1 second instead of 10 seconds? try. (should be enough to wait)
return
}
})
.catch(e => { // browser is in indeed dead, or almost dead. readyLive() will auto recover itself.
log.verbose('PuppetWeb', 'onServerDisconnect() browser dead, waiting it recover itself: %s', e.message)
return
})
}
/**
* `unload` event is sent from js@browser to webserver via socketio
......
......@@ -29,7 +29,7 @@ test('Bridge retry-promise testing', function(t) {
return delay50()
})
.then(r => {
t.fail('retry-promise should not be resolved here')
t.fail('should not resolved retry-promise here')
})
.catch(e => {
t.equal(e, EXPECTED_REJECT, `retry-promise got ${EXPECTED_REJECT} when wait not enough`)
......@@ -60,28 +60,31 @@ test('Bridge retry-promise testing', function(t) {
test('Bridge smoking test', function(t) {
const browser = new Browser({port: PORT})
t.ok(browser, 'Browser instance created')
t.ok(browser, 'should instanciated a browser')
const mockPuppet = {browser: browser}
const b = new Bridge({puppet: mockPuppet})
t.ok(b, 'Bridge instance creted')
t.ok(b, 'should instanciated a bridge with mocked puppet')
co(function* () {
yield browser.init()
t.pass('should instanciated a browser')
yield browser.open()
t.pass('inited & opened')
t.pass('should open success')
yield b.inject()
t.pass('wechaty injected')
t.pass('should injected wechaty')
const retDing = yield b.execute('return Wechaty && Wechaty.ding()')
t.equal(retDing, 'dong', 'execute Wechaty.ding()')
const retDing = yield b.execute('return Wechaty.ding()')
t.equal(retDing, 'dong', 'should got dong after execute Wechaty.ding()')
const retReady = yield b.execute('return Wechaty && Wechaty.isReady()')
t.equal(typeof retReady, 'boolean', 'execute Wechaty.isReady()')
// @deprecated
// const retReady = yield b.execute('return Wechaty.isReady()')
// t.equal(typeof retReady, 'boolean', 'should got a boolean return after execute Wechaty.isReady()')
const retCode = yield b.proxyWechaty('getLoginStatusCode')
t.equal(typeof retCode, 'number', 'getLoginStatusCode')
t.equal(typeof retCode, 'number', 'should got a number after call proxyWechaty(getLoginStatusCode)')
})
.catch((e) => { // Rejected
t.fail('co promise rejected:' + e)
......
......@@ -10,17 +10,17 @@ const SESSION = 'unit-test-session.wechaty.json'
test('Browser class cookie smoking tests', function(t) {
const b = new Browser({port: PORT, head: HEAD})
t.ok(b, 'Browser instance created')
t.ok(b, 'should instanciate a browser instance')
co(function* () {
yield b.init()
t.pass('inited')
t.pass('should inited')
yield b.open()
t.pass('opened')
t.pass('should opened')
const two = yield b.execute('return 1+1')
t.equal(two, 2, 'execute script ok')
t.equal(two, 2, 'should got 2 after execute script 1+1')
let cookies = yield b.driver.manage().getCookies()
t.ok(cookies.length, 'should got plenty of cookies')
......@@ -58,6 +58,12 @@ test('Browser class cookie smoking tests', function(t) {
t.pass('re-opened url')
const cookieAfterOpen = yield b.driver.manage().getCookie(EXPECTED_COOKIES[0].name)
t.equal(cookieAfterOpen.name, EXPECTED_COOKIES[0].name, 'getCookie() should get expected cookie named after re-open url')
const dead = b.dead()
t.equal(dead, false, 'should be a not dead browser')
const live = yield b.readyLive()
t.equal(live, true, 'should be a live browser')
})
.catch((e) => { // Rejected
t.fail('co promise rejected:' + e)
......
......@@ -13,11 +13,11 @@ const PuppetWeb = require('../src/puppet-web')
test('PuppetWeb smoke testing', function(t) {
let pw = new PuppetWeb({port: PORT, head: HEAD, session: SESSION})
t.ok(pw, 'new PuppetWeb')
t.ok(pw, 'should instantiated a PuppetWeb')
co(function* () {
yield pw.init()
t.pass('pw full inited')
t.pass('should be inited')
t.equal(pw.logined() , false , 'should be not logined')
// XXX find a better way to mock...
......@@ -37,7 +37,7 @@ test('PuppetWeb smoke testing', function(t) {
pw.once('logout', r => {
process.nextTick(() => { // wait to next tick for pw clean logined user status
// log.verbose('TestPuppetWeb', 'on(logout) received %s, islogined: %s', r, pw.logined())
t.equal(pw.logined() , false , 'logouted after logout event')
t.equal(pw.logined() , false , 'should be logouted after logout event')
resolve()
})
})
......@@ -56,14 +56,16 @@ test('PuppetWeb smoke testing', function(t) {
test('Puppet Web server/browser communication', function(t) {
let pw = new PuppetWeb({port: PORT, head: HEAD, session: SESSION})
t.ok(pw, 'new PuppetWeb')
t.ok(pw, 'should instantiated a PuppetWeb')
const EXPECTED_DING_DATA='dingdong'
co(function* () {
yield pw.init()
t.pass('pw inited')
t.pass('should be inited')
const retSocket = yield dingSocket(pw.server)
t.equal(retSocket, 'dong', 'dingSocket got dong')
t.equal(retSocket, EXPECTED_DING_DATA, 'should got EXPECTED_DING_DATA after resolved dingSocket()')
})
.catch(e => { // Reject
log.warn('TestPuppetWeb', 'error: %s', e)
......@@ -102,7 +104,7 @@ test('Puppet Web server/browser communication', function(t) {
log.verbose('TestPuppetWeb', 'socket recv event dong: ' + data)
return resolve(data)
})
server.socketClient.emit('ding')
server.socketClient.emit('ding', EXPECTED_DING_DATA)
}
})
}
......@@ -110,32 +112,37 @@ test('Puppet Web server/browser communication', function(t) {
test('Puppet Web watchdog timer', function(t) {
const pw = new PuppetWeb({port: PORT, head: HEAD, session: SESSION})
t.ok(pw, 'new PuppetWeb')
t.ok(pw, 'should instantiate a PuppetWeb')
co(function* () {
yield pw.initBrowser()
t.pass('should init the browser')
yield pw.initBridge()
t.pass('should init the bridge')
yield pw.bridge.quit().catch(e => {/* fail safe */})
yield pw.browser.quit().catch(e => {/* fail safe */})
t.pass('should kill both browser & bridge')
pw.once('error', e => {
t.ok(/watchdog timeout/i.test(e), 'should emit error after watchdog timeout')
})
pw.watchDog('test', {timeout: 1})
pw.watchDog('feed_and_active_it', {timeout: 10})
t.pass('should feed the watchdog and set timeout')
const dong = yield waitDing()
t.equal(dong, 'dong', 'should got dong from ding after watchdog reset')
const EXPECTED_DING_DATA = 'dingdong'
const dong = yield waitDing(EXPECTED_DING_DATA)
t.equal(dong, EXPECTED_DING_DATA, 'should get EXPECTED_DING_DATA from ding after watchdog reset')
})
.catch(e => { // Exception
t.fail(e.message || e)
t.fail('co exception: ' + e.message)
})
.then(t.end) // Finally
return
/////////////////////////////////////////////////////////////////////////////
function waitDing() {
function waitDing(data) {
const max = 30
const backoff = 100
......@@ -147,16 +154,16 @@ test('Puppet Web watchdog timer', function(t) {
return retryPromise({ max: max, backoff: backoff }, function (attempt) {
log.silly('TestPuppetWeb', 'waitDing() retryPromise: attampt %s/%s time for timeout %s'
, attempt, max, timeout)
return pw.ding()
return pw.ding(data)
.then(r => {
if (!r) {
throw new Error('got empty return')
} else {
return r
}
return r
})
.catch(e => {
log.verbose('TestPuppetWeb', 'waitDing() exception: %s', e.message || e)
log.verbose('TestPuppetWeb', 'waitDing() exception: %s', e.message)
throw e
})
})
......
......@@ -19,15 +19,15 @@ const HEAD = process.env.WECHATY_HEAD || false
test('WebDriver process create & quit test', function(t) {
co(function* () {
const b = new PuppetWebBrowser({port: PORT, head: HEAD})
t.ok(b, 'Browser instnace')
t.ok(b, 'should instanciate a browser')
yield b.init()
t.pass('inited')
t.pass('should be inited successful')
yield b.open()
t.pass('opened')
t.pass('should open successful')
let pids = yield b.getBrowserPids()
t.ok(pids.length > 0, 'driver process exist')
t.ok(pids.length > 0, 'should exist browser process after b.open()')
// console.log(b.driver.getSession())
......@@ -55,29 +55,30 @@ test('WebDriver smoke testing', function(t) {
co(function* () {
const m = (yield wb.getBrowserPids()).length
t.equal(m, 0, 'driver process not exist before get()')
t.equal(m, 0, 'should has no browser process before get()')
driver = yield wb.initDriver()
t.ok(driver, 'driver inited')
t.ok(driver, 'should init driver success')
const injectio = bridge.getInjectio()
t.ok(injectio.length > 10, 'got injectio')
t.ok(injectio.length > 10, 'should got injectio script')
// XXX: if get rid of this dummy,
// driver.get() will fail due to cant start phantomjs process
yield Promise.resolve()
yield driver.get('https://wx.qq.com/')
t.pass('driver url opened')
t.pass('should open wx.qq.com')
const n = (yield wb.getBrowserPids()).length
t.ok(n > 0, 'driver process exist after get()')
t.ok(n > 0, 'should exist browser process after get()')
const retAdd = yield execute('return 1+1')
t.equal(retAdd, 2, 'execute js in browser')
const retAdd = yield driverExecute('return 1+1')
t.equal(retAdd, 2, 'should return 2 for execute 1+1 in browser')
const retInject = yield execute(injectio, PORT)
t.equal(retInject, 'Wechaty', 'injected wechaty')
const retInject = yield driverExecute(injectio, PORT)
t.ok(retInject, 'should return a object contains status of inject operation')
t.equal(retInject.code, 200, 'should got code 200 for a success wechaty inject')
})
.catch(e => t.fail('promise rejected. e:' + e)) // Rejected
......@@ -88,7 +89,7 @@ test('WebDriver smoke testing', function(t) {
return
//////////////////////////////////
function execute() {
function driverExecute() {
return driver.executeScript.apply(driver, arguments)
}
})
......@@ -3,13 +3,13 @@ const log = require('../src/npmlog-env')
test('Wechaty Library', function(t) {
const Wechaty = require('../')
t.ok(Wechaty , 'should have Wechaty exports')
t.ok(Wechaty.Message , 'should have Wechaty.Message exports')
t.ok(Wechaty.Contact , 'should have Wechaty.Contact exports')
t.ok(Wechaty.Room , 'should have Wechaty.Room exports')
t.ok(Wechaty , 'should export Wechaty')
t.ok(Wechaty.Message , 'should export Wechaty.Message')
t.ok(Wechaty.Contact , 'should export Wechaty.Contact')
t.ok(Wechaty.Room , 'should export Wechaty.Room')
t.ok(Wechaty.Puppet , 'should have Wechaty.Puppet exports')
t.ok(Wechaty.Puppet.Web , 'should have Wechaty.Puppet.Web exports')
t.ok(Wechaty.Puppet , 'should export Wechaty.Puppet')
t.ok(Wechaty.Puppet.Web , 'should export Wechaty.Puppet.Web')
t.end()
})
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册