@testomatio/reporter 1.2.0-beta-2 → 1.2.0-beta-4

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.
package/lib/pipe/html.js CHANGED
@@ -4,6 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const chalk = require('chalk');
6
6
  const handlebars = require('handlebars');
7
+ const fileUrl = require('file-url');
7
8
 
8
9
  const { fileSystem, isSameTest, ansiRegExp } = require('../utils/utils');
9
10
  const { HTML_REPORT } = require('../constants');
@@ -14,17 +15,14 @@ class HtmlPipe {
14
15
  this.title = params.title || process.env.TESTOMATIO_TITLE;
15
16
  this.apiKey = params.apiKey || process.env.TESTOMATIO;
16
17
  this.isHtml = process.env.TESTOMATIO_HTML_REPORT_SAVE;
17
- this.data = {};
18
18
 
19
- debug('HTML Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
20
- if (!this.apiKey) {
21
- return;
22
- }
19
+ debug('HTML Pipe: ', this.apiKey ? 'API KEY' : '*no api key provided*');
23
20
 
24
21
  this.isEnabled = false;
25
22
  this.htmlOutputPath = "";
23
+ this.fullHtmlOutputPath = "";
24
+ this.filenameMsg = "";
26
25
  this.tests = [];
27
- this.data = {};
28
26
 
29
27
  if (this.isHtml) {
30
28
  this.isEnabled = true;
@@ -35,10 +33,9 @@ class HtmlPipe {
35
33
  }
36
34
 
37
35
  if (process.env.TESTOMATIO_HTML_FILENAME && !process.env.TESTOMATIO_HTML_FILENAME.endsWith(".html")) {
38
- console.log(
39
- chalk.blue(`The name must include the extension ".html". The default report name is used!`)
40
- );
41
36
  this.htmlReportName = HTML_REPORT.REPORT_DEFAULT_NAME;
37
+ this.filenameMsg = "HTML filename must include the extension \".html\"." +
38
+ ` The default report name "${this.htmlReportDir}/${HTML_REPORT.REPORT_DEFAULT_NAME}" is used!`;
42
39
  }
43
40
 
44
41
  if (!process.env.TESTOMATIO_HTML_FILENAME) {
@@ -90,79 +87,116 @@ class HtmlPipe {
90
87
  if (!this.isEnabled) return;
91
88
 
92
89
  if (this.isHtml) {
90
+ // GENERATE HTML reports based on the results data
91
+ this.buildReport({
92
+ runParams,
93
+ tests: this.tests,
94
+ outputPath: this.htmlOutputPath,
95
+ templatePath: this.templateHtmlPath,
96
+ warningMsg: this.filenameMsg
97
+ });
98
+ }
99
+ }
100
+ /**
101
+ * Generates an HTML report based on provided test data and a template.
102
+ * @param {object} opts - Test options used to generate the HTML report:
103
+ * runParams, tests, outputPath, templatePath
104
+ * @returns {void} - This function does not return anything.
105
+ */
93
106
 
94
- this.tests.forEach(test => {
107
+ buildReport(opts) {
108
+ const { runParams, tests, outputPath, templatePath, warningMsg: msg } = opts;
109
+
110
+ debug('HTML tests data:', tests);
95
111
 
96
- if (!test.message || test.message.trim() === "") {
97
- test.message = "This test has no 'message' code";
98
- }
99
-
100
- if (!test.suite_title || test.suite_title.trim() === "") {
101
- test.suite_title = "Unknown suite";
102
- }
112
+ if (!outputPath) {
113
+ console.log(chalk.yellow(`🚨 HTML export path is not set, ignoring...`));
114
+ return;
115
+ }
103
116
 
104
- if (!test.title || test.title.trim() === "") {
105
- test.title = "Unknown test title";
106
- }
117
+ console.log(chalk.yellow(`⏳ The test results will be added to the HTML report. It will take some time...`));
107
118
 
108
- if (!test.files || test.files.length === 0) {
109
- test.files = "This test has no files";
110
- }
119
+ if (msg) {
120
+ console.log(chalk.blue(msg));
121
+ }
111
122
 
112
- if (test.steps) {
113
- if (!test.steps || test.steps.trim() === "") {
114
- test.steps = "This test has no 'steps' code";
115
- }
116
- else {
117
- test.steps = this.#removeAnsiColorCodes(test.steps);
118
- }
119
- }
120
-
121
- // TODO: u can added an additional test values to this checks in the future
122
- });
123
+ tests.forEach(test => {
123
124
 
124
- this.data = {
125
- runId: this.store.runId,
126
- status: runParams.status,
127
- parallel: runParams.isParallel,
128
- runUrl: this.store.runUrl,
129
- executionTime: testExecutionSumTime(this.tests),
130
- executionDate: getCurrentDateTimeFormatted(),
131
- tests: this.tests
132
- };
125
+ if (!test.message || test.message.trim() === "") {
126
+ test.message = "This test has no 'message' code";
127
+ }
133
128
 
134
- // GENERATE HTML reports based on the results data
135
- this.buildReport(this.data);
136
- }
137
- }
129
+ if (!test.suite_title || test.suite_title.trim() === "") {
130
+ test.suite_title = "Unknown suite";
131
+ }
138
132
 
139
- buildReport(data) {
140
- debug('HTML tests data:', data);
133
+ if (!test.title || test.title.trim() === "") {
134
+ test.title = "Unknown test title";
135
+ }
141
136
 
142
- if (!this.htmlOutputPath && this.htmlOutputPath !== "") {
143
- console.log(chalk.yellow(`HTML export path is not set, ignoring...`));
144
- return;
145
- }
137
+ if (!test.files || test.files.length === 0) {
138
+ test.files = "This test has no files";
139
+ }
140
+
141
+ if (test.steps) {
142
+ if (!test.steps || test.steps.trim() === "") {
143
+ test.steps = "This test has no 'steps' code";
144
+ }
145
+ else {
146
+ test.steps = removeAnsiColorCodes(test.steps);
147
+ }
148
+ }
149
+
150
+ // TODO: u can added an additional test values to this checks in the future
151
+ });
146
152
 
147
- console.log(chalk.yellow(`The test results will be added to the HTML report. It will take some time...`));
153
+ const data = {
154
+ runId: this.store.runId || "",
155
+ status: runParams.status || "No status info",
156
+ parallel: runParams.isParallel || "No parallel info",
157
+ runUrl: this.store.runUrl || "",
158
+ executionTime: testExecutionSumTime(tests),
159
+ executionDate: getCurrentDateTimeFormatted(),
160
+ tests
161
+ };
148
162
  // generate output HTML based on the template
149
- const html = this.#generateHTMLReport(data);
163
+ const html = this.#generateHTMLReport(data, templatePath);
164
+
165
+ if (!html) return;
150
166
 
151
- fs.writeFileSync(this.htmlOutputPath, html, 'utf-8');
167
+ fs.writeFileSync(outputPath, html, 'utf-8');
168
+ // Check if the file exists
169
+ if (fs.existsSync(outputPath)) {
170
+ // Get the absolute path of the file
171
+ const absolutePath = path.resolve(outputPath);
172
+ // Convert the file path to a file URL
173
+ const fileUrlPath = fileUrl(absolutePath, {resolve: true});
174
+
175
+ debug('HTML tests data:', fileUrlPath);
176
+
177
+ console.log(chalk.green(`📊 The HTML report was successfully generated. Full filepath: ${fileUrlPath}`));
178
+ } else {
179
+ console.log(chalk.red(`🚨 Failed to generate the HTML report.`));
180
+ }
152
181
  }
153
182
 
154
- #generateHTMLReport(data) {
155
- if (!this.templateHtmlPath) {
156
- console.log(chalk.red(`HTML template not found. Report generation is impossible!`))
183
+ /**
184
+ * Generates an HTML report based on provided test data and a template path.
185
+ * @param {any} data - Test data used to generate the HTML report.
186
+ * @param {string} [templatePath=""] - The path to the HTML template used for generating the report.
187
+ * @returns {string | void} - The generated HTML report as a string or void if templatePath is not provided.
188
+ */
189
+ #generateHTMLReport(data, templatePath = "") {
190
+ if (!templatePath) {
191
+ console.log(chalk.red(`🚨 HTML template not found. Report generation is impossible!`))
157
192
  return;
158
193
  }
159
194
 
160
- const templateSource = fs.readFileSync(this.templateHtmlPath, 'utf8');
195
+ const templateSource = fs.readFileSync(templatePath, 'utf8');
161
196
  this.#loadReportHelpers();
162
197
  try {
163
198
  const template = handlebars.compile(templateSource);
164
199
 
165
- console.log(chalk.green(`Generated HTML report saved in file: ${this.htmlOutputPath}`));
166
200
  return template(data);
167
201
  }
168
202
  catch (e) {
@@ -213,18 +247,17 @@ class HtmlPipe {
213
247
  });
214
248
  }
215
249
 
216
- #removeAnsiColorCodes(str) {
217
- let updatedStr = str.replace(ansiRegExp(), "");
218
- updatedStr = updatedStr.replace(/\n/g, '<br>');
219
-
220
- return updatedStr;
221
- }
222
-
223
250
  toString() {
224
251
  return 'HTML Reporter';
225
252
  }
226
253
  }
227
254
 
255
+ /**
256
+ * Calculates the total execution time for an array of tests.
257
+ * @param {Object[]} tests - An array of test objects.
258
+ * @param {number} tests[].run_time - The execution time of each test in milliseconds.
259
+ * @returns {string} - The total execution time in a formatted duration string.
260
+ */
228
261
  function testExecutionSumTime(tests) {
229
262
  const totalMilliseconds = tests.reduce((sum, test) => {
230
263
  if (typeof test.run_time === 'number') {
@@ -236,6 +269,23 @@ function testExecutionSumTime(tests) {
236
269
  return formatDuration(totalMilliseconds);
237
270
  }
238
271
 
272
+ /**
273
+ * Removes ANSI color codes and converts newline characters to HTML line breaks in a given string.
274
+ * @param {string} str - The input string containing ANSI color codes.
275
+ * @returns {string} - The updated string with removed ANSI color codes and replaced newline characters.
276
+ */
277
+ function removeAnsiColorCodes(str) {
278
+ let updatedStr = str.replace(ansiRegExp(), "");
279
+ updatedStr = updatedStr.replace(/\n/g, '<br>');
280
+
281
+ return updatedStr;
282
+ }
283
+
284
+ /**
285
+ * Formats duration in milliseconds into a human-readable string representation.
286
+ * @param {number} duration - The duration in milliseconds.
287
+ * @returns {string} - The formatted duration string (e.g., "2h 30m 15s 500ms").
288
+ */
239
289
  function formatDuration(duration) {
240
290
  const milliseconds = duration % 1000;
241
291
  duration = (duration - milliseconds) / 1000;
@@ -247,6 +297,10 @@ function formatDuration(duration) {
247
297
  return `${hours}h ${minutes}m ${seconds}s ${milliseconds}ms`;
248
298
  }
249
299
 
300
+ /**
301
+ * Retrieves the current date and time in a formatted string.
302
+ * @returns {string} - The formatted date and time string (e.g., "(01/01/2023 12:00:00)").
303
+ */
250
304
  function getCurrentDateTimeFormatted() {
251
305
  const currentDate = new Date();
252
306
  const day = currentDate.getDate().toString().padStart(2, '0');
@@ -7,7 +7,11 @@
7
7
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
8
8
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
9
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ==" crossorigin="anonymous" referrerpolicy="no-referrer">
10
- <title>Report #{{runId}} - Testomat.io</title>
10
+ {{#if runId}}
11
+ <title>Report #{{runId}} - Testomat.io</title>
12
+ {{else}}
13
+ <title>Report Testomat.io</title>
14
+ {{/if}}
11
15
  <style>
12
16
  body {
13
17
  padding: 0;
@@ -32,7 +36,7 @@
32
36
  }
33
37
  p, span{
34
38
  font-weight: 400;
35
- font-size: 12px;
39
+ font-size: 14px;
36
40
  color: grey;
37
41
  margin: 0;
38
42
  }
@@ -245,6 +249,33 @@
245
249
  font-size: 14px;
246
250
  white-space: pre-line;
247
251
  }
252
+ .test__empty__list {
253
+ margin-bottom: 10px;
254
+ color: grey;
255
+ }
256
+ /* Passed TAB*/
257
+ #passedTest:not(:checked) + .btn-outline-dark {
258
+ background-color: #39BD2F;
259
+ }
260
+ #passedTest:checked + .btn-outline-dark {
261
+ background-color: #68cf61;
262
+ color: white;
263
+ }
264
+ /* Failed TAB*/
265
+ #failedTest:not(:checked) + .btn-outline-dark {
266
+ background-color: #F2230C;
267
+ }
268
+ #failedTest:checked + .btn-outline-dark {
269
+ background-color: #FA6E5E;
270
+ color: white;
271
+ }
272
+ /* Skipped TAB*/
273
+ #skippedTest:not(:checked) + .btn-outline-dark {
274
+ background-color: #F2C00C;
275
+ }
276
+ #skippedTest:checked + .btn-outline-dark {
277
+ background-color: #dfc155;
278
+ }
248
279
  </style>
249
280
  </head>
250
281
 
@@ -256,19 +287,27 @@
256
287
  <div class="col-12">
257
288
  <div class="header">
258
289
  <div class="header__block">
259
- <div class="header__case">
260
- <p>Run</p>
261
- <span><strong>#</strong>{{runId}}</span>
262
- </div>
290
+ {{#if runId}}
291
+ <div class="header__case">
292
+ <p>Run</p>
293
+ <span><strong>#</strong>{{runId}}</span>
294
+ </div>
295
+ {{/if}}
263
296
  <p class="header__type">
264
297
  <i class="fa-solid fa-face-smile"></i>
265
298
  automated job
266
299
  </p>
267
300
  </div>
268
301
  <div class="header__block">
269
- <a href="{{runUrl}}" target="_blank" class="btn btn-primary">
270
- Full Report
271
- </a>
302
+ {{#if runUrl}}
303
+ <a href="{{runUrl}}" target="_blank" class="btn btn-primary">
304
+ Full Report
305
+ </a>
306
+ {{else}}
307
+ <button class="btn btn-primary" disabled>
308
+ Full Report
309
+ </button>
310
+ {{/if}}
272
311
  </div>
273
312
  </div>
274
313
  </div>
@@ -366,72 +405,78 @@
366
405
 
367
406
  <div class="row level_5">
368
407
  <div class="col-12">
408
+ {{#if tests.length}}
409
+ <div class="testWrapp">
410
+ {{#each tests}}
369
411
 
370
- <div class="testWrapp">
371
- {{#each tests}}
372
-
373
- <div class="testitem d-none" name="testitem" type="dummy" category="false">
412
+ <div class="testitem d-none" name="testitem" type="dummy" category="false">
374
413
 
375
- <div class="testitem__top">
376
- <div class="testitem__icon">
377
- <i class="fa-solid fa-chevron-right testitem__ico testitem__ico_right"></i>
378
- <i class="fa-solid fa-chevron-down d-none testitem__ico testitem__ico_down"></i>
414
+ <div class="testitem__top">
415
+ <div class="testitem__icon">
416
+ <i class="fa-solid fa-chevron-right testitem__ico testitem__ico_right"></i>
417
+ <i class="fa-solid fa-chevron-down d-none testitem__ico testitem__ico_down"></i>
418
+ </div>
419
+ <p class="testitem__name">Test</p>
379
420
  </div>
380
- <p class="testitem__name">Test</p>
381
- </div>
382
421
 
383
- <div class="testitem__body d-none">
422
+ <div class="testitem__body d-none">
384
423
 
385
- <div class="testitem__menu">
386
- <span type="steps" class="testitem__mitem testitem__mitem_active">Steps <i class="fa-solid fa-arrow-right"></i></span>
387
- <span type="status" class="testitem__mitem">Status <i class="fa-solid fa-arrow-right"></i></span>
388
- <span type="message" class="testitem__mitem">Message <i class="fa-solid fa-arrow-right"></i></span>
389
- <span type="files" class="testitem__mitem">Files <i class="fa-solid fa-arrow-right"></i></span>
390
- </div>
424
+ <div class="testitem__menu">
425
+ <span type="steps" class="testitem__mitem testitem__mitem_active">Steps <i class="fa-solid fa-arrow-right"></i></span>
426
+ <span type="status" class="testitem__mitem">Status <i class="fa-solid fa-arrow-right"></i></span>
427
+ <span type="message" class="testitem__mitem">Message <i class="fa-solid fa-arrow-right"></i></span>
428
+ <span type="files" class="testitem__mitem">Files <i class="fa-solid fa-arrow-right"></i></span>
429
+ </div>
391
430
 
392
- <div class="testitem__content">
431
+ <div class="testitem__content">
393
432
 
394
- <!-- 1 -->
395
- <div class="testitem__case" type="steps">
396
- <p class="testitem__title">Steps</p>
397
- <div class="testitem__block">
398
- <span>...</span>
433
+ <!-- 1 -->
434
+ <div class="testitem__case" type="steps">
435
+ <p class="testitem__title">Steps</p>
436
+ <div class="testitem__block">
437
+ <span>...</span>
438
+ </div>
399
439
  </div>
400
- </div>
401
- <!-- 1 -->
402
-
403
- <!-- 2 -->
404
- <div class="testitem__case d-none" type="status">
405
- <p class="testitem__title">Status</p>
406
- <div class="testitem__block">
407
- <span>...</span>
440
+ <!-- 1 -->
441
+
442
+ <!-- 2 -->
443
+ <div class="testitem__case d-none" type="status">
444
+ <p class="testitem__title">Status</p>
445
+ <div class="testitem__block">
446
+ <span>...</span>
447
+ </div>
408
448
  </div>
409
- </div>
410
- <!-- 2 -->
411
-
412
- <!-- 3 -->
413
- <div class="testitem__case d-none" type="message">
414
- <p class="testitem__title">Message</p>
415
- <div class="testitem__block">
416
- <span>...</span>
449
+ <!-- 2 -->
450
+
451
+ <!-- 3 -->
452
+ <div class="testitem__case d-none" type="message">
453
+ <p class="testitem__title">Message</p>
454
+ <div class="testitem__block">
455
+ <span>...</span>
456
+ </div>
417
457
  </div>
418
- </div>
419
- <!-- 3 -->
420
-
421
- <!-- 4 -->
422
- <div class="testitem__case d-none" type="files">
423
- <p class="testitem__title">Files</p>
424
- <div class="testitem__block">
425
- <span>...</span>
458
+ <!-- 3 -->
459
+
460
+ <!-- 4 -->
461
+ <div class="testitem__case d-none" type="files">
462
+ <p class="testitem__title">Files</p>
463
+ <div class="testitem__block">
464
+ <span>...</span>
465
+ </div>
426
466
  </div>
467
+ <!-- 4 -->
427
468
  </div>
428
- <!-- 4 -->
429
469
  </div>
430
470
  </div>
471
+ {{/each}}
472
+ </div>
473
+ {{else}}
474
+ <div class="row">
475
+ <div class="col-12 test__empty__list">
476
+ <p>No tests found to display 😔</p>
431
477
  </div>
432
- {{/each}}
433
- </div>
434
-
478
+ </div>
479
+ {{/if}}
435
480
  </div>
436
481
  </div>
437
482
 
@@ -446,23 +491,33 @@
446
491
 
447
492
  // Draw the chart and set the chart values
448
493
  function drawChart() {
494
+ let data = {};
449
495
  const passedTests = {{getTestsByStatus tests "PASSED"}};
450
496
  const failedTests = {{getTestsByStatus tests "FAILED"}};
451
497
  const skippedTests = {{getTestsByStatus tests "SKIPPED"}};
452
498
 
453
- const data = google.visualization.arrayToDataTable([
499
+ if (passedTests === 0 && failedTests === 0 && skippedTests === 0) {
500
+ data = google.visualization.arrayToDataTable([
501
+ ['Task', 'Tests'],
502
+ ['No Tests', 1]
503
+ ])
504
+ }
505
+ else {
506
+ data = google.visualization.arrayToDataTable([
454
507
  ['Task', 'Tests'],
508
+ ['No Tests', 0],
455
509
  ['Passed', passedTests],
456
510
  ['Failed', failedTests],
457
511
  ['Skipped', skippedTests],
458
512
  ]);
513
+ }
459
514
 
460
515
  // Optional: add a title and set the width and height of the chart
461
516
  const options = {
462
517
  'title':'',
463
518
  'width':550,
464
519
  'height':300,
465
- 'colors': ['#39BD2F', '#F2230C', '#F2C00C']
520
+ 'colors': ['#939992', '#39BD2F', '#F2230C', '#F2C00C']
466
521
  };
467
522
 
468
523
  // Display the chart inside the <div> element with id="piechart"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.2.0-beta-2",
3
+ "version": "1.2.0-beta-4",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",
@@ -20,6 +20,7 @@
20
20
  "debug": "^4.3.4",
21
21
  "dotenv": "^16.0.1",
22
22
  "fast-xml-parser": "^4.0.8",
23
+ "file-url": "3.0.0",
23
24
  "glob": "^10.3",
24
25
  "handlebars": "^4.7.8",
25
26
  "has-flag": "^5.0.1",
@@ -65,6 +66,7 @@
65
66
  "eslint-plugin-import": "^2.25.4",
66
67
  "jasmine": "^3.10.0",
67
68
  "jest": "^27.4.7",
69
+ "jsdom": "^22.1.0",
68
70
  "mocha": "^9.2.0",
69
71
  "mock-http-server": "^1.4.5",
70
72
  "pino": "^8.15.0",
@@ -1,47 +0,0 @@
1
- // TODO: use as example for the unit-tests
2
-
3
- const result = {
4
- "runId": "51eb2798",
5
- "status": "failed",
6
- "runUrl": "https://beta.testomat.io/projects/codecept-new-mode-exmple/runs/51eb2798/report",
7
- "executionTime": "0 seconds",
8
- "executionDate": "(30/07/2023 11:11:11)",
9
- "tests": [{
10
- "files": [],
11
- "steps": "\u001b[32m\u001b[1mOn TodosPage: goto \u001b[22m\u001b[39m\n",
12
- "status": "passed",
13
- "stack": "I execute script () => sessionStorage.clear()",
14
- "example": null,
15
- "code": null,
16
- "title": "Create a new todo item @T50e82737",
17
- "suite_title": "Create Tasks @step:06 @smoke @story:12 @S2f5c1942",
18
- "test_id": "50e82737",
19
- "message": "",
20
- "run_time": 121,
21
- "artifacts": [],
22
- "api_key": "tstmt_gRqrhBUaVxTpezGpZjRmlahOeqcRBBbDMA1692050199",
23
- "create": false
24
- }, {
25
- "files": [{
26
- "path": "/home/codeceptjs-testomat-example/codeceptJS/output/Create_2_@T5b8d1186.failed.png",
27
- "type": "image/png"
28
- }],
29
- "steps": "I am on page \"http://todomvc.com/examples/angularjs/#/\"",
30
- "status": "failed",
31
- "stack": "I am on page \"http://todomvc.com/examples/angularjs/#/\"",
32
- "example": null,
33
- "code": null,
34
- "title": "Create a new todo item part 2 @T5b8d1186",
35
- "suite_title": "@second Create Tasks @step:07 @smoke @story:13 @S2f5c1942",
36
- "test_id": "5b8d1186",
37
- "message": "expected expected number of visible is 2, but found 1 \"1\" to equal \"2\"",
38
- "run_time": 176,
39
- "artifacts": [null],
40
- "api_key": "tstmt_gRqrhBUaVxTpezGpZjRmlahOeqcRBBbDMA1692050199",
41
- "create": false
42
- }]
43
- }
44
-
45
- module.exports = {
46
- result
47
- }