blob: 60b12ff3d82285bca5612d2976bb714396faafa8 [file] [log] [blame]
window.UIHelper = class UIHelper {
static isIOSFamily()
{
return testRunner.isIOSFamily;
}
static isWebKit2()
{
return testRunner.isWebKit2;
}
static doubleClickAt(x, y)
{
eventSender.mouseMoveTo(x, y);
eventSender.mouseDown();
eventSender.mouseUp();
eventSender.mouseDown();
eventSender.mouseUp();
}
static doubleClickAtMouseDown(x1, y1)
{
eventSender.mouseMoveTo(x1, y1);
eventSender.mouseDown();
eventSender.mouseUp();
eventSender.mouseDown();
}
static mouseUp()
{
eventSender.mouseUp();
}
static doubleClickAtThenDragTo(x1, y1, x2, y2)
{
eventSender.mouseMoveTo(x1, y1);
eventSender.mouseDown();
eventSender.mouseUp();
eventSender.mouseDown();
eventSender.mouseMoveTo(x2, y2);
eventSender.mouseUp();
}
static dragMouseAcrossElement(element)
{
const x1 = element.offsetLeft + element.offsetWidth;
const x2 = element.offsetLeft + element.offsetWidth * .75;
const x3 = element.offsetLeft + element.offsetWidth / 2;
const x4 = element.offsetLeft + element.offsetWidth / 4;
const x5 = element.offsetLeft;
const y = element.offsetTop + element.offsetHeight / 2;
eventSender.mouseMoveTo(x1, y);
eventSender.mouseMoveTo(x2, y);
eventSender.mouseMoveTo(x3, y);
eventSender.mouseMoveTo(x4, y);
eventSender.mouseMoveTo(x5, y);
}
static doubleClickElementMouseDown(element1)
{
const x1 = element1.offsetLeft + element1.offsetWidth / 2;
const y1 = element1.offsetTop + element1.offsetHeight / 2;
return UIHelper.doubleClickAtMouseDown(x1, y1);
}
static async moveMouseAndWaitForFrame(x, y)
{
eventSender.mouseMoveTo(x, y);
await UIHelper.animationFrame();
}
static async mouseWheelScrollAt(x, y, beginX, beginY, deltaX, deltaY)
{
if (beginX === undefined)
beginX = 0;
if (beginY === undefined)
beginY = -1;
if (deltaX === undefined)
deltaX = 0;
if (deltaY === undefined)
deltaY = -10;
eventSender.monitorWheelEvents();
eventSender.mouseMoveTo(x, y);
eventSender.mouseScrollByWithWheelAndMomentumPhases(beginX, beginY, "began", "none");
eventSender.mouseScrollByWithWheelAndMomentumPhases(deltaX, deltaY, "changed", "none");
eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, "ended", "none");
return new Promise(resolve => {
eventSender.callAfterScrollingCompletes(() => {
requestAnimationFrame(resolve);
});
});
}
static async mouseWheelMayBeginAt(x, y)
{
eventSender.mouseMoveTo(x, y);
eventSender.mouseScrollByWithWheelAndMomentumPhases(x, y, "maybegin", "none");
await UIHelper.animationFrame();
}
static async mouseWheelCancelAt(x, y)
{
eventSender.mouseMoveTo(x, y);
eventSender.mouseScrollByWithWheelAndMomentumPhases(x, y, "cancelled", "none");
await UIHelper.animationFrame();
}
static async waitForScrollCompletion()
{
return new Promise(resolve => {
eventSender.callAfterScrollingCompletes(() => {
requestAnimationFrame(resolve);
});
});
}
static async animationFrame()
{
return new Promise(requestAnimationFrame);
}
static async renderingUpdate()
{
await UIHelper.animationFrame();
await UIHelper.delayFor(0);
}
static async waitForCondition(conditionFunc)
{
while (!conditionFunc()) {
await UIHelper.animationFrame();
}
}
static sendEventStream(eventStream)
{
const eventStreamAsString = JSON.stringify(eventStream);
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.sendEventStream(\`${eventStreamAsString}\`, () => {
uiController.uiScriptComplete();
});
})();
`, resolve);
});
}
static tapAt(x, y, modifiers=[])
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
console.assert(!modifiers || !modifiers.length);
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.singleTapAtPointWithModifiers(${x}, ${y}, ${JSON.stringify(modifiers)}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static tapElement(element, delay = 0)
{
const x = element.offsetLeft + (element.offsetWidth / 2);
const y = element.offsetTop + (element.offsetHeight / 2);
this.tapAt(x, y);
}
static doubleTapElement(element, delay = 0)
{
const x = element.offsetLeft + (element.offsetWidth / 2);
const y = element.offsetTop + (element.offsetHeight / 2);
this.doubleTapAt(x, y, delay);
}
static doubleTapAt(x, y, delay = 0)
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.doubleTapAtPoint(${x}, ${y}, ${delay}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static humanSpeedDoubleTapAt(x, y)
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
// FIXME: Add a sleep in here.
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return UIHelper.doubleTapAt(x, y, 0.12);
}
static humanSpeedZoomByDoubleTappingAt(x, y)
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
// FIXME: Add a sleep in here.
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return new Promise(async (resolve) => {
testRunner.runUIScript(`
uiController.didEndZoomingCallback = () => {
uiController.didEndZoomingCallback = null;
uiController.uiScriptComplete(uiController.zoomScale);
};
uiController.doubleTapAtPoint(${x}, ${y}, 0.12, () => { });`, resolve);
});
}
static zoomByDoubleTappingAt(x, y)
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.didEndZoomingCallback = () => {
uiController.didEndZoomingCallback = null;
uiController.uiScriptComplete(uiController.zoomScale);
};
uiController.doubleTapAtPoint(${x}, ${y}, 0, () => { });`, resolve);
});
}
static activateAt(x, y, modifiers=[])
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.mouseMoveTo(x, y);
eventSender.mouseDown(0, modifiers);
eventSender.mouseUp(0, modifiers);
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.singleTapAtPointWithModifiers(${x}, ${y}, ${JSON.stringify(modifiers)}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static activateElement(element, modifiers=[])
{
const x = element.offsetLeft + element.offsetWidth / 2;
const y = element.offsetTop + element.offsetHeight / 2;
return UIHelper.activateAt(x, y, modifiers);
}
static async doubleActivateAt(x, y)
{
if (this.isIOSFamily())
await UIHelper.doubleTapAt(x, y);
else
await UIHelper.doubleClickAt(x, y);
}
static async doubleActivateAtSelectionStart()
{
const rects = window.getSelection().getRangeAt(0).getClientRects();
const x = rects[0].left;
const y = rects[0].top;
if (this.isIOSFamily()) {
await UIHelper.activateAndWaitForInputSessionAt(x, y);
await UIHelper.doubleTapAt(x, y);
// This is only here to deal with async/sync copy/paste calls, so
// once <rdar://problem/16207002> is resolved, should be able to remove for faster tests.
await new Promise(resolve => testRunner.runUIScript("uiController.uiScriptComplete()", resolve));
} else
await UIHelper.doubleClickAt(x, y);
}
static async selectWordByDoubleTapOrClick(element, relativeX = 5, relativeY = 5)
{
const boundingRect = element.getBoundingClientRect();
const x = boundingRect.x + relativeX;
const y = boundingRect.y + relativeY;
if (this.isIOSFamily()) {
await UIHelper.activateAndWaitForInputSessionAt(x, y);
await UIHelper.doubleTapAt(x, y);
// This is only here to deal with async/sync copy/paste calls, so
// once <rdar://problem/16207002> is resolved, should be able to remove for faster tests.
await new Promise(resolve => testRunner.runUIScript("uiController.uiScriptComplete()", resolve));
} else {
await UIHelper.doubleClickAt(x, y);
}
}
static keyDown(key, modifiers=[])
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.keyDown(key, modifiers);
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`uiController.keyDown("${key}", ${JSON.stringify(modifiers)});`, resolve);
});
}
static toggleCapsLock()
{
return new Promise((resolve) => {
testRunner.runUIScript(`uiController.toggleCapsLock(() => uiController.uiScriptComplete());`, resolve);
});
}
static keyboardIsAutomaticallyShifted()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.keyboardIsAutomaticallyShifted`, result => resolve(result === "true"));
});
}
static isAnimatingDragCancel()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.isAnimatingDragCancel`, result => resolve(result === "true"));
});
}
static ensurePresentationUpdate()
{
if (!this.isWebKit2()) {
testRunner.display();
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.doAfterPresentationUpdate(function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static ensureStablePresentationUpdate()
{
if (!this.isWebKit2()) {
testRunner.display();
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static ensurePositionInformationUpdateForElement(element)
{
const boundingRect = element.getBoundingClientRect();
const x = boundingRect.x + 5;
const y = boundingRect.y + 5;
if (!this.isWebKit2()) {
testRunner.display();
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.ensurePositionInformationIsUpToDateAt(${x}, ${y}, function () {
uiController.uiScriptComplete();
});`, resolve);
});
}
static delayFor(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
static scrollTo(x, y, unconstrained)
{
if (!this.isWebKit2()) {
window.scrollTo(x, y);
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.didEndScrollingCallback = function() {
uiController.uiScriptComplete();
}
uiController.scrollToOffset(${x}, ${y}, { unconstrained: ${unconstrained} });
})()`, resolve);
});
}
static immediateScrollTo(x, y, unconstrained)
{
if (!this.isWebKit2()) {
window.scrollTo(x, y);
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.immediateScrollToOffset(${x}, ${y}, { unconstrained: ${unconstrained} });`, resolve);
});
}
static immediateUnstableScrollTo(x, y, unconstrained)
{
if (!this.isWebKit2()) {
window.scrollTo(x, y);
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.stableStateOverride = false;
uiController.immediateScrollToOffset(${x}, ${y}, { unconstrained: ${unconstrained} });`, resolve);
});
}
static immediateScrollElementAtContentPointToOffset(x, y, scrollX, scrollY, scrollUpdatesDisabled = false)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.scrollUpdatesDisabled = ${scrollUpdatesDisabled};
uiController.immediateScrollElementAtContentPointToOffset(${x}, ${y}, ${scrollX}, ${scrollY});`, resolve);
});
}
static ensureVisibleContentRectUpdate()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
const visibleContentRectUpdateScript = "uiController.doAfterVisibleContentRectUpdate(() => uiController.uiScriptComplete())";
testRunner.runUIScript(visibleContentRectUpdateScript, resolve);
});
}
static longPressAndGetContextMenuContentAt(x, y)
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.didShowContextMenuCallback = function() {
uiController.uiScriptComplete(JSON.stringify(uiController.contentsOfUserInterfaceItem('contextMenu')));
};
uiController.longPressAtPoint(${x}, ${y}, function() { });
})();`, result => resolve(JSON.parse(result)));
});
}
static activateAndWaitForInputSessionAt(x, y)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return this.activateAt(x, y);
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
function clearCallbacksAndScriptComplete() {
uiController.didShowContextMenuCallback = null;
uiController.didShowKeyboardCallback = null;
uiController.willPresentPopoverCallback = null;
uiController.uiScriptComplete();
}
uiController.didShowContextMenuCallback = clearCallbacksAndScriptComplete;
uiController.didShowKeyboardCallback = clearCallbacksAndScriptComplete;
uiController.willPresentPopoverCallback = clearCallbacksAndScriptComplete;
uiController.singleTapAtPoint(${x}, ${y}, function() { });
})()`, resolve);
});
}
static waitForInputSessionToDismiss()
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingKeyboard && !uiController.isShowingContextMenu && !uiController.isShowingPopover) {
uiController.uiScriptComplete();
return;
}
function clearCallbacksAndScriptComplete() {
uiController.didHideKeyboardCallback = null;
uiController.didDismissPopoverCallback = null;
uiController.didDismissContextMenuCallback = null;
uiController.uiScriptComplete();
}
uiController.didHideKeyboardCallback = clearCallbacksAndScriptComplete;
uiController.didDismissPopoverCallback = clearCallbacksAndScriptComplete;
uiController.didDismissContextMenuCallback = clearCallbacksAndScriptComplete;
})()`, resolve);
});
}
static activateElementAndWaitForInputSession(element)
{
const x = element.offsetLeft + element.offsetWidth / 2;
const y = element.offsetTop + element.offsetHeight / 2;
return this.activateAndWaitForInputSessionAt(x, y);
}
static activateFormControl(element)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return this.activateElement(element);
const x = element.offsetLeft + element.offsetWidth / 2;
const y = element.offsetTop + element.offsetHeight / 2;
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.didStartFormControlInteractionCallback = function() {
uiController.uiScriptComplete();
};
uiController.singleTapAtPoint(${x}, ${y}, function() { });
})()`, resolve);
});
}
static dismissFormAccessoryView()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.dismissFormAccessoryView();
uiController.uiScriptComplete();
})()`, resolve);
});
}
static isShowingKeyboard()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.isShowingKeyboard", result => resolve(result === "true"));
});
}
static isShowingPopover()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.isShowingPopover", result => resolve(result === "true"));
});
}
static hasInputSession()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.hasInputSession", result => resolve(result === "true"));
});
}
static isPresentingModally()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.isPresentingModally", result => resolve(result === "true"));
});
}
static deactivateFormControl(element)
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
element.blur();
return Promise.resolve();
}
return new Promise(async resolve => {
element.blur();
while (await this.isPresentingModally())
continue;
while (await this.isShowingKeyboard())
continue;
resolve();
});
}
static waitForPopoverToPresent()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingPopover)
uiController.uiScriptComplete();
else
uiController.willPresentPopoverCallback = () => uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForPopoverToDismiss()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingPopover)
uiController.didDismissPopoverCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForContextMenuToShow()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingContextMenu)
uiController.didShowContextMenuCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForContextMenuToHide()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingContextMenu)
uiController.didDismissContextMenuCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForKeyboardToHide()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingKeyboard)
uiController.didHideKeyboardCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static getUICaretRect()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionCaretRect));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getUISelectionRects()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionRangeRects));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getUICaretViewRect()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionCaretViewRect));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getUISelectionViewRects()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getSelectionStartGrabberViewRect()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionStartGrabberViewRect));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getSelectionEndGrabberViewRect()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionEndGrabberViewRect));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static midPointOfRect(rect) {
return { x: rect.left + (rect.width / 2), y: rect.top + (rect.height / 2) };
}
static selectionCaretBackgroundColor()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.uiScriptComplete(uiController.selectionCaretBackgroundColor)", resolve);
});
}
static tapHighlightViewRect()
{
return new Promise(resolve => {
testRunner.runUIScript("JSON.stringify(uiController.tapHighlightViewRect)", jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static replaceTextAtRange(text, location, length) {
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.replaceTextAtRange("${text}", ${location}, ${length});
uiController.uiScriptComplete();
})()`, resolve);
});
}
static wait(promise)
{
testRunner.waitUntilDone();
if (window.finishJSTest)
window.jsTestIsAsync = true;
let finish = () => {
if (window.finishJSTest)
finishJSTest();
else
testRunner.notifyDone();
}
return promise.then(finish, finish);
}
static withUserGesture(callback)
{
internals.withUserGesture(callback);
}
static selectFormAccessoryPickerRow(rowIndex)
{
const selectRowScript = `uiController.selectFormAccessoryPickerRow(${rowIndex})`;
return new Promise(resolve => testRunner.runUIScript(selectRowScript, resolve));
}
static selectFormAccessoryHasCheckedItemAtRow(rowIndex)
{
return new Promise(resolve => testRunner.runUIScript(`uiController.selectFormAccessoryHasCheckedItemAtRow(${rowIndex})`, result => {
resolve(result === "true");
}));
}
static selectFormPopoverTitle()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.selectFormPopoverTitle);
})()`, resolve);
});
}
static setSelectedColorForColorPicker(red, green, blue)
{
const selectColorScript = `uiController.setSelectedColorForColorPicker(${red}, ${green}, ${blue})`;
return new Promise(resolve => testRunner.runUIScript(selectColorScript, resolve));
}
static enterText(text)
{
const escapedText = text.replace(/`/g, "\\`");
const enterTextScript = `(() => uiController.enterText(\`${escapedText}\`))()`;
return new Promise(resolve => testRunner.runUIScript(enterTextScript, resolve));
}
static setTimePickerValue(hours, minutes)
{
const setValueScript = `(() => uiController.setTimePickerValue(${hours}, ${minutes}))()`;
return new Promise(resolve => testRunner.runUIScript(setValueScript, resolve));
}
static timerPickerValues()
{
if (!this.isIOSFamily())
return Promise.resolve();
const uiScript = "JSON.stringify([uiController.timePickerValueHour, uiController.timePickerValueMinute])";
return new Promise(resolve => testRunner.runUIScript(uiScript, result => {
const [hour, minute] = JSON.parse(result)
resolve({ hour: hour, minute: minute });
}));
}
static textContentType()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.textContentType);
})()`, resolve);
});
}
static formInputLabel()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.formInputLabel);
})()`, resolve);
});
}
static dismissFilePicker()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
const script = `uiController.dismissFilePicker(() => {
uiController.uiScriptComplete();
})`;
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static filePickerAcceptedTypeIdentifiers()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.filePickerAcceptedTypeIdentifiers));
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static activateDataListSuggestion(index) {
const script = `uiController.activateDataListSuggestion(${index}, () => {
uiController.uiScriptComplete("");
});`;
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static isShowingDataListSuggestions()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.isShowingDataListSuggestions);
})()`, result => resolve(result === "true"));
});
}
static isShowingDateTimePicker()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.isShowingDateTimePicker);
})()`, result => resolve(result === "true"));
});
}
static dateTimePickerValue()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.dateTimePickerValue);
})()`, valueAsString => resolve(parseFloat(valueAsString)));
});
}
static chooseDateTimePickerValue()
{
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.chooseDateTimePickerValue();
uiController.uiScriptComplete();
`, resolve);
});
}
static zoomScale()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.zoomScale);
})()`, scaleAsString => resolve(parseFloat(scaleAsString)));
});
}
static zoomToScale(scale)
{
const uiScript = `uiController.zoomToScale(${scale}, () => uiController.uiScriptComplete(uiController.zoomScale))`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static immediateZoomToScale(scale)
{
const uiScript = `uiController.immediateZoomToScale(${scale})`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static typeCharacter(characterString)
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.keyDown(characterString);
return;
}
const escapedString = characterString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
const uiScript = `uiController.typeCharacterUsingHardwareKeyboard(\`${escapedString}\`, () => uiController.uiScriptComplete())`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static applyAutocorrection(newText, oldText)
{
if (!this.isWebKit2())
return;
const [escapedNewText, escapedOldText] = [newText.replace(/`/g, "\\`"), oldText.replace(/`/g, "\\`")];
const uiScript = `uiController.applyAutocorrection(\`${escapedNewText}\`, \`${escapedOldText}\`, () => uiController.uiScriptComplete())`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static inputViewBounds()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.inputViewBounds));
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static calendarType()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.doAfterNextStablePresentationUpdate(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.calendarType));
})
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static setDefaultCalendarType(calendarIdentifier, localeIdentifier)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setDefaultCalendarType('${calendarIdentifier}', '${localeIdentifier}')`, resolve));
}
static setViewScale(scale)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setViewScale(${scale})`, resolve));
}
static resignFirstResponder()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.resignFirstResponder()`, resolve));
}
static becomeFirstResponder()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.becomeFirstResponder()`, resolve));
}
static removeViewFromWindow()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.removeViewFromWindow()`, resolve));
}
static addViewToWindow()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.addViewToWindow()`, resolve));
}
static minimumZoomScale()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.minimumZoomScale);
})()`, scaleAsString => resolve(parseFloat(scaleAsString)))
});
}
static stylusTapAt(x, y, modifiers=[])
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.stylusTapAtPointWithModifiers(${x}, ${y}, 2, 1, 0.5, ${JSON.stringify(modifiers)}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static attachmentInfo(attachmentIdentifier)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.attachmentInfo('${attachmentIdentifier}')));
})()`, jsonString => {
resolve(JSON.parse(jsonString));
})
});
}
static insertAttachmentForFilePath(path, contentType)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.insertAttachmentForFilePath('${path}', '${contentType}', function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static setMinimumEffectiveWidth(effectiveWidth)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setMinimumEffectiveWidth(${effectiveWidth})`, resolve));
}
static setAllowsViewportShrinkToFit(allows)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setAllowsViewportShrinkToFit(${allows})`, resolve));
}
static setKeyboardInputModeIdentifier(identifier)
{
if (!this.isWebKit2())
return Promise.resolve();
const escapedIdentifier = identifier.replace(/`/g, "\\`");
return new Promise(resolve => testRunner.runUIScript(`uiController.setKeyboardInputModeIdentifier(\`${escapedIdentifier}\`)`, resolve));
}
static contentOffset()
{
if (!this.isIOSFamily())
return Promise.resolve();
const uiScript = "JSON.stringify([uiController.contentOffsetX, uiController.contentOffsetY])";
return new Promise(resolve => testRunner.runUIScript(uiScript, result => {
const [offsetX, offsetY] = JSON.parse(result)
resolve({ x: offsetX, y: offsetY });
}));
}
static undoAndRedoLabels()
{
if (!this.isWebKit2())
return Promise.resolve();
const script = "JSON.stringify([uiController.lastUndoLabel, uiController.firstRedoLabel])";
return new Promise(resolve => testRunner.runUIScript(script, result => resolve(JSON.parse(result))));
}
static waitForMenuToShow()
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingMenu)
uiController.didShowMenuCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForMenuToHide()
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingMenu)
uiController.didHideMenuCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static isShowingMenu()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.isShowingMenu`, result => resolve(result === "true"));
});
}
static isDismissingMenu()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.isDismissingMenu`, result => resolve(result === "true"));
});
}
static menuRect()
{
return new Promise(resolve => {
testRunner.runUIScript("JSON.stringify(uiController.menuRect)", result => resolve(JSON.parse(result)));
});
}
static setHardwareKeyboardAttached(attached)
{
return new Promise(resolve => testRunner.runUIScript(`uiController.setHardwareKeyboardAttached(${attached ? "true" : "false"})`, resolve));
}
static rectForMenuAction(action)
{
return new Promise(resolve => {
testRunner.runUIScript(`
(() => {
const rect = uiController.rectForMenuAction("${action}");
uiController.uiScriptComplete(rect ? JSON.stringify(rect) : "");
})();
`, stringResult => {
resolve(stringResult.length ? JSON.parse(stringResult) : null);
});
});
}
static chooseMenuAction(action)
{
return new Promise(resolve => {
testRunner.runUIScript(`
(() => {
uiController.chooseMenuAction("${action}", () => {
uiController.uiScriptComplete();
});
})();
`, resolve);
});
}
static waitForEvent(target, eventName)
{
return new Promise(resolve => target.addEventListener(eventName, resolve, { once: true }));
}
static callFunctionAndWaitForEvent(functionToCall, target, eventName)
{
return new Promise(async resolve => {
let event;
await Promise.all([
new Promise((eventListenerResolve) => {
target.addEventListener(eventName, (e) => {
event = e;
eventListenerResolve();
}, {once: true});
}),
new Promise(async functionResolve => {
await functionToCall();
functionResolve();
})
]);
resolve(event);
});
}
static callFunctionAndWaitForScrollToFinish(functionToCall, ...theArguments)
{
return UIHelper.callFunctionAndWaitForTargetScrollToFinish(window, functionToCall, theArguments)
}
static callFunctionAndWaitForTargetScrollToFinish(scrollTarget, functionToCall, ...theArguments)
{
return new Promise((resolved) => {
function scrollDidFinish() {
scrollTarget.removeEventListener("scroll", handleScroll, true);
resolved();
}
let lastScrollTimerId = 0; // When the timer with this id fires then the page has finished scrolling.
function handleScroll() {
if (lastScrollTimerId) {
window.clearTimeout(lastScrollTimerId);
lastScrollTimerId = 0;
}
lastScrollTimerId = window.setTimeout(scrollDidFinish, 300); // Over 250ms to give some room for error.
}
scrollTarget.addEventListener("scroll", handleScroll, true);
functionToCall.apply(this, theArguments);
});
}
static waitForTargetScrollAnimationToSettle(scrollTarget)
{
return new Promise((resolved) => {
let lastObservedScrollPosition = [scrollTarget.scrollLeft, scrollTarget.scrollTop];
let frameOfLastChange = 0;
let totalFrames = 0;
function animationFrame() {
if (lastObservedScrollPosition[0] != scrollTarget.scrollLeft ||
lastObservedScrollPosition[1] != scrollTarget.scrollTop) {
lastObservedScrollPosition = [scrollTarget.scrollLeft, scrollTarget.scrollTop];
frameOfLastChange = totalFrames;
}
// If we have gone 20 frames without changing, resolve. If we have gone 500, then time out.
// This matches the amount of frames used in the WPT scroll animation helper.
if (totalFrames - frameOfLastChange >= 20 || totalFrames > 500)
resolved();
totalFrames++;
requestAnimationFrame(animationFrame);
}
requestAnimationFrame(animationFrame);
});
}
static rotateDevice(orientationName, animatedResize = false)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.${animatedResize ? "simulateRotationLikeSafari" : "simulateRotation"}("${orientationName}", function() {
uiController.doAfterVisibleContentRectUpdate(() => uiController.uiScriptComplete());
});
})()`, resolve);
});
}
static getScrollingTree()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
return uiController.scrollingTreeAsText;
})()`, resolve);
});
}
static getUIViewTree()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
return uiController.uiViewTreeAsText;
})()`, resolve);
});
}
static dragFromPointToPoint(fromX, fromY, toX, toY, duration)
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.mouseMoveTo(fromX, fromY);
eventSender.mouseDown();
eventSender.leapForward(duration * 1000);
eventSender.mouseMoveTo(toX, toY);
eventSender.mouseUp();
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.dragFromPointToPoint(${fromX}, ${fromY}, ${toX}, ${toY}, ${duration}, () => {
uiController.uiScriptComplete();
});
})();`, resolve);
});
}
static setWindowIsKey(isKey)
{
const script = `uiController.windowIsKey = ${isKey}`;
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static windowIsKey()
{
const script = "uiController.uiScriptComplete(uiController.windowIsKey)";
return new Promise(resolve => testRunner.runUIScript(script, (result) => {
resolve(result === "true");
}));
}
static waitForDoubleTapDelay()
{
const uiScript = `uiController.doAfterDoubleTapDelay(() => uiController.uiScriptComplete(""))`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static async waitForSelectionToAppear() {
while (true) {
let selectionRects = await this.getUISelectionViewRects();
if (selectionRects.length > 0)
return selectionRects;
}
}
static async waitForSelectionToDisappear() {
while (true) {
if (!(await this.getUISelectionViewRects()).length)
break;
}
}
static async copyText(text) {
const copyTextScript = `uiController.copyText(\`${text.replace(/`/g, "\\`")}\`)`;
return new Promise(resolve => testRunner.runUIScript(copyTextScript, resolve));
}
static async paste() {
return new Promise(resolve => testRunner.runUIScript(`uiController.paste()`, resolve));
}
static async setContinuousSpellCheckingEnabled(enabled) {
return new Promise(resolve => {
testRunner.runUIScript(`uiController.setContinuousSpellCheckingEnabled(${enabled})`, resolve);
});
}
static async longPressElement(element)
{
return this.longPressAtPoint(element.offsetLeft + element.offsetWidth / 2, element.offsetTop + element.offsetHeight / 2);
}
static async longPressAtPoint(x, y)
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.longPressAtPoint(${x}, ${y}, function() {
uiController.uiScriptComplete();
});
})();`, resolve);
});
}
static async setSpellCheckerResults(results)
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.setSpellCheckerResults(${JSON.stringify(results)});
uiController.uiScriptComplete();
})()`, resolve);
});
}
static async activateElementAfterInstallingTapGestureOnWindow(element)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return activateElement(element);
const x = element.offsetLeft + element.offsetWidth / 2;
const y = element.offsetTop + element.offsetHeight / 2;
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
let progress = 0;
function incrementProgress() {
if (++progress == 2)
uiController.uiScriptComplete();
}
uiController.installTapGestureOnWindow(incrementProgress);
uiController.singleTapAtPoint(${x}, ${y}, incrementProgress);
})();`, resolve);
});
}
static mayContainEditableElementsInRect(x, y, width, height)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve(false);
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.doAfterPresentationUpdate(function() {
uiController.uiScriptComplete(uiController.mayContainEditableElementsInRect(${x}, ${y}, ${width}, ${height}));
})
})();`, result => resolve(result === "true"));
});
}
static moveToNextByKeyboardAccessoryBar()
{
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.keyboardAccessoryBarNext();
uiController.uiScriptComplete();
`, resolve);
});
}
static moveToPrevByKeyboardAccessoryBar()
{
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.keyboardAccessoryBarPrevious();
uiController.uiScriptComplete();
`, resolve);
});
}
static waitForContactPickerToShow()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingContactPicker)
uiController.didShowContactPickerCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForContactPickerToHide()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingContactPicker)
uiController.didHideContactPickerCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static dismissContactPickerWithContacts(contacts)
{
const script = `(() => uiController.dismissContactPickerWithContacts(${JSON.stringify(contacts)}))()`;
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static addChromeInputField()
{
return new Promise(resolve => testRunner.addChromeInputField(resolve));
}
static removeChromeInputField()
{
return new Promise(resolve => testRunner.removeChromeInputField(resolve));
}
static setTextInChromeInputField(text)
{
return new Promise(resolve => testRunner.setTextInChromeInputField(text, resolve));
}
static selectChromeInputField()
{
return new Promise(resolve => testRunner.selectChromeInputField(resolve));
}
static getSelectedTextInChromeInputField()
{
return new Promise(resolve => testRunner.getSelectedTextInChromeInputField(resolve));
}
}
UIHelper.EventStreamBuilder = class {
constructor()
{
// FIXME: This could support additional customization options, such as interpolation, timestep, and different
// digitizer indices in the future. For now, just make it simpler to string together sequences of pan gestures.
this._reset();
}
_reset() {
this.events = [];
this.currentTimeOffset = 0;
this.currentX = 0;
this.currentY = 0;
}
begin(x, y) {
console.assert(this.currentTimeOffset === 0);
this.events.push({
interpolate : "linear",
timestep : 0.016,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "began", id : 1, x : x, y : y, pressure : 0 }]
},
endEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "began", id : 1, x : x, y : y, pressure : 0 }]
}
});
this.currentX = x;
this.currentY = y;
return this;
}
wait(duration) {
this.currentTimeOffset += duration;
return this;
}
move(x, y, duration = 0) {
const previousTimeOffset = this.currentTimeOffset;
this.currentTimeOffset += duration;
this.events.push({
interpolate : "linear",
timestep : 0.016,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : previousTimeOffset,
touches : [{ inputType : "finger", phase : "moved", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
},
endEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "moved", id : 1, x : x, y : y, pressure : 0 }]
}
});
this.currentX = x;
this.currentY = y;
return this;
}
end() {
this.events.push({
interpolate : "linear",
timestep : 0.016,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "ended", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
},
endEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "ended", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
}
});
return this;
}
takeResult() {
const events = this.events;
this._reset();
return { "events": events };
}
}