/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.extension.test.po; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.xwiki.extension.ExtensionId; import org.xwiki.test.ui.po.BaseElement; /** * Displays an extension. * * @version $Id: 6f9fca51a67d9a2cf3a95d9d14a548ff0949e62d $ * @since 4.2M1 */ public class ExtensionPane extends BaseElement { /** * The XPath used to locate an extension action button. */ private static final String ACTION_BUTTON_XPATH = ".//button[@name = 'extensionAction' and @value='%s' and normalize-space(text())='%s']"; /** * The name of the "class" attribute of HTML elements. */ private static final String CLASS_ATTRIBUTE = "class"; /** * The element that wraps the extension display. */ private final WebElement container; /** * Creates a new instance. * * @param container the element that wraps the extension display */ public ExtensionPane(WebElement container) { this.container = container; } /** * @return the extension status (loading, core, installed, remote, remote-installed, remote-core, * remote-installed-incompatible, remote-core-incompatible) */ public String getStatus() { String[] classNames = container.getAttribute(CLASS_ATTRIBUTE).split("\\s+"); if (classNames.length < 2) { return null; } return classNames[1].substring("extension-item-".length()); } /** * @return the extension status message */ public String getStatusMessage() { By xpath = By.xpath("*[@class = 'extension-header']//*[@class = 'extension-status']"); List<WebElement> found = getDriver().findElementsWithoutWaiting(container, xpath); return found.size() > 0 ? found.get(0).getText() : null; } /** * @return the extension identifier */ public ExtensionId getId() { return new ExtensionId(getName(), getVersion()); } /** * @return the extension name */ public String getName() { By xpath = By.xpath("*[@class = 'extension-header']//*[@class = 'extension-name']"); return getDriver().findElementWithoutWaiting(container, xpath).getText(); } /** * @return the extension version */ public String getVersion() { By xpath = By.xpath("*[@class = 'extension-header']//*[@class = 'extension-version']"); return getDriver().findElementWithoutWaiting(container, xpath).getText(); } /** * @return the extension authors */ public List<WebElement> getAuthors() { return getDriver().findElementsWithoutWaiting(container, By.className("extension-author")); } /** * @return the extension summary */ public String getSummary() { List<WebElement> found = getDriver().findElementsWithoutWaiting(container, By.className("extension-summary")); return found.size() > 0 ? found.get(0).getText() : null; } /** * Clicks on the show details button. * * @return the extension pane showing the extension details */ public ExtensionPane showDetails() { WebElement showDetailsButton = getShowDetailsButton(); if (showDetailsButton.getAttribute(CLASS_ATTRIBUTE).contains("visibilityAction")) { // Just toggle show/hide details. showDetailsButton.click(); return this; } else { // Retrieve the details. Wait until the extension body is not loading. return clickAndWaitUntilElementIsVisible(showDetailsButton, "/*[@class = 'extension-body']"); } } /** * @return the button used to show the extension details */ public WebElement getShowDetailsButton() { return maybeFindElement(By.xpath(String.format(ACTION_BUTTON_XPATH, "showDetails", "Show details"))); } /** * Clicks on the given button and waits for the specified element to be visible. * * @param button the button to be clicked * @param xpathSuffix the XPath suffix inside the 'extension-item' element * @return the new extension pane, after the specified element became visible */ private ExtensionPane clickAndWaitUntilElementIsVisible(WebElement button, String xpathSuffix) { String xpath = getXPath(); button.click(); getDriver().waitUntilElementIsVisible(By.xpath(xpath + xpathSuffix)); // We have to create a new extension pane because the DOM has changed. return new ExtensionPane(getDriver().findElementWithoutWaiting(By.xpath(xpath))); } /** * @return the XPath used to locate this extension */ private String getXPath() { String nameAndVersion = getDriver().findElementWithoutWaiting(container, By.className("extension-title")).getText(); return String.format("//form[contains(@class, 'extension-item') and descendant::*[" + "contains(@class, 'extension-title') and normalize-space(.) = '%s']]", nameAndVersion); } /** * Clicks on the hide details button. * * @return the extension pane that doesn't show the extension details */ public ExtensionPane hideDetails() { getHideDetailsButton().click(); return this; } /** * @return the button used to hide the extension details */ public WebElement getHideDetailsButton() { return maybeFindElement(By.xpath(String.format(ACTION_BUTTON_XPATH, "hideDetails", "Hide details"))); } /** * Clicks on the given button and waits for a confirmation or for the job/action to be done. * * @param button the button to be clicked * @return the extension pane showing the confirmation or the job log */ private ExtensionPane clickAndWaitForConfirmationOrJobDone(WebElement button) { // Wait until the the continue button is present or the extension is not loading and both the extension body and // the progress section are present and not loading. return clickAndWaitUntilElementIsVisible(button, "[descendant::button[@name = 'extensionAction' and @value = 'continue' and not(@disabled)] or (" + "not(contains(@class, 'loading')) and descendant::*[@class = 'extension-body']" + "/*[@class = 'extension-body-progress extension-body-section'])]"); } /** * Clicks on the install button and waits for the install plan to be computed. * * @return the extension pane displaying the install plan */ public ExtensionPane install() { return maybeOpenActionDropDownMenu().clickAndWaitForConfirmationOrJobDone(getInstallButton()); } /** * @return the install button, if present */ public WebElement getInstallButton() { return maybeFindElement(By.xpath(String.format(ACTION_BUTTON_XPATH, "install", "Install"))); } /** * @param locator specifies the element to look for * @return the specified element, if found, {@code null} otherwise */ private WebElement maybeFindElement(By locator) { List<WebElement> found = getDriver().findElementsWithoutWaiting(container, locator); return found.size() > 0 ? found.get(0) : null; } /** * Clicks on the drop-down toggle, if present, to expand the group of extension action buttons. * * @return this */ private ExtensionPane maybeOpenActionDropDownMenu() { String xpath = ".//*[@class = 'extension-actions']//*[@class = 'dropdown-toggle']"; List<WebElement> found = getDriver().findElementsWithoutWaiting(container, By.xpath(xpath)); if (found.size() > 0) { found.get(0).click(); } return this; } /** * Clicks on the uninstall button and waits for the uninstall plan to be computed. * * @return the extension pane displaying the uninstall plan */ public ExtensionPane uninstall() { return maybeOpenActionDropDownMenu().clickAndWaitForConfirmationOrJobDone(getUninstallButton()); } /** * @return the uninstall button, if present */ public WebElement getUninstallButton() { return maybeFindElement(By.xpath(String.format(ACTION_BUTTON_XPATH, "uninstall", "Uninstall"))); } /** * Clicks on the upgrade button and waits for the upgrade plan to be computed. * * @return the extension pane displaying the upgrade plan */ public ExtensionPane upgrade() { return maybeOpenActionDropDownMenu().clickAndWaitForConfirmationOrJobDone(getUpgradeButton()); } /** * @return the upgrade button, if present */ public WebElement getUpgradeButton() { return maybeFindElement(By.xpath(String.format(ACTION_BUTTON_XPATH, "install", "Upgrade"))); } /** * Clicks on the downgrade button and waits for the downgrade plan to be computed. * * @return the extension pane displaying the downgrade plan */ public ExtensionPane downgrade() { return maybeOpenActionDropDownMenu().clickAndWaitForConfirmationOrJobDone(getDowngradeButton()); } /** * @return the downgrade button, if present */ public WebElement getDowngradeButton() { return maybeFindElement(By.xpath(String.format(ACTION_BUTTON_XPATH, "install", "Downgrade"))); } /** * Confirms the current action and wait for it to be performed. * * @return the extension pane displaying the extension after the current action has been performed */ public ExtensionPane confirm() { return clickAndWaitForConfirmationOrJobDone(getContinueButton()); } /** * @return the button used to continue the current job or to execute a previously computed job plan */ public WebElement getContinueButton() { return maybeFindElement(By.xpath(String.format(ACTION_BUTTON_XPATH, "continue", "Continue"))); } /** * Clicks on the specified tab and returns the corresponding section if it's available. * * @param label the tab label * @return the element that wraps the section corresponding to the specified tab */ private WebElement clickTab(String label) { By tabXPath = By.xpath(".//*[@class = 'innerMenu']//a[normalize-space(.) = '" + label + "']"); List<WebElement> found = getDriver().findElementsWithoutWaiting(container, tabXPath); if (found.size() == 0) { return null; } String sectionAnchor = StringUtils.substringAfterLast(found.get(0).getAttribute("href"), "#"); found.get(0).click(); By sectionXPath = By.xpath(".//*[contains(@class, 'extension-body-section') and preceding-sibling::*[1][@id = '" + sectionAnchor + "']]"); return getDriver().findElementWithoutWaiting(container, sectionXPath); } /** * Selects the extension description tab. * * @return the extension description section */ public ExtensionDescriptionPane openDescriptionSection() { WebElement section = clickTab("Description"); return section != null ? new ExtensionDescriptionPane(section) : null; } /** * Selects the extension dependencies tab. * * @return the extension dependencies section */ public ExtensionDependenciesPane openDependenciesSection() { WebElement section = clickTab("Dependencies"); return section != null ? new ExtensionDependenciesPane(section) : null; } /** * Selects the extension progress tab. * * @return the extension progress section */ public ExtensionProgressPane openProgressSection() { WebElement section = clickTab("Progress"); return section != null ? new ExtensionProgressPane(section) : null; } /** * @return the progress bar pane */ public ProgressBarPane getProgressBar() { List<WebElement> found = getDriver().findElementsWithoutWaiting(container, By.className("ui-progress")); return found.size() != 1 ? null : new ProgressBarPane(found.get(0)); } }