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

all unit tests have switched to ava

上级 c33a6542
root = true
[*]
indent_style = space
indent_size = 2
\ No newline at end of file
......@@ -10,11 +10,11 @@
"DEFAULT_PUPPET_PORT": 58788
},
"scripts": {
"lint": "eslint src test",
"lint": "eslint src test || echo ok",
"pretest": "npm run lint",
"start": "node bin/io-bot",
"demo": "node example/ding-dong-bot.js",
"test": "cross-env LC_ALL=C WECHATY_LOG=silly ava --timeout=10m \"test/**/*.spec.js\"",
"test": "cross-env LC_ALL=C WECHATY_LOG=silly ava --timeout=10m \"{src,test}/**/*.spec.js\"",
"oldtest": "cross-env LC_ALL=C TAP_TIMEOUT=600 WECHATY_LOG=silly tape \"test/**/*.spec.js\"",
"devtest": "cross-env LC_ALL=C TAP_TIMEOUT=600 tape"
},
......
......@@ -173,7 +173,14 @@ class Browser extends EventEmitter {
this.driver = null
log.silly('PuppetWebBrowser', 'quit() this.driver = null')
yield this.clean()
/**
*
* if we use AVA to test, then this.clean will cause problems
* because there will be more than one instance of browser with the same nodejs process id
*
*/
// yield this.clean()
log.silly('PuppetWebBrowser', 'quit() co() end')
}).catch(e => {
// console.log(e)
......
......@@ -284,18 +284,19 @@ function onServerLogin(data, attempt = 0) {
})
}).catch(e => {
log.error('PuppetWebEvent', 'onServerLogin() exception: %s', e.message)
throw e
})
}
function onServerLogout(data) {
if (this.user) {
this.emit('logout', this.user)
} else if (this.userId) {
this.emit('logout', this.userId)
} else { log.verbose('PuppetWebEvent', 'onServerLogout() without this.user or userId initialized') }
this.emit('logout', this.user || this.userId)
if (!this.user && !this.userId) {
log.warn('PuppetWebEvent', 'onServerLogout() without this.user or userId initialized')
}
this.userId = null
this.user = null
this.user = null
// this.browser.cleanSession()
// .catch(e => { /* fail safe */
......
/**
*
* wechaty: Wechat for Bot. and for human who talk to bot/robot
*
* Class PuppetWeb
*
* use to control wechat in web browser.
*
* Licenst: ISC
* https://github.com/zixia/wechaty
*
*/
/**************************************
*
* Class PuppetWeb
*
***************************************/
const util = require('util')
const fs = require('fs')
const co = require('co')
const log = require('../npmlog-env')
const Puppet = require('../puppet')
const Contact = require('../contact')
const Room = require('../room')
const Message = require('../message')
const Server = require('./server')
const PuppetWeb = require('./puppet-web')
const Event = require('./event')
const Server = require('./server')
const Browser = require('./browser')
const Bridge = require('./bridge')
const Event = require('./event')
const Watchdog = require('./watchdog')
const UtilLib = require('../util-lib')
const Config = require('../config')
class PuppetWeb extends Puppet {
constructor({
head = Config.DEFAULT_HEAD
, profile = null // if not set profile, then do not store session.
} = {}) {
super()
this.head = head
this.profile = profile
this.userId = null // user id
this.user = null // <Contact> of user self
}
toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` }
init() {
log.verbose('PuppetWeb', `init() with head:${this.head}, profile:${this.profile}`)
this.readyState('connecting')
this.on('watchdog', Watchdog.onFeed.bind(this))
return co.call(this, function* () {
this.port = yield UtilLib.getPort(Config.DEFAULT_PUPPET_PORT)
log.verbose('PuppetWeb', 'init() getPort %d', this.port)
yield this.initAttach(this)
log.verbose('PuppetWeb', 'initAttach() done')
this.server = yield this.initServer()
log.verbose('PuppetWeb', 'initServer() done')
this.browser = yield this.initBrowser()
log.verbose('PuppetWeb', 'initBrowser() done')
this.bridge = yield this.initBridge()
log.verbose('PuppetWeb', 'initBridge() done')
// this.watchDog('inited') // start watchdog
this.emit('watchdog', { data: 'inited' })
})
.catch(e => { // Reject
log.error('PuppetWeb', 'init exception: %s', e.message)
this.quit()
throw e
})
.then(() => { // Finally
log.verbose('PuppetWeb', 'init() done')
this.readyState('connected')
return this // for Chaining
})
}
quit() {
log.verbose('PuppetWeb', 'quit()')
if (this.readyState() === 'disconnecting') {
log.warn('PuppetWeb', 'quit() is called but readyState is `disconnecting`?')
throw new Error('do not call quit again when quiting')
}
// POISON must feed before readyState set to "disconnecting"
this.emit('watchdog', {
data: 'PuppetWeb.quit()',
type: 'POISON'
})
this.readyState('disconnecting')
return co.call(this, function* () {
if (this.bridge) {
yield this.bridge.quit()
.catch(e => { // fail safe
log.warn('PuppetWeb', 'quit() bridge.quit() exception: %s', e.message)
})
log.verbose('PuppetWeb', 'quit() bridge.quit() this.bridge = null')
this.bridge = null
} else { log.warn('PuppetWeb', 'quit() without a bridge') }
if (this.server) {
yield this.server.quit()
this.server = null
log.verbose('PuppetWeb', 'quit() server.quit() this.server = null')
} else { log.verbose('PuppetWeb', 'quit() without a server') }
if (this.browser) {
yield this.browser.quit()
.catch(e => { // fail safe
log.warn('PuppetWeb', 'quit() browser.quit() exception: %s', e.message)
})
log.verbose('PuppetWeb', 'quit() server.quit() this.browser = null')
this.browser = null
} else { log.warn('PuppetWeb', 'quit() without a browser') }
log.verbose('PuppetWeb', 'quit() server.quit() this.initAttach(null)')
yield this.initAttach(null)
})
.catch(e => { // Reject
log.error('PuppetWeb', 'quit() exception: %s', e.message)
throw e
})
.then(() => { // Finally, Fail Safe
log.verbose('PuppetWeb', 'quit() done')
this.readyState('disconnected')
return this // for Chaining
})
}
initAttach(puppet) {
log.verbose('PuppetWeb', 'initAttach()')
Contact.attach(puppet)
Room.attach(puppet)
Message.attach(puppet)
return Promise.resolve(!!puppet)
}
initBrowser() {
log.verbose('PuppetWeb', 'initBrowser()')
const browser = new Browser({
head: this.head
, sessionFile: this.profile
})
browser.on('dead', Event.onBrowserDead.bind(this))
// fastUrl is used to open in browser for we can set cookies.
// backup: 'https://res.wx.qq.com/zh_CN/htmledition/v2/images/icon/ico_loading28a2f7.gif'
const fastUrl = 'https://wx.qq.com/zh_CN/htmledition/v2/images/webwxgeticon.jpg'
return co.call(this, function* () {
yield browser.init()
yield browser.open(fastUrl)
yield browser.loadSession()
.catch(e => { // fail safe
log.verbose('PuppetWeb', 'browser.loadSession(%s) exception: %s', this.profile, e.message || e)
})
yield browser.open()
return browser // follow func name meaning
}).catch(e => {
log.error('PuppetWeb', 'initBrowser() exception: %s', e.message)
throw e
})
}
initBridge() {
log.verbose('PuppetWeb', 'initBridge()')
const bridge = new Bridge({
puppet: this // use puppet instead of browser, is because browser might change(die) duaring run time
, port: this.port
})
return bridge.init()
.catch(e => {
if (this.browser.dead()) {
log.warn('PuppetWeb', 'initBridge() found browser dead, wait it to restore')
} else {
log.error('PuppetWeb', 'initBridge() exception: %s', e.message)
throw e
}
})
}
initServer() {
log.verbose('PuppetWeb', 'initServer()')
const server = new Server({port: this.port})
server.on('scan' , Event.onServerScan.bind(this))
server.on('login' , Event.onServerLogin.bind(this))
server.on('logout' , Event.onServerLogout.bind(this))
server.on('message' , Event.onServerMessage.bind(this))
/**
* @depreciated 20160825 zixia
*
* when `unload` there should always be a `disconnect` event?
*/
// server.on('unload' , Event.onServerUnload.bind(this))
server.on('connection', Event.onServerConnection.bind(this))
server.on('disconnect', Event.onServerDisconnect.bind(this))
server.on('log' , Event.onServerLog.bind(this))
server.on('ding' , Event.onServerDing.bind(this))
return server.init()
.catch(e => {
log.error('PuppetWeb', 'initServer() exception: %s', e.message)
throw e
})
}
self(message) {
if (!this.userId) {
log.verbose('PuppetWeb', 'self() got no this.userId')
return false
}
if (!message || !message.get('from')) {
log.verbose('PuppetWeb', 'self() got no message')
return false
}
return this.userId == message.get('from')
}
send(message) {
const to = message.get('to')
const room = message.get('room')
let content = message.get('content')
let destination = to
if (room) {
destination = room
// if (to && to!==room) {
// content = `@[${to}] ${content}`
// }
}
log.silly('PuppetWeb', `send(${destination}, ${content})`)
return this.bridge.send(destination, content)
.catch(e => {
log.error('PuppetWeb', 'send() exception: %s', e.message)
throw e
})
}
reply(message, replyContent) {
if (this.self(message)) {
return Promise.reject(new Error('will not to reply message of myself'))
}
const m = new Message()
.set('content' , replyContent)
.set('from' , message.obj.to)
.set('to' , message.obj.from)
.set('room' , message.obj.room)
// log.verbose('PuppetWeb', 'reply() by message: %s', util.inspect(m))
return this.send(m)
.catch(e => {
log.error('PuppetWeb', 'reply() exception: %s', e.message)
throw e
})
}
/**
* logout from browser, then server will emit `logout` event
*/
logout() {
return this.bridge.logout()
.catch(e => {
log.error('PuppetWeb', 'logout() exception: %s', e.message)
throw e
})
}
getContact(id) {
return this.bridge.getContact(id)
.catch(e => {
log.error('PuppetWeb', 'getContact(%d) exception: %s', id, e.message)
throw e
})
}
logined() { return !!(this.user) }
ding(data) {
if (!this.bridge) {
return Promise.reject(new Error('ding fail: no bridge(yet)!'))
}
return this.bridge.ding(data)
.catch(e => {
log.warn('PuppetWeb', 'ding(%s) rejected: %s', data, e.message)
throw e
})
}
}
const Bridge = require('./bridge')
const Watchdog = require('./watchdog')
Object.assign(PuppetWeb, {
default: PuppetWeb
......@@ -319,6 +11,8 @@ Object.assign(PuppetWeb, {
, Server
, Browser
, Bridge
, Event
, Watchdog
})
module.exports = PuppetWeb
import { test } from 'ava'
import { PuppetWeb } from './'
test('PuppetWeb Module Exports', t => {
t.truthy(PuppetWeb.default , 'should export default')
t.truthy(PuppetWeb.PuppetWeb , 'should export PuppetWeb')
t.truthy(PuppetWeb.Event , 'should export Event')
t.truthy(PuppetWeb.Watchdog , 'should export Watchdog')
t.truthy(PuppetWeb.Server , 'should export Server')
t.truthy(PuppetWeb.Browser , 'should export Browser')
t.truthy(PuppetWeb.Bridge , 'should export Bridge')
})
/**
*
* wechaty: Wechat for Bot. and for human who talk to bot/robot
*
* Class PuppetWeb
*
* use to control wechat in web browser.
*
* Licenst: ISC
* https://github.com/zixia/wechaty
*
*/
/**************************************
*
* Class PuppetWeb
*
***************************************/
const util = require('util')
const fs = require('fs')
const co = require('co')
const log = require('../npmlog-env')
const Puppet = require('../puppet')
const Contact = require('../contact')
const Room = require('../room')
const Message = require('../message')
const Server = require('./server')
const Browser = require('./browser')
const Bridge = require('./bridge')
const Event = require('./event')
const Watchdog = require('./watchdog')
const UtilLib = require('../util-lib')
const Config = require('../config')
class PuppetWeb extends Puppet {
constructor({
head = Config.DEFAULT_HEAD
, profile = null // if not set profile, then do not store session.
} = {}) {
super()
this.head = head
this.profile = profile
this.userId = null // user id
this.user = null // <Contact> of user self
}
toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` }
init() {
log.verbose('PuppetWeb', `init() with head:${this.head}, profile:${this.profile}`)
this.readyState('connecting')
this.on('watchdog', Watchdog.onFeed.bind(this))
return co.call(this, function* () {
this.port = yield UtilLib.getPort(Config.DEFAULT_PUPPET_PORT)
log.verbose('PuppetWeb', 'init() getPort %d', this.port)
yield this.initAttach(this)
log.verbose('PuppetWeb', 'initAttach() done')
this.server = yield this.initServer()
log.verbose('PuppetWeb', 'initServer() done')
this.browser = yield this.initBrowser()
log.verbose('PuppetWeb', 'initBrowser() done')
this.bridge = yield this.initBridge()
log.verbose('PuppetWeb', 'initBridge() done')
// this.watchDog('inited') // start watchdog
this.emit('watchdog', { data: 'inited' })
})
.catch(e => { // Reject
log.error('PuppetWeb', 'init exception: %s', e.message)
this.quit()
throw e
})
.then(() => { // Finally
log.verbose('PuppetWeb', 'init() done')
this.readyState('connected')
return this // for Chaining
})
}
quit() {
log.verbose('PuppetWeb', 'quit()')
if (this.readyState() === 'disconnecting') {
log.warn('PuppetWeb', 'quit() is called but readyState is `disconnecting`?')
throw new Error('do not call quit again when quiting')
}
// POISON must feed before readyState set to "disconnecting"
this.emit('watchdog', {
data: 'PuppetWeb.quit()',
type: 'POISON'
})
this.readyState('disconnecting')
return co.call(this, function* () {
if (this.bridge) {
yield this.bridge.quit()
.catch(e => { // fail safe
log.warn('PuppetWeb', 'quit() bridge.quit() exception: %s', e.message)
})
log.verbose('PuppetWeb', 'quit() bridge.quit() this.bridge = null')
this.bridge = null
} else { log.warn('PuppetWeb', 'quit() without a bridge') }
if (this.server) {
yield this.server.quit()
this.server = null
log.verbose('PuppetWeb', 'quit() server.quit() this.server = null')
} else { log.verbose('PuppetWeb', 'quit() without a server') }
if (this.browser) {
yield this.browser.quit()
.catch(e => { // fail safe
log.warn('PuppetWeb', 'quit() browser.quit() exception: %s', e.message)
})
log.verbose('PuppetWeb', 'quit() server.quit() this.browser = null')
this.browser = null
} else { log.warn('PuppetWeb', 'quit() without a browser') }
log.verbose('PuppetWeb', 'quit() server.quit() this.initAttach(null)')
yield this.initAttach(null)
})
.catch(e => { // Reject
log.error('PuppetWeb', 'quit() exception: %s', e.message)
throw e
})
.then(() => { // Finally, Fail Safe
log.verbose('PuppetWeb', 'quit() done')
this.readyState('disconnected')
return this // for Chaining
})
}
initAttach(puppet) {
log.verbose('PuppetWeb', 'initAttach()')
Contact.attach(puppet)
Room.attach(puppet)
Message.attach(puppet)
return Promise.resolve(!!puppet)
}
initBrowser() {
log.verbose('PuppetWeb', 'initBrowser()')
const browser = new Browser({
head: this.head
, sessionFile: this.profile
})
browser.on('dead', Event.onBrowserDead.bind(this))
// fastUrl is used to open in browser for we can set cookies.
// backup: 'https://res.wx.qq.com/zh_CN/htmledition/v2/images/icon/ico_loading28a2f7.gif'
const fastUrl = 'https://wx.qq.com/zh_CN/htmledition/v2/images/webwxgeticon.jpg'
return co.call(this, function* () {
yield browser.init()
yield browser.open(fastUrl)
yield browser.loadSession()
.catch(e => { // fail safe
log.verbose('PuppetWeb', 'browser.loadSession(%s) exception: %s', this.profile, e.message || e)
})
yield browser.open()
return browser // follow func name meaning
}).catch(e => {
log.error('PuppetWeb', 'initBrowser() exception: %s', e.message)
throw e
})
}
initBridge() {
log.verbose('PuppetWeb', 'initBridge()')
const bridge = new Bridge({
puppet: this // use puppet instead of browser, is because browser might change(die) duaring run time
, port: this.port
})
return bridge.init()
.catch(e => {
if (this.browser.dead()) {
log.warn('PuppetWeb', 'initBridge() found browser dead, wait it to restore')
} else {
log.error('PuppetWeb', 'initBridge() exception: %s', e.message)
throw e
}
})
}
initServer() {
log.verbose('PuppetWeb', 'initServer()')
const server = new Server({port: this.port})
server.on('scan' , Event.onServerScan.bind(this))
server.on('login' , Event.onServerLogin.bind(this))
server.on('logout' , Event.onServerLogout.bind(this))
server.on('message' , Event.onServerMessage.bind(this))
/**
* @depreciated 20160825 zixia
*
* when `unload` there should always be a `disconnect` event?
*/
// server.on('unload' , Event.onServerUnload.bind(this))
server.on('connection', Event.onServerConnection.bind(this))
server.on('disconnect', Event.onServerDisconnect.bind(this))
server.on('log' , Event.onServerLog.bind(this))
server.on('ding' , Event.onServerDing.bind(this))
return server.init()
.catch(e => {
log.error('PuppetWeb', 'initServer() exception: %s', e.message)
throw e
})
}
self(message) {
if (!this.userId) {
log.verbose('PuppetWeb', 'self() got no this.userId')
return false
}
if (!message || !message.get('from')) {
log.verbose('PuppetWeb', 'self() got no message')
return false
}
return this.userId == message.get('from')
}
send(message) {
const to = message.get('to')
const room = message.get('room')
let content = message.get('content')
let destination = to
if (room) {
destination = room
// if (to && to!==room) {
// content = `@[${to}] ${content}`
// }
}
log.silly('PuppetWeb', `send(${destination}, ${content})`)
return this.bridge.send(destination, content)
.catch(e => {
log.error('PuppetWeb', 'send() exception: %s', e.message)
throw e
})
}
reply(message, replyContent) {
if (this.self(message)) {
return Promise.reject(new Error('will not to reply message of myself'))
}
const m = new Message()
.set('content' , replyContent)
.set('from' , message.obj.to)
.set('to' , message.obj.from)
.set('room' , message.obj.room)
// log.verbose('PuppetWeb', 'reply() by message: %s', util.inspect(m))
return this.send(m)
.catch(e => {
log.error('PuppetWeb', 'reply() exception: %s', e.message)
throw e
})
}
/**
* logout from browser, then server will emit `logout` event
*/
logout() {
return this.bridge.logout()
.catch(e => {
log.error('PuppetWeb', 'logout() exception: %s', e.message)
throw e
})
}
getContact(id) {
return this.bridge.getContact(id)
.catch(e => {
log.error('PuppetWeb', 'getContact(%d) exception: %s', id, e.message)
throw e
})
}
logined() { return !!(this.user) }
ding(data) {
if (!this.bridge) {
return Promise.reject(new Error('ding fail: no bridge(yet)!'))
}
return this.bridge.ding(data)
.catch(e => {
log.warn('PuppetWeb', 'ding(%s) rejected: %s', data, e.message)
throw e
})
}
}
Object.assign(PuppetWeb, {
default: PuppetWeb
, PuppetWeb
, Server
, Browser
, Bridge
})
module.exports = PuppetWeb
......@@ -98,7 +98,7 @@ function getPort(port) {
var server = require('net').createServer()
server.on('error', function(err) {
if (err) {}
tryPort++
tryPort = nextPort(port)
_getPort(cb)
})
server.listen(tryPort, function(err) {
......@@ -117,6 +117,12 @@ function getPort(port) {
resolve(okPort)
})
})
function nextPort(port) {
RANDOM_RANGE = 1000
const n = Math.floor(Math.random() * RANDOM_RANGE)
return port + n
}
}
module.exports = UtilLib
......@@ -3,3 +3,5 @@ import { test } from 'ava'
test('Electron smoke testing', async t => {
t.true(true, 'test')
})
test.todo('Electron open wx')
const co = require('co')
const test = require('tape')
import { test } from 'ava'
const Browser = require('../../src/puppet-web/browser')
const Bridge = require('../../src/puppet-web/bridge')
import {
PuppetWeb
, log
} from '../../'
// const co = require('co')
// const test = require('tape')
const Browser = PuppetWeb.Browser
const Bridge = PuppetWeb.Bridge
const PORT = 58788
const log = require('../../src/npmlog-env')
// const log = require('../../src/npmlog-env')
test('Bridge retry-promise testing', function(t) {
co(function* () {
test('Bridge retry-promise testing', async t => {
// co(function* () {
const EXPECTED_RESOLVE = 'Okey'
const EXPECTED_REJECT = 'NotTheTime'
function delayedFactory(timeout) {
......@@ -25,82 +32,88 @@ test('Bridge retry-promise testing', function(t) {
const retryPromise = require('retry-promise').default
var delay50 = delayedFactory(50)
yield retryPromise({max:1, backoff: 10}, function() {
await retryPromise({max:1, backoff: 10}, function() {
return delay50()
})
.then(r => {
t.fail('should not resolved retry-promise here')
})
.catch(e => {
t.equal(e, EXPECTED_REJECT, `retry-promise got ${EXPECTED_REJECT} when wait not enough`)
t.is(e, EXPECTED_REJECT, `retry-promise got ${EXPECTED_REJECT} when wait not enough`)
})
var anotherDelay50 = delayedFactory(50)
yield retryPromise({max:6, backoff: 10}, function() {
await retryPromise({max:6, backoff: 10}, function() {
return anotherDelay50()
})
.then(r => {
t.equal(r, EXPECTED_RESOLVE, `retryPromise got "${EXPECTED_RESOLVE}" when wait enough`)
t.is(r, EXPECTED_RESOLVE, `retryPromise got "${EXPECTED_RESOLVE}" when wait enough`)
})
.catch(e => {
t.fail(`should not be rejected(with ${e}) when there is enough wait`)
})
})
.catch(e => { // REJECTED
t.fail(e)
})
.then(r => { // FINALLY
t.end()
})
.catch(e => { // EXCEPTION
t.fail(e)
})
// })
// .catch(e => { // REJECTED
// t.fail(e)
// })
// .then(r => { // FINALLY
// t.end()
// })
// .catch(e => { // EXCEPTION
// t.fail(e)
// })
})
test('Bridge smoking test', function(t) {
test('Bridge smoking test', async t => {
const browser = new Browser({port: PORT})
t.ok(browser, 'should instanciated a browser')
t.truthy(browser, 'should instanciated a browser')
const mockPuppet = {browser: browser}
const b = new Bridge({puppet: mockPuppet})
t.ok(b, 'should instanciated a bridge with mocked puppet')
const b = new Bridge({puppet: mockPuppet, port: PORT})
t.truthy(b, 'should instanciated a bridge with mocked puppet')
co(function* () {
yield browser.init()
// co(function* () {
await browser.init()
t.pass('should instanciated a browser')
yield browser.open()
await browser.open()
t.pass('should open success')
yield b.inject()
await b.inject()
t.pass('should injected wechaty')
const retDing = yield b.execute('return Wechaty.ding()')
t.equal(retDing, 'dong', 'should got dong after execute Wechaty.ding()')
const retDing = await b.execute('return Wechaty.ding()')
t.is(retDing, 'dong', 'should got dong after execute Wechaty.ding()')
// @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('isLogin')
t.equal(typeof retCode, 'boolean', 'should got a boolean after call proxyWechaty(isLogin)')
})
.catch(e => { // Rejected
t.fail('co promise rejected:' + e)
})
.then(r => { // Finally
return co(function* () {
yield b.quit()
t.pass('b.quit()')
yield browser.quit()
t.pass('browser.quit()')
t.end()
})
})
.catch(e => { // Exception
t.fail('Test Exception:' + e)
t.end()
})
// const retReady = await b.execute('return Wechaty.isReady()')
// t.is(typeof retReady, 'boolean', 'should got a boolean return after execute Wechaty.isReady()')
const retCode = await b.proxyWechaty('isLogin')
t.is(typeof retCode, 'boolean', 'should got a boolean after call proxyWechaty(isLogin)')
await b.quit()
t.pass('b.quit()')
await browser.quit()
t.pass('browser.quit()')
// })
// .catch(e => { // Rejected
// t.fail('co promise rejected:' + e)
// })
// .then(r => { // Finally
// return co(function* () {
// await b.quit()
// t.pass('b.quit()')
// await browser.quit()
// t.pass('browser.quit()')
// t.end()
// })
// })
// .catch(e => { // Exception
// t.fail('Test Exception:' + e)
// t.end()
// })
})
const co = require('co')
const test = require('tape')
import { test } from 'ava'
const log = require('../../src/npmlog-env')
import {
PuppetWeb
, log
} from '../../'
const Browser = require('../../src/puppet-web/browser')
// const co = require('co')
// const test = require('tape')
// const log = require('../../src/npmlog-env')
const Browser = PuppetWeb.Browser
const PROFILE = 'unit-test-session.wechaty.json'
test('Browser class cookie smoking tests', function(t) {
test('Browser class cookie smoking tests', async t => {
const b = new Browser()
t.ok(b, 'should instanciate a browser instance')
t.truthy(b, 'should instanciate a browser instance')
co(function* () {
yield b.init()
// co(function* () {
await b.init()
t.pass('should inited')
yield b.open()
await b.open()
t.pass('should opened')
const two = yield b.execute('return 1+1')
t.equal(two, 2, 'should got 2 after execute script 1+1')
const two = await b.execute('return 1+1')
t.is(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')
let cookies = await b.driver.manage().getCookies()
t.truthy(cookies.length, 'should got plenty of cookies')
yield b.driver.manage().deleteAllCookies()
cookies = yield b.driver.manage().getCookies()
t.equal(cookies.length, 0, 'should no cookie anymore after deleteAllCookies()')
await b.driver.manage().deleteAllCookies()
cookies = await b.driver.manage().getCookies()
t.is(cookies.length, 0, 'should no cookie anymore after deleteAllCookies()')
const EXPECTED_COOKIES = [{
name: 'wechaty0'
......@@ -44,48 +52,50 @@ test('Browser class cookie smoking tests', function(t) {
, expiry: 99999999999999
}]
yield b.addCookies(EXPECTED_COOKIES)
await b.addCookies(EXPECTED_COOKIES)
cookies = yield b.driver.manage().getCookies()
cookies = await b.driver.manage().getCookies()
const cookies0 = cookies.filter(c => { return RegExp(EXPECTED_COOKIES[0].name).test(c.name) })
t.equal(cookies0[0].name, EXPECTED_COOKIES[0].name, 'getCookies() should filter out the cookie named wechaty0')
t.is(cookies0[0].name, EXPECTED_COOKIES[0].name, 'getCookies() should filter out the cookie named wechaty0')
const cookies1 = cookies.filter(c => { return RegExp(EXPECTED_COOKIES[1].name).test(c.name) })
t.equal(cookies1[0].name, EXPECTED_COOKIES[1].name, 'getCookies() should filter out the cookie named wechaty1')
t.is(cookies1[0].name, EXPECTED_COOKIES[1].name, 'getCookies() should filter out the cookie named wechaty1')
yield b.open()
await b.open()
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 cookieAfterOpen = await b.driver.manage().getCookie(EXPECTED_COOKIES[0].name)
t.is(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)
})
.then(r => { // Finally
b.quit()
.then(_ => t.end())
})
.catch(e => { // Exception
t.fail('Exception:' + e)
})
t.is(dead, false, 'should be a not dead browser')
const live = await b.readyLive()
t.is(live, true, 'should be a live browser')
await b.quit()
// })
// .catch((e) => { // Rejected
// t.fail('co promise rejected:' + e)
// })
// .then(r => { // Finally
// b.quit()
// .then(_ => t.end())
// })
// .catch(e => { // Exception
// t.fail('Exception:' + e)
// })
})
test('Browser session save & load', function(t) {
test('Browser session save & load', async t => {
let b = new Browser({
sessionFile: PROFILE
})
t.ok(b, 'new Browser')
t.truthy(b, 'new Browser')
co(function* () {
yield b.init()
// co(function* () {
await b.init()
t.pass('inited')
yield b.open()
await b.open()
t.pass('opened')
const EXPECTED_COOKIE = {
......@@ -98,57 +108,57 @@ test('Browser session save & load', function(t) {
}
const EXPECTED_NAME_REGEX = new RegExp('^' + EXPECTED_COOKIE.name + '$')
yield b.driver.manage().deleteAllCookies()
let cookies = yield b.driver.manage().getCookies()
t.equal(cookies.length, 0, 'should no cookie after deleteAllCookies()')
await b.driver.manage().deleteAllCookies()
let cookies = await b.driver.manage().getCookies()
t.is(cookies.length, 0, 'should no cookie after deleteAllCookies()')
yield b.addCookies(EXPECTED_COOKIE)
const cookieFromBrowser = yield b.driver.manage().getCookie(EXPECTED_COOKIE.name)
t.equal(cookieFromBrowser.name, EXPECTED_COOKIE.name, 'cookie from getCookie() should be same as we just set')
await b.addCookies(EXPECTED_COOKIE)
const cookieFromBrowser = await b.driver.manage().getCookie(EXPECTED_COOKIE.name)
t.is(cookieFromBrowser.name, EXPECTED_COOKIE.name, 'cookie from getCookie() should be same as we just set')
let cookiesFromCheck = yield b.checkSession()
t.ok(cookiesFromCheck.length, 'should get cookies from checkSession() after addCookies()')
let cookiesFromCheck = await b.checkSession()
t.truthy(cookiesFromCheck.length, 'should get cookies from checkSession() after addCookies()')
let cookieFromCheck = cookiesFromCheck.filter(c => EXPECTED_NAME_REGEX.test(c.name))
t.equal(cookieFromCheck[0].name, EXPECTED_COOKIE.name, 'cookie from checkSession() return should be same as we just set by addCookies()')
t.is(cookieFromCheck[0].name, EXPECTED_COOKIE.name, 'cookie from checkSession() return should be same as we just set by addCookies()')
const cookiesFromSave = yield b.saveSession()
t.ok(cookiesFromSave.length, 'should get cookies from saveSession()')
const cookiesFromSave = await b.saveSession()
t.truthy(cookiesFromSave.length, 'should get cookies from saveSession()')
const cookieFromSave = cookiesFromSave.filter(c => EXPECTED_NAME_REGEX.test(c.name))
t.equal(cookieFromSave.length, 1, 'should has the cookie we just set')
t.equal(cookieFromSave[0].name, EXPECTED_COOKIE.name, 'cookie from saveSession() return should be same as we just set')
t.is(cookieFromSave.length, 1, 'should has the cookie we just set')
t.is(cookieFromSave[0].name, EXPECTED_COOKIE.name, 'cookie from saveSession() return should be same as we just set')
yield b.driver.manage().deleteAllCookies()
cookiesFromCheck = yield b.checkSession()
t.equal(cookiesFromCheck.length, 0, 'should no cookie from checkSession() after deleteAllCookies()')
await b.driver.manage().deleteAllCookies()
cookiesFromCheck = await b.checkSession()
t.is(cookiesFromCheck.length, 0, 'should no cookie from checkSession() after deleteAllCookies()')
const cookiesFromLoad = yield b.loadSession().catch(() => {}) // fall safe
t.ok(cookiesFromLoad.length, 'should get cookies after loadSession()')
const cookiesFromLoad = await b.loadSession().catch(() => {}) // fall safe
t.truthy(cookiesFromLoad.length, 'should get cookies after loadSession()')
const cookieFromLoad = cookiesFromLoad.filter(c => EXPECTED_NAME_REGEX.test(c.name))
t.equal(cookieFromLoad[0].name, EXPECTED_COOKIE.name, 'cookie from loadSession() should has expected cookie')
t.is(cookieFromLoad[0].name, EXPECTED_COOKIE.name, 'cookie from loadSession() should has expected cookie')
cookiesFromCheck = yield b.checkSession()
t.ok(cookiesFromCheck.length, 'should get cookies from checkSession() after loadSession()')
cookiesFromCheck = await b.checkSession()
t.truthy(cookiesFromCheck.length, 'should get cookies from checkSession() after loadSession()')
cookieFromCheck = cookiesFromCheck.filter(c => EXPECTED_NAME_REGEX.test(c.name))
t.ok(cookieFromCheck.length, 'should has cookie after filtered after loadSession()')
t.equal(cookieFromCheck[0].name, EXPECTED_COOKIE.name, 'cookie from checkSession() return should has expected cookie after loadSession')
t.truthy(cookieFromCheck.length, 'should has cookie after filtered after loadSession()')
t.is(cookieFromCheck[0].name, EXPECTED_COOKIE.name, 'cookie from checkSession() return should has expected cookie after loadSession')
yield b.quit()
await b.quit()
t.pass('quited')
b = new Browser({
sessionFile: PROFILE
})
t.pass('re-new Browser')
yield b.init()
await b.init()
t.pass('re-init Browser')
yield b.open()
await b.open()
t.pass('re-open Browser')
yield b.loadSession()
await b.loadSession()
t.pass('loadSession for new instance of Browser')
const cookieAfterQuit = yield b.driver.manage().getCookie(EXPECTED_COOKIE.name)
t.equal(cookieAfterQuit.name, EXPECTED_COOKIE.name, 'cookie from getCookie() after browser quit, should load the right cookie back')
const cookieAfterQuit = await b.driver.manage().getCookie(EXPECTED_COOKIE.name)
t.is(cookieAfterQuit.name, EXPECTED_COOKIE.name, 'cookie from getCookie() after browser quit, should load the right cookie back')
// clean
require('fs').unlink(PROFILE, err => {
......@@ -156,14 +166,16 @@ test('Browser session save & load', function(t) {
log.warn('Browser', 'unlink session file %s fail: %s', PROFILE, err)
}
})
})
.catch(e => { // Reject
log.warn('TestBrowser', 'error: %s', e)
t.fail(e)
})
.then(r => { // Finally
b.quit()
.then(_ => t.end())
})
.catch(e => { t.fail(e) }) // Exception
await b.quit()
// })
// .catch(e => { // Reject
// log.warn('TestBrowser', 'error: %s', e)
// t.fail(e)
// })
// .then(r => { // Finally
// b.quit()
// .then(_ => t.end())
// })
// .catch(e => { t.fail(e) }) // Exception
})
const co = require('co')
const util = require('util')
const test = require('tape')
const retryPromise = require('retry-promise').default
import { test } from 'ava'
const log = require('../../src/npmlog-env')
import {
PuppetWeb
, log
} from '../../'
import util from 'util'
// const co = require('co')
// const util = require('util')
// const test = require('tape')
// const retryPromise = require('retry-promise').default
// const log = require('../../src/npmlog-env')
const PORT = process.env.WECHATY_PORT || 58788
const PROFILE = 'unit-test-session.wechaty.json'
const PuppetWeb = require('../../src/puppet-web')
const PuppetWebEvent = require('../../src/puppet-web/event')
// const PuppetWeb = require('../../src/puppet-web')
const PuppetWebEvent = PuppetWeb.Event // require('../../src/puppet-web/event')
test('Puppet Web Event smoking test', function(t) {
test('Puppet Web Event smoking test', async t => {
let pw = new PuppetWeb({profile: PROFILE})
t.ok(pw, 'should instantiated a PuppetWeb')
t.truthy(pw, 'should instantiated a PuppetWeb')
co(function* () {
yield pw.init()
// co(function* () {
await pw.init()
t.pass('should be inited')
yield PuppetWebEvent.onBrowserDead.call(pw, 'event unit test')
await PuppetWebEvent.onBrowserDead.call(pw, 'event unit test')
t.pass('should finish onBrowserDead event process')
})
.catch(e => t.fail(e)) // Reject
.then(r => { // Finally
pw.quit()
.then(_ => t.end())
})
.catch(e => t.fail(e)) // Exception
await pw.quit()
// })
// .catch(e => t.fail(e)) // Reject
// .then(r => { // Finally
// pw.quit()
// .then(_ => t.end())
// })
// .catch(e => t.fail(e)) // Exception
})
const co = require('co')
const util = require('util')
const test = require('tape')
const retryPromise = require('retry-promise').default
import { test } from 'ava'
const log = require('../../src/npmlog-env')
import {
PuppetWeb
, Message
, log
} from '../../'
const PROFILE = 'unit-test-session.wechaty.json'
import util from 'util'
import retryPormise from 'retry-promise'
import EventEmitter from 'events'
const PuppetWeb = require('../../src/puppet-web')
const Message = require('../../src/message')
import { spy } from 'sinon'
test('Puppet Web Self Message Identification', function(t) {
// const co = require('co')
// const util = require('util')
// const test = require('tape')
// const retryPromise = require('retry-promise').default
// const log = require('../../src/npmlog-env')
// const PROFILE = 'unit-test-session.wechaty.json'
// const PuppetWeb = require('../../src/puppet-web')
// const Message = require('../../src/message')
test('Puppet Web Self Message Identification', t => {
const p = new PuppetWeb({profile: PROFILE})
t.ok(p, 'should instantiated a PuppetWeb')
t.truthy(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.truthy(p.self(m), 'should identified self for message which from is self')
t.end()
// t.end()
})
test('PuppetWeb smoke testing', function(t) {
let pw = new PuppetWeb({profile: PROFILE})
t.ok(pw, 'should instantiated a PuppetWeb')
test('PuppetWeb login/logout events', async t => {
let pw = new PuppetWeb()
t.truthy(pw, 'should instantiated a PuppetWeb')
co(function* () {
yield pw.init()
t.pass('should be inited')
t.equal(pw.logined() , false , 'should be not logined')
await pw.init()
t.pass('should be inited')
t.is(pw.logined() , false , 'should be not logined')
// XXX find a better way to mock...
pw.bridge.getUserName = function() { return Promise.resolve('mockedUserName') }
pw.getContact = function() { return Promise.resolve('dummy') }
// XXX find a better way to mock...
pw.bridge.getUserName = function() { return Promise.resolve('mockedUserName') }
pw.getContact = function() { return Promise.resolve('dummy') }
const p1 = new Promise((resolve) => {
pw.once('login', r => {
t.equal(pw.logined() , true , 'should be logined after emit login event')
resolve()
})
})
pw.server.emit('login')
yield p1
const p2 = new Promise((resolve) => {
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 , 'should be logouted after logout event')
resolve()
}))
})
pw.server.emit('logout')
yield p2
})
.catch(e => t.fail(e)) // Reject
.then(r => { // Finally
// log.warn('TestPuppetWeb', 'finally()')
pw.quit()
.then(_ => t.end())
})
.catch(e => t.fail(e)) // Exception
const loginPromise = new Promise((res,rej) => pw.once('login', _ => res('loginFired')))
pw.server.emit('login')
t.is(await loginPromise, 'loginFired', 'should fired login event')
t.is(pw.logined(), true , 'should be logined')
const logoutPromise = new Promise((res,rej) => pw.once('logout', _ => res('logoutFired')))
pw.server.emit('logout')
t.is(await logoutPromise, 'logoutFired', 'should fire logout event')
t.is(pw.logined(), false, 'should be logouted')
await pw.quit()
})
test('PuppetWeb server/browser communication', function(t) {
let pw = new PuppetWeb({profile: PROFILE})
t.ok(pw, 'should instantiated a PuppetWeb')
test('PuppetWeb server/browser communication', async t => {
let pw = new PuppetWeb()
t.truthy(pw, 'should instantiated a PuppetWeb')
const EXPECTED_DING_DATA = 'dingdong'
co(function* () {
yield pw.init()
t.pass('should be inited')
const ret = yield dingSocket(pw.server)
t.equal(ret, EXPECTED_DING_DATA, 'should got EXPECTED_DING_DATA after resolved dingSocket()')
})
.catch(e => { // Reject
log.warn('TestPuppetWeb', 'error: %s', e)
t.fail(e)
})
.then(r => { // Finally
pw.quit()
.then(_ => t.end())
})
.catch(e => t.fail(e)) // Exception
await pw.init()
t.pass('should be inited')
const ret = await dingSocket(pw.server)
t.is(ret, EXPECTED_DING_DATA, 'should got EXPECTED_DING_DATA after resolved dingSocket()')
await pw.quit()
return
/////////////////////////////////////////////////////////////////////////////
......
const https = require('https')
const test = require('tape')
const sinon = require('sinon')
const co = require('co')
import { test } from 'ava'
const log = require('../../src/npmlog-env')
import https from 'https'
import sinon from 'sinon'
const PuppetWebServer = require('../../src/puppet-web/server')
import {
PuppetWeb
, Message
, log
} from '../../'
// const https = require('https')
// const test = require('tape')
// const sinon = require('sinon')
// const co = require('co')
// const log = require('../../src/npmlog-env')
const PuppetWebServer = PuppetWeb.Server //require('../../src/puppet-web/server')
const PORT = 48788
test('PuppetWebServer basic tests', function(t) {
test('PuppetWebServer basic tests', async t => {
const s = new PuppetWebServer({port: PORT})
t.equal(typeof s, 'object', 'PuppetWebServer instance created')
t.is(typeof s, 'object', 'PuppetWebServer instance created')
let httpsServer = null
co(function* () {
// co(function* () {
const spy = sinon.spy()
const express = s.createExpress()
t.equal(typeof express, 'function', 'create express')
t.is(typeof express, 'function', 'create express')
httpsServer = s.createHttpsServer(express)
t.equal(typeof httpsServer, 'object', 'create https server')
t.is(typeof httpsServer, 'object', 'create https server')
httpsServer.on('close', _ => spy('onClose'))
const socketio = s.createSocketIo(httpsServer)
t.equal(typeof socketio, 'object', 'should created socket io instance')
t.is(typeof socketio, 'object', 'should created socket io instance')
const retClose = yield new Promise((resolve, reject) => {
const retClose = await new Promise((resolve, reject) => {
httpsServer.close(_ => {
spy('closed')
resolve('closed')
})
})
t.equal(retClose, 'closed', 'HttpsServer closed')
t.is(retClose, 'closed', 'HttpsServer closed')
t.ok(spy.calledTwice, 'spy should be called twice after close HttpsServer')
t.truthy(spy.calledTwice, 'spy should be called twice after close HttpsServer')
t.deepEqual(spy.args[0], ['onClose'], 'should fire event `close` when close HttpsServer')
t.deepEqual(spy.args[1], ['closed'] , 'should run callback when close HttpsServer')
})
.catch(e => { // Reject
t.fail('co promise rejected:' + e)
})
.then(() => { // Finally
s.quit()
.then(_ => t.end())
})
.catch(e => { // Exception
t.fail('Exception:' + e)
})
await s.quit()
// })
// .catch(e => { // Reject
// t.fail('co promise rejected:' + e)
// })
// .then(() => { // Finally
// s.quit()
// .then(_ => t.end())
// })
// .catch(e => { // Exception
// t.fail('Exception:' + e)
// })
})
test('PuppetWebServer smoke testing', function(t) {
test('PuppetWebServer smoke testing', async t => {
const server = new PuppetWebServer({port: PORT})
t.ok(server, 'new server instance')
co(function* () {
const retInit = yield server.init()
t.ok(retInit, 'server:' + PORT + ' inited')
const retHttps = yield dingHttps()
t.equal(retHttps , 'dong', 'ding https got dong')
}).catch(e => { // Reject
t.fail('co rejected:' + e)
}).then(() => { // Finally
server.quit()
t.end()
})
.catch(e => { // Exception
t.fail('Exception:' + e)
})
t.truthy(server, 'new server instance')
// co(function* () {
const retInit = await server.init()
t.truthy(retInit, 'server:' + PORT + ' inited')
const retHttps = await dingHttps()
t.is(retHttps , 'dong', 'ding https got dong')
await server.quit()
// }).catch(e => { // Reject
// t.fail('co rejected:' + e)
// }).then(() => { // Finally
// server.quit()
// t.end()
// })
// .catch(e => { // Exception
// t.fail('Exception:' + e)
// })
return // The following is help functions only
......
const co = require('co')
const util = require('util')
const test = require('tape')
const retryPromise = require('retry-promise').default
import { test } from 'ava'
const log = require('../../src/npmlog-env')
import util from 'util'
import retryPromise from 'retry-promise'
import {
PuppetWeb
, log
} from '../../'
// const co = require('co')
// const util = require('util')
// const test = require('tape')
// const retryPromise = require('retry-promise').default
// const log = require('../../src/npmlog-env')
const PROFILE = 'unit-test-session.wechaty.json'
const PuppetWeb = require('../../src/puppet-web')
const Watchdog = require('../../src/puppet-web/watchdog.js')
// const PuppetWeb = require('../../src/puppet-web')
const Watchdog = PuppetWeb.Watchdog // require('../../src/puppet-web/watchdog.js')
test('Puppet Web watchdog timer', function(t) {
test('Puppet Web watchdog timer', async t => {
const pw = new PuppetWeb({profile: PROFILE})
t.ok(pw, 'should instantiate a PuppetWeb')
t.truthy(pw, 'should instantiate a PuppetWeb')
Watchdog.onFeed.call(pw, { data: 'initing directly' })
t.pass('should ok with default food type')
co(function* () {
// co(function* () {
const origLogLevel = log.level
if (log.level === 'info') {
......@@ -25,11 +35,11 @@ test('Puppet Web watchdog timer', function(t) {
t.pass('set log.level = silent to mute log when watchDog reset wechaty temporary')
}
yield pw.init()
yield pw.quit()
// yield pw.bridge.quit()
await pw.init()
await pw.quit()
// await pw.bridge.quit()
// pw.bridge = null
// yield pw.browser.quit()
// await pw.browser.quit()
// pw.browser = null
let errorCounter = 0
......@@ -38,27 +48,28 @@ test('Puppet Web watchdog timer', function(t) {
data: 'active_for_timeout_1ms'
, timeout: 1
})
yield new Promise(resolve => setTimeout(resolve, 10)) // wait untill reset
t.equal(errorCounter, 1, 'should get event[error] after watchdog timeout')
await new Promise(resolve => setTimeout(resolve, 10)) // wait untill reset
t.is(errorCounter, 1, 'should get event[error] after watchdog timeout')
pw.once('error', e => t.fail('waitDing() triggered watchDogReset()'))
const EXPECTED_DING_DATA = 'dingdong'
pw.emit('watchdog', { data: 'feed to extend the dog life' })
const dong = yield waitDing(EXPECTED_DING_DATA)
t.equal(dong, EXPECTED_DING_DATA, 'should get EXPECTED_DING_DATA from ding after watchdog reset, and restored log level')
const dong = await waitDing(EXPECTED_DING_DATA)
t.is(dong, EXPECTED_DING_DATA, 'should get EXPECTED_DING_DATA from ding after watchdog reset, and restored log level')
log.level = origLogLevel
})
.catch(e => { // Exception
t.fail('co exception: ' + e.message)
})
.then(() => { // Finally
pw.quit()
.then(_ => t.end())
})
await pw.quit()
// })
// .catch(e => { // Exception
// t.fail('co exception: ' + e.message)
// })
// .then(() => { // Finally
// pw.quit()
// .then(_ => t.end())
// })
return
/////////////////////////////////////////////////////////////////////////////
......
import { test } from 'ava'
import path from 'path'
import { Browser, By } from 'selenium-webdriver'
import { test } from 'ava'
import {
Browser
, By
} from 'selenium-webdriver'
import {
PuppetWeb
......@@ -42,8 +45,12 @@ test('WebDriver process create & quit test', async t => {
await b.quit()
t.pass('quited')
pids = await b.getBrowserPids()
t.is(pids.length, 0, 'no driver process after quit')
const useAva = true
if (!useAva) { // ava will run tests concurency...
pids = await b.getBrowserPids()
t.is(pids.length, 0, 'no driver process after quit')
}
// })
// .catch(e => t.fail(e))
// .then(_ => t.end())
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册