Background
In Visual Studio Webtest, you can set Browser's User-Agent.
Use this function, you can test your website simulating various Browsers(IE, FireFox, Opera, Safari...).
But we can see results only in Visual Studio Test Results window which is using IE as rendering engine.
Viewing in other browser, you have to extract website response from TestResult file.
TestResult File Structure
Response information is stored in WebRequestResult element like this.
<WebRequestResult exceptionMessage="" run="0" submitted="true" cached="false"
isRedirectFollow="false" requestBodyBytesHandle="-2" responseBytesHandle="0">
<Request url="http://doda.jp/">http://doda.jp/" command="GET / HTTP/1.1" encoding="utf-8">
<Headers>User-Agent : Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
Accept : */*
Accept-Language : ja,en-us;q=0.5
Accept-Encoding : GZIP
Host : doda.jp
Connection : Keep-Alive
</Headers>
</Request>
<Response url="http://doda.jp/index.html">http://doda.jp/index.html" contentType="text/html; charset=Shift_JIS"
statusLine="HTTP/1.1 200 OK" pageTime="0.995" time="0.104" statusCodeString="200 OK" contentLength="45922">
<Headers>Content-Location : http://doda.jp/index.html
Vary : Accept-Encoding
Accept-Ranges : bytes
Content-Length : 45922
Content-Type : text/html
Date : Wed, 05 Dec 2007 15:27:03 GMT
ETag : "092f3fd3b35c81:23fe"
Last-Modified : Sun, 02 Dec 2007 23:35:16 GMT
Set-Cookie : BIGipServerwww_80=220795052.20480.0000; path=/
Server : Microsoft-IIS/6.0
</Headers>
</Response>
And Response Body is stored in Entry element like this.
<Entry handle="0" bytes="H4sIAAAAAAAEAO29B2AcSZYlJi9tyn
WebRequestResult references Entry by responseBytesHandle attribute and handle attribute.
And Entry element's bytes attribute is Compressed by GZip and then Encoded by Base64.
Overview
1.Extracting Response Body from TestResult.
2.Writing Response Body to files.
3.Rewrite link url.
Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.IO.Compression;
using System.Xml;
namespace Rei
{
public class Converter
{
private Encoding defaultEncoding = Encoding.GetEncoding("Shift-JIS");
private Regex srcRegex = new Regex("src=\"(?<url>[^\"]+)\"", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private Regex hrefRegex = new Regex("href=\"(?<url>[^\"]+)\"", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public void Convert(string inputFileName,string outputDirName)
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(inputFileName);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmldoc.NameTable);
nsmgr.AddNamespace("a", "http:");
List<WebRequestResult> reqResults = new List<WebRequestResult>();
foreach (XmlNode node in xmldoc.SelectNodes("//a:WebRequestResult",nsmgr))
{
string responseHandle = node.Attributes["responseBytesHandle"].Value;
XmlNode childNode = node.SelectSingleNode("a:Request",nsmgr);
string url = childNode.Attributes["url"].Value;
WebRequestResult reqresult = new WebRequestResult();
reqresult.ResponseHandle = responseHandle;
reqresult.Url = url;
XmlNode responseNode = node.SelectSingleNode("a:Response", nsmgr);
reqresult.ContentType = responseNode.Attributes["contentType"].Value;
reqResults.Add(reqresult);
}
foreach (WebRequestResult reqresult in reqResults)
{
string handle = reqresult.ResponseHandle;
XmlNode entryNode = xmldoc.SelectSingleNode("//a:Entry[@handle =\"" + handle + "\"]",nsmgr);
if (entryNode != null)
{
reqresult.EncodedResponse = entryNode.Attributes["bytes"].Value;
}
}
foreach (WebRequestResult reqresult in reqResults)
{
string outFileName = outputDirName + "\\" + reqresult.fileName;
using(FileStream fs = new FileStream(outFileName,FileMode.Create))
{
if (!reqresult.ContentType.StartsWith("text"))
{
byte[] bytes = reqresult.Response;
fs.Write(bytes, 0, bytes.Length);
fs.Close();
}
else
{
Encoding enc = null;
if (reqresult.Charset != string.Empty)
{
enc = Encoding.GetEncoding(reqresult.Charset);
}
else
{
enc = defaultEncoding;
}
string body = enc.GetString(reqresult.Response, 0, reqresult.Response.Length);
Dictionary<string, string> urlMap = new Dictionary<string, string>();
MatchCollection srcMatches = srcRegex.Matches(body);
foreach (Match match in srcMatches)
{
string src = match.Groups["url"].Value;
if (!urlMap.ContainsKey(src))
{
for (int i = 0; i < reqResults.Count; i++)
{
if (reqResults[i].Url.EndsWith(src))
{
urlMap.Add(src, reqResults[i].fileName);
break;
}
}
}
if (src.StartsWith("/") || src.StartsWith("."))
{
Uri uri = new Uri(new Uri(reqresult.Url), src);
if (!urlMap.ContainsKey(src))
{
for (int i = 0; i < reqResults.Count; i++)
{
if (reqResults[i].Url.EndsWith(uri.AbsoluteUri))
{
urlMap.Add(src, reqResults[i].fileName);
break;
}
}
}
}
}
MatchCollection hrefMatches = hrefRegex.Matches(body);
foreach (Match match in hrefMatches)
{
string src = match.Groups["url"].Value;
if (!urlMap.ContainsKey(src))
{
for (int i = 0; i < reqResults.Count; i++)
{
if (reqResults[i].Url.EndsWith(src))
{
urlMap.Add(src, reqResults[i].fileName);
break;
}
}
}
if (src.StartsWith("/") || src.StartsWith("."))
{
Uri uri = new Uri(new Uri(reqresult.Url), src);
if (!urlMap.ContainsKey(src))
{
for (int i = 0; i < reqResults.Count; i++)
{
if (reqResults[i].Url.EndsWith(uri.AbsoluteUri))
{
urlMap.Add(src, reqResults[i].fileName);
break;
}
}
}
}
}
foreach (string beforeUrl in urlMap.Keys)
{
Regex regex1 = new Regex("src=\"" + beforeUrl + "\"", RegexOptions.IgnoreCase);
Regex regex2 = new Regex("href=\"" + beforeUrl + "\"", RegexOptions.IgnoreCase);
body = regex1.Replace(body, "src=\"" + urlMap[beforeUrl] + "\"");
body = regex2.Replace(body, "href=\"" + urlMap[beforeUrl] + "\"");
}
byte[] converted = enc.GetBytes(body);
fs.Write(converted, 0, converted.Length);
fs.Close();
}
}
}
}
}
internal class WebRequestResult
{
private static Regex charsetRegex = new Regex(@"charset=(?<charsetName>.+)", RegexOptions.Compiled);
public string Charset
{
get {
MatchCollection matches = charsetRegex.Matches(this.contentType);
if (matches.Count > 0)
{
return matches[0].Groups["charsetName"].Value;
}
return string.Empty;
}
}
private string contentType;
public string ContentType
{
get { return this.contentType; }
set {this.contentType = value;}
}
private string url;
public string Url
{
get { return this.url; }
set { this.url = value; }
}
private string guid = Guid.NewGuid().ToString();
public string fileName
{
get
{
string name = Url;
name = name.Replace("//", "_");
name = name.Replace('/', '_');
name = name.Replace(':', '_');
name = name.Replace('*', '_');
name = name.Replace('"', '_');
name = name.Replace('<', '_');
name = name.Replace('>', '_');
name = name.Replace('|', '_');
name = name.Replace('?', '_');
if (name.EndsWith("\\"))
{
name = name.Substring(0, name.Length - 1);
}
if (name.Length > 100)
{
return this.guid;
}
return name;
}
}
private string responseHandle;
public string ResponseHandle
{
get { return this.responseHandle; }
set { this.responseHandle = value; }
}
private string encodedResponse;
public string EncodedResponse
{
get { return this.encodedResponse; }
set { this.encodedResponse = value; }
}
public byte[] Response
{
get {
if (this.encodedResponse == null || this.encodedResponse == string.Empty)
{
return new byte[0];
}
byte[] compressed = Convert.FromBase64String(this.encodedResponse);
MemoryStream ms = new MemoryStream();
ms.Write(compressed, 0, compressed.Length);
ms.Position = 0;
GZipStream gzipStream = new GZipStream(ms, CompressionMode.Decompress,true);
MemoryStream msOut = new MemoryStream();
int uncompressedByte = -1;
while ((uncompressedByte = gzipStream.ReadByte()) != -1)
{
msOut.WriteByte((byte)uncompressedByte);
}
return msOut.ToArray();
}
}
}
}