@depup/artillery 2.0.30-depup.0

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 (90) hide show
  1. package/README.md +63 -0
  2. package/bin/run +29 -0
  3. package/bin/run.cmd +3 -0
  4. package/changes.json +138 -0
  5. package/console-reporter.js +1 -0
  6. package/lib/artillery-global.js +33 -0
  7. package/lib/cli/banner.js +8 -0
  8. package/lib/cli/common-flags.js +80 -0
  9. package/lib/cli/hooks/version.js +20 -0
  10. package/lib/cmds/dino.js +109 -0
  11. package/lib/cmds/quick.js +122 -0
  12. package/lib/cmds/report.js +34 -0
  13. package/lib/cmds/run-aci.js +91 -0
  14. package/lib/cmds/run-fargate.js +192 -0
  15. package/lib/cmds/run-lambda.js +96 -0
  16. package/lib/cmds/run.js +671 -0
  17. package/lib/console-capture.js +92 -0
  18. package/lib/console-reporter.js +438 -0
  19. package/lib/create-bom/built-in-plugins.js +12 -0
  20. package/lib/create-bom/create-bom.js +301 -0
  21. package/lib/dispatcher.js +9 -0
  22. package/lib/dist.js +222 -0
  23. package/lib/index.js +5 -0
  24. package/lib/launch-platform.js +439 -0
  25. package/lib/load-plugins.js +113 -0
  26. package/lib/platform/aws/aws-cloudwatch.js +106 -0
  27. package/lib/platform/aws/aws-create-sqs-queue.js +58 -0
  28. package/lib/platform/aws/aws-ensure-s3-bucket-exists.js +78 -0
  29. package/lib/platform/aws/aws-get-account-id.js +26 -0
  30. package/lib/platform/aws/aws-get-bucket-region.js +18 -0
  31. package/lib/platform/aws/aws-get-credentials.js +28 -0
  32. package/lib/platform/aws/aws-get-default-region.js +26 -0
  33. package/lib/platform/aws/aws-whoami.js +15 -0
  34. package/lib/platform/aws/constants.js +7 -0
  35. package/lib/platform/aws/iam-cf-templates/aws-iam-fargate-cf-template.yml +219 -0
  36. package/lib/platform/aws/iam-cf-templates/aws-iam-lambda-cf-template.yml +125 -0
  37. package/lib/platform/aws/iam-cf-templates/gh-oidc-fargate.yml +241 -0
  38. package/lib/platform/aws/iam-cf-templates/gh-oidc-lambda.yml +153 -0
  39. package/lib/platform/aws-ecs/ecs.js +247 -0
  40. package/lib/platform/aws-ecs/legacy/aws-util.js +134 -0
  41. package/lib/platform/aws-ecs/legacy/bom.js +528 -0
  42. package/lib/platform/aws-ecs/legacy/constants.js +27 -0
  43. package/lib/platform/aws-ecs/legacy/create-s3-client.js +24 -0
  44. package/lib/platform/aws-ecs/legacy/create-test.js +247 -0
  45. package/lib/platform/aws-ecs/legacy/errors.js +34 -0
  46. package/lib/platform/aws-ecs/legacy/find-public-subnets.js +149 -0
  47. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-inspect-script/index.js +27 -0
  48. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/azure-aqs.js +80 -0
  49. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/index.js +202 -0
  50. package/lib/platform/aws-ecs/legacy/plugins.js +16 -0
  51. package/lib/platform/aws-ecs/legacy/run-cluster.js +1994 -0
  52. package/lib/platform/aws-ecs/legacy/sqs-reporter.js +401 -0
  53. package/lib/platform/aws-ecs/legacy/tags.js +22 -0
  54. package/lib/platform/aws-ecs/legacy/test-run-status.js +9 -0
  55. package/lib/platform/aws-ecs/legacy/time.js +67 -0
  56. package/lib/platform/aws-ecs/legacy/util.js +97 -0
  57. package/lib/platform/aws-ecs/worker/Dockerfile +64 -0
  58. package/lib/platform/aws-ecs/worker/helpers.sh +80 -0
  59. package/lib/platform/aws-ecs/worker/loadgen-worker +656 -0
  60. package/lib/platform/aws-lambda/dependencies.js +130 -0
  61. package/lib/platform/aws-lambda/index.js +734 -0
  62. package/lib/platform/aws-lambda/lambda-handler/a9-handler-dependencies.js +73 -0
  63. package/lib/platform/aws-lambda/lambda-handler/a9-handler-helpers.js +43 -0
  64. package/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js +235 -0
  65. package/lib/platform/aws-lambda/lambda-handler/package.json +15 -0
  66. package/lib/platform/aws-lambda/prices.js +29 -0
  67. package/lib/platform/az/aci.js +694 -0
  68. package/lib/platform/az/aqs-queue-consumer.js +88 -0
  69. package/lib/platform/az/regions.js +52 -0
  70. package/lib/platform/cloud/api.js +72 -0
  71. package/lib/platform/cloud/cloud.js +448 -0
  72. package/lib/platform/cloud/http-client.js +19 -0
  73. package/lib/platform/local/artillery-worker-local.js +154 -0
  74. package/lib/platform/local/index.js +174 -0
  75. package/lib/platform/local/worker.js +261 -0
  76. package/lib/platform/worker-states.js +13 -0
  77. package/lib/queue-consumer/index.js +56 -0
  78. package/lib/stash.js +41 -0
  79. package/lib/telemetry.js +78 -0
  80. package/lib/util/await-on-ee.js +24 -0
  81. package/lib/util/generate-id.js +9 -0
  82. package/lib/util/parse-tag-string.js +21 -0
  83. package/lib/util/prepare-test-execution-plan.js +216 -0
  84. package/lib/util/sleep.js +7 -0
  85. package/lib/util/validate-script.js +132 -0
  86. package/lib/util.js +294 -0
  87. package/lib/utils-config.js +31 -0
  88. package/package.json +323 -0
  89. package/types.d.ts +317 -0
  90. package/util.js +1 -0
