From 8a50eb08427fa3a73e3f145ef6efd708fba47247 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=ABl=20Kr=C3=A4hemann?= <joel.kraehemann@frentix.com>
Date: Thu, 18 Oct 2012 17:38:48 +0200
Subject: [PATCH] OO-302: enhanced syncing threads and implemented new test.

---
 .../course/nodes/feed/FunctionalBlogTest.java | 220 ++++++++++++++++--
 .../java/org/olat/course/nodes/feed/blog.zip  | Bin 0 -> 511 bytes
 .../FunctionalAdministrationSiteUtil.java     | 161 ++++++++++++-
 .../org/olat/util/FunctionalCourseUtil.java   |   5 +
 .../java/org/olat/util/FunctionalUtil.java    |  74 +++++-
 .../java/org/olat/util/FunctionalVOUtil.java  |  81 +++++--
 6 files changed, 498 insertions(+), 43 deletions(-)
 create mode 100644 src/test/java/org/olat/course/nodes/feed/blog.zip

diff --git a/src/test/java/org/olat/course/nodes/feed/FunctionalBlogTest.java b/src/test/java/org/olat/course/nodes/feed/FunctionalBlogTest.java
index fd1239e214c..7f0a7c7292a 100644
--- a/src/test/java/org/olat/course/nodes/feed/FunctionalBlogTest.java
+++ b/src/test/java/org/olat/course/nodes/feed/FunctionalBlogTest.java
@@ -25,6 +25,8 @@ import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.commons.lang.ArrayUtils;
 import org.jboss.arquillian.container.test.api.Deployment;
@@ -39,12 +41,15 @@ import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.olat.restapi.support.vo.CourseVO;
+import org.olat.restapi.support.vo.RepositoryEntryVO;
 import org.olat.test.ArquillianDeployments;
 import org.olat.user.restapi.UserVO;
+import org.olat.util.FunctionalAdministrationSiteUtil;
 import org.olat.util.FunctionalCourseUtil;
 import org.olat.util.FunctionalCourseUtil.BlogEdit;
 import org.olat.util.FunctionalRepositorySiteUtil;
 import org.olat.util.FunctionalUtil;
+import org.olat.util.FunctionalUtil.OlatSite;
 import org.olat.util.FunctionalUtil.WaitLimitAttribute;
 import org.olat.util.FunctionalVOUtil;
 import org.olat.util.FunctionalCourseUtil.CourseNodeAlias;
@@ -52,6 +57,7 @@ import org.olat.util.browser.Browser1;
 import org.olat.util.browser.Browser2;
 import org.olat.util.browser.Student1;
 import org.olat.util.browser.Student2;
+import org.olat.util.browser.Tutor1;
 
 import com.thoughtworks.selenium.DefaultSelenium;
 import com.thoughtworks.selenium.Selenium;
