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

753 lines
20 KiB
JavaScript
Executable file

/* global require, module */
'use strict';
var assert = require('assert');
function Music(client, origin) {
this.client = client;
this.origin = origin || ('app://' + Music.DEFAULT_ORIGIN);
this.actions = client.loader.getActions();
}
module.exports = Music;
Music.DEFAULT_ORIGIN = 'music.gaiamobile.org';
Music.Selector = Object.freeze({
viewFrame: '#view-stack iframe',
secondaryViewFrame: '#view-stack iframe:nth-child(2)',
activeViewFrame: '#view-stack iframe.active',
homeViewFrame: 'iframe[src*="/views/home/index.html"]',
songsViewFrame: 'iframe[src*="/views/songs/index.html"]',
artistsViewFrame: 'iframe[src*="/views/artists/index.html"]',
artistDetailViewFrame: 'iframe[src*="/views/artist-detail/index.html"]',
albumsViewFrame: 'iframe[src*="/views/albums/index.html"]',
albumDetailViewFrame: 'iframe[src*="/views/album-detail/index.html"]',
playlistsViewFrame: 'iframe[src*="/views/playlists/index.html"]',
playlistDetailViewFrame: 'iframe[src*="/views/playlist-detail/index.html"]',
playerViewFrame: 'iframe[src*="/views/player/index.html"]',
homeViewFrameActive:
'iframe.active[src*="/views/home/index.html"]',
songsViewFrameActive:
'iframe.active[src*="/views/songs/index.html"]',
artistsViewFrameActive:
'iframe.active[src*="/views/artists/index.html"]',
artistDetailViewFrameActive:
'iframe.active[src*="/views/artist-detail/index.html"]',
albumsViewFrameActive:
'iframe.active[src*="/views/albums/index.html"]',
albumDetailViewFrameActive:
'iframe.active[src*="/views/album-detail/index.html"]',
playlistsViewFrameActive:
'iframe.active[src*="/views/playlists/index.html"]',
playlistDetailViewFrameActive:
'iframe.active[src*="/views/playlist-detail/index.html"]',
playerViewFrameActive:
'iframe.active[src*="/views/player/index.html"]',
messageOverlay: '#empty-overlay',
firstTile: '.tile',
tabBar: '#tab-bar',
playlistsTab: '#tab-bar button[value="playlists"]',
artistsTab: '#tab-bar button[value="artists"]',
albumsTab: '#tab-bar button[value="albums"]',
songsTab: '#tab-bar button[value="songs"]',
playerCover: '#artwork',
// search fields
searchBox: 'music-search-box',
searchResults: 'music-search-results',
searchField: '#input',
// search results
searchArtists: '#views-search-artists',
searchAlbums: '#views-search-albums',
searchTitles: '#views-search-titles',
searchNoResult: '#views-search-no-result',
tilesView: '#tiles',
listItem: '#list .gfl-item',
playButton: '#player-controls-play',
progressBar: '#player-seek-bar-progress',
shareButton: 'button[data-action="share"]',
shareMenu: 'form[data-z-index-level="action-menu"]',
pickDoneButton: '#title-done',
header: '#header',
titleText: '#header-title',
sublistShuffleButton: '#views-sublist-controls-shuffle',
playerIcon: '#player-button'
});
Music.prototype = {
client: null,
// debug function.
debugDocument: function() {
var debug = this.client.executeScript(function() {
return document.documentElement.innerHTML;
});
console.log('debug', debug);
},
debugShadowRootDocument: function(element) {
var debug = this.client.executeScript(function(element) {
return element.shadowRoot.innerHTML;
}, [element]);
console.log('debug', debug);
},
get viewFrame() {
return this.client.findElement(Music.Selector.viewFrame);
},
get secondaryViewFrame() {
return this.client.findElement(Music.Selector.secondaryViewFrame);
},
get activeViewFrame() {
return this.client.findElement(Music.Selector.activeViewFrame);
},
get homeViewFrame() {
return this.client.findElement(Music.Selector.homeViewFrame);
},
get artistsViewFrame() {
return this.client.findElement(Music.Selector.artistsViewFrame);
},
get artistDetailViewFrame() {
return this.client.findElement(Music.Selector.artistDetailViewFrame);
},
get albumsViewFrame() {
return this.client.findElement(Music.Selector.albumsViewFrame);
},
get albumDetailViewFrame() {
return this.client.findElement(Music.Selector.albumDetailViewFrame);
},
get songsViewFrame() {
return this.client.findElement(Music.Selector.songsViewFrame);
},
get playlistsViewFrame() {
return this.client.findElement(Music.Selector.playlistsViewFrame);
},
get playlistDetailViewFrame() {
return this.client.findElement(Music.Selector.playlistDetailViewFrame);
},
get playerViewFrame() {
return this.client.findElement(Music.Selector.playerViewFrame);
},
get messageOverlay() {
return this.client.findElement(Music.Selector.messageOverlay);
},
get firstTile() {
var homeView = this.homeViewFrame;
this.client.switchToFrame(homeView);
var element = this.client.findElement(Music.Selector.firstTile);
this.switchToMe();
return element;
},
get playlistsTab() {
return this.client.helper.waitForElement(Music.Selector.playlistsTab);
},
get artistsTab() {
return this.client.helper.waitForElement(Music.Selector.artistsTab);
},
get albumsTab() {
return this.client.helper.waitForElement(Music.Selector.albumsTab);
},
get songsTab() {
return this.client.helper.waitForElement(Music.Selector.songsTab);
},
// get the first song from the active frame.
get firstSong() {
return this.client.helper.waitForElement(Music.Selector.listItem);
},
getStarRating: function() {
var frame = this.playerViewFrame;
assert.ok(frame);
this.client.switchToFrame(frame);
this.client.switchToShadowRoot(
this.client.findElement(Music.Selector.playerCover));
var ratingEl = this.client.findElement('#rating');
this.client.switchToShadowRoot(ratingEl);
var container = this.client.findElement('#container');
var value = container.getAttribute('data-value');
this.client.switchToShadowRoot();
this.client.switchToShadowRoot();
this.switchToMe();
return value ? parseInt(value, 10) : 0;
},
getShuffleSetting: function() {
var frame = this.playerViewFrame;
assert.ok(frame);
this.client.switchToFrame(frame);
var artwork = this.client.findElement(Music.Selector.playerCover);
this.client.switchToShadowRoot(artwork);
assert.ok(artwork);
var shuffleBtn = this.client.findElement(
'#controls button[data-action="shuffle"]');
assert.ok(shuffleBtn);
var value = shuffleBtn.getAttribute('data-value');
assert.ok(value);
this.client.switchToShadowRoot();
this.switchToMe();
return value;
},
// Helper for the getter.
parseListItemsData: function(elements) {
var elementsData = [];
for (var i = 0; i < elements.length; i++) {
var data = {};
var a = elements[i];
// We ignore items that are hidden by CSS.
if (a.style.display === 'none') {
continue;
}
data.filePath = a.dataset.filePath;
data.href = a.href;
data.section = a.dataset.section;
data.index = elements[i].dataset.number;
var h3 = elements[i].getElementsByTagName('h3');
if (h3.length) {
data.title = h3[0].textContent;
}
var p = elements[i].getElementsByTagName('p');
if (p.length) {
data.text = p[0].textContent;
}
var img = elements[i].getElementsByTagName('img');
if (img.length) {
data.img = img[0].src;
}
elementsData.push(data);
}
return elementsData;
},
_getListItemsData: function(frame) {
var client = this.client;
assert.ok(frame, 'Frame must be valid.' + frame);
this.client.switchToFrame(frame);
// Not all lists have have images
var hasImages = client.executeScript(
'var elements = document.querySelectorAll(\'.gfl-item img\');' +
'return !!elements.length'
);
// Wait for images to be displayed
// before to reading their src.
if (hasImages) {
this.client.helper.waitForElement('.gfl-item img');
}
var listItems = this.client.executeScript(
'var parse = ' + this.parseListItemsData.toString() + '\n' +
'var elements = document.querySelectorAll(\'.gfl-item\');\n' +
'return parse(elements);\n'
);
this.switchToMe();
return listItems;
},
get firstListItem() {
return this.client.helper.waitForElement(Music.Selector.firstListItem);
},
get albumsListItemsData() {
return this._getListItemsData(this.albumsViewFrame);
},
get artistsListItemsData() {
return this._getListItemsData(this.artistsViewFrame);
},
get songsListItemsData() {
return this._getListItemsData(this.songsViewFrame);
},
get songs() {
return this._getListItemsData(this.activeViewFrame);
},
get playButton() {
return this.client.findElement(Music.Selector.playButton);
},
get shareButton() {
return this.client.findElement(Music.Selector.shareButton);
},
get title() {
var header = this.client.findElement(Music.Selector.header);
return header.findElement(Music.Selector.titleText);
},
get sublistShuffleButton() {
return this.client.helper.waitForElement(
Music.Selector.sublistShuffleButton);
},
// TODO(gareth): Move this shareMenu stuff into the helper.
get shareMenu() {
// Switch to the system app first.
this.client.switchToFrame();
return this.client.helper.waitForElement(Music.Selector.shareMenu);
},
get progressBar() {
return this.client.findElement(Music.Selector.progressBar);
},
get pickDoneButton() {
return this.client.findElement(Music.Selector.pickDoneButton);
},
get header() {
return this.client.findElement(Music.Selector.header);
},
get playerIcon() {
return this.client.findElement(Music.Selector.playerIcon);
},
get isPlaying() {
return this.playButton.getAttribute('class').indexOf('is-pause') === -1;
},
get songProgress() {
return parseFloat(this.progressBar.getAttribute('value'));
},
launch: function() {
this.client.switchToFrame();
this.client.apps.launch(this.origin);
this.client.apps.switchToApp(this.origin);
this.client.helper.waitForElement('body');
},
close: function() {
this.client.apps.close(this.origin);
},
switchToMe: function(options) {
options = options || {};
this.client.switchToFrame();
// Switch to music even when it is in background.
// We cannot use switchToApp here, because it will wait
// for the app to come to foreground.
if (options.background) {
var musicFrame = this.client.findElement('iframe[src*="' +
this.origin + '"]');
this.client.switchToFrame(musicFrame);
} else {
this.client.apps.switchToApp(this.origin);
}
},
waitForListEnumerate: function(frameSelector) {
var frame = this.client.findElement(frameSelector);
assert.ok(frame, 'Frame must be valid.' + frameSelector);
this.client.waitFor(function() {
var selector = frameSelector;
return this.client.executeScript(function(selector) {
return document.querySelector(selector);
}, [selector]);
}.bind(this));
this.client.switchToFrame(frame);
this.client.waitFor(function() {
return this.client.executeScript(function() {
return document.readyState === 'complete';
});
}.bind(this));
// Here we assume that if the body of the document
// is no longer hidden that we have content.
// See view.js:View.prototype.setupList
this.client.waitFor(function() {
return this.client.executeScript(function() {
return document.body.hidden === false;
});
}.bind(this));
this.switchToMe();
},
waitFinishedScanning: function() {
this.client.waitFor(function() {
return this.client.executeScript(function() {
return window.wrappedJSObject.Database.initialScanComplete === true;
});
}.bind(this));
},
waitForFirstTile: function() {
this.client.switchToFrame(this.homeViewFrame);
this.client.helper.waitForElement(Music.Selector.firstTile);
this.switchToMe();
},
waitForMessageOverlayShown: function(shouldBeShown) {
this.client.waitFor(function() {
var shown = this.messageOverlay.displayed();
return shown === shouldBeShown;
}.bind(this));
},
waitForAList: function(frame) {
assert.ok(frame, 'Frame must be valid ' + frame);
this.client.switchToFrame(frame);
this.client.waitFor(function() {
return this.client.executeScript(function() {
return document.querySelector('#list');
});
}.bind(this));
this.switchToMe();
},
// In sublist view.
// XXX allow in list view too.
waitForSongs: function(callback) {
this.client.waitFor(function() {
var songs = this.songs;
return callback(songs);
}.bind(this));
},
waitForListView: function() {
this.client.helper.waitForElement(Music.Selector.viewFrame);
},
waitForSecondaryListView: function() {
this.client.helper.waitForElement(Music.Selector.secondaryViewFrame);
},
waitForSongsView: function() {
this.client.helper.waitForElement(
Music.Selector.songsViewFrameActive);
},
waitForArtistsView: function() {
this.client.helper
.waitForElement(Music.Selector.artistsViewFrameActive);
},
waitForArtistDetailView: function() {
this.client.helper.waitForElement(
Music.Selector.artistDetailViewFrameActive);
},
waitForAlbumsView: function() {
this.client.helper.waitForElement(
Music.Selector.albumsViewFrameActive);
},
waitForAlbumDetailView: function() {
this.client.helper.waitForElement(
Music.Selector.albumDetailViewFrameActive);
},
waitForPlaylistsView: function() {
this.client.helper.waitForElement(
Music.Selector.playlistsViewFrameActive);
},
waitForPlaylistDetailView: function() {
this.client.helper.waitForElement(
Music.Selector.playlistDetailViewFrameActive);
},
waitForPlayerView: function() {
this.client.helper.waitForElement(
Music.Selector.playerViewFrameActive);
},
// we check for the attribute "hidden"
checkPlayerIconShown: function(shouldBeShown) {
var hidden = this.playerIcon.getAttribute('hidden');
var result = (hidden === 'false');
assert.equal(shouldBeShown, result);
},
showSearchInput: function(viewSelector) {
var frame = this.activeViewFrame;
assert.ok(frame);
this.client.switchToFrame(frame);
var view = this.client.findElement(viewSelector);
var chain = this.actions.press(view, 10, 10).perform();
chain.moveByOffset(0, 110).perform();
chain.release().perform();
this.switchToMe();
},
searchArtists: function(searchTerm) {
this.search(Music.Selector.artistsViewFrame, searchTerm);
},
searchTiles: function(searchTerm) {
this.search(Music.Selector.homeViewFrame, searchTerm);
},
search: function(viewSelector, searchTerm) {
assert.ok(viewSelector, 'Not a valid selector. Fix your test.');
var frame = this.client.findElement(viewSelector);
assert.ok(frame, viewSelector, 'can\'t be found.');
this.client.switchToFrame(frame);
var searchBox = this.client.helper.waitForElement(Music.Selector.searchBox);
this.client.switchToShadowRoot(searchBox);
var input = this.client.helper.waitForElement(Music.Selector.searchField);
assert.ok(input);
this.client.switchToShadowRoot(input);
var field = this.client.helper.waitForElement('input');
// clear search
// input.scriptWith(function(element) {
// // var input = element.querySelector('gaia-text-input');
// element.clear();
// });
this.client.waitFor(field.displayed.bind(field));
field.sendKeys(searchTerm);
this.client.switchToShadowRoot();
this.client.switchToShadowRoot();
this.switchToMe();
},
switchToArtistsView: function() {
this.artistsTab.tap();
this.waitForArtistsView();
},
switchToSongsView: function() {
this.songsTab.tap();
this.waitForSongsView();
},
switchToAlbumsView: function() {
this.albumsTab.tap();
this.waitForAlbumsView();
},
switchToPlaylistsView: function() {
this.playlistsTab.tap();
this.waitForPlaylistsView();
},
_selectItem: function(name, frame) {
assert.ok(frame, 'expected a valid frame');
this.client.switchToFrame(frame);
var elements = this.client.findElements('#list .gfl-item');
assert.ok(elements.length > 0);
var matching = elements.filter(function (element) {
return element.findElement('h3').text() === name;
});
assert.ok(matching.length > 0);
matching[0].tap();
this.switchToMe();
},
selectAlbum: function(name) {
this._selectItem(name, this.albumsViewFrame);
},
selectArtist: function(name) {
this._selectItem(name, this.artistsViewFrame);
},
selectPlaylist: function(name) {
this._selectItem(name, this.playlistsViewFrame);
},
// only from a list (song list)
playFirstSong: function() {
this.client.switchToFrame(this.songsViewFrame);
this.firstSong.click();
this.switchToMe();
},
playFirstSongByArtist: function() {
this.client.switchToFrame(this.artistDetailViewFrame);
this.firstSong.click();
this.switchToMe();
},
playFirstSongByAlbum: function() {
this.client.switchToFrame(this.albumDetailViewFrame);
this.firstSong.click();
this.switchToMe();
},
playFirstSongByPlaylist: function() {
this.client.switchToFrame(this.playlistDetailViewFrame);
this.firstSong.click();
this.switchToMe();
},
tapPlayButton: function() {
this.actions.tap(this.playButton).perform();
},
tapHeaderActionButton: function() {
this.header.tap(25, 25);
},
showSongInfo: function() {
var frame = this.playerViewFrame;
assert.ok(frame);
this.client.switchToFrame(frame);
var playerCover =
this.client.helper.waitForElement(Music.Selector.playerCover);
playerCover.click();
this.client.switchToShadowRoot(playerCover);
this.client.helper.waitForElement('#rating');
this.client.switchToShadowRoot();
this.switchToMe();
},
waitForRatingOverlayHidden: function() {
var frame = this.playerViewFrame;
assert.ok(frame);
this.client.switchToFrame(frame);
this.client.waitFor(function() {
var cover = this.client.findElement(Music.Selector.playerCover);
assert.ok(cover);
this.client.switchToShadowRoot(cover);
var container = this.client.findElement('#container');
assert.ok(container);
var isHidden = (container.getAttribute('class').split(' ').
indexOf('show-overlay') === -1);
this.client.switchToShadowRoot();
return isHidden;
}.bind(this));
this.switchToMe();
},
tapRating: function(rating) {
this.showSongInfo();
var frame = this.playerViewFrame;
assert.ok(frame);
this.client.switchToFrame(frame);
var cover = this.client.findElement(Music.Selector.playerCover);
this.client.switchToShadowRoot(cover);
assert.ok(cover);
var ratingEl = this.client.findElement('#rating');
assert.ok(rating);
this.client.switchToShadowRoot(ratingEl);
// we must really wait that the element be visible. or click will fail.
var star = this.client.helper.waitForElement(
'button[value="' + rating + '"]');
assert.ok(star);
star.click();
this.client.switchToShadowRoot();
this.client.switchToShadowRoot();
this.switchToMe();
},
shareWith: function(appName) {
// Allow findElement to fail quickly.
var quickly = this.client.scope({
searchTimeout: 50
});
var shareMenu;
// Wait until the share menu is displayed.
// Try to click the cover image followed by the share button in the case
// that it hides before we get a chance to click it.
this.client.waitFor(function() {
var frame = this.playerViewFrame;
assert.ok(frame);
this.client.switchToFrame(frame);
var cover = this.client.findElement(Music.Selector.playerCover);
this.client.switchToShadowRoot(cover);
assert.ok(cover);
this.shareButton.tap();
this.client.switchToShadowRoot();
this.client.switchToShadowRoot();
this.client.switchToFrame();
try {
shareMenu = quickly.findElement(Music.Selector.shareMenu);
} catch(e) {
this.client.apps.switchToApp(this.origin);
return false;
}
var isDisplayed = shareMenu.displayed();
this.client.apps.switchToApp(this.origin);
return isDisplayed;
}.bind(this));
var list = this.shareMenu.findElements('button');
for (var i = 0; i < list.length; i++) {
if (list[i].text() === appName) {
list[i].tap();
return;
}
}
},
finishPick: function() {
this.actions.tap(this.pickDoneButton).perform();
}
};