miniflux-legacy/tests/integration/minifluxTestCase.php

673 lines
22 KiB
PHP
Raw Permalink Normal View History

2014-12-04 18:20:15 +01:00
<?php
use PHPUnit_Extensions_Selenium2TestCase_Keys as Keys;
2014-12-04 18:20:15 +01:00
abstract class minifluxTestCase extends PHPUnit_Extensions_Selenium2TestCase
{
2016-04-18 01:44:45 +02:00
protected $basePageHeading = null;
protected $expectedPageUrl = null;
protected $expectedDataSet = null;
protected $expectedCounterPage = null;
2014-12-04 18:20:15 +01:00
protected $expectedCounterUnread = '';
2016-04-18 01:44:45 +02:00
protected $ignorePageTitle = false;
2016-04-18 01:44:45 +02:00
protected static $databaseConnection = null;
protected static $databaseTester = null;
2014-12-04 18:20:15 +01:00
private $waitTimeout = 5000;
2014-12-04 18:20:15 +01:00
public static function browsers()
{
return json_decode(PHPUNIT_TESTSUITE_EXTENSION_SELENIUM_BROWSERS, true);
}
2014-12-04 18:20:15 +01:00
protected function setUp()
{
parent::setUp();
// trigger database fixtures onSetUp routines
$dataset = $this->getDataSet('fixture_feed1', 'fixture_feed2');
$this->getDatabaseTester($dataset)->onSetUp();
2014-12-04 18:20:15 +01:00
// Set the base URL for the tests.
$this->setBrowserUrl(PHPUNIT_TESTSUITE_EXTENSION_SELENIUM_BASEURL);
}
public function doLoginIfRequired($url)
2014-12-04 18:20:15 +01:00
{
// (re)load the requested page
$this->url($url);
// check if login is need and login
$elements = $this->elements($this->using('css selector')->value('body#login-page'));
if (count($elements) === 1) {
$this->byCssSelector("input[value='".DB_FILENAME."']")->click();
2014-12-04 18:20:15 +01:00
$this->byId('form-username')->click();
$this->keys('admin');
$this->byId('form-password')->click();
$this->keys('admin');
$this->byTag('form')->submit();
2014-12-04 18:20:15 +01:00
$this->url($url);
}
}
public static function tearDownAfterClass()
{
2016-04-18 01:44:45 +02:00
static::$databaseConnection = null;
static::$databaseTester = null;
2014-12-04 18:20:15 +01:00
}
2014-12-04 18:20:15 +01:00
protected function assertPostConditions()
{
// counter exists on every page
$this->assertTrue($this->waitForElementByIdText('page-counter', $this->expectedCounterPage), 'page-counter differ from expected');
$this->assertTrue($this->waitForElementByIdText('nav-counter', $this->expectedCounterUnread), 'unread counter differ from expectation');
2014-12-04 18:20:15 +01:00
// url has not been changed (its likely that everything was done via javascript then)
$this->assertEquals($this->expectedPageUrl, $this->url(), 'URL has been changed.');
// some tests switch to a page where no counter exists and the expected
// pagetitle doesn't match to definition.
2016-04-18 01:44:45 +02:00
if ($this->ignorePageTitle === false) {
2014-12-04 18:20:15 +01:00
//remove LEFT-TO-RIGHT MARK char from string as the webdriver does it when using text() on the page <h[1|2|3]>
$pagetitle = preg_replace('/\x{200E}/u', '', $this->title());
$this->assertEquals($this->getExpectedPageTitle(), $pagetitle, 'page title differ from expectation');
}
2014-12-04 18:20:15 +01:00
// assert that the current database matches the expected database
$expectedDataSetFiltered = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($this->expectedDataSet);
$expectedDataSetFiltered->addIncludeTables(array('items'));
$expectedDataSetFiltered->setExcludeColumnsForTable('items', array('updated'));
2014-12-04 18:20:15 +01:00
// TODO: changes row order, why?
//$actualDataSet = $this->getConnection()->createDataSet();
2014-12-04 18:20:15 +01:00
$actualDataSet = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection());
$actualDataSet->addTable('items', 'SELECT * FROM items');
2014-12-04 18:20:15 +01:00
$actualDataSetFiltered = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($actualDataSet);
$actualDataSetFiltered->setExcludeColumnsForTable('items', array('updated'));
PHPUnit_Extensions_Database_TestCase::assertDataSetsEqual($expectedDataSetFiltered, $actualDataSetFiltered, 'Unexpected changes in database');
}
protected function getDataSet()
2014-12-04 18:20:15 +01:00
{
$compositeDs = new PHPUnit_Extensions_Database_DataSet_CompositeDataSet();
$dataSetFiles = func_get_args();
2014-12-04 18:20:15 +01:00
foreach ($dataSetFiles as $dataSetFile) {
$ds = new PHPUnit_Extensions_Database_DataSet_XmlDataSet(dirname(__FILE__).DIRECTORY_SEPARATOR.'datasets'.DIRECTORY_SEPARATOR.$dataSetFile.'.xml');
$compositeDs->addDataSet($ds);
2014-12-04 18:20:15 +01:00
}
2014-12-04 18:20:15 +01:00
return $compositeDs;
}
2014-12-04 18:20:15 +01:00
protected function getConnection()
{
2016-05-03 10:45:07 +02:00
if (static::$databaseConnection === null) {
2014-12-04 18:20:15 +01:00
// let Miniflux setup the database
require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'common.php';
2014-12-04 18:20:15 +01:00
if (!ENABLE_MULTIPLE_DB) {
throw new Exception('Enable multiple databases support to run the tests!');
}
$picoDb = new PicoDb\Database(array(
'driver' => 'sqlite',
'filename' => \Model\Database\get_path(),
));
$picoDb->schema()->check(Schema\VERSION);
// make the database world writeable, maybe the current
// user != webserver user
chmod(\Model\Database\get_path(), 0666);
2014-12-04 18:20:15 +01:00
// get pdo object
$pdo = $picoDb->getConnection();
2014-12-04 18:20:15 +01:00
// disable fsync! its awefull slow without transactions and I found
// no way to use setDataSet function with transactions
$pdo->exec("pragma synchronous = off;");
static::$databaseConnection = new PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection($pdo, 'sqlite');
}
2014-12-04 18:20:15 +01:00
return static::$databaseConnection;
}
protected function getDatabaseTester($dataset)
2014-12-04 18:20:15 +01:00
{
2016-05-03 10:45:07 +02:00
if (static::$databaseTester === null) {
$rdataset = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($dataset);
$rdataset->addSubStrReplacement('##TIMESTAMP##', substr((string)(time()-100), 0, -2));
2014-12-04 18:20:15 +01:00
// article/feed import on database->onSetUp();
$tester = new PHPUnit_Extensions_Database_DefaultTester($this->getConnection());
2014-12-04 18:20:15 +01:00
$tester->setSetUpOperation(PHPUnit_Extensions_Database_Operation_Factory::CLEAN_INSERT());
$tester->setDataSet($rdataset);
2014-12-04 18:20:15 +01:00
static::$databaseTester = $tester;
}
2014-12-04 18:20:15 +01:00
return static::$databaseTester;
}
// public to be accessible within an closure
public function isElementVisible($element)
{
$displaySize = $element->size();
return ($element->displayed() && $displaySize['height']>0 && $displaySize['width']>0);
}
// public to be accessible within an closure
public function isElementInvisible($element)
{
$displaySize = $element->size();
2016-04-18 01:44:45 +02:00
return ($element->displayed() === false || $displaySize['height']=0 || $displaySize['width']=0);
}
2014-12-04 18:20:15 +01:00
private function waitForElementVisibility($element, $visible)
{
// return false in case of timeout
try {
// Workaround for PHP < 5.4
$CI = $this;
2016-04-18 01:44:45 +02:00
$value = $this->waitUntil(function () use ($CI, $element,$visible) {
2014-12-04 18:20:15 +01:00
// a "No such Element" or "Stale Element Reference" exception is
// valid if an object should disappear
try {
if (($visible && $CI->isElementVisible($element))
|| (! $visible && $CI->isElementInvisible($element))) {
2016-04-18 01:44:45 +02:00
return true;
2014-12-04 18:20:15 +01:00
}
2016-04-18 01:44:45 +02:00
} catch (PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
2014-12-04 18:20:15 +01:00
$noSuchElement = ($e->getCode() === PHPUnit_Extensions_Selenium2TestCase_WebDriverException::NoSuchElement
|| $e->getCode() === PHPUnit_Extensions_Selenium2TestCase_WebDriverException::StaleElementReference);
2016-04-18 01:44:45 +02:00
if (($visible === false) && ($noSuchElement)) {
return true;
2014-12-04 18:20:15 +01:00
} else {
throw $e;
}
}
}, $this->waitTimeout);
2016-04-18 01:44:45 +02:00
} catch (PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
2014-12-04 18:20:15 +01:00
if ($e->getCode() === PHPUnit_Extensions_Selenium2TestCase_WebDriverException::Timeout) {
2016-04-18 01:44:45 +02:00
return false;
2014-12-04 18:20:15 +01:00
} else {
throw $e;
}
}
return $value;
}
private function waitForElementCountByCssSelector($cssSelector, $elementCount)
{
// return false in case of timeout
try {
// Workaround for PHP < 5.4
$CI = $this;
2016-04-18 01:44:45 +02:00
$value = $this->waitUntil(function () use ($cssSelector, $elementCount,$CI) {
2014-12-04 18:20:15 +01:00
$elements = $CI->elements($CI->using('css selector')->value($cssSelector));
2014-12-04 18:20:15 +01:00
if (count($elements) === $elementCount) {
2016-04-18 01:44:45 +02:00
return true;
2014-12-04 18:20:15 +01:00
}
}, $this->waitTimeout);
2016-04-18 01:44:45 +02:00
} catch (PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
2014-12-04 18:20:15 +01:00
if ($e->getCode() === PHPUnit_Extensions_Selenium2TestCase_WebDriverException::Timeout) {
2016-04-18 01:44:45 +02:00
return false;
2014-12-04 18:20:15 +01:00
} else {
throw $e;
}
}
return $value;
}
2016-04-18 01:44:45 +02:00
private function waitForElementByIdText($id, $text)
{
// return false in case of timeout
try {
// Workaround for PHP < 5.4
$CI = $this;
2016-04-18 01:44:45 +02:00
$value = $this->waitUntil(function () use ($CI, $id,$text) {
try {
$elements = $this->elements($this->using('id')->value($id));
if (count($elements) === 1 && $elements[0]->text() == $text
2016-05-03 10:45:07 +02:00
|| count($elements) === 0 && $text === null) {
2016-04-18 01:44:45 +02:00
return true;
}
2016-04-18 01:44:45 +02:00
} catch (PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
$noSuchElement = ($e->getCode() === PHPUnit_Extensions_Selenium2TestCase_WebDriverException::NoSuchElement
|| $e->getCode() === PHPUnit_Extensions_Selenium2TestCase_WebDriverException::StaleElementReference);
// everything else than "No such Element" or
// "Stale Element Reference" is unexpected
if (! $noSuchElement) {
throw $e;
}
}
}, $this->waitTimeout);
2016-04-18 01:44:45 +02:00
} catch (PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
if ($e->getCode() === PHPUnit_Extensions_Selenium2TestCase_WebDriverException::Timeout) {
2016-04-18 01:44:45 +02:00
return false;
} else {
throw $e;
}
}
return $value;
}
2016-04-18 01:44:45 +02:00
private function waitForElementAttributeHasValue($element, $attribute, $attributeValue, $invertMatch = false)
2014-12-04 18:20:15 +01:00
{
// return false in case of timeout
try {
2016-04-18 01:44:45 +02:00
$value = $this->waitUntil(function () use ($element, $attribute, $attributeValue,$invertMatch) {
2014-12-04 18:20:15 +01:00
$attributeHasValue = ($element->attribute($attribute) === $attributeValue);
2014-12-04 18:20:15 +01:00
if (($attributeHasValue && !$invertMatch) || (!$attributeHasValue && $invertMatch)) {
2016-04-18 01:44:45 +02:00
return true;
2014-12-04 18:20:15 +01:00
}
}, $this->waitTimeout);
2016-04-18 01:44:45 +02:00
} catch (PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
2014-12-04 18:20:15 +01:00
if ($e->getCode() === PHPUnit_Extensions_Selenium2TestCase_WebDriverException::Timeout) {
2016-04-18 01:44:45 +02:00
return false;
2014-12-04 18:20:15 +01:00
} else {
throw $e;
}
}
2014-12-04 18:20:15 +01:00
return $value;
}
2014-12-04 18:20:15 +01:00
private function waitForIconMarkRead($article, $visible)
{
$icon = $article->elements($article->using('css selector')->value('span.read-icon'));
$value = $this->waitForElementVisibility($icon[0], $visible);
return $value;
}
private function waitForIconBookmark($article, $visible)
{
$icon = $article->elements($article->using('css selector')->value('span.bookmark-icon'));
$value = $this->waitForElementVisibility($icon[0], $visible);
return $value;
}
public function getBasePageHeading()
{
/*
* WORKAROUND: Its not possible to get an elements text content without
* the text of its childs. Thats why we have to differ between
* pageheadings with counter and without counter.
*/
2014-12-04 18:20:15 +01:00
// text of its childs
$pageHeading = $this->byCssSelector('div.page-header > h2:first-child')->text();
2014-12-04 18:20:15 +01:00
// Some PageHeadings have a counter included
$innerHeadingElements = $this->elements($this->using('css selector')->value('div.page-header > h2:first-child *'));
2016-04-18 01:44:45 +02:00
if (count($innerHeadingElements) > 0) {
2014-12-04 18:20:15 +01:00
$innerHeading = $innerHeadingElements[0]->text();
$pageHeading = substr($pageHeading, 0, (strlen($innerHeading) * -1));
}
return $pageHeading;
}
public function getURLPageUnread()
{
return PHPUNIT_TESTSUITE_EXTENSION_SELENIUM_BASEURL.'?action=unread';
}
public function getURLPageBookmarks()
{
return PHPUNIT_TESTSUITE_EXTENSION_SELENIUM_BASEURL.'?action=bookmarks';
}
public function getURLPageHistory()
{
return PHPUNIT_TESTSUITE_EXTENSION_SELENIUM_BASEURL.'?action=history';
}
public function getURLPageFirstFeed()
{
return PHPUNIT_TESTSUITE_EXTENSION_SELENIUM_BASEURL.'?action=feed-items&feed_id=1';
}
2014-12-04 18:20:15 +01:00
public function getURLPagePreferences()
{
return PHPUNIT_TESTSUITE_EXTENSION_SELENIUM_BASEURL.'?action=config';
}
public function getURLPageSubscriptions()
{
return PHPUNIT_TESTSUITE_EXTENSION_SELENIUM_BASEURL.'?action=feeds';
}
2014-12-04 18:20:15 +01:00
public function getShortcutNextItemA()
{
return 'n';
}
public function getShortcutNextItemB()
{
return 'j';
}
public function getShortcutNextItemC()
{
return PHPUnit_Extensions_Selenium2TestCase_Keys::RIGHT;
}
2014-12-04 18:20:15 +01:00
public function getShortcutPreviousItemA()
{
return 'p';
}
public function getShortcutPreviousItemB()
{
return 'k';
}
public function getShortcutPreviousItemC()
{
return PHPUnit_Extensions_Selenium2TestCase_Keys::LEFT;
}
2014-12-04 18:20:15 +01:00
public function getShortcutToogleReadStatus()
{
return 'm';
}
2014-12-04 18:20:15 +01:00
public function getShortcutToogleBookmarkStatus()
{
return 'f';
}
2014-12-04 18:20:15 +01:00
public function getShortcutGoToUnread()
{
return 'gu';
}
2014-12-04 18:20:15 +01:00
public function getArticles()
{
$cssSelector = 'article';
$articles = $this->elements($this->using('css selector')->value($cssSelector));
return $articles;
}
2014-12-04 18:20:15 +01:00
public function getArticlesUnread()
{
$cssSelector = 'article[data-item-status="unread"]';
2014-12-04 18:20:15 +01:00
$articles = $this->elements($this->using('css selector')->value($cssSelector));
return $articles;
}
2014-12-04 18:20:15 +01:00
public function getArticlesRead()
{
$cssSelector = 'article[data-item-status="read"]';
2014-12-04 18:20:15 +01:00
$articles = $this->elements($this->using('css selector')->value($cssSelector));
return $articles;
}
2014-12-04 18:20:15 +01:00
public function getArticlesNotBookmarked()
{
$cssSelector = 'article[data-item-bookmark="0"]';
2014-12-04 18:20:15 +01:00
$articles = $this->elements($this->using('css selector')->value($cssSelector));
return $articles;
}
2014-12-04 18:20:15 +01:00
public function getArticlesNotFromFeedOne()
{
$cssSelector = 'article:not(.feed-1)';
2014-12-04 18:20:15 +01:00
$articles = $this->elements($this->using('css selector')->value($cssSelector));
return $articles;
}
public function getFeedFailed()
{
$cssSelector = 'article[data-feed-id="4"]';
$feed = $this->element($this->using('css selector')->value($cssSelector));
return $feed;
}
public function getFeedDisabled()
{
$cssSelector = 'article[data-feed-id="2"]';
$feed = $this->element($this->using('css selector')->value($cssSelector));
return $feed;
}
public function getFeedErrorMessages()
{
$cssSelector = 'article .feed-parsing-error';
if (func_num_args() === 0) {
$feed = $this;
2016-04-18 01:44:45 +02:00
} else {
$feed = func_get_arg(0);
}
$feeds = $feed->elements($this->using('css selector')->value($cssSelector));
// Workaround for PHP < 5.4
$CI = $this;
2016-04-18 01:44:45 +02:00
return array_filter($feeds, function ($feed) use ($CI) {
return $CI->isElementVisible($feed);
});
}
2014-12-04 18:20:15 +01:00
public function getArticleUnreadNotBookmarked()
{
$cssSelector = 'article[data-item-id="7c6afaa5"]';
$article = $this->element($this->using('css selector')->value($cssSelector));
return $article;
}
2014-12-04 18:20:15 +01:00
public function getArticleReadNotBookmarked()
{
$cssSelector = 'article[data-item-id="9b20eb66"]';
2014-12-04 18:20:15 +01:00
$article = $this->element($this->using('css selector')->value($cssSelector));
return $article;
}
public function getArticleUnreadBookmarked()
{
$cssSelector = 'article[data-item-id="7cb2809d"]';
2014-12-04 18:20:15 +01:00
$article = $this->element($this->using('css selector')->value($cssSelector));
return $article;
2016-04-18 01:44:45 +02:00
}
2014-12-04 18:20:15 +01:00
public function getArticleReadBookmarked()
{
$cssSelector = 'article[data-item-id="9fa78b54"]';
$articles = $this->element($this->using('css selector')->value($cssSelector));
return $articles;
}
public function getLinkReadStatusToogle($article)
{
$link = $article->element($article->using('css selector')->value('a.mark'));
return $link;
}
public function getLinkBookmarkStatusToogle($article)
{
$link = $article->element($article->using('css selector')->value('a.bookmark'));
return $link;
}
public function getLinkRemove($article)
{
$link = $article->element($article->using('css selector')->value('a.delete'));
return $link;
}
2014-12-04 18:20:15 +01:00
public function getLinkFeedMarkReadHeader()
{
$link = $this->element($this->using('css selector')->value('div.page-header a[data-action="mark-feed-read"]'));
return $link;
}
2014-12-04 18:20:15 +01:00
public function getLinkFeedMarkReadBottom()
{
$link = $this->element($this->using('css selector')->value('div#bottom-menu a[data-action="mark-feed-read"]'));
return $link;
}
2014-12-04 18:20:15 +01:00
public function getLinkMarkAllReadHeader()
{
$link = $this->element($this->using('css selector')->value('div.page-header a[href|="?action=mark-all-read"]'));
2014-12-04 18:20:15 +01:00
return $link;
}
2014-12-04 18:20:15 +01:00
public function getLinkMarkAllReadBottom()
{
$link = $this->element($this->using('css selector')->value('div#bottom-menu a[href|="?action=mark-all-read"]'));
2014-12-04 18:20:15 +01:00
return $link;
}
2014-12-04 18:20:15 +01:00
public function getLinkFlushHistory()
{
$link = $this->element($this->using('css selector')->value('div.page-header a[href="?action=confirm-flush-history"]'));
return $link;
}
public function getLinkDestructive()
{
$link = $this->element($this->using('css selector')->value('a.btn-red'));
return $link;
}
public function getAlertBox()
{
$cssSelector = 'p.alert';
$alertBox = $this->elements($this->using('css selector')->value($cssSelector));
return $alertBox;
}
2014-12-04 18:20:15 +01:00
public function waitForArticleIsCurrentArticle($article)
{
$isCurrent = $this->waitForElementAttributeHasValue($article, 'id', 'current-item');
return $isCurrent;
}
2014-12-04 18:20:15 +01:00
public function waitForArticleIsNotCurrentArticle($article)
{
2016-04-18 01:44:45 +02:00
$isCurrent = $this->waitForElementAttributeHasValue($article, 'id', 'current-item', true);
2014-12-04 18:20:15 +01:00
return $isCurrent;
}
2014-12-04 18:20:15 +01:00
public function waitForIconMarkReadVisible($article)
{
2016-04-18 01:44:45 +02:00
$visible = $this->waitForIconMarkRead($article, true);
2014-12-04 18:20:15 +01:00
return $visible;
}
public function waitForIconMarkReadInvisible($article)
{
2016-04-18 01:44:45 +02:00
$invisible = $this->waitForIconMarkRead($article, false);
2014-12-04 18:20:15 +01:00
return $invisible;
}
public function waitForIconBookmarkVisible($article)
{
2016-04-18 01:44:45 +02:00
$visible = $this->waitForIconBookmark($article, true);
2014-12-04 18:20:15 +01:00
return $visible;
}
public function waitForIconBookmarkInvisible($article)
{
2016-04-18 01:44:45 +02:00
$invisible = $this->waitForIconBookmark($article, false);
2014-12-04 18:20:15 +01:00
return $invisible;
}
public function waitForArticleInvisible($article)
{
2016-04-18 01:44:45 +02:00
$invisible = $this->waitForElementVisibility($article, false);
2014-12-04 18:20:15 +01:00
return $invisible;
}
2014-12-04 18:20:15 +01:00
public function waitForArticlesMarkRead()
{
$cssSelector = 'article[data-item-status="unread"]';
2014-12-04 18:20:15 +01:00
$read = $this->waitForElementCountByCssSelector($cssSelector, 0);
return $read;
}
2014-12-04 18:20:15 +01:00
public function waitForAlert()
{
$cssSelector = 'p.alert';
2014-12-04 18:20:15 +01:00
$visible = $this->waitForElementCountByCssSelector($cssSelector, 1);
return $visible;
}
2014-12-04 18:20:15 +01:00
public function sendKeysAndWaitForPageLoaded($keys)
{
$this->keys($keys);
2014-12-04 18:20:15 +01:00
// Workaround for PHP < 5.4
$CI = $this;
2016-04-18 01:44:45 +02:00
$this->waitUntil(function () use ($CI) {
2014-12-04 18:20:15 +01:00
$readyState = $CI->execute(array(
'script' => 'return document.readyState;',
'args' => array()
));
if ($readyState === 'complete') {
2016-04-18 01:44:45 +02:00
return true;
2014-12-04 18:20:15 +01:00
}
}, $this->waitTimeout);
2014-12-04 18:20:15 +01:00
}
2014-12-04 18:20:15 +01:00
public function setArticleAsCurrentArticle($article)
{
$script = 'document.getElementById("' .$article->attribute('id') .'").id = "current-item";'
. 'return true';
$this->execute(array(
'script' => $script,
'args' => array()
));
2014-12-04 18:20:15 +01:00
$result = $this->waitForArticleIsCurrentArticle($article);
2016-04-18 01:44:45 +02:00
if ($result === false) {
2014-12-04 18:20:15 +01:00
throw new Exception('the article could not be set as current article.');
}
}
}