diff --git a/composer.json b/composer.json index ffb2291..96c57b6 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,9 @@ } ], "minimum-stability": "dev", + "config": { + "bin-dir": "bin" + }, "require": { "php": ">=5.5, <7.1", "typo3/cms": "^7.6", @@ -49,7 +52,14 @@ "t3kit/theme-t3kit": "dev-master", "t3kit/theme-t3kit-bluemountain": "dev-master", "pixelant/pxa-form-enhancement": "^1.0", - "pixelant/pxa-newsletter-subscription": "dev-master" + "pixelant/pxa-newsletter-subscription": "dev-master", + "lauri/dockert3kit": "~2.2.2", + + "behat/behat": "~3.0@dev", + "behat/mink-extension": "~2.0@dev", + "behat/mink-selenium2-driver": "~1.2@dev", + "emuse/behat-html-formatter": "dev-master", + "guzzlehttp/guzzle": "~6.2.1" }, "extra": { "typo3/cms": { diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..7a069e3 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,115 @@ +# Testing t3kit with behat +### t3kit with behat php framework + +## Getting Started: + +First you need to understand what is behat test by go to this link + +### How to integrate behat test to your project directory + +1. Add this code below to your `composer.json` root file of project + + ```json + "require-dev": { + "behat/behat": "~3.0@dev", + "behat/mink-extension": "~2.0@dev", + "behat/mink-selenium2-driver": "~1.2@dev", + "emuse/behat-html-formatter": "dev-master" + }, + "config": { + "bin-dir": "bin/" + } + ``` +2. Run `composer update behat/behat` +3. Create `tests` folder inside your root project directory +4. Create `behaviour` then `features` and `bootstrap` folder +5. Create `FeatureContext` php file inside `bootstrap` folder + + ```php + assertSession()->elementExists('css', self::LOGO_SELECTOR); + if (empty($element)) { + throw new \Exception(sprintf("The logo with css '%s' selector does not exist.", self::LOGO_SELECTOR)); + } + } + + } + ``` +6. Create `feature file` to check homepage inside `features` folder + ```gherkin + Feature: Homepage Overview + In order to see and view homepage + As website visitor + I want to open the website and see an overview of the page + Background: + Given I am in full screen + And I am on the homepage + Scenario: Check logo on homepage + Then I should see the logo + ``` +7. Create `behat.yml` and configuration + ```yaml + default: + autoload: + '': %paths.base%/features/bootstrap + extensions: + Behat\MinkExtension: + # put your + base_url: http://local.t3kit.wehost.asia/ + default_session: selenium2 + files_path: features/Resources + show_cmd: 'open %s' + selenium2: + browser: chrome + wd_host: http://dockerhost:4444/wd/hub + + emuse\BehatHTMLFormatter\BehatHTMLFormatterExtension: + name: html + renderer: Twig,Behat2 + file_name: uat-report + print_args: true + print_outp: true + loop_break: true + formatters: + pretty: true + junit: + output_path: ./build/logs + html: + output_path: ./build/logs + + suites: + default: + paths: &featurePaths + - '%paths.base%/features' + contexts: &contexts + - FeatureContext + - XmlContext + - ResponsiveContext: + screenSizes: + desktop: + width: 1200 + height: 1400 + tablet: + width: 785 + height: 1024 + mobile: + width: 320 + height: 1024 + filters: + tags: ~@skip + ``` +Then you can follow `README` in root file of docker with path `User Aceptance Test` diff --git a/tests/behaviour/behat.yml b/tests/behaviour/behat.yml new file mode 100644 index 0000000..0ab9782 --- /dev/null +++ b/tests/behaviour/behat.yml @@ -0,0 +1,47 @@ +default: + autoload: + '': %paths.base%/features/bootstrap + extensions: + Behat\MinkExtension: + base_url: http://local.t3kit.wehost.asia/ + default_session: selenium2 + files_path: features/Resources + show_cmd: 'open %s' + selenium2: + browser: chrome + wd_host: http://dockerhost:4444/wd/hub + + emuse\BehatHTMLFormatter\BehatHTMLFormatterExtension: + name: html + renderer: Twig,Behat2 + file_name: uat-report + print_args: true + print_outp: true + loop_break: true + formatters: + pretty: true + junit: + output_path: ./build/logs + html: + output_path: ./build/logs + + suites: + default: + paths: &featurePaths + - '%paths.base%/features' + contexts: &contexts + - FeatureContext + - XmlContext + - ResponsiveContext: + screenSizes: + desktop: + width: 1200 + height: 1400 + tablet: + width: 785 + height: 1024 + mobile: + width: 320 + height: 1024 + filters: + tags: ~@skip diff --git a/tests/behaviour/features/bootstrap/FeatureContext.php b/tests/behaviour/features/bootstrap/FeatureContext.php new file mode 100644 index 0000000..378bcca --- /dev/null +++ b/tests/behaviour/features/bootstrap/FeatureContext.php @@ -0,0 +1,366 @@ +isVisible()) { + $visibleNodes[] = $node; + } + } + return $visibleNodes; + } + + /** + * Checks, logo with the predefined css selector + * Example: I should see the logo + * + * @Then I should see the logo + * + * @throws \Exception + */ + public function iShouldSeeTheLogo() { + $element = $this->assertSession()->elementExists('css', self::LOGO_SELECTOR); + if (empty($element)) { + throw new \Exception(sprintf("The logo with css '%s' selector does not exist.", self::LOGO_SELECTOR)); + } + } + + /** + * Checks, page have css selectors + * Example: The element with css selector "div.className" exists + * + * @Then /^the element with css selector "([^"]*)" exists$/ + * + * @param $cssSelector + * + * @throws \Exception + */ + public function theElementWithCssSelector($cssSelector) { + $element = $this->getSession()->getPage()->has("css", $cssSelector); + if (empty($element)) { + throw new \Exception(sprintf("The page '%s' does not contain the css selector '%s'", $this->getSession()->getCurrentUrl(), $cssSelector)); + } + } + + /** + * Checks, tag should have an attribute with value + * Example: Then tag "a" should have an attribute "title" with "Federation de la Haute Horlogerie" + * + * @param $tag + * @param $attribute + * @param $value + * + * @Then tag :tag should have an attribute :attribute with value :value + * @throws \Exception + */ + public function tagShouldHaveAnAttributeWithValue($tag, $attribute, $value) { + $session = $this->getSession(); + $element = $session->getPage()->find("css", $tag . "[" . $attribute . "='" . $value . "']"); + if (empty($element)) { + throw new \Exception(sprintf("The page does not contain tag '%s' with attribute '%s' equal '%s'.", $tag, $attribute, $value)); + } + } + + /** + * Hover on the element with the provided css selector + * Example: When I hover the element "nav ul li a" + * + * @When /^I hover over the element "([^"]*)"$/ + * + * @param $locator + * + * @throws \Exception + */ + public function iHoverOverTheElement($locator) { + $session = $this->getSession(); + $element = $session->getPage()->find('css', $locator); + if (empty($element)) { + throw new \InvalidArgumentException(sprintf('Could not evaluate CSS selector: "%s"', $locator)); + } + $element->mouseOver(); + } + + /** + * Click some text in span + * Example: When I click on the text "spanText" + * + * @When /^I click on the text "([^"]*)"$/ + * + * @param $text + * + * @throws \Exception + */ + public function iClickOnTheText($text) { + $session = $this->getSession(); + $element = $session->getPage()->find('xpath', $session->getSelectorsHandler()->selectorToXpath('xpath', '//span[text()="' . $text . '"]')); + if (empty($element)) { + throw new \InvalidArgumentException(sprintf('Cannot find text: "%s"', $text)); + } + $element->click(); + } + + /** + * @param $nth + * @param $selector + * + * Click on the elements that have the same value + * Example: I follow the "1" of "nav ul li a" + * + * @When /^I follow the "([^"]*)" of "([^"]*)" element$/ + * + * @throws \Exception + */ + public function iFollowTheNthElement($nth, $selector) { + $elements = $this->getSession()->getPage()->findAll('css', $selector); + $elements = $this->filterVisibleNodes($elements); + if (empty($elements)) { + throw new \Exception(sprintf("The element with css '%s' selector does not exist.", $selector)); + } + + if ($nth < 1 || $nth > count($elements)) { + throw new \Exception(sprintf("Index '%s' out of range. The element index starts from 1.", $nth)); + } + + /** @var NodeElement $element */ + $element = $elements[$nth - 1]; + $element->click(); + } + + /** + * Click on the element with the provided css selector + * Example: When I click on the "1" element "nav ul li a" + * + * @When /^I click on the "([^"]*)" element "([^"]*)"$/ + * + * @param $nth + * @param $locator + * + * @throws \Exception + */ + public function iClickOnTheElement($nth, $locator) { + $this->iFollowTheNthElement($nth, $locator); + } + + /** + * Loop menu navigation + * Example: I should access all pages of navigation with css selector "#main-nav ul li a" + + * @param string $selector + * @throws \Exception + * + * @Then /^I should access all pages of navigation with css selector "([^"]*)"$/ + */ + public function iShouldAccessAllPagesOfNavigationWithCssSelector($selector) { + $page = $this->getSession()->getPage(); + $elements = $page->findAll('css', $selector); + if (empty($elements)) { + throw new \Exception(sprintf("Cannot find css '%s' selector.", $selector)); + } + + /** @var \Behat\Mink\Element\NodeElement $element */ + foreach ($elements as $element) { + + /** + * Force to every visit the page go to homepage first. + * It also prevents external links inside main menu. + */ + $this->visitPath('/'); + + $link = $element->getAttribute('href'); + $string = sprintf('Visiting page with link: %s', $link); + echo "\033[36m -> " . strtr($string, array("\n" => "\n| ")) . "\033[0m\n"; + $this->visitPath($link); + } + } + + /** + * Wait a number of seconds + * Example: When I wait for "2" seconds + * + * @When /^I wait for "([^"]*)" seconds$/ + * + * @param $num + */ + public function iWaitForSeconds($num) { + $session = $this->getSession(); + if ($session->getDriver() instanceof Selenium2Driver) { + $this->getSession()->wait($num * 1000); + } + } + + /** + * Wait a number of milliseconds + * Example: When I wait for "2" milliseconds + * + * @When /^I wait for "([^"]*)" milliseconds$/ + * + * @param $num + */ + public function iWaitForMilliseconds($num) { + $session = $this->getSession(); + if ($session->getDriver() instanceof Selenium2Driver) { + $this->getSession()->wait($num); + } + } + + /** + * This code works only for Tinymce version >= 4.* + * Example: I fill :id of tinymce with :value + * - ":id" the id of textarea that you want to wrap tinymce + * + * @Then /^I fill "([^"]*)" of tinymce with "([^"]*)"$/ + * + * @param $id + * @param $value + */ + public function iSetTinymce($id, $value) { + $js = sprintf('tinymce.get("%s").setContent("%s");', $id, $value); + $this->getSession()->executeScript($js); + } + + /** + * @param $nth + * @param $linkText + * + * Example: I follow the "1" of link "link-text" + * + * @When /^I follow the "([^"]*)" of link "([^"]*)"$/ + * + * @throws \Exception + */ + public function iFollowTheNthText($nth, $linkText) { + $linkText = $this->fixStepArgument($linkText); + $links = $this->getSession()->getPage()->findAll('named', array('link', $linkText)); + $links = $this->filterVisibleNodes($links); + if (empty($links)) { + throw new \Exception(sprintf("The '%s' text does not exist.", $linkText)); + } + + if ($nth < 1 || $nth > count($links)) { + throw new \Exception(sprintf("Index '%s' is out of range. The element index starts from 1.", $nth)); + } + + /** @var NodeElement $link */ + $link = $links[$nth - 1]; + $link->click(); + } + + /** + * @param $nth + * @param $text + * + * Example: I press the "1" of button "text" + * + * @When /^I press the "([^"]*)" of button "([^"]*)"$/ + * + * @throws \Exception + */ + public function iPressTheNthText($nth, $text) { + $text = $this->fixStepArgument($text); + $buttons = $this->getSession()->getPage()->findAll('named', array('button', $text)); + $buttons = $this->filterVisibleNodes($buttons); + if (empty($buttons)) { + throw new \Exception(sprintf("The button with '%s' text does not exist.", $text)); + } + + if ($nth < 1 || $nth > count($buttons)) { + throw new \Exception(sprintf("Index '%s' is out of range. The element index starts from 1.", $nth)); + } + + /** @var NodeElement $button */ + $button = $buttons[$nth - 1]; + $button->press(); + } + + /** + * @When /^I should be on the top of page$/ + */ + public function iShouldBeOnTheTopOfPage() { + $this->getSession()->executeScript("window.scrollTo(0, 0);"); + } + + /** + * Scroll the page down to the specific element + * + * @When /^I scroll down "([^"]*)" pixels$/ + * + * @param $number + * + * @throws \Exception + */ + public function scrollDown($number) { + $function = <<getSession()->executeScript($function); + } catch(Exception $e) { + throw new \Exception("ScrollIntoView failed"); + } + } + + /** + * Locates url, based on provided path. + * Override to provide custom routing mechanism. + * + * @param string $path + * + * @return string + */ + public function locatePathUrl($path) { + $startUrl = rtrim($this->getMinkParameter('base_url'), '//') . '//'; + return 0 !== strpos($path, 'http') ? $startUrl . ltrim($path, '//') : $path; + } + + /** + * Checks, that current page PATH is equal to specified + * Example: Then I should be on the page "//" + * Example: And I should be on the page "//bats" + * Example: And I should be on the page "http://google.com" + * + * @Then /^(?:|I )should be on the page "(?P[^"]+)"$/ + * + * @param $page + */ + public function assertPageUrlAddress($page) { + $this->assertSession()->addressEquals($this->locatePathUrl($page)); + } + + + /** + * Check, that the page should open with the new tab + * Example: Then the page should open in a new tab + * + * @Then /^the page should be opened in a new tab$/ + * + * @throws \Exception + */ + public function documentShouldBeOpenedInNewTab() { + $session = $this->getSession(); + $windowNames = $session->getWindowNames(); + if (count($windowNames) < 2) { + throw new \ErrorException("Expected to see at least new tab opened"); + } + $session->switchToWindow($windowNames[1]); + } + +} diff --git a/tests/behaviour/features/bootstrap/ResponsiveContext.php b/tests/behaviour/features/bootstrap/ResponsiveContext.php new file mode 100644 index 0000000..fe23891 --- /dev/null +++ b/tests/behaviour/features/bootstrap/ResponsiveContext.php @@ -0,0 +1,51 @@ +screenSizes = $screenSizes; + } + + /** + * Resize the browser window to a preset layout + * + * @When /^I am in "([^"]*)" layout$/ + */ + public function iResizeTheWindowToLayout($layout) { + if (array_key_exists($layout, $this->screenSizes)) { + $currentLayout = $this->screenSizes[$layout]; + $this->getSession()->getDriver()->resizeWindow($currentLayout['width'], $currentLayout['height'], 'current'); + return TRUE; + } + throw new \Exception(sprintf('Layout "%s" not defined', $layout)); + } + + /** + * Resize the browser with full screen + * Example When I am in full screen + * + * @When /^I am in full screen$/ + */ + public function resizeWindow() { + $this->getSession()->maximizeWindow(); + } + +} diff --git a/tests/behaviour/features/bootstrap/XmlContext.php b/tests/behaviour/features/bootstrap/XmlContext.php new file mode 100644 index 0000000..7a220f5 --- /dev/null +++ b/tests/behaviour/features/bootstrap/XmlContext.php @@ -0,0 +1,124 @@ + + * + ***************************************************************/ + +use Behat\Mink\Exception\ExpectationException; +use Behat\MinkExtension\Context\RawMinkContext; +use Behat\Behat\Context\Context as ContextInterface; + +/** + * Xml context + */ +class XmlContext extends RawMinkContext implements ContextInterface { + + + + /** + * @return \DOMDocument + */ + public function getXmlDataFromCurrentPage() { + $rawSource = $this->getSession()->getPage()->getContent(); + $domDocument = new DOMDocument(); + $domDocument->loadXML($rawSource); + return $domDocument; + } + + /** + * @param \DOMDocument $domDocument + * + * @return array + */ + public function adjustXmlSiteampToArrayOfLinks($domDocument) { + /** @var \DOMNodeList $xmlLinks */ + $xmlLinks = $domDocument->getElementsByTagName('loc'); + $links = []; + /** @var \DOMElement $xmlLink */ + foreach ($xmlLinks as $xmlLink) { + $links[] = $xmlLink->nodeValue; + } + return $links; + } + + /** + * Checks, that xml response contain specified string + * + * @param $text + * @throws ExpectationException + * + * @Then /^the xml response should contain "([^"]*)"$/ + */ + public function xmlResponseShouldContain($text) { + $actual = $this->getSession()->getDriver()->getContent(); + $regex = '/'.preg_quote($text, '/').'/ui'; + + if (!preg_match($regex, $actual)) { + $message = sprintf('The string "%s" was not found anywhere in the XML response of the current page.', $text); + throw new ExpectationException($message, $this->getSession()); + } + } + + /** + * @param $xmlElement + * @throws \Exception + * + * @Then /^I should see xml element "([^"]*)"$/ + */ + public function iShouldSeeXmlElement($xmlElement) { + /** @var \DOMDocument $domDocument */ + $domDocument = $this->getXmlDataFromCurrentPage(); + $xmlElementNodes = $domDocument->getElementsByTagName($xmlElement); + if ($xmlElementNodes->length == 0) { + throw new Exception($xmlElement . ' is not found in xml source!'); + } + } + + /** + * @param $xmlEncoding + * @throws \Exception + * + * @Then /^the xml encoding should be "([^"]*)"$/ + */ + public function xmlEncodingShouldBe($xmlEncoding) { + /** @var \DOMDocument $domDocument */ + $domDocument = $this->getXmlDataFromCurrentPage(); + $domDocumentEncoding = $domDocument->encoding; + if ($xmlEncoding !== $domDocumentEncoding) { + throw new Exception($xmlEncoding . ' is not found in xml source!'); + } + } + + /** + * Example: the xml sitemap should contain links "/,/company.html,/blog.html" + * + * @param $stringLinks + * @throws \Exception + * + * @Then /^the xml sitemap should contain links "([^"]*)"$/ + */ + public function xmlShouldContainLinks($stringLinks) { + /** @var \DOMDocument $domDocument */ + $domDocument = $this->getXmlDataFromCurrentPage(); + /** @var array $xmlLinks */ + $xmlLinks = $this->adjustXmlSiteampToArrayOfLinks($domDocument); + if (count($xmlLinks) == 0) { + throw new Exception("There is no links in sitemap.xml"); + } + + $baseUrl = rtrim($this->getMinkParameter('base_url'), "/"); + $links = explode(',', $stringLinks); + foreach ($links as $link) { + $link = $baseUrl . trim($link); + if (! in_array($link, $xmlLinks)) { + throw new Exception(sprintf("The link '%s' does not exist in sitemap.xml", $link)); + } + } + } +} diff --git a/tests/behaviour/features/homepage.feature b/tests/behaviour/features/homepage.feature new file mode 100644 index 0000000..9b3e604 --- /dev/null +++ b/tests/behaviour/features/homepage.feature @@ -0,0 +1,31 @@ +Feature: Homepage Overview + In order to see and view homepage + As website visitor + I want to open the website and see an overview of the page + + Background: + Given I am in full screen + And I am on the homepage + + Scenario: User click access to main menu navigation + Then I should access all pages of navigation with css selector "nav.main-navigation div ul li a" + + Scenario: Header on the top menu + Then I should see the logo + And I should see "Call us on" + And I should see "Email us at" + And I should see "Login" + And I should see "Sitemap" + And I should see "Icon text and link" + + Scenario: Homepage has slideshow + Then I should see "Vel mollis massa varius sed" + And I should see "Read more" + When I follow "Read more" + Then the page should be opened in a new tab + Then I should see "Lagen" + + Scenario: User click scroll down + When I scroll down "400" pixels + Then I should see "EVOLUTION CONTINUES ... with 7LTS" + And I should see " GET IT NOW - it's better than ever!" diff --git a/tests/behaviour/features/responsive.feature b/tests/behaviour/features/responsive.feature new file mode 100644 index 0000000..4c31a4a --- /dev/null +++ b/tests/behaviour/features/responsive.feature @@ -0,0 +1,32 @@ +Feature: Homepage Responsive + In order to see and view homepage with responsive + As website visitor + I want to open the website and see an overview of the page + + Background: + Given I am on the homepage + And I am in "mobile" layout + + Scenario: User click access to main menu navigation + Then I should access all pages of navigation with css selector "nav.main-navigation div ul li a" + + Scenario: Homepage has slideshow + Then I should see "Vel mollis massa varius sed" + And I should see "Read more" + When I follow "Read more" + Then the page should be opened in a new tab + Then I should see "Lagen" + + Scenario: User click scroll down + When I scroll down "400" pixels + Then I should see "EVOLUTION CONTINUES ... with 7LTS" + And I should see " GET IT NOW - it's better than ever!" + + Scenario: Header on the top menu + When I am in "tablet" layout + Then I should see the logo + And I should see "Call us on" + And I should see "Email us at" + And I should see "Login" + And I should see "Sitemap" + And I should see "Icon text and link" diff --git a/tests/behaviour/features/sitemapXml.feature b/tests/behaviour/features/sitemapXml.feature new file mode 100644 index 0000000..d681299 --- /dev/null +++ b/tests/behaviour/features/sitemapXml.feature @@ -0,0 +1,12 @@ +Feature: Sitemap XML + In order to see the sitemap + As a website owner + I want to see list of xml sitemap + + Scenario: sitemap.xml is in valid XML sitemap format + Given I am on "/sitemap.xml" + Then the xml encoding should be "UTF-8" + And I should see xml element "urlset" + And I should see xml element "url" + And I should see xml element "loc" + And the xml sitemap should contain links "/content/plugins/news/,/content/form-elements/mail-form/,/content/typical-page-content/headers/" diff --git a/typo3conf/PackageStates.php b/typo3conf/PackageStates.php index b9377f7..61faf8e 100644 --- a/typo3conf/PackageStates.php +++ b/typo3conf/PackageStates.php @@ -304,9 +304,7 @@ 'composerName' => 'dmitryd/typo3-realurl', 'state' => 'active', 'packagePath' => 'typo3conf/ext/realurl/', - 'suggestions' => [ - 'static_info_tables', - ], + 'suggestions' => [], ], 'realurl_404_multilingual' => [ 'composerName' => 'realurl_404_multilingual', @@ -314,12 +312,6 @@ 'packagePath' => 'typo3conf/ext/realurl_404_multilingual/', 'suggestions' => [], ], - 'seo_basics' => [ - 'composerName' => 'b13/seo_basics', - 'state' => 'active', - 'packagePath' => 'typo3conf/ext/seo_basics/', - 'suggestions' => [], - ], 'go_maps_ext' => [ 'composerName' => 'clickstorm/go_maps_ext', 'state' => 'active', @@ -344,16 +336,16 @@ 'packagePath' => 'typo3conf/ext/pxa_newsletter_subscription/', 'suggestions' => [], ], - 'solr' => [ - 'composerName' => 'apache-solr-for-typo3/solr', + 'seo_basics' => [ + 'composerName' => 'b13/seo_basics', 'state' => 'active', - 'packagePath' => 'typo3conf/ext/solr/', + 'packagePath' => 'typo3conf/ext/seo_basics/', 'suggestions' => [], ], - 'yaml_parser' => [ - 'composerName' => 'typo3-ter/yaml-parser', + 'solr' => [ + 'composerName' => 'apache-solr-for-typo3/solr', 'state' => 'active', - 'packagePath' => 'typo3conf/ext/yaml_parser/', + 'packagePath' => 'typo3conf/ext/solr/', 'suggestions' => [], ], 'adodb' => [