Merge pull request #8758 from nextcloud/stable13-8594-add-acceptance-tests-for-permissions-on-public-shared-folders

[stable13] Add acceptance tests for permissions on public shared folders
This commit is contained in:
Morris Jobke 2018-03-09 15:23:09 +01:00 committed by GitHub
commit f17cabd63e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 638 additions and 299 deletions

View File

@ -12,6 +12,7 @@ default:
- AppNavigationContext - AppNavigationContext
- CommentsAppContext - CommentsAppContext
- FeatureContext - FeatureContext
- FileListContext
- FilesAppContext - FilesAppContext
- FilesSharingAppContext - FilesSharingAppContext
- LoginPageContext - LoginPageContext

View File

@ -41,6 +41,71 @@ Feature: app-files
And I open the Share menu And I open the Share menu
Then I see that the Share menu is shown 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 Scenario: set a password to a shared link
Given I am logged in Given I am logged in
And I share the link for "welcome.txt" And I share the link for "welcome.txt"

View File

@ -0,0 +1,67 @@
<?php
/**
*
* @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
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);
}
}

View File

@ -0,0 +1,374 @@
<?php
/**
*
* @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
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));
}
}

View File

@ -26,6 +26,7 @@ use Behat\Behat\Context\Context;
class FilesAppContext implements Context, ActorAwareInterface { class FilesAppContext implements Context, ActorAwareInterface {
use ActorAware; use ActorAware;
use FileListAncestorSetter;
/** /**
* @return array * @return array
@ -213,6 +214,18 @@ class FilesAppContext implements Context, ActorAwareInterface {
describedAs("Share link field in the details view in Files app"); 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 * @return Locator
*/ */
@ -241,185 +254,6 @@ class FilesAppContext implements Context, ActorAwareInterface {
describedAs("Password protect working icon in the details view in Files app"); 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 * @Given I close the details view
*/ */
@ -441,44 +275,11 @@ class FilesAppContext implements Context, ActorAwareInterface {
$this->actor->find(self::tabHeaderInCurrentSectionDetailsViewNamed($tabName), 10)->click(); $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 * @Given I share the link for :fileName
*/ */
public function iShareTheLinkFor($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(); $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" // 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 // tab is loaded), but its value is the actual shared link only when it
// is visible. // is visible.
if (!$this->waitForElementToBeEventuallyShown( if (!WaitFor::elementToBeEventuallyShown(
$this->actor,
self::shareLinkField(), self::shareLinkField(),
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The shared link was not shown yet after $timeout seconds"); 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(); $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 * @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(); $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 * @When I protect the shared link with the password :password
*/ */
@ -542,6 +342,8 @@ class FilesAppContext implements Context, ActorAwareInterface {
PHPUnit_Framework_Assert::assertStringStartsWith( PHPUnit_Framework_Assert::assertStringStartsWith(
$this->actor->locatePath("/apps/files/"), $this->actor->locatePath("/apps/files/"),
$this->actor->getSession()->getCurrentUrl()); $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 * @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 * @When I see that the :tabName tab in the details view is eventually loaded
*/ */
public function iSeeThatTheTabInTheDetailsViewIsEventuallyLoaded($tabName) { public function iSeeThatTheTabInTheDetailsViewIsEventuallyLoaded($tabName) {
if (!$this->waitForElementToBeEventuallyNotShown( if (!WaitFor::elementToBeEventuallyNotShown(
$this->actor,
self::loadingIconForTabInCurrentSectionDetailsViewNamed($tabName), self::loadingIconForTabInCurrentSectionDetailsViewNamed($tabName),
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The $tabName tab in the details view has not been loaded after $timeout seconds"); 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 * @Then I see that the working icon for password protect is eventually not shown
*/ */
public function iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown() { public function iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown() {
if (!$this->waitForElementToBeEventuallyNotShown( if (!WaitFor::elementToBeEventuallyNotShown(
$this->actor,
self::passwordProtectWorkingIcon(), self::passwordProtectWorkingIcon(),
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The working icon for password protect is still shown after $timeout seconds"); 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(); $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);
}
} }

View File

@ -26,6 +26,7 @@ use Behat\Behat\Context\Context;
class FilesSharingAppContext implements Context, ActorAwareInterface { class FilesSharingAppContext implements Context, ActorAwareInterface {
use ActorAware; use ActorAware;
use FileListAncestorSetter;
/** /**
* @return Locator * @return Locator
@ -140,6 +141,8 @@ class FilesSharingAppContext implements Context, ActorAwareInterface {
PHPUnit_Framework_Assert::assertEquals( PHPUnit_Framework_Assert::assertEquals(
$this->actor->getSharedNotebook()["shared link"], $this->actor->getSharedNotebook()["shared link"],
$this->actor->getSession()->getCurrentUrl()); $this->actor->getSession()->getCurrentUrl());
$this->setFileListAncestorForActor(null, $this->actor);
} }
/** /**
@ -157,8 +160,8 @@ class FilesSharingAppContext implements Context, ActorAwareInterface {
// Unlike other menus, the Share menu is always present in the DOM, so // 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 // the element could be found when it was no made visible yet due to the
// command not having been processed by the browser. // command not having been processed by the browser.
if (!$this->waitForElementToBeEventuallyShown( if (!WaitFor::elementToBeEventuallyShown(
self::shareMenu(), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { $this->actor, self::shareMenu(), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The Share menu is not visible yet after $timeout seconds"); PHPUnit_Framework_Assert::fail("The Share menu is not visible yet after $timeout seconds");
} }
@ -177,18 +180,4 @@ class FilesSharingAppContext implements Context, ActorAwareInterface {
PHPUnit_Framework_Assert::assertContains($text, $this->actor->find(self::textPreview(), 10)->getText()); 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);
}
} }

View File

@ -0,0 +1,78 @@
<?php
/**
*
* @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* 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);
}
}

View File

@ -60,6 +60,11 @@
*/ */
class Actor { class Actor {
/**
* @var string
*/
private $name;
/** /**
* @var \Behat\Mink\Session * @var \Behat\Mink\Session
*/ */
@ -83,18 +88,29 @@ class Actor {
/** /**
* Creates a new 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 * @param \Behat\Mink\Session $session the Mink Session used to control its
* web browser. * web browser.
* @param string $baseUrl the base URL used when solving relative URLs. * @param string $baseUrl the base URL used when solving relative URLs.
* @param array $sharedNotebook the notebook shared between all actors. * @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->session = $session;
$this->baseUrl = $baseUrl; $this->baseUrl = $baseUrl;
$this->sharedNotebook = &$sharedNotebook; $this->sharedNotebook = &$sharedNotebook;
$this->findTimeoutMultiplier = 1; $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. * Sets the base URL.
* *

View File

@ -135,7 +135,7 @@ class ActorContext extends RawMinkContext {
$this->actors = array(); $this->actors = array();
$this->sharedNotebook = 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->actors["default"]->setFindTimeoutMultiplier($this->actorTimeoutMultiplier);
$this->currentActor = $this->actors["default"]; $this->currentActor = $this->actors["default"];
@ -159,7 +159,7 @@ class ActorContext extends RawMinkContext {
*/ */
public function iActAs($actorName) { public function iActAs($actorName) {
if (!array_key_exists($actorName, $this->actors)) { 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); $this->actors[$actorName]->setFindTimeoutMultiplier($this->actorTimeoutMultiplier);
} }