mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
609 lines
20 KiB
JavaScript
609 lines
20 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
|
*/
|
|
|
|
// Tests that searching for add-ons works correctly
|
|
|
|
const PREF_GETADDONS_GETSEARCHRESULTS = "extensions.getAddons.search.url";
|
|
const SEARCH_URL = TESTROOT + "browser_searching.xml";
|
|
const NO_MATCH_URL = TESTROOT + "browser_searching_empty.xml";
|
|
|
|
const QUERY = "SEARCH";
|
|
const NO_MATCH_QUERY = "NOMATCHQUERY";
|
|
const REMOTE_TO_INSTALL = "remote1";
|
|
const REMOTE_INSTALL_URL = TESTROOT + "addons/browser_searching.xpi";
|
|
|
|
var gManagerWindow;
|
|
var gCategoryUtilities;
|
|
var gProvider;
|
|
var gServer;
|
|
var gAddonInstalled = false;
|
|
|
|
function test() {
|
|
requestLongerTimeout(2);
|
|
// Turn on searching for this test
|
|
Services.prefs.setIntPref(PREF_SEARCH_MAXRESULTS, 15);
|
|
|
|
waitForExplicitFinish();
|
|
|
|
gProvider = new MockProvider();
|
|
|
|
gProvider.createAddons([{
|
|
id: "addon1@tests.mozilla.org",
|
|
name: "PASS - f",
|
|
description: "Test description - SEARCH",
|
|
size: 3,
|
|
version: "1.0",
|
|
updateDate: new Date(2010, 4, 2, 0, 0, 1)
|
|
}, {
|
|
id: "fail-addon1@tests.mozilla.org",
|
|
name: "FAIL",
|
|
description: "Does not match query"
|
|
}, {
|
|
id: "addon2@tests.mozilla.org",
|
|
name: "PASS - c",
|
|
description: "Test description - reSEARCHing SEARCH SEARCH",
|
|
size: 6,
|
|
version: "2.0",
|
|
updateDate: new Date(2010, 4, 2, 0, 0, 0)
|
|
}]);
|
|
|
|
var installs = gProvider.createInstalls([{
|
|
name: "PASS - a - SEARCHing",
|
|
sourceURI: "http://example.com/install1.xpi"
|
|
}, {
|
|
name: "PASS - g - reSEARCHing SEARCH",
|
|
sourceURI: "http://example.com/install2.xpi"
|
|
}, {
|
|
// Does not match query
|
|
name: "FAIL",
|
|
sourceURI: "http://example.com/fail-install1.xpi"
|
|
}]);
|
|
|
|
installs.forEach(function(aInstall) { aInstall.install(); });
|
|
|
|
open_manager("addons://list/extension", function(aWindow) {
|
|
gManagerWindow = aWindow;
|
|
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
|
run_next_test();
|
|
});
|
|
}
|
|
|
|
function end_test() {
|
|
close_manager(gManagerWindow, function() {
|
|
var installedAddon = get_addon_item(REMOTE_TO_INSTALL).mAddon;
|
|
installedAddon.uninstall();
|
|
|
|
AddonManager.getAllInstalls(function(aInstallsList) {
|
|
for (var i = 0; i < aInstallsList.length; i++) {
|
|
var install = aInstallsList[i];
|
|
var sourceURI = install.sourceURI.spec;
|
|
if (sourceURI == REMOTE_INSTALL_URL ||
|
|
sourceURI.match(/^http:\/\/example\.com\/(.+)\.xpi$/) != null)
|
|
install.cancel();
|
|
}
|
|
|
|
finish();
|
|
});
|
|
});
|
|
}
|
|
|
|
function getAnonymousElementByAttribute(aElement, aName, aValue) {
|
|
return gManagerWindow.document.getAnonymousElementByAttribute(aElement,
|
|
aName,
|
|
aValue);
|
|
}
|
|
|
|
/*
|
|
* Checks whether or not the Add-ons Manager is currently searching
|
|
*
|
|
* @param aExpectedSearching
|
|
* The expected isSearching state
|
|
*/
|
|
function check_is_searching(aExpectedSearching) {
|
|
var loading = gManagerWindow.document.getElementById("search-loading");
|
|
is(!is_hidden(loading), aExpectedSearching,
|
|
"Search throbber should be showing iff currently searching");
|
|
}
|
|
|
|
/*
|
|
* Completes a search
|
|
*
|
|
* @param aQuery
|
|
* The query to search for
|
|
* @param aFinishImmediately
|
|
* Boolean representing whether or not the search is expected to
|
|
* finish immediately
|
|
* @param aCallback
|
|
* The callback to call when the search is done
|
|
* @param aCategoryType
|
|
* The expected selected category after the search is done.
|
|
* Optional and defaults to "search"
|
|
*/
|
|
function search(aQuery, aFinishImmediately, aCallback, aCategoryType) {
|
|
// Point search to the correct xml test file
|
|
var url = (aQuery == NO_MATCH_QUERY) ? NO_MATCH_URL : SEARCH_URL;
|
|
Services.prefs.setCharPref(PREF_GETADDONS_GETSEARCHRESULTS, url);
|
|
|
|
aCategoryType = aCategoryType ? aCategoryType : "search";
|
|
|
|
var searchBox = gManagerWindow.document.getElementById("header-search");
|
|
searchBox.value = aQuery;
|
|
|
|
EventUtils.synthesizeMouseAtCenter(searchBox, { }, gManagerWindow);
|
|
EventUtils.synthesizeKey("VK_RETURN", { }, gManagerWindow);
|
|
|
|
var finishImmediately = true;
|
|
wait_for_view_load(gManagerWindow, function() {
|
|
is(gCategoryUtilities.selectedCategory, aCategoryType, "Expected category view should be selected");
|
|
is(gCategoryUtilities.isTypeVisible("search"), aCategoryType == "search",
|
|
"Search category should only be visible if it is the current view");
|
|
check_is_searching(false);
|
|
is(finishImmediately, aFinishImmediately, "Search should finish immediately only if expected");
|
|
|
|
aCallback();
|
|
});
|
|
|
|
finishImmediately = false
|
|
if (!aFinishImmediately)
|
|
check_is_searching(true);
|
|
}
|
|
|
|
/*
|
|
* Return results of a search
|
|
*
|
|
* @return Array of objects, each containing the name and item of a specific
|
|
* result
|
|
*/
|
|
function get_actual_results() {
|
|
var list = gManagerWindow.document.getElementById("search-list");
|
|
var rows = list.getElementsByTagName("richlistitem");
|
|
|
|
var results = [];
|
|
for (var i = 0; i < rows.length; i++) {
|
|
var item = rows[i];
|
|
|
|
// Only consider items that are currently showing
|
|
var style = gManagerWindow.document.defaultView.getComputedStyle(item, "");
|
|
if (style.display == "none" || style.visibility != "visible")
|
|
continue;
|
|
|
|
if (item.mInstall || item.isPending("install")) {
|
|
var sourceURI = item.mInstall.sourceURI.spec;
|
|
if (sourceURI == REMOTE_INSTALL_URL) {
|
|
results.push({name: REMOTE_TO_INSTALL, item: item});
|
|
continue;
|
|
}
|
|
|
|
var result = sourceURI.match(/^http:\/\/example\.com\/(.+)\.xpi$/);
|
|
if (result != null) {
|
|
is(item.mInstall.name.indexOf("PASS"), 0, "Install name should start with PASS");
|
|
results.push({name: result[1], item: item});
|
|
continue;
|
|
}
|
|
}
|
|
else if (item.mAddon) {
|
|
var result = item.mAddon.id.match(/^(.+)@tests\.mozilla\.org$/);
|
|
if (result != null) {
|
|
is(item.mAddon.name.indexOf("PASS"), 0, "Addon name should start with PASS");
|
|
results.push({name: result[1], item: item});
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
ok(false, "Found an item in the list that was neither installing or installed");
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/*
|
|
* Returns expected results when searching for QUERY with default ordering
|
|
*
|
|
* @param aSortBy
|
|
* How the results are sorted (e.g. "name")
|
|
* @param aLocalExpected
|
|
* Boolean representing if local results are expected
|
|
* @return A pair: [array of results with an expected order,
|
|
* array of results with unknown order]
|
|
*/
|
|
function get_expected_results(aSortBy, aLocalExpected) {
|
|
var expectedOrder = null, unknownOrder = null;
|
|
switch (aSortBy) {
|
|
case "relevancescore":
|
|
expectedOrder = [ "remote4" , "addon2", "remote1" , "remote2",
|
|
"install2", "addon1", "install1", "remote3" ];
|
|
unknownOrder = [];
|
|
break;
|
|
case "name":
|
|
// Defaults to ascending order
|
|
expectedOrder = [ "install1", "remote1", "addon2" , "remote2",
|
|
"remote3" , "addon1" , "install2", "remote4" ];
|
|
unknownOrder = [];
|
|
break;
|
|
case "dateUpdated":
|
|
expectedOrder = [ "addon1", "addon2" ];
|
|
// Updated date not available for installs and remote add-ons
|
|
unknownOrder = [ "install1", "install2", "remote1",
|
|
"remote2" , "remote3" , "remote4" ];
|
|
break;
|
|
default:
|
|
ok(false, "Should recognize sortBy when checking the order of items");
|
|
}
|
|
|
|
// Only keep expected results
|
|
function filterResults(aId) {
|
|
// Include REMOTE_TO_INSTALL as a local add-on if it has been installed
|
|
if (gAddonInstalled && aId == REMOTE_TO_INSTALL)
|
|
return aLocalExpected;
|
|
|
|
if (aId.indexOf("addon") == 0 || aId.indexOf("install") == 0)
|
|
return aLocalExpected;
|
|
if (aId.indexOf("remote") == 0)
|
|
return !aLocalExpected;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
return [expectedOrder.filter(filterResults),
|
|
unknownOrder.filter(filterResults)]
|
|
}
|
|
|
|
/*
|
|
* Check that the actual and expected results are the same
|
|
*
|
|
* @param aQuery
|
|
* The search query used
|
|
* @param aSortBy
|
|
* How the results are sorted (e.g. "name")
|
|
* @param aReverseOrder
|
|
* Boolean representing if the results are in reverse default order
|
|
* @param aShowLocal
|
|
* Boolean representing if local results are being shown
|
|
*/
|
|
function check_results(aQuery, aSortBy, aReverseOrder, aShowLocal) {
|
|
var localFilterSelected = gManagerWindow.document.getElementById("search-filter-local").selected;
|
|
var remoteFilterSelected = gManagerWindow.document.getElementById("search-filter-remote").selected;
|
|
is(localFilterSelected, aShowLocal, "Local filter should be selected if showing local items");
|
|
is(remoteFilterSelected, !aShowLocal, "Remote filter should be selected if showing remote items");
|
|
|
|
// Get expected order assuming default order
|
|
var expectedOrder = [], unknownOrder = [];
|
|
if (aQuery == QUERY)
|
|
[expectedOrder, unknownOrder] = get_expected_results(aSortBy, aShowLocal);
|
|
|
|
// Get actual order of results
|
|
var actualResults = get_actual_results();
|
|
var actualOrder = [result.name for each(result in actualResults)];
|
|
|
|
// Reverse array of actual results if supposed to be in reverse order.
|
|
// Reverse actualOrder instead of expectedOrder so can always check
|
|
// expectedOrder before unknownOrder
|
|
if (aReverseOrder)
|
|
actualOrder.reverse();
|
|
|
|
// Check actual vs. expected list of results
|
|
var totalExpectedResults = expectedOrder.length + unknownOrder.length;
|
|
is(actualOrder.length, totalExpectedResults, "Should get correct number of results");
|
|
|
|
// Check the "first" and "last" attributes are set correctly
|
|
for (let i = 0; i < actualResults.length; i++) {
|
|
if (i == 0) {
|
|
is(actualResults[0].item.hasAttribute("first"), true,
|
|
"First item should have 'first' attribute set");
|
|
is(actualResults[0].item.hasAttribute("last"), false,
|
|
"First item should not have 'last' attribute set");
|
|
} else if (i == (actualResults.length - 1)) {
|
|
is(actualResults[actualResults.length - 1].item.hasAttribute("first"), false,
|
|
"Last item should not have 'first' attribute set");
|
|
is(actualResults[actualResults.length - 1].item.hasAttribute("last"), true,
|
|
"Last item should have 'last' attribute set");
|
|
} else {
|
|
is(actualResults[i].item.hasAttribute("first"), false,
|
|
"Item " + i + " should not have 'first' attribute set");
|
|
is(actualResults[i].item.hasAttribute("last"), false,
|
|
"Item " + i + " should not have 'last' attribute set");
|
|
}
|
|
}
|
|
|
|
var i = 0;
|
|
for (; i < expectedOrder.length; i++)
|
|
is(actualOrder[i], expectedOrder[i], "Should have seen expected item");
|
|
|
|
// Items with data that is unknown can appear in any order among themselves,
|
|
// so just check that these items exist
|
|
for (; i < actualOrder.length; i++) {
|
|
var unknownOrderIndex = unknownOrder.indexOf(actualOrder[i]);
|
|
ok(unknownOrderIndex >= 0, "Should expect to see item with data that is unknown");
|
|
unknownOrder[unknownOrderIndex] = null;
|
|
}
|
|
|
|
// Check status of empty notice
|
|
var emptyNotice = gManagerWindow.document.getElementById("search-list-empty");
|
|
is(emptyNotice.hidden, totalExpectedResults > 0,
|
|
"Empty notice should be hidden only if expecting shown items");
|
|
}
|
|
|
|
/*
|
|
* Check results of a search with different filterings
|
|
*
|
|
* @param aQuery
|
|
* The search query used
|
|
* @param aSortBy
|
|
* How the results are sorted (e.g. "name")
|
|
* @param aReverseOrder
|
|
* Boolean representing if the results are in reverse default order
|
|
*/
|
|
function check_filtered_results(aQuery, aSortBy, aReverseOrder) {
|
|
var localFilter = gManagerWindow.document.getElementById("search-filter-local");
|
|
var remoteFilter = gManagerWindow.document.getElementById("search-filter-remote");
|
|
|
|
var list = gManagerWindow.document.getElementById("search-list");
|
|
list.ensureElementIsVisible(localFilter);
|
|
|
|
// Check with showing local add-ons
|
|
EventUtils.synthesizeMouseAtCenter(localFilter, { }, gManagerWindow);
|
|
check_results(aQuery, aSortBy, aReverseOrder, true);
|
|
|
|
// Check with showing remote add-ons
|
|
EventUtils.synthesizeMouseAtCenter(remoteFilter, { }, gManagerWindow);
|
|
check_results(aQuery, aSortBy, aReverseOrder, false);
|
|
}
|
|
|
|
/*
|
|
* Get item for a specific add-on by name
|
|
*
|
|
* @param aName
|
|
* The name of the add-on to search for
|
|
* @return Row of add-on if found, null otherwise
|
|
*/
|
|
function get_addon_item(aName) {
|
|
var id = aName + "@tests.mozilla.org";
|
|
var list = gManagerWindow.document.getElementById("search-list");
|
|
var rows = list.getElementsByTagName("richlistitem");
|
|
for (var i = 0; i < rows.length; i++) {
|
|
var row = rows[i];
|
|
if (row.mAddon && row.mAddon.id == id)
|
|
return row;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* Get item for a specific install by name
|
|
*
|
|
* @param aName
|
|
* The name of the install to search for
|
|
* @return Row of install if found, null otherwise
|
|
*/
|
|
function get_install_item(aName) {
|
|
var sourceURI = "http://example.com/" + aName + ".xpi";
|
|
var list = gManagerWindow.document.getElementById("search-list");
|
|
var rows = list.getElementsByTagName("richlistitem");
|
|
for (var i = 0; i < rows.length; i++) {
|
|
var row = rows[i];
|
|
if (row.mInstall && row.mInstall.sourceURI.spec == sourceURI)
|
|
return row;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* Gets the install button for a specific item
|
|
*
|
|
* @param aItem
|
|
* The item to get the install button for
|
|
* @return The install button for aItem
|
|
*/
|
|
function get_install_button(aItem) {
|
|
isnot(aItem, null, "Item should not be null when checking state of install button");
|
|
var installStatus = getAnonymousElementByAttribute(aItem, "anonid", "install-status");
|
|
return getAnonymousElementByAttribute(installStatus, "anonid", "install-remote-btn");
|
|
}
|
|
|
|
|
|
// Tests that searching for the empty string does nothing when not in the search view
|
|
add_test(function() {
|
|
is(gCategoryUtilities.isTypeVisible("search"), false, "Search category should initially be hidden");
|
|
|
|
var selectedCategory = gCategoryUtilities.selectedCategory;
|
|
isnot(selectedCategory, "search", "Selected type should not initially be the search view");
|
|
search("", true, run_next_test, selectedCategory);
|
|
});
|
|
|
|
// Tests that the results from a query are sorted by relevancescore in descending order.
|
|
// Also test that double clicking non-install items goes to the detail view, and that
|
|
// only remote items have install buttons showing
|
|
add_test(function() {
|
|
search(QUERY, false, function() {
|
|
check_filtered_results(QUERY, "relevancescore", false);
|
|
|
|
var list = gManagerWindow.document.getElementById("search-list");
|
|
var results = get_actual_results();
|
|
for (var i = 0; i < results.length; i++) {
|
|
var result = results[i];
|
|
var installBtn = get_install_button(result.item);
|
|
is(installBtn.hidden, result.name.indexOf("remote") != 0,
|
|
"Install button should only be showing for remote items");
|
|
}
|
|
|
|
var currentIndex = -1;
|
|
function run_next_double_click_test() {
|
|
currentIndex++;
|
|
if (currentIndex >= results.length) {
|
|
run_next_test();
|
|
return;
|
|
}
|
|
|
|
var result = results[currentIndex];
|
|
if (result.name.indexOf("install") == 0) {
|
|
run_next_double_click_test();
|
|
return;
|
|
}
|
|
|
|
var item = result.item;
|
|
list.ensureElementIsVisible(item);
|
|
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow);
|
|
wait_for_view_load(gManagerWindow, function() {
|
|
var name = gManagerWindow.document.getElementById("detail-name").textContent;
|
|
is(name, item.mAddon.name, "Name in detail view should be correct");
|
|
var version = gManagerWindow.document.getElementById("detail-version").value;
|
|
is(version, item.mAddon.version, "Version in detail view should be correct");
|
|
|
|
EventUtils.synthesizeMouseAtCenter(gManagerWindow.document.getElementById("category-search"),
|
|
{ }, gManagerWindow);
|
|
wait_for_view_load(gManagerWindow, run_next_double_click_test);
|
|
});
|
|
}
|
|
|
|
run_next_double_click_test();
|
|
});
|
|
});
|
|
|
|
// Tests that the sorters and filters correctly manipulate the results
|
|
add_test(function() {
|
|
var sorters = gManagerWindow.document.getElementById("search-sorters");
|
|
var originalHandler = sorters.handler;
|
|
|
|
var sorterNames = ["name", "dateUpdated"];
|
|
var buttonIds = ["name-btn", "date-btn"];
|
|
var currentIndex = 0;
|
|
var currentReversed = false;
|
|
|
|
function run_sort_test() {
|
|
if (currentIndex >= sorterNames.length) {
|
|
sorters.handler = originalHandler;
|
|
run_next_test();
|
|
}
|
|
|
|
// Simulate clicking on a specific sorter
|
|
var buttonId = buttonIds[currentIndex];
|
|
var sorter = getAnonymousElementByAttribute(sorters, "anonid", buttonId);
|
|
EventUtils.synthesizeMouseAtCenter(sorter, { }, gManagerWindow);
|
|
}
|
|
|
|
sorters.handler = {
|
|
onSortChanged: function(aSortBy, aAscending) {
|
|
if (originalHandler && "onSortChanged" in originalHandler)
|
|
originalHandler.onSortChanged(aSortBy, aAscending);
|
|
|
|
check_filtered_results(QUERY, sorterNames[currentIndex], currentReversed);
|
|
|
|
if (currentReversed)
|
|
currentIndex++;
|
|
currentReversed = !currentReversed;
|
|
|
|
run_sort_test();
|
|
}
|
|
};
|
|
|
|
check_filtered_results(QUERY, "relevancescore", false);
|
|
run_sort_test();
|
|
});
|
|
|
|
// Tests that searching for the empty string does nothing when in search view
|
|
add_test(function() {
|
|
search("", true, function() {
|
|
check_filtered_results(QUERY, "dateUpdated", true);
|
|
run_next_test();
|
|
});
|
|
});
|
|
|
|
// Tests that clicking a different category hides the search query
|
|
add_test(function() {
|
|
gCategoryUtilities.openType("extension", function() {
|
|
is(gCategoryUtilities.isTypeVisible("search"), false, "Search category should be hidden");
|
|
is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
|
|
run_next_test();
|
|
});
|
|
});
|
|
|
|
// Tests that re-searching for query doesn't actually complete a new search,
|
|
// and the last sort is still used
|
|
add_test(function() {
|
|
search(QUERY, true, function() {
|
|
check_filtered_results(QUERY, "dateUpdated", true);
|
|
run_next_test();
|
|
});
|
|
});
|
|
|
|
// Tests that getting zero results works correctly
|
|
add_test(function() {
|
|
search(NO_MATCH_QUERY, false, function() {
|
|
check_filtered_results(NO_MATCH_QUERY, "relevancescore", false);
|
|
run_next_test();
|
|
});
|
|
});
|
|
|
|
// Tests that installing a remote add-on works
|
|
add_test(function() {
|
|
var installBtn = null;
|
|
|
|
var listener = {
|
|
onInstallEnded: function(aInstall, aAddon) {
|
|
// Don't immediately consider the installed add-on as local because
|
|
// if the user was filtering out local add-ons, the installed add-on
|
|
// would vanish. Only consider add-on as local on new searches.
|
|
|
|
aInstall.removeListener(this);
|
|
|
|
is(installBtn.hidden, true, "Install button should be hidden after install ended");
|
|
check_filtered_results(QUERY, "relevancescore", false);
|
|
run_next_test();
|
|
}
|
|
}
|
|
|
|
search(QUERY, false, function() {
|
|
var list = gManagerWindow.document.getElementById("search-list");
|
|
var remoteItem = get_addon_item(REMOTE_TO_INSTALL);
|
|
list.ensureElementIsVisible(remoteItem);
|
|
|
|
installBtn = get_install_button(remoteItem);
|
|
is(installBtn.hidden, false, "Install button should be showing before install");
|
|
remoteItem.mAddon.install.addListener(listener);
|
|
EventUtils.synthesizeMouseAtCenter(installBtn, { }, gManagerWindow);
|
|
});
|
|
});
|
|
|
|
// Tests that re-searching for query results in correct results
|
|
add_test(function() {
|
|
// Select a different category
|
|
gCategoryUtilities.openType("extension", function() {
|
|
is(gCategoryUtilities.isTypeVisible("search"), false, "Search category should be hidden");
|
|
is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
|
|
|
|
var installBtn = get_install_button(get_addon_item(REMOTE_TO_INSTALL));
|
|
is(installBtn.hidden, true, "Install button should be hidden for installed item");
|
|
|
|
search(QUERY, true, function() {
|
|
check_filtered_results(QUERY, "relevancescore", false);
|
|
run_next_test();
|
|
});
|
|
});
|
|
});
|
|
|
|
// Tests that restarting the manager doesn't change search results
|
|
add_test(function() {
|
|
restart_manager(gManagerWindow, null, function(aWindow) {
|
|
gManagerWindow = aWindow;
|
|
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
|
|
|
// Installed add-on is considered local on new search
|
|
gAddonInstalled = true;
|
|
|
|
search(QUERY, false, function() {
|
|
check_filtered_results(QUERY, "relevancescore", false);
|
|
|
|
var installBtn = get_install_button(get_addon_item(REMOTE_TO_INSTALL));
|
|
is(installBtn.hidden, true, "Install button should be hidden for installed item");
|
|
|
|
run_next_test();
|
|
});
|
|
});
|
|
});
|
|
|