Beta.cs 21.9 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
/************************************************************************************************
* All code in this file is under the MS-RL License (https://opensource.org/licenses/MS-RL)      *
* By using the code in this file in any way, you agree to the above license terms.              *
* Copyright (C) LIGHTS IN LINE AB (https://www.lightsinline.se)                                 *
* Repository, Wiki, Issue tracker and more at https://git.lightsinline.se/products/VSTT-Plugins *
*                                                                                               *
* Contributors                                                                                  *
* LIGHTS IN LINE AB                                                                             *
* SWEDBANK AB                                                                                   *
* SKATTEVERKET                                                                                  *
************************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.WebTesting;
using System.ComponentModel;
using Microsoft.VisualStudio.TestTools.WebTesting.Rules;
using System.Text.RegularExpressions;
using System.IO;
using System.IO.Compression;
using Microsoft.VisualStudio.TestTools.LoadTesting;

namespace LIL_VSTT_Plugins
{
    [DisplayName("Add Parameter To Reporting Name")]
    [Description("This request plugin will add the specified parameter to the end of the reporting name of the request")]
    public class AddParameterToReportingName : WebTestRequestPlugin
    {
        [DisplayName("Parameter Name"), DefaultValue(""), Description("Name of the parameter which value should be added to the reporting name.")]
        public string ParameterName { get; set; }
        [DisplayName("Add to empty Report Name"), DefaultValue(false), Description("The parameter value will be added to report name even if it not set. Warning, will also set all redirects to the same name.")]
        public bool AddToEmptyReportName { get; set; }
        [DisplayName("Run on dependent requests"), DefaultValue(false), Description("The parameter value will be set as the report name on all dependent requests (only works in loadtests)")]
        public bool RunOnDependents { get; set; }
        public override void PreRequest(object sender, PreRequestEventArgs e)
        {
            base.PreRequest(sender, e);
            if (e.WebTest.Context.ContainsKey(ParameterName) && (!String.IsNullOrEmpty(e.Request.ReportingName) || AddToEmptyReportName))
            {
                e.Request.ReportingName = e.Request.ReportingName + e.WebTest.Context[ParameterName].ToString();
            }
        }
        public override void PostRequest(object sender, PostRequestEventArgs e)
        {
            base.PostRequest(sender, e);
            if (RunOnDependents && e.WebTest.Context.ContainsKey(ParameterName))
            {
                foreach (WebTestRequest w in e.Request.DependentRequests)
                {
                    w.ReportingName = e.WebTest.Context[ParameterName].ToString();
                }
            }
        }
    }

    [DisplayName("Stop Here After")]
    [Description("Add this plugin to a request in order to force the webtest to stop after the request has finished.")]
    public class StopHereAfter : WebTestRequestPlugin
    {
        [DisplayName("Fail the test"), DefaultValue(true), Description("If set to true will fail the test iteration.")]
        public bool FailTest { get; set; }
        public override void PostRequest(object sender, PostRequestEventArgs e)
        {
            base.PostRequest(sender, e);
            e.WebTest.AddCommentToResult("STOP HERE AFTER: WebTest will stop after the next request because of Request Plugin 'Stop Here After' was added to it.");
            if (FailTest)
            {
                e.WebTest.Outcome = Outcome.Fail;
                e.WebTest.AddCommentToResult("FAIL THE TEST: WebTest will fail after the next request because of option to fail the test was set to true in the plugin.");
            }
            e.WebTest.Stop();
        }
    }

    [DisplayName("Zip File Upload"), Description("Creates an ZIP archive of each of the files to be uploaded using the files name and adding .zip. Warning, uses %TEMP% for temp storage.")]
    public class ZipFileUploadBeforePost : WebTestRequestPlugin
    {
        Queue<String> deleteDirs = new Queue<string>();
        public override void PreRequest(object sender, PreRequestEventArgs e)
        {
            if (e.Request.Body.GetType() == typeof(FormPostHttpBody)) {
                FormPostHttpBody body = (FormPostHttpBody)e.Request.Body;
                foreach (FormPostParameter param in body.FormPostParameters) {
                    if(param.GetType() == typeof(FileUploadParameter))
                    {
                        FileUploadParameter fparam = (FileUploadParameter)param;
                        String tempDir = Path.GetTempPath() + "\\" + Guid.NewGuid().ToString();
                        Directory.CreateDirectory(tempDir);
                        deleteDirs.Enqueue(tempDir);
                        Directory.CreateDirectory(tempDir + @"\ZipDir");
                        File.Copy(fparam.FileName, tempDir + @"\ZipDir\" + Path.GetFileName(fparam.FileName));
                        ZipFile.CreateFromDirectory(tempDir + @"\ZipDir", tempDir + "\\" + Path.GetFileName(fparam.FileName) + ".zip");

                        fparam.FileName = tempDir + "\\" + Path.GetFileName(fparam.FileName) + ".zip";
                        fparam.FileUploadName = fparam.FileUploadName + ".zip";
                    }
                }
            }
            base.PreRequest(sender, e);
        }

        public override void PostRequest(object sender, PostRequestEventArgs e)
        {
            foreach (String dir in deleteDirs) Directory.Delete(dir, true);
            deleteDirs.Clear();
            base.PostRequest(sender, e);
        }
    }

    [DisplayName("Set Request Think Time"), Description("Changes the thinktime on requests with a set thinktime over 0 to the value of the ThinkTime context parameter")]
    public class SetRequestThinkTime : WebTestPlugin
    {
        [DisplayName("Debug"), DefaultValue(false), Description("Debug logging when thinktime is set on requests")]
        public bool DebugMode { get; set; }
        public override void PreRequest(object sender, PreRequestEventArgs e)
        {
            WebTestContext ctx = e.WebTest.Context;

            if (ctx.ContainsKey("ThinkTime"))
            {
                int tt = Int32.Parse(ctx["ThinkTime"].ToString());
                if (e.Request.ThinkTime > 0)
                {
                    if (DebugMode) e.WebTest.AddCommentToResult("Setting Think Time to " + tt);
                    e.Request.ThinkTime = tt;
                }
            }
        }
    }

    [DisplayName("Clear Cookies"), Description("Clears all cookies from previous iterations of this webtest")]
    public class ClearCookies : WebTestPlugin
    {
        public override void PreWebTest(object sender, PreWebTestEventArgs e)
        {
            e.WebTest.Context.CookieContainer = new System.Net.CookieContainer();
        }
    }

    [DisplayName("Think Time Emulator 10/190"), Description("Sets a context parameter named ThinkTime in each starting test to a random value between 10%-190% of the specified value.")]
    public class ThinkTimeEmulator10190 : ILoadTestPlugin
    {
        [DisplayName("ThinkTime"), DefaultValue(35), Description("The Think Time to be used seconds. Default is 35.")]
        public int ThinkTime { get; set; }
        [DisplayName("Minimum"), DefaultValue(10), Description("Percentage of ThinkTime to be used as the minimum value")]
        public int Min { get; set; }
        [DisplayName("Maximum"), DefaultValue(190), Description("Percentage of ThinkTime to be used as the maximum value")]
        public int Max { get; set; }
        [DisplayName("Only on Tests matching"), DefaultValue(""), Description("Regular expression matching only the tests you want this plugin instance to work on. If it does not match the test name, the plugin instance will not set the ThinkTime")]
        public string RegExTestName { get; set; }
        [DisplayName("Only on Scenarios matching"), DefaultValue(""), Description("Regular expression matching only the scenarios you want this plugin instance to work on. If it does not match the scenario name, the plugin instance will not set the ThinkTime")]
        public string RegExScenarioName { get; set; }

        //store the load test object.  
        LoadTest mLoadTest;
        Random rnd = new Random();
        Regex rxTestName = null;
        Regex rxScenarioName = null;

        public void Initialize(LoadTest loadTest)
        {
            mLoadTest = loadTest;
            if(!String.IsNullOrEmpty(RegExTestName))
                rxTestName = new Regex(RegExTestName);
            if (!String.IsNullOrEmpty(RegExScenarioName))
                rxScenarioName = new Regex(RegExScenarioName);

            // Sanity checks
            if (ThinkTime == 0) ThinkTime = 35;
            if (Min > Max) { Min = 100; Max = 100; }
            if (Min == 0) Min = 10;
            if (Max == 0) Max = 190;

            //connect to the TestStarting event.
            mLoadTest.TestStarting += new EventHandler<TestStartingEventArgs>(mLoadTest_TestStarting);
        }


        void mLoadTest_TestStarting(object sender, TestStartingEventArgs e)
        {
            if (rxTestName != null && rxTestName.IsMatch(e.TestName) != true) return;
            if (rxScenarioName != null && rxScenarioName.IsMatch(e.ScenarioName) != true) return;

            // Set the think time parameter in the tests context to a new value
            double tt = ThinkTime;
            double min = Min / 100d;
            min = tt * min;
            double max = Max / 100d;
            max = tt * max;
            e.TestContextProperties.Add("ThinkTime", rnd.Next((int)min, (int)max));
        }
    }

    [DisplayName("Set Cookie from Query String"), Description("Sets a cookie with given name from the value of a given Query String parameter if found in the current request")]
    public class SetCookieFromQueryString : WebTestPlugin
    {
        [DisplayName("Cookie name"), Description("Name of the cookie to set")]
        public String CookieName { get; set; }

        [DisplayName("Query String parameter name"), Description("Name of the query string parameter to look for")]
        public String ParamName { get; set; }

        public override void PreRequest(object sender, PreRequestEventArgs e)
        {
            base.PreRequest(sender, e);
            QueryStringParameterCollection col = e.Request.QueryStringParameters;
            for(int x=0; x < col.Count; x++)
            {
                if (col[x].Name == ParamName)
                {
                    e.Request.Cookies.Add(new System.Net.Cookie(CookieName, col[x].Value));
                    return;
                }
            }
        }

        public override void PostRequest(object sender, PostRequestEventArgs e)
        {
            base.PostRequest(sender, e);
            if (e.Request.HasDependentRequests)
            {
                foreach (WebTestRequest item in e.Request.DependentRequests)
                {
                    QueryStringParameterCollection col = item.QueryStringParameters;
                    for (int x = 0; x < col.Count; x++)
                    {
                        if (col[x].Name == ParamName)
                        {
                            item.Cookies.Add(new System.Net.Cookie(CookieName, col[x].Value));
                        }
                    }
                }
            }
        }
    }

    [DisplayName("Add Header"), Description("Adds the specified header to all requests matching a given URL regex")]
    public class AddHeader : WebTestPlugin
    {
        [DisplayName("Header Name"), Description("Name of the header to set")]
        public String HeaderName { get; set; }

        [DisplayName("Header Value"), Description("Value of the header to set")]
        public String HeaderValue { get; set; }

        [DisplayName("URL RegEx"), Description("Regular Expression to match on the URL. Empty matches all requests.")]
        public String RegEx { get; set; }

        public override void PreRequest(object sender, PreRequestEventArgs e)
        {
            base.PreRequest(sender, e);
            if (Regex.Match(e.Request.Url, RegEx).Success || RegEx.Length == 0)
            {
                e.Request.Headers.Add(HeaderName, HeaderValue);
            }
        }

        public override void PostRequest(object sender, PostRequestEventArgs e)
        {
            base.PostRequest(sender, e);
            if (e.Request.HasDependentRequests)
            {
                foreach (WebTestRequest item in e.Request.DependentRequests)
                {
                    if (Regex.Match(item.Url, RegEx).Success || RegEx.Length == 0)
                    {
                        item.Headers.Add(HeaderName, HeaderValue);
                    }
                }
            }
        }
    }

    [DisplayName("Log Context Arrays to File"), Description("Logs the specified context parameter arrays to file after each test iteration, one row for each array value")]
    public class LogContextArrayToFile : WebTestPlugin
    {
        [DisplayName("Parameter arrays"), Description("Comma separated list of array parameters to log")]
        public String Params { get; set; }

        [DisplayName("File Name"), Description("The file name to use for logging")]
        public String Filename { get; set; }

        private bool header = true;
        private string[] paramlist = null;

        public override void PostWebTest(object sender, PostWebTestEventArgs e)
        {
            if (Params.Length == 0 || Filename.Length == 0) return;
            if (paramlist == null) paramlist = Params.Split(',');
            if (header)
            {
                File.AppendAllText(Filename, Params + "\r\n");
                header = false;
            }
            // Check that the first param array has a count
            int count = 0;
            WebTestContext ctx = e.WebTest.Context;
            if (ctx.ContainsKey(paramlist[0] + "_count")) count = Int32.Parse(ctx[paramlist[0] + "_count"].ToString());
            for (int i = 1; i <= count; i++)
            {
                string row = "";
                foreach (string param in paramlist)
                {
                    if (row.Length > 0) row += ",";
                    if (ctx.ContainsKey(param + "_" + i)) row += ctx[param + "_" + i].ToString();
                }
                File.AppendAllText(Filename, row + "\r\n");
            }
            base.PostWebTest(sender, e);
        }
    }

    [DisplayName("Log Context to File"), Description("Logs the specified context parameters to file after each test iteration")]
    public class LogContextToFile : WebTestPlugin
    {
        [DisplayName("Regular Expression"), Description("The RegEx to use to match context parameter names")]
        public String myRegex { get; set; }

        [DisplayName("File Name"), Description("The file name to use for logging")]
        public String Filename { get; set; }

        [DisplayName("Autogenerate File Name"), Description("Automatically generate a filename using the test name and user id from the load test")]
        public bool autoFilename { get; set; }

        [DisplayName("Write header"), DefaultValue(false), Description("Writes the parameter names as a header. Will write a new header for each user (dont use in loadtest)")]
        public bool useHeader { get; set; }

        [DisplayName("Log if passed"), DefaultValue(true), Description("Logs the parameters if the webtest passed")]
        public bool logPassed { get; set; }

        [DisplayName("Log if failed"), DefaultValue(true), Description("Logs the parameters if the webtest failed")]
        public bool logFailed { get; set; }

        private bool header = true;

        public override void PostWebTest(object sender, PostWebTestEventArgs e)
        {
            String completeFileName = Filename + e.WebTest.Context.WebTestUserId + ".log";
            if (autoFilename)
            {
                completeFileName = e.WebTest.Name + e.WebTest.Context.WebTestUserId + ".log";
            }
            if ((e.WebTest.Outcome == Outcome.Pass && logPassed) || (e.WebTest.Outcome == Outcome.Fail && logFailed)) {
                string row = "";
                string hrow = "";

                foreach (KeyValuePair<string, object> pair in e.WebTest.Context)
                {
                    if (Regex.Match(pair.Key, myRegex).Success)
                    {
                        if (header && useHeader)
                        {
                            if (hrow.Length == 0) hrow = pair.Key.ToString();
                            else hrow += "," + pair.Key.ToString();
                        }
                        if (row.Length == 0) row = pair.Value.ToString();
                        else row += "," + pair.Value.ToString();
                    }
                }
                if (header && useHeader)
                {
                    File.AppendAllText(completeFileName, hrow + "\r\n");
                    header = false;
                }
                File.AppendAllText(completeFileName, row + "\r\n");
                base.PostWebTest(sender, e);
            }
        }
    }

    [DisplayName("Multi Regular Expression"), Description("Saves all matches for a regular expression including a count")]
    public class MultiRegExExtract : ExtractionRule
    {
        [DisplayName("Regular Expression"), Description("The RegEx to use, supports grouping, for example (.+?)")]
        public String Regex { get; set; }

        [DisplayName("Group Name"), DefaultValue("1"), Description("The group to use as the value for the parameter, default is 1")]
        public String GroupName { get; set; }

        [DisplayName("Pass if not found"), DefaultValue(false), Description("Wherever this extration rule should pass even if no matches are found. Default false.")]
        public bool PassIfNotFound { get; set; }

        public override void Extract(object sender, ExtractionEventArgs e)
        {
            MatchCollection matches = System.Text.RegularExpressions.Regex.Matches(
                e.Response.BodyString,
                Regex,
                RegexOptions.IgnoreCase);
            int index = 0;
            foreach (Match match in matches)
            {
                index++;
                e.WebTest.Context[ContextParameterName + "_" + index] = match.Groups[GroupName].Value;
            }
            if (index > 0)
            {
                e.WebTest.Context[ContextParameterName + "_count"] = index.ToString();
                e.Success = true;
                e.Message = "Found " + index + " matches";
            }
            else
            {
                e.WebTest.Context[ContextParameterName + "_count"] = index.ToString();
                e.Success = PassIfNotFound;
                e.Message = "No matches found. Body size is " + e.Response.BodyString.Length;
            }
        }
    }

    [DisplayName("Extract Count"), Description("Counts all matches for a given RegEx and saves the count to the specified parameter")]
    public class ExtractCount : ExtractionRule
    {
        [DisplayName("Regular Expression"), Description("The RegEx to use")]
        public String Regex { get; set; }

        [DisplayName("Fail on zero count"), DefaultValue(false), Description("Wherever this extration rule should fail if no matches are found")]
        public bool FailOnZero { get; set; }

        public override void Extract(object sender, ExtractionEventArgs e)
        {
            MatchCollection matches = System.Text.RegularExpressions.Regex.Matches(
                e.Response.BodyString,
                Regex,
                RegexOptions.IgnoreCase);
            
            e.WebTest.Context[ContextParameterName] = matches.Count.ToString();
            e.Message = "Found " + matches.Count + " matches";
            if (FailOnZero && matches.Count == 0)
            {
                e.Success = false;
            }
        }
    }

    [DisplayName("Regular Expression Loop"), Description("Loop Condition that matches once on each regexp in previous response body")]
    public class RegExpLoop : ConditionalRule
    {
        private Int32 CurrentMatch { get; set; }
        private String LastUrl { get; set; }
        private MatchCollection Matches { get; set; }

        [DisplayName("Context parameter"), IsContextParameterName(true),
        Description("Name of context parameter where the current value should be set in each loop")]
        public string ContextParameterName { get; set; }

        [DisplayName("Regex"), Description("The RegEx to use, supports grouping, for example (.+?)")]
        public String Regex { get; set; }

        [DisplayName("GroupName"), Description("The group to use as the value for the parameter if several are specified, default is 1")]
        public String GroupName { get; set; }

        // Methods
        public override void CheckCondition(object sender, ConditionalEventArgs e)
        {
            if (CurrentMatch < Matches.Count)
            {
                e.WebTest.Context[ContextParameterName] =
                Matches[CurrentMatch].Groups[GroupName].Value;
                e.IsMet = true;
                CurrentMatch++;
                return;
            }

            e.IsMet = false;
        }

        public override void Initialize(object sender, ConditionalEventArgs e)
        {
            CurrentMatch = 0;
            Matches = System.Text.RegularExpressions.Regex.Matches(
            e.WebTest.LastResponse.BodyString,
            Regex,
            RegexOptions.IgnoreCase);
        }

        public override string StringRepresentation()
        {
            return "Regex condition " + Regex;
        }
    }
}