siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
320 lines (247 loc) • 10.3 kB
text/typescript
import * as types from '../../../generic/simulator/Types'
import {ServerEndPoint} from "../../channel/websocket/ServerEndPoint.js";
import {Envelop, Message} from "../../../generic/channel/Types.js";
import {SimulatorCommand} from "../../../generic/simulator/Commands.js";
import * as commands from "../../../generic/simulator/Commands.js";
import {filterPathAccordingToPrecision, getPathBetweenPoints, Point} from "../../../generic/util/Point.js";
import {delay} from "../../../generic/util/helper/Delay.js";
export class SimulatorServerPuppeteer extends ServerEndPoint {
page : any
mouseMovePrecision : number = 3
// see https://github.com/octalmage/robotjs/issues/347
afterActionDelay : number = 100
currentPosition : Point = [ 0, 0 ]
doProcessRawChannelMessage (message : Message, envelop : Envelop) {
}
doDispatchEnvelop (envelop : Envelop) {
const cmd = envelop.payload as SimulatorCommand
let action
switch (cmd.type) {
case 'move_pointer' : action = this.doMovePointer(<commands.MovePointer>cmd); break
case 'set_pointer_state' : action = this.doSetPointerState(<commands.SetPointerState>cmd); break
case 'pointer_click' : action = this.doPointerClick(<commands.PointerClick>cmd); break
case 'pointer_double_click' : action = this.doPointerDoubleClick(<commands.PointerDoubleClick>cmd); break
case 'mouse_wheel' : action = this.doMouseWheel(<commands.MouseWheel>cmd); break
case 'pointer_drag' : action = this.doPointerDrag(<commands.PointerDrag>cmd); break
case 'type' : action = this.doType(<commands.Type>cmd); break
case 'reset' : action = this.doSimulationReset(<commands.Reset>cmd); break
case 'set_key_state' : action = this.doSetKeyState(<commands.SetKeyState>cmd); break
}
action.then(result => this.replyWith(envelop, result), reason => {
this.info("Simulator command failed: " + reason)
this.replyWith(envelop, reason, true)
})
}
pageMouseMove (x, y, options = undefined) : Promise<any> {
return this.page.mouse.move(x, y, options).then(() => {
this.currentPosition[ 0 ] = x
this.currentPosition[ 1 ] = y
})
}
siestaKeyToSimulatorKey (key : string) : string {
if (key.length == 1) return key
return siestaToPuppeteerKeys[ key ]
}
toggleModifierKeys (modifierKeys : types.ModifierKey[] = [], state : types.KeyState) : Promise<any> {
let cont = Promise.resolve()
modifierKeys.forEach(modKey => {
cont = cont.then(() => this.page.keyboard[ state ](this.siestaKeyToSimulatorKey(modKey)))
})
return cont
}
doWithModifierKeys (action : Function, modifierKeys : types.ModifierKey[] = []) : Promise<any> {
return this.toggleModifierKeys(modifierKeys, types.KeyState.Down).then(
() => action()
).then(
() => this.toggleModifierKeys(modifierKeys, types.KeyState.Up)
)
}
doMovePointer (command : commands.MovePointer) : Promise<any> {
return this.doWithModifierKeys(() => {
if (command.moveKind == 'instant')
return this.page.mouse.move(command.x, command.y)
else if (command.moveKind == 'smooth') {
return this.moveMouseTo(command.x, command.y, command.mouseMovePrecision)
}
}, command.modifierKey)
}
doSetPointerState (command : commands.SetPointerState) : Promise<any> {
return this.doWithModifierKeys(
() => this.page.mouse[ command.state ]({ button : command.modifier }),
command.modifierKey
).then(
() => delay(this.afterActionDelay)
)
}
doPointerClick (command : commands.PointerClick) : Promise<any> {
const mouse = this.page.mouse
return this.doWithModifierKeys(
() => {
return mouse.down({ button : command.modifier, clickCount : 1 }).then(
() => mouse.up({ button : command.modifier, clickCount : 1 })
)
},
command.modifierKey
).then(
() => delay(this.afterActionDelay)
)
}
doPointerDoubleClick (command : commands.PointerDoubleClick) : Promise<any> {
const mouse = this.page.mouse
return this.doWithModifierKeys(
() => {
return mouse.down({ button : command.modifier, clickCount : 1 }).then(
() => mouse.up({ button : command.modifier, clickCount : 1 })
).then(
() => mouse.down({ button : command.modifier, clickCount : 2 })
).then(
() => mouse.up({ button : command.modifier, clickCount : 2 })
)
},
command.modifierKey
).then(
() => delay(this.afterActionDelay)
)
}
doSimulationReset (command : commands.Reset) : Promise<any> {
let cont = Promise.resolve()
Object.keys(siestaToPuppeteerKeys).forEach(siestaKey => {
cont = cont.then(
() => this.page.keyboard.up(this.siestaKeyToSimulatorKey(siestaKey))
)
})
Object.keys(types.PointerModifier).forEach(button => {
if (isNaN(Number(button))) {
cont = cont.then(
() => this.page.mouse.up({ button : types.PointerModifier[ button ] })
)
}
})
cont = cont.then(
() => this.pageMouseMove(0, 0)
).then(
() => delay(this.afterActionDelay)
)
return cont
}
doMouseWheel (command : commands.MouseWheel) : Promise<any> {
return this.doWithModifierKeys(
() => {
},
command.modifierKey
).then(
() => delay(this.afterActionDelay)
)
}
doPointerDrag (command : commands.PointerDrag) : Promise<any> {
const mouse = this.page.mouse
return this.doWithModifierKeys(
() => {
return this.moveMouseTo(command.fromX, command.fromY).then(
() => mouse.down({ button : command.modifier })
).then(
() => this.moveMouseTo(command.toX, command.toY, 1)
).then(
() => command.dragOnly ? Promise.resolve() : mouse.up({ button : command.modifier })
)
},
command.modifierKey
).then(
() => delay(this.afterActionDelay)
)
}
doSetKeyState (command : commands.SetKeyState) : Promise<any> {
const keyboard = this.page.keyboard
return keyboard[ command.state ](this.siestaKeyToSimulatorKey(command.key))
}
doType (command : commands.Type) : Promise<any> {
const text = command.text
let cont = Promise.resolve()
for (let i = 0; i < text.length; i++) {
cont = cont.then(() => this.typeSingleChar(text[ i ], command.modifierKey))
}
return cont.then(() => delay(this.afterActionDelay))
}
typeSingleChar (key : string, modifierKey : types.ModifierKey[] = []) : Promise<any> {
const keyboard = this.page.keyboard
if (key.length > 1)
return this.doWithModifierKeys(
() => keyboard.press(this.siestaKeyToSimulatorKey(key)),
modifierKey
)
else {
// seems no special processing is needed for Puppeteer, see analogous RobotJS method however
return this.doWithModifierKeys(
() => keyboard.press(this.siestaKeyToSimulatorKey(key)),
modifierKey
)
}
}
moveMouseTo (x : number, y : number, mouseMovePrecision : number = null) : Promise<any> {
var path = getPathBetweenPoints(this.currentPosition, [ x, y ])
path = filterPathAccordingToPrecision(path, mouseMovePrecision || this.mouseMovePrecision)
let cont = Promise.resolve()
path.forEach(step => {
cont = cont.then(() => this.pageMouseMove(step[ 0 ], step[ 1 ]))
})
return cont
}
}
const siestaToPuppeteerKeys = {
'BACKSPACE' : 'Backspace',
'TAB' : 'Tab',
'RETURN' : 'Enter',
'ENTER' : 'Enter',
//special
'SHIFT' : 'Shift',
'CTRL' : 'Control',
'ALT' : 'Alt',
'CMD' : 'Meta', // Mac
// //weird
'PAUSE-BREAK' : 'Pause',
'CAPS' : 'CapsLock',
'ESCAPE' : 'Escape',
'ESC' : 'Escape',
'NUM-LOCK' : 'NumLock',
'SCROLL-LOCK' : 'ScrollLock',
'PRINT' : 'Print',
//navigation
'PAGE-UP' : 'PageUp',
'PAGE-DOWN' : 'PageDown',
'END' : 'End',
'HOME' : 'Home',
'LEFT' : 'ArrowLeft',
'ARROWLEFT' : 'ArrowLeft',
'UP' : 'ArrowUp',
'ARROWUP' : 'ArrowUp',
'RIGHT' : 'ArrowRight',
'ARROWRIGHT' : 'ArrowRight',
'DOWN' : 'ArrowDown',
'ARROWDOWN' : 'ArrowDown',
'INSERT' : 'Insert',
'DELETE' : 'Delete',
//NORMAL-CHARACTERS, NUMPAD
'NUM0' : 'Numpad0',
'NUM1' : 'Numpad1',
'NUM2' : 'Numpad2',
'NUM3' : 'Numpad3',
'NUM4' : 'Numpad4',
'NUM5' : 'Numpad5',
'NUM6' : 'Numpad6',
'NUM7' : 'Numpad7',
'NUM8' : 'Numpad8',
'NUM9' : 'Numpad9',
'F1' : 'F1',
'F2' : 'F2',
'F3' : 'F3',
'F4' : 'F4',
'F5' : 'F5',
'F6' : 'F6',
'F7' : 'F7',
'F8' : 'F8',
'F9' : 'F9',
'F10' : 'F10',
'F11' : 'F11',
'F12' : 'F12'
}
export const SimulatorServer = SimulatorServerPuppeteer