From c62c7dda825893212c7fb64a8096a00c70739480 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 (moving parametrized locators like "fileActionsMenuItemFor" 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 | 181 ++++++++++++++++++ .../features/bootstrap/FilesAppContext.php | 147 +------------- 3 files changed, 183 insertions(+), 146 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 10e1d42502..9af01efcf0 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -11,6 +11,7 @@ default: - AppNavigationContext - 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 0000000000..227cdd2614 --- /dev/null +++ b/tests/acceptance/features/bootstrap/FileListContext.php @@ -0,0 +1,181 @@ +. + * + */ + +use Behat\Behat\Context\Context; + +class FileListContext implements Context, ActorAwareInterface { + + use ActorAware; + + /** + * @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 favoriteActionForFile($fileName) { + return Locator::forThe()->css(".action-favorite")-> + descendantOf(self::rowForFile($fileName))-> + describedAs("Favorite action for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function favoritedStateIconForFile($fileName) { + return Locator::forThe()->css(".icon-starred")-> + descendantOf(self::favoriteActionForFile($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 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 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->actor->find(self::favoriteActionForFile($fileName), 10)->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 :fileName is marked as favorite + */ + public function iSeeThatIsMarkedAsFavorite($fileName) { + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::favoritedStateIconForFile($fileName), 10)); + } + +} diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 76921ba738..dfa8f9114d 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -232,110 +232,6 @@ class FilesAppContext implements Context, ActorAwareInterface { describedAs("Password protect working icon in the details view 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 favoriteActionForFile($fileName) { - return Locator::forThe()->css(".action-favorite")->descendantOf(self::rowForFile($fileName))-> - describedAs("Favorite action for file $fileName in Files app"); - } - - /** - * @return Locator - */ - public static function favoritedStateIconForFile($fileName) { - return Locator::forThe()->css(".icon-starred")->descendantOf(self::favoriteActionForFile($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 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 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 open the input field for tags in the details view */ @@ -350,29 +246,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->actor->find(self::favoriteActionForFile($fileName), 10)->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(); } @@ -393,15 +271,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 */ @@ -471,20 +340,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 :fileName is marked as favorite - */ - public function iSeeThatIsMarkedAsFavorite($fileName) { - PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::favoritedStateIconForFile($fileName), 10)); - } - /** * @Then I see that the file name shown in the details view is :fileName */ From 292e95566eecd4e0d201e9de907511ab4cf5691f 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 bf2f5a7367..f47373593e 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 d6fb63694e..2cdc4b01ff 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 3c6a26913892c1e62aed15392237910c8bc1b933 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 | 72 +++++++++++-------- .../features/bootstrap/FilesAppContext.php | 2 +- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/tests/acceptance/features/bootstrap/FileListContext.php b/tests/acceptance/features/bootstrap/FileListContext.php index 227cdd2614..ec80dc6d74 100644 --- a/tests/acceptance/features/bootstrap/FileListContext.php +++ b/tests/acceptance/features/bootstrap/FileListContext.php @@ -27,67 +27,79 @@ class FileListContext implements Context, ActorAwareInterface { use ActorAware; + /** + * @var Locator + */ + private $fileListAncestor; + + /** + * @BeforeScenario + */ + public function initializeFileListAncestor() { + $this->fileListAncestor = FilesAppContext::currentSectionMainView(); + } + /** * @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 favoriteActionForFile($fileName) { + public static function favoriteActionForFile($fileListAncestor, $fileName) { return Locator::forThe()->css(".action-favorite")-> - descendantOf(self::rowForFile($fileName))-> - describedAs("Favorite action for file $fileName in Files app"); + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Favorite action 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::favoriteActionForFile($fileName))-> - describedAs("Favorited state icon for file $fileName in Files app"); + descendantOf(self::favoriteActionForFile($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"); } /** @@ -95,7 +107,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"); } /** @@ -104,7 +116,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"); } /** @@ -132,7 +144,7 @@ class FileListContext implements Context, ActorAwareInterface { * @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(); } @@ -141,25 +153,25 @@ 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"); } /** * @Given I mark :fileName as favorite */ public function iMarkAsFavorite($fileName) { - $this->actor->find(self::favoriteActionForFile($fileName), 10)->click(); + $this->actor->find(self::favoriteActionForFile($this->fileListAncestor, $fileName), 10)->click(); } /** * @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(); } @@ -168,14 +180,14 @@ 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 :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)); } } diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index dfa8f9114d..ca4ee057e1 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -250,7 +250,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 d757a19b9c4d6197248d92d93e83bf4d144c096f 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 rename :fileName1 to :fileName2 in the public shared folder" instead of "I rename :fileName1 to :fileName2") 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 0000000000..2f8d3ad00e --- /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 ec80dc6d74..5c832f08af 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 ca4ee057e1..39764c46dc 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 @@ -305,6 +306,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 88c1180c75..12caed3b45 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 @@ -90,6 +91,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 6e41e0bda5f39063b566f0598f14bcf650d9d3e0 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 ++------- .../acceptance/features/bootstrap/WaitFor.php | 78 +++++++++++++++++++ 2 files changed, 84 insertions(+), 30 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 39764c46dc..7bdcc951c1 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -263,7 +263,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"); @@ -403,7 +404,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"); @@ -421,7 +423,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"); @@ -438,31 +441,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/WaitFor.php b/tests/acceptance/features/bootstrap/WaitFor.php new file mode 100644 index 0000000000..038de3e42c --- /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 1218cee069bb1e2f083eda56be41364fe1ee766a 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 | 81 +++++++++++++++++++ .../features/bootstrap/FilesAppContext.php | 35 ++++++++ 3 files changed, 181 insertions(+) diff --git a/tests/acceptance/features/app-files.feature b/tests/acceptance/features/app-files.feature index 0f7c0c532e..648d928684 100644 --- a/tests/acceptance/features/app-files.feature +++ b/tests/acceptance/features/app-files.feature @@ -30,6 +30,71 @@ Feature: app-files Then I see that the file list contains a file named "farewell.txt" And I see that the file name shown in the details view is "farewell.txt" + 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 5c832f08af..3410198a50 100644 --- a/tests/acceptance/features/bootstrap/FileListContext.php +++ b/tests/acceptance/features/bootstrap/FileListContext.php @@ -78,6 +78,49 @@ 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 + */ + public static function createMenuButton($fileListAncestor) { + return Locator::forThe()->css("#controls .button.new")-> + descendantOf($fileListAncestor)-> + describedAs("Create menu button in file list"); + } + + /** + * @return Locator + */ + private static function createMenuItemFor($fileListAncestor, $newType) { + return Locator::forThe()->xpath("//div[contains(concat(' ', normalize-space(@class), ' '), ' newFileMenu ')]//span[normalize-space() = '$newType']/ancestor::li")-> + descendantOf($fileListAncestor)-> + describedAs("Create $newType menu item in file list"); + } + + /** + * @return Locator + */ + public static function createNewFolderMenuItem($fileListAncestor) { + return self::createMenuItemFor($fileListAncestor, "New folder"); + } + + /** + * @return Locator + */ + public static function createNewFolderMenuItemNameInput($fileListAncestor) { + return Locator::forThe()->css(".filenameform input")-> + descendantOf(self::createNewFolderMenuItem($fileListAncestor))-> + describedAs("Name input in create new folder menu item in file list"); + } + /** * @return Locator */ @@ -179,6 +222,23 @@ class FileListContext implements Context, ActorAwareInterface { return self::fileActionsMenuItemFor("View in folder"); } + /** + * @Given I create a new folder named :folderName + */ + public function iCreateANewFolderNamed($folderName) { + $this->actor->find(self::createMenuButton($this->fileListAncestor), 10)->click(); + + $this->actor->find(self::createNewFolderMenuItem($this->fileListAncestor), 2)->click(); + $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 */ @@ -215,6 +275,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 7bdcc951c1..50997d98b0 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -78,6 +78,15 @@ class FilesAppContext implements Context, ActorAwareInterface { describedAs("Current section details view in Files app"); } + /** + * @return Locator + */ + public static function closeDetailsViewButton() { + return Locator::forThe()->css(".icon-close")-> + descendantOf(self::currentSectionDetailsView())-> + describedAs("Close current section details view in Files app"); + } + /** * @return Locator */ @@ -205,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 */ @@ -233,6 +254,13 @@ class FilesAppContext implements Context, ActorAwareInterface { describedAs("Password protect working icon in the details view in Files app"); } + /** + * @Given I close the details view + */ + public function iCloseTheDetailsView() { + $this->actor->find(self::closeDetailsViewButton(), 10)->click(); + } + /** * @Given I open the input field for tags in the details view */ @@ -291,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 */