ScenarioModel.js 15.1 KB
Newer Older
Q
q4speed 已提交
1 2 3
import {
  Element,
  TestElement,
Q
q4speed 已提交
4
  HashTree,
Q
q4speed 已提交
5 6 7
  TestPlan,
  ThreadGroup,
  HeaderManager,
Q
q4speed 已提交
8
  HTTPSamplerProxy,
Q
q4speed 已提交
9
  HTTPSamplerArguments,
Q
q4speed 已提交
10 11
  Arguments,
  DurationAssertion,
Q
q4speed 已提交
12 13
  ResponseCodeAssertion,
  ResponseDataAssertion,
Q
NaN  
q4speed 已提交
14
  ResponseHeadersAssertion,
Q
q4speed 已提交
15
  RegexExtractor, JSONPostProcessor, XPath2Extractor,
Q
q4speed 已提交
16 17
} from "./JMX";

Q
q4speed 已提交
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
export const uuid = function () {
  let d = new Date().getTime()
  let d2 = (performance && performance.now && (performance.now() * 1000)) || 0;
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    let r = Math.random() * 16;
    if (d > 0) {
      r = (d + r) % 16 | 0;
      d = Math.floor(d / 16);
    } else {
      r = (d2 + r) % 16 | 0;
      d2 = Math.floor(d2 / 16);
    }
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
}
Q
q4speed 已提交
33 34

export const BODY_TYPE = {
Q
q4speed 已提交
35 36 37
  KV: "KeyValue",
  FORM_DATA: "Form Data",
  RAW: "Raw"
Q
q4speed 已提交
38 39 40
}

export const ASSERTION_TYPE = {
Q
q4speed 已提交
41 42
  TEXT: "Text",
  REGEX: "Regex",
Q
q4speed 已提交
43
  DURATION: "Duration"
Q
q4speed 已提交
44 45
}

Q
q4speed 已提交
46 47 48 49 50 51
export const ASSERTION_REGEX_SUBJECT = {
  RESPONSE_CODE: "Response Code",
  RESPONSE_HEADERS: "Response Headers",
  RESPONSE_DATA: "Response Data"
}

Q
q4speed 已提交
52 53 54 55 56 57
export const EXTRACT_TYPE = {
  REGEX: "Regex",
  JSON_PATH: "JSONPath",
  XPATH: "XPath"
}

Q
q4speed 已提交
58
export class BaseConfig {
Q
q4speed 已提交
59 60 61 62 63 64 65 66 67 68 69

  set(options) {
    options = this.initOptions(options)

    for (let name in options) {
      if (options.hasOwnProperty(name)) {
        if (!(this[name] instanceof Array)) {
          this[name] = options[name];
        }
      }
    }
Q
q4speed 已提交
70 71
  }

Q
q4speed 已提交
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
  sets(types, options) {
    options = this.initOptions(options)
    if (types) {
      for (let name in types) {
        if (types.hasOwnProperty(name) && options.hasOwnProperty(name)) {
          options[name].forEach((o) => {
            this[name].push(new types[name](o));
          })
        }
      }
    }
  }

  initOptions(options) {
    return options || {};
  }
Q
q4speed 已提交
88 89 90 91

  isValid() {
    return true;
  }
Q
q4speed 已提交
92 93 94 95 96
}

