@elastic/synthetics 1.0.0-beta.2 → 1.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/NOTICE.txt +250 -0
  3. package/README.md +2 -1
  4. package/dist/cli.js +80 -49
  5. package/dist/cli.js.map +1 -1
  6. package/dist/common_types.d.ts +77 -10
  7. package/dist/common_types.d.ts.map +1 -1
  8. package/dist/config.d.ts +2 -5
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/expect.d.ts +177 -0
  12. package/dist/core/expect.d.ts.map +1 -0
  13. package/dist/core/expect.js +33 -0
  14. package/dist/core/expect.js.map +1 -0
  15. package/dist/core/gatherer.d.ts +4 -7
  16. package/dist/core/gatherer.d.ts.map +1 -1
  17. package/dist/core/gatherer.js +33 -9
  18. package/dist/core/gatherer.js.map +1 -1
  19. package/dist/core/index.d.ts +5 -5
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/core/index.js +1 -1
  22. package/dist/core/index.js.map +1 -1
  23. package/dist/core/logger.d.ts +0 -1
  24. package/dist/core/logger.d.ts.map +1 -1
  25. package/dist/core/logger.js +2 -15
  26. package/dist/core/logger.js.map +1 -1
  27. package/dist/core/runner.d.ts +54 -45
  28. package/dist/core/runner.d.ts.map +1 -1
  29. package/dist/core/runner.js +173 -63
  30. package/dist/core/runner.js.map +1 -1
  31. package/dist/dsl/journey.d.ts +13 -4
  32. package/dist/dsl/journey.d.ts.map +1 -1
  33. package/dist/dsl/journey.js +40 -1
  34. package/dist/dsl/journey.js.map +1 -1
  35. package/dist/formatter/javascript.d.ts +73 -0
  36. package/dist/formatter/javascript.d.ts.map +1 -0
  37. package/dist/formatter/javascript.js +274 -0
  38. package/dist/formatter/javascript.js.map +1 -0
  39. package/dist/helpers.d.ts +33 -12
  40. package/dist/helpers.d.ts.map +1 -1
  41. package/dist/helpers.js +100 -46
  42. package/dist/helpers.js.map +1 -1
  43. package/dist/index.d.ts +8 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +6 -7
  46. package/dist/index.js.map +1 -1
  47. package/dist/parse_args.d.ts +5 -2
  48. package/dist/parse_args.d.ts.map +1 -1
  49. package/dist/parse_args.js +59 -16
  50. package/dist/parse_args.js.map +1 -1
  51. package/dist/plugins/browser-console.d.ts +5 -8
  52. package/dist/plugins/browser-console.d.ts.map +1 -1
  53. package/dist/plugins/browser-console.js +26 -7
  54. package/dist/plugins/browser-console.js.map +1 -1
  55. package/dist/plugins/network.d.ts +20 -20
  56. package/dist/plugins/network.d.ts.map +1 -1
  57. package/dist/plugins/network.js +110 -108
  58. package/dist/plugins/network.js.map +1 -1
  59. package/dist/plugins/performance.d.ts +5 -5
  60. package/dist/plugins/performance.d.ts.map +1 -1
  61. package/dist/plugins/performance.js +5 -5
  62. package/dist/plugins/performance.js.map +1 -1
  63. package/dist/plugins/plugin-manager.d.ts +11 -11
  64. package/dist/plugins/plugin-manager.d.ts.map +1 -1
  65. package/dist/plugins/plugin-manager.js +40 -24
  66. package/dist/plugins/plugin-manager.js.map +1 -1
  67. package/dist/plugins/tracing.d.ts +10 -25
  68. package/dist/plugins/tracing.d.ts.map +1 -1
  69. package/dist/plugins/tracing.js +40 -26
  70. package/dist/plugins/tracing.js.map +1 -1
  71. package/dist/reporters/base.d.ts +0 -1
  72. package/dist/reporters/base.d.ts.map +1 -1
  73. package/dist/reporters/base.js +22 -23
  74. package/dist/reporters/base.js.map +1 -1
  75. package/dist/reporters/json.d.ts +43 -7
  76. package/dist/reporters/json.d.ts.map +1 -1
  77. package/dist/reporters/json.js +181 -35
  78. package/dist/reporters/json.js.map +1 -1
  79. package/dist/sdk/lh-trace-processor.d.ts +304 -0
  80. package/dist/sdk/lh-trace-processor.d.ts.map +1 -0
  81. package/dist/sdk/lh-trace-processor.js +832 -0
  82. package/dist/sdk/lh-trace-processor.js.map +1 -0
  83. package/dist/sdk/trace-metrics.d.ts +61 -0
  84. package/dist/sdk/trace-metrics.d.ts.map +1 -0
  85. package/dist/sdk/trace-metrics.js +252 -0
  86. package/dist/sdk/trace-metrics.js.map +1 -0
  87. package/dist/sdk/trace-processor.d.ts +106 -0
  88. package/dist/sdk/trace-processor.d.ts.map +1 -0
  89. package/dist/sdk/trace-processor.js +74 -0
  90. package/dist/sdk/trace-processor.js.map +1 -0
  91. package/dist/tsconfig.tsbuildinfo +1 -1575
  92. package/package.json +24 -13
  93. package/src/cli.ts +68 -45
  94. package/src/common_types.ts +92 -10
  95. package/src/config.ts +2 -5
  96. package/src/core/expect.ts +187 -0
  97. package/src/core/gatherer.ts +44 -23
  98. package/src/core/index.ts +6 -6
  99. package/src/core/logger.ts +1 -12
  100. package/src/core/runner.ts +259 -112
  101. package/src/dsl/journey.ts +28 -6
  102. package/src/formatter/javascript.ts +330 -0
  103. package/src/helpers.ts +114 -24
  104. package/src/index.ts +7 -6
  105. package/src/parse_args.ts +97 -22
  106. package/src/plugins/browser-console.ts +30 -13
  107. package/src/plugins/network.ts +137 -144
  108. package/src/plugins/performance.ts +7 -7
  109. package/src/plugins/plugin-manager.ts +47 -36
  110. package/src/plugins/tracing.ts +43 -54
  111. package/src/reporters/base.ts +24 -23
  112. package/src/reporters/json.ts +251 -41
  113. package/src/sdk/lh-trace-processor.ts +1033 -0
  114. package/src/sdk/trace-metrics.ts +287 -0
  115. package/src/sdk/trace-processor.ts +147 -0
