blob: 4de405cfed37acbbbda11bc3495fdd9773222d64 [file] [log] [blame]
const isDebug = false;
function logDebug(callback)
{
if (isDebug)
console.log(callback());
}
function pause(duration) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, duration);
});
}
function dispatchMouseActions(actions, pointerType)
{
if (!window.eventSender)
return Promise.reject(new Error("window.eventSender is undefined."));
return new Promise(resolve => {
setTimeout(async () => {
eventSender.dragMode = false;
for (let action of actions) {
switch (action.type) {
case "pointerMove":
let origin = { x: 0, y: 0 };
if (action.origin instanceof Element) {
const bounds = action.origin.getBoundingClientRect();
logDebug(() => `${action.origin.id} [${bounds.left}, ${bounds.top}, ${bounds.width}, ${bounds.height}]`);
origin.x = bounds.left + 1;
origin.y = bounds.top + 1;
}
logDebug(() => `eventSender.mouseMoveTo(${action.x + origin.x}, ${action.y + origin.y})`);
eventSender.mouseMoveTo(action.x + origin.x, action.y + origin.y, pointerType);
break;
case "pointerDown":
logDebug(() => `eventSender.mouseDown()`);
eventSender.mouseDown(action.button, [], pointerType);
break;
case "pointerUp":
logDebug(() => `eventSender.mouseUp()`);
eventSender.mouseUp(action.button, [], pointerType);
break;
case "pause":
logDebug(() => `pause(${action.duration})`);
await pause(action.duration);
break;
default:
return Promise.reject(new Error(`Unknown action type "${action.type}".`));
}
}
resolve();
});
});
}
function dispatchTouchActions(actions, options = { insertPauseAfterPointerUp: false })
{
if (!window.testRunner || typeof window.testRunner.runUIScript !== "function")
return Promise.reject(new Error("window.testRunner.runUIScript() is undefined."));
let x = 0;
let y = 0;
let timeOffset = 0;
let pointerDown = false;
let id = 1;
const events = [];
for (let action of actions) {
const touch = {
inputType : "finger",
id,
x : action.x,
y : action.y,
pressure : 0
};
const command = {
inputType : "hand",
coordinateSpace : "content",
touches : [touch],
timeOffset
};
let timeOffsetIncrease = 0;
switch (action.type) {
case "pointerMove":
touch.phase = "moved";
if (action.origin instanceof Element) {
const bounds = action.origin.getBoundingClientRect();
touch.x += bounds.left + 1;
touch.y += bounds.top + 1;
}
break;
case "pointerDown":
pointerDown = true;
touch.phase = "began";
if (action.x === undefined)
touch.x = x;
if (action.y === undefined)
touch.y = y;
break;
case "pointerUp":
pointerDown = false;
touch.phase = "ended";
touch.x = x;
touch.y = y;
id++;
// We need to add a pause after a pointer up to ensure that a subsequent tap may be recognized as such.
if (options.insertPauseAfterPointerUp)
timeOffsetIncrease = 0.5;
break;
case "pause":
timeOffsetIncrease = action.duration / 1000;
break;
default:
return Promise.reject(new Error(`Unknown action type "${action.type}".`));
}
if (action.type !== "pause") {
x = touch.x;
y = touch.y;
}
if (!pointerDown && touch.phase == "moved")
continue;
timeOffset += timeOffsetIncrease;
if (action.type !== "pause")
events.push(command);
}
const stream = JSON.stringify({ events });
logDebug(() => stream);
return new Promise(resolve => testRunner.runUIScript(`(function() {
(function() { uiController.sendEventStream('${stream}') })();
uiController.uiScriptComplete();
})();`, resolve));
}
if (window.test_driver_internal === undefined)
window.test_driver_internal = { };
window.test_driver_internal.send_keys = function(element, keys)
{
if (!window.eventSender)
return Promise.reject(new Error("window.eventSender is undefined."));
// https://seleniumhq.github.io/selenium/docs/api/py/webdriver/selenium.webdriver.common.keys.html
// FIXME: Add more cases.
const SeleniumCharCodeToEventSenderKey = {
0xE003: { key: 'delete' },
0XE004: { key: '\t' },
0XE005: { key: 'clear' },
0XE006: { key: '\r' },
0XE007: { key: '\n' },
0xE008: { key: 'leftShift', modifier: 'shiftKey' },
0xE009: { key: 'leftControl', modifier: 'ctrlKey' },
0xE00A: { key: 'leftAlt', modifier: 'altKey' },
0XE00C: { key: 'escape' },
0xE00D: { key: ' ' },
0XE00E: { key: 'pageUp' },
0XE00F: { key: 'pageDown' },
0XE010: { key: 'end' },
0XE011: { key: 'home' },
0xE012: { key: 'leftArrow' },
0xE013: { key: 'upArrow' },
0xE014: { key: 'rightArrow' },
0xE015: { key: 'downArrow' },
0XE016: { key: 'insert' },
0XE017: { key: 'delete' },
0XE018: { key: ';' },
0XE019: { key: '=' },
0XE031: { key: 'F1' },
0XE032: { key: 'F2' },
0XE033: { key: 'F3' },
0XE034: { key: 'F4' },
0XE035: { key: 'F5' },
0XE036: { key: 'F6' },
0XE037: { key: 'F7' },
0XE038: { key: 'F8' },
0XE039: { key: 'F9' },
0XE03A: { key: 'F10' },
0XE03B: { key: 'F11' },
0XE03C: { key: 'F12' },
0xE03D: { key: 'leftMeta', modifier: 'metaKey' },
};
function convertSeleniumKeyCode(key)
{
const code = key.charCodeAt(0);
return SeleniumCharCodeToEventSenderKey[code] || { key: key };
}
const keyList = [];
const modifiers = [];
for (const key of keys) {
let convertedKey = convertSeleniumKeyCode(key);
keyList.push(convertedKey.key);
if (convertedKey.modifier)
modifiers.push(convertedKey.modifier);
}
if (testRunner.isIOSFamily && testRunner.isWebKit2) {
return new Promise((resolve) => {
testRunner.runUIScript(`
const keyList = JSON.parse('${JSON.stringify(keyList)}');
for (const key of keyList)
uiController.keyDown(key, modifiers);`, resolve);
});
}
for (const key of keyList)
eventSender.keyDown(key, modifiers);
return Promise.resolve();
}
window.test_driver_internal.click = function (element, coords)
{
if (!window.testRunner)
return Promise.resolve();
if (!window.eventSender)
return Promise.reject(new Error("window.eventSender is undefined."));
if (testRunner.isIOSFamily && testRunner.isWebKit2) {
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.singleTapAtPoint(${coords.x}, ${coords.y}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
eventSender.mouseMoveTo(coords.x, coords.y);
eventSender.mouseDown();
eventSender.mouseUp();
return Promise.resolve();
}
window.test_driver_internal.action_sequence = function(sources)
{
// https://w3c.github.io/webdriver/#processing-actions
let noneSource;
let pointerSource;
for (let source of sources) {
switch (source.type) {
case "none":
noneSource = source;
break;
case "pointer":
pointerSource = source;
break;
default:
return Promise.reject(new Error(`Unknown source type "${action.type}".`));
}
}
if (!pointerSource)
return Promise.reject(new Error(`Unknown pointer type pointer type "${action.parameters.pointerType}".`));
const pointerType = pointerSource.parameters.pointerType;
if (pointerType !== "mouse" && pointerType !== "touch" && pointerType !== "pen")
return Promise.reject(new Error(`Unknown pointer type "${pointerType}".`));
// If we have a "none" source, let's inject any pause with non-zero durations into the pointer source
// after the matching action in the pointer source.
if (noneSource) {
let injectedActions = 0;
noneSource.actions.forEach((action, index) => {
if (action.duration > 0) {
pointerSource.actions.splice(index + injectedActions + 1, 0, action);
injectedActions++;
}
});
}
logDebug(() => JSON.stringify(pointerSource));
if (pointerType === "touch")
return dispatchTouchActions(pointerSource.actions);
if (testRunner.isIOSFamily && "createTouch" in document)
return dispatchTouchActions(pointerSource.actions, { insertPauseAfterPointerUp: true });
if (pointerType === "mouse" || pointerType === "pen")
return dispatchMouseActions(pointerSource.actions, pointerType);
};
window.test_driver_internal.set_permission = function(permission_params, context=null)
{
if (window.testRunner && permission_params.descriptor.name == "geolocation") {
setInterval(() => {
window.testRunner.setMockGeolocationPosition(51.478, -0.166, 100);
}, 100);
testRunner.setGeolocationPermission(permission_params.state == "granted");
return Promise.resolve();
}
return Promise.reject(new Error("unimplemented"));
};