* Introduce functional js testing * Reorganize travis configurationtags/3.3.0-beta1
@@ -6,3 +6,5 @@ libraries/**/* | |||
sites/**/libraries/**/* | |||
profiles/**/libraries/**/* | |||
**/js_test_files/**/* | |||
docroot/**/* | |||
tests/modules/thunder_test/js/jquery.simulate.js |
@@ -5,5 +5,4 @@ | |||
/libraries | |||
/themes/infinite/ | |||
.DS_Store | |||
tests/behat/behat.local.yml | |||
composer.lock |
@@ -5,6 +5,9 @@ cache: | |||
- "$HOME/.composer/cache" | |||
- "$HOME/.drush/cache" | |||
- "vendor" | |||
- "travis_phantomjs" | |||
- "travis_selenium" | |||
php: | |||
- 5.6 | |||
- 7.0 | |||
@@ -28,58 +31,28 @@ mysql: | |||
encoding: utf8 | |||
before_install: | |||
- phpenv config-rm xdebug.ini | |||
- composer self-update | |||
- composer --version | |||
# Clear drush release history cache, to pick up new releases. | |||
- rm -f ~/.drush/cache/download/*---updates.drupal.org-release-history-* | |||
- source ./scripts/travis/setup-environment.sh | |||
- bash -x -e ./scripts/travis/before-install.sh | |||
install: | |||
# Set global environment variables | |||
- export THUNDER_DIST_DIR=`echo $(pwd)` | |||
- export TEST_DIR=`echo ${THUNDER_DIST_DIR}"/../test-dir"` | |||
- bash -e ${THUNDER_DIST_DIR}/scripts/keep-travis-running.sh & | |||
# add composer's global bin directory to the path | |||
# see: https://github.com/drush-ops/drush#install---composer | |||
- export PATH="$HOME/.composer/vendor/bin:$PATH" | |||
# Install Drush | |||
- composer global require drush/drush:~8 --prefer-source | |||
- phpenv rehash | |||
- drush dl drupalorg_drush-7.x | |||
- drush verify-makefile | |||
# MySQL Options | |||
- mysql -e 'SET GLOBAL wait_timeout = 5400;' | |||
- mysql -e "SHOW VARIABLES LIKE 'wait_timeout'" | |||
# PHP conf tweaks | |||
- echo 'max_execution_time = 120' >> drupal.php.ini; | |||
- echo 'sendmail_path = /bin/true' >> drupal.php.ini; | |||
- phpenv config-add drupal.php.ini | |||
- phpenv rehash | |||
- mkdir -p ${TEST_DIR} | |||
# Install Test requirements and set global environment variables | |||
- composer global require "drupal/drupal-extension:^3.2" "devinci/devinci-behat-extension:dev-master" | |||
- BEHAT_PARAMS='{"extensions":{"Drupal\\DrupalExtension":{"drupal":{"drupal_root":"TEST_DIR_MACRO/docroot"}},"Behat\\MinkExtension":{"base_url":"http://localhost:8080"}}}' | |||
- BEHAT_PARAMS=`echo $BEHAT_PARAMS | sed -e s#TEST_DIR_MACRO#$TEST_DIR#g` | |||
- export BEHAT_PARAMS | |||
# Build docroot and install thunder | |||
- bash -x -e ${THUNDER_DIST_DIR}/scripts/build-docroot.sh | |||
- bash -x -e ./scripts/travis/install-requirements.sh | |||
- bash -x -e ./scripts/travis/install-thunder.sh | |||
before_script: | |||
- cd ${TEST_DIR}/docroot | |||
- drush cr | |||
- drush runserver --default-server=builtin 8080 &>/dev/null & | |||
- phantomjs --webdriver=4444 > /dev/null & | |||
- bash -x -e ./scripts/travis/setup-tests.sh | |||
script: | |||
# Run Drupal tests (@group Thunder) | |||
- php ${TEST_DIR}/docroot/core/scripts/run-tests.sh --php `which php` --die-on-fail --verbose --url http://localhost:8080 Thunder | |||
# Run Behat tests | |||
- behat --config ${THUNDER_DIST_DIR}/tests/behat/behat.travis.yml | |||
- bash -x -e ./scripts/travis/run-tests.sh | |||
deploy: | |||
provider: s3 | |||
access_key_id: AKIAJULZG77ZX2XFLPOA | |||
secret_access_key: | |||
secure: AtsY08zKG01jR6ZQLzIrH6MAF52heHB1fdvWLUs+S9eJJ4h31Z4MJqRtlQQn8Ur1JzFWWS4Gmt3biKILDRm7uCBFz3z3b8kuEt4dPDHzBRdhe6TN4QvTyUz6pL5Q6AR8pVMKc0jNNR2tDCMWqjCMpBCHbdS+28iyobdtx8tQn/HP6Udapl2FHByF1TqCtpMQ9ISbpcjf0AC9WUADL1BnyHoqybAvwUpyoO3Iuym6EhOzCJ3aZULt64IamwpWy3fikVwOtzxwQHS7Jp4M7k0QhgGM0hvlEKnKwS7X6CRysBuxH/exY11UdDUgTzPMw5AsnORkFbyKIh2WW2mvJ9aQJuKbSxAmnr4zQKSS9/7MmDM3FEkeQF8DL5SKb/Q0ap3zCgdcbSVfS5UElS4YrKHSGioudfyEpIQUtMOkW1Lz7SDdLCRD5PaubR8BvifbxRvKNLJX2RjVJ2HyPzKNdkn6mviAteNJrh2YwMgLvC5aFRwCHRFgsJwoek43LL4w3M5jZS8Vz4PrjJkBsLwhVzZbeR0S4CpeewttM5hoC19CllygpbRjjJrChfdre1RsfDRYKsty4WpqZD4jZoxU0sNfyNcn0KnCvspRV40bZqzy0uxY3mVPw76OnZUvWBLVi89PvJ56/AXuHhNalkQbgRxyd0SXAjK34EBMLOyLCGBi3NM= | |||
bucket: thunder-travis-ci | |||
region: eu-central-1 | |||
local-dir: /tmp/thunder-travis-ci | |||
upload-dir: thunder-distribution | |||
acl: private | |||
on: | |||
repo: BurdaMagazinOrg/thunder-distribution |
@@ -94,9 +94,6 @@ | |||
"valiton/harbourmaster": "~8.1" | |||
}, | |||
"require-dev": { | |||
"burdamagazinorg/thunder-dev-tools": "dev-master", | |||
"behat/behat": "~3.0", | |||
"drupal/drupal-extension": "^3.2", | |||
"devinci/devinci-behat-extension": "dev-master" | |||
"burdamagazinorg/thunder-dev-tools": "dev-master" | |||
} | |||
} |
@@ -5,7 +5,7 @@ | |||
(function ($) { | |||
"use strict"; | |||
'use strict'; | |||
/** | |||
* Attaches the behavior of the media entity browser view. | |||
@@ -4,16 +4,16 @@ | |||
* Media related javascripts. | |||
*/ | |||
(function ($) { | |||
'use strict'; | |||
'use strict'; | |||
/** | |||
* Registers behaviours related to thunder media. | |||
*/ | |||
Drupal.behaviors.fixGallery = { | |||
attach: function (context) { | |||
$('.media-gallery img').height($('.media-gallery img').attr('height')); | |||
} | |||
}; | |||
/** | |||
* Registers behaviours related to thunder media. | |||
*/ | |||
Drupal.behaviors.fixGallery = { | |||
attach: function (context) { | |||
$('.media-gallery img').height($('.media-gallery img').attr('height')); | |||
} | |||
}; | |||
}(jQuery, Drupal, drupalSettings)); | |||
@@ -0,0 +1,39 @@ | |||
#!/usr/bin/env bash | |||
# Download thunder from drupal.org with drush | |||
drush_download_thunder() { | |||
DOWNLOAD_PATH=$1 | |||
mkdir -p ${DOWNLOAD_PATH} | |||
cd ${DOWNLOAD_PATH} | |||
drush dl thunder --drupal-project-rename="docroot" -y | |||
} | |||
# remove xdebug to make php execute faster | |||
phpenv config-rm xdebug.ini | |||
# Set MySQL Options | |||
mysql -e 'SET GLOBAL wait_timeout = 5400;' | |||
mysql -e "SHOW VARIABLES LIKE 'wait_timeout'" | |||
# PHP conf tweaks | |||
echo 'max_execution_time = 120' >> drupal.php.ini; | |||
echo 'sendmail_path = /bin/true' >> drupal.php.ini; | |||
phpenv config-add drupal.php.ini | |||
phpenv rehash | |||
# Prepare test directory | |||
mkdir -p ${TEST_DIR} | |||
# Clear drush release history cache, to pick up new releases. | |||
rm -f ~/.drush/cache/download/*---updates.drupal.org-release-history-* | |||
# keep travis running without output | |||
bash -e ${THUNDER_DIST_DIR}/scripts/travis/keep-travis-running.sh & | |||
# If we test update, we also need the previous version of thunder downloaded | |||
if [[ ${TEST_UPDATE} == "true" ]]; then | |||
# Download latest release from drupal.org | |||
drush_download_thunder {$UPDATE_BASE_PATH} | |||
fi |
@@ -0,0 +1,36 @@ | |||
#!/usr/bin/env bash | |||
# update composer | |||
composer self-update | |||
# install required phantomjs Version, if its not already build from travis cache | |||
if [ $(phantomjs --version) != ${PHANTOMJS_VERSION} ]; then | |||
rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; | |||
fi | |||
if [ $(phantomjs --version) != ${PHANTOMJS_VERSION} ]; then | |||
wget https://assets.membergetmember.co/software/phantomjs-${PHANTOMJS_VERSION}-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-${PHANTOMJS_VERSION}-linux-x86_64.tar.bz2; | |||
fi | |||
if [ $(phantomjs --version) != ${PHANTOMJS_VERSION} ]; then | |||
tar -xf $PWD/travis_phantomjs/phantomjs-${PHANTOMJS_VERSION}-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; | |||
fi | |||
phantomjs --version | |||
# download + install Selenium2 | |||
if [ ! -d "$SELENIUM_PATH" ]; then | |||
mkdir -p $SELENIUM_PATH; | |||
fi | |||
if [ ! -f "$SELENIUM_PATH/selenium-server-standalone-2.53.1.jar" ]; then | |||
wget http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar -O "$SELENIUM_PATH/selenium-server-standalone-2.53.1.jar" | |||
fi | |||
# Install Drush and drupalorg_drush module | |||
composer global require drush/drush:~8 | |||
phpenv rehash | |||
drush dl drupalorg_drush-7.x | |||
# verify, that makefile is accepted by drupal.org, otherwise we do not need to go any further | |||
drush verify-makefile | |||
# install image magick | |||
printf "\n" | pecl install imagick |
@@ -1,4 +1,5 @@ | |||
#!/bin/bash | |||
#!/usr/bin/env bash | |||
# Install thunder and enable Test module | |||
# in provided folder | |||
@@ -9,19 +10,19 @@ install_thunder() { | |||
drush en simpletest -y | |||
} | |||
# For daily cron runs, current version from Drupal will be installed | |||
# and after that update will be executed and tested | |||
if [[ ${TRAVIS_EVENT_TYPE} == "cron" ]]; then | |||
# Install last version released on Drupal | |||
mkdir -p ${TEST_DIR}-cron-base | |||
cd ${TEST_DIR}-cron-base | |||
# Update thunder to current test version | |||
update_thunder() { | |||
# Link sites folder from initial installation | |||
mv ${TEST_DIR}/docroot/sites ${TEST_DIR}/docroot/_sites | |||
ln -s ${UPDATE_BASE_PATH}/docroot/sites ${TEST_DIR}/docroot/sites | |||
drush dl thunder --drupal-project-rename="docroot" -y | |||
install_thunder ${TEST_DIR}-cron-base/docroot | |||
fi | |||
cd ${TEST_DIR}/docroot | |||
# Install thunder from repository | |||
if [[ ${INSTALL_METHOD} == "drush_make" ]]; then | |||
# Execute all required updates | |||
drush updatedb -y | |||
} | |||
drush_make_thunder() { | |||
cd ${THUNDER_DIST_DIR} | |||
# Build drupal + thunder from makefile | |||
@@ -31,25 +32,42 @@ if [[ ${INSTALL_METHOD} == "drush_make" ]]; then | |||
rsync -a . ${TEST_DIR}/docroot/profiles/thunder --exclude docroot | |||
drush make -y --no-core ${TEST_DIR}/docroot/profiles/thunder/drupal-org.make ${TEST_DIR}/docroot/profiles/thunder | |||
elif [[ ${INSTALL_METHOD} == "composer" ]]; then | |||
# Build thunder by composer | |||
} | |||
composer_create_thunder() { | |||
cd ${THUNDER_DIST_DIR} | |||
composer create-project burdamagazinorg/thunder-infrastructure ${TEST_DIR} --stability dev --no-interaction --no-install | |||
cd ${TEST_DIR} | |||
composer config repositories.thunder path ${THUNDER_DIST_DIR} | |||
composer require "burdamagazinorg/thunder:*" --no-progress | |||
fi | |||
# Post install part | |||
if [[ ${TRAVIS_EVENT_TYPE} == "cron" ]]; then | |||
# Link sites folder from initial installation | |||
mv ${TEST_DIR}/docroot/sites ${TEST_DIR}/docroot/_sites | |||
ln -s ${TEST_DIR}-cron-base/docroot/sites ${TEST_DIR}/docroot/sites | |||
} | |||
apply_patches() { | |||
cd ${TEST_DIR}/docroot | |||
# Execute all required updates | |||
drush updatedb -y | |||
# apply cookie expire patch for javascript tests | |||
wget https://www.drupal.org/files/issues/test-session-expire-2771547-64.patch | |||
patch -p1 < test-session-expire-2771547-64.patch | |||
# return correct error code from run-tests.php script | |||
wget https://www.drupal.org/files/issues/2776071-25.patch | |||
patch -p1 < 2776071-25.patch | |||
} | |||
# Build current revision of thunder | |||
if [[ ${INSTALL_METHOD} == "drush_make" ]]; then | |||
drush_make_thunder | |||
elif [[ ${INSTALL_METHOD} == "composer" ]]; then | |||
composer_create_thunder | |||
fi | |||
# Install Thunder | |||
if [[ ${TEST_UPDATE} == "true" ]]; then | |||
# Install last drupal org version and update to currently tested version | |||
install_thunder ${UPDATE_BASE_PATH}/docroot | |||
update_thunder | |||
else | |||
install_thunder ${TEST_DIR}/docroot | |||
fi | |||
fi | |||
apply_patches |
@@ -3,5 +3,5 @@ | |||
while true | |||
do | |||
echo "Still testing...." | |||
sleep 60 | |||
sleep 300 | |||
done |
@@ -0,0 +1,7 @@ | |||
#!/usr/bin/env bash | |||
# Run Drupal tests (@group Thunder) | |||
cd ${TEST_DIR}/docroot | |||
# execute Drupal tests | |||
php ${TEST_DIR}/docroot/core/scripts/run-tests.sh --php `which php` --die-on-fail --verbose --color --url http://localhost:8080 Thunder |
@@ -0,0 +1,37 @@ | |||
#!/usr/bin/env bash | |||
## Setup environment | |||
export PHANTOMJS_VERSION=2.1.1 | |||
# export PATH=$PWD/travis_phantomjs/phantomjs-${PHANTOMJS_VERSION}-linux-x86_64/bin:$PATH | |||
# add composer's global bin directory to the path | |||
# see: https://github.com/drush-ops/drush#install---composer | |||
export PATH="$HOME/.composer/vendor/bin:$PATH" | |||
export THUNDER_DIST_DIR=`echo $(pwd)` | |||
export TEST_DIR=`echo ${THUNDER_DIST_DIR}"/../test-dir"` | |||
# depending on install method, the composer vendor dirrectory is in different places | |||
if [[ ${INSTALL_METHOD} == "drush_make" ]]; then | |||
LOCAL_COMPOSER_VENDOR_DIR=${TEST_DIR}/docroot/vendor | |||
elif [[ ${INSTALL_METHOD} == "composer" ]]; then | |||
LOCAL_COMPOSER_VENDOR_DIR=${TEST_DIR}/vendor | |||
fi | |||
export LOCAL_COMPOSER_VENDOR_DIR | |||
# For daily cron runs, current version from Drupal will be installed | |||
# and after that update will be executed and tested | |||
if [[ ${TRAVIS_EVENT_TYPE} == "cron" ]]; then | |||
TEST_UPDATE="true" | |||
else | |||
TEST_UPDATE="" | |||
fi | |||
export TEST_UPDATE; | |||
# base path for update tests | |||
export UPDATE_BASE_PATH=${TEST_DIR}-update-base | |||
# Setup Selenium2 parameters | |||
export DISPLAY=:99.0 | |||
SELENIUM_PATH="$PWD/travis_selenium" | |||
export SELENIUM_PATH |
@@ -0,0 +1,21 @@ | |||
#!/usr/bin/env bash | |||
# Rebuild caches and start servers | |||
cd ${TEST_DIR}/docroot | |||
# require Selenium2 Driver | |||
composer require "behat/mink-selenium2-driver" | |||
# Final cache rebuild, to make sure every code change is respected | |||
drush cr | |||
# Run the webserver | |||
drush runserver --default-server=builtin 8080 &>/dev/null & | |||
# Run phantomjs for javascript tests | |||
#phantomjs --ssl-protocol=any --ignore-ssl-errors=true ${LOCAL_COMPOSER_VENDOR_DIR}/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 false &>/dev/null & | |||
# Run Selenium2 Server | |||
bash -e /etc/init.d/xvfb start | |||
sleep 3 | |||
java -jar "${SELENIUM_PATH}/selenium-server-standalone-2.53.1.jar" > /dev/null 2>&1 & |
@@ -1,10 +0,0 @@ | |||
default: | |||
extensions: | |||
Behat\MinkExtension: | |||
# Adjust "base_url" to Local installed Thunder site. | |||
base_url: 'http://localhost:8080' | |||
Drupal\DrupalExtension: | |||
drupal: | |||
# Adjust "drupal_root" to point on local installation docroot. | |||
# It can be full path or relative path from location of Behat YAML configuration file. | |||
drupal_root: '../..' |
@@ -1,33 +0,0 @@ | |||
default: | |||
suites: | |||
default: | |||
paths: | |||
features: '%paths.base%/features' | |||
contexts: | |||
- Thunder\behat\FeatureContext | |||
- Devinci\DevinciExtension\Context\JavascriptContext: | |||
maximum_wait: 120 | |||
- Drupal\DrupalExtension\Context\DrupalContext | |||
- Drupal\DrupalExtension\Context\DrushContext | |||
- Drupal\DrupalExtension\Context\MessageContext | |||
- Drupal\DrupalExtension\Context\MinkContext | |||
extensions: | |||
Behat\MinkExtension: | |||
files_path: "%paths.base%/files/" | |||
goutte: ~ | |||
selenium2: ~ | |||
Drupal\DrupalExtension: | |||
blackbox: ~ | |||
api_driver: 'drupal' | |||
region_map: | |||
Teaser: '#edit-field-teaser-media-wrapper' | |||
Content: '.region-content' | |||
Paragraphs: '#edit-field-paragraphs-wrapper' | |||
Right Sidebar: '.layout-region-node-secondary' | |||
Footer Bar: '.layout-region-node-footer' | |||
selectors: | |||
message_selector: '.messages' | |||
error_message_selector: '.messages.messages-error' | |||
success_message_selector: '.messages.messages-status' | |||
# Local configuration for Travis CI test run is provided over environment variable: $BEHAT_PARAMS |
@@ -1,33 +0,0 @@ | |||
default: | |||
suites: | |||
default: | |||
paths: | |||
features: '%paths.base%/features' | |||
contexts: | |||
- Thunder\behat\FeatureContext | |||
- Devinci\DevinciExtension\Context\JavascriptContext: | |||
maximum_wait: 120 | |||
- Drupal\DrupalExtension\Context\DrupalContext | |||
- Drupal\DrupalExtension\Context\DrushContext | |||
- Drupal\DrupalExtension\Context\MessageContext | |||
- Drupal\DrupalExtension\Context\MinkContext | |||
extensions: | |||
Behat\MinkExtension: | |||
files_path: "%paths.base%/files/" | |||
goutte: ~ | |||
selenium2: ~ | |||
Drupal\DrupalExtension: | |||
blackbox: ~ | |||
api_driver: 'drupal' | |||
region_map: | |||
Teaser: '#edit-field-teaser-media-wrapper' | |||
Content: '.region-content' | |||
Paragraphs: '#edit-field-paragraphs-wrapper' | |||
Right Sidebar: '.layout-region-node-secondary' | |||
Footer Bar: '.layout-region-node-footer' | |||
selectors: | |||
message_selector: '.messages' | |||
error_message_selector: '.messages.messages-error' | |||
success_message_selector: '.messages.messages-status' | |||
imports: | |||
- behat.local.yml |
@@ -1,38 +0,0 @@ | |||
@api | |||
@javascript | |||
@critical | |||
@article | |||
Feature: Article Teaser | |||
Scenario: As Administrator I make Article with Teaser | |||
Given I am logged in as a user with the "administrator" role | |||
# Create basic channel term. | |||
Given I am on "admin/structure/taxonomy/manage/channel/add" | |||
And I fill in "Behat Teaser channel" for "Name" | |||
And I press "Save" | |||
# Create basic article structure. | |||
Given I am on "/node/add/article" | |||
And I select "Behat Teaser channel" from "Channel" | |||
And I fill in "Behat Teaser Title" for "Title" | |||
And I fill in "This is the page title" for "SEO Title" | |||
And I wait for page to load content | |||
And I expand "URL path settings" option in the "Right Sidebar" region | |||
And I uncheck the box "Generate automatic URL alias" | |||
And I fill in "/behat-teaser-channel/article-teaser" for "URL alias" in the "Right Sidebar" region | |||
# Add 'Teaser'. | |||
And I press "Select entities" in the "Teaser" region | |||
And I wait for page to load content | |||
And I drop the file "thunder-main-1.png" in drop zone and select it | |||
And I wait for page to load content | |||
And I should see an image in the "Teaser" region | |||
And I press "Save and publish" for drop-down button in the "Footer Bar" region | |||
# Check channel page. | |||
Given I am on "/behat-teaser-channel" | |||
And I wait for "Behat Teaser Title" | |||
Then I should see the text "Behat Teaser Title" | |||
Then I should see an image in the "Content" region |
@@ -1,39 +0,0 @@ | |||
@api | |||
@javascript | |||
@critical | |||
@article | |||
Feature: Paragraph Media | |||
Scenario: As Administrator I make Article with Media Paragraph | |||
Given I am logged in as a user with the "administrator" role | |||
# Create basic channel term. | |||
Given I am on "admin/structure/taxonomy/manage/channel/add" | |||
And I fill in "Behat channel" for "Name" | |||
And I press "Save" | |||
# Create basic article structure. | |||
Given I am on "/node/add/article" | |||
And I select "Behat channel" from "Channel" | |||
And I fill in "Behat Test Title" for "Title" | |||
And I fill in "This is the page title" for "SEO Title" | |||
And I wait for page to load content | |||
And I expand "URL path settings" option in the "Right Sidebar" region | |||
And I uncheck the box "Generate automatic URL alias" | |||
And I fill in "/behat/article-media" for "URL alias" in the "Right Sidebar" region | |||
# Add 'Media' paragraph. | |||
And I press "Add Media" for drop-down button in the "Paragraphs" region | |||
And I wait for page to load content | |||
And I press "Select entities" in the "Paragraphs" region | |||
And I wait for page to load content | |||
And I drop the file "thunder-main-1.png" in drop zone and select it | |||
And I wait for page to load content | |||
And I should see an image in the "Paragraphs" region | |||
And I press "Save and publish" for drop-down button in the "Footer Bar" region | |||
# Check node page. | |||
Given I am on "/behat/article-media" | |||
And I wait for "Behat Test Title" | |||
Then I should see an image in the "Content" region |
@@ -1,34 +0,0 @@ | |||
@api | |||
@javascript | |||
@critical | |||
@article | |||
Feature: Paragraph Quote | |||
Scenario: As Administrator I make Article with Quote Paragraph | |||
Given I am logged in as a user with the "administrator" role | |||
# Create basic channel term. | |||
Given I am on "admin/structure/taxonomy/manage/channel/add" | |||
And I fill in "Behat channel" for "Name" | |||
And I press "Save" | |||
# Create basic article structure. | |||
Given I am on "/node/add/article" | |||
And I select "Behat channel" from "Channel" | |||
And I fill in "Behat Test Title" for "Title" | |||
And I fill in "This is the page title" for "SEO Title" | |||
And I wait for page to load content | |||
And I expand "URL path settings" option in the "Right Sidebar" region | |||
And I uncheck the box "Generate automatic URL alias" | |||
And I fill in "/behat/article-quote" for "URL alias" in the "Right Sidebar" region | |||
# Add 'Quote' paragraph. | |||
And I press "Add Quote" for drop-down button in the "Paragraphs" region | |||
And I wait for page to load content | |||
And I fill CKEditor with "Simple Quote Paragraph Test" in the "Paragraphs" region | |||
And I press "Save and publish" for drop-down button in the "Footer Bar" region | |||
# Check node page. | |||
Given I am on "/behat/article-quote" | |||
Then I should see the text "Simple Quote Paragraph Test" |
@@ -1,34 +0,0 @@ | |||
@api | |||
@javascript | |||
@critical | |||
@article | |||
Feature: Paragraph Text | |||
Scenario: As Administrator I make Article with Text Paragraph | |||
Given I am logged in as a user with the "administrator" role | |||
# Create basic channel term. | |||
Given I am on "admin/structure/taxonomy/manage/channel/add" | |||
And I fill in "Behat channel" for "Name" | |||
And I press "Save" | |||
# Create basic article structure. | |||
Given I am on "/node/add/article" | |||
And I select "Behat channel" from "Channel" | |||
And I fill in "Behat Test Title" for "Title" | |||
And I fill in "This is the page title" for "SEO Title" | |||
And I wait for page to load content | |||
And I expand "URL path settings" option in the "Right Sidebar" region | |||
And I uncheck the box "Generate automatic URL alias" | |||
And I fill in "/behat/article-text" for "URL alias" in the "Right Sidebar" region | |||
# Add 'Text' paragraph. | |||
And I press "Add Text" for drop-down button in the "Paragraphs" region | |||
And I wait for page to load content | |||
And I fill CKEditor with "Simple Text Paragraph Test" in the "Paragraphs" region | |||
And I press "Save and publish" for drop-down button in the "Footer Bar" region | |||
# Check node page. | |||
Given I am on "/behat/article-text" | |||
Then I should see the text "Simple Text Paragraph Test" |
@@ -1,393 +0,0 @@ | |||
<?php | |||
namespace Thunder\behat; | |||
use Behat\Behat\Context\SnippetAcceptingContext; | |||
use Drupal\DrupalExtension\Context\RawDrupalContext; | |||
/** | |||
* Class FeatureContext. | |||
* | |||
* Defines application features from the specific context. | |||
*/ | |||
class FeatureContext extends RawDrupalContext implements SnippetAcceptingContext { | |||
/** | |||
* Initializes context. | |||
* | |||
* Every scenario gets its own context instance. | |||
* You can also pass arbitrary arguments to the | |||
* context constructor through behat.yml. | |||
*/ | |||
public function __construct() { | |||
} | |||
/** | |||
* Set screen size before test. | |||
* | |||
* @BeforeScenario | |||
*/ | |||
public function beforeScenario() { | |||
$this->getSession()->getDriver()->resizeWindow(1680, 3150); | |||
} | |||
/** | |||
* Get defined Region. | |||
* | |||
* @param string $region | |||
* Region name defined in YAML file. | |||
* | |||
* @return \Behat\Mink\Element\NodeElement|mixed|null | |||
* Returns element when found, otherwise throws exception. | |||
* | |||
* @throws \Exception | |||
*/ | |||
public function getRegion($region) { | |||
$session = $this->getSession(); | |||
$regionObj = $session->getPage()->find('region', $region); | |||
if (!$regionObj) { | |||
throw new \Exception( | |||
sprintf( | |||
'No region "%s" found on the page %s.', | |||
$region, | |||
$session->getCurrentUrl() | |||
) | |||
); | |||
} | |||
return $regionObj; | |||
} | |||
/** | |||
* Wait for AJAX to finish, so that content on page is updated. | |||
* | |||
* @Given I wait for page to load content | |||
*/ | |||
public function iWaitForPageToUpdate() { | |||
$this->getSession() | |||
->wait(5000, '(typeof(jQuery)=="undefined" || (0 === jQuery.active && 0 === jQuery(\':animated\').length))'); | |||
} | |||
/** | |||
* Checks on drop-down button option in defined region. | |||
* | |||
* @param string $option | |||
* The option text of the button to be pressed. | |||
* @param string $region | |||
* The region in which the button should be pressed. | |||
* | |||
* @throws \Exception | |||
* If region or button within it cannot be found. | |||
* | |||
* @When I press :option for drop-down button in the :region( region) | |||
*/ | |||
public function iClickOnDropDownButtonOptionInRegion($option, $region) { | |||
$regionObj = $this->getRegion($region); | |||
// Click drop-down button. | |||
$toggleButton = $regionObj->find('css', '.dropbutton-toggle'); | |||
if (empty($toggleButton)) { | |||
throw new \Exception( | |||
sprintf( | |||
'The drop-down button was not found in the region "%s" on the page %s', | |||
$region, | |||
$this->getSession()->getCurrentUrl() | |||
) | |||
); | |||
} | |||
$toggleButton->click(); | |||
// Select option. | |||
$dropdownOption = $regionObj->find('named', array('button', $option)); | |||
if (empty($dropdownOption)) { | |||
throw new \Exception( | |||
sprintf( | |||
'The drop-down option "%s" was not found in the region "%s" on the page %s', | |||
$option, | |||
$this->getSession()->getCurrentUrl() | |||
) | |||
); | |||
} | |||
$dropdownOption->click(); | |||
} | |||
/** | |||
* File value in CKEditor from defined region. | |||
* | |||
* @param string $value | |||
* Text that will be written in CKEditor. | |||
* @param string $region | |||
* The region that contains CKEditor. | |||
* | |||
* @throws \Exception | |||
* | |||
* @Then I fill CKEditor with :value in the :region( region) | |||
*/ | |||
public function iFillInCkEditorInRegion($value, $region) { | |||
$regionObj = $this->getRegion($region); | |||
// Find CKEditor. | |||
$ckEditor = $regionObj->find('css', '.form-textarea'); | |||
if (empty($ckEditor)) { | |||
throw new \Exception( | |||
sprintf( | |||
'CKEditor was not found in the region "%s" on the page %s', | |||
$region, | |||
$this->getSession()->getCurrentUrl() | |||
) | |||
); | |||
} | |||
$ckEditorId = $ckEditor->getAttribute('id'); | |||
$this->getSession() | |||
->executeScript("CKEDITOR.instances[\"$ckEditorId\"].setData(\"$value\");"); | |||
} | |||
/** | |||
* Expand/Collapse option in defined region. | |||
* | |||
* @param string $option | |||
* The option text of the button to be pressed. | |||
* @param string $region | |||
* The region in which the menu option should be expanded/collapsed. | |||
* | |||
* @throws \Exception | |||
* If region or menu option within it cannot be found. | |||
* | |||
* @When I expand/collapse :option option in the :region( region) | |||
*/ | |||
public function iToggleOptionInRegion($option, $region) { | |||
$regionObj = $this->getRegion($region); | |||
// Find menu option to expand/collapse. | |||
$xpathQuery = "//details/child::summary[text() = '$option']"; | |||
$menuOption = $regionObj->find('xpath', $xpathQuery); | |||
// Sometimes it's rendered as link tag. | |||
if (empty($menuOption)) { | |||
$xpathQuery = "//details/summary/child::a[text() = '$option']"; | |||
$menuOption = $regionObj->find('xpath', $xpathQuery); | |||
} | |||
if (empty($menuOption)) { | |||
throw new \Exception( | |||
sprintf( | |||
'Unable to find option "%s" in the region "%s" on the page %s', | |||
$option, | |||
$region, | |||
$this->getSession()->getCurrentUrl() | |||
) | |||
); | |||
} | |||
$menuOption->click(); | |||
} | |||
/** | |||
* Upload file to drop zone of Entity selector. | |||
* | |||
* Mimic functionality by exposing file field and uploading over it. | |||
* | |||
* @param string $path | |||
* File name used to be uploaded in Drop files field. | |||
* | |||
* @throws \Exception | |||
* If file field is not found for drop down. | |||
* | |||
* @When I drop the file :path in drop zone and select it | |||
*/ | |||
public function dropFileInSelectEntities($path) { | |||
// Select entity browser iframe. | |||
$iframe = $this->getSession() | |||
->getPage() | |||
->find('css', 'iframe.entity-browser-modal-iframe'); | |||
if (empty($iframe)) { | |||
throw new \Exception( | |||
sprintf( | |||
'Unable to find entity browser iframe on page %s', | |||
$this->getSession()->getCurrentUrl() | |||
) | |||
); | |||
} | |||
$iframeName = $iframe->getAttribute('name'); | |||
// Go into iframe scope from Entity Browsers. | |||
$this->getSession()->switchToIFrame($iframeName); | |||
// Wait that iframe is loaded and jQuery is available. | |||
$this->getSession()->wait(10000, '(typeof jQuery !== "undefined")'); | |||
// Click all tabs until we find upload Tab. | |||
$tabLinks = $this->getSession()->getPage()->findAll('css', '.eb-tabs a'); | |||
if (empty($tabLinks)) { | |||
throw new \Exception( | |||
sprintf( | |||
'Unable to find tabs in entity browser iframe on page %s', | |||
$this->getSession()->getCurrentUrl() | |||
) | |||
); | |||
} | |||
// Click all tabs until input file field for upload is found. | |||
$fileFieldSelector = "input[type='file'].dz-hidden-input"; | |||
foreach ($tabLinks as $tabLink) { | |||
/* @var \Behat\Mink\Element\NodeElement $tabLink */ | |||
$tabLink->click(); | |||
$fileField = $this->getSession() | |||
->getPage() | |||
->find('css', $fileFieldSelector); | |||
if (!empty($fileField)) { | |||
break; | |||
} | |||
} | |||
if (empty($fileField)) { | |||
throw new \Exception( | |||
sprintf( | |||
'The drop-down file field was not found on the page %s', | |||
$this->getSession()->getCurrentUrl() | |||
) | |||
); | |||
} | |||
// Make file field visible and isolate possible problems with "multiple". | |||
$this->getSession() | |||
->executeScript('jQuery("' . $fileFieldSelector . '").show(0).css("visibility","visible").width(200).height(30).removeAttr("multiple");'); | |||
// Generate full path to file. | |||
if ($this->getMinkParameter('files_path')) { | |||
$fullPath = rtrim(realpath($this->getMinkParameter('files_path')), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $path; | |||
if (is_file($fullPath)) { | |||
$path = $fullPath; | |||
} | |||
} | |||
$fileField->attachFile($path); | |||
// Wait for file to upload and use press Select button. | |||
$this->iWaitForPageToUpdate(); | |||
// Wait up to 10 sec that "Submit" button is active. | |||
$this->getSession()->wait( | |||
10000, | |||
'(typeof jQuery === "undefined" || !jQuery(\'input[name="op"]\').is(":disabled"))' | |||
); | |||
// Go back to Page scope. | |||
$this->getSession()->switchToWindow(); | |||
// Click Select button - inside iframe. | |||
$this->getSession() | |||
->executeScript('document.querySelector(\'iframe[name="' . $iframeName . '"]\').contentWindow.jQuery(\'input[name="op"]\').click();'); | |||
// Wait up to 10 sec that main page is loaded with new selected images. | |||
$this->getSession()->wait( | |||
10000, | |||
'(typeof jQuery === "undefined" || jQuery(\'.button.js-form-submit.form-submit[value="Remove"]\').length > 0)' | |||
); | |||
} | |||
/** | |||
* Check that alt text for image exists is displayed. | |||
* | |||
* NOTE: We specify a regex to allow escaped quotes in the alt text. | |||
* | |||
* @param string $text | |||
* Alt text that should be checked. | |||
* @param string $region | |||
* The region that contains image. | |||
* | |||
* @throws \Exception | |||
* | |||
* @Then /^I should see the image alt "(?P<text>(?:[^"]|\\")*)" in the "(?P<region>[^"]*)" region$/ | |||
*/ | |||
public function assertAltRegion($text, $region) { | |||
$regionObj = $this->getRegion($region); | |||
$element = $regionObj->find('css', 'img'); | |||
if (empty($element)) { | |||
throw new \Exception(sprintf('No alt text matching "%s" in the "%s" region on the page %s', $text, $region, $this->getSession() | |||
->getCurrentUrl())); | |||
} | |||
$tmp = $element->getAttribute('alt'); | |||
if ($text == $tmp) { | |||
$result = $text; | |||
} | |||
if (empty($result)) { | |||
throw new \Exception(sprintf('No alt text matching "%s" in the "%s" region on the page %s', $text, $region, $this->getSession() | |||
->getCurrentUrl())); | |||
} | |||
} | |||
/** | |||
* Asserts that an image is present and not broken. | |||
* | |||
* @param string $region | |||
* The region where image should appear. | |||
* | |||
* @throws \Exception | |||
* | |||
* @Then I should see an image in the :region region | |||
*/ | |||
public function assertValidImageRegion($region) { | |||
$regionObj = $this->getRegion($region); | |||
// In order to give browser chance to load image, wait for 10sec. | |||
$elements = $regionObj->waitFor(10, function () use ($regionObj) { | |||
return $regionObj->findAll('css', 'img'); | |||
}); | |||
if (empty($elements)) { | |||
throw new \Exception(sprintf('Image was not found in the "%s" region on the page %s', $region, $this->getSession() | |||
->getCurrentUrl())); | |||
} | |||
$src = $elements[0]->getAttribute('src'); | |||
if (empty($src)) { | |||
$src = $elements[0]->getAttribute('srcset'); | |||
} | |||
if (!empty($src)) { | |||
$params = array('http' => array('method' => 'HEAD')); | |||
$context = stream_context_create($params); | |||
$file_uri = file_create_url(ltrim($src, '/')); | |||
$fp = @fopen($file_uri, 'rb', FALSE, $context); | |||
if (!$fp) { | |||
throw new \Exception(sprintf('Unable to download <img src="%s"> in the "%s" region on the page %s', $src, $region, $this->getSession() | |||
->getCurrentUrl())); | |||
} | |||
$meta = stream_get_meta_data($fp); | |||
fclose($fp); | |||
if ($meta === FALSE) { | |||
throw new \Exception(sprintf('Error reading from <img src="%s"> in the "%s" region on the page %s', $src, $region, $this->getSession() | |||
->getCurrentUrl())); | |||
} | |||
$wrapper_data = $meta['wrapper_data']; | |||
$found = FALSE; | |||
if (is_array($wrapper_data)) { | |||
foreach ($wrapper_data as $header) { | |||
if (substr(strtolower($header), 0, 19) == 'content-type: image') { | |||
$found = TRUE; | |||
} | |||
} | |||
} | |||
if (!$found) { | |||
throw new \Exception(sprintf('Not a valid image <img src="%s"> in the "%s" region on the page %s', $src, $region, $this->getSession() | |||
->getCurrentUrl())); | |||
} | |||
} | |||
else { | |||
throw new \Exception(sprintf('No image had no src="..." attribute in the "%s" region on the page %s', $region, $this->getSession() | |||
->getCurrentUrl())); | |||
} | |||
} | |||
} |
@@ -0,0 +1,331 @@ | |||
/*! | |||
* jQuery Simulate [email protected] - simulate browser mouse and keyboard events | |||
* https://github.com/jquery/jquery-simulate | |||
* | |||
* Copyright jQuery Foundation and other contributors | |||
* Released under the MIT license. | |||
* http://jquery.org/license | |||
* | |||
* Date: @DATE | |||
*/ | |||
;(function( $, undefined ) { | |||
var rkeyEvent = /^key/, | |||
rmouseEvent = /^(?:mouse|contextmenu)|click/; | |||
$.fn.simulate = function( type, options ) { | |||
return this.each(function() { | |||
new $.simulate( this, type, options ); | |||
}); | |||
}; | |||
$.simulate = function( elem, type, options ) { | |||
var method = $.camelCase( "simulate-" + type ); | |||
this.target = elem; | |||
this.options = options; | |||
if ( this[ method ] ) { | |||
this[ method ](); | |||
} else { | |||
this.simulateEvent( elem, type, options ); | |||
} | |||
}; | |||
$.extend( $.simulate, { | |||
keyCode: { | |||
BACKSPACE: 8, | |||
COMMA: 188, | |||
DELETE: 46, | |||
DOWN: 40, | |||
END: 35, | |||
ENTER: 13, | |||
ESCAPE: 27, | |||
HOME: 36, | |||
LEFT: 37, | |||
NUMPAD_ADD: 107, | |||
NUMPAD_DECIMAL: 110, | |||
NUMPAD_DIVIDE: 111, | |||
NUMPAD_ENTER: 108, | |||
NUMPAD_MULTIPLY: 106, | |||
NUMPAD_SUBTRACT: 109, | |||
PAGE_DOWN: 34, | |||
PAGE_UP: 33, | |||
PERIOD: 190, | |||
RIGHT: 39, | |||
SPACE: 32, | |||
TAB: 9, | |||
UP: 38 | |||
}, | |||
buttonCode: { | |||
LEFT: 0, | |||
MIDDLE: 1, | |||
RIGHT: 2 | |||
} | |||
}); | |||
$.extend( $.simulate.prototype, { | |||
simulateEvent: function( elem, type, options ) { | |||
var event = this.createEvent( type, options ); | |||
this.dispatchEvent( elem, type, event, options ); | |||
}, | |||
createEvent: function( type, options ) { | |||
if ( rkeyEvent.test( type ) ) { | |||
return this.keyEvent( type, options ); | |||
} | |||
if ( rmouseEvent.test( type ) ) { | |||
return this.mouseEvent( type, options ); | |||
} | |||
}, | |||
mouseEvent: function( type, options ) { | |||
var event, eventDoc, doc, body; | |||
options = $.extend({ | |||
bubbles: true, | |||
cancelable: (type !== "mousemove"), | |||
view: window, | |||
detail: 0, | |||
screenX: 0, | |||
screenY: 0, | |||
clientX: 1, | |||
clientY: 1, | |||
ctrlKey: false, | |||
altKey: false, | |||
shiftKey: false, | |||
metaKey: false, | |||
button: 0, | |||
relatedTarget: undefined | |||
}, options ); | |||
if ( document.createEvent ) { | |||
event = document.createEvent( "MouseEvents" ); | |||
event.initMouseEvent( type, options.bubbles, options.cancelable, | |||
options.view, options.detail, | |||
options.screenX, options.screenY, options.clientX, options.clientY, | |||
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, | |||
options.button, options.relatedTarget || document.body.parentNode ); | |||
// IE 9+ creates events with pageX and pageY set to 0. | |||
// Trying to modify the properties throws an error, | |||
// so we define getters to return the correct values. | |||
if ( event.pageX === 0 && event.pageY === 0 && Object.defineProperty ) { | |||
eventDoc = event.relatedTarget.ownerDocument || document; | |||
doc = eventDoc.documentElement; | |||
body = eventDoc.body; | |||
Object.defineProperty( event, "pageX", { | |||
get: function() { | |||
return options.clientX + | |||
( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - | |||
( doc && doc.clientLeft || body && body.clientLeft || 0 ); | |||
} | |||
}); | |||
Object.defineProperty( event, "pageY", { | |||
get: function() { | |||
return options.clientY + | |||
( doc && doc.scrollTop || body && body.scrollTop || 0 ) - | |||
( doc && doc.clientTop || body && body.clientTop || 0 ); | |||
} | |||
}); | |||
} | |||
} else if ( document.createEventObject ) { | |||
event = document.createEventObject(); | |||
$.extend( event, options ); | |||
// standards event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ff974877(v=vs.85).aspx | |||
// old IE event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ms533544(v=vs.85).aspx | |||
// so we actually need to map the standard back to oldIE | |||
event.button = { | |||
0: 1, | |||
1: 4, | |||
2: 2 | |||
}[ event.button ] || ( event.button === -1 ? 0 : event.button ); | |||
} | |||
return event; | |||
}, | |||
keyEvent: function( type, options ) { | |||
var event; | |||
options = $.extend({ | |||
bubbles: true, | |||
cancelable: true, | |||
view: window, | |||
ctrlKey: false, | |||
altKey: false, | |||
shiftKey: false, | |||
metaKey: false, | |||
keyCode: 0, | |||
charCode: undefined | |||
}, options ); | |||
if ( document.createEvent ) { | |||
try { | |||
event = document.createEvent( "KeyEvents" ); | |||
event.initKeyEvent( type, options.bubbles, options.cancelable, options.view, | |||
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, | |||
options.keyCode, options.charCode ); | |||
// initKeyEvent throws an exception in WebKit | |||
// see: http://stackoverflow.com/questions/6406784/initkeyevent-keypress-only-works-in-firefox-need-a-cross-browser-solution | |||
// and also https://bugs.webkit.org/show_bug.cgi?id=13368 | |||
// fall back to a generic event until we decide to implement initKeyboardEvent | |||
} catch( err ) { | |||
event = document.createEvent( "Events" ); | |||
event.initEvent( type, options.bubbles, options.cancelable ); | |||
$.extend( event, { | |||
view: options.view, | |||
ctrlKey: options.ctrlKey, | |||
altKey: options.altKey, | |||
shiftKey: options.shiftKey, | |||
metaKey: options.metaKey, | |||
keyCode: options.keyCode, | |||
charCode: options.charCode | |||
}); | |||
} | |||
} else if ( document.createEventObject ) { | |||
event = document.createEventObject(); | |||
$.extend( event, options ); | |||
} | |||
if ( !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ) || (({}).toString.call( window.opera ) === "[object Opera]") ) { | |||
event.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode; | |||
event.charCode = undefined; | |||
} | |||
return event; | |||
}, | |||
dispatchEvent: function( elem, type, event ) { | |||
if ( elem.dispatchEvent ) { | |||
elem.dispatchEvent( event ); | |||
} else if ( type === "click" && elem.click && elem.nodeName.toLowerCase() === "input" ) { | |||
elem.click(); | |||
} else if ( elem.fireEvent ) { | |||
elem.fireEvent( "on" + type, event ); | |||
} | |||
}, | |||
simulateFocus: function() { | |||
var focusinEvent, | |||
triggered = false, | |||
element = $( this.target ); | |||
function trigger() { | |||
triggered = true; | |||
} | |||
element.bind( "focus", trigger ); | |||
element[ 0 ].focus(); | |||
if ( !triggered ) { | |||
focusinEvent = $.Event( "focusin" ); | |||
focusinEvent.preventDefault(); | |||
element.trigger( focusinEvent ); | |||
element.triggerHandler( "focus" ); | |||
} | |||
element.unbind( "focus", trigger ); | |||
}, | |||
simulateBlur: function() { | |||
var focusoutEvent, | |||
triggered = false, | |||
element = $( this.target ); | |||
function trigger() { | |||
triggered = true; | |||
} | |||
element.bind( "blur", trigger ); | |||
element[ 0 ].blur(); | |||
// blur events are async in IE | |||
setTimeout(function() { | |||
// IE won't let the blur occur if the window is inactive | |||
if ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) { | |||
element[ 0 ].ownerDocument.body.focus(); | |||
} | |||
// Firefox won't trigger events if the window is inactive | |||
// IE doesn't trigger events if we had to manually focus the body | |||
if ( !triggered ) { | |||
focusoutEvent = $.Event( "focusout" ); | |||
focusoutEvent.preventDefault(); | |||
element.trigger( focusoutEvent ); | |||
element.triggerHandler( "blur" ); | |||
} | |||
element.unbind( "blur", trigger ); | |||
}, 1 ); | |||
} | |||
}); | |||
/** complex events **/ | |||
function findCenter( elem ) { | |||
var offset, | |||
document = $( elem.ownerDocument ); | |||
elem = $( elem ); | |||
offset = elem.offset(); | |||
return { | |||
x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(), | |||
y: offset.top + elem.outerHeight() / 2 - document.scrollTop() | |||
}; | |||
} | |||
function findCorner( elem ) { | |||
var offset, | |||
document = $( elem.ownerDocument ); | |||
elem = $( elem ); | |||
offset = elem.offset(); | |||
return { | |||
x: offset.left - document.scrollLeft(), | |||
y: offset.top - document.scrollTop() | |||
}; | |||
} | |||
$.extend( $.simulate.prototype, { | |||
simulateDrag: function() { | |||
var i = 0, | |||
target = this.target, | |||
eventDoc = target.ownerDocument, | |||
options = this.options, | |||
center = options.handle === "corner" ? findCorner( target ) : findCenter( target ), | |||
x = Math.floor( center.x ), | |||
y = Math.floor( center.y ), | |||
coord = { clientX: x, clientY: y }, | |||
dx = options.dx || ( options.x !== undefined ? options.x - x : 0 ), | |||
dy = options.dy || ( options.y !== undefined ? options.y - y : 0 ), | |||
moves = options.moves || 3; | |||
this.simulateEvent( target, "mousedown", coord ); | |||
for ( ; i < moves ; i++ ) { | |||
x += dx / moves; | |||
y += dy / moves; | |||
coord = { | |||
clientX: Math.round( x ), | |||
clientY: Math.round( y ) | |||
}; | |||
this.simulateEvent( eventDoc, "mousemove", coord ); | |||
} | |||
if ( $.contains( eventDoc, target ) ) { | |||
this.simulateEvent( target, "mouseup", coord ); | |||
this.simulateEvent( target, "click", coord ); | |||
} else { | |||
this.simulateEvent( eventDoc, "mouseup", coord ); | |||
} | |||
} | |||
}); | |||
})( jQuery ); |
@@ -0,0 +1,6 @@ | |||
name: 'Thunder test' | |||
type: module | |||
description: 'Support module for the thunder tests.' | |||
core: 8.x | |||
package: Testing | |||
version: VERSION |
@@ -0,0 +1,7 @@ | |||
thunder_test_simulate_jquery: | |||
version: VERSION | |||
js: | |||
js/jquery.simulate.js: {} | |||
dependencies: | |||
- core/jquery | |||
- core/drupal |
@@ -0,0 +1,13 @@ | |||
<?php | |||
/** | |||
* @file | |||
* Contains hooks for Thunder JavaScript tests. | |||
*/ | |||
/** | |||
* Add jQuery library to simulate actions. | |||
*/ | |||
function thunder_test_page_attachments(array &$page) { | |||
$page['#attached']['library'][] = 'thunder_test/thunder_test_simulate_jquery'; | |||
} |
@@ -0,0 +1,67 @@ | |||
<?php | |||
namespace Drupal\Tests\thunder\FunctionalJavascript; | |||
/** | |||
* Tests the article creation. | |||
* | |||
* @group Thunder | |||
*/ | |||
class ArticleCreationTest extends ThunderJavascriptTestBase { | |||
use ThunderParagraphsTestTrait; | |||
use ThunderMediaTestTrait; | |||
/** | |||
* Test Creation of Article. | |||
*/ | |||
public function testCreateArticle() { | |||
$this->drupalGet('node/add/article'); | |||
$page = $this->getSession()->getPage(); | |||
$page->selectFieldOption('field_channel', 1); | |||
$page->fillField('title[0][value]', 'Test article'); | |||
$page->fillField('field_seo_title[0][value]', 'Massive gaining seo traffic text'); | |||
$this->selectMedia('field_teaser_media', 'image_browser', ['media:1']); | |||
// Paragraph 1. | |||
$this->addMediaParagraph('field_paragraphs', ['media:5']); | |||
// Paragraph 2. | |||
$this->addTextParagraph('field_paragraphs', 'Awesome text'); | |||
// Paragraph 3. | |||
$this->addGalleryParagraph('field_paragraphs', 'Test gallery', [ | |||
'media:1', | |||
'media:5', | |||
]); | |||
// Paragraph 4. | |||
$this->addTextParagraph('field_paragraphs', 'Awesome quote', 'quote'); | |||
$this->scrollElementInView('#edit-actions'); | |||
$this->createScreenshot($this->getScreenshotFolder() . '/ArticleCreationTest_BeforeSave_' . date('Ymd_His') . '.png'); | |||
$page->pressButton('Save as unpublished'); | |||
$this->createScreenshot($this->getScreenshotFolder() . '/ArticleCreationTest_AfterSave_' . date('Ymd_His') . '.png'); | |||
$this->assertPageTitle('Massive gaining seo traffic text'); | |||
$this->assertSession()->pageTextContains('Test article'); | |||
$this->assertSession()->pageTextContains('Awesome text'); | |||
$this->assertSession()->pageTextContains('Awesome quote'); | |||
$this->assertSession() | |||
->elementExists('xpath', '//div[contains(@class, "field--name-field-paragraphs")]/div[contains(@class, "field__item")][1]//img'); | |||
$this->assertSession() | |||
->elementExists('xpath', '//div[contains(@class, "field--name-field-paragraphs")]/div[contains(@class, "field__item")][3]//img'); | |||
$this->getSession()->stop(); | |||
} | |||
} |
@@ -0,0 +1,63 @@ | |||
<?php | |||
namespace Drupal\Tests\thunder\FunctionalJavascript; | |||
/** | |||
* Tests the Gallery media modification. | |||
* | |||
* @group Thunder | |||
*/ | |||
class MediaGalleryModifyTest extends ThunderJavascriptTestBase { | |||
/** | |||
* Modules to enable. | |||
* | |||
* @var array | |||
*/ | |||
public static $modules = [ | |||
'thunder_test', | |||
]; | |||
/** | |||
* Test order change for Gallery. | |||
* | |||
* @throws \Exception | |||
*/ | |||
public function testOrderChange() { | |||
$this->drupalGet("node/7/edit"); | |||
$page = $this->getSession()->getPage(); | |||
$this->createScreenshot($this->getScreenshotFolder() . '/MediaGalleryModifyTest_BeforeOrderChange_' . date('Ymd_His') . '.png'); | |||
$this->scrollElementInView('[name="field_paragraphs_0_edit"]'); | |||
$page->pressButton('field_paragraphs_0_edit'); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
$editButton = $page->find('css', 'input[data-drupal-selector="edit-field-paragraphs-0-subform-field-media-entities-0-actions-ief-entity-edit"]'); | |||
$editButton->click(); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
$cssSelector = 'div[data-drupal-selector="edit-field-paragraphs-0-subform-field-media-form-inline-entity-form-entities-0-form-field-media-images-current"]'; | |||
$this->scrollElementInView($cssSelector . ' > *:nth-child(2)'); | |||
$this->getSession() | |||
->getDriver() | |||
->executeScript('jQuery(\'' . $cssSelector . ' div[data-entity-id="media:8"]\').simulate( "drag", { moves: 1, dx: 0, dy: 300 });'); | |||
$this->createScreenshot($this->getScreenshotFolder() . '/MediaGalleryModifyTest_AfterOrderChange_' . date('Ymd_His') . '.png'); | |||
$secondElement = $page->find('xpath', '//div[@data-drupal-selector="edit-field-paragraphs-0-subform-field-media-form-inline-entity-form-entities-0-form-field-media-images-current"]/div[2]'); | |||
if (empty($secondElement)) { | |||
throw new \Exception('Second element in Gallery is not found'); | |||
} | |||
$this->assertSame('media:8', $secondElement->getAttribute('data-entity-id')); | |||
$this->getSession()->stop(); | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
<?php | |||
namespace Drupal\Tests\thunder\FunctionalJavascript; | |||
use Drupal\file\Entity\File; | |||
use Drupal\image\Entity\ImageStyle; | |||
use Drupal\media_entity\Entity\Media; | |||
/** | |||
* Tests the Image media modification. | |||
* | |||
* @group Thunder | |||
*/ | |||
class MediaImageModifyTest extends ThunderJavascriptTestBase { | |||
/** | |||
* Test Focal Point change. | |||
*/ | |||
public function testFocalPointChange() { | |||
// Media ID used for testing. | |||
$mediaId = 9; | |||
$page = $this->getSession()->getPage(); | |||
$this->drupalGet("media/$mediaId/edit"); | |||
$this->createScreenshot($this->getScreenshotFolder() . '/MediaImageModifyTest_BeforeFocalPointChange_' . date('Ymd_His') . '.png'); | |||
$this->getSession() | |||
->getDriver() | |||
->executeScript('var e = new jQuery.Event("click"); e.offsetX = 48; e.offsetY = 15; jQuery(".focal-point-wrapper img").trigger(e);'); | |||
$this->createScreenshot($this->getScreenshotFolder() . '/MediaImageModifyTest_AfterFocalPointChange_' . date('Ymd_His') . '.png'); | |||
$page->pressButton('Save and keep publish'); | |||
$media = Media::load($mediaId); | |||
$img = $media->get('field_image')->target_id; | |||
$file = File::load($img); | |||
$path = $file->getFileUri(); | |||
$derivativeUri = ImageStyle::load('teaser')->buildUri($path); | |||
ImageStyle::load('teaser')->createDerivative($path, $derivativeUri); | |||
$image1 = new \Imagick($derivativeUri); | |||
$image2 = new \Imagick(dirname(__FILE__) . '/../../fixtures/reference.jpg'); | |||
$result = $image1->compareImages($image2, \Imagick::METRIC_MEANSQUAREERROR); | |||
$this->assertTrue($result[1] < 0.01, 'Images are identical'); | |||
$image1->clear(); | |||
$image2->clear(); | |||
$this->getSession()->stop(); | |||
} | |||
} |
@@ -0,0 +1,205 @@ | |||
<?php | |||
namespace Drupal\Tests\thunder\FunctionalJavascript; | |||
use Behat\Mink\Driver\Selenium2Driver; | |||
use Drupal\Component\Utility\SafeMarkup; | |||
use Drupal\Core\Session\AccountInterface; | |||
use Drupal\Core\Session\AnonymousUserSession; | |||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase; | |||
use Drupal\Tests\BrowserTestBase; | |||
/** | |||
* Base class for Thunder Javascript functional tests. | |||
* | |||
* @package Drupal\Tests\thunder\FunctionalJavascript | |||
*/ | |||
abstract class ThunderJavascriptTestBase extends JavascriptTestBase { | |||
/** | |||
* The profile to install as a basis for testing. | |||
* | |||
* @var string | |||
*/ | |||
protected $profile = 'thunder'; | |||
/** | |||
* {@inheritdoc} | |||
*/ | |||
protected $minkDefaultDriverClass = Selenium2Driver::class; | |||
/** | |||
* Directory path for saving screenshots. | |||
* | |||
* @var string | |||
*/ | |||
protected $screenshotDirectory = '/tmp/thunder-travis-ci'; | |||
/** | |||
* {@inheritdoc} | |||
*/ | |||
protected function initMink() { | |||
// Set up the template cache used by the PhantomJS mink driver. | |||
$path = $this->tempFilesDirectory . DIRECTORY_SEPARATOR . 'browsertestbase-templatecache'; | |||
$this->minkDefaultDriverArgs = [ | |||
'firefox', | |||
NULL, | |||
'http://127.0.0.1:4444/wd/hub', | |||
]; | |||
if (!file_exists($path)) { | |||
mkdir($path); | |||
} | |||
try { | |||
return BrowserTestBase::initMink(); | |||
} | |||
catch (Exception $e) { | |||
$this->markTestSkipped('An unexpected error occurred while starting Mink: ' . $e->getMessage()); | |||
} | |||
} | |||
/** | |||
* {@inheritdoc} | |||
*/ | |||
protected function drupalLogin(AccountInterface $account) { | |||
if ($this->loggedInUser) { | |||
$this->drupalLogout(); | |||
} | |||
$this->drupalGet('user'); | |||
$this->submitForm(array( | |||
'name' => $account->getUsername(), | |||
'pass' => $account->passRaw, | |||
), t('Log in')); | |||
// @see BrowserTestBase::drupalUserIsLoggedIn() | |||
$account->sessionId = $this->getSession() | |||
->getCookie($this->getSessionName()); | |||
$this->assertTrue($this->drupalUserIsLoggedIn($account), SafeMarkup::format('User %name successfully logged in.', array('name' => $account->getUsername()))); | |||
$this->loggedInUser = $account; | |||
$this->container->get('current_user')->setAccount($account); | |||
} | |||
/** | |||
* {@inheritdoc} | |||
*/ | |||
protected function drupalLogout() { | |||
// Make a request to the logout page, and redirect to the user page, the | |||
// idea being if you were properly logged out you should be seeing a login | |||
// screen. | |||
$assert_session = $this->assertSession(); | |||
$this->drupalGet('user/logout', array('query' => array('destination' => 'user'))); | |||
$assert_session->fieldExists('name'); | |||
$assert_session->fieldExists('pass'); | |||
// @see BrowserTestBase::drupalUserIsLoggedIn() | |||
unset($this->loggedInUser->sessionId); | |||
$this->loggedInUser = FALSE; | |||
$this->container->get('current_user') | |||
->setAccount(new AnonymousUserSession()); | |||
} | |||
/** | |||
* {@inheritdoc} | |||
*/ | |||
protected function setUp() { | |||
parent::setUp(); | |||
$editor = $this->drupalCreateUser(); | |||
$editor->addRole('editor'); | |||
$editor->save(); | |||
$this->drupalLogin($editor); | |||
} | |||
/** | |||
* Opening of Inline Entity Form. | |||
* | |||
* @param string $fieldName | |||
* Field Name. | |||
*/ | |||
protected function openIefComplex($fieldName) { | |||
$page = $this->getSession()->getPage(); | |||
$selector = "div[data-drupal-selector='edit-" . str_replace('_', '-', $fieldName) . "-wrapper'] > div"; | |||
$this->assertSession()->elementExists('css', $selector); | |||
$iefForm = $page->find('css', $selector); | |||
$iefId = $iefForm->getAttribute('id'); | |||
$page->pressButton(str_replace('inline-entity-form', 'ief', $iefId) . '-add'); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
} | |||
/** | |||
* Waits and asserts that a given element is visible. | |||
* | |||
* @param string $selector | |||
* The CSS selector. | |||
* @param int $timeout | |||
* (Optional) Timeout in milliseconds, defaults to 1000. | |||
* @param string $message | |||
* (Optional) Message to pass to assertJsCondition(). | |||
*/ | |||
protected function waitUntilVisible($selector, $timeout = 1000, $message = '') { | |||
$condition = "jQuery('" . $selector . ":visible').length > 0"; | |||
$this->assertJsCondition($condition, $timeout, $message); | |||
} | |||
/** | |||
* Get directory for saving of screenshots. | |||
* | |||
* Directory will be created if it does not already exist. | |||
* | |||
* @return string | |||
* Return directory path to store screenshots. | |||
* | |||
* @throws \Exception | |||
*/ | |||
protected function getScreenshotFolder() { | |||
if (!is_dir($this->screenshotDirectory)) { | |||
if (mkdir($this->screenshotDirectory, 0777, TRUE) === FALSE) { | |||
throw new \Exception('Unable to create directory: ' . $this->screenshotDirectory); | |||
} | |||
} | |||
return realpath($this->screenshotDirectory); | |||
} | |||
/** | |||
* Scroll element with defined css selector in middle of browser view. | |||
* | |||
* @param string $cssSelector | |||
* CSS Selector for element that should be centralized. | |||
*/ | |||
protected function scrollElementInView($cssSelector) { | |||
$this->getSession() | |||
->executeScript('var viewPortHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); var elementTop = jQuery(\'' . $cssSelector . '\').offset().top; window.scroll(0, elementTop-(viewPortHeight/2));'); | |||
} | |||
/** | |||
* Assert page title. | |||
* | |||
* @param string $expectedTitle | |||
* Expected title. | |||
*/ | |||
protected function assertPageTitle($expectedTitle) { | |||
$driver = $this->getSession()->getDriver(); | |||
if ($driver instanceof Selenium2Driver) { | |||
$actualTitle = $driver->getWebDriverSession()->title(); | |||
static::assertTrue($expectedTitle === $actualTitle, 'Title found'); | |||
} | |||
else { | |||
$this->assertSession()->titleEquals($expectedTitle); | |||
} | |||
} | |||
} |
@@ -0,0 +1,79 @@ | |||
<?php | |||
namespace Drupal\Tests\thunder\FunctionalJavascript; | |||
/** | |||
* Trait for handling of Media related test actions. | |||
* | |||
* @package Drupal\Tests\thunder\FunctionalJavascript | |||
*/ | |||
trait ThunderMediaTestTrait { | |||
/** | |||
* Select Medias for field. | |||
* | |||
* @param string $fieldName | |||
* Field name. | |||
* @param string $entityBrowser | |||
* Entity browser identifier. | |||
* @param array $medias | |||
* List of media identifiers. | |||
*/ | |||
public function selectMedia($fieldName, $entityBrowser, $medias) { | |||
/** @var \Behat\Mink\Element\DocumentElement $page */ | |||
$page = $this->getSession()->getPage(); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
$buttonName = $fieldName . '_entity_browser_entity_browser'; | |||
$this->scrollElementInView("[name=\"{$buttonName}\"]"); | |||
$page->pressButton($buttonName); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
$this->getSession() | |||
->switchToIFrame('entity_browser_iframe_' . $entityBrowser); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
foreach ($medias as $media) { | |||
$this->getSession() | |||
->executeScript("jQuery('[name=\"entity_browser_select[$media]\"]').prop('checked', true);"); | |||
} | |||
$page->pressButton('Select entities'); | |||
if ($entityBrowser == 'multiple_image_browser') { | |||
$page->pressButton('Use selected'); | |||
} | |||
$this->getSession()->switchToIFrame(); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
$this->waitUntilVisible('div[data-drupal-selector="edit-' . str_replace('_', '-', $fieldName) . '-wrapper"] img'); | |||
} | |||
/** | |||
* Create gallery with selected medias. | |||
* | |||
* @param string $name | |||
* Name of gallery. | |||
* @param string $fieldName | |||
* Field name. | |||
* @param string $medias | |||
* List of media identifiers. | |||
*/ | |||
public function createGallery($name, $fieldName, $medias) { | |||
$page = $this->getSession()->getPage(); | |||
$selector = "input[data-drupal-selector='edit-" . str_replace('_', '-', $fieldName) . "-form-inline-entity-form-name-0-value']"; | |||
$this->assertSession()->elementExists('css', $selector); | |||
$nameField = $page->find('css', $selector); | |||
$nameField->setValue($name); | |||
$this->selectMedia("${fieldName}_form_inline_entity_form_field_media_images", 'multiple_image_browser', $medias); | |||
} | |||
} |
@@ -0,0 +1,117 @@ | |||
<?php | |||
namespace Drupal\Tests\thunder\FunctionalJavascript; | |||
/** | |||
* Trait for handling of Paragraph related test actions. | |||
* | |||
* @package Drupal\Tests\thunder\FunctionalJavascript | |||
*/ | |||
trait ThunderParagraphsTestTrait { | |||
/** | |||
* Counter used to count number of added Paragraphs. | |||
* | |||
* @var int $paragraphCount | |||
*/ | |||
protected $paragraphCount; | |||
/** | |||
* Add paragraph for field with defined paragraph type. | |||
* | |||
* @param string $fieldName | |||
* Field name. | |||
* @param string $type | |||
* Type of the paragraph. | |||
*/ | |||
public function addParagraph($fieldName, $type) { | |||
$page = $this->getSession()->getPage(); | |||
$toggleButtonSelector = '#edit-' . str_replace('_', '-', $fieldName) . '-wrapper .dropbutton-toggle button'; | |||
$toggleButton = $page->find('css', $toggleButtonSelector); | |||
$this->scrollElementInView($toggleButtonSelector); | |||
$toggleButton->click(); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
$addMoreButtonName = "${fieldName}_${type}_add_more"; | |||
$this->scrollElementInView("[name=\"$addMoreButtonName\"]"); | |||
$page->pressButton($addMoreButtonName); | |||
$this->assertSession()->assertWaitOnAjaxRequest(); | |||
if (!isset($this->paragraphCount[$fieldName])) { | |||
$this->paragraphCount[$fieldName] = 0; | |||
} | |||
else { | |||
$this->paragraphCount[$fieldName]++; | |||
} | |||
$this->waitUntilVisible('div[data-drupal-selector="edit-' . str_replace('_', '-', $fieldName) . '-' . $this->paragraphCount[$fieldName] . '-subform"]'); | |||
} | |||
/** | |||
* Add Media paragraph. | |||
* | |||
* @param string $fieldName | |||
* Field name. | |||
* @param array $media | |||
* List of media identifiers. | |||
*/ | |||
public function addMediaParagraph($fieldName, $media) { | |||
$this->addParagraph($fieldName, 'media'); | |||
$count = $this->paragraphCount[$fieldName]; | |||
$this->selectMedia("${fieldName}_${count}_subform_field_media", 'media_browser', $media); | |||
} | |||
/** | |||
* Add Gallery paragraph. | |||
* | |||
* @param string $fieldName | |||
* Field name. | |||
* @param string $name | |||
* Name of the gallery. | |||
* @param array $media | |||
* List of media identifiers. | |||
*/ | |||
public function addGalleryParagraph($fieldName, $name, $media) { | |||
$this->addParagraph($fieldName, 'gallery'); | |||
$count = $this->paragraphCount[$fieldName]; | |||
$this->openIefComplex("${fieldName}_${count}_subform_field_media"); | |||
$this->createGallery($name, "${fieldName}_${count}_subform_field_media", $media); | |||
} | |||
/** | |||
* Adding text type paragraphs. | |||
* | |||
* @param string $fieldName | |||
* Field name. | |||
* @param string $text | |||
* Text for paragraph. | |||
* @param string $type | |||
* Type of text paragraph. | |||
*/ | |||
public function addTextParagraph($fieldName, $text, $type = 'text') { | |||
$this->addParagraph($fieldName, $type); | |||
$count = $this->paragraphCount[$fieldName]; | |||
$page = $this->getSession()->getPage(); | |||
$ckEditor = $page->find('css', "textarea[name='${fieldName}[${count}][subform][field_text][0][value]']"); | |||
$ckEditorId = $ckEditor->getAttribute('id'); | |||
$this->getSession() | |||
->getDriver() | |||
->executeScript("CKEDITOR.instances[\"$ckEditorId\"].setData(\"$text\");"); | |||
} | |||
} |