@@ -23,13 +23,32 @@
23
23
  *
24
24
  */
25
25
 
26
+ import { readFileSync } from 'fs';
27
+ import { join } from 'path';
28
+ import sharp from 'sharp';
29
+ import { createHash } from 'crypto';
26
30
  import BaseReporter from './base';
27
- import { formatError, getTimestamp } from '../helpers';
31
+ import {
32
+ formatError,
33
+ getTimestamp,
34
+ CACHE_PATH,
35
+ totalist,
36
+ isDirectory,
37
+ getDurationInUs,
38
+ } from '../helpers';
28
39
  import { Journey, Step } from '../dsl';
29
40
  import snakeCaseKeys from 'snakecase-keys';
30
- import { NetworkInfo, StatusValue } from '../common_types';
41
+ import {
42
+ NetworkInfo,
43
+ NetworkConditions,
44
+ TraceOutput,
45
+ StatusValue,
46
+ PerfMetrics,
47
+ Params,
48
+ Screenshot,
49
+ } from '../common_types';
31
50
  import { Protocol } from 'playwright-chromium/types/protocol';
32
- import { Metrics } from '../plugins';
51
+ import { PageMetrics } from '../plugins';
33
52
 
34
53
  /* eslint-disable @typescript-eslint/no-var-requires */
35
54
  const { version, name } = require('../../package.json');
@@ -38,10 +57,13 @@ type OutputType =
38
57
  | 'synthetics/metadata'
39
58
  | 'journey/register'
40
59
  | 'journey/start'
60
+ | 'screenshot/block'
61
+ | 'step/screenshot_ref'
41
62
  | 'step/screenshot'
63
+ | 'step/metrics'
64
+ | 'step/filmstrips'
42
65
  | 'step/end'
43
66
  | 'journey/network_info'
44
- | 'journey/filmstrips'
45
67
  | 'journey/browserconsole'
46
68
  | 'journey/end';
47
69
 
