@elastic/synthetics 1.0.0-beta.25 → 1.0.0-beta.28

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 (126) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +56 -8
  3. package/dist/cli.js.map +1 -1
  4. package/dist/common_types.d.ts +59 -5
  5. package/dist/common_types.d.ts.map +1 -1
  6. package/dist/core/gatherer.d.ts +2 -2
  7. package/dist/core/gatherer.d.ts.map +1 -1
  8. package/dist/core/gatherer.js +2 -2
  9. package/dist/core/gatherer.js.map +1 -1
  10. package/dist/core/index.d.ts +4 -0
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/core/index.js +15 -1
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/runner.d.ts +10 -58
  15. package/dist/core/runner.d.ts.map +1 -1
  16. package/dist/core/runner.js +92 -49
  17. package/dist/core/runner.js.map +1 -1
  18. package/dist/dsl/journey.d.ts +3 -0
  19. package/dist/dsl/journey.d.ts.map +1 -1
  20. package/dist/dsl/journey.js +15 -0
  21. package/dist/dsl/journey.js.map +1 -1
  22. package/dist/{reporters/reporter.d.ts → dsl/locations.d.ts} +12 -8
  23. package/dist/dsl/locations.d.ts.map +1 -0
  24. package/dist/dsl/locations.js +41 -0
  25. package/dist/dsl/locations.js.map +1 -0
  26. package/dist/dsl/monitor.d.ts +65 -0
  27. package/dist/dsl/monitor.d.ts.map +1 -0
  28. package/dist/dsl/monitor.js +63 -0
  29. package/dist/dsl/monitor.js.map +1 -0
  30. package/dist/generator/index.d.ts +21 -0
  31. package/dist/generator/index.d.ts.map +1 -0
  32. package/dist/generator/index.js +169 -0
  33. package/dist/generator/index.js.map +1 -0
  34. package/{src/reporters/reporter.ts → dist/generator/utils.d.ts} +4 -11
  35. package/dist/generator/utils.d.ts.map +1 -0
  36. package/dist/generator/utils.js +57 -0
  37. package/dist/generator/utils.js.map +1 -0
  38. package/dist/helpers.d.ts +12 -6
  39. package/dist/helpers.d.ts.map +1 -1
  40. package/dist/helpers.js +33 -43
  41. package/dist/helpers.js.map +1 -1
  42. package/dist/index.d.ts +2 -7
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -3
  45. package/dist/index.js.map +1 -1
  46. package/dist/loader.js +4 -4
  47. package/dist/loader.js.map +1 -1
  48. package/dist/options.d.ts +7 -0
  49. package/dist/options.d.ts.map +1 -1
  50. package/dist/options.js +66 -12
  51. package/dist/options.js.map +1 -1
  52. package/dist/plugins/performance.d.ts +1 -16
  53. package/dist/plugins/performance.d.ts.map +1 -1
  54. package/dist/plugins/performance.js.map +1 -1
  55. package/dist/plugins/plugin-manager.d.ts +1 -1
  56. package/dist/push/bundler.d.ts +34 -0
  57. package/dist/push/bundler.d.ts.map +1 -0
  58. package/dist/push/bundler.js +119 -0
  59. package/dist/push/bundler.js.map +1 -0
  60. package/dist/{reporters/reporter.js → push/index.d.ts} +4 -3
  61. package/dist/push/index.d.ts.map +1 -0
  62. package/dist/push/index.js +58 -0
  63. package/dist/push/index.js.map +1 -0
  64. package/dist/push/monitor.d.ts +32 -0
  65. package/dist/push/monitor.d.ts.map +1 -0
  66. package/dist/push/monitor.js +62 -0
  67. package/dist/push/monitor.js.map +1 -0
  68. package/dist/push/plugin.d.ts +33 -0
  69. package/dist/push/plugin.d.ts.map +1 -0
  70. package/dist/push/plugin.js +123 -0
  71. package/dist/push/plugin.js.map +1 -0
  72. package/dist/push/request.d.ts +43 -0
  73. package/dist/push/request.d.ts.map +1 -0
  74. package/dist/push/request.js +89 -0
  75. package/dist/push/request.js.map +1 -0
  76. package/dist/reporters/base.d.ts +14 -6
  77. package/dist/reporters/base.d.ts.map +1 -1
  78. package/dist/reporters/base.js +45 -47
  79. package/dist/reporters/base.js.map +1 -1
  80. package/dist/reporters/index.d.ts +18 -8
  81. package/dist/reporters/index.d.ts.map +1 -1
  82. package/dist/reporters/index.js.map +1 -1
  83. package/dist/reporters/json.d.ts +7 -3
  84. package/dist/reporters/json.d.ts.map +1 -1
  85. package/dist/reporters/json.js +118 -118
  86. package/dist/reporters/json.js.map +1 -1
  87. package/dist/reporters/junit.d.ts +7 -1
  88. package/dist/reporters/junit.d.ts.map +1 -1
  89. package/dist/reporters/junit.js +91 -86
  90. package/dist/reporters/junit.js.map +1 -1
  91. package/dist/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +17 -11
  93. package/src/cli.ts +91 -14
  94. package/src/common_types.ts +69 -5
  95. package/src/core/gatherer.ts +4 -4
  96. package/src/core/index.ts +15 -0
  97. package/src/core/runner.ts +93 -113
  98. package/src/dsl/journey.ts +18 -0
  99. package/src/dsl/locations.ts +38 -0
  100. package/src/dsl/monitor.ts +87 -0
  101. package/src/generator/index.ts +212 -0
  102. package/src/generator/utils.ts +54 -0
  103. package/src/helpers.ts +36 -52
  104. package/src/index.ts +9 -1
  105. package/src/loader.ts +4 -4
  106. package/src/options.ts +72 -15
  107. package/src/plugins/performance.ts +1 -17
  108. package/src/push/bundler.ts +99 -0
  109. package/src/push/index.ts +63 -0
  110. package/src/push/monitor.ts +68 -0
  111. package/src/push/plugin.ts +113 -0
  112. package/src/push/request.ts +116 -0
  113. package/src/reporters/base.ts +57 -56
  114. package/src/reporters/index.ts +26 -3
  115. package/src/reporters/json.ts +153 -145
  116. package/src/reporters/junit.ts +98 -93
  117. package/templates/.github/workflows/run-synthetics.yml +28 -0
  118. package/templates/README.md +21 -0
  119. package/templates/journeys/advanced-example-helpers.ts +54 -0
  120. package/templates/journeys/advanced-example.journey.ts +43 -0
  121. package/templates/journeys/example.journey.ts +18 -0
  122. package/templates/synthetics.config.ts +26 -0
  123. package/CHANGELOG.md +0 -348
  124. package/NOTICE.txt +0 -221
  125. package/dist/reporters/reporter.d.ts.map +0 -1
  126. package/dist/reporters/reporter.js.map +0 -1
