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

doc

...@@ -16,5 +16,5 @@ notifications: ...@@ -16,5 +16,5 @@ notifications:
urls: urls:
- ${GITTER_IM_URL} - ${GITTER_IM_URL}
on_success: change # options: [always|never|change] default: always on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always on_failure: change # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always on_start: never # options: [always|never|change] default: always
{ {
"name": "Wechaty Cloud Bot for IO", "name": "Wechaty Cloud Bot for IO @ Heroku",
"description": "Wechat for Bot. Get your own cloud bot by deploy me", "description": "Wechat for Bot. Get your Heroku cloud bot by deploy me",
"repository": "https://github.com/zixia/wechaty", "repository": "https://github.com/zixia/wechaty",
"logo": "https://raw.githubusercontent.com/zixia/wechaty/master/image/wechaty-icon.png", "logo": "https://raw.githubusercontent.com/zixia/wechaty/master/image/wechaty-icon.png",
"keywords": ["wechaty", "wechat", "bot", "chatbot", "framework", "cloudbot"], "keywords": ["wechaty", "wechat", "bot", "chatbot", "framework", "cloudbot"],
......
#!/usr/bin/env node
const Wechaty = require('..') const Wechaty = require('..')
const log = Wechaty.log const log = Wechaty.log
......
...@@ -31,6 +31,7 @@ const bot = new Wechaty({ profile: 'example-bot.wechaty.json' }) ...@@ -31,6 +31,7 @@ const bot = new Wechaty({ profile: 'example-bot.wechaty.json' })
bot bot
.on('login' , user => log.info('Bot', `${user.name()} logined`)) .on('login' , user => log.info('Bot', `${user.name()} logined`))
.on('logout' , user => log.info('Bot', `${user.name()} logouted`)) .on('logout' , user => log.info('Bot', `${user.name()} logouted`))
.on('error' , e => log.info('Bot', 'error: %s', e))
.on('scan', ({url, code}) => { .on('scan', ({url, code}) => {
if (!/201|200/.test(code)) { if (!/201|200/.test(code)) {
let loginUrl = url.replace(/\/qrcode\//, '/l/') let loginUrl = url.replace(/\/qrcode\//, '/l/')
......
{ {
"name": "wechaty", "name": "wechaty",
"version": "0.2.7", "version": "0.2.8",
"description": "Wechat for Bot. (Personal Account NOT Official Account)", "description": "Wechat for Bot. (Personal Account NOT Official Account)",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"lint": "eslint src test", "lint": "eslint src test",
"pretest": "npm run lint", "pretest": "npm run lint",
"start": "node bin/io-bot.js", "start": "node bin/io-bot",
"demo": "node example/ding-dong-bot.js", "demo": "node example/ding-dong-bot.js",
"test": "cross-env TAP_TIMEOUT=600 tap --reporter=tap test" "test": "cross-env TAP_TIMEOUT=600 tap --reporter=tap test/{*,**/*}.spec.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
"phantomjs-prebuilt": "latest", "phantomjs-prebuilt": "latest",
"ps-tree": "latest", "ps-tree": "latest",
"retry-promise": "latest", "retry-promise": "latest",
"selenium-webdriver": "^2.53.2", "selenium-webdriver": "latest",
"socket.io": "latest", "socket.io": "latest",
"ws": "latest" "ws": "latest"
}, },
......
...@@ -167,3 +167,7 @@ Object.keys(Message.Type).forEach(k => { ...@@ -167,3 +167,7 @@ Object.keys(Message.Type).forEach(k => {
Message.attach = function(puppet) { Message.puppet = puppet } Message.attach = function(puppet) { Message.puppet = puppet }
module.exports = Message module.exports = Message
/*
* join room in mac client: https://support.weixin.qq.com/cgi-bin/mmsupport-bin/addchatroombyinvite?ticket=AUbv%2B4GQA1Oo65ozlIqRNw%3D%3D&exportkey=AS9GWEg4L82fl3Y8e2OeDbA%3D&lang=en&pass_ticket=T6dAZXE27Y6R29%2FFppQPqaBlNwZzw9DAN5RJzzzqeBA%3D&wechat_real_lang=en
*/
const log = require('npmlog') const log = require('npmlog')
const level = process.env.WECHATY_LOG const level = process.env.WECHATY_LOG
const levelRegexStr = 'silly|verbose|info|warn|error|silent' const levelRegexStr = 'silly|verbose|info|warn|error|silent'
const levelRegex = new RegExp(levelRegexStr, 'i') const levelRegex = new RegExp(levelRegexStr, 'i')
if (levelRegex.test(level)) { if (levelRegex.test(level)) {
log.level = level.toLowerCase() log.level = level.toLowerCase()
log.verbose('NpmLog', 'WECHATY_LOG set level to %s', level) log.verbose('NpmLog', 'WECHATY_LOG set level to %s', level)
} }
else if (level){ else if (level){
log.warn('NpmLog', 'env WECHATY_LOG(%s) must be one of silly|verbose|info|warn|error|silent', level) log.warn('NpmLog', 'env WECHATY_LOG(%s) must be one of silly|verbose|info|warn|error|silent', level)
} }
module.exports = log module.exports = log
...@@ -108,13 +108,20 @@ class Browser extends EventEmitter { ...@@ -108,13 +108,20 @@ class Browser extends EventEmitter {
// const phantomjsExe = require('phantomjs2').path // const phantomjsExe = require('phantomjs2').path
const phantomjsArgs = [ const phantomjsArgs = [
'--ignore-ssl-errors=true' // this help socket.io connect with localhost '--load-images=false'
, '--load-images=false' , '--ignore-ssl-errors=true' // this help socket.io connect with localhost
// , '--webdriver-logfile=/tmp/wd.log' , '--web-security=false' // https://github.com/ariya/phantomjs/issues/12440#issuecomment-52155299
// , '--webdriver-loglevel=DEBUG' , '--ssl-protocol=TLSv1' // https://github.com/ariya/phantomjs/issues/11239#issuecomment-42362211
// issue: Secure WebSocket(wss) do not work with Self Signed Certificate in PhantomJS #12
// , '--ssl-certificates-path=D:\\cygwin64\\home\\zixia\\git\\wechaty' // http://stackoverflow.com/a/32690349/1123955
// , '--ssl-client-certificate-file=cert.pem' //
] ]
if (process.env.WECHATY_DEBUG) { if (process.env.WECHATY_DEBUG) {
phantomjsArgs.push('--remote-debugger-port=8080') // XXX: be careful when in production usage. phantomjsArgs.push('--remote-debugger-port=8080') // XXX: be careful when in production env.
phantomjsArgs.push('--webdriver-loglevel=DEBUG')
// phantomjsArgs.push('--webdriver-logfile=webdriver.debug.log')
} }
const customPhantom = WebDriver.Capabilities.phantomjs() const customPhantom = WebDriver.Capabilities.phantomjs()
...@@ -208,22 +215,24 @@ class Browser extends EventEmitter { ...@@ -208,22 +215,24 @@ class Browser extends EventEmitter {
return return
} }
let browserRe let browserRe
switch (this.head) {
case true: switch (true) {
case 'chrome': case !this.head: // no head default to phantomjs
browserRe = 'chrome(?!driver)' case /phantomjs/i.test(this.head):
break case /phantom/i.test(this.head):
case false:
case undefined:
case null:
case 'phantomjs':
browserRe = 'phantomjs' browserRe = 'phantomjs'
break break
case this.head: // head default to chrome
case /chrome/i.test(this.head):
browserRe = 'chrome(?!driver)'
break
default: default:
log.warn('PuppetWebBrowser', 'getBrowserPids() for unsupported head: %s', this.head) log.warn('PuppetWebBrowser', 'getBrowserPids() for unsupported head: %s', this.head)
browserRe = this.head browserRe = this.head
} }
let matchRegex = new RegExp(browserRe, 'i') let matchRegex = new RegExp(browserRe, 'i')
const pids = children.filter(child => { const pids = children.filter(child => {
// https://github.com/indexzero/ps-tree/issues/18 // https://github.com/indexzero/ps-tree/issues/18
...@@ -284,6 +293,11 @@ class Browser extends EventEmitter { ...@@ -284,6 +293,11 @@ class Browser extends EventEmitter {
}) })
} }
/**
*
* check whether browser is full functional
*
*/
readyLive() { readyLive() {
log.verbose('PuppetWebBrowser', 'readyLive()') log.verbose('PuppetWebBrowser', 'readyLive()')
if (this.dead()) { if (this.dead()) {
......
...@@ -48,7 +48,7 @@ function onBrowserDead(e) { ...@@ -48,7 +48,7 @@ function onBrowserDead(e) {
// guard by variable: isBrowserBirthing to prevent the 2nd time entrance. // guard by variable: isBrowserBirthing to prevent the 2nd time entrance.
if (this.isBrowserBirthing) { if (this.isBrowserBirthing) {
if (this.isBrowserBirthing === true) { if (this.isBrowserBirthing === true) {
log.warn('PuppetWebEvent', 'onBrowserDead() Im busy, dont call me again before I return. this time will return and do nothing') log.verbose('PuppetWebEvent', 'onBrowserDead() Im busy, dont call me again before I return. this time will return and do nothing')
return return
} else { } else {
log.warn('PuppetWebEvent', 'onBrowserDead() Im FAKE busy? isBrowserBirthing is not boolean true!') log.warn('PuppetWebEvent', 'onBrowserDead() Im FAKE busy? isBrowserBirthing is not boolean true!')
...@@ -56,7 +56,7 @@ function onBrowserDead(e) { ...@@ -56,7 +56,7 @@ function onBrowserDead(e) {
} }
return co.call(this, function* () { return co.call(this, function* () {
log.warn('PuppetWebEvent', 'onBrowserDead() co() set isBrowserBirthing true') log.verbose('PuppetWebEvent', 'onBrowserDead() co() set isBrowserBirthing true')
this.isBrowserBirthing = true this.isBrowserBirthing = true
const TIMEOUT = 180000 // 180s / 3m const TIMEOUT = 180000 // 180s / 3m
...@@ -171,7 +171,10 @@ function onServerDisconnect(data) { ...@@ -171,7 +171,10 @@ function onServerDisconnect(data) {
// because the browser has just refreshed, need some time to re-init to ready. // because the browser has just refreshed, need some time to re-init to ready.
// if the browser is not ready, bridge init will fail, // if the browser is not ready, bridge init will fail,
// caused browser dead and have to be restarted. 2016/6/12 // caused browser dead and have to be restarted. 2016/6/12
setTimeout(() => { setTimeout(_ => {
if (!this.bridge) {
return // XXX: sometimes this.bridge gone in this timeout. why? what's happend between the last if(!this.bridge) check and the timeout call?
}
this.bridge.init() this.bridge.init()
.then(r => log.verbose('PuppetWebEvent', 'onServerDisconnect() bridge re-inited: %s', r)) .then(r => log.verbose('PuppetWebEvent', 'onServerDisconnect() bridge re-inited: %s', r))
.catch(e => log.error('PuppetWebEvent', 'onServerDisconnect() exception: [%s]', e)) .catch(e => log.error('PuppetWebEvent', 'onServerDisconnect() exception: [%s]', e))
......
...@@ -449,7 +449,7 @@ return (function(port) { ...@@ -449,7 +449,7 @@ return (function(port) {
} }
/*global io*/ // Wechaty global variable: socket /*global io*/ // Wechaty global variable: socket
var socket = Wechaty.vars.socket = io.connect('https://127.0.0.1:' + port) var socket = Wechaty.vars.socket = io.connect('wss://127.0.0.1:' + port/*, {transports: ['websocket']}*/)
// ding -> dong. for test & live check purpose // ding -> dong. for test & live check purpose
// ping/pong are reserved by socket.io https://github.com/socketio/socket.io/issues/2414 // ping/pong are reserved by socket.io https://github.com/socketio/socket.io/issues/2414
......
...@@ -99,7 +99,8 @@ class Server extends EventEmitter { ...@@ -99,7 +99,8 @@ class Server extends EventEmitter {
}) })
socketServer.sockets.on('connection', (s) => { socketServer.sockets.on('connection', (s) => {
log.verbose('PuppetWebServer', 'createSocketIo() got connection from browser') log.verbose('PuppetWebServer', 'createSocketIo() got connection from browser')
if (this.socketClient) { this.socketClient = null } // close() ??? // console.log(s.handshake)
if (this.socketClient) { this.socketClient = undefined } // close() ???
this.socketClient = s this.socketClient = s
this.initEventsFromClient(s) this.initEventsFromClient(s)
}) })
...@@ -112,9 +113,9 @@ class Server extends EventEmitter { ...@@ -112,9 +113,9 @@ class Server extends EventEmitter {
this.emit('connection', client) this.emit('connection', client)
client.on('disconnect', e => { client.on('disconnect', e => {
log.verbose('PuppetWebServer', 'socket.io disconnect: %s', e) log.silly('PuppetWebServer', 'socket.io disconnect: %s', e)
// 1. Browser reload / 2. Lost connection(Bad network) // 1. Browser reload / 2. Lost connection(Bad network)
this.socketClient = null this.socketClient = undefined
this.emit('disconnect', e) this.emit('disconnect', e)
}) })
......
...@@ -6,8 +6,11 @@ ...@@ -6,8 +6,11 @@
* so it will not be a security issue. * so it will not be a security issue.
* *
* http://blog.mgechev.com/2014/02/19/create-https-tls-ssl-application-with-express-nodejs/ * http://blog.mgechev.com/2014/02/19/create-https-tls-ssl-application-with-express-nodejs/
* openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 * openssl req -x509 -days 3650 -nodes -newkey rsa:2048 -keyout key.pem -out cert.pem
* openssl rsa -in key.pem -out newkey.pem && mv newkey.pem key.pem * openssl rsa -in key.pem -out newkey.pem && mv newkey.pem key.pem
*
* Reference:
* What is a Pem file - http://serverfault.com/a/9717
*/ */
const key = ` const key = `
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
......
...@@ -32,6 +32,9 @@ function onFeed({ ...@@ -32,6 +32,9 @@ function onFeed({
, timeout = 60000 // 60s default. can be override in options but be careful about the number zero(0) , timeout = 60000 // 60s default. can be override in options but be careful about the number zero(0)
} = {}) { } = {}) {
type = type || 'HEARTBEAT' // BUG compatible with issue: node-tap strange behaviour cause CircleCI & Travis-CI keep failing #11
timeout = timeout || 60000 // BUG compatible with issue: node-tap strange behaviour cause CircleCI & Travis-CI keep failing #11
if (!this) { if (!this) {
throw new Error('onFeed() must has `this` of instanceof PuppetWeb') throw new Error('onFeed() must has `this` of instanceof PuppetWeb')
} }
...@@ -66,7 +69,9 @@ function clearWatchDogTimer() { ...@@ -66,7 +69,9 @@ function clearWatchDogTimer() {
if (this.watchDogTimer) { if (this.watchDogTimer) {
clearTimeout(this.watchDogTimer) clearTimeout(this.watchDogTimer)
this.watchDogTimer = null this.watchDogTimer = null
log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() cleared')
const timeLeft = this.watchDogTimerTime - Date.now()
log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() [%d] seconds left', Math.ceil(timeLeft / 1000))
} else { } else {
log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() nothing to clear') log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() nothing to clear')
} }
...@@ -79,6 +84,7 @@ function setWatchDogTimer(timeout) { ...@@ -79,6 +84,7 @@ function setWatchDogTimer(timeout) {
log.silly('PuppetWebWatchdog', 'setWatchDogTimer(%d)', timeout) log.silly('PuppetWebWatchdog', 'setWatchDogTimer(%d)', timeout)
this.watchDogTimer = setTimeout(watchDogReset.bind(this, timeout), timeout) this.watchDogTimer = setTimeout(watchDogReset.bind(this, timeout), timeout)
this.watchDogTimerTime = Date.now() + timeout
// block quit, force to use quit() // this.watchDogTimer.unref() // dont block quit // block quit, force to use quit() // this.watchDogTimer.unref() // dont block quit
} }
......
...@@ -12,7 +12,20 @@ const PROFILE = 'unit-test-session.wechaty.json' ...@@ -12,7 +12,20 @@ const PROFILE = 'unit-test-session.wechaty.json'
const PuppetWeb = require('../../src/puppet-web') const PuppetWeb = require('../../src/puppet-web')
const Message = require('../../src/message') const Message = require('../../src/message')
test('PuppetWeb smoke testing', function(t) { test('Puppet Web Self Message Identification', function(t) {
const p = new PuppetWeb({port: PORT, head: HEAD, profile: PROFILE})
t.ok(p, 'should instantiated a PuppetWeb')
const EXPECTED_USER_ID = 'zixia'
const m = new Message()
m.set('from', EXPECTED_USER_ID)
p.userId = EXPECTED_USER_ID
t.ok(p.self(m), 'should identified self for message which from is self')
t.end()
})
false && test('PuppetWeb smoke testing', function(t) {
let pw = new PuppetWeb({port: PORT, head: HEAD, profile: PROFILE}) let pw = new PuppetWeb({port: PORT, head: HEAD, profile: PROFILE})
t.ok(pw, 'should instantiated a PuppetWeb') t.ok(pw, 'should instantiated a PuppetWeb')
...@@ -63,7 +76,6 @@ test('Puppet Web server/browser communication', function(t) { ...@@ -63,7 +76,6 @@ test('Puppet Web server/browser communication', function(t) {
yield pw.init() yield pw.init()
t.pass('should be inited') t.pass('should be inited')
log.level = 'silly'
const ret = yield dingSocket(pw.server) const ret = yield dingSocket(pw.server)
t.equal(ret, EXPECTED_DING_DATA, 'should got EXPECTED_DING_DATA after resolved dingSocket()') t.equal(ret, EXPECTED_DING_DATA, 'should got EXPECTED_DING_DATA after resolved dingSocket()')
}) })
...@@ -74,8 +86,6 @@ log.level = 'silly' ...@@ -74,8 +86,6 @@ log.level = 'silly'
.then(r => { // Finally .then(r => { // Finally
pw.quit() pw.quit()
.then(t.end) .then(t.end)
log.level = 'info'
}) })
.catch(e => { t.fail(e) }) // Exception .catch(e => { t.fail(e) }) // Exception
...@@ -83,7 +93,7 @@ log.level = 'info' ...@@ -83,7 +93,7 @@ log.level = 'info'
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
function dingSocket(server) { function dingSocket(server) {
const maxTime = 60000 // 60s const maxTime = 60000 // 60s
const waitTime = 500 const waitTime = 3000
let totalTime = 0 let totalTime = 0
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
log.verbose('TestPuppetWeb', 'dingSocket()') log.verbose('TestPuppetWeb', 'dingSocket()')
...@@ -96,7 +106,7 @@ log.level = 'info' ...@@ -96,7 +106,7 @@ log.level = 'info'
return testDing() return testDing()
function testDing() { function testDing() {
// log.silly('TestPuppetWeb', server.socketio) log.silly('TestPuppetWeb', 'dingSocket() server.socketServer: %s', server.socketServer)
if (!server.socketClient) { if (!server.socketClient) {
totalTime += waitTime totalTime += waitTime
if (totalTime > maxTime) { if (totalTime > maxTime) {
...@@ -107,7 +117,7 @@ log.level = 'info' ...@@ -107,7 +117,7 @@ log.level = 'info'
setTimeout(testDing, waitTime) setTimeout(testDing, waitTime)
return return
} }
//log.silly('TestPuppetWebServer', server.socketClient) log.silly('TestPuppetWeb', 'dingSocket() server.socketClient: %s', server.socketClient)
server.socketClient.once('dong', data => { server.socketClient.once('dong', data => {
log.verbose('TestPuppetWeb', 'socket recv event dong: ' + data) log.verbose('TestPuppetWeb', 'socket recv event dong: ' + data)
return resolve(data) return resolve(data)
...@@ -117,16 +127,3 @@ log.level = 'info' ...@@ -117,16 +127,3 @@ log.level = 'info'
}) })
} }
}) })
test('Puppet Web Self Message Identification', function(t) {
const p = new PuppetWeb({port: PORT, head: HEAD, profile: PROFILE})
t.ok(p, 'should instantiated a PuppetWeb')
const EXPECTED_USER_ID = 'zixia'
const m = new Message()
m.set('from', EXPECTED_USER_ID)
p.userId = EXPECTED_USER_ID
t.ok(p.self(m), 'should identified self for message which from is self')
t.end()
})
...@@ -21,6 +21,12 @@ test('Puppet Web watchdog timer', function(t) { ...@@ -21,6 +21,12 @@ test('Puppet Web watchdog timer', function(t) {
co(function* () { co(function* () {
const origLogLevel = log.level
if (log.level === 'info') {
log.level = 'silent'
t.pass('set log.level = silent to mute log when watchDog reset wechaty temporary')
}
yield pw.init() yield pw.init()
pw.quit() pw.quit()
// yield pw.bridge.quit() // yield pw.bridge.quit()
...@@ -34,7 +40,7 @@ test('Puppet Web watchdog timer', function(t) { ...@@ -34,7 +40,7 @@ test('Puppet Web watchdog timer', function(t) {
data: 'active_for_timeout_1ms' data: 'active_for_timeout_1ms'
, timeout: 1 , timeout: 1
}) })
yield new Promise((resolve) => setTimeout(_ => resolve(), 10)) // wait untill reset yield new Promise(resolve => setTimeout(resolve, 1000)) // wait untill reset
t.equal(errorCounter, 1, 'should get event[error] after watchdog timeout') t.equal(errorCounter, 1, 'should get event[error] after watchdog timeout')
pw.once('error', e => t.fail('waitDing() triggered watchDogReset()')) pw.once('error', e => t.fail('waitDing() triggered watchDogReset()'))
...@@ -42,12 +48,11 @@ test('Puppet Web watchdog timer', function(t) { ...@@ -42,12 +48,11 @@ test('Puppet Web watchdog timer', function(t) {
const EXPECTED_DING_DATA = 'dingdong' const EXPECTED_DING_DATA = 'dingdong'
pw.emit('watchdog', { data: 'feed to extend the dog life' }) pw.emit('watchdog', { data: 'feed to extend the dog life' })
const origLogLevel = log.level
log.level = 'silly'
t.pass('set log.level = silent to mute log when watchDog reset wechaty temporary')
const dong = yield waitDing(EXPECTED_DING_DATA) const dong = yield waitDing(EXPECTED_DING_DATA)
log.level = origLogLevel
t.equal(dong, EXPECTED_DING_DATA, 'should get EXPECTED_DING_DATA from ding after watchdog reset, and restored log level') t.equal(dong, EXPECTED_DING_DATA, 'should get EXPECTED_DING_DATA from ding after watchdog reset, and restored log level')
log.level = origLogLevel
}) })
.catch(e => { // Exception .catch(e => { // Exception
t.fail('co exception: ' + e.message) t.fail('co exception: ' + e.message)
...@@ -73,6 +78,7 @@ test('Puppet Web watchdog timer', function(t) { ...@@ -73,6 +78,7 @@ test('Puppet Web watchdog timer', function(t) {
return retryPromise({max: max, backoff: backoff}, function(attempt) { return retryPromise({max: max, backoff: backoff}, function(attempt) {
log.silly('TestPuppetWeb', 'waitDing() retryPromise: attampt %s/%s time for timeout %s' log.silly('TestPuppetWeb', 'waitDing() retryPromise: attampt %s/%s time for timeout %s'
, attempt, max, timeout) , attempt, max, timeout)
return pw.ding(data) return pw.ding(data)
.then(r => { .then(r => {
if (!r) { if (!r) {
...@@ -85,6 +91,7 @@ test('Puppet Web watchdog timer', function(t) { ...@@ -85,6 +91,7 @@ test('Puppet Web watchdog timer', function(t) {
log.verbose('TestPuppetWeb', 'waitDing() exception: %s', e.message) log.verbose('TestPuppetWeb', 'waitDing() exception: %s', e.message)
throw e throw e
}) })
}) })
.catch(e => { .catch(e => {
log.error('TestPuppetWeb', 'retryPromise() waitDing() finally FAIL: %s', e.message) log.error('TestPuppetWeb', 'retryPromise() waitDing() finally FAIL: %s', e.message)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册