@@ -51,19 +73,25 @@ type Payload = {
51
73
  end?: number;
52
74
  url?: string;
53
75
  status?: StatusValue | number;
54
- metrics?: Metrics;
55
- params?: Record<string, unknown>;
76
+ pagemetrics?: PageMetrics;
77
+ params?: Params;
56
78
  type?: OutputType;
57
79
  text?: string;
58
80
  index?: number;
81
+ network_conditions?: NetworkConditions;
59
82
  };
60
83
 
61
84
  type OutputFields = {
62
85
  type: OutputType;
86
+ _id?: string;
63
87
  journey?: Journey;
64
88
  timestamp?: number;
65
89
  url?: string;
66
- step?: Partial<Step>;
90
+ step?: Partial<Step> & {
91
+ duration?: {
92
+ us: number;
93
+ };
94
+ };
67
95
  error?: Error;
68
96
  root_fields?: Record<string, unknown>;
69
97
  payload?: Payload | NetworkInfo;
@@ -71,6 +99,23 @@ type OutputFields = {
71
99
  blob_mime?: string;
72
100
  };
73
101
 
102
+ type ScreenshotBlob = {
103
+ blob: string;
104
+ id: string;
105
+ };
106
+
107
+ type ScreenshotReference = {
108
+ width: number;
109
+ height: number;
110
+ blocks: Array<{
111
+ hash: string;
112
+ top: number;
113
+ left: number;
114
+ width: number;
115
+ height: number;
116
+ }>;
117
+ };
118
+
74
119
  function getMetadata() {
75
120
  return {
76
121
  os: {
@@ -200,6 +245,7 @@ function journeyInfo(
200
245
  return {
201
246
  name: journey.name,
202
247
  id: journey.id,
248
+ tags: journey.tags,
203
249
  status: type === 'journey/end' ? status : undefined,
204
250
  };
205
251
  }
@@ -216,21 +262,103 @@ function stepInfo(
216
262
  name: step.name,
217
263
  index: step.index,
218
264
  status: type === 'step/end' ? status : undefined,
265
+ duration: step.duration,
219
266
  };
220
267
  }
221
268
 
269
+ export async function getScreenshotBlocks(screenshot: Buffer) {
270
+ const img = sharp(screenshot, { sequentialRead: true });
271
+ const { width, height } = await img.metadata();
272
+ /**
273
+ * Chop the screenshot image (1280*720) which is the default
274
+ * viewport size in to 64 equal blocks for a given image
275
+ * which can be acheived by keeping the division to 8
276
+ *
277
+ * Changing division to 16 would yield us 256 smaller blocks, but it is not
278
+ * optimal for caching in the ES and proved out to be bad, so we stick to 8
279
+ */
280
+ const divisions = 8;
281
+ const blockWidth = Math.floor(width / divisions);
282
+ const blockHeight = Math.floor(height / divisions);
283
+ const reference: ScreenshotReference = {
284
+ width,
285
+ height,
286
+ blocks: [],
287
+ };
288
+ const blocks: Array<ScreenshotBlob> = [];
289
+
290
+ for (let row = 0; row < divisions; row++) {
291
+ const top = row * blockHeight;
292
+ for (let col = 0; col < divisions; col++) {
293
+ const left = col * blockWidth;
294
+ const buf = await img
295
+ .extract({ top, left, width: blockWidth, height: blockHeight })
296
+ .jpeg()
297
+ .toBuffer();
298
+
299
+ const hash = createHash('sha1').update(buf).digest('hex');
300
+ blocks.push({
301
+ blob: buf.toString('base64'),
302
+ id: hash,
303
+ });
304
+ /**
305
+ * We dont write the width, height of individual blocks on the
306
+ * reference as we use similar sized blocks for each extraction,
307
+ * we would need to send the width and height here if we decide to
308
+ * go with dynamic block extraction.
309
+ */
310
+ reference.blocks.push({
311
+ hash,
312
+ top,
313
+ left,
314
+ width: blockWidth,
315
+ height: blockHeight,
316
+ });
317
+ }
318
+ }
319
+
320
+ return { blocks, reference, blob_mime: 'image/jpeg' };
321
+ }
322
+
323
+ /**
324
+ * Get all the screenshots from the cached screenshot location
325
+ * at the end of each journey and construct equally sized blocks out
326
+ * of the individual screenshot image.
327
+ */
328
+ export async function gatherScreenshots(
329
+ screenshotsPath: string,
330
+ callback: (data: Screenshot) => Promise<void>
331
+ ) {
332
+ if (isDirectory(screenshotsPath)) {
333
+ await totalist(screenshotsPath, async (_, absPath) => {
334
+ try {
335
+ const content = readFileSync(absPath, 'utf8');
336
+ const screenshot: Screenshot = JSON.parse(content);
337
+ await callback(screenshot);
338
+ } catch (_) {
339
+ // TODO: capture progarammatic synthetic errors under different type
340
+ }
341
+ });
342
+ }
343
+ }
344
+
222
345
  export default class JSONReporter extends BaseReporter {
223
346
  _registerListeners() {
224
347
  /**
225
348
  * report the number of journeys that exists on a suite which
226
349
  * could be used for better sharding
227
350
  */
228
- this.runner.on('start', ({ numJourneys }) => {
351
+ this.runner.on('start', ({ numJourneys, networkConditions }) => {
229
352
  this.writeJSON({
230
353
  type: 'synthetics/metadata',
231
354
  root_fields: {
232
355
  num_journeys: numJourneys,
233
356
  },
357
+ payload: networkConditions
358
+ ? {
359
+ network_conditions: networkConditions,
360
+ }
361
+ : undefined,
234
362
  });
235
363
  });
236
364
 
@@ -258,33 +386,47 @@ export default class JSONReporter extends BaseReporter {
258
386
  start,
259
387
  end,
260
388
  error,
261
- screenshot,
262
389
  url,
263
390
  status,
391
+ pagemetrics,
392
+ traces,
264
393
  metrics,
394
+ filmstrips,
265
395
  }) => {
266
- if (screenshot) {
267
- this.writeJSON({
268
- type: 'step/screenshot',
269
- journey,
270
- step,
271
- blob: screenshot,
272
- blob_mime: 'image/jpeg',
396
+ this.writeMetrics(journey, step, 'relative_trace', traces);
397
+ this.writeMetrics(journey, step, 'experience', metrics);
398
+ if (filmstrips) {
399
+ // Write each filmstrip separately so that we don't get documents that are too large
400
+ filmstrips.forEach((strip, index) => {
401
+ this.writeJSON({
402
+ type: 'step/filmstrips',
403
+ journey,
404
+ step,
405
+ payload: { index },
406
+ root_fields: {
407
+ browser: { relative_trace: { start: strip.start } },
408
+ },
409
+ blob: strip.blob,
410
+ blob_mime: strip.mime,
411
+ });
273
412
  });
274
413
  }
275
414
  this.writeJSON({
276
415
  type: 'step/end',
277
416
  journey,
278
- step,
417
+ step: {
418
+ ...step,
419
+ duration: {
420
+ us: getDurationInUs(end - start),
421
+ },
422
+ },
279
423
  url,
280
424
  error,
281
425
  payload: {
282
426
  source: step.callback.toString(),
283
- start,
284
- end,
285
427
  url,
286
428
  status,
287
- metrics,
429
+ pagemetrics,
288
430
  },
289
431
  });
290
432
  }
@@ -292,16 +434,45 @@ export default class JSONReporter extends BaseReporter {
292
434
 
293
435
  this.runner.on(
294
436
  'journey:end',
295
- ({
437
+ async ({
296
438
  journey,
439
+ timestamp,
297
440
  start,
298
441
  end,
299
- filmstrips,
300
442
  networkinfo,
301
443
  browserconsole,
302
444
  status,
303
445
  error,
446
+ options,
304
447
  }) => {
448
+ const { ssblocks, screenshots } = options;
449
+ const writeScreenshots =
450
+ screenshots === 'on' ||
451
+ (screenshots === 'only-on-failure' && status === 'failed');
452
+ if (writeScreenshots) {
453
+ await gatherScreenshots(
454
+ join(CACHE_PATH, 'screenshots'),
455
+ async screenshot => {
456
+ const { data, timestamp, step } = screenshot;
457
+ if (!data) {
458
+ return;
459
+ }
460
+ if (ssblocks) {
461
+ await this.writeScreenshotBlocks(journey, screenshot);
462
+ } else {
463
+ this.writeJSON({
464
+ type: 'step/screenshot',
465
+ timestamp,
466
+ journey,
467
+ step,
468
+ blob: data,
469
+ blob_mime: 'image/jpeg',
470
+ });
471
+ }
472
+ }
473
+ );
474
+ }
475
+
305
476
  if (networkinfo) {
306
477
  networkinfo.forEach(ni => {
307
478
  const { ecs, payload } = formatNetworkFields(ni);
@@ -315,37 +486,26 @@ export default class JSONReporter extends BaseReporter {
315
486
  });
316
487
  });
317
488
  }
318
- if (filmstrips) {
319
- // Write each filmstrip separately so that we don't get documents that are too large
320
- filmstrips.forEach((strip, index) => {
321
- this.writeJSON({
322
- type: 'journey/filmstrips',
323
- journey,
324
- payload: {
325
- index,
326
- ...{
327
- startTime: strip.startTime,
328
- ts: strip.ts,
329
- },
330
- },
331
- blob: strip.snapshot,
332
- });
333
- });
334
- }
335
489
  if (browserconsole) {
336
- browserconsole.forEach(({ timestamp, text, type, step }) => {
490
+ browserconsole.forEach(({ timestamp, text, type, step, error }) => {
337
491
  this.writeJSON({
338
492
  type: 'journey/browserconsole',
339
493
  journey,
340
494
  timestamp,
341
495
  step,
342
- payload: { text, type } as Payload,
496
+ error,
497
+ payload: {
498
+ text,
499
+ type,
500
+ } as Payload,
343
501
  });
344
502
  });
345
503
  }
504
+
346
505
  this.writeJSON({
347
506
  type: 'journey/end',
348
507
  journey,
508
+ timestamp,
349
509
  error,
350
510
  payload: {
351
511
  start,
@@ -353,16 +513,65 @@ export default class JSONReporter extends BaseReporter {
353
513
  status,
354
514
  },
355
515
  });
516
+ process.nextTick(() => this.runner.emit('journey:end:reported', {}));
356
517
  }
357
518
  );
358
519
  }
359
520
 
521
+ async writeScreenshotBlocks(journey: Journey, screenshot: Screenshot) {
522
+ const { blob_mime, blocks, reference } = await getScreenshotBlocks(
523
+ Buffer.from(screenshot.data, 'base64')
524
+ );
525
+ for (let i = 0; i < blocks.length; i++) {
526
+ const block = blocks[i];
527
+ this.writeJSON({
528
+ type: 'screenshot/block',
529
+ timestamp: screenshot.timestamp,
530
+ _id: block.id,
531
+ blob: block.blob,
532
+ blob_mime,
533
+ });
534
+ }
535
+ this.writeJSON({
536
+ type: 'step/screenshot_ref',
537
+ timestamp: screenshot.timestamp,
538
+ journey,
539
+ step: screenshot.step,
540
+ root_fields: {
541
+ screenshot_ref: reference,
542
+ },
543
+ });
544
+ }
545
+
546
+ writeMetrics(
547
+ journey: Journey,
548
+ step: Step,
549
+ type: string,
550
+ events: Array<TraceOutput> | PerfMetrics
551
+ ) {
552
+ const metrics = Array.isArray(events) ? events : [events];
553
+ metrics.forEach(event => {
554
+ event &&
555
+ this.writeJSON({
556
+ type: 'step/metrics',
557
+ journey,
558
+ step,
559
+ root_fields: {
560
+ browser: {
561
+ [type]: event,
562
+ },
563
+ },
564
+ });
565
+ });
566
+ }
567
+
360
568
  // Writes a structured synthetics event
361
569
  // Note that blob is ultimately stored in ES as a base64 encoded string. You must base 64 encode
362
570
  // it before passing it into this function!
363
571
  // The payload field is an un-indexed field with no ES mapping, so users can put arbitary structured
364
572
  // stuff in there
365
573
  writeJSON({
574
+ _id,
366
575
  journey,
367
576
  type,
368
577
  timestamp,
@@ -376,6 +585,7 @@ export default class JSONReporter extends BaseReporter {
376
585
  }: OutputFields) {
377
586
  this.write({
378
587
  type,
588
+ _id,
379
589
  '@timestamp': timestamp || getTimestamp(),
380
590
  journey: journeyInfo(journey, type, payload?.status),
381
591
  step: stepInfo(step, type, payload?.status),