export class Test extends BaseConfig {
  constructor(options) {
    super();
97
    this.type = "MS API CONFIG";
Q
q4speed 已提交
98
    this.version = '1.0.0';
Q
q4speed 已提交
99
    this.id = uuid();
Q
q4speed 已提交
100 101
    this.name = undefined;
    this.projectId = undefined;
Q
q4speed 已提交
102
    this.scenarioDefinition = [];
103
    this.schedule = {};
Q
q4speed 已提交
104

Q
q4speed 已提交
105 106
    this.set(options);
    this.sets({scenarioDefinition: Scenario}, options);
Q
q4speed 已提交
107 108
  }

109 110 111 112 113 114 115 116 117 118
  export() {
    let obj = {
      type: this.type,
      version: this.version,
      scenarios: this.scenarioDefinition
    };

    return JSON.stringify(obj);
  }

Q
q4speed 已提交
119
  initOptions(options) {
Q
q4speed 已提交
120 121 122 123
    options = options || {};
    options.scenarioDefinition = options.scenarioDefinition || [new Scenario()];
    return options;
  }
Q
q4speed 已提交
124

Q
q4speed 已提交
125 126 127 128 129 130 131 132 133
  isValid() {
    for (let i = 0; i < this.scenarioDefinition.length; i++) {
      if (this.scenarioDefinition[i].isValid()) {
        return this.projectId && this.name;
      }
    }
    return false;
  }

Q
q4speed 已提交
134 135 136 137 138 139
  toJMX() {
    return {
      name: this.name + '.jmx',
      xml: new JMXGenerator(this).toXML()
    };
  }
Q
q4speed 已提交
140 141
}

Q
q4speed 已提交
142
export class Scenario extends BaseConfig {
Q
q4speed 已提交
143
  constructor(options) {
Q
q4speed 已提交
144
    super();
Q
q4speed 已提交
145 146
    this.name = undefined;
    this.url = undefined;
Q
q4speed 已提交
147
    this.variables = [];
Q
q4speed 已提交
148 149
    this.headers = [];
    this.requests = [];
C
chenjianxing 已提交
150
    this.environmentId = undefined;
Q
q4speed 已提交
151

Q
q4speed 已提交
152
    this.set(options);
Q
q4speed 已提交
153
    this.sets({variables: KeyValue, headers: KeyValue, requests: Request}, options);
Q
q4speed 已提交
154 155
  }

Q
q4speed 已提交
156
  initOptions(options) {
Q
q4speed 已提交
157 158 159
    options = options || {};
    options.requests = options.requests || [new Request()];
    return options;
Q
q4speed 已提交
160
  }
Q
q4speed 已提交
161 162 163 164

  clone() {
    return new Scenario(this);
  }
Q
q4speed 已提交
165 166 167

  isValid() {
    for (let i = 0; i < this.requests.length; i++) {
C
chenjianxing 已提交
168 169
      if (!this.requests[i].isValid()) {
        return false;
Q
q4speed 已提交
170 171
      }
    }
C
chenjianxing 已提交
172 173 174 175
    if (!this.name) {
      return false;
    }
    return true;
Q
q4speed 已提交
176
  }
Q
q4speed 已提交
177 178
}

Q
q4speed 已提交
179
export class Request extends BaseConfig {
Q
q4speed 已提交
180
  constructor(options) {
Q
q4speed 已提交
181
    super();
Q
q4speed 已提交
182 183
    this.name = undefined;
    this.url = undefined;
C
chenjianxing 已提交
184
    this.path = undefined;
Q
q4speed 已提交
185
    this.method = undefined;
Q
q4speed 已提交
186 187
    this.parameters = [];
    this.headers = [];
Q
q4speed 已提交
188 189 190
    this.body = undefined;
    this.assertions = undefined;
    this.extract = undefined;
C
chenjianxing 已提交
191 192
    this.environment = undefined;
    this.useEnvironment = undefined;
Q
q4speed 已提交
193

Q
q4speed 已提交
194 195
    this.set(options);
    this.sets({parameters: KeyValue, headers: KeyValue}, options);
Q
q4speed 已提交
196 197
  }

Q
q4speed 已提交
198
  initOptions(options) {
Q
q4speed 已提交
199
    options = options || {};
Q
q4speed 已提交
200
    options.method = options.method || "GET";
Q
q4speed 已提交
201 202
    options.body = new Body(options.body);
    options.assertions = new Assertions(options.assertions);
Q
q4speed 已提交
203
    options.extract = new Extract(options.extract);
Q
q4speed 已提交
204
    return options;
Q
q4speed 已提交
205
  }
Q
q4speed 已提交
206 207

  isValid() {
C
chenjianxing 已提交
208
    return ((!this.useEnvironment && !!this.url) || (this.useEnvironment && !!this.path && this.environment)) && !!this.method
Q
q4speed 已提交
209
  }
Q
q4speed 已提交
210 211
}

