VulpesOS/gaia/apps/homescreen/test/marionette/lib/homescreen.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

455 lines
12 KiB
JavaScript
Executable file

'use strict';
/**
* Abstraction around homescreen.
* @constructor
*/
function Homescreen(client) {
this.client = client;
this.system = client.loader.getAppClass('system');
}
Homescreen.URL = 'app://homescreen.gaiamobile.org';
Homescreen.Selectors = {
appsScrollable: '#apps-panel .scrollable',
apps: '#apps',
pages: '#pages',
icon: '#apps gaia-app-icon',
card: '#pages gaia-pin-card',
bottomBar: '#bottombar',
remove: '#remove',
rename: '#rename',
done: '#done',
cancelDownload: '#cancel-download',
resumeDownload: '#resume-download'
};
Homescreen.prototype = {
URL: Homescreen.URL,
Selectors: Homescreen.Selectors,
get appsScrollable() {
return this.client.findElement(Homescreen.Selectors.appsScrollable);
},
get container() {
return this.client.findElement(Homescreen.Selectors.apps);
},
get icons() {
return this.client.findElements(Homescreen.Selectors.icon);
},
get cards() {
return this.client.findElements(Homescreen.Selectors.card);
},
get visibleIcons() {
return this.icons.filter(function(el) {
return el.scriptWith(function(el) {
return el.parentNode.style.display !== 'none';
});
});
},
get visibleCards() {
return this.client.executeScript(function(selector) {
var cards = document.body.querySelectorAll(selector);
var visibles = [];
for (var i = cards.length - 1; i >= 0; i--) {
if (cards[i].parentNode.style.display !== 'none') {
visibles.push(cards[i]);
}
}
return visibles;
}, [Homescreen.Selectors.card]);
},
get removeButton() {
return this.client.helper.waitForElement(Homescreen.Selectors.remove);
},
get renameButton() {
return this.client.helper.waitForElement(Homescreen.Selectors.rename);
},
get doneButton() {
return this.client.helper.waitForElement(Homescreen.Selectors.done);
},
get cancelDownloadDialog() {
return this.client.findElement(Homescreen.Selectors.cancelDownload);
},
get resumeDownloadDialog() {
return this.client.findElement(Homescreen.Selectors.resumeDownload);
},
/**
* Waits for the homescreen to launch and switches to the frame.
*/
waitForLaunch: function() {
var client = this.client;
client.helper.waitForElement('body');
client.apps.switchToApp(Homescreen.URL);
// Wait for startup to complete
client.waitFor(function() {
return client.executeScript(function() {
return window.wrappedJSObject.appWindow.apps.startupMetadata === null;
});
});
},
/**
* Waits for the edit bar to fully appear.
*/
waitForEditBar: function() {
var body = this.client.findElement('body');
var bar = this.client.helper.waitForElement(Homescreen.Selectors.bottomBar);
this.client.waitFor(function() {
var barRect = bar.rect();
return barRect.y === (body.rect().height - barRect.height);
});
},
/**
* Find and return every id for all the items on the grid... Each element
* can be used with `.getIcon` to find the element for a given id.
*
* @return {Array[String]}
*/
getIconIdentifiers: function() {
return this.visibleIcons.map(function(el) {
return this.getIconId(el);
}, this);
},
/**
* Fetch an icon element on the homescreen by its identifier.
* For apps, the identifier is the manifestURL, or its manifestURL,
* followed by a '/' followed by its entry point. For bookmarks, the
* identifier is the bookmarked URL.
*
* @param {String} identifier The identifier of the icon.
* @return {Marionette.Element}
*/
getIcon: function(identifier) {
return this.client.findElement(
Homescreen.Selectors.apps + ' [data-identifier*="' + identifier + '"]');
},
/**
* Fetch a card element on the homescreen by its identifier.
* For apps, the identifier is the manifestURL, or its manifestURL,
* followed by a '/' followed by its entry point. For pin, the
* identifier is the pinned URL.
*/
getCard: function(identifier) {
return this.client.findElement(
Homescreen.Selectors.pages + ' [data-id="' + identifier + '"]');
},
/**
* Fetch an icon element on the homescreen by its name.
*
* @param {String} name The name of the icon.
* @return {Marionette.Element}
*/
getIconByName: function(name) {
function getName(icon) {
return icon.shadowRoot.querySelector('#subtitle').textContent;
}
var icons = this.icons;
for (var i = 0, iLen = icons.length; i < iLen; i++) {
if (icons[i].scriptWith(getName) === name) {
return icons[i];
}
}
return null;
},
/**
* Returns a homescreen icon element's identifier.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
getIconId: function(icon) {
return icon.scriptWith(function(el) {
return el.dataset.identifier;
});
},
/**
* Returns a homescreen icon element's text.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
getIconText: function(icon) {
return icon.scriptWith(function(el) {
return el.shadowRoot.querySelector('#subtitle').textContent;
});
},
/**
* Returns a data-url of the image element in a homescreen icon, if it exists.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
getIconImage: function(icon) {
return icon.scriptWith(function(el) {
var image = el.shadowRoot.querySelector('#image-container img');
if (!image) {
return null;
}
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(image, 0, 0);
return canvas.toDataURL();
});
},
/**
* Returns a homescreen icon element's image URL.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
getIconImageUrl: function(icon) {
return icon.scriptWith(function(el) {
return el.dataset.testIconUrl;
});
},
/**
* Returns a homescreen card element's image URL.
*
* @param {Marionette.Element} card A homescreen card element reference.
*/
getCardImageUrl: function(card) {
return card.scriptWith(function(el) {
var icon = el.shadowRoot.querySelector('.icon-container i');
return icon.style.backgroundImage;
});
},
/**
* Waits for an icon's image URL to contain a certain value.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
* @0aram {String} string A string to search for in the image URL.
*/
waitForIconImageUrl: function(icon, string) {
this.client.waitFor(function() {
var src = this.getIconImageUrl(icon);
return src && src.indexOf(string) !== -1;
}.bind(this));
},
/**
* Returns true if a homescreen icon is in a loading state, false otherwise.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
iconIsLoading: function(icon) {
return icon.scriptWith(function(el) {
return el.shadowRoot.querySelector('#image-container').
classList.contains('downloading');
});
},
/**
* Launches an icon and switches to the corresponding application.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
launchIcon: function(icon) {
var identifier =
icon.scriptWith(function(el) { return el.dataset.identifier; });
icon.tap();
var identifierWithoutEntryPoint = identifier.replace(/\/[^\/]*$/, '');
var client = this.client.scope({ searchTimeout: 100 });
var frame;
client.waitFor(function() {
// wait for the app to show up
client.switchToFrame();
try {
frame = client.findElement('iframe[mozapp="' + identifier + '"]');
} catch (e1) {
try {
frame = client.findElement(
'iframe[mozapp="' + identifierWithoutEntryPoint + '"]');
} catch(e2) {
try {
frame = client.findElement(
'iframe[data-frame-origin*="' + identifier + '"]');
} catch (e3) {
// try again...
client.switchToFrame(this.system.getHomescreenIframe());
icon.tap();
return false;
}
}
}
client.switchToFrame(frame);
return true;
}.bind(this));
},
/**
* Scrolls the homescreen so that the given icon is located roughly in
* the center of the screen, vertically.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
scrollIconToCenter: function(icon) {
this.client.executeScript(function(icon, scrollable, position) {
var midPoint = scrollable.clientHeight / 2;
scrollable.scrollTop = scrollable.scrollTop + (position - midPoint);
}, [icon, this.appsScrollable, icon.location().y]);
},
/**
* Restart the homescreen then refocus on it.
*/
restart: function() {
this.client.executeScript(function() {
window.close();
});
// initialize our frames again since we killed the iframe
this.client.switchToFrame();
this.client.switchToFrame(this.system.getHomescreenIframe());
},
/**
* Perform an action in an action dialog.
*
* @param {Marionette.Element} dialog An action dialog element reference.
* @param {String} action The data-l10n-id of the action to perform.
*/
actionDialog: function(dialog, action) {
var button = this.client.helper.waitForElement(
'button[data-l10n-id="' + action + '"]');
button.tap();
this.client.helper.waitForElementToDisappear(button);
},
/**
* Click confirm on a particular type of confirmation dialog.
*
* @param {String} type of dialog.
* @param {String} selector of the button. Defaults to .confirm.
*/
confirmDialog: function(type, button) {
var selector = 'gaia-confirm[data-type="' + type + '"]';
var dialog = this.client.helper.waitForElement(selector);
var confirm;
this.client.waitFor(function() {
confirm = dialog.findElement(button || '.confirm');
return confirm && confirm.displayed();
});
// XXX: Hack to use faster polling
var quickly = this.client.scope({ searchTimeout: 50 });
confirm.client = quickly;
// tricky logic to ensure the dialog has been removed and clicked
this.client.waitFor(function() {
try {
// click the dialog to dismiss it
confirm.click();
// ensure it is either hidden or hits the stale element ref
return !confirm.displayed();
} catch (e) {
if (e.type === 'StaleElementReference') {
// element was successfully removed
return true;
}
throw e;
}
});
},
/**
* Emulates pressing of the hardware home button.
*/
pressHomeButton: function() {
this.client.executeScript(function() {
var home = new CustomEvent('home');
window.dispatchEvent(home);
});
this.client.apps.switchToApp(Homescreen.URL);
},
/**
* Gets a localized application name from a manifest.
* @param {String} app to open
* @param {String} entryPoint to open
* @param {String} locale
*/
localizedAppName: function(app, entryPoint, locale) {
if (!locale) {
locale = entryPoint;
entryPoint = null;
}
var file = 'app://' + app + '.gaiamobile.org/manifest.webapp';
// use a chrome-scoped Marionette client for the cross-domain XHR
var chromeClient = this.client.scope({context: 'chrome'});
var manifest = chromeClient.executeAsyncScript(function(file) {
var xhr = new XMLHttpRequest();
xhr.open('GET', file, true);
xhr.onload = function(o) {
var data = JSON.parse(xhr.response);
marionetteScriptFinished(data);
};
xhr.send(null);
}, [file]);
var locales;
if (entryPoint) {
locales = manifest.entry_points[entryPoint].locales;
} else {
locales = manifest.locales;
}
if (!locales) {
return false;
}
if (locale.indexOf('-x-ps') > -1) {
return this.client.executeScript(function(locale, name) {
var mozL10n = window.wrappedJSObject.navigator.mozL10n;
return mozL10n.qps[locale].translate(name);
}, [locale, locales['en-US'].name]);
}
return locales[locale].name;
},
/**
* Returns a localized string from a properties file.
* @param {String} key of the string to lookup.
*/
l10n: function(key) {
var string = this.client.executeScript(function(key) {
return window.wrappedJSObject.navigator.mozL10n.get(key);
}, [key]);
return string;
}
};
module.exports = Homescreen;