@@ -73,6 +79,17 @@ public class FunctionalBlogTest {
 	public final static String DELETE_BLOG_DESCRIPTION = "The first blog entry will be deleted";
 	public final static String DELETE_BLOG_CONTENT = "You should be able to choose to create or feed from existing blog.";
 	
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG = "/org/olat/course/nodes/feed/blog.zip";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_FILE_NAME = "blog.zip";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_RESOURCE_NAME = "Blog";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_DISPLAY_NAME = "Parallel Computing Blog";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_POST1_TITLE = "Conditional locks with Java";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_POST1_DESCRIPTION = "Advanced thread safety in Java.";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_POST1_CONTENT = "Please take a look at ReentrantLock class in JavaSE package java.util.concurrent.locks for further information.";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_POST2_TITLE = "Creating conditions";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_POST2_DESCRIPTION = "Wait until condition is fulfilled.";
+	public final static String CONCURRENT_CLEAR_CACHE_BLOG_POST2_CONTENT = "With the ReentrantLock class you may create new conditions like following:<br><code>final Lock lock = new ReentrantLock();<br>final Condition cond  = lock.newCondition()<br></code>";
+	
 	public final static String CONCURRENT_RW_BLOG_SHORT_TITLE = "blog";
 	public final static String CONCURRENT_RW_BLOG_LONG_TITLE = "blog cleared cache";
 	public final static String CONCURRENT_RW_BLOG_DESCRIPTION = "During open blog cache will be cleared";
@@ -93,6 +110,7 @@ public class FunctionalBlogTest {
 	static FunctionalUtil functionalUtil;
 	static FunctionalRepositorySiteUtil functionalRepositorySiteUtil;
 	static FunctionalCourseUtil functionalCourseUtil;
+	static FunctionalAdministrationSiteUtil functionalAdministrationSiteUtil;
 	static FunctionalVOUtil functionalVOUtil;
 
 	static boolean initialized = false;
@@ -105,7 +123,9 @@ public class FunctionalBlogTest {
 
 			functionalRepositorySiteUtil = functionalUtil.getFunctionalRepositorySiteUtil();
 			functionalCourseUtil = functionalRepositorySiteUtil.getFunctionalCourseUtil();
-
+			
+			functionalAdministrationSiteUtil = functionalUtil.getFunctionalAdministrationSiteUtil();
+			
 			functionalVOUtil = new FunctionalVOUtil(functionalUtil.getUsername(), functionalUtil.getPassword());
 
 			initialized = true;
@@ -141,8 +161,139 @@ public class FunctionalBlogTest {
 	@Ignore
 	@Test
 	@RunAsClient
-	public void checkConcurrentClearCache(){
+	public void checkDelete(){
+		//TODO:JK: implement me
+	}
+	
+	@Ignore
+	@Test
+	@RunAsClient
+	public void checkConcurrentClearCache(@Drone @Tutor1 DefaultSelenium tutor0, @Drone @Student1 DefaultSelenium student0) throws IOException, URISyntaxException{
+		/*
+		 * Setup
+		 */
+		/* create author */
+		int tutorCount = 1;
+			
+		final UserVO[] tutors = new UserVO[tutorCount];
+		functionalVOUtil.createTestUsers(deploymentUrl, tutorCount).toArray(tutors);
+		
+		/* create user */
+		int userCount = 1;
+			
+		final UserVO[] students = new UserVO[userCount];
+		functionalVOUtil.createTestUsers(deploymentUrl, userCount).toArray(students);
+		
+		/* create blog and set rights for tutor	 */
+		RepositoryEntryVO repoEntry = functionalVOUtil.importBlog(deploymentUrl,
+				CONCURRENT_CLEAR_CACHE_BLOG,
+				CONCURRENT_CLEAR_CACHE_BLOG_FILE_NAME, CONCURRENT_CLEAR_CACHE_BLOG_RESOURCE_NAME, CONCURRENT_CLEAR_CACHE_BLOG_DISPLAY_NAME);
+		//FIXME:JK: set rights
+		
+		/*
+		 * Create content and visit it.
+		 */
+		/* tutor creates a new post */
+		Assert.assertTrue(functionalUtil.login(tutor0, tutors[0].getLogin(), tutors[0].getPassword(), true));
+		Assert.assertTrue(functionalCourseUtil.openBlog(tutor0, repoEntry.getKey()));
+		Assert.assertTrue(functionalCourseUtil.editBlogEntry(tutor0,
+				CONCURRENT_CLEAR_CACHE_BLOG_POST1_TITLE, CONCURRENT_CLEAR_CACHE_BLOG_POST1_DESCRIPTION, CONCURRENT_CLEAR_CACHE_BLOG_POST1_CONTENT,
+				-1, null));
+		
+		/* student visits content */
+		Assert.assertTrue(functionalUtil.login(student0, students[0].getLogin(), students[0].getPassword(), true));
+		Assert.assertTrue(functionalCourseUtil.openBlog(student0, repoEntry.getKey()));
+
+		
+		/*
+		 * Clear cache and verify content.
+		 */
+		/* admin clears cache */
+		Assert.assertTrue(functionalUtil.login(browser, functionalUtil.getUsername(), functionalUtil.getPassword(), true));
+		Assert.assertTrue(functionalAdministrationSiteUtil.clearCache(browser,
+				new String[]{
+				"org.olat.core.util.cache.n.impl.svm.SingleVMCacher@org.olat.modules.webFeed.dispatching.Path_feed__0",
+				"org.olat.core.util.cache.n.impl.svm.SingleVMCacher@org.olat.modules.webFeed.managers.FeedManagerImpl_feed__0"
+				}
+		));
+
+		Assert.assertTrue(functionalUtil.logout(browser));
+		
+		/* tutor adds a new post */
+		//Assert.assertTrue(functionalCourseUtil.backBlogEntry(tutor0));
+		Assert.assertTrue(functionalCourseUtil.editBlogEntry(tutor0,
+				CONCURRENT_CLEAR_CACHE_BLOG_POST2_TITLE, CONCURRENT_CLEAR_CACHE_BLOG_POST2_DESCRIPTION, CONCURRENT_CLEAR_CACHE_BLOG_POST2_CONTENT,
+				-1, null));
+		
+		/* student verifies title - month */
+		functionalUtil.idle(student0);
 		
+		StringBuffer selectorBuffer = new StringBuffer();
+		
+		selectorBuffer.append("xpath=(//ul[contains(@class, '")
+		.append(functionalCourseUtil.getBlogMonthCss())
+		.append("')]//li//a)");
+		
+		int iStop = student0.getXpathCount(selectorBuffer.toString()).intValue();
+		boolean foundTitleInMonth = false;
+		
+		for(int i = 0; i < iStop; i++){
+			functionalUtil.idle(student0);
+			
+			StringBuffer currentBuffer = new StringBuffer(selectorBuffer);
+			
+			currentBuffer.append('[')
+			.append(i + 1)
+			.append(']');
+			
+			student0.click(currentBuffer.toString());
+			
+			functionalUtil.idle(student0);
+			
+			if(student0.isTextPresent(CONCURRENT_CLEAR_CACHE_BLOG_POST2_TITLE)){
+				foundTitleInMonth = true;
+				break;
+			}
+		}
+		
+		Assert.assertTrue(foundTitleInMonth);
+		
+		/* student verifies title - year */
+		functionalUtil.idle(student0);
+		
+		selectorBuffer = new StringBuffer();
+		
+		selectorBuffer.append("xpath=(//div//a[contains(@class, '")
+		.append(functionalCourseUtil.getBlogYearCss())
+		.append("')])");
+		
+		iStop = student0.getXpathCount(selectorBuffer.toString()).intValue();
+		boolean foundTitleInYear = false;
+		
+		for(int i = 0; i < iStop; i++){
+			functionalUtil.idle(student0);
+			
+			StringBuffer currentBuffer = new StringBuffer(selectorBuffer);
+			
+			currentBuffer.append('[')
+			.append(i + 1)
+			.append(']');
+			
+			student0.click(currentBuffer.toString());
+			
+			functionalUtil.idle(student0);
+			
+			if(student0.isTextPresent(CONCURRENT_CLEAR_CACHE_BLOG_POST2_TITLE)){
+				foundTitleInYear = true;
+				break;
+			}
+		}
+		
+		Assert.assertTrue(foundTitleInYear);
+		
+		/* logout */
+		Assert.assertTrue(functionalUtil.logout(tutor0));
+		Assert.assertTrue(functionalUtil.logout(student0));
 	}
 	
 	@Test
@@ -183,10 +334,19 @@ public class FunctionalBlogTest {
 		student[1] = (Selenium) student1;
 		
 		final boolean[] success = new boolean[userCount];
-		Arrays.fill(success, false);
+		Arrays.fill(success, true);
+		
+		/* for syncing threads */
+		final ReentrantLock lock = new ReentrantLock();
+		final ReentrantLock subroutineLock = new ReentrantLock();
+		final Condition cond = lock.newCondition();
+		final boolean[] doSignal = new boolean[1];
+		doSignal[0] = false;
 		
-		List<Thread> threads = new ArrayList<Thread>();
+		final boolean[] finished = new boolean[userCount];
+		Arrays.fill(finished, false);
 		
+		/* students log in an visit blog */
 		for(int i = 0; i < userCount; i++){
 			final int index = i;
 			
@@ -203,19 +363,33 @@ public class FunctionalBlogTest {
 						functionalCourseUtil.openBlogWithoutBusinessPath(student[i], course.getRepoEntryKey(), 0);
 						functionalCourseUtil.openBlogEntry(student[i], 0);
 					}catch(Exception e){
-						// success[i] = false;
+						success[i] = false;
+					}finally{
+						finished[i] = true;
+						
+						lock.lock();
+						if(doSignal[0]){
+							cond.signal();
+						}
+						lock.unlock();
 					}
 				}
 				
 			});
 			
 			thread.start();
-			threads.add(thread);
 		}
 		
 		/* wait for browsers to be ready */
-		for(Thread currentThread: threads){
-			currentThread.join(60000);
+		lock.lock();
+		doSignal[0] = true;
+		
+		try{
+			while(ArrayUtils.contains(finished, false)){
+				cond.await();
+			}
+		}finally{
+			lock.unlock();
 		}
 		
 		/* edit blog as author */
@@ -223,7 +397,8 @@ public class FunctionalBlogTest {
 				null, null, CONCURRENT_RW_BLOG_NEW_CONTENT, 0, new BlogEdit[]{BlogEdit.CONTENT}));
 		
 		/* open entry by users */
-		threads = new ArrayList<Thread>();
+		Arrays.fill(finished, false);
+		doSignal[0] = false;
 		
 		for(int i = 0; i < userCount; i++){
 			final int index = i;
@@ -238,25 +413,40 @@ public class FunctionalBlogTest {
 				public void run() {
 					try{
 						functionalCourseUtil.backBlogEntry(student[i]);
-						functionalUtil.waitForPageToLoadContent(student[i], null, CONCURRENT_RW_BLOG_NEW_CONTENT, WaitLimitAttribute.VERY_SAVE, null, false);
+						functionalCourseUtil.openBlogEntry(student[i], 0);
+						functionalUtil.waitForPageToLoadContent(student[i], null, CONCURRENT_RW_BLOG_NEW_CONTENT, WaitLimitAttribute.VERY_SAVE, null, true);
 						functionalUtil.logout(student[i]);
-						success[i] = true;
 					}catch(Exception e){
-						// empty
+						success[i] = false;
+					}finally{
+						finished[i] = true;
+
+						lock.lock();
+						if(doSignal[0]){
+							cond.signal();
+						}
+						lock.unlock();
 					}
 				}
 				
 			});
 			
 			thread.start();
-			threads.add(thread);
 		}
 		
 		/* wait for browsers to be logged out */
-		for(Thread currentThread: threads){
-			currentThread.join(60000);
+		lock.lock();
+		doSignal[0] = true;
+		
+		try{
+			while(ArrayUtils.contains(finished, false)){
+				cond.await();
+			}
+		}finally{
+			lock.unlock();
 		}
 		
+		
 		Assert.assertFalse(ArrayUtils.contains(success, false));
 
 		Assert.assertTrue(functionalUtil.logout(browser));
diff --git a/src/test/java/org/olat/course/nodes/feed/blog.zip b/src/test/java/org/olat/course/nodes/feed/blog.zip
new file mode 100644
index 0000000000000000000000000000000000000000..3064933f8387d1e20e8ababb501af218cab5ed1d
GIT binary patch
literal 511
zcmWIWW@Zs#-~dADq98{GB)|b=r=_N*=vCzAFwSF0{BL6qWGR%T78Pga=h-S5>KQ00
zq~;~(r)1`(+bV^IxanFb+1qj1fHc{0DJa-vrr24SnHZZ|npv7!8XB6L+2{j>Kr$tj
z1*vv!nK`LJsm1xFMaijpPCowbHu_+3kb>O&l+>Ish*fq*Hu@-hu-PT4xt=M-`at{i
z!4?L1vvV9cFo$6=(Ao2VH~^=|*nqN`K<&l)j0_C_|ARCFMHpB?1Q6iYn46lCnTSgx
zBa;XNA~2C%1GWKTK9qz7ZGbnbE|4_LDIk|3z)c_%;%?&g2m!qaadLn+E7&+@24-;h
Jcmrh^7y!L}T$lg=

