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/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/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 new file mode 100644 index 00000000000..bc225e3f9b1 --- /dev/null +++ b/tests/acceptance/features/bootstrap/FileListContext.php @@ -0,0 +1,374 @@ +. + * + */ + +use Behat\Behat\Context\Context; + +class FileListContext implements Context, ActorAwareInterface { + + /** + * @var Actor + */ + private $actor; + + /** + * @var array + */ + private $fileListAncestorsByActor; + + /** + * @var Locator + */ + private $fileListAncestor; + + /** + * @BeforeScenario + */ + 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; + } + + /** + * @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 + */ + 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($fileListAncestor)-> + describedAs("Row for file $fileName in file list"); + } + + /** + * @return Locator + */ + 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($fileListAncestor, $fileName2))-> + describedAs("Row for file $fileName1 preceding $fileName2 in file list"); + } + + /** + * @return Locator + */ + public static function favoriteMarkForFile($fileListAncestor, $fileName) { + return Locator::forThe()->css(".favorite-mark")-> + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Favorite mark for file $fileName in file list"); + } + + /** + * @return Locator + */ + public static function notFavoritedStateIconForFile($fileListAncestor, $fileName) { + return Locator::forThe()->css(".icon-star")-> + descendantOf(self::favoriteMarkForFile($fileListAncestor, $fileName))-> + describedAs("Not favorited state icon for file $fileName in file list"); + } + + /** + * @return Locator + */ + public static function favoritedStateIconForFile($fileListAncestor, $fileName) { + return Locator::forThe()->css(".icon-starred")-> + descendantOf(self::favoriteMarkForFile($fileListAncestor, $fileName))-> + describedAs("Favorited state icon for file $fileName in file list"); + } + + /** + * @return Locator + */ + public static function mainLinkForFile($fileListAncestor, $fileName) { + return Locator::forThe()->css(".name")-> + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Main link for file $fileName in file list"); + } + + /** + * @return Locator + */ + public static function renameInputForFile($fileListAncestor, $fileName) { + return Locator::forThe()->css("input.filename")-> + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Rename input for file $fileName in file list"); + } + + /** + * @return Locator + */ + public static function shareActionForFile($fileListAncestor, $fileName) { + return Locator::forThe()->css(".action-share")-> + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("Share action for file $fileName in file list"); + } + + /** + * @return Locator + */ + public static function fileActionsMenuButtonForFile($fileListAncestor, $fileName) { + return Locator::forThe()->css(".action-menu")-> + descendantOf(self::rowForFile($fileListAncestor, $fileName))-> + describedAs("File actions menu button for file $fileName in file list"); + } + + /** + * @return Locator + */ + public static function fileActionsMenu() { + return Locator::forThe()->css(".fileActionsMenu")-> + describedAs("File actions menu in file list"); + } + + /** + * @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 file list"); + } + + /** + * @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($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 + */ + public function iOpenTheDetailsViewFor($fileName) { + $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $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($this->fileListAncestor, $fileName1), 10)->click(); + + $this->actor->find(self::renameMenuItem(), 2)->click(); + + $this->actor->find(self::renameInputForFile($this->fileListAncestor, $fileName1), 10)->setValue($fileName2 . "\r"); + } + + /** + * @Given I mark :fileName as favorite + */ + public function iMarkAsFavorite($fileName) { + $this->iSeeThatIsNotMarkedAsFavorite($fileName); + + $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $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($this->fileListAncestor, $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($this->fileListAncestor, $fileName), 10)->click(); + + $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 + */ + public function iSeeThatTheFileListContainsAFileNamed($fileName) { + 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($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($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($this->fileListAncestor, $fileName), 10)); + } + +} diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 117f3b54fb8..50997d98b0f 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 @@ -213,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 */ @@ -241,185 +254,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 +275,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(self::currentSectionMainView(), $fileName), 10)->click(); $this->actor->find(self::shareLinkCheckbox(), 5)->click(); } @@ -490,7 +291,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"); @@ -499,15 +301,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 */ @@ -526,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 */ @@ -542,6 +342,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); } /** @@ -577,34 +379,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 */ @@ -665,7 +439,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"); @@ -683,7 +458,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"); @@ -700,31 +476,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 4b7dd08c83e..4f9dabc60e6 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); } /** @@ -182,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"); } @@ -202,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); + } + +} 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); }