From 49f2ba16f03c27c562b67eb0461fbec9c97657cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 18 Feb 2018 23:52:44 +0100 Subject: [PATCH 1/6] Extract file list locators and steps to its own class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Besides the extraction some minor adjustments (sorting locators for file action menu entries to reflect the order of the menu entries in the UI, moving parametrized locators like "createMenuItemFor" above the locators that use them and placing "descendantOf" calls always in a new line) were made too. Signed-off-by: Daniel Calviño Sánchez --- tests/acceptance/config/behat.yml | 1 + .../features/bootstrap/FileListContext.php | 286 ++++++++++++++++++ .../features/bootstrap/FilesAppContext.php | 251 +-------------- 3 files changed, 288 insertions(+), 250 deletions(-) create mode 100644 tests/acceptance/features/bootstrap/FileListContext.php diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index 3495769457d..ba41618b895 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -12,6 +12,7 @@ default: - AppNavigationContext - CommentsAppContext - FeatureContext + - FileListContext - FilesAppContext - FilesSharingAppContext - LoginPageContext diff --git a/tests/acceptance/features/bootstrap/FileListContext.php b/tests/acceptance/features/bootstrap/FileListContext.php new file mode 100644 index 00000000000..65cd02a3abc --- /dev/null +++ b/tests/acceptance/features/bootstrap/FileListContext.php @@ -0,0 +1,286 @@ +. + * + */ + +use Behat\Behat\Context\Context; + +class FileListContext implements Context, ActorAwareInterface { + + use ActorAware; + + /** + * @return Locator + */ + public static function createMenuButton() { + return Locator::forThe()->css("#controls .button.new")-> + descendantOf(FilesAppContext::currentSectionMainView())-> + describedAs("Create menu button in Files app"); + } + + /** + * @return Locator + */ + private static function createMenuItemFor($newType) { + return Locator::forThe()->xpath("//div[contains(concat(' ', normalize-space(@class), ' '), ' newFileMenu ')]//span[normalize-space() = '$newType']/ancestor::li")-> + descendantOf(FilesAppContext::currentSectionMainView())-> + describedAs("Create $newType menu item in Files app"); + } + + /** + * @return Locator + */ + public static function createNewFolderMenuItem() { + return self::createMenuItemFor("New folder"); + } + + /** + * @return Locator + */ + public static function createNewFolderMenuItemNameInput() { + return Locator::forThe()->css(".filenameform input")-> + descendantOf(self::createNewFolderMenuItem())-> + describedAs("Name input in create new folder menu item in Files app"); + } + + /** + * @return Locator + */ + public static function rowForFile($fileName) { + return Locator::forThe()->xpath("//*[@id = 'fileList']//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName']/ancestor::tr")-> + descendantOf(FilesAppContext::currentSectionMainView())-> + describedAs("Row for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function rowForFilePreceding($fileName1, $fileName2) { + return Locator::forThe()->xpath("//preceding-sibling::tr//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName1']/ancestor::tr")-> + descendantOf(self::rowForFile($fileName2))-> + describedAs("Row for file $fileName1 preceding $fileName2 in Files app"); + } + + /** + * @return Locator + */ + public static function favoriteMarkForFile($fileName) { + return Locator::forThe()->css(".favorite-mark")-> + descendantOf(self::rowForFile($fileName))-> + describedAs("Favorite mark for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function notFavoritedStateIconForFile($fileName) { + return Locator::forThe()->css(".icon-star")-> + descendantOf(self::favoriteMarkForFile($fileName))-> + describedAs("Not favorited state icon for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function favoritedStateIconForFile($fileName) { + return Locator::forThe()->css(".icon-starred")-> + descendantOf(self::favoriteMarkForFile($fileName))-> + describedAs("Favorited state icon for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function mainLinkForFile($fileName) { + return Locator::forThe()->css(".name")-> + descendantOf(self::rowForFile($fileName))-> + describedAs("Main link for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function renameInputForFile($fileName) { + return Locator::forThe()->css("input.filename")-> + descendantOf(self::rowForFile($fileName))-> + describedAs("Rename input for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function shareActionForFile($fileName) { + return Locator::forThe()->css(".action-share")-> + descendantOf(self::rowForFile($fileName))-> + describedAs("Share action for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function fileActionsMenuButtonForFile($fileName) { + return Locator::forThe()->css(".action-menu")-> + descendantOf(self::rowForFile($fileName))-> + describedAs("File actions menu button for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function fileActionsMenu() { + return Locator::forThe()->css(".fileActionsMenu")-> + describedAs("File actions menu in Files app"); + } + + /** + * @return Locator + */ + private static function fileActionsMenuItemFor($itemText) { + return Locator::forThe()->xpath("//a[normalize-space() = '$itemText']")-> + descendantOf(self::fileActionsMenu())-> + describedAs($itemText . " item in file actions menu in Files app"); + } + + /** + * @return Locator + */ + public static function addToFavoritesMenuItem() { + return self::fileActionsMenuItemFor("Add to favorites"); + } + + /** + * @return Locator + */ + public static function removeFromFavoritesMenuItem() { + return self::fileActionsMenuItemFor("Remove from favorites"); + } + + /** + * @return Locator + */ + public static function detailsMenuItem() { + return self::fileActionsMenuItemFor("Details"); + } + + /** + * @return Locator + */ + public static function renameMenuItem() { + return self::fileActionsMenuItemFor("Rename"); + } + + /** + * @return Locator + */ + public static function viewFileInFolderMenuItem() { + return self::fileActionsMenuItemFor("View in folder"); + } + + /** + * @Given I create a new folder named :folderName + */ + public function iCreateANewFolderNamed($folderName) { + $this->actor->find(self::createMenuButton(), 10)->click(); + + $this->actor->find(self::createNewFolderMenuItem(), 2)->click(); + $this->actor->find(self::createNewFolderMenuItemNameInput(), 2)->setValue($folderName . "\r"); + } + + /** + * @Given I open the details view for :fileName + */ + public function iOpenTheDetailsViewFor($fileName) { + $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); + + $this->actor->find(self::detailsMenuItem(), 2)->click(); + } + + /** + * @Given I rename :fileName1 to :fileName2 + */ + public function iRenameTo($fileName1, $fileName2) { + $this->actor->find(self::fileActionsMenuButtonForFile($fileName1), 10)->click(); + + $this->actor->find(self::renameMenuItem(), 2)->click(); + + $this->actor->find(self::renameInputForFile($fileName1), 10)->setValue($fileName2 . "\r"); + } + + /** + * @Given I mark :fileName as favorite + */ + public function iMarkAsFavorite($fileName) { + $this->iSeeThatIsNotMarkedAsFavorite($fileName); + + $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); + + $this->actor->find(self::addToFavoritesMenuItem(), 2)->click(); + } + + /** + * @Given I unmark :fileName as favorite + */ + public function iUnmarkAsFavorite($fileName) { + $this->iSeeThatIsMarkedAsFavorite($fileName); + + $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); + + $this->actor->find(self::removeFromFavoritesMenuItem(), 2)->click(); + } + + /** + * @When I view :fileName in folder + */ + public function iViewInFolder($fileName) { + $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); + + $this->actor->find(self::viewFileInFolderMenuItem(), 2)->click(); + } + + /** + * @Then I see that the file list contains a file named :fileName + */ + public function iSeeThatTheFileListContainsAFileNamed($fileName) { + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFile($fileName), 10)); + } + + /** + * @Then I see that :fileName1 precedes :fileName2 in the file list + */ + public function iSeeThatPrecedesInTheFileList($fileName1, $fileName2) { + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFilePreceding($fileName1, $fileName2), 10)); + } + + /** + * @Then I see that :fileName is marked as favorite + */ + public function iSeeThatIsMarkedAsFavorite($fileName) { + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::favoritedStateIconForFile($fileName), 10)); + } + + /** + * @Then I see that :fileName is not marked as favorite + */ + public function iSeeThatIsNotMarkedAsFavorite($fileName) { + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::notFavoritedStateIconForFile($fileName), 10)); + } + +} diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 117f3b54fb8..34f83ce426f 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -241,185 +241,6 @@ class FilesAppContext implements Context, ActorAwareInterface { describedAs("Password protect working icon in the details view in Files app"); } - /** - * @return Locator - */ - public static function createMenuButton() { - return Locator::forThe()->css("#controls .button.new")-> - descendantOf(self::currentSectionMainView())-> - describedAs("Create menu button in Files app"); - } - - /** - * @return Locator - */ - public static function createNewFolderMenuItem() { - return self::createMenuItemFor("New folder"); - } - - /** - * @return Locator - */ - public static function createNewFolderMenuItemNameInput() { - return Locator::forThe()->css(".filenameform input")-> - descendantOf(self::createNewFolderMenuItem())-> - describedAs("Name input in create new folder menu item in Files app"); - } - - /** - * @return Locator - */ - private static function createMenuItemFor($newType) { - return Locator::forThe()->xpath("//div[contains(concat(' ', normalize-space(@class), ' '), ' newFileMenu ')]//span[normalize-space() = '$newType']/ancestor::li")-> - descendantOf(self::currentSectionMainView())-> - describedAs("Create $newType menu item in Files app"); - } - - /** - * @return Locator - */ - public static function rowForFile($fileName) { - return Locator::forThe()->xpath("//*[@id = 'fileList']//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName']/ancestor::tr")-> - descendantOf(self::currentSectionMainView())-> - describedAs("Row for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function rowForFilePreceding($fileName1, $fileName2) { - return Locator::forThe()->xpath("//preceding-sibling::tr//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName1']/ancestor::tr")-> - descendantOf(self::rowForFile($fileName2))-> - describedAs("Row for file $fileName1 preceding $fileName2 in Files app"); - } - - /** - * @return Locator - */ - public static function favoriteMarkForFile($fileName) { - return Locator::forThe()->css(".favorite-mark")->descendantOf(self::rowForFile($fileName))-> - describedAs("Favorite mark for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function notFavoritedStateIconForFile($fileName) { - return Locator::forThe()->css(".icon-star")->descendantOf(self::favoriteMarkForFile($fileName))-> - describedAs("Not favorited state icon for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function favoritedStateIconForFile($fileName) { - return Locator::forThe()->css(".icon-starred")->descendantOf(self::favoriteMarkForFile($fileName))-> - describedAs("Favorited state icon for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function mainLinkForFile($fileName) { - return Locator::forThe()->css(".name")->descendantOf(self::rowForFile($fileName))-> - describedAs("Main link for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function renameInputForFile($fileName) { - return Locator::forThe()->css("input.filename")->descendantOf(self::rowForFile($fileName))-> - describedAs("Rename input for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function shareActionForFile($fileName) { - return Locator::forThe()->css(".action-share")->descendantOf(self::rowForFile($fileName))-> - describedAs("Share action for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function fileActionsMenuButtonForFile($fileName) { - return Locator::forThe()->css(".action-menu")->descendantOf(self::rowForFile($fileName))-> - describedAs("File actions menu button for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function fileActionsMenu() { - return Locator::forThe()->css(".fileActionsMenu")-> - describedAs("File actions menu in Files app"); - } - - /** - * @return Locator - */ - public static function detailsMenuItem() { - return self::fileActionsMenuItemFor("Details"); - } - - /** - * @return Locator - */ - public static function renameMenuItem() { - return self::fileActionsMenuItemFor("Rename"); - } - - /** - * @return Locator - */ - public static function addToFavoritesMenuItem() { - return self::fileActionsMenuItemFor("Add to favorites"); - } - - /** - * @return Locator - */ - public static function removeFromFavoritesMenuItem() { - return self::fileActionsMenuItemFor("Remove from favorites"); - } - - /** - * @return Locator - */ - public static function viewFileInFolderMenuItem() { - return self::fileActionsMenuItemFor("View in folder"); - } - - /** - * @return Locator - */ - private static function fileActionsMenuItemFor($itemText) { - return Locator::forThe()->xpath("//a[normalize-space() = '$itemText']")-> - descendantOf(self::fileActionsMenu())-> - describedAs($itemText . " item in file actions menu in Files app"); - } - - /** - * @Given I create a new folder named :folderName - */ - public function iCreateANewFolderNamed($folderName) { - $this->actor->find(self::createMenuButton(), 10)->click(); - - $this->actor->find(self::createNewFolderMenuItem(), 2)->click(); - $this->actor->find(self::createNewFolderMenuItemNameInput(), 2)->setValue($folderName . "\r"); - } - - /** - * @Given I open the details view for :fileName - */ - public function iOpenTheDetailsViewFor($fileName) { - $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); - - $this->actor->find(self::detailsMenuItem(), 2)->click(); - } - /** * @Given I close the details view */ @@ -441,44 +262,11 @@ class FilesAppContext implements Context, ActorAwareInterface { $this->actor->find(self::tabHeaderInCurrentSectionDetailsViewNamed($tabName), 10)->click(); } - /** - * @Given I rename :fileName1 to :fileName2 - */ - public function iRenameTo($fileName1, $fileName2) { - $this->actor->find(self::fileActionsMenuButtonForFile($fileName1), 10)->click(); - - $this->actor->find(self::renameMenuItem(), 2)->click(); - - $this->actor->find(self::renameInputForFile($fileName1), 10)->setValue($fileName2 . "\r"); - } - - /** - * @Given I mark :fileName as favorite - */ - public function iMarkAsFavorite($fileName) { - $this->iSeeThatIsNotMarkedAsFavorite($fileName); - - $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); - - $this->actor->find(self::addToFavoritesMenuItem(), 2)->click(); - } - - /** - * @Given I unmark :fileName as favorite - */ - public function iUnmarkAsFavorite($fileName) { - $this->iSeeThatIsMarkedAsFavorite($fileName); - - $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); - - $this->actor->find(self::removeFromFavoritesMenuItem(), 2)->click(); - } - /** * @Given I share the link for :fileName */ public function iShareTheLinkFor($fileName) { - $this->actor->find(self::shareActionForFile($fileName), 10)->click(); + $this->actor->find(FileListContext::shareActionForFile($fileName), 10)->click(); $this->actor->find(self::shareLinkCheckbox(), 5)->click(); } @@ -499,15 +287,6 @@ class FilesAppContext implements Context, ActorAwareInterface { $this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::shareLinkField())->getValue(); } - /** - * @When I view :fileName in folder - */ - public function iViewInFolder($fileName) { - $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); - - $this->actor->find(self::viewFileInFolderMenuItem(), 2)->click(); - } - /** * @When I check the tag :tag in the dropdown for tags in the details view */ @@ -577,34 +356,6 @@ class FilesAppContext implements Context, ActorAwareInterface { } } - /** - * @Then I see that the file list contains a file named :fileName - */ - public function iSeeThatTheFileListContainsAFileNamed($fileName) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFile($fileName), 10)); - } - - /** - * @Then I see that :fileName1 precedes :fileName2 in the file list - */ - public function iSeeThatPrecedesInTheFileList($fileName1, $fileName2) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFilePreceding($fileName1, $fileName2), 10)); - } - - /** - * @Then I see that :fileName is marked as favorite - */ - public function iSeeThatIsMarkedAsFavorite($fileName) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::favoritedStateIconForFile($fileName), 10)); - } - - /** - * @Then I see that :fileName is not marked as favorite - */ - public function iSeeThatIsNotMarkedAsFavorite($fileName) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::notFavoritedStateIconForFile($fileName), 10)); - } - /** * @Then I see that the file name shown in the details view is :fileName */ From 10490f2ac3d3ec75925a06e3e5dbdeb2afec475a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 19 Feb 2018 10:32:11 +0100 Subject: [PATCH 2/6] Store the name of the actor in the Actor object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is needed to be able to easily use the actor as a key in an array. Signed-off-by: Daniel Calviño Sánchez --- tests/acceptance/features/core/Actor.php | 18 +++++++++++++++++- .../acceptance/features/core/ActorContext.php | 4 ++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/acceptance/features/core/Actor.php b/tests/acceptance/features/core/Actor.php index bf2f5a7367d..f47373593e9 100644 --- a/tests/acceptance/features/core/Actor.php +++ b/tests/acceptance/features/core/Actor.php @@ -60,6 +60,11 @@ */ class Actor { + /** + * @var string + */ + private $name; + /** * @var \Behat\Mink\Session */ @@ -83,18 +88,29 @@ class Actor { /** * Creates a new Actor. * + * @param string $name the name of the actor. * @param \Behat\Mink\Session $session the Mink Session used to control its * web browser. * @param string $baseUrl the base URL used when solving relative URLs. * @param array $sharedNotebook the notebook shared between all actors. */ - public function __construct(\Behat\Mink\Session $session, $baseUrl, &$sharedNotebook) { + public function __construct($name, \Behat\Mink\Session $session, $baseUrl, &$sharedNotebook) { + $this->name = $name; $this->session = $session; $this->baseUrl = $baseUrl; $this->sharedNotebook = &$sharedNotebook; $this->findTimeoutMultiplier = 1; } + /** + * Returns the name of this Actor. + * + * @return string the name of this Actor. + */ + public function getName() { + return $this->name; + } + /** * Sets the base URL. * diff --git a/tests/acceptance/features/core/ActorContext.php b/tests/acceptance/features/core/ActorContext.php index d6fb63694ec..2cdc4b01ff1 100644 --- a/tests/acceptance/features/core/ActorContext.php +++ b/tests/acceptance/features/core/ActorContext.php @@ -135,7 +135,7 @@ class ActorContext extends RawMinkContext { $this->actors = array(); $this->sharedNotebook = array(); - $this->actors["default"] = new Actor($this->getSession(), $this->getMinkParameter("base_url"), $this->sharedNotebook); + $this->actors["default"] = new Actor("default", $this->getSession(), $this->getMinkParameter("base_url"), $this->sharedNotebook); $this->actors["default"]->setFindTimeoutMultiplier($this->actorTimeoutMultiplier); $this->currentActor = $this->actors["default"]; @@ -159,7 +159,7 @@ class ActorContext extends RawMinkContext { */ public function iActAs($actorName) { if (!array_key_exists($actorName, $this->actors)) { - $this->actors[$actorName] = new Actor($this->getSession($actorName), $this->getMinkParameter("base_url"), $this->sharedNotebook); + $this->actors[$actorName] = new Actor($actorName, $this->getSession($actorName), $this->getMinkParameter("base_url"), $this->sharedNotebook); $this->actors[$actorName]->setFindTimeoutMultiplier($this->actorTimeoutMultiplier); } From 766135cb644bf0e9561d3271e52ff9bae8c386ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 19 Feb 2018 14:24:01 +0100 Subject: [PATCH 3/6] Generalize file list locators so a specific ancestor can be used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file list is used in other places besides the Files app (for example, the File sharing app); in those cases the locators for the file list elements are the same, but not for the ancestor of the file list. To make possible to reuse the file list locators in those cases too now they receive the ancestor to use. Note that the locators for the file actions menu were not using an ancestor locator because it is expected that there is only one file actions menu at a time in the whole page; that may change in the future, but for the time being it is a valid assumption and thus the ancestor was not added to those locators in this commit. Although the locators were generalized the steps themselves still use the "FilesAppContext::currentSectionMainView" locator as ancestor; the steps will be generalized in a following commit. Signed-off-by: Daniel Calviño Sánchez --- .../features/bootstrap/FileListContext.php | 118 ++++++++++-------- .../features/bootstrap/FilesAppContext.php | 2 +- 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/tests/acceptance/features/bootstrap/FileListContext.php b/tests/acceptance/features/bootstrap/FileListContext.php index 65cd02a3abc..d15e8ba40f4 100644 --- a/tests/acceptance/features/bootstrap/FileListContext.php +++ b/tests/acceptance/features/bootstrap/FileListContext.php @@ -27,119 +27,131 @@ class FileListContext implements Context, ActorAwareInterface { use ActorAware; + /** + * @var Locator + */ + private $fileListAncestor; + + /** + * @BeforeScenario + */ + public function initializeFileListAncestor() { + $this->fileListAncestor = FilesAppContext::currentSectionMainView(); + } + /** * @return Locator */ - public static function createMenuButton() { + public static function createMenuButton($fileListAncestor) { return Locator::forThe()->css("#controls .button.new")-> - descendantOf(FilesAppContext::currentSectionMainView())-> - describedAs("Create menu button in Files app"); + descendantOf($fileListAncestor)-> + describedAs("Create menu button in file list"); } /** * @return Locator */ - private static function createMenuItemFor($newType) { + private static function createMenuItemFor($fileListAncestor, $newType) { return Locator::forThe()->xpath("//div[contains(concat(' ', normalize-space(@class), ' '), ' newFileMenu ')]//span[normalize-space() = '$newType']/ancestor::li")-> - descendantOf(FilesAppContext::currentSectionMainView())-> - describedAs("Create $newType menu item in Files app"); + descendantOf($fileListAncestor)-> + describedAs("Create $newType menu item in file list"); } /** * @return Locator */ - public static function createNewFolderMenuItem() { - return self::createMenuItemFor("New folder"); + public static function createNewFolderMenuItem($fileListAncestor) { + return self::createMenuItemFor($fileListAncestor, "New folder"); } /** * @return Locator */ - public static function createNewFolderMenuItemNameInput() { + public static function createNewFolderMenuItemNameInput($fileListAncestor) { return Locator::forThe()->css(".filenameform input")-> - descendantOf(self::createNewFolderMenuItem())-> - describedAs("Name input in create new folder menu item in Files app"); + descendantOf(self::createNewFolderMenuItem($fileListAncestor))-> + describedAs("Name input in create new folder menu item in file list"); } /** * @return Locator */ - public static function rowForFile($fileName) { + public static function rowForFile($fileListAncestor, $fileName) { return Locator::forThe()->xpath("//*[@id = 'fileList']//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName']/ancestor::tr")-> - descendantOf(FilesAppContext::currentSectionMainView())-> - describedAs("Row for file $fileName in Files app"); + descendantOf($fileListAncestor)-> + describedAs("Row for file $fileName in file list"); } /** * @return Locator */ - public static function rowForFilePreceding($fileName1, $fileName2) { + public static function rowForFilePreceding($fileListAncestor, $fileName1, $fileName2) { return Locator::forThe()->xpath("//preceding-sibling::tr//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName1']/ancestor::tr")-> - descendantOf(self::rowForFile($fileName2))-> - describedAs("Row for file $fileName1 preceding $fileName2 in Files app"); + descendantOf(self::rowForFile($fileListAncestor, $fileName2))-> + describedAs("Row for file $fileName1 preceding $fileName2 in file list"); } /** * @return Locator */ - public static function favoriteMarkForFile($fileName) { + public static function favoriteMarkForFile($fileListAncestor, $fileName) { return Locator::forThe()->css(".favorite-mark")-> - descendantOf(self::rowForFile($fileName))-> - describedAs("Favorite mark for file $fileName in Files app"); + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Favorite mark for file $fileName in file list"); } /** * @return Locator */ - public static function notFavoritedStateIconForFile($fileName) { + public static function notFavoritedStateIconForFile($fileListAncestor, $fileName) { return Locator::forThe()->css(".icon-star")-> - descendantOf(self::favoriteMarkForFile($fileName))-> - describedAs("Not favorited state icon for file $fileName in Files app"); + descendantOf(self::favoriteMarkForFile($fileListAncestor, $fileName))-> + describedAs("Not favorited state icon for file $fileName in file list"); } /** * @return Locator */ - public static function favoritedStateIconForFile($fileName) { + public static function favoritedStateIconForFile($fileListAncestor, $fileName) { return Locator::forThe()->css(".icon-starred")-> - descendantOf(self::favoriteMarkForFile($fileName))-> - describedAs("Favorited state icon for file $fileName in Files app"); + descendantOf(self::favoriteMarkForFile($fileListAncestor, $fileName))-> + describedAs("Favorited state icon for file $fileName in file list"); } /** * @return Locator */ - public static function mainLinkForFile($fileName) { + public static function mainLinkForFile($fileListAncestor, $fileName) { return Locator::forThe()->css(".name")-> - descendantOf(self::rowForFile($fileName))-> - describedAs("Main link for file $fileName in Files app"); + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Main link for file $fileName in file list"); } /** * @return Locator */ - public static function renameInputForFile($fileName) { + public static function renameInputForFile($fileListAncestor, $fileName) { return Locator::forThe()->css("input.filename")-> - descendantOf(self::rowForFile($fileName))-> - describedAs("Rename input for file $fileName in Files app"); + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Rename input for file $fileName in file list"); } /** * @return Locator */ - public static function shareActionForFile($fileName) { + public static function shareActionForFile($fileListAncestor, $fileName) { return Locator::forThe()->css(".action-share")-> - descendantOf(self::rowForFile($fileName))-> - describedAs("Share action for file $fileName in Files app"); + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Share action for file $fileName in file list"); } /** * @return Locator */ - public static function fileActionsMenuButtonForFile($fileName) { + public static function fileActionsMenuButtonForFile($fileListAncestor, $fileName) { return Locator::forThe()->css(".action-menu")-> - descendantOf(self::rowForFile($fileName))-> - describedAs("File actions menu button for file $fileName in Files app"); + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("File actions menu button for file $fileName in file list"); } /** @@ -147,7 +159,7 @@ class FileListContext implements Context, ActorAwareInterface { */ public static function fileActionsMenu() { return Locator::forThe()->css(".fileActionsMenu")-> - describedAs("File actions menu in Files app"); + describedAs("File actions menu in file list"); } /** @@ -156,7 +168,7 @@ class FileListContext implements Context, ActorAwareInterface { private static function fileActionsMenuItemFor($itemText) { return Locator::forThe()->xpath("//a[normalize-space() = '$itemText']")-> descendantOf(self::fileActionsMenu())-> - describedAs($itemText . " item in file actions menu in Files app"); + describedAs($itemText . " item in file actions menu in file list"); } /** @@ -198,17 +210,17 @@ class FileListContext implements Context, ActorAwareInterface { * @Given I create a new folder named :folderName */ public function iCreateANewFolderNamed($folderName) { - $this->actor->find(self::createMenuButton(), 10)->click(); + $this->actor->find(self::createMenuButton($this->fileListAncestor), 10)->click(); - $this->actor->find(self::createNewFolderMenuItem(), 2)->click(); - $this->actor->find(self::createNewFolderMenuItemNameInput(), 2)->setValue($folderName . "\r"); + $this->actor->find(self::createNewFolderMenuItem($this->fileListAncestor), 2)->click(); + $this->actor->find(self::createNewFolderMenuItemNameInput($this->fileListAncestor), 2)->setValue($folderName . "\r"); } /** * @Given I open the details view for :fileName */ public function iOpenTheDetailsViewFor($fileName) { - $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); + $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click(); $this->actor->find(self::detailsMenuItem(), 2)->click(); } @@ -217,11 +229,11 @@ class FileListContext implements Context, ActorAwareInterface { * @Given I rename :fileName1 to :fileName2 */ public function iRenameTo($fileName1, $fileName2) { - $this->actor->find(self::fileActionsMenuButtonForFile($fileName1), 10)->click(); + $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName1), 10)->click(); $this->actor->find(self::renameMenuItem(), 2)->click(); - $this->actor->find(self::renameInputForFile($fileName1), 10)->setValue($fileName2 . "\r"); + $this->actor->find(self::renameInputForFile($this->fileListAncestor, $fileName1), 10)->setValue($fileName2 . "\r"); } /** @@ -230,7 +242,7 @@ class FileListContext implements Context, ActorAwareInterface { public function iMarkAsFavorite($fileName) { $this->iSeeThatIsNotMarkedAsFavorite($fileName); - $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); + $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click(); $this->actor->find(self::addToFavoritesMenuItem(), 2)->click(); } @@ -241,7 +253,7 @@ class FileListContext implements Context, ActorAwareInterface { public function iUnmarkAsFavorite($fileName) { $this->iSeeThatIsMarkedAsFavorite($fileName); - $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); + $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click(); $this->actor->find(self::removeFromFavoritesMenuItem(), 2)->click(); } @@ -250,7 +262,7 @@ class FileListContext implements Context, ActorAwareInterface { * @When I view :fileName in folder */ public function iViewInFolder($fileName) { - $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click(); + $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click(); $this->actor->find(self::viewFileInFolderMenuItem(), 2)->click(); } @@ -259,28 +271,28 @@ class FileListContext implements Context, ActorAwareInterface { * @Then I see that the file list contains a file named :fileName */ public function iSeeThatTheFileListContainsAFileNamed($fileName) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFile($fileName), 10)); + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFile($this->fileListAncestor, $fileName), 10)); } /** * @Then I see that :fileName1 precedes :fileName2 in the file list */ public function iSeeThatPrecedesInTheFileList($fileName1, $fileName2) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFilePreceding($fileName1, $fileName2), 10)); + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFilePreceding($this->fileListAncestor, $fileName1, $fileName2), 10)); } /** * @Then I see that :fileName is marked as favorite */ public function iSeeThatIsMarkedAsFavorite($fileName) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::favoritedStateIconForFile($fileName), 10)); + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::favoritedStateIconForFile($this->fileListAncestor, $fileName), 10)); } /** * @Then I see that :fileName is not marked as favorite */ public function iSeeThatIsNotMarkedAsFavorite($fileName) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::notFavoritedStateIconForFile($fileName), 10)); + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::notFavoritedStateIconForFile($this->fileListAncestor, $fileName), 10)); } } diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 34f83ce426f..81ba7b69844 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -266,7 +266,7 @@ class FilesAppContext implements Context, ActorAwareInterface { * @Given I share the link for :fileName */ public function iShareTheLinkFor($fileName) { - $this->actor->find(FileListContext::shareActionForFile($fileName), 10)->click(); + $this->actor->find(FileListContext::shareActionForFile(self::currentSectionMainView(), $fileName), 10)->click(); $this->actor->find(self::shareLinkCheckbox(), 5)->click(); } From fcd6cf08e0d146f3daf3b6c58a3ad32a5c062659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 19 Feb 2018 16:58:57 +0100 Subject: [PATCH 4/6] Generalize file list steps so a specific ancestor can be used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "FileListContext" provides steps to interact with and check the behaviour of a file list. However, the "FileListContext" does not know the right file list ancestor that has to be used by the file list steps, so until now the file list steps were explicitly wired to the Files app and they could be used only in that case. Instead of duplicating the steps with a slightly different name (for example, "I create a new folder named :folderName in the public shared folder" instead of "I create a new folder named :folderName") the steps were generalized; now contexts that "know" that certain file list ancestor has to be used by the FileListContext steps performed by certain actor from that point on (until changed again) set it explicitly. For example, when the current page is the Files app then the ancestor of the file list is the main view of the current section of the Files app, but when the current page is a shared link then the ancestor is set to null (because there will be just one file list, and thus its ancestor is not relevant to differentiate between instances) A helper trait, "FileListAncestorSetter", was introduced to reduce the boilerplate needed to set the file list ancestor from other contexts. Signed-off-by: Daniel Calviño Sánchez --- .../bootstrap/FileListAncestorSetter.php | 67 +++++++++++++++++++ .../features/bootstrap/FileListContext.php | 45 ++++++++++++- .../features/bootstrap/FilesAppContext.php | 3 + .../bootstrap/FilesSharingAppContext.php | 3 + 4 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 tests/acceptance/features/bootstrap/FileListAncestorSetter.php diff --git a/tests/acceptance/features/bootstrap/FileListAncestorSetter.php b/tests/acceptance/features/bootstrap/FileListAncestorSetter.php new file mode 100644 index 00000000000..2f8d3ad00e5 --- /dev/null +++ b/tests/acceptance/features/bootstrap/FileListAncestorSetter.php @@ -0,0 +1,67 @@ +. + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; + +/** + * Helper trait to set the ancestor of the file list. + * + * The FileListContext provides steps to interact with and check the behaviour + * of a file list. However, the FileListContext does not know the right file + * list ancestor that has to be used by the file list steps; this has to be set + * from other contexts, for example, when the Files app or the public page for a + * shared folder is opened. + * + * Contexts that "know" that certain file list ancestor has to be used by the + * FileListContext steps should use this trait and call + * "setFileListAncestorForActor" when needed. + */ +trait FileListAncestorSetter { + + /** + * @var FileListContext + */ + private $fileListContext; + + /** + * @BeforeScenario + */ + public function getSiblingFileListContext(BeforeScenarioScope $scope) { + $environment = $scope->getEnvironment(); + + $this->fileListContext = $environment->getContext("FileListContext"); + } + + /** + * Sets the file list ancestor to be used in the file list steps performed + * by the given actor. + * + * @param null|Locator $fileListAncestor the file list ancestor + * @param Actor $actor the actor + */ + private function setFileListAncestorForActor($fileListAncestor, Actor $actor) { + $this->fileListContext->setFileListAncestorForActor($fileListAncestor, $actor); + } + +} diff --git a/tests/acceptance/features/bootstrap/FileListContext.php b/tests/acceptance/features/bootstrap/FileListContext.php index d15e8ba40f4..236adafcbf8 100644 --- a/tests/acceptance/features/bootstrap/FileListContext.php +++ b/tests/acceptance/features/bootstrap/FileListContext.php @@ -25,7 +25,15 @@ use Behat\Behat\Context\Context; class FileListContext implements Context, ActorAwareInterface { - use ActorAware; + /** + * @var Actor + */ + private $actor; + + /** + * @var array + */ + private $fileListAncestorsByActor; /** * @var Locator @@ -35,8 +43,39 @@ class FileListContext implements Context, ActorAwareInterface { /** * @BeforeScenario */ - public function initializeFileListAncestor() { - $this->fileListAncestor = FilesAppContext::currentSectionMainView(); + public function initializeFileListAncestors() { + $this->fileListAncestorsByActor = array(); + $this->fileListAncestor = null; + } + + /** + * @param Actor $actor + */ + public function setCurrentActor(Actor $actor) { + $this->actor = $actor; + + if (array_key_exists($actor->getName(), $this->fileListAncestorsByActor)) { + $this->fileListAncestor = $this->fileListAncestorsByActor[$actor->getName()]; + } else { + $this->fileListAncestor = null; + } + } + + /** + * Sets the file list ancestor to be used in the steps performed by the + * given actor from that point on (until changed again). + * + * This is meant to be called from other contexts, for example, when the + * Files app or the public page for a shared folder are opened. + * + * The FileListAncestorSetter trait can be used to reduce the boilerplate + * needed to set the file list ancestor from other contexts. + * + * @param null|Locator $fileListAncestor the file list ancestor + * @param Actor $actor the actor + */ + public function setFileListAncestorForActor($fileListAncestor, Actor $actor) { + $this->fileListAncestorsByActor[$actor->getName()] = $fileListAncestor; } /** diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 81ba7b69844..a9bb8a619e2 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -26,6 +26,7 @@ use Behat\Behat\Context\Context; class FilesAppContext implements Context, ActorAwareInterface { use ActorAware; + use FileListAncestorSetter; /** * @return array @@ -321,6 +322,8 @@ class FilesAppContext implements Context, ActorAwareInterface { PHPUnit_Framework_Assert::assertStringStartsWith( $this->actor->locatePath("/apps/files/"), $this->actor->getSession()->getCurrentUrl()); + + $this->setFileListAncestorForActor(self::currentSectionMainView(), $this->actor); } /** diff --git a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php index 4b7dd08c83e..7609b799143 100644 --- a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php @@ -26,6 +26,7 @@ use Behat\Behat\Context\Context; class FilesSharingAppContext implements Context, ActorAwareInterface { use ActorAware; + use FileListAncestorSetter; /** * @return Locator @@ -156,6 +157,8 @@ class FilesSharingAppContext implements Context, ActorAwareInterface { PHPUnit_Framework_Assert::assertEquals( $this->actor->getSharedNotebook()["shared link"], $this->actor->getSession()->getCurrentUrl()); + + $this->setFileListAncestorForActor(null, $this->actor); } /** From 1a2d9a2fdda708fafe08b9d2a19f9e2fdabbe821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 19 Feb 2018 17:38:19 +0100 Subject: [PATCH 5/6] Extract common "wait for" functions to a helper class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .../features/bootstrap/FilesAppContext.php | 36 ++------- .../bootstrap/FilesSharingAppContext.php | 18 +---- .../acceptance/features/bootstrap/WaitFor.php | 78 +++++++++++++++++++ 3 files changed, 86 insertions(+), 46 deletions(-) create mode 100644 tests/acceptance/features/bootstrap/WaitFor.php diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index a9bb8a619e2..42ca284881c 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -279,7 +279,8 @@ class FilesAppContext implements Context, ActorAwareInterface { // The shared link field always exists in the DOM (once the "Sharing" // tab is loaded), but its value is the actual shared link only when it // is visible. - if (!$this->waitForElementToBeEventuallyShown( + if (!WaitFor::elementToBeEventuallyShown( + $this->actor, self::shareLinkField(), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { PHPUnit_Framework_Assert::fail("The shared link was not shown yet after $timeout seconds"); @@ -419,7 +420,8 @@ class FilesAppContext implements Context, ActorAwareInterface { * @When I see that the :tabName tab in the details view is eventually loaded */ public function iSeeThatTheTabInTheDetailsViewIsEventuallyLoaded($tabName) { - if (!$this->waitForElementToBeEventuallyNotShown( + if (!WaitFor::elementToBeEventuallyNotShown( + $this->actor, self::loadingIconForTabInCurrentSectionDetailsViewNamed($tabName), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { PHPUnit_Framework_Assert::fail("The $tabName tab in the details view has not been loaded after $timeout seconds"); @@ -437,7 +439,8 @@ class FilesAppContext implements Context, ActorAwareInterface { * @Then I see that the working icon for password protect is eventually not shown */ public function iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown() { - if (!$this->waitForElementToBeEventuallyNotShown( + if (!WaitFor::elementToBeEventuallyNotShown( + $this->actor, self::passwordProtectWorkingIcon(), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { PHPUnit_Framework_Assert::fail("The working icon for password protect is still shown after $timeout seconds"); @@ -454,31 +457,4 @@ class FilesAppContext implements Context, ActorAwareInterface { $this->iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown(); } - private function waitForElementToBeEventuallyShown($elementLocator, $timeout = 10, $timeoutStep = 1) { - $actor = $this->actor; - - $elementShownCallback = function() use ($actor, $elementLocator) { - try { - return $actor->find($elementLocator)->isVisible(); - } catch (NoSuchElementException $exception) { - return false; - } - }; - - return Utils::waitFor($elementShownCallback, $timeout, $timeoutStep); - } - - private function waitForElementToBeEventuallyNotShown($elementLocator, $timeout = 10, $timeoutStep = 1) { - $actor = $this->actor; - - $elementNotShownCallback = function() use ($actor, $elementLocator) { - try { - return !$actor->find($elementLocator)->isVisible(); - } catch (NoSuchElementException $exception) { - return true; - } - }; - - return Utils::waitFor($elementNotShownCallback, $timeout, $timeoutStep); - } } diff --git a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php index 7609b799143..4f9dabc60e6 100644 --- a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php @@ -185,8 +185,8 @@ class FilesSharingAppContext implements Context, ActorAwareInterface { // Unlike other menus, the Share menu is always present in the DOM, so // the element could be found when it was no made visible yet due to the // command not having been processed by the browser. - if (!$this->waitForElementToBeEventuallyShown( - self::shareMenu(), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { + if (!WaitFor::elementToBeEventuallyShown( + $this->actor, self::shareMenu(), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { PHPUnit_Framework_Assert::fail("The Share menu is not visible yet after $timeout seconds"); } @@ -205,18 +205,4 @@ class FilesSharingAppContext implements Context, ActorAwareInterface { PHPUnit_Framework_Assert::assertContains($text, $this->actor->find(self::textPreview(), 10)->getText()); } - private function waitForElementToBeEventuallyShown($elementLocator, $timeout = 10, $timeoutStep = 1) { - $actor = $this->actor; - - $elementShownCallback = function() use ($actor, $elementLocator) { - try { - return $actor->find($elementLocator)->isVisible(); - } catch (NoSuchElementException $exception) { - return false; - } - }; - - return Utils::waitFor($elementShownCallback, $timeout, $timeoutStep); - } - } diff --git a/tests/acceptance/features/bootstrap/WaitFor.php b/tests/acceptance/features/bootstrap/WaitFor.php new file mode 100644 index 00000000000..038de3e42c2 --- /dev/null +++ b/tests/acceptance/features/bootstrap/WaitFor.php @@ -0,0 +1,78 @@ +. + * + */ + +/** + * Helper class with common "wait for" functions. + */ +class WaitFor { + + /** + * Waits for the element to be visible. + * + * @param Actor $actor the Actor used to find the element. + * @param Locator $elementLocator the locator for the element. + * @param float $timeout the number of seconds (decimals allowed) to wait at + * most for the element to be visible. + * @param float $timeoutStep the number of seconds (decimals allowed) to + * wait before checking the visibility again. + * @return boolean true if the element is visible before (or exactly when) + * the timeout expires, false otherwise. + */ + public static function elementToBeEventuallyShown(Actor $actor, Locator $elementLocator, $timeout = 10, $timeoutStep = 1) { + $elementShownCallback = function() use ($actor, $elementLocator) { + try { + return $actor->find($elementLocator)->isVisible(); + } catch (NoSuchElementException $exception) { + return false; + } + }; + + return Utils::waitFor($elementShownCallback, $timeout, $timeoutStep); + } + + /** + * Waits for the element to be hidden (either not visible or not found in + * the DOM). + * + * @param Actor $actor the Actor used to find the element. + * @param Locator $elementLocator the locator for the element. + * @param float $timeout the number of seconds (decimals allowed) to wait at + * most for the element to be hidden. + * @param float $timeoutStep the number of seconds (decimals allowed) to + * wait before checking the visibility again. + * @return boolean true if the element is hidden before (or exactly when) + * the timeout expires, false otherwise. + */ + public static function elementToBeEventuallyNotShown(Actor $actor, Locator $elementLocator, $timeout = 10, $timeoutStep = 1) { + $elementNotShownCallback = function() use ($actor, $elementLocator) { + try { + return !$actor->find($elementLocator)->isVisible(); + } catch (NoSuchElementException $exception) { + return true; + } + }; + + return Utils::waitFor($elementNotShownCallback, $timeout, $timeoutStep); + } + +} From 15c12eb0270a35fa875e519b04d8378afc6e795d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 19 Feb 2018 18:46:49 +0100 Subject: [PATCH 6/6] Add acceptance tests for creation of subfolders in public shared folders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- tests/acceptance/features/app-files.feature | 65 +++++++++++++++++++ .../features/bootstrap/FileListContext.php | 37 +++++++++++ .../features/bootstrap/FilesAppContext.php | 19 ++++++ 3 files changed, 121 insertions(+) diff --git a/tests/acceptance/features/app-files.feature b/tests/acceptance/features/app-files.feature index dd5340d6374..97f17344187 100644 --- a/tests/acceptance/features/app-files.feature +++ b/tests/acceptance/features/app-files.feature @@ -41,6 +41,71 @@ Feature: app-files And I open the Share menu Then I see that the Share menu is shown + Scenario: creation is not possible by default in a public shared folder + Given I act as John + And I am logged in + And I create a new folder named "Shared folder" + # To share the link the "Share" inline action has to be clicked but, as the + # details view is opened automatically when the folder is created, clicking + # on the inline action could fail if it is covered by the details view due + # to its opening animation. Instead of ensuring that the animations of the + # contents and the details view have both finished it is easier to close the + # details view and wait until it is closed before continuing. + And I close the details view + And I see that the details view is closed + And I share the link for "Shared folder" + And I write down the shared link + When I act as Jane + And I visit the shared link I wrote down + And I see that the current page is the shared link I wrote down + And I see that the file list is eventually loaded + Then I see that it is not possible to create new files + + Scenario: create folder in a public editable shared folder + Given I act as John + And I am logged in + And I create a new folder named "Editable shared folder" + # To share the link the "Share" inline action has to be clicked but, as the + # details view is opened automatically when the folder is created, clicking + # on the inline action could fail if it is covered by the details view due + # to its opening animation. Instead of ensuring that the animations of the + # contents and the details view have both finished it is easier to close the + # details view and wait until it is closed before continuing. + And I close the details view + And I see that the details view is closed + And I share the link for "Editable shared folder" + And I set the shared link as editable + And I write down the shared link + When I act as Jane + And I visit the shared link I wrote down + And I see that the current page is the shared link I wrote down + And I create a new folder named "Subfolder" + Then I see that the file list contains a file named "Subfolder" + + Scenario: owner sees folder created in the public page of an editable shared folder + Given I act as John + And I am logged in + And I create a new folder named "Editable shared folder" + # To share the link the "Share" inline action has to be clicked but, as the + # details view is opened automatically when the folder is created, clicking + # on the inline action could fail if it is covered by the details view due + # to its opening animation. Instead of ensuring that the animations of the + # contents and the details view have both finished it is easier to close the + # details view and wait until it is closed before continuing. + And I close the details view + And I see that the details view is closed + And I share the link for "Editable shared folder" + And I set the shared link as editable + And I write down the shared link + And I act as Jane + And I visit the shared link I wrote down + And I see that the current page is the shared link I wrote down + And I create a new folder named "Subfolder" + And I see that the file list contains a file named "Subfolder" + When I act as John + And I enter in the folder named "Editable shared folder" + Then I see that the file list contains a file named "Subfolder" + Scenario: set a password to a shared link Given I am logged in And I share the link for "welcome.txt" diff --git a/tests/acceptance/features/bootstrap/FileListContext.php b/tests/acceptance/features/bootstrap/FileListContext.php index 236adafcbf8..bc225e3f9b1 100644 --- a/tests/acceptance/features/bootstrap/FileListContext.php +++ b/tests/acceptance/features/bootstrap/FileListContext.php @@ -78,6 +78,15 @@ class FileListContext implements Context, ActorAwareInterface { $this->fileListAncestorsByActor[$actor->getName()] = $fileListAncestor; } + /** + * @return Locator + */ + public static function mainWorkingIcon($fileListAncestor) { + return Locator::forThe()->css(".mask.icon-loading")-> + descendantOf($fileListAncestor)-> + describedAs("Main working icon in file list"); + } + /** * @return Locator */ @@ -255,6 +264,13 @@ class FileListContext implements Context, ActorAwareInterface { $this->actor->find(self::createNewFolderMenuItemNameInput($this->fileListAncestor), 2)->setValue($folderName . "\r"); } + /** + * @Given I enter in the folder named :folderName + */ + public function iEnterInTheFolderNamed($folderName) { + $this->actor->find(self::mainLinkForFile($this->fileListAncestor, $folderName), 10)->click(); + } + /** * @Given I open the details view for :fileName */ @@ -306,6 +322,27 @@ class FileListContext implements Context, ActorAwareInterface { $this->actor->find(self::viewFileInFolderMenuItem(), 2)->click(); } + /** + * @Then I see that the file list is eventually loaded + */ + public function iSeeThatTheFileListIsEventuallyLoaded() { + if (!WaitFor::elementToBeEventuallyNotShown( + $this->actor, + self::mainWorkingIcon($this->fileListAncestor), + $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { + PHPUnit_Framework_Assert::fail("The main working icon for the file list is still shown after $timeout seconds"); + } + } + + /** + * @Then I see that it is not possible to create new files + */ + public function iSeeThatItIsNotPossibleToCreateNewFiles() { + // Once a file list is loaded the "Create" menu button is always in the + // DOM, so it is checked if it is visible or not. + PHPUnit_Framework_Assert::assertFalse($this->actor->find(self::createMenuButton($this->fileListAncestor))->isVisible()); + } + /** * @Then I see that the file list contains a file named :fileName */ diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 42ca284881c..50997d98b0f 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -214,6 +214,18 @@ class FilesAppContext implements Context, ActorAwareInterface { describedAs("Share link field in the details view in Files app"); } + /** + * @return Locator + */ + public static function allowUploadAndEditingRadioButton() { + // forThe()->radio("Allow upload and editing") can not be used here; + // that would return the radio button itself, but the element that the + // user interacts with is the label. + return Locator::forThe()->xpath("//label[normalize-space() = 'Allow upload and editing']")-> + descendantOf(self::currentSectionDetailsView())-> + describedAs("Allow upload and editing radio button in the details view in Files app"); + } + /** * @return Locator */ @@ -307,6 +319,13 @@ class FilesAppContext implements Context, ActorAwareInterface { $this->actor->find(self::itemInDropdownForTag($tag), 10)->click(); } + /** + * @When I set the shared link as editable + */ + public function iSetTheSharedLinkAsEditable() { + $this->actor->find(self::allowUploadAndEditingRadioButton(), 10)->click(); + } + /** * @When I protect the shared link with the password :password */