Q
q4speed 已提交
212
export class Body extends BaseConfig {
Q
q4speed 已提交
213
  constructor(options) {
Q
q4speed 已提交
214
    super();
Q
q4speed 已提交
215 216
    this.type = undefined;
    this.raw = undefined;
Q
q4speed 已提交
217 218
    this.kvs = [];

Q
q4speed 已提交
219 220
    this.set(options);
    this.sets({kvs: KeyValue}, options);
Q
q4speed 已提交
221 222
  }

Q
q4speed 已提交
223 224 225 226 227 228 229 230 231 232
  isValid() {
    if (this.isKV()) {
      return this.kvs.some(kv => {
        return kv.isValid();
      })
    } else {
      return !!this.raw;
    }
  }

Q
q4speed 已提交
233 234 235 236 237
  isKV() {
    return this.type === BODY_TYPE.KV;
  }
}

Q
q4speed 已提交
238
export class KeyValue extends BaseConfig {
Q
q4speed 已提交
239 240 241 242 243 244 245 246 247 248 249
  constructor() {
    let options, key, value;
    if (arguments.length === 1) {
      options = arguments[0];
    }

    if (arguments.length === 2) {
      key = arguments[0];
      value = arguments[1];
    }

Q
q4speed 已提交
250
    super();
Q
q4speed 已提交
251 252
    this.name = key;
    this.value = value;
Q
q4speed 已提交
253

Q
q4speed 已提交
254
    this.set(options);
Q
q4speed 已提交
255
  }
Q
q4speed 已提交
256 257 258 259

  isValid() {
    return !!this.name || !!this.value;
  }
Q
q4speed 已提交
260 261
}

Q
q4speed 已提交
262
export class Assertions extends BaseConfig {
Q
q4speed 已提交
263
  constructor(options) {
Q
q4speed 已提交
264
    super();
Q
q4speed 已提交
265 266
    this.text = [];
    this.regex = [];
Q
q4speed 已提交
267
    this.duration = undefined;
Q
q4speed 已提交
268

Q
q4speed 已提交
269
    this.set(options);
Q
q4speed 已提交
270
    this.sets({text: Text, regex: Regex}, options);
Q
q4speed 已提交
271 272
  }

Q
q4speed 已提交
273
  initOptions(options) {
Q
q4speed 已提交
274
    options = options || {};
Q
q4speed 已提交
275
    options.duration = new Duration(options.duration);
Q
q4speed 已提交
276
    return options;
Q
q4speed 已提交
277 278 279
  }
}

Q
q4speed 已提交
280
export class AssertionType extends BaseConfig {
Q
q4speed 已提交
281
  constructor(type) {
Q
q4speed 已提交
282
    super();
Q
q4speed 已提交
283 284 285 286 287 288 289
    this.type = type;
  }
}

export class Text extends AssertionType {
  constructor(options) {
    super(ASSERTION_TYPE.TEXT);
Q
q4speed 已提交
290 291 292
    this.subject = undefined;
    this.condition = undefined;
    this.value = undefined;
Q
q4speed 已提交
293

Q
q4speed 已提交
294
    this.set(options);
Q
q4speed 已提交
295 296 297 298 299 300
  }
}

export class Regex extends AssertionType {
  constructor(options) {
    super(ASSERTION_TYPE.REGEX);
Q
q4speed 已提交
301 302 303
    this.subject = undefined;
    this.expression = undefined;
    this.description = undefined;
Q
q4speed 已提交
304

Q
q4speed 已提交
305
    this.set(options);
Q
q4speed 已提交
306
  }
Q
q4speed 已提交
307 308 309 310

  isValid() {
    return !!this.subject && !!this.expression;
  }
Q
q4speed 已提交
311 312
}

Q
q4speed 已提交
313
export class Duration extends AssertionType {
Q
q4speed 已提交
314
  constructor(options) {
Q
q4speed 已提交
315
    super(ASSERTION_TYPE.DURATION);
Q
q4speed 已提交
316
    this.value = undefined;
Q
q4speed 已提交
317

Q
q4speed 已提交
318
    this.set(options);
Q
q4speed 已提交
319
  }
Q
q4speed 已提交
320 321 322 323

