apify-test-tools 0.3.0 → 0.5.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.
package/lib/consts.ts CHANGED
@@ -12,4 +12,5 @@ export const TO_FINISH_WITH_OPTIONS: ToFinishWithOptionsWithDefaults = {
12
12
  'ReferenceError',
13
13
  'TypeError',
14
14
  ],
15
+ maxRetriesPerRequest: null,
15
16
  };
@@ -117,6 +117,8 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
117
117
  ...userOptions,
118
118
  };
119
119
 
120
+ const failedAssertions: string[] = [];
121
+
120
122
  const diffs: Diffs = {
121
123
  pass: true,
122
124
  actual: ['Run:'],
@@ -125,9 +127,13 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
125
127
  {
126
128
  const expected = options?.status;
127
129
  const actual = received.status;
128
- diffs.pass = expected === actual;
129
- diffs.actual.push(`status=${actual}`);
130
- diffs.expected.push(`status=${expected}`);
130
+ const statusTest = expected === actual;
131
+ if (statusTest === false) {
132
+ diffs.pass = false;
133
+ diffs.actual.push(`status=${actual}`);
134
+ diffs.expected.push(`status=${expected}`);
135
+ failedAssertions.push(`Failed status check, expected "${expected}", got "${actual}".`);
136
+ }
131
137
  }
132
138
 
133
139
  const {
@@ -135,12 +141,25 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
135
141
  stats: { durationMillis },
136
142
  } = await received.getRunInfo();
137
143
  const datasetItemCount = (await received.getDataset()).items.length;
138
- const stats = (await received.getStatistics());
144
+ const stats = await received.getStatistics();
139
145
 
140
- isWithinInterval(diffs, datasetItemCount, options, 'datasetItemCount');
141
- isWithinInterval(diffs, durationMillis, options, 'duration');
142
- isWithinInterval(diffs, stats?.requestsFailed, options, 'failedRequests');
143
- isWithinInterval(diffs, stats?.requestsRetries, options, 'requestsRetries');
146
+ const checkInterval = (
147
+ value: number | undefined,
148
+ key: ['datasetItemCount', 'duration', 'failedRequests', 'requestsRetries', 'maxRetriesPerRequest'][number],
149
+ label: string,
150
+ ) => {
151
+ const result = isWithinInterval(diffs, value, options, key);
152
+ if (result === false) {
153
+ failedAssertions.push(`Failed ${label} check, expected ${JSON.stringify(options[key])}, got ${value}.`);
154
+ }
155
+ };
156
+
157
+ checkInterval(datasetItemCount, 'datasetItemCount', 'dataset item count');
158
+ checkInterval(durationMillis, 'duration', 'duration');
159
+ checkInterval(stats?.requestsFailed, 'failedRequests', 'failed requests');
160
+ checkInterval(stats?.requestsRetries, 'requestsRetries', 'requests retries');
161
+ const maxRetriesObserved = (stats?.requestRetryHistogram ?? [0]).length - 1;
162
+ checkInterval(maxRetriesObserved, 'maxRetriesPerRequest', 'max retries per request');
144
163
 
145
164
  const ppeDiffs: Diffs = {
146
165
  pass: true,
@@ -175,9 +194,12 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
175
194
  isWithinInterval(ppeDiffs, actual[ppeEvent], expected, ppeEvent as PpeEvent);
176
195
  }
177
196
 
178
- diffs.pass = diffs.pass && ppeDiffs.pass;
179
- diffs.actual.push(ppeDiffs.actual.join('\n '));
180
- diffs.expected.push(ppeDiffs.expected.join('\n '));
197
+ if (ppeDiffs.pass === false) {
198
+ diffs.pass = ppeDiffs.pass;
199
+ diffs.actual.push(ppeDiffs.actual.join('\n '));
200
+ diffs.expected.push(ppeDiffs.expected.join('\n '));
201
+ failedAssertions.push(`Failed PPE event counts check, expected ${JSON.stringify(options.chargedEventCounts)}, got ${JSON.stringify(chargedEventCounts)}.`);
202
+ }
181
203
  }
182
204
 
183
205
  {
@@ -193,12 +215,13 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
193
215
  diffs.pass = false;
194
216
  diffs.actual.push(` logs=[${occuredLogs.join(', ')}]`);
195
217
  diffs.expected.push(` logs=[]`);
218
+ failedAssertions.push(`Failed forbidden logs check, expected [] but got [${occuredLogs.join(', ')}].`);
196
219
  }
197
220
  }
198
221
 
199
222
  return {
200
223
  pass: diffs.pass,
201
- message: () => `Run ${received.id} didn't finish as expected`,
224
+ message: () => `Run did not finish as expected. Failed assertions: ${failedAssertions.join(' ',)}`,
202
225
  actual: diffs.actual.join('\n '),
203
226
  expected: diffs.expected.join('\n '),
204
227
  };
@@ -256,6 +279,7 @@ const isWithinInterval = <T extends string>(
256
279
  diffs.pass = false;
257
280
  diffs.actual.push(`${intervalOption}=${actual}`);
258
281
  diffs.expected.push(`${intervalOption}=${expected}`);
282
+ return false
259
283
  }
