diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php
index 5fd0e3ce34db244bc6850f91de8b461491684903..3ff6907baf148be6759fd149b931402cf5ed5ecc 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php
@@ -26,6 +26,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling;
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\DataSet;
+use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
+use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\ResponseContent;
 
 /**
  * Functional test for the DataHandler
@@ -265,4 +267,141 @@ abstract class AbstractDataHandlerActionTestCase extends \TYPO3\CMS\Core\Tests\F
 		return $differentFields;
 	}
 
+	/**
+	 * @param ResponseContent $responseContent
+	 * @param string $structureRecordIdentifier
+	 * @param string $structureFieldName
+	 * @param string $tableName
+	 * @param string $fieldName
+	 * @param string|array $values
+	 */
+	protected function assertResponseContentStructureHasRecords(ResponseContent $responseContent, $structureRecordIdentifier, $structureFieldName, $tableName, $fieldName, $values) {
+		$nonMatchingVariants = array();
+
+		foreach ($responseContent->findStructures($structureRecordIdentifier, $structureFieldName) as $path => $structure) {
+			$nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($structure, $tableName, $fieldName, $values);
+
+			if (empty($nonMatchingValues)) {
+				// Increase assertion counter
+				$this->assertEmpty($nonMatchingValues);
+				return;
+			}
+
+			$nonMatchingVariants[$path] = $nonMatchingValues;
+		}
+
+		$nonMatchingMessage = '';
+		foreach ($nonMatchingVariants as $path => $nonMatchingValues) {
+			$nonMatchingMessage .= '* ' . $path . ': ' . implode(', ', $nonMatchingValues);
+		}
+
+		$this->fail('Could not assert all values for "' . $tableName . '.' . $fieldName . '"' . LF . $nonMatchingMessage);
+	}
+
+	/**
+	 * @param ResponseContent $responseContent
+	 * @param string $structureRecordIdentifier
+	 * @param string $structureFieldName
+	 * @param string $tableName
+	 * @param string $fieldName
+	 * @param string|array $values
+	 */
+	protected function assertResponseContentStructureDoesNotHaveRecords(ResponseContent $responseContent, $structureRecordIdentifier, $structureFieldName, $tableName, $fieldName, $values) {
+		if (is_string($values)) {
+			$values = array($values);
+		}
+
+		$matchingVariants = array();
+
+		foreach ($responseContent->findStructures($structureRecordIdentifier, $structureFieldName) as $path => $structure) {
+			$nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($structure, $tableName, $fieldName, $values);
+			$matchingValues = array_diff($values, $nonMatchingValues);
+
+			if (!empty($matchingValues)) {
+				$matchingVariants[$path] = $matchingValues;
+			}
+		}
+
+		if (empty($matchingVariants)) {
+			// Increase assertion counter
+			$this->assertEmpty($matchingVariants);
+			return;
+		}
+
+		$matchingMessage = '';
+		foreach ($matchingVariants as $path => $matchingValues) {
+			$matchingMessage .= '* ' . $path . ': ' . implode(', ', $matchingValues);
+		}
+
+		$this->fail('Could not assert not having values for "' . $tableName . '.' . $fieldName . '"' . LF . $matchingMessage);
+	}
+
+	/**
+	 * @param ResponseContent $responseContent
+	 * @param string $tableName
+	 * @param string $fieldName
+	 * @param string|array $values
+	 */
+	protected function assertResponseContentHasRecords(ResponseContent $responseContent, $tableName, $fieldName, $values) {
+		$nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($responseContent->getRecords(), $tableName, $fieldName, $values);
+
+		if (!empty($nonMatchingValues)) {
+			$this->fail('Could not assert all values for "' . $tableName . '.' . $fieldName . '": ' . implode(', ', $nonMatchingValues));
+		}
+
+		// Increase assertion counter
+		$this->assertEmpty($nonMatchingValues);
+	}
+
+	/**
+	 * @param ResponseContent $responseContent
+	 * @param string $tableName
+	 * @param string $fieldName
+	 * @param string|array $values
+	 */
+	protected function assertResponseContentDoesNotHaveRecords(ResponseContent $responseContent, $tableName, $fieldName, $values) {
+		if (is_string($values)) {
+			$values = array($values);
+		}
+
+		$nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($responseContent->getRecords(), $tableName, $fieldName, $values);
+		$matchingValues = array_diff($values, $nonMatchingValues);
+
+		if (!empty($matchingValues)) {
+			$this->fail('Could not assert not having values for "' . $tableName . '.' . $fieldName . '": ' . implode(', ', $matchingValues));
+		}
+
+		// Increase assertion counter
+		$this->assertTrue(TRUE);
+	}
+
+	/**
+	 * @param string|array $data
+	 * @param string $tableName
+	 * @param string $fieldName
+	 * @param string|array $values
+	 * @return array
+	 */
+	protected function getNonMatchingValuesFrontendResponseRecords($data, $tableName, $fieldName, $values) {
+		if (empty($data) || !is_array($data)) {
+			$this->fail('Frontend Response data does not have any records');
+		}
+
+		if (is_string($values)) {
+			$values = array($values);
+		}
+
+		foreach ($data as $recordIdentifier => $recordData) {
+			if (strpos($recordIdentifier, $tableName . ':') !== 0) {
+				continue;
+			}
+
+			if (($foundValueIndex = array_search($recordData[$fieldName], $values)) !== FALSE) {
+				unset($values[$foundValueIndex]);
+			}
+		}
+
+		return $values;
+	}
+
 }
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..59202ed3595e5da57d165890b0b16465a024c104
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php
@@ -0,0 +1,71 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Handler for backend user
+ */
+class BackendUserHandler implements \TYPO3\CMS\Core\SingletonInterface {
+
+	/**
+	 * @param array $parameters
+	 * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController
+	 */
+	public function initialize(array $parameters, \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController) {
+		$backendUserId = (int)GeneralUtility::_GP('backendUserId');
+		$workspaceId = (int)GeneralUtility::_GP('workspaceId');
+
+		if (empty($backendUserId) || empty($workspaceId)) {
+			return;
+		}
+
+		$backendUser = $this->createBackendUser();
+		$backendUser->user = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'be_users', 'uid=' . $backendUserId);
+		$backendUser->setTemporaryWorkspace($workspaceId);
+		$frontendController->beUserLogin = 1;
+
+		$parameters['BE_USER'] = $backendUser;
+		$GLOBALS['BE_USER'] = $backendUser;
+	}
+
+	/**
+	 * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
+	 */
+	protected function createBackendUser() {
+		return GeneralUtility::makeInstance(
+			'TYPO3\\CMS\\Backend\\FrontendBackendUserAuthentication'
+		);
+	}
+
+	/**
+	 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+	 */
+	protected function getDatabaseConnection() {
+		return $GLOBALS['TYPO3_DB'];
+	}
+
+}
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..e628a24d645bbdba04ae70aeeaa3b8a77002e89e
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php
@@ -0,0 +1,191 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RenderLevel;
+use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RenderElement;
+
+/**
+ * Watcher for the content object rendering process
+ */
+class ContentObjectRendererWatcher implements \TYPO3\CMS\Frontend\ContentObject\ContentObjectPostInitHookInterface, \TYPO3\CMS\Core\SingletonInterface {
+
+	/**
+	 * @var RenderLevel
+	 */
+	protected $renderLevel;
+
+	/**
+	 * @var RenderElement
+	 */
+	protected $nextParentRenderElement;
+
+	/**
+	 * @var array
+	 */
+	protected $nextParentConfiguration;
+
+	/**
+	 * Holds parent objects (cObj) locally
+	 * to avoid spl_object_hash() reassignments.
+	 *
+	 * @var array
+	 */
+	protected $localParentObjects = array();
+
+	/**
+	 * @param string $name
+	 * @param NULL|array $configuration
+	 * @param string $typoScriptKey
+	 * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject
+	 * @return string
+	 */
+	public function cObjGetSingleExt($name, $configuration, $typoScriptKey, \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject) {
+		$this->localParentObjects[] = $parentObject;
+		$this->nextParentRenderElement = NULL;
+		$this->nextParentConfiguration = NULL;
+
+		if (($foundRenderElement = $this->renderLevel->findRenderElement($parentObject)) !== NULL) {
+			$this->nextParentRenderElement = $foundRenderElement;
+			$this->nextParentConfiguration = $configuration;
+			if (!empty($configuration['table'])) {
+				$this->nextParentRenderElement->addExpectedTableName($configuration['table']);
+			}
+		}
+
+		$contentObject = $parentObject->getContentObject($name);
+		if ($contentObject) {
+			$contentObject->render($configuration);
+		}
+
+		return '';
+	}
+
+	/**
+	 * Hook for post processing the initialization of ContentObjectRenderer
+	 *
+	 * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject Parent content object
+	 */
+	public function postProcessContentObjectInitialization(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer &$parentObject) {
+		$this->localParentObjects[] = $parentObject;
+
+		if (!isset($this->renderLevel)) {
+			$this->renderLevel = RenderLevel::create($parentObject);
+			$this->renderLevel->add($parentObject);
+		} elseif (($foundRenderLevel = $this->renderLevel->findRenderLevel($parentObject)) !== NULL) {
+			$foundRenderLevel->add($parentObject);
+		} elseif ($this->nextParentRenderElement !== NULL) {
+			$level = $this->nextParentRenderElement->add($parentObject);
+			$level->add($parentObject);
+			if (!empty($this->nextParentConfiguration['watcher.']['parentRecordField'])) {
+				$level->setParentRecordField($this->nextParentConfiguration['watcher.']['parentRecordField']);
+			}
+			$this->nextParentRenderElement = NULL;
+			$this->nextParentConfiguration = NULL;
+		}
+	}
+
+	/**
+	 * @param string $query
+	 * @param string $fromTable
+	 */
+	public function addQuery($query, $fromTable) {
+		if ($this->nextParentRenderElement === NULL) {
+			return;
+		}
+
+		$this->nextParentRenderElement->addQuery($query, $fromTable);
+	}
+
+	/**
+	 * @param array $parameters
+	 * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController
+	 */
+	public function show(array $parameters, \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController) {
+		if (!isset($this->renderLevel) || empty($parameters['enableOutput']) || !empty($frontendController->content)) {
+			return;
+		}
+
+		$tableFields = NULL;
+		if (!empty($this->getFrontendController()->tmpl->setup['watcher.']['tableFields.'])) {
+			$tableFields = $this->getFrontendController()->tmpl->setup['watcher.']['tableFields.'];
+			foreach ($tableFields as &$fieldList) {
+				$fieldList = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $fieldList, TRUE);
+			}
+			unset($fieldList);
+		}
+
+		$structureData = $this->renderLevel->structureData($tableFields);
+
+		$result = array(
+			'structure' => $structureData,
+			'structurePaths' => $this->getStructurePaths($structureData),
+			'records' => $this->renderLevel->mergeData($tableFields),
+			'queries' => $this->renderLevel->mergeQueries(),
+		);
+
+		$frontendController->content = json_encode($result);
+	}
+
+	/**
+	 * @param array $structureData
+	 * @param array $currentStructurePaths
+	 * @return array
+	 */
+	protected function getStructurePaths(array $structureData, array $currentStructurePaths = array()) {
+		$structurePaths = array();
+
+		foreach ($structureData as $recordIdentifier => $recordData) {
+			$structurePaths[$recordIdentifier][] = $currentStructurePaths;
+			foreach ($recordData as $fieldName => $fieldValue) {
+				if (!is_array($fieldValue)) {
+					continue;
+				}
+
+				$nestedStructurePaths = $this->getStructurePaths(
+					$fieldValue,
+					array_merge($currentStructurePaths, array($recordIdentifier, $fieldName))
+				);
+
+				foreach ($nestedStructurePaths as $nestedRecordIdentifier => $nestedStructurePathDetails) {
+					$structurePaths[$nestedRecordIdentifier] = array_merge(
+							(array)$structurePaths[$nestedRecordIdentifier],
+						$nestedStructurePathDetails
+					);
+				}
+			}
+		}
+
+		return $structurePaths;
+	}
+
+	/**
+	 * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
+	 */
+	protected function getFrontendController() {
+		return $GLOBALS['TSFE'];
+	}
+
+}
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..02f0d7f7694991321953128b24dea8fb8629c9c7
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php
@@ -0,0 +1,143 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Watcher for executed database queries
+ */
+class DatabaseConnectionWatcher implements \TYPO3\CMS\Core\Database\PostProcessQueryHookInterface, \TYPO3\CMS\Core\SingletonInterface {
+
+	/**
+	 * @var array
+	 */
+	protected $queries = array();
+
+	/**
+	 * Constructs this object and ensures that full database
+	 * queries are stored locally in the DatabaseConnection.
+	 */
+	public function __construct() {
+		$this->getDatabaseConnection()->store_lastBuiltQuery = TRUE;
+	}
+
+	public function getQueries() {
+		return $this->queries;
+	}
+
+	/**
+	 * Post-processor for the SELECTquery method.
+	 *
+	 * @param string $select_fields Fields to be selected
+	 * @param string $from_table Table to select data from
+	 * @param string $where_clause Where clause
+	 * @param string $groupBy Group by statement
+	 * @param string $orderBy Order by statement
+	 * @param integer $limit Database return limit
+	 * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject
+	 * @return void
+	 */
+	public function exec_SELECTquery_postProcessAction(&$select_fields, &$from_table, &$where_clause, &$groupBy, &$orderBy, &$limit, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) {
+		$this->getContentObjectRendererWatcher()->addQuery(
+			$parentObject->debug_lastBuiltQuery,
+			$from_table
+		);
+	}
+
+	/**
+	 * Post-processor for the exec_INSERTquery method.
+	 *
+	 * @param string $table Database table name
+	 * @param array $fieldsValues Field values as key => value pairs
+	 * @param string /array $noQuoteFields List/array of keys NOT to quote
+	 * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject
+	 * @return void
+	 */
+	public function exec_INSERTquery_postProcessAction(&$table, array &$fieldsValues, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) {
+	}
+
+	/**
+	 * Post-processor for the exec_INSERTmultipleRows method.
+	 *
+	 * @param string $table Database table name
+	 * @param array $fields Field names
+	 * @param array $rows Table rows
+	 * @param string /array $noQuoteFields List/array of keys NOT to quote
+	 * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject
+	 * @return void
+	 */
+	public function exec_INSERTmultipleRows_postProcessAction(&$table, array &$fields, array &$rows, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) {
+	}
+
+	/**
+	 * Post-processor for the exec_UPDATEquery method.
+	 *
+	 * @param string $table Database table name
+	 * @param string $where WHERE clause
+	 * @param array $fieldsValues Field values as key => value pairs
+	 * @param string /array $noQuoteFields List/array of keys NOT to quote
+	 * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject
+	 * @return void
+	 */
+	public function exec_UPDATEquery_postProcessAction(&$table, &$where, array &$fieldsValues, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) {
+	}
+
+	/**
+	 * Post-processor for the exec_DELETEquery method.
+	 *
+	 * @param string $table Database table name
+	 * @param string $where WHERE clause
+	 * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject
+	 * @return void
+	 */
+	public function exec_DELETEquery_postProcessAction(&$table, &$where, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) {
+	}
+
+	/**
+	 * Post-processor for the exec_TRUNCATEquery method.
+	 *
+	 * @param string $table Database table name
+	 * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject
+	 * @return void
+	 */
+	public function exec_TRUNCATEquery_postProcessAction(&$table, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) {
+	}
+
+	/**
+	 * @return ContentObjectRendererWatcher
+	 */
+	protected function getContentObjectRendererWatcher() {
+		return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+			'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\Hook\\ContentObjectRendererWatcher'
+		);
+	}
+
+	/**
+	 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+	 */
+	protected function getDatabaseConnection() {
+		return $GLOBALS['TYPO3_DB'];
+	}
+
+}
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd6c8a5b0e2f4fa41fe6ecd5f5762346b36b8055
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php
@@ -0,0 +1,240 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
+
+/**
+ * Model of rendered content elements
+ */
+class RenderElement {
+
+	/**
+	 * @var array
+	 */
+	protected $recordData;
+
+	/**
+	 * @var string
+	 */
+	protected $recordIdentifier;
+
+	/**
+	 * @var string
+	 */
+	protected $recordTableName;
+
+	/**
+	 * @var array|RenderLevel[]
+	 */
+	protected $levels;
+
+	/**
+	 * @var array
+	 */
+	protected $expectedTableNames = array();
+
+	/**
+	 * @var array
+	 */
+	protected $queries = array();
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 * @return RenderElement
+	 */
+	public static function create(ContentObjectRenderer $contentObjectRenderer) {
+		return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+			'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\RenderElement',
+			$contentObjectRenderer
+		);
+	}
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 */
+	public function __construct(ContentObjectRenderer $contentObjectRenderer) {
+		$this->recordIdentifier = $contentObjectRenderer->currentRecord;
+		list($this->recordTableName) = explode(':', $this->recordIdentifier);
+		$this->recordData = $contentObjectRenderer->data;
+	}
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 * @return RenderLevel
+	 */
+	public function add(ContentObjectRenderer $contentObjectRenderer) {
+		$level = RenderLevel::create($contentObjectRenderer);
+		$level->setParentRecordIdentifier($this->recordIdentifier);
+		$this->levels[] = $level;
+		return $level;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getRecordIdentifier() {
+		return $this->recordIdentifier;
+	}
+
+	/**
+	 * @param string $expectedTableName
+	 */
+	public function addExpectedTableName($expectedTableName) {
+		if (!$this->hasExpectedTableName($expectedTableName)) {
+			$this->expectedTableNames[] = $expectedTableName;
+		}
+	}
+
+	/**
+	 * @param string $tableName
+	 * @return bool
+	 */
+	public function hasExpectedTableName($tableName) {
+		if (in_array($tableName, $this->expectedTableNames)) {
+			return TRUE;
+		}
+		// Handling JOIN constructions
+		// e.g. "sys_category JOIN sys_category_record_mm ON sys_category_record_mm.uid_local = sys_category.uid"
+		foreach ($this->getExpectedTableNames() as $expectedTableName) {
+			if (strpos($tableName, $expectedTableName . ' ') === 0) {
+				return TRUE;
+			}
+		}
+		return FALSE;
+	}
+
+	/**
+	 * @return array
+	 */
+	public function getExpectedTableNames() {
+		return $this->expectedTableNames;
+	}
+
+	/**
+	 * @param string $query
+	 * @param string $fromTable
+	 */
+	public function addQuery($query, $fromTable) {
+		if (empty($this->expectedTableNames) || $this->hasExpectedTableName($fromTable)) {
+			$this->queries[] = $query;
+		}
+	}
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 * @return NULL|RenderLevel
+	 */
+	public function findRenderLevel(ContentObjectRenderer $contentObjectRenderer) {
+		if (empty($this->levels)) {
+			return NULL;
+		}
+
+		foreach ($this->levels as $level) {
+			$result = $level->findRenderLevel($contentObjectRenderer);
+			if ($result !== NULL) {
+				return $result;
+			}
+		}
+
+		return NULL;
+	}
+
+	/**
+	 * @param NULL|array $tableFields
+	 * @return array
+	 */
+	public function getRecordData(array $tableFields = NULL) {
+		$recordData = $this->recordData;
+
+		if (!empty($tableFields[$this->recordTableName])) {
+			$recordData = array_intersect_key(
+				$recordData,
+				array_flip($tableFields[$this->recordTableName])
+			);
+		}
+
+		return $recordData;
+	}
+
+	/**
+	 * @param NULL|array $tableFields
+	 * @return array
+	 */
+	public function structureData(array $tableFields = NULL) {
+		$data = array(
+			$this->recordIdentifier => $this->getRecordData($tableFields)
+		);
+
+		foreach ($this->levels as $level) {
+			$parentRecordIdentifier = $level->getParentRecordIdentifier();
+			$parentRecordField = $level->getParentRecordField();
+
+			foreach ($level->getElements() as $element) {
+				if (empty($parentRecordIdentifier) || empty($parentRecordField) || !isset($data[$parentRecordIdentifier])) {
+					$data = array_merge($data, $element->structureData($tableFields));
+					continue;
+				}
+
+				if (!isset($data[$parentRecordIdentifier][$parentRecordField]) || !is_array($data[$parentRecordIdentifier][$parentRecordField])) {
+					$data[$parentRecordIdentifier][$parentRecordField] = array();
+				}
+
+				$data[$parentRecordIdentifier][$parentRecordField] = array_merge(
+					$data[$parentRecordIdentifier][$parentRecordField],
+					$element->structureData($tableFields)
+				);
+			}
+		}
+
+		return $data;
+	}
+
+	/**
+	 * @param NULL|array $tableFields
+	 * @return array
+	 */
+	public function mergeData(array $tableFields = NULL) {
+		$data = array(
+			$this->recordIdentifier => $this->getRecordData($tableFields),
+		);
+		foreach ($this->levels as $level) {
+			$data = array_merge($data, $level->mergeData($tableFields));
+		}
+		return $data;
+	}
+
+	/**
+	 * @return array
+	 */
+	public function mergeQueries() {
+		$queries = $this->queries;
+		foreach ($this->levels as $level) {
+			$queries = array_merge($queries, $level->mergeQueries());
+		}
+		return $queries;
+	}
+
+}
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php
new file mode 100644
index 0000000000000000000000000000000000000000..545e3180bf18040c56a3cb615ce9a7ba0a34c5a1
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php
@@ -0,0 +1,206 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
+
+/**
+ * Model of rendered content levels
+ */
+class RenderLevel {
+
+	/**
+	 * @var string
+	 */
+	protected $identifier;
+
+	/**
+	 * @var string
+	 */
+	protected $parentRecordIdentifier;
+
+	/**
+	 * @var string
+	 */
+	protected $parentRecordField;
+
+	/**
+	 * @var array|RenderElement[]
+	 */
+	protected $elements = array();
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 * @return RenderLevel
+	 */
+	public static function create(ContentObjectRenderer $contentObjectRenderer) {
+		return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+			'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\RenderLevel',
+			$contentObjectRenderer
+		);
+	}
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 */
+	public function __construct(ContentObjectRenderer $contentObjectRenderer) {
+		$this->identifier = spl_object_hash($contentObjectRenderer);
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getIdentifier() {
+		return $this->identifier;
+	}
+
+	/**
+	 * @param string $parentRecordIdentifier
+	 */
+	public function setParentRecordIdentifier($parentRecordIdentifier) {
+		$this->parentRecordIdentifier = $parentRecordIdentifier;
+	}
+
+	public function getParentRecordIdentifier() {
+		return $this->parentRecordIdentifier;
+	}
+
+	/**
+	 * @param string $parentRecordField
+	 */
+	public function setParentRecordField($parentRecordField) {
+		$this->parentRecordField = $parentRecordField;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getParentRecordField() {
+		return $this->parentRecordField;
+	}
+
+	/**
+	 * @return array|RenderElement[]
+	 */
+	public function getElements() {
+		return $this->elements;
+	}
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 * @return RenderElement
+	 */
+	public function add(ContentObjectRenderer $contentObjectRenderer) {
+		$element = RenderElement::create($contentObjectRenderer);
+		$this->elements[] = $element;
+		return $element;
+	}
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 * @return NULL|RenderLevel
+	 */
+	public function findRenderLevel(ContentObjectRenderer $contentObjectRenderer) {
+		if (spl_object_hash($contentObjectRenderer) === $this->identifier) {
+			return $this;
+		}
+
+		foreach ($this->elements as $element) {
+			$result = $element->findRenderLevel($contentObjectRenderer);
+			if ($result !== NULL) {
+				return $result;
+			}
+		}
+
+		return NULL;
+	}
+
+	/**
+	 * @param ContentObjectRenderer $contentObjectRenderer
+	 * @return NULL|RenderElement
+	 */
+	public function findRenderElement(ContentObjectRenderer $contentObjectRenderer) {
+		$foundRenderLevel = $this->findRenderLevel($contentObjectRenderer);
+
+		if ($foundRenderLevel === NULL) {
+			return NULL;
+		}
+
+		if ($foundRenderLevel !== $this) {
+			return $foundRenderLevel->findRenderElement($contentObjectRenderer);
+		}
+
+
+		foreach ($this->elements as $element) {
+			if ($element->getRecordIdentifier() === $contentObjectRenderer->currentRecord) {
+				return $element;
+			}
+		}
+
+		return NULL;
+	}
+
+	/**
+	 * @param NULL|array $tableFields
+	 * @return array
+	 */
+	public function structureData(array $tableFields = NULL) {
+		$data = array();
+
+		foreach ($this->elements as $element) {
+			$data = array_merge($data, $element->structureData($tableFields));
+		}
+
+		return $data;
+	}
+
+	/**
+	 * @param NULL|array $tableFields
+	 * @return array
+	 */
+	public function mergeData(array $tableFields = NULL) {
+		$data = array();
+
+		foreach ($this->elements as $element) {
+			$data = array_merge($data, $element->mergeData($tableFields));
+		}
+
+		return $data;
+	}
+
+	/**
+	 * @return array
+	 */
+	public function mergeQueries() {
+		$queries = array();
+
+		foreach ($this->elements as $element) {
+			$queries = array_merge($queries, $element->mergeQueries());
+		}
+
+		return $queries;
+	}
+
+}
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..623a02357d87c8e378c416a8bcc18dcc8c91510c
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php
@@ -0,0 +1,111 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Bootstrap for direct CLI Request
+ */
+class RequestBootstrap {
+
+	/**
+	 * @return void
+	 */
+	static public function setGlobalVariables() {
+		if (empty($_SERVER['argv'][1]) || ($requestArguments = json_decode($_SERVER['argv'][1], TRUE)) === FALSE) {
+			die('No JSON encoded arguments given');
+		}
+
+		if (empty($requestArguments['documentRoot'])) {
+			die('No documentRoot given');
+		}
+
+		if (empty($requestArguments['requestUrl']) || ($requestUrlParts = parse_url($requestArguments['requestUrl'])) === FALSE) {
+			die('No valid request URL given');
+		}
+
+		// Populating $_GET and $_REQUEST is query part is set:
+		if (isset($requestUrlParts['query'])) {
+			parse_str($requestUrlParts['query'], $_GET);
+			parse_str($requestUrlParts['query'], $_REQUEST);
+		}
+
+		// Populating $_POST
+		$_POST = array();
+		// Populating $_COOKIE
+		$_COOKIE = array();
+
+		// Setting up the server environment
+		$_SERVER = array();
+		$_SERVER['DOCUMENT_ROOT'] = $requestArguments['documentRoot'];
+		$_SERVER['HTTP_USER_AGENT'] = 'TYPO3 Functional Test Request';
+		$_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'] = $requestUrlParts['host'];
+		$_SERVER['SERVER_ADDR'] = $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+		$_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'] = '/index.php';
+		$_SERVER['SCRIPT_FILENAME'] = $_SERVER['_'] = $_SERVER['PATH_TRANSLATED'] = $requestArguments['documentRoot'] . '/index.php';
+		$_SERVER['QUERY_STRING'] = (isset($requestUrlParts['query']) ? $requestUrlParts['query'] : '');
+		$_SERVER['REQUEST_URI'] = $requestUrlParts['path'] . (isset($requestUrlParts['query']) ? '?' . $requestUrlParts['query'] : '');
+		$_SERVER['REQUEST_METHOD'] = 'GET';
+
+		// Define a port if used in the URL:
+		if (isset($requestUrlParts['port'])) {
+			$_SERVER['SERVER_PORT'] = $requestUrlParts['port'];
+		}
+		// Define HTTPS disposal:
+		if ($requestUrlParts['scheme'] === 'https') {
+			$_SERVER['HTTPS'] = 'on';
+		}
+
+		if (!is_dir($_SERVER['DOCUMENT_ROOT'])) {
+			die('Document root directory "' . $_SERVER['SCRIPT_FILENAME'] . '" does not exist');
+		}
+
+		if (!is_file($_SERVER['SCRIPT_FILENAME'])) {
+			die('Script file "' . $_SERVER['SCRIPT_FILENAME'] . '" does not exist');
+		}
+	}
+
+	/**
+	 * @return void
+	 */
+	static public function executeAndOutput() {
+		global $TT, $TSFE, $TYPO3_CONF_VARS, $BE_USER, $TYPO3_MISC;
+
+		$result = array('status' => 'failure', 'content' => NULL, 'error' => NULL);
+
+		ob_start();
+		try {
+			chdir($_SERVER['DOCUMENT_ROOT']);
+			include($_SERVER['SCRIPT_FILENAME']);
+			$result['status'] = 'success';
+			$result['content'] = ob_get_contents();
+		} catch(\Exception $exception) {
+			$result['error'] = $exception->__toString();
+		}
+		ob_end_clean();
+
+		echo json_encode($result);
+	}
+
+}
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php
new file mode 100644
index 0000000000000000000000000000000000000000..b0e68392a4a1ba2e9696f5f72b05ee27333efebe
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php
@@ -0,0 +1,97 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Model of frontend response
+ */
+class Response {
+
+	const STATUS_Success = 'success';
+	const STATUS_Failure = 'failure';
+
+	/**
+	 * @var string
+	 */
+	protected $status;
+
+	/**
+	 * @var NULL|string|array
+	 */
+	protected $content;
+
+	/**
+	 * @var string
+	 */
+	protected $error;
+
+	/**
+	 * @var ResponseContent
+	 */
+	protected $responseContent;
+
+	/**
+	 * @param string $status
+	 * @param string $content
+	 * @param string $error
+	 */
+	public function __construct($status, $content, $error) {
+		$this->status = $status;
+		$this->content = $content;
+		$this->error = $error;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getStatus() {
+		return $this->status;
+	}
+
+	/**
+	 * @return array|NULL|string
+	 */
+	public function getContent() {
+		return $this->content;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getError() {
+		return $this->error;
+	}
+
+	/**
+	 * @return ResponseContent
+	 */
+	public function getResponseContent() {
+		if (!isset($this->responseContent)) {
+			$this->responseContent = new ResponseContent($this);
+		}
+		return $this->responseContent;
+	}
+
+}
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php
new file mode 100644
index 0000000000000000000000000000000000000000..93ccb36c078f9955bd2c7836c6bebc32b9b7cd29
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php
@@ -0,0 +1,136 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Model of frontend response content
+ */
+class ResponseContent {
+
+	/**
+	 * @var Response
+	 */
+	protected $response;
+
+	/**
+	 * @var array
+	 */
+	protected $structure;
+
+	/**
+	 * @var array
+	 */
+	protected $structurePaths;
+
+	/**
+	 * @var array
+	 */
+	protected $records;
+
+	/**
+	 * @var array
+	 */
+	protected $queries;
+
+	/**
+	 * @param Response $response
+	 */
+	public function __construct(Response $response) {
+		$this->response = $response;
+		$content = json_decode($response->getContent(), TRUE);
+
+		if ($content !== NULL && is_array($content)) {
+			$this->structure = $content['structure'];
+			$this->structurePaths = $content['structurePaths'];
+			$this->records = $content['records'];
+			$this->queries = $content['queries'];
+		}
+	}
+
+	/**
+	 * @return array
+	 */
+	public function getStructure() {
+		return $this->structure;
+	}
+
+	/**
+	 * @return array
+	 */
+	public function getStructurePaths() {
+		return $this->structurePaths;
+	}
+
+	/**
+	 * @return array
+	 */
+	public function getRecords() {
+		return $this->records;
+	}
+
+	/**
+	 * @return array
+	 */
+	public function getQueries() {
+		return $this->queries;
+	}
+
+	/**
+	 * @param string $recordIdentifier
+	 * @param string $fieldName
+	 * @return array
+	 */
+	public function findStructures($recordIdentifier, $fieldName = '') {
+		$structures = array();
+
+		if (empty($this->structurePaths[$recordIdentifier])) {
+			return $structures;
+		}
+
+		foreach ($this->structurePaths[$recordIdentifier] as $steps) {
+			$structure = $this->structure;
+			$steps[] = $recordIdentifier;
+
+			if (!empty($fieldName)) {
+				$steps[] = $fieldName;
+			}
+
+			foreach ($steps as $step) {
+				if (!isset($structure[$step])) {
+					$structure = NULL;
+					break;
+				}
+				$structure = $structure[$step];
+			}
+
+			if (!empty($structure)) {
+				$structures[implode('/', $steps)] = $structure;
+			}
+		}
+
+		return $structures;
+	}
+
+}
diff --git a/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php b/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php
new file mode 100644
index 0000000000000000000000000000000000000000..ff599a6a9ab69bf84d609b45456c4e224c78c6fc
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php
@@ -0,0 +1,8 @@
+<?php
+require dirname(dirname(dirname(dirname(__DIR__)))) . '/Classes/Core/CliBootstrap.php';
+\TYPO3\CMS\Core\Core\CliBootstrap::checkEnvironmentOrDie();
+
+require dirname(__DIR__) . '/Frontend/RequestBootstrap.php';
+\TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RequestBootstrap::setGlobalVariables();
+\TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RequestBootstrap::executeAndOutput();
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/FunctionalTestCase.php b/typo3/sysext/core/Tests/FunctionalTestCase.php
index aa9204970eed9359820421c1b8f4077916edd1a4..f81624242ab51252ffb63af15785784bb90958cb 100644
--- a/typo3/sysext/core/Tests/FunctionalTestCase.php
+++ b/typo3/sysext/core/Tests/FunctionalTestCase.php
@@ -24,6 +24,8 @@ namespace TYPO3\CMS\Core\Tests;
  * This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
 
+use \TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
+
 /**
  * Base test case class for functional tests, all TYPO3 CMS
  * functional tests should extend from this class!
@@ -134,6 +136,13 @@ abstract class FunctionalTestCase extends BaseTestCase {
 	 */
 	private $bootstrapUtility = NULL;
 
+	/**
+	 * Path to TYPO3 CMS test installation for this test case
+	 *
+	 * @var string
+	 */
+	private $instancePath;
+
 	/**
 	 * Set up creates a test instance and database.
 	 *
@@ -146,7 +155,7 @@ abstract class FunctionalTestCase extends BaseTestCase {
 			$this->markTestSkipped('Functional tests must be called through phpunit on CLI');
 		}
 		$this->bootstrapUtility = new FunctionalTestCaseBootstrapUtility();
-		$this->bootstrapUtility->setUp(
+		$this->instancePath = $this->bootstrapUtility->setUp(
 			get_class($this),
 			$this->coreExtensionsToLoad,
 			$this->testExtensionsToLoad,
@@ -273,4 +282,92 @@ abstract class FunctionalTestCase extends BaseTestCase {
 			}
 		}
 	}
+
+	/**
+	 * @param int $pageId
+	 * @param array $typoScriptFiles
+	 */
+	protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = array()) {
+		$pageId = (int)$pageId;
+		$page = $this->getDatabase()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . $pageId);
+
+		if (empty($page)) {
+			$this->fail('Cannot set up frontend root page "' . $pageId . '"');
+		}
+
+		$pagesFields = array(
+			'is_siteroot' => 1
+		);
+
+		$this->getDatabase()->exec_UPDATEquery('pages', 'uid=' . $pageId, $pagesFields);
+
+		$templateFields = array(
+			'pid' => $pageId,
+			'title' => '',
+			'config' => '',
+			'clear' => 3,
+			'root' => 1,
+		);
+
+		foreach ($typoScriptFiles as $typoScriptFile) {
+			$templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF;
+		}
+
+		$this->getDatabase()->exec_INSERTquery('sys_template', $templateFields);
+	}
+
+	/**
+	 * @param int $pageId
+	 * @param int $languageId
+	 * @param int $backendUserId
+	 * @param int $workspaceId
+	 * @param bool $failOnFailure
+	 * @return Response
+	 */
+	protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = TRUE) {
+		$pageId = (int)$pageId;
+		$languageId = (int)$languageId;
+
+		if (defined('PHP_BINARY')) {
+			$phpExecutable = PHP_BINARY;
+		} else {
+			$phpExecutable = rtrim(PHP_BINDIR, '/') . '/php';
+		}
+
+		$additionalParameter = '';
+
+		if (!empty($backendUserId)) {
+			$additionalParameter .= '&backendUserId=' . (int)$backendUserId;
+		}
+		if (!empty($workspaceId)) {
+			$additionalParameter .= '&workspaceId=' . (int)$workspaceId;
+		}
+
+		$arguments = array(
+			'documentRoot' => $this->instancePath,
+			'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter,
+		);
+
+		$commandParts = array(
+			escapeshellcmd($phpExecutable),
+			escapeshellarg(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php'),
+			escapeshellarg(json_encode($arguments)),
+		);
+
+		$command = trim(implode(' ', $commandParts));
+		$response = shell_exec($command);
+		$result = json_decode($response, TRUE);
+
+		if ($result === FALSE) {
+			$this->fail('Frontend Response is empty');
+		}
+
+		if ($failOnFailure && $result['status'] === Response::STATUS_Failure) {
+			$this->fail('Frontend Response has failure:' . LF . $result['error']);
+		}
+
+		$response = new Response($result['status'], $result['content'], $result['error']);
+		return $response;
+	}
+
 }
diff --git a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php b/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php
index 069a8bf22cbd085ccab7c253513c15d2249e509f..36f7ce062644009e1a322b2c3d75d3380d3c4278 100644
--- a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php
+++ b/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php
@@ -75,7 +75,7 @@ class FunctionalTestCaseBootstrapUtility {
 	 * @param array $coreExtensionsToLoad Array of core extensions to load
 	 * @param array $testExtensionsToLoad Array of test extensions to load
 	 * @param array $pathsToLinkInTestInstance Array of source => destination path pairs to be linked
-	 * @return void
+	 * @return string Path to TYPO3 CMS test installation for this test case
 	 */
 	public function setUp(
 		$testCaseClassName,
@@ -96,6 +96,8 @@ class FunctionalTestCaseBootstrapUtility {
 		$this->setUpTestDatabase();
 		\TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(TRUE);
 		$this->createDatabaseStructure();
+
+		return $this->instancePath;
 	}
 
 	/**