  isValid() {
    return !!this.value;
  }
Q
q4speed 已提交
324 325
}

Q
q4speed 已提交
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
export class Extract extends BaseConfig {
  constructor(options) {
    super();
    this.regex = [];
    this.json = [];
    this.xpath = [];

    this.set(options);
    let types = {
      json: ExtractJSONPath,
      xpath: ExtractXPath,
      regex: ExtractRegex
    }
    this.sets(types, options);
  }
}

export class ExtractType extends BaseConfig {
  constructor(type) {
    super();
    this.type = type;
  }
}

export class ExtractCommon extends ExtractType {
  constructor(type, options) {
    super(type);
Q
q4speed 已提交
353
    this.variable = undefined;
354
    this.useHeaders = undefined;
Q
q4speed 已提交
355
    this.value = ""; // ${variable}
Q
q4speed 已提交
356 357
    this.expression = undefined;
    this.description = undefined;
Q
q4speed 已提交
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

    this.set(options);
  }

  isValid() {
    return !!this.variable && !!this.expression;
  }
}

export class ExtractRegex extends ExtractCommon {
  constructor(options) {
    super(EXTRACT_TYPE.REGEX, options);
  }
}

export class ExtractJSONPath extends ExtractCommon {
  constructor(options) {
    super(EXTRACT_TYPE.JSON_PATH, options);
  }
}

export class ExtractXPath extends ExtractCommon {
  constructor(options) {
    super(EXTRACT_TYPE.XPATH, options);
  }
}

Q
NaN  
q4speed 已提交
385
/** ------------------------------------------------------------------------ **/
Q
q4speed 已提交
386 387 388 389 390 391 392 393 394 395 396
const JMX_ASSERTION_CONDITION = {
  MATCH: 1,
  CONTAINS: 1 << 1,
  NOT: 1 << 2,
  EQUALS: 1 << 3,
  SUBSTRING: 1 << 4,
  OR: 1 << 5
}

class JMXRequest {
  constructor(request) {
C
chenjianxing 已提交
397
    if (request && request instanceof Request && (request.url || request.path)) {
C
chenjianxing 已提交
398 399
      this.useEnvironment = request.useEnvironment;
      this.environment = request.environment;
C
chenjianxing 已提交
400 401 402 403 404 405 406 407 408 409
      this.path = decodeURIComponent(request.path);
      this.method = request.method;
      if (!request.useEnvironment) {
        let url = new URL(request.url);
        this.hostname = decodeURIComponent(url.hostname);
        this.pathname = decodeURIComponent(url.pathname);
        this.port = url.port;
        this.protocol = url.protocol.split(":")[0];
      }

Q
q4speed 已提交
410
      if (this.method.toUpperCase() !== "GET") {
C
chenjianxing 已提交
411 412 413 414 415 416 417
        // this.pathname += url.search.replace('&', '&amp;');
        this.pathname += '?';
        request.parameters.forEach(parameter => {
          if (parameter.name) {
            this.pathname += (parameter.name + '=' + parameter.value + '&');
          }
        });
Q
q4speed 已提交
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
      }
    }
  }
}

class JMeterTestPlan extends Element {
  constructor() {
    super('jmeterTestPlan', {
      version: "1.2", properties: "5.0", jmeter: "5.2.1"
    });

    this.add(new HashTree());
  }

  put(te) {
    if (te instanceof TestElement) {
      this.elements[0].add(te);
    }
  }
}

class JMXGenerator {
  constructor(test) {
441
    if (!test || !test.id || !(test instanceof Test)) return undefined;
Q
q4speed 已提交
442 443

    let testPlan = new TestPlan(test.name);
444 445 446 447 448 449 450
    this.addScenarios(testPlan, test.scenarioDefinition);

    this.jmeterTestPlan = new JMeterTestPlan();
    this.jmeterTestPlan.put(testPlan);
  }