260
284
  } else if (typeof expected === 'object') {
261
285
  const { min, max } = expected;
@@ -263,6 +287,8 @@ const isWithinInterval = <T extends string>(
263
287
  diffs.pass = false;
264
288
  diffs.actual.push(`${intervalOption}=${actual}`);
265
289
  diffs.expected.push(`${intervalOption}=<${min ?? ''},${max ?? ''}>`);
290
+ return false
266
291
  }
267
292
  }
293
+ return
268
294
  };
package/lib/lib.ts CHANGED
@@ -5,10 +5,9 @@ import {
5
5
  TestFunction,
6
6
  test as vitestTest,
7
7
  SuiteFactory,
8
- TestOptions,
9
8
  TestContext,
10
9
  } from 'vitest';
11
- import type { ActorBuild, RunOptions } from './types';
10
+ import type { ActorBuild, ActorTestOptions, RunOptions } from './types';
12
11
  import { RunTestResult } from './run-test-result.js';
13
12
  import { extendExpect } from './extend-expect.js';
14
13
  import { getActorPrefilledInput, sleep } from './utils.js';
@@ -38,7 +37,7 @@ export { ExpectStatic };
38
37
  const { TESTER_APIFY_TOKEN, RUN_PLATFORM_TESTS, RUN_ALL_PLATFORM_TESTS } = process.env;
39
38
  const apifyClient = new ApifyClient({ token: TESTER_APIFY_TOKEN });
40
39
 
41
- const DEFAULT_TEST_OPTIONS: TestOptions = {
40
+ const DEFAULT_TEST_OPTIONS: ActorTestOptions = {
42
41
  // we want to run tests concurrently
43
42
  concurrent: true,
44
43
  // test should finish within 1 hour
@@ -48,12 +47,12 @@ const DEFAULT_TEST_OPTIONS: TestOptions = {
48
47
  export const describe = (
49
48
  name: string,
50
49
  fn?: SuiteFactory<object>,
51
- options: TestOptions = DEFAULT_TEST_OPTIONS,
50
+ options: ActorTestOptions = DEFAULT_TEST_OPTIONS,
52
51
  ) => {
53
52
  vitestDescribe.runIf(!!RUN_PLATFORM_TESTS || !!RUN_ALL_PLATFORM_TESTS)(name, options, fn);
54
53
  };
55
54
 
56
- const DEFAULT_TEST_ACTOR_OPTIONS: TestOptions = {
55
+ const DEFAULT_TEST_ACTOR_OPTIONS: ActorTestOptions = {
57
56
  retry: 1,
58
57
  };
59
58
 
@@ -61,7 +60,7 @@ export const testActor = <T>(
61
60
  actorName: string,
62
61
  testName: string,
63
62
  fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>,
64
- testOptions?: TestOptions,
63
+ testOptions?: ActorTestOptions,
65
64
  ) => {
66
65
  const options = {
67
66
  ...DEFAULT_TEST_ACTOR_OPTIONS,
@@ -90,7 +89,7 @@ export const testStandbyActor = <I = any, O = any>(
90
89
  actorName: string,
91
90
  testName: string,
92
91
  fn: TestFunction<{ callStandby: ReturnType<typeof createStartStandbyFn<I, O>> }>,
93
- testOptions?: TestOptions,
92
+ testOptions?: ActorTestOptions,
94
93
  ) => {
95
94
  const options = {
96
95
  ...DEFAULT_TEST_ACTOR_OPTIONS,
package/lib/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ActorCallOptions } from 'apify-client';
2
- import { Assertion } from 'vitest';
2
+ import { Assertion, TestOptions } from 'vitest';
3
3
 
4
4
  export type ActorBuild = {
5
5
  buildId: string;
@@ -46,6 +46,7 @@ export type ToFinishWithOptionsWithDefaults = {
46
46
  failedRequests: Interval | null
47
47
  requestsRetries: Interval | null
48
48
  forbiddenLogs: string[]
49
+ maxRetriesPerRequest: number | null
49
50
  }
50
51
  export type IntervalOption<PpeEvent extends string> = keyof Pick<
51
52
  ToFinishWithOptions<PpeEvent>,
@@ -123,6 +124,7 @@ export interface ActorMatchers<R = unknown> {
123
124
  * - `failedRequests` (default: `0`)
124
125
  * - `requestsRetries` (default: `{ max: 3 }`)
125
126
  * - `forbiddenLogs` (default: `['ReferenceError', 'TypeError']`)
127
+ * - `maxRetriesPerRequest` (not checked by default)
126
128
  * - `datasetItemCount` (required)
127
129
  * - `chargedEventCounts`
128
130
  */
@@ -131,6 +133,17 @@ export interface ActorMatchers<R = unknown> {
131
133
  hard: <T>(actual: T, message?: string) => Assertion
132
134
  }
133
135
 
136
+ export type ActorTestOptions = Omit<TestOptions, 'retry'> & {
137
+ /**
138
+ * Times to retry the test if fails. Useful for making flaky tests more stable.
139
+ * When retries is up, the last test error will be thrown.
140
+ *
141
+ * @default 1
142
+ */
143
+ // we are just extending the docs here to replace the default value, otherwise it's the exact same
144
+ retry?: TestOptions['retry'];
145
+ };
146
+
134
147
  declare module 'vitest' {
135
148
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
149
  interface Assertion<T = any> extends ActorMatchers<T> { }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "engines": {