@@ -47,8 +47,12 @@ import {
47
47
  PerfMetrics,
48
48
  Params,
49
49
  Screenshot,
50
+ StartEvent,
51
+ JourneyStartResult,
52
+ StepEndResult,
53
+ JourneyEndResult,
54
+ PageMetrics,
50
55
  } from '../common_types';
51
- import { PageMetrics } from '../plugins';
52
56
 
53
57
  /* eslint-disable @typescript-eslint/no-var-requires */
54
58
  const { version, name } = require('../../package.json');
@@ -296,179 +300,183 @@ export async function gatherScreenshots(
296
300
  }
297
301
 
298
302
  export default class JSONReporter extends BaseReporter {
299
- override _registerListeners() {
303
+ onStart(params: StartEvent) {
300
304
  /**
301
305
  * report the number of journeys that exists on a suite which
302
306
  * could be used for better sharding
303
307
  */
304
- this.runner.on('start', ({ numJourneys, networkConditions }) => {
305
- this.writeJSON({
306
- type: 'synthetics/metadata',
307
- root_fields: {
308
- num_journeys: numJourneys,
309
- },
310
- payload: networkConditions
311
- ? {
312
- network_conditions: networkConditions,
313
- }
314
- : undefined,
315
- });
308
+ this.writeJSON({
309
+ type: 'synthetics/metadata',
310
+ root_fields: {
311
+ num_journeys: params.numJourneys,
312
+ },
313
+ payload: params.networkConditions
314
+ ? {
315
+ network_conditions: params.networkConditions,
316
+ }
317
+ : undefined,
316
318
  });
319
+ }
317
320
 
318
- this.runner.on('journey:register', ({ journey }) => {
319
- this.writeJSON({
320
- type: 'journey/register',
321
- journey,
322
- });
321
+ onJourneyRegister(journey: Journey): void {
322
+ this.writeJSON({
323
+ type: 'journey/register',
324
+ journey,
323
325
  });
326
+ }
324
327
 
325
- this.runner.on('journey:start', ({ journey, timestamp, params }) => {
326
- this.writeJSON({
327
- type: 'journey/start',
328
- journey,
329
- timestamp,
330
- payload: { params, source: journey.callback.toString() },
331
- });
328
+ override onJourneyStart(
329
+ journey: Journey,
330
+ { timestamp, params }: JourneyStartResult
331
+ ) {
332
+ this.writeJSON({
333
+ type: 'journey/start',
334
+ journey,
335
+ timestamp,
336
+ payload: { params, source: journey.callback.toString() },
332
337
  });
338
+ }
333
339
 
334
- this.runner.on(
335
- 'step:end',
336
- ({
337
- journey,
338
- step,
339
- start,
340
- end,
341
- error,
342
- url,
343
- status,
344
- pagemetrics,
345
- traces,
346
- metrics,
347
- filmstrips,
348
- }) => {
349
- this.writeMetrics(journey, step, 'relative_trace', traces);
350
- this.writeMetrics(journey, step, 'experience', metrics);
351
- if (filmstrips) {
352
- // Write each filmstrip separately so that we don't get documents that are too large
353
- filmstrips.forEach((strip, index) => {
354
- this.writeJSON({
355
- type: 'step/filmstrips',
356
- journey,
357
- step,
358
- payload: { index },
359
- root_fields: {
360
- browser: { relative_trace: { start: strip.start } },
361
- },
362
- blob: strip.blob,
363
- blob_mime: strip.mime,
364
- });
365
- });
366
- }
340
+ override onStepEnd(
341
+ journey: Journey,
342
+ step: Step,
343
+ {
344
+ start,
345
+ end,
346
+ error,
347
+ url,
348
+ status,
349
+ pagemetrics,
350
+ traces,
351
+ metrics,
352
+ filmstrips,
353
+ }: StepEndResult
354
+ ) {
355
+ this.writeMetrics(journey, step, 'relative_trace', traces);
356
+ this.writeMetrics(journey, step, 'experience', metrics);
357
+ if (filmstrips) {
358
+ // Write each filmstrip separately so that we don't get documents that are too large
359
+ filmstrips.forEach((strip, index) => {
367
360
  this.writeJSON({
368
- type: 'step/end',
361
+ type: 'step/filmstrips',
369
362
  journey,
370
- step: {
371
- ...step,
372
- duration: {
373
- us: getDurationInUs(end - start),
374
- },
375
- },
376
- url,
377
- error,
378
- payload: {
379
- source: step.callback.toString(),
380
- url,
381
- status,
382
- pagemetrics,
363
+ step,
364
+ payload: { index },
365
+ root_fields: {
366
+ browser: { relative_trace: { start: strip.start } },
383
367
  },
368
+ blob: strip.blob,
369
+ blob_mime: strip.mime,
384
370
  });
385
- }
386
- );
371
+ });
372
+ }
387
373
 
388
- this.runner.on(
389
- 'journey:end',
390
- async ({
391
- journey,
392
- timestamp,
393
- start,
394
- end,
395
- networkinfo,
396
- browserconsole,
374
+ this.writeJSON({
375
+ type: 'step/end',
376
+ journey,
377
+ step: {
378
+ ...step,
379
+ duration: {
380
+ us: getDurationInUs(end - start),
381
+ },
382
+ },
383
+ url,
384
+ error,
385
+ payload: {
386
+ source: step.callback.toString(),
387
+ url,
397
388
  status,
398
- error,
399
- options,
400
- }) => {
401
- const { ssblocks, screenshots } = options;
402
- const writeScreenshots =
403
- screenshots === 'on' ||
404
- (screenshots === 'only-on-failure' && status === 'failed');
405
- if (writeScreenshots) {
406
- await gatherScreenshots(
407
- join(CACHE_PATH, 'screenshots'),
408
- async screenshot => {
409
- const { data, timestamp, step } = screenshot;
410
- if (!data) {
411
- return;
412
- }
413
- if (ssblocks) {
414
- await this.writeScreenshotBlocks(journey, screenshot);
415
- } else {
416
- this.writeJSON({
417
- type: 'step/screenshot',
418
- timestamp,
419
- journey,
420
- step,
421
- blob: data,
422
- blob_mime: 'image/jpeg',
423
- });
424
- }
425
- }
426
- );
427
- }
389
+ pagemetrics,
390
+ },
391
+ });
392
+ }
428
393
 
429
- if (networkinfo) {
430
- networkinfo.forEach(ni => {
431
- const { ecs, payload } = formatNetworkFields(ni);
432
- this.writeJSON({
433
- type: 'journey/network_info',
434
- journey,
435
- timestamp: ni.timestamp,
436
- root_fields: snakeCaseKeys(ecs),
437
- step: ni.step,
438
- payload: snakeCaseKeys(payload),
439
- });
440
- });
441
- }
442
- if (browserconsole) {
443
- browserconsole.forEach(({ timestamp, text, type, step, error }) => {
394
+ override async onJourneyEnd(
395
+ journey: Journey,
396
+ {
397
+ timestamp,
398
+ start,
399
+ end,
400
+ networkinfo,
401
+ browserconsole,
402
+ status,
403
+ error,
404
+ options,
405
+ }: JourneyEndResult
406
+ ) {
407
+ const { ssblocks, screenshots } = options;
408
+ const writeScreenshots =
409
+ screenshots === 'on' ||
410
+ (screenshots === 'only-on-failure' && status === 'failed');
411
+ if (writeScreenshots) {
412
+ await gatherScreenshots(
413
+ join(CACHE_PATH, 'screenshots'),
414
+ async screenshot => {
415
+ const { data, timestamp, step } = screenshot;
416
+ if (!data) {
417
+ return;
418
+ }
419
+ if (ssblocks) {
420
+ await this.writeScreenshotBlocks(journey, screenshot);
421
+ } else {
444
422
  this.writeJSON({
445
- type: 'journey/browserconsole',
446
- journey,
423
+ type: 'step/screenshot',
447
424
  timestamp,
425
+ journey,
448
426
  step,
449
- error,
450
- payload: {
451
- text,
452
- type,
453
- } as Payload,
427
+ blob: data,
428
+ blob_mime: 'image/jpeg',
454
429
  });
455
- });
430
+ }
456
431
  }
432
+ );
433
+ }
457
434
 
435
+ if (networkinfo) {
436
+ networkinfo.forEach(ni => {
437
+ const { ecs, payload } = formatNetworkFields(ni);
458
438
  this.writeJSON({
459
- type: 'journey/end',
439
+ type: 'journey/network_info',
440
+ journey,
441
+ timestamp: ni.timestamp,
442
+ root_fields: snakeCaseKeys(ecs),
443
+ step: ni.step,
444
+ payload: snakeCaseKeys(payload),
445
+ });
446
+ });
447
+ }
448
+
449
+ if (browserconsole) {
450
+ browserconsole.forEach(({ timestamp, text, type, step, error }) => {
451
+ this.writeJSON({
452
+ type: 'journey/browserconsole',
460
453
  journey,
461
454
  timestamp,
455
+ step,
462
456
  error,
463
457
  payload: {
464
- start,
465
- end,
466
- status,
467
- },
458
+ text,
459
+ type,
460
+ } as Payload,
468
461
  });
469
- process.nextTick(() => this.runner.emit('journey:end:reported', {}));
470
- }
471
- );
462
+ });
463
+ }
464
+
465
+ this.writeJSON({
466
+ type: 'journey/end',
467
+ journey,
468
+ timestamp,
469
+ error,
470
+ payload: {
471
+ start,
472
+ end,
473
+ status,
474
+ },
475
+ });
476
+ }
477
+
478
+ override onEnd() {
479
+ this.stream.flushSync();
472
480
  }
473
481
 
474
482
  async writeScreenshotBlocks(journey: Journey, screenshot: Screenshot) {
@@ -23,8 +23,14 @@
23
23
  *
24
24
  */
25
25
 
26
- import { mkdirSync, writeFileSync } from 'fs';
26
+ import { mkdir, writeFile } from 'fs/promises';
27
27
  import { dirname } from 'path';
28
+ import {
29
+ JourneyEndResult,
30
+ JourneyStartResult,
31
+ StepEndResult,
32
+ } from '../common_types';
33
+ import { Journey, Step } from '../dsl';
28
34
  import { formatError, indent, now } from '../helpers';
29
35
  import BaseReporter from './base';
30
36
 
@@ -51,104 +57,103 @@ export default class JUnitReporter extends BaseReporter {
51
57
  private totalTests = 0;
52
58
  private totalFailures = 0;
53
59
  private totalSkipped = 0;
60
+ #journeyMap = new Map<string, XMLEntry>();
54
61
 
55
- override _registerListeners() {
56
- const journeyMap = new Map<string, XMLEntry>();
62
+ override onJourneyStart(journey: Journey, {}: JourneyStartResult) {
63
+ if (!this.#journeyMap.has(journey.name)) {
64
+ const entry = {
65
+ name: 'testsuite',
66
+ attributes: {
67
+ name: journey.name,
68
+ tests: 0,
69
+ failures: 0,
70
+ skipped: 0,
71
+ errors: 0,
72
+ },
73
+ children: [],
74
+ };
75
+ this.#journeyMap.set(journey.name, entry);
76
+ }
77
+ }
57
78
 
58
- this.runner.on('journey:start', ({ journey }) => {
59
- if (!journeyMap.has(journey.name)) {
60
- const entry = {
61
- name: 'testsuite',
62
- attributes: {
63
- name: journey.name,
64
- tests: 0,
65
- failures: 0,
66
- skipped: 0,
67
- errors: 0,
68
- },
69
- children: [],
70
- };
71
- journeyMap.set(journey.name, entry);
72
- }
73
- });
79
+ override onStepEnd(
80
+ journey: Journey,
81
+ step: Step,
82
+ { status, error, start, end }: StepEndResult
83
+ ) {
84
+ if (!this.#journeyMap.has(journey.name)) {
85
+ return;
86
+ }
87
+ const entry = this.#journeyMap.get(journey.name);
88
+ const caseEntry = {
89
+ name: 'testcase',
90
+ attributes: {
91
+ name: step.name,
92
+ classname: journey.name + ' ' + step.name,
93
+ time: end - start,
94
+ },
95
+ children: [],
96
+ };
74
97
 
75
- this.runner.on(
76
- 'step:end',
77
- ({ journey, step, status, error, start, end }) => {
78
- if (!journeyMap.has(journey.name)) {
79
- return;
80
- }
81
- const entry = journeyMap.get(journey.name);
82
- const caseEntry = {
83
- name: 'testcase',
84
- attributes: {
85
- name: step.name,
86
- classname: journey.name + ' ' + step.name,
87
- time: end - start,
88
- },
89
- children: [],
90
- };
98
+ entry.attributes.tests++;
99
+ if (status === 'failed') {
100
+ const { name, message, stack } = formatError(error);
101
+ caseEntry.children.push({
102
+ name: 'failure',
103
+ attributes: {
104
+ message,
105
+ type: name,
106
+ },
107
+ text: stack,
108
+ });
109
+ entry.attributes.failures++;
110
+ } else if (status === 'skipped') {
111
+ caseEntry.children.push({
112
+ name: 'skipped',
113
+ attributes: {
114
+ message: 'previous step failed',
115
+ },
116
+ });
117
+ entry.attributes.skipped++;
118
+ }
119
+ entry.children.push(caseEntry);
120
+ }
91
121
 
92
- entry.attributes.tests++;
93
- if (status === 'failed') {
94
- const { name, message, stack } = formatError(error);
95
- caseEntry.children.push({
96
- name: 'failure',
97
- attributes: {
98
- message,
99
- type: name,
100
- },
101
- text: stack,
102
- });
103
- entry.attributes.failures++;
104
- } else if (status === 'skipped') {
105
- caseEntry.children.push({
106
- name: 'skipped',
107
- attributes: {
108
- message: 'previous step failed',
109
- },
110
- });
111
- entry.attributes.skipped++;
112
- }
113
- entry.children.push(caseEntry);
114
- }
115
- );
122
+ override onJourneyEnd(journey: Journey, {}: JourneyEndResult) {
123
+ if (!this.#journeyMap.has(journey.name)) {
124
+ return;
125
+ }
126
+ const { attributes } = this.#journeyMap.get(journey.name);
127
+ this.totalTests += attributes.tests;
128
+ this.totalFailures += attributes.failures;
129
+ this.totalSkipped += attributes.skipped;
130
+ }
116
131
 
117
- this.runner.on('journey:end', ({ journey }) => {
118
- if (!journeyMap.has(journey.name)) {
119
- return;
120
- }
121
- const { attributes } = journeyMap.get(journey.name);
122
- this.totalTests += attributes.tests;
123
- this.totalFailures += attributes.failures;
124
- this.totalSkipped += attributes.skipped;
125
- });
132
+ override async onEnd() {
133
+ const root: XMLEntry = {
134
+ name: 'testsuites',
135
+ attributes: {
136
+ name: '',
137
+ tests: this.totalTests,
138
+ failures: this.totalFailures,
139
+ skipped: this.totalSkipped,
140
+ errors: 0,
141
+ time: parseInt(String(now())) / 1000,
142
+ },
143
+ children: [...this.#journeyMap.values()],
144
+ };
126
145
 
127
- this.runner.on('end', () => {
128
- const root: XMLEntry = {
129
- name: 'testsuites',
130
- attributes: {
131
- name: '',
132
- tests: this.totalTests,
133
- failures: this.totalFailures,
134
- skipped: this.totalSkipped,
135
- errors: 0,
136
- time: parseInt(String(now())) / 1000,
137
- },
138
- children: [...journeyMap.values()],
139
- };
140
- const output = serializeEntries(root).join('\n');
141
- /**
142
- * write the xml output to a file if specified via env flag
143
- */
144
- const fileName = process.env['SYNTHETICS_JUNIT_FILE'];
145
- if (fileName) {
146
- mkdirSync(dirname(fileName), { recursive: true });
147
- writeFileSync(fileName, output);
148
- } else {
149
- this.write(output);
150
- }
151
- });
146
+ const output = serializeEntries(root).join('\n');
147
+ /**
148
+ * write the xml output to a file if specified via env flag
149
+ */
150
+ const fileName = process.env['SYNTHETICS_JUNIT_FILE'];
151
+ if (fileName) {
152
+ await mkdir(dirname(fileName), { recursive: true });
153
+ await writeFile(fileName, output);
154
+ } else {
155
+ this.write(output);
156
+ }
152
157
  }
153
158
  }
154
159
 
@@ -0,0 +1,28 @@
1
+ name: Synthetics Run
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ schedule:
7
+ - cron: '* 0 * * *'
8
+ jobs:
9
+ todos_tests:
10
+ runs-on: ubuntu-latest
11
+ defaults:
12
+ run:
13
+ working-directory: 'todos/synthetics-tests'
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - uses: actions/setup-node@v2
17
+ with:
18
+ node-version: '12.22.3'
19
+ - run: npm install
20
+ - run: "SYNTHETICS_JUNIT_FILE='junit-synthetics.xml' npx @elastic/synthetics . --reporter=junit"
21
+ continue-on-error: true
22
+ - name: Publish Unit Test Results
23
+ uses: EnricoMi/publish-unit-test-result-action@v1.19
24
+ if: always()
25
+ with:
26
+ files: '**/junit-*.xml'
27
+ fail_on: "test failures"
28
+ check_name: Todos Unit Tests
@@ -0,0 +1,21 @@
1
+ ## Get started with Elastic Synthetics!
2
+
3
+ Browse the directory structure here to see an example skeleton of an [Elastic Synthetics](https://www.elastic.co/observability/synthetic-monitoring) project.
4
+ Make sure to [read the docs](https://www.elastic.co/guide/en/observability/current/monitor-uptime-synthetics.html) as well if you're unfamiliar with Elastic Synthetics.
5
+
6
+ This directory was created with the command `npx @elastic/synthetics <target-directory>`, with `<target-directory>` being the name of the directory you'd like it to make.
7
+ You can always use this command to create new projects.
8
+
9
+ Key places to look:
10
+
11
+ - The `synthetics.config.ts` file contains configuration for your project.
12
+ - The `journeys` directory contains both basic and more advanced examples of using synthetics. It tests a publicly hosted [Todos List](https://elastic.github.io/synthetics-demo/).
13
+ - The `.github` directory contains an example github action, demonstrating the use of a CI service for automatically running tests on merges and PR creation.
14
+
15
+ To run the tests here simply use the following command:
16
+
17
+ ```bash
18
+ npx @elastic/synthetics .
19
+ ```
20
+
21
+ Curious to learn more? [Read the docs](https://www.elastic.co/guide/en/observability/current/monitor-uptime-synthetics.html) to learn about pushing configurations to an Elastic cloud stack and more.
@@ -0,0 +1,54 @@
1
+ import { step, Page, expect } from '@elastic/synthetics';
2
+
3
+ // This file contains helper files for advanced-example.journey.ts
4
+ // The functions here let you write more concise tests and re-use functionality
5
+ // across tests.
6
+
7
+ export const loadAppStep = (page: Page, url: string) => {
8
+ step('launch app', async () => {
9
+ await page.goto(url);
10
+ });
11
+ };
12
+
13
+ export const addTaskStep = (page: Page, task: string) => {
14
+ step(`add task ${task}`, async () => {
15
+ const input = await page.$('input.new-todo');
16
+ await input.type(task);
17
+ await input.press('Enter');
18
+ });
19
+ };
20
+
21
+ const todosSelector = 'ul.todo-list li.todo';
22
+
23
+ export const findTask = async (page: Page, task: string) => {
24
+ return await page.waitForSelector(`${todosSelector} >> text="${task}"`);
25
+ };
26
+
27
+ export const assertTaskListSizeStep = async (page: Page, size: number) => {
28
+ step(`check that task list has exactly ${size} elements`, async () => {
29
+ expect((await page.$$(todosSelector)).length).toBe(size);
30
+ });
31
+ };
32
+
33
+ export const checkForTaskStep = async (page: Page, task: string) => {
34
+ step(`check for task '${task}' in list`, async () => {
35
+ return findTask(page, task);
36
+ });
37
+ };
38
+
39
+ export const destroyTaskStep = async (page: Page, task: string) => {
40
+ step(`destroy task '${task}'`, async () => {
41
+ const label = await findTask(page, task);
42
+ // xpath indexes arrays starting at 1!!! Easy to forget!
43
+ const li = await label.$('xpath=ancestor::li[1]');
44
+ const destroyButton = await li.$('button');
45
+
46
+ // The destroy button is not visible until hovered. Setup a click test which
47
+ // will wait up to 30s for the button to be visible.
48
+ const clickFuture = destroyButton.click();
49
+ // now hover, making the destroy button clickable
50
+ li.hover();
51
+ // now we are done
52
+ await clickFuture;
53
+ });
54
+ };