  addScenarios(testPlan, scenarios) {
Q
q4speed 已提交
451 452
    scenarios.forEach(s => {
      let scenario = s.clone();
453

454
      let threadGroup = new ThreadGroup(scenario.name || "");
Q
q4speed 已提交
455

Q
q4speed 已提交
456 457 458 459
      this.addScenarioVariables(threadGroup, scenario);

      this.addScenarioHeaders(threadGroup, scenario);

Q
q4speed 已提交
460
      scenario.requests.forEach(request => {
Q
q4speed 已提交
461 462
        if (!request.isValid()) return;

463
        let httpSamplerProxy = new HTTPSamplerProxy(request.name || "", new JMXRequest(request));
Q
q4speed 已提交
464 465 466 467 468 469 470 471 472 473 474

        this.addRequestHeader(httpSamplerProxy, request);

        if (request.method.toUpperCase() === 'GET') {
          this.addRequestArguments(httpSamplerProxy, request);
        } else {
          this.addRequestBody(httpSamplerProxy, request);
        }

        this.addRequestAssertion(httpSamplerProxy, request);

Q
q4speed 已提交
475 476
        this.addRequestExtractor(httpSamplerProxy, request);

Q
q4speed 已提交
477 478 479 480 481
        threadGroup.put(httpSamplerProxy);
      })

      testPlan.put(threadGroup);
    })
482
  }
Q
q4speed 已提交
483

Q
q4speed 已提交
484
  addScenarioVariables(threadGroup, scenario) {
C
chenjianxing 已提交
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
    let scenarioVariableKeys = new Set();
    scenario.variables.forEach(item => {
      scenarioVariableKeys.add(item.name);
    });
    let environment = scenario.environment;
    if (environment) {
      let envVariables = environment.variables;
      if (!(envVariables instanceof Array)) {
        envVariables = JSON.parse(environment.variables);
        envVariables.forEach(item => {
          if (item.name && !scenarioVariableKeys.has(item.name)) {
            scenario.variables.push(new KeyValue(item.name, item.value));
          }
        })
      }
    }
Q
q4speed 已提交
501
    let args = this.filterKV(scenario.variables);
Q
q4speed 已提交
502 503 504 505 506 507 508
    if (args.length > 0) {
      let name = scenario.name + " Variables"
      threadGroup.put(new Arguments(name, args));
    }
  }

  addScenarioHeaders(threadGroup, scenario) {
C
chenjianxing 已提交
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
    let scenarioHeaderKeys = new Set();
    scenario.headers.forEach(item => {
      scenarioHeaderKeys.add(item.name);
    });
    let environment = scenario.environment;
    if (environment) {
      let envHeaders = environment.headers;
      if (!(envHeaders instanceof Array)) {
        envHeaders = JSON.parse(environment.headers);
        envHeaders.forEach(item => {
          if (item.name && !scenarioHeaderKeys.has(item.name)) {
            scenario.headers.push(new KeyValue(item.name, item.value));
          }
        })
      }
    }
Q
q4speed 已提交
525
    let headers = this.filterKV(scenario.headers);
Q
q4speed 已提交
526 527 528 529 530 531
    if (headers.length > 0) {
      let name = scenario.name + " Headers"
      threadGroup.put(new HeaderManager(name, headers));
    }
  }

Q
q4speed 已提交
532 533
  addRequestHeader(httpSamplerProxy, request) {
    let name = request.name + " Headers";
Q
q4speed 已提交
534
    let headers = this.filterKV(request.headers);
Q
q4speed 已提交
535
    if (headers.length > 0) {
Q
q4speed 已提交
536
      httpSamplerProxy.put(new HeaderManager(name, headers));
Q
q4speed 已提交
537
    }
Q
q4speed 已提交
538 539 540
  }

  addRequestArguments(httpSamplerProxy, request) {
Q
q4speed 已提交
541
    let args = this.filterKV(request.parameters);
Q
q4speed 已提交
542
    if (args.length > 0) {
Q
q4speed 已提交
543
      httpSamplerProxy.add(new HTTPSamplerArguments(args));
Q
q4speed 已提交
544
    }
Q
q4speed 已提交
545 546 547 548 549
  }

