diff --git a/Features/Context/PlatformUI.php b/Features/Context/PlatformUI.php new file mode 100644 index 000000000..0c53dab5a --- /dev/null +++ b/Features/Context/PlatformUI.php @@ -0,0 +1,201 @@ +getSession()->getPage(); + $loadingClasses = array( + '.yui3-app-transitioning', + '.is-app-loading', + '.is-app-transitioning', + // content tree + '.ez-view-treeactionview.is-expanded .ez-view-treeview:not(.is-tree-loaded)', + '.is-tree-node-loading', + // contenttype menu + '.ez-view-createcontentactionview.is-expanded:not(.is-contenttypeselector-loaded)' + ); + $loadingSelector = implode(',', $loadingClasses); + while ($page->find('css', $loadingSelector) != null) { + usleep(100 * 1000); // 100ms + } + } + + /** + * @Given I create a content of content type :type with: + */ + public function iCreateContentType($type, TableNode $fields) + { + $this->clickNavigationZone("Platform"); + $this->waitForLoadings(); + $this->iClickAtLink("Content structure"); + $this->waitForLoadings(); + $this->clickActionBar("Create a content"); + $this->waitForLoadings(); + $this->clickContentType($type); + $this->waitForLoadings(); + foreach ($fields as $fieldArray) { + $keys = array_keys($fieldArray); + for ($i = 0; $i < count($keys); $i++) { + $this->fillFieldWithValue($keys[$i], $fieldArray[$keys[$i]]); + } + } + } + + /** + * @Then I see Content :contentName of type :contentType + */ + public function contentExists($contentName, $contentType) + { + $contentId = $this->getLocationId(); + $content = $this->getContentManager()->loadContentWithLocationId($contentId); + $contentInfo = $content->contentInfo; + $contentTypeName = $this->getContentManager()->getContentType($content); + Assertion::assertEquals($contentName, $contentInfo->name, "Content has wrong name"); + Assertion::assertEquals($contentType, $contentTypeName, "Content has wrong type"); + } + + /** + * @Then I should see (an) element :element with (an) file :file + */ + public function iSeeElementFile($element, $file) + { + $url = $this->getFileUrl($element, '.ez-fieldview-label'); + $fileContentActual = file_get_contents($url); + $file = rtrim(realpath($this->getMinkParameter('files_path')), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$file; + $fileContentExpected = file_get_contents($file); + Assertion::assertEquals($fileContentActual, $fileContentExpected); + } + + /** + * @Then I should see elements with the following names: + */ + public function iSeeElements(TableNode $elements) + { + foreach ($elements as $element) { + $found = false; + $name = array_values($element)[0]; + $found = $this->getElementByText($name, '.ez-selection-filter-item'); + Assertion::assertNotNull($found, "Element: $name not found"); + } + } + + /** + * Runs a empty Javascript between step so that the next step is only executed when the previous Javascript finished + * + * @AfterStep + */ + public function waitForJs() + { + $this->waitForLoadings(); + } + + /** + * Initialize class + * + * @param string $uri + */ + public function __construct($uri, $user = null, $password = null) + { + parent::__construct(); + $this->platformUiUri = $uri; + if ($user != null) { + $this->user = $user; + } + if ($password != null) { + $this->password = $password; + } + } + + /** + * Checks if platform is waiting for publishing a content and if it is publishes it + */ + private function executeDelayedActions() + { + if ($this->platformStatus == self::WAITING_FOR_PUBLISHING) { + $this->clickEditActionBar("Publish"); + } + $this->waitForLoadings(); + } + + /** + * Attaches a file to a input field on the HTML + * + * @param string $file file name relative to mink definitions + * @param string $selector CSS file upload element selector + */ + protected function attachFile($fileName, $selector) + { + if ($this->getMinkParameter('files_path')) { + $fullPath = rtrim( + realpath( + $this->getMinkParameter('files_path') + ), + DIRECTORY_SEPARATOR + ).DIRECTORY_SEPARATOR.$fileName; + + if (is_file($fullPath)) { + $fileInput = 'input[type="file"]' . $selector; + $field = $this->getSession()->getPage()->find('css', $fileInput); + + if (null === $field) { + throw new Exception("File input $selector is not found"); + } + $field->attachFile($fullPath); + } + } else { + throw new Exception("File $fileName is not found at the given location: $fullPath"); + } + } +} diff --git a/Features/Context/SubContext/Authentication.php b/Features/Context/SubContext/Authentication.php new file mode 100644 index 000000000..152776f0e --- /dev/null +++ b/Features/Context/SubContext/Authentication.php @@ -0,0 +1,93 @@ +visit($this->platformUiUri . $url); + } + + /** + * @Given I go to PlatformUI app with username :user and password :password + */ + public function goToPlatformUiAndLogIn($username, $password) + { + // Given I go to PlatformUI app + $this->goToPlatformUi(); + //wait fos JS + $this->waitForJs(); + // And I fill in "Username" with "admin" + $this->fillFieldWithValue('Username', $username); + //And I fill in "Password" with "publish" + $this->fillFieldWithValue('Password', $password); + //And I click on the "Login" button + $this->iClickAtButton('Login'); + //wait fos JS + $this->waitForJs(); + //Then I should be logged in + $this->iShouldBeLoggedIn(); + } + + /** + * @Given I am logged in as admin on PlatformUI + */ + public function loggedAsAdminPlatformUi() + { + $this->goToPlatformUiAndLogIn($this->user, $this->password); + } + + /** + * @Given I logout + */ + public function iLogout() + { + $this->shouldBeLoggedIn = false; + $this->goToPlatformUi('#/dashboard'); + $this->waitForJs(); + $this->iClickAtLink("Logout"); + } + + /** + * @Then I should be logged in + */ + public function iShouldBeLoggedIn() + { + $this->shouldBeLoggedIn = true; + + $verification = new WebAssert($this->getSession()); + $verification->elementNotExists('css', '.ez-loginform'); + $jsCode = "return (document.querySelector('.ez-loginform') === null);"; + } + + /** + * Logs the user out + * + * @AfterScenario + */ + public function loggOutAfterScenario() + { + $this->iLogout(); + } +} diff --git a/Features/Context/SubContext/CommonActions.php b/Features/Context/SubContext/CommonActions.php new file mode 100644 index 000000000..e6f29f5f4 --- /dev/null +++ b/Features/Context/SubContext/CommonActions.php @@ -0,0 +1,209 @@ +getSession()->getPage(); + $selector = '.ez-logo a'; + $page->find('css', $selector)->click(); + } + + /** + * @Given I click (on) the tab :tab + * Clicks on a PlatformUI tab + * + * @param string $tab Text of the element to click + */ + public function clickTab($tab) + { + $this->clickElementByText($tab, ".ez-tabs-label a[href]"); + } + + /** + * @Given I click (on) the navigation zone :zone + * Click on a PlatformUI menu zone + * + * @param string $zone Text of the element to click + */ + public function clickNavigationZone($zone) + { + $this->clickElementByText($zone, ".ez-zone-name"); + } + + /** + * @Given I click on the :button button number :index + * Click on a PlatformUI button + * + * @param string $button Text of the element to click + * @param string $index WHAT IS THIS?! + */ + public function clickButtonWithIndex($button, $index) + { + $this->clickElementByText($button, "button", $index); + } + + /** + * @Given I click (on) the navigation item :item + * Click on a PlatformUI sub-menu option + * + * @param string $item Text of the element to click + */ + public function clickNavigationItem($item) + { + $this->clickElementByText($item, '.ez-navigation-item'); + } + + /** + * @Given I click (on) the discovery bar button :button + * Click on a PlatformUI discovery bar + * + * @param string $button Text of the element to click + */ + public function clickDiscoveryBar($button) + { + $this->clickElementByText($button, '.ez-view-discoverybarview .ez-action', '.action-label'); + } + + /** + * @Given I click (on) the action bar button :button + * Click on a PlatformUI action bar + * + * @param string $button Text of the element to click + */ + public function clickActionBar($button) + { + $this->clickElementByText($button, '.ez-actionbar-container .ez-action', '.action-label'); + } + + /** + * @Given I click (on) the edit action bar button :button + * Click on a PlatformUI edit action bar + * + * @param string $button Text of the element to click + */ + public function clickEditActionBar($button) + { + $this->clickElementByText($button, '.ez-editactionbar-container .ez-action', '.action-label'); + } + + /** + * @Given I click (on) the content type :contentType + * Click on a PlatformUI side menu content type + * + * @param string $contentType Text of the element to click + */ + public function clickContentType($contentType) + { + $this->clickElementByText($contentType, '.ez-contenttypeselector-types .ez-selection-filter-item '); + } + + /** + * @Given I click (on) the content tree with path :path + * Explores the content tree, expanding it and click on the desired element + * + * @param string $path The content tree path such as 'Content1/Content2/ContentIWantToClick' + */ + public function openTreePath($path) + { + $this->clickDiscoveryBar("Content tree"); + $this->waitForLoadings(); + $path = explode("/", $path); + $node = null; + foreach ($path as $pathNode) { + $node = $this->openTreeNode($pathNode, $node); + $this->waitForLoadings(); + } + $node->find('css', '.ez-tree-navigate')->click(); + } + + /** + * Opens a content tree node based on the root of the tree or a given node + * + * @param string $pathNode The text of the node that is going to be opened + * @param NodeElement $node The base node to expand from, if null defaults to the content tree root + * @return NodeElement The node that was opened + */ + private function openTreeNode($pathNode, $node) + { + $page = $this->getSession()->getPage(); + $notFound = true; + if ($node == null) { + $node = $page->find('css', '.ez-platformui-app-body'); + } + $subNodes = $node->findAll('css', '.ez-tree-node'); + foreach ($subNodes as $subNode) { + $leafNode = $subNode->find('css', '.ez-tree-navigate'); + if ($leafNode->getText() == $pathNode) { + $notFound = false; + if ($subNode->hasClass('is-tree-node-close')) { + $toggleNode = $subNode->find('css', '.ez-tree-node-toggle'); + if ($toggleNode->isVisible()) { + $toggleNode->click(); + } + } + return $subNode; + } + } + if ($notFound) { + throw new \Exception("The path node: $pathNode was not found for the given path"); + } + return $node; + } + + /** + * Finds an HTML element by class and the text value and clicks it + * + * @param string $text Text value of the element + * @param string $selector CSS selector of the element + */ + protected function clickElementByText($text, $selector, $textSelector = null, $index = 1) + { + $index + 1; //DO NOT FORGET SOMETHING THAT I DON'T REMENBER + $element = $this->getElementByText($text, $selector, $textSelector); + if ($element) { + $element->click(); + } else { + throw new \Exception("Can't click \" $text \" element: Not Found"); + } + } + + /** + * Finds an HTML element by class and the text value and returns it + * + * @param string $text Text value of the element + * @param string $selector CSS selector of the element + * @param string $textSelector extra CSS selector for text of the element + * @return array + */ + protected function getElementByText($text, $selector, $textSelector = null) + { + $page = $this->getSession()->getPage(); + $elements = $page->findAll('css', $selector); + foreach ($elements as $element) { + if ($textSelector != null) { + $elementText = $element->find('css', $textSelector)->getText(); + } else { + $elementText = $element->getText(); + } + if ($elementText == $text) { + return $element; + } + } + return false; + } +} diff --git a/Features/Context/SubContext/Fields.php b/Features/Context/SubContext/Fields.php new file mode 100644 index 000000000..abef15073 --- /dev/null +++ b/Features/Context/SubContext/Fields.php @@ -0,0 +1,206 @@ +getFieldTypeManager(); + $fieldManager->setFieldContentState(FieldType::CONTENT_TYPE_PUBLISHED); + $type = $fieldManager->getThisContentTypeName('eng-GB'); + + $this->loggedAsAdminPlatformUi(); + $this->clickNavigationZone("Platform"); + $this->waitForLoadings(); + $this->clickNavigationItem("Content structure"); + $this->waitForLoadings(); + $this->clickActionBar("Create a content"); + $this->waitForLoadings(); + $this->clickContentType($type); + $this->waitForLoadings(); + $this->platformStatus = self::WAITING_FOR_PUBLISHING; + } + + /** + * @When I edit this content + */ + public function editThisContent() + { + $fieldManager = $this->getFieldTypeManager(); + $fieldManager->setFieldContentState(FieldType::CONTENT_PUBLISHED); + $name = $fieldManager->getThisContentName('eng-GB'); + + $this->loggedAsAdminPlatformUi(); + $this->clickNavigationZone("Platform"); + $this->waitForLoadings(); + $this->clickNavigationItem("Content structure"); + $this->waitForLoadings(); + $this->openTreePath($name); + $this->waitForLoadings(); + $this->clickActionBar("Edit"); + $this->waitForLoadings(); + $this->platformStatus = self::WAITING_FOR_PUBLISHING; + } + + /** + * @When I view this Content + */ + public function viewThisContent() + { + $fieldManager = $this->getFieldTypeManager(); + $fieldManager->setFieldContentState(FieldType::CONTENT_PUBLISHED); + $name = $fieldManager->getThisContentName('eng-GB'); + + $this->loggedAsAdminPlatformUi(); + $this->clickNavigationZone("Platform"); + $this->waitForLoadings(); + $this->clickNavigationItem("Content structure"); + $this->waitForLoadings(); + $this->openTreePath($name); + $this->waitForLoadings(); + } + + /** + * @When I set :value as the Field Value + * @And I set :value as the Field Value + */ + public function setFieldValue($value) + { + $fieldManager = $this->getFieldTypeManager(); + $name = $fieldManager->getThisFieldTypeName('eng-GB'); + $this->fillFieldWithValue($name, $value); + } + + /** + * @When I publish the content + * @And I publish the content + */ + public function publishContent() + { + if ($this->platformStatus == self::WAITING_FOR_PUBLISHING) { + $this->clickEditActionBar("Publish"); + } else { + throw new \Exception("Cannot publish content, application in wrong state"); + } + } + + /** + * @When I check the Field Value + * @And I check the Field Value + */ + public function checkFieldValue() + { + if ($this->platformStatus == self::WAITING_FOR_PUBLISHING) { + $fieldManager = $this->getFieldTypeManager(); + $name = $fieldManager->getThisFieldTypeName('eng-GB'); + $this->checkOption($name); + } else { + throw new \Exception("Cannot publish content, application in wrong state"); + } + } + + /** + * @When I uncheck the Field Value + * @And I uncheck the Field Value + */ + public function uncheckFieldValue() + { + if ($this->platformStatus == self::WAITING_FOR_PUBLISHING) { + $fieldManager = $this->getFieldTypeManager(); + $name = $fieldManager->getThisFieldTypeName('eng-GB'); + $this->uncheckOption($name); + } else { + throw new \Exception("Cannot publish content, application in wrong state"); + } + } + + /** + * @Then I should see an/a :type field + * @Then I should see an/a :label label related with the :type field + */ + public function seeFieldtOfType($type, $label = null) + { + $this->executeDelayedActions(); + $verification = new WebAssert($this->getSession()); + $internalName = $this->getFieldTypeManager()->getFieldTypeInternalIdentifier($type); + $selector = ".ez-fieldview-$internalName"; + $verification->elementExists('css', $selector); + if ($label != null) { + $verification->elementTextContains('css', '.ez-fieldview-name', $label); + } + } + + /** + * @Then I should see an/a field with value :value + */ + public function seeFieldtWithValue($value) + { + $this->executeDelayedActions(); + $verification = new WebAssert($this->getSession()); + $verification->elementTextContains('css', '.ez-fieldview-value-content', $value); + } + + /** + * @Then the :label field should be marked as mandatory + */ + public function seeRequiredFieldtOfType($label) + { + if ($this->platformStatus == self::WAITING_FOR_PUBLISHING) { + $verification = new WebAssert($this->getSession()); + $verification->elementTextContains('css', '.ez-fielddefinition-name', $label . '*'); + } else { + throw new \Exception("Cannot publish content, application in wrong state"); + } + } + + /** + * @Given I set an/a empty value as the Field Value + * @And I set an/a empty value as the Field Value + * + */ + public function setFieldValueToNothing() + { + $this->setFieldValue(""); + } + + /** + * @Then Publishing fails with validation error message :messege + * + * Creates a Content with the previously defined ContentType + */ + public function failsWithMessage($message) + { + $verification = new WebAssert($this->getSession()); + $verification->elementTextContains('css', '.ez-editfield-error-message', $message); + } + + /** + * @Then the Content is successfully published + */ + public function contentIsPublished() + { + $page = $this->getSession()->getPage(); + $errors = $page->findAll('css', '.ez-editfield-error-message'); + foreach ($errors as $error) { + $errorMessage = $error->getText(); + if ($errorMessage != '') { + throw new \Exception("Content could not be published with error message: $errorMessage"); + } + } + } +} diff --git a/Features/Standard/platformui.feature b/Features/Standard/platformui.feature new file mode 100644 index 000000000..3488a7da1 --- /dev/null +++ b/Features/Standard/platformui.feature @@ -0,0 +1,30 @@ +Feature: Basic PlatfomrUi interaction tests + + @javascript + Scenario: As a admin User, I want to login to PlatformUI + Given I go to PlatformUI app with username "admin" and password "publish" + Then I should see "Welcome to eZ Platform" title + + @javascript + Scenario: As a admin User, I want to minimize the discovery bar + Given I go to PlatformUI app with username "admin" and password "publish" + And I click on the navigation zone "Platform" + When I click on the navigation item "Content structure" + Then I click on the discovery bar button "Minimize" + + @javascript + Scenario: As a admin User, I want to minimize the action bar + Given I go to PlatformUI app with username "admin" and password "publish" + And I click on the navigation zone "Platform" + When I click on the navigation item "Content structure" + Then I click on the action bar button "Minimize" + + @javascript + Scenario: As a admin User, I want to minimize the action bar + Given I go to PlatformUI app with username "admin" and password "publish" + And I click on the navigation zone "Platform" + And I click on the navigation item "Content structure" + When I click on the action bar button "Edit" + And I fill in "Name" with "HomePage" + And I click on the edit action bar button "Publish" + Then I should see "HomePage" title diff --git a/Features/doc/README.md b/Features/doc/README.md new file mode 100644 index 000000000..57e1b8adc --- /dev/null +++ b/Features/doc/README.md @@ -0,0 +1,28 @@ +## Pre-requisites: +* [Behat bundle](https://github.com/ezsystems/BehatBundle) is installed and configured properly; +* [PlatformUI bundle](https://github.com/ezsystems/PlatformUIBundle) is installed and configured properly. + +## Installation: +* Update behat.yml, on the installation root, with PlatformUI parameters( check example behat.yml provided ); +* Make sure you configure the suites in the behat.yml if you want to add more features; +* That's it! + +## Known issues: +* Sahi web driver can't publish content; +* Selenium web driver does not work with Firefox 35 and up, Firefox 34 recommended; +* Currently it is not possible to run multiple scenarios in sequence (using the same browser instance), possible issue with cookies. + +## How to run: +* Selenium + * Download the latest Selenium server .jar file from the [offical site](http://www.seleniumhq.org/download/); + * In the directory where the Selenium server .jar was downloaded start the Selenium server with the command `$ java -jar .jar` + * On your ezpublish installation directory run the command `$ php bin/behat --profile platformui` + * That's it! + +* Sahi + * Download the latest Sahi server from [here](http://sourceforge.net/projects/sahi/files/); + * Extract the Sahi server to a desired directory, navigate to the bin/ directory inside and start the Sahi server with the command `$ ./sahi.sh` + * On your ezpublish installation directory run the command `$ php bin/behat --profile platformui` + * That's it! + +#### Check Sentences file provided for all the available sentences. diff --git a/Features/doc/Sentences.md b/Features/doc/Sentences.md new file mode 100644 index 000000000..9fb886672 --- /dev/null +++ b/Features/doc/Sentences.md @@ -0,0 +1,30 @@ +PlatformUI available sentences: + - Given/When: + Given I go to homepage + Given I go to PlatformUI app with username :user and password :password + Given I click (on) the logo + Given I click (on) the tab :tab + Given I click (on) the navigation zone :zone + Given I over (on) the navigation zone :zone + Given I click (on) the navigation item :subMenu + Given I click (on) the actionbar action :sideMenuOption + Given I click (on) the content type :contentType + Given I click (on) the content tree with path :path + Given I fill in :field subform with: + Given I upload the image :path + Given I upload the file :path + Given I drag and drop a file :file to upload + Given I logout + + -Then: + Then I see Content :contentName of type :contentType + Then I should see the following elements: + Then I shouldn't see the following elements: + Then I should not see the following elements: + Then I should see (an) element :element with value :value + Then I should see (an) element :element with (an) image + Then I should see (an) element :element with (an) file :file + Then I should see elements with the following names: + Then I should be logged in + + diff --git a/Features/doc/behat.yml.example b/Features/doc/behat.yml.example new file mode 100644 index 000000000..1e78af465 --- /dev/null +++ b/Features/doc/behat.yml.example @@ -0,0 +1,36 @@ +default: + extensions: + Behat\MinkExtension: + base_url: '' + # parameter for files directory used on testing such as upload + files_path: '' + goutte: ~ + sahi: + limit: 1200 + selenium2: + capabilities: { "browserName": "firefox", "version": "35" } + javascript_session: selenium2 + browser_name: firefox + + Behat\Symfony2Extension: + kernel: + bootstrap: ezpublish/autoload.php + path: ezpublish/EzPublishKernel.php + class: EzPublishKernel + env: prod + debug: false + + # default profile: no suites + suites: ~ + +# suite for the standart features suite that comes with core +platformui: + suites: + standard: + paths: [ vendor/ezsystems/platform-ui-bundle/Features/Standard ] + contexts: + - EzSystems\PlatformUIBundle\Features\Context\PlatformUI: + # URI of the PlatformUI application + uri: /shell + # Web driver in use, must match default:extensions:javascript_session + driver: Selenium2