literal 0
HcmV?d00001

diff --git a/src/test/java/org/olat/util/FunctionalAdministrationSiteUtil.java b/src/test/java/org/olat/util/FunctionalAdministrationSiteUtil.java
index 655bd2d45b4..59cd38099ae 100644
--- a/src/test/java/org/olat/util/FunctionalAdministrationSiteUtil.java
+++ b/src/test/java/org/olat/util/FunctionalAdministrationSiteUtil.java
@@ -19,8 +19,10 @@
  */
 package org.olat.util;
 
+import org.junit.Assert;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.util.FunctionalUtil.OlatSite;
 
 import com.thoughtworks.selenium.Selenium;
 
@@ -124,6 +126,21 @@ public class FunctionalAdministrationSiteUtil {
 		}
 	}
 	
+	public enum SystemInformationTabs {
+		SESSIONS,
+		INFOMSG,
+		ERRORS,
+		LOGLEVELS,
+		SYSINFO,
+		SNOOP,
+		REQUESTLOGLEVEL,
+		USERSESSIONS,
+		LOCKS,
+		HIBERNATE,
+		CACHES,
+		BUILDINFO;
+	}
+	
 	private FunctionalUtil functionalUtil;
 	
 	public FunctionalAdministrationSiteUtil(FunctionalUtil functionalUtil){
@@ -138,9 +155,149 @@ public class FunctionalAdministrationSiteUtil {
 	 * @return true on success otherwise false
 	 */
 	public boolean openActionByMenuTree(Selenium browser, Object action){
-		//TODO:JK: implement me
+		functionalUtil.idle(browser);
+		
+		StringBuffer selectorBuffer;
+		
+		if(action instanceof AdministrationSiteAction){
+			 selectorBuffer = new StringBuffer();
+			
+			selectorBuffer.append("xpath=//li[contains(@class, '")
+			.append(((AdministrationSiteAction) action).getActionCss())
+			.append("')]//a[contains(@class, '")
+			.append(functionalUtil.getTreeLevel1Css())
+			.append("')]");
+		}else if(action instanceof SystemConfigurationAction){
+			/* check if collapsed */
+			 selectorBuffer = new StringBuffer();
+			
+			 selectorBuffer.append("xpath=//li[contains(@class, '")
+			 .append(AdministrationSiteAction.CONFIGURATION.getActionCss())
+			 .append("')]//a[contains(@class, '")
+			 .append(functionalUtil.getTreeLevelOpenCss())
+			 .append("')]");
+			 
+			if(browser.isElementPresent(selectorBuffer.toString())){
+				browser.click(selectorBuffer.toString());
+				functionalUtil.idle(browser);
+			}
+
+			/* click */
+			selectorBuffer = new StringBuffer();
+			
+			selectorBuffer.append("xpath=//li[contains(@class, '")
+			.append(((SystemConfigurationAction) action).getActionCss())
+			.append("')]//a[contains(@class, '")
+			.append(functionalUtil.getTreeLevel2Css())
+			.append("')]");
+		}else if(action instanceof SystemMaintenanceAction){
+			/* check if collapsed */
+			 selectorBuffer = new StringBuffer();
+			
+			 selectorBuffer.append("xpath=//li[contains(@class, '")
+			 .append(AdministrationSiteAction.MAINTENANCE.getActionCss())
+			 .append("')]//a[contains(@class, '")
+			 .append(functionalUtil.getTreeLevelOpenCss())
+			 .append("')]");
+			 
+			if(browser.isElementPresent(selectorBuffer.toString())){
+				browser.click(selectorBuffer.toString());
+				functionalUtil.idle(browser);
+			}
+
+			/* click */
+			selectorBuffer.append("xpath=//li[contains(@class, '")
+			.append(((SystemMaintenanceAction) action).getActionCss())
+			.append("')]//a[contains(@class, '")
+			.append(functionalUtil.getTreeLevel2Css())
+			.append("')]");
+		}else if(action instanceof CustomizingAction){
+			/* check if collapsed */
+			 selectorBuffer = new StringBuffer();
+			
+			 selectorBuffer.append("xpath=//li[contains(@class, '")
+			 .append(AdministrationSiteAction.CUSTOMIZATION.getActionCss())
+			 .append("')]//a[contains(@class, '")
+			 .append(functionalUtil.getTreeLevelOpenCss())
+			 .append("')]");
+			 
+			if(browser.isElementPresent(selectorBuffer.toString())){
+				browser.click(selectorBuffer.toString());
+				functionalUtil.idle(browser);
+			}
+
+			/* click */
+			selectorBuffer.append("xpath=//li[contains(@class, '")
+			.append(((CustomizingAction) action).getActionCss())
+			.append("')]//a[contains(@class, '")
+			.append(functionalUtil.getTreeLevel2Css())
+			.append("')]");
+		}else{
+			return(false);
+		}
+		
+		functionalUtil.waitForPageToLoadElement(browser, selectorBuffer.toString());
+		browser.click(selectorBuffer.toString());
+		
+		return(true);
+	}
+	
+	/**
+	 * Clears the specified cache which matches to keys. 
+	 * 
+	 * @param browser
+	 * @param keys
+	 * @return
+	 */
+	public boolean clearCache(Selenium browser, String[] keys){
+		if(!functionalUtil.openSite(browser, OlatSite.ADMINISTRATION)){
+			return(false);
+		}
+		
+		if(!openActionByMenuTree(browser, AdministrationSiteAction.INFORMATION)){
+			return(false);
+		}
+		
+		if(!functionalUtil.openContentTab(browser, SystemInformationTabs.CACHES.ordinal())){
+			return(false);
+		}
+		
+		functionalUtil.idle(browser);
+		
+		/* click show all*/
+		StringBuffer selectorBuffer = new StringBuffer();
+		
+		selectorBuffer.append("xpath=//a[contains(@class, '")
+		.append(functionalUtil.getTableAllCss())
+		.append("')]");
+		
+		if(browser.isElementPresent(selectorBuffer.toString())){
+			browser.click(selectorBuffer.toString());
+		}
+		
+		boolean success = true;
+		
+		/* clear appropriate cache */
+		for(String currentKey: keys){
+			functionalUtil.idle(browser);
+			
+			selectorBuffer = new StringBuffer();
+			
+			selectorBuffer.append("xpath=//table//tr//td[contains(@text, '")
+			.append(currentKey)
+			.append("\\n")
+			.append("')]");
+			
+			if(browser.isElementPresent(selectorBuffer.toString())){
+				selectorBuffer.append("/../td[last()]//a");
+				
+				browser.click(selectorBuffer.toString());
+			}else{
+				success = false;
+			}
+		}
 		
-		return(false);
+		return(success);
 	}
 
 	public FunctionalUtil getFunctionalUtil() {
diff --git a/src/test/java/org/olat/util/FunctionalCourseUtil.java b/src/test/java/org/olat/util/FunctionalCourseUtil.java
index 96f16b82d60..d7131575f8b 100644
--- a/src/test/java/org/olat/util/FunctionalCourseUtil.java
+++ b/src/test/java/org/olat/util/FunctionalCourseUtil.java
@@ -1326,6 +1326,11 @@ public class FunctionalCourseUtil {
 		if(!openBlogWithoutBusinessPath(browser, courseId, nth))
 			return(false);
 
+		return(editBlogEntry(browser, title, description, content, entry, edit));
+	}
+	
+	public boolean editBlogEntry(Selenium browser,
+			String title, String description, String content, int entry, BlogEdit[] edit){
 		StringBuffer selectorBuffer = new StringBuffer();
 		
 		if(edit == null){
diff --git a/src/test/java/org/olat/util/FunctionalUtil.java b/src/test/java/org/olat/util/FunctionalUtil.java
index 1da9a422b46..5aeb1193f07 100644
--- a/src/test/java/org/olat/util/FunctionalUtil.java
+++ b/src/test/java/org/olat/util/FunctionalUtil.java
@@ -126,11 +126,19 @@ public class FunctionalUtil {
 	public final static String BUTTON_CSS = "b_button";
 	public final static String BUTTON_DIRTY_CSS = "b_button_dirty";
 	public final static String BACK_BUTTON_CSS = "b_link_back";
+	
 	public final static String TABLE_FIRST_CHILD_CSS = "b_first_child";
 	public final static String TABLE_LAST_CHILD_CSS = "b_last_child";
+	public final static String TABLE_ALL_CSS = "b_table_page_all";
+	
 	public final static String TREE_NODE_ANCHOR_CSS = "x-tree-node-anchor";
 	public final static String TREE_NODE_CSS = "x-tree-node";
 	public final static String TREE_NODE_LOADING_CSS = "x-tree-node-loading";
+	public final static String TREE_LEVEL1_CSS = "b_tree_l1";
+	public final static String TREE_LEVEL2_CSS = "b_tree_l2";
+	public final static String TREE_LEVEL_OPEN_CSS = "b_tree_level_open";
+	public final static String TREE_LEVEL_CLOSE_CSS = "b_tree_level_close";
+	
 	public final static String WINDOW_CLOSE_LINK_CSS = "b_link_close";
 	
 	public final static String FORM_SAVE_XPATH = "//button[@type='button' and last()]";
@@ -174,14 +182,22 @@ public class FunctionalUtil {
 	private String mceContentBodyCss;
 	
 	private String windowCloseLinkCss;
+	
 	private String buttonCss;
 	private String buttonDirtyCss;
 	private String backButtonCss;
+	
 	private String tableFirstChildCss;
 	private String tableLastChildCss;
+	private String tableAllCss;
+	
 	private String treeNodeAnchorCss;
 	private String treeNodeCss;
 	private String treeNodeLoadingCss;
+	private String treeLevel1Css;
+	private String treeLevel2Css;
+	private String treeLevelOpenCss;
+	private String treeLevelCloseCss;
 	
 	private FunctionalHomeSiteUtil functionalHomeSiteUtil;
 	private FunctionalGroupsSiteUtil functionalGroupsSiteUtil;
@@ -242,15 +258,23 @@ public class FunctionalUtil {
 		
 		mceContentBodyCss = MCE_CONTENT_BODY_CSS;
 		
-		windowCloseLinkCss = WINDOW_CLOSE_LINK_CSS; 
+		windowCloseLinkCss = WINDOW_CLOSE_LINK_CSS;
+		
 		buttonCss = BUTTON_CSS;
 		buttonDirtyCss = BUTTON_DIRTY_CSS;
 		backButtonCss = BACK_BUTTON_CSS;
+		
 		tableFirstChildCss = TABLE_FIRST_CHILD_CSS;
 		tableLastChildCss = TABLE_LAST_CHILD_CSS;
+		tableAllCss = TABLE_ALL_CSS;
+		
 		treeNodeAnchorCss = TREE_NODE_ANCHOR_CSS;
 		treeNodeCss = TREE_NODE_CSS;
 		treeNodeLoadingCss = TREE_NODE_LOADING_CSS;
+		treeLevel1Css = TREE_LEVEL1_CSS;
+		treeLevel2Css = TREE_LEVEL2_CSS;
+		treeLevelOpenCss = TREE_LEVEL_OPEN_CSS;
+		treeLevelCloseCss = TREE_LEVEL_CLOSE_CSS;
 		
 		functionalHomeSiteUtil = new FunctionalHomeSiteUtil(this);
 		functionalGroupsSiteUtil = new FunctionalGroupsSiteUtil(this);
@@ -435,12 +459,14 @@ public class FunctionalUtil {
 			}
 			
 			if((content == null && source == null) || 
-					(flags != null && source != null && !ArrayUtils.contains(flags, WaitForContentFlag.EQUALS) && source.contains(content)) ||
-					content.equals(source)){
+					(flags != null && source != null && !ArrayUtils.contains(flags, WaitForContentFlag.EQUALS) && content.equals(source)) ||
+					source.contains(content)){
 				log.info("found content after " + (currentTime - startTime) + "ms");
 				
 				/* go back to toplevel */
-				browser.selectFrame("relative=top");
+				if(iframeSelectors != null){
+					browser.selectFrame("relative=top");
+				}
 				
 				return(true);
 			}
@@ -1599,6 +1625,14 @@ public class FunctionalUtil {
 		this.tableLastChildCss = tableLastChildCss;
 	}
 
+	public String getTableAllCss() {
+		return tableAllCss;
+	}
+
+	public void setTableAllCss(String tableAllCss) {
+		this.tableAllCss = tableAllCss;
+	}
+
 	public String getTreeNodeAnchorCss() {
 		return treeNodeAnchorCss;
 	}
@@ -1622,4 +1656,36 @@ public class FunctionalUtil {
 	public void setTreeNodeLoadingCss(String treeNodeLoadingCss) {
 		this.treeNodeLoadingCss = treeNodeLoadingCss;
 	}
+
+	public String getTreeLevel1Css() {
+		return treeLevel1Css;
+	}
+
+	public void setTreeLevel1Css(String treeLevel1Css) {
+		this.treeLevel1Css = treeLevel1Css;
+	}
+
+	public String getTreeLevel2Css() {
+		return treeLevel2Css;
+	}
+
+	public void setTreeLevel2Css(String treeLevel2Css) {
+		this.treeLevel2Css = treeLevel2Css;
+	}
+
+	public String getTreeLevelOpenCss() {
+		return treeLevelOpenCss;
+	}
+
+	public void setTreeLevelOpenCss(String treeLevelOpenCss) {
+		this.treeLevelOpenCss = treeLevelOpenCss;
+	}
+
+	public String getTreeLevelCloseCss() {
+		return treeLevelCloseCss;
+	}
+
+	public void setTreeLevelCloseCss(String treeLevelCloseCss) {
+		this.treeLevelCloseCss = treeLevelCloseCss;
+	}
 }
diff --git a/src/test/java/org/olat/util/FunctionalVOUtil.java b/src/test/java/org/olat/util/FunctionalVOUtil.java
index 5b617c0ef7e..b142e409eec 100644
--- a/src/test/java/org/olat/util/FunctionalVOUtil.java
+++ b/src/test/java/org/olat/util/FunctionalVOUtil.java
@@ -19,6 +19,7 @@
  */
 package org.olat.util;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -37,6 +38,7 @@ import javax.ws.rs.core.UriBuilder;
 
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.entity.mime.HttpMultipartMode;
@@ -49,6 +51,7 @@ import org.olat.core.logging.Tracing;
 import org.olat.restapi.RestConnection;
 import org.olat.restapi.support.vo.CourseVO;
 import org.olat.restapi.support.vo.RepositoryEntryVO;
+import org.olat.user.restapi.RolesVO;
 import org.olat.user.restapi.UserVO;
 
 public class FunctionalVOUtil {
@@ -90,13 +93,13 @@ public class FunctionalVOUtil {
 	}
 	
 	/**
+	 * Creates the selenium test users with random passwords and
+	 * writes it to credentials.properties.
+	 * 
 	 * @param deploymentUrl
 	 * @param count
 	 * @throws IOException
 	 * @throws URISyntaxException
-	 * 
-	 * Creates the selenium test users with random passwords and
-	 * writes it to credentials.properties.
 	 */
 	public List<UserVO> createTestUsers(URL deploymentUrl, int count) throws IOException, URISyntaxException{
 		RestConnection restConnection = new RestConnection(deploymentUrl);
@@ -140,11 +143,40 @@ public class FunctionalVOUtil {
 		return(user);
 	}
 	
-	public void addUserToSysGroup(UserVO user, SysGroups group){
-		//TODO:JK: implement me
+	public List<UserVO> createTestAuthors(URL deploymentUrl, int count) throws IOException, URISyntaxException{
+		/* create ordinary users */
+		List<UserVO> user = createTestUsers(deploymentUrl, count);
+
+		/* make a tutor */
+		RestConnection restConnection = new RestConnection(deploymentUrl);
+
+		restConnection.login(getUsername(), getPassword());
+		
+		for(UserVO currentUser: user){
+			/* retrieve roles */
+			URI request = UriBuilder.fromUri(deploymentUrl.toURI()).path("/users/" + currentUser.getKey() + "/roles").build();
+			HttpGet getMethod = restConnection.createGet(request, MediaType.APPLICATION_JSON, true);
+			HttpResponse response = restConnection.execute(getMethod);
+			assertEquals(200, response.getStatusLine().getStatusCode());
+			RolesVO roles = restConnection.parse(response, RolesVO.class);
+			
+			/* send appropriate role */
+			roles.setAuthor(true);
+			roles.setUserManager(true);
+			
+			request = UriBuilder.fromUri(deploymentUrl.toURI()).path("/users/" + currentUser.getKey() + "/roles").build();
+			HttpPost postMethod = restConnection.createPost(request, MediaType.APPLICATION_JSON, true);
+			restConnection.addJsonEntity(postMethod, roles);
+			response = restConnection.execute(postMethod);
+			assertEquals(200, response.getStatusLine().getStatusCode());
+		}
+		
+		return(user);
 	}
 	
 	/**
+	 * Imports the specified course via REST.
+	 * 
 	 * @param deploymentUrl
 	 * @param path
 	 * @param filename
@@ -153,8 +185,6 @@ public class FunctionalVOUtil {
 	 * @return CourseVO
 	 * @throws URISyntaxException
 	 * @throws IOException
-	 * 
-	 * Imports the specified course via REST.
 	 */
 	public CourseVO importCourse(URL deploymentUrl, String path, String filename, String resourcename, String displayname) throws URISyntaxException, IOException{
 		//TODO:JK: may be replace this code because the course will just be deployed and not imported 
@@ -188,53 +218,60 @@ public class FunctionalVOUtil {
 		return(vo);
 	}
 	
+	/**
+	 * 
+	 * @param deploymentUrl
+	 * @return
+	 * @throws URISyntaxException
+	 * @throws IOException
+	 */
 	public CourseVO importEmptyCourse(URL deploymentUrl) throws URISyntaxException, IOException{
 		return(importCourse(deploymentUrl, "/org/olat/course/Empty_Course.zip", "Empty_Course.zip", "Empty Course", "Empty Course"));
 	}
 	
 	/**
+	 * Imports the "All Elements Course" via REST.
+	 * 
 	 * @param deploymentUrl
 	 * @return
 	 * @throws URISyntaxException
 	 * @throws IOException
-	 * 
-	 * Imports the "All Elements Course" via REST.
 	 */
 	public CourseVO importAllElementsCourse(URL deploymentUrl) throws URISyntaxException, IOException{
 		return(importCourse(deploymentUrl, "/org/olat/course/All_Elements_Course_Without_External_Content.zip", "All_Elements_Course_Without_External_Content.zip", "All Elements Course Without External Content", "All Elements Course Without External Content"));
 	}
 
 	/**
+	 * Imports the "Course including Forum" via REST.
+	 * 
 	 * @param deploymentUrl
 	 * @return
 	 * @throws URISyntaxException
 	 * @throws IOException
-	 * 
-	 * Imports the "Course including Forum" via REST.
 	 */
 	public CourseVO importCourseIncludingForum(URL deploymentUrl) throws URISyntaxException, IOException{
 		return(importCourse(deploymentUrl, "/org/olat/portfolio/Course_including_Forum.zip", "Course_including_Forum.zip", "Course including Forum", "Course including Forum"));
 	}
 	
 	/**
+	 * Imports the "Course including Blog" via REST.
+	 * 
 	 * @param deploymentUrl
 	 * @return
 	 * @throws URISyntaxException
 	 * @throws IOException
-	 * 
-	 * Imports the "Course including Blog" via REST.
 	 */
 	public CourseVO importCourseIncludingBlog(URL deploymentUrl) throws URISyntaxException, IOException{
 		return(importCourse(deploymentUrl, "/org/olat/portfolio/Course_including_Blog.zip", "Course_including_Blog.zip", "Course including Blog", "Course including Blog"));
 	}
 	
 	/**
+	 * imports a wiki via REST.
+	 * 
 	 * @param deploymentUrl
 	 * @return
 	 * @throws URISyntaxException
 	 * @throws IOException
-	 * 
-	 * imports a wiki via REST.
 	 */
 	public RepositoryEntryVO importWiki(URL deploymentUrl) throws URISyntaxException, IOException{
 		URL wikiUrl = FunctionalVOUtil.class.getResource("/org/olat/portfolio/wiki.zip");
@@ -268,17 +305,17 @@ public class FunctionalVOUtil {
 	}
 	
 	/**
+	 * Imports a blog via REST.
+	 * 
 	 * @param deploymentUrl
 	 * @param login
 	 * @param password
 	 * @return
 	 * @throws URISyntaxException
 	 * @throws IOException
-	 * 
-	 * Imports a blog via REST.
 	 */
-	public RepositoryEntryVO importBlog(URL deploymentUrl) throws URISyntaxException, IOException{
-		URL blogUrl = FunctionalVOUtil.class.getResource("/org/olat/portfolio/blog.zip");
+	public RepositoryEntryVO importBlog(URL deploymentUrl, String path, String filename, String resourcename, String displayname) throws URISyntaxException, IOException{
+		URL blogUrl = FunctionalVOUtil.class.getResource(path);
 		Assert.assertNotNull(blogUrl);
 		
 		File blog = new File(blogUrl.toURI());
@@ -291,9 +328,9 @@ public class FunctionalVOUtil {
 		HttpPut method = restConnection.createPut(request, MediaType.APPLICATION_JSON, true);
 		MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
 		entity.addPart("file", new FileBody(blog));
-		entity.addPart("filename", new StringBody("blog.zip"));
-		entity.addPart("resourcename", new StringBody("Blog"));
-		entity.addPart("displayname", new StringBody("Blog"));
+		entity.addPart("filename", new StringBody(filename));
+		entity.addPart("resourcename", new StringBody(resourcename));
+		entity.addPart("displayname", new StringBody(displayname));
 		entity.addPart("access", new StringBody("3"));
 		method.setEntity(entity);
 		
-- 
GitLab