Index: /CKTester/_dev/gen-profile.bat
===================================================================
--- /CKTester/_dev/gen-profile.bat	(revision 4100)
+++ /CKTester/_dev/gen-profile.bat	(revision 4100)
@@ -0,0 +1,15 @@
+@echo off
+
+if "%ANT_HOME%"=="" goto noAntHome
+if "%JAVA_HOME%"=="" goto noJavaHome
+ant -buildfile gen-profile.xml
+goto end
+
+:noAntHome
+echo ANT_HOME environment variable is not set
+goto end
+
+:noJavaHome
+echo JAVA_HOME environment variable is not set
+
+:end
Index: /CKTester/_dev/gen-profile.js
===================================================================
--- /CKTester/_dev/gen-profile.js	(revision 4100)
+++ /CKTester/_dev/gen-profile.js	(revision 4100)
@@ -0,0 +1,86 @@
+/*
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+
+/**
+ * @fileOverview
+ * Ant scripts for auto-generate the testing fort profile file( profile.js ) from the testing cell files.  
+ */
+importClass( java.io.File );
+importClass( java.io.File );
+importClass( java.io.FileReader );
+importClass( java.io.BufferedReader );
+importClass( org.apache.tools.ant.util.FileUtils );
+
+( function()
+{
+	function echo( msg, file )
+	{
+		var echo = genProfileProject.createTask( "echo" );
+		echo.setEncoding( 'utf-8' );
+		echo.setMessage( msg );
+		file && echo.setFile( file );
+		echo.perform();
+	}
+
+	function getFileContent( file )
+	{
+		return String( FileUtils.readFully( new BufferedReader( new FileReader( file ) ) ) );
+	}
+
+	// Modify the followng to define working directories and paths.
+	var testsRoot = '../../',
+		profileTemplateFileName = 'profile.js.tpl',
+		outputProfileFileName = 'profile.js',
+		includesWilcard = '**/*.html',
+		excludesWilcard = '*,**/_asset/**/*,**/_editor/**/*,**/cktester/**/*';
+
+	// Grouping all the cell files. 
+	var fs = project.createDataType( "fileset" );
+	fs.setDir( new File( testsRoot ) );
+	fs.setIncludes( includesWilcard );
+	fs.setExcludes( excludesWilcard );
+	var srcFiles = fs.getDirectoryScanner( project ).getIncludedFiles(),
+		allCells = "";
+
+	// Processing cell content.
+	for ( var i=0; i < srcFiles.length; i++ )
+	{
+	  // get the values via Java API
+	  var dir = fs.getDir( project ),
+		  filePath = srcFiles[ i ],
+		  file = new File( dir, filePath ),
+		  content = getFileContent( file ),
+		  tags = [];
+
+		// Grab tags defined inside test html files.
+		content.replace( /<meta\s*name=\"tags\"\s*content=\"([^'\"]*?)\"\s*>/gi,
+			function( match, g1 ){ tags = g1.split( ',' ); } );
+
+		// Figure out the relative path to CKTester runner.
+		var deliminator =  File.separator,
+			relativePath = String( file.getPath() ).replace( /\.\w+$/, '' ).split( deliminator );
+		relativePath.shift();
+		relativePath = relativePath.join( '/' );
+
+		allCells += "\t[ "
+			+ "'" + relativePath + "'"	// cell paths
+			+ ( tags.length ? ",[ '"+ tags.join( "','" ) + "' ] " : '' )	// cell tags
+			+ " ]"
+			+ ( ( i == srcFiles.length - 1 ) ? '\n' : ',\n' );
+	}
+	allCells = '\n[\n' + allCells + ']';
+
+	// Output the cells into the testing profile file.
+	var template = getFileContent( new File( testsRoot, profileTemplateFileName ) ),
+		result = template.replace( '${cells}', allCells ),
+		profileFile = new File( testsRoot, outputProfileFileName );
+	echo( result, profileFile );
+} )();
+
+
+
+
+
+
Index: /CKTester/_dev/gen-profile.xml
===================================================================
--- /CKTester/_dev/gen-profile.xml	(revision 4100)
+++ /CKTester/_dev/gen-profile.xml	(revision 4100)
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<project name="genProfileProject" basedir="." default="main">
+  <target name="main">
+    <script language="javascript" src="gen-profile.js"></script>
+  </target>
+</project>
Index: /CKTester/cell.js
===================================================================
--- /CKTester/cell.js	(revision 4099)
+++ /CKTester/cell.js	(revision 4100)
@@ -1,20 +1,32 @@
+/*
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+
 ( function()
 {
-	function whereIsRunner()
+
+	function redirectToFort()
 	{
-		// Dunno where's the profile :( guess it!
-		function whereIsProfile()
+		var tags, metas = document.getElementsByTagName( 'meta' );
+		for( var i = 0 ; i < metas.length ; i++ )
 		{
-			return '../profile.js';
+			if ( metas[ i ].name == 'tags' )
+				tags = metas[ i ].content;
+		}
+		
+		var scripts = document.getElementsByTagName( 'script' ), src, newUrl;
+		for( var i = 0 ; i < scripts.length ; i++ )
+		{
+			if ( ( src = scripts [ i ].src )
+				&& ( ( newUrl = src.match(/(.*)\bcell\.js/)[ 1 ] ) != -1 ) )
+			{
+				newUrl = newUrl + 'fort.html?'
+					+ '&cells=' + document.location.href
+					+ '&tags=' + tags || "";
+			}
 		}
 
-		var scripts = document.getElementsByTagName( 'script' ), src, runnerURL;
-		for( var i = 0 ; i < scripts.length ; i++ )
-		{
-			if( ( src = scripts [ i ].src )
-				&& ( ( runnerURL = src.match( /(.*)\bcell\.js/ )[ 1 ] ) != -1 ) )
-				return runnerURL + 'fort.html?profile=' + whereIsProfile()
-						+ '&' + 'cells=' + document.location.href;
-		}
+		document.location.href = newUrl;
 	}
 
@@ -24,10 +36,10 @@
 	if ( !runnerWindow )
 	{
-		document.location.href = whereIsRunner();
+		redirectToFort();
 		return;
 	}
 
 	var mode = ( runnerWindow == opener ) ? 'standalone' : 'managed',
-		runner = runnerWindow.CKTester.runner,
+		runner = runnerWindow.CKTester.fort,
 		cell = runner.currentCell,
 		dependencies = cell && cell.environment;
@@ -51,5 +63,5 @@
 			{
 				getAbsolutePath : runnerWindow.Function.prototype.curry.call(
-						 runnerWindow.CKTester.runner.getAbsolutePath, window ),
+						 runnerWindow.CKTester.fort.getAbsolutePath, window ),
 
 				start : mode == 'managed' ?
Index: /CKTester/fort.html
===================================================================
--- /CKTester/fort.html	(revision 4099)
+++ /CKTester/fort.html	(revision 4100)
@@ -13,5 +13,7 @@
 	<div id="tagsAutoComplete">
 		<label for="tagsInput">Tags:</label>
-		<input id="tagsInput" type="text" /><input id="start-test-btn" type="button" value="Run"/>
+		<input id="tagsInput" type="text" />
+		<input id="start-test-btn" type="button" value="Run"/>
+		<input id="reload-fort-btn" type="button" value="Reset" />
 		<div id="tagsContainer"></div>
 	</div>
Index: /CKTester/fort.js
===================================================================
--- /CKTester/fort.js	(revision 4099)
+++ /CKTester/fort.js	(revision 4100)
@@ -1,13 +1,18 @@
-// Shortcuts
+/*
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
 
 var CKTester =
 {
-	runner :
+	fort :
 	{
 		registeredCells : [],
-		cellVariables : {},
+		pendingCells : [],		// The cells that are pending to run.
+		profile : {}, 			// Override in 'profile.js' file.
+		variables : {}, 		// Override in 'variables.js' file.
 		criterias : [],
 /*
-		currentCell : // The solved current running cell.
+		currentCell : 			// The solved current running cell.
 		{
 			path : '../editor/tt/unit/htmldataprocessor',
@@ -25,4 +30,7 @@
 		testFrame : null,
 
+		defaultProfilePath : '../profile.js',
+		defaultVariables : '../variables.js',
+
 		init : function()
 		{
@@ -34,20 +42,42 @@
 		bootstrap : function()
 		{
-			// Receive 'profile' and 'criteria' from URL parts.
-			var url = window.location.search,
-				pattern = '=(.*?)(?:&|$)',
-				match,
-				profile = ( match = url.match( new RegExp( 'profile' + pattern ) ) ) && match[ 1 ],
-				criterias = ( match = url.match( new RegExp( 'cells' + pattern ) ) ) && match[ 1 ].split( ',' );
-
-			profile && document.write( '<script src="' + profile + '" type="text/javascript"><\/script>' );
-			criterias && ( this.criterias = criterias );
-			window.onload = function()
-			{
+			function getUrlParam( paramName )
+			{
+				var pattern = '=(.*?)(?:&|%26|$)',
+					match;
+				return ( match = query.match( new RegExp( paramName + pattern ) ) ) && match[ 1 ]
+			}
+
+			var url = window.location.href,
+				baseUrl = url.substr( 0, url.indexOf( '?' ) ),
+				query = window.location.search, match,
+				// Receive 'profile','variables' and 'criteria' from URL parts.
+				profile = getUrlParam( 'profile' ) || this.defaultProfilePath,
+				variables = getUrlParam( 'variables' ) || this.defaultVariables,
+				criterias = ( getUrlParam( 'cells' ) || '' ).split( ',' ),
+				// Single cell if cell absolute path is specified as criteria.   
+				singleCell = criterias[ 0 ] && criterias[ 0 ].indexOf( '://' ) != -1,
+					// Force cell path as relative to fort root.
+					cellPath = singleCell && this.getRelativePath( baseUrl, criterias[ 0 ].replace( /\.\w+$/, '' ) ),
+					// Receive tags defined inside the cell itself.
+					cellTags = singleCell && ( getUrlParam( 'tags' ) || '' ).split( ',' );
+
+			// Include variables and profile definitions.
+			document.write( '<script src="' + profile + '" type="text/javascript"><\/script>' );
+			document.write( '<script src="' + variables + '" type="text/javascript"><\/script>' );
+
+			$( document ).observe( 'dom:loaded', function()
+			{
+				this.profile = this.getProfile();
+				!singleCell && ( this.criterias = criterias );
+				// Ignore the profile cell definition, if only with the specified single cell .
+				singleCell && ( this.profile.cells = [ [ cellPath, cellTags ] ] );
+
 				this.init();
-				// Auto start only if there's criteria found.
-				if ( this.criterias.length )
-					this.rullAll();
-			}.bind( this );
+
+				// Auto start only if there's criteria found OR it's single cell.
+				if ( this.criterias.length || singleCell )
+					this.runAll();
+			}.bind( this ) );
 		},
 
@@ -57,4 +87,5 @@
 			var tagsDataSource = new YAHOO.util.LocalDataSource( this.registeredTags );
 			var startButton = $( 'start-test-btn' ),
+				resetButton = $( 'reload-fort-btn' ),
 				tagsInput = $( 'tagsInput' ),
 				tagsAutoComp = new YAHOO.widget.AutoComplete( tagsInput,"tagsContainer", tagsDataSource ),
@@ -62,22 +93,25 @@
 				{
 					this.criterias = tagsInput.value.split( ' ' ).without( '' );
-					this.rullAll();
-				}.bind( this );
-
+					this.runAll();
+				}.bind( this ),
+				reset = function()
+				{
+					document.location.search = '';
+				};
+			
 			tagsAutoComp.delimChar = ' ';
 			tagsAutoComp.typeAhead = true;
 			tagsAutoComp.useShadow = true;
 			startButton.observe( 'click', start );
+			resetButton.observe( 'click', reset );
 		},
 
 		processProfile : function ()
 		{
-			var profile = this.getProfile(),
+			var profile = this.profile,
 				cells = profile.cells,
 				resolvers = profile.cellResolvers,
 				tags = [];
 
-			// Receive path variables first, cell resolving may need them.
-			this.variables = profile.variables;
 			// Merge resolvers in profile.
 			this.cellResolvers = this.cellResolvers.concat( resolvers );
@@ -115,15 +149,15 @@
 		},
 
-		rullAll : function()
+		runAll : function()
 		{
 			this.reset();
 			if ( !this.registeredCells.length )
 			{
-				throw 'None test cell existed in the profile!';
+				throw 'None test cell found!';
 				return;
 			}
 
 			var criterias = this.criterias;
-			this.pendingCells = this.registeredCells.findAll( function( cell ){
+			this.pendingCells = criterias.length ? this.registeredCells.findAll( function( cell ){
 
 				return ( criterias.indexOf( cell.path ) != -1
@@ -132,5 +166,6 @@
 								return criterias.indexOf( tag ) != -1;
 							} ) );
-			} );
+			} ) : this.registeredCells.clone();
+			this.pendingCells.each( this.resolvePath, this );
 			this.runCell();
 		},
@@ -139,5 +174,5 @@
 		{
 			this.currentCell = this.pendingCells.shift();
-			this.currentCell && this.testFrame.setAttribute( 'src', this.currentCell.path );
+			this.currentCell && this.testFrame.setAttribute( 'src', this.currentCell.path + '.html' );
 		},
 
@@ -146,5 +181,5 @@
 			this.currentCell = cell;
 			// Open the test frame in a new popup window.
-			window.open( cell.path );
+			window.open( cell.path + '.html' );
 		},
 
@@ -186,5 +221,7 @@
 			var div = $('testLogger').lastChild;
 			div.innerHTML = html;
-			this.setupCellLink( div, cell );
+			$( div ).childElements().grep( new Selector( 'a' ) )[ 0 ]
+					.observe( 'click', this.runSingleCell.curry( cell ).bind( this ) );
+
 
 			this.totalFailed += failed;
@@ -214,5 +251,5 @@
 		{
 			for( var i in this.variables )
-				str = str.replace( new RegExp( "\\$({|%7B)" + i + "(}|%7D)", 'g' ), this.variables[ i ] ) ;
+				str = str.replace( new RegExp( "\\$({|%7B)" + i + "(}|%7D)", 'g' ), this.variables[ i ] || '' ) ;
 			return str ;
 		},
@@ -220,7 +257,43 @@
 		getAbsolutePath : function( win, path )
 		{
-			var temp = new ( win || window ).Image();
-			temp.src = path;
-			return temp.src;
+			// If this is not a full or absolute path.
+			if ( path.indexOf('://') == -1 && path.indexOf( '/' ) !== 0 )
+			{
+				var temp = new ( win || window ).Image();
+				temp.src = path;
+				return temp.src;
+			}
+			else
+				return path;
+		},
+
+		// Calculate file with 'pathB's relative path to file with 'pathA'.
+		getRelativePath : function ( pathA, pathB )
+		{
+			var deliminator = /[\/\\]+/,
+				pathA = pathA.split( deliminator ),
+				pathB = pathB.split( deliminator ),
+				length = pathA.length - 1,
+				result = [];
+			for( var i = 0 ; i < length; i++ )
+			{
+				if( pathA[ i ] != pathB [ i ] )
+					break;
+			}
+			if( length - i > 0 )
+			{
+				result = result.concat( $R( 1, length - i ).collect( function( n ){ return '..' ; } ) );
+			}
+			result = result.concat( pathB.slice( i ) );
+			return result.join( '/' );
+		},
+
+		// Figure out the real paths of cell's environments.
+		resolvePath : function ( cell )
+		{
+			for ( var i = 0 ; i < cell.environment.length ; i++ )
+			{
+				cell.environment[ i ] = this.getAbsolutePath( null, this.replaceVars( cell.environment[ i ] ) );
+			}
 		},
 
@@ -251,15 +324,4 @@
 										'runners/selenium/extensions.js' ] );
 				cell.environment = env.concat( cell.environment );
-			},
-
-			// Figure out absolute paths of cell and it's environment.
-			// Note: Execute this resolver at last.
-			function z( cell )
-			{
-				cell.path = this.getAbsolutePath( null, cell.path + '.html' );
-				for ( var i = 0 ; i < cell.environment.length ; i++ )
-				{
-					cell.environment[ i ] = this.getAbsolutePath( null, this.replaceVars( cell.environment[ i ] ) );
-				}
 			}
 		]
@@ -268,3 +330,3 @@
 };
 
-CKTester.runner.bootstrap();
+CKTester.fort.bootstrap();