  addRequestBody(httpSamplerProxy, request) {
    let body = [];
    if (request.body.isKV()) {
Q
q4speed 已提交
550
      body = this.filterKV(request.body.kvs);
Q
q4speed 已提交
551
    } else {
Q
q4speed 已提交
552
      httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true);
Q
q4speed 已提交
553
      body.push({name: '', value: request.body.raw, encode: false});
Q
q4speed 已提交
554 555
    }

Q
q4speed 已提交
556
    httpSamplerProxy.add(new HTTPSamplerArguments(body));
Q
q4speed 已提交
557 558 559 560 561 562
  }

  addRequestAssertion(httpSamplerProxy, request) {
    let assertions = request.assertions;
    if (assertions.regex.length > 0) {
      assertions.regex.filter(this.filter).forEach(regex => {
Q
q4speed 已提交
563
        httpSamplerProxy.put(this.getAssertion(regex));
Q
q4speed 已提交
564 565 566 567
      })
    }

    if (assertions.duration.isValid()) {
Q
q4speed 已提交
568
      let name = "Response In Time: " + assertions.duration.value
Q
q4speed 已提交
569
      httpSamplerProxy.put(new DurationAssertion(name, assertions.duration.value));
Q
q4speed 已提交
570 571 572 573
    }
  }

  getAssertion(regex) {
Q
q4speed 已提交
574
    let name = regex.description;
Q
q4speed 已提交
575
    let type = JMX_ASSERTION_CONDITION.CONTAINS; // 固定用Match,自己写正则
Q
q4speed 已提交
576
    let value = regex.expression;
Q
q4speed 已提交
577 578 579 580 581 582 583 584 585 586
    switch (regex.subject) {
      case ASSERTION_REGEX_SUBJECT.RESPONSE_CODE:
        return new ResponseCodeAssertion(name, type, value);
      case ASSERTION_REGEX_SUBJECT.RESPONSE_DATA:
        return new ResponseDataAssertion(name, type, value);
      case ASSERTION_REGEX_SUBJECT.RESPONSE_HEADERS:
        return new ResponseHeadersAssertion(name, type, value);
    }
  }

Q
q4speed 已提交
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
  addRequestExtractor(httpSamplerProxy, request) {
    let extract = request.extract;
    if (extract.regex.length > 0) {
      extract.regex.filter(this.filter).forEach(regex => {
        httpSamplerProxy.put(this.getExtractor(regex));
      })
    }

    if (extract.json.length > 0) {
      extract.json.filter(this.filter).forEach(json => {
        httpSamplerProxy.put(this.getExtractor(json));
      })
    }

    if (extract.xpath.length > 0) {
      extract.xpath.filter(this.filter).forEach(xpath => {
        httpSamplerProxy.put(this.getExtractor(xpath));
      })
    }
  }

  getExtractor(extractCommon) {
    let props = {
Q
q4speed 已提交
610 611
      name: extractCommon.variable,
      expression: extractCommon.expression,
Q
q4speed 已提交
612 613 614 615 616
    }
    let testName = props.name
    switch (extractCommon.type) {
      case EXTRACT_TYPE.REGEX:
        testName += " RegexExtractor";
617
        props.headers = extractCommon.useHeaders; // 对应jMeter body
Q
q4speed 已提交
618 619 620 621 622 623 624 625 626 627 628
        props.template = "$1$";
        return new RegexExtractor(testName, props);
      case EXTRACT_TYPE.JSON_PATH:
        testName += " JSONExtractor";
        return new JSONPostProcessor(testName, props);
      case EXTRACT_TYPE.XPATH:
        testName += " XPath2Evaluator";
        return new XPath2Extractor(testName, props);
    }
  }

Q
q4speed 已提交
629 630 631 632
  filter(config) {
    return config.isValid();
  }

Q
q4speed 已提交
633 634
  filterKV(kvs) {
    return kvs.filter(this.filter);
Q
q4speed 已提交
635 636
  }

Q
q4speed 已提交
637 638 639 640 641 642 643 644
  toXML() {
    let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
    xml += this.jmeterTestPlan.toXML();
    return xml;
  }
}