VulpesOS/gaia/apps/communications/contacts/test/marionette/lib/contacts.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

477 lines
14 KiB
JavaScript
Executable file

'use strict';
/**
* Abstraction around contacts app.
* @constructor
* @param {Marionette.Client} client for operations.
*/
function Contacts(client) {
this.client = client;
this.client.setSearchTimeout(10000);
this.actions = this.client.loader.getActions();
}
/**
* @type String Origin of contacts app
*/
Contacts.URL = 'app://communications.gaiamobile.org';
Contacts.config = {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
};
Contacts.Selectors = {
body: 'body',
bodyReady: 'body .view-body',
settingsButton: '#settings-button',
confirmHeader: '#confirmation-message h1',
confirmBody: '#confirmation-message p',
confirmDismiss: '#confirmation-message menu button',
confirmDelete: '#confirmation-message .danger',
details: '#view-contact-details',
detailsEditContact: '#edit-contact-button',
detailsTelLabelFirst: '#phone-details-template-0 h2',
detailsTelButtonFirst: '.button.icon-call[data-tel]',
detailsEmail: '#contact-detail-inner #email-details-template-0 div.item',
detailsAddress: '#contact-detail-inner #address-details-template-0 div.item',
detailsOrg: '#contact-detail-inner #org-title',
detailsNote: '#contact-detail-inner #note-details-template-0',
detailsFindDuplicate: '#contact-detail-inner #find-merge-button',
detailsFavoriteButton: '#toggle-favorite',
detailsContactName: '#contact-name-title',
detailsHeader: '#details-view-header',
detailsSocialLabel: '#contact-detail-inner #details-list #social-label',
detailsSocialTemplate: '#contact-detail-inner #details-list .social-actions',
detailsCoverImage: '#cover-img',
detailsShareButton: '#contact-detail-inner #share_button',
findDupsButton: '#details-list #find-merge-button',
duplicateFrame: 'iframe[src*="matching_contacts.html"]',
duplicateHeader: '#title',
duplicateClose: '#merge-close',
duplicateMerge: '#merge-action',
exportButton: '#exportContacts button',
exportSDCard: '#export-sd-option button',
form: '#view-contact-form',
formTitle: '#contact-form-title',
formCustomTag: '#custom-tag',
formCustomTagPage: '#view-select-tag',
formCustomTagDone: '#view-select-tag #settings-done',
formNew: '#add-contact-button',
formGivenName: '#givenName',
formOrg: '#org',
formFamilyName: '#familyName',
formSave: '#save-button',
formTel: '#contacts-form-phones input[type="tel"]',
formDelFirstTel: '#add-phone-0 .img-delete-button',
formTelLabelFirst: '#tel_type_0',
formTelNumberFirst: '#number_0',
formTelNumberSecond: '#number_1',
formEmailFirst: '#email_0',
formEmailSecond: '#email_1',
formPhotoButton: '#photo-button',
formAddNewTel: '#add-new-phone',
formAddNewEmail: '#add-new-email',
formHeader: '#contact-form-header',
formPhotoImg: '#thumbnail-photo',
deleteContact: '#delete-contact',
groupList: ' #groups-list',
contacts: '#groups-list .contact-item',
list: '#view-contacts-list',
listContactFirst: 'li:not([data-group="ice"]).contact-item',
listContactFirstText: 'li:not([data-group="ice"]).contact-item p',
contactsItems: 'li:not([data-group="ice"]).contact-item p',
contactListHeader: '#contacts-list-header',
contactsIframe: '#iframe-contacts',
searchLabel: '#search-start',
searchInput: '#search-contact',
searchCancel: '#cancel-search',
searchResultFirst: '#search-list .contact-item',
scrollbar: 'nav[data-type="scrollbar"]',
overlay: 'nav[data-type="scrollbar"] p',
settingsView: '#view-settings',
settingsClose: '#settings-close',
bulkDelete: '#bulkDelete',
orderSwitch: '#settingsOrder gaia-switch',
changeOrder: '#switch',
editForm: '#selectable-form',
editMenu: '#select-all-wrapper',
selectAction: '#select-action',
selectAllButton: '#select-all',
clearOrgButton: '#clear-org',
setIceButton: '#set-ice',
iceHeader: '#ice-header',
iceSettingsHeader: '#ice-settings-header',
iceSettings: '#ice-settings',
iceSwitch1: '#ice-contacts-1-switch',
iceInputSwitch1: '#ice-contacts-1-switch input[type="checkbox"]',
iceSwitch2: '#ice-contacts-2-switch',
iceInputSwitch2: '#ice-contacts-2-switch input[type="checkbox"]',
iceButton1: '#select-ice-contact-1',
iceButton2: '#select-ice-contact-2',
iceGroupOpen: '#section-group-ice',
iceContact: '#ice-group .contact-item',
importContacts: '#importContacts',
importSDCard: '#import-sd-option button',
importHeader: '#import-settings-header',
activityChooser: 'form[data-type="action"]',
buttonActivityChooser: 'form[data-type="action"] button',
actionMenu: '#action-menu',
actionMenuList: '#value-menu',
actionButton: '.action-button',
multipleSelectSave: '#save-button',
multipleSelectStatus: '#statusMsg p',
multipleSelectList: '#multiple-select-container',
systemMenu: 'form[data-z-index-level="action-menu"]',
galleryImage: '.thumbnail img',
galleryDone: '#crop-done-button',
header: '#edit-title',
noContacts: '#no-contacts'
};
Contacts.prototype = {
/**
* Launches contacts app and focuses on frame.
*/
launch: function() {
this.client.apps.launch(Contacts.URL, 'contacts');
this.switchToApp();
this.client.helper.waitForElement(Contacts.Selectors.bodyReady);
},
relaunch: function() {
this.close();
this.launch();
},
close: function() {
this.client.apps.close(Contacts.URL, 'contacts');
},
switchToApp: function() {
this.client.switchToFrame();
// switchToApp already waits for the app to be displayed
this.client.apps.switchToApp(Contacts.URL, 'contacts');
},
switchToCreateNewContactActivity: function() {
this._switchToActivity('views/form/form.html');
},
switchToMergeInterface: function() {
this._switchToActivity('views/matching/matching_contacts.html');
},
_switchToActivity: function(href) {
this.client.switchToFrame();
// switchToActivity already waits for the app to be displayed
this.client.apps.switchToActivity(Contacts.URL, '/contacts/' + href);
},
/**
* Returns a localized string from a properties file.
* @param {String} file to open.
* @param {String} key of the string to lookup.
*/
l10n: function(file, key) {
var ast = this.client.executeScript(function(file, key) {
var xhr = new XMLHttpRequest();
var data;
xhr.open('GET', file, false); // Intentional sync
xhr.onload = function(o) {
data = JSON.parse(xhr.response);
};
xhr.send(null);
return data;
}, [file, key]);
for (var i = 0; i < ast.length; i++) {
if (ast[i].$i === key) {
return ast[i].$v;
}
}
},
waitSlideLeft: function(elementKey) {
var element = this.client.findElement(Contacts.Selectors[elementKey]),
location;
var test = function() {
location = element.location();
return location.x <= 0;
};
this.client.waitFor(test);
},
waitForSlideDown: function(element) {
var bodyHeight = this.client.findElement(Contacts.Selectors.body).
size().height;
var test = function() {
return element.location().y >= bodyHeight;
};
this.client.waitFor(test);
},
waitForSlideUp: function(element) {
var test = function() {
return element.location().y <= 0;
};
this.client.waitFor(test);
},
waitForFadeIn: function(element) {
var test = function() {
var opacity = element.cssProperty('opacity');
var pointerEvents = element.cssProperty('pointer-events');
return opacity == 1 && pointerEvents == 'auto';
};
this.client.waitFor(test);
},
waitForFormShown: function() {
var form = this.client.helper.waitForElement(Contacts.Selectors.form),
location;
var test = function() {
location = form.location();
return location.y <= 0;
};
this.client.waitFor(test);
},
waitForFormTransition: function() {
var selectors = Contacts.Selectors;
var form = this.client.findElement(selectors.form);
try {
this.client.helper.waitForElementToDisappear(form);
} catch (e) {
// When the contact details pages was created in an activity and we close
// it, we might encounter errors like "GenericError: can't access dead
// object" or 'JavaScriptError: unload was called'. So we drop them as it
// makes the tests crash.
var isKnownError = e.message.indexOf(
'GenericError: can\'t access dead object') > -1 ||
e.message.indexOf('JavaScriptError: unload was called') > -1;
if (!isKnownError) {
throw e;
}
}
},
editContact: function() {
var selectors = Contacts.Selectors;
var edit = this.client.helper.waitForElement(selectors.detailsEditContact);
this.clickOn(edit);
this.waitForFadeIn(this.client.helper.waitForElement(selectors.form));
},
// Goes back to the Contact List view from a Details view
backToList: function() {
var selectors = Contacts.Selectors;
// Now we go back to the ICE settings and check that our ICE remains
this.waitForFadeIn(this.client.helper.waitForElement(selectors.details));
var header = this.client.helper.waitForElement(selectors.detailsHeader);
this.actions.wait(0.5).tap(header, 10, 10).perform();
this.waitSlideLeft('list');
},
enterContactDetails: function(details) {
var selectors = Contacts.Selectors;
details = details || {
givenName: 'Hello',
familyName: 'Contact',
org: 'Enterprise'
};
this.waitForFormShown();
for (var i in details) {
// Camelcase details to match form.* selectors.
var key = 'form' + i.charAt(0).toUpperCase() + i.slice(1);
this.client.findElement(selectors[key])
.sendKeys(details[i]);
}
this.client.findElement(selectors.formSave).click();
},
addContact: function(details) {
var selectors = Contacts.Selectors;
var addContact = this.client.findElement(selectors.formNew);
addContact.click();
this.enterContactDetails(details);
this.waitForFormTransition();
this.client.helper.waitForElement(selectors.list);
},
mergeContact: function(details) {
var selectors = Contacts.Selectors;
var addContact = this.client.findElement(selectors.formNew);
addContact.click();
this.enterContactDetails(details);
this.switchToMergeInterface();
var duplicateFrame = this.client.findElement(selectors.duplicateFrame);
var mergeAction =
this.client.helper.waitForElement(selectors.duplicateMerge);
this.clickOn(mergeAction);
this.switchTo();
this.waitForSlideDown(duplicateFrame);
},
addContactMultipleEmails: function(details) {
var selectors = Contacts.Selectors;
var addContact = this.client.findElement(selectors.formNew);
addContact.click();
this.client.helper.waitForElement(selectors.formAddNewEmail).click();
this.enterContactDetails(details);
this.waitForFormTransition();
this.client.helper.waitForElement(selectors.list);
},
tapContact: function(contactName) {
// There are currently no way to search on the text of an HTML Element
// That's why this function do the filter and check that no more than 1
// contact has the name we look up.
var elements = this.client.findElements(Contacts.Selectors.contacts);
var contactsWithName = elements.filter(function(el) {
var data = el.text();
return data && data.indexOf(contactName) > -1;
});
if (contactsWithName.length === 1) {
contactsWithName[0].tap();
} else if (contactsWithName.length > 1) {
throw new Error('There are more than 1 contact that contains "' +
contactName + '"');
} else {
throw new Error('"' + contactName + '" was not found');
}
},
getContactFirstName: function() {
var ci = this.client.findElement(Contacts.Selectors.contactsIframe);
this.client.switchToFrame(ci);
var contactInContactList = this.client.findElement(
Contacts.Selectors.listContactFirst
);
var contactName = contactInContactList.text();
return contactName;
},
tapSave: function() {
this.client.helper.waitForElement(Contacts.Selectors.formSave).click();
},
createNewContactAndReturnToDialer: function(contactName) {
this.client.helper.waitForElement(Contacts.Selectors.formGivenName);
this.client.helper.fillInputField(
Contacts.Selectors.formGivenName, contactName
);
this.client.helper.waitForElement(Contacts.Selectors.formSave).click();
var dialer = this.client.loader.getAppClass('dialer');
dialer.switchTo();
return dialer;
},
tapUpdate: function() {
this.tapSave();
},
get systemMenu() {
var selectors = Contacts.Selectors;
// Switch to the system app first.
this.client.switchToFrame();
return this.client.helper.waitForElement(selectors.systemMenu);
},
/**
* Helper method to simulate clicks on iFrames which is not currently
* working in the Marionette JS Runner.
* @param {Marionette.Element} element The element to simulate the click on.
**/
clickOn: function(element) {
element.scriptWith(function(elementEl) {
var event = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': true
});
elementEl.dispatchEvent(event);
});
},
goToSettings: function() {
this.client.helper.waitForElement(
Contacts.Selectors.settingsButton).tap();
var settings = this.client.helper.waitForElement(
Contacts.Selectors.settingsView);
this.waitForFadeIn(settings);
},
getElementStyle: function(selector, type) {
return this.client.executeScript(function(selector, type) {
return document.querySelector(selector).style[type];
}, [selector, type]);
},
getNumberOfContacts: function() {
return this.contactsNames.length;
},
get contactsNames() {
try {
var contactElements = this.client.findElements(
Contacts.Selectors.contactsItems);
return contactElements.map((element) => {
return element.text();
});
} catch(e) {
return [];
}
}
};
module.exports = Contacts;