@@ -0,0 +1,88 @@
1
+ // Copyright (c) Artillery Software Inc.
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+ //
4
+ // Non-evaluation use of Artillery on Azure requires a commercial license
5
+ //
6
+
7
+ const EventEmitter = require('eventemitter3');
8
+
9
+ const { QueueClient } = require('@azure/storage-queue');
10
+ const { DefaultAzureCredential } = require('@azure/identity');
11
+
12
+ const debug = require('debug')('platform:azure-aci');
13
+
14
+ class AzureQueueConsumer extends EventEmitter {
15
+ constructor(
16
+ opts = { poolSize: 30 },
17
+ {
18
+ queueUrl,
19
+ pollIntervalMsec = 5000,
20
+ visibilityTimeout = 60,
21
+ batchSize = 32,
22
+ handleMessage
23
+ }
24
+ ) {
25
+ super();
26
+ this.queueUrl = queueUrl;
27
+ this.batchSize = batchSize;
28
+ this.visibilityTimeout = visibilityTimeout;
29
+ this.handleMessage = handleMessage;
30
+ this.pollIntervalMsec = pollIntervalMsec;
31
+
32
+ this.poolSize = opts.poolSize;
33
+
34
+ this.consumers = [];
35
+ }
36
+
37
+ async start() {
38
+ const credential = new DefaultAzureCredential();
39
+
40
+ for (let i = 0; i < this.poolSize; i++) {
41
+ debug('Creating consumer in pool', i);
42
+ const queueClient = new QueueClient(this.queueUrl, credential);
43
+ const pollInterval = setInterval(async () => {
44
+ const messages = await queueClient.receiveMessages({
45
+ numberOfMessages: this.batchSize,
46
+ visibilityTimeout: this.visibilityTimeout
47
+ });
48
+
49
+ // TODO: Handle errors - no auth, no queue, network etc
50
+
51
+ for (const messageItem of messages.receivedMessageItems) {
52
+ const message = {
53
+ Body: messageItem.messageText
54
+ };
55
+
56
+ let processed = false;
57
+ try {
58
+ await this.handleMessage(message);
59
+ processed = true;
60
+ } catch (err) {
61
+ console.log(err);
62
+ }
63
+
64
+ if (processed) {
65
+ try {
66
+ await queueClient.deleteMessage(
67
+ messageItem.messageId,
68
+ messageItem.popReceipt
69
+ );
70
+ } catch (_err) {}
71
+ }
72
+ }
73
+ }, this.pollIntervalMsec);
74
+
75
+ this.consumers.push(pollInterval);
76
+ }
77
+ }
78
+
79
+ async stop() {
80
+ for (const interval of this.consumers) {
81
+ clearInterval(interval);
82
+ }
83
+ }
84
+
85
+ // TODO: events: error, empty
86
+ }
87
+
88
+ module.exports = { QueueConsumer: AzureQueueConsumer };
@@ -0,0 +1,52 @@
1
+ const regionNames = [
2
+ 'australiacentral',
3
+ 'australiacentral2',
4
+ 'australiaeast',
5
+ 'australiasoutheast',
6
+ 'brazilsouth',
7
+ 'canadacentral',
8
+ 'canadaeast',
9
+ 'centralindia',
10
+ 'centralus',
11
+ 'eastasia',
12
+ 'eastus',
13
+ 'eastus2',
14
+ 'francecentral',
15
+ 'francesouth',
16
+ 'germanynorth',
17
+ 'germanywestcentral',
18
+ 'israelcentral',
19
+ 'italynorth',
20
+ 'japaneast',
21
+ 'japanwest',
22
+ 'jioindiawest',
23
+ 'koreacentral',
24
+ 'koreasouth',
25
+ 'mexicocentral',
26
+ 'northcentralus',
27
+ 'northeurope',
28
+ 'norwayeast',
29
+ 'norwaywest',
30
+ 'polandcentral',
31
+ 'qatarcentral',
32
+ 'southafricanorth',
33
+ 'southafricawest',
34
+ 'southcentralus',
35
+ 'southeastasia',
36
+ 'southindia',
37
+ 'spaincentral',
38
+ 'swedencentral',
39
+ 'switzerlandnorth',
40
+ 'switzerlandwest',
41
+ 'uaecentral',
42
+ 'uaenorth',
43
+ 'uksouth',
44
+ 'ukwest',
45
+ 'westcentralus',
46
+ 'westeurope',
47
+ 'westindia',
48
+ 'westus',
49
+ 'westus2'
50
+ ];
51
+
52
+ module.exports = { regionNames };
@@ -0,0 +1,72 @@
1
+ const { cloudHttpClient: request } = require('./http-client');
2
+
3
+ class Client {
4
+ constructor({ apiKey, baseUrl }) {
5
+ this.apiKey = apiKey || process.env.ARTILLERY_CLOUD_API_KEY;
6
+
7
+ if (!apiKey) {
8
+ const err = new Error();
9
+ err.name = 'CloudAPIKeyMissing';
10
+ throw err;
11
+ }
12
+
13
+ this.baseUrl =
14
+ baseUrl ||
15
+ process.env.ARTILLERY_CLOUD_ENDPOINT ||
16
+ 'https://app.artillery.io';
17
+
18
+ this.whoamiEndpoint = `${this.baseUrl}/api/user/whoami`;
19
+ this.stashDetailsEndpoint = `${this.baseUrl}/api/stash`;
20
+
21
+ this.defaultHeaders = {
22
+ 'x-auth-token': this.apiKey
23
+ };
24
+ }
25
+
26
+ async whoami() {
27
+ const res = await request.get(this.whoamiEndpoint, {
28
+ headers: this.defaultHeaders
29
+ });
30
+
31
+ const body = JSON.parse(res.body);
32
+ this.orgId = body.activeOrg;
33
+ return body;
34
+ }
35
+
36
+ async getStashDetails({ orgId }) {
37
+ const currentOrgId = orgId || this.orgId;
38
+
39
+ const res = await request.get(
40
+ `${this.baseUrl}/api/org/${currentOrgId}/stash`,
41
+ {
42
+ headers: this.defaultHeaders
43
+ }
44
+ );
45
+
46
+ if (res.statusCode === 200) {
47
+ let body = {};
48
+ try {
49
+ body = JSON.parse(res.body);
50
+ } catch (err) {
51
+ console.error(err);
52
+ return null;
53
+ }
54
+
55
+ if (body.url && body.token) {
56
+ return { url: body.url, token: body.token };
57
+ } else {
58
+ return null;
59
+ }
60
+ } else {
61
+ return null;
62
+ }
63
+ }
64
+ }
65
+
66
+ function createClient(opts) {
67
+ return new Client(opts);
68
+ }
69
+
70
+ module.exports = {
71
+ createClient
72
+ };
@@ -0,0 +1,448 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
+
5
+
6
+
7
+ const debug = require('debug')('cloud');
8
+ const { cloudHttpClient: request } = require('./http-client');
9
+ const awaitOnEE = require('../../util/await-on-ee');
10
+ const sleep = require('../../util/sleep');
11
+ const util = require('node:util');
12
+ const chokidar = require('chokidar');
13
+ const fs = require('node:fs');
14
+ const path = require('node:path');
15
+ const { isCI, name: ciName, GITHUB_ACTIONS } = require('ci-info');
16
+
17
+ class ArtilleryCloudPlugin {
18
+ constructor(_script, _events, { flags }) {
19
+ this.enabled = false;
20
+
21
+ const isInteractiveUse = typeof flags.record !== 'undefined';
22
+ const enabledInCloudWorker =
23
+ typeof process.env.WORKER_ID !== 'undefined' &&
24
+ typeof process.env.ARTILLERY_CLOUD_API_KEY !== 'undefined';
25
+
26
+ if (!isInteractiveUse && !enabledInCloudWorker) {
27
+ return;
28
+ }
29
+
30
+ this.enabled = true;
31
+
32
+ this.apiKey = flags.key || process.env.ARTILLERY_CLOUD_API_KEY;
33
+
34
+ this.baseUrl =
35
+ process.env.ARTILLERY_CLOUD_ENDPOINT || 'https://app.artillery.io';
36
+ this.eventsEndpoint = `${this.baseUrl}/api/events`;
37
+ this.whoamiEndpoint = `${this.baseUrl}/api/user/whoami`;
38
+ this.getAssetUploadUrls = `${this.baseUrl}/api/asset-upload-urls`;
39
+ this.pingEndpoint = `${this.baseUrl}/api/ping`;
40
+
41
+ this.defaultHeaders = {
42
+ 'x-auth-token': this.apiKey
43
+ };
44
+ this.unprocessedLogsCounter = 0;
45
+ this.cancellationRequestedBy = '';
46
+
47
+ let testEndInfo = {};
48
+
49
+ // This value is available in cloud workers only. With interactive use, it'll be set
50
+ // in the test:init event handler.
51
+ this.testRunId = process.env.ARTILLERY_TEST_RUN_ID;
52
+
53
+ if (isInteractiveUse) {
54
+ global.artillery.globalEvents.on('test:init', async (testInfo) => {
55
+ debug('test:init', testInfo);
56
+
57
+ this.testRunId = testInfo.testRunId;
58
+
59
+ const testRunUrl = `${this.baseUrl}/${this.orgId}/load-tests/${global.artillery.testRunId}`;
60
+ testEndInfo.testRunUrl = testRunUrl;
61
+
62
+ this.getLoadTestEndpoint = `${this.baseUrl}/api/load-tests/${this.testRunId}/status`;
63
+
64
+ let ciURL = null;
65
+ if (isCI && GITHUB_ACTIONS) {
66
+ const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID } =
67
+ process.env;
68
+ if (GITHUB_SERVER_URL && GITHUB_REPOSITORY && GITHUB_RUN_ID) {
69
+ ciURL = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
70
+ }
71
+ }
72
+
73
+ const metadata = Object.assign({}, testInfo.metadata, {
74
+ isCI,
75
+ ciName,
76
+ ciURL
77
+ });
78
+
79
+ await this._event('testrun:init', {
80
+ metadata: metadata
81
+ });
82
+ this.setGetLoadTestInterval = this.setGetStatusInterval();
83
+
84
+ if (typeof testInfo.flags.note !== 'undefined') {
85
+ await this._event('testrun:addnote', { text: testInfo.flags.note });
86
+ }
87
+
88
+ this.uploading = 0;
89
+ });
90
+
91
+ global.artillery.globalEvents.on('phaseStarted', async (phase) => {
92
+ await this._event('testrun:event', {
93
+ eventName: 'phaseStarted',
94
+ eventAttributes: phase
95
+ });
96
+ });
97
+
98
+ global.artillery.globalEvents.on('phaseCompleted', async (phase) => {
99
+ await this._event('testrun:event', {
100
+ eventName: 'phaseCompleted',
101
+ eventAttributes: phase
102
+ });
103
+ });
104
+
105
+ global.artillery.globalEvents.on('stats', async (report) => {
106
+ debug('stats', new Date());
107
+ const ts = Number(report.period);
108
+ await this._event('testrun:metrics', { report, ts });
109
+ });
110
+
111
+ global.artillery.globalEvents.on('done', async (report) => {
112
+ debug('done');
113
+ debug(
114
+ 'testrun:aggregatereport: payload size:',
115
+ JSON.stringify(report).length
116
+ );
117
+ await this._event('testrun:aggregatereport', { aggregate: report });
118
+ });
119
+
120
+ global.artillery.globalEvents.on('checks', async (checks) => {
121
+ debug('checks');
122
+ await this._event('testrun:checks', { checks });
123
+ });
124
+
125
+ global.artillery.globalEvents.on('logLines', async (lines, ts) => {
126
+ debug('logLines event', ts);
127
+ this.unprocessedLogsCounter += 1;
128
+
129
+ let text = '';
130
+
131
+ try {
132
+ JSON.stringify(lines);
133
+ } catch (stringifyErr) {
134
+ console.log('Could not serialize console log');
135
+ console.log(stringifyErr);
136
+ }
137
+ for (const args of lines) {
138
+ text += util.format(...Object.keys(args).map((k) => args[k])) + '\n';
139
+ }
140
+
141
+ try {
142
+ await this._event('testrun:textlog', { lines: text, ts });
143
+ } catch (err) {
144
+ debug(err);
145
+ } finally {
146
+ this.unprocessedLogsCounter -= 1;
147
+ }
148
+
149
+ debug('last 100 characters:');
150
+ debug(text.slice(text.length - 100, text.length));
151
+ });
152
+
153
+ global.artillery.globalEvents.on('metadata', async (metadata) => {
154
+ await this._event('testrun:addmetadata', {
155
+ metadata
156
+ });
157
+ });
158
+ } // isInteractiveUse
159
+
160
+ global.artillery.ext({
161
+ ext: 'beforeExit',
162
+ method: async ({ testInfo, report }) => {
163
+ debug('beforeExit');
164
+ testEndInfo = {
165
+ ...testEndInfo,
166
+ ...testInfo,
167
+ report
168
+ };
169
+ }
170
+ });
171
+
172
+ // Send test end events just before the CLI shuts down. This ensures that all console
173
+ // output has been captured and sent to the dashboard.
174
+ global.artillery.ext({
175
+ ext: 'onShutdown',
176
+ method: async (opts) => {
177
+ if (!this.enabled || this.off) {
178
+ return;
179
+ }
180
+
181
+ if (isInteractiveUse) {
182
+ clearInterval(this.setGetLoadTestInterval);
183
+
184
+ // Wait for the last logLines events to be processed, as they can sometimes finish processing after shutdown has finished
185
+ await awaitOnEE(
186
+ global.artillery.globalEvents,
187
+ 'logLines',
188
+ 200,
189
+ 1 * 1000 //wait at most 1 second for a final log lines event emitter to be fired
190
+ );
191
+ }
192
+
193
+ await this.waitOnUnprocessedLogs(5 * 60 * 1000); //just waiting for ee is not enough, as the api call takes time
194
+
195
+ if (isInteractiveUse) {
196
+ await this._event('testrun:end', {
197
+ ts: testEndInfo.endTime,
198
+ exitCode: global.artillery.suggestedExitCode || opts.exitCode,
199
+ isEarlyStop: !!opts.earlyStop,
200
+ report: testEndInfo.report
201
+ });
202
+
203
+ console.log('\n');
204
+ if (this.cancellationRequestedBy) {
205
+ console.log(`Test run stopped by ${this.cancellationRequestedBy}.`);
206
+ }
207
+ console.log(`Run URL: ${testEndInfo.testRunUrl}`);
208
+ }
209
+ }
210
+ });
211
+ }
212
+
213
+ async init() {
214
+ if (!this.apiKey) {
215
+ const err = new Error();
216
+ err.name = 'CloudAPIKeyMissing';
217
+ this.off = true;
218
+ throw err;
219
+ }
220
+
221
+ let res;
222
+ let body;
223
+ try {
224
+ res = await request.get(this.whoamiEndpoint, {
225
+ headers: this.defaultHeaders,
226
+ retry: { limit: 0 }
227
+ });
228
+
229
+ body = JSON.parse(res.body);
230
+ debug(res.body);
231
+ this.orgId = body.activeOrg;
232
+ } catch (err) {
233
+ this.off = true;
234
+ throw err;
235
+ }
236
+
237
+ if (res.statusCode === 401) {
238
+ const err = new Error();
239
+ err.name = 'APIKeyUnauthorized';
240
+ this.off = true;
241
+ throw err;
242
+ }
243
+
244
+ let postSucceeded = false;
245
+ try {
246
+ res = await request.post(this.pingEndpoint, {
247
+ headers: this.defaultHeaders
248
+ });
249
+
250
+ if (res.statusCode === 200) {
251
+ postSucceeded = true;
252
+ }
253
+ } catch (_err) {
254
+ this.off = true;
255
+ }
256
+
257
+ if (!postSucceeded) {
258
+ const err = new Error();
259
+ err.name = 'PingFailed';
260
+ this.off = true;
261
+ throw err;
262
+ }
263
+
264
+ console.log('Artillery Cloud reporting is configured for this test run');
265
+ console.log(
266
+ `Run URL: ${this.baseUrl}/${this.orgId}/load-tests/${global.artillery.testRunId}`
267
+ );
268
+
269
+ this.user = {
270
+ id: body.id,
271
+ email: body.email
272
+ };
273
+
274
+ const outputDir =
275
+ process.env.PLAYWRIGHT_TRACING_OUTPUT_DIR ||
276
+ `/tmp/${global.artillery.testRunId}/`;
277
+
278
+ try {
279
+ fs.mkdirSync(outputDir, { recursive: true });
280
+ } catch (_err) {}
281
+
282
+ const watcher = chokidar.watch(outputDir, {
283
+ ignored: /(^|[/\\])\../, // ignore dotfiles
284
+ persistent: true,
285
+ ignorePermissionErrors: true,
286
+ ignoreInitial: true,
287
+ awaitWriteFinish: {
288
+ stabilityThreshold: 2000,
289
+ pollInterval: 500
290
+ }
291
+ });
292
+
293
+ watcher.on('add', (fp) => {
294
+ if (path.basename(fp).startsWith('trace-') && fp.endsWith('.zip')) {
295
+ this.uploading++;
296
+ this._uploadAsset(fp);
297
+ }
298
+ });
299
+ }
300
+
301
+ async _uploadAsset(localFilename) {
302
+ const payload = {
303
+ testRunId: this.testRunId,
304
+ filenames: [path.basename(localFilename)]
305
+ };
306
+
307
+ debug(payload);
308
+
309
+ let url;
310
+ try {
311
+ // TODO: This could get rejected if a limit is exceeded so need to handle that case
312
+ const res = await request.post(this.getAssetUploadUrls, {
313
+ headers: this.defaultHeaders,
314
+ json: payload
315
+ });
316
+
317
+ const body = JSON.parse(res.body);
318
+ debug(body);
319
+
320
+ url = body.urls[path.basename(localFilename)];
321
+ } catch (err) {
322
+ debug(err);
323
+ }
324
+
325
+ if (!url) {
326
+ return;
327
+ }
328
+
329
+ const fileStream = fs.createReadStream(localFilename);
330
+ try {
331
+ const _response = await request.put(url, {
332
+ body: fileStream
333
+ });
334
+ } catch (error) {
335
+ console.error('Failed to upload Playwright trace recording:', error);
336
+ console.log(error.code, error.name, error.message, error.stack);
337
+ } finally {
338
+ this.uploading--;
339
+ artillery.globalEvents.emit('counter', 'browser.traces.uploaded', 1);
340
+ try {
341
+ fs.unlinkSync(localFilename);
342
+ } catch (err) {
343
+ debug(err);
344
+ }
345
+ }
346
+ }
347
+
348
+ async waitOnUnprocessedLogs(maxWaitTime) {
349
+ let waitedTime = 0;
350
+ while (
351
+ (this.unprocessedLogsCounter > 0 || this.uploading > 0) &&
352
+ waitedTime < maxWaitTime
353
+ ) {
354
+ debug('waiting on unprocessed logs');
355
+ await sleep(500);
356
+ waitedTime += 500;
357
+ }
358
+
359
+ return true;
360
+ }
361
+
362
+ setGetStatusInterval() {
363
+ const interval = setInterval(async () => {
364
+ if (this.cancellationRequestedBy) {
365
+ return;
366
+ }
367
+ const res = await this._getLoadTestStatus();
368
+
369
+ if (!res) {
370
+ debug('No response from Artillery Cloud get status');
371
+ return;
372
+ }
373
+
374
+ if (res.status !== 'CANCELLATION_REQUESTED') {
375
+ return;
376
+ }
377
+
378
+ console.log(
379
+ `WARNING: Artillery Cloud user ${res.cancelledBy} requested to stop the test. Stopping test run - this may take a few seconds.`
380
+ );
381
+ this.cancellationRequestedBy = res.cancelledBy;
382
+ global.artillery.suggestedExitCode = 8;
383
+ await global.artillery.shutdown({ earlyStop: true });
384
+ }, 5000);
385
+
386
+ return interval;
387
+ }
388
+
389
+ async _getLoadTestStatus() {
390
+ debug('☁️', 'Getting load test status');
391
+
392
+ try {
393
+ const res = await request.get(this.getLoadTestEndpoint, {
394
+ headers: this.defaultHeaders
395
+ });
396
+
397
+ return JSON.parse(res.body);
398
+ } catch (error) {
399
+ debug(error);
400
+ }
401
+ }
402
+
403
+ async _event(eventName, eventPayload) {
404
+ debug('☁️', eventName, eventPayload);
405
+
406
+ try {
407
+ const res = await request.post(this.eventsEndpoint, {
408
+ headers: this.defaultHeaders,
409
+ json: {
410
+ eventType: eventName,
411
+ eventData: Object.assign({}, eventPayload, {
412
+ testRunId: this.testRunId
413
+ })
414
+ },
415
+ retry: { limit: 2 }
416
+ });
417
+
418
+ if (res.statusCode !== 200) {
419
+ if (res.statusCode === 401) {
420
+ console.log(
421
+ 'Error: API key is invalid. Could not send test data to Artillery Cloud.'
422
+ );
423
+ } else {
424
+ console.log('Error: error sending test data to Artillery Cloud');
425
+ console.log('Test report may be incomplete');
426
+ }
427
+ let body;
428
+ try {
429
+ body = JSON.parse(res.body);
430
+ } catch (_err) {}
431
+
432
+ if (body?.requestId) {
433
+ console.log('Request ID:', body.requestId);
434
+ }
435
+ }
436
+ debug('☁️', eventName, 'sent');
437
+ } catch (err) {
438
+ debug(err);
439
+ }
440
+ }
441
+
442
+ cleanup(done) {
443
+ debug('cleaning up');
444
+ done(null);
445
+ }
446
+ }
447
+
448
+ module.exports.Plugin = ArtilleryCloudPlugin;
@@ -0,0 +1,19 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
+
5
+ const got = require('got');
6
+
7
+ const DEFAULT_TIMEOUT_MS = 20 * 10000;
8
+ const DEFAULT_RETRY_LIMIT = 3;
9
+
10
+ const cloudHttpClient = got.extend({
11
+ timeout: { response: DEFAULT_TIMEOUT_MS },
12
+ retry: {
13
+ limit: DEFAULT_RETRY_LIMIT,
14
+ methods: ['GET', 'POST', 'PUT']
15
+ },
16
+ throwHttpErrors: false
17
+ });
18
+
19
+ module.exports = { cloudHttpClient };